using Cryville.Culture; using System; using System.Collections.Generic; using System.Linq; namespace Cryville.Common.Font { public abstract class FontMatcher { protected FontManager Manager { get; private set; } public FontMatcher(FontManager manager) { Manager = manager; } public abstract IEnumerable MatchLanguage(LanguageId lang, bool distinctFamily = false); } public class FallbackListFontMatcher : FontMatcher { readonly LanguageMatching _matcher; static readonly string UltimateFallbackScript = "zzzz"; public Dictionary> MapScriptToTypefaces = new(); public static Dictionary> GetDefaultWindowsFallbackMap() { var map = new Dictionary>(StringComparer.OrdinalIgnoreCase); FillKeysWithScripts(map, () => new List()); // Reference: https://github.com/chromium/chromium/blob/main/third_party/blink/renderer/platform/fonts/win/font_fallback_win.cc map[UltimateFallbackScript].Insert(0, "SimSun"); // Custom map[UltimateFallbackScript].Insert(0, "SimHei"); // Custom map[UltimateFallbackScript].Insert(0, "Microsoft YaHei"); // Custom map[UltimateFallbackScript].Insert(0, "Arial"); map[UltimateFallbackScript].Insert(0, "Times New Roman"); map[UltimateFallbackScript].Insert(0, "Segoe UI"); // Custom map["arab"].Insert(0, "Tahoma"); map["cyrl"].Insert(0, "Times New Roman"); map["grek"].Insert(0, "Times New Roman"); map["hebr"].Insert(0, "David"); map["jpan"].Insert(0, "MS PGothic"); map["latn"].Insert(0, "Times New Roman"); map["hans"].Insert(0, "SimSun"); map["hans"].Insert(0, "SimHei"); // Custom map["thai"].Insert(0, "Tahoma"); map["hans"].Insert(0, "PMingLiU"); // Reference: https://learn.microsoft.com/en-us/globalization/input/font-support var ver = Environment.OSVersion.Version; if (ver >= new Version(5, 0)) { // Windows 2000 map["armn"].Insert(0, "Sylfaen"); map["deva"].Insert(0, "Mangal"); map["geor"].Insert(0, "Sylfaen"); map["taml"].Insert(0, "Latha"); } if (ver >= new Version(5, 1)) { // Windows XP map["gujr"].Insert(0, "Shruti"); map["guru"].Insert(0, "Raavi"); map["knda"].Insert(0, "Tunga"); map["syrc"].Insert(0, "Estrangelo Edessa"); map["telu"].Insert(0, "Gautami"); map["thaa"].Insert(0, "MV Boli"); // SP2 map["beng"].Insert(0, "Vrinda"); map["mlym"].Insert(0, "Kartika"); } if (ver >= new Version(6, 0)) { // Windows Vista map["cans"].Insert(0, "Euphemia"); map["cher"].Insert(0, "Plantagenet"); map["ethi"].Insert(0, "Nyala"); map["khmr"].Insert(0, "DaunPenh MoolBoran"); map["kore"].Insert(0, "Malgun Gothic"); // Reference: https://en.wikipedia.org/wiki/List_of_typefaces_included_with_Microsoft_Windows map["laoo"].Insert(0, "DokChampa"); map["mong"].Insert(0, "Mongolian Baiti"); map["orya"].Insert(0, "Kalinga"); map["sinh"].Insert(0, "Iskoola Pota"); map["tibt"].Insert(0, "Microsoft Himalaya"); map["yiii"].Insert(0, "Microsoft Yi Baiti"); map["arab"].Insert(0, "Segoe UI"); map["cyrl"].Insert(0, "Segoe UI"); map["grek"].Insert(0, "Segoe UI"); map["latn"].Insert(0, "Segoe UI"); map["hans"].Add("SimSun-ExtB"); map["hant"].Add("MingLiU-ExtB"); map["hant"].Add("MingLiU_HKSCS-ExtB"); map["arab"].Add("Microsoft Uighur"); map["zmth"].Insert(0, "Cambria Math"); // Reference: https://en.wikipedia.org/wiki/List_of_CJK_fonts map["jpan"].Insert(0, "Meiryo"); map["hans"].Insert(0, "Microsoft YaHei"); } if (ver >= new Version(6, 1)) { // Windows 7 map["brai"].Insert(0, "Segoe UI Symbol"); map["dsrt"].Insert(0, "Segoe UI Symbol"); map["talu"].Insert(0, "Microsoft New Tai Lue"); map["ogam"].Insert(0, "Segoe UI Symbol"); map["osma"].Insert(0, "Ebrima"); map["phag"].Insert(0, "Microsoft PhagsPa"); map["runr"].Insert(0, "Segoe UI Symbol"); map["zsym"].Insert(0, "Segoe UI Symbol"); map["tale"].Insert(0, "Microsoft Tai Le"); map["tfng"].Insert(0, "Ebrima"); map["vaii"].Insert(0, "Ebrima"); } if (ver >= new Version(6, 2)) { // Windows 8 map["glag"].Insert(0, "Segoe UI Symbol"); map["goth"].Insert(0, "Segoe UI Symbol"); map["hang"].Add("Malgun Gothic"); map["ital"].Insert(0, "Segoe UI Symbol"); map["lisu"].Insert(0, "Segoe UI"); map["mymr"].Insert(0, "Myanmar Text"); map["nkoo"].Insert(0, "Ebrima"); map["orkh"].Insert(0, "Segoe UI Symbol"); map["ethi"].Insert(0, "Ebrima"); map["cans"].Insert(0, "Gadugi"); map["hant"].Insert(0, "Microsoft JhengHei UI"); map["hans"].Insert(0, "Microsoft YaHei UI"); map["beng"].Insert(0, "Nirmala UI"); map["deva"].Insert(0, "Nirmala UI"); map["gujr"].Insert(0, "Nirmala UI"); map["guru"].Insert(0, "Nirmala UI"); // NOT DOCUMENTED, UNVERIFIED map["knda"].Insert(0, "Nirmala UI"); // NOT DOCUMENTED, UNVERIFIED map["mlym"].Insert(0, "Nirmala UI"); map["orya"].Insert(0, "Nirmala UI"); map["sinh"].Insert(0, "Nirmala UI"); // NOT DOCUMENTED, UNVERIFIED map["taml"].Insert(0, "Nirmala UI"); // NOT DOCUMENTED, UNVERIFIED map["telu"].Insert(0, "Nirmala UI"); map["armn"].Insert(0, "Segoe UI"); map["geor"].Insert(0, "Segoe UI"); map["hebr"].Insert(0, "Segoe UI"); } if (ver >= new Version(6, 3)) { // Windows 8.1 map["bugi"].Insert(0, "Leelawadee UI"); map["copt"].Insert(0, "Segoe UI Symbol"); map["java"].Insert(0, "Javanese Text"); map["merc"].Insert(0, "Segoe UI Symbol"); map["olck"].Insert(0, "Nirmala UI"); map["sora"].Insert(0, "Nirmala UI"); map["khmr"].Insert(0, "Leelawadee UI"); map["laoo"].Insert(0, "Leelawadee UI"); map["thai"].Insert(0, "Leelawadee UI"); map["zsye"].Insert(0, "Segoe UI Emoji"); } if (ver >= new Version(10, 0)) { // Windows 10 map["brah"].Insert(0, "Segoe UI Historic"); map["cari"].Insert(0, "Segoe UI Historic"); map["cprt"].Insert(0, "Segoe UI Historic"); map["egyp"].Insert(0, "Segoe UI Historic"); map["armi"].Insert(0, "Segoe UI Historic"); map["phli"].Insert(0, "Segoe UI Historic"); map["prti"].Insert(0, "Segoe UI Historic"); map["khar"].Insert(0, "Segoe UI Historic"); map["lyci"].Insert(0, "Segoe UI Historic"); map["lydi"].Insert(0, "Segoe UI Historic"); map["phnx"].Insert(0, "Segoe UI Historic"); map["xpeo"].Insert(0, "Segoe UI Historic"); map["sarb"].Insert(0, "Segoe UI Historic"); map["shaw"].Insert(0, "Segoe UI Historic"); map["xsux"].Insert(0, "Segoe UI Historic"); map["ugar"].Insert(0, "Segoe UI Historic"); // Segoe UI Symbol -> Segoe UI Historic map["glag"].Insert(0, "Segoe UI Historic"); map["goth"].Insert(0, "Segoe UI Historic"); map["merc"].Insert(0, "Segoe UI Historic"); map["ogam"].Insert(0, "Segoe UI Historic"); map["ital"].Insert(0, "Segoe UI Historic"); map["orkh"].Insert(0, "Segoe UI Historic"); map["runr"].Insert(0, "Segoe UI Historic"); // map["jpan"].Insert(0, "Yu Gothic UI"); map["zsym"].Add("Segoe MDL2 Assets"); } return map; } public static Dictionary> GetDefaultAndroidFallbackMap() { var map = new Dictionary>(StringComparer.OrdinalIgnoreCase); FillKeysWithScripts(map, () => new List()); map[UltimateFallbackScript].Insert(0, "Noto Sans CJK TC"); // TODO Modify default fallback map[UltimateFallbackScript].Insert(0, "Noto Sans CJK JP"); map[UltimateFallbackScript].Insert(0, "Noto Sans CJK SC"); map[UltimateFallbackScript].Insert(0, "Roboto"); map["zsye"].Insert(0, "Noto Color Emoji"); map["zsye"].Add("Noto Color Emoji Flags"); map["arab"].Insert(0, "Noto Naskh Arabic"); map["adlm"].Insert(0, "Noto Sans Adlam"); map["ahom"].Insert(0, "Noto Sans Ahom"); map["hluw"].Insert(0, "Noto Sans Anatolian Hieroglyphs"); map["armn"].Insert(0, "Noto Sans Armenian"); map["avst"].Insert(0, "Noto Sans Avestan"); map["bali"].Insert(0, "Noto Sans Balinese"); map["bamu"].Insert(0, "Noto Sans Bamum"); map["bass"].Insert(0, "Noto Sans Bassa Vah"); map["batk"].Insert(0, "Noto Sans Batak"); map["beng"].Insert(0, "Noto Sans Bengali"); map["bhks"].Insert(0, "Noto Sans Bhaiksuki"); map["brah"].Insert(0, "Noto Sans Brahmi"); map["bugi"].Insert(0, "Noto Sans Buginese"); map["buhd"].Insert(0, "Noto Sans Buhid"); map["jpan"].Insert(0, "Noto Sans CJK JP"); map["kore"].Insert(0, "Noto Sans CJK KR"); map["hans"].Insert(0, "Noto Sans CJK SC"); map["hant"].Insert(0, "Noto Sans CJK TC"); map["hant"].Add("Noto Sans CJK HK"); map["cans"].Insert(0, "Noto Sans Canadian Aboriginal"); map["cari"].Insert(0, "Noto Sans Carian"); map["cakm"].Insert(0, "Noto Sans Chakma"); map["cham"].Insert(0, "Noto Sans Cham"); map["cher"].Insert(0, "Noto Sans Cherokee"); map["copt"].Insert(0, "Noto Sans Coptic"); map["xsux"].Insert(0, "Noto Sans Cuneiform"); map["cprt"].Insert(0, "Noto Sans Cypriot"); map["dsrt"].Insert(0, "Noto Sans Deseret"); map["deva"].Insert(0, "Noto Sans Devanagari"); map["egyp"].Insert(0, "Noto Sans Egyptian Hieroglyphs"); map["elba"].Insert(0, "Noto Sans Elbasan"); map["ethi"].Insert(0, "Noto Sans Ethiopic"); map["geor"].Insert(0, "Noto Sans Georgian"); map["glag"].Insert(0, "Noto Sans Glagolitic"); map["goth"].Insert(0, "Noto Sans Gothic"); map["gran"].Insert(0, "Noto Sans Grantha"); map["gujr"].Insert(0, "Noto Sans Gujarati"); map["gong"].Insert(0, "Noto Sans Gunjala Gondi"); map["guru"].Insert(0, "Noto Sans Gurmukhi"); map["rohg"].Insert(0, "Noto Sans Hanifi Rohingya"); map["hano"].Insert(0, "Noto Sans Hanunoo"); map["hatr"].Insert(0, "Noto Sans Hatran"); map["hebr"].Insert(0, "Noto Sans Hebrew"); map["armi"].Insert(0, "Noto Sans Imperial Aramaic"); map["phli"].Insert(0, "Noto Sans Inscriptional Pahlavi"); map["prti"].Insert(0, "Noto Sans Inscriptional Parthian"); map["java"].Insert(0, "Noto Sans Javanese"); map["kthi"].Insert(0, "Noto Sans Kaithi"); map["knda"].Insert(0, "Noto Sans Kannada"); map["kali"].Insert(0, "Noto Sans KayahLi"); map["khar"].Insert(0, "Noto Sans Kharoshthi"); map["khmr"].Insert(0, "Noto Sans Khmer"); map["khoj"].Insert(0, "Noto Sans Khojki"); map["laoo"].Insert(0, "Noto Sans Lao"); map["lepc"].Insert(0, "Noto Sans Lepcha"); map["limb"].Insert(0, "Noto Sans Limbu"); map["lina"].Insert(0, "Noto Sans Linear A"); map["linb"].Insert(0, "Noto Sans Linear B"); map["lisu"].Insert(0, "Noto Sans Lisu"); map["lyci"].Insert(0, "Noto Sans Lycian"); map["lydi"].Insert(0, "Noto Sans Lydian"); map["mlym"].Insert(0, "Noto Sans Malayalam"); map["mand"].Insert(0, "Noto Sans Mandiac"); map["mani"].Insert(0, "Noto Sans Manichaean"); map["marc"].Insert(0, "Noto Sans Marchen"); map["gonm"].Insert(0, "Noto Sans Masaram Gondi"); map["medf"].Insert(0, "Noto Sans Medefaidrin"); map["mtei"].Insert(0, "Noto Sans Meetei Mayek"); map["merc"].Insert(0, "Noto Sans Meroitic"); map["mero"].Insert(0, "Noto Sans Meroitic"); map["plrd"].Insert(0, "Noto Sans Miao"); map["modi"].Insert(0, "Noto Sans Modi"); map["mong"].Insert(0, "Noto Sans Mongolian"); map["mroo"].Insert(0, "Noto Sans Mro"); map["mult"].Insert(0, "Noto Sans Multani"); map["mymr"].Insert(0, "Noto Sans Myanmar"); map["nkoo"].Insert(0, "Noto Sans Nko"); map["nbat"].Insert(0, "Noto Sans Nabataean"); map["talu"].Insert(0, "Noto Sans New Tai Lue"); map["newa"].Insert(0, "Noto Sans Newa"); map["ogam"].Insert(0, "Noto Sans Ogham"); map["olck"].Insert(0, "Noto Sans Ol Chiki"); map["ital"].Insert(0, "Noto Sans Old Italian"); map["narb"].Insert(0, "Noto Sans Old North Arabian"); map["perm"].Insert(0, "Noto Sans Old Permic"); map["xpeo"].Insert(0, "Noto Sans Old Persian"); map["sarb"].Insert(0, "Noto Sans Old South Arabian"); map["orkh"].Insert(0, "Noto Sans Old Turkic"); map["orya"].Insert(0, "Noto Sans Oriya"); map["osge"].Insert(0, "Noto Sans Osage"); map["osma"].Insert(0, "Noto Sans Osmanya"); map["hmng"].Insert(0, "Noto Sans Pahawh Hmong"); map["palm"].Insert(0, "Noto Sans Palmyrene"); map["pauc"].Insert(0, "Noto Sans Pau Cin Hau"); map["phag"].Insert(0, "Noto Sans Phags Pa"); map["phnx"].Insert(0, "Noto Sans Phoenician"); map["rjng"].Insert(0, "Noto Sans Rejang"); map["runr"].Insert(0, "Noto Sans Runic"); map["samr"].Insert(0, "Noto Sans Samaritan"); map["saur"].Insert(0, "Noto Sans Saurashtra"); map["shrd"].Insert(0, "Noto Sans Sharada"); map["shaw"].Insert(0, "Noto Sans Shavian"); map["sinh"].Insert(0, "Noto Sans Sinhala"); map["sora"].Insert(0, "Noto Sans Sora Sompeng"); map["soyo"].Insert(0, "Noto Sans Soyombo"); map["sund"].Insert(0, "Noto Sans Sundanese"); map["sylo"].Insert(0, "Noto Sans Syloti Nagri"); map["zsym"].Insert(0, "Noto Sans Symbols"); map["syrc"].Add("Noto Sans Syriac Eastern"); map["syrc"].Add("Noto Sans Syriac Western"); map["syrc"].Add("Noto Sans Syriac Estrangela"); map["tglg"].Insert(0, "Noto Sans Tagalog"); map["tagb"].Insert(0, "Noto Sans Tagbanwa"); map["tale"].Insert(0, "Noto Sans Tai Le"); map["lana"].Insert(0, "Noto Sans Tai Tham"); map["tavt"].Insert(0, "Noto Sans Tai Viet"); map["takr"].Insert(0, "Noto Sans Takri"); map["taml"].Insert(0, "Noto Sans Tamil"); map["telu"].Insert(0, "Noto Sans Telugu"); map["thaa"].Insert(0, "Noto Sans Thaana"); map["thai"].Insert(0, "Noto Sans Thai"); map["tfng"].Insert(0, "Noto Sans Tifinagh"); map["ugar"].Insert(0, "Noto Sans Ugaritic"); map["vaii"].Insert(0, "Noto Sans Vai"); map["wcho"].Insert(0, "Noto Sans Wancho"); map["wara"].Insert(0, "Noto Sans Warang Citi"); map["yiii"].Insert(0, "Noto Sans Yi"); return map; } static void FillKeysWithScripts(IDictionary map, Func value) { foreach (var s in IdValidity.Enumerate("script")) map.Add(s, value()); } public FallbackListFontMatcher(LanguageMatching matcher, FontManager manager) : base(manager) { _matcher = matcher; } public override IEnumerable MatchLanguage(LanguageId lang, bool distinctFamily = false) { var supported = MapScriptToTypefaces.Keys.Select(i => new LanguageId(i)).ToList(); bool flag = false; while (_matcher.Match(lang, supported, out var match, out var distance)) { if (distance > 40) break; Shared.Logger.Log(0, "UI", "Matching fonts for language {0}, distance = {1}", match, distance); if (match.Script.Equals(UltimateFallbackScript, StringComparison.OrdinalIgnoreCase)) { flag = true; } var candidates = MapScriptToTypefaces[match.Script]; foreach (var typeface in EnumerateTypefaces(candidates, distinctFamily)) { yield return typeface; } supported.Remove(match); } if (flag) yield break; Shared.Logger.Log(0, "UI", "Matching fallback fonts"); foreach (var typeface in EnumerateTypefaces(MapScriptToTypefaces[UltimateFallbackScript], distinctFamily)) { yield return typeface; } } IEnumerable EnumerateTypefaces(List candidates, bool distinctFamily) { foreach (var candidate in candidates) { if (Manager.MapFullNameToTypeface.TryGetValue(candidate, out var typeface1)) { yield return typeface1; } if (distinctFamily) continue; if (Manager.MapNameToTypefaces.TryGetValue(candidate, out IReadOnlyCollection typefaces2)) { foreach (var typeface in typefaces2) { if (typeface1 == typeface) continue; yield return typeface; } } } } } }