From 5dd1feaa362f740a9069f1287305d1b9ac43e135 Mon Sep 17 00:00:00 2001 From: aziesemer Date: Thu, 2 Aug 2018 16:28:52 -0400 Subject: [PATCH 01/14] Add support to PDFs with multiple pages I kept the interface the same, for compatibility with existing code, but adding the option to pass an array of bytestring, file_obj, url, or tree objects (this last one as an optimization when converting the same svg to multiple formats), instead of a single object only. For PDFs it should call the method addPage for each new page, except the first one. For all other formats it should raise NotImplementedError for now. --- cairosvg/surface.py | 71 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/cairosvg/surface.py b/cairosvg/surface.py index d2a15611..330d1d67 100644 --- a/cairosvg/surface.py +++ b/cairosvg/surface.py @@ -108,16 +108,17 @@ class Surface(object): surface_class = None @classmethod - def convert(cls, bytestring=None, *, file_obj=None, url=None, dpi=96, + def convert(cls, bytestrings=None, *, file_objs=None, urls=None, dpi=96, parent_width=None, parent_height=None, scale=1, unsafe=False, - write_to=None, **kwargs): + write_to=None, tree_objs=None, **kwargs): """Convert a SVG document to the format for this class. Specify the input by passing one of these: - :param bytestring: The SVG source as a byte-string. - :param file_obj: A file-like object. - :param url: A filename. + :param bytestring: The SVG source as a byte-string or array. + :param file_objs: A file-like object or array. + :param url: A filename or array. + :tree_objs: A Tree object or array Give some options: @@ -137,12 +138,35 @@ def convert(cls, bytestring=None, *, file_obj=None, url=None, dpi=96, parameters are keyword-only. """ - tree = Tree( - bytestring=bytestring, file_obj=file_obj, url=url, unsafe=unsafe, - **kwargs) + trees = [] + if (bytestrings is not None): + if not isinstance(bytestrings, list): bytestrings = [bytestrings] + for bytestring in bytestrings: + trees.append(Tree(bytestring=bytestring, unsafe=unsafe, **kwargs)) + + if(file_objs is not None): + if not isinstance(file_objs, list): file_objs = [file_objs] + for file in file_objs: + trees.append(Tree(file_obj=file, unsafe=unsafe, **kwargs)) + + if (urls is not None): + if not isinstance(urls, list): urls = [urls] + for url in urls: + trees.append(Tree(url=url, unsafe=unsafe, **kwargs)) + + if (tree_objs is not None): + if not isinstance(tree_objs, list): tree_objs = [tree_objs] + trees.extend(tree_objs) + output = write_to or io.BytesIO() - instance = cls( - tree, output, dpi, None, parent_width, parent_height, scale) + + for tree in trees: + if 'instance' in locals(): + instance.addPage(tree, parent_width, parent_height, scale) + else: + instance = cls( + tree, output, dpi, None, parent_width, parent_height, scale) + instance.finish() if write_to is None: return output.getvalue() @@ -201,6 +225,10 @@ def __init__(self, tree, output, dpi, parent_surface=None, self.context.move_to(0, 0) self.draw(tree) + def addPage(self, tree, parent_width=None, parent_height=None, scale=1): + raise NotImplementedError("Multiple pages are not supported for this " + "format yet") + @property def points_per_pixel(self): """Surface resolution.""" @@ -447,6 +475,29 @@ class PDFSurface(Surface): """A surface that writes in PDF format.""" surface_class = cairo.PDFSurface + def addPage(self, tree, parent_width=None, parent_height=None, scale=1): + self.context_width, self.context_height = parent_width, parent_height + self.cursor_position = [0, 0] + self.cursor_d_position = [0, 0] + self.text_path_width = 0 + self.tree_cache = {(tree.url, tree.get('id')): tree} + self.markers = {} + self.gradients = {} + self.patterns = {} + self.masks = {} + self.paths = {} + self.filters = {} + + self.context.save() + width, height, viewbox = node_format(self, tree) + width *= scale + height *= scale + self.cairo.show_page() + self.set_context_size(width, height, viewbox, scale, preserved_ratio(tree)) + self.cairo.set_size(width * self.device_units_per_user_units, height * self.device_units_per_user_units) + self.draw(tree) + self.context.restore() + class PSSurface(Surface): """A surface that writes in PostScript format.""" From 4d751b26376410bd8cbb8e438e633423864578cf Mon Sep 17 00:00:00 2001 From: aziesemer Date: Thu, 2 Aug 2018 16:35:39 -0400 Subject: [PATCH 02/14] Fix comment: it is actually a list, not array --- cairosvg/surface.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cairosvg/surface.py b/cairosvg/surface.py index 330d1d67..f7a22a48 100644 --- a/cairosvg/surface.py +++ b/cairosvg/surface.py @@ -115,10 +115,10 @@ def convert(cls, bytestrings=None, *, file_objs=None, urls=None, dpi=96, Specify the input by passing one of these: - :param bytestring: The SVG source as a byte-string or array. - :param file_objs: A file-like object or array. - :param url: A filename or array. - :tree_objs: A Tree object or array + :param bytestring: The SVG source as a byte-string or list. + :param file_objs: A file-like object or list. + :param url: A filename or list. + :tree_objs: A Tree object or list Give some options: From ecf386df3deae4e26522b0dbf6d6309abb142a0b Mon Sep 17 00:00:00 2001 From: aziesemer Date: Thu, 2 Aug 2018 16:49:00 -0400 Subject: [PATCH 03/14] Fix code formatting to make Travis happy --- cairosvg/surface.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/cairosvg/surface.py b/cairosvg/surface.py index f7a22a48..4322566a 100644 --- a/cairosvg/surface.py +++ b/cairosvg/surface.py @@ -140,22 +140,27 @@ def convert(cls, bytestrings=None, *, file_objs=None, urls=None, dpi=96, """ trees = [] if (bytestrings is not None): - if not isinstance(bytestrings, list): bytestrings = [bytestrings] + if not isinstance(bytestrings, list): + bytestrings = [bytestrings] for bytestring in bytestrings: - trees.append(Tree(bytestring=bytestring, unsafe=unsafe, **kwargs)) + trees.append(Tree(bytestring=bytestring, unsafe=unsafe, + **kwargs)) if(file_objs is not None): - if not isinstance(file_objs, list): file_objs = [file_objs] + if not isinstance(file_objs, list): + file_objs = [file_objs] for file in file_objs: trees.append(Tree(file_obj=file, unsafe=unsafe, **kwargs)) if (urls is not None): - if not isinstance(urls, list): urls = [urls] + if not isinstance(urls, list): + urls = [urls] for url in urls: trees.append(Tree(url=url, unsafe=unsafe, **kwargs)) if (tree_objs is not None): - if not isinstance(tree_objs, list): tree_objs = [tree_objs] + if not isinstance(tree_objs, list): + tree_objs = [tree_objs] trees.extend(tree_objs) output = write_to or io.BytesIO() @@ -164,8 +169,8 @@ def convert(cls, bytestrings=None, *, file_objs=None, urls=None, dpi=96, if 'instance' in locals(): instance.addPage(tree, parent_width, parent_height, scale) else: - instance = cls( - tree, output, dpi, None, parent_width, parent_height, scale) + instance = cls(tree, output, dpi, None, parent_width, + parent_height, scale) instance.finish() if write_to is None: @@ -493,8 +498,10 @@ def addPage(self, tree, parent_width=None, parent_height=None, scale=1): width *= scale height *= scale self.cairo.show_page() - self.set_context_size(width, height, viewbox, scale, preserved_ratio(tree)) - self.cairo.set_size(width * self.device_units_per_user_units, height * self.device_units_per_user_units) + self.set_context_size(width, height, viewbox, scale, + preserved_ratio(tree)) + self.cairo.set_size(width * self.device_units_per_user_units, + height * self.device_units_per_user_units) self.draw(tree) self.context.restore() From a0ad9c1775840224757c65eb372a1c5504327307 Mon Sep 17 00:00:00 2001 From: aziesemer Date: Thu, 2 Aug 2018 16:54:56 -0400 Subject: [PATCH 04/14] Still trying to make Travis happy --- cairosvg/surface.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/cairosvg/surface.py b/cairosvg/surface.py index 4322566a..b78e879d 100644 --- a/cairosvg/surface.py +++ b/cairosvg/surface.py @@ -140,37 +140,38 @@ def convert(cls, bytestrings=None, *, file_objs=None, urls=None, dpi=96, """ trees = [] if (bytestrings is not None): - if not isinstance(bytestrings, list): + if not isinstance(bytestrings, list): bytestrings = [bytestrings] for bytestring in bytestrings: - trees.append(Tree(bytestring=bytestring, unsafe=unsafe, + trees.append(Tree(bytestring=bytestring, unsafe=unsafe, **kwargs)) if(file_objs is not None): - if not isinstance(file_objs, list): + if not isinstance(file_objs, list): file_objs = [file_objs] for file in file_objs: trees.append(Tree(file_obj=file, unsafe=unsafe, **kwargs)) if (urls is not None): - if not isinstance(urls, list): + if not isinstance(urls, list): urls = [urls] for url in urls: trees.append(Tree(url=url, unsafe=unsafe, **kwargs)) if (tree_objs is not None): - if not isinstance(tree_objs, list): + if not isinstance(tree_objs, list): tree_objs = [tree_objs] trees.extend(tree_objs) output = write_to or io.BytesIO() + instance = None for tree in trees: - if 'instance' in locals(): - instance.addPage(tree, parent_width, parent_height, scale) - else: - instance = cls(tree, output, dpi, None, parent_width, + if instance is None: + instance = cls(tree, output, dpi, None, parent_width, parent_height, scale) + else: + instance.addPage(tree, parent_width, parent_height, scale) instance.finish() if write_to is None: @@ -498,7 +499,7 @@ def addPage(self, tree, parent_width=None, parent_height=None, scale=1): width *= scale height *= scale self.cairo.show_page() - self.set_context_size(width, height, viewbox, scale, + self.set_context_size(width, height, viewbox, scale, preserved_ratio(tree)) self.cairo.set_size(width * self.device_units_per_user_units, height * self.device_units_per_user_units) From 23310f140c0bc6630455eaa43426fb81c65715f5 Mon Sep 17 00:00:00 2001 From: aziesemer Date: Thu, 2 Aug 2018 17:18:03 -0400 Subject: [PATCH 05/14] Check for empty array --- cairosvg/surface.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cairosvg/surface.py b/cairosvg/surface.py index b78e879d..8cfc2cbc 100644 --- a/cairosvg/surface.py +++ b/cairosvg/surface.py @@ -173,7 +173,8 @@ def convert(cls, bytestrings=None, *, file_objs=None, urls=None, dpi=96, else: instance.addPage(tree, parent_width, parent_height, scale) - instance.finish() + if instance is not None: + instance.finish() if write_to is None: return output.getvalue() From 757766456d581aee764fc9ae7a22b424b1dd514b Mon Sep 17 00:00:00 2001 From: aziesemer Date: Thu, 2 Aug 2018 17:23:31 -0400 Subject: [PATCH 06/14] Fix indentation --- cairosvg/surface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cairosvg/surface.py b/cairosvg/surface.py index 8cfc2cbc..74dedf22 100644 --- a/cairosvg/surface.py +++ b/cairosvg/surface.py @@ -174,7 +174,7 @@ def convert(cls, bytestrings=None, *, file_objs=None, urls=None, dpi=96, instance.addPage(tree, parent_width, parent_height, scale) if instance is not None: - instance.finish() + instance.finish() if write_to is None: return output.getvalue() From cb28f8396fcb7f42b653784ddd9f488a625e37a9 Mon Sep 17 00:00:00 2001 From: aziesemer Date: Thu, 2 Aug 2018 20:43:01 -0400 Subject: [PATCH 07/14] Fix method interface to be backward compatible --- cairosvg/surface.py | 45 +++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/cairosvg/surface.py b/cairosvg/surface.py index 74dedf22..90c84c5f 100644 --- a/cairosvg/surface.py +++ b/cairosvg/surface.py @@ -108,17 +108,17 @@ class Surface(object): surface_class = None @classmethod - def convert(cls, bytestrings=None, *, file_objs=None, urls=None, dpi=96, + def convert(cls, bytestring=None, *, file_obj=None, url=None, dpi=96, parent_width=None, parent_height=None, scale=1, unsafe=False, - write_to=None, tree_objs=None, **kwargs): + write_to=None, tree_obj=None, **kwargs): """Convert a SVG document to the format for this class. Specify the input by passing one of these: :param bytestring: The SVG source as a byte-string or list. - :param file_objs: A file-like object or list. + :param file_obj: A file-like object or list. :param url: A filename or list. - :tree_objs: A Tree object or list + :tree_obj: A Tree object or list Give some options: @@ -139,28 +139,25 @@ def convert(cls, bytestrings=None, *, file_objs=None, urls=None, dpi=96, """ trees = [] - if (bytestrings is not None): - if not isinstance(bytestrings, list): - bytestrings = [bytestrings] - for bytestring in bytestrings: - trees.append(Tree(bytestring=bytestring, unsafe=unsafe, + if (bytestring is not None): + bytestrings = bytestring if isinstance(bytestring, list) else [bytestring] + for item in bytestrings: + trees.append(Tree(bytestring=item, unsafe=unsafe, **kwargs)) - if(file_objs is not None): - if not isinstance(file_objs, list): - file_objs = [file_objs] - for file in file_objs: - trees.append(Tree(file_obj=file, unsafe=unsafe, **kwargs)) - - if (urls is not None): - if not isinstance(urls, list): - urls = [urls] - for url in urls: - trees.append(Tree(url=url, unsafe=unsafe, **kwargs)) - - if (tree_objs is not None): - if not isinstance(tree_objs, list): - tree_objs = [tree_objs] + if(file_obj is not None): + file_objs = file_obj if isinstance(file_obj, list) else [file_obj] + for item in file_objs: + trees.append(Tree(file_obj=item, unsafe=unsafe, **kwargs)) + + if (url is not None): + urls = url if isinstance(url, list) else [url] + for item in urls: + trees.append(Tree(url=item, unsafe=unsafe, **kwargs)) + + if (tree_obj is not None): + if not isinstance(tree_obj, list): + tree_objs = [tree_obj] trees.extend(tree_objs) output = write_to or io.BytesIO() From 9fd2d6329d949c2a2d4e467d9f38ec06e00d5c25 Mon Sep 17 00:00:00 2001 From: aziesemer Date: Thu, 2 Aug 2018 20:45:14 -0400 Subject: [PATCH 08/14] Missed one --- cairosvg/surface.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cairosvg/surface.py b/cairosvg/surface.py index 90c84c5f..a7277280 100644 --- a/cairosvg/surface.py +++ b/cairosvg/surface.py @@ -156,8 +156,7 @@ def convert(cls, bytestring=None, *, file_obj=None, url=None, dpi=96, trees.append(Tree(url=item, unsafe=unsafe, **kwargs)) if (tree_obj is not None): - if not isinstance(tree_obj, list): - tree_objs = [tree_obj] + tree_objs = tree_obj if isinstance(tree_obj, list) else [tree_obj] trees.extend(tree_objs) output = write_to or io.BytesIO() From 3965ecd00d2b8d9f841a5a45b9e2eaac3a23f23c Mon Sep 17 00:00:00 2001 From: aziesemer Date: Thu, 2 Aug 2018 21:11:04 -0400 Subject: [PATCH 09/14] Again --- cairosvg/surface.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cairosvg/surface.py b/cairosvg/surface.py index a7277280..ec110a31 100644 --- a/cairosvg/surface.py +++ b/cairosvg/surface.py @@ -140,7 +140,8 @@ def convert(cls, bytestring=None, *, file_obj=None, url=None, dpi=96, """ trees = [] if (bytestring is not None): - bytestrings = bytestring if isinstance(bytestring, list) else [bytestring] + bytestrings = bytestring if isinstance(bytestring, list)\ + else [bytestring] for item in bytestrings: trees.append(Tree(bytestring=item, unsafe=unsafe, **kwargs)) From 70eff7357f7bfed1fc6f3226be955690285af743 Mon Sep 17 00:00:00 2001 From: aziesemer Date: Thu, 2 Aug 2018 21:20:45 -0400 Subject: [PATCH 10/14] Again --- cairosvg/surface.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cairosvg/surface.py b/cairosvg/surface.py index ec110a31..2ec911f8 100644 --- a/cairosvg/surface.py +++ b/cairosvg/surface.py @@ -140,9 +140,8 @@ def convert(cls, bytestring=None, *, file_obj=None, url=None, dpi=96, """ trees = [] if (bytestring is not None): - bytestrings = bytestring if isinstance(bytestring, list)\ - else [bytestring] - for item in bytestrings: + bss = bytestring if isinstance(bytestring, list) else [bytestring] + for item in bss: trees.append(Tree(bytestring=item, unsafe=unsafe, **kwargs)) From 00d0fb0eb973e8ec6f1169ff77985167b89d97f8 Mon Sep 17 00:00:00 2001 From: aziesemer Date: Thu, 2 Aug 2018 21:25:46 -0400 Subject: [PATCH 11/14] Fix TypeError exception --- cairosvg/surface.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cairosvg/surface.py b/cairosvg/surface.py index 2ec911f8..accaebc9 100644 --- a/cairosvg/surface.py +++ b/cairosvg/surface.py @@ -158,6 +158,9 @@ def convert(cls, bytestring=None, *, file_obj=None, url=None, dpi=96, if (tree_obj is not None): tree_objs = tree_obj if isinstance(tree_obj, list) else [tree_obj] trees.extend(tree_objs) + + if(not trees) + raise TypeError output = write_to or io.BytesIO() From 397f7f40e4b45890472a2a3716833a183fbf991a Mon Sep 17 00:00:00 2001 From: aziesemer Date: Thu, 2 Aug 2018 21:29:27 -0400 Subject: [PATCH 12/14] Again --- cairosvg/surface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cairosvg/surface.py b/cairosvg/surface.py index accaebc9..2ef144b4 100644 --- a/cairosvg/surface.py +++ b/cairosvg/surface.py @@ -159,7 +159,7 @@ def convert(cls, bytestring=None, *, file_obj=None, url=None, dpi=96, tree_objs = tree_obj if isinstance(tree_obj, list) else [tree_obj] trees.extend(tree_objs) - if(not trees) + if not trees: raise TypeError output = write_to or io.BytesIO() From e106fae32d1536580532cf6c95a21af18ae8233c Mon Sep 17 00:00:00 2001 From: aziesemer Date: Thu, 2 Aug 2018 21:34:16 -0400 Subject: [PATCH 13/14] Again --- cairosvg/surface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cairosvg/surface.py b/cairosvg/surface.py index 2ef144b4..c44bba8c 100644 --- a/cairosvg/surface.py +++ b/cairosvg/surface.py @@ -158,7 +158,7 @@ def convert(cls, bytestring=None, *, file_obj=None, url=None, dpi=96, if (tree_obj is not None): tree_objs = tree_obj if isinstance(tree_obj, list) else [tree_obj] trees.extend(tree_objs) - + if not trees: raise TypeError From 9ea74df9b4804a3eccd619d8a3f6a45a551b8f54 Mon Sep 17 00:00:00 2001 From: aziesemer Date: Thu, 2 Aug 2018 21:43:07 -0400 Subject: [PATCH 14/14] More pythonic --- cairosvg/surface.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cairosvg/surface.py b/cairosvg/surface.py index c44bba8c..fe51ef9f 100644 --- a/cairosvg/surface.py +++ b/cairosvg/surface.py @@ -139,23 +139,23 @@ def convert(cls, bytestring=None, *, file_obj=None, url=None, dpi=96, """ trees = [] - if (bytestring is not None): + if bytestring is not None: bss = bytestring if isinstance(bytestring, list) else [bytestring] for item in bss: trees.append(Tree(bytestring=item, unsafe=unsafe, **kwargs)) - if(file_obj is not None): + if file_obj is not None: file_objs = file_obj if isinstance(file_obj, list) else [file_obj] for item in file_objs: trees.append(Tree(file_obj=item, unsafe=unsafe, **kwargs)) - if (url is not None): + if url is not None: urls = url if isinstance(url, list) else [url] for item in urls: trees.append(Tree(url=item, unsafe=unsafe, **kwargs)) - if (tree_obj is not None): + if tree_obj is not None: tree_objs = tree_obj if isinstance(tree_obj, list) else [tree_obj] trees.extend(tree_objs)