using Cryville.Common.Culture; using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace Cryville.Common.Font { public abstract class FontManager { public IReadOnlyDictionary> MapFullNameToTypeface { get; private set; } public IReadOnlyDictionary> MapNameToTypefaces { get; private set; } public FontManager() { var map1 = new Dictionary>(); var map2 = new Dictionary>(); foreach (var f in EnumerateAllTypefaces()) { List set1; if (!map1.TryGetValue(f.FullName, out set1)) { map1.Add(f.FullName, set1 = new List()); } set1.Add(f); List set2; if (!map2.TryGetValue(f.FamilyName, out set2)) { map2.Add(f.FamilyName, set2 = new List()); } set2.Add(f); } MapFullNameToTypeface = map1.ToDictionary(i => i.Key, i => (IReadOnlyCollection)i.Value); MapNameToTypefaces = map2.ToDictionary(i => i.Key, i => (IReadOnlyCollection)i.Value); } protected abstract IEnumerable EnumerateAllTypefaces(); public abstract IEnumerable MatchScript(string script = null); public abstract IEnumerable MatchFamily(string[] candidates); protected static IEnumerable ScanDirectoryForTypefaces(string dir) { foreach (var f in new DirectoryInfo(dir).EnumerateFiles()) { FontFile file; try { file = FontFile.Create(f); } catch (InvalidDataException) { continue; } if (file == null) continue; var enumerator = file.GetEnumerator(); while (enumerator.MoveNext()) { Typeface ret; try { ret = enumerator.Current; } catch (InvalidDataException) { continue; } yield return ret; } file.Close(); } } } public class FontManagerAndroid : FontManager { protected override IEnumerable EnumerateAllTypefaces() { return ScanDirectoryForTypefaces("/system/fonts"); } public override IEnumerable MatchScript(string script = null) { throw new NotImplementedException(); } public override IEnumerable MatchFamily(string[] candidates) { throw new NotImplementedException(); } } public class FontManagerWindows : FontManager { public static Dictionary> MapScriptToTypefaces = new Dictionary>(); static FontManagerWindows() { if (Environment.OSVersion.Platform != PlatformID.Win32NT) return; 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, "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"); } } protected override IEnumerable EnumerateAllTypefaces() { return ScanDirectoryForTypefaces("C:/Windows/Fonts"); } public override IEnumerable MatchScript(string script = null) { 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) { Typeface typeface; if (MapFullNameToTypeface.TryGetValue(candidate, out typeface)) { yield return typeface; } IReadOnlyCollection typefaces; if (MapNameToTypefaces.TryGetValue(candidate, out typefaces)) { foreach (var typeface2 in typefaces) { if (typeface2 == typeface) continue; yield return typeface2; } } } } } candidateScripts = ScriptUtils.EnumerateFallbackScripts(script); } } public override IEnumerable MatchFamily(string[] candidates) { throw new NotImplementedException(); } } }