diff --git a/src/Shaper/OtlTags.php b/src/Shaper/OtlTags.php
index 1d86972aa..07c8c57e4 100644
--- a/src/Shaper/OtlTags.php
+++ b/src/Shaper/OtlTags.php
@@ -30,6 +30,15 @@ class OtlTags
'mym2' => 'mymr',
];
+ /**
+ * The Chinese regions with language systems of their own, in the order HarfBuzz looks for them.
+ */
+ private static $chineseRegions = [
+ 'hk' => ['ZHH '],
+ 'mo' => ['ZHTM', 'ZHH '],
+ 'tw' => ['ZHT '],
+ ];
+
/**
* The script tag a run is laid out with, out of what the table offers.
*
@@ -77,7 +86,8 @@ public static function script(array $ScriptLang, $scripttag, $scriptblock, $shap
* @param string $ietf The language of the text as an IETF tag, e.g. 'sr-Cyrl' or 'zh-Hant-HK'
* @param string $available The language systems the script offers, as one string of tags
*
- * @return string The tag, 'DFLT' where the script offers that and not the language, or '' for neither
+ * @return string The first of the language's tags the script offers, 'DFLT' where it offers none of
+ * them and offers that, or '' for neither
*/
public static function language($ietf, $available)
{
@@ -85,35 +95,59 @@ public static function language($ietf, $available)
return '';
}
- $tags = $ietf ? explode('-', $ietf) : [];
-
- $lang = isset($tags[0]) ? strtolower($tags[0]) : '';
+ $subtags = $ietf ? explode('-', strtolower($ietf)) : [];
- // The region, where the second subtag is one; a third subtag always is, the second being
- // the script
- $country = '';
- if (isset($tags[1]) && strlen($tags[1]) == 2) {
- $country = strtolower($tags[1]);
+ $candidates = [];
+ if (isset($subtags[0]) && $subtags[0] == 'zh') {
+ $candidates = self::chinese($subtags);
+ } elseif (isset($subtags[0]) && isset(Ucdn::$ot_languages[$subtags[0]])) {
+ $candidates = [Ucdn::$ot_languages[$subtags[0]]];
}
- if (isset($tags[2]) && $tags[2]) {
- $country = strtolower($tags[2]);
+
+ foreach ($candidates as $langsys) {
+ if (strpos($available, $langsys) !== false) {
+ return $langsys;
+ }
}
- $region = $lang . '-' . $country;
+ return strpos($available, 'DFLT') !== false ? 'DFLT' : '';
+ }
- if ($lang != '' && isset(Ucdn::$ot_languages[$lang])) {
- $langsys = Ucdn::$ot_languages[$lang];
- } elseif ($lang != '' && $country != '' && isset(Ucdn::$ot_languages[$region])) {
- $langsys = Ucdn::$ot_languages[$region];
- } else {
- $langsys = 'DFLT';
+ /**
+ * The language systems for Chinese, in the order HarfBuzz tries them
+ * (hb_ot_tags_from_complex_language() in hb-ot-tag-table.hh).
+ *
+ * The script subtag outranks the region, so zh-Hans-HK is Simplified, except that Traditional
+ * Chinese in Hong Kong or Macao keeps its region's tag. Macao has its own ZHTM, and where a font
+ * lacks it takes Hong Kong's ZHH before the default. A tag with neither is Simplified.
+ *
+ * The zh- keys of Ucdn::$ot_languages are not read: they hold one tag per region, and nothing for
+ * the script or for zh alone.
+ *
+ * @param string[] $subtags The lower-cased subtags of a tag whose language is zh
+ *
+ * @return string[]
+ */
+ private static function chinese(array $subtags)
+ {
+ $script = isset($subtags[1]) ? $subtags[1] : '';
+
+ if ($script == 'hans') {
+ return ['ZHS '];
}
+ if ($script == 'hant') {
+ $region = isset($subtags[2]) ? $subtags[2] : '';
- if (strpos($available, $langsys) !== false) {
- return $langsys;
+ return $region == 'hk' || $region == 'mo' ? self::$chineseRegions[$region] : ['ZHT '];
}
- return strpos($available, 'DFLT') !== false ? 'DFLT' : '';
+ foreach (self::$chineseRegions as $region => $langsys) {
+ if (in_array($region, $subtags, true)) {
+ return $langsys;
+ }
+ }
+
+ return ['ZHS '];
}
/**
diff --git a/tests/Mpdf/ChineseLangSysTest.php b/tests/Mpdf/ChineseLangSysTest.php
new file mode 100644
index 000000000..f78f1352b
--- /dev/null
+++ b/tests/Mpdf/ChineseLangSysTest.php
@@ -0,0 +1,74 @@
+assertSame([$expected], $this->drawn('NotoSansTC-RegionLangSys-Synthetic.ttf', $lang));
+ }
+
+ public function languages()
+ {
+ return [
+ 'zh-HK' => ['zh-HK', self::ZHH],
+ 'zh-TW' => ['zh-TW', self::ZHT],
+ 'zh-CN' => ['zh-CN', self::ZHS],
+ 'zh' => ['zh', self::ZHS],
+ 'zh-Hans' => ['zh-Hans', self::ZHS],
+ 'zh-Hant' => ['zh-Hant', self::ZHT],
+ 'zh-Hans-HK' => ['zh-Hans-HK', self::ZHS],
+ 'zh-Hant-CN' => ['zh-Hant-CN', self::ZHT],
+ 'zh-MO, where the font has no ZHTM' => ['zh-MO', self::ZHH],
+ ];
+ }
+
+ public function testMacaoSelectsZHTMWhereTheFontOffersIt()
+ {
+ $this->assertSame([self::ZHTM], $this->drawn('NotoSansTC-MacaoLangSys-Synthetic.ttf', 'zh-MO'));
+ }
+
+ private function drawn($font, $lang)
+ {
+ $key = strtolower(str_replace('-', '', basename($font, '.ttf')));
+
+ $mpdf = new TextRecordingMpdf([
+ 'mode' => 'utf-8',
+ 'fontDir' => [__DIR__ . '/../data/ttf'],
+ 'fontdata' => [$key => [
+ 'R' => $font,
+ 'useOTL' => 0xFF,
+ ]],
+ 'default_font' => $key,
+ ]);
+ $mpdf->WriteHTML(sprintf('
%04X;
', $lang, self::BONE));
+
+ return array_values(unpack('N*', mb_convert_encoding($mpdf->drawnText[0], 'UTF-32BE', 'UTF-8')));
+ }
+
+}
diff --git a/tests/Mpdf/ChineseRegionLangSysTest.php b/tests/Mpdf/ChineseRegionLangSysTest.php
deleted file mode 100644
index be97538a8..000000000
--- a/tests/Mpdf/ChineseRegionLangSysTest.php
+++ /dev/null
@@ -1,55 +0,0 @@
- ['zh-HK', self::ZHH],
- 'zh-TW' => ['zh-TW', self::ZHT],
- 'zh-CN' => ['zh-CN', self::ZHS],
- ];
- }
-
- /**
- * @dataProvider regions
- */
- public function testTheRegionSelectsTheLanguageSystem($lang, $expected)
- {
- $mpdf = new TextRecordingMpdf([
- 'mode' => 'utf-8',
- 'fontDir' => [__DIR__ . '/../data/ttf'],
- 'fontdata' => ['notosanstcregionlangsyssynthetic' => [
- 'R' => 'NotoSansTC-RegionLangSys-Synthetic.ttf',
- 'useOTL' => 0xFF,
- ]],
- 'default_font' => 'notosanstcregionlangsyssynthetic',
- ]);
- $mpdf->WriteHTML(sprintf('%04X;
', $lang, self::BONE));
-
- $drawn = array_values(unpack('N*', mb_convert_encoding($mpdf->drawnText[0], 'UTF-32BE', 'UTF-8')));
-
- $this->assertSame([$expected], $drawn);
- }
-
-}
diff --git a/tests/Mpdf/Shaper/OtlTagsTest.php b/tests/Mpdf/Shaper/OtlTagsTest.php
index a421cb2fd..fbdc26eb2 100644
--- a/tests/Mpdf/Shaper/OtlTagsTest.php
+++ b/tests/Mpdf/Shaper/OtlTagsTest.php
@@ -124,36 +124,44 @@ public function languages()
}
/**
- * @dataProvider chineseRegions
+ * @dataProvider chinese
*/
- public function testAChineseRegionSelectsItsLanguageSystem($ietf, $expected)
+ public function testChineseSelectsItsLanguageSystemTheWayHarfBuzzDoes($ietf, $expected)
{
- $this->assertSame($expected, OtlTags::language($ietf, 'DFLT ZHH ZHS ZHT '));
+ $this->assertSame($expected, OtlTags::language($ietf, 'DFLT ZHH ZHS ZHT ZHTM '));
}
- public function chineseRegions()
+ public function chinese()
{
return [
'Hong Kong' => ['zh-HK', 'ZHH '],
- 'Hong Kong, after the script' => ['zh-Hant-HK', 'ZHH '],
'Taiwan' => ['zh-TW', 'ZHT '],
- 'Macao' => ['zh-MO', 'ZHT '],
+ 'Macao' => ['zh-MO', 'ZHTM'],
'China' => ['zh-CN', 'ZHS '],
'Singapore' => ['zh-SG', 'ZHS '],
'lower case' => ['zh-tw', 'ZHT '],
- 'a region the table has no entry for' => ['zh-US', 'DFLT'],
+ 'a region with no language system of its own' => ['zh-US', 'ZHS '],
+ 'no script or region' => ['zh', 'ZHS '],
+ 'Simplified' => ['zh-Hans', 'ZHS '],
+ 'Traditional' => ['zh-Hant', 'ZHT '],
+ 'upper case' => ['ZH-HANT', 'ZHT '],
+ 'Simplified, in Hong Kong' => ['zh-Hans-HK', 'ZHS '],
+ 'Simplified, in Taiwan' => ['zh-Hans-TW', 'ZHS '],
+ 'Simplified, in Macao' => ['zh-Hans-MO', 'ZHS '],
+ 'Traditional, in China' => ['zh-Hant-CN', 'ZHT '],
+ 'Traditional, in Taiwan' => ['zh-Hant-TW', 'ZHT '],
+ 'Traditional, in Hong Kong' => ['zh-Hant-HK', 'ZHH '],
+ 'Traditional, in Macao' => ['zh-Hant-MO', 'ZHTM'],
+ 'another script, in Hong Kong' => ['zh-Latn-HK', 'ZHH '],
+ 'Min Nan, a retired tag' => ['zh-min-nan', 'ZHS '],
];
}
- /**
- * Pins #201 as it stands: without a region, Chinese has no language system, where HarfBuzz
- * takes ZHS for zh and zh-Hans and ZHT for zh-Hant.
- */
- public function testChineseWithoutARegionHasNoLanguageSystem()
+ public function testMacaoFallsBackToHongKong()
{
- $this->assertSame('DFLT', OtlTags::language('zh', 'DFLT ZHH ZHS ZHT '));
- $this->assertSame('DFLT', OtlTags::language('zh-Hant', 'DFLT ZHH ZHS ZHT '));
- $this->assertSame('DFLT', OtlTags::language('zh-Hans', 'DFLT ZHH ZHS ZHT '));
+ $this->assertSame('ZHH ', OtlTags::language('zh-MO', 'DFLT ZHH ZHS ZHT '));
+ $this->assertSame('ZHH ', OtlTags::language('zh-Hant-MO', 'DFLT ZHH ZHS ZHT '));
+ $this->assertSame('DFLT', OtlTags::language('zh-MO', 'DFLT ZHS ZHT '));
}
}
diff --git a/tests/data/fontcache/NotoSansTC-MacaoLangSys-Synthetic.json b/tests/data/fontcache/NotoSansTC-MacaoLangSys-Synthetic.json
new file mode 100644
index 000000000..58c35d481
--- /dev/null
+++ b/tests/data/fontcache/NotoSansTC-MacaoLangSys-Synthetic.json
@@ -0,0 +1,111 @@
+{
+ "_": "Generated by composer fontcache:update. See tests/Mpdf/Fonts/ParserGoldenMaster.php.",
+ "fullName": "NotoSansTC-MacaoLangSys-Synthetic",
+ "mtx": {
+ "GSUBScriptLang": {
+ "hani": "DFLT ZHH ZHS ZHT ZHTM "
+ },
+ "GSUBFeatures": {
+ "hani": {
+ "ZHH ": {
+ "locl": [
+ 0
+ ]
+ },
+ "ZHS ": {
+ "locl": [
+ 1
+ ]
+ },
+ "ZHT ": {
+ "locl": [
+ 2
+ ]
+ },
+ "ZHTM": {
+ "locl": [
+ 3
+ ]
+ }
+ }
+ },
+ "GSUBLookups": [
+ {
+ "Type": 1,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 152
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 1,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 166
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 1,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 180
+ ],
+ "MarkFilteringSet": ""
+ },
+ {
+ "Type": 1,
+ "Flag": 0,
+ "SubtableCount": 1,
+ "Subtables": [
+ 194
+ ],
+ "MarkFilteringSet": ""
+ }
+ ],
+ "GPOSScriptLang": [],
+ "GPOSFeatures": [],
+ "GPOSLookups": [],
+ "MarkGlyphSets": [],
+ "rtlPUAstr": "",
+ "haskernGPOS": false,
+ "hassmallcapsGSUB": false
+ },
+ "cache": {
+ "GDEFdata.json": {
+ "GlyphClassBases": " 04E00| 04E09| 04E8C| 056DB| 09AA8",
+ "GlyphClassMarks": "",
+ "GlyphClassLigatures": "",
+ "GlyphClassComponents": "",
+ "MarkGlyphSets": [],
+ "MarkAttachmentType": []
+ },
+ "GSUB.dat": "206 bytes, sha256 15a34b758887cf89b199b406d9176b260da726c485910751dbf71edbb7515e8a",
+ "GSUBdata.json": [
+ [
+ {
+ "39592": 0
+ }
+ ],
+ [
+ {
+ "39592": 0
+ }
+ ],
+ [
+ {
+ "39592": 0
+ }
+ ],
+ [
+ {
+ "39592": 0
+ }
+ ]
+ ]
+ }
+}
diff --git a/tests/data/otldump/NotoSansTC-MacaoLangSys-Synthetic.txt b/tests/data/otldump/NotoSansTC-MacaoLangSys-Synthetic.txt
new file mode 100644
index 000000000..828995639
--- /dev/null
+++ b/tests/data/otldump/NotoSansTC-MacaoLangSys-Synthetic.txt
@@ -0,0 +1,17 @@
+GDEF table
+Glyph classes
+Glyph class 1
+Base glyph (single character, spacing glyph)
+一 三 二 四 骨
+GSUB Tables
+GSUB Scripts & Languages
+
+GPOS table not defined
+
+=== detail: script hani language DFLT ===
+GSUB Tables
+This font's GSUB script "hani" offers no language system "DFLT". It has: ZHH, ZHS, ZHT, ZHTM
+
+GPOS table not defined
diff --git a/tests/data/shaping/NotoSansTC-MacaoLangSys-Synthetic.txt b/tests/data/shaping/NotoSansTC-MacaoLangSys-Synthetic.txt
new file mode 100644
index 000000000..7c53de204
--- /dev/null
+++ b/tests/data/shaping/NotoSansTC-MacaoLangSys-Synthetic.txt
@@ -0,0 +1,92 @@
+=== 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
+0x80 0628 0640 0645 0644 0627 => 0628 0640 0645 0644 0627 group=CCCCC
+0xFF +kern -liga 0628 0640 0645 0644 0627 => 0628 0640 0645 0644 0627 group=CCCCC
+=== 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 0937 093F group=CCCC
+0x80 0915 094D 0937 093F => 0915 094D 0937 093F group=CCCC
+0xFF +kern -liga 0915 094D 0937 093F => 0915 094D 0937 093F group=CCCC
+=== bengali ===
+0xFF 0995 09CD 09B7 09BF => 0995 09CD 09B7 09BF group=CCCC
+0x80 0995 09CD 09B7 09BF => 0995 09CD 09B7 09BF group=CCCC
+0xFF +kern -liga 0995 09CD 09B7 09BF => 0995 09CD 09B7 09BF group=CCCC
+=== gurmukhi ===
+0xFF 0A15 0A4D 0A38 0A3F => 0A15 0A4D 0A38 0A3F group=CCCC
+0x80 0A15 0A4D 0A38 0A3F => 0A15 0A4D 0A38 0A3F group=CCCC
+0xFF +kern -liga 0A15 0A4D 0A38 0A3F => 0A15 0A4D 0A38 0A3F 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 => 1780 17D2 1781 17C1 group=CCCC
+0x80 1780 17D2 1781 17C1 => 1780 17D2 1781 17C1 group=CCCC
+0xFF +kern -liga 1780 17D2 1781 17C1 => 1780 17D2 1781 17C1 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 4E00 4E09 4E8C 56DB 9AA8 => 0020 4E00 4E09 4E8C 56DB 9AA8 group=SCCCCC
+0x80 0020 4E00 4E09 4E8C 56DB 9AA8 => 0020 4E00 4E09 4E8C 56DB 9AA8 group=SCCCCC
+0xFF +kern -liga 0020 4E00 4E09 4E8C 56DB 9AA8 => 0020 4E00 4E09 4E8C 56DB 9AA8 group=SCCCCC
diff --git a/tests/data/subset/NotoSansTC-MacaoLangSys-Synthetic.json b/tests/data/subset/NotoSansTC-MacaoLangSys-Synthetic.json
new file mode 100644
index 000000000..95c105180
--- /dev/null
+++ b/tests/data/subset/NotoSansTC-MacaoLangSys-Synthetic.json
@@ -0,0 +1,123 @@
+{
+ "_": "Generated by composer subset:update. See tests/Mpdf/Fonts/SubsetGoldenMaster.php.",
+ "useOTL=0x00": {
+ "characters": 7,
+ "makeSubset": {
+ "bytes": 1828,
+ "sha256": "1a08d48939adc69ce292ff650076355c5052f120c731933af2ee7639286467ef",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x553CF586, sha256 4feb4a7666c967ad3bedc80ca0480d43b41a41ffe07c31028a6ef52218d38859",
+ "cmap": "108 bytes, checksum 0x3758A5CD, sha256 27e5bdb67154aea061c1a0b3e62f0b894a919ea72a9df76f6a575a3398b6c21f",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "616 bytes, checksum 0xF2BD808C, sha256 eb64d774f26f4e9a473759bbcdc30a0a8b61b20877fdfe44a02c0bd1355b96c6",
+ "head": "54 bytes, checksum 0x26853F95, sha256 1acb9bbfa64aefe9fb625673eb5b941acd732a30a7a9cdbe48b0867d476876aa",
+ "hhea": "36 bytes, checksum 0x087602F8, sha256 d4af68535beeb99ce192587063523bb1862795cbb69021e6a6f6837509a41164",
+ "hmtx": "28 bytes, checksum 0x185001B1, sha256 dcdc38d773361123ed46e2ed00be835f86453b8de0b415d657f8894a0356a4ae",
+ "loca": "16 bytes, checksum 0x013C0204, sha256 16b586cebec1f59e81f7212f6264e445b9d03d7549e66ab0595a3c8f8b8b5a01",
+ "maxp": "32 bytes, checksum 0x00100051, sha256 a90cbb1baa544bddcc9065d5a64a0fb38a90285012ee709966a26b270ec7f551",
+ "name": "612 bytes, checksum 0x31BF4960, sha256 23c77f4598d07365d682aa25460b20f76e80c4c511e019e04b5390477f8ebb4b",
+ "post": "32 bytes, checksum 0xFF860032, sha256 22dd499f08932214bde96943f3882f3619324478217095c049b68a57c6c10693"
+ },
+ "maxUni": 65535,
+ "defaultWidth": 1000,
+ "codeToGlyph": "7 entries, sha256 33437c8ffd013e5cb18588ae09402766e4327e02b063f3258c7c8414254c0e19"
+ },
+ "makeSubsetSIP": {
+ "bytes": 1804,
+ "sha256": "7e85dff50a5defaf99bfafaa39871f02e383726483048542d2d524839c9ad202",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x5910D1E5, sha256 9b6aabb1544d4c39e4e3ce746b7822eaef6c698888d89808bf3fe66f6a62bf7f",
+ "cmap": "84 bytes, checksum 0x00380090, sha256 b744253609e2a88d21149d2993bd74c60e813a46a40dbdd5fa6a10f27bd1defe",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "616 bytes, checksum 0xF2BD808C, sha256 eb64d774f26f4e9a473759bbcdc30a0a8b61b20877fdfe44a02c0bd1355b96c6",
+ "head": "54 bytes, checksum 0x26853F95, sha256 d3c8bad2f1b5344ce391c0bdbbaf20112ab08c2549862b0f290b099c920716cf",
+ "hhea": "36 bytes, checksum 0x087602F8, sha256 d4af68535beeb99ce192587063523bb1862795cbb69021e6a6f6837509a41164",
+ "hmtx": "28 bytes, checksum 0x185001B1, sha256 dcdc38d773361123ed46e2ed00be835f86453b8de0b415d657f8894a0356a4ae",
+ "loca": "16 bytes, checksum 0x013C0204, sha256 16b586cebec1f59e81f7212f6264e445b9d03d7549e66ab0595a3c8f8b8b5a01",
+ "maxp": "32 bytes, checksum 0x00100051, sha256 a90cbb1baa544bddcc9065d5a64a0fb38a90285012ee709966a26b270ec7f551",
+ "name": "612 bytes, checksum 0x31B84960, sha256 54f060d3e1c9cbea3eba51254e89baf8171d80dc6c5b4ffddcdbb053e7fc612b",
+ "post": "32 bytes, checksum 0xFF860032, sha256 22dd499f08932214bde96943f3882f3619324478217095c049b68a57c6c10693"
+ },
+ "maxUniChar": 65535,
+ "defaultWidth": 1000
+ },
+ "repackageTTF": {
+ "bytes": 1800,
+ "sha256": "6691f7d9e584dad1f8b60832ec511ea8bdaf26b6e87fab8060151f58b9374998",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x553CF586, sha256 4feb4a7666c967ad3bedc80ca0480d43b41a41ffe07c31028a6ef52218d38859",
+ "cmap": "92 bytes, checksum 0xC9881364, sha256 733b6a16d046f7383c0562d37943c507574d58ad29e5f1215189dde306cb0f29",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "610 bytes, checksum 0x310A423E, sha256 07f8c133a7cd0ae670db6b0b91d1c28df3f22310dbe6371f44400eda2f635330",
+ "head": "54 bytes, checksum 0x26853F95, sha256 bfd07cb84f0abdc2d9b0567935b1128c4e0496677e5451dceb5c1bf7c35208c6",
+ "hhea": "36 bytes, checksum 0x087602F4, sha256 3b8df10855749dc2b94ca33deacd41aa368f250bd21bfe9561090e4dece3ed54",
+ "hmtx": "20 bytes, checksum 0x09490118, sha256 25146f57e5c57b537146373684d9d1a2321c5a9161766675cf932e2c89f231e0",
+ "loca": "16 bytes, checksum 0x013501FB, sha256 aae769be1e60c3529a0063171e29d79ca147197e2ed59c40dcbc5bbb949381ed",
+ "maxp": "32 bytes, checksum 0x00100051, sha256 a90cbb1baa544bddcc9065d5a64a0fb38a90285012ee709966a26b270ec7f551",
+ "name": "612 bytes, checksum 0x31BF4960, sha256 23c77f4598d07365d682aa25460b20f76e80c4c511e019e04b5390477f8ebb4b",
+ "post": "32 bytes, checksum 0xFF860032, sha256 22dd499f08932214bde96943f3882f3619324478217095c049b68a57c6c10693"
+ },
+ "maxUni": 0
+ }
+ },
+ "useOTL=0xFF": {
+ "characters": 7,
+ "makeSubset": {
+ "bytes": 1828,
+ "sha256": "1a08d48939adc69ce292ff650076355c5052f120c731933af2ee7639286467ef",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x553CF586, sha256 4feb4a7666c967ad3bedc80ca0480d43b41a41ffe07c31028a6ef52218d38859",
+ "cmap": "108 bytes, checksum 0x3758A5CD, sha256 27e5bdb67154aea061c1a0b3e62f0b894a919ea72a9df76f6a575a3398b6c21f",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "616 bytes, checksum 0xF2BD808C, sha256 eb64d774f26f4e9a473759bbcdc30a0a8b61b20877fdfe44a02c0bd1355b96c6",
+ "head": "54 bytes, checksum 0x26853F95, sha256 1acb9bbfa64aefe9fb625673eb5b941acd732a30a7a9cdbe48b0867d476876aa",
+ "hhea": "36 bytes, checksum 0x087602F8, sha256 d4af68535beeb99ce192587063523bb1862795cbb69021e6a6f6837509a41164",
+ "hmtx": "28 bytes, checksum 0x185001B1, sha256 dcdc38d773361123ed46e2ed00be835f86453b8de0b415d657f8894a0356a4ae",
+ "loca": "16 bytes, checksum 0x013C0204, sha256 16b586cebec1f59e81f7212f6264e445b9d03d7549e66ab0595a3c8f8b8b5a01",
+ "maxp": "32 bytes, checksum 0x00100051, sha256 a90cbb1baa544bddcc9065d5a64a0fb38a90285012ee709966a26b270ec7f551",
+ "name": "612 bytes, checksum 0x31BF4960, sha256 23c77f4598d07365d682aa25460b20f76e80c4c511e019e04b5390477f8ebb4b",
+ "post": "32 bytes, checksum 0xFF860032, sha256 22dd499f08932214bde96943f3882f3619324478217095c049b68a57c6c10693"
+ },
+ "maxUni": 65535,
+ "defaultWidth": 1000,
+ "codeToGlyph": "7 entries, sha256 33437c8ffd013e5cb18588ae09402766e4327e02b063f3258c7c8414254c0e19"
+ },
+ "makeSubsetSIP": {
+ "bytes": 1804,
+ "sha256": "7e85dff50a5defaf99bfafaa39871f02e383726483048542d2d524839c9ad202",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x5910D1E5, sha256 9b6aabb1544d4c39e4e3ce746b7822eaef6c698888d89808bf3fe66f6a62bf7f",
+ "cmap": "84 bytes, checksum 0x00380090, sha256 b744253609e2a88d21149d2993bd74c60e813a46a40dbdd5fa6a10f27bd1defe",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "616 bytes, checksum 0xF2BD808C, sha256 eb64d774f26f4e9a473759bbcdc30a0a8b61b20877fdfe44a02c0bd1355b96c6",
+ "head": "54 bytes, checksum 0x26853F95, sha256 d3c8bad2f1b5344ce391c0bdbbaf20112ab08c2549862b0f290b099c920716cf",
+ "hhea": "36 bytes, checksum 0x087602F8, sha256 d4af68535beeb99ce192587063523bb1862795cbb69021e6a6f6837509a41164",
+ "hmtx": "28 bytes, checksum 0x185001B1, sha256 dcdc38d773361123ed46e2ed00be835f86453b8de0b415d657f8894a0356a4ae",
+ "loca": "16 bytes, checksum 0x013C0204, sha256 16b586cebec1f59e81f7212f6264e445b9d03d7549e66ab0595a3c8f8b8b5a01",
+ "maxp": "32 bytes, checksum 0x00100051, sha256 a90cbb1baa544bddcc9065d5a64a0fb38a90285012ee709966a26b270ec7f551",
+ "name": "612 bytes, checksum 0x31B84960, sha256 54f060d3e1c9cbea3eba51254e89baf8171d80dc6c5b4ffddcdbb053e7fc612b",
+ "post": "32 bytes, checksum 0xFF860032, sha256 22dd499f08932214bde96943f3882f3619324478217095c049b68a57c6c10693"
+ },
+ "maxUniChar": 65535,
+ "defaultWidth": 1000
+ },
+ "repackageTTF": {
+ "bytes": 1808,
+ "sha256": "2112a40561ecf5ba8871bae9f44ff1d365a448e36d2990f6abb05c613cf393c5",
+ "tables": {
+ "OS/2": "96 bytes, checksum 0x553CF586, sha256 4feb4a7666c967ad3bedc80ca0480d43b41a41ffe07c31028a6ef52218d38859",
+ "cmap": "100 bytes, checksum 0xC9881391, sha256 1d4376603e4499f42e96a03200d95c4a71efad8458f72b5f4a3879e07784b1bc",
+ "gasp": "8 bytes, checksum 0x00000010, sha256 4ca731f86ad506ac0e320283dc9461926346de3c4d91d539ea7f6620d3826940",
+ "glyf": "610 bytes, checksum 0x310A423E, sha256 07f8c133a7cd0ae670db6b0b91d1c28df3f22310dbe6371f44400eda2f635330",
+ "head": "54 bytes, checksum 0x26853F95, sha256 92de6dfe6f3b4c977d8d4cd3346333f92231c0ef7f08acdd9a2e12a3530ea8e7",
+ "hhea": "36 bytes, checksum 0x087602F4, sha256 3b8df10855749dc2b94ca33deacd41aa368f250bd21bfe9561090e4dece3ed54",
+ "hmtx": "20 bytes, checksum 0x09490118, sha256 25146f57e5c57b537146373684d9d1a2321c5a9161766675cf932e2c89f231e0",
+ "loca": "16 bytes, checksum 0x013501FB, sha256 aae769be1e60c3529a0063171e29d79ca147197e2ed59c40dcbc5bbb949381ed",
+ "maxp": "32 bytes, checksum 0x00100051, sha256 a90cbb1baa544bddcc9065d5a64a0fb38a90285012ee709966a26b270ec7f551",
+ "name": "612 bytes, checksum 0x31BF4960, sha256 23c77f4598d07365d682aa25460b20f76e80c4c511e019e04b5390477f8ebb4b",
+ "post": "32 bytes, checksum 0xFF860032, sha256 22dd499f08932214bde96943f3882f3619324478217095c049b68a57c6c10693"
+ },
+ "maxUni": 0
+ }
+ }
+}
diff --git a/tests/data/ttf/NotoSansTC-MacaoLangSys-Synthetic.ttf b/tests/data/ttf/NotoSansTC-MacaoLangSys-Synthetic.ttf
new file mode 100644
index 000000000..d84eaa7cc
Binary files /dev/null and b/tests/data/ttf/NotoSansTC-MacaoLangSys-Synthetic.ttf differ