diff --git a/Dalamud/Interface/ManagedFontAtlas/FluentGlyphRangeBuilder.cs b/Dalamud/Interface/ManagedFontAtlas/FluentGlyphRangeBuilder.cs new file mode 100644 index 000000000..ec395a61f --- /dev/null +++ b/Dalamud/Interface/ManagedFontAtlas/FluentGlyphRangeBuilder.cs @@ -0,0 +1,361 @@ +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.Unicode; + +using Dalamud.Interface.ManagedFontAtlas.Internals; + +namespace Dalamud.Interface.ManagedFontAtlas; + +/// A fluent ImGui glyph range builder. +[SuppressMessage( + "StyleCop.CSharp.SpacingRules", + "SA1010:Opening square brackets should be spaced correctly", + Justification = "No")] +public struct FluentGlyphRangeBuilder +{ + private const int ImUnicodeCodepointMax = char.MaxValue; + + private BitArray? characters; + + /// Clears the builder. + /// this for method chaining. + /// A builder is in cleared state on first use. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public FluentGlyphRangeBuilder Clear() + { + this.characters?.SetAll(false); + return this; + } + + /// Adds a single codepoint to the builder. + /// The codepoint to add. + /// this for method chaining. + /// Unsupported codepoints will be ignored. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public FluentGlyphRangeBuilder With(char codepoint) => this.With((int)codepoint); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public FluentGlyphRangeBuilder With(uint codepoint) => + codepoint <= char.MaxValue ? this.With((int)codepoint) : this; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public FluentGlyphRangeBuilder With(int codepoint) + { + if (codepoint <= ImUnicodeCodepointMax) + this.EnsureCharacters().Set(codepoint, true); + return this; + } + + /// Adds a unicode range to the builder. + /// The unicode range to add. + /// this for method chaining. + /// Unsupported codepoints will be ignored. + public FluentGlyphRangeBuilder With(UnicodeRange range) => + this.With(range.FirstCodePoint, (range.FirstCodePoint + range.Length) - 1); + + /// Adds unicode ranges to the builder. + /// The 1st unicode range to add. + /// The 2st unicode range to add. + /// this for method chaining. + /// Unsupported codepoints will be ignored. + public FluentGlyphRangeBuilder With(UnicodeRange range1, UnicodeRange range2) => + this.With(range1.FirstCodePoint, (range1.FirstCodePoint + range1.Length) - 1) + .With(range2.FirstCodePoint, (range2.FirstCodePoint + range2.Length) - 1); + + /// Adds unicode ranges to the builder. + /// The 1st unicode range to add. + /// The 2st unicode range to add. + /// The 3rd unicode range to add. + /// this for method chaining. + /// Unsupported codepoints will be ignored. + public FluentGlyphRangeBuilder With(UnicodeRange range1, UnicodeRange range2, UnicodeRange range3) => + this.With(range1.FirstCodePoint, (range1.FirstCodePoint + range1.Length) - 1) + .With(range2.FirstCodePoint, (range2.FirstCodePoint + range2.Length) - 1) + .With(range3.FirstCodePoint, (range3.FirstCodePoint + range3.Length) - 1); + + /// Adds unicode ranges to the builder. + /// The 1st unicode range to add. + /// The 2st unicode range to add. + /// The 3rd unicode range to add. + /// The 4th unicode range to add. + /// Even more unicode ranges to add. + /// this for method chaining. + /// Unsupported codepoints will be ignored. + public FluentGlyphRangeBuilder With( + UnicodeRange range1, + UnicodeRange range2, + UnicodeRange range3, + UnicodeRange range4, + params UnicodeRange[] evenMoreRanges) => + this.With(range1.FirstCodePoint, (range1.FirstCodePoint + range1.Length) - 1) + .With(range2.FirstCodePoint, (range2.FirstCodePoint + range2.Length) - 1) + .With(range3.FirstCodePoint, (range3.FirstCodePoint + range3.Length) - 1) + .With(range4.FirstCodePoint, (range4.FirstCodePoint + range4.Length) - 1) + .With(evenMoreRanges); + + /// Adds unicode ranges to the builder. + /// Unicode ranges to add. + /// this for method chaining. + /// Unsupported codepoints will be ignored. + public FluentGlyphRangeBuilder With(IEnumerable ranges) + { + foreach (var range in ranges) + this.With(range); + return this; + } + + /// Adds a range of characters to the builder. + /// The first codepoint, inclusive. + /// The last codepoint, inclusive. + /// this for method chaining. + /// + /// Unsupported codepoints will be ignored. + /// If is more than , then they will be swapped. + /// + public FluentGlyphRangeBuilder With(char from, char to) => + this.With(Math.Clamp(from, int.MinValue, int.MaxValue), Math.Clamp(to, int.MinValue, int.MaxValue)); + + /// + public FluentGlyphRangeBuilder With(uint from, uint to) => + this.With((int)Math.Min(from, int.MaxValue), (int)Math.Min(to, int.MaxValue)); + + /// + public FluentGlyphRangeBuilder With(int from, int to) + { + from = Math.Clamp(from, 1, ImUnicodeCodepointMax); + to = Math.Clamp(to, 1, ImUnicodeCodepointMax); + if (from > to) + (from, to) = (to, from); + + var bits = this.EnsureCharacters(); + for (; from <= to; from++) + bits.Set(from, true); + return this; + } + + /// Adds characters from a UTF-8 character sequence. + /// The sequence. + /// this for method chaining. + /// Unsupported codepoints will be ignored. + public FluentGlyphRangeBuilder With(ReadOnlySpan utf8Sequence) + { + var bits = this.EnsureCharacters(); + while (!utf8Sequence.IsEmpty) + { + if (Rune.DecodeFromUtf8(utf8Sequence, out var rune, out var len) == OperationStatus.Done + && rune.Value < ImUnicodeCodepointMax) + bits.Set(rune.Value, true); + utf8Sequence = utf8Sequence[len..]; + } + + return this; + } + + /// Adds characters from a UTF-8 character sequence. + /// The sequence. + /// this for method chaining. + /// Unsupported codepoints will be ignored. + public FluentGlyphRangeBuilder With(IEnumerable utf8Sequence) + { + Span buf = stackalloc byte[4]; + var bufp = 0; + var bits = this.EnsureCharacters(); + foreach (var b in utf8Sequence) + { + buf[bufp++] = b; + + while (Rune.DecodeFromUtf8(buf[..bufp], out var rune, out var len) is var state + && state != OperationStatus.NeedMoreData) + { + switch (state) + { + case OperationStatus.Done when rune.Value <= ImUnicodeCodepointMax: + bits.Set(rune.Value, true); + goto case OperationStatus.InvalidData; + + case OperationStatus.InvalidData: + bufp -= len; + break; + + case OperationStatus.NeedMoreData: + case OperationStatus.DestinationTooSmall: + default: + throw new InvalidOperationException($"Unexpected return from {Rune.DecodeFromUtf8}."); + } + } + } + + return this; + } + + /// Adds characters from a UTF-16 character sequence. + /// The sequence. + /// this for method chaining. + /// Unsupported codepoints will be ignored. + public FluentGlyphRangeBuilder With(ReadOnlySpan utf16Sequence) + { + var bits = this.EnsureCharacters(); + while (!utf16Sequence.IsEmpty) + { + if (Rune.DecodeFromUtf16(utf16Sequence, out var rune, out var len) == OperationStatus.Done + && rune.Value <= ImUnicodeCodepointMax) + bits.Set(rune.Value, true); + utf16Sequence = utf16Sequence[len..]; + } + + return this; + } + + /// Adds characters from a UTF-16 character sequence. + /// The sequence. + /// this for method chaining. + /// Unsupported codepoints will be ignored. + public FluentGlyphRangeBuilder With(IEnumerable utf16Sequence) + { + var bits = this.EnsureCharacters(); + foreach (var c in utf16Sequence) + { + if (!char.IsSurrogate(c)) + bits.Set(c, true); + } + + return this; + } + + /// Adds characters from a string. + /// The string. + /// this for method chaining. + /// Unsupported codepoints will be ignored. + public FluentGlyphRangeBuilder With(string @string) => this.With(@string.AsSpan()); + + /// Adds glyphs that are likely to be used in the given culture to the builder. + /// A culture info. + /// this for method chaining. + /// Unsupported codepoints will be ignored. Unsupported culture will do nothing. + /// Do make a PR if you need more. + public FluentGlyphRangeBuilder WithLanguage(CultureInfo cultureInfo) + { + // Call in chunks of three to avoid allocating arrays. + // Avoid adding ranges that goes over BMP; that is, ranges that goes over ImUnicodeCodepointMax. + switch (cultureInfo.TwoLetterISOLanguageName) + { + case "ja": + // http://www.rikai.com/library/kanjitables/kanji_codes.unicode.shtml + return + this + .With( + UnicodeRanges.CjkSymbolsandPunctuation, + UnicodeRanges.Hiragana, + UnicodeRanges.Katakana) + .With( + UnicodeRanges.HalfwidthandFullwidthForms, + UnicodeRanges.CjkUnifiedIdeographs, + UnicodeRanges.CjkUnifiedIdeographsExtensionA) + // Blame Japanese cell carriers for the below. + .With( + UnicodeRanges.EnclosedCjkLettersandMonths); + case "zh": + return + this + .With( + UnicodeRanges.CjkUnifiedIdeographs, + UnicodeRanges.CjkUnifiedIdeographsExtensionA); + case "ko": + return + this + .With( + UnicodeRanges.HangulJamo, + UnicodeRanges.HangulCompatibilityJamo, + UnicodeRanges.HangulSyllables) + .With( + UnicodeRanges.HangulJamoExtendedA, + UnicodeRanges.HangulJamoExtendedB); + default: + return this; + } + } + + /// Adds glyphs that are likely to be used in the given culture to the builder. + /// A language tag that will be used to locate the culture info. + /// this for method chaining. + /// See documentation for supported language tags. + /// + public FluentGlyphRangeBuilder WithLanguage(string languageTag) => + this.WithLanguage(CultureInfo.GetCultureInfo(languageTag)); + + /// Builds the accumulated data into an ImGui glyph range. + /// Whether to add the default fallback codepoints to the range. + /// Whether to add the default ellipsis codepoints to the range. + /// The built ImGui glyph ranges. + public ushort[] Build(bool addFallbackCodepoints = true, bool addEllipsisCodepoints = true) + { + if (addFallbackCodepoints) + this.With(FontAtlasFactory.FallbackCodepoints); + if (addEllipsisCodepoints) + this.With(FontAtlasFactory.EllipsisCodepoints).With('.'); + return this.BuildExact(); + } + + /// Builds the accumulated data into an ImGui glyph range, exactly as specified. + /// The built ImGui glyph ranges. + public ushort[] BuildExact() + { + if (this.characters is null) + return [0]; + var bits = this.characters; + + // Count the number of ranges first. + var numRanges = 0; + var lastCodepoint = -1; + for (var i = 1; i <= ImUnicodeCodepointMax; i++) + { + if (bits.Get(i)) + { + if (lastCodepoint == -1) + lastCodepoint = i; + } + else + { + if (lastCodepoint != -1) + { + numRanges++; + lastCodepoint = -1; + } + } + } + + // Handle the final range that terminates on the ending boundary. + if (lastCodepoint != -1) + numRanges++; + + // Allocate the array and build the range. + var res = GC.AllocateUninitializedArray((numRanges * 2) + 1); + var resp = 0; + for (var i = 1; i <= ImUnicodeCodepointMax; i++) + { + if (bits.Get(i) == ((resp & 1) == 0)) + res[resp++] = unchecked((ushort)i); + } + + // Handle the final range that terminates on the ending boundary. + if ((resp & 1) == 1) + res[resp++] = ImUnicodeCodepointMax; + + // Add the zero terminator. + res[resp] = 0; + + return res; + } + + /// Ensures that is not null, by creating one as necessary. + /// An instance of . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private BitArray EnsureCharacters() => this.characters ??= new(ImUnicodeCodepointMax + 1); +} diff --git a/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildToolkitUtilities.cs b/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildToolkitUtilities.cs index 4c3e9023a..3b8bfd965 100644 --- a/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildToolkitUtilities.cs +++ b/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildToolkitUtilities.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Text.Unicode; using Dalamud.Interface.Utility; @@ -12,6 +13,34 @@ namespace Dalamud.Interface.ManagedFontAtlas; /// public static class FontAtlasBuildToolkitUtilities { + /// Begins building a new array of containing ImGui glyph ranges. + /// The chars. + /// A new range builder. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FluentGlyphRangeBuilder BeginGlyphRange(this IEnumerable chars) => + default(FluentGlyphRangeBuilder).With(chars); + + /// Begins building a new array of containing ImGui glyph ranges. + /// The chars. + /// A new range builder. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FluentGlyphRangeBuilder BeginGlyphRange(this ReadOnlySpan chars) => + default(FluentGlyphRangeBuilder).With(chars); + + /// Begins building a new array of containing ImGui glyph ranges. + /// The chars. + /// A new range builder. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FluentGlyphRangeBuilder BeginGlyphRange(this string chars) => + default(FluentGlyphRangeBuilder).With(chars); + + /// Begins building a new array of containing ImGui glyph ranges. + /// The unicode range. + /// A new range builder. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FluentGlyphRangeBuilder BeginGlyphRange(this UnicodeRange range) => + default(FluentGlyphRangeBuilder).With(range); + /// /// Compiles given s into an array of containing ImGui glyph ranges. /// @@ -19,16 +48,12 @@ public static class FontAtlasBuildToolkitUtilities /// Add fallback codepoints to the range. /// Add ellipsis codepoints to the range. /// The compiled range. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort[] ToGlyphRange( this IEnumerable enumerable, bool addFallbackCodepoints = true, - bool addEllipsisCodepoints = true) - { - using var builderScoped = ImGuiHelpers.NewFontGlyphRangeBuilderPtrScoped(out var builder); - foreach (var c in enumerable) - builder.AddChar(c); - return builder.BuildRangesToArray(addFallbackCodepoints, addEllipsisCodepoints); - } + bool addEllipsisCodepoints = true) => + enumerable.BeginGlyphRange().Build(addFallbackCodepoints, addEllipsisCodepoints); /// /// Compiles given s into an array of containing ImGui glyph ranges. @@ -37,16 +62,12 @@ public static class FontAtlasBuildToolkitUtilities /// Add fallback codepoints to the range. /// Add ellipsis codepoints to the range. /// The compiled range. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort[] ToGlyphRange( this ReadOnlySpan span, bool addFallbackCodepoints = true, - bool addEllipsisCodepoints = true) - { - using var builderScoped = ImGuiHelpers.NewFontGlyphRangeBuilderPtrScoped(out var builder); - foreach (var c in span) - builder.AddChar(c); - return builder.BuildRangesToArray(addFallbackCodepoints, addEllipsisCodepoints); - } + bool addEllipsisCodepoints = true) => + span.BeginGlyphRange().Build(addFallbackCodepoints, addEllipsisCodepoints); /// /// Compiles given string into an array of containing ImGui glyph ranges. @@ -55,11 +76,12 @@ public static class FontAtlasBuildToolkitUtilities /// Add fallback codepoints to the range. /// Add ellipsis codepoints to the range. /// The compiled range. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort[] ToGlyphRange( this string @string, bool addFallbackCodepoints = true, bool addEllipsisCodepoints = true) => - @string.AsSpan().ToGlyphRange(addFallbackCodepoints, addEllipsisCodepoints); + @string.BeginGlyphRange().Build(addFallbackCodepoints, addEllipsisCodepoints); /// /// Finds the corresponding in diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPreBuild.cs b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPreBuild.cs index 9b80d27ff..b32e3db18 100644 --- a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPreBuild.cs +++ b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPreBuild.cs @@ -1,4 +1,5 @@ -using System.IO; +using System.Globalization; +using System.IO; using System.Runtime.InteropServices; using Dalamud.Interface.FontIdentifier; @@ -8,6 +9,8 @@ using Dalamud.Utility; using ImGuiNET; +using TerraFX.Interop.DirectX; + namespace Dalamud.Interface.ManagedFontAtlas; /// @@ -216,10 +219,35 @@ public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit /// The added font. ImFontPtr AddGameGlyphs(GameFontStyle gameFontStyle, ushort[]? glyphRanges, ImFontPtr mergeFont); + /// Adds glyphs from the Windows default font for the given culture info into the provided font. + /// The culture info. + /// The font config. If is not set, then + /// will be used as the target. If that is empty too, then it will do + /// nothing. + /// The font weight, in range from 1 to 1000. 400 is regular(normal). + /// + /// The font stretch, in range from 1 to 9. 5 is medium(normal). + /// + /// The font style, in range from 0 to 2. 0 is normal. + /// + /// May do nothing at all if is unsupported by Dalamud font handler. + /// See + /// Microsoft + /// Learn for the fonts. + /// + void AttachWindowsDefaultFont( + CultureInfo cultureInfo, + in SafeFontConfig fontConfig, + int weight = (int)DWRITE_FONT_WEIGHT.DWRITE_FONT_WEIGHT_NORMAL, + int stretch = (int)DWRITE_FONT_STRETCH.DWRITE_FONT_STRETCH_NORMAL, + int style = (int)DWRITE_FONT_STYLE.DWRITE_FONT_STYLE_NORMAL); + /// /// Adds glyphs of extra languages into the provided font, depending on Dalamud Configuration.
/// will be ignored. ///
- /// The font config. + /// The font config. If is not set, then + /// will be used as the target. If that is empty too, then it will do + /// nothing. void AttachExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig); } diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs index 55af20329..34d28ccbd 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs @@ -1,9 +1,9 @@ using System.Buffers; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; -using System.Text.Unicode; using Dalamud.Configuration.Internal; using Dalamud.Interface.FontIdentifier; @@ -17,6 +17,8 @@ using ImGuiNET; using SharpDX.DXGI; +using TerraFX.Interop.DirectX; + namespace Dalamud.Interface.ManagedFontAtlas.Internals; /// @@ -433,9 +435,163 @@ internal sealed partial class FontAtlasFactory public ImFontPtr AddGameGlyphs(GameFontStyle gameFontStyle, ushort[]? glyphRanges, ImFontPtr mergeFont) => this.gameFontHandleSubstance.AttachGameGlyphs(this, mergeFont, gameFontStyle, glyphRanges); + /// + public void AttachWindowsDefaultFont( + CultureInfo cultureInfo, + in SafeFontConfig fontConfig, + int weight = (int)DWRITE_FONT_WEIGHT.DWRITE_FONT_WEIGHT_NORMAL, + int stretch = (int)DWRITE_FONT_STRETCH.DWRITE_FONT_STRETCH_NORMAL, + int style = (int)DWRITE_FONT_STYLE.DWRITE_FONT_STYLE_NORMAL) + { + var targetFont = fontConfig.MergeFont; + if (targetFont.IsNull()) + targetFont = this.Font; + if (targetFont.IsNull()) + return; + + // https://learn.microsoft.com/en-us/windows/apps/design/globalizing/loc-international-fonts + var splitTag = cultureInfo.IetfLanguageTag.Split("-"); + foreach (var test in new[] + { + cultureInfo.IetfLanguageTag, + $"{splitTag[0]}-{splitTag[^1]}", + }) + { + var familyName = test switch + { + "af-ZA" => "Segoe UI", + "am-ET" => "Ebrima", + "ar-SA" => "Segoe UI", + "as-IN" => "Nirmala UI", + "az-Latn-AZ" => "Segoe UI", + "be-BY" => "Segoe UI", + "bg-BG" => "Segoe UI", + "bn-BD" => "Nirmala UI", + "bn-IN" => "Nirmala UI", + "bs-Latn-BA" => "Segoe UI", + "ca-ES" => "Segoe UI", + "ca-ES-valencia" => "Segoe UI", + "chr-CHER-US" => "Gadugi", + "cs-CZ" => "Segoe UI", + "cy-GB" => "Segoe UI", + "da-DK" => "Segoe UI", + "de-DE" => "Segoe UI", + "el-GR" => "Segoe UI", + "en-GB" => "Segoe UI", + "es-ES" => "Segoe UI", + "et-EE" => "Segoe UI", + "eu-ES" => "Segoe UI", + "fa-IR" => "Segoe UI", + "fi-FI" => "Segoe UI", + "fil-PH" => "Segoe UI", + "fr-FR" => "Segoe UI", + "ga-IE" => "Segoe UI", + "gd-GB" => "Segoe UI", + "gl-ES" => "Segoe UI", + "gu-IN" => "Nirmala UI", + "ha-Latn-NG" => "Segoe UI", + "he-IL" => "Segoe UI", + "hi-IN" => "Nirmala UI", + "hr-HR" => "Segoe UI", + "hu-HU" => "Segoe UI", + "hy-AM" => "Segoe UI", + "id-ID" => "Segoe UI", + "ig-NG" => "Segoe UI", + "is-IS" => "Segoe UI", + "it-IT" => "Segoe UI", + "ja-JP" => "Yu Gothic UI", + "ka-GE" => "Segoe UI", + "kk-KZ" => "Segoe UI", + "km-KH" => "Leelawadee UI", + "kn-IN" => "Nirmala UI", + "ko-KR" => "Malgun Gothic", + "kok-IN" => "Nirmala UI", + "ku-ARAB-IQ" => "Segoe UI", + "ky-KG" => "Segoe UI", + "lb-LU" => "Segoe UI", + "lt-LT" => "Segoe UI", + "lv-LV" => "Segoe UI", + "mi-NZ" => "Segoe UI", + "mk-MK" => "Segoe UI", + "ml-IN" => "Nirmala UI", + "mn-MN" => "Segoe UI", + "mr-IN" => "Nirmala UI", + "ms-MY" => "Segoe UI", + "mt-MT" => "Segoe UI", + "nb-NO" => "Segoe UI", + "ne-NP" => "Nirmala UI", + "nl-NL" => "Segoe UI", + "nn-NO" => "Segoe UI", + "nso-ZA" => "Segoe UI", + "or-IN" => "Nirmala UI", + "pa-Arab-PK" => "Segoe UI", + "pa-IN" => "Nirmala UI", + "pl-PL" => "Segoe UI", + "prs-AF" => "Segoe UI", + "pt-BR" => "Segoe UI", + "pt-PT" => "Segoe UI", + "qut-GT" => "Segoe UI", + "quz-PE" => "Segoe UI", + "ro-RO" => "Segoe UI", + "ru-RU" => "Segoe UI", + "rw-RW" => "Segoe UI", + "sd-Arab-PK" => "Segoe UI", + "si-LK" => "Nirmala UI", + "sk-SK" => "Segoe UI", + "sl-SI" => "Segoe UI", + "sq-AL" => "Segoe UI", + "sr-Cyrl-BA" => "Segoe UI", + "sr-Cyrl-CS" => "Segoe UI", + "sr-Latn-CS" => "Segoe UI", + "sv-SE" => "Segoe UI", + "sw-KE" => "Segoe UI", + "ta-IN" => "Nirmala UI", + "te-IN" => "Nirmala UI", + "tg-Cyrl-TJ" => "Segoe UI", + "th-TH" => "Leelawadee UI", + "ti-ET" => "Ebrima", + "tk-TM" => "Segoe UI", + "tn-ZA" => "Segoe UI", + "tr-TR" => "Segoe UI", + "tt-RU" => "Segoe UI", + "ug-CN" => "Segoe UI", + "uk-UA" => "Segoe UI", + "ur-PK" => "Segoe UI", + "uz-Latn-UZ" => "Segoe UI", + "vi-VN" => "Segoe UI", + "wo-SN" => "Segoe UI", + "xh-ZA" => "Segoe UI", + "yo-NG" => "Segoe UI", + "zh-CN" => "Microsoft YaHei UI", + "zh-HK" => "Microsoft JhengHei UI", + "zh-TW" => "Microsoft JhengHei UI", + "zh-Hans" => "Microsoft YaHei UI", + "zh-Hant" => "Microsoft YaHei UI", + "zu-ZA" => "Segoe UI", + _ => null, + }; + if (familyName is null) + continue; + var family = IFontFamilyId + .ListSystemFonts(false) + .FirstOrDefault( + x => x.EnglishName.Equals(familyName, StringComparison.InvariantCultureIgnoreCase)); + if (family?.Fonts[family.FindBestMatch(weight, stretch, style)] is not { } font) + return; + font.AddToBuildToolkit(this, fontConfig with { MergeFont = targetFont }); + return; + } + } + /// public void AttachExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig) { + var targetFont = fontConfig.MergeFont; + if (targetFont.IsNull()) + targetFont = this.Font; + if (targetFont.IsNull()) + return; + var dalamudConfiguration = Service.Get(); if (dalamudConfiguration.EffectiveLanguage == "ko" || Service.GetNullable()?.EncounteredHangul is true) @@ -444,41 +600,24 @@ internal sealed partial class FontAtlasFactory DalamudAsset.NotoSansKrRegular, fontConfig with { - GlyphRanges = ImGuiHelpers.CreateImGuiRangesFrom( - UnicodeRanges.HangulJamo, - UnicodeRanges.HangulCompatibilityJamo, - UnicodeRanges.HangulSyllables, - UnicodeRanges.HangulJamoExtendedA, - UnicodeRanges.HangulJamoExtendedB), + MergeFont = targetFont, + GlyphRanges = default(FluentGlyphRangeBuilder).WithLanguage("ko-kr").BuildExact(), }); } - var windowsDir = Environment.GetFolderPath(Environment.SpecialFolder.Windows); - var fontPathChs = Path.Combine(windowsDir, "Fonts", "msyh.ttc"); - if (!File.Exists(fontPathChs)) - fontPathChs = null; - - var fontPathCht = Path.Combine(windowsDir, "Fonts", "msjh.ttc"); - if (!File.Exists(fontPathCht)) - fontPathCht = null; - - if (fontPathCht != null && Service.Get().EffectiveLanguage == "tw") + if (Service.Get().EffectiveLanguage == "tw") { - this.AddFontFromFile(fontPathCht, fontConfig with + this.AttachWindowsDefaultFont(CultureInfo.GetCultureInfo("zh-hant"), fontConfig with { - GlyphRanges = ImGuiHelpers.CreateImGuiRangesFrom( - UnicodeRanges.CjkUnifiedIdeographs, - UnicodeRanges.CjkUnifiedIdeographsExtensionA), + GlyphRanges = default(FluentGlyphRangeBuilder).WithLanguage("zh-hant").BuildExact(), }); } - else if (fontPathChs != null && (Service.Get().EffectiveLanguage == "zh" - || Service.GetNullable()?.EncounteredHan is true)) + else if (Service.Get().EffectiveLanguage == "zh" + || Service.GetNullable()?.EncounteredHan is true) { - this.AddFontFromFile(fontPathChs, fontConfig with + this.AttachWindowsDefaultFont(CultureInfo.GetCultureInfo("zh-hans"), fontConfig with { - GlyphRanges = ImGuiHelpers.CreateImGuiRangesFrom( - UnicodeRanges.CjkUnifiedIdeographs, - UnicodeRanges.CjkUnifiedIdeographsExtensionA), + GlyphRanges = default(FluentGlyphRangeBuilder).WithLanguage("zh-hans").BuildExact(), }); } }