Add optional font hinting - #1116
Conversation
Hinting grid-fits glyph outlines so that stems align to whole pixels, which noticeably improves legibility of small text. It is off by default and has to be asked for through `Options::hinting`, because an SVG is resolution independent while hinting is not: outlines have to be fitted for one specific pixel grid, and usvg has to commit to one while flattening text into paths, long before the scale the tree is rendered at is known. The grid is derived from the font size, so hinted output lands on whole pixels for an unscaled render. Hinting only changes the outlines, never the glyph positions, so text occupies the same space either way. Elements asking for `text-rendering="geometricPrecision"` are left unhinted, since that property asks for the exact outlines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
This is something worth exploring, but I'm strongly against Also, I'm not sure about using the |
|
@RazrFalcon this again is an addition I added for byonk since I want to allow full control of resulting render from within svg and since hinting decisions can depend on font choice a general flag is a hammer way to big for beautifully rendered svgs with hinted font. |
|
|
The tests rendered through the `extra` path only because a hinted outline is fitted to the pixel grid of one size, and the regular path scales every test to 300px wide. Sizing the file to exactly that width makes the scale 1 anyway, so it can go back to being an ordinary reference image test, with the unhinted rendering falling out of `gen-tests.py` like any other. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
111041c to
dbd7091
Compare
|
Both points addressed. The custom properties are gone, along with the attribute name codegen change they needed, so the generated For the record on "if you can tweak hinting via CSS/SVG properties, do it that way": as far as I can tell there is no standard property for it. The tests are regular reference image tests now. They only went through |
|
One thing I would like to sound out before building it, since it is the part this design gives up. A document may reasonably want different hinting for different fonts: a pixel font and a variable font in the same file want opposite treatment, and with a single global setting one of them loses. Rather than bring back per element properties for that, the natural fit looks like a resolver alongside It would also land in a better place than a CSS property could. Hinting is applied per glyph, after fallback has been resolved, so a font keyed resolver naturally does the right thing for a document that falls back from a variable font to a bitmap font mid-run, which no property on the element could express. And it keeps the markup standard: the SVG already says which font each element uses, so designers get per element behaviour through Would you be open to that, as a follow-up once this lands? Happy to leave it out entirely if you would rather keep the surface at |
|
Let's rename As for tweaking hinting via |
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Renames `--hinting` to `--font-hinting` in both CLIs and the `Hinting*` types to `FontHinting*`, matching `Options::font_hinting`. The `resvg` CLI was also missing a help entry for the flag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
done |
|
Renamed. The The |
Adds font hinting on top of skrifa's
HintingInstance, which grid-fits glyph outlines sothat stems align to whole pixels. It noticeably improves legibility of small text.
Opt-in, and why
Hinting is off by default and has to be asked for through
Options::font_hinting. An SVG isresolution independent while hinting is not: outlines have to be fitted for one specific
pixel grid, and usvg has to commit to one while flattening text into paths, long before
the scale the tree is eventually rendered at is known. The grid is taken from the font
size in user units, so hinted output lands on whole pixels for an unscaled render, or at
an integer zoom factor.
Being opt-in also means no existing reference image changes.
Two further properties worth stating up front:
shaping. Text occupies the same space whether it is hinted or not.
text-rendering="geometricPrecision"is never hinted, as that property asks for exactoutlines. That is the one per element control here, and it is a spec property rather
than anything new. It is covered by a line in the test SVG that stays byte-identical
across every hinted variant while the rest of the image changes.
API
Options::font_hinting: Option<FontHintingOptions>, whereFontHintingOptionscarries an engine and atarget. These mirror what skrifa exposes, wrapped rather than re-exported so that skrifa
does not become part of usvg's public API:
FontHintingEngineInterpreter,Auto,AutoFallback(default)FontHintingTargetMono,Smooth { mode, symmetric_rendering, preserve_linear_metrics }(default)FontHintingSmoothModeNormal(default),Light,Lcd,VerticalLcdBoth CLIs gained a
--font-hintingflag that enables the defaults.Outlines are drawn at the fitted size, which yields pixels rather than font units, so they
are scaled back to font units and the existing per-glyph transform then applies unchanged.
The outline cache is keyed by the configuration and the grid.
Tests
tests/text/hinting/sizes.svgis an ordinary reference image test: the unhinted renderingcomes out of
gen-tests.pylike any other, and three further tests render the same filewith a different configuration each. The file is 300px wide, which is what the harness
scales tests to, so it renders unscaled and the hinted outlines land on the grid they were
fitted for. All four outputs are pairwise distinct.
Worth noting for review:
FontHintingTarget::Monomakes no difference under the TrueTypeinterpreter, which is why the mono test pairs it with the automatic hinter. That matches
FreeType's v40 interpreter behaviour and is called out in the docs for
Mono, rather thanpretending the knob always does something.