Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ This can also be enabled programmatically with `warnings.simplefilter('default',
* Chinese translation of the [Tuto 7 - Creating PDF/A Documents](https://py-pdf.github.io/fpdf2/Tutorial-zh.html#-pdfa) tutorial section - _cf._ [issue #267](https://github.com/py-pdf/fpdf2/issues/267) - _cf._ [PR #2138](https://github.com/py-pdf/fpdf2/pull/2138) - thanks to @zheng-guannan577*
* `FPDF.write_html()` makes the `id` of an element, and the `name` of an `<a>`, a named destination, so that `<a href="#...">` links within the document work, like in browsers, instead of raising `FPDFException: Named destination ... was referenced but never set` - thanks to @RavSinghChandan
### Fixed
* SVG: an element whose `clip-path` references a `<clipPath>` without any shape to clip with, such as one without children or with only a `<g>`, is now clipped away, as the SVG specification says, and one whose `clip-path` references no `<clipPath>` is drawn without clipping, with a warning, as in browsers, instead of failing with `KeyError` - thanks to @RavSinghChandan
* SVG: an `@import`, `@charset` or other at-rule without a block at the start of a `<style>`, such as the `@import` of a Google Font, no longer hides the CSS rule that follows it; a `;` inside its URL, as in `wght@600;700`, is part of the URL - thanks to @RavSinghChandan
* `FPDF.write_html()`: a link with `href="#"`, as in the "Back to top" links of many web pages, now goes to the top of the document, like in browsers, instead of making `output()` fail with `FPDFException: Named destination '' was referenced but never set` - thanks to @RavSinghChandan
* `FPDF.output()` no longer fails with `struct.error: unpack requires a buffer of 2 bytes` for a font with Apple bitmap tables, such as the `Geneva.ttf` and `Cochin.ttc` of macOS: their `bdat` and `bloc` tables are now dropped when subsetting, like the `EBDT` and `EBLC` tables they correspond to - thanks to @RavSinghChandan
Expand Down
19 changes: 17 additions & 2 deletions fpdf/svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -3078,8 +3078,23 @@ def apply_clipping_path(
clip_value = svg_element.attrib.get("clip-path")
# "none" (or any value that is not a url(#id) reference) clips nothing
clipping_path_id = self._extract_gradient_id(clip_value)
if clipping_path_id:
stylable.clipping_path = self.cross_references[clipping_path_id]
if not clipping_path_id:
return
element = self._elements_by_id.get(clipping_path_id)
if element is None or element.tag not in xmlns_lookup("svg", "clipPath"):
# Browsers draw the element without clipping it
LOGGER.warning(
"Ignoring clip-path reference to nonexistent <clipPath> %s",
clipping_path_id,
)
return
clipping_path = self.cross_references.get(clipping_path_id)
if clipping_path is None:
# A <clipPath> without any shape it can clip with, such as one without
# children or with only a <g>, is empty: it clips the whole element away
clipping_path = ClippingPath()
clipping_path.rectangle(0, 0, 0, 0)
stylable.clipping_path = clipping_path

@force_nodocument
def build_image(self, image: "Element") -> "SVGImage | GraphicsContext":
Expand Down
35 changes: 35 additions & 0 deletions test/svg/test_svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,41 @@ def test_svg_use_of_use_of_nothing(self, referenced, caplog):
assert outer.path_items[0].path_items == []
assert "#clone" not in caplog.text

@pytest.mark.parametrize(
"clip_path",
[
'<clipPath id="c"/>',
# children that cannot be part of a clipping path
'<clipPath id="c"><g><rect width="5" height="5"/></g></clipPath>',
'<clipPath id="c"><switch><rect width="5" height="5"/></switch></clipPath>',
],
)
def test_svg_empty_clip_path(self, clip_path):
"""A clipping path without any shape clips the whole element away."""
pdf = fpdf.FPDF()
pdf.compress = False
pdf.add_page()
fpdf.svg.SVGObject(
'<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10">'
f'{clip_path}<rect width="10" height="10" fill="red" clip-path="url(#c)"/>'
"</svg>"
).draw_to_page(pdf)
assert "1 0 0 rg 0 0 m W n 0 0 m 0 0 10 10 re f" in bytes(pdf.output()).decode(
"latin-1"
)

@pytest.mark.parametrize("target", ["", '<rect id="c" width="5" height="5"/>'])
def test_svg_clip_path_reference_to_nothing(self, target, caplog):
"""Browsers draw an element whose clip-path references no clipPath unclipped."""
svg = fpdf.svg.SVGObject(
f'<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><defs>{target}'
'</defs><path d="M0 0h5v5z" fill="red" clip-path="url(#c)"/></svg>'
)
assert svg.base_group.path_items[-1].clipping_path is None
assert (
"Ignoring clip-path reference to nonexistent <clipPath> #c" in caplog.text
)

def test_svg_display_none_referenced(self):
"""An element in a hidden group can still be drawn by a <use>."""
svg = fpdf.svg.SVGObject(
Expand Down
Loading