Skip to content

Add basic <symbol> support in SVG #1819

Merged
andersonhc merged 19 commits into
py-pdf:masterfrom
Theo1335:svg-feature
May 16, 2026
Merged

Add basic <symbol> support in SVG #1819
andersonhc merged 19 commits into
py-pdf:masterfrom
Theo1335:svg-feature

Conversation

@Theo1335
Copy link
Copy Markdown

@Theo1335 Theo1335 commented Apr 15, 2026

Add basic support in SVG parsing

This PR adds support for SVG <symbol> elements.

  • Reuse existing group parsing logic for symbols.
  • Add a unit test covering symbol parsing and cross-reference registration.

Checklist:

  • A unit test is covering the code added / modified by this PR

  • In case of a new feature, docstrings have been added, with also some documentation in the docs/ folder (N/A)

  • A mention of the change is present in CHANGELOG.md

  • This PR is ready to be merged

By submitting this pull request, I confirm that my contribution is made under the terms of the GNU LGPL 3.0 license.

@andersonhc
Copy link
Copy Markdown
Collaborator

Thank you for this PR @Theo1335 , it's looking very promising!

I found one problem testing this SVG

image

The symbols are rendered but the scale is wrong.

Can you please check how to fix this?

@Theo1335
Copy link
Copy Markdown
Author

Theo1335 commented Apr 21, 2026

Thank you for your feedback @andersonhc !

I think I've finally solved the scaling issue with <symbol> elements: the viewBox is now extracted and stored when <symbol> is parsed, and when a <use> directive references a symbol with a width and height, the appropriate scaling is applied based on the `viewBox's dimensions.

Please let me know what you think.

Comment thread fpdf/svg.py Outdated
Comment on lines +1515 to +1520
viewbox = symbol.attrib.get("viewBox")
if viewbox:
parts = viewbox.replace(",", " ").split()
if len(parts) >= 4:
setattr(group, "_vw", float(parts[2]))
setattr(group, "_vh", float(parts[3]))
Copy link
Copy Markdown
Collaborator

@andersonhc andersonhc Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now the test is failing:

    @force_nodocument
    def build_symbol(self, symbol: "Element") -> GraphicsContext:
        """Parse <symbol> 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:
>               setattr(group, "_vw", float(parts[2]))
E               AttributeError: 'GraphicsContext' object has no attribute '_vw'

I don't think adding the viewbox attribute to the GraphicsContext is the best approach.
I recommend looking into using the GaphicsContext's transform to apply the viewbox.

Apply translate(-vx, -vy) @ scale(1 / vw, 1 / vh) to the symbol group should make it normalized to unit coordinates. In build_xref(), apply scale(width, height) @ translate(x, y).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @andersonhc , I've implemented your suggestion using transforms. The viewBox is now applied in build_symbol with scale(1/vw, 1/vh) @ translate(-vx, -vy), and build_xref applies scale(width, height) when specified. I also ignore percentage values for width/height. I tested it and it should be working now. Let me know what you think !

@Theo1335 Theo1335 requested a review from andersonhc May 13, 2026 00:07
Copy link
Copy Markdown
Collaborator

@andersonhc andersonhc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see suggested changes.

I would also add you to add a new item on test/svg/parameters.py on the test_svg_sources set:

pytest.param(svgfile("symbol.svg"), id="symbol reused in different placements"),

then create the file test/svg/svg_sources/symbol.svg with the content below:

<svg viewBox="0 0 80 20" xmlns="http://www.w3.org/2000/svg">
  <!-- Our symbol in its own coordinate system -->
  <symbol id="myDot" width="10" height="10" viewBox="0 0 2 2">
    <circle cx="1" cy="1" r="1" />
  </symbol>

  <!-- A grid to materialize our symbol positioning -->
  <path
    d="M0,10 h80 M10,0 v20 M25,0 v20 M40,0 v20 M55,0 v20 M70,0 v20"
    fill="none"
    stroke="pink" />

  <!-- All instances of our symbol -->
  <use href="#myDot" x="5" y="5" opacity="1.0" />
  <use href="#myDot" x="20" y="5" opacity="0.8" />
  <use href="#myDot" x="35" y="5" opacity="0.6" />
  <use href="#myDot" x="50" y="5" opacity="0.4" />
  <use href="#myDot" x="65" y="5" opacity="0.2" />
</svg>

After that you will need to execute test_svg_conversion() once adding generate=True to assert_pdf_equal so it can generate the new PDF reference file.

Comment thread fpdf/svg.py
Comment thread fpdf/svg.py
Comment thread fpdf/svg.py
Comment thread fpdf/svg.py Outdated
Comment thread fpdf/svg.py Outdated
Comment thread test/svg/test_svg.py Outdated
@Theo1335
Copy link
Copy Markdown
Author

Thanks for the detailed feedback! I will implement your suggestions and get back to you once it's done, or if I don't understand some details.

renovate Bot and others added 4 commits May 16, 2026 11:12
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
@Theo1335
Copy link
Copy Markdown
Author

Hi @andersonhc, thank you again! I've implemented all your suggestions, let me know what you think.

@Theo1335 Theo1335 requested a review from andersonhc May 16, 2026 10:40
@andersonhc
Copy link
Copy Markdown
Collaborator

@allcontributors please add @Theo1335 for code

@allcontributors
Copy link
Copy Markdown

@andersonhc

I've put up a pull request to add @Theo1335! 🎉

@andersonhc andersonhc merged commit c9864b4 into py-pdf:master May 16, 2026
23 checks passed
@andersonhc
Copy link
Copy Markdown
Collaborator

Thank you for the contribution @Theo1335
The PR has been merged

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants