Skip to content

Fix bare-number/px unit inconsistency and coordinate system (2.0b1)#450

Open
deeplook wants to merge 6 commits intomainfrom
fix/issue-439-bare-number-px-consistency
Open

Fix bare-number/px unit inconsistency and coordinate system (2.0b1)#450
deeplook wants to merge 6 commits intomainfrom
fix/issue-439-bare-number-px-consistency

Conversation

@deeplook
Copy link
Copy Markdown
Owner

Fixes #439.

Summary

SVG spec §5.9.2 defines 1 user unit = 1 px = 0.75 pt (at 96 dpi). Previous
releases treated 1 user unit as 1 pt, causing font-size="13" and
font-size="13px" to render at different sizes, and all output to be 33%
larger than a browser would produce.

Changes

  • convertLength now returns user units (px) instead of ReportLab points.
  • New convertLengthToPt helper multiplies by PX_TO_PT = 0.75 for the few
    places that need absolute points (font sizes, canvas dimensions).
  • get_box parses viewBox as raw floats — never applies unit conversion
    (viewBox defines a unitless coordinate space, SVG spec §8.1).
  • renderSvg viewport scale = canvas_pts / viewBox_user_units, which
    naturally incorporates ×0.75 for all shape coordinates via the group
    transform.
  • SVGs with a viewBox but no explicit width/height now fall back to
    viewBox dimensions for the scale, fixing cropping of such files.
  • DEFAULT_FONT_SIZE restored to 12 (pt, the CSS default); em bases use
    DEFAULT_FONT_SIZE / PX_TO_PT = 16 user units.
  • Regression tests added for bare-number == px consistency at both the
    convertLength level and the rendered fontSize level.

⚠️ Breaking change

Output PDF sizes will differ from 1.x: any SVG whose dimensions are expressed
in user units or px will produce a document that is 75% of its previous
linear size (correct physical size per spec).

Migration — to restore 1.x dimensions:

drawing = svg2rlg("file.svg")
factor = 4 / 3   # 1 / 0.75
drawing.width  *= factor
drawing.height *= factor
drawing.scale(factor, factor)

See CHANGELOG.rst for full details.

Related issues filed

SVG user units and px are equivalent (1 user unit = 1 px per spec §5.9.2).
ReportLab works in points (1 pt = 1/72 in; 1 px = 0.75 pt at 96 dpi).

Previously, `convertLength` mixed user-unit and point semantics: bare
numbers were treated as points (1:1), while `px` was already scaled ×0.75.
This made `font-size="13"` and `font-size="13px"` produce different sizes,
and caused incorrect physical drawing dimensions.

The fix applies the px→pt conversion at the correct architectural level:

- `convertLength` now returns **user units** for all length types:
  - bare numbers → 1:1 (unchanged, already user units)
  - `px` → 1:1 (was ×0.75)
  - `pt` → ×(96/72) ≈ ×1.333
  - `pc` → ×16 (1 pc = 12 pt = 16 px)
  - `mm`/`cm`/`in` → toLength(x) / PX_TO_PT

- A new `convertLengthToPt` method (= `convertLength × PX_TO_PT`) is used
  for values that must be absolute points in ReportLab: font-size (which
  bypasses group transforms) and SVG element canvas dimensions.

- `get_box` parses `viewBox` as raw floats (unitless coordinate space per
  spec §8.1) rather than through `convertLength`.

- The viewport scale in `renderSvg` is now `canvas_pt / view_box_user_units`,
  which naturally incorporates the ×0.75 factor. This ensures path data
  coordinates (which bypass `convertLength`) and shape attributes are
  handled consistently by the same transform.

- `DEFAULT_FONT_SIZE` updated from 12 (pt) to 16 (user units = px), which
  equals the same 12 pt after px→pt conversion.

Results:
- `font-size="13"` and `font-size="13px"` both produce 9.75 pt ✓
- `font-size="13pt"` stays 13.0 pt ✓
- SVG drawings are physically correct: a 150×40 px SVG produces a
  112.5×30 pt Drawing ✓
- Flags and other path-heavy SVGs render without cropping ✓

Fixes #439, addresses #433.
- DEFAULT_FONT_SIZE is now 12 pt (CSS default) instead of 16 user units;
  em_base defaults use DEFAULT_FONT_SIZE / PX_TO_PT = 16 user units
