diff --git a/CHANGELOG.md b/CHANGELOG.md index dc633d55a..64dc8bd44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This can also be enabled programmatically with `warnings.simplefilter('default', ## [2.8.8] - Not released yet ### Added * Punjabi (pa) tutorial translation - thanks to @Pawansingh3889 +* basic support for SVG `` elements in the SVG parser * `resource_access_policy` and [Security considerations](https://py-pdf.github.io/fpdf2/Security.html) documentation ### Fixed * text rendering when the first text on a page starts with a fallback glyph - _cf._ [issue #1772](https://github.com/py-pdf/fpdf2/issues/1772) diff --git a/fpdf/svg.py b/fpdf/svg.py index 9318a9ed1..dbe26a97a 100644 --- a/fpdf/svg.py +++ b/fpdf/svg.py @@ -1473,6 +1473,8 @@ def handle_defs(self, defs: "Element") -> None: for child in defs: if child.tag in xmlns_lookup("svg", "g"): self.build_group(child) + elif child.tag in xmlns_lookup("svg", "symbol"): + self.build_symbol(child) elif child.tag in xmlns_lookup("svg", "a"): # tags aren't supported but we need to recurse into them to # render nested elements. @@ -1506,6 +1508,20 @@ def handle_defs(self, defs: "Element") -> None: without_ns(child.tag), ) + @force_nodocument + def build_symbol(self, symbol: "Element") -> GraphicsContext: + """Parse as reusable content, not rendered directly.""" + group = self.build_group(symbol) + viewbox = symbol.attrib.get("viewBox") + if viewbox: + parts = viewbox.replace(",", " ").split() + if len(parts) >= 4: + vx, vy, vw, vh = (float(p) for p in parts[:4]) + group.transform = Transform.scaling( + x=1 / vw, y=1 / vh + ) @ Transform.translation(x=-vx, y=-vy) + return group + # this assumes xrefs only reference already-defined ids. # I don't know if this is required by the SVG spec. @force_nodocument @@ -1525,7 +1541,8 @@ def build_xref(self, xref: "Element") -> GraphicsContext: raise ValueError(f"use {xref} doesn't contain known xref attribute") try: - pdf_group.add_item(self.cross_references[ref]) + target = self.cross_references[ref] + pdf_group.add_item(target) except KeyError: raise ValueError( f"use {xref} references nonexistent ref id {ref}" @@ -1536,7 +1553,20 @@ def build_xref(self, xref: "Element") -> GraphicsContext: # > The x and y properties define an additional transformation translate(x,y) x, y = float(xref.attrib.get("x", 0)), float(xref.attrib.get("y", 0)) pdf_group.transform = Transform.translation(x=x, y=y) - # Note that we currently do not support "width" & "height" in + # Note that we currently do not support "width" & "height" with % in + + if "width" in xref.attrib or "height" in xref.attrib: + w_str = xref.attrib.get("width", "") + h_str = xref.attrib.get("height", "") + if "%" not in w_str and "%" not in h_str: + w = float(w_str) if w_str else 1 + h = float(h_str) if h_str else 1 + if pdf_group.transform is None: + pdf_group.transform = Transform.scaling(x=w, y=h) + else: + pdf_group.transform = ( + Transform.scaling(x=w, y=h) @ pdf_group.transform + ) return pdf_group @@ -1567,6 +1597,8 @@ def build_group( elif child.tag in xmlns_lookup("svg", "style"): # Stylesheets already parsed globally. continue + elif child.tag in xmlns_lookup("svg", "symbol"): + self.build_symbol(child) elif child.tag in xmlns_lookup("svg", "g"): pdf_group.add_item(self.build_group(child, None, merged_style), False) elif child.tag in xmlns_lookup("svg", "a"): diff --git a/test/svg/test_svg.py b/test/svg/test_svg.py index 9b8d9c207..1cb2d8fcd 100644 --- a/test/svg/test_svg.py +++ b/test/svg/test_svg.py @@ -246,6 +246,20 @@ def test_missing_xref(self): with pytest.raises(ValueError): fpdf.svg.SVGObject(svg_data) + def test_svg_symbol(self): + svg_data = ( + '' + "" + '' + "" + '' + "" + ) + svg = fpdf.svg.SVGObject(svg_data) + assert svg is not None + assert "#rond" in svg.cross_references + def test_svg_conversion_no_transparency(self, tmp_path): svg = fpdf.svg.SVGObject.from_file(parameters.svgfile("SVG_logo.svg"))