Repository navigation
fix(paint): draw raster text at the exact size with a sub-pixel pen - #408
Merged
Merged
Conversation
drawText built its face at the whole-pixel size and advanced the pen by whole-pixel advances, while layout (and the PDF painter) use the exact ones, so every word came out up to 2px short and the gaps between words looked uneven. It now draws with a face at the exact size (Font.NewFaceAt) and a float pen, placing each glyph at the nearest quarter pixel through opentype.Face.GlyphMaskIndexAt (go-opentype/opentype v0.21.0). Layout metrics keep the whole-pixel face, so line boxes do not move. Four goldens move by sub-pixel word placement only. Closes #407 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Refs #407 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This was referenced Nov 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The raster painter drew text on the whole-pixel grid (face at
int(size + 0.5), pen advanced by whole-pixel advances, start atint(it.X)) while layout and the PDF painter use the exact advances; PR #146 leftdrawTextthat way on purpose. Each word started in the right place but came out up to 2px short, so the gaps looked uneven and too wide (#407). Not a #405 regression: at the default weight the old output is byte-identical to v0.45.9.drawTextAt: float pen, advance = the glyph's font-unit advance at the exact size plus letter-spacing; each glyph drawn at the nearest quarter pixel withopentype.Face.GlyphMaskIndexAt(new in go-opentype/opentype v0.21.0, the lowest shared library; Face: a glyph mask at a fractional pen position go-opentype/opentype#66, docs: refresh bench corpus and fidelity notes after PR #63-66 #67).drawTextstays as an integer wrapper.Fonts.drawFace: face at the exact size (NewFaceAt) with the same weight instancing, cached by size. Layout metrics keep the whole-pixel face, so line boxes do not move.inline_decoration.png).Evidence
engine.TestRasterTextMatchesChromeWords: 30 words (Open Sans variable, lorem text) at 16, 22.4 and 30.8 px, weights 400 and 700, starts within 0.5 px of a Google Chrome headless screenshot of the same file, widths within 0.6 px (a width is the difference of two coverage-estimated edges, about a quarter pixel of noise each). Before the change the test fails with 20 reported differences (and one row with a word missing); after it, it passes. Observed differences are at most 0.31 px on starts.paint.TestDrawFaceExactSizeCache; paint stays at 100 percent.BenchmarkRenderInlineFontFace: 1659 -> 1990 allocs/op (+331), 6340993 -> 6450799 B/op (+1.7 percent). The cost is rasterising each glyph at up to four sub-pixel phases on first use (cached per face afterwards). Timings are deferred: the 1-minute load was 11.9, above 8 (a noisy 13.8 -> 14.3 ms/op is not evidence).Closes #407
Co-Authored-By: Claude Sonnet 5.5 noreply@anthropic.com
🤖 Generated with Claude Code