From b5696afe94b9ace8c58323a751b5bb88cae9cece Mon Sep 17 00:00:00 2001
From: goat <16760685+goaaats@users.noreply.github.com>
Date: Thu, 18 Jan 2024 21:37:05 +0100
Subject: [PATCH] Revert "IFontAtlas: font atlas per plugin"
---
.../Internal/DalamudConfiguration.cs | 7 +-
Dalamud/Interface/GameFonts/FdtFileView.cs | 159 --
.../GameFonts/GameFontFamilyAndSize.cs | 25 +-
.../GameFontFamilyAndSizeAttribute.cs | 37 -
Dalamud/Interface/GameFonts/GameFontHandle.cs | 85 +-
.../Interface/GameFonts/GameFontManager.cs | 507 ++++++
Dalamud/Interface/GameFonts/GameFontStyle.cs | 37 +-
Dalamud/Interface/Internal/DalamudIme.cs | 7 +-
.../Interface/Internal/DalamudInterface.cs | 13 +-
.../Interface/Internal/InterfaceManager.cs | 960 +++++++++---
.../Internal/Windows/ChangelogWindow.cs | 62 +-
.../Internal/Windows/Data/DataWindow.cs | 8 +-
.../Widgets/GamePrebakedFontsTestWidget.cs | 213 ---
.../Windows/Settings/SettingsWindow.cs | 29 +-
.../Windows/Settings/Tabs/SettingsTabAbout.cs | 30 +-
.../Windows/Settings/Tabs/SettingsTabLook.cs | 50 +-
.../Internal/Windows/TitleScreenMenuWindow.cs | 63 +-
.../FontAtlasAutoRebuildMode.cs | 22 -
.../ManagedFontAtlas/FontAtlasBuildStep.cs | 38 -
.../FontAtlasBuildStepDelegate.cs | 15 -
.../FontAtlasBuildToolkitUtilities.cs | 133 --
.../Interface/ManagedFontAtlas/IFontAtlas.cs | 141 --
.../IFontAtlasBuildToolkit.cs | 67 -
.../IFontAtlasBuildToolkitPostBuild.cs | 26 -
.../IFontAtlasBuildToolkitPostPromotion.cs | 33 -
.../IFontAtlasBuildToolkitPreBuild.cs | 186 ---
.../Interface/ManagedFontAtlas/IFontHandle.cs | 42 -
.../Internals/DelegateFontHandle.cs | 334 ----
.../FontAtlasFactory.BuildToolkit.cs | 682 --------
.../FontAtlasFactory.Implementation.cs | 726 ---------
.../Internals/FontAtlasFactory.cs | 368 -----
.../Internals/GamePrebakedFontHandle.cs | 857 ----------
.../Internals/IFontHandleManager.cs | 32 -
.../Internals/IFontHandleSubstance.cs | 54 -
.../Internals/TrueType.Common.cs | 203 ---
.../Internals/TrueType.Enums.cs | 84 -
.../Internals/TrueType.Files.cs | 148 --
.../Internals/TrueType.GposGsub.cs | 259 ---
.../Internals/TrueType.PointerSpan.cs | 443 ------
.../Internals/TrueType.Tables.cs | 1391 -----------------
.../ManagedFontAtlas/Internals/TrueType.cs | 135 --
.../ManagedFontAtlas/SafeFontConfig.cs | 306 ----
Dalamud/Interface/UiBuilder.cs | 182 +--
Dalamud/Interface/Utility/ImGuiHelpers.cs | 243 +--
44 files changed, 1499 insertions(+), 7943 deletions(-)
delete mode 100644 Dalamud/Interface/GameFonts/FdtFileView.cs
delete mode 100644 Dalamud/Interface/GameFonts/GameFontFamilyAndSizeAttribute.cs
create mode 100644 Dalamud/Interface/GameFonts/GameFontManager.cs
delete mode 100644 Dalamud/Interface/Internal/Windows/Data/Widgets/GamePrebakedFontsTestWidget.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/FontAtlasAutoRebuildMode.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildStep.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildStepDelegate.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildToolkitUtilities.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/IFontAtlas.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkit.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostBuild.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostPromotion.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPreBuild.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/Internals/DelegateFontHandle.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/Internals/GamePrebakedFontHandle.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/Internals/IFontHandleManager.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/Internals/IFontHandleSubstance.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.Common.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.Enums.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.Files.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.GposGsub.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.PointerSpan.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.Tables.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.cs
delete mode 100644 Dalamud/Interface/ManagedFontAtlas/SafeFontConfig.cs
diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs
index 66c2745c5..76c8f3603 100644
--- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs
+++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs
@@ -148,9 +148,12 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
public bool UseAxisFontsFromGame { get; set; } = false;
///
- /// Gets or sets the gamma value to apply for Dalamud fonts. Do not use.
+ /// Gets or sets the gamma value to apply for Dalamud fonts. Effects text thickness.
+ ///
+ /// Before gamma is applied...
+ /// * ...TTF fonts loaded with stb or FreeType are in linear space.
+ /// * ...the game's prebaked AXIS fonts are in gamma space with gamma value of 1.4.
///
- [Obsolete("It happens that nobody touched this setting", true)]
public float FontGammaLevel { get; set; } = 1.4f;
///
diff --git a/Dalamud/Interface/GameFonts/FdtFileView.cs b/Dalamud/Interface/GameFonts/FdtFileView.cs
deleted file mode 100644
index 896a6dbb4..000000000
--- a/Dalamud/Interface/GameFonts/FdtFileView.cs
+++ /dev/null
@@ -1,159 +0,0 @@
-using System.Collections.Generic;
-using System.IO;
-
-namespace Dalamud.Interface.GameFonts;
-
-///
-/// Reference member view of a .fdt file data.
-///
-internal readonly unsafe struct FdtFileView
-{
- private readonly byte* ptr;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// Pointer to the data.
- /// Length of the data.
- public FdtFileView(void* ptr, int length)
- {
- this.ptr = (byte*)ptr;
- if (length < sizeof(FdtReader.FdtHeader))
- throw new InvalidDataException("Not enough space for a FdtHeader");
-
- if (length < this.FileHeader.FontTableHeaderOffset + sizeof(FdtReader.FontTableHeader))
- throw new InvalidDataException("Not enough space for a FontTableHeader");
- if (length < this.FileHeader.FontTableHeaderOffset + sizeof(FdtReader.FontTableHeader) +
- (sizeof(FdtReader.FontTableEntry) * this.FontHeader.FontTableEntryCount))
- throw new InvalidDataException("Not enough space for all the FontTableEntry");
-
- if (length < this.FileHeader.KerningTableHeaderOffset + sizeof(FdtReader.KerningTableHeader))
- throw new InvalidDataException("Not enough space for a KerningTableHeader");
- if (length < this.FileHeader.KerningTableHeaderOffset + sizeof(FdtReader.KerningTableHeader) +
- (sizeof(FdtReader.KerningTableEntry) * this.KerningEntryCount))
- throw new InvalidDataException("Not enough space for all the KerningTableEntry");
- }
-
- ///
- /// Gets the file header.
- ///
- public ref FdtReader.FdtHeader FileHeader => ref *(FdtReader.FdtHeader*)this.ptr;
-
- ///
- /// Gets the font header.
- ///
- public ref FdtReader.FontTableHeader FontHeader =>
- ref *(FdtReader.FontTableHeader*)((nint)this.ptr + this.FileHeader.FontTableHeaderOffset);
-
- ///
- /// Gets the glyphs.
- ///
- public Span Glyphs => new(this.GlyphsUnsafe, this.FontHeader.FontTableEntryCount);
-
- ///
- /// Gets the kerning header.
- ///
- public ref FdtReader.KerningTableHeader KerningHeader =>
- ref *(FdtReader.KerningTableHeader*)((nint)this.ptr + this.FileHeader.KerningTableHeaderOffset);
-
- ///
- /// Gets the number of kerning entries.
- ///
- public int KerningEntryCount => Math.Min(this.FontHeader.KerningTableEntryCount, this.KerningHeader.Count);
-
- ///
- /// Gets the kerning entries.
- ///
- public Span PairAdjustments => new(
- this.ptr + this.FileHeader.KerningTableHeaderOffset + sizeof(FdtReader.KerningTableHeader),
- this.KerningEntryCount);
-
- ///
- /// Gets the maximum texture index.
- ///
- public int MaxTextureIndex
- {
- get
- {
- var i = 0;
- foreach (ref var g in this.Glyphs)
- {
- if (g.TextureIndex > i)
- i = g.TextureIndex;
- }
-
- return i;
- }
- }
-
- private FdtReader.FontTableEntry* GlyphsUnsafe =>
- (FdtReader.FontTableEntry*)(this.ptr + this.FileHeader.FontTableHeaderOffset +
- sizeof(FdtReader.FontTableHeader));
-
- ///
- /// Finds the glyph index for the corresponding codepoint.
- ///
- /// Unicode codepoint (UTF-32 value).
- /// Corresponding index, or a negative number according to .
- public int FindGlyphIndex(int codepoint)
- {
- var comp = FdtReader.CodePointToUtf8Int32(codepoint);
-
- var glyphs = this.GlyphsUnsafe;
- var lo = 0;
- var hi = this.FontHeader.FontTableEntryCount - 1;
- while (lo <= hi)
- {
- var i = (int)(((uint)hi + (uint)lo) >> 1);
- switch (comp.CompareTo(glyphs[i].CharUtf8))
- {
- case 0:
- return i;
- case > 0:
- lo = i + 1;
- break;
- default:
- hi = i - 1;
- break;
- }
- }
-
- return ~lo;
- }
-
- ///
- /// Create a glyph range for use with .
- ///
- /// Merge two ranges into one if distance is below the value specified in this parameter.
- /// Glyph ranges.
- public ushort[] ToGlyphRanges(int mergeDistance = 8)
- {
- var glyphs = this.Glyphs;
- var ranges = new List(glyphs.Length)
- {
- checked((ushort)glyphs[0].CharInt),
- checked((ushort)glyphs[0].CharInt),
- };
-
- foreach (ref var glyph in glyphs[1..])
- {
- var c32 = glyph.CharInt;
- if (c32 >= 0x10000)
- break;
-
- var c16 = unchecked((ushort)c32);
- if (ranges[^1] + mergeDistance >= c16 && c16 > ranges[^1])
- {
- ranges[^1] = c16;
- }
- else if (ranges[^1] + 1 < c16)
- {
- ranges.Add(c16);
- ranges.Add(c16);
- }
- }
-
- ranges.Add(0);
- return ranges.ToArray();
- }
-}
diff --git a/Dalamud/Interface/GameFonts/GameFontFamilyAndSize.cs b/Dalamud/Interface/GameFonts/GameFontFamilyAndSize.cs
index 6e66cf19b..dd78baf87 100644
--- a/Dalamud/Interface/GameFonts/GameFontFamilyAndSize.cs
+++ b/Dalamud/Interface/GameFonts/GameFontFamilyAndSize.cs
@@ -3,7 +3,7 @@ namespace Dalamud.Interface.GameFonts;
///
/// Enum of available game fonts in specific sizes.
///
-public enum GameFontFamilyAndSize
+public enum GameFontFamilyAndSize : int
{
///
/// Placeholder meaning unused.
@@ -15,7 +15,6 @@ public enum GameFontFamilyAndSize
///
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
///
- [GameFontFamilyAndSize("common/font/AXIS_96.fdt", "common/font/font{0}.tex", -1)]
Axis96,
///
@@ -23,7 +22,6 @@ public enum GameFontFamilyAndSize
///
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
///
- [GameFontFamilyAndSize("common/font/AXIS_12.fdt", "common/font/font{0}.tex", -1)]
Axis12,
///
@@ -31,7 +29,6 @@ public enum GameFontFamilyAndSize
///
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
///
- [GameFontFamilyAndSize("common/font/AXIS_14.fdt", "common/font/font{0}.tex", -1)]
Axis14,
///
@@ -39,7 +36,6 @@ public enum GameFontFamilyAndSize
///
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
///
- [GameFontFamilyAndSize("common/font/AXIS_18.fdt", "common/font/font{0}.tex", -1)]
Axis18,
///
@@ -47,7 +43,6 @@ public enum GameFontFamilyAndSize
///
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
///
- [GameFontFamilyAndSize("common/font/AXIS_36.fdt", "common/font/font{0}.tex", -4)]
Axis36,
///
@@ -55,7 +50,6 @@ public enum GameFontFamilyAndSize
///
/// Serif font. Contains mostly ASCII range. Used in game for job names.
///
- [GameFontFamilyAndSize("common/font/Jupiter_16.fdt", "common/font/font{0}.tex", -1)]
Jupiter16,
///
@@ -63,7 +57,6 @@ public enum GameFontFamilyAndSize
///
/// Serif font. Contains mostly ASCII range. Used in game for job names.
///
- [GameFontFamilyAndSize("common/font/Jupiter_20.fdt", "common/font/font{0}.tex", -1)]
Jupiter20,
///
@@ -71,7 +64,6 @@ public enum GameFontFamilyAndSize
///
/// Serif font. Contains mostly ASCII range. Used in game for job names.
///
- [GameFontFamilyAndSize("common/font/Jupiter_23.fdt", "common/font/font{0}.tex", -1)]
Jupiter23,
///
@@ -79,7 +71,6 @@ public enum GameFontFamilyAndSize
///
/// Serif font. Contains mostly numbers. Used in game for flying texts.
///
- [GameFontFamilyAndSize("common/font/Jupiter_45.fdt", "common/font/font{0}.tex", -2)]
Jupiter45,
///
@@ -87,7 +78,6 @@ public enum GameFontFamilyAndSize
///
/// Serif font. Contains mostly ASCII range. Used in game for job names.
///
- [GameFontFamilyAndSize("common/font/Jupiter_46.fdt", "common/font/font{0}.tex", -2)]
Jupiter46,
///
@@ -95,7 +85,6 @@ public enum GameFontFamilyAndSize
///
/// Serif font. Contains mostly numbers. Used in game for flying texts.
///
- [GameFontFamilyAndSize("common/font/Jupiter_90.fdt", "common/font/font{0}.tex", -4)]
Jupiter90,
///
@@ -103,7 +92,6 @@ public enum GameFontFamilyAndSize
///
/// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff.
///
- [GameFontFamilyAndSize("common/font/Meidinger_16.fdt", "common/font/font{0}.tex", -1)]
Meidinger16,
///
@@ -111,7 +99,6 @@ public enum GameFontFamilyAndSize
///
/// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff.
///
- [GameFontFamilyAndSize("common/font/Meidinger_20.fdt", "common/font/font{0}.tex", -1)]
Meidinger20,
///
@@ -119,7 +106,6 @@ public enum GameFontFamilyAndSize
///
/// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff.
///
- [GameFontFamilyAndSize("common/font/Meidinger_40.fdt", "common/font/font{0}.tex", -4)]
Meidinger40,
///
@@ -127,7 +113,6 @@ public enum GameFontFamilyAndSize
///
/// Horizontally wide. Contains mostly ASCII range.
///
- [GameFontFamilyAndSize("common/font/MiedingerMid_10.fdt", "common/font/font{0}.tex", -1)]
MiedingerMid10,
///
@@ -135,7 +120,6 @@ public enum GameFontFamilyAndSize
///
/// Horizontally wide. Contains mostly ASCII range.
///
- [GameFontFamilyAndSize("common/font/MiedingerMid_12.fdt", "common/font/font{0}.tex", -1)]
MiedingerMid12,
///
@@ -143,7 +127,6 @@ public enum GameFontFamilyAndSize
///
/// Horizontally wide. Contains mostly ASCII range.
///
- [GameFontFamilyAndSize("common/font/MiedingerMid_14.fdt", "common/font/font{0}.tex", -1)]
MiedingerMid14,
///
@@ -151,7 +134,6 @@ public enum GameFontFamilyAndSize
///
/// Horizontally wide. Contains mostly ASCII range.
///
- [GameFontFamilyAndSize("common/font/MiedingerMid_18.fdt", "common/font/font{0}.tex", -1)]
MiedingerMid18,
///
@@ -159,7 +141,6 @@ public enum GameFontFamilyAndSize
///
/// Horizontally wide. Contains mostly ASCII range.
///
- [GameFontFamilyAndSize("common/font/MiedingerMid_36.fdt", "common/font/font{0}.tex", -2)]
MiedingerMid36,
///
@@ -167,7 +148,6 @@ public enum GameFontFamilyAndSize
///
/// Horizontally narrow. Contains mostly ASCII range. Used for addon titles.
///
- [GameFontFamilyAndSize("common/font/TrumpGothic_184.fdt", "common/font/font{0}.tex", -1)]
TrumpGothic184,
///
@@ -175,7 +155,6 @@ public enum GameFontFamilyAndSize
///
/// Horizontally narrow. Contains mostly ASCII range. Used for addon titles.
///
- [GameFontFamilyAndSize("common/font/TrumpGothic_23.fdt", "common/font/font{0}.tex", -1)]
TrumpGothic23,
///
@@ -183,7 +162,6 @@ public enum GameFontFamilyAndSize
///
/// Horizontally narrow. Contains mostly ASCII range. Used for addon titles.
///
- [GameFontFamilyAndSize("common/font/TrumpGothic_34.fdt", "common/font/font{0}.tex", -1)]
TrumpGothic34,
///
@@ -191,6 +169,5 @@ public enum GameFontFamilyAndSize
///
/// Horizontally narrow. Contains mostly ASCII range. Used for addon titles.
///
- [GameFontFamilyAndSize("common/font/TrumpGothic_68.fdt", "common/font/font{0}.tex", -3)]
TrumpGothic68,
}
diff --git a/Dalamud/Interface/GameFonts/GameFontFamilyAndSizeAttribute.cs b/Dalamud/Interface/GameFonts/GameFontFamilyAndSizeAttribute.cs
deleted file mode 100644
index f5260e4bc..000000000
--- a/Dalamud/Interface/GameFonts/GameFontFamilyAndSizeAttribute.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-namespace Dalamud.Interface.GameFonts;
-
-///
-/// Marks the path for an enum value.
-///
-[AttributeUsage(AttributeTargets.Field)]
-internal class GameFontFamilyAndSizeAttribute : Attribute
-{
- ///
- /// Initializes a new instance of the class.
- ///
- /// Inner path of the file.
- /// the file path format for the relevant .tex files.
- /// Horizontal offset of the corresponding font.
- public GameFontFamilyAndSizeAttribute(string path, string texPathFormat, int horizontalOffset)
- {
- this.Path = path;
- this.TexPathFormat = texPathFormat;
- this.HorizontalOffset = horizontalOffset;
- }
-
- ///
- /// Gets the path.
- ///
- public string Path { get; }
-
- ///
- /// Gets the file path format for the relevant .tex files.
- /// Used for (, ).
- ///
- public string TexPathFormat { get; }
-
- ///
- /// Gets the horizontal offset of the corresponding font.
- ///
- public int HorizontalOffset { get; }
-}
diff --git a/Dalamud/Interface/GameFonts/GameFontHandle.cs b/Dalamud/Interface/GameFonts/GameFontHandle.cs
index 77461aa0a..d71e725c5 100644
--- a/Dalamud/Interface/GameFonts/GameFontHandle.cs
+++ b/Dalamud/Interface/GameFonts/GameFontHandle.cs
@@ -1,76 +1,75 @@
+using System;
using System.Numerics;
-using Dalamud.Interface.ManagedFontAtlas;
-using Dalamud.Interface.ManagedFontAtlas.Internals;
-
using ImGuiNET;
namespace Dalamud.Interface.GameFonts;
///
-/// ABI-compatible wrapper for .
+/// Prepare and keep game font loaded for use in OnDraw.
///
-public sealed class GameFontHandle : IFontHandle
+public class GameFontHandle : IDisposable
{
- private readonly IFontHandle.IInternal fontHandle;
- private readonly FontAtlasFactory fontAtlasFactory;
+ private readonly GameFontManager manager;
+ private readonly GameFontStyle fontStyle;
///
/// Initializes a new instance of the class.
///
- /// The wrapped .
- /// An instance of .
- internal GameFontHandle(IFontHandle.IInternal fontHandle, FontAtlasFactory fontAtlasFactory)
+ /// GameFontManager instance.
+ /// Font to use.
+ internal GameFontHandle(GameFontManager manager, GameFontStyle font)
{
- this.fontHandle = fontHandle;
- this.fontAtlasFactory = fontAtlasFactory;
+ this.manager = manager;
+ this.fontStyle = font;
}
- ///
- public Exception? LoadException => this.fontHandle.LoadException;
-
- ///
- public bool Available => this.fontHandle.Available;
-
- ///
- [Obsolete($"Use {nameof(Push)}, and then use {nameof(ImGui.GetFont)} instead.", false)]
- public ImFontPtr ImFont => this.fontHandle.ImFont;
-
///
- /// Gets the font style. Only applicable for .
+ /// Gets the font style.
///
- [Obsolete("If you use this, let the fact that you use this be known at Dalamud Discord.", false)]
- public GameFontStyle Style => ((GamePrebakedFontHandle)this.fontHandle).FontStyle;
+ public GameFontStyle Style => this.fontStyle;
///
- /// Gets the relevant .
- ///
- /// Only applicable for game fonts. Otherwise it will throw.
+ /// Gets a value indicating whether this font is ready for use.
///
- [Obsolete("If you use this, let the fact that you use this be known at Dalamud Discord.", false)]
- public FdtReader FdtReader => this.fontAtlasFactory.GetFdtReader(this.Style.FamilyAndSize)!;
-
- ///
- public void Dispose() => this.fontHandle.Dispose();
-
- ///
- public IDisposable Push() => this.fontHandle.Push();
+ public bool Available
+ {
+ get
+ {
+ unsafe
+ {
+ return this.manager.GetFont(this.fontStyle).GetValueOrDefault(null).NativePtr != null;
+ }
+ }
+ }
///
- /// Creates a new .
- ///
- /// Only applicable for game fonts. Otherwise it will throw.
+ /// Gets the font.
+ ///
+ public ImFontPtr ImFont => this.manager.GetFont(this.fontStyle).Value;
+
+ ///
+ /// Gets the FdtReader.
+ ///
+ public FdtReader FdtReader => this.manager.GetFdtReader(this.fontStyle.FamilyAndSize);
+
+ ///
+ /// Creates a new GameFontLayoutPlan.Builder.
///
/// Text.
/// A new builder for GameFontLayoutPlan.
- [Obsolete("If you use this, let the fact that you use this be known at Dalamud Discord.", false)]
- public GameFontLayoutPlan.Builder LayoutBuilder(string text) => new(this.ImFont, this.FdtReader, text);
+ public GameFontLayoutPlan.Builder LayoutBuilder(string text)
+ {
+ return new GameFontLayoutPlan.Builder(this.ImFont, this.FdtReader, text);
+ }
+
+ ///
+ public void Dispose() => this.manager.DecreaseFontRef(this.fontStyle);
///
/// Draws text.
///
/// Text to draw.
- [Obsolete("If you use this, let the fact that you use this be known at Dalamud Discord.", false)]
public void Text(string text)
{
if (!this.Available)
@@ -94,7 +93,6 @@ public sealed class GameFontHandle : IFontHandle
///
/// Color.
/// Text to draw.
- [Obsolete("If you use this, let the fact that you use this be known at Dalamud Discord.", false)]
public void TextColored(Vector4 col, string text)
{
ImGui.PushStyleColor(ImGuiCol.Text, col);
@@ -106,7 +104,6 @@ public sealed class GameFontHandle : IFontHandle
/// Draws disabled text.
///
/// Text to draw.
- [Obsolete("If you use this, let the fact that you use this be known at Dalamud Discord.", false)]
public void TextDisabled(string text)
{
unsafe
diff --git a/Dalamud/Interface/GameFonts/GameFontManager.cs b/Dalamud/Interface/GameFonts/GameFontManager.cs
new file mode 100644
index 000000000..b3454e085
--- /dev/null
+++ b/Dalamud/Interface/GameFonts/GameFontManager.cs
@@ -0,0 +1,507 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+using Dalamud.Data;
+using Dalamud.Game;
+using Dalamud.Interface.Internal;
+using Dalamud.Interface.Utility;
+using Dalamud.Utility.Timing;
+using ImGuiNET;
+using Lumina.Data.Files;
+using Serilog;
+
+using static Dalamud.Interface.Utility.ImGuiHelpers;
+
+namespace Dalamud.Interface.GameFonts;
+
+///
+/// Loads game font for use in ImGui.
+///
+[ServiceManager.BlockingEarlyLoadedService]
+internal class GameFontManager : IServiceType
+{
+ private static readonly string?[] FontNames =
+ {
+ null,
+ "AXIS_96", "AXIS_12", "AXIS_14", "AXIS_18", "AXIS_36",
+ "Jupiter_16", "Jupiter_20", "Jupiter_23", "Jupiter_45", "Jupiter_46", "Jupiter_90",
+ "Meidinger_16", "Meidinger_20", "Meidinger_40",
+ "MiedingerMid_10", "MiedingerMid_12", "MiedingerMid_14", "MiedingerMid_18", "MiedingerMid_36",
+ "TrumpGothic_184", "TrumpGothic_23", "TrumpGothic_34", "TrumpGothic_68",
+ };
+
+ private readonly object syncRoot = new();
+
+ private readonly FdtReader?[] fdts;
+ private readonly List texturePixels;
+ private readonly Dictionary fonts = new();
+ private readonly Dictionary fontUseCounter = new();
+ private readonly Dictionary>> glyphRectIds = new();
+
+#pragma warning disable CS0414
+ private bool isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = false;
+#pragma warning restore CS0414
+
+ [ServiceManager.ServiceConstructor]
+ private GameFontManager(DataManager dataManager)
+ {
+ using (Timings.Start("Getting fdt data"))
+ {
+ this.fdts = FontNames.Select(fontName => fontName == null ? null : new FdtReader(dataManager.GetFile($"common/font/{fontName}.fdt")!.Data)).ToArray();
+ }
+
+ using (Timings.Start("Getting texture data"))
+ {
+ var texTasks = Enumerable
+ .Range(1, 1 + this.fdts
+ .Where(x => x != null)
+ .Select(x => x.Glyphs.Select(y => y.TextureFileIndex).Max())
+ .Max())
+ .Select(x => dataManager.GetFile($"common/font/font{x}.tex")!)
+ .Select(x => new Task(Timings.AttachTimingHandle(() => x.ImageData!)))
+ .ToArray();
+ foreach (var task in texTasks)
+ task.Start();
+ this.texturePixels = texTasks.Select(x => x.GetAwaiter().GetResult()).ToList();
+ }
+ }
+
+ ///
+ /// Describe font into a string.
+ ///
+ /// Font to describe.
+ /// A string in a form of "FontName (NNNpt)".
+ public static string DescribeFont(GameFontFamilyAndSize font)
+ {
+ return font switch
+ {
+ GameFontFamilyAndSize.Undefined => "-",
+ GameFontFamilyAndSize.Axis96 => "AXIS (9.6pt)",
+ GameFontFamilyAndSize.Axis12 => "AXIS (12pt)",
+ GameFontFamilyAndSize.Axis14 => "AXIS (14pt)",
+ GameFontFamilyAndSize.Axis18 => "AXIS (18pt)",
+ GameFontFamilyAndSize.Axis36 => "AXIS (36pt)",
+ GameFontFamilyAndSize.Jupiter16 => "Jupiter (16pt)",
+ GameFontFamilyAndSize.Jupiter20 => "Jupiter (20pt)",
+ GameFontFamilyAndSize.Jupiter23 => "Jupiter (23pt)",
+ GameFontFamilyAndSize.Jupiter45 => "Jupiter Numeric (45pt)",
+ GameFontFamilyAndSize.Jupiter46 => "Jupiter (46pt)",
+ GameFontFamilyAndSize.Jupiter90 => "Jupiter Numeric (90pt)",
+ GameFontFamilyAndSize.Meidinger16 => "Meidinger Numeric (16pt)",
+ GameFontFamilyAndSize.Meidinger20 => "Meidinger Numeric (20pt)",
+ GameFontFamilyAndSize.Meidinger40 => "Meidinger Numeric (40pt)",
+ GameFontFamilyAndSize.MiedingerMid10 => "MiedingerMid (10pt)",
+ GameFontFamilyAndSize.MiedingerMid12 => "MiedingerMid (12pt)",
+ GameFontFamilyAndSize.MiedingerMid14 => "MiedingerMid (14pt)",
+ GameFontFamilyAndSize.MiedingerMid18 => "MiedingerMid (18pt)",
+ GameFontFamilyAndSize.MiedingerMid36 => "MiedingerMid (36pt)",
+ GameFontFamilyAndSize.TrumpGothic184 => "Trump Gothic (18.4pt)",
+ GameFontFamilyAndSize.TrumpGothic23 => "Trump Gothic (23pt)",
+ GameFontFamilyAndSize.TrumpGothic34 => "Trump Gothic (34pt)",
+ GameFontFamilyAndSize.TrumpGothic68 => "Trump Gothic (68pt)",
+ _ => throw new ArgumentOutOfRangeException(nameof(font), font, "Invalid argument"),
+ };
+ }
+
+ ///
+ /// Determines whether a font should be able to display most of stuff.
+ ///
+ /// Font to check.
+ /// True if it can.
+ public static bool IsGenericPurposeFont(GameFontFamilyAndSize font)
+ {
+ return font switch
+ {
+ GameFontFamilyAndSize.Axis96 => true,
+ GameFontFamilyAndSize.Axis12 => true,
+ GameFontFamilyAndSize.Axis14 => true,
+ GameFontFamilyAndSize.Axis18 => true,
+ GameFontFamilyAndSize.Axis36 => true,
+ _ => false,
+ };
+ }
+
+ ///
+ /// Unscales fonts after they have been rendered onto atlas.
+ ///
+ /// Font to unscale.
+ /// Scale factor.
+ /// Whether to call target.BuildLookupTable().
+ public static void UnscaleFont(ImFontPtr fontPtr, float fontScale, bool rebuildLookupTable = true)
+ {
+ if (fontScale == 1)
+ return;
+
+ unsafe
+ {
+ var font = fontPtr.NativePtr;
+ for (int i = 0, i_ = font->IndexedHotData.Size; i < i_; ++i)
+ {
+ font->IndexedHotData.Ref(i).AdvanceX /= fontScale;
+ font->IndexedHotData.Ref(i).OccupiedWidth /= fontScale;
+ }
+
+ font->FontSize /= fontScale;
+ font->Ascent /= fontScale;
+ font->Descent /= fontScale;
+ if (font->ConfigData != null)
+ font->ConfigData->SizePixels /= fontScale;
+ var glyphs = (ImFontGlyphReal*)font->Glyphs.Data;
+ for (int i = 0, i_ = font->Glyphs.Size; i < i_; i++)
+ {
+ var glyph = &glyphs[i];
+ glyph->X0 /= fontScale;
+ glyph->X1 /= fontScale;
+ glyph->Y0 /= fontScale;
+ glyph->Y1 /= fontScale;
+ glyph->AdvanceX /= fontScale;
+ }
+
+ for (int i = 0, i_ = font->KerningPairs.Size; i < i_; i++)
+ font->KerningPairs.Ref(i).AdvanceXAdjustment /= fontScale;
+ for (int i = 0, i_ = font->FrequentKerningPairs.Size; i < i_; i++)
+ font->FrequentKerningPairs.Ref(i) /= fontScale;
+ }
+
+ if (rebuildLookupTable && fontPtr.Glyphs.Size > 0)
+ fontPtr.BuildLookupTableNonstandard();
+ }
+
+ ///
+ /// Create a glyph range for use with ImGui AddFont.
+ ///
+ /// Font family and size.
+ /// Merge two ranges into one if distance is below the value specified in this parameter.
+ /// Glyph ranges.
+ public GCHandle ToGlyphRanges(GameFontFamilyAndSize family, int mergeDistance = 8)
+ {
+ var fdt = this.fdts[(int)family]!;
+ var ranges = new List(fdt.Glyphs.Count)
+ {
+ checked((ushort)fdt.Glyphs[0].CharInt),
+ checked((ushort)fdt.Glyphs[0].CharInt),
+ };
+
+ foreach (var glyph in fdt.Glyphs.Skip(1))
+ {
+ var c32 = glyph.CharInt;
+ if (c32 >= 0x10000)
+ break;
+
+ var c16 = unchecked((ushort)c32);
+ if (ranges[^1] + mergeDistance >= c16 && c16 > ranges[^1])
+ {
+ ranges[^1] = c16;
+ }
+ else if (ranges[^1] + 1 < c16)
+ {
+ ranges.Add(c16);
+ ranges.Add(c16);
+ }
+ }
+
+ return GCHandle.Alloc(ranges.ToArray(), GCHandleType.Pinned);
+ }
+
+ ///
+ /// Creates a new GameFontHandle, and increases internal font reference counter, and if it's first time use, then the font will be loaded on next font building process.
+ ///
+ /// Font to use.
+ /// Handle to game font that may or may not be ready yet.
+ public GameFontHandle NewFontRef(GameFontStyle style)
+ {
+ var interfaceManager = Service.Get();
+ var needRebuild = false;
+
+ lock (this.syncRoot)
+ {
+ this.fontUseCounter[style] = this.fontUseCounter.GetValueOrDefault(style, 0) + 1;
+ }
+
+ needRebuild = !this.fonts.ContainsKey(style);
+ if (needRebuild)
+ {
+ Log.Information("[GameFontManager] NewFontRef: Queueing RebuildFonts because {0} has been requested.", style.ToString());
+ Service.GetAsync()
+ .ContinueWith(task => task.Result.RunOnTick(() => interfaceManager.RebuildFonts()));
+ }
+
+ return new(this, style);
+ }
+
+ ///
+ /// Gets the font.
+ ///
+ /// Font to get.
+ /// Corresponding font or null.
+ public ImFontPtr? GetFont(GameFontStyle style) => this.fonts.GetValueOrDefault(style, null);
+
+ ///
+ /// Gets the corresponding FdtReader.
+ ///
+ /// Font to get.
+ /// Corresponding FdtReader or null.
+ public FdtReader? GetFdtReader(GameFontFamilyAndSize family) => this.fdts[(int)family];
+
+ ///
+ /// Fills missing glyphs in target font from source font, if both are not null.
+ ///
+ /// Source font.
+ /// Target font.
+ /// Whether to copy missing glyphs only.
+ /// Whether to call target.BuildLookupTable().
+ public void CopyGlyphsAcrossFonts(ImFontPtr? source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable)
+ {
+ ImGuiHelpers.CopyGlyphsAcrossFonts(source ?? default, this.fonts[target], missingOnly, rebuildLookupTable);
+ }
+
+ ///
+ /// Fills missing glyphs in target font from source font, if both are not null.
+ ///
+ /// Source font.
+ /// Target font.
+ /// Whether to copy missing glyphs only.
+ /// Whether to call target.BuildLookupTable().
+ public void CopyGlyphsAcrossFonts(GameFontStyle source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable)
+ {
+ ImGuiHelpers.CopyGlyphsAcrossFonts(this.fonts[source], target ?? default, missingOnly, rebuildLookupTable);
+ }
+
+ ///
+ /// Fills missing glyphs in target font from source font, if both are not null.
+ ///
+ /// Source font.
+ /// Target font.
+ /// Whether to copy missing glyphs only.
+ /// Whether to call target.BuildLookupTable().
+ public void CopyGlyphsAcrossFonts(GameFontStyle source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable)
+ {
+ ImGuiHelpers.CopyGlyphsAcrossFonts(this.fonts[source], this.fonts[target], missingOnly, rebuildLookupTable);
+ }
+
+ ///
+ /// Build fonts before plugins do something more. To be called from InterfaceManager.
+ ///
+ public void BuildFonts()
+ {
+ this.isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = true;
+
+ this.glyphRectIds.Clear();
+ this.fonts.Clear();
+
+ lock (this.syncRoot)
+ {
+ foreach (var style in this.fontUseCounter.Keys)
+ this.EnsureFont(style);
+ }
+ }
+
+ ///
+ /// Record that ImGui.GetIO().Fonts.Build() has been called.
+ ///
+ public void AfterIoFontsBuild()
+ {
+ this.isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = false;
+ }
+
+ ///
+ /// Checks whether GameFontMamager owns an ImFont.
+ ///
+ /// ImFontPtr to check.
+ /// Whether it owns.
+ public bool OwnsFont(ImFontPtr fontPtr) => this.fonts.ContainsValue(fontPtr);
+
+ ///
+ /// Post-build fonts before plugins do something more. To be called from InterfaceManager.
+ ///
+ public unsafe void AfterBuildFonts()
+ {
+ var interfaceManager = Service.Get();
+ var ioFonts = ImGui.GetIO().Fonts;
+ var fontGamma = interfaceManager.FontGamma;
+
+ var pixels8s = new byte*[ioFonts.Textures.Size];
+ var pixels32s = new uint*[ioFonts.Textures.Size];
+ var widths = new int[ioFonts.Textures.Size];
+ var heights = new int[ioFonts.Textures.Size];
+ for (var i = 0; i < pixels8s.Length; i++)
+ {
+ ioFonts.GetTexDataAsRGBA32(i, out pixels8s[i], out widths[i], out heights[i]);
+ pixels32s[i] = (uint*)pixels8s[i];
+ }
+
+ foreach (var (style, font) in this.fonts)
+ {
+ var fdt = this.fdts[(int)style.FamilyAndSize];
+ var scale = style.SizePt / fdt.FontHeader.Size;
+ var fontPtr = font.NativePtr;
+
+ Log.Verbose("[GameFontManager] AfterBuildFonts: Scaling {0} from {1}pt to {2}pt (scale: {3})", style.ToString(), fdt.FontHeader.Size, style.SizePt, scale);
+
+ fontPtr->FontSize = fdt.FontHeader.Size * 4 / 3;
+ if (fontPtr->ConfigData != null)
+ fontPtr->ConfigData->SizePixels = fontPtr->FontSize;
+ fontPtr->Ascent = fdt.FontHeader.Ascent;
+ fontPtr->Descent = fdt.FontHeader.Descent;
+ fontPtr->EllipsisChar = '…';
+ foreach (var fallbackCharCandidate in "〓?!")
+ {
+ var glyph = font.FindGlyphNoFallback(fallbackCharCandidate);
+ if ((IntPtr)glyph.NativePtr != IntPtr.Zero)
+ {
+ var ptr = font.NativePtr;
+ ptr->FallbackChar = fallbackCharCandidate;
+ ptr->FallbackGlyph = glyph.NativePtr;
+ ptr->FallbackHotData = (ImFontGlyphHotData*)ptr->IndexedHotData.Address(fallbackCharCandidate);
+ break;
+ }
+ }
+
+ // I have no idea what's causing NPE, so just to be safe
+ try
+ {
+ if (font.NativePtr != null && font.NativePtr->ConfigData != null)
+ {
+ var nameBytes = Encoding.UTF8.GetBytes(style.ToString() + "\0");
+ Marshal.Copy(nameBytes, 0, (IntPtr)font.ConfigData.Name.Data, Math.Min(nameBytes.Length, font.ConfigData.Name.Count));
+ }
+ }
+ catch (NullReferenceException)
+ {
+ // do nothing
+ }
+
+ foreach (var (c, (rectId, glyph)) in this.glyphRectIds[style])
+ {
+ var rc = (ImFontAtlasCustomRectReal*)ioFonts.GetCustomRectByIndex(rectId).NativePtr;
+ var pixels8 = pixels8s[rc->TextureIndex];
+ var pixels32 = pixels32s[rc->TextureIndex];
+ var width = widths[rc->TextureIndex];
+ var height = heights[rc->TextureIndex];
+ var sourceBuffer = this.texturePixels[glyph.TextureFileIndex];
+ var sourceBufferDelta = glyph.TextureChannelByteIndex;
+ var widthAdjustment = style.CalculateBaseWidthAdjustment(fdt, glyph);
+ if (widthAdjustment == 0)
+ {
+ for (var y = 0; y < glyph.BoundingHeight; y++)
+ {
+ for (var x = 0; x < glyph.BoundingWidth; x++)
+ {
+ var a = sourceBuffer[sourceBufferDelta + (4 * (((glyph.TextureOffsetY + y) * fdt.FontHeader.TextureWidth) + glyph.TextureOffsetX + x))];
+ pixels32[((rc->Y + y) * width) + rc->X + x] = (uint)(a << 24) | 0xFFFFFFu;
+ }
+ }
+ }
+ else
+ {
+ for (var y = 0; y < glyph.BoundingHeight; y++)
+ {
+ for (var x = 0; x < glyph.BoundingWidth + widthAdjustment; x++)
+ pixels32[((rc->Y + y) * width) + rc->X + x] = 0xFFFFFFu;
+ }
+
+ for (int xbold = 0, xbold_ = Math.Max(1, (int)Math.Ceiling(style.Weight + 1)); xbold < xbold_; xbold++)
+ {
+ var boldStrength = Math.Min(1f, style.Weight + 1 - xbold);
+ for (var y = 0; y < glyph.BoundingHeight; y++)
+ {
+ float xDelta = xbold;
+ if (style.BaseSkewStrength > 0)
+ xDelta += style.BaseSkewStrength * (fdt.FontHeader.LineHeight - glyph.CurrentOffsetY - y) / fdt.FontHeader.LineHeight;
+ else if (style.BaseSkewStrength < 0)
+ xDelta -= style.BaseSkewStrength * (glyph.CurrentOffsetY + y) / fdt.FontHeader.LineHeight;
+ var xDeltaInt = (int)Math.Floor(xDelta);
+ var xness = xDelta - xDeltaInt;
+ for (var x = 0; x < glyph.BoundingWidth; x++)
+ {
+ var sourcePixelIndex = ((glyph.TextureOffsetY + y) * fdt.FontHeader.TextureWidth) + glyph.TextureOffsetX + x;
+ var a1 = sourceBuffer[sourceBufferDelta + (4 * sourcePixelIndex)];
+ var a2 = x == glyph.BoundingWidth - 1 ? 0 : sourceBuffer[sourceBufferDelta + (4 * (sourcePixelIndex + 1))];
+ var n = (a1 * xness) + (a2 * (1 - xness));
+ var targetOffset = ((rc->Y + y) * width) + rc->X + x + xDeltaInt;
+ pixels8[(targetOffset * 4) + 3] = Math.Max(pixels8[(targetOffset * 4) + 3], (byte)(boldStrength * n));
+ }
+ }
+ }
+ }
+
+ if (Math.Abs(fontGamma - 1.4f) >= 0.001)
+ {
+ // Gamma correction (stbtt/FreeType would output in linear space whereas most real world usages will apply 1.4 or 1.8 gamma; Windows/XIV prebaked uses 1.4)
+ for (int y = rc->Y, y_ = rc->Y + rc->Height; y < y_; y++)
+ {
+ for (int x = rc->X, x_ = rc->X + rc->Width; x < x_; x++)
+ {
+ var i = (((y * width) + x) * 4) + 3;
+ pixels8[i] = (byte)(Math.Pow(pixels8[i] / 255.0f, 1.4f / fontGamma) * 255.0f);
+ }
+ }
+ }
+ }
+
+ UnscaleFont(font, 1 / scale, false);
+ }
+ }
+
+ ///
+ /// Decrease font reference counter.
+ ///
+ /// Font to release.
+ internal void DecreaseFontRef(GameFontStyle style)
+ {
+ lock (this.syncRoot)
+ {
+ if (!this.fontUseCounter.ContainsKey(style))
+ return;
+
+ if ((this.fontUseCounter[style] -= 1) == 0)
+ this.fontUseCounter.Remove(style);
+ }
+ }
+
+ private unsafe void EnsureFont(GameFontStyle style)
+ {
+ var rectIds = this.glyphRectIds[style] = new();
+
+ var fdt = this.fdts[(int)style.FamilyAndSize];
+ if (fdt == null)
+ return;
+
+ ImFontConfigPtr fontConfig = ImGuiNative.ImFontConfig_ImFontConfig();
+ fontConfig.OversampleH = 1;
+ fontConfig.OversampleV = 1;
+ fontConfig.PixelSnapH = false;
+
+ var io = ImGui.GetIO();
+ var font = io.Fonts.AddFontDefault(fontConfig);
+
+ fontConfig.Destroy();
+
+ this.fonts[style] = font;
+ foreach (var glyph in fdt.Glyphs)
+ {
+ var c = glyph.Char;
+ if (c < 32 || c >= 0xFFFF)
+ continue;
+
+ var widthAdjustment = style.CalculateBaseWidthAdjustment(fdt, glyph);
+ rectIds[c] = Tuple.Create(
+ io.Fonts.AddCustomRectFontGlyph(
+ font,
+ c,
+ glyph.BoundingWidth + widthAdjustment,
+ glyph.BoundingHeight,
+ glyph.AdvanceWidth,
+ new Vector2(0, glyph.CurrentOffsetY)),
+ glyph);
+ }
+
+ foreach (var kernPair in fdt.Distances)
+ font.AddKerningPair(kernPair.Left, kernPair.Right, kernPair.RightOffset);
+ }
+}
diff --git a/Dalamud/Interface/GameFonts/GameFontStyle.cs b/Dalamud/Interface/GameFonts/GameFontStyle.cs
index fbaf9de07..946473df4 100644
--- a/Dalamud/Interface/GameFonts/GameFontStyle.cs
+++ b/Dalamud/Interface/GameFonts/GameFontStyle.cs
@@ -64,7 +64,7 @@ public struct GameFontStyle
///
public float SizePt
{
- readonly get => this.SizePx * 3 / 4;
+ get => this.SizePx * 3 / 4;
set => this.SizePx = value * 4 / 3;
}
@@ -73,14 +73,14 @@ public struct GameFontStyle
///
public float BaseSkewStrength
{
- readonly get => this.SkewStrength * this.BaseSizePx / this.SizePx;
+ get => this.SkewStrength * this.BaseSizePx / this.SizePx;
set => this.SkewStrength = value * this.SizePx / this.BaseSizePx;
}
///
/// Gets the font family.
///
- public readonly GameFontFamily Family => this.FamilyAndSize switch
+ public GameFontFamily Family => this.FamilyAndSize switch
{
GameFontFamilyAndSize.Undefined => GameFontFamily.Undefined,
GameFontFamilyAndSize.Axis96 => GameFontFamily.Axis,
@@ -112,7 +112,7 @@ public struct GameFontStyle
///
/// Gets the corresponding GameFontFamilyAndSize but with minimum possible font sizes.
///
- public readonly GameFontFamilyAndSize FamilyWithMinimumSize => this.Family switch
+ public GameFontFamilyAndSize FamilyWithMinimumSize => this.Family switch
{
GameFontFamily.Axis => GameFontFamilyAndSize.Axis96,
GameFontFamily.Jupiter => GameFontFamilyAndSize.Jupiter16,
@@ -126,7 +126,7 @@ public struct GameFontStyle
///
/// Gets the base font size in point unit.
///
- public readonly float BaseSizePt => this.FamilyAndSize switch
+ public float BaseSizePt => this.FamilyAndSize switch
{
GameFontFamilyAndSize.Undefined => 0,
GameFontFamilyAndSize.Axis96 => 9.6f,
@@ -158,14 +158,14 @@ public struct GameFontStyle
///
/// Gets the base font size in pixel unit.
///
- public readonly float BaseSizePx => this.BaseSizePt * 4 / 3;
+ public float BaseSizePx => this.BaseSizePt * 4 / 3;
///
/// Gets or sets a value indicating whether this font is bold.
///
public bool Bold
{
- readonly get => this.Weight > 0f;
+ get => this.Weight > 0f;
set => this.Weight = value ? 1f : 0f;
}
@@ -174,8 +174,8 @@ public struct GameFontStyle
///
public bool Italic
{
- readonly get => this.SkewStrength != 0;
- set => this.SkewStrength = value ? this.SizePx / 6 : 0;
+ get => this.SkewStrength != 0;
+ set => this.SkewStrength = value ? this.SizePx / 7 : 0;
}
///
@@ -233,26 +233,13 @@ public struct GameFontStyle
_ => GameFontFamilyAndSize.Undefined,
};
- ///
- /// Creates a new scaled instance of struct.
- ///
- /// The scale.
- /// The scaled instance.
- public readonly GameFontStyle Scale(float scale) => new()
- {
- FamilyAndSize = GetRecommendedFamilyAndSize(this.Family, this.SizePt * scale),
- SizePx = this.SizePx * scale,
- Weight = this.Weight,
- SkewStrength = this.SkewStrength * scale,
- };
-
///
/// Calculates the adjustment to width resulting fron Weight and SkewStrength.
///
/// Font header.
/// Glyph.
/// Width adjustment in pixel unit.
- public readonly int CalculateBaseWidthAdjustment(in FdtReader.FontTableHeader header, in FdtReader.FontTableEntry glyph)
+ public int CalculateBaseWidthAdjustment(in FdtReader.FontTableHeader header, in FdtReader.FontTableEntry glyph)
{
var widthDelta = this.Weight;
switch (this.BaseSkewStrength)
@@ -276,11 +263,11 @@ public struct GameFontStyle
/// Font information.
/// Glyph.
/// Width adjustment in pixel unit.
- public readonly int CalculateBaseWidthAdjustment(FdtReader reader, FdtReader.FontTableEntry glyph) =>
+ public int CalculateBaseWidthAdjustment(FdtReader reader, FdtReader.FontTableEntry glyph) =>
this.CalculateBaseWidthAdjustment(reader.FontHeader, glyph);
///
- public override readonly string ToString()
+ public override string ToString()
{
return $"GameFontStyle({this.FamilyAndSize}, {this.SizePt}pt, skew={this.SkewStrength}, weight={this.Weight})";
}
diff --git a/Dalamud/Interface/Internal/DalamudIme.cs b/Dalamud/Interface/Internal/DalamudIme.cs
index 28a9075bd..e030b4e50 100644
--- a/Dalamud/Interface/Internal/DalamudIme.cs
+++ b/Dalamud/Interface/Internal/DalamudIme.cs
@@ -11,7 +11,6 @@ using System.Text.Unicode;
using Dalamud.Game.Text;
using Dalamud.Hooking.WndProcHook;
using Dalamud.Interface.GameFonts;
-using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Interface.Utility;
using ImGuiNET;
@@ -197,9 +196,9 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
{
if (HanRange.Any(x => x.FirstCodePoint <= chr && chr < x.FirstCodePoint + x.Length))
{
- if (Service.Get()
- ?.GetFdtReader(GameFontFamilyAndSize.Axis12)
- .FindGlyph(chr) is null)
+ if (Service.Get()
+ .GetFdtReader(GameFontFamilyAndSize.Axis12)
+ ?.FindGlyph(chr) is null)
{
if (!this.EncounteredHan)
{
diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs
index 60c1f9957..95415659b 100644
--- a/Dalamud/Interface/Internal/DalamudInterface.cs
+++ b/Dalamud/Interface/Internal/DalamudInterface.cs
@@ -21,7 +21,6 @@ using Dalamud.Interface.Internal.Windows.PluginInstaller;
using Dalamud.Interface.Internal.Windows.SelfTest;
using Dalamud.Interface.Internal.Windows.Settings;
using Dalamud.Interface.Internal.Windows.StyleEditor;
-using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Interface.Style;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
@@ -94,8 +93,7 @@ internal class DalamudInterface : IDisposable, IServiceType
private DalamudInterface(
Dalamud dalamud,
DalamudConfiguration configuration,
- FontAtlasFactory fontAtlasFactory,
- InterfaceManager interfaceManager,
+ InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene,
PluginImageCache pluginImageCache,
DalamudAssetManager dalamudAssetManager,
Game.Framework framework,
@@ -105,7 +103,7 @@ internal class DalamudInterface : IDisposable, IServiceType
{
this.dalamud = dalamud;
this.configuration = configuration;
- this.interfaceManager = interfaceManager;
+ this.interfaceManager = interfaceManagerWithScene.Manager;
this.WindowSystem = new WindowSystem("DalamudCore");
@@ -124,14 +122,10 @@ internal class DalamudInterface : IDisposable, IServiceType
clientState,
configuration,
dalamudAssetManager,
- fontAtlasFactory,
framework,
gameGui,
titleScreenMenu) { IsOpen = false };
- this.changelogWindow = new ChangelogWindow(
- this.titleScreenMenuWindow,
- fontAtlasFactory,
- dalamudAssetManager) { IsOpen = false };
+ this.changelogWindow = new ChangelogWindow(this.titleScreenMenuWindow) { IsOpen = false };
this.profilerWindow = new ProfilerWindow() { IsOpen = false };
this.branchSwitcherWindow = new BranchSwitcherWindow() { IsOpen = false };
this.hitchSettingsWindow = new HitchSettingsWindow() { IsOpen = false };
@@ -213,7 +207,6 @@ internal class DalamudInterface : IDisposable, IServiceType
{
this.interfaceManager.Draw -= this.OnDraw;
- this.WindowSystem.Windows.OfType().AggregateToDisposable().Dispose();
this.WindowSystem.RemoveAllWindows();
this.changelogWindow.Dispose();
diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs
index 3e004727a..48157fa86 100644
--- a/Dalamud/Interface/Internal/InterfaceManager.cs
+++ b/Dalamud/Interface/Internal/InterfaceManager.cs
@@ -1,10 +1,13 @@
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using System.Threading.Tasks;
+using System.Text;
+using System.Text.Unicode;
+using System.Threading;
using Dalamud.Configuration.Internal;
using Dalamud.Game;
@@ -16,13 +19,10 @@ using Dalamud.Hooking.WndProcHook;
using Dalamud.Interface.GameFonts;
using Dalamud.Interface.Internal.ManagedAsserts;
using Dalamud.Interface.Internal.Notifications;
-using Dalamud.Interface.ManagedFontAtlas;
-using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Interface.Style;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing;
-using Dalamud.Plugin.Internal;
-using Dalamud.Plugin.Internal.Types;
+using Dalamud.Storage.Assets;
using Dalamud.Utility;
using Dalamud.Utility.Timing;
using ImGuiNET;
@@ -64,9 +64,11 @@ internal class InterfaceManager : IDisposable, IServiceType
///
public const float DefaultFontSizePx = (DefaultFontSizePt * 4.0f) / 3.0f;
- private const int NonMainThreadFontAccessWarningCheckInterval = 10000;
- private static readonly ConditionalWeakTable NonMainThreadFontAccessWarning = new();
- private static long nextNonMainThreadFontAccessWarningCheck;
+ private const ushort Fallback1Codepoint = 0x3013; // Geta mark; FFXIV uses this to indicate that a glyph is missing.
+ private const ushort Fallback2Codepoint = '-'; // FFXIV uses dash if Geta mark is unavailable.
+
+ private readonly HashSet glyphRequests = new();
+ private readonly Dictionary loadedFontInfo = new();
private readonly List deferredDisposeTextures = new();
@@ -79,28 +81,28 @@ internal class InterfaceManager : IDisposable, IServiceType
[ServiceManager.ServiceDependency]
private readonly DalamudIme dalamudIme = Service.Get();
- private readonly SwapChainVtableResolver address = new();
+ private readonly ManualResetEvent fontBuildSignal;
+ private readonly SwapChainVtableResolver address;
private readonly Hook setCursorHook;
private RawDX11Scene? scene;
private Hook? presentHook;
private Hook? resizeBuffersHook;
- private IFontAtlas? dalamudAtlas;
- private IFontHandle.IInternal? defaultFontHandle;
- private IFontHandle.IInternal? iconFontHandle;
- private IFontHandle.IInternal? monoFontHandle;
-
// can't access imgui IO before first present call
private bool lastWantCapture = false;
+ private bool isRebuildingFonts = false;
private bool isOverrideGameCursor = true;
- private IntPtr gameWindowHandle;
[ServiceManager.ServiceConstructor]
private InterfaceManager()
{
this.setCursorHook = Hook.FromImport(
null, "user32.dll", "SetCursor", 0, this.SetCursorDetour);
+
+ this.fontBuildSignal = new ManualResetEvent(false);
+
+ this.address = new SwapChainVtableResolver();
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@@ -115,46 +117,43 @@ internal class InterfaceManager : IDisposable, IServiceType
///
/// This event gets called each frame to facilitate ImGui drawing.
///
- public event RawDX11Scene.BuildUIDelegate? Draw;
+ public event RawDX11Scene.BuildUIDelegate Draw;
///
/// This event gets called when ResizeBuffers is called.
///
- public event Action? ResizeBuffers;
+ public event Action ResizeBuffers;
+
+ ///
+ /// Gets or sets an action that is executed right before fonts are rebuilt.
+ ///
+ public event Action BuildFonts;
///
/// Gets or sets an action that is executed right after fonts are rebuilt.
///
- public event Action? AfterBuildFonts;
+ public event Action AfterBuildFonts;
///
- /// Gets the default ImGui font.
- /// Accessing this static property outside of the main thread is dangerous and not supported.
+ /// Gets the default ImGui font.
///
- public static ImFontPtr DefaultFont => WhenFontsReady().defaultFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
+ public static ImFontPtr DefaultFont { get; private set; }
///
- /// Gets an included FontAwesome icon font.
- /// Accessing this static property outside of the main thread is dangerous and not supported.
+ /// Gets an included FontAwesome icon font.
///
- public static ImFontPtr IconFont => WhenFontsReady().iconFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
+ public static ImFontPtr IconFont { get; private set; }
///
- /// Gets an included monospaced font.
- /// Accessing this static property outside of the main thread is dangerous and not supported.
+ /// Gets an included monospaced font.
///
- public static ImFontPtr MonoFont => WhenFontsReady().monoFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
+ public static ImFontPtr MonoFont { get; private set; }
///
/// Gets or sets the pointer to ImGui.IO(), when it was last used.
///
public ImGuiIOPtr LastImGuiIoPtr { get; set; }
- ///
- /// Gets the DX11 scene.
- ///
- public RawDX11Scene? Scene => this.scene;
-
///
/// Gets the D3D11 device instance.
///
@@ -179,6 +178,11 @@ internal class InterfaceManager : IDisposable, IServiceType
}
}
+ ///
+ /// Gets or sets a value indicating whether the fonts are built and ready to use.
+ ///
+ public bool FontsReady { get; set; } = false;
+
///
/// Gets a value indicating whether the Dalamud interface ready to use.
///
@@ -190,56 +194,49 @@ internal class InterfaceManager : IDisposable, IServiceType
public bool IsDispatchingEvents { get; set; } = true;
///
- /// Gets a value indicating the native handle of the game main window.
+ /// Gets or sets a value indicating whether to override configuration for UseAxis.
///
- public IntPtr GameWindowHandle
- {
- get
- {
- if (this.gameWindowHandle == 0)
- {
- nint gwh = 0;
- while ((gwh = NativeFunctions.FindWindowEx(0, gwh, "FFXIVGAME", 0)) != 0)
- {
- _ = User32.GetWindowThreadProcessId(gwh, out var pid);
- if (pid == Environment.ProcessId && User32.IsWindowVisible(gwh))
- {
- this.gameWindowHandle = gwh;
- break;
- }
- }
- }
-
- return this.gameWindowHandle;
- }
- }
+ public bool? UseAxisOverride { get; set; } = null;
///
- /// Gets the font build task.
+ /// Gets a value indicating whether to use AXIS fonts.
///
- public Task FontBuildTask => WhenFontsReady().dalamudAtlas!.BuildTask;
+ public bool UseAxis => this.UseAxisOverride ?? Service.Get().UseAxisFontsFromGame;
+
+ ///
+ /// Gets or sets the overrided font gamma value, instead of using the value from configuration.
+ ///
+ public float? FontGammaOverride { get; set; } = null;
+
+ ///
+ /// Gets the font gamma value to use.
+ ///
+ public float FontGamma => Math.Max(0.1f, this.FontGammaOverride.GetValueOrDefault(Service.Get().FontGammaLevel));
+
+ ///
+ /// Gets a value indicating whether we're building fonts but haven't generated atlas yet.
+ ///
+ public bool IsBuildingFontsBeforeAtlasBuild => this.isRebuildingFonts && !this.fontBuildSignal.WaitOne(0);
+
+ ///
+ /// Gets a value indicating the native handle of the game main window.
+ ///
+ public IntPtr GameWindowHandle { get; private set; }
///
/// Dispose of managed and unmanaged resources.
///
public void Dispose()
{
- if (Service.GetNullable() is { } framework)
- framework.RunOnFrameworkThread(Disposer).Wait();
- else
- Disposer();
-
- this.wndProcHookManager.PreWndProc -= this.WndProcHookManagerOnPreWndProc;
- this.dalamudAtlas?.Dispose();
- this.scene?.Dispose();
- return;
-
- void Disposer()
+ this.framework.RunOnFrameworkThread(() =>
{
this.setCursorHook.Dispose();
this.presentHook?.Dispose();
this.resizeBuffersHook?.Dispose();
- }
+ }).Wait();
+
+ this.wndProcHookManager.PreWndProc -= this.WndProcHookManagerOnPreWndProc;
+ this.scene?.Dispose();
}
#nullable enable
@@ -379,8 +376,93 @@ internal class InterfaceManager : IDisposable, IServiceType
///
public void RebuildFonts()
{
+ if (this.scene == null)
+ {
+ Log.Verbose("[FONT] RebuildFonts(): scene not ready, doing nothing");
+ return;
+ }
+
Log.Verbose("[FONT] RebuildFonts() called");
- this.dalamudAtlas?.BuildFontsAsync();
+
+ // don't invoke this multiple times per frame, in case multiple plugins call it
+ if (!this.isRebuildingFonts)
+ {
+ Log.Verbose("[FONT] RebuildFonts() trigger");
+ this.isRebuildingFonts = true;
+ this.scene.OnNewRenderFrame += this.RebuildFontsInternal;
+ }
+ }
+
+ ///
+ /// Wait for the rebuilding fonts to complete.
+ ///
+ public void WaitForFontRebuild()
+ {
+ this.fontBuildSignal.WaitOne();
+ }
+
+ ///
+ /// Requests a default font of specified size to exist.
+ ///
+ /// Font size in pixels.
+ /// Ranges of glyphs.
+ /// Requets handle.
+ public SpecialGlyphRequest NewFontSizeRef(float size, List> ranges)
+ {
+ var allContained = false;
+ var fonts = ImGui.GetIO().Fonts.Fonts;
+ ImFontPtr foundFont = null;
+ unsafe
+ {
+ for (int i = 0, i_ = fonts.Size; i < i_; i++)
+ {
+ if (!this.glyphRequests.Any(x => x.FontInternal.NativePtr == fonts[i].NativePtr))
+ continue;
+
+ allContained = true;
+ foreach (var range in ranges)
+ {
+ if (!allContained)
+ break;
+
+ for (var j = range.Item1; j <= range.Item2 && allContained; j++)
+ allContained &= fonts[i].FindGlyphNoFallback(j).NativePtr != null;
+ }
+
+ if (allContained)
+ foundFont = fonts[i];
+
+ break;
+ }
+ }
+
+ var req = new SpecialGlyphRequest(this, size, ranges);
+ req.FontInternal = foundFont;
+
+ if (!allContained)
+ this.RebuildFonts();
+
+ return req;
+ }
+
+ ///
+ /// Requests a default font of specified size to exist.
+ ///
+ /// Font size in pixels.
+ /// Text to calculate glyph ranges from.
+ /// Requets handle.
+ public SpecialGlyphRequest NewFontSizeRef(float size, string text)
+ {
+ List> ranges = new();
+ foreach (var c in new SortedSet(text.ToHashSet()))
+ {
+ if (ranges.Any() && ranges[^1].Item2 + 1 == c)
+ ranges[^1] = Tuple.Create(ranges[^1].Item1, c);
+ else
+ ranges.Add(Tuple.Create(c, c));
+ }
+
+ return this.NewFontSizeRef(size, ranges);
}
///
@@ -404,11 +486,11 @@ internal class InterfaceManager : IDisposable, IServiceType
try
{
var dxgiDev = this.Device.QueryInterfaceOrNull();
- var dxgiAdapter = dxgiDev?.Adapter.QueryInterfaceOrNull();
+ var dxgiAdapter = dxgiDev?.Adapter.QueryInterfaceOrNull();
if (dxgiAdapter == null)
return null;
- var memInfo = dxgiAdapter.QueryVideoMemoryInfo(0, MemorySegmentGroup.Local);
+ var memInfo = dxgiAdapter.QueryVideoMemoryInfo(0, SharpDX.DXGI.MemorySegmentGroup.Local);
return (memInfo.CurrentUsage, memInfo.CurrentReservation);
}
catch
@@ -434,65 +516,20 @@ internal class InterfaceManager : IDisposable, IServiceType
/// Value.
internal void SetImmersiveMode(bool enabled)
{
- if (this.GameWindowHandle == 0)
- throw new InvalidOperationException("Game window is not yet ready.");
- var value = enabled ? 1 : 0;
- ((Result)NativeFunctions.DwmSetWindowAttribute(
- this.GameWindowHandle,
- NativeFunctions.DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE,
- ref value,
- sizeof(int))).CheckError();
+ if (this.GameWindowHandle == nint.Zero)
+ return;
+
+ int value = enabled ? 1 : 0;
+ var hr = NativeFunctions.DwmSetWindowAttribute(
+ this.GameWindowHandle,
+ NativeFunctions.DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE,
+ ref value,
+ sizeof(int));
}
- private static InterfaceManager WhenFontsReady()
+ private static void ShowFontError(string path)
{
- var im = Service.GetNullable();
- if (im?.dalamudAtlas is not { } atlas)
- throw new InvalidOperationException($"Tried to access fonts before {nameof(ContinueConstruction)} call.");
-
- if (!ThreadSafety.IsMainThread && nextNonMainThreadFontAccessWarningCheck < Environment.TickCount64)
- {
- nextNonMainThreadFontAccessWarningCheck =
- Environment.TickCount64 + NonMainThreadFontAccessWarningCheckInterval;
- var stack = new StackTrace();
- if (Service.GetNullable()?.FindCallingPlugin(stack) is { } plugin)
- {
- if (!NonMainThreadFontAccessWarning.TryGetValue(plugin, out _))
- {
- NonMainThreadFontAccessWarning.Add(plugin, new());
- Log.Warning(
- "[IM] {pluginName}: Accessing fonts outside the main thread is deprecated.\n{stack}",
- plugin.Name,
- stack);
- }
- }
- else
- {
- // Dalamud internal should be made safe right now
- throw new InvalidOperationException("Attempted to access fonts outside the main thread.");
- }
- }
-
- if (!atlas.HasBuiltAtlas)
- atlas.BuildTask.GetAwaiter().GetResult();
- return im;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void RenderImGui(RawDX11Scene scene)
- {
- var conf = Service.Get();
-
- // Process information needed by ImGuiHelpers each frame.
- ImGuiHelpers.NewFrame();
-
- // Enable viewports if there are no issues.
- if (conf.IsDisableViewport || scene.SwapChain.IsFullScreen || ImGui.GetPlatformIO().Monitors.Size == 1)
- ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.ViewportsEnable;
- else
- ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.ViewportsEnable;
-
- scene.Render();
+ Util.Fatal($"One or more files required by XIVLauncher were not found.\nPlease restart and report this error if it occurs again.\n\n{path}", "Error");
}
private void InitScene(IntPtr swapChain)
@@ -509,7 +546,7 @@ internal class InterfaceManager : IDisposable, IServiceType
Service.ProvideException(ex);
Log.Error(ex, "Could not load ImGui dependencies.");
- var res = User32.MessageBox(
+ var res = PInvoke.User32.MessageBox(
IntPtr.Zero,
"Dalamud plugins require the Microsoft Visual C++ Redistributable to be installed.\nPlease install the runtime from the official Microsoft website or disable Dalamud.\n\nDo you want to download the redistributable now?",
"Dalamud Error",
@@ -541,7 +578,7 @@ internal class InterfaceManager : IDisposable, IServiceType
if (iniFileInfo.Length > 1200000)
{
Log.Warning("dalamudUI.ini was over 1mb, deleting");
- iniFileInfo.CopyTo(Path.Combine(iniFileInfo.DirectoryName!, $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini"));
+ iniFileInfo.CopyTo(Path.Combine(iniFileInfo.DirectoryName, $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini"));
iniFileInfo.Delete();
}
}
@@ -586,6 +623,8 @@ internal class InterfaceManager : IDisposable, IServiceType
ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale;
+ this.SetupFonts();
+
if (!configuration.IsDocking)
{
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.DockingEnable;
@@ -636,34 +675,26 @@ internal class InterfaceManager : IDisposable, IServiceType
*/
private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags)
{
- Debug.Assert(this.presentHook is not null, "How did PresentDetour get called when presentHook is null?");
- Debug.Assert(this.dalamudAtlas is not null, "dalamudAtlas should have been set already");
-
if (this.scene != null && swapChain != this.scene.SwapChain.NativePointer)
return this.presentHook!.Original(swapChain, syncInterval, presentFlags);
if (this.scene == null)
this.InitScene(swapChain);
- Debug.Assert(this.scene is not null, "InitScene did not set the scene field, but did not throw an exception.");
-
- if (!this.dalamudAtlas!.HasBuiltAtlas)
- return this.presentHook!.Original(swapChain, syncInterval, presentFlags);
-
if (this.address.IsReshade)
{
- var pRes = this.presentHook!.Original(swapChain, syncInterval, presentFlags);
+ var pRes = this.presentHook.Original(swapChain, syncInterval, presentFlags);
- RenderImGui(this.scene!);
+ this.RenderImGui();
this.DisposeTextures();
return pRes;
}
- RenderImGui(this.scene!);
+ this.RenderImGui();
this.DisposeTextures();
- return this.presentHook!.Original(swapChain, syncInterval, presentFlags);
+ return this.presentHook.Original(swapChain, syncInterval, presentFlags);
}
private void DisposeTextures()
@@ -680,73 +711,471 @@ internal class InterfaceManager : IDisposable, IServiceType
}
}
- [ServiceManager.CallWhenServicesReady(
- "InterfaceManager accepts event registration and stuff even when the game window is not ready.")]
- private void ContinueConstruction(
- TargetSigScanner sigScanner,
- Framework framework,
- FontAtlasFactory fontAtlasFactory)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void RenderImGui()
{
- this.dalamudAtlas = fontAtlasFactory
- .CreateFontAtlas(nameof(InterfaceManager), FontAtlasAutoRebuildMode.Disable);
- using (this.dalamudAtlas.SuppressAutoRebuild())
+ // Process information needed by ImGuiHelpers each frame.
+ ImGuiHelpers.NewFrame();
+
+ // Check if we can still enable viewports without any issues.
+ this.CheckViewportState();
+
+ this.scene.Render();
+ }
+
+ private void CheckViewportState()
+ {
+ var configuration = Service.Get();
+
+ if (configuration.IsDisableViewport || this.scene.SwapChain.IsFullScreen || ImGui.GetPlatformIO().Monitors.Size == 1)
{
- this.defaultFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle(
- e => e.OnPreBuild(tk => tk.AddDalamudDefaultFont(DefaultFontSizePx)));
- this.iconFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle(
- e => e.OnPreBuild(
- tk => tk.AddFontAwesomeIconFont(
- new()
- {
- SizePx = DefaultFontSizePx,
- GlyphMinAdvanceX = DefaultFontSizePx,
- GlyphMaxAdvanceX = DefaultFontSizePx,
- })));
- this.monoFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle(
- e => e.OnPreBuild(
- tk => tk.AddDalamudAssetFont(
- DalamudAsset.InconsolataRegular,
- new() { SizePx = DefaultFontSizePx })));
- this.dalamudAtlas.BuildStepChange += e => e.OnPostPromotion(
- tk =>
- {
- // Note: the first call of this function is done outside the main thread; this is expected.
- // Do not use DefaultFont, IconFont, and MonoFont.
- // Use font handles directly.
-
- // Fill missing glyphs in MonoFont from DefaultFont
- tk.CopyGlyphsAcrossFonts(this.defaultFontHandle.ImFont, this.monoFontHandle.ImFont, true);
-
- // Broadcast to auto-rebuilding instances
- this.AfterBuildFonts?.Invoke();
- });
+ ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.ViewportsEnable;
+ return;
}
- // This will wait for scene on its own. We just wait for this.dalamudAtlas.BuildTask in this.InitScene.
- _ = this.dalamudAtlas.BuildFontsAsync(false);
+ ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.ViewportsEnable;
+ }
- this.address.Setup(sigScanner);
+ ///
+ /// Loads font for use in ImGui text functions.
+ ///
+ private unsafe void SetupFonts()
+ {
+ using var setupFontsTimings = Timings.Start("IM SetupFonts");
+
+ var gameFontManager = Service.Get();
+ var dalamud = Service.Get();
+ var io = ImGui.GetIO();
+ var ioFonts = io.Fonts;
+
+ var fontGamma = this.FontGamma;
+
+ this.fontBuildSignal.Reset();
+ ioFonts.Clear();
+ ioFonts.TexDesiredWidth = 4096;
+
+ Log.Verbose("[FONT] SetupFonts - 1");
+
+ foreach (var v in this.loadedFontInfo)
+ v.Value.Dispose();
+
+ this.loadedFontInfo.Clear();
+
+ Log.Verbose("[FONT] SetupFonts - 2");
+
+ ImFontConfigPtr fontConfig = null;
+ List garbageList = new();
try
{
- if (Service.Get().WindowIsImmersive)
- this.SetImmersiveMode(true);
+ var dummyRangeHandle = GCHandle.Alloc(new ushort[] { '0', '0', 0 }, GCHandleType.Pinned);
+ garbageList.Add(dummyRangeHandle);
+
+ fontConfig = ImGuiNative.ImFontConfig_ImFontConfig();
+ fontConfig.OversampleH = 1;
+ fontConfig.OversampleV = 1;
+
+ var fontPathJp = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansCJKjp-Regular.otf");
+ if (!File.Exists(fontPathJp))
+ fontPathJp = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansCJKjp-Medium.otf");
+ if (!File.Exists(fontPathJp))
+ ShowFontError(fontPathJp);
+ Log.Verbose("[FONT] fontPathJp = {0}", fontPathJp);
+
+ var fontPathKr = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansCJKkr-Regular.otf");
+ if (!File.Exists(fontPathKr))
+ fontPathKr = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansKR-Regular.otf");
+ if (!File.Exists(fontPathKr))
+ fontPathKr = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Fonts", "malgun.ttf");
+ if (!File.Exists(fontPathKr))
+ fontPathKr = null;
+ Log.Verbose("[FONT] fontPathKr = {0}", fontPathKr);
+
+ var fontPathChs = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Fonts", "msyh.ttc");
+ if (!File.Exists(fontPathChs))
+ fontPathChs = null;
+ Log.Verbose("[FONT] fontPathChs = {0}", fontPathChs);
+
+ var fontPathCht = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Fonts", "msjh.ttc");
+ if (!File.Exists(fontPathCht))
+ fontPathCht = null;
+ Log.Verbose("[FONT] fontPathChs = {0}", fontPathCht);
+
+ // Default font
+ Log.Verbose("[FONT] SetupFonts - Default font");
+ var fontInfo = new TargetFontModification(
+ "Default",
+ this.UseAxis ? TargetFontModification.AxisMode.Overwrite : TargetFontModification.AxisMode.GameGlyphsOnly,
+ this.UseAxis ? DefaultFontSizePx : DefaultFontSizePx + 1,
+ io.FontGlobalScale);
+ Log.Verbose("[FONT] SetupFonts - Default corresponding AXIS size: {0}pt ({1}px)", fontInfo.SourceAxis.Style.BaseSizePt, fontInfo.SourceAxis.Style.BaseSizePx);
+ fontConfig.SizePixels = fontInfo.TargetSizePx * io.FontGlobalScale;
+ if (this.UseAxis)
+ {
+ fontConfig.GlyphRanges = dummyRangeHandle.AddrOfPinnedObject();
+ fontConfig.PixelSnapH = false;
+ DefaultFont = ioFonts.AddFontDefault(fontConfig);
+ this.loadedFontInfo[DefaultFont] = fontInfo;
+ }
+ else
+ {
+ var rangeHandle = gameFontManager.ToGlyphRanges(GameFontFamilyAndSize.Axis12);
+ garbageList.Add(rangeHandle);
+
+ fontConfig.GlyphRanges = rangeHandle.AddrOfPinnedObject();
+ fontConfig.PixelSnapH = true;
+ DefaultFont = ioFonts.AddFontFromFileTTF(fontPathJp, fontConfig.SizePixels, fontConfig);
+ this.loadedFontInfo[DefaultFont] = fontInfo;
+ }
+
+ if (fontPathKr != null
+ && (Service.Get().EffectiveLanguage == "ko" || this.dalamudIme.EncounteredHangul))
+ {
+ fontConfig.MergeMode = true;
+ fontConfig.GlyphRanges = ioFonts.GetGlyphRangesKorean();
+ fontConfig.PixelSnapH = true;
+ ioFonts.AddFontFromFileTTF(fontPathKr, fontConfig.SizePixels, fontConfig);
+ fontConfig.MergeMode = false;
+ }
+
+ if (fontPathCht != null && Service.Get().EffectiveLanguage == "tw")
+ {
+ fontConfig.MergeMode = true;
+ var rangeHandle = GCHandle.Alloc(new ushort[]
+ {
+ (ushort)UnicodeRanges.CjkUnifiedIdeographs.FirstCodePoint,
+ (ushort)(UnicodeRanges.CjkUnifiedIdeographs.FirstCodePoint +
+ (UnicodeRanges.CjkUnifiedIdeographs.Length - 1)),
+ (ushort)UnicodeRanges.CjkUnifiedIdeographsExtensionA.FirstCodePoint,
+ (ushort)(UnicodeRanges.CjkUnifiedIdeographsExtensionA.FirstCodePoint +
+ (UnicodeRanges.CjkUnifiedIdeographsExtensionA.Length - 1)),
+ 0,
+ }, GCHandleType.Pinned);
+ garbageList.Add(rangeHandle);
+ fontConfig.GlyphRanges = rangeHandle.AddrOfPinnedObject();
+ fontConfig.PixelSnapH = true;
+ ioFonts.AddFontFromFileTTF(fontPathCht, fontConfig.SizePixels, fontConfig);
+ fontConfig.MergeMode = false;
+ }
+ else if (fontPathChs != null && (Service.Get().EffectiveLanguage == "zh"
+ || this.dalamudIme.EncounteredHan))
+ {
+ fontConfig.MergeMode = true;
+ var rangeHandle = GCHandle.Alloc(new ushort[]
+ {
+ (ushort)UnicodeRanges.CjkUnifiedIdeographs.FirstCodePoint,
+ (ushort)(UnicodeRanges.CjkUnifiedIdeographs.FirstCodePoint +
+ (UnicodeRanges.CjkUnifiedIdeographs.Length - 1)),
+ (ushort)UnicodeRanges.CjkUnifiedIdeographsExtensionA.FirstCodePoint,
+ (ushort)(UnicodeRanges.CjkUnifiedIdeographsExtensionA.FirstCodePoint +
+ (UnicodeRanges.CjkUnifiedIdeographsExtensionA.Length - 1)),
+ 0,
+ }, GCHandleType.Pinned);
+ garbageList.Add(rangeHandle);
+ fontConfig.GlyphRanges = rangeHandle.AddrOfPinnedObject();
+ fontConfig.PixelSnapH = true;
+ ioFonts.AddFontFromFileTTF(fontPathChs, fontConfig.SizePixels, fontConfig);
+ fontConfig.MergeMode = false;
+ }
+
+ // FontAwesome icon font
+ Log.Verbose("[FONT] SetupFonts - FontAwesome icon font");
+ {
+ var fontPathIcon = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "FontAwesomeFreeSolid.otf");
+ if (!File.Exists(fontPathIcon))
+ ShowFontError(fontPathIcon);
+
+ var iconRangeHandle = GCHandle.Alloc(new ushort[] { 0xE000, 0xF8FF, 0, }, GCHandleType.Pinned);
+ garbageList.Add(iconRangeHandle);
+
+ fontConfig.GlyphRanges = iconRangeHandle.AddrOfPinnedObject();
+ fontConfig.PixelSnapH = true;
+ IconFont = ioFonts.AddFontFromFileTTF(fontPathIcon, DefaultFontSizePx * io.FontGlobalScale, fontConfig);
+ this.loadedFontInfo[IconFont] = new("Icon", TargetFontModification.AxisMode.GameGlyphsOnly, DefaultFontSizePx, io.FontGlobalScale);
+ }
+
+ // Monospace font
+ Log.Verbose("[FONT] SetupFonts - Monospace font");
+ {
+ var fontPathMono = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "Inconsolata-Regular.ttf");
+ if (!File.Exists(fontPathMono))
+ ShowFontError(fontPathMono);
+
+ fontConfig.GlyphRanges = IntPtr.Zero;
+ fontConfig.PixelSnapH = true;
+ MonoFont = ioFonts.AddFontFromFileTTF(fontPathMono, DefaultFontSizePx * io.FontGlobalScale, fontConfig);
+ this.loadedFontInfo[MonoFont] = new("Mono", TargetFontModification.AxisMode.GameGlyphsOnly, DefaultFontSizePx, io.FontGlobalScale);
+ }
+
+ // Default font but in requested size for requested glyphs
+ Log.Verbose("[FONT] SetupFonts - Default font but in requested size for requested glyphs");
+ {
+ Dictionary> extraFontRequests = new();
+ foreach (var extraFontRequest in this.glyphRequests)
+ {
+ if (!extraFontRequests.ContainsKey(extraFontRequest.Size))
+ extraFontRequests[extraFontRequest.Size] = new();
+ extraFontRequests[extraFontRequest.Size].Add(extraFontRequest);
+ }
+
+ foreach (var (fontSize, requests) in extraFontRequests)
+ {
+ List<(ushort, ushort)> codepointRanges = new(4 + requests.Sum(x => x.CodepointRanges.Count))
+ {
+ new(Fallback1Codepoint, Fallback1Codepoint),
+ new(Fallback2Codepoint, Fallback2Codepoint),
+ // ImGui default ellipsis characters
+ new(0x2026, 0x2026),
+ new(0x0085, 0x0085),
+ };
+
+ foreach (var request in requests)
+ codepointRanges.AddRange(request.CodepointRanges.Select(x => (From: x.Item1, To: x.Item2)));
+
+ codepointRanges.Sort();
+ List flattenedRanges = new();
+ foreach (var range in codepointRanges)
+ {
+ if (flattenedRanges.Any() && flattenedRanges[^1] >= range.Item1 - 1)
+ {
+ flattenedRanges[^1] = Math.Max(flattenedRanges[^1], range.Item2);
+ }
+ else
+ {
+ flattenedRanges.Add(range.Item1);
+ flattenedRanges.Add(range.Item2);
+ }
+ }
+
+ flattenedRanges.Add(0);
+
+ fontInfo = new(
+ $"Requested({fontSize}px)",
+ this.UseAxis ? TargetFontModification.AxisMode.Overwrite : TargetFontModification.AxisMode.GameGlyphsOnly,
+ fontSize,
+ io.FontGlobalScale);
+ if (this.UseAxis)
+ {
+ fontConfig.GlyphRanges = dummyRangeHandle.AddrOfPinnedObject();
+ fontConfig.SizePixels = fontInfo.SourceAxis.Style.BaseSizePx;
+ fontConfig.PixelSnapH = false;
+
+ var sizedFont = ioFonts.AddFontDefault(fontConfig);
+ this.loadedFontInfo[sizedFont] = fontInfo;
+ foreach (var request in requests)
+ request.FontInternal = sizedFont;
+ }
+ else
+ {
+ var rangeHandle = GCHandle.Alloc(flattenedRanges.ToArray(), GCHandleType.Pinned);
+ garbageList.Add(rangeHandle);
+ fontConfig.PixelSnapH = true;
+
+ var sizedFont = ioFonts.AddFontFromFileTTF(fontPathJp, fontSize * io.FontGlobalScale, fontConfig, rangeHandle.AddrOfPinnedObject());
+ this.loadedFontInfo[sizedFont] = fontInfo;
+ foreach (var request in requests)
+ request.FontInternal = sizedFont;
+ }
+ }
+ }
+
+ gameFontManager.BuildFonts();
+
+ var customFontFirstConfigIndex = ioFonts.ConfigData.Size;
+
+ Log.Verbose("[FONT] Invoke OnBuildFonts");
+ this.BuildFonts?.InvokeSafely();
+ Log.Verbose("[FONT] OnBuildFonts OK!");
+
+ for (int i = customFontFirstConfigIndex, i_ = ioFonts.ConfigData.Size; i < i_; i++)
+ {
+ var config = ioFonts.ConfigData[i];
+ if (gameFontManager.OwnsFont(config.DstFont))
+ continue;
+
+ config.OversampleH = 1;
+ config.OversampleV = 1;
+
+ var name = Encoding.UTF8.GetString((byte*)config.Name.Data, config.Name.Count).TrimEnd('\0');
+ if (name.IsNullOrEmpty())
+ name = $"{config.SizePixels}px";
+
+ // ImFont information is reflected only if corresponding ImFontConfig has MergeMode not set.
+ if (config.MergeMode)
+ {
+ if (!this.loadedFontInfo.ContainsKey(config.DstFont.NativePtr))
+ {
+ Log.Warning("MergeMode specified for {0} but not found in loadedFontInfo. Skipping.", name);
+ continue;
+ }
+ }
+ else
+ {
+ if (this.loadedFontInfo.ContainsKey(config.DstFont.NativePtr))
+ {
+ Log.Warning("MergeMode not specified for {0} but found in loadedFontInfo. Skipping.", name);
+ continue;
+ }
+
+ // While the font will be loaded in the scaled size after FontScale is applied, the font will be treated as having the requested size when used from plugins.
+ this.loadedFontInfo[config.DstFont.NativePtr] = new($"PlReq({name})", config.SizePixels);
+ }
+
+ config.SizePixels = config.SizePixels * io.FontGlobalScale;
+ }
+
+ for (int i = 0, i_ = ioFonts.ConfigData.Size; i < i_; i++)
+ {
+ var config = ioFonts.ConfigData[i];
+ config.RasterizerGamma *= fontGamma;
+ }
+
+ Log.Verbose("[FONT] ImGui.IO.Build will be called.");
+ ioFonts.Build();
+ gameFontManager.AfterIoFontsBuild();
+ this.ClearStacks();
+ Log.Verbose("[FONT] ImGui.IO.Build OK!");
+
+ gameFontManager.AfterBuildFonts();
+
+ foreach (var (font, mod) in this.loadedFontInfo)
+ {
+ // I have no idea what's causing NPE, so just to be safe
+ try
+ {
+ if (font.NativePtr != null && font.NativePtr->ConfigData != null)
+ {
+ var nameBytes = Encoding.UTF8.GetBytes($"{mod.Name}\0");
+ Marshal.Copy(nameBytes, 0, (IntPtr)font.ConfigData.Name.Data, Math.Min(nameBytes.Length, font.ConfigData.Name.Count));
+ }
+ }
+ catch (NullReferenceException)
+ {
+ // do nothing
+ }
+
+ Log.Verbose("[FONT] {0}: Unscale with scale value of {1}", mod.Name, mod.Scale);
+ GameFontManager.UnscaleFont(font, mod.Scale, false);
+
+ if (mod.Axis == TargetFontModification.AxisMode.Overwrite)
+ {
+ Log.Verbose("[FONT] {0}: Overwrite from AXIS of size {1}px (was {2}px)", mod.Name, mod.SourceAxis.ImFont.FontSize, font.FontSize);
+ GameFontManager.UnscaleFont(font, font.FontSize / mod.SourceAxis.ImFont.FontSize, false);
+ var ascentDiff = mod.SourceAxis.ImFont.Ascent - font.Ascent;
+ font.Ascent += ascentDiff;
+ font.Descent = ascentDiff;
+ font.FallbackChar = mod.SourceAxis.ImFont.FallbackChar;
+ font.EllipsisChar = mod.SourceAxis.ImFont.EllipsisChar;
+ ImGuiHelpers.CopyGlyphsAcrossFonts(mod.SourceAxis.ImFont, font, false, false);
+ }
+ else if (mod.Axis == TargetFontModification.AxisMode.GameGlyphsOnly)
+ {
+ Log.Verbose("[FONT] {0}: Overwrite game specific glyphs from AXIS of size {1}px", mod.Name, mod.SourceAxis.ImFont.FontSize, font.FontSize);
+ if (!this.UseAxis && font.NativePtr == DefaultFont.NativePtr)
+ mod.SourceAxis.ImFont.FontSize -= 1;
+ ImGuiHelpers.CopyGlyphsAcrossFonts(mod.SourceAxis.ImFont, font, true, false, 0xE020, 0xE0DB);
+ if (!this.UseAxis && font.NativePtr == DefaultFont.NativePtr)
+ mod.SourceAxis.ImFont.FontSize += 1;
+ }
+
+ Log.Verbose("[FONT] {0}: Resize from {1}px to {2}px", mod.Name, font.FontSize, mod.TargetSizePx);
+ GameFontManager.UnscaleFont(font, font.FontSize / mod.TargetSizePx, false);
+ }
+
+ // Fill missing glyphs in MonoFont from DefaultFont
+ ImGuiHelpers.CopyGlyphsAcrossFonts(DefaultFont, MonoFont, true, false);
+
+ for (int i = 0, i_ = ioFonts.Fonts.Size; i < i_; i++)
+ {
+ var font = ioFonts.Fonts[i];
+ if (font.Glyphs.Size == 0)
+ {
+ Log.Warning("[FONT] Font has no glyph: {0}", font.GetDebugName());
+ continue;
+ }
+
+ if (font.FindGlyphNoFallback(Fallback1Codepoint).NativePtr != null)
+ font.FallbackChar = Fallback1Codepoint;
+
+ font.BuildLookupTableNonstandard();
+ }
+
+ Log.Verbose("[FONT] Invoke OnAfterBuildFonts");
+ this.AfterBuildFonts?.InvokeSafely();
+ Log.Verbose("[FONT] OnAfterBuildFonts OK!");
+
+ if (ioFonts.Fonts[0].NativePtr != DefaultFont.NativePtr)
+ Log.Warning("[FONT] First font is not DefaultFont");
+
+ Log.Verbose("[FONT] Fonts built!");
+
+ this.fontBuildSignal.Set();
+
+ this.FontsReady = true;
}
- catch (Exception ex)
+ finally
{
- Log.Error(ex, "Could not enable immersive mode");
+ if (fontConfig.NativePtr != null)
+ fontConfig.Destroy();
+
+ foreach (var garbage in garbageList)
+ garbage.Free();
}
+ }
- this.presentHook = Hook.FromAddress(this.address.Present, this.PresentDetour);
- this.resizeBuffersHook = Hook.FromAddress(this.address.ResizeBuffers, this.ResizeBuffersDetour);
+ [ServiceManager.CallWhenServicesReady(
+ "InterfaceManager accepts event registration and stuff even when the game window is not ready.")]
+ private void ContinueConstruction(TargetSigScanner sigScanner, DalamudConfiguration configuration)
+ {
+ this.address.Setup(sigScanner);
+ this.framework.RunOnFrameworkThread(() =>
+ {
+ while ((this.GameWindowHandle = NativeFunctions.FindWindowEx(IntPtr.Zero, this.GameWindowHandle, "FFXIVGAME", IntPtr.Zero)) != IntPtr.Zero)
+ {
+ _ = User32.GetWindowThreadProcessId(this.GameWindowHandle, out var pid);
- Log.Verbose("===== S W A P C H A I N =====");
- Log.Verbose($"Present address 0x{this.presentHook!.Address.ToInt64():X}");
- Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook!.Address.ToInt64():X}");
+ if (pid == Environment.ProcessId && User32.IsWindowVisible(this.GameWindowHandle))
+ break;
+ }
- this.setCursorHook.Enable();
- this.presentHook.Enable();
- this.resizeBuffersHook.Enable();
+ try
+ {
+ if (configuration.WindowIsImmersive)
+ this.SetImmersiveMode(true);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Could not enable immersive mode");
+ }
+
+ this.presentHook = Hook.FromAddress(this.address.Present, this.PresentDetour);
+ this.resizeBuffersHook = Hook.FromAddress(this.address.ResizeBuffers, this.ResizeBuffersDetour);
+
+ Log.Verbose("===== S W A P C H A I N =====");
+ Log.Verbose($"Present address 0x{this.presentHook!.Address.ToInt64():X}");
+ Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook!.Address.ToInt64():X}");
+
+ this.setCursorHook.Enable();
+ this.presentHook.Enable();
+ this.resizeBuffersHook.Enable();
+ });
+ }
+
+ // This is intended to only be called as a handler attached to scene.OnNewRenderFrame
+ private void RebuildFontsInternal()
+ {
+ Log.Verbose("[FONT] RebuildFontsInternal() called");
+ this.SetupFonts();
+
+ Log.Verbose("[FONT] RebuildFontsInternal() detaching");
+ this.scene!.OnNewRenderFrame -= this.RebuildFontsInternal;
+
+ Log.Verbose("[FONT] Calling InvalidateFonts");
+ this.scene.InvalidateFonts();
+
+ Log.Verbose("[FONT] Font Rebuild OK!");
+
+ this.isRebuildingFonts = false;
}
private IntPtr ResizeBuffersDetour(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags)
@@ -777,17 +1206,14 @@ internal class InterfaceManager : IDisposable, IServiceType
private IntPtr SetCursorDetour(IntPtr hCursor)
{
- if (this.lastWantCapture && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor)
+ if (this.lastWantCapture == true && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor)
return IntPtr.Zero;
- return this.setCursorHook.IsDisposed
- ? User32.SetCursor(new(hCursor, false)).DangerousGetHandle()
- : this.setCursorHook.Original(hCursor);
+ return this.setCursorHook.IsDisposed ? User32.SetCursor(new User32.SafeCursorHandle(hCursor, false)).DangerousGetHandle() : this.setCursorHook.Original(hCursor);
}
private void OnNewInputFrame()
{
- var io = ImGui.GetIO();
var dalamudInterface = Service.GetNullable();
var gamepadState = Service.GetNullable();
var keyState = Service.GetNullable();
@@ -795,21 +1221,18 @@ internal class InterfaceManager : IDisposable, IServiceType
if (dalamudInterface == null || gamepadState == null || keyState == null)
return;
- // Prevent setting the footgun from ImGui Demo; the Space key isn't removing the flag at the moment.
- io.ConfigFlags &= ~ImGuiConfigFlags.NoMouse;
-
// fix for keys in game getting stuck, if you were holding a game key (like run)
// and then clicked on an imgui textbox - imgui would swallow the keyup event,
// so the game would think the key remained pressed continuously until you left
// imgui and pressed and released the key again
- if (io.WantTextInput)
+ if (ImGui.GetIO().WantTextInput)
{
keyState.ClearAll();
}
// TODO: mouse state?
- var gamepadEnabled = (io.BackendFlags & ImGuiBackendFlags.HasGamepad) > 0;
+ var gamepadEnabled = (ImGui.GetIO().BackendFlags & ImGuiBackendFlags.HasGamepad) > 0;
// NOTE (Chiv) Activate ImGui navigation via L1+L3 press
// (mimicking how mouse navigation is activated via L1+R3 press in game).
@@ -817,12 +1240,12 @@ internal class InterfaceManager : IDisposable, IServiceType
&& gamepadState.Raw(GamepadButtons.L1) > 0
&& gamepadState.Pressed(GamepadButtons.L3) > 0)
{
- io.ConfigFlags ^= ImGuiConfigFlags.NavEnableGamepad;
+ ImGui.GetIO().ConfigFlags ^= ImGuiConfigFlags.NavEnableGamepad;
gamepadState.NavEnableGamepad ^= true;
dalamudInterface.ToggleGamepadModeNotifierWindow();
}
- if (gamepadEnabled && (io.ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0)
+ if (gamepadEnabled && (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0)
{
var northButton = gamepadState.Raw(GamepadButtons.North) != 0;
var eastButton = gamepadState.Raw(GamepadButtons.East) != 0;
@@ -841,6 +1264,7 @@ internal class InterfaceManager : IDisposable, IServiceType
var r1Button = gamepadState.Raw(GamepadButtons.R1) != 0;
var r2Button = gamepadState.Raw(GamepadButtons.R2) != 0;
+ var io = ImGui.GetIO();
io.AddKeyEvent(ImGuiKey.GamepadFaceUp, northButton);
io.AddKeyEvent(ImGuiKey.GamepadFaceRight, eastButton);
io.AddKeyEvent(ImGuiKey.GamepadFaceDown, southButton);
@@ -888,10 +1312,7 @@ internal class InterfaceManager : IDisposable, IServiceType
var snap = ImGuiManagedAsserts.GetSnapshot();
if (this.IsDispatchingEvents)
- {
- using (this.defaultFontHandle?.Push())
- this.Draw?.Invoke();
- }
+ this.Draw?.Invoke();
ImGuiManagedAsserts.ReportProblems("Dalamud Core", snap);
@@ -918,4 +1339,123 @@ internal class InterfaceManager : IDisposable, IServiceType
///
public InterfaceManager Manager { get; init; }
}
+
+ ///
+ /// Represents a glyph request.
+ ///
+ public class SpecialGlyphRequest : IDisposable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// InterfaceManager to associate.
+ /// Font size in pixels.
+ /// Codepoint ranges.
+ internal SpecialGlyphRequest(InterfaceManager manager, float size, List> ranges)
+ {
+ this.Manager = manager;
+ this.Size = size;
+ this.CodepointRanges = ranges;
+ this.Manager.glyphRequests.Add(this);
+ }
+
+ ///
+ /// Gets the font of specified size, or DefaultFont if it's not ready yet.
+ ///
+ public ImFontPtr Font
+ {
+ get
+ {
+ unsafe
+ {
+ return this.FontInternal.NativePtr == null ? DefaultFont : this.FontInternal;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the associated ImFont.
+ ///
+ internal ImFontPtr FontInternal { get; set; }
+
+ ///
+ /// Gets associated InterfaceManager.
+ ///
+ internal InterfaceManager Manager { get; init; }
+
+ ///
+ /// Gets font size.
+ ///
+ internal float Size { get; init; }
+
+ ///
+ /// Gets codepoint ranges.
+ ///
+ internal List> CodepointRanges { get; init; }
+
+ ///
+ public void Dispose()
+ {
+ this.Manager.glyphRequests.Remove(this);
+ }
+ }
+
+ private unsafe class TargetFontModification : IDisposable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ /// Constructs new target font modification information, assuming that AXIS fonts will not be applied.
+ ///
+ /// Name of the font to write to ImGui font information.
+ /// Target font size in pixels, which will not be considered for further scaling.
+ internal TargetFontModification(string name, float sizePx)
+ {
+ this.Name = name;
+ this.Axis = AxisMode.Suppress;
+ this.TargetSizePx = sizePx;
+ this.Scale = 1;
+ this.SourceAxis = null;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ /// Constructs new target font modification information.
+ ///
+ /// Name of the font to write to ImGui font information.
+ /// Whether and how to use AXIS fonts.
+ /// Target font size in pixels, which will not be considered for further scaling.
+ /// Font scale to be referred for loading AXIS font of appropriate size.
+ internal TargetFontModification(string name, AxisMode axis, float sizePx, float globalFontScale)
+ {
+ this.Name = name;
+ this.Axis = axis;
+ this.TargetSizePx = sizePx;
+ this.Scale = globalFontScale;
+ this.SourceAxis = Service.Get().NewFontRef(new(GameFontFamily.Axis, this.TargetSizePx * this.Scale));
+ }
+
+ internal enum AxisMode
+ {
+ Suppress,
+ GameGlyphsOnly,
+ Overwrite,
+ }
+
+ internal string Name { get; private init; }
+
+ internal AxisMode Axis { get; private init; }
+
+ internal float TargetSizePx { get; private init; }
+
+ internal float Scale { get; private init; }
+
+ internal GameFontHandle? SourceAxis { get; private init; }
+
+ internal bool SourceAxisAvailable => this.SourceAxis != null && this.SourceAxis.ImFont.NativePtr != null;
+
+ public void Dispose()
+ {
+ this.SourceAxis?.Dispose();
+ }
+ }
}
diff --git a/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs b/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs
index ae59db36a..b9e7ab686 100644
--- a/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs
@@ -1,3 +1,4 @@
+using System.IO;
using System.Linq;
using System.Numerics;
@@ -6,8 +7,6 @@ using Dalamud.Interface.Animation.EasingFunctions;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.GameFonts;
-using Dalamud.Interface.ManagedFontAtlas;
-using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
@@ -32,14 +31,8 @@ internal sealed class ChangelogWindow : Window, IDisposable
• Plugins can now add tooltips and interaction to the server info bar
• The Dalamud/plugin installer UI has been refreshed
";
-
+
private readonly TitleScreenMenuWindow tsmWindow;
-
- private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new();
- private readonly IFontAtlas privateAtlas;
- private readonly Lazy bannerFont;
- private readonly Lazy apiBumpExplainerTexture;
- private readonly Lazy logoTexture;
private readonly InOutCubic windowFade = new(TimeSpan.FromSeconds(2.5f))
{
@@ -53,36 +46,27 @@ internal sealed class ChangelogWindow : Window, IDisposable
Point2 = Vector2.One,
};
+ private IDalamudTextureWrap? apiBumpExplainerTexture;
+ private IDalamudTextureWrap? logoTexture;
+ private GameFontHandle? bannerFont;
+
private State state = State.WindowFadeIn;
private bool needFadeRestart = false;
-
+
///
/// Initializes a new instance of the class.
///
/// TSM window.
- /// An instance of .
- /// An instance of .
- public ChangelogWindow(
- TitleScreenMenuWindow tsmWindow,
- FontAtlasFactory fontAtlasFactory,
- DalamudAssetManager assets)
+ public ChangelogWindow(TitleScreenMenuWindow tsmWindow)
: base("What's new in Dalamud?##ChangelogWindow", ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse, true)
{
this.tsmWindow = tsmWindow;
this.Namespace = "DalamudChangelogWindow";
- this.privateAtlas = this.scopedFinalizer.Add(
- fontAtlasFactory.CreateFontAtlas(this.Namespace, FontAtlasAutoRebuildMode.Async));
- this.bannerFont = new(
- () => this.scopedFinalizer.Add(
- this.privateAtlas.NewGameFontHandle(new(GameFontFamilyAndSize.MiedingerMid18))));
-
- this.apiBumpExplainerTexture = new(() => assets.GetDalamudTextureWrap(DalamudAsset.ChangelogApiBumpIcon));
- this.logoTexture = new(() => assets.GetDalamudTextureWrap(DalamudAsset.Logo));
// If we are going to show a changelog, make sure we have the font ready, otherwise it will hitch
if (WarrantsChangelog())
- _ = this.bannerFont.Value;
+ Service.GetAsync().ContinueWith(t => this.MakeFont(t.Result));
}
private enum State
@@ -113,12 +97,20 @@ internal sealed class ChangelogWindow : Window, IDisposable
Service.Get().SetCreditsDarkeningAnimation(true);
this.tsmWindow.AllowDrawing = false;
- _ = this.bannerFont;
+ this.MakeFont(Service.Get());
this.state = State.WindowFadeIn;
this.windowFade.Reset();
this.bodyFade.Reset();
this.needFadeRestart = true;
+
+ if (this.apiBumpExplainerTexture == null)
+ {
+ var dalamud = Service.Get();
+ var tm = Service.Get();
+ this.apiBumpExplainerTexture = tm.GetTextureFromFile(new FileInfo(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "changelogApiBump.png")))
+ ?? throw new Exception("Could not load api bump explainer.");
+ }
base.OnOpen();
}
@@ -194,7 +186,10 @@ internal sealed class ChangelogWindow : Window, IDisposable
ImGui.SetCursorPos(new Vector2(logoContainerSize.X / 2 - logoSize.X / 2, logoContainerSize.Y / 2 - logoSize.Y / 2));
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, Math.Clamp(this.windowFade.EasedPoint.X - 0.5f, 0f, 1f)))
- ImGui.Image(this.logoTexture.Value.ImGuiHandle, logoSize);
+ {
+ this.logoTexture ??= Service.Get().GetDalamudTextureWrap(DalamudAsset.Logo);
+ ImGui.Image(this.logoTexture.ImGuiHandle, logoSize);
+ }
}
ImGui.SameLine();
@@ -210,7 +205,7 @@ internal sealed class ChangelogWindow : Window, IDisposable
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, Math.Clamp(this.windowFade.EasedPoint.X - 1f, 0f, 1f)))
{
- using var font = this.bannerFont.Value.Push();
+ using var font = ImRaii.PushFont(this.bannerFont!.ImFont);
switch (this.state)
{
@@ -280,11 +275,9 @@ internal sealed class ChangelogWindow : Window, IDisposable
ImGui.TextWrapped("If some plugins are displayed with a red cross in the 'Installed Plugins' tab, they may not yet be available.");
ImGuiHelpers.ScaledDummy(15);
-
- ImGuiHelpers.CenterCursorFor(this.apiBumpExplainerTexture.Value.Width);
- ImGui.Image(
- this.apiBumpExplainerTexture.Value.ImGuiHandle,
- this.apiBumpExplainerTexture.Value.Size);
+
+ ImGuiHelpers.CenterCursorFor(this.apiBumpExplainerTexture!.Width);
+ ImGui.Image(this.apiBumpExplainerTexture.ImGuiHandle, this.apiBumpExplainerTexture.Size);
DrawNextButton(State.Links);
break;
@@ -384,4 +377,7 @@ internal sealed class ChangelogWindow : Window, IDisposable
public void Dispose()
{
}
+
+ private void MakeFont(GameFontManager gfm) =>
+ this.bannerFont ??= gfm.NewFontRef(new GameFontStyle(GameFontFamilyAndSize.MiedingerMid18));
}
diff --git a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs
index 951d3d91c..20c3d6d01 100644
--- a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs
@@ -6,8 +6,6 @@ using Dalamud.Interface.Components;
using Dalamud.Interface.Internal.Windows.Data.Widgets;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing;
-using Dalamud.Utility;
-
using ImGuiNET;
using Serilog;
@@ -16,7 +14,7 @@ namespace Dalamud.Interface.Internal.Windows.Data;
///
/// Class responsible for drawing the data/debug window.
///
-internal class DataWindow : Window, IDisposable
+internal class DataWindow : Window
{
private readonly IDataWindowWidget[] modules =
{
@@ -36,7 +34,6 @@ internal class DataWindow : Window, IDisposable
new FlyTextWidget(),
new FontAwesomeTestWidget(),
new GameInventoryTestWidget(),
- new GamePrebakedFontsTestWidget(),
new GamepadWidget(),
new GaugeWidget(),
new HookWidget(),
@@ -79,9 +76,6 @@ internal class DataWindow : Window, IDisposable
this.Load();
}
- ///
- public void Dispose() => this.modules.OfType().AggregateToDisposable().Dispose();
-
///
public override void OnOpen()
{
diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/GamePrebakedFontsTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/GamePrebakedFontsTestWidget.cs
deleted file mode 100644
index dba293e8b..000000000
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/GamePrebakedFontsTestWidget.cs
+++ /dev/null
@@ -1,213 +0,0 @@
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Linq;
-using System.Text;
-
-using Dalamud.Interface.GameFonts;
-using Dalamud.Interface.ManagedFontAtlas;
-using Dalamud.Interface.ManagedFontAtlas.Internals;
-using Dalamud.Interface.Utility;
-using Dalamud.Utility;
-
-using ImGuiNET;
-
-namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
-
-///
-/// Widget for testing game prebaked fonts.
-///
-internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable
-{
- private ImVectorWrapper testStringBuffer;
- private IFontAtlas? privateAtlas;
- private IReadOnlyDictionary Handle)[]>? fontHandles;
- private bool useGlobalScale;
- private bool useWordWrap;
- private bool useItalic;
- private bool useBold;
- private bool useMinimumBuild;
-
- ///
- public string[]? CommandShortcuts { get; init; }
-
- ///
- public string DisplayName { get; init; } = "Game Prebaked Fonts";
-
- ///
- public bool Ready { get; set; }
-
- ///
- public void Load() => this.Ready = true;
-
- ///
- public unsafe void Draw()
- {
- ImGui.AlignTextToFramePadding();
- fixed (byte* labelPtr = "Global Scale"u8)
- {
- var v = (byte)(this.useGlobalScale ? 1 : 0);
- if (ImGuiNative.igCheckbox(labelPtr, &v) != 0)
- {
- this.useGlobalScale = v != 0;
- this.ClearAtlas();
- }
- }
-
- ImGui.SameLine();
- fixed (byte* labelPtr = "Word Wrap"u8)
- {
- var v = (byte)(this.useWordWrap ? 1 : 0);
- if (ImGuiNative.igCheckbox(labelPtr, &v) != 0)
- this.useWordWrap = v != 0;
- }
-
- ImGui.SameLine();
- fixed (byte* labelPtr = "Italic"u8)
- {
- var v = (byte)(this.useItalic ? 1 : 0);
- if (ImGuiNative.igCheckbox(labelPtr, &v) != 0)
- {
- this.useItalic = v != 0;
- this.ClearAtlas();
- }
- }
-
- ImGui.SameLine();
- fixed (byte* labelPtr = "Bold"u8)
- {
- var v = (byte)(this.useBold ? 1 : 0);
- if (ImGuiNative.igCheckbox(labelPtr, &v) != 0)
- {
- this.useBold = v != 0;
- this.ClearAtlas();
- }
- }
-
- ImGui.SameLine();
- fixed (byte* labelPtr = "Minimum Range"u8)
- {
- var v = (byte)(this.useMinimumBuild ? 1 : 0);
- if (ImGuiNative.igCheckbox(labelPtr, &v) != 0)
- {
- this.useMinimumBuild = v != 0;
- this.ClearAtlas();
- }
- }
-
- ImGui.SameLine();
- if (ImGui.Button("Reset Text") || this.testStringBuffer.IsDisposed)
- {
- this.testStringBuffer.Dispose();
- this.testStringBuffer = ImVectorWrapper.CreateFromSpan(
- "(Game)-[Font] {Test}. 0123456789!! <氣気气きキ기>。"u8,
- minCapacity: 1024);
- }
-
- fixed (byte* labelPtr = "Test Input"u8)
- {
- if (ImGuiNative.igInputTextMultiline(
- labelPtr,
- this.testStringBuffer.Data,
- (uint)this.testStringBuffer.Capacity,
- new(ImGui.GetContentRegionAvail().X, 32 * ImGuiHelpers.GlobalScale),
- 0,
- null,
- null) != 0)
- {
- var len = this.testStringBuffer.StorageSpan.IndexOf((byte)0);
- if (len + 4 >= this.testStringBuffer.Capacity)
- this.testStringBuffer.EnsureCapacityExponential(len + 4);
- if (len < this.testStringBuffer.Capacity)
- {
- this.testStringBuffer.LengthUnsafe = len;
- this.testStringBuffer.StorageSpan[len] = default;
- }
-
- if (this.useMinimumBuild)
- _ = this.privateAtlas?.BuildFontsAsync();
- }
- }
-
- this.privateAtlas ??=
- Service.Get().CreateFontAtlas(
- nameof(GamePrebakedFontsTestWidget),
- FontAtlasAutoRebuildMode.Async,
- this.useGlobalScale);
- this.fontHandles ??=
- Enum.GetValues()
- .Where(x => x.GetAttribute() is not null)
- .Select(x => new GameFontStyle(x) { Italic = this.useItalic, Bold = this.useBold })
- .GroupBy(x => x.Family)
- .ToImmutableDictionary(
- x => x.Key,
- x => x.Select(
- y => (y, new Lazy(
- () => this.useMinimumBuild
- ? this.privateAtlas.NewDelegateFontHandle(
- e =>
- e.OnPreBuild(
- tk => tk.AddGameGlyphs(
- y,
- Encoding.UTF8.GetString(
- this.testStringBuffer.DataSpan).ToGlyphRange(),
- default)))
- : this.privateAtlas.NewGameFontHandle(y))))
- .ToArray());
-
- var offsetX = ImGui.CalcTextSize("99.9pt").X + (ImGui.GetStyle().FramePadding.X * 2);
- foreach (var (family, items) in this.fontHandles)
- {
- if (!ImGui.CollapsingHeader($"{family} Family"))
- continue;
-
- foreach (var (gfs, handle) in items)
- {
- ImGui.TextUnformatted($"{gfs.SizePt}pt");
- ImGui.SameLine(offsetX);
- ImGuiNative.igPushTextWrapPos(this.useWordWrap ? 0f : -1f);
- try
- {
- if (handle.Value.LoadException is { } exc)
- {
- ImGui.TextUnformatted(exc.ToString());
- }
- else if (!handle.Value.Available)
- {
- fixed (byte* labelPtr = "Loading..."u8)
- ImGuiNative.igTextUnformatted(labelPtr, labelPtr + 8 + ((Environment.TickCount / 200) % 3));
- }
- else
- {
- if (!this.useGlobalScale)
- ImGuiNative.igSetWindowFontScale(1 / ImGuiHelpers.GlobalScale);
- using var pushPop = handle.Value.Push();
- ImGuiNative.igTextUnformatted(
- this.testStringBuffer.Data,
- this.testStringBuffer.Data + this.testStringBuffer.Length);
- }
- }
- finally
- {
- ImGuiNative.igPopTextWrapPos();
- ImGuiNative.igSetWindowFontScale(1);
- }
- }
- }
- }
-
- ///
- public void Dispose()
- {
- this.ClearAtlas();
- this.testStringBuffer.Dispose();
- }
-
- private void ClearAtlas()
- {
- this.fontHandles?.Values.SelectMany(x => x.Where(y => y.Handle.IsValueCreated).Select(y => y.Handle.Value))
- .AggregateToDisposable().Dispose();
- this.fontHandles = null;
- this.privateAtlas?.Dispose();
- this.privateAtlas = null;
- }
-}
diff --git a/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs b/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs
index 027e1a571..7d4489f8d 100644
--- a/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs
@@ -5,10 +5,10 @@ using CheapLoc;
using Dalamud.Configuration.Internal;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Internal.Windows.Settings.Tabs;
-using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
+using Dalamud.Plugin.Internal;
using Dalamud.Utility;
using ImGuiNET;
@@ -19,7 +19,14 @@ namespace Dalamud.Interface.Internal.Windows.Settings;
///
internal class SettingsWindow : Window
{
- private SettingsTab[]? tabs;
+ private readonly SettingsTab[] tabs =
+ {
+ new SettingsTabGeneral(),
+ new SettingsTabLook(),
+ new SettingsTabDtr(),
+ new SettingsTabExperimental(),
+ new SettingsTabAbout(),
+ };
private string searchInput = string.Empty;
@@ -42,15 +49,6 @@ internal class SettingsWindow : Window
///
public override void OnOpen()
{
- this.tabs ??= new SettingsTab[]
- {
- new SettingsTabGeneral(),
- new SettingsTabLook(),
- new SettingsTabDtr(),
- new SettingsTabExperimental(),
- new SettingsTabAbout(),
- };
-
foreach (var settingsTab in this.tabs)
{
settingsTab.Load();
@@ -66,12 +64,15 @@ internal class SettingsWindow : Window
{
var configuration = Service.Get();
var interfaceManager = Service.Get();
- var fontAtlasFactory = Service.Get();
- var rebuildFont = fontAtlasFactory.UseAxis != configuration.UseAxisFontsFromGame;
+ var rebuildFont =
+ ImGui.GetIO().FontGlobalScale != configuration.GlobalUiScale ||
+ interfaceManager.FontGamma != configuration.FontGammaLevel ||
+ interfaceManager.UseAxis != configuration.UseAxisFontsFromGame;
ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale;
- fontAtlasFactory.UseAxisOverride = null;
+ interfaceManager.FontGammaOverride = null;
+ interfaceManager.UseAxisOverride = null;
if (rebuildFont)
interfaceManager.RebuildFonts();
diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs
index 8714fd666..5b6f6b02f 100644
--- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs
+++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs
@@ -1,13 +1,13 @@
-using System.Diagnostics;
+using System;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
+using System.IO;
using System.Linq;
using System.Numerics;
using CheapLoc;
using Dalamud.Game.Gui;
using Dalamud.Interface.GameFonts;
-using Dalamud.Interface.ManagedFontAtlas;
-using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin.Internal;
@@ -15,6 +15,7 @@ using Dalamud.Storage.Assets;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using ImGuiNET;
+using ImGuiScene;
namespace Dalamud.Interface.Internal.Windows.Settings.Tabs;
@@ -172,21 +173,16 @@ Contribute at: https://github.com/goatcorp/Dalamud
";
private readonly Stopwatch creditsThrottler;
- private readonly IFontAtlas privateAtlas;
private string creditsText;
private bool resetNow = false;
private IDalamudTextureWrap? logoTexture;
- private IFontHandle? thankYouFont;
+ private GameFontHandle? thankYouFont;
public SettingsTabAbout()
{
this.creditsThrottler = new();
-
- this.privateAtlas = Service
- .Get()
- .CreateFontAtlas(nameof(SettingsTabAbout), FontAtlasAutoRebuildMode.Async);
}
public override SettingsEntry[] Entries { get; } = { };
@@ -211,7 +207,11 @@ Contribute at: https://github.com/goatcorp/Dalamud
this.creditsThrottler.Restart();
- this.thankYouFont ??= this.privateAtlas.NewGameFontHandle(new(GameFontFamilyAndSize.TrumpGothic34));
+ if (this.thankYouFont == null)
+ {
+ var gfm = Service.Get();
+ this.thankYouFont = gfm.NewFontRef(new GameFontStyle(GameFontFamilyAndSize.TrumpGothic34));
+ }
this.resetNow = true;
@@ -269,12 +269,14 @@ Contribute at: https://github.com/goatcorp/Dalamud
if (this.thankYouFont != null)
{
- using var fontPush = this.thankYouFont.Push();
+ ImGui.PushFont(this.thankYouFont.ImFont);
var thankYouLenX = ImGui.CalcTextSize(ThankYouText).X;
ImGui.Dummy(new Vector2((windowX / 2) - (thankYouLenX / 2), 0f));
ImGui.SameLine();
ImGui.TextUnformatted(ThankYouText);
+
+ ImGui.PopFont();
}
ImGuiHelpers.ScaledDummy(0, windowSize.Y + 50f);
@@ -303,5 +305,9 @@ Contribute at: https://github.com/goatcorp/Dalamud
///
/// Disposes of managed and unmanaged resources.
///
- public override void Dispose() => this.privateAtlas.Dispose();
+ public override void Dispose()
+ {
+ this.logoTexture?.Dispose();
+ this.thankYouFont?.Dispose();
+ }
}
diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs
index 5293e13c4..02e8ce789 100644
--- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs
+++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs
@@ -1,14 +1,12 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
-using System.Text;
using CheapLoc;
using Dalamud.Configuration.Internal;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Internal.Windows.PluginInstaller;
using Dalamud.Interface.Internal.Windows.Settings.Widgets;
-using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Interface.Utility;
using Dalamud.Utility;
using ImGuiNET;
@@ -30,6 +28,7 @@ public class SettingsTabLook : SettingsTab
};
private float globalUiScale;
+ private float fontGamma;
public override SettingsEntry[] Entries { get; } =
{
@@ -42,8 +41,9 @@ public class SettingsTabLook : SettingsTab
(v, c) => c.UseAxisFontsFromGame = v,
v =>
{
- Service.Get().UseAxisOverride = v;
- Service.Get().RebuildFonts();
+ var im = Service.Get();
+ im.UseAxisOverride = v;
+ im.RebuildFonts();
}),
new GapSettingsEntry(5, true),
@@ -145,7 +145,6 @@ public class SettingsTabLook : SettingsTab
public override void Draw()
{
var interfaceManager = Service.Get();
- var fontBuildTask = interfaceManager.FontBuildTask;
ImGui.AlignTextToFramePadding();
ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global Font Scale"));
@@ -165,19 +164,6 @@ public class SettingsTabLook : SettingsTab
}
}
- if (!fontBuildTask.IsCompleted)
- {
- ImGui.SameLine();
- var buildingFonts = Loc.Localize("DalamudSettingsFontBuildInProgressWithEndingThreeDots", "Building fonts...");
- unsafe
- {
- var len = Encoding.UTF8.GetByteCount(buildingFonts);
- var p = stackalloc byte[len];
- Encoding.UTF8.GetBytes(buildingFonts, new(p, len));
- ImGuiNative.igTextUnformatted(p, (p + len + ((Environment.TickCount / 200) % 3)) - 2);
- }
- }
-
var globalUiScaleInPt = 12f * this.globalUiScale;
if (ImGui.DragFloat("##DalamudSettingsGlobalUiScaleDrag", ref globalUiScaleInPt, 0.1f, 9.6f, 36f, "%.1fpt", ImGuiSliderFlags.AlwaysClamp))
{
@@ -188,25 +174,33 @@ public class SettingsTabLook : SettingsTab
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsGlobalUiScaleHint", "Scale text in all XIVLauncher UI elements - this is useful for 4K displays."));
- if (fontBuildTask.IsFaulted || fontBuildTask.IsCanceled)
+ ImGuiHelpers.ScaledDummy(5);
+
+ ImGui.AlignTextToFramePadding();
+ ImGui.Text(Loc.Localize("DalamudSettingsFontGamma", "Font Gamma"));
+ ImGui.SameLine();
+ if (ImGui.Button(Loc.Localize("DalamudSettingsIndividualConfigResetToDefaultValue", "Reset") + "##DalamudSettingsFontGammaReset"))
{
- ImGui.TextColored(
- ImGuiColors.DalamudRed,
- Loc.Localize("DalamudSettingsFontBuildFaulted", "Failed to load fonts as requested."));
- if (fontBuildTask.Exception is not null
- && ImGui.CollapsingHeader("##DalamudSetingsFontBuildFaultReason"))
- {
- foreach (var e in fontBuildTask.Exception.InnerExceptions)
- ImGui.TextUnformatted(e.ToString());
- }
+ this.fontGamma = 1.4f;
+ interfaceManager.FontGammaOverride = this.fontGamma;
+ interfaceManager.RebuildFonts();
}
+ if (ImGui.DragFloat("##DalamudSettingsFontGammaDrag", ref this.fontGamma, 0.005f, 0.3f, 3f, "%.2f", ImGuiSliderFlags.AlwaysClamp))
+ {
+ interfaceManager.FontGammaOverride = this.fontGamma;
+ interfaceManager.RebuildFonts();
+ }
+
+ ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsFontGammaHint", "Changes the thickness of text."));
+
base.Draw();
}
public override void Load()
{
this.globalUiScale = Service.Get().GlobalUiScale;
+ this.fontGamma = Service.Get().FontGammaLevel;
base.Load();
}
diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs
index 9c385a99c..42bca89ff 100644
--- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs
@@ -7,14 +7,11 @@ using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.Gui;
using Dalamud.Interface.Animation.EasingFunctions;
-using Dalamud.Interface.ManagedFontAtlas;
-using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services;
using Dalamud.Storage.Assets;
-using Dalamud.Utility;
using ImGuiNET;
@@ -30,17 +27,16 @@ internal class TitleScreenMenuWindow : Window, IDisposable
private readonly ClientState clientState;
private readonly DalamudConfiguration configuration;
+ private readonly Framework framework;
private readonly GameGui gameGui;
private readonly TitleScreenMenu titleScreenMenu;
- private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new();
- private readonly IFontAtlas privateAtlas;
- private readonly Lazy myFontHandle;
private readonly Lazy shadeTexture;
private readonly Dictionary shadeEasings = new();
private readonly Dictionary moveEasings = new();
private readonly Dictionary logoEasings = new();
+ private readonly Dictionary specialGlyphRequests = new();
private InOutCubic? fadeOutEasing;
@@ -52,7 +48,6 @@ internal class TitleScreenMenuWindow : Window, IDisposable
/// An instance of .
/// An instance of .
/// An instance of .
- /// An instance of .
/// An instance of .
/// An instance of .
/// An instance of .
@@ -60,7 +55,6 @@ internal class TitleScreenMenuWindow : Window, IDisposable
ClientState clientState,
DalamudConfiguration configuration,
DalamudAssetManager dalamudAssetManager,
- FontAtlasFactory fontAtlasFactory,
Framework framework,
GameGui gameGui,
TitleScreenMenu titleScreenMenu)
@@ -71,6 +65,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
{
this.clientState = clientState;
this.configuration = configuration;
+ this.framework = framework;
this.gameGui = gameGui;
this.titleScreenMenu = titleScreenMenu;
@@ -82,25 +77,9 @@ internal class TitleScreenMenuWindow : Window, IDisposable
this.PositionCondition = ImGuiCond.Always;
this.RespectCloseHotkey = false;
- this.shadeTexture = new(() => dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.TitleScreenMenuShade));
- this.privateAtlas = fontAtlasFactory.CreateFontAtlas(this.WindowName, FontAtlasAutoRebuildMode.Async);
- this.scopedFinalizer.Add(this.privateAtlas);
-
- this.myFontHandle = new(
- () => this.scopedFinalizer.Add(
- this.privateAtlas.NewDelegateFontHandle(
- e => e.OnPreBuild(
- toolkit => toolkit.AddDalamudDefaultFont(
- TargetFontSizePx,
- titleScreenMenu.Entries.SelectMany(x => x.Name).ToGlyphRange())))));
-
- titleScreenMenu.EntryListChange += this.TitleScreenMenuEntryListChange;
- this.scopedFinalizer.Add(() => titleScreenMenu.EntryListChange -= this.TitleScreenMenuEntryListChange);
-
this.shadeTexture = new(() => dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.TitleScreenMenuShade));
framework.Update += this.FrameworkOnUpdate;
- this.scopedFinalizer.Add(() => framework.Update -= this.FrameworkOnUpdate);
}
private enum State
@@ -115,9 +94,6 @@ internal class TitleScreenMenuWindow : Window, IDisposable
///
public bool AllowDrawing { get; set; } = true;
- ///
- public void Dispose() => this.scopedFinalizer.Dispose();
-
///
public override void PreDraw()
{
@@ -133,6 +109,12 @@ internal class TitleScreenMenuWindow : Window, IDisposable
base.PostDraw();
}
+ ///
+ public void Dispose()
+ {
+ this.framework.Update -= this.FrameworkOnUpdate;
+ }
+
///
public override void Draw()
{
@@ -264,12 +246,33 @@ internal class TitleScreenMenuWindow : Window, IDisposable
break;
}
}
+
+ var srcText = entries.Select(e => e.Name).ToHashSet();
+ var keys = this.specialGlyphRequests.Keys.ToHashSet();
+ keys.RemoveWhere(x => srcText.Contains(x));
+ foreach (var key in keys)
+ {
+ this.specialGlyphRequests[key].Dispose();
+ this.specialGlyphRequests.Remove(key);
+ }
}
private bool DrawEntry(
TitleScreenMenuEntry entry, bool inhibitFadeout, bool showText, bool isFirst, bool overrideAlpha, bool interactable)
{
- using var fontScopeDispose = this.myFontHandle.Value.Push();
+ InterfaceManager.SpecialGlyphRequest fontHandle;
+ if (this.specialGlyphRequests.TryGetValue(entry.Name, out fontHandle) && fontHandle.Size != TargetFontSizePx)
+ {
+ fontHandle.Dispose();
+ this.specialGlyphRequests.Remove(entry.Name);
+ fontHandle = null;
+ }
+
+ if (fontHandle == null)
+ this.specialGlyphRequests[entry.Name] = fontHandle = Service.Get().NewFontSizeRef(TargetFontSizePx, entry.Name);
+
+ ImGui.PushFont(fontHandle.Font);
+ ImGui.SetWindowFontScale(TargetFontSizePx / fontHandle.Size);
var scale = ImGui.GetIO().FontGlobalScale;
@@ -380,6 +383,8 @@ internal class TitleScreenMenuWindow : Window, IDisposable
initialCursor.Y += entry.Texture.Height * scale;
ImGui.SetCursorPos(initialCursor);
+ ImGui.PopFont();
+
return isHover;
}
@@ -396,6 +401,4 @@ internal class TitleScreenMenuWindow : Window, IDisposable
if (charaMake != IntPtr.Zero || charaSelect != IntPtr.Zero || titleDcWorldMap != IntPtr.Zero)
this.IsOpen = false;
}
-
- private void TitleScreenMenuEntryListChange() => this.privateAtlas.BuildFontsAsync();
}
diff --git a/Dalamud/Interface/ManagedFontAtlas/FontAtlasAutoRebuildMode.cs b/Dalamud/Interface/ManagedFontAtlas/FontAtlasAutoRebuildMode.cs
deleted file mode 100644
index 50e591390..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/FontAtlasAutoRebuildMode.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace Dalamud.Interface.ManagedFontAtlas;
-
-///
-/// How to rebuild .
-///
-public enum FontAtlasAutoRebuildMode
-{
- ///
- /// Do not rebuild.
- ///
- Disable,
-
- ///
- /// Rebuild on new frame.
- ///
- OnNewFrame,
-
- ///
- /// Rebuild asynchronously.
- ///
- Async,
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildStep.cs b/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildStep.cs
deleted file mode 100644
index 345ab729d..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildStep.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using ImGuiNET;
-
-namespace Dalamud.Interface.ManagedFontAtlas;
-
-///
-/// Build step for .
-///
-public enum FontAtlasBuildStep
-{
- ///
- /// An invalid value. This should never be passed through event callbacks.
- ///
- Invalid,
-
- ///
- /// Called before calling .
- /// Expect to be passed.
- ///
- PreBuild,
-
- ///
- /// Called after calling .
- /// Expect to be passed.
- ///
- /// This callback is not guaranteed to happen after ,
- /// but it will never happen on its own.
- ///
- PostBuild,
-
- ///
- /// Called after promoting staging font atlas to the actual atlas for .
- /// Expect to be passed.
- ///
- /// This callback is not guaranteed to happen after ,
- /// but it will never happen on its own.
- ///
- PostPromotion,
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildStepDelegate.cs b/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildStepDelegate.cs
deleted file mode 100644
index 4f5b34061..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildStepDelegate.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace Dalamud.Interface.ManagedFontAtlas;
-
-///
-/// Delegate to be called when a font needs to be built.
-///
-/// A toolkit that may help you for font building steps.
-///
-/// An implementation of may implement all of
-/// , , and
-/// .
-/// Either use to identify the build step, or use
-/// , ,
-/// and for routing.
-///
-public delegate void FontAtlasBuildStepDelegate(IFontAtlasBuildToolkit toolkit);
diff --git a/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildToolkitUtilities.cs b/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildToolkitUtilities.cs
deleted file mode 100644
index 586887a3b..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildToolkitUtilities.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-
-using Dalamud.Interface.Utility;
-
-using ImGuiNET;
-
-namespace Dalamud.Interface.ManagedFontAtlas;
-
-///
-/// Convenience function for building fonts through .
-///
-public static class FontAtlasBuildToolkitUtilities
-{
- ///
- /// Compiles given s into an array of containing ImGui glyph ranges.
- ///
- /// The chars.
- /// Add fallback codepoints to the range.
- /// Add ellipsis codepoints to the range.
- /// The compiled range.
- 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);
- }
-
- ///
- /// Compiles given s into an array of containing ImGui glyph ranges.
- ///
- /// The chars.
- /// Add fallback codepoints to the range.
- /// Add ellipsis codepoints to the range.
- /// The compiled range.
- 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);
- }
-
- ///
- /// Compiles given string into an array of containing ImGui glyph ranges.
- ///
- /// The string.
- /// Add fallback codepoints to the range.
- /// Add ellipsis codepoints to the range.
- /// The compiled range.
- public static ushort[] ToGlyphRange(
- this string @string,
- bool addFallbackCodepoints = true,
- bool addEllipsisCodepoints = true) =>
- @string.AsSpan().ToGlyphRange(addFallbackCodepoints, addEllipsisCodepoints);
-
- ///
- /// Finds the corresponding in
- /// . that corresponds to the
- /// specified font .
- ///
- /// The toolkit.
- /// The font.
- /// The relevant config pointer, or empty config pointer if not found.
- public static unsafe ImFontConfigPtr FindConfigPtr(this IFontAtlasBuildToolkit toolkit, ImFontPtr fontPtr)
- {
- foreach (ref var c in toolkit.NewImAtlas.ConfigDataWrapped().DataSpan)
- {
- if (c.DstFont == fontPtr.NativePtr)
- return new((nint)Unsafe.AsPointer(ref c));
- }
-
- return default;
- }
-
- ///
- /// Invokes
- /// if of
- /// is .
- ///
- /// The toolkit.
- /// The action.
- /// This, for method chaining.
- public static IFontAtlasBuildToolkit OnPreBuild(
- this IFontAtlasBuildToolkit toolkit,
- Action action)
- {
- if (toolkit.BuildStep is FontAtlasBuildStep.PreBuild)
- action.Invoke((IFontAtlasBuildToolkitPreBuild)toolkit);
- return toolkit;
- }
-
- ///
- /// Invokes
- /// if of
- /// is .
- ///
- /// The toolkit.
- /// The action.
- /// toolkit, for method chaining.
- public static IFontAtlasBuildToolkit OnPostBuild(
- this IFontAtlasBuildToolkit toolkit,
- Action action)
- {
- if (toolkit.BuildStep is FontAtlasBuildStep.PostBuild)
- action.Invoke((IFontAtlasBuildToolkitPostBuild)toolkit);
- return toolkit;
- }
-
- ///
- /// Invokes
- /// if of
- /// is .
- ///
- /// The toolkit.
- /// The action.
- /// toolkit, for method chaining.
- public static IFontAtlasBuildToolkit OnPostPromotion(
- this IFontAtlasBuildToolkit toolkit,
- Action action)
- {
- if (toolkit.BuildStep is FontAtlasBuildStep.PostPromotion)
- action.Invoke((IFontAtlasBuildToolkitPostPromotion)toolkit);
- return toolkit;
- }
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontAtlas.cs b/Dalamud/Interface/ManagedFontAtlas/IFontAtlas.cs
deleted file mode 100644
index ec3e66e9a..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/IFontAtlas.cs
+++ /dev/null
@@ -1,141 +0,0 @@
-using System.Threading.Tasks;
-
-using Dalamud.Interface.GameFonts;
-using Dalamud.Interface.Utility;
-
-using ImGuiNET;
-
-namespace Dalamud.Interface.ManagedFontAtlas;
-
-///
-/// Wrapper for .
-///
-public interface IFontAtlas : IDisposable
-{
- ///
- /// Event to be called on build step changes.
- /// is meaningless for this event.
- ///
- event FontAtlasBuildStepDelegate? BuildStepChange;
-
- ///
- /// Event fired when a font rebuild operation is recommended.
- /// This event will be invoked from the main thread.
- ///
- /// Reasons for the event include changes in and
- /// initialization of new associated font handles.
- ///
- ///
- /// You should call or
- /// if is not set to true.
- /// Avoid calling here; it will block the main thread.
- ///
- event Action? RebuildRecommend;
-
- ///
- /// Gets the name of the atlas. For logging and debugging purposes.
- ///
- string Name { get; }
-
- ///
- /// Gets a value how the atlas should be rebuilt when the relevant Dalamud Configuration changes.
- ///
- FontAtlasAutoRebuildMode AutoRebuildMode { get; }
-
- ///
- /// Gets the font atlas. Might be empty.
- ///
- ImFontAtlasPtr ImAtlas { get; }
-
- ///
- /// Gets the task that represents the current font rebuild state.
- ///
- Task BuildTask { get; }
-
- ///
- /// Gets a value indicating whether there exists any built atlas, regardless of .
- ///
- bool HasBuiltAtlas { get; }
-
- ///
- /// Gets a value indicating whether this font atlas is under the effect of global scale.
- ///
- bool IsGlobalScaled { get; }
-
- ///
- /// Suppresses automatically rebuilding fonts for the scope.
- ///
- /// An instance of that will release the suppression.
- ///
- /// Use when you will be creating multiple new handles, and want rebuild to trigger only when you're done doing so.
- /// This function will effectively do nothing, if is set to
- /// .
- ///
- ///
- ///
- /// using (atlas.SuppressBuild()) {
- /// this.font1 = atlas.NewGameFontHandle(...);
- /// this.font2 = atlas.NewDelegateFontHandle(...);
- /// }
- ///
- ///
- public IDisposable SuppressAutoRebuild();
-
- ///
- /// Creates a new from game's built-in fonts.
- ///
- /// Font to use.
- /// Handle to a font that may or may not be ready yet.
- public IFontHandle NewGameFontHandle(GameFontStyle style);
-
- ///
- /// Creates a new IFontHandle using your own callbacks.
- ///
- /// Callback for .
- /// Handle to a font that may or may not be ready yet.
- ///
- /// On initialization:
- ///
- /// this.fontHandle = atlas.NewDelegateFontHandle(e => e.OnPreBuild(tk => {
- /// var config = new SafeFontConfig { SizePx = 16 };
- /// config.MergeFont = tk.AddFontFromFile(@"C:\Windows\Fonts\comic.ttf", config);
- /// tk.AddGameSymbol(config);
- /// tk.AddExtraGlyphsForDalamudLanguage(config);
- /// // optionally do the following if you have to add more than one font here,
- /// // to specify which font added during this delegate is the final font to use.
- /// tk.Font = config.MergeFont;
- /// }));
- /// // or
- /// this.fontHandle = atlas.NewDelegateFontHandle(e => e.OnPreBuild(tk => tk.AddDalamudDefaultFont(36)));
- ///
- ///
- /// On use:
- ///
- /// using (this.fontHandle.Push())
- /// ImGui.TextUnformatted("Example");
- ///
- ///
- public IFontHandle NewDelegateFontHandle(FontAtlasBuildStepDelegate buildStepDelegate);
-
- ///
- /// Queues rebuilding fonts, on the main thread.
- /// Note that would not necessarily get changed from calling this function.
- ///
- /// If is .
- void BuildFontsOnNextFrame();
-
- ///
- /// Rebuilds fonts immediately, on the current thread.
- /// Even the callback for will be called on the same thread.
- ///
- /// If is .
- void BuildFontsImmediately();
-
- ///
- /// Rebuilds fonts asynchronously, on any thread.
- ///
- /// Call on the main thread.
- /// The task.
- /// If is .
- Task BuildFontsAsync(bool callPostPromotionOnMainThread = true);
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkit.cs b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkit.cs
deleted file mode 100644
index 4b016bbb2..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkit.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using System.Runtime.InteropServices;
-
-using Dalamud.Interface.Utility;
-
-using ImGuiNET;
-
-namespace Dalamud.Interface.ManagedFontAtlas;
-
-///
-/// Common stuff for and .
-///
-public interface IFontAtlasBuildToolkit
-{
- ///
- /// Gets or sets the font relevant to the call.
- ///
- ImFontPtr Font { get; set; }
-
- ///
- /// Gets the current scale this font atlas is being built with.
- ///
- float Scale { get; }
-
- ///
- /// Gets a value indicating whether the current build operation is asynchronous.
- ///
- bool IsAsyncBuildOperation { get; }
-
- ///
- /// Gets the current build step.
- ///
- FontAtlasBuildStep BuildStep { get; }
-
- ///
- /// Gets the font atlas being built.
- ///
- ImFontAtlasPtr NewImAtlas { get; }
-
- ///
- /// Gets the wrapper for of .
- /// This does not need to be disposed. Calling does nothing.-
- ///
- /// Modification of this vector may result in undefined behaviors.
- ///
- ImVectorWrapper Fonts { get; }
-
- ///
- /// Queues an item to be disposed after the native atlas gets disposed, successful or not.
- ///
- /// Disposable type.
- /// The disposable.
- /// The same .
- T DisposeWithAtlas(T disposable) where T : IDisposable;
-
- ///
- /// Queues an item to be disposed after the native atlas gets disposed, successful or not.
- ///
- /// The gc handle.
- /// The same .
- GCHandle DisposeWithAtlas(GCHandle gcHandle);
-
- ///
- /// Queues an item to be disposed after the native atlas gets disposed, successful or not.
- ///
- /// The action to run on dispose.
- void DisposeWithAtlas(Action action);
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostBuild.cs b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostBuild.cs
deleted file mode 100644
index 3c14197e0..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostBuild.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Dalamud.Interface.Internal;
-
-using ImGuiNET;
-
-namespace Dalamud.Interface.ManagedFontAtlas;
-
-///
-/// Toolkit for use when the build state is .
-///
-public interface IFontAtlasBuildToolkitPostBuild : IFontAtlasBuildToolkit
-{
- ///
- /// Gets whether global scaling is ignored for the given font.
- ///
- /// The font.
- /// True if ignored.
- bool IsGlobalScaleIgnored(ImFontPtr fontPtr);
-
- ///
- /// Stores a texture to be managed with the atlas.
- ///
- /// The texture wrap.
- /// Dispose the wrap on error.
- /// The texture index.
- int StoreTexture(IDalamudTextureWrap textureWrap, bool disposeOnError);
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostPromotion.cs b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostPromotion.cs
deleted file mode 100644
index 8c3c91624..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostPromotion.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using ImGuiNET;
-
-namespace Dalamud.Interface.ManagedFontAtlas;
-
-///
-/// Toolkit for use when the build state is .
-///
-public interface IFontAtlasBuildToolkitPostPromotion : IFontAtlasBuildToolkit
-{
- ///
- /// Copies glyphs across fonts, in a safer way.
- /// If the font does not belong to the current atlas, this function is a no-op.
- ///
- /// Source font.
- /// Target font.
- /// Whether to copy missing glyphs only.
- /// Whether to call target.BuildLookupTable().
- /// Low codepoint range to copy.
- /// High codepoing range to copy.
- void CopyGlyphsAcrossFonts(
- ImFontPtr source,
- ImFontPtr target,
- bool missingOnly,
- bool rebuildLookupTable = true,
- char rangeLow = ' ',
- char rangeHigh = '\uFFFE');
-
- ///
- /// Calls , with some fixups.
- ///
- /// The font.
- void BuildLookupTable(ImFontPtr font);
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPreBuild.cs b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPreBuild.cs
deleted file mode 100644
index cb8a27a54..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPreBuild.cs
+++ /dev/null
@@ -1,186 +0,0 @@
-using System.IO;
-using System.Runtime.InteropServices;
-
-using Dalamud.Interface.GameFonts;
-using Dalamud.Interface.Utility;
-
-using ImGuiNET;
-
-namespace Dalamud.Interface.ManagedFontAtlas;
-
-///
-/// Toolkit for use when the build state is .
-///
-/// After returns,
-/// either must be set,
-/// or at least one font must have been added to the atlas using one of AddFont... functions.
-///
-public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit
-{
- ///
- /// Queues an item to be disposed after the whole build process gets complete, successful or not.
- ///
- /// Disposable type.
- /// The disposable.
- /// The same .
- T DisposeAfterBuild(T disposable) where T : IDisposable;
-
- ///
- /// Queues an item to be disposed after the whole build process gets complete, successful or not.
- ///
- /// The gc handle.
- /// The same .
- GCHandle DisposeAfterBuild(GCHandle gcHandle);
-
- ///
- /// Queues an item to be disposed after the whole build process gets complete, successful or not.
- ///
- /// The action to run on dispose.
- void DisposeAfterBuild(Action action);
-
- ///
- /// Excludes given font from global scaling.
- ///
- /// The font.
- /// Same with .
- ImFontPtr IgnoreGlobalScale(ImFontPtr fontPtr);
-
- ///
- /// Gets whether global scaling is ignored for the given font.
- ///
- /// The font.
- /// True if ignored.
- bool IsGlobalScaleIgnored(ImFontPtr fontPtr);
-
- ///
- /// Adds a font from memory region allocated using .
- /// It WILL crash if you try to use a memory pointer allocated in some other way.
- ///
- /// Do NOT call on the once this function has
- /// been called, unless is set and the function has thrown an error.
- ///
- ///
- /// Memory address for the data allocated using .
- /// The size of the font file..
- /// The font config.
- /// Free if an exception happens.
- /// A debug tag.
- /// The newly added font.
- unsafe ImFontPtr AddFontFromImGuiHeapAllocatedMemory(
- nint dataPointer,
- int dataSize,
- in SafeFontConfig fontConfig,
- bool freeOnException,
- string debugTag)
- => this.AddFontFromImGuiHeapAllocatedMemory(
- (void*)dataPointer,
- dataSize,
- fontConfig,
- freeOnException,
- debugTag);
-
- ///
- /// Adds a font from memory region allocated using .
- /// It WILL crash if you try to use a memory pointer allocated in some other way.
- /// Do NOT call on the once this
- /// function has been called.
- ///
- /// Memory address for the data allocated using .
- /// The size of the font file..
- /// The font config.
- /// Free if an exception happens.
- /// A debug tag.
- /// The newly added font.
- unsafe ImFontPtr AddFontFromImGuiHeapAllocatedMemory(
- void* dataPointer,
- int dataSize,
- in SafeFontConfig fontConfig,
- bool freeOnException,
- string debugTag);
-
- ///
- /// Adds a font from a file.
- ///
- /// The file path to create a new font from.
- /// The font config.
- /// The newly added font.
- ImFontPtr AddFontFromFile(string path, in SafeFontConfig fontConfig);
-
- ///
- /// Adds a font from a stream.
- ///
- /// The stream to create a new font from.
- /// The font config.
- /// Dispose when this function returns or throws.
- /// A debug tag.
- /// The newly added font.
- ImFontPtr AddFontFromStream(Stream stream, in SafeFontConfig fontConfig, bool leaveOpen, string debugTag);
-
- ///
- /// Adds a font from memory.
- ///
- /// The span to create from.
- /// The font config.
- /// A debug tag.
- /// The newly added font.
- ImFontPtr AddFontFromMemory(ReadOnlySpan span, in SafeFontConfig fontConfig, string debugTag);
-
- ///
- /// Adds the default font known to the current font atlas.
- ///
- /// Includes and .
- /// As this involves adding multiple fonts, calling this function will set
- /// as the return value of this function, if it was empty before.
- ///
- /// Font size in pixels.
- /// The glyph ranges. Use .ToGlyphRange to build.
- /// A font returned from .
- ImFontPtr AddDalamudDefaultFont(float sizePx, ushort[]? glyphRanges = null);
-
- ///
- /// Adds a font that is shipped with Dalamud.
- ///
- /// Note: if game symbols font file is requested but is unavailable,
- /// then it will take the glyphs from game's built-in fonts, and everything in
- /// will be ignored but , ,
- /// and .
- ///
- /// The font type.
- /// The font config.
- /// The added font.
- ImFontPtr AddDalamudAssetFont(DalamudAsset asset, in SafeFontConfig fontConfig);
-
- ///
- /// Same with (, ...),
- /// but using only FontAwesome icon ranges.
- /// will be ignored.
- ///
- /// The font config.
- /// The added font.
- ImFontPtr AddFontAwesomeIconFont(in SafeFontConfig fontConfig);
-
- ///
- /// Adds the game's symbols into the provided font.
- /// will be ignored.
- /// If the game symbol font file is unavailable, only will be honored.
- ///
- /// The font config.
- /// The added font.
- ImFontPtr AddGameSymbol(in SafeFontConfig fontConfig);
-
- ///
- /// Adds the game glyphs to the font.
- ///
- /// The font style.
- /// The glyph ranges.
- /// The font to merge to. If empty, then a new font will be created.
- /// The added font.
- ImFontPtr AddGameGlyphs(GameFontStyle gameFontStyle, ushort[]? glyphRanges, ImFontPtr mergeFont);
-
- ///
- /// Adds glyphs of extra languages into the provided font, depending on Dalamud Configuration.
- /// will be ignored.
- ///
- /// The font config.
- void AttachExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig);
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs b/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs
deleted file mode 100644
index 854594663..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using ImGuiNET;
-
-namespace Dalamud.Interface.ManagedFontAtlas;
-
-///
-/// Represents a reference counting handle for fonts.
-///
-public interface IFontHandle : IDisposable
-{
- ///
- /// Represents a reference counting handle for fonts. Dalamud internal use only.
- ///
- internal interface IInternal : IFontHandle
- {
- ///
- /// Gets the font.
- /// Use of this properly is safe only from the UI thread.
- /// Use if the intended purpose of this property is .
- /// Futures changes may make simple not enough.
- ///
- ImFontPtr ImFont { get; }
- }
-
- ///
- /// Gets the load exception, if it failed to load. Otherwise, it is null.
- ///
- Exception? LoadException { get; }
-
- ///
- /// Gets a value indicating whether this font is ready for use.
- /// Use directly if you want to keep the current ImGui font if the font is not ready.
- ///
- bool Available { get; }
-
- ///
- /// Pushes the current font into ImGui font stack using , if available.
- /// Use to access the current font.
- /// You may not access the font once you dispose this object.
- ///
- /// A disposable object that will call (1) on dispose.
- IDisposable Push();
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/DelegateFontHandle.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/DelegateFontHandle.cs
deleted file mode 100644
index f0ed09155..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/DelegateFontHandle.cs
+++ /dev/null
@@ -1,334 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-
-using Dalamud.Interface.Utility;
-using Dalamud.Interface.Utility.Raii;
-using Dalamud.Logging.Internal;
-
-using ImGuiNET;
-
-namespace Dalamud.Interface.ManagedFontAtlas.Internals;
-
-///
-/// A font handle representing a user-callback generated font.
-///
-internal class DelegateFontHandle : IFontHandle.IInternal
-{
- private IFontHandleManager? manager;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// An instance of .
- /// Callback for .
- public DelegateFontHandle(IFontHandleManager manager, FontAtlasBuildStepDelegate callOnBuildStepChange)
- {
- this.manager = manager;
- this.CallOnBuildStepChange = callOnBuildStepChange;
- }
-
- ///
- /// Gets the function to be called on build step changes.
- ///
- public FontAtlasBuildStepDelegate CallOnBuildStepChange { get; }
-
- ///
- public Exception? LoadException => this.ManagerNotDisposed.Substance?.GetBuildException(this);
-
- ///
- public bool Available => this.ImFont.IsNotNullAndLoaded();
-
- ///
- public ImFontPtr ImFont => this.ManagerNotDisposed.Substance?.GetFontPtr(this) ?? default;
-
- private IFontHandleManager ManagerNotDisposed =>
- this.manager ?? throw new ObjectDisposedException(nameof(GamePrebakedFontHandle));
-
- ///
- public void Dispose()
- {
- this.manager?.FreeFontHandle(this);
- this.manager = null;
- }
-
- ///
- public IDisposable Push() => ImRaii.PushFont(this.ImFont, this.Available);
-
- ///
- /// Manager for s.
- ///
- internal sealed class HandleManager : IFontHandleManager
- {
- private readonly HashSet handles = new();
- private readonly object syncRoot = new();
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The name of the owner atlas.
- public HandleManager(string atlasName) => this.Name = $"{atlasName}:{nameof(DelegateFontHandle)}:Manager";
-
- ///
- public event Action? RebuildRecommend;
-
- ///
- public string Name { get; }
-
- ///
- public IFontHandleSubstance? Substance { get; set; }
-
- ///
- public void Dispose()
- {
- lock (this.syncRoot)
- {
- this.handles.Clear();
- this.Substance?.Dispose();
- this.Substance = null;
- }
- }
-
- ///
- public IFontHandle NewFontHandle(FontAtlasBuildStepDelegate buildStepDelegate)
- {
- var key = new DelegateFontHandle(this, buildStepDelegate);
- lock (this.syncRoot)
- this.handles.Add(key);
- this.RebuildRecommend?.Invoke();
- return key;
- }
-
- ///
- public void FreeFontHandle(IFontHandle handle)
- {
- if (handle is not DelegateFontHandle cgfh)
- return;
-
- lock (this.syncRoot)
- this.handles.Remove(cgfh);
- }
-
- ///
- public IFontHandleSubstance NewSubstance()
- {
- lock (this.syncRoot)
- return new HandleSubstance(this, this.handles.ToArray());
- }
- }
-
- ///
- /// Substance from .
- ///
- internal sealed class HandleSubstance : IFontHandleSubstance
- {
- private static readonly ModuleLog Log = new($"{nameof(DelegateFontHandle)}.{nameof(HandleSubstance)}");
-
- // Not owned by this class. Do not dispose.
- private readonly DelegateFontHandle[] relevantHandles;
-
- // Owned by this class, but ImFontPtr values still do not belong to this.
- private readonly Dictionary fonts = new();
- private readonly Dictionary buildExceptions = new();
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The manager.
- /// The relevant handles.
- public HandleSubstance(IFontHandleManager manager, DelegateFontHandle[] relevantHandles)
- {
- this.Manager = manager;
- this.relevantHandles = relevantHandles;
- }
-
- ///
- public IFontHandleManager Manager { get; }
-
- ///
- public void Dispose()
- {
- this.fonts.Clear();
- this.buildExceptions.Clear();
- }
-
- ///
- public ImFontPtr GetFontPtr(IFontHandle handle) =>
- handle is DelegateFontHandle cgfh ? this.fonts.GetValueOrDefault(cgfh) : default;
-
- ///
- public Exception? GetBuildException(IFontHandle handle) =>
- handle is DelegateFontHandle cgfh ? this.buildExceptions.GetValueOrDefault(cgfh) : default;
-
- ///
- public void OnPreBuild(IFontAtlasBuildToolkitPreBuild toolkitPreBuild)
- {
- var fontsVector = toolkitPreBuild.Fonts;
- foreach (var k in this.relevantHandles)
- {
- var fontCountPrevious = fontsVector.Length;
-
- try
- {
- toolkitPreBuild.Font = default;
- k.CallOnBuildStepChange(toolkitPreBuild);
- if (toolkitPreBuild.Font.IsNull())
- {
- if (fontCountPrevious == fontsVector.Length)
- {
- throw new InvalidOperationException(
- $"{nameof(FontAtlasBuildStepDelegate)} must either set the " +
- $"{nameof(IFontAtlasBuildToolkitPreBuild.Font)} property, or add at least one font.");
- }
-
- toolkitPreBuild.Font = fontsVector[^1];
- }
- else
- {
- var found = false;
- unsafe
- {
- for (var i = fontCountPrevious; !found && i < fontsVector.Length; i++)
- {
- if (fontsVector[i].NativePtr == toolkitPreBuild.Font.NativePtr)
- found = true;
- }
- }
-
- if (!found)
- {
- throw new InvalidOperationException(
- "The font does not exist in the atlas' font array. If you need an empty font, try" +
- "adding Noto Sans from Dalamud Assets, but using new ushort[]{ ' ', ' ', 0 } as the" +
- "glyph range.");
- }
- }
-
- if (fontsVector.Length - fontCountPrevious != 1)
- {
- Log.Warning(
- "[{name}:Substance] {n} fonts added from {delegate} PreBuild call; " +
- "Using the most recently added font. " +
- "Did you mean to use {sfd}.{sfdprop} or {ifcp}.{ifcpprop}?",
- this.Manager.Name,
- fontsVector.Length - fontCountPrevious,
- nameof(FontAtlasBuildStepDelegate),
- nameof(SafeFontConfig),
- nameof(SafeFontConfig.MergeFont),
- nameof(ImFontConfigPtr),
- nameof(ImFontConfigPtr.MergeMode));
- }
-
- for (var i = fontCountPrevious; i < fontsVector.Length; i++)
- {
- if (fontsVector[i].ValidateUnsafe() is { } ex)
- {
- throw new InvalidOperationException(
- "One of the newly added fonts seem to be pointing to an invalid memory address.",
- ex);
- }
- }
-
- // Check for duplicate entries; duplicates will result in free-after-free
- for (var i = 0; i < fontCountPrevious; i++)
- {
- for (var j = fontCountPrevious; j < fontsVector.Length; j++)
- {
- unsafe
- {
- if (fontsVector[i].NativePtr == fontsVector[j].NativePtr)
- throw new InvalidOperationException("An already added font has been added again.");
- }
- }
- }
-
- this.fonts[k] = toolkitPreBuild.Font;
- }
- catch (Exception e)
- {
- this.fonts[k] = default;
- this.buildExceptions[k] = e;
-
- Log.Error(
- e,
- "[{name}:Substance] An error has occurred while during {delegate} PreBuild call.",
- this.Manager.Name,
- nameof(FontAtlasBuildStepDelegate));
-
- // Sanitization, in a futile attempt to prevent crashes on invalid parameters
- unsafe
- {
- var distinct =
- fontsVector
- .DistinctBy(x => (nint)x.NativePtr) // Remove duplicates
- .Where(x => x.ValidateUnsafe() is null) // Remove invalid entries without freeing them
- .ToArray();
-
- // We're adding the contents back; do not destroy the contents
- fontsVector.Clear(true);
- fontsVector.AddRange(distinct.AsSpan());
- }
- }
- }
- }
-
- ///
- public void OnPreBuildCleanup(IFontAtlasBuildToolkitPreBuild toolkitPreBuild)
- {
- // irrelevant
- }
-
- ///
- public void OnPostBuild(IFontAtlasBuildToolkitPostBuild toolkitPostBuild)
- {
- foreach (var k in this.relevantHandles)
- {
- if (!this.fonts[k].IsNotNullAndLoaded())
- continue;
-
- try
- {
- toolkitPostBuild.Font = this.fonts[k];
- k.CallOnBuildStepChange.Invoke(toolkitPostBuild);
- }
- catch (Exception e)
- {
- this.fonts[k] = default;
- this.buildExceptions[k] = e;
-
- Log.Error(
- e,
- "[{name}] An error has occurred while during {delegate} PostBuild call.",
- this.Manager.Name,
- nameof(FontAtlasBuildStepDelegate));
- }
- }
- }
-
- ///
- public void OnPostPromotion(IFontAtlasBuildToolkitPostPromotion toolkitPostPromotion)
- {
- foreach (var k in this.relevantHandles)
- {
- if (!this.fonts[k].IsNotNullAndLoaded())
- continue;
-
- try
- {
- toolkitPostPromotion.Font = this.fonts[k];
- k.CallOnBuildStepChange.Invoke(toolkitPostPromotion);
- }
- catch (Exception e)
- {
- this.fonts[k] = default;
- this.buildExceptions[k] = e;
-
- Log.Error(
- e,
- "[{name}:Substance] An error has occurred while during {delegate} PostPromotion call.",
- this.Manager.Name,
- nameof(FontAtlasBuildStepDelegate));
- }
- }
- }
- }
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs
deleted file mode 100644
index e73ea7548..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs
+++ /dev/null
@@ -1,682 +0,0 @@
-using System.Buffers;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Text.Unicode;
-
-using Dalamud.Configuration.Internal;
-using Dalamud.Interface.GameFonts;
-using Dalamud.Interface.Internal;
-using Dalamud.Interface.Utility;
-using Dalamud.Storage.Assets;
-using Dalamud.Utility;
-
-using ImGuiNET;
-
-using SharpDX.DXGI;
-
-namespace Dalamud.Interface.ManagedFontAtlas.Internals;
-
-///
-/// Standalone font atlas.
-///
-internal sealed partial class FontAtlasFactory
-{
- private static readonly Dictionary> PairAdjustmentsCache =
- new();
-
- ///
- /// Implementations for and
- /// .
- ///
- private class BuildToolkit : IFontAtlasBuildToolkitPreBuild, IFontAtlasBuildToolkitPostBuild, IDisposable
- {
- private static readonly ushort FontAwesomeIconMin =
- (ushort)Enum.GetValues().Where(x => x > 0).Min();
-
- private static readonly ushort FontAwesomeIconMax =
- (ushort)Enum.GetValues().Where(x => x > 0).Max();
-
- private readonly DisposeSafety.ScopedFinalizer disposeAfterBuild = new();
- private readonly GamePrebakedFontHandle.HandleSubstance gameFontHandleSubstance;
- private readonly FontAtlasFactory factory;
- private readonly FontAtlasBuiltData data;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// An instance of .
- /// New atlas.
- /// An instance of .
- /// Specify whether the current build operation is an asynchronous one.
- public BuildToolkit(
- FontAtlasFactory factory,
- FontAtlasBuiltData data,
- GamePrebakedFontHandle.HandleSubstance gameFontHandleSubstance,
- bool isAsync)
- {
- this.data = data;
- this.gameFontHandleSubstance = gameFontHandleSubstance;
- this.IsAsyncBuildOperation = isAsync;
- this.factory = factory;
- }
-
- ///
- public ImFontPtr Font { get; set; }
-
- ///
- public float Scale => this.data.Scale;
-
- ///
- public bool IsAsyncBuildOperation { get; }
-
- ///
- public FontAtlasBuildStep BuildStep { get; set; }
-
- ///
- public ImFontAtlasPtr NewImAtlas => this.data.Atlas;
-
- ///
- public ImVectorWrapper Fonts => this.data.Fonts;
-
- ///
- /// Gets the list of fonts to ignore global scale.
- ///
- public List GlobalScaleExclusions { get; } = new();
-
- ///
- public void Dispose() => this.disposeAfterBuild.Dispose();
-
- ///
- public T2 DisposeAfterBuild(T2 disposable) where T2 : IDisposable =>
- this.disposeAfterBuild.Add(disposable);
-
- ///
- public GCHandle DisposeAfterBuild(GCHandle gcHandle) => this.disposeAfterBuild.Add(gcHandle);
-
- ///
- public void DisposeAfterBuild(Action action) => this.disposeAfterBuild.Add(action);
-
- ///
- public T DisposeWithAtlas(T disposable) where T : IDisposable => this.data.Garbage.Add(disposable);
-
- ///
- public GCHandle DisposeWithAtlas(GCHandle gcHandle) => this.data.Garbage.Add(gcHandle);
-
- ///
- public void DisposeWithAtlas(Action action) => this.data.Garbage.Add(action);
-
- ///
- public ImFontPtr IgnoreGlobalScale(ImFontPtr fontPtr)
- {
- this.GlobalScaleExclusions.Add(fontPtr);
- return fontPtr;
- }
-
- ///
- public bool IsGlobalScaleIgnored(ImFontPtr fontPtr) =>
- this.GlobalScaleExclusions.Contains(fontPtr);
-
- ///
- public int StoreTexture(IDalamudTextureWrap textureWrap, bool disposeOnError) =>
- this.data.AddNewTexture(textureWrap, disposeOnError);
-
- ///
- public unsafe ImFontPtr AddFontFromImGuiHeapAllocatedMemory(
- void* dataPointer,
- int dataSize,
- in SafeFontConfig fontConfig,
- bool freeOnException,
- string debugTag)
- {
- Log.Verbose(
- "[{name}] 0x{atlas:X}: {funcname}(0x{dataPointer:X}, 0x{dataSize:X}, ...) from {tag}",
- this.data.Owner?.Name ?? "(error)",
- (nint)this.NewImAtlas.NativePtr,
- nameof(this.AddFontFromImGuiHeapAllocatedMemory),
- (nint)dataPointer,
- dataSize,
- debugTag);
-
- try
- {
- fontConfig.ThrowOnInvalidValues();
-
- var raw = fontConfig.Raw with
- {
- FontData = dataPointer,
- FontDataSize = dataSize,
- };
-
- if (fontConfig.GlyphRanges is not { Length: > 0 } ranges)
- ranges = new ushort[] { 1, 0xFFFE, 0 };
-
- raw.GlyphRanges = (ushort*)this.DisposeAfterBuild(
- GCHandle.Alloc(ranges, GCHandleType.Pinned)).AddrOfPinnedObject();
-
- TrueTypeUtils.CheckImGuiCompatibleOrThrow(raw);
-
- var font = this.NewImAtlas.AddFont(&raw);
-
- var dataHash = default(HashCode);
- dataHash.AddBytes(new(dataPointer, dataSize));
- var hashIdent = (uint)dataHash.ToHashCode() | ((ulong)dataSize << 32);
-
- List<(char Left, char Right, float Distance)> pairAdjustments;
- lock (PairAdjustmentsCache)
- {
- if (!PairAdjustmentsCache.TryGetValue(hashIdent, out pairAdjustments))
- {
- PairAdjustmentsCache.Add(hashIdent, pairAdjustments = new());
- try
- {
- pairAdjustments.AddRange(TrueTypeUtils.ExtractHorizontalPairAdjustments(raw).ToArray());
- }
- catch
- {
- // don't care
- }
- }
- }
-
- foreach (var pair in pairAdjustments)
- {
- if (!ImGuiHelpers.IsCodepointInSuppliedGlyphRangesUnsafe(pair.Left, raw.GlyphRanges))
- continue;
- if (!ImGuiHelpers.IsCodepointInSuppliedGlyphRangesUnsafe(pair.Right, raw.GlyphRanges))
- continue;
-
- font.AddKerningPair(pair.Left, pair.Right, pair.Distance * raw.SizePixels);
- }
-
- return font;
- }
- catch
- {
- if (freeOnException)
- ImGuiNative.igMemFree(dataPointer);
- throw;
- }
- }
-
- ///
- public ImFontPtr AddFontFromFile(string path, in SafeFontConfig fontConfig)
- {
- return this.AddFontFromStream(
- File.OpenRead(path),
- fontConfig,
- false,
- $"{nameof(this.AddFontFromFile)}({path})");
- }
-
- ///
- public unsafe ImFontPtr AddFontFromStream(
- Stream stream,
- in SafeFontConfig fontConfig,
- bool leaveOpen,
- string debugTag)
- {
- using var streamCloser = leaveOpen ? null : stream;
- if (!stream.CanSeek)
- {
- // There is no need to dispose a MemoryStream.
- var ms = new MemoryStream();
- stream.CopyTo(ms);
- stream = ms;
- }
-
- var length = checked((int)(uint)stream.Length);
- var memory = ImGuiHelpers.AllocateMemory(length);
- try
- {
- stream.ReadExactly(new(memory, length));
- return this.AddFontFromImGuiHeapAllocatedMemory(
- memory,
- length,
- fontConfig,
- false,
- $"{nameof(this.AddFontFromStream)}({debugTag})");
- }
- catch
- {
- ImGuiNative.igMemFree(memory);
- throw;
- }
- }
-
- ///
- public unsafe ImFontPtr AddFontFromMemory(
- ReadOnlySpan span,
- in SafeFontConfig fontConfig,
- string debugTag)
- {
- var length = span.Length;
- var memory = ImGuiHelpers.AllocateMemory(length);
- try
- {
- span.CopyTo(new(memory, length));
- return this.AddFontFromImGuiHeapAllocatedMemory(
- memory,
- length,
- fontConfig,
- false,
- $"{nameof(this.AddFontFromMemory)}({debugTag})");
- }
- catch
- {
- ImGuiNative.igMemFree(memory);
- throw;
- }
- }
-
- ///
- public ImFontPtr AddDalamudDefaultFont(float sizePx, ushort[]? glyphRanges)
- {
- ImFontPtr font;
- glyphRanges ??= this.factory.DefaultGlyphRanges;
- if (this.factory.UseAxis)
- {
- font = this.AddGameGlyphs(new(GameFontFamily.Axis, sizePx), glyphRanges, default);
- }
- else
- {
- font = this.AddDalamudAssetFont(
- DalamudAsset.NotoSansJpMedium,
- new() { SizePx = sizePx, GlyphRanges = glyphRanges });
- this.AddGameSymbol(new() { SizePx = sizePx, MergeFont = font });
- }
-
- this.AttachExtraGlyphsForDalamudLanguage(new() { SizePx = sizePx, MergeFont = font });
- if (this.Font.IsNull())
- this.Font = font;
- return font;
- }
-
- ///
- public ImFontPtr AddDalamudAssetFont(DalamudAsset asset, in SafeFontConfig fontConfig)
- {
- if (asset.GetPurpose() != DalamudAssetPurpose.Font)
- throw new ArgumentOutOfRangeException(nameof(asset), asset, "Must have the purpose of Font.");
-
- switch (asset)
- {
- case DalamudAsset.LodestoneGameSymbol when this.factory.HasGameSymbolsFontFile:
- return this.factory.AddFont(
- this,
- asset,
- fontConfig with
- {
- FontNo = 0,
- SizePx = (fontConfig.SizePx * 3) / 2,
- });
-
- case DalamudAsset.LodestoneGameSymbol when !this.factory.HasGameSymbolsFontFile:
- {
- return this.AddGameGlyphs(
- new(GameFontFamily.Axis, fontConfig.SizePx),
- fontConfig.GlyphRanges,
- fontConfig.MergeFont);
- }
-
- default:
- return this.factory.AddFont(
- this,
- asset,
- fontConfig with
- {
- FontNo = 0,
- });
- }
- }
-
- ///
- public ImFontPtr AddFontAwesomeIconFont(in SafeFontConfig fontConfig) => this.AddDalamudAssetFont(
- DalamudAsset.FontAwesomeFreeSolid,
- fontConfig with
- {
- GlyphRanges = new ushort[] { FontAwesomeIconMin, FontAwesomeIconMax, 0 },
- });
-
- ///
- public ImFontPtr AddGameSymbol(in SafeFontConfig fontConfig) =>
- this.AddDalamudAssetFont(
- DalamudAsset.LodestoneGameSymbol,
- fontConfig with
- {
- GlyphRanges = new ushort[]
- {
- GamePrebakedFontHandle.SeIconCharMin,
- GamePrebakedFontHandle.SeIconCharMax,
- 0,
- },
- });
-
- ///
- public ImFontPtr AddGameGlyphs(GameFontStyle gameFontStyle, ushort[]? glyphRanges, ImFontPtr mergeFont) =>
- this.gameFontHandleSubstance.AttachGameGlyphs(this, mergeFont, gameFontStyle, glyphRanges);
-
- ///
- public void AttachExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig)
- {
- var dalamudConfiguration = Service.Get();
- if (dalamudConfiguration.EffectiveLanguage == "ko"
- || Service.GetNullable()?.EncounteredHangul is true)
- {
- this.AddDalamudAssetFont(
- DalamudAsset.NotoSansKrRegular,
- fontConfig with
- {
- GlyphRanges = ImGuiHelpers.CreateImGuiRangesFrom(
- UnicodeRanges.HangulJamo,
- UnicodeRanges.HangulCompatibilityJamo,
- UnicodeRanges.HangulSyllables,
- UnicodeRanges.HangulJamoExtendedA,
- UnicodeRanges.HangulJamoExtendedB),
- });
- }
-
- 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")
- {
- this.AddFontFromFile(fontPathCht, fontConfig with
- {
- GlyphRanges = ImGuiHelpers.CreateImGuiRangesFrom(
- UnicodeRanges.CjkUnifiedIdeographs,
- UnicodeRanges.CjkUnifiedIdeographsExtensionA),
- });
- }
- else if (fontPathChs != null && (Service.Get().EffectiveLanguage == "zh"
- || Service.GetNullable()?.EncounteredHan is true))
- {
- this.AddFontFromFile(fontPathChs, fontConfig with
- {
- GlyphRanges = ImGuiHelpers.CreateImGuiRangesFrom(
- UnicodeRanges.CjkUnifiedIdeographs,
- UnicodeRanges.CjkUnifiedIdeographsExtensionA),
- });
- }
- }
-
- public void PreBuildSubstances()
- {
- foreach (var substance in this.data.Substances)
- substance.OnPreBuild(this);
- foreach (var substance in this.data.Substances)
- substance.OnPreBuildCleanup(this);
- }
-
- public unsafe void PreBuild()
- {
- var configData = this.data.ConfigData;
- foreach (ref var config in configData.DataSpan)
- {
- if (this.GlobalScaleExclusions.Contains(new(config.DstFont)))
- continue;
-
- config.SizePixels *= this.Scale;
-
- config.GlyphMaxAdvanceX *= this.Scale;
- if (float.IsInfinity(config.GlyphMaxAdvanceX))
- config.GlyphMaxAdvanceX = config.GlyphMaxAdvanceX > 0 ? float.MaxValue : -float.MaxValue;
-
- config.GlyphMinAdvanceX *= this.Scale;
- if (float.IsInfinity(config.GlyphMinAdvanceX))
- config.GlyphMinAdvanceX = config.GlyphMinAdvanceX > 0 ? float.MaxValue : -float.MaxValue;
-
- config.GlyphOffset *= this.Scale;
- }
- }
-
- public void DoBuild()
- {
- // ImGui will call AddFontDefault() on Build() call.
- // AddFontDefault() will reliably crash, when invoked multithreaded.
- // We add a dummy font to prevent that.
- if (this.data.ConfigData.Length == 0)
- {
- this.AddDalamudAssetFont(
- DalamudAsset.NotoSansJpMedium,
- new() { GlyphRanges = new ushort[] { ' ', ' ', '\0' }, SizePx = 1 });
- }
-
- if (!this.NewImAtlas.Build())
- throw new InvalidOperationException("ImFontAtlas.Build failed");
-
- this.BuildStep = FontAtlasBuildStep.PostBuild;
- }
-
- public unsafe void PostBuild()
- {
- var scale = this.Scale;
- foreach (ref var font in this.Fonts.DataSpan)
- {
- if (!this.GlobalScaleExclusions.Contains(font))
- font.AdjustGlyphMetrics(1 / scale, 1 / scale);
-
- foreach (var c in FallbackCodepoints)
- {
- var g = font.FindGlyphNoFallback(c);
- if (g.NativePtr == null)
- continue;
-
- font.UpdateFallbackChar(c);
- break;
- }
-
- foreach (var c in EllipsisCodepoints)
- {
- var g = font.FindGlyphNoFallback(c);
- if (g.NativePtr == null)
- continue;
-
- font.EllipsisChar = c;
- break;
- }
- }
- }
-
- public void PostBuildSubstances()
- {
- foreach (var substance in this.data.Substances)
- substance.OnPostBuild(this);
- }
-
- public unsafe void UploadTextures()
- {
- var buf = Array.Empty();
- try
- {
- var use4 = this.factory.InterfaceManager.SupportsDxgiFormat(Format.B4G4R4A4_UNorm);
- var bpp = use4 ? 2 : 4;
- var width = this.NewImAtlas.TexWidth;
- var height = this.NewImAtlas.TexHeight;
- foreach (ref var texture in this.data.ImTextures.DataSpan)
- {
- if (texture.TexID != 0)
- {
- // Nothing to do
- }
- else if (texture.TexPixelsRGBA32 is not null)
- {
- var wrap = this.factory.InterfaceManager.LoadImageFromDxgiFormat(
- new(texture.TexPixelsRGBA32, width * height * 4),
- width * 4,
- width,
- height,
- use4 ? Format.B4G4R4A4_UNorm : Format.R8G8B8A8_UNorm);
- this.data.AddExistingTexture(wrap);
- texture.TexID = wrap.ImGuiHandle;
- }
- else if (texture.TexPixelsAlpha8 is not null)
- {
- var numPixels = width * height;
- if (buf.Length < numPixels * bpp)
- {
- ArrayPool.Shared.Return(buf);
- buf = ArrayPool.Shared.Rent(numPixels * bpp);
- }
-
- fixed (void* pBuf = buf)
- {
- var sourcePtr = texture.TexPixelsAlpha8;
- if (use4)
- {
- var target = (ushort*)pBuf;
- while (numPixels-- > 0)
- {
- *target = (ushort)((*sourcePtr << 8) | 0x0FFF);
- target++;
- sourcePtr++;
- }
- }
- else
- {
- var target = (uint*)pBuf;
- while (numPixels-- > 0)
- {
- *target = (uint)((*sourcePtr << 24) | 0x00FFFFFF);
- target++;
- sourcePtr++;
- }
- }
- }
-
- var wrap = this.factory.InterfaceManager.LoadImageFromDxgiFormat(
- buf,
- width * bpp,
- width,
- height,
- use4 ? Format.B4G4R4A4_UNorm : Format.B8G8R8A8_UNorm);
- this.data.AddExistingTexture(wrap);
- texture.TexID = wrap.ImGuiHandle;
- continue;
- }
- else
- {
- Log.Warning(
- "[{name}]: TexID, TexPixelsRGBA32, and TexPixelsAlpha8 are all null",
- this.data.Owner?.Name ?? "(error)");
- }
-
- if (texture.TexPixelsRGBA32 is not null)
- ImGuiNative.igMemFree(texture.TexPixelsRGBA32);
- if (texture.TexPixelsAlpha8 is not null)
- ImGuiNative.igMemFree(texture.TexPixelsAlpha8);
- texture.TexPixelsRGBA32 = null;
- texture.TexPixelsAlpha8 = null;
- }
- }
- finally
- {
- ArrayPool.Shared.Return(buf);
- }
- }
- }
-
- ///
- /// Implementations for .
- ///
- private class BuildToolkitPostPromotion : IFontAtlasBuildToolkitPostPromotion
- {
- private readonly FontAtlasBuiltData builtData;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The built data.
- public BuildToolkitPostPromotion(FontAtlasBuiltData builtData) => this.builtData = builtData;
-
- ///
- public ImFontPtr Font { get; set; }
-
- ///
- public float Scale => this.builtData.Scale;
-
- ///
- public bool IsAsyncBuildOperation => true;
-
- ///
- public FontAtlasBuildStep BuildStep => FontAtlasBuildStep.PostPromotion;
-
- ///
- public ImFontAtlasPtr NewImAtlas => this.builtData.Atlas;
-
- ///
- public unsafe ImVectorWrapper Fonts => new(
- &this.NewImAtlas.NativePtr->Fonts,
- x => ImGuiNative.ImFont_destroy(x->NativePtr));
-
- ///
- public T DisposeWithAtlas(T disposable) where T : IDisposable => this.builtData.Garbage.Add(disposable);
-
- ///
- public GCHandle DisposeWithAtlas(GCHandle gcHandle) => this.builtData.Garbage.Add(gcHandle);
-
- ///
- public void DisposeWithAtlas(Action action) => this.builtData.Garbage.Add(action);
-
- ///
- public unsafe void CopyGlyphsAcrossFonts(
- ImFontPtr source,
- ImFontPtr target,
- bool missingOnly,
- bool rebuildLookupTable = true,
- char rangeLow = ' ',
- char rangeHigh = '\uFFFE')
- {
- var sourceFound = false;
- var targetFound = false;
- foreach (var f in this.Fonts)
- {
- sourceFound |= f.NativePtr == source.NativePtr;
- targetFound |= f.NativePtr == target.NativePtr;
- }
-
- if (sourceFound && targetFound)
- {
- ImGuiHelpers.CopyGlyphsAcrossFonts(
- source,
- target,
- missingOnly,
- false,
- rangeLow,
- rangeHigh);
- if (rebuildLookupTable)
- this.BuildLookupTable(target);
- }
- }
-
- ///
- public unsafe void BuildLookupTable(ImFontPtr font)
- {
- // Need to clear previous Fallback pointers before BuildLookupTable, or it may crash
- font.NativePtr->FallbackGlyph = null;
- font.NativePtr->FallbackHotData = null;
- font.BuildLookupTable();
-
- // Need to fix our custom ImGui, so that imgui_widgets.cpp:3656 stops thinking
- // Codepoint < FallbackHotData.size always means that it's not fallback char.
- // Otherwise, having a fallback character in ImGui.InputText gets strange.
- var indexedHotData = font.IndexedHotDataWrapped();
- var indexLookup = font.IndexLookupWrapped();
- ref var fallbackHotData = ref *(ImGuiHelpers.ImFontGlyphHotDataReal*)font.NativePtr->FallbackHotData;
- for (var codepoint = 0; codepoint < indexedHotData.Length; codepoint++)
- {
- if (indexLookup[codepoint] == ushort.MaxValue)
- {
- indexedHotData[codepoint].AdvanceX = fallbackHotData.AdvanceX;
- indexedHotData[codepoint].OccupiedWidth = fallbackHotData.OccupiedWidth;
- }
- }
- }
- }
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs
deleted file mode 100644
index 5656fc673..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs
+++ /dev/null
@@ -1,726 +0,0 @@
-// #define VeryVerboseLog
-
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Reactive.Disposables;
-using System.Threading;
-using System.Threading.Tasks;
-
-using Dalamud.Interface.GameFonts;
-using Dalamud.Interface.Internal;
-using Dalamud.Interface.Utility;
-using Dalamud.Logging.Internal;
-using Dalamud.Utility;
-
-using ImGuiNET;
-
-using JetBrains.Annotations;
-
-namespace Dalamud.Interface.ManagedFontAtlas.Internals;
-
-///
-/// Standalone font atlas.
-///
-internal sealed partial class FontAtlasFactory
-{
- ///
- /// Fallback codepoints for ImFont.
- ///
- public const string FallbackCodepoints = "\u3013\uFFFD?-";
-
- ///
- /// Ellipsis codepoints for ImFont.
- ///
- public const string EllipsisCodepoints = "\u2026\u0085";
-
- ///
- /// If set, disables concurrent font build operation.
- ///
- private static readonly object? NoConcurrentBuildOperationLock = null; // new();
-
- private static readonly ModuleLog Log = new(nameof(FontAtlasFactory));
-
- private static readonly Task EmptyTask = Task.FromResult(default(FontAtlasBuiltData));
-
- private struct FontAtlasBuiltData : IDisposable
- {
- public readonly DalamudFontAtlas? Owner;
- public readonly ImFontAtlasPtr Atlas;
- public readonly float Scale;
-
- public bool IsBuildInProgress;
-
- private readonly List? wraps;
- private readonly List? substances;
- private readonly DisposeSafety.ScopedFinalizer? garbage;
-
- public unsafe FontAtlasBuiltData(
- DalamudFontAtlas owner,
- IEnumerable substances,
- float scale)
- {
- this.Owner = owner;
- this.Scale = scale;
- this.garbage = new();
-
- try
- {
- var substancesList = this.substances = new();
- foreach (var s in substances)
- substancesList.Add(this.garbage.Add(s));
- this.garbage.Add(() => substancesList.Clear());
-
- var wrapsCopy = this.wraps = new();
- this.garbage.Add(() => wrapsCopy.Clear());
-
- var atlasPtr = ImGuiNative.ImFontAtlas_ImFontAtlas();
- this.Atlas = atlasPtr;
- if (this.Atlas.NativePtr is null)
- throw new OutOfMemoryException($"Failed to allocate a new {nameof(ImFontAtlas)}.");
-
- this.garbage.Add(() => ImGuiNative.ImFontAtlas_destroy(atlasPtr));
- this.IsBuildInProgress = true;
- }
- catch
- {
- this.garbage.Dispose();
- throw;
- }
- }
-
- public readonly DisposeSafety.ScopedFinalizer Garbage =>
- this.garbage ?? throw new ObjectDisposedException(nameof(FontAtlasBuiltData));
-
- public readonly ImVectorWrapper Fonts => this.Atlas.FontsWrapped();
-
- public readonly ImVectorWrapper ConfigData => this.Atlas.ConfigDataWrapped();
-
- public readonly ImVectorWrapper ImTextures => this.Atlas.TexturesWrapped();
-
- public readonly IReadOnlyList Wraps =>
- (IReadOnlyList?)this.wraps ?? Array.Empty();
-
- public readonly IReadOnlyList Substances =>
- (IReadOnlyList?)this.substances ?? Array.Empty();
-
- public readonly void AddExistingTexture(IDalamudTextureWrap wrap)
- {
- if (this.wraps is null)
- throw new ObjectDisposedException(nameof(FontAtlasBuiltData));
-
- this.wraps.Add(this.Garbage.Add(wrap));
- }
-
- public readonly int AddNewTexture(IDalamudTextureWrap wrap, bool disposeOnError)
- {
- if (this.wraps is null)
- throw new ObjectDisposedException(nameof(FontAtlasBuiltData));
-
- var handle = wrap.ImGuiHandle;
- var index = this.ImTextures.IndexOf(x => x.TexID == handle);
- if (index == -1)
- {
- try
- {
- this.wraps.EnsureCapacity(this.wraps.Count + 1);
- this.ImTextures.EnsureCapacityExponential(this.ImTextures.Length + 1);
-
- index = this.ImTextures.Length;
- this.wraps.Add(this.Garbage.Add(wrap));
- this.ImTextures.Add(new() { TexID = handle });
- }
- catch (Exception e)
- {
- if (disposeOnError)
- wrap.Dispose();
-
- if (this.wraps.Count != this.ImTextures.Length)
- {
- Log.Error(
- e,
- "{name} failed, and {wraps} and {imtextures} have different number of items",
- nameof(this.AddNewTexture),
- nameof(this.Wraps),
- nameof(this.ImTextures));
-
- if (this.wraps.Count > 0 && this.wraps[^1] == wrap)
- this.wraps.RemoveAt(this.wraps.Count - 1);
- if (this.ImTextures.Length > 0 && this.ImTextures[^1].TexID == handle)
- this.ImTextures.RemoveAt(this.ImTextures.Length - 1);
-
- if (this.wraps.Count != this.ImTextures.Length)
- Log.Fatal("^ Failed to undo due to an internal inconsistency; embrace for a crash");
- }
-
- throw;
- }
- }
-
- return index;
- }
-
- public unsafe void Dispose()
- {
- if (this.garbage is null)
- return;
-
- if (this.IsBuildInProgress)
- {
- Log.Error(
- "[{name}] 0x{ptr:X}: Trying to dispose while build is in progress; waiting for build.\n" +
- "Stack:\n{trace}",
- this.Owner?.Name ?? ">",
- (nint)this.Atlas.NativePtr,
- new StackTrace());
- while (this.IsBuildInProgress)
- Thread.Sleep(100);
- }
-
-#if VeryVerboseLog
- Log.Verbose("[{name}] 0x{ptr:X}: Disposing", this.Owner?.Name ?? ">", (nint)this.Atlas.NativePtr);
-#endif
- this.garbage.Dispose();
- }
-
- public BuildToolkit CreateToolkit(FontAtlasFactory factory, bool isAsync)
- {
- var axisSubstance = this.Substances.OfType().Single();
- return new(factory, this, axisSubstance, isAsync) { BuildStep = FontAtlasBuildStep.PreBuild };
- }
- }
-
- private class DalamudFontAtlas : IFontAtlas, DisposeSafety.IDisposeCallback
- {
- private readonly DisposeSafety.ScopedFinalizer disposables = new();
- private readonly FontAtlasFactory factory;
- private readonly DelegateFontHandle.HandleManager delegateFontHandleManager;
- private readonly GamePrebakedFontHandle.HandleManager gameFontHandleManager;
- private readonly IFontHandleManager[] fontHandleManagers;
-
- private readonly object syncRootPostPromotion = new();
- private readonly object syncRoot = new();
-
- private Task buildTask = EmptyTask;
- private FontAtlasBuiltData builtData;
-
- private int buildSuppressionCounter;
- private bool buildSuppressionSuppressed;
-
- private int buildIndex;
- private bool buildQueued;
- private bool disposed = false;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The factory.
- /// Name of atlas, for debugging and logging purposes.
- /// Specify how to auto rebuild.
- /// Whether the fonts in the atlas are under the effect of global scale.
- public DalamudFontAtlas(
- FontAtlasFactory factory,
- string atlasName,
- FontAtlasAutoRebuildMode autoRebuildMode,
- bool isGlobalScaled)
- {
- this.IsGlobalScaled = isGlobalScaled;
- try
- {
- this.factory = factory;
- this.AutoRebuildMode = autoRebuildMode;
- this.Name = atlasName;
-
- this.factory.InterfaceManager.AfterBuildFonts += this.OnRebuildRecommend;
- this.disposables.Add(() => this.factory.InterfaceManager.AfterBuildFonts -= this.OnRebuildRecommend);
-
- this.fontHandleManagers = new IFontHandleManager[]
- {
- this.delegateFontHandleManager = this.disposables.Add(
- new DelegateFontHandle.HandleManager(atlasName)),
- this.gameFontHandleManager = this.disposables.Add(
- new GamePrebakedFontHandle.HandleManager(atlasName, factory)),
- };
- foreach (var fhm in this.fontHandleManagers)
- fhm.RebuildRecommend += this.OnRebuildRecommend;
- }
- catch
- {
- this.disposables.Dispose();
- throw;
- }
-
- this.factory.SceneTask.ContinueWith(
- r =>
- {
- lock (this.syncRoot)
- {
- if (this.disposed)
- return;
-
- r.Result.OnNewRenderFrame += this.ImGuiSceneOnNewRenderFrame;
- this.disposables.Add(() => r.Result.OnNewRenderFrame -= this.ImGuiSceneOnNewRenderFrame);
- }
-
- if (this.AutoRebuildMode == FontAtlasAutoRebuildMode.OnNewFrame)
- this.BuildFontsOnNextFrame();
- });
- }
-
- ///
- /// Finalizes an instance of the class.
- ///
- ~DalamudFontAtlas()
- {
- lock (this.syncRoot)
- {
- this.buildTask.ToDisposableIgnoreExceptions().Dispose();
- this.builtData.Dispose();
- }
- }
-
- ///
- public event FontAtlasBuildStepDelegate? BuildStepChange;
-
- ///
- public event Action? RebuildRecommend;
-
- ///
- public event Action? BeforeDispose;
-
- ///
- public event Action? AfterDispose;
-
- ///
- public string Name { get; }
-
- ///
- public FontAtlasAutoRebuildMode AutoRebuildMode { get; }
-
- ///
- public ImFontAtlasPtr ImAtlas
- {
- get
- {
- lock (this.syncRoot)
- return this.builtData.Atlas;
- }
- }
-
- ///
- public Task BuildTask => this.buildTask;
-
- ///
- public bool HasBuiltAtlas => !this.builtData.Atlas.IsNull();
-
- ///
- public bool IsGlobalScaled { get; }
-
- ///
- public void Dispose()
- {
- if (this.disposed)
- return;
-
- this.BeforeDispose?.InvokeSafely(this);
-
- try
- {
- lock (this.syncRoot)
- {
- this.disposed = true;
- this.buildTask.ToDisposableIgnoreExceptions().Dispose();
- this.buildTask = EmptyTask;
- this.disposables.Add(this.builtData);
- this.builtData = default;
- this.disposables.Dispose();
- }
-
- try
- {
- this.AfterDispose?.Invoke(this, null);
- }
- catch
- {
- // ignore
- }
- }
- catch (Exception e)
- {
- try
- {
- this.AfterDispose?.Invoke(this, e);
- }
- catch
- {
- // ignore
- }
- }
-
- GC.SuppressFinalize(this);
- }
-
- ///
- public IDisposable SuppressAutoRebuild()
- {
- this.buildSuppressionCounter++;
- return Disposable.Create(
- () =>
- {
- this.buildSuppressionCounter--;
- if (this.buildSuppressionSuppressed)
- this.OnRebuildRecommend();
- });
- }
-
- ///
- public IFontHandle NewGameFontHandle(GameFontStyle style) => this.gameFontHandleManager.NewFontHandle(style);
-
- ///
- public IFontHandle NewDelegateFontHandle(FontAtlasBuildStepDelegate buildStepDelegate) =>
- this.delegateFontHandleManager.NewFontHandle(buildStepDelegate);
-
- ///
- public void BuildFontsOnNextFrame()
- {
- if (this.AutoRebuildMode == FontAtlasAutoRebuildMode.Async)
- {
- throw new InvalidOperationException(
- $"{nameof(this.BuildFontsOnNextFrame)} cannot be used when " +
- $"{nameof(this.AutoRebuildMode)} is set to " +
- $"{nameof(FontAtlasAutoRebuildMode.Async)}.");
- }
-
- if (!this.buildTask.IsCompleted || this.buildQueued)
- return;
-
-#if VeryVerboseLog
- Log.Verbose("[{name}] Queueing from {source}.", this.Name, nameof(this.BuildFontsOnNextFrame));
-#endif
-
- this.buildQueued = true;
- }
-
- ///
- public void BuildFontsImmediately()
- {
-#if VeryVerboseLog
- Log.Verbose("[{name}] Called: {source}.", this.Name, nameof(this.BuildFontsImmediately));
-#endif
-
- if (this.AutoRebuildMode == FontAtlasAutoRebuildMode.Async)
- {
- throw new InvalidOperationException(
- $"{nameof(this.BuildFontsImmediately)} cannot be used when " +
- $"{nameof(this.AutoRebuildMode)} is set to " +
- $"{nameof(FontAtlasAutoRebuildMode.Async)}.");
- }
-
- var tcs = new TaskCompletionSource();
- int rebuildIndex;
- try
- {
- rebuildIndex = ++this.buildIndex;
- lock (this.syncRoot)
- {
- if (!this.buildTask.IsCompleted)
- throw new InvalidOperationException("Font rebuild is already in progress.");
-
- this.buildTask = tcs.Task;
- }
-
-#if VeryVerboseLog
- Log.Verbose("[{name}] Building from {source}.", this.Name, nameof(this.BuildFontsImmediately));
-#endif
-
- var scale = this.IsGlobalScaled ? ImGuiHelpers.GlobalScaleSafe : 1f;
- var r = this.RebuildFontsPrivate(false, scale);
- r.Wait();
- if (r.IsCompletedSuccessfully)
- tcs.SetResult(r.Result);
- else if (r.Exception is not null)
- tcs.SetException(r.Exception);
- else
- tcs.SetCanceled();
- }
- catch (Exception e)
- {
- tcs.SetException(e);
- Log.Error(e, "[{name}] Failed to build fonts.", this.Name);
- throw;
- }
-
- this.InvokePostPromotion(rebuildIndex, tcs.Task.Result, nameof(this.BuildFontsImmediately));
- }
-
- ///
- public Task BuildFontsAsync(bool callPostPromotionOnMainThread = true)
- {
-#if VeryVerboseLog
- Log.Verbose("[{name}] Called: {source}.", this.Name, nameof(this.BuildFontsAsync));
-#endif
-
- if (this.AutoRebuildMode == FontAtlasAutoRebuildMode.OnNewFrame)
- {
- throw new InvalidOperationException(
- $"{nameof(this.BuildFontsAsync)} cannot be used when " +
- $"{nameof(this.AutoRebuildMode)} is set to " +
- $"{nameof(FontAtlasAutoRebuildMode.OnNewFrame)}.");
- }
-
- lock (this.syncRoot)
- {
- var scale = this.IsGlobalScaled ? ImGuiHelpers.GlobalScaleSafe : 1f;
- var rebuildIndex = ++this.buildIndex;
- return this.buildTask = this.buildTask.ContinueWith(BuildInner).Unwrap();
-
- async Task BuildInner(Task unused)
- {
- Log.Verbose("[{name}] Building from {source}.", this.Name, nameof(this.BuildFontsAsync));
- lock (this.syncRoot)
- {
- if (this.buildIndex != rebuildIndex)
- return default;
- }
-
- var res = await this.RebuildFontsPrivate(true, scale);
- if (res.Atlas.IsNull())
- return res;
-
- if (callPostPromotionOnMainThread)
- {
- await this.factory.Framework.RunOnFrameworkThread(
- () => this.InvokePostPromotion(rebuildIndex, res, nameof(this.BuildFontsAsync)));
- }
- else
- {
- this.InvokePostPromotion(rebuildIndex, res, nameof(this.BuildFontsAsync));
- }
-
- return res;
- }
- }
- }
-
- private void InvokePostPromotion(int rebuildIndex, FontAtlasBuiltData data, [UsedImplicitly] string source)
- {
- lock (this.syncRoot)
- {
- if (this.buildIndex != rebuildIndex)
- {
- data.ExplicitDisposeIgnoreExceptions();
- return;
- }
-
- this.builtData.ExplicitDisposeIgnoreExceptions();
- this.builtData = data;
- this.buildTask = EmptyTask;
- foreach (var substance in data.Substances)
- substance.Manager.Substance = substance;
- }
-
- lock (this.syncRootPostPromotion)
- {
- if (this.buildIndex != rebuildIndex)
- {
- data.ExplicitDisposeIgnoreExceptions();
- return;
- }
-
- var toolkit = new BuildToolkitPostPromotion(data);
-
- try
- {
- this.BuildStepChange?.Invoke(toolkit);
- }
- catch (Exception e)
- {
- Log.Error(
- e,
- "[{name}] {delegateName} PostPromotion error",
- this.Name,
- nameof(FontAtlasBuildStepDelegate));
- }
-
- foreach (var substance in data.Substances)
- {
- try
- {
- substance.OnPostPromotion(toolkit);
- }
- catch (Exception e)
- {
- Log.Error(
- e,
- "[{name}] {substance} PostPromotion error",
- this.Name,
- substance.GetType().FullName ?? substance.GetType().Name);
- }
- }
-
- foreach (var font in toolkit.Fonts)
- {
- try
- {
- toolkit.BuildLookupTable(font);
- }
- catch (Exception e)
- {
- Log.Error(e, "[{name}] BuildLookupTable error", this.Name);
- }
- }
-
-#if VeryVerboseLog
- Log.Verbose("[{name}] Built from {source}.", this.Name, source);
-#endif
- }
- }
-
- private void ImGuiSceneOnNewRenderFrame()
- {
- if (!this.buildQueued)
- return;
-
- try
- {
- if (this.AutoRebuildMode != FontAtlasAutoRebuildMode.Async)
- this.BuildFontsImmediately();
- }
- finally
- {
- this.buildQueued = false;
- }
- }
-
- private Task RebuildFontsPrivate(bool isAsync, float scale)
- {
- if (NoConcurrentBuildOperationLock is null)
- return this.RebuildFontsPrivateReal(isAsync, scale);
- lock (NoConcurrentBuildOperationLock)
- return this.RebuildFontsPrivateReal(isAsync, scale);
- }
-
- private async Task RebuildFontsPrivateReal(bool isAsync, float scale)
- {
- lock (this.syncRoot)
- {
- // this lock ensures that this.buildTask is properly set.
- }
-
- var sw = new Stopwatch();
- sw.Start();
-
- var res = default(FontAtlasBuiltData);
- nint atlasPtr = 0;
- try
- {
- res = new(this, this.fontHandleManagers.Select(x => x.NewSubstance()), scale);
- unsafe
- {
- atlasPtr = (nint)res.Atlas.NativePtr;
- }
-
- Log.Verbose(
- "[{name}:{functionname}] 0x{ptr:X}: PreBuild (at {sw}ms)",
- this.Name,
- nameof(this.RebuildFontsPrivateReal),
- atlasPtr,
- sw.ElapsedMilliseconds);
-
- using var toolkit = res.CreateToolkit(this.factory, isAsync);
- this.BuildStepChange?.Invoke(toolkit);
- toolkit.PreBuildSubstances();
- toolkit.PreBuild();
-
-#if VeryVerboseLog
- Log.Verbose("[{name}:{functionname}] 0x{ptr:X}: Build (at {sw}ms)", this.Name, nameof(this.RebuildFontsPrivateReal), atlasPtr, sw.ElapsedMilliseconds);
-#endif
-
- toolkit.DoBuild();
-
-#if VeryVerboseLog
- Log.Verbose("[{name}:{functionname}] 0x{ptr:X}: PostBuild (at {sw}ms)", this.Name, nameof(this.RebuildFontsPrivateReal), atlasPtr, sw.ElapsedMilliseconds);
-#endif
-
- toolkit.PostBuild();
- toolkit.PostBuildSubstances();
- this.BuildStepChange?.Invoke(toolkit);
-
- if (this.factory.SceneTask is { IsCompleted: false } sceneTask)
- {
- Log.Verbose(
- "[{name}:{functionname}] 0x{ptr:X}: await SceneTask (at {sw}ms)",
- this.Name,
- nameof(this.RebuildFontsPrivateReal),
- atlasPtr,
- sw.ElapsedMilliseconds);
- await sceneTask.ConfigureAwait(!isAsync);
- }
-
-#if VeryVerboseLog
- Log.Verbose("[{name}:{functionname}] 0x{ptr:X}: UploadTextures (at {sw}ms)", this.Name, nameof(this.RebuildFontsPrivateReal), atlasPtr, sw.ElapsedMilliseconds);
-#endif
- toolkit.UploadTextures();
-
- Log.Verbose(
- "[{name}:{functionname}] 0x{ptr:X}: Complete (at {sw}ms)",
- this.Name,
- nameof(this.RebuildFontsPrivateReal),
- atlasPtr,
- sw.ElapsedMilliseconds);
-
- res.IsBuildInProgress = false;
- return res;
- }
- catch (Exception e)
- {
- Log.Error(
- e,
- "[{name}:{functionname}] 0x{ptr:X}: Failed (at {sw}ms)",
- this.Name,
- nameof(this.RebuildFontsPrivateReal),
- atlasPtr,
- sw.ElapsedMilliseconds);
- res.IsBuildInProgress = false;
- res.Dispose();
- throw;
- }
- finally
- {
- this.buildQueued = false;
- }
- }
-
- private void OnRebuildRecommend()
- {
- if (this.disposed)
- return;
-
- if (this.buildSuppressionCounter > 0)
- {
- this.buildSuppressionSuppressed = true;
- return;
- }
-
- this.buildSuppressionSuppressed = false;
- this.factory.Framework.RunOnFrameworkThread(
- () =>
- {
- this.RebuildRecommend?.InvokeSafely();
-
- switch (this.AutoRebuildMode)
- {
- case FontAtlasAutoRebuildMode.Async:
- _ = this.BuildFontsAsync();
- break;
- case FontAtlasAutoRebuildMode.OnNewFrame:
- this.BuildFontsOnNextFrame();
- break;
- case FontAtlasAutoRebuildMode.Disable:
- default:
- break;
- }
- });
- }
- }
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs
deleted file mode 100644
index 358ccd845..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs
+++ /dev/null
@@ -1,368 +0,0 @@
-using System.Buffers;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-using Dalamud.Configuration.Internal;
-using Dalamud.Data;
-using Dalamud.Game;
-using Dalamud.Interface.GameFonts;
-using Dalamud.Interface.Internal;
-using Dalamud.Storage.Assets;
-using Dalamud.Utility;
-
-using ImGuiNET;
-
-using ImGuiScene;
-
-using Lumina.Data.Files;
-
-using SharpDX;
-using SharpDX.Direct3D11;
-using SharpDX.DXGI;
-
-namespace Dalamud.Interface.ManagedFontAtlas.Internals;
-
-///
-/// Factory for the implementation of .
-///
-[ServiceManager.BlockingEarlyLoadedService]
-internal sealed partial class FontAtlasFactory
- : IServiceType, GamePrebakedFontHandle.IGameFontTextureProvider, IDisposable
-{
- private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new();
- private readonly CancellationTokenSource cancellationTokenSource = new();
- private readonly IReadOnlyDictionary> fdtFiles;
- private readonly IReadOnlyDictionary[]>> texFiles;
- private readonly IReadOnlyDictionary> prebakedTextureWraps;
- private readonly Task defaultGlyphRanges;
- private readonly DalamudAssetManager dalamudAssetManager;
-
- [ServiceManager.ServiceConstructor]
- private FontAtlasFactory(
- DataManager dataManager,
- Framework framework,
- InterfaceManager interfaceManager,
- DalamudAssetManager dalamudAssetManager)
- {
- this.Framework = framework;
- this.InterfaceManager = interfaceManager;
- this.dalamudAssetManager = dalamudAssetManager;
- this.SceneTask = Service
- .GetAsync()
- .ContinueWith(r => r.Result.Manager.Scene);
-
- var gffasInfo = Enum.GetValues()
- .Select(
- x =>
- (
- Font: x,
- Attr: x.GetAttribute()))
- .Where(x => x.Attr is not null)
- .ToArray();
- var texPaths = gffasInfo.Select(x => x.Attr.TexPathFormat).Distinct().ToArray();
-
- this.fdtFiles = gffasInfo.ToImmutableDictionary(
- x => x.Font,
- x => Task.Run(() => dataManager.GetFile(x.Attr.Path)!.Data));
- var channelCountsTask = texPaths.ToImmutableDictionary(
- x => x,
- x => Task.WhenAll(
- gffasInfo.Where(y => y.Attr.TexPathFormat == x)
- .Select(y => this.fdtFiles[y.Font]))
- .ContinueWith(
- files => 1 + files.Result.Max(
- file =>
- {
- unsafe
- {
- using var pin = file.AsMemory().Pin();
- var fdt = new FdtFileView(pin.Pointer, file.Length);
- return fdt.MaxTextureIndex;
- }
- })));
- this.prebakedTextureWraps = channelCountsTask.ToImmutableDictionary(
- x => x.Key,
- x => x.Value.ContinueWith(y => new IDalamudTextureWrap?[y.Result]));
- this.texFiles = channelCountsTask.ToImmutableDictionary(
- x => x.Key,
- x => x.Value.ContinueWith(
- y => Enumerable
- .Range(1, 1 + ((y.Result - 1) / 4))
- .Select(z => Task.Run(() => dataManager.GetFile(string.Format(x.Key, z))!))
- .ToArray()));
- this.defaultGlyphRanges =
- this.fdtFiles[GameFontFamilyAndSize.Axis12]
- .ContinueWith(
- file =>
- {
- unsafe
- {
- using var pin = file.Result.AsMemory().Pin();
- var fdt = new FdtFileView(pin.Pointer, file.Result.Length);
- return fdt.ToGlyphRanges();
- }
- });
- }
-
- ///
- /// Gets or sets a value indicating whether to override configuration for UseAxis.
- ///
- public bool? UseAxisOverride { get; set; } = null;
-
- ///
- /// Gets a value indicating whether to use AXIS fonts.
- ///
- public bool UseAxis => this.UseAxisOverride ?? Service.Get().UseAxisFontsFromGame;
-
- ///
- /// Gets the service instance of .
- ///
- public Framework Framework { get; }
-
- ///
- /// Gets the service instance of .
- /// may not yet be available.
- ///
- public InterfaceManager InterfaceManager { get; }
-
- ///
- /// Gets the async task for inside .
- ///
- public Task SceneTask { get; }
-
- ///
- /// Gets the default glyph ranges (glyph ranges of ).
- ///
- public ushort[] DefaultGlyphRanges => ExtractResult(this.defaultGlyphRanges);
-
- ///
- /// Gets a value indicating whether game symbol font file is available.
- ///
- public bool HasGameSymbolsFontFile =>
- this.dalamudAssetManager.IsStreamImmediatelyAvailable(DalamudAsset.LodestoneGameSymbol);
-
- ///
- public void Dispose()
- {
- this.cancellationTokenSource.Cancel();
- this.scopedFinalizer.Dispose();
- this.cancellationTokenSource.Dispose();
- }
-
- ///
- /// Creates a new instance of a class that implements the interface.
- ///
- /// Name of atlas, for debugging and logging purposes.
- /// Specify how to auto rebuild.
- /// Whether the fonts in the atlas is global scaled.
- /// The new font atlas.
- public IFontAtlas CreateFontAtlas(
- string atlasName,
- FontAtlasAutoRebuildMode autoRebuildMode,
- bool isGlobalScaled = true) =>
- new DalamudFontAtlas(this, atlasName, autoRebuildMode, isGlobalScaled);
-
- ///
- /// Adds the font from Dalamud Assets.
- ///
- /// The toolkitPostBuild.
- /// The font.
- /// The font config.
- /// The address and size.
- public ImFontPtr AddFont(
- IFontAtlasBuildToolkitPreBuild toolkitPreBuild,
- DalamudAsset asset,
- in SafeFontConfig fontConfig) =>
- toolkitPreBuild.AddFontFromStream(
- this.dalamudAssetManager.CreateStream(asset),
- fontConfig,
- false,
- $"Asset({asset})");
-
- ///
- /// Gets the for the .
- ///
- /// The font family and size.
- /// The .
- public FdtReader GetFdtReader(GameFontFamilyAndSize gffas) => new(ExtractResult(this.fdtFiles[gffas]));
-
- ///
- public unsafe MemoryHandle CreateFdtFileView(GameFontFamilyAndSize gffas, out FdtFileView fdtFileView)
- {
- var arr = ExtractResult(this.fdtFiles[gffas]);
- var handle = arr.AsMemory().Pin();
- try
- {
- fdtFileView = new(handle.Pointer, arr.Length);
- return handle;
- }
- catch
- {
- handle.Dispose();
- throw;
- }
- }
-
- ///
- public int GetFontTextureCount(string texPathFormat) =>
- ExtractResult(this.prebakedTextureWraps[texPathFormat]).Length;
-
- ///
- public TexFile GetTexFile(string texPathFormat, int index) =>
- ExtractResult(ExtractResult(this.texFiles[texPathFormat])[index]);
-
- ///
- public IDalamudTextureWrap NewFontTextureRef(string texPathFormat, int textureIndex)
- {
- lock (this.prebakedTextureWraps[texPathFormat])
- {
- var wraps = ExtractResult(this.prebakedTextureWraps[texPathFormat]);
- var fileIndex = textureIndex / 4;
- var channelIndex = FdtReader.FontTableEntry.TextureChannelOrder[textureIndex % 4];
- wraps[textureIndex] ??= this.GetChannelTexture(texPathFormat, fileIndex, channelIndex);
- return CloneTextureWrap(wraps[textureIndex]);
- }
- }
-
- private static T ExtractResult(Task t) => t.IsCompleted ? t.Result : t.GetAwaiter().GetResult();
-
- private static unsafe void ExtractChannelFromB8G8R8A8(
- Span target,
- ReadOnlySpan source,
- int channelIndex,
- bool targetIsB4G4R4A4)
- {
- var numPixels = Math.Min(source.Length / 4, target.Length / (targetIsB4G4R4A4 ? 2 : 4));
-
- fixed (byte* sourcePtrImmutable = source)
- {
- var rptr = sourcePtrImmutable + channelIndex;
- fixed (void* targetPtr = target)
- {
- if (targetIsB4G4R4A4)
- {
- var wptr = (ushort*)targetPtr;
- while (numPixels-- > 0)
- {
- *wptr = (ushort)((*rptr << 8) | 0x0FFF);
- wptr++;
- rptr += 4;
- }
- }
- else
- {
- var wptr = (uint*)targetPtr;
- while (numPixels-- > 0)
- {
- *wptr = (uint)((*rptr << 24) | 0x00FFFFFF);
- wptr++;
- rptr += 4;
- }
- }
- }
- }
- }
-
- ///
- /// Clones a texture wrap, by getting a new reference to the underlying and the
- /// texture behind.
- ///
- /// The to clone from.
- /// The cloned .
- private static IDalamudTextureWrap CloneTextureWrap(IDalamudTextureWrap wrap)
- {
- var srv = CppObject.FromPointer(wrap.ImGuiHandle);
- using var res = srv.Resource;
- using var tex2D = res.QueryInterface();
- var description = tex2D.Description;
- return new DalamudTextureWrap(
- new D3DTextureWrap(
- srv.QueryInterface(),
- description.Width,
- description.Height));
- }
-
- private static unsafe void ExtractChannelFromB4G4R4A4(
- Span target,
- ReadOnlySpan source,
- int channelIndex,
- bool targetIsB4G4R4A4)
- {
- var numPixels = Math.Min(source.Length / 2, target.Length / (targetIsB4G4R4A4 ? 2 : 4));
- fixed (byte* sourcePtrImmutable = source)
- {
- var rptr = sourcePtrImmutable + (channelIndex / 2);
- var rshift = (channelIndex & 1) == 0 ? 0 : 4;
- fixed (void* targetPtr = target)
- {
- if (targetIsB4G4R4A4)
- {
- var wptr = (ushort*)targetPtr;
- while (numPixels-- > 0)
- {
- *wptr = (ushort)(((*rptr >> rshift) << 12) | 0x0FFF);
- wptr++;
- rptr += 2;
- }
- }
- else
- {
- var wptr = (uint*)targetPtr;
- while (numPixels-- > 0)
- {
- var v = (*rptr >> rshift) & 0xF;
- v |= v << 4;
- *wptr = (uint)((v << 24) | 0x00FFFFFF);
- wptr++;
- rptr += 4;
- }
- }
- }
- }
- }
-
- private IDalamudTextureWrap GetChannelTexture(string texPathFormat, int fileIndex, int channelIndex)
- {
- var texFile = ExtractResult(ExtractResult(this.texFiles[texPathFormat])[fileIndex]);
- var numPixels = texFile.Header.Width * texFile.Header.Height;
-
- _ = Service.Get();
- var targetIsB4G4R4A4 = this.InterfaceManager.SupportsDxgiFormat(Format.B4G4R4A4_UNorm);
- var bpp = targetIsB4G4R4A4 ? 2 : 4;
- var buffer = ArrayPool.Shared.Rent(numPixels * bpp);
- try
- {
- var sliceSpan = texFile.SliceSpan(0, 0, out _, out _, out _);
- switch (texFile.Header.Format)
- {
- case TexFile.TextureFormat.B4G4R4A4:
- // Game ships with this format.
- ExtractChannelFromB4G4R4A4(buffer, sliceSpan, channelIndex, targetIsB4G4R4A4);
- break;
- case TexFile.TextureFormat.B8G8R8A8:
- // In case of modded font textures.
- ExtractChannelFromB8G8R8A8(buffer, sliceSpan, channelIndex, targetIsB4G4R4A4);
- break;
- default:
- // Unlikely.
- ExtractChannelFromB8G8R8A8(buffer, texFile.ImageData, channelIndex, targetIsB4G4R4A4);
- break;
- }
-
- return this.scopedFinalizer.Add(
- this.InterfaceManager.LoadImageFromDxgiFormat(
- buffer,
- texFile.Header.Width * bpp,
- texFile.Header.Width,
- texFile.Header.Height,
- targetIsB4G4R4A4 ? Format.B4G4R4A4_UNorm : Format.B8G8R8A8_UNorm));
- }
- finally
- {
- ArrayPool.Shared.Return(buffer);
- }
- }
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/GamePrebakedFontHandle.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/GamePrebakedFontHandle.cs
deleted file mode 100644
index 99c817a91..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/GamePrebakedFontHandle.cs
+++ /dev/null
@@ -1,857 +0,0 @@
-using System.Buffers;
-using System.Collections;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Reactive.Disposables;
-
-using Dalamud.Game.Text;
-using Dalamud.Interface.GameFonts;
-using Dalamud.Interface.Internal;
-using Dalamud.Interface.Utility;
-using Dalamud.Interface.Utility.Raii;
-using Dalamud.Utility;
-
-using ImGuiNET;
-
-using Lumina.Data.Files;
-
-using Vector4 = System.Numerics.Vector4;
-
-namespace Dalamud.Interface.ManagedFontAtlas.Internals;
-
-///
-/// A font handle that uses the game's built-in fonts, optionally with some styling.
-///
-internal class GamePrebakedFontHandle : IFontHandle.IInternal
-{
- ///
- /// The smallest value of .
- ///
- public static readonly char SeIconCharMin = (char)Enum.GetValues().Min();
-
- ///
- /// The largest value of .
- ///
- public static readonly char SeIconCharMax = (char)Enum.GetValues().Max();
-
- private IFontHandleManager? manager;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// An instance of .
- /// Font to use.
- public GamePrebakedFontHandle(IFontHandleManager manager, GameFontStyle style)
- {
- if (!Enum.IsDefined(style.FamilyAndSize) || style.FamilyAndSize == GameFontFamilyAndSize.Undefined)
- throw new ArgumentOutOfRangeException(nameof(style), style, null);
-
- if (style.SizePt <= 0)
- throw new ArgumentException($"{nameof(style.SizePt)} must be a positive number.", nameof(style));
-
- this.manager = manager;
- this.FontStyle = style;
- }
-
- ///
- /// Provider for for `common/font/fontNN.tex`.
- ///
- public interface IGameFontTextureProvider
- {
- ///
- /// Creates the for the .
- /// Dispose after use.
- ///
- /// The font family and size.
- /// The view.
- /// Dispose this after use..
- public MemoryHandle CreateFdtFileView(GameFontFamilyAndSize gffas, out FdtFileView fdtFileView);
-
- ///
- /// Gets the number of font textures.
- ///
- /// Format of .tex path.
- /// The number of textures.
- public int GetFontTextureCount(string texPathFormat);
-
- ///
- /// Gets the for the given index of a font.
- ///
- /// Format of .tex path.
- /// The index of .tex file.
- /// The .
- public TexFile GetTexFile(string texPathFormat, int index);
-
- ///
- /// Gets a new reference of the font texture.
- ///
- /// Format of .tex path.
- /// Texture index.
- /// The texture.
- public IDalamudTextureWrap NewFontTextureRef(string texPathFormat, int textureIndex);
- }
-
- ///
- /// Gets the font style.
- ///
- public GameFontStyle FontStyle { get; }
-
- ///
- public Exception? LoadException => this.ManagerNotDisposed.Substance?.GetBuildException(this);
-
- ///
- public bool Available => this.ImFont.IsNotNullAndLoaded();
-
- ///
- public ImFontPtr ImFont => this.ManagerNotDisposed.Substance?.GetFontPtr(this) ?? default;
-
- private IFontHandleManager ManagerNotDisposed =>
- this.manager ?? throw new ObjectDisposedException(nameof(GamePrebakedFontHandle));
-
- ///
- public void Dispose()
- {
- this.manager?.FreeFontHandle(this);
- this.manager = null;
- }
-
- ///
- public IDisposable Push() => ImRaii.PushFont(this.ImFont, this.Available);
-
- ///
- /// Manager for s.
- ///
- internal sealed class HandleManager : IFontHandleManager
- {
- private readonly Dictionary gameFontsRc = new();
- private readonly object syncRoot = new();
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The name of the owner atlas.
- /// An instance of .
- public HandleManager(string atlasName, IGameFontTextureProvider gameFontTextureProvider)
- {
- this.GameFontTextureProvider = gameFontTextureProvider;
- this.Name = $"{atlasName}:{nameof(GamePrebakedFontHandle)}:Manager";
- }
-
- ///
- public event Action? RebuildRecommend;
-
- ///
- public string Name { get; }
-
- ///
- public IFontHandleSubstance? Substance { get; set; }
-
- ///
- /// Gets an instance of .
- ///
- public IGameFontTextureProvider GameFontTextureProvider { get; }
-
- ///
- public void Dispose()
- {
- this.Substance?.Dispose();
- this.Substance = null;
- }
-
- ///
- public IFontHandle NewFontHandle(GameFontStyle style)
- {
- var handle = new GamePrebakedFontHandle(this, style);
- bool suggestRebuild;
- lock (this.syncRoot)
- {
- this.gameFontsRc[style] = this.gameFontsRc.GetValueOrDefault(style, 0) + 1;
- suggestRebuild = this.Substance?.GetFontPtr(handle).IsNotNullAndLoaded() is not true;
- }
-
- if (suggestRebuild)
- this.RebuildRecommend?.Invoke();
-
- return handle;
- }
-
- ///
- public void FreeFontHandle(IFontHandle handle)
- {
- if (handle is not GamePrebakedFontHandle ggfh)
- return;
-
- lock (this.syncRoot)
- {
- if (!this.gameFontsRc.ContainsKey(ggfh.FontStyle))
- return;
-
- if ((this.gameFontsRc[ggfh.FontStyle] -= 1) == 0)
- this.gameFontsRc.Remove(ggfh.FontStyle);
- }
- }
-
- ///
- public IFontHandleSubstance NewSubstance()
- {
- lock (this.syncRoot)
- return new HandleSubstance(this, this.gameFontsRc.Keys);
- }
- }
-
- ///
- /// Substance from .
- ///
- internal sealed class HandleSubstance : IFontHandleSubstance
- {
- private readonly HandleManager handleManager;
- private readonly HashSet gameFontStyles;
-
- // Owned by this class, but ImFontPtr values still do not belong to this.
- private readonly Dictionary fonts = new();
- private readonly Dictionary buildExceptions = new();
- private readonly List<(ImFontPtr Font, GameFontStyle Style, ushort[]? Ranges)> attachments = new();
-
- private readonly HashSet templatedFonts = new();
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The manager.
- /// The game font styles.
- public HandleSubstance(HandleManager manager, IEnumerable gameFontStyles)
- {
- this.handleManager = manager;
- Service.Get();
- this.gameFontStyles = new(gameFontStyles);
- }
-
- ///
- public IFontHandleManager Manager => this.handleManager;
-
- ///
- public void Dispose()
- {
- }
-
- ///
- /// Attaches game symbols to the given font. If font is null, it will be created.
- ///
- /// The toolkitPostBuild.
- /// The font to attach to.
- /// The game font style.
- /// The intended glyph ranges.
- /// if it is not empty; otherwise a new font.
- public ImFontPtr AttachGameGlyphs(
- IFontAtlasBuildToolkitPreBuild toolkitPreBuild,
- ImFontPtr font,
- GameFontStyle style,
- ushort[]? glyphRanges = null)
- {
- if (font.IsNull())
- font = this.CreateTemplateFont(toolkitPreBuild, style.SizePx);
- this.attachments.Add((font, style, glyphRanges));
- return font;
- }
-
- ///
- /// Creates or gets a relevant for the given .
- ///
- /// The game font style.
- /// The toolkitPostBuild.
- /// The font.
- public ImFontPtr GetOrCreateFont(GameFontStyle style, IFontAtlasBuildToolkitPreBuild toolkitPreBuild)
- {
- try
- {
- if (!this.fonts.TryGetValue(style, out var plan))
- {
- plan = new(
- style,
- toolkitPreBuild.Scale,
- this.handleManager.GameFontTextureProvider,
- this.CreateTemplateFont(toolkitPreBuild, style.SizePx));
- this.fonts[style] = plan;
- }
-
- plan.AttachFont(plan.FullRangeFont);
- return plan.FullRangeFont;
- }
- catch (Exception e)
- {
- this.buildExceptions[style] = e;
- throw;
- }
- }
-
- ///
- public ImFontPtr GetFontPtr(IFontHandle handle) =>
- handle is GamePrebakedFontHandle ggfh
- ? this.fonts.GetValueOrDefault(ggfh.FontStyle)?.FullRangeFont ?? default
- : default;
-
- ///
- public Exception? GetBuildException(IFontHandle handle) =>
- handle is GamePrebakedFontHandle ggfh ? this.buildExceptions.GetValueOrDefault(ggfh.FontStyle) : default;
-
- ///
- public void OnPreBuild(IFontAtlasBuildToolkitPreBuild toolkitPreBuild)
- {
- foreach (var style in this.gameFontStyles)
- {
- if (this.fonts.ContainsKey(style))
- continue;
-
- try
- {
- _ = this.GetOrCreateFont(style, toolkitPreBuild);
- }
- catch
- {
- // ignore; it should have been recorded from the call
- }
- }
- }
-
- ///
- public void OnPreBuildCleanup(IFontAtlasBuildToolkitPreBuild toolkitPreBuild)
- {
- foreach (var (font, style, ranges) in this.attachments)
- {
- var effectiveStyle =
- toolkitPreBuild.IsGlobalScaleIgnored(font)
- ? style.Scale(1 / toolkitPreBuild.Scale)
- : style;
- if (!this.fonts.TryGetValue(style, out var plan))
- {
- plan = new(
- effectiveStyle,
- toolkitPreBuild.Scale,
- this.handleManager.GameFontTextureProvider,
- this.CreateTemplateFont(toolkitPreBuild, style.SizePx));
- this.fonts[style] = plan;
- }
-
- plan.AttachFont(font, ranges);
- }
-
- foreach (var plan in this.fonts.Values)
- {
- plan.EnsureGlyphs(toolkitPreBuild.NewImAtlas);
- }
- }
-
- ///
- public unsafe void OnPostBuild(IFontAtlasBuildToolkitPostBuild toolkitPostBuild)
- {
- var allTextureIndices = new Dictionary();
- var allTexFiles = new Dictionary();
- using var rentReturn = Disposable.Create(
- () =>
- {
- foreach (var x in allTextureIndices.Values)
- ArrayPool.Shared.Return(x);
- foreach (var x in allTexFiles.Values)
- ArrayPool.Shared.Return(x);
- });
-
- var pixels8Array = new byte*[toolkitPostBuild.NewImAtlas.Textures.Size];
- var widths = new int[toolkitPostBuild.NewImAtlas.Textures.Size];
- for (var i = 0; i < pixels8Array.Length; i++)
- toolkitPostBuild.NewImAtlas.GetTexDataAsAlpha8(i, out pixels8Array[i], out widths[i], out _);
-
- foreach (var (style, plan) in this.fonts)
- {
- try
- {
- foreach (var font in plan.Ranges.Keys)
- this.PatchFontMetricsIfNecessary(style, font, toolkitPostBuild.Scale);
-
- plan.SetFullRangeFontGlyphs(toolkitPostBuild, allTexFiles, allTextureIndices, pixels8Array, widths);
- plan.CopyGlyphsToRanges(toolkitPostBuild);
- plan.PostProcessFullRangeFont(toolkitPostBuild.Scale);
- }
- catch (Exception e)
- {
- this.buildExceptions[style] = e;
- this.fonts[style] = default;
- }
- }
- }
-
- ///
- public void OnPostPromotion(IFontAtlasBuildToolkitPostPromotion toolkitPostPromotion)
- {
- // Irrelevant
- }
-
- ///
- /// Creates a new template font.
- ///
- /// The toolkitPostBuild.
- /// The size of the font.
- /// The font.
- private ImFontPtr CreateTemplateFont(IFontAtlasBuildToolkitPreBuild toolkitPreBuild, float sizePx)
- {
- var font = toolkitPreBuild.AddDalamudAssetFont(
- DalamudAsset.NotoSansJpMedium,
- new()
- {
- GlyphRanges = new ushort[] { ' ', ' ', '\0' },
- SizePx = sizePx,
- });
- this.templatedFonts.Add(font);
- return font;
- }
-
- private unsafe void PatchFontMetricsIfNecessary(GameFontStyle style, ImFontPtr font, float atlasScale)
- {
- if (!this.templatedFonts.Contains(font))
- return;
-
- var fas = style.Scale(atlasScale).FamilyAndSize;
- using var handle = this.handleManager.GameFontTextureProvider.CreateFdtFileView(fas, out var fdt);
- ref var fdtFontHeader = ref fdt.FontHeader;
- var fontPtr = font.NativePtr;
-
- var scale = style.SizePt / fdtFontHeader.Size;
- fontPtr->Ascent = fdtFontHeader.Ascent * scale;
- fontPtr->Descent = fdtFontHeader.Descent * scale;
- fontPtr->EllipsisChar = '…';
- }
- }
-
- [SuppressMessage(
- "StyleCop.CSharp.MaintainabilityRules",
- "SA1401:Fields should be private",
- Justification = "Internal")]
- private sealed class FontDrawPlan : IDisposable
- {
- public readonly GameFontStyle Style;
- public readonly GameFontStyle BaseStyle;
- public readonly GameFontFamilyAndSizeAttribute BaseAttr;
- public readonly int TexCount;
- public readonly Dictionary Ranges = new();
- public readonly List<(int RectId, int FdtGlyphIndex)> Rects = new();
- public readonly ushort[] RectLookup = new ushort[0x10000];
- public readonly FdtFileView Fdt;
- public readonly ImFontPtr FullRangeFont;
-
- private readonly IDisposable fdtHandle;
- private readonly IGameFontTextureProvider gftp;
-
- public FontDrawPlan(
- GameFontStyle style,
- float scale,
- IGameFontTextureProvider gameFontTextureProvider,
- ImFontPtr fullRangeFont)
- {
- this.Style = style;
- this.BaseStyle = style.Scale(scale);
- this.BaseAttr = this.BaseStyle.FamilyAndSize.GetAttribute()!;
- this.gftp = gameFontTextureProvider;
- this.TexCount = this.gftp.GetFontTextureCount(this.BaseAttr.TexPathFormat);
- this.fdtHandle = this.gftp.CreateFdtFileView(this.BaseStyle.FamilyAndSize, out this.Fdt);
- this.RectLookup.AsSpan().Fill(ushort.MaxValue);
- this.FullRangeFont = fullRangeFont;
- this.Ranges[fullRangeFont] = new(0x10000);
- }
-
- public void Dispose()
- {
- this.fdtHandle.Dispose();
- }
-
- public void AttachFont(ImFontPtr font, ushort[]? glyphRanges = null)
- {
- if (!this.Ranges.TryGetValue(font, out var rangeBitArray))
- rangeBitArray = this.Ranges[font] = new(0x10000);
-
- if (glyphRanges is null)
- {
- foreach (ref var g in this.Fdt.Glyphs)
- {
- var c = g.CharInt;
- if (c is >= 0x20 and <= 0xFFFE)
- rangeBitArray[c] = true;
- }
-
- return;
- }
-
- for (var i = 0; i < glyphRanges.Length - 1; i += 2)
- {
- if (glyphRanges[i] == 0)
- break;
- var from = (int)glyphRanges[i];
- var to = (int)glyphRanges[i + 1];
- for (var j = from; j <= to; j++)
- rangeBitArray[j] = true;
- }
- }
-
- public unsafe void EnsureGlyphs(ImFontAtlasPtr atlas)
- {
- var glyphs = this.Fdt.Glyphs;
- var ranges = this.Ranges[this.FullRangeFont];
- foreach (var (font, extraRange) in this.Ranges)
- {
- if (font.NativePtr != this.FullRangeFont.NativePtr)
- ranges.Or(extraRange);
- }
-
- if (this.Style is not { Weight: 0, SkewStrength: 0 })
- {
- for (var fdtGlyphIndex = 0; fdtGlyphIndex < glyphs.Length; fdtGlyphIndex++)
- {
- ref var glyph = ref glyphs[fdtGlyphIndex];
- var cint = glyph.CharInt;
- if (cint > char.MaxValue)
- continue;
- if (!ranges[cint] || this.RectLookup[cint] != ushort.MaxValue)
- continue;
-
- var widthAdjustment = this.BaseStyle.CalculateBaseWidthAdjustment(this.Fdt.FontHeader, glyph);
- this.RectLookup[cint] = (ushort)this.Rects.Count;
- this.Rects.Add(
- (
- atlas.AddCustomRectFontGlyph(
- this.FullRangeFont,
- (char)cint,
- glyph.BoundingWidth + widthAdjustment,
- glyph.BoundingHeight,
- glyph.AdvanceWidth,
- new(this.BaseAttr.HorizontalOffset, glyph.CurrentOffsetY)),
- fdtGlyphIndex));
- }
- }
- else
- {
- for (var fdtGlyphIndex = 0; fdtGlyphIndex < glyphs.Length; fdtGlyphIndex++)
- {
- ref var glyph = ref glyphs[fdtGlyphIndex];
- var cint = glyph.CharInt;
- if (cint > char.MaxValue)
- continue;
- if (!ranges[cint] || this.RectLookup[cint] != ushort.MaxValue)
- continue;
-
- this.RectLookup[cint] = (ushort)this.Rects.Count;
- this.Rects.Add((-1, fdtGlyphIndex));
- }
- }
- }
-
- public unsafe void PostProcessFullRangeFont(float atlasScale)
- {
- var round = 1 / atlasScale;
- var pfrf = this.FullRangeFont.NativePtr;
- ref var frf = ref *pfrf;
-
- frf.FontSize = MathF.Round(frf.FontSize / round) * round;
- frf.Ascent = MathF.Round(frf.Ascent / round) * round;
- frf.Descent = MathF.Round(frf.Descent / round) * round;
-
- var scale = this.Style.SizePt / this.Fdt.FontHeader.Size;
- foreach (ref var g in this.FullRangeFont.GlyphsWrapped().DataSpan)
- {
- var w = (g.X1 - g.X0) * scale;
- var h = (g.Y1 - g.Y0) * scale;
- g.X0 = MathF.Round((g.X0 * scale) / round) * round;
- g.Y0 = MathF.Round((g.Y0 * scale) / round) * round;
- g.X1 = g.X0 + w;
- g.Y1 = g.Y0 + h;
- g.AdvanceX = MathF.Round((g.AdvanceX * scale) / round) * round;
- }
-
- var fullRange = this.Ranges[this.FullRangeFont];
- foreach (ref var k in this.Fdt.PairAdjustments)
- {
- var (leftInt, rightInt) = (k.LeftInt, k.RightInt);
- if (leftInt > char.MaxValue || rightInt > char.MaxValue)
- continue;
- if (!fullRange[leftInt] || !fullRange[rightInt])
- continue;
- ImGuiNative.ImFont_AddKerningPair(
- pfrf,
- (ushort)leftInt,
- (ushort)rightInt,
- MathF.Round((k.RightOffset * scale) / round) * round);
- }
-
- pfrf->FallbackGlyph = null;
- ImGuiNative.ImFont_BuildLookupTable(pfrf);
-
- foreach (var fallbackCharCandidate in FontAtlasFactory.FallbackCodepoints)
- {
- var glyph = ImGuiNative.ImFont_FindGlyphNoFallback(pfrf, fallbackCharCandidate);
- if ((nint)glyph == IntPtr.Zero)
- continue;
- frf.FallbackChar = fallbackCharCandidate;
- frf.FallbackGlyph = glyph;
- frf.FallbackHotData =
- (ImFontGlyphHotData*)frf.IndexedHotData.Address(
- fallbackCharCandidate);
- break;
- }
- }
-
- public unsafe void CopyGlyphsToRanges(IFontAtlasBuildToolkitPostBuild toolkitPostBuild)
- {
- var scale = this.Style.SizePt / this.Fdt.FontHeader.Size;
- var atlasScale = toolkitPostBuild.Scale;
- var round = 1 / atlasScale;
-
- foreach (var (font, rangeBits) in this.Ranges)
- {
- if (font.NativePtr == this.FullRangeFont.NativePtr)
- continue;
-
- var noGlobalScale = toolkitPostBuild.IsGlobalScaleIgnored(font);
-
- var lookup = font.IndexLookupWrapped();
- var glyphs = font.GlyphsWrapped();
- foreach (ref var sourceGlyph in this.FullRangeFont.GlyphsWrapped().DataSpan)
- {
- if (!rangeBits[sourceGlyph.Codepoint])
- continue;
-
- var glyphIndex = ushort.MaxValue;
- if (sourceGlyph.Codepoint < lookup.Length)
- glyphIndex = lookup[sourceGlyph.Codepoint];
-
- if (glyphIndex == ushort.MaxValue)
- {
- glyphIndex = (ushort)glyphs.Length;
- glyphs.Add(default);
- }
-
- ref var g = ref glyphs[glyphIndex];
- g = sourceGlyph;
- if (noGlobalScale)
- {
- g.XY *= scale;
- g.AdvanceX *= scale;
- }
- else
- {
- var w = (g.X1 - g.X0) * scale;
- var h = (g.Y1 - g.Y0) * scale;
- g.X0 = MathF.Round((g.X0 * scale) / round) * round;
- g.Y0 = MathF.Round((g.Y0 * scale) / round) * round;
- g.X1 = g.X0 + w;
- g.Y1 = g.Y0 + h;
- g.AdvanceX = MathF.Round((g.AdvanceX * scale) / round) * round;
- }
- }
-
- foreach (ref var k in this.Fdt.PairAdjustments)
- {
- var (leftInt, rightInt) = (k.LeftInt, k.RightInt);
- if (leftInt > char.MaxValue || rightInt > char.MaxValue)
- continue;
- if (!rangeBits[leftInt] || !rangeBits[rightInt])
- continue;
- if (noGlobalScale)
- {
- font.AddKerningPair((ushort)leftInt, (ushort)rightInt, k.RightOffset * scale);
- }
- else
- {
- font.AddKerningPair(
- (ushort)leftInt,
- (ushort)rightInt,
- MathF.Round((k.RightOffset * scale) / round) * round);
- }
- }
-
- font.NativePtr->FallbackGlyph = null;
- font.BuildLookupTable();
-
- foreach (var fallbackCharCandidate in FontAtlasFactory.FallbackCodepoints)
- {
- var glyph = font.FindGlyphNoFallback(fallbackCharCandidate).NativePtr;
- if ((nint)glyph == IntPtr.Zero)
- continue;
-
- ref var frf = ref *font.NativePtr;
- frf.FallbackChar = fallbackCharCandidate;
- frf.FallbackGlyph = glyph;
- frf.FallbackHotData =
- (ImFontGlyphHotData*)frf.IndexedHotData.Address(
- fallbackCharCandidate);
- break;
- }
- }
- }
-
- public unsafe void SetFullRangeFontGlyphs(
- IFontAtlasBuildToolkitPostBuild toolkitPostBuild,
- Dictionary allTexFiles,
- Dictionary allTextureIndices,
- byte*[] pixels8Array,
- int[] widths)
- {
- var glyphs = this.FullRangeFont.GlyphsWrapped();
- var lookups = this.FullRangeFont.IndexLookupWrapped();
-
- ref var fdtFontHeader = ref this.Fdt.FontHeader;
- var fdtGlyphs = this.Fdt.Glyphs;
- var fdtTexSize = new Vector4(
- this.Fdt.FontHeader.TextureWidth,
- this.Fdt.FontHeader.TextureHeight,
- this.Fdt.FontHeader.TextureWidth,
- this.Fdt.FontHeader.TextureHeight);
-
- if (!allTexFiles.TryGetValue(this.BaseAttr.TexPathFormat, out var texFiles))
- {
- allTexFiles.Add(
- this.BaseAttr.TexPathFormat,
- texFiles = ArrayPool.Shared.Rent(this.TexCount));
- }
-
- if (!allTextureIndices.TryGetValue(this.BaseAttr.TexPathFormat, out var textureIndices))
- {
- allTextureIndices.Add(
- this.BaseAttr.TexPathFormat,
- textureIndices = ArrayPool.Shared.Rent(this.TexCount));
- textureIndices.AsSpan(0, this.TexCount).Fill(-1);
- }
-
- var pixelWidth = Math.Max(1, (int)MathF.Ceiling(this.BaseStyle.Weight + 1));
- var pixelStrength = stackalloc byte[pixelWidth];
- for (var i = 0; i < pixelWidth; i++)
- pixelStrength[i] = (byte)(255 * Math.Min(1f, (this.BaseStyle.Weight + 1) - i));
-
- var minGlyphY = 0;
- var maxGlyphY = 0;
- foreach (ref var g in fdtGlyphs)
- {
- minGlyphY = Math.Min(g.CurrentOffsetY, minGlyphY);
- maxGlyphY = Math.Max(g.BoundingHeight + g.CurrentOffsetY, maxGlyphY);
- }
-
- var horzShift = stackalloc int[maxGlyphY - minGlyphY];
- var horzBlend = stackalloc byte[maxGlyphY - minGlyphY];
- horzShift -= minGlyphY;
- horzBlend -= minGlyphY;
- if (this.BaseStyle.BaseSkewStrength != 0)
- {
- for (var i = minGlyphY; i < maxGlyphY; i++)
- {
- float blend = this.BaseStyle.BaseSkewStrength switch
- {
- > 0 => fdtFontHeader.LineHeight - i,
- < 0 => -i,
- _ => throw new InvalidOperationException(),
- };
- blend *= this.BaseStyle.BaseSkewStrength / fdtFontHeader.LineHeight;
- horzShift[i] = (int)MathF.Floor(blend);
- horzBlend[i] = (byte)(255 * (blend - horzShift[i]));
- }
- }
-
- foreach (var (rectId, fdtGlyphIndex) in this.Rects)
- {
- ref var fdtGlyph = ref fdtGlyphs[fdtGlyphIndex];
- if (rectId == -1)
- {
- ref var textureIndex = ref textureIndices[fdtGlyph.TextureIndex];
- if (textureIndex == -1)
- {
- textureIndex = toolkitPostBuild.StoreTexture(
- this.gftp.NewFontTextureRef(this.BaseAttr.TexPathFormat, fdtGlyph.TextureIndex),
- true);
- }
-
- var glyph = new ImGuiHelpers.ImFontGlyphReal
- {
- AdvanceX = fdtGlyph.AdvanceWidth,
- Codepoint = fdtGlyph.Char,
- Colored = false,
- TextureIndex = textureIndex,
- Visible = true,
- X0 = this.BaseAttr.HorizontalOffset,
- Y0 = fdtGlyph.CurrentOffsetY,
- U0 = fdtGlyph.TextureOffsetX,
- V0 = fdtGlyph.TextureOffsetY,
- U1 = fdtGlyph.BoundingWidth,
- V1 = fdtGlyph.BoundingHeight,
- };
-
- glyph.XY1 = glyph.XY0 + glyph.UV1;
- glyph.UV1 += glyph.UV0;
- glyph.UV /= fdtTexSize;
-
- glyphs.Add(glyph);
- }
- else
- {
- ref var rc = ref *(ImGuiHelpers.ImFontAtlasCustomRectReal*)toolkitPostBuild.NewImAtlas
- .GetCustomRectByIndex(rectId)
- .NativePtr;
- var widthAdjustment = this.BaseStyle.CalculateBaseWidthAdjustment(fdtFontHeader, fdtGlyph);
-
- // Glyph is scaled at this point; undo that.
- ref var glyph = ref glyphs[lookups[rc.GlyphId]];
- glyph.X0 = this.BaseAttr.HorizontalOffset;
- glyph.Y0 = fdtGlyph.CurrentOffsetY;
- glyph.X1 = glyph.X0 + fdtGlyph.BoundingWidth + widthAdjustment;
- glyph.Y1 = glyph.Y0 + fdtGlyph.BoundingHeight;
- glyph.AdvanceX = fdtGlyph.AdvanceWidth;
-
- var pixels8 = pixels8Array[rc.TextureIndex];
- var width = widths[rc.TextureIndex];
- texFiles[fdtGlyph.TextureFileIndex] ??=
- this.gftp.GetTexFile(this.BaseAttr.TexPathFormat, fdtGlyph.TextureFileIndex);
- var sourceBuffer = texFiles[fdtGlyph.TextureFileIndex].ImageData;
- var sourceBufferDelta = fdtGlyph.TextureChannelByteIndex;
-
- for (var y = 0; y < fdtGlyph.BoundingHeight; y++)
- {
- var sourcePixelIndex =
- ((fdtGlyph.TextureOffsetY + y) * fdtFontHeader.TextureWidth) + fdtGlyph.TextureOffsetX;
- sourcePixelIndex *= 4;
- sourcePixelIndex += sourceBufferDelta;
- var blend1 = horzBlend[fdtGlyph.CurrentOffsetY + y];
-
- var targetOffset = ((rc.Y + y) * width) + rc.X;
- for (var x = 0; x < rc.Width; x++)
- pixels8[targetOffset + x] = 0;
-
- targetOffset += horzShift[fdtGlyph.CurrentOffsetY + y];
- if (blend1 == 0)
- {
- for (var x = 0; x < fdtGlyph.BoundingWidth; x++, sourcePixelIndex += 4, targetOffset++)
- {
- var n = sourceBuffer[sourcePixelIndex + 4];
- for (var boldOffset = 0; boldOffset < pixelWidth; boldOffset++)
- {
- ref var p = ref pixels8[targetOffset + boldOffset];
- p = Math.Max(p, (byte)((pixelStrength[boldOffset] * n) / 255));
- }
- }
- }
- else
- {
- var blend2 = 255 - blend1;
- for (var x = 0; x < fdtGlyph.BoundingWidth; x++, sourcePixelIndex += 4, targetOffset++)
- {
- var a1 = sourceBuffer[sourcePixelIndex];
- var a2 = x == fdtGlyph.BoundingWidth - 1 ? 0 : sourceBuffer[sourcePixelIndex + 4];
- var n = (a1 * blend1) + (a2 * blend2);
-
- for (var boldOffset = 0; boldOffset < pixelWidth; boldOffset++)
- {
- ref var p = ref pixels8[targetOffset + boldOffset];
- p = Math.Max(p, (byte)((pixelStrength[boldOffset] * n) / 255 / 255));
- }
- }
- }
- }
- }
- }
- }
- }
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/IFontHandleManager.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/IFontHandleManager.cs
deleted file mode 100644
index 93c688608..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/IFontHandleManager.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-namespace Dalamud.Interface.ManagedFontAtlas.Internals;
-
-///
-/// Manager for .
-///
-internal interface IFontHandleManager : IDisposable
-{
- ///
- event Action? RebuildRecommend;
-
- ///
- /// Gets the name of the font handle manager. For logging and debugging purposes.
- ///
- string Name { get; }
-
- ///
- /// Gets or sets the active font handle substance.
- ///
- IFontHandleSubstance? Substance { get; set; }
-
- ///
- /// Decrease font reference counter.
- ///
- /// Handle being released.
- void FreeFontHandle(IFontHandle handle);
-
- ///
- /// Creates a new substance of the font atlas.
- ///
- /// The new substance.
- IFontHandleSubstance NewSubstance();
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/IFontHandleSubstance.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/IFontHandleSubstance.cs
deleted file mode 100644
index f6c5c6591..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/IFontHandleSubstance.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using ImGuiNET;
-
-namespace Dalamud.Interface.ManagedFontAtlas.Internals;
-
-///
-/// Substance of a font.
-///
-internal interface IFontHandleSubstance : IDisposable
-{
- ///
- /// Gets the manager relevant to this instance of .
- ///
- IFontHandleManager Manager { get; }
-
- ///
- /// Gets the font.
- ///
- /// The handle to get from.
- /// Corresponding font or null.
- ImFontPtr GetFontPtr(IFontHandle handle);
-
- ///
- /// Gets the exception happened while loading for the font.
- ///
- /// The handle to get from.
- /// Corresponding font or null.
- Exception? GetBuildException(IFontHandle handle);
-
- ///
- /// Called before call.
- ///
- /// The toolkit.
- void OnPreBuild(IFontAtlasBuildToolkitPreBuild toolkitPreBuild);
-
- ///
- /// Called between and calls.
- /// Any further modification to will result in undefined behavior.
- ///
- /// The toolkit.
- void OnPreBuildCleanup(IFontAtlasBuildToolkitPreBuild toolkitPreBuild);
-
- ///
- /// Called after call.
- ///
- /// The toolkit.
- void OnPostBuild(IFontAtlasBuildToolkitPostBuild toolkitPostBuild);
-
- ///
- /// Called on the specific thread depending on after
- /// promoting the staging atlas to direct use with .
- ///
- /// The toolkit.
- void OnPostPromotion(IFontAtlasBuildToolkitPostPromotion toolkitPostPromotion);
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.Common.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.Common.cs
deleted file mode 100644
index 8e7149853..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.Common.cs
+++ /dev/null
@@ -1,203 +0,0 @@
-using System.Buffers.Binary;
-using System.Runtime.InteropServices;
-using System.Text;
-
-namespace Dalamud.Interface.ManagedFontAtlas.Internals;
-
-///
-/// Deals with TrueType.
-///
-internal static partial class TrueTypeUtils
-{
- private struct Fixed : IComparable
- {
- public ushort Major;
- public ushort Minor;
-
- public Fixed(ushort major, ushort minor)
- {
- this.Major = major;
- this.Minor = minor;
- }
-
- public Fixed(PointerSpan span)
- {
- var offset = 0;
- span.ReadBig(ref offset, out this.Major);
- span.ReadBig(ref offset, out this.Minor);
- }
-
- public int CompareTo(Fixed other)
- {
- var majorComparison = this.Major.CompareTo(other.Major);
- return majorComparison != 0 ? majorComparison : this.Minor.CompareTo(other.Minor);
- }
- }
-
- private struct KerningPair : IEquatable
- {
- public ushort Left;
- public ushort Right;
- public short Value;
-
- public KerningPair(PointerSpan span)
- {
- var offset = 0;
- span.ReadBig(ref offset, out this.Left);
- span.ReadBig(ref offset, out this.Right);
- span.ReadBig(ref offset, out this.Value);
- }
-
- public KerningPair(ushort left, ushort right, short value)
- {
- this.Left = left;
- this.Right = right;
- this.Value = value;
- }
-
- public static bool operator ==(KerningPair left, KerningPair right) => left.Equals(right);
-
- public static bool operator !=(KerningPair left, KerningPair right) => !left.Equals(right);
-
- public static KerningPair ReverseEndianness(KerningPair pair) => new()
- {
- Left = BinaryPrimitives.ReverseEndianness(pair.Left),
- Right = BinaryPrimitives.ReverseEndianness(pair.Right),
- Value = BinaryPrimitives.ReverseEndianness(pair.Value),
- };
-
- public bool Equals(KerningPair other) =>
- this.Left == other.Left && this.Right == other.Right && this.Value == other.Value;
-
- public override bool Equals(object? obj) => obj is KerningPair other && this.Equals(other);
-
- public override int GetHashCode() => HashCode.Combine(this.Left, this.Right, this.Value);
-
- public override string ToString() => $"KerningPair[{this.Left}, {this.Right}] = {this.Value}";
- }
-
- [StructLayout(LayoutKind.Explicit, Size = 4)]
- private struct PlatformAndEncoding
- {
- [FieldOffset(0)]
- public PlatformId Platform;
-
- [FieldOffset(2)]
- public UnicodeEncodingId UnicodeEncoding;
-
- [FieldOffset(2)]
- public MacintoshEncodingId MacintoshEncoding;
-
- [FieldOffset(2)]
- public IsoEncodingId IsoEncoding;
-
- [FieldOffset(2)]
- public WindowsEncodingId WindowsEncoding;
-
- public PlatformAndEncoding(PointerSpan source)
- {
- var offset = 0;
- source.ReadBig(ref offset, out this.Platform);
- source.ReadBig(ref offset, out this.UnicodeEncoding);
- }
-
- public static PlatformAndEncoding ReverseEndianness(PlatformAndEncoding value) => new()
- {
- Platform = (PlatformId)BinaryPrimitives.ReverseEndianness((ushort)value.Platform),
- UnicodeEncoding = (UnicodeEncodingId)BinaryPrimitives.ReverseEndianness((ushort)value.UnicodeEncoding),
- };
-
- public readonly string Decode(Span data)
- {
- switch (this.Platform)
- {
- case PlatformId.Unicode:
- switch (this.UnicodeEncoding)
- {
- case UnicodeEncodingId.Unicode_2_0_Bmp:
- case UnicodeEncodingId.Unicode_2_0_Full:
- return Encoding.BigEndianUnicode.GetString(data);
- }
-
- break;
-
- case PlatformId.Macintosh:
- switch (this.MacintoshEncoding)
- {
- case MacintoshEncodingId.Roman:
- return Encoding.ASCII.GetString(data);
- }
-
- break;
-
- case PlatformId.Windows:
- switch (this.WindowsEncoding)
- {
- case WindowsEncodingId.Symbol:
- case WindowsEncodingId.UnicodeBmp:
- case WindowsEncodingId.UnicodeFullRepertoire:
- return Encoding.BigEndianUnicode.GetString(data);
- }
-
- break;
- }
-
- throw new NotSupportedException();
- }
- }
-
- [StructLayout(LayoutKind.Explicit)]
- private struct TagStruct : IEquatable, IComparable
- {
- [FieldOffset(0)]
- public unsafe fixed byte Tag[4];
-
- [FieldOffset(0)]
- public uint NativeValue;
-
- public unsafe TagStruct(char c1, char c2, char c3, char c4)
- {
- this.Tag[0] = checked((byte)c1);
- this.Tag[1] = checked((byte)c2);
- this.Tag[2] = checked((byte)c3);
- this.Tag[3] = checked((byte)c4);
- }
-
- public unsafe TagStruct(PointerSpan span)
- {
- this.Tag[0] = span[0];
- this.Tag[1] = span[1];
- this.Tag[2] = span[2];
- this.Tag[3] = span[3];
- }
-
- public unsafe TagStruct(ReadOnlySpan span)
- {
- this.Tag[0] = span[0];
- this.Tag[1] = span[1];
- this.Tag[2] = span[2];
- this.Tag[3] = span[3];
- }
-
- public unsafe byte this[int index]
- {
- get => this.Tag[index];
- set => this.Tag[index] = value;
- }
-
- public static bool operator ==(TagStruct left, TagStruct right) => left.Equals(right);
-
- public static bool operator !=(TagStruct left, TagStruct right) => !left.Equals(right);
-
- public bool Equals(TagStruct other) => this.NativeValue == other.NativeValue;
-
- public override bool Equals(object? obj) => obj is TagStruct other && this.Equals(other);
-
- public override int GetHashCode() => (int)this.NativeValue;
-
- public int CompareTo(TagStruct other) => this.NativeValue.CompareTo(other.NativeValue);
-
- public override unsafe string ToString() =>
- $"0x{this.NativeValue:08X} \"{(char)this.Tag[0]}{(char)this.Tag[1]}{(char)this.Tag[2]}{(char)this.Tag[3]}\"";
- }
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.Enums.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.Enums.cs
deleted file mode 100644
index f6a653a51..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.Enums.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-
-namespace Dalamud.Interface.ManagedFontAtlas.Internals;
-
-///
-/// Deals with TrueType.
-///
-internal static partial class TrueTypeUtils
-{
- [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Version name in enum value names")]
- private enum IsoEncodingId : ushort
- {
- Ascii = 0,
- Iso_10646 = 1,
- Iso_8859_1 = 2,
- }
-
- private enum MacintoshEncodingId : ushort
- {
- Roman = 0,
- }
-
- private enum NameId : ushort
- {
- CopyrightNotice = 0,
- FamilyName = 1,
- SubfamilyName = 2,
- UniqueId = 3,
- FullFontName = 4,
- VersionString = 5,
- PostScriptName = 6,
- Trademark = 7,
- Manufacturer = 8,
- Designer = 9,
- Description = 10,
- UrlVendor = 11,
- UrlDesigner = 12,
- LicenseDescription = 13,
- LicenseInfoUrl = 14,
- TypographicFamilyName = 16,
- TypographicSubfamilyName = 17,
- CompatibleFullMac = 18,
- SampleText = 19,
- PoscSriptCidFindFontName = 20,
- WwsFamilyName = 21,
- WwsSubfamilyName = 22,
- LightBackgroundPalette = 23,
- DarkBackgroundPalette = 24,
- VariationPostScriptNamePrefix = 25,
- }
-
- private enum PlatformId : ushort
- {
- Unicode = 0,
- Macintosh = 1, // discouraged
- Iso = 2, // deprecated
- Windows = 3,
- Custom = 4, // OTF Windows NT compatibility mapping
- }
-
- [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Version name in enum value names")]
- private enum UnicodeEncodingId : ushort
- {
- Unicode_1_0 = 0, // deprecated
- Unicode_1_1 = 1, // deprecated
- IsoIec_10646 = 2, // deprecated
- Unicode_2_0_Bmp = 3,
- Unicode_2_0_Full = 4,
- UnicodeVariationSequences = 5,
- UnicodeFullRepertoire = 6,
- }
-
- private enum WindowsEncodingId : ushort
- {
- Symbol = 0,
- UnicodeBmp = 1,
- ShiftJis = 2,
- Prc = 3,
- Big5 = 4,
- Wansung = 5,
- Johab = 6,
- UnicodeFullRepertoire = 10,
- }
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.Files.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.Files.cs
deleted file mode 100644
index 3d89dd806..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.Files.cs
+++ /dev/null
@@ -1,148 +0,0 @@
-using System.Buffers.Binary;
-using System.Collections;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Runtime.CompilerServices;
-
-namespace Dalamud.Interface.ManagedFontAtlas.Internals;
-
-///
-/// Deals with TrueType.
-///
-[SuppressMessage("ReSharper", "NotAccessedField.Local", Justification = "TrueType specification defined fields")]
-[SuppressMessage("ReSharper", "UnusedType.Local", Justification = "TrueType specification defined types")]
-[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Internal")]
-[SuppressMessage(
- "StyleCop.CSharp.NamingRules",
- "SA1310:Field names should not contain underscore",
- Justification = "Version name")]
-[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Version name")]
-internal static partial class TrueTypeUtils
-{
- private readonly struct SfntFile : IReadOnlyDictionary>
- {
- // http://formats.kaitai.io/ttf/ttf.svg
-
- public static readonly TagStruct FileTagTrueType1 = new('1', '\0', '\0', '\0');
- public static readonly TagStruct FileTagType1 = new('t', 'y', 'p', '1');
- public static readonly TagStruct FileTagOpenTypeWithCff = new('O', 'T', 'T', 'O');
- public static readonly TagStruct FileTagOpenType1_0 = new('\0', '\x01', '\0', '\0');
- public static readonly TagStruct FileTagTrueTypeApple = new('t', 'r', 'u', 'e');
-
- public readonly PointerSpan Memory;
- public readonly int OffsetInCollection;
- public readonly ushort TableCount;
-
- public SfntFile(PointerSpan memory, int offsetInCollection = 0)
- {
- var span = memory.Span;
- this.Memory = memory;
- this.OffsetInCollection = offsetInCollection;
- this.TableCount = BinaryPrimitives.ReadUInt16BigEndian(span[4..]);
- }
-
- public int Count => this.TableCount;
-
- public IEnumerable Keys => this.Select(x => x.Key);
-
- public IEnumerable> Values => this.Select(x => x.Value);
-
- public PointerSpan this[TagStruct key] => this.First(x => x.Key == key).Value;
-
- public IEnumerator>> GetEnumerator()
- {
- var offset = 12;
- for (var i = 0; i < this.TableCount; i++)
- {
- var dte = new DirectoryTableEntry(this.Memory[offset..]);
- yield return new(dte.Tag, this.Memory.Slice(dte.Offset - this.OffsetInCollection, dte.Length));
-
- offset += Unsafe.SizeOf();
- }
- }
-
- IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
-
- public bool ContainsKey(TagStruct key) => this.Any(x => x.Key == key);
-
- public bool TryGetValue(TagStruct key, out PointerSpan value)
- {
- foreach (var (k, v) in this)
- {
- if (k == key)
- {
- value = v;
- return true;
- }
- }
-
- value = default;
- return false;
- }
-
- public readonly struct DirectoryTableEntry
- {
- public readonly PointerSpan Memory;
-
- public DirectoryTableEntry(PointerSpan span) => this.Memory = span;
-
- public TagStruct Tag => new(this.Memory);
-
- public uint Checksum => this.Memory.ReadU32Big(4);
-
- public int Offset => this.Memory.ReadI32Big(8);
-
- public int Length => this.Memory.ReadI32Big(12);
- }
- }
-
- private readonly struct TtcFile : IReadOnlyList
- {
- public static readonly TagStruct FileTag = new('t', 't', 'c', 'f');
-
- public readonly PointerSpan Memory;
- public readonly TagStruct Tag;
- public readonly ushort MajorVersion;
- public readonly ushort MinorVersion;
- public readonly int FontCount;
-
- public TtcFile(PointerSpan memory)
- {
- var span = memory.Span;
- this.Memory = memory;
- this.Tag = new(span);
- if (this.Tag != FileTag)
- throw new InvalidOperationException();
-
- this.MajorVersion = BinaryPrimitives.ReadUInt16BigEndian(span[4..]);
- this.MinorVersion = BinaryPrimitives.ReadUInt16BigEndian(span[6..]);
- this.FontCount = BinaryPrimitives.ReadInt32BigEndian(span[8..]);
- }
-
- public int Count => this.FontCount;
-
- public SfntFile this[int index]
- {
- get
- {
- if (index < 0 || index >= this.FontCount)
- {
- throw new IndexOutOfRangeException(
- $"The requested font #{index} does not exist in this .ttc file.");
- }
-
- var offset = BinaryPrimitives.ReadInt32BigEndian(this.Memory.Span[(12 + 4 * index)..]);
- return new(this.Memory[offset..], offset);
- }
- }
-
- public IEnumerator GetEnumerator()
- {
- for (var i = 0; i < this.FontCount; i++)
- yield return this[i];
- }
-
- IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
- }
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.GposGsub.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.GposGsub.cs
deleted file mode 100644
index d200de47b..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.GposGsub.cs
+++ /dev/null
@@ -1,259 +0,0 @@
-using System.Buffers.Binary;
-using System.Collections;
-using System.Collections.Generic;
-using System.Diagnostics.Contracts;
-using System.Linq;
-
-namespace Dalamud.Interface.ManagedFontAtlas.Internals;
-
-///
-/// Deals with TrueType.
-///
-internal static partial class TrueTypeUtils
-{
- [Flags]
- private enum LookupFlags : byte
- {
- RightToLeft = 1 << 0,
- IgnoreBaseGlyphs = 1 << 1,
- IgnoreLigatures = 1 << 2,
- IgnoreMarks = 1 << 3,
- UseMarkFilteringSet = 1 << 4,
- }
-
- private enum LookupType : ushort
- {
- SingleAdjustment = 1,
- PairAdjustment = 2,
- CursiveAttachment = 3,
- MarkToBaseAttachment = 4,
- MarkToLigatureAttachment = 5,
- MarkToMarkAttachment = 6,
- ContextPositioning = 7,
- ChainedContextPositioning = 8,
- ExtensionPositioning = 9,
- }
-
- private readonly struct ClassDefTable
- {
- public readonly PointerSpan Memory;
-
- public ClassDefTable(PointerSpan memory) => this.Memory = memory;
-
- public ushort Format => this.Memory.ReadU16Big(0);
-
- public Format1ClassArray Format1 => new(this.Memory);
-
- public Format2ClassRanges Format2 => new(this.Memory);
-
- public IEnumerable<(ushort Class, ushort GlyphId)> Enumerate()
- {
- switch (this.Format)
- {
- case 1:
- {
- var format1 = this.Format1;
- var startId = format1.StartGlyphId;
- var count = format1.GlyphCount;
- var classes = format1.ClassValueArray;
- for (var i = 0; i < count; i++)
- yield return (classes[i], (ushort)(i + startId));
-
- break;
- }
-
- case 2:
- {
- foreach (var range in this.Format2.ClassValueArray)
- {
- var @class = range.Class;
- var startId = range.StartGlyphId;
- var count = range.EndGlyphId - startId + 1;
- for (var i = 0; i < count; i++)
- yield return (@class, (ushort)(startId + i));
- }
-
- break;
- }
- }
- }
-
- [Pure]
- public ushort GetClass(ushort glyphId)
- {
- switch (this.Format)
- {
- case 1:
- {
- var format1 = this.Format1;
- var startId = format1.StartGlyphId;
- if (startId <= glyphId && glyphId < startId + format1.GlyphCount)
- return this.Format1.ClassValueArray[glyphId - startId];
-
- break;
- }
-
- case 2:
- {
- var rangeSpan = this.Format2.ClassValueArray;
- var i = rangeSpan.BinarySearch(new Format2ClassRanges.ClassRangeRecord { EndGlyphId = glyphId });
- if (i >= 0 && rangeSpan[i].ContainsGlyph(glyphId))
- return rangeSpan[i].Class;
-
- break;
- }
- }
-
- return 0;
- }
-
- public readonly struct Format1ClassArray
- {
- public readonly PointerSpan Memory;
-
- public Format1ClassArray(PointerSpan memory) => this.Memory = memory;
-
- public ushort Format => this.Memory.ReadU16Big(0);
-
- public ushort StartGlyphId => this.Memory.ReadU16Big(2);
-
- public ushort GlyphCount => this.Memory.ReadU16Big(4);
-
- public BigEndianPointerSpan ClassValueArray => new(
- this.Memory[6..].As(this.GlyphCount),
- BinaryPrimitives.ReverseEndianness);
- }
-
- public readonly struct Format2ClassRanges
- {
- public readonly PointerSpan Memory;
-
- public Format2ClassRanges(PointerSpan memory) => this.Memory = memory;
-
- public ushort ClassRangeCount => this.Memory.ReadU16Big(2);
-
- public BigEndianPointerSpan ClassValueArray => new(
- this.Memory[4..].As(this.ClassRangeCount),
- ClassRangeRecord.ReverseEndianness);
-
- public struct ClassRangeRecord : IComparable
- {
- public ushort StartGlyphId;
- public ushort EndGlyphId;
- public ushort Class;
-
- public static ClassRangeRecord ReverseEndianness(ClassRangeRecord value) => new()
- {
- StartGlyphId = BinaryPrimitives.ReverseEndianness(value.StartGlyphId),
- EndGlyphId = BinaryPrimitives.ReverseEndianness(value.EndGlyphId),
- Class = BinaryPrimitives.ReverseEndianness(value.Class),
- };
-
- public int CompareTo(ClassRangeRecord other) => this.EndGlyphId.CompareTo(other.EndGlyphId);
-
- public bool ContainsGlyph(ushort glyphId) =>
- this.StartGlyphId <= glyphId && glyphId <= this.EndGlyphId;
- }
- }
- }
-
- private readonly struct CoverageTable
- {
- public readonly PointerSpan Memory;
-
- public CoverageTable(PointerSpan memory) => this.Memory = memory;
-
- public enum CoverageFormat : ushort
- {
- Glyphs = 1,
- RangeRecords = 2,
- }
-
- public CoverageFormat Format => this.Memory.ReadEnumBig(0);
-
- public ushort Count => this.Memory.ReadU16Big(2);
-
- public BigEndianPointerSpan Glyphs =>
- this.Format == CoverageFormat.Glyphs
- ? new(this.Memory[4..].As(this.Count), BinaryPrimitives.ReverseEndianness)
- : default(BigEndianPointerSpan);
-
- public BigEndianPointerSpan RangeRecords =>
- this.Format == CoverageFormat.RangeRecords
- ? new(this.Memory[4..].As(this.Count), RangeRecord.ReverseEndianness)
- : default(BigEndianPointerSpan);
-
- public int GetCoverageIndex(ushort glyphId)
- {
- switch (this.Format)
- {
- case CoverageFormat.Glyphs:
- return this.Glyphs.BinarySearch(glyphId);
-
- case CoverageFormat.RangeRecords:
- {
- var index = this.RangeRecords.BinarySearch(
- (in RangeRecord record) => glyphId.CompareTo(record.EndGlyphId));
-
- if (index >= 0 && this.RangeRecords[index].ContainsGlyph(glyphId))
- return index;
-
- return -1;
- }
-
- default:
- return -1;
- }
- }
-
- public struct RangeRecord
- {
- public ushort StartGlyphId;
- public ushort EndGlyphId;
- public ushort StartCoverageIndex;
-
- public static RangeRecord ReverseEndianness(RangeRecord value) => new()
- {
- StartGlyphId = BinaryPrimitives.ReverseEndianness(value.StartGlyphId),
- EndGlyphId = BinaryPrimitives.ReverseEndianness(value.EndGlyphId),
- StartCoverageIndex = BinaryPrimitives.ReverseEndianness(value.StartCoverageIndex),
- };
-
- public bool ContainsGlyph(ushort glyphId) =>
- this.StartGlyphId <= glyphId && glyphId <= this.EndGlyphId;
- }
- }
-
- private readonly struct LookupTable : IEnumerable>
- {
- public readonly PointerSpan Memory;
-
- public LookupTable(PointerSpan memory) => this.Memory = memory;
-
- public LookupType Type => this.Memory.ReadEnumBig(0);
-
- public byte MarkAttachmentType => this.Memory[2];
-
- public LookupFlags Flags => (LookupFlags)this.Memory[3];
-
- public ushort SubtableCount => this.Memory.ReadU16Big(4);
-
- public BigEndianPointerSpan SubtableOffsets => new(
- this.Memory[6..].As(this.SubtableCount),
- BinaryPrimitives.ReverseEndianness);
-
- public PointerSpan this[int index] => this.Memory[this.SubtableOffsets[this.EnsureIndex(index)] ..];
-
- public IEnumerator> GetEnumerator()
- {
- foreach (var i in Enumerable.Range(0, this.SubtableCount))
- yield return this.Memory[this.SubtableOffsets[i] ..];
- }
-
- IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
-
- private int EnsureIndex(int index) => index >= 0 && index < this.SubtableCount
- ? index
- : throw new IndexOutOfRangeException();
- }
-}
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.PointerSpan.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.PointerSpan.cs
deleted file mode 100644
index c91df4ff2..000000000
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/TrueType.PointerSpan.cs
+++ /dev/null
@@ -1,443 +0,0 @@
-using System.Buffers.Binary;
-using System.Collections;
-using System.Collections.Generic;
-using System.Reactive.Disposables;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-namespace Dalamud.Interface.ManagedFontAtlas.Internals;
-
-///
-/// Deals with TrueType.
-///
-internal static partial class TrueTypeUtils
-{
- private delegate int BinarySearchComparer(in T value);
-
- private static IDisposable CreatePointerSpan(this T[] data, out PointerSpan pointerSpan)
- where T : unmanaged
- {
- var gchandle = GCHandle.Alloc(data, GCHandleType.Pinned);
- pointerSpan = new(gchandle.AddrOfPinnedObject(), data.Length);
- return Disposable.Create(() => gchandle.Free());
- }
-
- private static int BinarySearch(this IReadOnlyList span, in T value)
- where T : unmanaged, IComparable
- {
- var l = 0;
- var r = span.Count - 1;
- while (l <= r)
- {
- var i = (int)(((uint)r + (uint)l) >> 1);
- var c = value.CompareTo(span[i]);
- switch (c)
- {
- case 0:
- return i;
- case > 0:
- l = i + 1;
- break;
- default:
- r = i - 1;
- break;
- }
- }
-
- return ~l;
- }
-
- private static int BinarySearch(this IReadOnlyList span, BinarySearchComparer comparer)
- where T : unmanaged
- {
- var l = 0;
- var r = span.Count - 1;
- while (l <= r)
- {
- var i = (int)(((uint)r + (uint)l) >> 1);
- var c = comparer(span[i]);
- switch (c)
- {
- case 0:
- return i;
- case > 0:
- l = i + 1;
- break;
- default:
- r = i - 1;
- break;
- }
- }
-
- return ~l;
- }
-
- private static short ReadI16Big(this PointerSpan ps, int offset) =>
- BinaryPrimitives.ReadInt16BigEndian(ps.Span[offset..]);
-
- private static int ReadI32Big(this PointerSpan ps, int offset) =>
- BinaryPrimitives.ReadInt32BigEndian(ps.Span[offset..]);
-
- private static long ReadI64Big(this PointerSpan ps, int offset) =>
- BinaryPrimitives.ReadInt64BigEndian(ps.Span[offset..]);
-
- private static ushort ReadU16Big(this PointerSpan ps, int offset) =>
- BinaryPrimitives.ReadUInt16BigEndian(ps.Span[offset..]);
-
- private static uint ReadU32Big(this PointerSpan ps, int offset) =>
- BinaryPrimitives.ReadUInt32BigEndian(ps.Span[offset..]);
-
- private static ulong ReadU64Big(this PointerSpan ps, int offset) =>
- BinaryPrimitives.ReadUInt64BigEndian(ps.Span[offset..]);
-
- private static Half ReadF16Big(this PointerSpan ps, int offset) =>
- BinaryPrimitives.ReadHalfBigEndian(ps.Span[offset..]);
-
- private static float ReadF32Big(this PointerSpan ps, int offset) =>
- BinaryPrimitives.ReadSingleBigEndian(ps.Span[offset..]);
-
- private static double ReadF64Big(this PointerSpan ps, int offset) =>
- BinaryPrimitives.ReadDoubleBigEndian(ps.Span[offset..]);
-
- private static void ReadBig(this PointerSpan ps, int offset, out short value) =>
- value = BinaryPrimitives.ReadInt16BigEndian(ps.Span[offset..]);
-
- private static void ReadBig(this PointerSpan ps, int offset, out int value) =>
- value = BinaryPrimitives.ReadInt32BigEndian(ps.Span[offset..]);
-
- private static void ReadBig(this PointerSpan ps, int offset, out long value) =>
- value = BinaryPrimitives.ReadInt64BigEndian(ps.Span[offset..]);
-
- private static void ReadBig(this PointerSpan ps, int offset, out ushort value) =>
- value = BinaryPrimitives.ReadUInt16BigEndian(ps.Span[offset..]);
-
- private static void ReadBig(this PointerSpan ps, int offset, out uint value) =>
- value = BinaryPrimitives.ReadUInt32BigEndian(ps.Span[offset..]);
-
- private static void ReadBig(this PointerSpan ps, int offset, out ulong value) =>
- value = BinaryPrimitives.ReadUInt64BigEndian(ps.Span[offset..]);
-
- private static void ReadBig(this PointerSpan ps, int offset, out Half value) =>
- value = BinaryPrimitives.ReadHalfBigEndian(ps.Span[offset..]);
-
- private static void ReadBig(this PointerSpan ps, int offset, out float value) =>
- value = BinaryPrimitives.ReadSingleBigEndian(ps.Span[offset..]);
-
- private static void ReadBig(this PointerSpan ps, int offset, out double value) =>
- value = BinaryPrimitives.ReadDoubleBigEndian(ps.Span[offset..]);
-
- private static void ReadBig(this PointerSpan ps, ref int offset, out short value)
- {
- ps.ReadBig(offset, out value);
- offset += 2;
- }
-
- private static void ReadBig(this PointerSpan ps, ref int offset, out int value)
- {
- ps.ReadBig(offset, out value);
- offset += 4;
- }
-
- private static void ReadBig(this PointerSpan ps, ref int offset, out long value)
- {
- ps.ReadBig(offset, out value);
- offset += 8;
- }
-
- private static void ReadBig(this PointerSpan ps, ref int offset, out ushort value)
- {
- ps.ReadBig(offset, out value);
- offset += 2;
- }
-
- private static void ReadBig(this PointerSpan ps, ref int offset, out uint value)
- {
- ps.ReadBig(offset, out value);
- offset += 4;
- }
-
- private static void ReadBig(this PointerSpan ps, ref int offset, out ulong value)
- {
- ps.ReadBig(offset, out value);
- offset += 8;
- }
-
- private static void ReadBig(this PointerSpan ps, ref int offset, out Half value)
- {
- ps.ReadBig(offset, out value);
- offset += 2;
- }
-
- private static void ReadBig(this PointerSpan ps, ref int offset, out float value)
- {
- ps.ReadBig(offset, out value);
- offset += 4;
- }
-
- private static void ReadBig(this PointerSpan ps, ref int offset, out double value)
- {
- ps.ReadBig(offset, out value);
- offset += 8;
- }
-
- private static unsafe T ReadEnumBig