Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds pandas-friendly formatting for multi-column and multi-index tables #980

Closed
wants to merge 14 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
todo fixed
afriedman412 committed Oct 18, 2023

Unverified

The committer email address is not verified.
commit d465cbdbcf7acc24ed871883aaefbce97d244545
46 changes: 19 additions & 27 deletions fpdf/svg.py
Original file line number Diff line number Diff line change
@@ -396,27 +396,27 @@ def rect(cls, tag, clipping_path: bool = False):
return path

@classmethod
def circle(cls, tag):
def circle(cls, tag, clipping_path: bool = False):
"""Convert an SVG <circle> into a PDF path."""
cx = float(tag.attrib.get("cx", 0))
cy = float(tag.attrib.get("cy", 0))
r = float(tag.attrib["r"])

path = cls.new_path(tag)
path = cls.new_path(tag, clipping_path)

path.circle(cx, cy, r)
return path

@classmethod
def ellipse(cls, tag):
def ellipse(cls, tag, clipping_path: bool = False):
"""Convert an SVG <ellipse> into a PDF path."""
cx = float(tag.attrib.get("cx", 0))
cy = float(tag.attrib.get("cy", 0))

rx = tag.attrib.get("rx", "auto")
ry = tag.attrib.get("ry", "auto")

path = cls.new_path(tag)
path = cls.new_path(tag, clipping_path)

if (rx == ry == "auto") or (rx == 0) or (ry == 0):
return path
@@ -460,11 +460,11 @@ def polyline(cls, tag):
return path

@classmethod
def polygon(cls, tag):
def polygon(cls, tag, clipping_path: bool = False):
"""Convert an SVG <polygon> into a PDF path."""
points = tag.attrib["points"]

path = cls.new_path(tag)
path = cls.new_path(tag, clipping_path)

points = "M" + points + "Z"
svg_path_converter(path, points)
@@ -668,6 +668,12 @@ def __init__(self, svg_text):
self.extract_shape_info(svg_tree)
self.convert_graphics(svg_tree)

@force_nodocument
def update_xref(self, key, referenced):
if key:
key = "#" + key if not key.startswith("#") else key
self.cross_references[key] = referenced

@force_nodocument
def extract_shape_info(self, root_tag):
"""Collect shape info from the given SVG."""
@@ -872,8 +878,6 @@ def handle_defs(self, defs):
for child_ in child:
self.build_clipping_path(child_, clip_id)

# We could/should also support <defs> that are rect, circle, ellipse, line, polyline, polygon...

# this assumes xrefs only reference already-defined ids.
# I don't know if this is required by the SVG spec.
@force_nodocument
@@ -895,7 +899,7 @@ def build_xref(self, xref):
pdf_group.add_item(self.cross_references[ref])
except KeyError:
raise ValueError(
f"use {xref} references nonexistent ref id {ref}, existing refs: {self.cross_references.keys()}"
f"use {xref} references nonexistent ref id {ref}"
) from None

if "x" in xref.attrib or "y" in xref.attrib:
@@ -915,7 +919,6 @@ def build_group(self, group, pdf_group=None):
apply_styles(pdf_group, group)

# handle defs before anything else
# TODO: add test
for child in [
child for child in group if child.tag in xmlns_lookup("svg", "defs")
]:
@@ -931,10 +934,7 @@ def build_group(self, group, pdf_group=None):
if child.tag in xmlns_lookup("svg", "use"):
pdf_group.add_item(self.build_xref(child))

try:
self.cross_references["#" + group.attrib["id"]] = pdf_group
except KeyError:
pass
self.update_xref(group.attrib.get("id"), pdf_group)

return pdf_group

@@ -945,15 +945,12 @@ def build_path(self, path):
apply_styles(pdf_path, path)
self.apply_clipping_path(pdf_path, path)

svg_path = path.attrib.get("d", None)
svg_path = path.attrib.get("d")

