Skip to content
Merged
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 @@ -30,6 +30,7 @@ This can also be enabled programmatically with `warnings.simplefilter('default',
* `FPDF.write_html()` no longer raises `KeyError: 'href'` on an `<a>` element without an `href` attribute, such as a named anchor (`<a name="intro">`); its text is rendered as plain text - thanks to @RavSinghChandan
* `FPDF.write_html()` treats an invalid `colspan` or `rowspan` on `<td>` / `<th>`, or one below 1, as 1 like browsers do, instead of raising `ValueError` or `ZeroDivisionError` - thanks to @RavSinghChandan
* warning detection during `FPDFRecorder.replay()` now correctly identifies context managers (`contextlib.AbstractContextManager`, `@contextmanager`, custom context managers) within `unbreakable()` sections
* SVG import: `opacity`, `fill-opacity` and `stroke-opacity` accept percentages (`50%`), and an out-of-range `opacity` is clamped to 0-1 like the other two instead of raising `ValueError` - thanks to @RavSinghChandan
### Changed
* Removed redundant trailing zeros from numbers in PDF dictionaries and arrays outside content streams, preserving existing numeric precision
### Removed
Expand Down
7 changes: 5 additions & 2 deletions fpdf/svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,9 @@ def convert_miterlimit(incoming: str) -> float:
@force_nodocument
def clamp_float(min_val: float, max_val: float) -> Callable[[str], float]:
def converter(value: str) -> float:
val = float(value)
value = value.strip()
# SVG 2 / CSS also allow opacities as a percentage: "50%" is 0.5
val = float(value[:-1]) / 100 if value.endswith("%") else float(value)
if val < min_val:
return min_val
if val > max_val:
Expand Down Expand Up @@ -418,7 +420,8 @@ def apply_styles(
if opacity_str is None:
opacity_str = _normalize_css_value(svg_element.attrib.get("opacity"))
if opacity_str is not None:
opacity = float(opacity_str)
# out-of-range values are clamped, as for fill-opacity and stroke-opacity
opacity = clamp_float(0.0, 1.0)(opacity_str)
stylable.style.fill_opacity = opacity
stylable.style.stroke_opacity = opacity

Expand Down
30 changes: 30 additions & 0 deletions test/svg/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,12 @@ def Gs(**kwargs):
no_error(),
id="fill-opacity too big",
),
pytest.param(
'<path fill-opacity="50%"/>',
Gs(fill_opacity=0.5),
no_error(),
id="fill-opacity percentage",
),
pytest.param(
'<path fill-opacity="inherit"/>',
Gs(fill_opacity=GraphicsStyle.INHERIT),
Expand Down Expand Up @@ -722,6 +728,30 @@ def Gs(**kwargs):
no_error(),
id="stroke-opacity too big",
),
pytest.param(
'<path stroke-opacity="150%"/>',
Gs(stroke_opacity=1.0),
no_error(),
id="stroke-opacity percentage too big",
),
pytest.param(
'<path opacity="25%"/>',
Gs(fill_opacity=0.25, stroke_opacity=0.25),
no_error(),
id="opacity percentage",
),
pytest.param(
'<path opacity="1.5"/>',
Gs(fill_opacity=1.0, stroke_opacity=1.0),
no_error(),
id="opacity too big",
),
pytest.param(
'<path opacity="-1"/>',
Gs(fill_opacity=0.0, stroke_opacity=0.0),
no_error(),
id="opacity too small",
),
pytest.param(
'<path stroke-opacity="inherit"/>',
Gs(stroke_opacity=GraphicsStyle.INHERIT),
Expand Down
Loading