Skip to content

fix(svg,line): draw-on paints nothing at zero, and <text> gets a font - #394

Merged
LeadcodeDev merged 1 commit into
mainfrom
fix/svg-line-drawing
Sep 27, 2026
Merged

LeadcodeDev merged 1 commit into
mainfrom
fix/svg-line-drawing

Conversation

@LeadcodeDev

Copy link
Copy Markdown
Owner

Closes #374. Closes #376. Refs #388.

#376 — two faults in draw-on

A dot at draw_progress: 0. The paint and dash effect were built before progress was checked, and Skia renders a zero-length dash with a round cap as a dot. Reverting the guard reproduces it exactly: "got 124 lit pixels".

The drawing stroke did not match the finished mark. A leftover set_stroke_width(sw / scale_avg) cancelled the canvas scale that had already been applied, so the stroke stayed the same thickness whatever the node size while resvg's finished render scaled with it — red proof small=4px large=4px across a 4× size change. And stroke-linecap/stroke-linejoin were never read off the SVG, so the last drawing frame's butt cap jumped to the finished frame's round one — red proof drawing_left=10 finished_left=2, an 8 px gap, exactly half the 16 px stroke width.

#374 — <text> was never drawn, and said nothing

usvg::Options::default() ships an empty fontdb::Database. When text-to-path conversion finds no face, usvg drops the <text> node from the tree entirely — and a sibling <rect> renders fine, which is what masks the loss. Parse options now share a process-wide fontdb initialised once with the system fonts. No new dependency: usvg already re-exports fontdb and roxmltree.

The residual gap, stated rather than left to be found. usvg appends a generic serif fallback to its own query, so once any system font is loaded even an unknown font-family resolves — to the wrong face, but visibly, not invisibly. A scenario-declared custom font feeds Skia's FontMgr for the text component, which is a completely separate font engine from usvg's; plumbing one into the other would need a public accessor for "all bytes registered for family X" that does not exist.

What genuinely stays silent is a host with no usable font anywhere. For that, warn_on_unresolved_svg_text compares the <text> count in the source against the resolved count in the tree and warns once per distinct payload rather than once per frame:

Warning: svg: 1 of 1 <text> element(s) have no matching font face for their
font-family and will not be drawn. Declare the family in the scenario's `fonts`
list (a "google" source or a local `path`), or use a font already installed.

usvg does its own log::warn! for a family miss, but nothing here installs a log backend, so that trace is discarded — documented in the rule file rather than fixed, since installing a global logger for a two-line fact is a different change.

Verified independently, because this branch was never pushed

The agent that wrote this reported a full green run but did not commit or push — the work was sitting uncommitted in a worktree. So none of its claims were taken on trust: the diff was applied onto current main and the whole gate re-run here.

cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace (1626) all clean. Comment count still 0.

End to end against the release binary:

result
12 px line at draw_progress: 0.0 0 lit pixels
<svg> with <text font-family="Helvetica">SVG</text> 1962 dark pixels, where it painted none

My first attempt at that second repro used content instead of data and the validator caught it — "unknown attribute 'content' on 'svg' — it is silently ignored". Worth noting that the attribute checker did its job on my own mistake.

Written comment-free, per the codebase-wide rule from #345.

**A dot at `draw_progress: 0`.** `Line::paint` built its paint and dash effect
before checking progress, and Skia renders a zero-length dash with a round cap
as a dot. It now returns before constructing either.

**An svg's draw-on did not match its own finished mark.** Two faults in the
segment loop: a leftover `set_stroke_width(sw / scale_avg)` cancelled the canvas
scale that had already been applied, so the stroke stayed the same thickness at
any node size while resvg's finished render scaled with it; and `stroke-linecap`
and `stroke-linejoin` were never read off the SVG, so the last drawing frame's
butt cap jumped to the finished frame's round one.

**An svg's `<text>` was never drawn, with nothing said.** `usvg::Options::default()`
ships an *empty* `fontdb::Database`, and when text-to-path conversion finds no
face, usvg drops the `<text>` node from the tree entirely — a sibling `<rect>`
renders fine and masks the loss. The parse options now share a process-wide
`fontdb` initialised once with the system fonts.

usvg appends a generic serif fallback to its own query, so once any system font
is loaded even an unknown `font-family` resolves — to the wrong face, but
visibly. What stays invisible is a host with no usable font at all, and for that
`warn_on_unresolved_svg_text` compares the `<text>` count in the source against
the resolved count in the tree and says so once per distinct payload rather than
once per frame.

Measured end to end rather than on the tests' word: a 12px line at
`draw_progress: 0.0` lights 0 pixels, and an `<svg>` carrying
`<text font-family="Helvetica">SVG</text>` paints 1962 dark pixels where it
painted none.

Closes #374
Closes #376
@LeadcodeDev LeadcodeDev added the bug Something isn't working label Sep 27, 2026
@LeadcodeDev LeadcodeDev self-assigned this Sep 27, 2026
@LeadcodeDev
LeadcodeDev merged commit b5560c9 into main Sep 27, 2026
3 of 4 checks passed
LeadcodeDev added a commit that referenced this pull request Sep 27, 2026
#394 turned main red. Two tests written against a Mac asserted that Helvetica
resolves; CI's Ubuntu runner installs `libfontconfig1-dev` and
`libfreetype6-dev` — the font *libraries* — and no font *files* at all, so
nothing resolved and usvg dropped the `<text>` node exactly as it does for any
host with an empty fontdb.

That is the residual gap #374's own rule file documents, arriving as a red
build rather than as a render.

Both sides fixed, because each was wrong on its own:

The tests no longer name a family. They ask the shared fontdb for a family the
host actually has and skip when there is none, so they mean the same thing on a
laptop and in a container. A new test covers the other side directly: with an
empty fontdb the `<text>` count resolves to 0, which is the condition the
warning exists for.

CI gains `fonts-dejavu-core`. A renderer that draws text needs font files on
the host, and a runner that has none is not a realistic environment to prove
anything about text in.

I merged #394 while its `test` job was still pending. That is what let this
reach main.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

1 participant