344 lines
15 KiB
C#
344 lines
15 KiB
C#
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<Typeface> MatchLanguage(LanguageId lang, bool distinctFamily = false);
|
|
}
|
|
public class FallbackListFontMatcher : FontMatcher {
|
|
readonly LanguageMatching _matcher;
|
|
static readonly string UltimateFallbackScript = "zzzz";
|
|
public Dictionary<string, List<string>> MapScriptToTypefaces = new();
|
|
public static Dictionary<string, List<string>> GetDefaultWindowsFallbackMap() {
|
|
var map = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
|
|
FillKeysWithScripts(map, () => new List<string>());
|
|
// 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<string, List<string>> GetDefaultAndroidFallbackMap() {
|
|
var map = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
|
|
FillKeysWithScripts(map, () => new List<string>());
|
|
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<T>(IDictionary<string, T> map, Func<T> 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<Typeface> 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;
|
|
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;
|
|
foreach (var typeface in EnumerateTypefaces(MapScriptToTypefaces[UltimateFallbackScript], distinctFamily)) {
|
|
yield return typeface;
|
|
}
|
|
}
|
|
IEnumerable<Typeface> EnumerateTypefaces(List<string> 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 var typefaces2)) {
|
|
foreach (var typeface in typefaces2) {
|
|
if (typeface1 == typeface) continue;
|
|
yield return typeface;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|