- font-size defaults use "12pt" string so convertLength parses them correctly
- SVGs with no explicit width/height but a viewBox now fall back to viewBox
  dimensions for scale, matching the Drawing size set in render(); fixes
  cropping of files like fill-rule-nonzero.svg
- test_units_svg assertion updated: em is measured in user units (16), not pt
@mosc9575
Copy link
Copy Markdown
Contributor

mosc9575 commented Apr 29, 2026

I tried this changes and they solve the problem I had with the font size. The transformation from SVG to PDF works well.

But the new factor PX_TO_PT = 0.75 is not reverted in reportlab.

The function setFont of class SVGCanvas in reportlab.renderSVG looks like

  def setFont(self, font, fontSize):
      if self._font != font or self._fontSize != fontSize:
          self._font = font
          self._fontSize = fontSize
          style = self.style
          for k in TEXT_STYLES:
              if k in style:
                  del style[k]
          svgAttrs = self.fontHacks[font] if font in self.fontHacks else {}
          if isinstance(font,RLString):
              svgAttrs.update(iter(font.svgAttrs.items()))
          if svgAttrs:
              for k,v in svgAttrs.items():
                  a = 'font-'+k
                  if a in TEXT_STYLES:
                      style[a] = v
          if 'font-family' not in style:
              style['font-family'] = font
          style['font-size'] = '%spx' % fontSize

In the last line the point to pixel ratio is 1:1. This should be reported.

My initial example in #433 shows this behavior. The initial font size is 13px when calling svglib.svg2rlg and after calling svg.asString(format="svg") the font size shrinks to 9.75px which is odd. I know, that this is an issue in reportlab.

It is great the the one direction now works as expected.

Once again I want to thank you for your work.

@deeplook
Copy link
Copy Markdown
Owner Author

Re: font size shrinkage in SVG round-trip

This is a known limitation caused by a bug in ReportLab's renderSVG module. When ReportLab renders a drawing back to SVG, SVGCanvas.setFont writes font sizes with px units even though the value is already in points:

# reportlab/graphics/renderSVG.py
style['font-size'] = '%spx' % fontSize  # should be 'pt', not 'px'

Minimal reproduction with ReportLab 4.4.3:

from reportlab.graphics.shapes import Drawing, String
from reportlab.graphics import renderSVG

# A string at 9.75pt (equivalent to 13px at 0.75 scale)
d = Drawing(200, 100)
d.add(String(10, 50, "Hello", fontSize=9.75))

svg_out = renderSVG.drawToString(d)
print(svg_out.decode())
# Output contains: font-size: 9.75px
# But 9.75 is in points — should be font-size: 9.75pt (or font-size: 13px)

The fix in ReportLab would be to change the last line of SVGCanvas.setFont to:

style['font-size'] = '%spt' % fontSize

This is outside svglib's control. The svg2rlg direction (SVG→RLG) now works correctly — the round-trip shrinkage is a pre-existing ReportLab issue.

@mosc9575
Copy link
Copy Markdown
Contributor

@deeplook Thanks for you summary. I will send a mail to a reportlab author and link your comment to fix also this issue.

New test module tests/test_units.py covers the end-to-end Drawing
dimension and font-size behaviour for all SVG length unit types (bare
numbers, px, pt, mm, cm, in), making the 96 dpi → 72 dpi conversion
contract explicit and regression-proof.

README.rst gains a "Units and output sizes" section explaining the
px→pt factor, a reference table, font-size implications, and guidance
on which unit to use for web-only, print, and mixed workflows.
@claudep
Copy link
Copy Markdown
Collaborator

claudep commented May 1, 2026

@deeplook, I'm sorry, but I will clearly be less active on this module, so don't count on me for reviews in the future. Thanks a LOT for all you are doing 🏅

@deeplook
Copy link
Copy Markdown
Owner Author

deeplook commented May 1, 2026

@deeplook, I'm sorry, but I will clearly be less active on this module, so don't count on me for reviews in the future. Thanks a LOT for all you are doing 🏅

@claudep Thank you for your many contributions in the past! Wishing you all the best!

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.

Inconsistency between bare-number and px unit handling in convertLength

3 participants