From 45138f8d62225efa913d9b201bf57f7dd6ef7d62 Mon Sep 17 00:00:00 2001 From: "L.G.C" Date: Thu, 9 Dec 2021 09:44:41 +0100 Subject: [PATCH] Configurable exported viewbox The area of the SVG that will be exported can be specified by giving the id of an SVG element. Its bounding box will be used to configure the area to export. --- cairosvg/__init__.py | 29 +++++++++++++++++++---------- cairosvg/__main__.py | 6 +++++- cairosvg/helpers.py | 13 +++++++++++++ cairosvg/surface.py | 21 ++++++++++++++++++--- 4 files changed, 55 insertions(+), 14 deletions(-) diff --git a/cairosvg/__init__.py b/cairosvg/__init__.py index 1ce5b344..88703dc4 100644 --- a/cairosvg/__init__.py +++ b/cairosvg/__init__.py @@ -38,62 +38,71 @@ def svg2svg(bytestring=None, *, file_obj=None, url=None, dpi=96, parent_width=None, parent_height=None, scale=1, unsafe=False, background_color=None, negate_colors=False, invert_images=False, - write_to=None, output_width=None, output_height=None): + write_to=None, output_width=None, output_height=None, + viewbox_id=None): return surface.SVGSurface.convert( bytestring=bytestring, file_obj=file_obj, url=url, dpi=dpi, parent_width=parent_width, parent_height=parent_height, scale=scale, background_color=background_color, negate_colors=negate_colors, invert_images=invert_images, unsafe=unsafe, write_to=write_to, output_width=output_width, - output_height=output_height) + output_height=output_height, viewbox_id=viewbox_id) def svg2png(bytestring=None, *, file_obj=None, url=None, dpi=96, parent_width=None, parent_height=None, scale=1, unsafe=False, background_color=None, negate_colors=False, invert_images=False, - write_to=None, output_width=None, output_height=None): + write_to=None, output_width=None, output_height=None, + viewbox_id=None): return surface.PNGSurface.convert( bytestring=bytestring, file_obj=file_obj, url=url, dpi=dpi, parent_width=parent_width, parent_height=parent_height, scale=scale, background_color=background_color, negate_colors=negate_colors, invert_images=invert_images, unsafe=unsafe, write_to=write_to, - output_width=output_width, output_height=output_height) + output_width=output_width, output_height=output_height, + viewbox_id=viewbox_id) def svg2pdf(bytestring=None, *, file_obj=None, url=None, dpi=96, parent_width=None, parent_height=None, scale=1, unsafe=False, background_color=None, negate_colors=False, invert_images=False, - write_to=None, output_width=None, output_height=None): + write_to=None, output_width=None, output_height=None, + viewbox_id=None): return surface.PDFSurface.convert( bytestring=bytestring, file_obj=file_obj, url=url, dpi=dpi, parent_width=parent_width, parent_height=parent_height, scale=scale, background_color=background_color, negate_colors=negate_colors, invert_images=invert_images, unsafe=unsafe, write_to=write_to, - output_width=output_width, output_height=output_height) + output_width=output_width, output_height=output_height, + viewbox_id=viewbox_id) def svg2ps(bytestring=None, *, file_obj=None, url=None, dpi=96, parent_width=None, parent_height=None, scale=1, unsafe=False, background_color=None, negate_colors=False, invert_images=False, - write_to=None, output_width=None, output_height=None): + write_to=None, output_width=None, output_height=None, + viewbox_id=None): return surface.PSSurface.convert( bytestring=bytestring, file_obj=file_obj, url=url, dpi=dpi, parent_width=parent_width, parent_height=parent_height, scale=scale, background_color=background_color, negate_colors=negate_colors, invert_images=invert_images, unsafe=unsafe, write_to=write_to, - output_width=output_width, output_height=output_height) + output_width=output_width, output_height=output_height, + viewbox_id=viewbox_id) def svg2eps(bytestring=None, *, file_obj=None, url=None, dpi=96, parent_width=None, parent_height=None, scale=1, unsafe=False, background_color=None, negate_colors=False, invert_images=False, - write_to=None, output_width=None, output_height=None): + write_to=None, output_width=None, output_height=None, + viewbox_id=None): return surface.EPSSurface.convert( bytestring=bytestring, file_obj=file_obj, url=url, dpi=dpi, parent_width=parent_width, parent_height=parent_height, scale=scale, background_color=background_color, negate_colors=negate_colors, invert_images=invert_images, unsafe=unsafe, write_to=write_to, - output_width=output_width, output_height=output_height) + output_width=output_width, output_height=output_height, + viewbox_id=viewbox_id) if __debug__: diff --git a/cairosvg/__main__.py b/cairosvg/__main__.py index 3ff6b5d1..9d776abd 100644 --- a/cairosvg/__main__.py +++ b/cairosvg/__main__.py @@ -50,6 +50,9 @@ def main(argv=None, stdout=None, stdin=None): parser.add_argument( '--output-height', default=None, type=float, help='desired output height in pixels') + parser.add_argument( + '--viewbox-id', default=None, + help='export viewbox defined by the element with the given id') parser.add_argument('-o', '--output', default='-', help='output filename') @@ -61,7 +64,8 @@ def main(argv=None, stdout=None, stdin=None): 'negate_colors': options.negate_colors, 'invert_images': options.invert_images, 'output_width': options.output_width, - 'output_height': options.output_height} + 'output_height': options.output_height, + 'viewbox_id': options.viewbox_id} stdin = stdin or sys.stdin stdout = stdout or sys.stdout kwargs['write_to'] = ( diff --git a/cairosvg/helpers.py b/cairosvg/helpers.py index c3fea7f8..e29c16ed 100644 --- a/cairosvg/helpers.py +++ b/cairosvg/helpers.py @@ -386,3 +386,16 @@ def size(surface, string, reference='xy'): # Unknown size return 0 + + +def find_child(node, element_id, default=None): + if element_id is not None: + if node.element.id == element_id: + return node + + for child in node.children: + found = find_child(child, element_id) + if found is not None: + return found + + return default diff --git a/cairosvg/surface.py b/cairosvg/surface.py index c5569e76..7206135e 100644 --- a/cairosvg/surface.py +++ b/cairosvg/surface.py @@ -8,13 +8,14 @@ import cairocffi as cairo +from .bounding_box import calculate_bounding_box from .colors import color, negate_color from .defs import ( apply_filter_after_painting, apply_filter_before_painting, clip_path, filter_, gradient_or_pattern, linear_gradient, marker, mask, paint_mask, parse_all_defs, pattern, prepare_filter, radial_gradient, use) from .helpers import ( - UNITS, PointError, clip_rect, node_format, normalize, paint, + UNITS, PointError, clip_rect, find_child, node_format, normalize, paint, preserve_ratio, size, transform) from .image import image, invert_image from .parser import Tree @@ -96,6 +97,7 @@ class Surface(object): @classmethod def convert(cls, bytestring=None, *, file_obj=None, url=None, dpi=96, parent_width=None, parent_height=None, scale=1, unsafe=False, + viewbox_id=None, background_color=None, negate_colors=False, invert_images=False, write_to=None, output_width=None, output_height=None, **kwargs): @@ -115,6 +117,7 @@ def convert(cls, bytestring=None, *, file_obj=None, url=None, dpi=96, :param scale: The ouptut scaling factor. :param unsafe: A boolean allowing XML entities and very large files (WARNING: vulnerable to XXE attacks and various DoS). + :param viewbox_id: SVG element id defining the area to export. Specifiy the output with: @@ -132,6 +135,7 @@ def convert(cls, bytestring=None, *, file_obj=None, url=None, dpi=96, instance = cls( tree, output, dpi, None, parent_width, parent_height, scale, output_width, output_height, background_color, + viewbox_id=viewbox_id if viewbox_id else None, map_rgba=negate_color if negate_colors else None, map_image=invert_image if invert_images else None) instance.finish() @@ -141,7 +145,8 @@ def convert(cls, bytestring=None, *, file_obj=None, url=None, dpi=96, def __init__(self, tree, output, dpi, parent_surface=None, parent_width=None, parent_height=None, scale=1, output_width=None, output_height=None, - background_color=None, map_rgba=None, map_image=None): + background_color=None, map_rgba=None, map_image=None, + viewbox_id=None): """Create the surface from a filename or a file-like object. The rendered content is written to ``output`` which can be a filename, @@ -179,7 +184,17 @@ def __init__(self, tree, output, dpi, parent_surface=None, self.dpi = dpi self.font_size = size(self, '12pt') self.stroke_and_fill = True - width, height, viewbox = node_format(self, tree) + + width, height, viewbox = (0, 0, None) + viewbox_node = find_child(tree, viewbox_id) + if viewbox_node: + viewbox = calculate_bounding_box(self, viewbox_node) + if viewbox: + width, height = viewbox[2:] + + if viewbox is None: + width, height, viewbox = node_format(self, tree) + if viewbox is None: viewbox = (0, 0, width, height)