Skip to content

Conversation

@Ani07-05
Copy link

Fixes #1580

This PR implements comprehensive SVG gradient support for fpdf2, enabling rendering of both linear and radial gradients from SVG documents.

Features

  • Linear gradients with all SVG coordinate systems
  • Radial gradients with focal point (fx, fy, fr) support
  • Gradient stops with color and opacity
  • Spread methods: pad, reflect, repeat (related to PR COLRv1 support enhancements #1636)
  • Gradient transforms (rotation, scaling, translation)
  • Support for both objectBoundingBox and userSpaceOnUse units
  • Gradients can be applied to both fill and stroke

Implementation

  • Added gradient parsing methods in SVGObject class
  • Integrated with existing fpdf2 GradientPaint system
  • Gradients stored in gradient_definitions dict by ID
  • Applied via url(#gradientId) references in fill/stroke

Tests

  • 18 comprehensive test cases covering all gradient features
  • All 287 SVG tests passing (18 new + 269 existing)
  • Added 5 SVG test fixtures and 5 PDF reference files

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
  • 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.

@Ani07-05
Copy link
Author

@andersonhc @Lucas-C please review and let me know if there are any changes.

Copy link
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 run pylint on your code before the next commit. Those are the errors I'm getting:

************* Module fpdf.svg
fpdf/svg.py:901:16: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
fpdf/svg.py:885:4: R6301: Method could be a function (no-self-use)
fpdf/svg.py:941:24: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
fpdf/svg.py:968:16: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
fpdf/svg.py:907:4: R6301: Method could be a function (no-self-use)
fpdf/svg.py:974:4: R6301: Method could be a function (no-self-use)
fpdf/svg.py:1017:12: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
fpdf/svg.py:1024:19: W0718: Catching too general exception Exception (broad-exception-caught)
fpdf/svg.py:1025:16: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
fpdf/svg.py:1030:12: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
fpdf/svg.py:1051:8: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
fpdf/svg.py:1079:12: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
fpdf/svg.py:1102:19: W0718: Catching too general exception Exception (broad-exception-caught)
fpdf/svg.py:1103:16: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
fpdf/svg.py:1107:12: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
fpdf/svg.py:1129:8: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
fpdf/svg.py:1140:16: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
fpdf/svg.py:1148:16: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
************* Module test.svg.test_svg_gradients
test/svg/test_svg_gradients.py:27:8: C0415: Import outside toplevel (fpdf.drawing.GradientPaint) (import-outside-toplevel)
test/svg/test_svg_gradients.py:9:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:31:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:49:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:72:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:93:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:124:8: C0415: Import outside toplevel (fpdf.enums.GradientUnits) (import-outside-toplevel)
test/svg/test_svg_gradients.py:109:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:145:8: C0415: Import outside toplevel (fpdf.enums.GradientUnits) (import-outside-toplevel)
test/svg/test_svg_gradients.py:131:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:152:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:170:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:187:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:203:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:219:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:235:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:252:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:266:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:282:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:299:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:315:4: R6301: Method could be a function (no-self-use)
test/svg/test_svg_gradients.py:2:0: W0611: Unused FPDF imported from fpdf (unused-import)

@Ani07-05
Copy link
Author

@andersonhc I have updated my files as per your suggestions please let me know if they require further corrections

@Ani07-05
Copy link
Author

@andersonhc any updates and suggestions?

Copy link
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 comments.

Also pylint errors:
************* Module fpdf.svg
fpdf/svg.py:885:4: R6301: Method could be a function (no-self-use)
fpdf/svg.py:907:4: R6301: Method could be a function (no-self-use)
fpdf/svg.py:974:4: R6301: Method could be a function (no-self-use)
fpdf/svg.py:1024:19: W0718: Catching too general exception Exception (broad-exception-caught)
fpdf/svg.py:1102:19: W0718: Catching too general exception Exception (broad-exception-caught)


and one test is failing on `pytest`:

=========================== short test summary info ============================
FAILED test/image/test_vector_image.py::test_svg_image_with_unsupported_color - Failed: DID NOT RAISE <class 'ValueError'>

Fixes py-pdf#1580

Features:
- Linear gradients with all SVG coordinate systems
- Radial gradients with focal point (fx, fy, fr) support
- Gradient stops with color and opacity
- Spread methods: pad, reflect, repeat (related to PR py-pdf#1636)
- Gradient transforms (rotation, scaling, translation)
- Support for both objectBoundingBox and userSpaceOnUse units
- Gradients can be applied to both fill and stroke

Implementation:
- Added gradient parsing methods in SVGObject class
- Integrated with existing fpdf2 GradientPaint system
- Gradients stored in gradient_definitions dict by ID
- Applied via url(#gradientId) references in fill/stroke

Tests:
- 18 comprehensive test cases covering all gradient features
- All 287 SVG tests passing (18 new + 269 existing)
- Added 5 SVG test fixtures and 5 PDF reference files
- Added gradient support section to docs/SVG.md with examples
- Removed gradients from unsupported features list
- Added CHANGELOG entry for gradient support
- Fix logging f-string warnings - use % formatting instead
- Add spread_method parameter to GradientPaint constructors
- Refactor tests to plain functions, remove class wrapper
- Clean up test imports
- Add gradient SVGs to integration tests
- Convert _convert_gradient_coordinate, _parse_gradient_stops, and
  _extract_gradient_id to static methods (fixes R6301: no-self-use)
- Replace broad Exception catches with specific exception types
  (ValueError, AttributeError, TypeError) on lines 1024 and 1102
- Fix test_svg_image_with_unsupported_color to test HSL colors instead
  of gradients (which are now supported)
- Remove detailed Gradients subsection from SVG.md per reviewer request
- Create [2.8.6] section in CHANGELOG.md and move SVG gradient entry
@Ani07-05 Ani07-05 force-pushed the feature/svg-gradient-support branch from 4cbe4c8 to 36c0364 Compare November 11, 2025 13:55
@Ani07-05
Copy link
Author

hey @andersonhc All feedback addressed! Here's what I did:

  1. Fixed all pylint errors:

    • Converted _convert_gradient_coordinate(), _parse_gradient_stops(), and _extract_gradient_id() to static methods (fixes R6301: no-self-use)
    • Replaced broad Exception catches with specific exception types (ValueError, AttributeError, TypeError) (fixes W0718: broad-exception-caught)
  2. Fixed failing test:

    • Updated test_svg_image_with_unsupported_color to test actual unsupported colors (HSL format) instead of gradients, which are now supported
    • Test now passes successfully
  3. Updated documentation:

    • Removed the detailed "Gradients" subsection from SVG.md as you requested
    • Kept only the bullet point in the features list for consistency with other SVG features
  4. Updated CHANGELOG:

    • Rebased on latest master
    • Moved the SVG gradient entry to[2.8.6] section as requested

All tests passing, no pylint errors, and branch is now up to date with master.

@andersonhc
Copy link
Collaborator

@allcontributors please add @Ani07-05 for code

@allcontributors
Copy link

@andersonhc

I've put up a pull request to add @Ani07-05! 🎉

@andersonhc andersonhc merged commit 54b52ad into py-pdf:master Nov 13, 2025
23 checks passed
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.

Feature request: Add SVG LinearGradient and RadialGradient support

2 participants