Add FluentGlyphRangeBuilder and AttachWindowsDefaultFont (#1745)

`FluentGlyphRangeBuilder` can build glyph ranges without having to
allocate native ImGui objects, and supports adding `ReadOnlySpan` and
`IEnumerable` of `char`s and `byte`s, `UnicodeRange`s, and known glyph
ranges for select cultures (CJK for now.)

Added `IFontAtlasBuildToolkitPreBuild.AttachWindowsDefaultFont` which
has a predefined list of default Windows fonts per language.
This commit is contained in:
srkizer 2024-03-30 07:06:10 +09:00 committed by GitHub
parent 2ea89e216a
commit 05c943df69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 594 additions and 44 deletions

View file

@ -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;
/// <summary>A fluent ImGui glyph range builder.</summary>
[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;
/// <summary>Clears the builder.</summary>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>A builder is in cleared state on first use.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public FluentGlyphRangeBuilder Clear()
{
this.characters?.SetAll(false);
return this;
}
/// <summary>Adds a single codepoint to the builder.</summary>
/// <param name="codepoint">The codepoint to add.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>Unsupported codepoints will be ignored.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public FluentGlyphRangeBuilder With(char codepoint) => this.With((int)codepoint);
/// <inheritdoc cref="With(char)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public FluentGlyphRangeBuilder With(uint codepoint) =>
codepoint <= char.MaxValue ? this.With((int)codepoint) : this;
/// <inheritdoc cref="With(char)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public FluentGlyphRangeBuilder With(int codepoint)
{
if (codepoint <= ImUnicodeCodepointMax)
this.EnsureCharacters().Set(codepoint, true);
return this;
}
/// <summary>Adds a unicode range to the builder.</summary>
/// <param name="range">The unicode range to add.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>Unsupported codepoints will be ignored.</remarks>
public FluentGlyphRangeBuilder With(UnicodeRange range) =>
this.With(range.FirstCodePoint, (range.FirstCodePoint + range.Length) - 1);
/// <summary>Adds unicode ranges to the builder.</summary>
/// <param name="range1">The 1st unicode range to add.</param>
/// <param name="range2">The 2st unicode range to add.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>Unsupported codepoints will be ignored.</remarks>
public FluentGlyphRangeBuilder With(UnicodeRange range1, UnicodeRange range2) =>
this.With(range1.FirstCodePoint, (range1.FirstCodePoint + range1.Length) - 1)
.With(range2.FirstCodePoint, (range2.FirstCodePoint + range2.Length) - 1);
/// <summary>Adds unicode ranges to the builder.</summary>
/// <param name="range1">The 1st unicode range to add.</param>
/// <param name="range2">The 2st unicode range to add.</param>
/// <param name="range3">The 3rd unicode range to add.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>Unsupported codepoints will be ignored.</remarks>
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);
/// <summary>Adds unicode ranges to the builder.</summary>
/// <param name="range1">The 1st unicode range to add.</param>
/// <param name="range2">The 2st unicode range to add.</param>
/// <param name="range3">The 3rd unicode range to add.</param>
/// <param name="range4">The 4th unicode range to add.</param>
/// <param name="evenMoreRanges">Even more unicode ranges to add.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>Unsupported codepoints will be ignored.</remarks>
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);
/// <summary>Adds unicode ranges to the builder.</summary>
/// <param name="ranges">Unicode ranges to add.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>Unsupported codepoints will be ignored.</remarks>
public FluentGlyphRangeBuilder With(IEnumerable<UnicodeRange> ranges)
{
foreach (var range in ranges)
this.With(range);
return this;
}
/// <summary>Adds a range of characters to the builder.</summary>
/// <param name="from">The first codepoint, inclusive.</param>
/// <param name="to">The last codepoint, inclusive.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>
/// <para>Unsupported codepoints will be ignored.</para>
/// <para>If <paramref name="from"/> is more than <paramref name="to"/>, then they will be swapped.</para>
/// </remarks>
public FluentGlyphRangeBuilder With(char from, char to) =>
this.With(Math.Clamp(from, int.MinValue, int.MaxValue), Math.Clamp(to, int.MinValue, int.MaxValue));
/// <inheritdoc cref="With(char,char)"/>
public FluentGlyphRangeBuilder With(uint from, uint to) =>
this.With((int)Math.Min(from, int.MaxValue), (int)Math.Min(to, int.MaxValue));
/// <inheritdoc cref="With(char,char)"/>
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;
}
/// <summary>Adds characters from a UTF-8 character sequence.</summary>
/// <param name="utf8Sequence">The sequence.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>Unsupported codepoints will be ignored.</remarks>
public FluentGlyphRangeBuilder With(ReadOnlySpan<byte> 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;
}
/// <summary>Adds characters from a UTF-8 character sequence.</summary>
/// <param name="utf8Sequence">The sequence.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>Unsupported codepoints will be ignored.</remarks>
public FluentGlyphRangeBuilder With(IEnumerable<byte> utf8Sequence)
{
Span<byte> 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;
}
/// <summary>Adds characters from a UTF-16 character sequence.</summary>
/// <param name="utf16Sequence">The sequence.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>Unsupported codepoints will be ignored.</remarks>
public FluentGlyphRangeBuilder With(ReadOnlySpan<char> 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;
}
/// <summary>Adds characters from a UTF-16 character sequence.</summary>
/// <param name="utf16Sequence">The sequence.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>Unsupported codepoints will be ignored.</remarks>
public FluentGlyphRangeBuilder With(IEnumerable<char> utf16Sequence)
{
var bits = this.EnsureCharacters();
foreach (var c in utf16Sequence)
{
if (!char.IsSurrogate(c))
bits.Set(c, true);
}
return this;
}
/// <summary>Adds characters from a string.</summary>
/// <param name="string">The string.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>Unsupported codepoints will be ignored.</remarks>
public FluentGlyphRangeBuilder With(string @string) => this.With(@string.AsSpan());
/// <summary>Adds glyphs that are likely to be used in the given culture to the builder.</summary>
/// <param name="cultureInfo">A culture info.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>Unsupported codepoints will be ignored. Unsupported culture will do nothing.
/// Do make a PR if you need more.</remarks>
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;
}
}
/// <summary>Adds glyphs that are likely to be used in the given culture to the builder.</summary>
/// <param name="languageTag">A language tag that will be used to locate the culture info.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>See <see cref="CultureInfo.GetCultureInfo(string)"/> documentation for supported language tags.
/// </remarks>
public FluentGlyphRangeBuilder WithLanguage(string languageTag) =>
this.WithLanguage(CultureInfo.GetCultureInfo(languageTag));
/// <summary>Builds the accumulated data into an ImGui glyph range.</summary>
/// <param name="addFallbackCodepoints">Whether to add the default fallback codepoints to the range.</param>
/// <param name="addEllipsisCodepoints">Whether to add the default ellipsis codepoints to the range.</param>
/// <returns>The built ImGui glyph ranges.</returns>
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();
}
/// <summary>Builds the accumulated data into an ImGui glyph range, exactly as specified.</summary>
/// <returns>The built ImGui glyph ranges.</returns>
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<ushort>((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;
}
/// <summary>Ensures that <see cref="characters"/> is not null, by creating one as necessary.</summary>
/// <returns>An instance of <see cref="BitArray"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private BitArray EnsureCharacters() => this.characters ??= new(ImUnicodeCodepointMax + 1);
}

