Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 6 additions & 25 deletions src/TTFontFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -2412,11 +2412,6 @@ function _getGSUBarray(array $Lookup, $lul, $scripttag)
{
$volt = [];

// A Type 5 Format 1 or 2 rule reads these without setting them, so hands whatever the last
// chained rule left in them to the Arabic shaper (#189).
$backtrackGlyphs = [];
$lookaheadGlyphs = [];

foreach ($lul as $i => $tag) {
$this->reportGSUBlookupStart($Lookup, $i, $tag);

Expand Down Expand Up @@ -2471,9 +2466,7 @@ function _getGSUBarray(array $Lookup, $lul, $scripttag)
[],
$inputGlyphs,
[],
count($inputGlyphs),
['', '', ''],
[$backtrackGlyphs, $lookaheadGlyphs]
count($inputGlyphs)
)));
}
}
Expand All @@ -2494,8 +2487,7 @@ function _getGSUBarray(array $Lookup, $lul, $scripttag)
$this->classInputGlyphs($subtable['InputClasses'], $inputClass, $rule),
[],
$rule['InputGlyphCount'],
$class0excl,
[$backtrackGlyphs, $lookaheadGlyphs]
$class0excl
)));
}
}
Expand All @@ -2517,15 +2509,12 @@ function _getGSUBarray(array $Lookup, $lul, $scripttag)
$inputGlyphs[0] = $firstInputGlyph;
ksort($inputGlyphs);

$backtrackGlyphs = $rule['BacktrackGlyphCount'] ? $rule['BacktrackGlyphs'] : [];
$lookaheadGlyphs = $rule['LookaheadGlyphCount'] ? $rule['LookaheadGlyphs'] : [];

