B1: dedupe CJK/symbols merge via AddCjkAndSymbols, trim fallback range; FontsReady + report self-test
This commit is contained in:
+98
-61
@@ -61,6 +61,17 @@ public sealed class FontManager : IDisposable
|
||||
private ushort[] Ranges = [];
|
||||
private ushort[] JpRange = [];
|
||||
|
||||
// B1: trimmed remainder the NotoSansCjk fallback is the sole source for
|
||||
// (Hangul + Simplified-Han); excludes the Default/Latin block already merged
|
||||
// by the global font, so the fallback no longer re-merges the full Ranges array.
|
||||
private ushort[] CjkFallbackGlyphRange = [];
|
||||
|
||||
// Report accessor for the ctor self-test: built glyph-range array lengths so
|
||||
// the step can show the B1 dedup effect (a small trimmed fallback vs the large
|
||||
// primary range) in its on-disk report instead of a bare Pass.
|
||||
internal (int Ranges, int JpRange, int CjkFallback) GlyphRangeLengths =>
|
||||
(Ranges.Length, JpRange.Length, CjkFallbackGlyphRange.Length);
|
||||
|
||||
public static readonly HashSet<float> AxisFontSizeList =
|
||||
[
|
||||
9.6f,
|
||||
@@ -170,6 +181,42 @@ public sealed class FontManager : IDisposable
|
||||
|
||||
// Instance method so Ranges / JpRange are reachable without parameter
|
||||
// plumbing; PascalCase field names follow the existing class style.
|
||||
// B1: shared CJK + symbols tail for both the regular and italic delegate
|
||||
// fonts. Earlier-merged fonts win for shared codepoints (imgui MergeMode),
|
||||
// so this runs AFTER the primary font is set as config.MergeFont. The CJK
|
||||
// fallback is the sole Hangul/Simplified-Han source when UseHellionFont=true
|
||||
// (global=Inter-Light), so it stays in the chain — only its glyph range is
|
||||
// trimmed (CjkFallbackGlyphRange) to drop the Default-block/endonym overlap.
|
||||
// basePt sizes the fallback to match the primary font; the Japanese merge
|
||||
// keeps its own configured size and the full JpRange (which owns Traditional
|
||||
// Han such as 體 U+9AD4), so japanese↔fallback no longer overlap.
|
||||
private void AddCjkAndSymbols(
|
||||
IFontAtlasBuildToolkitPreBuild tk,
|
||||
SafeFontConfig config,
|
||||
float basePt
|
||||
)
|
||||
{
|
||||
config.SizePt = Plugin.Config.JapaneseFontV2.SizePt;
|
||||
config.GlyphRanges = JpRange;
|
||||
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
|
||||
|
||||
// v1.5.3: NotoSansCjk fallback covers Hangul, Simplified-Chinese-specific
|
||||
// Han (e.g. 简) and other CJK glyphs the primary (Inter Light / global font)
|
||||
// and the FFXIV Japanese font do not ship. B1: trimmed to CjkFallbackGlyphRange
|
||||
// so it no longer re-merges the Default block. Merged last so earlier fonts win.
|
||||
config.SizePt = basePt;
|
||||
config.GlyphRanges = CjkFallbackGlyphRange;
|
||||
AddFontWithFallback(
|
||||
tk,
|
||||
new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular),
|
||||
config,
|
||||
"noto-cjk-fallback"
|
||||
);
|
||||
|
||||
config.SizePt = ResolveSymbolsFontPt();
|
||||
tk.AddGameSymbol(config);
|
||||
}
|
||||
|
||||
private IFontHandle BuildRegularFontHandle(IFontAtlas atlas) =>
|
||||
atlas.NewDelegateFontHandle(e =>
|
||||
e.OnPreBuild(tk =>
|
||||
@@ -183,25 +230,7 @@ public sealed class FontManager : IDisposable
|
||||
? tk.AddFontFromMemory(bundledBytes, config, "Inter-Light")
|
||||
: AddFontWithFallback(tk, Plugin.Config.GlobalFontV2.FontId, config, "global");
|
||||
|
||||
config.SizePt = Plugin.Config.JapaneseFontV2.SizePt;
|
||||
config.GlyphRanges = JpRange;
|
||||
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
|
||||
|
||||
// v1.5.3: NotoSansCjk fallback covers Hangul, Simplified-Chinese
|
||||
// -specific Han (e.g. 简) and other CJK glyphs that the primary
|
||||
// (Inter Light / global font) and the FFXIV Japanese font do not
|
||||
// ship. Merged last so earlier fonts win for shared codepoints.
|
||||
config.SizePt = basePt;
|
||||
config.GlyphRanges = Ranges;
|
||||
AddFontWithFallback(
|
||||
tk,
|
||||
new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular),
|
||||
config,
|
||||
"noto-cjk-fallback"
|
||||
);
|
||||
|
||||
config.SizePt = ResolveSymbolsFontPt();
|
||||
tk.AddGameSymbol(config);
|
||||
AddCjkAndSymbols(tk, config, basePt);
|
||||
|
||||
tk.Font = config.MergeFont;
|
||||
})
|
||||
@@ -223,22 +252,7 @@ public sealed class FontManager : IDisposable
|
||||
"italic"
|
||||
);
|
||||
|
||||
config.SizePt = Plugin.Config.JapaneseFontV2.SizePt;
|
||||
config.GlyphRanges = JpRange;
|
||||
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
|
||||
|
||||
// v1.5.3: NotoSansCjk fallback (see BuildRegularFontHandle).
|
||||
config.SizePt = Plugin.Config.ItalicFontV2.SizePt;
|
||||
config.GlyphRanges = Ranges;
|
||||
AddFontWithFallback(
|
||||
tk,
|
||||
new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular),
|
||||
config,
|
||||
"noto-cjk-fallback"
|
||||
);
|
||||
|
||||
config.SizePt = ResolveSymbolsFontPt();
|
||||
tk.AddGameSymbol(config);
|
||||
AddCjkAndSymbols(tk, config, Plugin.Config.ItalicFontV2.SizePt);
|
||||
|
||||
tk.Font = config.MergeFont;
|
||||
})
|
||||
@@ -282,7 +296,11 @@ public sealed class FontManager : IDisposable
|
||||
|
||||
private unsafe void SetUpRanges()
|
||||
{
|
||||
ushort[] BuildRange(IReadOnlyList<ushort>? chars, params nint[] ranges)
|
||||
ushort[] BuildRange(
|
||||
IReadOnlyList<ushort>? chars,
|
||||
bool includeCommonExtras,
|
||||
params nint[] ranges
|
||||
)
|
||||
{
|
||||
var builder = new ImFontGlyphRangesBuilderPtr(ImGuiNative.ImFontGlyphRangesBuilder());
|
||||
foreach (var range in ranges)
|
||||
@@ -300,33 +318,43 @@ public sealed class FontManager : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
// Ingame supported ranges
|
||||
var reader = new FdtReader(Plugin.DataManager.GetFile("common/font/axis_12.fdt")!.Data);
|
||||
foreach (var c in reader.Glyphs)
|
||||
builder.AddChar(c.Char);
|
||||
// Common extras (Axis ingame glyphs, endonyms, enclosed alphanumerics)
|
||||
// belong to the primary/Japanese ranges only. The trimmed CJK fallback
|
||||
// (B1) skips them so it stays a pure Hangul/Simplified-Han remainder and
|
||||
// does not re-merge the Default-block work the global font already did.
|
||||
if (includeCommonExtras)
|
||||
{
|
||||
// Ingame supported ranges
|
||||
var reader = new FdtReader(
|
||||
Plugin.DataManager.GetFile("common/font/axis_12.fdt")!.Data
|
||||
);
|
||||
foreach (var c in reader.Glyphs)
|
||||
builder.AddChar(c.Char);
|
||||
|
||||
// French
|
||||
// Romanian
|
||||
builder.AddText("Œœ");
|
||||
builder.AddText("ĂăÂâÎîȘșȚț");
|
||||
// French
|
||||
// Romanian
|
||||
builder.AddText("Œœ");
|
||||
builder.AddText("ĂăÂâÎîȘșȚț");
|
||||
|
||||
// v1.5.3: language-dropdown endonyms. The dropdown renders
|
||||
// with the currently active font range; without these glyphs
|
||||
// a user on an English UI cannot read non-Latin language names
|
||||
// before switching. Auto-activation in Settings.Apply then
|
||||
// pulls in the full ExtraGlyphRange for the chosen locale.
|
||||
builder.AddText(
|
||||
"Català Čeština Dansk Deutsch Ελληνικά English Español Suomi"
|
||||
+ " Français Magyar Italiano 日本語 한국어 Norsk bokmål Nederlands"
|
||||
+ " Polski Português Brasil (Portugal) Română Русский Svenska"
|
||||
+ " Türkçe Українська 简体中文 繁體中文"
|
||||
);
|
||||
// v1.5.3: language-dropdown endonyms. The dropdown renders
|
||||
// with the currently active font range; without these glyphs
|
||||
// a user on an English UI cannot read non-Latin language names
|
||||
// before switching. Auto-activation in Settings.Apply then
|
||||
// pulls in the full ExtraGlyphRange for the chosen locale.
|
||||
builder.AddText(
|
||||
"Català Čeština Dansk Deutsch Ελληνικά English Español Suomi"
|
||||
+ " Français Magyar Italiano 日本語 한국어 Norsk bokmål Nederlands"
|
||||
+ " Polski Português Brasil (Portugal) Română Русский Svenska"
|
||||
+ " Türkçe Українська 简体中文 繁體中文"
|
||||
);
|
||||
|
||||
// "Enclosed Alphanumerics" (partial) https://www.compart.com/en/unicode/block/U+2460
|
||||
for (var i = 0x2460; i <= 0x24B5; i++)
|
||||
builder.AddChar((char)i);
|
||||
// "Enclosed Alphanumerics" (partial) https://www.compart.com/en/unicode/block/U+2460
|
||||
for (var i = 0x2460; i <= 0x24B5; i++)
|
||||
builder.AddChar((char)i);
|
||||
|
||||
builder.AddChar('⓪');
|
||||
}
|
||||
|
||||
builder.AddChar('⓪');
|
||||
return builder.BuildRangesToArray();
|
||||
}
|
||||
|
||||
@@ -356,8 +384,17 @@ public sealed class FontManager : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
Ranges = BuildRange(customChars.Count > 0 ? customChars : null, ranges.ToArray());
|
||||
JpRange = BuildRange(GlyphRangesJapanese.GlyphRanges);
|
||||
Ranges = BuildRange(
|
||||
customChars.Count > 0 ? customChars : null,
|
||||
includeCommonExtras: true,
|
||||
ranges.ToArray()
|
||||
);
|
||||
JpRange = BuildRange(GlyphRangesJapanese.GlyphRanges, includeCommonExtras: true);
|
||||
|
||||
// B1: the fallback gets only the trimmed Hangul/Simplified-Han remainder.
|
||||
// No Default block, no endonyms — those are already merged by the global and
|
||||
// Japanese fonts, so re-merging them on the fallback was wasted atlas work.
|
||||
CjkFallbackGlyphRange = BuildRange(CjkFallbackRange.Pairs, includeCommonExtras: false);
|
||||
}
|
||||
|
||||
// Add font with fallback to NotoSansCjkRegular if unavailable
|
||||
|
||||
@@ -86,6 +86,49 @@ internal sealed class FontManagerCtorSmokeStep : ISelfTestStep
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
// B1: assert the atlas actually finished building all required handles,
|
||||
// not just that the references are non-null. FontsReady is the observable
|
||||
// state the trimmed-fallback rebuild must still reach; a half-built atlas
|
||||
// would pass the null/exception checks above but fail here.
|
||||
if (!fm.FontsReady)
|
||||
{
|
||||
ImGui.Text("FontManager.FontsReady is false (atlas not fully built).");
|
||||
SelfTestReport.Append(
|
||||
Name,
|
||||
"FAIL",
|
||||
new[] { "FontsReady is false — atlas not fully built." }
|
||||
);
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
// Report what was actually verified (Flo's request: don't just show Pass).
|
||||
// The glyph-range entry counts make the B1 dedup visible — the cjk-fallback
|
||||
// range is now a small trimmed remainder next to the large primary range.
|
||||
var counts = fm.GlyphRangeLengths;
|
||||
var italicState =
|
||||
fm.ItalicFont is null ? "disabled (null)"
|
||||
: fm.ItalicFont.Available ? "available"
|
||||
: "NOT available";
|
||||
var path = SelfTestReport.Append(
|
||||
Name,
|
||||
"PASS",
|
||||
new[]
|
||||
{
|
||||
$"Axis available: {fm.Axis.Available}",
|
||||
$"AxisItalic available: {fm.AxisItalic.Available}",
|
||||
$"FontAwesome available: {fm.FontAwesome.Available}",
|
||||
$"RegularFont available: {fm.RegularFont.Available}",
|
||||
$"ItalicFont: {italicState}",
|
||||
$"FontsReady: {fm.FontsReady}",
|
||||
$"Glyph-range entries: primary={counts.Ranges}, jp={counts.JpRange}, "
|
||||
+ $"cjk-fallback={counts.CjkFallback} (B1 trimmed)",
|
||||
$"UseHellionFont={Plugin.Config.UseHellionFont}, ItalicEnabled={Plugin.Config.ItalicEnabled}",
|
||||
}
|
||||
);
|
||||
ImGui.Text(
|
||||
$"PASS — FontsReady, ranges primary={counts.Ranges}/jp={counts.JpRange}/"
|
||||
+ $"cjk-fallback={counts.CjkFallback}. Report: {path}"
|
||||
);
|
||||
return SelfTestStepResult.Pass;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user