Shaper\Arabic::shape() gives a transparent-joining character its own form from the two characters beside it:
if ($crntChar && isset($transparentJoin[hexdec($crntChar)])) {
// If next_char = RightJoining && prev_char = LeftJoining:
if (isset($chars[$i + 1]) && $chars[$i + 1] && isset(self::$rightJoining[hexdec($chars[$i + 1])]) && $prevChar && isset(self::$leftJoining[$prevChar])) {
$output[] = self::glyphs($crntChar, 1, ...); // <final> form
} else {
$output[] = self::glyphs($crntChar, 0, ...); // <isolated> form
}
src/Shaper/Arabic.php:187. The two halves of that && disagree about what a neighbour is.
$prevChar is the character joining sees behind $i, past any transparent-joining characters;
$chars[$i + 1] is whatever is written next, mark or not. So the first mark of a stack sees the
second mark as the character in front of it, a mark is in neither joining table, and the form falls
to isolated - while the same mark alone between the same two letters is given its final form.
Measured
Zawgyi-One is the one font in the bundle whose rtlSUB states positional forms for a
transparent-joining character: 006E7 and 006EB carry init, medi and fina, and 00654 carries
fina. BEH either side, the glyphs each run draws in logical order:
| run |
drawn |
| BEH + U+06E7 + BEH |
0FE91 006E8 0FE90 |
| BEH + U+06E7 + U+06E7 + BEH |
0FE91 **006E7** 006E8 0FE90 |
| BEH + U+06E7 + U+06EB + BEH |
0FE91 **006E7** 006ED 0FE90 |
006E8 is the form the font states; 006E7 is the nominal code point left behind when the isolated
form is asked for and the font states none. The mark nearest the first BEH is the one that loses its
substitution, and it loses it only because another mark follows it. The letters are unaffected -
0FE91 and 0FE90 throughout - so this is about the mark's own form and nothing else.
This bites at two marks, not four, so it is reachable with ordinary pointed Arabic rather than the
heavy pointing of #154.
Expected
Both marks in the stack sit between the same two letters and should be given the same form. The
character in front of a transparent one is the letter past the rest of the stack, the way the
character behind it already is.
$nextChar is that letter already: shape() walks the run backwards and a transparent character
continues without overwriting it, so it holds the last non-transparent character passed. Reading
it in place of $chars[$i + 1] makes the two halves of the && agree and draws 006E8 on all
three rows above, measured. It leaves no raw neighbour read in the joining code.
Shaper\Arabic::shape()gives a transparent-joining character its own form from the two characters beside it:src/Shaper/Arabic.php:187. The two halves of that&&disagree about what a neighbour is.$prevCharis the character joining sees behind$i, past any transparent-joining characters;$chars[$i + 1]is whatever is written next, mark or not. So the first mark of a stack sees thesecond mark as the character in front of it, a mark is in neither joining table, and the form falls
to isolated - while the same mark alone between the same two letters is given its final form.
Measured
Zawgyi-One is the one font in the bundle whose
rtlSUBstates positional forms for atransparent-joining character:
006E7and006EBcarry init, medi and fina, and00654carriesfina. BEH either side, the glyphs each run draws in logical order:
0FE91 006E8 0FE900FE91 **006E7** 006E8 0FE900FE91 **006E7** 006ED 0FE90006E8is the form the font states;006E7is the nominal code point left behind when the isolatedform is asked for and the font states none. The mark nearest the first BEH is the one that loses its
substitution, and it loses it only because another mark follows it. The letters are unaffected -
0FE91and0FE90throughout - so this is about the mark's own form and nothing else.This bites at two marks, not four, so it is reachable with ordinary pointed Arabic rather than the
heavy pointing of #154.
Expected
Both marks in the stack sit between the same two letters and should be given the same form. The
character in front of a transparent one is the letter past the rest of the stack, the way the
character behind it already is.
$nextCharis that letter already:shape()walks the run backwards and a transparent charactercontinues without overwriting it, so it holds the last non-transparent character passed. Readingit in place of
$chars[$i + 1]makes the two halves of the&&agree and draws006E8on allthree rows above, measured. It leaves no raw neighbour read in the joining code.