$this->addTo($volt, $this->gsubContextRule($Lookup, $i, $c, $tag, $scripttag, $ignore, $this->contextRule(
$rctr,
$rule['SubstLookupRecord'],
$backtrackGlyphs,
$rule['BacktrackGlyphCount'] ? $rule['BacktrackGlyphs'] : [],
$inputGlyphs,
$lookaheadGlyphs,
$rule['LookaheadGlyphCount'] ? $rule['LookaheadGlyphs'] : [],
count($inputGlyphs)
)));
}
Expand Down Expand Up @@ -2590,17 +2579,11 @@ function _getGSUBarray(array $Lookup, $lul, $scripttag)
* @param string[] $class0excl For a class-based rule, every glyph some class of the input,
* backtrack and lookahead Class Definitions names, which is what each
* one's class 0 excludes
* @param array $arabic [backtrack, lookahead] for the entry an Arabic joining form's rule
* becomes, where that is not the rule's own: see _getGSUBarray()
*
* @return array
*/
private function contextRule($index, array $records, array $backtrack, array $input, array $lookahead, $nInput, array $class0excl = ['', '', ''], $arabic = null)
private function contextRule($index, array $records, array $backtrack, array $input, array $lookahead, $nInput, array $class0excl = ['', '', ''])
{
if ($arabic === null) {
$arabic = [$backtrack, $lookahead];
}

return [
'index' => $index,
'records' => $records,
Expand All @@ -2609,8 +2592,6 @@ private function contextRule($index, array $records, array $backtrack, array $in
'lookahead' => $lookahead,
'nInput' => $nInput,
'class0excl' => $class0excl,
'prel' => $arabic[0],
'postl' => $arabic[1],
];
}

Expand Down Expand Up @@ -2767,7 +2748,7 @@ protected function gsubContextRule(array $Lookup, $i, $c, $tag, $scripttag, $ign
}

if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') {
$volt[] = ['match' => $lookupGlyphs[0], 'replace' => implode(" ", $luss['substitute']), 'tag' => $tag, 'prel' => $rule['prel'], 'postl' => $rule['postl'], 'ignore' => $ignore];
$volt[] = ['match' => $lookupGlyphs[0], 'replace' => implode(" ", $luss['substitute']), 'tag' => $tag, 'prel' => $rule['backtrack'], 'postl' => $rule['lookahead'], 'ignore' => $ignore];
} else {
$subRule['rules'][] = ['type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],];
}
Expand Down
81 changes: 81 additions & 0 deletions tests/Mpdf/ContextRuleArabicFormTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace Mpdf;

/**
* A plain context rule (GSUB Type 5) of an Arabic joining form has no backtrack or lookahead, so the
* shaper gives the form wherever the joining calls for it, whatever chained rule came before (#189).
*
* No font in the corpus has such a rule, so NotoSansArabic-GSUB5Form-Synthetic is
* NotoSansArabic-Joining-Subset (Noto Sans Arabic 2.012, OFL 1.1) with its GSUB replaced by three
* lookups for beh, in this order: a 'fina' Type 6 Format 3 giving the dotless final form after beh and
* before low alef, an 'init' Type 5 Format 1 giving the dotless initial form, and a 'medi' Type 5
* Format 2 giving the dotless medial form. `hb-shape` draws the glyphs these tests expect.
*/
class ContextRuleArabicFormTest extends \Yoast\PHPUnitPolyfills\TestCases\TestCase
{

const BEH = 0x0628;

const LOW_ALEF = 0x08AD;

/** The dotless forms have no codepoint, so they are mapped into the Private Use Area */
const DOTLESS_BEH_FINAL = 0xE002;

const DOTLESS_BEH_MEDIAL = 0xE003;

const DOTLESS_BEH_INITIAL = 0xE004;

/**
* @param int[] $codepoints
*
* @return int[] the codepoints of the line as it is handed to the drawing code, in visual order
*/
private function drawn($codepoints)
{
$html = '';
foreach ($codepoints as $codepoint) {
$html .= sprintf('&#x%04X;', $codepoint);
}

$mpdf = new TextRecordingMpdf([
'mode' => 'utf-8',
'fontDir' => [__DIR__ . '/../data/ttf'],
'fontdata' => ['notosansarabicgsub5formsynthetic' => [
'R' => 'NotoSansArabic-GSUB5Form-Synthetic.ttf',
'useOTL' => 0xFF,
]],
'default_font' => 'notosansarabicgsub5formsynthetic',
]);
$mpdf->WriteHTML('<p>' . $html . '</p>');

return array_values(unpack('N*', mb_convert_encoding($mpdf->drawnText[0], 'UTF-32BE', 'UTF-8')));
}

public function dataRuns()
{
return [
'an initial form from Format 1, with nothing before it' => [
[self::BEH, self::BEH],
[self::BEH, self::DOTLESS_BEH_INITIAL],
],
'an initial form from Format 1, before the chained rule\'s final form' => [
[self::BEH, self::BEH, self::LOW_ALEF],
[self::LOW_ALEF, self::DOTLESS_BEH_FINAL, self::DOTLESS_BEH_INITIAL],
],
'a medial form from Format 2, with no low alef after it' => [
[self::BEH, self::BEH, self::BEH, self::LOW_ALEF],
[self::LOW_ALEF, self::DOTLESS_BEH_FINAL, self::DOTLESS_BEH_MEDIAL, self::DOTLESS_BEH_INITIAL],
],
];
}

/**
* @dataProvider dataRuns
*/
public function testTheFormNeedsNoContext($codepoints, $expected)
{
$this->assertSame($expected, $this->drawn($codepoints));
}

}
8 changes: 4 additions & 4 deletions tests/Mpdf/Fonts/GsubArrayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ public function testTheDumpReportsEachClassRuleWithItsOwnPositions()
}

/**
* A Type 5 rule has no backtrack or lookahead, yet where it belongs to an Arabic joining form the
* parser hands the shaper the sequences of the chained rule it read last (#189).
* A Type 5 rule has no backtrack or lookahead, so where it belongs to an Arabic joining form the
* shaper gets none, not those of the chained rule read before it (#189).
*/
public function testAPlainContextRuleOfAnArabicFormCarriesTheLastChainedRulesSequences()
public function testAPlainContextRuleOfAnArabicFormHasNoBacktrackOrLookahead()
{
$parser = $this->withGdef(new TTFontFile($this->cache(), 'win'));

Expand All @@ -78,7 +78,7 @@ public function testAPlainContextRuleOfAnArabicFormCarriesTheLastChainedRulesSeq

$volt = $parser->_getGSUBarray($Lookup, [0 => 'init', 2 => 'init'], 'arab');

$this->assertSame(['match' => '00041', 'replace' => '00044', 'tag' => 'init', 'prel' => ['00043'], 'postl' => [], 'ignore' => '()'], end($volt));
$this->assertSame(['match' => '00041', 'replace' => '00044', 'tag' => 'init', 'prel' => [], 'postl' => [], 'ignore' => '()'], end($volt));
}

private function chainedClassRules()
Expand Down
Loading
Loading