diff --git a/src/Shaper/OtlTags.php b/src/Shaper/OtlTags.php index 07c8c57e4..428d19b06 100644 --- a/src/Shaper/OtlTags.php +++ b/src/Shaper/OtlTags.php @@ -30,6 +30,46 @@ class OtlTags 'mym2' => 'mymr', ]; + /** + * Variant and script subtags with a language system of their own whatever the language, in the + * order HarfBuzz tests them. + */ + private static $variants = [ + 'fonnapa' => ['APPH'], + 'polyton' => ['PGR '], + 'arevmda' => ['HYE '], + 'provenc' => ['PRO '], + 'fonipa' => ['IPPH'], + 'geok' => ['KGE '], + 'syre' => ['SYRE'], + 'syrj' => ['SYRJ'], + 'syrn' => ['SYRN'], + ]; + + /** + * Retired tags HarfBuzz reads whole, before the extended language rule would read no-nyn as + * Nkole. + */ + private static $retired = [ + 'art-lojban' => ['JBO '], + 'i-hak' => ['ZHS '], + 'i-lux' => ['LTZ '], + 'i-navajo' => ['NAV ', 'ATH '], + 'no-bok' => ['NOR '], + 'no-nyn' => ['NYN '], + 'zh-min' => ['ZHS '], + 'zh-min-nan' => ['ZHS '], + ]; + + /** + * Languages that take another language system for one script or region. + */ + private static $languageSubtags = [ + 'ga' => ['latg' => ['IRT ']], + 'mnw' => ['th' => ['MONT']], + 'ro' => ['md' => ['MOL ', 'ROM ']], + ]; + /** * The Chinese regions with language systems of their own, in the order HarfBuzz looks for them. */ @@ -95,48 +135,112 @@ public static function language($ietf, $available) return ''; } - $subtags = $ietf ? explode('-', strtolower($ietf)) : []; + foreach (self::languageSystems($ietf) as $langsys) { + if (strpos($available, $langsys) !== false) { + return $langsys; + } + } + + return strpos($available, 'DFLT') !== false ? 'DFLT' : ''; + } + + /** + * The language systems for an IETF tag, in the order HarfBuzz 14.3.1 tries them + * (hb_ot_tags_from_language() in hb-ot-tag.cc and hb_ot_tags_from_complex_language() in + * hb-ot-tag-table.hh): + * + * 1. a variant or script that has its own language system in any language, e.g. -polyton + * 2. a retired tag, read whole + * 3. a script or region with its own language system in this language, e.g. ro-MD + * 4. Chinese scripts and regions + * 5. an extended language subtag, e.g. zh-yue + * 6. the language subtag + * + * Only the subtags before the first singleton count, so an extension or private use subtag + * selects nothing. A script is the subtag after the language; a region may be anywhere. + * + * An extended language Ucdn::$ot_languages has no key for keeps the language subtag's tags, where + * HarfBuzz would take an unlisted one as its own ISO 639-3 code. + * + * @param string $ietf + * + * @return string[] + */ + private static function languageSystems($ietf) + { + $tag = strtolower((string) $ietf); + if ($tag == '') { + return []; + } - $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]]]; + $subtags = explode('-', preg_replace('/-[^-]-.*/s', '', $tag)); + $language = array_shift($subtags); + if ($language == 'x') { + return []; } - foreach ($candidates as $langsys) { - if (strpos($available, $langsys) !== false) { + foreach (self::$variants as $variant => $langsys) { + if (in_array($variant, $subtags, true)) { return $langsys; } } - return strpos($available, 'DFLT') !== false ? 'DFLT' : ''; + if (isset(self::$retired[$tag])) { + return self::$retired[$tag]; + } + + $script = isset($subtags[0]) ? $subtags[0] : ''; + + if (isset(self::$languageSubtags[$language])) { + foreach (self::$languageSubtags[$language] as $subtag => $langsys) { + if (strlen($subtag) == 4 ? $script == $subtag : in_array($subtag, $subtags, true)) { + return $langsys; + } + } + } + + $own = self::tags($language); + + $chinese = self::chinese($own, $script, $subtags); + if ($chinese) { + return $chinese; + } + + // A three-digit region such as 419 has no key, so three characters that find one are a language + $extended = strlen($script) == 3 ? self::tags($script) : []; + + return $extended ?: $own; } /** - * The language systems for Chinese, in the order HarfBuzz tries them - * (hb_ot_tags_from_complex_language() in hb-ot-tag-table.hh). + * The Chinese language systems a script or region gives, in the order HarfBuzz tries them. * * 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. + * lacks it takes Hong Kong's ZHH before the default. * - * 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. + * Only Hans moves Cantonese (yue, ZHH) and Literary Chinese (lzh, ZHT) off their own tags. Every + * other Chinese language is Simplified by default and takes zh's rules. * - * @param string[] $subtags The lower-cased subtags of a tag whose language is zh + * @param string[] $own The language's own tags + * @param string $script The subtag after the language + * @param string[] $subtags The subtags after the language * - * @return string[] + * @return string[] The language systems, or none */ - private static function chinese(array $subtags) + private static function chinese(array $own, $script, array $subtags) { - $script = isset($subtags[1]) ? $subtags[1] : ''; - + if (!$own || !in_array($own[0], ['ZHS ', 'ZHT ', 'ZHH '], true)) { + return []; + } if ($script == 'hans') { return ['ZHS ']; } + if ($own[0] != 'ZHS ') { + return []; + } if ($script == 'hant') { - $region = isset($subtags[2]) ? $subtags[2] : ''; + $region = isset($subtags[1]) ? $subtags[1] : ''; return $region == 'hk' || $region == 'mo' ? self::$chineseRegions[$region] : ['ZHT ']; } @@ -147,7 +251,17 @@ private static function chinese(array $subtags) } } - return ['ZHS ']; + return []; + } + + /** + * @param string $language A language subtag + * + * @return string[] Its language systems in Ucdn::$ot_languages, or none + */ + private static function tags($language) + { + return isset(Ucdn::$ot_languages[$language]) ? (array) Ucdn::$ot_languages[$language] : []; } /** diff --git a/src/Ucdn.php b/src/Ucdn.php index 9378db229..103f7f741 100644 --- a/src/Ucdn.php +++ b/src/Ucdn.php @@ -471,12 +471,17 @@ public static function get_script($code) 'bxr' => 'RBU ', /* Russian Buriat */ 'byn' => 'BIL ', /* Bilen */ 'ca' => 'CAT ', /* Catalan */ + 'cdo' => 'ZHS ', /* Min Dong Chinese */ 'ce' => 'CHE ', /* Chechen */ 'ceb' => 'CEB ', /* Cebuano */ 'chp' => 'CHP ', /* Chipewyan */ 'chr' => 'CHR ', /* Cherokee */ + 'cjy' => 'ZHS ', /* Jinyu Chinese */ 'ckt' => 'CHK ', /* Chukchi */ + 'cmn' => 'ZHS ', /* Mandarin Chinese */ + 'cnp' => 'ZHS ', /* Northern Ping Chinese */ 'cop' => 'COP ', /* Coptic */ + 'cpx' => 'ZHS ', /* Pu-Xian Chinese */ 'cr' => 'CRE ', /* Cree */ 'crh' => 'CRT ', /* Crimean Tatar */ 'crj' => 'ECR ', /* [Southern] East Cree */ @@ -484,10 +489,13 @@ public static function get_script($code) 'crm' => 'MCR ', /* Moose Cree */ 'crx' => 'CRR ', /* Carrier */ 'cs' => 'CSY ', /* Czech */ + 'csp' => 'ZHS ', /* Southern Ping Chinese */ 'cu' => 'CSL ', /* Church Slavic */ 'cv' => 'CHU ', /* Chuvash */ 'cwd' => 'DCR ', /* Woods Cree */ 'cy' => 'WEL ', /* Welsh */ + 'czh' => 'ZHS ', /* Huizhou Chinese */ + 'czo' => 'ZHS ', /* Min Zhong Chinese */ 'da' => 'DAN ', /* Danish */ 'dap' => 'NIS ', /* Nisi (India) */ 'dar' => 'DAR ', /* Dargwa */ @@ -522,9 +530,10 @@ public static function get_script($code) 'fr' => 'FRA ', /* French */ 'fur' => 'FRL ', /* Friulian */ 'fy' => 'FRI ', /* Western Frisian */ - 'ga' => 'IRI ', /* Irish */ + 'ga' => ['IRI ', 'IRT '], /* Irish */ 'gaa' => 'GAD ', /* Ga */ 'gag' => 'GAG ', /* Gagauz */ + 'gan' => 'ZHS ', /* Gan Chinese */ 'gbm' => 'GAW ', /* Garhwali */ 'gd' => 'GAE ', /* Scottish Gaelic */ 'gez' => 'GEZ ', /* Ge'ez */ @@ -538,6 +547,7 @@ public static function get_script($code) 'guk' => 'GMZ ', /* Gumuz */ 'gv' => 'MNX ', /* Manx Gaelic */ 'ha' => 'HAU ', /* Hausa */ + 'hak' => 'ZHS ', /* Hakka Chinese */ 'har' => 'HRI ', /* Harari */ 'haw' => 'HAW ', /* Hawaiin */ 'he' => 'IWR ', /* Hebrew */ @@ -545,11 +555,13 @@ public static function get_script($code) 'hil' => 'HIL ', /* Hiligaynon */ 'hnd' => 'HND ', /* [Southern] Hindko */ 'hne' => 'CHH ', /* Chattisgarhi */ + 'hnm' => 'ZHS ', /* Hainanese */ 'hno' => 'HND ', /* [Northern] Hindko */ 'hoc' => 'HO ', /* Ho */ 'hoj' => 'HAR ', /* Harauti */ 'hr' => 'HRV ', /* Croatian */ 'hsb' => 'USB ', /* Upper Sorbian */ + 'hsn' => 'ZHS ', /* Xiang Chinese */ 'ht' => 'HAI ', /* Haitian */ 'hu' => 'HUN ', /* Hungarian */ 'hy' => 'HYE ', /* Armenian */ @@ -619,10 +631,12 @@ public static function get_script($code) 'lt' => 'LTH ', /* Lithuanian */ 'lu' => 'LUB ', /* Luba-Katanga */ 'lua' => 'LUB ', /* Luba-Kasai */ + 'luh' => 'ZHS ', /* Leizhou Chinese */ 'luo' => 'LUO ', /* Luo (Kenya and Tanzania) */ 'lus' => 'MIZ ', /* Mizo */ 'luy' => 'LUH ', /* Luhya [macrolanguage] */ 'lv' => 'LVI ', /* Latvian */ + 'lzh' => 'ZHT ', /* Literary Chinese */ 'lzz' => 'LAZ ', /* Laz */ 'mai' => 'MTH ', /* Maithili */ 'mdc' => 'MLE ', /* Male (Papua New Guinea) */ @@ -638,6 +652,7 @@ public static function get_script($code) 'mnc' => 'MCH ', /* Manchu */ 'mni' => 'MNI ', /* Manipuri */ 'mnk' => 'MND ', /* Mandinka */ + 'mnp' => 'ZHS ', /* Min Bei Chinese */ 'mns' => 'MAN ', /* Mansi */ 'mnw' => 'MON ', /* Mon */ 'mo' => 'MOL ', /* Moldavian */ @@ -652,6 +667,7 @@ public static function get_script($code) 'mym' => 'MEN ', /* Me'en */ 'myv' => 'ERZ ', /* Erzya */ 'nag' => 'NAG ', /* Naga-Assamese */ + 'nan' => 'ZHS ', /* Min Nan Chinese */ 'nb' => 'NOR ', /* Norwegian Bokmål */ 'nco' => 'SIB ', /* Sibe */ 'nd' => 'NDB ', /* [North] Ndebele */ @@ -670,6 +686,7 @@ public static function get_script($code) 'nr' => 'NDB ', /* [South] Ndebele */ 'nsk' => 'NAS ', /* Naskapi */ 'nso' => 'SOT ', /* [Northern] Sotho */ + 'nv' => ['NAV ', 'ATH '], /* Navajo */ 'ny' => 'CHI ', /* Nyanja */ 'nyn' => 'NKL ', /* Nkole */ 'oc' => 'OCI ', /* Occitan (post 1500) */ @@ -711,6 +728,7 @@ public static function get_script($code) 'shn' => 'SHN ', /* Shan */ 'si' => 'SNH ', /* Sinhala */ 'sid' => 'SID ', /* Sidamo */ + 'sjc' => 'ZHS ', /* Shaojiang Chinese */ 'sjd' => 'KSM ', /* Kildin Sami */ 'sk' => 'SKY ', /* Slovak */ 'skr' => 'SRK ', /* Seraiki */ @@ -765,6 +783,7 @@ public static function get_script($code) 'wbm' => 'WA ', /* Wa */ 'wbr' => 'WAG ', /* Wagdi */ 'wo' => 'WLF ', /* Wolof */ + 'wuu' => 'ZHS ', /* Wu Chinese */ 'xal' => 'KLM ', /* Kalmyk */ 'xh' => 'XHS ', /* Xhosa */ 'xom' => 'KMO ', /* Komo (Sudan) */ @@ -773,13 +792,10 @@ public static function get_script($code) 'yid' => 'JII ', /* Yiddish */ 'yo' => 'YBA ', /* Yoruba */ 'yso' => 'NIS ', /* Nisi (China) */ + 'yue' => 'ZHH ', /* Yue Chinese */ + 'zh' => 'ZHS ', /* Chinese */ 'zne' => 'ZND ', /* Zande */ 'zu' => 'ZUL ', /* Zulu */ - 'zh-cn' => 'ZHS ', /* Chinese (China) */ - 'zh-hk' => 'ZHH ', /* Chinese (Hong Kong) */ - 'zh-mo' => 'ZHT ', /* Chinese (Macao) */ - 'zh-sg' => 'ZHS ', /* Chinese (Singapore) */ - 'zh-tw' => 'ZHT ', /* Chinese (Taiwan) */ ]; // hb-unicode.h diff --git a/tests/Mpdf/LanguageTagLangSysTest.php b/tests/Mpdf/LanguageTagLangSysTest.php new file mode 100644 index 000000000..e5e549078 --- /dev/null +++ b/tests/Mpdf/LanguageTagLangSysTest.php @@ -0,0 +1,64 @@ + 'utf-8', + 'fontDir' => [__DIR__ . '/../data/ttf'], + 'fontdata' => ['notolanguagetagssynthetic' => [ + 'R' => 'Noto-LanguageTags-Synthetic.ttf', + 'useOTL' => 0xFF, + ]], + 'default_font' => 'notolanguagetagssynthetic', + ]); + $mpdf->WriteHTML(sprintf('
%04X;
', $lang, $character)); + + $this->assertSame([$expected], array_values(unpack('N*', mb_convert_encoding($mpdf->drawnText[0], 'UTF-32BE', 'UTF-8')))); + } + + public function languages() + { + return [ + 'el-polyton, PGR' => ['el-polyton', 0x03B1, 0x03B2], + 'ga-Latg, IRT' => ['ga-Latg', 0x61, 0x69], + 'ro-MD, MOL' => ['ro-MD', 0x61, 0x6D], + 'mnw-TH, MONT' => ['mnw-TH', 0x1000, 0x1001], + 'oc-provenc, PRO' => ['oc-provenc', 0x61, 0x70], + 'syr-Syre, SYRE' => ['syr-Syre', 0x0710, 0x0712], + 'en-fonipa, IPPH' => ['en-fonipa', 0x61, 0x68], + 'ka-Geok, KGE' => ['ka-Geok', 0x10A0, 0x10A1], + 'yue, ZHH' => ['yue', 0x9AA8, 0x4E09], + 'yue-Hant-HK, ZHH' => ['yue-Hant-HK', 0x9AA8, 0x4E09], + 'cmn-Hans, ZHS' => ['cmn-Hans', 0x9AA8, 0x4E00], + 'lzh, ZHT' => ['lzh', 0x9AA8, 0x4E8C], + 'zh-yue, ZHH' => ['zh-yue', 0x9AA8, 0x4E09], + 'zh-lzh, ZHT' => ['zh-lzh', 0x9AA8, 0x4E8C], + 'ga, IRT after IRI' => ['ga', 0x61, 0x69], + 'nv, ATH after NAV' => ['nv', 0x61, 0x74], + 'cmn-Hant-TW, ZHT' => ['cmn-Hant-TW', 0x9AA8, 0x4E8C], + 'yue-Hans, ZHS' => ['yue-Hans', 0x9AA8, 0x4E00], + 'ro, ROM not offered' => ['ro', 0x61, 0x61], + ]; + } + +} diff --git a/tests/Mpdf/Shaper/OtlTagsTest.php b/tests/Mpdf/Shaper/OtlTagsTest.php index fbdc26eb2..5a7be3ed7 100644 --- a/tests/Mpdf/Shaper/OtlTagsTest.php +++ b/tests/Mpdf/Shaper/OtlTagsTest.php @@ -164,4 +164,110 @@ public function testMacaoFallsBackToHongKong() $this->assertSame('DFLT', OtlTags::language('zh-MO', 'DFLT ZHS ZHT ')); } + /** + * @dataProvider complexLanguages + */ + public function testSubtagsBeyondTheLanguageSelectTheLanguageSystemHarfBuzzDoes($ietf, $expected) + { + $offered = 'DFLT ATH ELL IPPH IRI IRT KAT KGE MOL MON MONT NAV OCI PGR PRO ROM SYR SYRE ZHH ZHS ZHT ZHTM '; + + $this->assertSame($expected, OtlTags::language($ietf, $offered)); + } + + public function complexLanguages() + { + return [ + 'el-polyton' => ['el-polyton', 'PGR '], + 'ga-Latg' => ['ga-Latg', 'IRT '], + 'ro-MD' => ['ro-MD', 'MOL '], + 'mnw-TH' => ['mnw-TH', 'MONT'], + 'oc-provenc' => ['oc-provenc', 'PRO '], + 'syr-Syre' => ['syr-Syre', 'SYRE'], + 'en-fonipa' => ['en-fonipa', 'IPPH'], + 'ka-Geok' => ['ka-Geok', 'KGE '], + 'yue' => ['yue', 'ZHH '], + 'yue-Hant-HK' => ['yue-Hant-HK', 'ZHH '], + 'cmn-Hans' => ['cmn-Hans', 'ZHS '], + 'lzh' => ['lzh', 'ZHT '], + 'zh-yue' => ['zh-yue', 'ZHH '], + 'zh-lzh' => ['zh-lzh', 'ZHT '], + 'a variant after a region' => ['el-GR-polyton', 'PGR '], + 'the region, after the script' => ['ro-Latn-MD', 'MOL '], + 'upper case' => ['RO-MD', 'MOL '], + 'Irish without Latg' => ['ga-IE', 'IRI '], + 'Mon outside Thailand' => ['mnw-MM', 'MON '], + 'a variant or region after a private use subtag' => ['ro-x-md', 'ROM '], + 'a variant after an extension' => ['el-u-polyton', 'ELL '], + 'a private use tag' => ['x-fonipa', 'DFLT'], + 'Cantonese, Traditional, in Taiwan' => ['yue-Hant-TW', 'ZHH '], + 'Cantonese, in Macao' => ['yue-MO', 'ZHH '], + 'Cantonese, Simplified' => ['yue-Hans', 'ZHS '], + 'Literary Chinese, in Hong Kong' => ['lzh-HK', 'ZHT '], + 'Literary Chinese, Simplified' => ['lzh-Hans', 'ZHS '], + 'Mandarin, Traditional, in Taiwan' => ['cmn-Hant-TW', 'ZHT '], + 'Mandarin, in Macao' => ['cmn-MO', 'ZHTM'], + 'Hakka, in Hong Kong' => ['hak-HK', 'ZHH '], + 'Min Nan, alone' => ['nan', 'ZHS '], + 'zh-yue, in Hong Kong, by the region' => ['zh-yue-HK', 'ZHH '], + 'zh-yue, Simplified, by the extended language' => ['zh-yue-Hans', 'ZHH '], + 'zh-cmn, Traditional, by the extended language' => ['zh-cmn-Hant', 'ZHS '], + 'Navajo' => ['nv', 'NAV '], + 'Navajo, retired' => ['i-navajo', 'NAV '], + ]; + } + + /** + * @dataProvider languageFallbacks + */ + public function testALanguageWithMoreThanOneTagTakesTheFirstTheScriptOffers($ietf, $offered, $expected) + { + $this->assertSame($expected, OtlTags::language($ietf, $offered)); + } + + public function languageFallbacks() + { + return [ + 'Moldova, with MOL' => ['ro-MD', 'DFLT MOL ROM ', 'MOL '], + 'Moldova, without MOL' => ['ro-MD', 'DFLT ROM ', 'ROM '], + 'Moldova, with neither' => ['ro-MD', 'DFLT ENG ', 'DFLT'], + 'Irish, with IRI' => ['ga', 'DFLT IRI IRT ', 'IRI '], + 'Irish, without IRI' => ['ga', 'DFLT IRT ', 'IRT '], + 'Irish, with neither' => ['ga', 'DFLT ENG ', 'DFLT'], + 'Navajo, without NAV' => ['nv', 'DFLT ATH ', 'ATH '], + 'Irish Traditional, without IRT' => ['ga-Latg', 'DFLT IRI ', 'DFLT'], + ]; + } + + /** + * HarfBuzz reads these whole. Read a subtag at a time, no-nyn would be Nkole. + * + * @dataProvider retiredTags + */ + public function testARetiredTagIsReadWhole($ietf, $expected) + { + $this->assertSame($expected, OtlTags::language($ietf, 'DFLT JBO LTZ NKL NOR NYN ZHS ')); + } + + public function retiredTags() + { + return [ + 'no-bok' => ['no-bok', 'NOR '], + 'no-nyn' => ['no-nyn', 'NYN '], + 'zh-min' => ['zh-min', 'ZHS '], + 'zh-min-nan' => ['zh-min-nan', 'ZHS '], + 'i-hak' => ['i-hak', 'ZHS '], + 'i-lux' => ['i-lux', 'LTZ '], + 'art-lojban' => ['art-lojban', 'JBO '], + ]; + } + + /** + * Ucdn::$ot_languages has no key for most extended languages. HarfBuzz's table gives most of them + * their macrolanguage's tag, as it gives Gulf Arabic ARA, so mPDF keeps the language subtag's. + */ + public function testAnExtendedLanguageWithNoTagKeepsTheLanguageSubtagsTag() + { + $this->assertSame('ARA ', OtlTags::language('ar-afb', 'DFLT ARA ')); + } + } diff --git a/tests/data/fontcache/Noto-LanguageTags-Synthetic.json b/tests/data/fontcache/Noto-LanguageTags-Synthetic.json new file mode 100644 index 000000000..d3981bec2 --- /dev/null +++ b/tests/data/fontcache/Noto-LanguageTags-Synthetic.json @@ -0,0 +1,278 @@ +{ + "_": "Generated by composer fontcache:update. See tests/Mpdf/Fonts/ParserGoldenMaster.php.", + "fullName": "Noto-LanguageTags-Synthetic", + "mtx": { + "GSUBScriptLang": { + "geor": "DFLT KGE ", + "grek": "DFLT PGR ", + "hani": "DFLT ZHH ZHS ZHT ", + "latn": "DFLT ATH IPPH IRT MOL PRO ", + "mym2": "DFLT MONT ", + "syrc": "DFLT SYRE " + }, + "GSUBFeatures": { + "geor": { + "KGE ": { + "locl": [ + 0 + ] + } + }, + "grek": { + "PGR ": { + "locl": [ + 1 + ] + } + }, + "hani": { + "ZHH ": { + "locl": [ + 2 + ] + }, + "ZHS ": { + "locl": [ + 3 + ] + }, + "ZHT ": { + "locl": [ + 4 + ] + } + }, + "latn": { + "ATH ": { + "locl": [ + 5 + ] + }, + "IPPH": { + "locl": [ + 6 + ] + }, + "IRT ": { + "locl": [ + 7 + ] + }, + "MOL ": { + "locl": [ + 8 + ] + }, + "PRO ": { + "locl": [ + 9 + ] + } + }, + "mym2": { + "MONT": { + "locl": [ + 10 + ] + } + }, + "syrc": { + "SYRE": { + "locl": [ + 11 + ] + } + } + }, + "GSUBLookups": [ + { + "Type": 1, + "Flag": 0, + "SubtableCount": 1, + "Subtables": [ + 426 + ], + "MarkFilteringSet": "" + }, + { + "Type": 1, + "Flag": 0, + "SubtableCount": 1, + "Subtables": [ + 446 + ], + "MarkFilteringSet": "" + }, + { + "Type": 1, + "Flag": 0, + "SubtableCount": 1, + "Subtables": [ + 466 + ], + "MarkFilteringSet": "" + }, + { + "Type": 1, + "Flag": 0, + "SubtableCount": 1, + "Subtables": [ + 480 + ], + "MarkFilteringSet": "" + }, + { + "Type": 1, + "Flag": 0, + "SubtableCount": 1, + "Subtables": [ + 494 + ], + "MarkFilteringSet": "" + }, + { + "Type": 1, + "Flag": 0, + "SubtableCount": 1, + "Subtables": [ + 514 + ], + "MarkFilteringSet": "" + }, + { + "Type": 1, + "Flag": 0, + "SubtableCount": 1, + "Subtables": [ + 528 + ], + "MarkFilteringSet": "" + }, + { + "Type": 1, + "Flag": 0, + "SubtableCount": 1, + "Subtables": [ + 542 + ], + "MarkFilteringSet": "" + }, + { + "Type": 1, + "Flag": 0, + "SubtableCount": 1, + "Subtables": [ + 556 + ], + "MarkFilteringSet": "" + }, + { + "Type": 1, + "Flag": 0, + "SubtableCount": 1, + "Subtables": [ + 570 + ], + "MarkFilteringSet": "" + }, + { + "Type": 1, + "Flag": 0, + "SubtableCount": 1, + "Subtables": [ + 590 + ], + "MarkFilteringSet": "" + }, + { + "Type": 1, + "Flag": 0, + "SubtableCount": 1, + "Subtables": [ + 610 + ], + "MarkFilteringSet": "" + } + ], + "GPOSScriptLang": [], + "GPOSFeatures": [], + "GPOSLookups": [], + "MarkGlyphSets": [], + "rtlPUAstr": "", + "haskernGPOS": false, + "hassmallcapsGSUB": false + }, + "cache": { + "GDEFdata.json": { + "GlyphClassBases": " 00061| 00068| 00069| 0006D| 00070| 00074| 003B1| 003B2| 00710| 00712| 01000| 01001| 010A0| 010A1| 04E00| 04E09| 04E8C| 09AA8", + "GlyphClassMarks": "", + "GlyphClassLigatures": "", + "GlyphClassComponents": "", + "MarkGlyphSets": [], + "MarkAttachmentType": [] + }, + "GSUB.dat": "622 bytes, sha256 57e022f98e985373333d011b0ae7d7a39d73da90186ea4c1a9b190b141d8f481", + "GSUBdata.json": [ + [ + { + "4256": 0 + } + ], + [ + { + "945": 0 + } + ], + [ + { + "39592": 0 + } + ], + [ + { + "39592": 0 + } + ], + [ + { + "39592": 0 + } + ], + [ + { + "97": 0 + } + ], + [ + { + "97": 0 + } + ], + [ + { + "97": 0 + } + ], + [ + { + "97": 0 + } + ], + [ + { + "97": 0 + } + ], + [ + { + "4096": 0 + } + ], + [ + { + "1808": 0 + } + ] + ] + } +} diff --git a/tests/data/otldump/Noto-LanguageTags-Synthetic.txt b/tests/data/otldump/Noto-LanguageTags-Synthetic.txt new file mode 100644 index 000000000..dbf2b3998 --- /dev/null +++ b/tests/data/otldump/Noto-LanguageTags-Synthetic.txt @@ -0,0 +1,17 @@ +