fix(pdf): lay a matrix-placed run out large and scale it down - #752
Merged
Conversation
andiwand
force-pushed
the
feat/pdf-dedupe-images
branch
2 times, most recently
from
August 27, 2026 21:26
0b4d9ba to
5846958
Compare
`const std::string &` over a braced list of `const char *` constructs a temporary per iteration, which gcc rejects under `-Werror=range-loop-construct`. Clang does not, so it reached main. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KiPhteiEmPp8iPQLBfnthA
andiwand
force-pushed
the
fix/pdf-matrix-run-font-size
branch
from
August 27, 2026 21:37
c4a9589 to
384d4d6
Compare
The matrix path laid a run out in its block's own space and let the CSS matrix scale it, so the CSS font size was the PDF's `Tf` — routinely `1`, the size living in `Tm`. A browser with a minimum font size (8px on android) clamped that up and the matrix multiplied the clamped size, which is how a page came out in giant overlapping type. Reproduced with `--blink-settings=minimumFontSize=8`. Blowing the local space up by a constant and dividing the matrix by it renders identically and puts the font size out of the clamp's reach. Constant rather than per run: runs in one block are compared against each other in that space. It also places such a run *more* accurately — a 1pt font is too small to carry the metrics the baseline is derived from, and the matrix multiplied that error too. Against a ghostscript raster of all 255 pages every file improves or holds: Rockrider 4.59 -> 2.39 and 4.68 -> 2.79 mean absolute difference, Laser 5.88 -> 5.30, none worse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0169dUoxqhqfi7ZU5ei8b9ov
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KiPhteiEmPp8iPQLBfnthA
andiwand
force-pushed
the
fix/pdf-matrix-run-font-size
branch
from
August 27, 2026 21:44
384d4d6 to
ee1dcb7
Compare
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.
🤖 Generated with Claude Code
Stacked on #751 → #750 → #749.
This is the bug the user actually reported. Text comes out in giant overlapping type on a phone.
Cause
The matrix path lays a run out in its block's own space and lets the CSS matrix scale it, so the CSS
font-sizeis the PDF'sTfsize — which InDesign and friends routinely write as1, putting the real size inTm:That emitted
.f2{font-size:1pt}(1.33px) withmatrix(10.1768,0,0,11.3076,0,0). A browser with a minimum font size clamps 1.33px up to its floor — 8px is the Android WebView default — and then the matrix multiplies the clamped size. 1.33 → 8 is a 6× boost, times the matrix.Reproduced exactly with
--blink-settings=minimumFontSize=8,minimumLogicalFontSize=8: the render is pixel-for-pixel the reported screenshot. It also explains why only some text broke — runs on the uniform branch carry their real size (.f1{font-size:14.7pt}) and were never clamped. In the report, "INHALT" is fine and the TOC under it is destroyed; that is exactly the uniform/matrix split.-webkit-text-size-adjust:nonedoes not help —minimumFontSizeis a separate Blink setting from the autosizer. Verified, byte-identical output.Fix
Blow the whole local space up by a constant (48) and divide the matrix by it.
font-size:1pt+matrix(10.1768,…)becomesfont-size:48pt+matrix(0.212017,…), same translate, same rendering, and the font size is now far out of the clamp's reach.Constant rather than per-run: runs in one block are compared against each other in that space, so a per-run factor would break block grouping.
It also renders better
A 1pt font is too small to carry the metrics the baseline is derived from, and the matrix multiplied that error too. Rasterising all 255 pages of the 11-file corpus and scoring against ghostscript, every file improves or holds — none regress:
Rockrider_..._GebrauchsinformationenRockrider_..._Allgemein_und_GarantieLaserentfernungsmesser_PLEM50_D5Sunfun_Melina_BBQ_PavillionFahrradtraeger_...(mean absolute difference vs the ghostscript raster, lower is better)
So the output does change — the old placement was wrong, and the reference output should be regenerated.
Two tests pin it: a
1 Tfrun with aTmscale emitsfont-size:48ptandmatrix(0.208333,0,0,0.416667,…), and a uniform run still states its own12pt.Full suite 1230 passed, 0 failed, 6 pre-existing skips.
Known residue, not in this PR
The uniform branch states the real size, so genuinely small text is still clamped — milder, since nothing multiplies it. Measured by rendering at
minimumFontSize0 / 8 / 24:FNIRSIstill shifts 4% of pixels andLaser0.2% — small text slightly enlarged, readable, no overlapNormstahlshifts 26% and does overlapFixing that means giving every run a transform, or scaling the whole text layer through one wrapper. Worth doing, but it is a much larger change than this one and deserves its own PR.