diff --git a/Dalamud/Interface/GameFonts/FdtFileView.cs b/Dalamud/Interface/GameFonts/FdtFileView.cs
index 78b2e22f3..896a6dbb4 100644
--- a/Dalamud/Interface/GameFonts/FdtFileView.cs
+++ b/Dalamud/Interface/GameFonts/FdtFileView.cs
@@ -6,7 +6,7 @@ namespace Dalamud.Interface.GameFonts;
///
/// Reference member view of a .fdt file data.
///
-internal readonly unsafe ref struct FdtFileView
+internal readonly unsafe struct FdtFileView
{
private readonly byte* ptr;
diff --git a/Dalamud/Interface/GameFonts/GameFontStyle.cs b/Dalamud/Interface/GameFonts/GameFontStyle.cs
index e219670b8..fbaf9de07 100644
--- a/Dalamud/Interface/GameFonts/GameFontStyle.cs
+++ b/Dalamud/Interface/GameFonts/GameFontStyle.cs
@@ -64,7 +64,7 @@ public struct GameFontStyle
///
public float SizePt
{
- get => this.SizePx * 3 / 4;
+ readonly get => this.SizePx * 3 / 4;
set => this.SizePx = value * 4 / 3;
}
@@ -73,14 +73,14 @@ public struct GameFontStyle
///
public float BaseSkewStrength
{
- get => this.SkewStrength * this.BaseSizePx / this.SizePx;
+ readonly get => this.SkewStrength * this.BaseSizePx / this.SizePx;
set => this.SkewStrength = value * this.SizePx / this.BaseSizePx;
}
///
/// Gets the font family.
///
- public GameFontFamily Family => this.FamilyAndSize switch
+ public readonly 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 GameFontFamilyAndSize FamilyWithMinimumSize => this.Family switch
+ public readonly 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 float BaseSizePt => this.FamilyAndSize switch
+ public readonly 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 float BaseSizePx => this.BaseSizePt * 4 / 3;
+ public readonly float BaseSizePx => this.BaseSizePt * 4 / 3;
///
/// Gets or sets a value indicating whether this font is bold.
///
public bool Bold
{
- get => this.Weight > 0f;
+ readonly get => this.Weight > 0f;
set => this.Weight = value ? 1f : 0f;
}
@@ -174,7 +174,7 @@ public struct GameFontStyle
///
public bool Italic
{
- get => this.SkewStrength != 0;
+ readonly get => this.SkewStrength != 0;
set => this.SkewStrength = value ? this.SizePx / 6 : 0;
}
@@ -233,13 +233,26 @@ 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 int CalculateBaseWidthAdjustment(in FdtReader.FontTableHeader header, in FdtReader.FontTableEntry glyph)
+ public readonly int CalculateBaseWidthAdjustment(in FdtReader.FontTableHeader header, in FdtReader.FontTableEntry glyph)
{
var widthDelta = this.Weight;
switch (this.BaseSkewStrength)
@@ -263,11 +276,11 @@ public struct GameFontStyle
/// Font information.
/// Glyph.
/// Width adjustment in pixel unit.
- public int CalculateBaseWidthAdjustment(FdtReader reader, FdtReader.FontTableEntry glyph) =>
+ public readonly int CalculateBaseWidthAdjustment(FdtReader reader, FdtReader.FontTableEntry glyph) =>
this.CalculateBaseWidthAdjustment(reader.FontHeader, glyph);
///
- public override string ToString()
+ public override readonly string ToString()
{
return $"GameFontStyle({this.FamilyAndSize}, {this.SizePt}pt, skew={this.SkewStrength}, weight={this.Weight})";
}
diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPreBuild.cs b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPreBuild.cs
index dbe8626e9..cb8a27a54 100644
--- a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPreBuild.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPreBuild.cs
@@ -1,6 +1,7 @@
using System.IO;
using System.Runtime.InteropServices;
+using Dalamud.Interface.GameFonts;
using Dalamud.Interface.Utility;
using ImGuiNET;
@@ -44,6 +45,13 @@ public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit
/// 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.
@@ -120,7 +128,7 @@ public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit
///
/// Adds the default font known to the current font atlas.
///
- /// Includes and .
+ /// 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.
///
@@ -153,15 +161,26 @@ public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit
///
/// Adds the game's symbols into the provided font.
- /// will be ignored.
+ /// will be ignored.
+ /// If the game symbol font file is unavailable, only will be honored.
///
/// The font config.
- void AddGameSymbol(in SafeFontConfig fontConfig);
+ /// 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 AddExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig);
+ void AttachExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig);
}
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/DelegateFontHandle.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/DelegateFontHandle.cs
index b6ec720dc..f0ed09155 100644
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/DelegateFontHandle.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/Internals/DelegateFontHandle.cs
@@ -271,6 +271,12 @@ internal class DelegateFontHandle : IFontHandle.IInternal
}
}
+ ///
+ public void OnPreBuildCleanup(IFontAtlasBuildToolkitPreBuild toolkitPreBuild)
+ {
+ // irrelevant
+ }
+
///
public void OnPostBuild(IFontAtlasBuildToolkitPostBuild toolkitPostBuild)
{
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs
index a3abc6681..46fb3f63d 100644
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs
@@ -273,24 +273,21 @@ internal sealed partial class FontAtlasFactory
///
public ImFontPtr AddDalamudDefaultFont(float sizePx, ushort[]? glyphRanges)
{
+ ImFontPtr font;
+ glyphRanges ??= this.factory.DefaultGlyphRanges;
if (Service.Get().UseAxis)
{
- return this.gameFontHandleSubstance.GetOrCreateFont(
- new(GameFontFamily.Axis, sizePx),
- this);
+ 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 });
}
- glyphRanges ??= this.factory.DefaultGlyphRanges;
-
- var fontConfig = new SafeFontConfig
- {
- SizePx = sizePx,
- GlyphRanges = glyphRanges,
- };
-
- var font = this.AddDalamudAssetFont(DalamudAsset.NotoSansJpMedium, fontConfig);
- this.AddExtraGlyphsForDalamudLanguage(fontConfig with { MergeFont = font });
- this.AddGameSymbol(fontConfig with { MergeFont = font });
+ this.AttachExtraGlyphsForDalamudLanguage(new() { SizePx = sizePx, MergeFont = font });
if (this.Font.IsNull())
this.Font = font;
return font;
@@ -315,11 +312,12 @@ internal sealed partial class FontAtlasFactory
});
case DalamudAsset.LodestoneGameSymbol when !this.factory.HasGameSymbolsFontFile:
- return this.gameFontHandleSubstance.AttachGameSymbols(
- this,
- fontConfig.MergeFont,
- fontConfig.SizePx,
- fontConfig.GlyphRanges);
+ {
+ return this.AddGameGlyphs(
+ new(GameFontFamily.Axis, fontConfig.SizePx),
+ fontConfig.GlyphRanges,
+ fontConfig.MergeFont);
+ }
default:
return this.factory.AddFont(
@@ -341,20 +339,25 @@ internal sealed partial class FontAtlasFactory
});
///
- public void AddGameSymbol(in SafeFontConfig fontConfig) => this.AddDalamudAssetFont(
- DalamudAsset.LodestoneGameSymbol,
- fontConfig with
- {
- GlyphRanges = new ushort[]
+ public ImFontPtr AddGameSymbol(in SafeFontConfig fontConfig) =>
+ this.AddDalamudAssetFont(
+ DalamudAsset.LodestoneGameSymbol,
+ fontConfig with
{
- GamePrebakedFontHandle.SeIconCharMin,
- GamePrebakedFontHandle.SeIconCharMax,
- 0,
- },
- });
+ GlyphRanges = new ushort[]
+ {
+ GamePrebakedFontHandle.SeIconCharMin,
+ GamePrebakedFontHandle.SeIconCharMax,
+ 0,
+ },
+ });
///
- public void AddExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig)
+ 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")
@@ -377,6 +380,8 @@ internal sealed partial class FontAtlasFactory
{
foreach (var substance in this.data.Substances)
substance.OnPreBuild(this);
+ foreach (var substance in this.data.Substances)
+ substance.OnPreBuildCleanup(this);
}
public unsafe void PreBuild()
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/GamePrebakedFontHandle.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/GamePrebakedFontHandle.cs
index 040f9a743..1ac6fdbce 100644
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/GamePrebakedFontHandle.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/Internals/GamePrebakedFontHandle.cs
@@ -1,5 +1,7 @@
using System.Buffers;
+using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reactive.Disposables;
@@ -207,15 +209,11 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
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 fonts = new();
private readonly Dictionary buildExceptions = new();
- private readonly Dictionary> fontCopyTargets = new();
+ private readonly List<(ImFontPtr Font, GameFontStyle Style, ushort[]? Ranges)> attachments = new();
private readonly HashSet templatedFonts = new();
- private readonly Dictionary> lateBuildRanges = new();
-
- private readonly Dictionary> glyphRectIds =
- new();
///
/// Initializes a new instance of the class.
@@ -238,29 +236,22 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
}
///
- /// Attaches game symbols to the given font.
+ /// Attaches game symbols to the given font. If font is null, it will be created.
///
/// The toolkitPostBuild.
/// The font to attach to.
- /// The font size in pixels.
+ /// The game font style.
/// The intended glyph ranges.
/// if it is not empty; otherwise a new font.
- public ImFontPtr AttachGameSymbols(
+ public ImFontPtr AttachGameGlyphs(
IFontAtlasBuildToolkitPreBuild toolkitPreBuild,
ImFontPtr font,
- float sizePx,
- ushort[]? glyphRanges)
+ GameFontStyle style,
+ ushort[]? glyphRanges = null)
{
- var style = new GameFontStyle(GameFontFamily.Axis, sizePx);
- var referenceFont = this.GetOrCreateFont(style, toolkitPreBuild);
-
if (font.IsNull())
- font = this.CreateTemplateFont(style, toolkitPreBuild);
-
- if (!this.fontCopyTargets.TryGetValue(referenceFont, out var copyTargets))
- this.fontCopyTargets[referenceFont] = copyTargets = new();
-
- copyTargets.Add((font, glyphRanges));
+ font = this.CreateTemplateFont(toolkitPreBuild, style.SizePx);
+ this.attachments.Add((font, style, glyphRanges));
return font;
}
@@ -272,14 +263,20 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
/// The font.
public ImFontPtr GetOrCreateFont(GameFontStyle style, IFontAtlasBuildToolkitPreBuild toolkitPreBuild)
{
- if (this.fonts.TryGetValue(style, out var font))
- return font;
-
try
{
- font = this.CreateFontPrivate(style, toolkitPreBuild, ' ', '\uFFFE', true);
- this.fonts.Add(style, font);
- return font;
+ 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)
{
@@ -290,7 +287,9 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
///
public ImFontPtr GetFontPtr(IFontHandle handle) =>
- handle is GamePrebakedFontHandle ggfh ? this.fonts.GetValueOrDefault(ggfh.FontStyle) : default;
+ handle is GamePrebakedFontHandle ggfh
+ ? this.fonts.GetValueOrDefault(ggfh.FontStyle)?.FullRangeFont ?? default
+ : default;
///
public Exception? GetBuildException(IFontHandle handle) =>
@@ -315,6 +314,34 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
}
}
+ ///
+ 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)
{
@@ -331,235 +358,19 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
var pixels8Array = new byte*[toolkitPostBuild.NewImAtlas.Textures.Size];
var widths = new int[toolkitPostBuild.NewImAtlas.Textures.Size];
- var heights = 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 heights[i]);
+ toolkitPostBuild.NewImAtlas.GetTexDataAsAlpha8(i, out pixels8Array[i], out widths[i], out _);
- foreach (var (style, font) in this.fonts)
+ foreach (var (style, plan) in this.fonts)
{
try
{
- var fas = GameFontStyle.GetRecommendedFamilyAndSize(
- style.Family,
- style.SizePt * toolkitPostBuild.Scale);
- var attr = fas.GetAttribute();
- var horizontalOffset = attr?.HorizontalOffset ?? 0;
- var texCount = this.handleManager.GameFontTextureProvider.GetFontTextureCount(attr.TexPathFormat);
- using var handle = this.handleManager.GameFontTextureProvider.CreateFdtFileView(fas, out var fdt);
- ref var fdtFontHeader = ref fdt.FontHeader;
- var fdtGlyphs = fdt.Glyphs;
- var fontPtr = font.NativePtr;
+ foreach (var font in plan.Ranges.Keys)
+ this.PatchFontMetricsIfNecessary(style, font, toolkitPostBuild.Scale);
- var glyphs = font.GlyphsWrapped();
- var scale = toolkitPostBuild.Scale * (style.SizePt / fdtFontHeader.Size);
-
- fontPtr->FontSize = toolkitPostBuild.Scale * style.SizePx;
- if (fontPtr->ConfigData != null)
- fontPtr->ConfigData->SizePixels = fontPtr->FontSize;
- fontPtr->Ascent = fdtFontHeader.Ascent * scale;
- fontPtr->Descent = fdtFontHeader.Descent * scale;
- fontPtr->EllipsisChar = '…';
-
- if (!allTexFiles.TryGetValue(attr.TexPathFormat, out var texFiles))
- allTexFiles.Add(attr.TexPathFormat, texFiles = ArrayPool.Shared.Rent(texCount));
-
- if (this.glyphRectIds.TryGetValue(style, out var rectIdToGlyphs))
- {
- foreach (var (rectId, fdtGlyphIndex) in rectIdToGlyphs.Values)
- {
- ref var glyph = ref fdtGlyphs[fdtGlyphIndex];
- var rc = (ImGuiHelpers.ImFontAtlasCustomRectReal*)toolkitPostBuild.NewImAtlas
- .GetCustomRectByIndex(rectId)
- .NativePtr;
- var pixels8 = pixels8Array[rc->TextureIndex];
- var width = widths[rc->TextureIndex];
- texFiles[glyph.TextureFileIndex] ??=
- this.handleManager
- .GameFontTextureProvider
- .GetTexFile(attr.TexPathFormat, glyph.TextureFileIndex);
- var sourceBuffer = texFiles[glyph.TextureFileIndex].ImageData;
- var sourceBufferDelta = glyph.TextureChannelByteIndex;
- var widthAdjustment = style.CalculateBaseWidthAdjustment(fdtFontHeader, 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) * fdtFontHeader.TextureWidth) +
- glyph.TextureOffsetX + x))];
- pixels8[((rc->Y + y) * width) + rc->X + x] = a;
- }
- }
- }
- else
- {
- for (var y = 0; y < glyph.BoundingHeight; y++)
- {
- for (var x = 0; x < glyph.BoundingWidth + widthAdjustment; x++)
- pixels8[((rc->Y + y) * width) + rc->X + x] = 0;
- }
-
- for (int xbold = 0, xboldTo = Math.Max(1, (int)Math.Ceiling(style.Weight + 1));
- xbold < xboldTo;
- 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 *
- (fdtFontHeader.LineHeight - glyph.CurrentOffsetY - y) /
- fdtFontHeader.LineHeight;
- }
- else if (style.BaseSkewStrength < 0)
- {
- xDelta -= style.BaseSkewStrength * (glyph.CurrentOffsetY + y) /
- fdtFontHeader.LineHeight;
- }
-
- var xDeltaInt = (int)Math.Floor(xDelta);
- var xness = xDelta - xDeltaInt;
- for (var x = 0; x < glyph.BoundingWidth; x++)
- {
- var sourcePixelIndex =
- ((glyph.TextureOffsetY + y) * fdtFontHeader.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] =
- Math.Max(pixels8[targetOffset], (byte)(boldStrength * n));
- }
- }
- }
- }
-
- glyphs[rc->GlyphId].XY *= scale;
- glyphs[rc->GlyphId].AdvanceX *= scale;
- }
- }
- else if (this.lateBuildRanges.TryGetValue(font, out var buildRanges))
- {
- buildRanges.Sort();
- for (var i = 0; i < buildRanges.Count; i++)
- {
- var current = buildRanges[i];
- if (current.From > current.To)
- buildRanges[i] = (From: current.To, To: current.From);
- }
-
- for (var i = 0; i < buildRanges.Count - 1; i++)
- {
- var current = buildRanges[i];
- var next = buildRanges[i + 1];
- if (next.From <= current.To)
- {
- buildRanges[i] = current with { To = next.To };
- buildRanges.RemoveAt(i + 1);
- i--;
- }
- }
-
- var fdtTexSize = new Vector4(
- fdtFontHeader.TextureWidth,
- fdtFontHeader.TextureHeight,
- fdtFontHeader.TextureWidth,
- fdtFontHeader.TextureHeight);
-
- if (!allTextureIndices.TryGetValue(attr.TexPathFormat, out var textureIndices))
- {
- allTextureIndices.Add(
- attr.TexPathFormat,
- textureIndices = ArrayPool.Shared.Rent(texCount));
- textureIndices.AsSpan(0, texCount).Fill(-1);
- }
-
- glyphs.EnsureCapacity(glyphs.Length + buildRanges.Sum(x => (x.To - x.From) + 1));
- foreach (var (rangeMin, rangeMax) in buildRanges)
- {
- var glyphIndex = fdt.FindGlyphIndex(rangeMin);
- if (glyphIndex < 0)
- glyphIndex = ~glyphIndex;
- var endIndex = fdt.FindGlyphIndex(rangeMax);
- if (endIndex < 0)
- endIndex = ~endIndex - 1;
- for (; glyphIndex <= endIndex; glyphIndex++)
- {
- var fdtg = fdtGlyphs[glyphIndex];
-
- // If the glyph already exists in the target font, we do not overwrite.
- if (
- !(fdtg.Char == ' ' && this.templatedFonts.Contains(font))
- && font.FindGlyphNoFallback(fdtg.Char).NativePtr is not null)
- {
- continue;
- }
-
- ref var textureIndex = ref textureIndices[fdtg.TextureIndex];
- if (textureIndex == -1)
- {
- textureIndex = toolkitPostBuild.StoreTexture(
- this.handleManager
- .GameFontTextureProvider
- .NewFontTextureRef(attr.TexPathFormat, fdtg.TextureIndex),
- true);
- }
-
- var glyph = new ImGuiHelpers.ImFontGlyphReal
- {
- AdvanceX = fdtg.AdvanceWidth,
- Codepoint = fdtg.Char,
- Colored = false,
- TextureIndex = textureIndex,
- Visible = true,
- X0 = horizontalOffset,
- Y0 = fdtg.CurrentOffsetY,
- U0 = fdtg.TextureOffsetX,
- V0 = fdtg.TextureOffsetY,
- U1 = fdtg.BoundingWidth,
- V1 = fdtg.BoundingHeight,
- };
-
- glyph.XY1 = glyph.XY0 + glyph.UV1;
- glyph.UV1 += glyph.UV0;
- glyph.UV /= fdtTexSize;
- glyph.XY *= scale;
- glyph.AdvanceX *= scale;
-
- glyphs.Add(glyph);
- }
- }
-
- font.NativePtr->FallbackGlyph = null;
-
- font.BuildLookupTable();
- }
-
- foreach (var fallbackCharCandidate in FontAtlasFactory.FallbackCodepoints)
- {
- 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;
- }
- }
-
- font.AdjustGlyphMetrics(1 / toolkitPostBuild.Scale, toolkitPostBuild.Scale);
+ plan.SetFullRangeFontGlyphs(toolkitPostBuild, allTexFiles, allTextureIndices, pixels8Array, widths);
+ plan.PostProcessFullRangeFont();
+ plan.CopyGlyphsToRanges();
}
catch (Exception e)
{
@@ -567,32 +378,6 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
this.fonts[style] = default;
}
}
-
- foreach (var (source, targets) in this.fontCopyTargets)
- {
- foreach (var target in targets)
- {
- if (target.Ranges is null)
- {
- ImGuiHelpers.CopyGlyphsAcrossFonts(source, target.Font, missingOnly: true);
- }
- else
- {
- for (var i = 0; i < target.Ranges.Length; i += 2)
- {
- if (target.Ranges[i] == 0)
- break;
- ImGuiHelpers.CopyGlyphsAcrossFonts(
- source,
- target.Font,
- true,
- true,
- target.Ranges[i],
- target.Ranges[i + 1]);
- }
- }
- }
- }
}
///
@@ -601,103 +386,401 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
// Irrelevant
}
- ///
- /// Creates a relevant for the given .
- ///
- /// The game font style.
- /// The toolkitPostBuild.
- /// Min range.
- /// Max range.
- /// Add extra language glyphs.
- /// The font.
- private ImFontPtr CreateFontPrivate(
- GameFontStyle style,
- IFontAtlasBuildToolkitPreBuild toolkitPreBuild,
- char minRange,
- char maxRange,
- bool addExtraLanguageGlyphs)
- {
- var font = toolkitPreBuild.IgnoreGlobalScale(this.CreateTemplateFont(style, toolkitPreBuild));
-
- if (addExtraLanguageGlyphs)
- {
- var cfg = toolkitPreBuild.FindConfigPtr(font);
- toolkitPreBuild.AddExtraGlyphsForDalamudLanguage(new()
- {
- MergeFont = cfg.DstFont,
- SizePx = cfg.SizePixels,
- });
- }
-
- var fas = GameFontStyle.GetRecommendedFamilyAndSize(style.Family, style.SizePt * toolkitPreBuild.Scale);
- var horizontalOffset = fas.GetAttribute()?.HorizontalOffset ?? 0;
- using var handle = this.handleManager.GameFontTextureProvider.CreateFdtFileView(fas, out var fdt);
- ref var fdtFontHeader = ref fdt.FontHeader;
- var existing = new SortedSet();
-
- if (style is { Bold: false, Italic: false })
- {
- if (!this.lateBuildRanges.TryGetValue(font, out var ranges))
- this.lateBuildRanges[font] = ranges = new();
-
- ranges.Add((minRange, maxRange));
- }
- else
- {
- if (this.glyphRectIds.TryGetValue(style, out var rectIds))
- existing.UnionWith(rectIds.Keys);
- else
- rectIds = this.glyphRectIds[style] = new();
-
- var glyphs = fdt.Glyphs;
- for (var fdtGlyphIndex = 0; fdtGlyphIndex < glyphs.Length; fdtGlyphIndex++)
- {
- ref var glyph = ref glyphs[fdtGlyphIndex];
- var cint = glyph.CharInt;
- if (cint < minRange || cint > maxRange)
- continue;
-
- var c = (char)cint;
- if (existing.Contains(c))
- continue;
-
- var widthAdjustment = style.CalculateBaseWidthAdjustment(fdtFontHeader, glyph);
- rectIds[c] = (
- toolkitPreBuild.NewImAtlas.AddCustomRectFontGlyph(
- font,
- c,
- glyph.BoundingWidth + widthAdjustment,
- glyph.BoundingHeight,
- glyph.AdvanceWidth,
- new(horizontalOffset, glyph.CurrentOffsetY)),
- fdtGlyphIndex);
- }
- }
-
- var scale = toolkitPreBuild.Scale * (style.SizePt / fdt.FontHeader.Size);
- foreach (ref var kernPair in fdt.PairAdjustments)
- font.AddKerningPair(kernPair.Left, kernPair.Right, kernPair.RightOffset * scale);
-
- return font;
- }
-
///
/// Creates a new template font.
///
- /// The game font style.
/// The toolkitPostBuild.
+ /// The size of the font.
/// The font.
- private ImFontPtr CreateTemplateFont(GameFontStyle style, IFontAtlasBuildToolkitPreBuild toolkitPreBuild)
+ private ImFontPtr CreateTemplateFont(IFontAtlasBuildToolkitPreBuild toolkitPreBuild, float sizePx)
{
var font = toolkitPreBuild.AddDalamudAssetFont(
DalamudAsset.NotoSansJpMedium,
new()
{
GlyphRanges = new ushort[] { ' ', ' ', '\0' },
- SizePx = style.SizePx * toolkitPreBuild.Scale,
+ 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()
+ {
+ var scale = this.Style.SizePt / this.Fdt.FontHeader.Size;
+ foreach (ref var g in this.FullRangeFont.GlyphsWrapped().DataSpan)
+ {
+ g.XY *= scale;
+ g.AdvanceX *= scale;
+ }
+
+ var pfrf = this.FullRangeFont.NativePtr;
+ ref var frf = ref *pfrf;
+ 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()
+ {
+ foreach (var (font, rangeBits) in this.Ranges)
+ {
+ if (font.NativePtr == this.FullRangeFont.NativePtr)
+ continue;
+
+ 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)
+ glyphs.Add(sourceGlyph);
+ else
+ glyphs[glyphIndex] = sourceGlyph;
+ }
+
+ 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/IFontHandleSubstance.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/IFontHandleSubstance.cs
index fbfa2d12e..f6c5c6591 100644
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/IFontHandleSubstance.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/Internals/IFontHandleSubstance.cs
@@ -31,6 +31,13 @@ internal interface IFontHandleSubstance : IDisposable
///
/// 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.
diff --git a/Dalamud/Interface/ManagedFontAtlas/SafeFontConfig.cs b/Dalamud/Interface/ManagedFontAtlas/SafeFontConfig.cs
index cd840e5ed..cb7f7c65a 100644
--- a/Dalamud/Interface/ManagedFontAtlas/SafeFontConfig.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/SafeFontConfig.cs
@@ -37,9 +37,13 @@ public struct SafeFontConfig
///
/// Config to copy from.
public unsafe SafeFontConfig(ImFontConfigPtr config)
+ : this()
{
- this.Raw = *config.NativePtr;
- this.Raw.GlyphRanges = null;
+ if (config.NativePtr is not null)
+ {
+ this.Raw = *config.NativePtr;
+ this.Raw.GlyphRanges = null;
+ }
}
///