View file

@ -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;
/// </summary>
public static class FontAtlasBuildToolkitUtilities
{
/// <summary>Begins building a new array of <see cref="ushort"/> containing ImGui glyph ranges.</summary>
/// <param name="chars">The chars.</param>
/// <returns>A new range builder.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static FluentGlyphRangeBuilder BeginGlyphRange(this IEnumerable<char> chars) =>
default(FluentGlyphRangeBuilder).With(chars);
/// <summary>Begins building a new array of <see cref="ushort"/> containing ImGui glyph ranges.</summary>
/// <param name="chars">The chars.</param>
/// <returns>A new range builder.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static FluentGlyphRangeBuilder BeginGlyphRange(this ReadOnlySpan<char> chars) =>
default(FluentGlyphRangeBuilder).With(chars);
/// <summary>Begins building a new array of <see cref="ushort"/> containing ImGui glyph ranges.</summary>
/// <param name="chars">The chars.</param>
/// <returns>A new range builder.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static FluentGlyphRangeBuilder BeginGlyphRange(this string chars) =>
default(FluentGlyphRangeBuilder).With(chars);
/// <summary>Begins building a new array of <see cref="ushort"/> containing ImGui glyph ranges.</summary>
/// <param name="range">The unicode range.</param>
/// <returns>A new range builder.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static FluentGlyphRangeBuilder BeginGlyphRange(this UnicodeRange range) =>
default(FluentGlyphRangeBuilder).With(range);
/// <summary>
/// Compiles given <see cref="char"/>s into an array of <see cref="ushort"/> containing ImGui glyph ranges.
/// </summary>
@ -19,16 +48,12 @@ public static class FontAtlasBuildToolkitUtilities
/// <param name="addFallbackCodepoints">Add fallback codepoints to the range.</param>
/// <param name="addEllipsisCodepoints">Add ellipsis codepoints to the range.</param>
/// <returns>The compiled range.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort[] ToGlyphRange(
this IEnumerable<char> 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);
/// <summary>
/// Compiles given <see cref="char"/>s into an array of <see cref="ushort"/> containing ImGui glyph ranges.
@ -37,16 +62,12 @@ public static class FontAtlasBuildToolkitUtilities
/// <param name="addFallbackCodepoints">Add fallback codepoints to the range.</param>
/// <param name="addEllipsisCodepoints">Add ellipsis codepoints to the range.</param>
/// <returns>The compiled range.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort[] ToGlyphRange(
this ReadOnlySpan<char> 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);
/// <summary>
/// Compiles given string into an array of <see cref="ushort"/> containing ImGui glyph ranges.
@ -55,11 +76,12 @@ public static class FontAtlasBuildToolkitUtilities
/// <param name="addFallbackCodepoints">Add fallback codepoints to the range.</param>
/// <param name="addEllipsisCodepoints">Add ellipsis codepoints to the range.</param>
/// <returns>The compiled range.</returns>
[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);
/// <summary>
/// Finds the corresponding <see cref="ImFontConfigPtr"/> in