if svg_path is not None:
svg_path_converter(pdf_path, svg_path)

try:
self.cross_references["#" + path.attrib["id"]] = pdf_path
except KeyError:
pass
self.update_xref(path.attrib.get("id"), pdf_path)

return pdf_path

@@ -963,20 +960,15 @@ def build_shape(self, shape):
shape_path = getattr(ShapeBuilder, shape_tags[shape.tag])(shape)
self.apply_clipping_path(shape_path, shape)

try:
self.cross_references["#" + shape.attrib["id"]] = shape_path
except KeyError:
pass
self.update_xref(shape.attrib.get("id"), shape_path)

return shape_path

@force_nodocument
def build_clipping_path(self, shape, clip_id):
clipping_path_shape = getattr(ShapeBuilder, shape_tags[shape.tag])(shape, True)

try:
self.cross_references["#" + clip_id] = clipping_path_shape
except KeyError:
pass
self.update_xref(clip_id, clipping_path_shape)

return clipping_path_shape

310 changes: 310 additions & 0 deletions output_audit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
a = [
b"%PDF-1.4",
b"%\xbf\xf7\xa2\xfe",
b"%QDF-1.0",
b"",
b"%% Original object ID: 2 0",
b"1 0 obj",
b"<<",
b" /OpenAction [",
b" 3 0 R",
b" /FitH",
b" null",
b" ]",
b" /PageLayout /OneColumn",
b" /Pages 4 0 R",
b" /Type /Catalog",
b">>",
b"endobj",
b"",
b"%% Original object ID: 10 0",
b"2 0 obj",
b"<<",
b" /CreationDate (D:19691231190000Z19'00')",
b">>",
b"endobj",
b"",
b"%% Page 1",
b"%% Original object ID: 3 0",
b"3 0 obj",
b"<<",
b" /Contents 5 0 R",
b" /Group <<",
b" /CS /DeviceRGB",
b" /S /Transparency",
b" /Type /Group",
b" >>",
b" /Parent 4 0 R",
b" /Resources 7 0 R",
b" /Type /Page",
b">>",
b"endobj",
b"",
b"%% Original object ID: 1 0",
b"4 0 obj",
b"<<",
b" /Count 1",
b" /Kids [",
b" 3 0 R",
b" ]",
b" /MediaBox [",
b" 0",
b" 0",
b" 340.16",
b" 113.39",
b" ]",
b" /Type /Pages",
b">>",
b"endobj",
b"",
b"%% Contents for page 1",
b"%% Original object ID: 4 0",
b"5 0 obj",
b"<<",
b" /Length 6 0 R",
b">>",
b"2 J",
b"0.57 w",
b"q 1 0 0 -1 0 113.3858 cm /GS3 gs [] 0 d q 0.2835 0 0 0.2835 -0 0 cm /GS0 gs q /GS1 gs 0 0 1 RG 0 0 m 1 1 1198 398 re S Q q /GS2 gs 1 0 0 rg 0 0 1 RG 0 0 m 700 200 m 700 255.2285 655.2285 300 600 300 c 544.7715 300 500 255.2285 500 200 c 500 144.7715 544.7715 100 600 100 c 655.2285 100 700 144.7715 700 200 c h B Q Q Q",
b"endstream",
b"endobj",
b"",
b"6 0 obj",
b"330",
b"endobj",
b"",
b"%% Original object ID: 9 0",
b"7 0 obj",
b"<<",
b" /ExtGState <<",
b" /GS0 8 0 R",
b" /GS1 9 0 R",
b" /GS2 10 0 R",
b" /GS3 11 0 R",
b" >>",
b" /ProcSet [",
b" /PDF",
b" /Text",
b" /ImageB",
b" /ImageC",
b" /ImageI",
b" ]",
b">>",
b"endobj",
b"",
b"%% Original object ID: 5 0",
b"8 0 obj",
b"<<",
b" /LC 0",
b" /Type /ExtGState",
b">>",
b"endobj",
b"",
b"%% Original object ID: 6 0",
b"9 0 obj",
b"<<",
b" /LW 2",
b" /Type /ExtGState",
b">>",
b"endobj",
b"",
b"%% Original object ID: 7 0",
b"10 0 obj",
b"<<",
b" /LW 10",
b" /Type /ExtGState",
b">>",
b"endobj",
b"",
b"%% Original object ID: 8 0",
b"11 0 obj",
b"<<",
b" /D [",
b" [",
b" ]",
b" 0",
b" ]",
b" /LW 0.567",
b" /Type /ExtGState",
b">>",
b"endobj",
b"",
b"xref",
b"0 12",
b"0000000000 65535 f ",
b"0000000052 00000 n ",
b"0000000208 00000 n ",
b"0000000309 00000 n ",
b"0000000499 00000 n ",
b"0000000673 00000 n ",
b"0000001058 00000 n ",
b"0000001105 00000 n ",
b"0000001309 00000 n ",
b"0000001385 00000 n ",
b"0000001461 00000 n ",
b"0000001539 00000 n ",
b"trailer <<",
b" /Info 2 0 R",
b" /Root 1 0 R",
b" /Size 12",
b" /ID [<d467afdac19e4512026fd75c955accd6><4dadfd0c58a5e5ee5c563f500783c5af>]",
b">>",
b"startxref",
b"1622",
b"%%EOF",
]
b = [
b"%PDF-1.4",
b"%\xbf\xf7\xa2\xfe",
b"%QDF-1.0",
b"",
b"%% Original object ID: 2 0",
b"1 0 obj",
b"<<",
b" /OpenAction [",
b" 3 0 R",
b" /FitH",
b" null",
b" ]",
b" /PageLayout /OneColumn",
b" /Pages 4 0 R",
b" /Type /Catalog",
b">>",
b"endobj",
b"",
b"%% Original object ID: 10 0",
b"2 0 obj",
b"<<",
b" /CreationDate (D:20231017193139Z19'31')",
b">>",
b"endobj",
b"",
b"%% Page 1",
b"%% Original object ID: 3 0",
b"3 0 obj",
b"<<",
b" /Contents 5 0 R",
b" /Group <<",
b" /CS /DeviceRGB",
b" /S /Transparency",
b" /Type /Group",
b" >>",
b" /Parent 4 0 R",
b" /Resources 7 0 R",
b" /Type /Page",
b">>",
b"endobj",
b"",
b"%% Original object ID: 1 0",
b"4 0 obj",
b"<<",
b" /Count 1",
b" /Kids [",
b" 3 0 R",
b" ]",
b" /MediaBox [",
b" 0",
b" 0",
b" 340.16",
b" 113.39",
b" ]",
b" /Type /Pages",
b">>",
b"endobj",
b"",
b"%% Contents for page 1",
b"%% Original object ID: 4 0",
b"5 0 obj",
b"<<",
b" /Length 6 0 R",
b">>",
b"2 J",
b"0.57 w",
b"q 1 0 0 -1 0 113.3858 cm /GS3 gs [] 0 d q 0.2835 0 0 0.2835 -0 0 cm /GS0 gs q /GS1 gs 0 0 1 RG 0 0 m 1 1 1198 398 re S Q q /GS2 gs 1 0 0 rg 0 0 1 RG 0 0 m 700 200 m 700 255.2285 655.2285 300 600 300 c 544.7715 300 500 255.2285 500 200 c 500 144.7715 544.7715 100 600 100 c 655.2285 100 700 144.7715 700 200 c h B Q Q Q",
b"endstream",
b"endobj",
b"",
b"6 0 obj",
b"330",
b"endobj",
b"",
b"%% Original object ID: 9 0",
b"7 0 obj",
b"<<",
b" /ExtGState <<",
b" /GS0 8 0 R",
b" /GS1 9 0 R",
b" /GS2 10 0 R",
b" /GS3 11 0 R",
b" >>",
b" /ProcSet [",
b" /PDF",
b" /Text",
b" /ImageB",
b" /ImageC",
b" /ImageI",
b" ]",
b">>",
b"endobj",
b"",
b"%% Original object ID: 5 0",
b"8 0 obj",
b"<<",
b" /LC 0",
b" /Type /ExtGState",
b">>",
b"endobj",
b"",
b"%% Original object ID: 6 0",
b"9 0 obj",
b"<<",
b" /LW 2",
b" /Type /ExtGState",
b">>",
b"endobj",
b"",
b"%% Original object ID: 7 0",
b"10 0 obj",
b"<<",
b" /LW 10",
b" /Type /ExtGState",
b">>",
b"endobj",
b"",
b"%% Original object ID: 8 0",
b"11 0 obj",
b"<<",
b" /D [",
b" [",
b" ]",
b" 0",
b" ]",
b" /LW 0.567",
b" /Type /ExtGState",
b">>",
b"endobj",
b"",
b"xref",
b"0 12",
b"0000000000 65535 f ",
b"0000000052 00000 n ",
b"0000000208 00000 n ",
b"0000000309 00000 n ",
b"0000000499 00000 n ",
b"0000000673 00000 n ",
b"0000001058 00000 n ",
b"0000001105 00000 n ",
b"0000001309 00000 n ",
b"0000001385 00000 n ",
b"0000001461 00000 n ",
b"0000001539 00000 n ",
b"trailer <<",
b" /Info 2 0 R",
b" /Root 1 0 R",
b" /Size 12",
b" /ID [<9c8b942c79f09e4c428cff498685f1e1><bbd59e39b35c557688c5a2e04a2afba1>]",
b">>",
b"startxref",
b"1622",
b"%%EOF",
]
Binary file added test/svg/generated_pdf/actual.pdf
Binary file not shown.
Binary file added test/svg/generated_pdf/actual_qpdf.pdf
Binary file not shown.
Binary file added test/svg/generated_pdf/circle01_def_test.pdf
Binary file not shown.
Binary file not shown.
Binary file added test/svg/generated_pdf/clip_path.pdf
Binary file not shown.
Binary file added test/svg/generated_pdf/expected_qpdf.pdf
Binary file not shown.
6 changes: 5 additions & 1 deletion test/svg/parameters.py
Original file line number Diff line number Diff line change
@@ -759,13 +759,17 @@ def Gs(**kwargs):
pytest.param(svgfile("SVG_logo.svg"), id="SVG logo from wikipedia"),
pytest.param(svgfile("viewbox.svg"), id="weird viewbox"),
pytest.param(svgfile("search.svg"), id="search icon"), # issue 356
# discovered while investigatin issue 358:
# discovered while investigating issue 358:
pytest.param(svgfile("issue_358b.svg"), id="repeated relative move"),
pytest.param(svgfile("issue_358.svg"), id="arc start & initial point"), # issue 358
pytest.param(svgfile("Ghostscript_colorcircle.svg"), id="ghostscript colorcircle"),
pytest.param(svgfile("Ghostscript_escher.svg"), id="ghostscript escher"),
pytest.param(svgfile("use-xlink-href.svg"), id="use xlink:href - issue #446"),
pytest.param(svgfile("rgb-color-issue-480.svg"), id="rgb() color - issue #480"),
pytest.param(
svgfile("circle01_def_test.svg"), id="circle defined in 'defs' tag - issue #858"
),
pytest.param(svgfile("clip_path.svg"), id="clip path - issue #858"),
)

svg_path_edge_cases = (
16 changes: 16 additions & 0 deletions test/svg/svg_sources/circle01_def_test.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions test/svg/svg_sources/clip_path.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.