Fall back from a v2 Indic tag to its own original tag only (#192) - #200
Merged
jakejackson1 merged 2 commits intoSep 17, 2026
Merged
Conversation
This was referenced Sep 16, 2026
OtlTags::script() walked the v2-to-original table from the run's own entry to the end, so a v2 tag the font did not offer reached the original tag of every script listed after it: Bengali was laid out with a font's deva lookups and the original specification's reordering, but Devanagari never reached beng. HarfBuzz asks for dev3, dev2 and deva for Devanagari (hb_ot_all_tags_from_script) and then DFLT, dflt and latn (hb_ot_layout_table_select_script), never another script's tag. The walk is now one lookup of the run's own original tag. NotoSansBengali-DevaScript-Synthetic makes it visible: a subset of Noto Sans Bengali with its GSUB replaced by a DFLT and a deva script, each with a 'locl' lookup giving KA a different glyph. KA drew deva's KHA; it now draws DFLT's GA, as hb-shape does. One existing fixture moves. NotoSansGurmukhiUI-GPOS71-Subset offers only gur2 and guru, so the golden master's Devanagari and Bengali runs used to reach guru and be reordered to the original specification. They now find no script and are left unshaped, as a run is in any font with no entry for its script. The font has no glyph for either run. Test font: NotoSansBengali-DevaScript-Synthetic (Noto Sans Bengali 3.011, OFL 1.1), built in fontTools 4.59.2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… what they cover (#192) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jakejackson1
force-pushed
the
refactor/160-otl-tenants
branch
from
September 17, 2026 01:46
37f1e92 to
a398165
Compare
jakejackson1
force-pushed
the
fix/192-indic-script-fallback
branch
from
September 17, 2026 01:47
a17f1fa to
47eabba
Compare
Member
Author
|
Rebased: #190 was squash-merged into |
Member
Author
|
This PR merged into a base branch that had already been squash-merged, so its change never reached |
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.
Closes #192
What
OtlTags::script()walked$originalIndicTagsfrom the run's own v2 tag to the end of the table. So a v2 tag the font doesn't offer reached the original tag of every script listed after it beforeDFLT/dflt/latn. Bengali text in a font offeringdevawas laid out with the Devanagari lookups and the original specification's reordering. Devanagari never reachedbeng, because the result depended on table order. The walk is now one lookup of the run's own original tag:The table's docblock no longer says order is load-bearing, because it isn't any more.
HarfBuzz
HarfBuzz 14.3.1 agrees with the issue.
hb_ot_all_tags_from_script()(src/hb-ot-tag.cc) lists only the script's own tags, newest first:hb_ot_layout_table_select_script()(src/hb-ot-layout.cc) tries those tags, then the defaults:So Bengali asks for
bng3,bng2,beng, thenDFLT,dflt,latn, and never another script's tag. mPDF has no*3(USE) tags, and adding them would widen this PR, so it stays out.Test font and shaping test
I checked every font in
tests/data/ttfandpackages/*/fonts. Several offer another Indic script's original tag without the run's own: Lohit-Kannada and Pothana2000 (deva), FreeSansBold and FreeSerifBold (deva,guru), the Myanmar bundle (mymr), andNotoSansGurmukhiUI-GPOS71-Subset. None of them has glyphs for the script that would fall through, so no page in the corpus can show the bug.NotoSansBengali-DevaScript-Synthetic.ttfmakes it visible. It's Noto Sans Bengali 3.011 (OFL 1.1), subset in fontTools 4.59.2 to space, U+25CC, KA, KHA, GA, SSA, I and VIRAMA, with GPOS dropped and name IDs 1, 4 and 6 renamed. Its GSUB is replaced by two scripts and no Bengali one:DFLT:loclgives KA (U+0995) the GA glyph (U+0997)deva:loclgives KA the KHA glyph (U+0996)IndicScriptFallbackTestrecords what's drawn for KA throughTextRecordingMpdf:hb-shape14.3.10996(deva)0997(DFLT)uni0997--script=devaforceduni0996OtlTagsTest's pinned case is replaced by a data provider with the issue's four rows, all nowDFLT(or''where the font has no default), plus a font offering bothbenganddeva, where Bengali still takesbeng.Fixtures
Regenerated from cold. I deleted
tests/Mpdf/tmp/mpdf,tmp/mpdfandtmp/ttfontdata, then rancomposer fontcache:update all,otldump:update all,shaping:update allandsubset:update all. Besides the new font's four fixtures, one existing fixture moved:tests/data/shaping/NotoSansGurmukhiUI-GPOS71-Subset.txt, in itsdevanagariandbengaliruns.That font offers only
gur2andguru.dev2andbng2used to fall through toguru, and the runs were reordered to the original specification (0915 094D 0937 093F→0915 094D 093F 0937). Now they find neither their own tag nor a default, so no script is chosen and they come back unshaped in logical order. That is what happens to any run in a font with no entry for its script. The font has no glyph for either run, so every glyph is.notdefeither way.HarfBuzz still reorders a run like that: with no script it runs the Indic shaper, to the original specification. mPDF's earlier output matched HarfBuzz here only because
guruhappened to be picked. The reverse also holds: underDFLT, HarfBuzz uses the default shaper and doesn't reorder, but mPDF does (the new font'sbengalirun is0997 09CD 09BF 09B7, andhb-shapegivesuni0997 uni09CD uni09B7 uni09BF). Which shaper runs for the chosen tag is a different decision from which tag is chosen, so I opened #199 for it rather than widening this PR.Mutation check
With the fallthrough walk put back and the caches cleared, 6 tests fail:
IndicScriptFallbackTest::testABengaliRunIsLaidOutWithTheDefaultScriptRatherThanDevanagariOtlTagsTest::testAV2TagFallsBackToItsOwnOriginalTagAndNoOtherScriptsOriginalTag, for "Bengali, where the font offers deva", "Devanagari, where the font offers gujr" and "Tamil, where the font offers only mymr"ShapingGoldenMasterTestforNotoSansBengali-DevaScript-SyntheticandNotoSansGurmukhiUI-GPOS71-SubsetMirroring upstream
Upstream still has the
switchinOtl::_getOTLscriptTag()(src/Otl.php:6079ondevelopment, insideif ($shaper) {), with a// fallthroughafter eachcase. For the mirror, eachcasereturns its own original tag or leaves the switch: replace every// fallthroughwithbreak;, for example:mym2is last and needs nobreak. Nothing else changes, and control falls on to the existingDFLT/dflt/latnchecks. The upstream PR also needs aCHANGELOG.mdentry. The test font,IndicScriptFallbackTest, and thescript()rows ofOtlTagsTest(as calls to_getOTLscriptTagthrough reflection) can be carried over.NotoSansGurmukhiUI-GPOS71-Subsetisn't upstream, so its fixture move doesn't apply there.Base
Stacked on #198 (
refactor/160-otl-tenants) → #190 → #188 → #186 →gravitypdf. Before pushing I checked all four: all still open, none merged, so this targetsrefactor/160-otl-tenantsat37f1e92without a rebase./simplify
Applied:
$original, instead of twice inside one longisset()OtlTagsTestprovider is renamedoriginalTagFallbacks, because its last row checks that a run's own original tag still wins when another script's tag is also offeredSkipped:
TextRecordingMpdf. About a dozen shaping tests already repeat that line, so it would mean editing tests this diff doesn't touch.DFLT/''rows are the issue's table, and together they cover a tag listed both before and after the run's own.Verification
vendor/bin/phpunit: 2065 tests, 5924 assertions, 1 skipped. The base was 2053 / 5904 / 1.vendor/bin/phpunit --group=snapshot: OK, 79 tests, 110 assertions.composer cs: clean.🤖 Generated with Claude Code