diff --git a/src/Shaper/Arabic.php b/src/Shaper/Arabic.php index f2264e9d9..706b10a62 100644 --- a/src/Shaper/Arabic.php +++ b/src/Shaper/Arabic.php @@ -174,27 +174,14 @@ public static function shape(&$info, $arabGlyphs, $glyphClassMarks, $usetags, $s $chars[] = $info[$i]['hex']; } - $crntChar = null; - $prevChar = null; $nextChar = null; $output = []; $max = count($chars); for ($i = $max - 1; $i >= 0; $i--) { $crntChar = $chars[$i]; - if ($i > 0) { - $prevChar = hexdec($chars[$i - 1]); - } else { - $prevChar = null; - } - if ($prevChar && isset($transparentJoin[$prevChar]) && isset($chars[$i - 2])) { - $prevChar = hexdec($chars[$i - 2]); - if ($prevChar && isset($transparentJoin[$prevChar]) && isset($chars[$i - 3])) { - $prevChar = hexdec($chars[$i - 3]); - if ($prevChar && isset($transparentJoin[$prevChar]) && isset($chars[$i - 4])) { - $prevChar = hexdec($chars[$i - 4]); - } - } - } + // joining sees the base a mark is written on, however many marks the base carries + $n = self::skipTransparent($chars, $i, -1, $transparentJoin); + $prevChar = isset($chars[$n]) ? hexdec($chars[$n]) : null; 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])) { diff --git a/tests/Mpdf/Shaper/ArabicTest.php b/tests/Mpdf/Shaper/ArabicTest.php index b64f38d54..b8f0f1ac3 100644 --- a/tests/Mpdf/Shaper/ArabicTest.php +++ b/tests/Mpdf/Shaper/ArabicTest.php @@ -35,6 +35,9 @@ class ArabicTest extends \Yoast\PHPUnitPolyfills\TestCases\TestCase /** U+073A SYRIAC HBASA ABOVE */ const HBASA = '0073A'; + /** U+073B SYRIAC HBASA BELOW */ + const HBASA_BELOW = '0073B'; + /** U+0712 SYRIAC LETTER BETH, dual-joining */ const BETH = '00712'; @@ -117,6 +120,40 @@ public function testAMarkBetweenTwoLettersDoesNotBreakTheirJoin() $this->assertSame([['B_INIT', 2], [self::FATHA, 0], ['B_FINA', 1]], $forms); } + /** + * Nothing limits how many marks a base carries, and the lookback stepped over at most three of + * them: a fourth left the following letter reading a mark as the character behind it, and a mark + * joins nothing. GravityPDF/mpdf#154. + * + * The first letter is asserted alongside the last because the two directions are found + * differently - forwards is the last letter the backwards walk over the run passed, which never + * had a limit - and it is the pair that says the marks are invisible to joining from either side. + */ + public function testALetterJoinsToTheBaseBeforeItHoweverManyMarksAreBetweenThem() + { + $four = [self::BETH, self::PTHAHA, self::ZQAPHA, self::RBASA, self::HBASA, self::BETH]; + $five = [self::BETH, self::PTHAHA, self::ZQAPHA, self::RBASA, self::HBASA, self::HBASA_BELOW, self::BETH]; + + $forms = $this->shape($four, self::ALL_FORMS, 'syrc'); + + $this->assertSame(['BE_INIT', 2], $forms[0]); + $this->assertSame(['BE_FINA', 1], $forms[5]); + $this->assertSame(['BE_FINA', 1], $this->shape($five, self::ALL_FORMS, 'syrc')[6]); + } + + /** + * The marks counted are the Transparent-Joining table together with GDEF's mark class, so a mark + * only the font declares takes up a place in the walk like any other. + */ + public function testAMarkOnlyGdefDeclaresIsSteppedOverWithTheRest() + { + $run = [self::BETH, self::PTHAHA, self::ZQAPHA, self::RBASA, self::COMBINING_GRAVE, self::BETH]; + + $forms = $this->shape($run, self::ALL_FORMS, 'syrc', self::COMBINING_GRAVE); + + $this->assertSame(['BE_FINA', 1], $forms[5]); + } + /** * The four form features can be switched off through OTLtags, in which case the character is left * as it came in rather than substituted @@ -300,6 +337,21 @@ public function testAPointedWordDrawsTheSameAlaphAsTheUnpointedWord() } } + /** + * Through a real font: the marks are drawn where they were written and the letters either side + * read as though they were not there, so a heavily pointed word has to draw the same two BETHs as + * the unpointed one. Estrangelo Edessa states no isolated BETH either, so the second letter losing + * its final form left the nominal U+0712 behind. + */ + public function testAHeavilyPointedWordDrawsTheSameLettersAsTheUnpointedWord() + { + $unpointed = $this->render([self::BETH, self::BETH]); + $pointed = $this->render([self::BETH, self::PTHAHA, self::ZQAPHA, self::RBASA, self::HBASA, self::BETH]); + + $this->assertSame($unpointed[0], $pointed[0]); + $this->assertSame($unpointed[1], $pointed[5]); + } + /** * The same character through a real font, which is where the wrong entry showed: Estrangelo Edessa * carries a BETH for each of the four forms, and the form the shaper asks for is the glyph that