Skip to content

A mark followed by another mark loses the form the font states for it #162

Description

@jakejackson1

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions