FOP-3328 Read Anchor Table format 3 device tables from the anchor table offset - #106
Open
plutext wants to merge 1 commit into
Open
FOP-3328 Read Anchor Table format 3 device tables from the anchor table offset#106plutext wants to merge 1 commit into
plutext wants to merge 1 commit into
Conversation
…le offset
readPosAnchor saved the reader's position on entry as cp, then passed cp as the
base offset for the anchor's device tables. Per the OpenType spec, an Anchor
Table format 3 measures its xDeviceOffset and yDeviceOffset from the beginning
of the anchor table, so the base must be anchorTableOffset. As written, the
reader seeks into an unrelated part of the font and parses whatever bytes it
finds there as a device table.
Anchor device tables are rare in non-variable fonts, which is why this has gone
unnoticed. In a variable font they are VariationIndex tables and are everywhere,
so the bad path is taken constantly: loading NotoSans[wght].ttf through
FontInfoFinder logs 22,700 device table reads for that one font, thousands of
them rejected as garbage ("invalid device table delta count: -101"). With the
base corrected the same tables are recognised as VariationIndex (delta format
0x8000) and ignored by the existing unsupported-delta-format branch.
Also reject a delta count of zero in readPosDeviceTable. n == 0, ie
es == ss - 1, passed the existing n < 0 guard and then tripped DeviceTable's
"startSize <= endSize" assertion, so with assertions enabled a misparsed device
table became an AssertionError that aborted font loading rather than a bad read
that could be ignored. A device table with no deltas is useless in any case.
Reported downstream as plutext/docx4j#686, where the
identical code, repackaged, threw that AssertionError on Fedora's
google-noto-vf fonts.
Verified in that downstream fork: the five Noto variable fonts which previously
failed now load, and font discovery over a machine with 1242 system fonts
produces byte-identical results before and after, so ordinary fonts are
unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Fixes FOP-3328.
There are two defects in
OTFAdvancedTypographicTableReader, one of which makes FOP misparse the GPOS table of any variable font.1. Anchor device table offsets use the wrong base (root cause)
readPosAnchor(long anchorTableOffset)saves the reader's position on entry:Per the OpenType spec, an Anchor Table format 3's
xDeviceOffset/yDeviceOffsetare measured "from beginning of Anchor table" — so the base must beanchorTableOffset, notcp. As written, the reader seeks into an unrelated part of the font and parses whatever bytes it finds as a device table.This has gone unnoticed because anchor device tables are rare in non-variable fonts. In a variable font they are VariationIndex tables and are everywhere, so the bad path is taken constantly.
2. A delta count of zero slips past the guard
readPosDeviceTablerejects a negative delta count:but
n == 0(iees == ss - 1) passes, and then tripsGlyphPositioningTable.DeviceTable'sassert startSize <= endSize. So with assertions enabled the misparse in (1) turns from a silently-ignored bad read into anAssertionErrorthat aborts font loading.Reproducing
Load
NotoSans[wght].ttf— Fedora'sgoogle-noto-sans-vf-fontspackage, also available from Google Fonts — throughorg.apache.fop.fonts.autodetect.FontInfoFinder, withorg.apache.fop.complexscriptsat DEBUG. FOP 2.11 logs 22,700 device table reads for that one font, thousands of them rejected as garbage:Those counts are nonsense because the offsets are wrong. With the base corrected, the same tables are recognised as VariationIndex (delta format
0x8000) and are cleanly ignored by the existing "unsupported device table delta format" branch.To be precise about what I did and did not reproduce: on these particular fonts, stock FOP 2.11 has every bad read caught by the
n < 0guard, so noAssertionErroris thrown — the garbage reads are the observable symptom. The identical code in a downstream fork (docx4j, which repackages this class) does hitn == 0on the same font file and throws. Originally reported there as docx4j#686, by a user on Fedora with-eaenabled:(package names differ in the fork; the code at those lines is yours, unmodified)
Whether a given font trips the assertion or merely reads garbage depends on what happens to be at the wrong offset, so I would treat (1) as the defect to fix and (2) as hardening.
Effect
-ea:AssertionErrorduring font loading. In the fork's case this aborted discovery of every remaining font, not just the offending one.-ea: any bogus device table that survives the guard feeds wrong adjustments into glyph positioning.Verification
I verified in the downstream fork that the fix does not disturb ordinary fonts: font discovery over a machine with 1242 system fonts produces byte-identical results before and after, and the five Noto variable fonts that previously failed now load.
Contribution is offered under the Apache License 2.0. Co-Authored-By: Claude Opus 5 (1M context)