diff --git a/src/TTFontFile.php b/src/TTFontFile.php
index 9b9444b78..957316dcc 100644
--- a/src/TTFontFile.php
+++ b/src/TTFontFile.php
@@ -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);
@@ -2471,9 +2466,7 @@ function _getGSUBarray(array $Lookup, $lul, $scripttag)
[],
$inputGlyphs,
[],
- count($inputGlyphs),
- ['', '', ''],
- [$backtrackGlyphs, $lookaheadGlyphs]
+ count($inputGlyphs)
)));
}
}
@@ -2494,8 +2487,7 @@ function _getGSUBarray(array $Lookup, $lul, $scripttag)
$this->classInputGlyphs($subtable['InputClasses'], $inputClass, $rule),
[],
$rule['InputGlyphCount'],
- $class0excl,
- [$backtrackGlyphs, $lookaheadGlyphs]
+ $class0excl
)));
}
}
@@ -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)
)));
}
@@ -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,
@@ -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],
];
}
@@ -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],];
}
diff --git a/tests/Mpdf/ContextRuleArabicFormTest.php b/tests/Mpdf/ContextRuleArabicFormTest.php
new file mode 100644
index 000000000..0652e4ff0
--- /dev/null
+++ b/tests/Mpdf/ContextRuleArabicFormTest.php
@@ -0,0 +1,81 @@
+ 'utf-8',
+ 'fontDir' => [__DIR__ . '/../data/ttf'],
+ 'fontdata' => ['notosansarabicgsub5formsynthetic' => [
+ 'R' => 'NotoSansArabic-GSUB5Form-Synthetic.ttf',
+ 'useOTL' => 0xFF,
+ ]],
+ 'default_font' => 'notosansarabicgsub5formsynthetic',
+ ]);
+ $mpdf->WriteHTML('
' . $html . '
');
+
+ 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));
+ }
+
+}
diff --git a/tests/Mpdf/Fonts/GsubArrayTest.php b/tests/Mpdf/Fonts/GsubArrayTest.php
index 8fbaaf8d2..8b7961579 100644
--- a/tests/Mpdf/Fonts/GsubArrayTest.php
+++ b/tests/Mpdf/Fonts/GsubArrayTest.php
@@ -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'));
@@ -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()
diff --git a/tests/data/fontcache/NotoSansArabic-GSUB5Form-Synthetic.json b/tests/data/fontcache/NotoSansArabic-GSUB5Form-Synthetic.json
new file mode 100644
index 000000000..111bb6fe7
--- /dev/null
+++ b/tests/data/fontcache/NotoSansArabic-GSUB5Form-Synthetic.json
@@ -0,0 +1,278 @@
+{
+ "_": "Generated by composer fontcache:update. See tests/Mpdf/Fonts/ParserGoldenMaster.php.",
+ "fullName": "NotoSansArabicGSUB5FormSynthetic-Regular",
+ "mtx": {
+ "GSUBScriptLang": {
+ "DFLT": "DFLT ",
+ "arab": "DFLT "
+ },
+ "GSUBFeatures": {
+ "DFLT": {
+ "DFLT": {
+ "fina": [
+ 0
+ ],
+ "init": [
+ 1
+ ],
+ "medi": [
+ 2
+ ]
+ }
+ },
+ "arab": {
+ "DFLT": {
+ "fina": [
+ 0
+ ],
+ "init": [
+ 1
+ ],
+ "medi": [
+ 2
+ ]
+ }
+ }
+ },
+ "GSUBLookups": [
+ {
+ "Type": 6,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 100
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 5,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 134
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 5,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 162
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 1,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 202
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 1,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 216
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 1,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 230
+ ],
+ "MarkFilteringSet": ""
+ }
+ ],
+ "GPOSScriptLang": {
+ "DFLT": "DFLT ",
+ "arab": "DFLT ",
+ "cyrl": "DFLT ",
+ "dev2": "DFLT ",
+ "grek": "DFLT ",
+ "latn": "DFLT "
+ },
+ "GPOSFeatures": {
+ "DFLT": {
+ "DFLT": {
+ "mark": [
+ 0
+ ],
+ "mkmk": [
+ 1
+ ]
+ }
+ },
+ "arab": {
+ "DFLT": {
+ "mark": [
+ 0
+ ],
+ "mkmk": [
+ 1
+ ]
+ }
+ },
+ "cyrl": {
+ "DFLT": {
+ "mark": [
+ 0
+ ],
+ "mkmk": [
+ 1
+ ]
+ }
+ },
+ "dev2": {
+ "DFLT": {
+ "mark": [
+ 0
+ ],
+ "mkmk": [
+ 1
+ ]
+ }
+ },
+ "grek": {
+ "DFLT": {
+ "mark": [
+ 0
+ ],
+ "mkmk": [
+ 1
+ ]
+ }
+ },
+ "latn": {
+ "DFLT": {
+ "mark": [
+ 0
+ ],
+ "mkmk": [
+ 1
+ ]
+ }
+ }
+ },
+ "GPOSLookups": [
+ {
+ "Type": 4,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 102
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 6,
+ "Flag": 16,
+ "SubtableCount": 1,
+ "Subtables": [
+ 194
+ ],
+ "MarkFilteringSet": 0
+ }
+ ],
+ "MarkGlyphSets": [
+ " 0E005"
+ ],
+ "rtlPUAstr": "\\x{0E002}-\\x{0E004}",
+ "haskernGPOS": false,
+ "hassmallcapsGSUB": false
+ },
+ "cache": {
+ "GDEFdata.json": {
+ "GlyphClassBases": " 00020| 00627| 008AD| 0E000| 0E001| 0E002| 0E003| 0E004",
+ "GlyphClassMarks": " 0E005",
+ "GlyphClassLigatures": "",
+ "GlyphClassComponents": "",
+ "MarkGlyphSets": [
+ " 0E005"
+ ],
+ "MarkAttachmentType": []
+ },
+ "GPOS.dat": "234 bytes, sha256 0f363351c5ea276d72ca75127977045024304d1f2b19b04e8cfded715c737a13",
+ "GPOSdata.json": [
+ [
+ {
+ "57349": 0
+ }
+ ],
+ [
+ {
+ "57349": 0
+ }
+ ]
+ ],
+ "GSUB.arab.DFLT.json": {
+ "rtlSUB": {
+ "00628": {
+ "1": "0E002",
+ "prel": {
+ "1": [
+ "00628"
+ ]
+ },
+ "postl": {
+ "1": [
+ "008AD"
+ ]
+ },
+ "ignore": {
+ "1": "()",
+ "2": "()",
+ "3": "()"
+ },
+ "2": "0E004",
+ "3": "0E003"
+ }
+ },
+ "finals": "0E002 ",
+ "rphf": [],
+ "half": [],
+ "pref": [],
+ "blwf": [],
+ "pstf": []
+ },
+ "GSUB.dat": "242 bytes, sha256 233543ea71d43c7342390dd0ebd1a6fa9a480924f1ea1ef2e97cf4873a7fbd84",
+ "GSUBdata.json": [
+ [
+ {
+ "1576": 0
+ }
+ ],
+ [
+ {
+ "1576": 0
+ }
+ ],
+ [
+ {
+ "1576": 0
+ }
+ ],
+ [
+ {
+ "1576": 0
+ }
+ ],
+ [
+ {
+ "1576": 0
+ }
+ ],
+ [
+ {
+ "1576": 0
+ }
+ ]
+ ]
+ }
+}
diff --git a/tests/data/otldump/NotoSansArabic-GSUB5Form-Synthetic.txt b/tests/data/otldump/NotoSansArabic-GSUB5Form-Synthetic.txt
new file mode 100644
index 000000000..908812490
--- /dev/null
+++ b/tests/data/otldump/NotoSansArabic-GSUB5Form-Synthetic.txt
@@ -0,0 +1,27 @@
+GDEF table
+Glyph classes
+Glyph class 1
+Base glyph (single character, spacing glyph)
+ ا ࢭ
+Glyph class 3
+Mark glyph (non-spacing combining glyph)
+◌
+Mark Glyph Sets
+Mark Glyph Set class: 0
+◌
+GSUB Tables
+GSUB Scripts & Languages
+
+GPOS Tables
+GPOS Scripts & Languages
+
+
DFLT
arab
cyrl
dev2
grek
latn
+
+
+=== detail: script arab language DFLT ===
+GSUB Tables
+Lookup #0 [tag: fina]
Subtable #0
LookupType 6: Chaining Contextual Substitution Subtable
Format 3: Coverage-based Chaining Context Glyph Substitution
CONTEXT:
Backtrack #0: U+0628
Input #0: ب
Lookahead #0: U+08AD
Substitution Position: 0
Lookup #3 [tag: fina]
Subtable #0
LookupType 1: Single Substitution Subtable
U+0628 ب بࢭ » » ب ࢭ M+E002
Lookup #1 [tag: init]
Subtable #0
LookupType 5: Contextual Substitution Subtable
Format 1: Context Substitution
SubRule: 0
Substitution Position: 0
Lookup #4 [tag: init]
Subtable #0
LookupType 1: Single Substitution Subtable
U+0628 ب » » M+E004
Lookup #2 [tag: medi]
Subtable #0
LookupType 5: Contextual Substitution Subtable
Format 2: Class-based Context Glyph Substitution
Input Class: 1
Rule: 0
Substitution Position: 0
Lookup #5 [tag: medi]
Subtable #0
LookupType 1: Single Substitution Subtable
U+0628 ب » » M+E003
+GPOS Tables
+Lookup #0 [tag: mark]
Subtable #0
LookupType 4: MarkToBase attachment
Marks: ◌
Bases: ا ࢭ
Example(s): ا ࢭ
Lookup #1 [tag: mkmk]
Ignoring: Marks outside Mark Glyph Set[0]
Subtable #0
LookupType 6: MarkToMark attachment
Marks: ◌
Bases: ◌
Example(s): ◌
diff --git a/tests/data/shaping/NotoSansArabic-GSUB5Form-Synthetic.txt b/tests/data/shaping/NotoSansArabic-GSUB5Form-Synthetic.txt
new file mode 100644
index 000000000..46b19791d
--- /dev/null
+++ b/tests/data/shaping/NotoSansArabic-GSUB5Form-Synthetic.txt
@@ -0,0 +1,96 @@
+=== latin ===
+0xFF 0041 0056 0041 0054 0061 0072 => 0041 0056 0041 0054 0061 0072 group=CCCCCC
+0x80 0041 0056 0041 0054 0061 0072 => 0041 0056 0041 0054 0061 0072 group=CCCCCC
+0xFF +kern -liga 0041 0056 0041 0054 0061 0072 => 0041 0056 0041 0054 0061 0072 group=CCCCCC
+=== cyrillic ===
+0xFF 0416 0430 0439 => 0416 0430 0439 group=CCC
+0x80 0416 0430 0439 => 0416 0430 0439 group=CCC
+0xFF +kern -liga 0416 0430 0439 => 0416 0430 0439 group=CCC
+=== greek ===
+0xFF 03B1 03B2 03C2 => 03B1 03B2 03C2 group=CCC
+0x80 03B1 03B2 03C2 => 03B1 03B2 03C2 group=CCC
+0xFF +kern -liga 03B1 03B2 03C2 => 03B1 03B2 03C2 group=CCC
+=== hiragana ===
+0xFF 3042 3043 3044 => 3042 3043 3044 group=CCC
+0x80 3042 3043 3044 => 3042 3043 3044 group=CCC
+0xFF +kern -liga 3042 3043 3044 => 3042 3043 3044 group=CCC
+=== arabic ===
+0xFF 0628 0640 0645 0644 0627 => E004 0640 0645 0644 0627 group=CCCCC gpos={"1":{"kashida":8}}
+0x80 0628 0640 0645 0644 0627 => E004 0640 0645 0644 0627 group=CCCCC gpos={"1":{"kashida":8}}
+0xFF +kern -liga 0628 0640 0645 0644 0627 => E004 0640 0645 0644 0627 group=CCCCC gpos={"1":{"kashida":8}}
+=== syriac ===
+0xFF 0710 0712 0713 0715 => 0710 0712 0713 0715 group=CCCC
+0x80 0710 0712 0713 0715 => 0710 0712 0713 0715 group=CCCC
+0xFF +kern -liga 0710 0712 0713 0715 => 0710 0712 0713 0715 group=CCCC
+=== nko ===
+0xFF 07CA 07CB 07CC => 07CA 07CB 07CC group=CCC
+0x80 07CA 07CB 07CC => 07CA 07CB 07CC group=CCC
+0xFF +kern -liga 07CA 07CB 07CC => 07CA 07CB 07CC group=CCC
+=== devanagari ===
+0xFF 0915 094D 0937 093F => 0915 094D 093F 0937 group=CCCC
+0x80 0915 094D 0937 093F => 0915 094D 093F 0937 group=CCCC
+0xFF +kern -liga 0915 094D 0937 093F => 0915 094D 093F 0937 group=CCCC
+=== bengali ===
+0xFF 0995 09CD 09B7 09BF => 0995 09CD 09BF 09B7 group=CCCC
+0x80 0995 09CD 09B7 09BF => 0995 09CD 09BF 09B7 group=CCCC
+0xFF +kern -liga 0995 09CD 09B7 09BF => 0995 09CD 09BF 09B7 group=CCCC
+=== gurmukhi ===
+0xFF 0A15 0A4D 0A38 0A3F => 0A15 0A4D 0A3F 0A38 group=CCCC
+0x80 0A15 0A4D 0A38 0A3F => 0A15 0A4D 0A3F 0A38 group=CCCC
+0xFF +kern -liga 0A15 0A4D 0A38 0A3F => 0A15 0A4D 0A3F 0A38 group=CCCC
+=== tamil ===
+0xFF 0B95 0BCD 0BB7 0BBF => 0B95 0BCD 0BB7 0BBF group=CCCC
+0x80 0B95 0BCD 0BB7 0BBF => 0B95 0BCD 0BB7 0BBF group=CCCC
+0xFF +kern -liga 0B95 0BCD 0BB7 0BBF => 0B95 0BCD 0BB7 0BBF group=CCCC
+=== malayalam ===
+0xFF 0D15 0D4D 0D37 0D3F => 0D15 0D4D 0D37 0D3F group=CCCC
+0x80 0D15 0D4D 0D37 0D3F => 0D15 0D4D 0D37 0D3F group=CCCC
+0xFF +kern -liga 0D15 0D4D 0D37 0D3F => 0D15 0D4D 0D37 0D3F group=CCCC
+=== sinhala ===
+0xFF 0D9A 0DCA 0DBB 0DBB => 0D9A 0DCA 0DBB 0DBB group=CCCC
+0x80 0D9A 0DCA 0DBB 0DBB => 0D9A 0DCA 0DBB 0DBB group=CCCC
+0xFF +kern -liga 0D9A 0DCA 0DBB 0DBB => 0D9A 0DCA 0DBB 0DBB group=CCCC
+=== khmer ===
+0xFF 1780 17D2 1781 17C1 => 17C1 1780 17D2 1781 group=CCCC
+0x80 1780 17D2 1781 17C1 => 17C1 1780 17D2 1781 group=CCCC
+0xFF +kern -liga 1780 17D2 1781 17C1 => 17C1 1780 17D2 1781 group=CCCC
+=== thai ===
+0xFF 0E01 0E34 0E48 0E23 => 0E01 0E34 0E48 0E23 group=CCCC
+0x80 0E01 0E34 0E48 0E23 => 0E01 0E34 0E48 0E23 group=CCCC
+0xFF +kern -liga 0E01 0E34 0E48 0E23 => 0E01 0E34 0E48 0E23 group=CCCC
+=== lao ===
+0xFF 0E81 0EB4 0E8D => 0E81 0EB4 0E8D group=CCC
+0x80 0E81 0EB4 0E8D => 0E81 0EB4 0E8D group=CCC
+0xFF +kern -liga 0E81 0EB4 0E8D => 0E81 0EB4 0E8D group=CCC
+=== myanmar ===
+0xFF 1000 103A 1039 1001 => 1000 103A 1039 1001 group=CCCC
+0x80 1000 103A 1039 1001 => 1000 103A 1039 1001 group=CCCC
+0xFF +kern -liga 1000 103A 1039 1001 => 1000 103A 1039 1001 group=CCCC
+=== new tai lue ===
+0xFF 1980 19B0 1981 => 1980 19B0 1981 group=CCC
+0x80 1980 19B0 1981 => 1980 19B0 1981 group=CCC
+0xFF +kern -liga 1980 19B0 1981 => 1980 19B0 1981 group=CCC
+=== cham ===
+0xFF AA00 AA33 AA01 => AA00 AA33 AA01 group=CCC
+0x80 AA00 AA33 AA01 => AA00 AA33 AA01 group=CCC
+0xFF +kern -liga AA00 AA33 AA01 => AA00 AA33 AA01 group=CCC
+=== tai tham ===
+0xFF 1A20 1A60 1A21 => 1A20 1A60 1A21 group=CCC
+0x80 1A20 1A60 1A21 => 1A20 1A60 1A21 group=CCC
+0xFF +kern -liga 1A20 1A60 1A21 => 1A20 1A60 1A21 group=CCC
+=== mixed scripts ===
+0xFF 0041 0628 0915 0042 => 0041 0628 0915 0042 group=CCCC
+0x80 0041 0628 0915 0042 => 0041 0628 0915 0042 group=CCCC
+0xFF +kern -liga 0041 0628 0915 0042 => 0041 0628 0915 0042 group=CCCC
+=== spaced ===
+0xFF 0041 0020 0628 0020 0042 => 0041 0020 0628 0020 0042 group=CSCSC
+0x80 0041 0020 0628 0020 0042 => 0041 0020 0628 0020 0042 group=CSCSC
+0xFF +kern -liga 0041 0020 0628 0020 0042 => 0041 0020 0628 0020 0042 group=CSCSC
+=== font characters 0 ===
+0xFF 0020 0627 0628 08AD E000 E001 E002 E003 => 0020 0627 0628 08AD E000 E001 E002 E003 group=SCCCCCCC gpos={"6":{"kashida":2}}
+0x80 0020 0627 0628 08AD E000 E001 E002 E003 => 0020 0627 0628 08AD E000 E001 E002 E003 group=SCCCCCCC gpos={"6":{"kashida":2}}
+0xFF +kern -liga 0020 0627 0628 08AD E000 E001 E002 E003 => 0020 0627 0628 08AD E000 E001 E002 E003 group=SCCCCCCC gpos={"6":{"kashida":2}}
+=== font characters 1 ===
+0xFF E004 E005 => E004 E005 group=CM gpos={"1":{"BaseWidth":269,"XPlacement":31,"YPlacement":-3}}
+0x80 E004 E005 => E004 E005 group=CM gpos={"1":{"BaseWidth":269,"XPlacement":31,"YPlacement":-3}}
+0xFF +kern -liga E004 E005 => E004 E005 group=CM gpos={"1":{"BaseWidth":269,"XPlacement":31,"YPlacement":-3}}
diff --git a/tests/data/subset/NotoSansArabic-GSUB5Form-Synthetic.json b/tests/data/subset/NotoSansArabic-GSUB5Form-Synthetic.json
new file mode 100644
index 000000000..d22479446
--- /dev/null
+++ b/tests/data/subset/NotoSansArabic-GSUB5Form-Synthetic.json
@@ -0,0 +1,129 @@
+{
+ "_": "Generated by composer subset:update. See tests/Mpdf/Fonts/SubsetGoldenMaster.php.",
+ "useOTL=0x00": {
+ "characters": 5,
+ "makeSubset": {
+ "bytes": 1504,
+ "sha256": "f4fdd245fc504f91d29aa1d36d25bf23f52fe641b72b15b2fdaed6a680d2fcc4",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x83C35E30, sha256 c8d44e1e3e21744fb6447d75613e7d094658598ae11cd4ecd6ea51953585ed86",
+ "cmap": "92 bytes, checksum 0x0EEF06F5, sha256 ae6fb5dc97aecd1fc550113941bb8dd17c1e315a921037dd742091775397192d",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "304 bytes, checksum 0x7C5B4089, sha256 dda0aee857654d74b832394e016fc1e1e7710819c3dd061b666da107b0c39414",
+ "head": "54 bytes, checksum 0x46EA9EA3, sha256 cbb13f84aeb4bc2a151fc56903ba6630710ff42a0221bc6906f2448cebaa5cd4",
+ "hhea": "36 bytes, checksum 0x29871F54, sha256 b6790f08d7b49267a846ddaa6976fa1ae4f2c482adfd5ca104166fe4ec48ca93",
+ "hmtx": "28 bytes, checksum 0x0CF90144, sha256 5a0c3e3815967f5179a662960145cd59d1377b1065498bb99d512c035752a67d",
+ "loca": "16 bytes, checksum 0x00D00114, sha256 82987de0caadd775301bb7f92dc6663a42f1351074a37aaa9e8f43ddd2629de3",
+ "maxp": "32 bytes, checksum 0x0058052C, sha256 712fd6ee38e7ed3e4f3271a00a1ea02f9588b526df499378ee6c2db8aeed859e",
+ "name": "590 bytes, checksum 0x32D14B7E, sha256 6f9cda0e4d4ba9540d0577c856ff741507bd754cc02fd0458f25bc4384698b52",
+ "post": "32 bytes, checksum 0xFF9F0032, sha256 4fdc83d5e42fe44650fe86f15a645d5cc926d07355ba945e1ede42737607bf83",
+ "prep": "7 bytes, checksum 0x68068C85, sha256 907c4106ff9989c1805827d5581bdf107a253b098cb71eb374f5e47c35604452"
+ },
+ "maxUni": 65535,
+ "defaultWidth": 600,
+ "codeToGlyph": "5 entries, sha256 e330c81f30e51088fd0070ff21260ad702c20dd4487cd3b50ac48068aee467ca"
+ },
+ "makeSubsetSIP": {
+ "bytes": 1492,
+ "sha256": "3597e5849f5bbdc804e1760189c3fbd33f05dc7885b36c63ef551e6fd8a9baff",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x69A2D688, sha256 69a32b780b72e95ba9dfa002a1b8a2d0019efbe42ba74209c65095bc15e06732",
+ "cmap": "80 bytes, checksum 0x002C0083, sha256 3d0e58f1892397e1e94f3d0f75c6038f1212ed4d46a99eef47bc722bc366ee41",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "304 bytes, checksum 0x7C5B4089, sha256 184dd6f6301fa823051edcb9ea0c09851b5c5fd96ef911f898c134000990f13b",
+ "head": "54 bytes, checksum 0x46EA9EA3, sha256 da78ebc30f918b92dffae4ba54332a58ba40685d424626ded224e4b0486b0c81",
+ "hhea": "36 bytes, checksum 0x29871F54, sha256 b6790f08d7b49267a846ddaa6976fa1ae4f2c482adfd5ca104166fe4ec48ca93",
+ "hmtx": "28 bytes, checksum 0x0CF90144, sha256 cd581a635bc9a3a20a5f498d3842038b3b5acfb25d1187221fab16a8cf9430ee",
+ "loca": "16 bytes, checksum 0x00C40114, sha256 b012200316381b41dc293cfa1f58f92eb79c195dbc958907b7309a3fcab6d76e",
+ "maxp": "32 bytes, checksum 0x0058052C, sha256 712fd6ee38e7ed3e4f3271a00a1ea02f9588b526df499378ee6c2db8aeed859e",
+ "name": "590 bytes, checksum 0x32CA4B7E, sha256 f90f89fb915f6df91ba20cca75fe74cd14b13f91214149ea9be3b728e6d6b2f4",
+ "post": "32 bytes, checksum 0xFF9F0032, sha256 4fdc83d5e42fe44650fe86f15a645d5cc926d07355ba945e1ede42737607bf83",
+ "prep": "7 bytes, checksum 0x68068C85, sha256 907c4106ff9989c1805827d5581bdf107a253b098cb71eb374f5e47c35604452"
+ },
+ "maxUniChar": 65535,
+ "defaultWidth": 600
+ },
+ "repackageTTF": {
+ "bytes": 1992,
+ "sha256": "2733c4c6d88c604fcca928312f2ed7f294e790e64cf952fcd07d4ba064db3009",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x83C35E30, sha256 c8d44e1e3e21744fb6447d75613e7d094658598ae11cd4ecd6ea51953585ed86",
+ "cmap": "72 bytes, checksum 0x06420F70, sha256 e29b8cf3f1e636faa27f210c98884c21505fdd6b6b8acf965ef0b86eafa0c480",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "668 bytes, checksum 0xD2CBF85C, sha256 64df708dd07ef438ab78c2fa6ec037cb9d2ec39a026eeecb018460dbe43ab527",
+ "head": "54 bytes, checksum 0x46EA9EA3, sha256 301a6f9e7dc4cd406ad7250e6ebd6c819f3d4f839b1c340d7295dd620460532f",
+ "hhea": "36 bytes, checksum 0x29871F58, sha256 d5c8aa00ac210ae41f8cae1f3201846d77c0ac04d04e11627ee19851bdfdcd08",
+ "hmtx": "44 bytes, checksum 0x14C501AA, sha256 af3326b0ee31557a2ee01c72ee59dac76903c6a84f8a077b74b99271f2ba42f5",
+ "loca": "24 bytes, checksum 0x036E041D, sha256 0a4cc922874d893e9926bbbf0eece0202d5cab7ec6724963cafca63946eec457",
+ "maxp": "32 bytes, checksum 0x005C052C, sha256 26689100d5138bb752af4293a4bbb556b534aa9d94134a46edd960ec269019ec",
+ "name": "590 bytes, checksum 0x32D14B7E, sha256 6f9cda0e4d4ba9540d0577c856ff741507bd754cc02fd0458f25bc4384698b52",
+ "post": "151 bytes, checksum 0xB924F86C, sha256 4baf6477b5a1ef33145e61b8c7bb9c828d6b17629e10af34eaf47d20e2978446",
+ "prep": "7 bytes, checksum 0x68068C85, sha256 907c4106ff9989c1805827d5581bdf107a253b098cb71eb374f5e47c35604452"
+ },
+ "maxUni": 0
+ }
+ },
+ "useOTL=0xFF": {
+ "characters": 11,
+ "makeSubset": {
+ "bytes": 1920,
+ "sha256": "1f970b9545ee2fb139c012a537448808c4590c7effdfc58a7a936edf94b44b50",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x83C35E30, sha256 c8d44e1e3e21744fb6447d75613e7d094658598ae11cd4ecd6ea51953585ed86",
+ "cmap": "108 bytes, checksum 0xEF02E71C, sha256 78a92931bee3fd46f5a9c5415eedaa0f295efa03625ca4afa41c4480a898243b",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "680 bytes, checksum 0x41A9897B, sha256 56ca9bb7742204cbdc96a05b2385d3fa81b1179034b69c093b9aa272a3e64d52",
+ "head": "54 bytes, checksum 0x46EA9EA3, sha256 34c7bb3f1f15e1bcff72d5ec4cd8526a5c6dcd484124e091f7c8ccb55b332c7c",
+ "hhea": "36 bytes, checksum 0x29871F58, sha256 d5c8aa00ac210ae41f8cae1f3201846d77c0ac04d04e11627ee19851bdfdcd08",
+ "hmtx": "44 bytes, checksum 0x14C501AA, sha256 af3326b0ee31557a2ee01c72ee59dac76903c6a84f8a077b74b99271f2ba42f5",
+ "loca": "24 bytes, checksum 0x03800432, sha256 2cb2965a2d09fea2eb65e980cd64ac413ab3d2cfcd850e65997ad5fef232b95b",
+ "maxp": "32 bytes, checksum 0x005C052C, sha256 26689100d5138bb752af4293a4bbb556b534aa9d94134a46edd960ec269019ec",
+ "name": "590 bytes, checksum 0x32D14B7E, sha256 6f9cda0e4d4ba9540d0577c856ff741507bd754cc02fd0458f25bc4384698b52",
+ "post": "32 bytes, checksum 0xFF9F0032, sha256 4fdc83d5e42fe44650fe86f15a645d5cc926d07355ba945e1ede42737607bf83",
+ "prep": "7 bytes, checksum 0x68068C85, sha256 907c4106ff9989c1805827d5581bdf107a253b098cb71eb374f5e47c35604452"
+ },
+ "maxUni": 65535,
+ "defaultWidth": 600,
+ "codeToGlyph": "11 entries, sha256 4976a91e70c6d83b58a35c5bf0afec8d29c446258274744a3317930df09fa365"
+ },
+ "makeSubsetSIP": {
+ "bytes": 1904,
+ "sha256": "286b25d292d3372d1ce1abed6b578d197cd7b63075b8f575184f08ca4a4f5684",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x69A2D68E, sha256 d3e6c0234f8db691dbb73991a5141f8184691d245eda464e73fcfd0645daf3a7",
+ "cmap": "92 bytes, checksum 0x005600B0, sha256 5febc775230d1d4c2ff1203cc3ba03258d79c52651c9eb23a521082d323ea414",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "680 bytes, checksum 0x41AB897C, sha256 78d00159157d7c704efacfa95bba99beebb11d2b1c4313852a6f55f06936c1af",
+ "head": "54 bytes, checksum 0x46EA9EA3, sha256 3545e46340e5ee740e00cef07d31694654f7157a48f88eef6fa8456a0616a280",
+ "hhea": "36 bytes, checksum 0x29871F58, sha256 d5c8aa00ac210ae41f8cae1f3201846d77c0ac04d04e11627ee19851bdfdcd08",
+ "hmtx": "44 bytes, checksum 0x14C501AA, sha256 1bcad69d16f9d4b7cdb1a2e30f1e1b5172902317c43c2383364b125bc9287e90",
+ "loca": "24 bytes, checksum 0x02D2038E, sha256 276b53cc6f655d4a16fd4168880b6fba91e36f6b119e3f1b5905dd0725dba35c",
+ "maxp": "32 bytes, checksum 0x005C052C, sha256 26689100d5138bb752af4293a4bbb556b534aa9d94134a46edd960ec269019ec",
+ "name": "590 bytes, checksum 0x32CA4B7E, sha256 f90f89fb915f6df91ba20cca75fe74cd14b13f91214149ea9be3b728e6d6b2f4",
+ "post": "32 bytes, checksum 0xFF9F0032, sha256 4fdc83d5e42fe44650fe86f15a645d5cc926d07355ba945e1ede42737607bf83",
+ "prep": "7 bytes, checksum 0x68068C85, sha256 907c4106ff9989c1805827d5581bdf107a253b098cb71eb374f5e47c35604452"
+ },
+ "maxUniChar": 65535,
+ "defaultWidth": 600
+ },
+ "repackageTTF": {
+ "bytes": 2020,
+ "sha256": "79220e4158e2fd618c233b23c77194e0062bf4c6581bc874e1f773dfe4b4e293",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x83C35E30, sha256 c8d44e1e3e21744fb6447d75613e7d094658598ae11cd4ecd6ea51953585ed86",
+ "cmap": "100 bytes, checksum 0xF793DE7F, sha256 0dbe9edb138b44d6281c2fb3bff43096c4966deba0e380e4bed0d7e8887d0cba",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "668 bytes, checksum 0xD2CBF85C, sha256 64df708dd07ef438ab78c2fa6ec037cb9d2ec39a026eeecb018460dbe43ab527",
+ "head": "54 bytes, checksum 0x46EA9EA3, sha256 d17965018acdace96214a0b6245317e968ecfa19183925f30e5e4d6c0e6ea9ad",
+ "hhea": "36 bytes, checksum 0x29871F58, sha256 d5c8aa00ac210ae41f8cae1f3201846d77c0ac04d04e11627ee19851bdfdcd08",
+ "hmtx": "44 bytes, checksum 0x14C501AA, sha256 af3326b0ee31557a2ee01c72ee59dac76903c6a84f8a077b74b99271f2ba42f5",
+ "loca": "24 bytes, checksum 0x036E041D, sha256 0a4cc922874d893e9926bbbf0eece0202d5cab7ec6724963cafca63946eec457",
+ "maxp": "32 bytes, checksum 0x005C052C, sha256 26689100d5138bb752af4293a4bbb556b534aa9d94134a46edd960ec269019ec",
+ "name": "590 bytes, checksum 0x32D14B7E, sha256 6f9cda0e4d4ba9540d0577c856ff741507bd754cc02fd0458f25bc4384698b52",
+ "post": "151 bytes, checksum 0xB924F86C, sha256 4baf6477b5a1ef33145e61b8c7bb9c828d6b17629e10af34eaf47d20e2978446",
+ "prep": "7 bytes, checksum 0x68068C85, sha256 907c4106ff9989c1805827d5581bdf107a253b098cb71eb374f5e47c35604452"
+ },
+ "maxUni": 0
+ }
+ }
+}
diff --git a/tests/data/ttf/NotoSansArabic-GSUB5Form-Synthetic.ttf b/tests/data/ttf/NotoSansArabic-GSUB5Form-Synthetic.ttf
new file mode 100644
index 000000000..a336b9f30
Binary files /dev/null and b/tests/data/ttf/NotoSansArabic-GSUB5Form-Synthetic.ttf differ