Skip to content

Commit 3f147c7

Browse files
committed
Supporting <text> tags from SVG files
1 parent e23e00d commit 3f147c7

File tree

5 files changed

+60
-24
lines changed

5 files changed

+60
-24
lines changed

fpdf/svg.py

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -342,21 +342,14 @@ def new_path(tag, clipping_path: bool = False):
342342
if clipping_path:
343343
path = ClippingPath()
344344
apply_styles(path, tag)
345-
346345
return path
347346

348347
@classmethod
349348
def rect(cls, tag, clipping_path: bool = False):
350349
"""Convert an SVG <rect> into a PDF path."""
351350
# svg rect is wound clockwise
352-
if "x" in tag.attrib:
353-
x = resolve_length(tag.attrib["x"])
354-
else:
355-
x = 0
356-
if "y" in tag.attrib:
357-
y = resolve_length(tag.attrib["y"])
358-
else:
359-
y = 0
351+
x = resolve_length(tag.attrib.get("x", 0))
352+
y = resolve_length(tag.attrib.get("y", 0))
360353
width = tag.attrib.get("width", "0")
361354
if width.endswith("%"):
362355
width = Percent(width[:-1])
@@ -397,7 +390,6 @@ def rect(cls, tag, clipping_path: bool = False):
397390
ry = height / 2
398391

399392
path = cls.new_path(tag, clipping_path)
400-
401393
path.rectangle(x, y, width, height, rx, ry)
402394
return path
403395

@@ -409,7 +401,6 @@ def circle(cls, tag, clipping_path: bool = False):
409401
r = float(tag.attrib["r"])
410402

411403
path = cls.new_path(tag, clipping_path)
412-
413404
path.circle(cx, cy, r)
414405
return path
415406

@@ -447,34 +438,24 @@ def line(cls, tag):
447438
y2 = float(tag.attrib["y2"])
448439

449440
path = cls.new_path(tag)
450-
451441
path.move_to(x1, y1)
452442
path.line_to(x2, y2)
453-
454443
return path
455444

456445
@classmethod
457446
def polyline(cls, tag):
458447
"""Convert an SVG <polyline> into a PDF path."""
459-
points = tag.attrib["points"]
460-
461448
path = cls.new_path(tag)
462-
463-
points = "M" + points
449+
points = "M" + tag.attrib["points"]
464450
svg_path_converter(path, points)
465-
466451
return path
467452

468453
@classmethod
469454
def polygon(cls, tag, clipping_path: bool = False):
470455
"""Convert an SVG <polygon> into a PDF path."""
471-
points = tag.attrib["points"]
472-
473456
path = cls.new_path(tag, clipping_path)
474-
475-
points = "M" + points + "Z"
457+
points = "M" + tag.attrib["points"] + "Z"
476458
svg_path_converter(path, points)
477-
478459
return path
479460

480461

@@ -878,6 +859,8 @@ def handle_defs(self, defs):
878859
self.build_path(child)
879860
elif child.tag in xmlns_lookup("svg", "image"):
880861
self.build_image(child)
862+
elif child.tag in xmlns_lookup("svg", "text"):
863+
self.build_text(child)
881864
elif child.tag in shape_tags:
882865
self.build_shape(child)
883866
elif child.tag in xmlns_lookup("svg", "clipPath"):
@@ -947,6 +930,8 @@ def build_group(self, group, pdf_group=None):
947930
pdf_group.add_item(self.build_xref(child))
948931
elif child.tag in xmlns_lookup("svg", "image"):
949932
pdf_group.add_item(self.build_image(child))
933+
elif child.tag in xmlns_lookup("svg", "text"):
934+
pdf_group.add_item(self.build_text(child))
950935
else:
951936
LOGGER.debug("Unsupported SVG tag: <%s>", child.tag)
952937

@@ -987,6 +972,43 @@ def apply_clipping_path(self, stylable, svg_element):
987972
clipping_path_id = re.search(r"url\((\#\w+)\)", clipping_path)
988973
stylable.clipping_path = self.cross_references[clipping_path_id[1]]
989974

975+
@force_nodocument
976+
def build_text(self, text):
977+
if "dx" in text.attrib or "dy" in text.attrib:
978+
raise NotImplementedError(
979+
'"dx" / "dy" defined on <text> is currently not supported (but contributions are welcome!)'
980+
)
981+
if "lengthAdjust" in text.attrib:
982+
raise NotImplementedError(
983+
'"lengthAdjust" defined on <text> is currently not supported (but contributions are welcome!)'
984+
)
985+
if "rotate" in text.attrib:
986+
raise NotImplementedError(
987+
'"rotate" defined on <text> is currently not supported (but contributions are welcome!)'
988+
)
989+
if "style" in text.attrib:
990+
raise NotImplementedError(
991+
'"style" defined on <text> is currently not supported (but contributions are welcome!)'
992+
)
993+
if "textLength" in text.attrib:
994+
raise NotImplementedError(
995+
'"textLength" defined on <text> is currently not supported (but contributions are welcome!)'
996+
)
997+
if "transform" in text.attrib:
998+
raise NotImplementedError(
999+
'"transform" defined on <text> is currently not supported (but contributions are welcome!)'
1000+
)
1001+
font_family = text.attrib.get("font-family")
1002+
font_size = text.attrib.get("font-size")
1003+
# TODO: reuse code from line_break & text_region modules.
1004+
# We could either:
1005+
# 1. handle text regions in this module (svg), with a dedicated SVGText class.
1006+
# 2. handle text regions in the drawing module, maybe by defining a PaintedPath.text() method.
1007+
# This may be the best approach, as we would benefit from the global transformation performed in SVGObject.transform_to_rect_viewport()
1008+
svg_text = None
1009+
self.update_xref(text.attrib.get("id"), svg_text)
1010+
return svg_text
1011+
9901012
@force_nodocument
9911013
def build_image(self, image):
9921014
href = None
1.21 KB
Binary file not shown.

test/svg/parameters.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,7 @@ def Gs(**kwargs):
775775
svgfile("use-image-def.svg"),
776776
id="Use xlink:href to insert an <image> from <defs>",
777777
),
778+
pytest.param(svgfile("text-samples.svg"), id="<text> tests"),
778779
)
779780

780781
svg_path_edge_cases = (

test/svg/svg_sources/embedded-raster-images.svg

Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 13 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)