Always push wght variation coordinates - #1099
Conversation
…apping logic Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Since I stumbled upon this problem while using svg2pdf and then found krilla for that matter, I was not aware that resvg has its own in-house svg -> png conversion CLI tool and made an unnecessary round-trip around pdf conversion for verifying this fix. I redid the test with the same svg file this time with resvg's own CLI and the results are the same as shown below.
|
|
The bug seems real, but we are currently in the process of porting resvg to skrifa (see #922), since that PR has been rebased too often already, we will likely wait until that has been merged before merging this. Hope you understand! |
|
The "Avoiding an outline-cache regression" section has become obsolete since the caching logic has changed through the |
font-weight to the wght axis even at 400 (variable fonts whose default instance isn't Regular render the wrong weight)weight variation coordinates
weight variation coordinateswght variation coordinates
|
Yep I think it should be fine now. Thank you! |


Summary
Variable font support introduced in #997 skips the
font-weightCSS property -> OpenTypewghtvariation axis mapping whenever the weight is 400, on the assumption that a variable font's default instance is always Regular (wght= 400). Incrates/usvg/src/parser/text.rs'sconvert_font:The problem is that this assumption doesn't hold for every variable font. Google's Noto CJK variable fonts, for example, default to Thin. In such case,
font-weight: 400or unstyled text render at the font's Thin instance instead of Regular, while every other weight value (100, 200, 300, 500...) is mapped and renders correctly.This PR fixes this problem by dropping the
weight != 400condition, keeping the existing!has_wghtcheck sofont-variation-settings: "wght" ...still takes precedence. It also adds a font-levelhas_variation_axescheck so this doesn't regress the glyph-outline cache for static fonts.Details
Why the mapping should be unconditional
CSS Fonts Module Level 4 doesn't carve out an exception for weight 400. The property-to-axis mapping table (https://www.w3.org/TR/css-fonts-4/#font-variation-settings-def) in the
font-variation-settingssection is unconditional:And the normative precedence algorithm (https://www.w3.org/TR/css-fonts-4/#feature-variation-precedence) confirms the intended layering:
font-weightalways applies at step 2, and an explicitfont-variation-settingsonly overrides it because it's applied later, at step 12, not becausefont-weightabstains at any particular value:Avoiding an outline-cache regression
The glyph-outlining path in
crates/usvg/src/text/flatten.rsuses!span.variations.is_empty()to decide whether it's appropriate to use the non-variable(font ID, glyph ID)outline cache. Simply dropping theweight != 400guard meansspan.variationsis non-empty for every span by default, not just for non-400 weights, since the default CSS weight is 400. Left alone, this change would make every span bypass that cache, including plain static fonts like Arial that have nofvartable and get no benefit from the variation machinery.To avoid that, this PR adds a second, font-level check,
has_variation_axes, memoized per font ID the same way the existinghas_opsz_axisalready is:and gates the cache bypass on it:
Static (non-variable) fonts now go right back through the ordinary outline path, same as before this change existed. This mirrors
has_opsz_axis's existing logic.Verification
Rendered with a small custom CLI wrapper built on krilla, a SVG-to-PDF library which depends on
usvg, recommended as the successor tosvg2pdf(a separate, now-unmaintained crate).svg2pdfitself couldn't be used to check this, since it's pinned to ausvgversion before variable font support. The wrapper's--text-to-pathsflag (mirroringsvg2pdf's own CLI) makes the PDF consumeusvg's flattened glyph outlines directly instead of embedding fonts, which is the only way to visually exercise the exactflatten.rscode this PR touches; the default embedded-font mode instead confirms the CSS weight ->wghtmapping intext.rs(shared by both paths) is picked up correctly on the embedding/shaping side.Tested with a ladder SVG containing
font-weight100 through 900, a line with nofont-weightattribute (implicit 400), and a line usingfont-variation-settings: 'wght' 700, all set in Noto Sans JP, and visually inspected the output in both modes.Before the fix, weight 400 and unstyled text were rendered as weight 100 (Thin); every other weight already worked. After the fix, those two instances are rendered correctly as intended. Also checked a static-font case (Arial at 400/700/default) to confirm no visual change there.
Test coverage
I haven't added an automated reftest yet. The repo's variable-font test asset,
RobotoFlex.subset.ttf, defaults towght400, so it can't reproduce this specific bug (only fonts whosefvardefault isn't 400 can). Reproducing it in an automated test would need a variable font with a non-400 default instance, which isn't currently in the test-fonts set. Happy to add a subsettedNotoSansJP-VF.ttf(OFL-licensed, same as the other Noto fonts already bundled) if that's an acceptable way to cover this, or to follow whatever approach you'd prefer.