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