From 9fb0d729b89efa8ff54262ee0baa95ebc0f48641 Mon Sep 17 00:00:00 2001 From: Malthe Borch Date: Tue, 14 May 2019 09:51:50 +0200 Subject: [PATCH 1/2] Backport 'Fix for PDF texts not being selectable' Co-authored-by: aziesemer --- NEWS.rst | 5 +++-- cairosvg/__init__.py | 3 +++ cairosvg/surface/__init__.py | 24 ++++++++++++++++++++---- cairosvg/surface/text.py | 7 ++++++- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index ceb2e14b..7fcc2f6a 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -3,9 +3,10 @@ ====== -Version 2.0, not released yet -============================= +Version 1.0.23, not released yet +================================ +* Make text selectable on generated PDF files * Fix markers * Fix URL/id handling * Test CairoSVG with Travis diff --git a/cairosvg/__init__.py b/cairosvg/__init__.py index b9143f57..b4088221 100644 --- a/cairosvg/__init__.py +++ b/cairosvg/__init__.py @@ -70,6 +70,9 @@ def main(): option_parser.add_option( "-o", "--output", default="", help="output filename") + option_parser.add_option( + "--text-as-text", action="store_true", + help="saves text in PDF as text instead of paths") options, args = option_parser.parse_args() # Print help if no argument is given diff --git a/cairosvg/surface/__init__.py b/cairosvg/surface/__init__.py index 48c49e7b..ba5c6224 100644 --- a/cairosvg/surface/__init__.py +++ b/cairosvg/surface/__init__.py @@ -75,9 +75,10 @@ class Surface(object): # Subclasses must either define this or override _create_surface() surface_class = None + draw_text_as_text = False @classmethod - def convert(cls, bytestring=None, **kwargs): + def convert(cls, bytestring=None, draw_text_as_text=None, **kwargs): """Convert a SVG document to the format for this class. Specify the input by passing one of these: @@ -90,6 +91,9 @@ def convert(cls, bytestring=None, **kwargs): :param write_to: The filename of file-like object where to write the output. If None or not provided, return a byte string. + :param draw_text_as_text: Draw text as text, instead of paths, reducing + the file size in PDFs and allowing text selection. May + not support some path clipping operations. Only ``source`` can be passed as a positional argument, other parameters are keyword-only. @@ -105,12 +109,14 @@ def convert(cls, bytestring=None, **kwargs): output = io.BytesIO() else: output = write_to - cls(tree, output, dpi, None, parent_width, parent_height).finish() + cls(tree, output, dpi, None, parent_width, parent_height, + draw_text_as_text).finish() if write_to is None: return output.getvalue() def __init__(self, tree, output, dpi, parent_surface=None, - parent_width=None, parent_height=None): + parent_width=None, parent_height=None, + draw_text_as_text=None): """Create the surface from a filename or a file-like object. The rendered content is written to ``output`` which can be a filename, @@ -144,6 +150,8 @@ def __init__(self, tree, output, dpi, parent_surface=None, self._old_parent_node = self.parent_node = None self.output = output self.dpi = dpi + if draw_text_as_text is not None: + self.draw_text_as_text = draw_text_as_text self.font_size = size(self, "12pt") self.stroke_and_fill = True width, height, viewbox = node_format(self, tree) @@ -323,6 +331,9 @@ def draw(self, node): self.context.clip() self.context.set_fill_rule(cairo.FILL_RULE_WINDING) + save_cursor = (self.cursor_position, self.cursor_d_position, + self.text_path_width) + if node.tag in TAGS: try: TAGS[node.tag](self, node) @@ -362,7 +373,12 @@ def draw(self, node): if node.get("fill-rule") == "evenodd": self.context.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) self.context.set_source_rgba(*color(paint_color, fill_opacity)) - self.context.fill_preserve() + if self.draw_text_as_text and TAGS[node.tag] == text: + self.cursor_position, self.cursor_d_position, \ + self.text_path_width = save_cursor + text(self, node) + else: + self.context.fill_preserve() self.context.restore() # Stroke diff --git a/cairosvg/surface/text.py b/cairosvg/surface/text.py index 7c973b68..ab2739cb 100644 --- a/cairosvg/surface/text.py +++ b/cairosvg/surface/text.py @@ -205,7 +205,12 @@ def text(surface, node): surface.context.rel_move_to(-x_align, y_align) surface.context.rotate(last_r if r is None else r) - surface.context.text_path(letter) + # Only draw characters with 'content' (workaround for bug in cairo) + if not letter.isspace(): + if surface.draw_text_as_text: + surface.context.show_text(letter) + else: + surface.context.text_path(letter) surface.context.restore() if not text_path: surface.cursor_position = cursor_position From fcec3680803466421b723ee4e43e6296621cd340 Mon Sep 17 00:00:00 2001 From: aziesemer Date: Mon, 26 Nov 2018 11:46:12 -0500 Subject: [PATCH 2/2] Fix tspan with --text-as-text --- cairosvg/surface/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cairosvg/surface/__init__.py b/cairosvg/surface/__init__.py index ba5c6224..9530e0ec 100644 --- a/cairosvg/surface/__init__.py +++ b/cairosvg/surface/__init__.py @@ -21,6 +21,8 @@ """ import io +import copy + try: import cairocffi as cairo # OSError means cairocffi is installed, @@ -331,8 +333,8 @@ def draw(self, node): self.context.clip() self.context.set_fill_rule(cairo.FILL_RULE_WINDING) - save_cursor = (self.cursor_position, self.cursor_d_position, - self.text_path_width) + save_cursor = copy.deepcopy((self.cursor_position, self.cursor_d_position, + self.text_path_width)) if node.tag in TAGS: try: