Join a letter to the base before it however many marks are between them - #161
Merged
Merged
Conversation
jakejackson1
changed the base branch from
fix/142-syriac-alaph-transparent
to
gravitypdf
September 16, 2026 04:44
shape() found the character behind each position through a nested if that stepped over at most three transparent-joining characters, reaching $chars[$i - 4] at the furthest. A base carrying four or more marks therefore stopped joining to the letter after it: the walk landed on a mark, nothing in $leftJoining holds a mark, the form came out isolated, and the Syriac corpus states no isolated form, so the nominal code point was drawn instead. Nothing limits how many marks a base carries. $transparentJoin is the Unicode Transparent-Joining table together with GDEF's mark class, and both are open-ended. skipTransparent(), added for #142, is the same walk without the limit, so the lookback goes through it. Estrangelo Edessa, BETH either side, the vowels U+0730, U+0733, U+0735, U+0737 and U+0739 between them, the glyphs each run draws in logical order: run drawn before drawn after BETH + BETH 0E008 0E00A unchanged BETH + 3 vowels + BETH 0E008 00730 00733 00735 0E00A unchanged BETH + 4 vowels + BETH ... 00737 00712 ... 00737 0E00A BETH + 5 vowels + BETH ... 00739 00712 ... 00739 0E00A 0E00A is the final BETH, 00712 the nominal letter the document asked for. The forward direction was never capped and is untouched: shape() walks the run backwards and $nextChar is whatever non-transparent character it last passed, which a transparent one leaves alone however many of them follow. No fixture moved. The shaping golden masters run an unpointed Syriac string, so nothing in the corpus stacks marks deep enough to have caught this. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jakejackson1
force-pushed
the
fix/154-transparent-walk-uncapped
branch
from
September 16, 2026 04:46
79aac8d to
f42b6b6
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.
Fixes #154.
Stacks on #155, which added the
skipTransparent()helper this uses, and should merge after it.The base is
fix/142-syriac-alaph-transparent, so the diff here is the one commit; GitHub retargetsit when #155 goes in.
A walk that stopped after three
Three nested reads,
$chars[$i - 4]at the furthest. Four marks between two letters and the walklands on the first of them rather than on the base. A mark is in neither joining table, so
$formstays 0, the letter asks for its isolated form, and Estrangelo Edessa - like the rest of the Syriac
corpus - states none, so the nominal code point is what gets drawn.
Nothing bounds how many marks a base carries.
$transparentJoinis Unicode's Transparent-Joiningtable together with GDEF's mark class, and both are open-ended, so any fixed depth is a guess about
how much pointing a document uses.
skipTransparent()is the same walk with no limit, and thenested
ifbecomes two lines that go through it.Measured
Estrangelo Edessa, BETH either side, the vowels U+0730, U+0733, U+0735, U+0737 and U+0739 between
them, the glyphs each run draws in logical order.
0E00Ais the final BETH the font states;00712is the nominal letter left behind when no form is found.
0E008 0E00A0E008 00730 0E00A0E008 00730 00733 0E00A0E008 00730 00733 00735 0E00A0E008 ... 00737 007120E008 ... 00737 0E00A0E008 ... 00739 007120E008 ... 00739 0E00AThree marks was the last row that worked, which is the cap read back out of the rendering. Every row
now draws what the unpointed row draws, and no row that already worked moved.
The other direction was never capped
shape()walks the run backwards, so the character in front of$iis one the loop has alreadypassed, and
$nextCharis simply carried:A mark never overwrites it, so any number of them leave it holding the last letter seen - which is
what
skipTransparent()would return, obtained for free from the loop's own carry. That is why thelookback was hand-rolled in the first place: only the backward direction needed a walk, and the walk
it got was bounded. Both directions are now unbounded.
The test asserts the first letter's initial form beside the last letter's final form for that
reason. The first assertion passes on the base branch and the second does not, which is the pair
saying the marks are invisible from either side and that only one side was broken.
No fixture moved
The whole suite, the snapshot suite with imagick and ghostscript present, and the shaping golden
masters are all green with no fixture rewritten. That is not luck:
ShapingGoldenMaster's Syriac runis
[0x710, 0x712, 0x713, 0x715]- four letters and no mark - and its Arabic run carries a tatweelrather than a vowel, so nothing in the corpus stacks marks deep enough to have seen this. Which is
the reason the change was held out of #155 rather than folded into it.
The tests added go with the ones #155 put in
ArabicTest: four and five marks between two BETHsthrough the fake
rtlSUBtable, the same through Estrangelo Edessa, and a run whose last mark isdeclared by GDEF rather than by the Unicode table, since
$transparentJoinis the union of the twoand the walk has to count both.
🤖 Generated with Claude Code