Skip to content

Nested <svg> width/height with decimal values are truncated to integers, mis-scaling and mis-positioning embedded content #134

Description

@Watremez

Description

When a nested <svg> element declares non-integer width/height (e.g. width="5.8"), the fractional part is silently dropped. The nested content is then scaled from the truncated value, so it renders smaller than expected and appears shifted towards the top-left of its intended box (the x/y position is parsed as Float and stays correct, only the scale is wrong).

My real-world case is a QR code with a logo embedded as a nested <svg> sized in QR-module units (width="5.8" on a 29-module grid): the logo renders off-center inside the QR.

Minimal reproduction

The tomato circle is sized to exactly fill the blue-stroked square:

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 10 10">
  <rect width="10" height="10" fill="#eeeeee"/>
  <rect x="2.1" y="2.1" width="5.8" height="5.8" fill="none" stroke="#0000ff" stroke-width="0.1"/>
  <svg x="2.1" y="2.1" width="5.8" height="5.8" viewBox="0 0 100 100">
    <circle cx="50" cy="50" r="48" fill="#ff6347"/>
  </svg>
</svg>

Expected: the circle fills the blue square (scale = 5.8/100) — this is how Safari, Chrome and librsvg render it.
Image

Actual with SwiftDraw: the circle is rendered at 5/100 scale (~86% of the expected size), anchored at the top-left of the square.
Image

Cause

DOM.Length is an integer type, and the parsed floating-point dimension is truncated when the DOM.SVG is built:

The truncated value then feeds the nested-svg transform in LayerTree.Builder.makeTransform (sx = width / viewBox.width5/320 instead of 5.8/320 in my real case).

Per the SVG spec, width/height are <length> values and may be fractional.

Environment

  • SwiftDraw 0.29.0 (also present on main at the time of writing)
  • Reproduced with swiftdrawcli repro.svg --format png and with SVGView(svg:) on iOS 26

Workaround

Replacing the nested <svg x y width height viewBox> with <g transform="translate(…) scale(…)"> avoids the truncation, since transform values are parsed as Float.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions