Reach the merged mask of a shared Lookup with a Khmer font of its own (#243) - #264
Open
jakejackson1 wants to merge 2 commits into
Open
jakejackson1 wants to merge 2 commits into
jakejackson1 wants to merge 2 commits into
Conversation
…#243) Otl::lookupsForStage() takes a Lookup two features of one stage name once and merges their masks, which was the one part of #243 with no test behind it. The merge is only reachable through the Khmer basic forms, the single stage that holds masked and unmasked features together, and no font in tests/data/ttf had a Lookup under two of those tags. Khmer-SharedMask-Synthetic is a 14-glyph subset of Battambang Regular 8.002 - Danh Hong, SIL OFL 1.1 with no Reserved Font Name, fsType 0 - carrying a GSUB of four hand-built single substitutions: blwf and abvf share one, ccmp and blwf share another, pref and blwf share a third, and blwf names a fourth alone. The below-base and pre-base forms substituted in are the donor's own; only the features that name them are new. It keeps the donor's copyright and licence strings, and carries a name of its own so that a font called Battambang that is not Battambang cannot mislead. The rows pin both halves of the merge. Where a masked and an unmasked feature share a Lookup the mask is nothing, whichever of the two is named first, so it reaches a base consonant the reordering marked for neither. Where two masked features share one the mask is the union of their bits, so it reaches the pre-base form only pref marks and the below-base form only blwf marks - both in the same syllable from the one Lookup - and still reaches neither on a base consonant. The Lookup blwf names alone is the control. hb-shape 14.3.1 draws all of this the same way save the lone base consonant of the blwf and abvf Lookup: HarfBuzz gives abvf a mask of its own, while indicFeatureMasks() leaves abvf out although the reordering sets Indic::ABVF on exactly the glyphs it would cover. That is a gap of its own rather than anything this merge decides, and the test docblock says so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…b-shape runs that decide (#243) The opening paragraph and seven of the nine hb-shape transcripts repeated what the code under test and the data provider say four lines apart. What is left is the part neither of them can say: that a Khmer font is the only route to the merge, what HarfBuzz's Khmer plan does with the three Lookups that carry the argument, and why one row is mPDF's answer rather than HarfBuzz's. 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.
#243 de-duplicates the Lookups of one stage of the plan and, where two features of that stage name the
same Lookup, merges their masks. A mask of
0means "apply to every glyph", so0has to absorb: aLookup named by one masked and one unmasked feature must come out unmasked, and two masked features must
give the union of their bits.
That line was the one part of #243 with no test behind it, and the reason was the corpus.
indicFeatureMasks()masksrphf pref blwf half pstf cfar init, and the only call that puts masked andunmasked features in one stage is the Khmer basic forms —
locl ccmp pref blwf abvf pstf cfar, wherepref blwf pstf cfarare masked andlocl ccmp abvfare not. There was no Khmer font intests/data/ttfto build a fixture from, and the only Khmer font in the repo,packages/Khmer-OS/fonts/KhmerOS.ttf, is LGPL-2.1-or-later with no font exception.No
src/change: this is the test coverage for a fix that already landed.The donor
Battambang Regular, and the licence is why it rather than KhmerOS:
Copyright 2019 The Battambang Project Authors (https://github.com/danhhong/Battambang); the only occurrence of "Reserved Font Name" in thelicence is inside its own definitions clause, not in the notice. The bundled copy is byte-identical to
https://raw.githubusercontent.com/danhhong/Battambang/master/OFL.txt. So clause 3 does not constrainthe derived name; clauses 1 and 2 still require the notice to travel, and name IDs 0, 13 and 14 are
kept.
fsType0, Installable Embedding, preserved in the synthetic — worth stating because the fixtureis embedded in a PDF rather than only parsed.
useOTLrefuses the font before the GSUB read.khmr/DFLT withabvf blwf clig pref pstf.Danh Hong, andpackages/Khmer-OS/fonts/LICENSE.txtisCopyright 2005 Danh Hong / Open Forum of Cambodia. Samedesign lineage, without the licence problem.
The donor is not added to the repository and no
packages/entry is added — only the synthetic, asevery other synthetic here is done.
The synthetic
tests/data/ttf/Khmer-SharedMask-Synthetic.ttf, a 14-glyph subset with GSUB and GPOS dropped and fourType 1 lookups built with feaLib. Every substituted form is one of the donor's own below-base or
pre-base glyphs — no outline was invented, only the features that name them. Built with
TTFont(src, recalcTimestamp=False),head.modifiedstill the donor's, and the build isbyte-reproducible from the donor.
Each feature's first Lookup index is its own, so the fixture does not lean on #245's collision fix. The
nine provider rows state each Lookup twice — on a glyph it should reach and one it should not.
The rows are live
src/is unchanged, so "revertsrc/and watch it fail" does not apply. Instead the merge was brokendeliberately, two ways, and the two breaks fail on different rows:
Dropping the merge line, so the first writer's mask sticks — 3 of 9 fail:
A plain
$stage[$lu]['mask'] |= $mask;, the most plausible wrong line — 2 of 9 fail, the other pair:Between them both directions of the absorb and both bits of the union are pinned.
hb-shape 14.3.1, including the row where it disagrees
Nine of ten runs agree with mPDF. The union half has a proper oracle:
prefandblwfare in one stageof HarfBuzz's Khmer plan and HarfBuzz merges their masks with
|=itself.The disagreement is not the merge, and it is now #263.
abvfhas no entry inindicFeatureMasks(), so it is applied with mask0— yetShaper\Indic::initial_reordering_syllable()sets
FLAG(ABVF)on exactly the glyphs it should reach (src/Shaper/Indic.php:792). The bit is writtenand never read, so mPDF substitutes on a base consonant HarfBuzz leaves alone. Adding
'abvf' => Indic::FLAG(Indic::ABVF)makes mPDF agree on all ten runs. That is not done here; the rowasserts mPDF's behaviour as it stands and the docblock names the cause.
If #263 is fixed, the masked-first/unmasked-second row must go with it.
abvfis the only unmaskedtag following a masked one in the Khmer stage list, so that direction of the absorb becomes unreachable
in mPDF and the case can no longer fail. #263 says so.
One wrinkle, recorded in the docblock: the
ccmp+blwfrow is not evidence either way againstHarfBuzz, which pauses to reorder between
ccmpand the basic forms and so takes that Lookup twice,once unmasked, reaching the same glyph by a route mPDF does not take. Same glyph, different mechanism.
Fixtures
Four new masters for the new font, written by the four
composer *:updatescripts. No existing mastermoves.
otldumpconfirms the design (khmr/DFLT: blwf abvf ccmp pref, four Type 1 lookups);fontcacheshows all four features surviving the parser with their Lookup lists;shapingcorroboratesthe unit test independently — the
khmerrun1780 17D2 1781 17C1 => 17C1 E002 17D2 E003, and thefont's own repertoire run substitutes
1780and1781while leaving1782and1783alone, which isthe merged-to-
0Lookups reaching base consonants and the masked one not.Verified
Still unreached after this: a merged non-zero mask on a Type 8 reverse Lookup.
_applyGSUBrulesSingly()passes no masks, so it can only arrive by the Indic path, and no font here hasa shared reverse Lookup under two masked tags.
Refs #243. Does not close it — #243 is already closed.
🤖 Generated with Claude Code