diff --git a/src/Otl.php b/src/Otl.php
index 13d1dd9a9..55908e07a 100644
--- a/src/Otl.php
+++ b/src/Otl.php
@@ -2061,7 +2061,9 @@ private function _applyGSUBsingleSubst($lookupID, $subtable, $ptr, $currGlyph, $
$DeltaGlyphID = $this->reader->readInt16();
$this->reader->seek($CoverageOffset);
$glyphs = $this->_getCoverageGID();
- $GlyphID = $glyphs[$GlyphPos] + $DeltaGlyphID;
+ // The modulo is how a font names a glyph below the one it covers, or above the end of the
+ // range: Chiron Hei HK's 'hist' reaches glyph 1688 with 15324 from glyph 51900
+ $GlyphID = ($glyphs[$GlyphPos] + $DeltaGlyphID) & 0xFFFF;
}
// Format 2:
elseif ($SubstFormat == 2) { // Specified output glyph indices
@@ -4616,6 +4618,23 @@ private function marksOutsideFilteringSet($marks, $set)
return $keep ? ' ' . implode('| ', $keep) : '';
}
+ /**
+ * The marks a lookup naming a mark attachment class skips: every mark outside that class, which is
+ * what the parser keeps MarkAttachmentType as.
+ *
+ * A font may name a class GDEF does not define - Carlito and NATS set the flag without a
+ * MarkAttachClassDef table at all - and then no mark is in the class, so the lookup skips every one
+ * of them.
+ *
+ * @param int $class The mark attachment class the lookup's flags name
+ *
+ * @return string Its glyphs, space-prefixed and "|"-separated
+ */
+ private function marksOutsideAttachmentClass($class)
+ {
+ return isset($this->MarkAttachmentType[$class]) ? $this->MarkAttachmentType[$class] : $this->GlyphClassMarks;
+ }
+
/**
* The characters a Lookup's flag says to skip over, as a set keyed by codepoint.
*
@@ -4660,10 +4679,8 @@ private function buildGCOMignoreList($flag, $MarkFilteringSet)
// Flag & 0xFF?? = MarkAttachmentType
if ($flag & 0xFF00) {
// "a lookup must ignore any mark glyphs that are not in the specified mark attachment class"
- // $this->MarkAttachmentType is already adjusted for this i.e. contains all Marks except those in the MarkAttachmentClassDef table
- $MarkAttachmentType = $flag >> 8;
$ignoreflag = $flag;
- $str = $this->MarkAttachmentType[$MarkAttachmentType];
+ $str = $this->marksOutsideAttachmentClass($flag >> 8);
}
// Flag & 0x0010 = UseMarkFilteringSet
@@ -4726,8 +4743,7 @@ private function _checkGCOMignore($flag, $glyph, $MarkFilteringSet)
// Flag & 0xFF?? = MarkAttachmentType
if ($flag & 0xFF00) {
// "a lookup must ignore any mark glyphs that are not in the specified mark attachment class"
- // $this->MarkAttachmentType is already adjusted for this i.e. contains all Marks except those in the MarkAttachmentClassDef table
- if (strpos($this->MarkAttachmentType[($flag >> 8)], $glyph)) {
+ if (strpos($this->marksOutsideAttachmentClass($flag >> 8), $glyph)) {
$ignore = true;
}
}
diff --git a/src/TTFontFile.php b/src/TTFontFile.php
index 1108feaf4..60128d31f 100644
--- a/src/TTFontFile.php
+++ b/src/TTFontFile.php
@@ -1698,16 +1698,21 @@ private function readGSUBrules(array &$Lookup)
$glyphs = $this->_getCoverage(false);
for ($g = 0; $g < count($glyphs); $g++) {
$replace = [];
- $substitute = [];
$replace[] = unicode_hex($this->glyphToChar[$glyphs[$g]][0]);
// Flag = Ignore
if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
continue;
}
if (isset($Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])) { // Format 1
- $substitute[] = unicode_hex($this->glyphToChar[($glyphs[$g] + $Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])][0]);
+ // The modulo is how a font names a glyph below the one it covers: Cactus
+ // Classical Serif reaches its extended em dash with -7504 from glyph 504
+ $gid = ($glyphs[$g] + $Lookup[$i]['Subtable'][$c]['DeltaGlyphID']) & 0xFFFF;
} else { // Format 2
- $substitute[] = unicode_hex($this->glyphToChar[($Lookup[$i]['Subtable'][$c]['Glyphs'][$g])][0]);
+ $gid = $Lookup[$i]['Subtable'][$c]['Glyphs'][$g];
+ }
+ $substitute = $this->substituteGlyph($gid);
+ if ($substitute === null) {
+ continue;
}
$Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute];
}
@@ -1717,7 +1722,6 @@ private function readGSUBrules(array &$Lookup)
$glyphs = $this->_getCoverage();
for ($g = 0; $g < count($glyphs); $g++) {
$replace = [];
- $substitute = [];
$replace[] = $glyphs[$g];
// Flag = Ignore
if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
@@ -1738,7 +1742,6 @@ private function readGSUBrules(array &$Lookup)
$glyphs = $this->_getCoverage();
for ($g = 0; $g < count($glyphs); $g++) {
$replace = [];
- $substitute = [];
$replace[] = $glyphs[$g];
// Flag = Ignore
if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
@@ -1758,7 +1761,6 @@ private function readGSUBrules(array &$Lookup)
for ($s = 0; $s < $LigSetCount; $s++) {
for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) {
$replace = [];
- $substitute = [];
$replace[] = $glyphs[$s];
// Flag = Ignore
if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
@@ -1774,10 +1776,10 @@ private function readGSUBrules(array &$Lookup)
$replace[] = $rpl;
}
$gid = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['LigGlyph'];
- if (!isset($this->glyphToChar[$gid][0])) {
+ $substitute = $this->substituteGlyph($gid);
+ if ($substitute === null) {
continue;
}
- $substitute[] = unicode_hex($this->glyphToChar[$gid][0]);
$Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute, 'CompCount' => $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']];
}
}
@@ -1977,7 +1979,6 @@ private function readGSUBrules(array &$Lookup)
$Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'] = [implode("|", $glyphs)];
for ($g = 0; $g < count($glyphs); $g++) {
$replace = [];
- $substitute = [];
$replace[] = $glyphs[$g];
// Flag = Ignore
if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
@@ -1986,11 +1987,10 @@ private function readGSUBrules(array &$Lookup)
if (!isset($Lookup[$i]['Subtable'][$c]['SubstituteGlyphID'][$g])) {
continue;
} // The substitutes must run parallel to the Coverage table; either an error in the font, or something has gone wrong
- $gid = $Lookup[$i]['Subtable'][$c]['SubstituteGlyphID'][$g];
- if (!isset($this->glyphToChar[$gid][0])) {
+ $substitute = $this->substituteGlyph($Lookup[$i]['Subtable'][$c]['SubstituteGlyphID'][$g]);
+ if ($substitute === null) {
continue;
}
- $substitute[] = unicode_hex($this->glyphToChar[$gid][0]);
$Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute];
}
for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']; $b++) {
@@ -2433,7 +2433,11 @@ function _getGSUBarray(array $Lookup, $lul, $scripttag)
$inputGlyphs = [];
- $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass];
+ if (isset($Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass])) {
+ $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass];
+ } else {
+ $inputGlyphs[0] = '';
+ }
if ($rule['InputGlyphCount'] > 1) {
// NB starts at 1
for ($gcl = 1; $gcl < $rule['InputGlyphCount']; $gcl++) {
@@ -2873,8 +2877,7 @@ function _checkGSUBignore($flag, $glyph, $MarkFilteringSet)
// Flag & 0xFF?? = MarkAttachmentType
if ($flag & 0xFF00) {
// "a lookup must ignore any mark glyphs that are not in the specified mark attachment class"
- // $this->MarkAttachmentType is already adjusted for this i.e. contains all Marks except those in the MarkAttachmentClassDef table
- if (strpos($this->MarkAttachmentType[($flag >> 8)], $glyph)) {
+ if (strpos($this->marksOutsideAttachmentClass($flag >> 8), $glyph)) {
$ignore = true;
}
}
@@ -2938,6 +2941,23 @@ private function marksOutsideFilteringSet($marks, $set)
return $keep ? ' ' . implode('| ', $keep) : '';
}
+ /**
+ * The marks a lookup naming a mark attachment class skips: every mark outside that class, which
+ * is what _getGDEFtables() keeps MarkAttachmentType as.
+ *
+ * A font may name a class GDEF does not define - Carlito and NATS set the flag without a
+ * MarkAttachClassDef table at all - and then no mark is in the class, so the lookup skips every
+ * one of them.
+ *
+ * @param int $class The mark attachment class the lookup's flags name
+ *
+ * @return string Its glyphs, space-prefixed and "|"-separated
+ */
+ private function marksOutsideAttachmentClass($class)
+ {
+ return isset($this->MarkAttachmentType[$class]) ? $this->MarkAttachmentType[$class] : $this->GlyphClassMarks;
+ }
+
/**
* The glyphs a lookup's flags say to skip, as a pattern that matches a run of them.
*
@@ -2961,10 +2981,8 @@ function _getGSUBignoreString($flag, $MarkFilteringSet)
// Flag & 0xFF?? = MarkAttachmentType
if ($flag & 0xFF00) {
// "a lookup must ignore any mark glyphs that are not in the specified mark attachment class"
- // $this->MarkAttachmentType is already adjusted for this i.e. contains all Marks except those in the MarkAttachmentClassDef table
- $MarkAttachmentType = $flag >> 8;
$ignoreflag = $flag;
- $str = $this->MarkAttachmentType[$MarkAttachmentType];
+ $str = $this->marksOutsideAttachmentClass($flag >> 8);
}
// Flag & 0x0010 = UseMarkFilteringSet
@@ -3436,9 +3454,11 @@ private function readScriptsAndFeatures($scriptListOffset, $featureListOffset)
$byFirstLookup = [];
foreach ($featureIndices as $featureIndex) {
$feature = $features[$featureIndex];
- // A feature that runs no lookups has nothing to be ordered by and nothing to do.
- // The spec permits one; no font in the 183 installed carries one, which is why the
- // GSUB reader went without this guard for years and the GPOS one grew it.
+ // A feature that runs no lookups has nothing to be ordered by and nothing to do. The
+ // spec permits one and fonts in the wild carry one - Sedan SC's 'smcp' lists no
+ // lookups at all - so dropping it is the whole of what is right to do with it: keying
+ // the row by the lookup it has not got put it under '', which ksort() then ordered
+ // ahead of every real lookup index.
if (isset($feature['LookupListIndex'][0])) {
$byFirstLookup[$feature['LookupListIndex'][0]] = $feature;
}
@@ -3606,6 +3626,26 @@ protected function multipleSubstitutes(array $sequence)
return $substitute;
}
+ /**
+ * One replacement glyph, as the character the shaper names it by.
+ *
+ * Every glyph of a font read with useOTL has a character: the ones the cmap does not reach are
+ * mapped into the Private Use Area. So the only glyph id without one is a glyph the font has not
+ * got, which Single, Ligature, Alternate and Reverse Chaining Substitution can all name.
+ *
+ * @param int $gid The glyph the rule replaces its match with
+ *
+ * @return array|null It as hex in a list of one, or null to record nothing for this one
+ */
+ private function substituteGlyph($gid)
+ {
+ if (!isset($this->glyphToChar[$gid][0])) {
+ return null;
+ }
+
+ return [unicode_hex($this->glyphToChar[$gid][0])];
+ }
+
/**
* What an Alternate Substitution puts in place of the glyph it covers.
*
@@ -3618,13 +3658,7 @@ protected function multipleSubstitutes(array $sequence)
*/
protected function alternateSubstitutes(array $alternateSet)
{
- $gid = $alternateSet['SubstituteGlyphID'][0];
-
- if (!isset($this->glyphToChar[$gid][0])) {
- return null;
- }
-
- return [unicode_hex($this->glyphToChar[$gid][0])];
+ return $this->substituteGlyph($alternateSet['SubstituteGlyphID'][0]);
}
/**
diff --git a/tests/Mpdf/DeltaGlyphIdTest.php b/tests/Mpdf/DeltaGlyphIdTest.php
new file mode 100644
index 000000000..9fa4b3210
--- /dev/null
+++ b/tests/Mpdf/DeltaGlyphIdTest.php
@@ -0,0 +1,76 @@
+ 'utf-8',
+ 'fontDir' => [__DIR__ . '/../data/ttf'],
+ 'fontdata' => ['gsub11wrap' => [
+ 'R' => 'NotoSansArabic-GSUB11Wrap-Synthetic.ttf',
+ 'useOTL' => 0xFF,
+ ]],
+ 'default_font' => 'gsub11wrap',
+ ]);
+ $mpdf->WriteHTML('
' . $html . '
');
+
+ return array_values(unpack('N*', mb_convert_encoding($mpdf->drawnText[0], 'UTF-32BE', 'UTF-8')));
+ }
+
+ /**
+ * The lookup covers glyph 32770 and adds 32767, so the sum is 65537 and the substitute is glyph
+ * 1. Without the modulo the letter drew as nothing at all.
+ */
+ public function testTheGlyphTheDeltaWrapsOntoIsDrawn()
+ {
+ $this->assertSame([self::ALEF_FINA], $this->drawn([self::ALEF]));
+ }
+
+ /**
+ * Text is drawn in visual order, which for Arabic is the reverse of the order it is written in.
+ * The letter the lookup does not cover is left where it is.
+ */
+ public function testALetterTheLookupDoesNotCoverIsLeftAlone()
+ {
+ $this->assertSame([self::ALEF_FINA, self::BEH], $this->drawn([self::BEH, self::ALEF]));
+ }
+
+}
diff --git a/tests/Mpdf/MarkAttachmentTypeTest.php b/tests/Mpdf/MarkAttachmentTypeTest.php
new file mode 100644
index 000000000..7d22eeb51
--- /dev/null
+++ b/tests/Mpdf/MarkAttachmentTypeTest.php
@@ -0,0 +1,62 @@
+ 'utf-8',
+ 'fontDir' => [__DIR__ . '/../data/ttf'],
+ 'fontdata' => ['markattachment' => [
+ 'R' => 'Carlito-MarkAttachmentType-Subset.ttf',
+ 'useOTL' => 0xFF,
+ ]],
+ 'default_font' => 'markattachment',
+ ]);
+ $mpdf->WriteHTML('' . $html . '
');
+
+ return array_values(unpack('N*', mb_convert_encoding($mpdf->drawnText[0], 'UTF-32BE', 'UTF-8')));
+ }
+
+ /**
+ * The two characters draw as themselves: the lookup that would replace the i skips the mark its
+ * lookahead needs, so it cannot match. `hb-shape` draws the same pair of glyphs.
+ */
+ public function testALookupNamingAnUndefinedMarkAttachmentClassSkipsTheMarkItLooksAheadFor()
+ {
+ $this->assertSame([self::I, self::COMMA_ABOVE], $this->drawn([self::I, self::COMMA_ABOVE]));
+ }
+
+}
diff --git a/tests/Mpdf/TTFontFileTest.php b/tests/Mpdf/TTFontFileTest.php
index 79ee11f40..6b160cc80 100644
--- a/tests/Mpdf/TTFontFileTest.php
+++ b/tests/Mpdf/TTFontFileTest.php
@@ -142,66 +142,105 @@ public function testAFinalFormOfMoreThanOneGlyphContributesOnlyTheBaseItIsDrawnO
}
/**
- * The two bytes at the head of the width table record how many characters it covers, and the filter
- * that fills the table admits characters up to 196,607 - three times what the field can hold. chr()
- * raises PHP 8's out-of-range deprecation on the overflow, which is fatal to anyone promoting
- * warnings, and writes a header that reads back as a number the font never had.
+ * The parser's half of the modulo the spec adds a Single Substitution Format 1 delta by. Adding
+ * without it lands outside glyphToChar, and unicode_hex() made U+0000 of the null that came back,
+ * so the rule named the null character. @see DeltaGlyphIdTest for the shaper's half and for the
+ * fonts that wrap.
*
- * The font is 256 blank glyphs cycled through a format 12 cmap over U+0020-U+101EF, which is 65,999
- * characters once U+FFFF is dropped - enough to reach past the field, and 7 KB of doing it. Adobe
- * Blank, where this was found, counts 194,521.
+ * The synthetic covers glyph 32770 and adds 32767, so the sum is 65537 and the substitute is glyph
+ * 1 - the only glyph in the font with no character of its own, which is why it is the only one in
+ * the Private Use Area. A font can only spell a wrap with tens of thousands of glyphs: an int16
+ * delta cannot carry a sum past either end from anywhere closer.
*/
- public function testTheCharacterCountHeaderStopsAtWhatTwoBytesHold()
+ public function testASingleSubstitutionDeltaIsAddedModulo65536()
{
- $raised = $this->diagnosticsWhileParsing('Blank-WideCmap-Synthetic.ttf', 0);
+ $raised = $this->diagnosticsWhileParsing('NotoSansArabic-GSUB11Wrap-Synthetic.ttf');
$this->assertSame([], $raised);
- $this->assertSame(0xFFFF, $this->characterCount($this->ttf->charWidths));
+ $this->assertSame('\x{0E000}', $this->ttf->rtlPUAstr);
}
/**
- * Under the limit the header is still the font's own count. Writer\FontWriter reads these two bytes
- * back and divides by them, which is why they cannot simply be left zeroed.
+ * A lookup flag naming a mark attachment class says to skip every mark outside that class. A font
+ * may name a class GDEF does not define - Carlito and NATS both set the flag without a
+ * MarkAttachClassDef table at all - and then no mark is in the class, so every one of them is
+ * skipped. Reading MarkAttachmentType by the class instead gave null, which is no marks skipped.
+ *
+ * The subset is Carlito 1.104 (OFL 1.1) cut to U+0069, U+006A and U+0313, keeping the two 'ccmp'
+ * chained contexts that carry the flag and the dotless forms they substitute.
*/
- public function testTheCharacterCountHeaderIsTheCountWhereItFits()
+ public function testALookupNamingAMarkAttachmentClassTheFontDoesNotDefineParses()
{
- $raised = $this->diagnosticsWhileParsing('Poppins-Regular.ttf', 0);
+ $raised = $this->diagnosticsWhileParsing('Carlito-MarkAttachmentType-Subset.ttf');
$this->assertSame([], $raised);
- $this->assertSame(470, $this->characterCount($this->ttf->charWidths));
+ $this->assertSame('Carlito-Regular', $this->ttf->fullName);
}
/**
- * @return int The number of characters the width table says it covers
+ * Class 0 of a Class Definition is every glyph the other classes do not name, so it has no list of
+ * glyphs to match against and mPDF matches nothing at such a position. Every input position above
+ * the first already said so; the first read InputClasses by the class and got null.
+ *
+ * The subset is Molengo 0.11 (OFL 1.1) cut to U+0069, U+006A, U+0268 and the marks its one 'ccmp'
+ * lookup classifies. That lookup's only rule set is class 0's, and its Coverage holds nothing but
+ * glyphs the font puts in class 1, so the rules it states cannot fire either way.
*/
- private function characterCount($charWidths)
+ public function testAContextRuleSetForClassZeroIsReadLikeEveryOther()
{
- return (ord($charWidths[0]) << 8) + ord($charWidths[1]);
+ $raised = $this->diagnosticsWhileParsing('Molengo-GSUB52Class0-Subset.ttf');
+
+ $this->assertSame([], $raised);
+ $this->assertSame('Molengo-Regular', $this->ttf->fullName);
}
/**
- * Parse a font, collecting every diagnostic PHP raised doing it. Deprecations are not converted to
- * exceptions, so a handler is what sees them.
+ * A feature that runs no lookups is not offered to the shaper: there is nothing for it to do, and
+ * the lookup index the features are ordered by is the one it has not got.
*
- * @return string[]
+ * The subset is Sedan SC 1.100 (OFL 1.1), whose 'smcp' lists no lookups at all. The subsetter drops
+ * a feature with none, so it is put back. The 'aalt' and 'c2sc' left in it are what says the real
+ * features still come out in lookup order.
*/
- private function diagnosticsWhileParsing($file, $useOTL = 0xFF)
+ public function testAFeatureThatRunsNoLookupsIsNotOffered()
{
- $raised = [];
+ $raised = $this->diagnosticsWhileParsing('SedanSC-EmptyFeature-Subset.ttf');
- set_error_handler(function ($number, $message, $path, $line) use (&$raised) {
- $raised[] = sprintf('%s in %s:%d', $message, basename($path), $line);
+ $this->assertSame([], $raised);
+ $this->assertSame(
+ ['aalt' => [0, 1], 'c2sc' => [2], 'ccmp' => [3]],
+ $this->ttf->GSUBFeatures['latn']['DFLT']
+ );
+ }
- return true;
- });
+ /**
+ * The two bytes at the head of the width table record how many characters it covers, and the filter
+ * that fills the table admits characters up to 196,607 - three times what the field can hold. chr()
+ * raises PHP 8's out-of-range deprecation on the overflow, which is fatal to anyone promoting
+ * warnings, and writes a header that reads back as a number the font never had.
+ *
+ * The font is 256 blank glyphs cycled through a format 12 cmap over U+0020-U+101EF, which is 65,999
+ * characters once U+FFFF is dropped - enough to reach past the field, and 7 KB of doing it. Adobe
+ * Blank, where this was found, counts 194,521.
+ */
+ public function testTheCharacterCountHeaderStopsAtWhatTwoBytesHold()
+ {
+ $raised = $this->diagnosticsWhileParsing('Blank-WideCmap-Synthetic.ttf', 0);
- try {
- $this->ttf->getMetrics(__DIR__ . '/../data/ttf/' . $file, uniqid('', true), 0, false, false, $useOTL);
- } finally {
- restore_error_handler();
- }
+ $this->assertSame([], $raised);
+ $this->assertSame(0xFFFF, $this->characterCount($this->ttf->charWidths));
+ }
- return $raised;
+ /**
+ * Under the limit the header is still the font's own count. Writer\FontWriter reads these two bytes
+ * back and divides by them, which is why they cannot simply be left zeroed.
+ */
+ public function testTheCharacterCountHeaderIsTheCountWhereItFits()
+ {
+ $raised = $this->diagnosticsWhileParsing('Poppins-Regular.ttf', 0);
+
+ $this->assertSame([], $raised);
+ $this->assertSame(470, $this->characterCount($this->ttf->charWidths));
}
/**
@@ -244,6 +283,39 @@ public function fontProvider()
];
}
+ /**
+ * Parse a font, collecting every diagnostic PHP raised doing it. Deprecations are not converted to
+ * exceptions, so a handler is what sees them.
+ *
+ * @return string[]
+ */
+ private function diagnosticsWhileParsing($file, $useOTL = 0xFF)
+ {
+ $raised = [];
+
+ set_error_handler(function ($number, $message, $path, $line) use (&$raised) {
+ $raised[] = sprintf('%s in %s:%d', $message, basename($path), $line);
+
+ return true;
+ });
+
+ try {
+ $this->ttf->getMetrics(__DIR__ . '/../data/ttf/' . $file, uniqid('', true), 0, false, false, $useOTL);
+ } finally {
+ restore_error_handler();
+ }
+
+ return $raised;
+ }
+
+ /**
+ * @return int The number of characters the width table says it covers
+ */
+ private function characterCount($charWidths)
+ {
+ return (ord($charWidths[0]) << 8) + ord($charWidths[1]);
+ }
+
/**
* Everything the parser extracted. The reader it extracted them with is not public, so it does
* not appear here and its position does not have to be excluded.
diff --git a/tests/data/fontcache/Carlito-MarkAttachmentType-Subset.json b/tests/data/fontcache/Carlito-MarkAttachmentType-Subset.json
new file mode 100644
index 000000000..b36a2dc8a
--- /dev/null
+++ b/tests/data/fontcache/Carlito-MarkAttachmentType-Subset.json
@@ -0,0 +1,239 @@
+{
+ "_": "Generated by composer fontcache:update. See tests/Mpdf/Fonts/ParserGoldenMaster.php.",
+ "fullName": "Carlito-Regular",
+ "mtx": {
+ "GSUBScriptLang": {
+ "cyrl": "DFLT ",
+ "grek": "DFLT ",
+ "latn": "DFLT IPPH TRK "
+ },
+ "GSUBFeatures": {
+ "cyrl": {
+ "DFLT": {
+ "ccmp": [
+ 2,
+ 3
+ ],
+ "sups": [
+ 4
+ ]
+ }
+ },
+ "grek": {
+ "DFLT": {
+ "ccmp": [
+ 2,
+ 3
+ ],
+ "sups": [
+ 4
+ ]
+ }
+ },
+ "latn": {
+ "DFLT": {
+ "ccmp": [
+ 2,
+ 3
+ ],
+ "sups": [
+ 4
+ ],
+ "dlig": [
+ 5
+ ],
+ "ordn": [
+ 6
+ ]
+ },
+ "IPPH": {
+ "ccmp": [
+ 2,
+ 3
+ ]
+ },
+ "TRK ": {
+ "ccmp": [
+ 3
+ ],
+ "sups": [
+ 4
+ ],
+ "dlig": [
+ 5
+ ],
+ "ordn": [
+ 6
+ ]
+ }
+ }
+ },
+ "GSUBLookups": [
+ {
+ "Type": 1,
+ "Flag": 256,
+ "SubtableCount": 1,
+ "Subtables": [
+ 246
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 1,
+ "Flag": 256,
+ "SubtableCount": 1,
+ "Subtables": [
+ 260
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 6,
+ "Flag": 256,
+ "SubtableCount": 1,
+ "Subtables": [
+ 274
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 6,
+ "Flag": 256,
+ "SubtableCount": 1,
+ "Subtables": [
+ 300
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 1,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 370
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 4,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 338
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 1,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 370
+ ],
+ "MarkFilteringSet": ""
+ }
+ ],
+ "GPOSScriptLang": {
+ "cyrl": "DFLT ",
+ "grek": "DFLT ",
+ "latn": "DFLT "
+ },
+ "GPOSFeatures": {
+ "cyrl": {
+ "DFLT": {
+ "kern": [
+ 0
+ ]
+ }
+ },
+ "grek": {
+ "DFLT": {
+ "kern": [
+ 0
+ ]
+ }
+ },
+ "latn": {
+ "DFLT": {
+ "kern": [
+ 0
+ ]
+ }
+ }
+ },
+ "GPOSLookups": [
+ {
+ "Type": 2,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 112
+ ],
+ "MarkFilteringSet": ""
+ }
+ ],
+ "MarkGlyphSets": [],
+ "rtlPUAstr": "",
+ "haskernGPOS": true,
+ "hassmallcapsGSUB": false
+ },
+ "cache": {
+ "GDEFdata.json": {
+ "GlyphClassBases": " 00020| 00069| 0006A| 0E000| 0E001| 0E002| 0E003| 0E004| 0FFFF",
+ "GlyphClassMarks": " 00313",
+ "GlyphClassLigatures": "",
+ "GlyphClassComponents": "",
+ "MarkGlyphSets": [],
+ "MarkAttachmentType": []
+ },
+ "GPOS.dat": "162 bytes, sha256 df60dbaa624bc282b48a5cf9b4c37ce951c001f9d70cfe4096f54dbbd1c0212e",
+ "GPOSdata.json": [
+ [
+ {
+ "105": 0,
+ "106": 1
+ }
+ ]
+ ],
+ "GSUB.dat": "388 bytes, sha256 4a3088648e0017ee922740a2b4220e5de9ab7c0930d919e8de5bb4f32e68bddc",
+ "GSUBdata.json": [
+ [
+ {
+ "105": 0
+ }
+ ],
+ [
+ {
+ "106": 0
+ }
+ ],
+ [
+ {
+ "105": 0
+ }
+ ],
+ [
+ {
+ "106": 0
+ }
+ ],
+ [
+ {
+ "105": 0,
+ "106": 1
+ }
+ ],
+ [
+ {
+ "105": 0
+ }
+ ],
+ [
+ {
+ "105": 0,
+ "106": 1
+ }
+ ]
+ ]
+ }
+}
diff --git a/tests/data/fontcache/Molengo-GSUB52Class0-Subset.json b/tests/data/fontcache/Molengo-GSUB52Class0-Subset.json
new file mode 100644
index 000000000..62a96b20d
--- /dev/null
+++ b/tests/data/fontcache/Molengo-GSUB52Class0-Subset.json
@@ -0,0 +1,150 @@
+{
+ "_": "Generated by composer fontcache:update. See tests/Mpdf/Fonts/ParserGoldenMaster.php.",
+ "fullName": "Molengo-Regular",
+ "mtx": {
+ "GSUBScriptLang": {
+ "DFLT": "DFLT ",
+ "latn": "DFLT "
+ },
+ "GSUBFeatures": {
+ "DFLT": {
+ "DFLT": {
+ "ccmp": [
+ 0
+ ]
+ }
+ },
+ "latn": {
+ "DFLT": {
+ "ccmp": [
+ 0
+ ]
+ }
+ }
+ },
+ "GSUBLookups": [
+ {
+ "Type": 5,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 64
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 1,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 184
+ ],
+ "MarkFilteringSet": ""
+ }
+ ],
+ "GPOSScriptLang": {
+ "DFLT": "DFLT ",
+ "latn": "DFLT "
+ },
+ "GPOSFeatures": {
+ "DFLT": {
+ "DFLT": {
+ "mark": [
+ 0
+ ],
+ "mkmk": [
+ 1
+ ]
+ }
+ },
+ "latn": {
+ "DFLT": {
+ "mark": [
+ 0
+ ],
+ "mkmk": [
+ 1
+ ]
+ }
+ }
+ },
+ "GPOSLookups": [
+ {
+ "Type": 4,
+ "Flag": 0,
+ "SubtableCount": 2,
+ "Subtables": [
+ 80,
+ 120
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 6,
+ "Flag": 0,
+ "SubtableCount": 2,
+ "Subtables": [
+ 172,
+ 276
+ ],
+ "MarkFilteringSet": ""
+ }
+ ],
+ "MarkGlyphSets": [],
+ "rtlPUAstr": "",
+ "haskernGPOS": false,
+ "hassmallcapsGSUB": false
+ },
+ "cache": {
+ "GDEFdata.json": {
+ "GlyphClassBases": " 00020| 00069| 0006A| 00268| 00307| 0E000| 0E001| 0E002| 0E003| 0E004| 0E005| 0E006| 0E007| 0E008| 0E009| 0E00A| 0E00B| 0E00C| 0E00D| 0E00E| 0E00F| 0E010| 0E011| 0E012| 0E013| 0E014| 0E015| 0E016| 0E017| 0E018| 0E019| 0E01A| 0E01B| 0E01C| 0E01D| 0E01E| 0E01F| 0E020| 0E021| 0E022| 0E023| 0E024| 0E025| 0E026| 0E027| 0E028| 0E029| 0E02A| 0E02B| 0E02C| 0E02D| 0E02E| 0E02F| 0E030| 0E031| 0E032| 0E033| 0E034| 0E035| 0E036| 0E037| 0E038| 0E039| 0E03A| 0E03B| 0E03C| 0E03D| 0E03E| 0E03F| 0E040| 0E041| 0E042| 0E043| 0E044| 0E045| 0E046| 0E047| 0E048| 0E049| 0E04A| 0E04B| 0E04C| 0E04D| 0E04E| 0E04F| 0E050| 0E051| 0E052| 0E053| 0E054| 0E055| 0E056| 0E057| 0E058| 0E059| 0E05A| 0E05B| 0E05C| 0E05D| 0E05E| 0E05F| 0E060| 0E061| 0E062| 0E063| 0E064| 0E065| 0E066| 0E067| 0E068| 0E069| 0E06A| 0E06B| 0E06C| 0E06D| 0E06E| 0E06F| 0E070| 0E071| 0E072| 0E073| 0E074| 0E075| 0E076| 0E077| 0E078| 0E079| 0E07A| 0E07B| 0E07C| 0E07D| 0E07E| 0E07F| 0E080| 0E081| 0E082| 0E083| 0E084| 0E085| 0E086| 0E087| 0E088| 0E089| 0E08A| 0E08B| 0E08C| 0E08D| 0E08E| 0E08F| 0E090| 0E091| 0E092| 0E093| 0E094| 0E095| 0E096| 0E097| 0E098| 0E099| 0E09A| 0E09B| 0E09C| 0E09D| 0E09E| 0E09F| 0E0A0| 0E0A1| 0E0A2| 0E0A3| 0E0A4| 0E0A5| 0E0A6| 0E0A7| 0E0A8| 0E0A9| 0E0AA| 0E0AB| 0E0AC| 0E0AD| 0E0AE| 0E0AF| 0E0B0| 0E0B1| 0E0B2| 0E0B3| 0E0B4| 0E0B5| 0E0B6| 0E0B7| 0E0B8| 0E0B9| 0E0BA| 0E0BB| 0E0BC| 0E0BD| 0E0BE| 0E0BF| 0E0C0| 0E0C1| 0E0C2| 0E0C3| 0E0C4| 0E0C5| 0E0C6| 0E0C7| 0E0C8| 0E0C9| 0E0CA| 0E0CB| 0E0CC| 0E0CD| 0E0CE| 0E0CF| 0E0D0| 0E0D1| 0E0D2| 0E0D3| 0E0D4| 0E0D5| 0E0D6| 0E0D7| 0E0D8| 0E0D9| 0E0DA| 0E0DB| 0E0DC| 0E0DD| 0E0DE| 0E0DF| 0E0E0| 0E0E1| 0E0E2| 0E0E3| 0E0E4| 0E0E5| 0E0E7| 0E0E8| 0E0E9| 0E0EA| 0E0EB| 0E0EC| 0E0ED| 0E0EE| 0E0EF| 0E0F0| 0E0F1| 0E0F2| 0E0F3| 0E0F4| 0E0F5| 0E0F6| 0E0F7| 0E0F8| 0E0F9| 0E0FA| 0E0FB| 0E0FC| 0E0FD| 0E0FE| 0E0FF| 0E100| 0E101| 0E102| 0E103| 0E104| 0E105| 0E106| 0E121| 0E122| 0E123| 0E124| 0E125| 0E126| 0E127| 0E128| 0E129| 0E12A| 0E12B| 0E12C| 0E12D| 0E12E| 0E12F| 0E130| 0E131| 0E132| 0E133| 0E134| 0E135| 0E136| 0E137| 0E138| 0E139| 0E13A| 0E13B| 0E13C| 0E13D| 0E13E| 0E13F| 0E140| 0E141| 0E142| 0E143| 0E144| 0E145| 0E146| 0E147| 0E148| 0E149| 0E14A| 0E14B| 0E14C| 0E14D| 0E14E| 0E14F| 0E150| 0E151| 0E152| 0E153| 0E154| 0E155| 0E156| 0E157| 0E158| 0E159| 0E15A| 0E15B| 0E15C| 0E15D| 0E15E| 0E15F| 0E160| 0E161| 0E162| 0E163| 0E164| 0E165| 0E166| 0E167| 0E168| 0E169",
+ "GlyphClassMarks": " 00301| 00303| 00309| 00323| 0E0E6| 0E107| 0E108| 0E109| 0E10A| 0E10B| 0E10C| 0E10D| 0E10E| 0E10F| 0E110| 0E111| 0E112| 0E113| 0E114| 0E115| 0E116| 0E117| 0E118| 0E119| 0E11A| 0E11B| 0E11C| 0E11D| 0E11E| 0E11F| 0E120",
+ "GlyphClassLigatures": "",
+ "GlyphClassComponents": "",
+ "MarkGlyphSets": [],
+ "MarkAttachmentType": []
+ },
+ "GPOS.dat": "316 bytes, sha256 795e0b128ee67d7f00f7409cdc761260d9f2ff9cffbaab81181e0366ca4513b8",
+ "GPOSdata.json": [
+ [
+ {
+ "769": 0,
+ "771": 1,
+ "775": 2,
+ "777": 3
+ },
+ {
+ "803": 0
+ }
+ ],
+ [
+ {
+ "769": 0,
+ "771": 1,
+ "775": 2,
+ "777": 3
+ },
+ {
+ "803": 0
+ }
+ ]
+ ],
+ "GSUB.dat": "206 bytes, sha256 8e01a7de9aa9e628960031a108897444f339a79f46c3d7788edd400810d577cf",
+ "GSUBdata.json": [
+ [
+ {
+ "105": 0,
+ "106": 1,
+ "616": 2
+ }
+ ],
+ [
+ {
+ "105": 0,
+ "106": 1,
+ "616": 2
+ }
+ ]
+ ]
+ }
+}
diff --git a/tests/data/fontcache/NotoSansArabic-GSUB11Wrap-Synthetic.json b/tests/data/fontcache/NotoSansArabic-GSUB11Wrap-Synthetic.json
new file mode 100644
index 000000000..a70cb1cc0
--- /dev/null
+++ b/tests/data/fontcache/NotoSansArabic-GSUB11Wrap-Synthetic.json
@@ -0,0 +1,62 @@
+{
+ "_": "Generated by composer fontcache:update. See tests/Mpdf/Fonts/ParserGoldenMaster.php.",
+ "fullName": "NotoSansArabic-GSUB11Wrap-Synthetic",
+ "mtx": {
+ "GSUBScriptLang": {
+ "DFLT": "DFLT ",
+ "arab": "DFLT "
+ },
+ "GSUBFeatures": {
+ "DFLT": {
+ "DFLT": {
+ "ccmp": [
+ 0
+ ]
+ }
+ },
+ "arab": {
+ "DFLT": {
+ "ccmp": [
+ 0
+ ]
+ }
+ }
+ },
+ "GSUBLookups": [
+ {
+ "Type": 1,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 62
+ ],
+ "MarkFilteringSet": ""
+ }
+ ],
+ "GPOSScriptLang": [],
+ "GPOSFeatures": [],
+ "GPOSLookups": [],
+ "MarkGlyphSets": [],
+ "rtlPUAstr": "\\x{0E000}",
+ "haskernGPOS": false,
+ "hassmallcapsGSUB": false
+ },
+ "cache": {
+ "GDEFdata.json": {
+ "GlyphClassBases": " 00020| 00627| 00628| 0E000",
+ "GlyphClassMarks": "",
+ "GlyphClassLigatures": "",
+ "GlyphClassComponents": "",
+ "MarkGlyphSets": [],
+ "MarkAttachmentType": []
+ },
+ "GSUB.dat": "74 bytes, sha256 6a09e5dce6613a9dead0e55896bee67a475c7fc6c8b34ca2881392dcc9a04168",
+ "GSUBdata.json": [
+ [
+ {
+ "1575": 0
+ }
+ ]
+ ]
+ }
+}
diff --git a/tests/data/fontcache/SedanSC-EmptyFeature-Subset.json b/tests/data/fontcache/SedanSC-EmptyFeature-Subset.json
new file mode 100644
index 000000000..9e68c91a2
--- /dev/null
+++ b/tests/data/fontcache/SedanSC-EmptyFeature-Subset.json
@@ -0,0 +1,163 @@
+{
+ "_": "Generated by composer fontcache:update. See tests/Mpdf/Fonts/ParserGoldenMaster.php.",
+ "fullName": "SedanSC-Regular",
+ "mtx": {
+ "GSUBScriptLang": {
+ "DFLT": "DFLT ",
+ "latn": "DFLT "
+ },
+ "GSUBFeatures": {
+ "DFLT": {
+ "DFLT": {
+ "aalt": [
+ 0,
+ 1
+ ],
+ "c2sc": [
+ 2
+ ],
+ "ccmp": [
+ 3
+ ]
+ }
+ },
+ "latn": {
+ "DFLT": {
+ "aalt": [
+ 0,
+ 1
+ ],
+ "c2sc": [
+ 2
+ ],
+ "ccmp": [
+ 3
+ ]
+ }
+ }
+ },
+ "GSUBLookups": [
+ {
+ "Type": 1,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 134
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 3,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 160
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 1,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 192
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 1,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 214
+ ],
+ "MarkFilteringSet": ""
+ }
+ ],
+ "GPOSScriptLang": {
+ "DFLT": "DFLT ",
+ "latn": "DFLT "
+ },
+ "GPOSFeatures": {
+ "latn": {
+ "DFLT": {
+ "kern": [
+ 0
+ ]
+ }
+ }
+ },
+ "GPOSLookups": [
+ {
+ "Type": 2,
+ "Flag": 8,
+ "SubtableCount": 2,
+ "Subtables": [
+ 74,
+ 146
+ ],
+ "MarkFilteringSet": ""
+ }
+ ],
+ "MarkGlyphSets": [],
+ "rtlPUAstr": "",
+ "haskernGPOS": true,
+ "hassmallcapsGSUB": true
+ },
+ "cache": {
+ "GDEFdata.json": {
+ "GlyphClassBases": " 00041| 00061| 0E000",
+ "GlyphClassMarks": "",
+ "GlyphClassLigatures": "",
+ "GlyphClassComponents": "",
+ "MarkGlyphSets": [],
+ "MarkAttachmentType": []
+ },
+ "GPOS.dat": "238 bytes, sha256 4d53e06509aa43a448e5fc03fa941ae40d1b10d0467578f8709d24685ee6b265",
+ "GPOSdata.json": [
+ [
+ {
+ "65": 0,
+ "97": 1,
+ "98": 2,
+ "57344": 3,
+ "57345": 4,
+ "57346": 5
+ },
+ {
+ "65": 0,
+ "97": 1,
+ "98": 2,
+ "57344": 3
+ }
+ ]
+ ],
+ "GSUB.dat": "228 bytes, sha256 fb3a2c6189b002e3ea031ac9922a44622afe67ec4f1ae9d0b512ef4a71fe3766",
+ "GSUBdata.json": [
+ [
+ {
+ "66": 0,
+ "98": 1
+ }
+ ],
+ [
+ {
+ "65": 0,
+ "97": 1
+ }
+ ],
+ [
+ {
+ "65": 0,
+ "66": 1
+ }
+ ],
+ [
+ {
+ "97": 0,
+ "98": 1
+ }
+ ]
+ ]
+ }
+}
diff --git a/tests/data/otldump/Carlito-MarkAttachmentType-Subset.txt b/tests/data/otldump/Carlito-MarkAttachmentType-Subset.txt
new file mode 100644
index 000000000..e2c782730
--- /dev/null
+++ b/tests/data/otldump/Carlito-MarkAttachmentType-Subset.txt
@@ -0,0 +1,24 @@
+GDEF table
+Glyph classes
+Glyph class 1
+Base glyph (single character, spacing glyph)
+ i j
+Glyph class 3
+Mark glyph (non-spacing combining glyph)
+◌̓
+GSUB Tables
+GSUB Scripts & Languages
+
+
cyrl
grek
latn
DFLT: ccmp sups dlig ordn
TRK : ccmp sups dlig ordn
+
+GPOS Tables
+GPOS Scripts & Languages
+
+
+=== detail: script latn language DFLT ===
+GSUB Tables
+Lookup #2 [tag: ccmp]
Ignoring: MarkAttachmentType[1]
Subtable #0
LookupType 6: Chaining Contextual Substitution Subtable
Format 3: Coverage-based Chaining Context Glyph Substitution
CONTEXT:
Input #0: i
Lookahead #0: U+0313
Substitution Position: 0
Lookup #0 [tag: ccmp]
Ignoring: MarkAttachmentType[1]
Subtable #0
LookupType 1: Single Substitution Subtable
U+0069 i◌̓ » » ◌̓ M+E000
Lookup #3 [tag: ccmp]
Ignoring: MarkAttachmentType[1]
Subtable #0
LookupType 6: Chaining Contextual Substitution Subtable
Format 3: Coverage-based Chaining Context Glyph Substitution
CONTEXT:
Input #0: j
Lookahead #0: U+0313
Substitution Position: 0
Lookup #1 [tag: ccmp]
Ignoring: MarkAttachmentType[1]
Subtable #0
LookupType 1: Single Substitution Subtable
U+006A j◌̓ » » ◌̓ M+E002
Lookup #4 [tag: sups]
Subtable #0
LookupType 1: Single Substitution Subtable
U+0069 i » » M+E003
U+006A j » » M+E004
Lookup #5 [tag: dlig]
Subtable #0
LookupType 4: Ligature Substitution Subtable
U+0069, U+006A i j » » M+E001
Lookup #6 [tag: ordn]
Subtable #0
LookupType 1: Single Substitution Subtable
U+0069 i » » M+E003
U+006A j » » M+E004
+GPOS Tables
+Lookup #0 [tag: kern]
Subtable #0
LookupType 2: Pair adjustment e.g. Kerning [Format 2]
diff --git a/tests/data/otldump/Molengo-GSUB52Class0-Subset.txt b/tests/data/otldump/Molengo-GSUB52Class0-Subset.txt
new file mode 100644
index 000000000..72df64dbe
--- /dev/null
+++ b/tests/data/otldump/Molengo-GSUB52Class0-Subset.txt
@@ -0,0 +1,27 @@
+GDEF table
+Glyph classes
+Glyph class 1
+Base glyph (single character, spacing glyph)
+ i j ɨ ̇
+Glyph class 3
+Mark glyph (non-spacing combining glyph)
+◌́ ◌̃ ◌̉ ◌̣ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌
+GSUB Tables
+GSUB Scripts & Languages
+
+GPOS Tables
+GPOS Scripts & Languages
+
+
+=== detail: script latn language DFLT ===
+8863 bytes, sha256 e037777f271dbdbe76e45f8cada8702b31aa5456ac2599e7dbe0a651e7648242
+first 8192 bytes:
+GSUB Tables
+Lookup #0 [tag: ccmp]
Subtable #0
LookupType 5: Contextual Substitution Subtable
Format 2: Class-based Context Glyph Substitution
Input Class: 0
Rule: 0
CONTEXT:
Input #0: [NOT i j ɨ ◌́ ◌ ◌̃ ◌ ◌ ̇ ◌ ◌̉ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌̣ ◌ ◌ ◌ ◌ ◌ ◌ ◌]
Input #1: ◌́ ◌ ◌̃ ◌ ◌ ̇ ◌ ◌̉ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌
Substitution Position: 0
Lookup #1 [tag: ccmp]
Subtable #0
LookupType 1: Single Substitution Subtable
Rule: 1
CONTEXT:
Input #0: [NOT i j ɨ ◌́ ◌ ◌̃ ◌ ◌ ̇ ◌ ◌̉ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌̣ ◌ ◌ ◌ ◌ ◌ ◌ ◌]
Input #1: ◌̣ ◌ ◌ ◌ ◌ ◌ ◌ ◌
Input #2: ◌́ ◌ ◌̃ ◌ ◌ ̇ ◌ ◌̉ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌
Substitution Position: 0
Lookup #1 [tag: ccmp]
Subtable #0
LookupType 1: Single Substitution Subtable
Rule: 2
CONTEXT:
Input #0: [NOT i j ɨ ◌́ ◌ ◌̃ ◌ ◌ ̇ ◌ ◌̉ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌̣ ◌ ◌ ◌ ◌ ◌ ◌ ◌]
Input #1: ◌̣ ◌ ◌ ◌ ◌ ◌ ◌ ◌
Input #2: ◌̣ ◌ ◌ ◌ ◌ ◌ ◌ ◌
Input #3: ◌́ ◌ ◌̃ ◌ ◌ ̇ ◌ ◌̉ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌
Substitution Position: 0
Lookup #1 [tag: ccmp]
Subtable #0
LookupType 1: Single Substitution Subtable
Rule: 3
CONTEXT:
Input #0: [NOT i j ɨ ◌́ ◌ ◌̃ ◌ ◌ ̇ ◌ ◌̉ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌̣ ◌ ◌ ◌ ◌ ◌ ◌ ◌]
Input #1: ◌̣ ◌ ◌ ◌ ◌ ◌ ◌ ◌
Input #2: ◌̣ ◌ ◌ ◌ ◌ ◌ ◌ ◌
Input #3: ◌̣ ◌ ◌ ◌ ◌ ◌ ◌ ◌
Input #4: ◌́ ◌ ◌̃ ◌ ◌ ̇ ◌ ◌̉ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ◌
Substitution Position: 0
Lookup #1 [tag: ccmp]
Subtable #0
LookupType 1: Single Substitution Subtable
+GPOS Tables
+Lookup #0 [tag: mark]
Subtable #0
LookupType 4: MarkToBase attachment
Marks: ◌́ ◌̃ ̇ ◌̉
Bases: ɨ
Example(s): ɨ́
Subtable #1
LookupType 4: MarkToBase attachment
Marks: ◌̣
Bases: i ɨ
Example(s): ị ɨ̣
Lookup #1 [tag: mkmk]
Subtable #0
LookupType 6: MarkToMark attachment
Marks: ◌́ ◌̃ ̇ ◌̉
Bases: i ◌́ ◌̃ ̇ ◌̉
ا ب
+
GSUB Tables
+
GSUB Scripts & Languages
+
+
GPOS table not defined
+
+=== detail: script arab language DFLT ===
+
GSUB Tables
+
Lookup #0 [tag: ccmp]
Subtable #0
LookupType 1: Single Substitution Subtable
U+0627 ا » » M+E000
+GPOS table not defined
diff --git a/tests/data/otldump/SedanSC-EmptyFeature-Subset.txt b/tests/data/otldump/SedanSC-EmptyFeature-Subset.txt
new file mode 100644
index 000000000..1891d603e
--- /dev/null
+++ b/tests/data/otldump/SedanSC-EmptyFeature-Subset.txt
@@ -0,0 +1,21 @@
+GDEF table
+Glyph classes
+Glyph class 1
+Base glyph (single character, spacing glyph)
+A a
+GSUB Tables
+GSUB Scripts & Languages
+
+GPOS Tables
+GPOS Scripts & Languages
+
+
+=== detail: script latn language DFLT ===
+GSUB Tables
+Lookup #0 [tag: aalt]
Subtable #0
LookupType 1: Single Substitution Subtable
U+0042 B » » M+E001
U+0062 b » » M+E001
Lookup #1 [tag: aalt]
Subtable #0
LookupType 3: Alternate Forms
U+0041 A » » M+E002 | ALT #1 M+E000
U+0061 a » » M+E002 | ALT #1 M+E000
Lookup #2 [tag: c2sc]
Subtable #0
LookupType 1: Single Substitution Subtable
U+0041 A » » M+E000
U+0042 B » » M+E001
Lookup #3 [tag: ccmp]
Subtable #0
LookupType 1: Single Substitution Subtable
U+0061 a » » M+E000
U+0062 b » » M+E001
+GPOS Tables
+Lookup #0 [tag: kern]
Ignoring: Mark Glyphs
Subtable #0
LookupType 2: Pair adjustment e.g. Kerning [Format 1]
A
a b
b
A
Subtable #1
LookupType 2: Pair adjustment e.g. Kerning [Format 2]
diff --git a/tests/data/shaping/Carlito-MarkAttachmentType-Subset.txt b/tests/data/shaping/Carlito-MarkAttachmentType-Subset.txt
new file mode 100644
index 000000000..086b38621
--- /dev/null
+++ b/tests/data/shaping/Carlito-MarkAttachmentType-Subset.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 => 0628 0640 0645 0644 0627 group=CCCCC gpos={"1":{"kashida":8}}
+0x80 0628 0640 0645 0644 0627 => 0628 0640 0645 0644 0627 group=CCCCC gpos={"1":{"kashida":8}}
+0xFF +kern -liga 0628 0640 0645 0644 0627 => 0628 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 0069 006A 0313 E000 E001 E002 E003 => 0020 0069 006A 0313 E000 E001 E002 E003 group=SCCMCCCC
+0x80 0020 0069 006A 0313 E000 E001 E002 E003 => 0020 0069 006A 0313 E000 E001 E002 E003 group=SCCMCCCC
+0xFF +kern -liga 0020 0069 006A 0313 E000 E001 E002 E003 => 0020 0069 006A 0313 E000 E001 E002 E003 group=SCCMCCCC
+=== font characters 1 ===
+0xFF E004 E005 E006 E007 => E004 E005 E006 E007 group=CCCC
+0x80 E004 E005 E006 E007 => E004 E005 E006 E007 group=CCCC
+0xFF +kern -liga E004 E005 E006 E007 => E004 E005 E006 E007 group=CCCC
diff --git a/tests/data/shaping/Molengo-GSUB52Class0-Subset.txt b/tests/data/shaping/Molengo-GSUB52Class0-Subset.txt
new file mode 100644
index 000000000..c0cedf1f6
--- /dev/null
+++ b/tests/data/shaping/Molengo-GSUB52Class0-Subset.txt
@@ -0,0 +1,184 @@
+=== 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 => 0628 0640 0645 0644 0627 group=CCCCC gpos={"1":{"kashida":8}}
+0x80 0628 0640 0645 0644 0627 => 0628 0640 0645 0644 0627 group=CCCCC gpos={"1":{"kashida":8}}
+0xFF +kern -liga 0628 0640 0645 0644 0627 => 0628 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 0069 006A 0268 0301 0303 0307 0309 => 0020 0069 006A 0268 0301 0303 0307 0309 group=SCCCMMCM gpos={"4":{"BaseWidth":604.16,"XPlacement":302,"YPlacement":519},"5":{"BaseWidth":604.16,"XPlacement":302,"YPlacement":959},"6":{"BaseWidth":604.16,"XPlacement":302,"YPlacement":1249},"7":{"BaseWidth":604.16,"XPlacement":302,"YPlacement":1768}}
+0x80 0020 0069 006A 0268 0301 0303 0307 0309 => 0020 0069 006A 0268 0301 0303 0307 0309 group=SCCCMMCM
+0xFF +kern -liga 0020 0069 006A 0268 0301 0303 0307 0309 => 0020 0069 006A 0268 0301 0303 0307 0309 group=SCCCMMCM gpos={"4":{"BaseWidth":604.16,"XPlacement":302,"YPlacement":519},"5":{"BaseWidth":604.16,"XPlacement":302,"YPlacement":959},"6":{"BaseWidth":604.16,"XPlacement":302,"YPlacement":1249},"7":{"BaseWidth":604.16,"XPlacement":302,"YPlacement":1768}}
+=== font characters 1 ===
+0xFF 0323 E000 E001 E002 E003 E004 E005 E006 => 0323 E000 E001 E002 E003 E004 E005 E006 group=MCCCCCCC
+0x80 0323 E000 E001 E002 E003 E004 E005 E006 => 0323 E000 E001 E002 E003 E004 E005 E006 group=MCCCCCCC
+0xFF +kern -liga 0323 E000 E001 E002 E003 E004 E005 E006 => 0323 E000 E001 E002 E003 E004 E005 E006 group=MCCCCCCC
+=== font characters 2 ===
+0xFF E007 E008 E009 E00A E00B E00C E00D E00E => E007 E008 E009 E00A E00B E00C E00D E00E group=CCCCCCCC
+0x80 E007 E008 E009 E00A E00B E00C E00D E00E => E007 E008 E009 E00A E00B E00C E00D E00E group=CCCCCCCC
+0xFF +kern -liga E007 E008 E009 E00A E00B E00C E00D E00E => E007 E008 E009 E00A E00B E00C E00D E00E group=CCCCCCCC
+=== font characters 3 ===
+0xFF E00F E010 E011 E012 E013 E014 E015 E016 => E00F E010 E011 E012 E013 E014 E015 E016 group=CCCCCCCC
+0x80 E00F E010 E011 E012 E013 E014 E015 E016 => E00F E010 E011 E012 E013 E014 E015 E016 group=CCCCCCCC
+0xFF +kern -liga E00F E010 E011 E012 E013 E014 E015 E016 => E00F E010 E011 E012 E013 E014 E015 E016 group=CCCCCCCC
+=== font characters 4 ===
+0xFF E017 E018 E019 E01A E01B E01C E01D E01E => E017 E018 E019 E01A E01B E01C E01D E01E group=CCCCCCCC
+0x80 E017 E018 E019 E01A E01B E01C E01D E01E => E017 E018 E019 E01A E01B E01C E01D E01E group=CCCCCCCC
+0xFF +kern -liga E017 E018 E019 E01A E01B E01C E01D E01E => E017 E018 E019 E01A E01B E01C E01D E01E group=CCCCCCCC
+=== font characters 5 ===
+0xFF E01F E020 E021 E022 E023 E024 E025 E026 => E01F E020 E021 E022 E023 E024 E025 E026 group=CCCCCCCC
+0x80 E01F E020 E021 E022 E023 E024 E025 E026 => E01F E020 E021 E022 E023 E024 E025 E026 group=CCCCCCCC
+0xFF +kern -liga E01F E020 E021 E022 E023 E024 E025 E026 => E01F E020 E021 E022 E023 E024 E025 E026 group=CCCCCCCC
+=== font characters 6 ===
+0xFF E027 E028 E029 E02A E02B E02C E02D E02E => E027 E028 E029 E02A E02B E02C E02D E02E group=CCCCCCCC
+0x80 E027 E028 E029 E02A E02B E02C E02D E02E => E027 E028 E029 E02A E02B E02C E02D E02E group=CCCCCCCC
+0xFF +kern -liga E027 E028 E029 E02A E02B E02C E02D E02E => E027 E028 E029 E02A E02B E02C E02D E02E group=CCCCCCCC
+=== font characters 7 ===
+0xFF E02F E030 E031 E032 E033 E034 E035 E036 => E02F E030 E031 E032 E033 E034 E035 E036 group=CCCCCCCC
+0x80 E02F E030 E031 E032 E033 E034 E035 E036 => E02F E030 E031 E032 E033 E034 E035 E036 group=CCCCCCCC
+0xFF +kern -liga E02F E030 E031 E032 E033 E034 E035 E036 => E02F E030 E031 E032 E033 E034 E035 E036 group=CCCCCCCC
+=== font characters 8 ===
+0xFF E037 E038 E039 E03A E03B E03C E03D E03E => E037 E038 E039 E03A E03B E03C E03D E03E group=CCCCCCCC
+0x80 E037 E038 E039 E03A E03B E03C E03D E03E => E037 E038 E039 E03A E03B E03C E03D E03E group=CCCCCCCC
+0xFF +kern -liga E037 E038 E039 E03A E03B E03C E03D E03E => E037 E038 E039 E03A E03B E03C E03D E03E group=CCCCCCCC
+=== font characters 9 ===
+0xFF E03F E040 E041 E042 E043 E044 E045 E046 => E03F E040 E041 E042 E043 E044 E045 E046 group=CCCCCCCC
+0x80 E03F E040 E041 E042 E043 E044 E045 E046 => E03F E040 E041 E042 E043 E044 E045 E046 group=CCCCCCCC
+0xFF +kern -liga E03F E040 E041 E042 E043 E044 E045 E046 => E03F E040 E041 E042 E043 E044 E045 E046 group=CCCCCCCC
+=== font characters 10 ===
+0xFF E047 E048 E049 E04A E04B E04C E04D E04E => E047 E048 E049 E04A E04B E04C E04D E04E group=CCCCCCCC
+0x80 E047 E048 E049 E04A E04B E04C E04D E04E => E047 E048 E049 E04A E04B E04C E04D E04E group=CCCCCCCC
+0xFF +kern -liga E047 E048 E049 E04A E04B E04C E04D E04E => E047 E048 E049 E04A E04B E04C E04D E04E group=CCCCCCCC
+=== font characters 11 ===
+0xFF E04F E050 E051 E052 E053 E054 E055 E056 => E04F E050 E051 E052 E053 E054 E055 E056 group=CCCCCCCC
+0x80 E04F E050 E051 E052 E053 E054 E055 E056 => E04F E050 E051 E052 E053 E054 E055 E056 group=CCCCCCCC
+0xFF +kern -liga E04F E050 E051 E052 E053 E054 E055 E056 => E04F E050 E051 E052 E053 E054 E055 E056 group=CCCCCCCC
+=== font characters 12 ===
+0xFF E057 E058 E059 E05A E05B E05C E05D E05E => E057 E058 E059 E05A E05B E05C E05D E05E group=CCCCCCCC
+0x80 E057 E058 E059 E05A E05B E05C E05D E05E => E057 E058 E059 E05A E05B E05C E05D E05E group=CCCCCCCC
+0xFF +kern -liga E057 E058 E059 E05A E05B E05C E05D E05E => E057 E058 E059 E05A E05B E05C E05D E05E group=CCCCCCCC
+=== font characters 13 ===
+0xFF E05F E060 E061 E062 E063 E064 E065 E066 => E05F E060 E061 E062 E063 E064 E065 E066 group=CCCCCCCC
+0x80 E05F E060 E061 E062 E063 E064 E065 E066 => E05F E060 E061 E062 E063 E064 E065 E066 group=CCCCCCCC
+0xFF +kern -liga E05F E060 E061 E062 E063 E064 E065 E066 => E05F E060 E061 E062 E063 E064 E065 E066 group=CCCCCCCC
+=== font characters 14 ===
+0xFF E067 E068 E069 E06A E06B E06C E06D E06E => E067 E068 E069 E06A E06B E06C E06D E06E group=CCCCCCCC
+0x80 E067 E068 E069 E06A E06B E06C E06D E06E => E067 E068 E069 E06A E06B E06C E06D E06E group=CCCCCCCC
+0xFF +kern -liga E067 E068 E069 E06A E06B E06C E06D E06E => E067 E068 E069 E06A E06B E06C E06D E06E group=CCCCCCCC
+=== font characters 15 ===
+0xFF E06F E070 E071 E072 E073 E074 E075 E076 => E06F E070 E071 E072 E073 E074 E075 E076 group=CCCCCCCC
+0x80 E06F E070 E071 E072 E073 E074 E075 E076 => E06F E070 E071 E072 E073 E074 E075 E076 group=CCCCCCCC
+0xFF +kern -liga E06F E070 E071 E072 E073 E074 E075 E076 => E06F E070 E071 E072 E073 E074 E075 E076 group=CCCCCCCC
+=== font characters 16 ===
+0xFF E077 E078 E079 E07A E07B E07C E07D E07E => E077 E078 E079 E07A E07B E07C E07D E07E group=CCCCCCCC
+0x80 E077 E078 E079 E07A E07B E07C E07D E07E => E077 E078 E079 E07A E07B E07C E07D E07E group=CCCCCCCC
+0xFF +kern -liga E077 E078 E079 E07A E07B E07C E07D E07E => E077 E078 E079 E07A E07B E07C E07D E07E group=CCCCCCCC
+=== font characters 17 ===
+0xFF E07F E080 E081 E082 E083 E084 E085 E086 => E07F E080 E081 E082 E083 E084 E085 E086 group=CCCCCCCC
+0x80 E07F E080 E081 E082 E083 E084 E085 E086 => E07F E080 E081 E082 E083 E084 E085 E086 group=CCCCCCCC
+0xFF +kern -liga E07F E080 E081 E082 E083 E084 E085 E086 => E07F E080 E081 E082 E083 E084 E085 E086 group=CCCCCCCC
+=== font characters 18 ===
+0xFF E087 E088 E089 E08A E08B E08C E08D E08E => E087 E088 E089 E08A E08B E08C E08D E08E group=CCCCCCCC
+0x80 E087 E088 E089 E08A E08B E08C E08D E08E => E087 E088 E089 E08A E08B E08C E08D E08E group=CCCCCCCC
+0xFF +kern -liga E087 E088 E089 E08A E08B E08C E08D E08E => E087 E088 E089 E08A E08B E08C E08D E08E group=CCCCCCCC
+=== font characters 19 ===
+0xFF E08F E090 E091 E092 E093 E094 E095 E096 => E08F E090 E091 E092 E093 E094 E095 E096 group=CCCCCCCC
+0x80 E08F E090 E091 E092 E093 E094 E095 E096 => E08F E090 E091 E092 E093 E094 E095 E096 group=CCCCCCCC
+0xFF +kern -liga E08F E090 E091 E092 E093 E094 E095 E096 => E08F E090 E091 E092 E093 E094 E095 E096 group=CCCCCCCC
+=== font characters 20 ===
+0xFF E097 E098 E099 E09A E09B E09C E09D E09E => E097 E098 E099 E09A E09B E09C E09D E09E group=CCCCCCCC
+0x80 E097 E098 E099 E09A E09B E09C E09D E09E => E097 E098 E099 E09A E09B E09C E09D E09E group=CCCCCCCC
+0xFF +kern -liga E097 E098 E099 E09A E09B E09C E09D E09E => E097 E098 E099 E09A E09B E09C E09D E09E group=CCCCCCCC
+=== font characters 21 ===
+0xFF E09F E0A0 E0A1 E0A2 E0A3 E0A4 E0A5 E0A6 => E09F E0A0 E0A1 E0A2 E0A3 E0A4 E0A5 E0A6 group=CCCCCCCC
+0x80 E09F E0A0 E0A1 E0A2 E0A3 E0A4 E0A5 E0A6 => E09F E0A0 E0A1 E0A2 E0A3 E0A4 E0A5 E0A6 group=CCCCCCCC
+0xFF +kern -liga E09F E0A0 E0A1 E0A2 E0A3 E0A4 E0A5 E0A6 => E09F E0A0 E0A1 E0A2 E0A3 E0A4 E0A5 E0A6 group=CCCCCCCC
+=== font characters 22 ===
+0xFF E0A7 E0A8 E0A9 E0AA E0AB E0AC E0AD E0AE => E0A7 E0A8 E0A9 E0AA E0AB E0AC E0AD E0AE group=CCCCCCCC
+0x80 E0A7 E0A8 E0A9 E0AA E0AB E0AC E0AD E0AE => E0A7 E0A8 E0A9 E0AA E0AB E0AC E0AD E0AE group=CCCCCCCC
+0xFF +kern -liga E0A7 E0A8 E0A9 E0AA E0AB E0AC E0AD E0AE => E0A7 E0A8 E0A9 E0AA E0AB E0AC E0AD E0AE group=CCCCCCCC
+=== font characters 23 ===
+0xFF E0AF E0B0 E0B1 E0B2 E0B3 E0B4 E0B5 E0B6 => E0AF E0B0 E0B1 E0B2 E0B3 E0B4 E0B5 E0B6 group=CCCCCCCC
+0x80 E0AF E0B0 E0B1 E0B2 E0B3 E0B4 E0B5 E0B6 => E0AF E0B0 E0B1 E0B2 E0B3 E0B4 E0B5 E0B6 group=CCCCCCCC
+0xFF +kern -liga E0AF E0B0 E0B1 E0B2 E0B3 E0B4 E0B5 E0B6 => E0AF E0B0 E0B1 E0B2 E0B3 E0B4 E0B5 E0B6 group=CCCCCCCC
diff --git a/tests/data/shaping/NotoSansArabic-GSUB11Wrap-Synthetic.txt b/tests/data/shaping/NotoSansArabic-GSUB11Wrap-Synthetic.txt
new file mode 100644
index 000000000..fc0233178
--- /dev/null
+++ b/tests/data/shaping/NotoSansArabic-GSUB11Wrap-Synthetic.txt
@@ -0,0 +1,184 @@
+=== 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 => 0628 0640 0645 0644 E000 group=CCCCC gpos={"1":{"kashida":8}}
+0x80 0628 0640 0645 0644 0627 => 0628 0640 0645 0644 E000 group=CCCCC gpos={"1":{"kashida":8}}
+0xFF +kern -liga 0628 0640 0645 0644 0627 => 0628 0640 0645 0644 E000 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 4EA7 4F51 4FFB 50A5 514F 51F9 52A3 => 0020 4EA7 4F51 4FFB 50A5 514F 51F9 52A3 group=SCCCCCCC
+0x80 0020 4EA7 4F51 4FFB 50A5 514F 51F9 52A3 => 0020 4EA7 4F51 4FFB 50A5 514F 51F9 52A3 group=SCCCCCCC
+0xFF +kern -liga 0020 4EA7 4F51 4FFB 50A5 514F 51F9 52A3 => 0020 4EA7 4F51 4FFB 50A5 514F 51F9 52A3 group=SCCCCCCC
+=== font characters 1 ===
+0xFF 534D 53F7 54A1 554B 55F5 569F 5749 57F3 => 534D 53F7 54A1 554B 55F5 569F 5749 57F3 group=CCCCCCCC
+0x80 534D 53F7 54A1 554B 55F5 569F 5749 57F3 => 534D 53F7 54A1 554B 55F5 569F 5749 57F3 group=CCCCCCCC
+0xFF +kern -liga 534D 53F7 54A1 554B 55F5 569F 5749 57F3 => 534D 53F7 54A1 554B 55F5 569F 5749 57F3 group=CCCCCCCC
+=== font characters 2 ===
+0xFF 589D 5947 59F1 5A9B 5B45 5BEF 5C99 5D43 => 589D 5947 59F1 5A9B 5B45 5BEF 5C99 5D43 group=CCCCCCCC
+0x80 589D 5947 59F1 5A9B 5B45 5BEF 5C99 5D43 => 589D 5947 59F1 5A9B 5B45 5BEF 5C99 5D43 group=CCCCCCCC
+0xFF +kern -liga 589D 5947 59F1 5A9B 5B45 5BEF 5C99 5D43 => 589D 5947 59F1 5A9B 5B45 5BEF 5C99 5D43 group=CCCCCCCC
+=== font characters 3 ===
+0xFF 5DED 5E97 5F41 5FEB 6095 613F 61E9 6293 => 5DED 5E97 5F41 5FEB 6095 613F 61E9 6293 group=CCCCCCCC
+0x80 5DED 5E97 5F41 5FEB 6095 613F 61E9 6293 => 5DED 5E97 5F41 5FEB 6095 613F 61E9 6293 group=CCCCCCCC
+0xFF +kern -liga 5DED 5E97 5F41 5FEB 6095 613F 61E9 6293 => 5DED 5E97 5F41 5FEB 6095 613F 61E9 6293 group=CCCCCCCC
+=== font characters 4 ===
+0xFF 633D 63E7 6491 653B 65E5 668F 6739 67E3 => 633D 63E7 6491 653B 65E5 668F 6739 67E3 group=CCCCCCCC
+0x80 633D 63E7 6491 653B 65E5 668F 6739 67E3 => 633D 63E7 6491 653B 65E5 668F 6739 67E3 group=CCCCCCCC
+0xFF +kern -liga 633D 63E7 6491 653B 65E5 668F 6739 67E3 => 633D 63E7 6491 653B 65E5 668F 6739 67E3 group=CCCCCCCC
+=== font characters 5 ===
+0xFF 688D 6937 69E1 6A8B 6B35 6BDF 6C89 6D33 => 688D 6937 69E1 6A8B 6B35 6BDF 6C89 6D33 group=CCCCCCCC
+0x80 688D 6937 69E1 6A8B 6B35 6BDF 6C89 6D33 => 688D 6937 69E1 6A8B 6B35 6BDF 6C89 6D33 group=CCCCCCCC
+0xFF +kern -liga 688D 6937 69E1 6A8B 6B35 6BDF 6C89 6D33 => 688D 6937 69E1 6A8B 6B35 6BDF 6C89 6D33 group=CCCCCCCC
+=== font characters 6 ===
+0xFF 6DDD 6E87 6F31 6FDB 7085 712F 71D9 7283 => 6DDD 6E87 6F31 6FDB 7085 712F 71D9 7283 group=CCCCCCCC
+0x80 6DDD 6E87 6F31 6FDB 7085 712F 71D9 7283 => 6DDD 6E87 6F31 6FDB 7085 712F 71D9 7283 group=CCCCCCCC
+0xFF +kern -liga 6DDD 6E87 6F31 6FDB 7085 712F 71D9 7283 => 6DDD 6E87 6F31 6FDB 7085 712F 71D9 7283 group=CCCCCCCC
+=== font characters 7 ===
+0xFF 732D 73D7 7481 752B 75D5 767F 7729 77D3 => 732D 73D7 7481 752B 75D5 767F 7729 77D3 group=CCCCCCCC
+0x80 732D 73D7 7481 752B 75D5 767F 7729 77D3 => 732D 73D7 7481 752B 75D5 767F 7729 77D3 group=CCCCCCCC
+0xFF +kern -liga 732D 73D7 7481 752B 75D5 767F 7729 77D3 => 732D 73D7 7481 752B 75D5 767F 7729 77D3 group=CCCCCCCC
+=== font characters 8 ===
+0xFF 787D 7927 79D1 7A7B 7B25 7BCF 7C79 7D23 => 787D 7927 79D1 7A7B 7B25 7BCF 7C79 7D23 group=CCCCCCCC
+0x80 787D 7927 79D1 7A7B 7B25 7BCF 7C79 7D23 => 787D 7927 79D1 7A7B 7B25 7BCF 7C79 7D23 group=CCCCCCCC
+0xFF +kern -liga 787D 7927 79D1 7A7B 7B25 7BCF 7C79 7D23 => 787D 7927 79D1 7A7B 7B25 7BCF 7C79 7D23 group=CCCCCCCC
+=== font characters 9 ===
+0xFF 7DCD 7E77 7F21 7FCB 8075 811F 81C9 8273 => 7DCD 7E77 7F21 7FCB 8075 811F 81C9 8273 group=CCCCCCCC
+0x80 7DCD 7E77 7F21 7FCB 8075 811F 81C9 8273 => 7DCD 7E77 7F21 7FCB 8075 811F 81C9 8273 group=CCCCCCCC
+0xFF +kern -liga 7DCD 7E77 7F21 7FCB 8075 811F 81C9 8273 => 7DCD 7E77 7F21 7FCB 8075 811F 81C9 8273 group=CCCCCCCC
+=== font characters 10 ===
+0xFF 831D 83C7 8471 851B 85C5 866F 8719 87C3 => 831D 83C7 8471 851B 85C5 866F 8719 87C3 group=CCCCCCCC
+0x80 831D 83C7 8471 851B 85C5 866F 8719 87C3 => 831D 83C7 8471 851B 85C5 866F 8719 87C3 group=CCCCCCCC
+0xFF +kern -liga 831D 83C7 8471 851B 85C5 866F 8719 87C3 => 831D 83C7 8471 851B 85C5 866F 8719 87C3 group=CCCCCCCC
+=== font characters 11 ===
+0xFF 886D 8917 89C1 8A6B 8B15 8BBF 8C69 8D13 => 886D 8917 89C1 8A6B 8B15 8BBF 8C69 8D13 group=CCCCCCCC
+0x80 886D 8917 89C1 8A6B 8B15 8BBF 8C69 8D13 => 886D 8917 89C1 8A6B 8B15 8BBF 8C69 8D13 group=CCCCCCCC
+0xFF +kern -liga 886D 8917 89C1 8A6B 8B15 8BBF 8C69 8D13 => 886D 8917 89C1 8A6B 8B15 8BBF 8C69 8D13 group=CCCCCCCC
+=== font characters 12 ===
+0xFF 8DBD 8E67 8F11 8FBB 9065 910F 91B9 9263 => 8DBD 8E67 8F11 8FBB 9065 910F 91B9 9263 group=CCCCCCCC
+0x80 8DBD 8E67 8F11 8FBB 9065 910F 91B9 9263 => 8DBD 8E67 8F11 8FBB 9065 910F 91B9 9263 group=CCCCCCCC
+0xFF +kern -liga 8DBD 8E67 8F11 8FBB 9065 910F 91B9 9263 => 8DBD 8E67 8F11 8FBB 9065 910F 91B9 9263 group=CCCCCCCC
+=== font characters 13 ===
+0xFF 930D 93B7 9461 950B 95B5 965F 9709 97B3 => 930D 93B7 9461 950B 95B5 965F 9709 97B3 group=CCCCCCCC
+0x80 930D 93B7 9461 950B 95B5 965F 9709 97B3 => 930D 93B7 9461 950B 95B5 965F 9709 97B3 group=CCCCCCCC
+0xFF +kern -liga 930D 93B7 9461 950B 95B5 965F 9709 97B3 => 930D 93B7 9461 950B 95B5 965F 9709 97B3 group=CCCCCCCC
+=== font characters 14 ===
+0xFF 985D 9907 99B1 9A5B 9B05 9BAF 9C59 9D03 => 985D 9907 99B1 9A5B 9B05 9BAF 9C59 9D03 group=CCCCCCCC
+0x80 985D 9907 99B1 9A5B 9B05 9BAF 9C59 9D03 => 985D 9907 99B1 9A5B 9B05 9BAF 9C59 9D03 group=CCCCCCCC
+0xFF +kern -liga 985D 9907 99B1 9A5B 9B05 9BAF 9C59 9D03 => 985D 9907 99B1 9A5B 9B05 9BAF 9C59 9D03 group=CCCCCCCC
+=== font characters 15 ===
+0xFF 9DAD 9E57 9F01 9FAB A055 A0FF A1A9 A253 => 9DAD 9E57 9F01 9FAB A055 A0FF A1A9 A253 group=CCCCCCCC
+0x80 9DAD 9E57 9F01 9FAB A055 A0FF A1A9 A253 => 9DAD 9E57 9F01 9FAB A055 A0FF A1A9 A253 group=CCCCCCCC
+0xFF +kern -liga 9DAD 9E57 9F01 9FAB A055 A0FF A1A9 A253 => 9DAD 9E57 9F01 9FAB A055 A0FF A1A9 A253 group=CCCCCCCC
+=== font characters 16 ===
+0xFF A2FD A3A7 A451 A4FB A5A5 A64F A6F9 A7A3 => A2FD A3A7 A451 A4FB A5A5 A64F A6F9 A7A3 group=CCCCCCCC
+0x80 A2FD A3A7 A451 A4FB A5A5 A64F A6F9 A7A3 => A2FD A3A7 A451 A4FB A5A5 A64F A6F9 A7A3 group=CCCCCCCC
+0xFF +kern -liga A2FD A3A7 A451 A4FB A5A5 A64F A6F9 A7A3 => A2FD A3A7 A451 A4FB A5A5 A64F A6F9 A7A3 group=CCCCCCCC
+=== font characters 17 ===
+0xFF A84D A8F7 A9A1 AA4B AAF5 AB9F AC49 ACF3 => A84D A8F7 A9A1 AA4B AAF5 AB9F AC49 ACF3 group=CCCCCCCC
+0x80 A84D A8F7 A9A1 AA4B AAF5 AB9F AC49 ACF3 => A84D A8F7 A9A1 AA4B AAF5 AB9F AC49 ACF3 group=CCCCCCCC
+0xFF +kern -liga A84D A8F7 A9A1 AA4B AAF5 AB9F AC49 ACF3 => A84D A8F7 A9A1 AA4B AAF5 AB9F AC49 ACF3 group=CCCCCCCC
+=== font characters 18 ===
+0xFF AD9D AE47 AEF1 AF9B B045 B0EF B199 B243 => AD9D AE47 AEF1 AF9B B045 B0EF B199 B243 group=CCCCCCCC
+0x80 AD9D AE47 AEF1 AF9B B045 B0EF B199 B243 => AD9D AE47 AEF1 AF9B B045 B0EF B199 B243 group=CCCCCCCC
+0xFF +kern -liga AD9D AE47 AEF1 AF9B B045 B0EF B199 B243 => AD9D AE47 AEF1 AF9B B045 B0EF B199 B243 group=CCCCCCCC
+=== font characters 19 ===
+0xFF B2ED B397 B441 B4EB B595 B63F B6E9 B793 => B2ED B397 B441 B4EB B595 B63F B6E9 B793 group=CCCCCCCC
+0x80 B2ED B397 B441 B4EB B595 B63F B6E9 B793 => B2ED B397 B441 B4EB B595 B63F B6E9 B793 group=CCCCCCCC
+0xFF +kern -liga B2ED B397 B441 B4EB B595 B63F B6E9 B793 => B2ED B397 B441 B4EB B595 B63F B6E9 B793 group=CCCCCCCC
+=== font characters 20 ===
+0xFF B83D B8E7 B991 BA3B BAE5 BB8F BC39 BCE3 => B83D B8E7 B991 BA3B BAE5 BB8F BC39 BCE3 group=CCCCCCCC
+0x80 B83D B8E7 B991 BA3B BAE5 BB8F BC39 BCE3 => B83D B8E7 B991 BA3B BAE5 BB8F BC39 BCE3 group=CCCCCCCC
+0xFF +kern -liga B83D B8E7 B991 BA3B BAE5 BB8F BC39 BCE3 => B83D B8E7 B991 BA3B BAE5 BB8F BC39 BCE3 group=CCCCCCCC
+=== font characters 21 ===
+0xFF BD8D BE37 BEE1 BF8B C035 C0DF C189 C233 => BD8D BE37 BEE1 BF8B C035 C0DF C189 C233 group=CCCCCCCC
+0x80 BD8D BE37 BEE1 BF8B C035 C0DF C189 C233 => BD8D BE37 BEE1 BF8B C035 C0DF C189 C233 group=CCCCCCCC
+0xFF +kern -liga BD8D BE37 BEE1 BF8B C035 C0DF C189 C233 => BD8D BE37 BEE1 BF8B C035 C0DF C189 C233 group=CCCCCCCC
+=== font characters 22 ===
+0xFF C2DD C387 C431 C4DB C585 C62F C6D9 C783 => C2DD C387 C431 C4DB C585 C62F C6D9 C783 group=CCCCCCCC
+0x80 C2DD C387 C431 C4DB C585 C62F C6D9 C783 => C2DD C387 C431 C4DB C585 C62F C6D9 C783 group=CCCCCCCC
+0xFF +kern -liga C2DD C387 C431 C4DB C585 C62F C6D9 C783 => C2DD C387 C431 C4DB C585 C62F C6D9 C783 group=CCCCCCCC
+=== font characters 23 ===
+0xFF C82D C8D7 C981 CA2B CAD5 CB7F CC29 CCD3 => C82D C8D7 C981 CA2B CAD5 CB7F CC29 CCD3 group=CCCCCCCC
+0x80 C82D C8D7 C981 CA2B CAD5 CB7F CC29 CCD3 => C82D C8D7 C981 CA2B CAD5 CB7F CC29 CCD3 group=CCCCCCCC
+0xFF +kern -liga C82D C8D7 C981 CA2B CAD5 CB7F CC29 CCD3 => C82D C8D7 C981 CA2B CAD5 CB7F CC29 CCD3 group=CCCCCCCC
diff --git a/tests/data/shaping/SedanSC-EmptyFeature-Subset.txt b/tests/data/shaping/SedanSC-EmptyFeature-Subset.txt
new file mode 100644
index 000000000..be8834bae
--- /dev/null
+++ b/tests/data/shaping/SedanSC-EmptyFeature-Subset.txt
@@ -0,0 +1,92 @@
+=== latin ===
+0xFF 0041 0056 0041 0054 0061 0072 => 0041 0056 0041 0054 E000 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 E000 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 => 0628 0640 0645 0644 0627 group=CCCCC gpos={"1":{"kashida":8}}
+0x80 0628 0640 0645 0644 0627 => 0628 0640 0645 0644 0627 group=CCCCC gpos={"1":{"kashida":8}}
+0xFF +kern -liga 0628 0640 0645 0644 0627 => 0628 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 0041 0042 0061 0062 E000 E001 E002 => 0020 0041 0042 E000 E001 E000 E001 E002 group=SCCCCCCC
+0x80 0020 0041 0042 0061 0062 E000 E001 E002 => 0020 0041 0042 0061 0062 E000 E001 E002 group=SCCCCCCC
+0xFF +kern -liga 0020 0041 0042 0061 0062 E000 E001 E002 => 0020 0041 0042 E000 E001 E000 E001 E002 group=SCCCCCCC gpos={"6":{"XAdvanceL":-40,"XAdvanceR":-40}}
diff --git a/tests/data/subset/Carlito-MarkAttachmentType-Subset.json b/tests/data/subset/Carlito-MarkAttachmentType-Subset.json
new file mode 100644
index 000000000..f395565dd
--- /dev/null
+++ b/tests/data/subset/Carlito-MarkAttachmentType-Subset.json
@@ -0,0 +1,141 @@
+{
+ "_": "Generated by composer subset:update. See tests/Mpdf/Fonts/SubsetGoldenMaster.php.",
+ "useOTL=0x00": {
+ "characters": 5,
+ "makeSubset": {
+ "bytes": 4540,
+ "sha256": "c2a9b378305be0b8ebd27810ac8e9f6c95a44b164ca6b9a42f36c326a9c103ae",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x78D79A16, sha256 fd5f0101bc702aa5db66a77d6e7c85900cc984770e380f38af3db363c459e218",
+ "cmap": "96 bytes, checksum 0xFDBC06AE, sha256 0da3b7508e72c4aeb630d0ac86d6addf710ec8dffeffe142fe10aab0f4e92118",
+ "cvt ": "56 bytes, checksum 0x25FE01C2, sha256 e3db322ed3df4de6b36549931ce7f6d0935bacdc92a6821bf6e3c2a670046610",
+ "fpgm": "2677 bytes, checksum 0xDBB62E8C, sha256 3714687bfb8520386d0219ded888a94f719555081ce5c58fe6c86524202cf458",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "592 bytes, checksum 0x0D1B0968, sha256 dbced69058b90d0fc7277a30c174ab7aee97ad0f7c4ca3052c61c39c4dda841d",
+ "head": "54 bytes, checksum 0x0B117750, sha256 52297c13fa9f20fb8cb5d93a42ab73d6a108e236aa40aafbfc4423327be1234b",
+ "hhea": "36 bytes, checksum 0x0CE600A0, sha256 1385b89dfcc664468b95b65199384412061d94930da35299ed941d4c39535cde",
+ "hmtx": "40 bytes, checksum 0x16970162, sha256 87b33daa5cfc2ae71c6b8d4085d1ac205101733c6b5723f13b1cd6f70d819c11",
+ "loca": "22 bytes, checksum 0x02A2020A, sha256 56c2af74f2ee27b0633ed290f441ed8dd553093ac9141feb2903d4f8dc678556",
+ "maxp": "32 bytes, checksum 0x01D50C56, sha256 a7b58a79501f2224181dc424ec4012f5a01879d92bf0ecfba651e234f25634ff",
+ "name": "422 bytes, checksum 0x243C3B06, sha256 cc80a1a66d20a8ba6e26ace0f2e892d8d12f6f3c5f550216962bb98cae10111e",
+ "post": "32 bytes, checksum 0xFF9C00C2, sha256 5755f0a5cfde4cfda9160c38cbdb686eeefdf4708193f4fd81fce8f81dc682eb",
+ "prep": "129 bytes, checksum 0x9A783A38, sha256 3ef25e2da89a077e0fc85664b659aa52f31c8334ee7bdd299f575504f60a3c8d"
+ },
+ "maxUni": 65535,
+ "defaultWidth": 1038,
+ "codeToGlyph": "5 entries, sha256 71485bf2795e5d26b6698ee3cddc1fe155ef71a1d28bcf372975773b74d1480c"
+ },
+ "makeSubsetSIP": {
+ "bytes": 4536,
+ "sha256": "ebf37bc7663172cf1b91f617c621e9b36488716af16367fc5682f6a45b495a1c",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x78620F03, sha256 f95fc5c29593446c6760743556656e96bdb775013cb1f90ea9124123163c1990",
+ "cmap": "92 bytes, checksum 0x00300094, sha256 48cd1bfd713cfcc3cf1619bbfb08e9a4f8ad39538efb1fde30edfb0829efb5b0",
+ "cvt ": "56 bytes, checksum 0x25FE01C2, sha256 e3db322ed3df4de6b36549931ce7f6d0935bacdc92a6821bf6e3c2a670046610",
+ "fpgm": "2677 bytes, checksum 0xDBB62E8C, sha256 3714687bfb8520386d0219ded888a94f719555081ce5c58fe6c86524202cf458",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "592 bytes, checksum 0x0D1B0968, sha256 dbced69058b90d0fc7277a30c174ab7aee97ad0f7c4ca3052c61c39c4dda841d",
+ "head": "54 bytes, checksum 0x0B117750, sha256 5d6f095f264c5879eb075e003eb608da8a22768026a16ecd19d9cb4e98649f77",
+ "hhea": "36 bytes, checksum 0x0CE600A0, sha256 1385b89dfcc664468b95b65199384412061d94930da35299ed941d4c39535cde",
+ "hmtx": "40 bytes, checksum 0x16970162, sha256 87b33daa5cfc2ae71c6b8d4085d1ac205101733c6b5723f13b1cd6f70d819c11",
+ "loca": "22 bytes, checksum 0x02A2020A, sha256 56c2af74f2ee27b0633ed290f441ed8dd553093ac9141feb2903d4f8dc678556",
+ "maxp": "32 bytes, checksum 0x01D50C56, sha256 a7b58a79501f2224181dc424ec4012f5a01879d92bf0ecfba651e234f25634ff",
+ "name": "422 bytes, checksum 0x24353B06, sha256 fea5ba1e0769be1c90a1e805a45f36d3da6989673d4594c3af8ae3090f994af3",
+ "post": "32 bytes, checksum 0xFF9C00C2, sha256 5755f0a5cfde4cfda9160c38cbdb686eeefdf4708193f4fd81fce8f81dc682eb",
+ "prep": "129 bytes, checksum 0x9A783A38, sha256 3ef25e2da89a077e0fc85664b659aa52f31c8334ee7bdd299f575504f60a3c8d"
+ },
+ "maxUniChar": 65535,
+ "defaultWidth": 1038
+ },
+ "repackageTTF": {
+ "bytes": 5164,
+ "sha256": "2b46607d317a6a670944585cdf3a13d8e0c0141bcdd4fc090def6186051ba8c3",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x78D79A16, sha256 fd5f0101bc702aa5db66a77d6e7c85900cc984770e380f38af3db363c459e218",
+ "cmap": "72 bytes, checksum 0x00870413, sha256 1023fac740fbdf91b259632bfab37d8ff7bafe823c93c761e45606b9a6865bd6",
+ "cvt ": "56 bytes, checksum 0x25FE01C2, sha256 e3db322ed3df4de6b36549931ce7f6d0935bacdc92a6821bf6e3c2a670046610",
+ "fpgm": "2677 bytes, checksum 0xDBB62E8C, sha256 3714687bfb8520386d0219ded888a94f719555081ce5c58fe6c86524202cf458",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "1224 bytes, checksum 0x4CA9D281, sha256 70b9e2281ef31ba5098f76fcc9106a9e48b39c6be98db67f15b72d6f7a311cbe",
+ "head": "54 bytes, checksum 0x0B117750, sha256 af94f3a11e43a8378a3d381833bb32f93c34226bb8c66963e065e3e7de0d59a5",
+ "hhea": "36 bytes, checksum 0x0CE600A3, sha256 0b205b836c82a46e063393e057eac95fa9834e7ef3383141b559db65266461ab",
+ "hmtx": "52 bytes, checksum 0x1D850245, sha256 e1c3c5eac66ce3b48f052b0a0c47a8a98c26f70e1cdaba80f37de1221a3cb1f6",
+ "loca": "28 bytes, checksum 0x0708089A, sha256 3c76f17baead2d341af2bb63328f428045ddb091108e3c0d96b6eb060f7a293d",
+ "maxp": "32 bytes, checksum 0x01D80C56, sha256 5ba32950076f5fe3d1199fd95f3259bb5fb841dcbcbdf1df45ee6534e2589d7e",
+ "name": "422 bytes, checksum 0x243C3B06, sha256 cc80a1a66d20a8ba6e26ace0f2e892d8d12f6f3c5f550216962bb98cae10111e",
+ "post": "32 bytes, checksum 0xFF9C00C2, sha256 5755f0a5cfde4cfda9160c38cbdb686eeefdf4708193f4fd81fce8f81dc682eb",
+ "prep": "129 bytes, checksum 0x9A783A38, sha256 3ef25e2da89a077e0fc85664b659aa52f31c8334ee7bdd299f575504f60a3c8d"
+ },
+ "maxUni": 0
+ }
+ },
+ "useOTL=0xFF": {
+ "characters": 13,
+ "makeSubset": {
+ "bytes": 5240,
+ "sha256": "3c1bf9749af45e95877bb3016ac42c3996924d0bdbe2fb305f0908172431a9b7",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x78D79A16, sha256 fd5f0101bc702aa5db66a77d6e7c85900cc984770e380f38af3db363c459e218",
+ "cmap": "144 bytes, checksum 0x06C49EBA, sha256 80182390f0150f7632b5139fe6fd3a857768cb4d0906f93e6a23fdca70bb1c2d",
+ "cvt ": "56 bytes, checksum 0x25FE01C2, sha256 e3db322ed3df4de6b36549931ce7f6d0935bacdc92a6821bf6e3c2a670046610",
+ "fpgm": "2677 bytes, checksum 0xDBB62E8C, sha256 3714687bfb8520386d0219ded888a94f719555081ce5c58fe6c86524202cf458",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "1228 bytes, checksum 0x84039B25, sha256 f9e48d85dc64ac1df33fa668d6ecb2069e86b868569f802385f46c5f4c060e06",
+ "head": "54 bytes, checksum 0x0B117750, sha256 ecd32f00229cc26ff11c70614f94563e20c1b029327c0d6d0f2c4bfbfd6ec6c2",
+ "hhea": "36 bytes, checksum 0x0CE600A3, sha256 0b205b836c82a46e063393e057eac95fa9834e7ef3383141b559db65266461ab",
+ "hmtx": "52 bytes, checksum 0x1D850245, sha256 e1c3c5eac66ce3b48f052b0a0c47a8a98c26f70e1cdaba80f37de1221a3cb1f6",
+ "loca": "28 bytes, checksum 0x070C08A0, sha256 a64bc1e83d6dbef5b62805efd44a4af82040d5c820f41a36cf1907ba7bcc6da6",
+ "maxp": "32 bytes, checksum 0x01D80C56, sha256 5ba32950076f5fe3d1199fd95f3259bb5fb841dcbcbdf1df45ee6534e2589d7e",
+ "name": "422 bytes, checksum 0x243C3B06, sha256 cc80a1a66d20a8ba6e26ace0f2e892d8d12f6f3c5f550216962bb98cae10111e",
+ "post": "32 bytes, checksum 0xFF9C00C2, sha256 5755f0a5cfde4cfda9160c38cbdb686eeefdf4708193f4fd81fce8f81dc682eb",
+ "prep": "129 bytes, checksum 0x9A783A38, sha256 3ef25e2da89a077e0fc85664b659aa52f31c8334ee7bdd299f575504f60a3c8d"
+ },
+ "maxUni": 65535,
+ "defaultWidth": 1038,
+ "codeToGlyph": "13 entries, sha256 d382373a79b85a6555735fe10f8b56a1d75a783f6a4d5a3c080097e16acd1bd3"
+ },
+ "makeSubsetSIP": {
+ "bytes": 5220,
+ "sha256": "c17db14748a904a3e2cbb3ba8173f78041d4fb244afcae2fe43cf31cf2d23bd7",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x78620F0B, sha256 cee942d42ecacec7fa716a9426c449518164cf981baf0610fcee0ab2ea7c8b66",
+ "cmap": "124 bytes, checksum 0x008C00FB, sha256 6b9156b829c8b5c71b6ee076927a6edf575a9969ee70c18af60f170b8d0dbdb1",
+ "cvt ": "56 bytes, checksum 0x25FE01C2, sha256 e3db322ed3df4de6b36549931ce7f6d0935bacdc92a6821bf6e3c2a670046610",
+ "fpgm": "2677 bytes, checksum 0xDBB62E8C, sha256 3714687bfb8520386d0219ded888a94f719555081ce5c58fe6c86524202cf458",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "1228 bytes, checksum 0x84069B28, sha256 6cde1ad6a24a3c4b2a71e31b879d71d8bd515ea36eca6ad23760e52b4ff6ab5e",
+ "head": "54 bytes, checksum 0x0B117750, sha256 467e33e15e18c3b149a71ade184f4b70ea4e4436c144f4f1f061d2313c31a9f0",
+ "hhea": "36 bytes, checksum 0x0CE600A3, sha256 0b205b836c82a46e063393e057eac95fa9834e7ef3383141b559db65266461ab",
+ "hmtx": "52 bytes, checksum 0x1D850245, sha256 814b648e96ab77417424e1259cc2dc46a9a739a2d6a113ccd69bbeaeee36dba0",
+ "loca": "28 bytes, checksum 0x067007C8, sha256 9ebef80ca31ec11c91472109080c6330e15f85f82f167e9453d2be31f648eb94",
+ "maxp": "32 bytes, checksum 0x01D80C56, sha256 5ba32950076f5fe3d1199fd95f3259bb5fb841dcbcbdf1df45ee6534e2589d7e",
+ "name": "422 bytes, checksum 0x24353B06, sha256 fea5ba1e0769be1c90a1e805a45f36d3da6989673d4594c3af8ae3090f994af3",
+ "post": "32 bytes, checksum 0xFF9C00C2, sha256 5755f0a5cfde4cfda9160c38cbdb686eeefdf4708193f4fd81fce8f81dc682eb",
+ "prep": "129 bytes, checksum 0x9A783A38, sha256 3ef25e2da89a077e0fc85664b659aa52f31c8334ee7bdd299f575504f60a3c8d"
+ },
+ "maxUniChar": 65535,
+ "defaultWidth": 1038
+ },
+ "repackageTTF": {
+ "bytes": 5228,
+ "sha256": "6de0498c902df420e1d939dda282ea394192d84d4d7f2cb9184d3d67befdb369",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x78D79A16, sha256 fd5f0101bc702aa5db66a77d6e7c85900cc984770e380f38af3db363c459e218",
+ "cmap": "134 bytes, checksum 0xE3D3C19E, sha256 bc6adace54e90c1fa5c672f99263f19b23d77e5a8505d808a25ea7784d4200d0",
+ "cvt ": "56 bytes, checksum 0x25FE01C2, sha256 e3db322ed3df4de6b36549931ce7f6d0935bacdc92a6821bf6e3c2a670046610",
+ "fpgm": "2677 bytes, checksum 0xDBB62E8C, sha256 3714687bfb8520386d0219ded888a94f719555081ce5c58fe6c86524202cf458",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "1224 bytes, checksum 0x4CA9D281, sha256 70b9e2281ef31ba5098f76fcc9106a9e48b39c6be98db67f15b72d6f7a311cbe",
+ "head": "54 bytes, checksum 0x0B117750, sha256 67631db3089b53fb31279345d1afdf6dd53f1a776df9ec10c651ba3d3112b361",
+ "hhea": "36 bytes, checksum 0x0CE600A3, sha256 0b205b836c82a46e063393e057eac95fa9834e7ef3383141b559db65266461ab",
+ "hmtx": "52 bytes, checksum 0x1D850245, sha256 e1c3c5eac66ce3b48f052b0a0c47a8a98c26f70e1cdaba80f37de1221a3cb1f6",
+ "loca": "28 bytes, checksum 0x0708089A, sha256 3c76f17baead2d341af2bb63328f428045ddb091108e3c0d96b6eb060f7a293d",
+ "maxp": "32 bytes, checksum 0x01D80C56, sha256 5ba32950076f5fe3d1199fd95f3259bb5fb841dcbcbdf1df45ee6534e2589d7e",
+ "name": "422 bytes, checksum 0x243C3B06, sha256 cc80a1a66d20a8ba6e26ace0f2e892d8d12f6f3c5f550216962bb98cae10111e",
+ "post": "32 bytes, checksum 0xFF9C00C2, sha256 5755f0a5cfde4cfda9160c38cbdb686eeefdf4708193f4fd81fce8f81dc682eb",
+ "prep": "129 bytes, checksum 0x9A783A38, sha256 3ef25e2da89a077e0fc85664b659aa52f31c8334ee7bdd299f575504f60a3c8d"
+ },
+ "maxUni": 0
+ }
+ }
+}
diff --git a/tests/data/subset/Molengo-GSUB52Class0-Subset.json b/tests/data/subset/Molengo-GSUB52Class0-Subset.json
new file mode 100644
index 000000000..41f43927e
--- /dev/null
+++ b/tests/data/subset/Molengo-GSUB52Class0-Subset.json
@@ -0,0 +1,141 @@
+{
+ "_": "Generated by composer subset:update. See tests/Mpdf/Fonts/SubsetGoldenMaster.php.",
+ "useOTL=0x00": {
+ "characters": 10,
+ "makeSubset": {
+ "bytes": 4408,
+ "sha256": "d434ff925f5b5bc7b2ee277121102ffc3a60d95c67dcdce00c2d9cb2ef92a252",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x75E46B84, sha256 ee253da421d103c42ca28e0e55736e74c7aea0ee78a3042e089c40f111929eda",
+ "cmap": "146 bytes, checksum 0x09F309B1, sha256 dc25a1342824950b062a1d1f214b2a8a71fd8af03efb69d973faf97e1c85c67f",
+ "cvt ": "48 bytes, checksum 0x17FD0532, sha256 44c0dafeb26a8bf9c71a449fed0d6b8441c94b2c894d890d7ca5a564a1955bfc",
+ "fpgm": "1865 bytes, checksum 0x4179FF97, sha256 829f333b3c9c7e8defe7eaaef94648ccfeccacb615ca9e5eb31c383cab31cfe1",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "1136 bytes, checksum 0x5ABF546E, sha256 ea91b967956e71a295c403c71d814da74826db66a9d23d36d435fc57e465356c",
+ "head": "54 bytes, checksum 0xFCEDC3CD, sha256 622995b1eb60ae11221f16156b7aa7780899571793902016d71cf3213552aeea",
+ "hhea": "36 bytes, checksum 0x0DD10453, sha256 922c105650942e4e0eaf9fa485ff2dde7b22de7fed371ec8f51a1eaf46f329e3",
+ "hmtx": "64 bytes, checksum 0x1767FE0E, sha256 cff6884681b78640bd29f14bab46de0aee81c4de887f30bbc9e5c5e9b565256c",
+ "loca": "34 bytes, checksum 0x08640780, sha256 08bf6ca2773dcffdf4d6fdb6c747d0f493e3ae5259a4374ad021c3a3359b68a0",
+ "maxp": "32 bytes, checksum 0x00F5082D, sha256 aaa0db599fd3e03384b3f7c771c6ee5b38dd93b147fdbf4d106142e14e027843",
+ "name": "500 bytes, checksum 0x283B3F4F, sha256 900c2bc9b1ddad4fbfc9d578e9a24a116a4b63f0d08cba92077f701e74d495b6",
+ "post": "32 bytes, checksum 0xFF12005A, sha256 6bf3093f805df1de7c9c099e6d922957801b768518d8d0e7ed42ea6ca7131fb1",
+ "prep": "111 bytes, checksum 0x7448E75E, sha256 1040160105cb46aed32c3d7aa1fe9c5badd154f5bc93c399c8110fab5d9d2e60"
+ },
+ "maxUni": 65535,
+ "defaultWidth": 886,
+ "codeToGlyph": "10 entries, sha256 46b7126035c7f89fd0b822976b9b3539d87832080e7e3717d01f72f1b0da6863"
+ },
+ "makeSubsetSIP": {
+ "bytes": 4360,
+ "sha256": "4a7596df688d91b5c4fc0de33b96fb8bfd8aaa9d7b4344d0842314491c6d3c57",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x7973E96B, sha256 857bd041a7023cc86c67a7c856847df9a3629243d3db298ea9767b62f9d7327c",
+ "cmap": "112 bytes, checksum 0x007700B7, sha256 50ee851afa0fee6e04adb5e69494254cf010dbfacf8f246ad1dfd6370e17ad5c",
+ "cvt ": "48 bytes, checksum 0x17FD0532, sha256 44c0dafeb26a8bf9c71a449fed0d6b8441c94b2c894d890d7ca5a564a1955bfc",
+ "fpgm": "1865 bytes, checksum 0x4179FF97, sha256 829f333b3c9c7e8defe7eaaef94648ccfeccacb615ca9e5eb31c383cab31cfe1",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "1136 bytes, checksum 0x5AB35468, sha256 aee2d574dc8222aea13a0225de2e87abc872d8bc0fed225e3fc8fbdb79cbecd2",
+ "head": "54 bytes, checksum 0xFCEDC3CD, sha256 f5fd6c314b7896e8c06b0d6a3a45d98da85ee0537cae3eb81e504e56227a909a",
+ "hhea": "36 bytes, checksum 0x0DD10451, sha256 079a7765850ab9b2c5776e397439c4d02556cad3bf813b99ee9e19e7e17dcccf",
+ "hmtx": "56 bytes, checksum 0x1767FE0E, sha256 4f23f147875bbd70c01b6ee833f93fbf78b64fd81317493c2006867bac6628f9",
+ "loca": "30 bytes, checksum 0x08640780, sha256 c05cc02fb0a1f0efa65fec60686121afd60b5405e8d5424f83a5223799da15f2",
+ "maxp": "32 bytes, checksum 0x00F3082D, sha256 c8a0e4a3cdafd92646e4812e1f3287ef19a3ce1892960dcdf2eb5caaad737417",
+ "name": "500 bytes, checksum 0x28343F4F, sha256 db3212125e5b0b8277c197da6cc3141b6dd6859a49cdc5281392ec4796cad4f5",
+ "post": "32 bytes, checksum 0xFF12005A, sha256 6bf3093f805df1de7c9c099e6d922957801b768518d8d0e7ed42ea6ca7131fb1",
+ "prep": "111 bytes, checksum 0x7448E75E, sha256 1040160105cb46aed32c3d7aa1fe9c5badd154f5bc93c399c8110fab5d9d2e60"
+ },
+ "maxUniChar": 65535,
+ "defaultWidth": 886
+ },
+ "repackageTTF": {
+ "bytes": 8732,
+ "sha256": "836386abd9d7f8af27641affff5549d3a7ea0e20c18a77307d4de7128674a3d3",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x75E46B84, sha256 ee253da421d103c42ca28e0e55736e74c7aea0ee78a3042e089c40f111929eda",
+ "cmap": "108 bytes, checksum 0x0DDF0BBB, sha256 60e0acd470c74bd2a6e4b482300b92cd5bbb7b7ea4512c5b599eddec816e7080",
+ "cvt ": "48 bytes, checksum 0x17FD0532, sha256 44c0dafeb26a8bf9c71a449fed0d6b8441c94b2c894d890d7ca5a564a1955bfc",
+ "fpgm": "1865 bytes, checksum 0x4179FF97, sha256 829f333b3c9c7e8defe7eaaef94648ccfeccacb615ca9e5eb31c383cab31cfe1",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "1122 bytes, checksum 0x59885FF8, sha256 02c08eaefbefdf5c85b0b7aefdf6a889c3d82464b2429c1879550d5d3240417a",
+ "head": "54 bytes, checksum 0xFCEDC3CD, sha256 a8935390d69e563055487dcccbcd0948409f53dee7e421d69dc7abcffc7a6809",
+ "hhea": "36 bytes, checksum 0x0DD105B8, sha256 56118c1064548c884609deb6648aadc12d81c8c221e77982d6a2c8d5b4cafaf5",
+ "hmtx": "1492 bytes, checksum 0x1767FE0E, sha256 32c288ccca35fb8a50357ab3189eaa257aa81bc10056b9b36cadd162c8fb9239",
+ "loca": "748 bytes, checksum 0x99F49B69, sha256 d1393554612c6569f844c23854102130953b21a55603c7d46b374ffac2f6a8e2",
+ "maxp": "32 bytes, checksum 0x025A082D, sha256 36f47c6798b386499c18b76a4a79bbe305d0281d83b93973f86f34b0748c3975",
+ "name": "500 bytes, checksum 0x283B3F4F, sha256 900c2bc9b1ddad4fbfc9d578e9a24a116a4b63f0d08cba92077f701e74d495b6",
+ "post": "2268 bytes, checksum 0xEF711D54, sha256 24460e5d3c428404998920f40ccee7e35dd55d8e8e8cdc4d725c09ab79b1f035",
+ "prep": "111 bytes, checksum 0x7448E75E, sha256 1040160105cb46aed32c3d7aa1fe9c5badd154f5bc93c399c8110fab5d9d2e60"
+ },
+ "maxUni": 0
+ }
+ },
+ "useOTL=0xFF": {
+ "characters": 373,
+ "makeSubset": {
+ "bytes": 7344,
+ "sha256": "1f353358babe08cd8fcc3352ede8058842524f08d2700547c9b361f32054f0d7",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x75E46B84, sha256 ee253da421d103c42ca28e0e55736e74c7aea0ee78a3042e089c40f111929eda",
+ "cmap": "944 bytes, checksum 0x3BD3DD9F, sha256 f759f3d554728f8c8724b8b0c8ad8be2ff059ff3295aa0a6c3777626372b9ba7",
+ "cvt ": "48 bytes, checksum 0x17FD0532, sha256 44c0dafeb26a8bf9c71a449fed0d6b8441c94b2c894d890d7ca5a564a1955bfc",
+ "fpgm": "1865 bytes, checksum 0x4179FF97, sha256 829f333b3c9c7e8defe7eaaef94648ccfeccacb615ca9e5eb31c383cab31cfe1",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "1136 bytes, checksum 0x6215576C, sha256 d26907d63c4476faddcf42bf6551e421ba45a1c3a5cb57bf352330c40e72701a",
+ "head": "54 bytes, checksum 0xFCEDC3CD, sha256 dfdf533c2b8bc7d166048bcb60acc340c77c35335ae2b5d1e0c98f409e2465d9",
+ "hhea": "36 bytes, checksum 0x0DD105B8, sha256 56118c1064548c884609deb6648aadc12d81c8c221e77982d6a2c8d5b4cafaf5",
+ "hmtx": "1492 bytes, checksum 0x1767FE0E, sha256 32c288ccca35fb8a50357ab3189eaa257aa81bc10056b9b36cadd162c8fb9239",
+ "loca": "748 bytes, checksum 0x9BEA9D64, sha256 6d0a1eba9dfb620a1468dad795d45563cb72860581e41dc61d5e38777f12151a",
+ "maxp": "32 bytes, checksum 0x025A082D, sha256 36f47c6798b386499c18b76a4a79bbe305d0281d83b93973f86f34b0748c3975",
+ "name": "500 bytes, checksum 0x283B3F4F, sha256 900c2bc9b1ddad4fbfc9d578e9a24a116a4b63f0d08cba92077f701e74d495b6",
+ "post": "32 bytes, checksum 0xFF12005A, sha256 6bf3093f805df1de7c9c099e6d922957801b768518d8d0e7ed42ea6ca7131fb1",
+ "prep": "111 bytes, checksum 0x7448E75E, sha256 1040160105cb46aed32c3d7aa1fe9c5badd154f5bc93c399c8110fab5d9d2e60"
+ },
+ "maxUni": 65535,
+ "defaultWidth": 886,
+ "codeToGlyph": "373 entries, sha256 b007c346a8ff05c7f81c3e3ea579513c0961b9725043b481b10f59a6cfb92980"
+ },
+ "makeSubsetSIP": {
+ "bytes": 7964,
+ "sha256": "b5a85a113f4bcb98395b52ec3ae1d41278f6854440878a56053fdf1a90940778",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x7973EAD6, sha256 4e4f05d2493e53fbe192fff81b8c4905e67a5e54abb5cc5cc82c98d1e6a36224",
+ "cmap": "1564 bytes, checksum 0x137916B7, sha256 af46ad33374ad0d645701c4238f4a456621d4c3285f1470163f1641b6021e077",
+ "cvt ": "48 bytes, checksum 0x17FD0532, sha256 44c0dafeb26a8bf9c71a449fed0d6b8441c94b2c894d890d7ca5a564a1955bfc",
+ "fpgm": "1865 bytes, checksum 0x4179FF97, sha256 829f333b3c9c7e8defe7eaaef94648ccfeccacb615ca9e5eb31c383cab31cfe1",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "1136 bytes, checksum 0x5EF15778, sha256 f39e3a3df0249ef7ccfec98143140ee3bdbf4ef31317e1d587159780466e472e",
+ "head": "54 bytes, checksum 0xFCEDC3CD, sha256 9a3ea3fd1d30f1f67121a5ab6a48f0b045b6adbc6693e47f554428c7f9ec3d8a",
+ "hhea": "36 bytes, checksum 0x0DD105B8, sha256 56118c1064548c884609deb6648aadc12d81c8c221e77982d6a2c8d5b4cafaf5",
+ "hmtx": "1492 bytes, checksum 0x1767FE0E, sha256 d3b8fcd789415dae1dbf22a5b261b6d65fdb9d2bf06b29bb2c7938a551863c4e",
+ "loca": "748 bytes, checksum 0x51915332, sha256 e5848f01b125cd1641a5851fca26b7c2014e4667f4feba56e2f2622017774d5c",
+ "maxp": "32 bytes, checksum 0x025A082D, sha256 36f47c6798b386499c18b76a4a79bbe305d0281d83b93973f86f34b0748c3975",
+ "name": "500 bytes, checksum 0x28343F4F, sha256 db3212125e5b0b8277c197da6cc3141b6dd6859a49cdc5281392ec4796cad4f5",
+ "post": "32 bytes, checksum 0xFF12005A, sha256 6bf3093f805df1de7c9c099e6d922957801b768518d8d0e7ed42ea6ca7131fb1",
+ "prep": "111 bytes, checksum 0x7448E75E, sha256 1040160105cb46aed32c3d7aa1fe9c5badd154f5bc93c399c8110fab5d9d2e60"
+ },
+ "maxUniChar": 65535,
+ "defaultWidth": 886
+ },
+ "repackageTTF": {
+ "bytes": 9560,
+ "sha256": "4233126bb7bf1895dff7e0875d5551461e1cab0d77e4913b24c93741773779cd",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x75E46B84, sha256 ee253da421d103c42ca28e0e55736e74c7aea0ee78a3042e089c40f111929eda",
+ "cmap": "934 bytes, checksum 0x1B2FFE36, sha256 b643c40adbaafbcff1f6bda18b078d069d5399c06c3b203d76b41aa0c5468228",
+ "cvt ": "48 bytes, checksum 0x17FD0532, sha256 44c0dafeb26a8bf9c71a449fed0d6b8441c94b2c894d890d7ca5a564a1955bfc",
+ "fpgm": "1865 bytes, checksum 0x4179FF97, sha256 829f333b3c9c7e8defe7eaaef94648ccfeccacb615ca9e5eb31c383cab31cfe1",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "1122 bytes, checksum 0x59885FF8, sha256 02c08eaefbefdf5c85b0b7aefdf6a889c3d82464b2429c1879550d5d3240417a",
+ "head": "54 bytes, checksum 0xFCEDC3CD, sha256 6a3e99c9a95a53e80f73abe901fcd871949706e33c9a922b2efa416fa9874b8e",
+ "hhea": "36 bytes, checksum 0x0DD105B8, sha256 56118c1064548c884609deb6648aadc12d81c8c221e77982d6a2c8d5b4cafaf5",
+ "hmtx": "1492 bytes, checksum 0x1767FE0E, sha256 32c288ccca35fb8a50357ab3189eaa257aa81bc10056b9b36cadd162c8fb9239",
+ "loca": "748 bytes, checksum 0x99F49B69, sha256 d1393554612c6569f844c23854102130953b21a55603c7d46b374ffac2f6a8e2",
+ "maxp": "32 bytes, checksum 0x025A082D, sha256 36f47c6798b386499c18b76a4a79bbe305d0281d83b93973f86f34b0748c3975",
+ "name": "500 bytes, checksum 0x283B3F4F, sha256 900c2bc9b1ddad4fbfc9d578e9a24a116a4b63f0d08cba92077f701e74d495b6",
+ "post": "2268 bytes, checksum 0xEF711D54, sha256 24460e5d3c428404998920f40ccee7e35dd55d8e8e8cdc4d725c09ab79b1f035",
+ "prep": "111 bytes, checksum 0x7448E75E, sha256 1040160105cb46aed32c3d7aa1fe9c5badd154f5bc93c399c8110fab5d9d2e60"
+ },
+ "maxUni": 0
+ }
+ }
+}
diff --git a/tests/data/subset/NotoSansArabic-GSUB11Wrap-Synthetic.json b/tests/data/subset/NotoSansArabic-GSUB11Wrap-Synthetic.json
new file mode 100644
index 000000000..76c04d2c9
--- /dev/null
+++ b/tests/data/subset/NotoSansArabic-GSUB11Wrap-Synthetic.json
@@ -0,0 +1,117 @@
+{
+ "_": "Generated by composer subset:update. See tests/Mpdf/Fonts/SubsetGoldenMaster.php.",
+ "useOTL=0x00": {
+ "characters": 32770,
+ "makeSubset": {
+ "bytes": 263512,
+ "sha256": "14bf1707ac349bfb896ba2b9d57930482b19082f8750789b350b872f14a904ee",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x83C42380, sha256 a9aae6f5015b805e58965c9f49b1ce65c2952d881034ce23c9d4c1032f5eb051",
+ "cmap": "65634 bytes, checksum 0x243E86FA, sha256 19fe0d0e24adb3f6168a0100b630697fa7f9121038eda21c12286a4f032ba36f",
+ "glyf": "280 bytes, checksum 0x7E05754F, sha256 d461908c7946b227247f9d2f1e8778bc50256c2fec4da8ed71f154bb25558890",
+ "head": "54 bytes, checksum 0x26907313, sha256 fead9e2f48bb89e90bcd27df4072457ee92ac2b457effb04cf4d0b7315855097",
+ "hhea": "36 bytes, checksum 0x092E7E23, sha256 8329cfdae3620de0e031f5360e3430c500f458cb6f1dd7653f57e61fcb3f27ba",
+ "hmtx": "131084 bytes, checksum 0x0708010C, sha256 df6ea878c1b33df79b88ebd3619b436d12b1d08869e72923709c2f1c58378eee",
+ "loca": "65544 bytes, checksum 0x008B00A0, sha256 1327b121a13405755727b5d1090c0708ad357d02027efc2445a7e22d9ee4bed0",
+ "maxp": "32 bytes, checksum 0x80070034, sha256 e47a8c23c74980a1e15db2899d8f8313094fc8ed227fdcc274df54de08c02863",
+ "name": "544 bytes, checksum 0x2E3145B0, sha256 941746858bd019b7dac7e3ba9146b4d976d37b18a50da1df5ba563b152c2a773",
+ "post": "32 bytes, checksum 0xFF9F0032, sha256 4fdc83d5e42fe44650fe86f15a645d5cc926d07355ba945e1ede42737607bf83"
+ },
+ "maxUni": 65535,
+ "defaultWidth": 600,
+ "codeToGlyph": "32770 entries, sha256 da9072bac1c28e45a0226ace491abec353fdb84b4ce9ecc53dbed42ef2a2cb24"
+ },
+ "makeSubsetSIP": {
+ "bytes": 328960,
+ "sha256": "34170f0783c6fc69d8687a44da8e478058f3e3298f202b47db4536fc7790f169",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x69A35685, sha256 5215cdffe0ff65edc4fb2da4d6fe596341df5f1a6ae4cb89ac86358ef8dc904a",
+ "cmap": "131152 bytes, checksum 0xA03E0057, sha256 e7d4f8d9a1e95b3d97a273892cf53f170259edc9d36030218fda3077e1d7ba97",
+ "glyf": "216 bytes, checksum 0xDA855C9E, sha256 70e1b514e67c939fd608d245c43b114a5b2a0b10f20c9d33c0e6d86a66470f30",
+ "head": "54 bytes, checksum 0x26907313, sha256 bcc462f8a3511693816f063eb920d053b7f5d3c48920e728e86934bd014b4b8a",
+ "hhea": "36 bytes, checksum 0x092E7E22, sha256 049c5d99fe0ea7b1fa71d91e36f9ef0a0a8af5b68656025f2b936fc0ed1742e5",
+ "hmtx": "131080 bytes, checksum 0x04B000C4, sha256 bb03ba5b0d5186c4a3c148787895d6a764aa87986362558ef1650fc59ebd4ae1",
+ "loca": "65542 bytes, checksum 0x002EFFCA, sha256 0d0759c43c8d3b3caefef2858fbdb87a036e244e60326c8abe1a9f1998db3df3",
+ "maxp": "32 bytes, checksum 0x80060034, sha256 ca811bcca93c03ad0cab957d6fa1a8648d7e0bfb3553bd87ea1ad68b58d17e36",
+ "name": "544 bytes, checksum 0x2E2A45B0, sha256 072a7c05088244ee3582476baf8b33b89b6a050fdefd9907b0097437ff51f0dd",
+ "post": "32 bytes, checksum 0xFF9F0032, sha256 4fdc83d5e42fe44650fe86f15a645d5cc926d07355ba945e1ede42737607bf83"
+ },
+ "maxUniChar": 65535,
+ "defaultWidth": 600
+ },
+ "repackageTTF": {
+ "bytes": 132404,
+ "sha256": "a42743bf35c8db217a8983ccdf7ebe22c8c353baa04b51f8671237d85b1991ed",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x83C42380, sha256 a9aae6f5015b805e58965c9f49b1ce65c2952d881034ce23c9d4c1032f5eb051",
+ "cmap": "72 bytes, checksum 0x863F54B7, sha256 770cfb6cad2f7bf7681cf721fb58ac00ec9ccc54775a2239dfc0bf3e292ea24c",
+ "glyf": "276 bytes, checksum 0x7E7B74DC, sha256 3453ef47e98a6dd797ae61efa604f6120f6d411cff967240e739ff499147e071",
+ "head": "54 bytes, checksum 0x26907313, sha256 236b71fb3e2f9e085285553c1a4a4bbeea45508b8d97a1766febf5934343483e",
+ "hhea": "36 bytes, checksum 0x092DFE21, sha256 9d376f195b2d98859b1d719bfb2f9e5e4b2d98559ec563e8384b293dd24ace0c",
+ "hmtx": "65544 bytes, checksum 0x02BE00A6, sha256 30ae8773de73e9f642e15f027b9bf7baeb83809fbc0dd0add86b566720280b69",
+ "loca": "65544 bytes, checksum 0xC089C09E, sha256 2071e00324c703a6fbf28d38bb05333de91b8561c0454590b04fc87f30fdfeae",
+ "maxp": "32 bytes, checksum 0x80070034, sha256 e47a8c23c74980a1e15db2899d8f8313094fc8ed227fdcc274df54de08c02863",
+ "name": "544 bytes, checksum 0x2E3145B0, sha256 941746858bd019b7dac7e3ba9146b4d976d37b18a50da1df5ba563b152c2a773",
+ "post": "32 bytes, checksum 0xFF9F0032, sha256 4fdc83d5e42fe44650fe86f15a645d5cc926d07355ba945e1ede42737607bf83"
+ },
+ "maxUni": 0
+ }
+ },
+ "useOTL=0xFF": {
+ "characters": 32771,
+ "makeSubset": {
+ "bytes": 263520,
+ "sha256": "f70c7efd8ee8900041704461a6f5427f03cd00abcd640ff3159cbe2bd51578ad",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x83C42380, sha256 a9aae6f5015b805e58965c9f49b1ce65c2952d881034ce23c9d4c1032f5eb051",
+ "cmap": "65644 bytes, checksum 0x9221F926, sha256 de2df2502dcb2852eef7bddc8f29f79feb9f3b7aab65dd18acad7f66eacd76d6",
+ "glyf": "280 bytes, checksum 0x7E05754F, sha256 d461908c7946b227247f9d2f1e8778bc50256c2fec4da8ed71f154bb25558890",
+ "head": "54 bytes, checksum 0x26907313, sha256 1389e4b3ab304ed3f03c3818a86b609081e4307532126210e7c19812f0ad552c",
+ "hhea": "36 bytes, checksum 0x092E7E23, sha256 8329cfdae3620de0e031f5360e3430c500f458cb6f1dd7653f57e61fcb3f27ba",
+ "hmtx": "131084 bytes, checksum 0x0708010C, sha256 df6ea878c1b33df79b88ebd3619b436d12b1d08869e72923709c2f1c58378eee",
+ "loca": "65544 bytes, checksum 0x008B00A0, sha256 1327b121a13405755727b5d1090c0708ad357d02027efc2445a7e22d9ee4bed0",
+ "maxp": "32 bytes, checksum 0x80070034, sha256 e47a8c23c74980a1e15db2899d8f8313094fc8ed227fdcc274df54de08c02863",
+ "name": "544 bytes, checksum 0x2E3145B0, sha256 941746858bd019b7dac7e3ba9146b4d976d37b18a50da1df5ba563b152c2a773",
+ "post": "32 bytes, checksum 0xFF9F0032, sha256 4fdc83d5e42fe44650fe86f15a645d5cc926d07355ba945e1ede42737607bf83"
+ },
+ "maxUni": 65535,
+ "defaultWidth": 600,
+ "codeToGlyph": "32771 entries, sha256 4a011a02c6f37e7f1e65c7c5df00ebed381b44d46a874ba77b1de0fb95bd4745"
+ },
+ "makeSubsetSIP": {
+ "bytes": 329032,
+ "sha256": "ca4857cb1c6e949e3abadd527bbbd305735227a3b4687003115a592b23fb3f2a",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x69A35686, sha256 fe6e701dfe345fd8e63a6a8471957d43e5ea1a6de8c739be451f699bd0473cdd",
+ "cmap": "131156 bytes, checksum 0xE025C07E, sha256 dea42deee74fe5c203f583ff25ecf8624025afa4f908915959195d9a35bf945e",
+ "glyf": "280 bytes, checksum 0x7E05754F, sha256 c1e32cbca0458183228bb1d91a482d4a1215e8ba9619ccb3b6747b18f2b74d43",
+ "head": "54 bytes, checksum 0x26907313, sha256 b3ca160a62c62c183162040cf75e76380266183d3cb01143779910f5bdb363ee",
+ "hhea": "36 bytes, checksum 0x092E7E23, sha256 8329cfdae3620de0e031f5360e3430c500f458cb6f1dd7653f57e61fcb3f27ba",
+ "hmtx": "131084 bytes, checksum 0x0708010C, sha256 5d92708d1bad519433decc7b2644c18659e798b8ec8a287819d0d1257a32c56d",
+ "loca": "65544 bytes, checksum 0x002F0056, sha256 cbeee760d3efd08910014626c7f3871398d25e9bd3ff0f60366b16b59d1b420f",
+ "maxp": "32 bytes, checksum 0x80070034, sha256 e47a8c23c74980a1e15db2899d8f8313094fc8ed227fdcc274df54de08c02863",
+ "name": "544 bytes, checksum 0x2E2A45B0, sha256 072a7c05088244ee3582476baf8b33b89b6a050fdefd9907b0097437ff51f0dd",
+ "post": "32 bytes, checksum 0xFF9F0032, sha256 4fdc83d5e42fe44650fe86f15a645d5cc926d07355ba945e1ede42737607bf83"
+ },
+ "maxUniChar": 65535,
+ "defaultWidth": 600
+ },
+ "repackageTTF": {
+ "bytes": 197968,
+ "sha256": "3efc4e5858d9637276367581c7a67e1d927cdf407f445eb0b367c7679f4e9f8a",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x83C42380, sha256 a9aae6f5015b805e58965c9f49b1ce65c2952d881034ce23c9d4c1032f5eb051",
+ "cmap": "65634 bytes, checksum 0x243F66FC, sha256 7fb9b8a01931f62b9002eecdc21a05589ef88ec6037752db02fb58dc2688d467",
+ "glyf": "276 bytes, checksum 0x7E7B74DC, sha256 3453ef47e98a6dd797ae61efa604f6120f6d411cff967240e739ff499147e071",
+ "head": "54 bytes, checksum 0x26907313, sha256 be89d5a505dcd54001764a8637a5e0cced79566c87836d875fde836d946c1414",
+ "hhea": "36 bytes, checksum 0x092DFE21, sha256 9d376f195b2d98859b1d719bfb2f9e5e4b2d98559ec563e8384b293dd24ace0c",
+ "hmtx": "65544 bytes, checksum 0x02BE00A6, sha256 30ae8773de73e9f642e15f027b9bf7baeb83809fbc0dd0add86b566720280b69",
+ "loca": "65544 bytes, checksum 0xC089C09E, sha256 2071e00324c703a6fbf28d38bb05333de91b8561c0454590b04fc87f30fdfeae",
+ "maxp": "32 bytes, checksum 0x80070034, sha256 e47a8c23c74980a1e15db2899d8f8313094fc8ed227fdcc274df54de08c02863",
+ "name": "544 bytes, checksum 0x2E3145B0, sha256 941746858bd019b7dac7e3ba9146b4d976d37b18a50da1df5ba563b152c2a773",
+ "post": "32 bytes, checksum 0xFF9F0032, sha256 4fdc83d5e42fe44650fe86f15a645d5cc926d07355ba945e1ede42737607bf83"
+ },
+ "maxUni": 0
+ }
+ }
+}
diff --git a/tests/data/subset/SedanSC-EmptyFeature-Subset.json b/tests/data/subset/SedanSC-EmptyFeature-Subset.json
new file mode 100644
index 000000000..ceec7453d
--- /dev/null
+++ b/tests/data/subset/SedanSC-EmptyFeature-Subset.json
@@ -0,0 +1,141 @@
+{
+ "_": "Generated by composer subset:update. See tests/Mpdf/Fonts/SubsetGoldenMaster.php.",
+ "useOTL=0x00": {
+ "characters": 6,
+ "makeSubset": {
+ "bytes": 6396,
+ "sha256": "11cb8467996c0c440f02e5915a36da445398b5d04f840874151f576bd755344d",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x5F5F5B76, sha256 cc4ad6c0e41dc604446972f590368ab428d9ad2c941312922acc9c17dda14d3e",
+ "cmap": "98 bytes, checksum 0x0028016C, sha256 21f95cbfea69e55f8b505c89926ba9f79ca688a848bdcb646e18648876cd9308",
+ "cvt ": "144 bytes, checksum 0x190A07A3, sha256 1e7c2ec1797e1ae814f510add7a144d6c11ebe4c121bd03c55de3375b7ef2e25",
+ "fpgm": "3596 bytes, checksum 0x622F037F, sha256 24c7d2293020be24d28f973149e99b5e04536533dfb66a518191cc7fa206f99b",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "1320 bytes, checksum 0x7B40DF1B, sha256 b0819e4c5dbed8a1305bf2561b901177bbd909a449e590b8868a865532023aaf",
+ "head": "54 bytes, checksum 0x29BD2C4A, sha256 383de87587da6ca4ce0043d1b25a47a49bbb101281d1d7170ff93a3873b1cf42",
+ "hhea": "36 bytes, checksum 0x073C01D8, sha256 e9b18e8db175b148df31d63d84879346b65e2c6370dfeb2224d360499a0210f4",
+ "hmtx": "24 bytes, checksum 0x0C520095, sha256 2ffce9f6317c5f18156d66550dca5eaa62139b1718fb2b4d6ea524e7abb440b9",
+ "loca": "14 bytes, checksum 0x0544040A, sha256 abe7cb76e2ad2ffd8e78e9c8449f34113b157315dc0d19227b434d23f3ebbd1f",
+ "maxp": "32 bytes, checksum 0x01800FD8, sha256 027a72c2d57b6996a98befba8643ca030fe5050789c703c07c8d71cea07012a3",
+ "name": "482 bytes, checksum 0x26DA3F33, sha256 9a6431fdb319205c516f8ac2746e3eeead418c6a954a03b649ddc3eacbf02b6b",
+ "post": "32 bytes, checksum 0xFFB80032, sha256 77a232e4db6b60c21f7b76ccf821e3c80c519901f11d10efb8c8df04ca82a4e8",
+ "prep": "214 bytes, checksum 0x4D565F16, sha256 e0d9caca58b52c80ed328dcf3e608416e387c10522c7fcfebd3478542b7f2703"
+ },
+ "maxUni": 65535,
+ "defaultWidth": 500,
+ "codeToGlyph": "6 entries, sha256 27503d13c6eb370e18366d20cc9675abc21d6c8ddddac6ff2c861c5c0d44acff"
+ },
+ "makeSubsetSIP": {
+ "bytes": 6392,
+ "sha256": "d450a0f03a5e767f8882566ea3c0fd794f87cf36e5178d14cd20a71bb7e9cac8",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x653EDC1A, sha256 9160104e07c9b843fe6979036a1ec3b9b502031e34a6549a8dfb1e941a2f0d2d",
+ "cmap": "96 bytes, checksum 0x004F007F, sha256 588392d8cf8860afe595b2209402ddb835c17fb546a9d41e52754b1421c2b2fe",
+ "cvt ": "144 bytes, checksum 0x190A07A3, sha256 1e7c2ec1797e1ae814f510add7a144d6c11ebe4c121bd03c55de3375b7ef2e25",
+ "fpgm": "3596 bytes, checksum 0x622F037F, sha256 24c7d2293020be24d28f973149e99b5e04536533dfb66a518191cc7fa206f99b",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "1320 bytes, checksum 0x7B40DF1B, sha256 b0819e4c5dbed8a1305bf2561b901177bbd909a449e590b8868a865532023aaf",
+ "head": "54 bytes, checksum 0x29BD2C4A, sha256 f963458d194045550b3abc098b163379ca52d12c858520eac08bc3ac003049a5",
+ "hhea": "36 bytes, checksum 0x073C01D8, sha256 e9b18e8db175b148df31d63d84879346b65e2c6370dfeb2224d360499a0210f4",
+ "hmtx": "24 bytes, checksum 0x0C520095, sha256 4a4408dda373138cf3b85e1b5bc50aea05c97a8ba2ff86426c77ce66ade2e1dc",
+ "loca": "14 bytes, checksum 0x040A02B0, sha256 64d145868c12ba92ca86625b27e35c09b356cceddc9922a7c122341ab8379a37",
+ "maxp": "32 bytes, checksum 0x01800FD8, sha256 027a72c2d57b6996a98befba8643ca030fe5050789c703c07c8d71cea07012a3",
+ "name": "482 bytes, checksum 0x26D33F33, sha256 08e689f505887f14686e1200d7ca05d5e2f5dfcfd6d137a95a274b92153bff8d",
+ "post": "32 bytes, checksum 0xFFB80032, sha256 77a232e4db6b60c21f7b76ccf821e3c80c519901f11d10efb8c8df04ca82a4e8",
+ "prep": "214 bytes, checksum 0x4D565F16, sha256 e0d9caca58b52c80ed328dcf3e608416e387c10522c7fcfebd3478542b7f2703"
+ },
+ "maxUniChar": 65535,
+ "defaultWidth": 500
+ },
+ "repackageTTF": {
+ "bytes": 7688,
+ "sha256": "061883fc9d1a39d0b9d243c566a33ae4ad3e4a798c1ea6c1d2baa654e6a0d141",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x5F5F5B76, sha256 cc4ad6c0e41dc604446972f590368ab428d9ad2c941312922acc9c17dda14d3e",
+ "cmap": "68 bytes, checksum 0x005D00EB, sha256 4b1175340994cbca5f116e230466c3a5a293c97bf6c232501176cfa3cf6fe72c",
+ "cvt ": "144 bytes, checksum 0x190A07A3, sha256 1e7c2ec1797e1ae814f510add7a144d6c11ebe4c121bd03c55de3375b7ef2e25",
+ "fpgm": "3596 bytes, checksum 0x622F037F, sha256 24c7d2293020be24d28f973149e99b5e04536533dfb66a518191cc7fa206f99b",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "2596 bytes, checksum 0xAEE9E758, sha256 d93e16d96b6eaacecb69608ef281b6db5ed6da9be4716032abd2b152e7cd073d",
+ "head": "54 bytes, checksum 0x29BD2C4A, sha256 83e09ce2983f2babfc13ccb805f4e816c139f57b245844cf7b5c050ab670df55",
+ "hhea": "36 bytes, checksum 0x073C01DB, sha256 4a0396d7fc07fc169ca30bfabf25c5753a9733d743a9e19a5cfe7ab402d5521c",
+ "hmtx": "36 bytes, checksum 0x11CC00FA, sha256 bf88d36bd5dc51e87e487d86d5975f4063b0a9e5a315f90788664a7f00a8380b",
+ "loca": "20 bytes, checksum 0x0B050D0D, sha256 5c8231a6bad4dfacf454bf57603b53ad7e649fd41b5697c3baa5d5dbb03519a8",
+ "maxp": "32 bytes, checksum 0x01830FD8, sha256 e3a9fe933752196fb755e0adc0fdfd624263cc6fd921dd79e7e43210a80695c1",
+ "name": "482 bytes, checksum 0x26DA3F33, sha256 9a6431fdb319205c516f8ac2746e3eeead418c6a954a03b649ddc3eacbf02b6b",
+ "post": "62 bytes, checksum 0xDC8F9243, sha256 d7666fbc36debeae35b76f8a2fa860e94cbfa7ffef8a0701fbaf3ed0d3469b11",
+ "prep": "214 bytes, checksum 0x4D565F16, sha256 e0d9caca58b52c80ed328dcf3e608416e387c10522c7fcfebd3478542b7f2703"
+ },
+ "maxUni": 0
+ }
+ },
+ "useOTL=0xFF": {
+ "characters": 9,
+ "makeSubset": {
+ "bytes": 7708,
+ "sha256": "e9114e5c0ebbdc17cfc71b9d3da8bfbc6534ea0192cdc3969851dc89eecb8fb2",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x5F5F5B76, sha256 cc4ad6c0e41dc604446972f590368ab428d9ad2c941312922acc9c17dda14d3e",
+ "cmap": "112 bytes, checksum 0xE079014A, sha256 8324f76c37d13136da2f3a9b9753616e830978d969d872853f5a1d321da06268",
+ "cvt ": "144 bytes, checksum 0x190A07A3, sha256 1e7c2ec1797e1ae814f510add7a144d6c11ebe4c121bd03c55de3375b7ef2e25",
+ "fpgm": "3596 bytes, checksum 0x622F037F, sha256 24c7d2293020be24d28f973149e99b5e04536533dfb66a518191cc7fa206f99b",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "2604 bytes, checksum 0xFDEE9853, sha256 c061f5e466a1ea3ff797cb3147cbac68bd69d21700bb92a8ce0b88fe90b843f3",
+ "head": "54 bytes, checksum 0x29BD2C4A, sha256 5381a291ac15be069c8265649b3a540cd8372c47987b804097ae00d8b6cb2405",
+ "hhea": "36 bytes, checksum 0x073C01DB, sha256 4a0396d7fc07fc169ca30bfabf25c5753a9733d743a9e19a5cfe7ab402d5521c",
+ "hmtx": "36 bytes, checksum 0x11CC00FA, sha256 bf88d36bd5dc51e87e487d86d5975f4063b0a9e5a315f90788664a7f00a8380b",
+ "loca": "20 bytes, checksum 0x0B0C0D16, sha256 173ee040c3073c3c62a09ed45db2481deda43acc6cc7e8b734ed69298dbb2c87",
+ "maxp": "32 bytes, checksum 0x01830FD8, sha256 e3a9fe933752196fb755e0adc0fdfd624263cc6fd921dd79e7e43210a80695c1",
+ "name": "482 bytes, checksum 0x26DA3F33, sha256 9a6431fdb319205c516f8ac2746e3eeead418c6a954a03b649ddc3eacbf02b6b",
+ "post": "32 bytes, checksum 0xFFB80032, sha256 77a232e4db6b60c21f7b76ccf821e3c80c519901f11d10efb8c8df04ca82a4e8",
+ "prep": "214 bytes, checksum 0x4D565F16, sha256 e0d9caca58b52c80ed328dcf3e608416e387c10522c7fcfebd3478542b7f2703"
+ },
+ "maxUni": 65535,
+ "defaultWidth": 500,
+ "codeToGlyph": "9 entries, sha256 7bad91db95d2c06667bf276d931334fb627449ea09d18604973ae109befe86df"
+ },
+ "makeSubsetSIP": {
+ "bytes": 7704,
+ "sha256": "e741372a0d3f8e7107a76a4fec81e55082454e59f0b7ede42122089f3270d951",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x653EDC1D, sha256 554ce119256ca51e4d7b0a178453afa831328b48da8f9fa403da882b5cd996a9",
+ "cmap": "108 bytes, checksum 0x005600BD, sha256 5294bf45a982e84705f3e9fb390ee1d8b211d2cc76df18225b959b8f98b66e14",
+ "cvt ": "144 bytes, checksum 0x190A07A3, sha256 1e7c2ec1797e1ae814f510add7a144d6c11ebe4c121bd03c55de3375b7ef2e25",
+ "fpgm": "3596 bytes, checksum 0x622F037F, sha256 24c7d2293020be24d28f973149e99b5e04536533dfb66a518191cc7fa206f99b",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "2604 bytes, checksum 0xFDEE9853, sha256 c061f5e466a1ea3ff797cb3147cbac68bd69d21700bb92a8ce0b88fe90b843f3",
+ "head": "54 bytes, checksum 0x29BD2C4A, sha256 41cb44175a8c0bb73b3ab1008320de96db0173ed8d5b4df21dc6461eb8e81f39",
+ "hhea": "36 bytes, checksum 0x073C01DB, sha256 4a0396d7fc07fc169ca30bfabf25c5753a9733d743a9e19a5cfe7ab402d5521c",
+ "hmtx": "36 bytes, checksum 0x11CC00FA, sha256 99cf9827642b44d84ef6a6979f7ac746e132e37cea80bf00f6b74c117ab2eddc",
+ "loca": "20 bytes, checksum 0x08000B0C, sha256 9a266d4f17b6aa97692c3cca1e4a71b16073f39405c86b9b211685afd5371734",
+ "maxp": "32 bytes, checksum 0x01830FD8, sha256 e3a9fe933752196fb755e0adc0fdfd624263cc6fd921dd79e7e43210a80695c1",
+ "name": "482 bytes, checksum 0x26D33F33, sha256 08e689f505887f14686e1200d7ca05d5e2f5dfcfd6d137a95a274b92153bff8d",
+ "post": "32 bytes, checksum 0xFFB80032, sha256 77a232e4db6b60c21f7b76ccf821e3c80c519901f11d10efb8c8df04ca82a4e8",
+ "prep": "214 bytes, checksum 0x4D565F16, sha256 e0d9caca58b52c80ed328dcf3e608416e387c10522c7fcfebd3478542b7f2703"
+ },
+ "maxUniChar": 65535,
+ "defaultWidth": 500
+ },
+ "repackageTTF": {
+ "bytes": 7724,
+ "sha256": "eaab3f08c782fc1dad90a8af709b9226f3aee98d8dde4860d89ca916d5324fdf",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x5F5F5B76, sha256 cc4ad6c0e41dc604446972f590368ab428d9ad2c941312922acc9c17dda14d3e",
+ "cmap": "102 bytes, checksum 0xC038217F, sha256 a1b184efcc22e1eaf3755cc9bb944649ddbdced8a28cb81f10faa61f050e6c43",
+ "cvt ": "144 bytes, checksum 0x190A07A3, sha256 1e7c2ec1797e1ae814f510add7a144d6c11ebe4c121bd03c55de3375b7ef2e25",
+ "fpgm": "3596 bytes, checksum 0x622F037F, sha256 24c7d2293020be24d28f973149e99b5e04536533dfb66a518191cc7fa206f99b",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "2596 bytes, checksum 0xAEE9E758, sha256 d93e16d96b6eaacecb69608ef281b6db5ed6da9be4716032abd2b152e7cd073d",
+ "head": "54 bytes, checksum 0x29BD2C4A, sha256 2d4d61d8637e2aaac8a1f0e5d3e4882d408a3659e8178f694937491297195a19",
+ "hhea": "36 bytes, checksum 0x073C01DB, sha256 4a0396d7fc07fc169ca30bfabf25c5753a9733d743a9e19a5cfe7ab402d5521c",
+ "hmtx": "36 bytes, checksum 0x11CC00FA, sha256 bf88d36bd5dc51e87e487d86d5975f4063b0a9e5a315f90788664a7f00a8380b",
+ "loca": "20 bytes, checksum 0x0B050D0D, sha256 5c8231a6bad4dfacf454bf57603b53ad7e649fd41b5697c3baa5d5dbb03519a8",
+ "maxp": "32 bytes, checksum 0x01830FD8, sha256 e3a9fe933752196fb755e0adc0fdfd624263cc6fd921dd79e7e43210a80695c1",
+ "name": "482 bytes, checksum 0x26DA3F33, sha256 9a6431fdb319205c516f8ac2746e3eeead418c6a954a03b649ddc3eacbf02b6b",
+ "post": "62 bytes, checksum 0xDC8F9243, sha256 d7666fbc36debeae35b76f8a2fa860e94cbfa7ffef8a0701fbaf3ed0d3469b11",
+ "prep": "214 bytes, checksum 0x4D565F16, sha256 e0d9caca58b52c80ed328dcf3e608416e387c10522c7fcfebd3478542b7f2703"
+ },
+ "maxUni": 0
+ }
+ }
+}
diff --git a/tests/data/ttf/Carlito-MarkAttachmentType-Subset.ttf b/tests/data/ttf/Carlito-MarkAttachmentType-Subset.ttf
new file mode 100644
index 000000000..f3b3653bf
Binary files /dev/null and b/tests/data/ttf/Carlito-MarkAttachmentType-Subset.ttf differ
diff --git a/tests/data/ttf/Molengo-GSUB52Class0-Subset.ttf b/tests/data/ttf/Molengo-GSUB52Class0-Subset.ttf
new file mode 100644
index 000000000..75c66b37d
Binary files /dev/null and b/tests/data/ttf/Molengo-GSUB52Class0-Subset.ttf differ
diff --git a/tests/data/ttf/NotoSansArabic-GSUB11Wrap-Synthetic.ttf b/tests/data/ttf/NotoSansArabic-GSUB11Wrap-Synthetic.ttf
new file mode 100644
index 000000000..8cf267d3a
Binary files /dev/null and b/tests/data/ttf/NotoSansArabic-GSUB11Wrap-Synthetic.ttf differ
diff --git a/tests/data/ttf/SedanSC-EmptyFeature-Subset.ttf b/tests/data/ttf/SedanSC-EmptyFeature-Subset.ttf
new file mode 100644
index 000000000..df38c419b
Binary files /dev/null and b/tests/data/ttf/SedanSC-EmptyFeature-Subset.ttf differ