View file

@ -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;
/// <summary>
@ -216,10 +219,35 @@ public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit
/// <returns>The added font.</returns>
ImFontPtr AddGameGlyphs(GameFontStyle gameFontStyle, ushort[]? glyphRanges, ImFontPtr mergeFont);
/// <summary>Adds glyphs from the Windows default font for the given culture info into the provided font.</summary>
/// <param name="cultureInfo">The culture info.</param>
/// <param name="fontConfig">The font config. If <see cref="SafeFontConfig.MergeFont"/> is not set, then
/// <see cref="IFontAtlasBuildToolkit.Font"/> will be used as the target. If that is empty too, then it will do
/// nothing.</param>
/// <param name="weight">The font weight, in range from <c>1</c> to <c>1000</c>. <c>400</c> is regular(normal).
/// </param>
/// <param name="stretch">The font stretch, in range from <c>1</c> to <c>9</c>. <c>5</c> is medium(normal).
/// </param>
/// <param name="style">The font style, in range from <c>0</c> to <c>2</c>. <c>0</c> is normal.</param>
/// <remarks>
/// <para>May do nothing at all if <paramref name="cultureInfo"/> is unsupported by Dalamud font handler.</para>
/// <para>See
/// <a href="https://learn.microsoft.com/en-us/windows/apps/design/globalizing/loc-international-fonts">Microsoft
/// Learn</a> for the fonts.</para>
/// </remarks>
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);
/// <summary>
/// Adds glyphs of extra languages into the provided font, depending on Dalamud Configuration.<br />
/// <see cref="SafeFontConfig.GlyphRanges"/> will be ignored.
/// </summary>
/// <param name="fontConfig">The font config.</param>
/// <param name="fontConfig">The font config. If <see cref="SafeFontConfig.MergeFont"/> is not set, then
/// <see cref="IFontAtlasBuildToolkit.Font"/> will be used as the target. If that is empty too, then it will do
/// nothing.</param>
void AttachExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig);
}

View file

@ -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;
/// <summary>
@ -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);
/// <inheritdoc/>
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;
}
}
/// <inheritdoc/>
public void AttachExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig)
{
var targetFont = fontConfig.MergeFont;
if (targetFont.IsNull())
targetFont = this.Font;
if (targetFont.IsNull())
return;
var dalamudConfiguration = Service<DalamudConfiguration>.Get();
if (dalamudConfiguration.EffectiveLanguage == "ko"
|| Service<DalamudIme>.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<DalamudConfiguration>.Get().EffectiveLanguage == "tw")
if (Service<DalamudConfiguration>.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<DalamudConfiguration>.Get().EffectiveLanguage == "zh"
|| Service<DalamudIme>.GetNullable()?.EncounteredHan is true))
else if (Service<DalamudConfiguration>.Get().EffectiveLanguage == "zh"
|| Service<DalamudIme>.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(),
});
}
}