mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Revert "IFontAtlas: font atlas per plugin"
This commit is contained in:
parent
14c5ad1605
commit
b5696afe94
44 changed files with 1499 additions and 7943 deletions
|
|
@ -148,9 +148,12 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
|
|||
public bool UseAxisFontsFromGame { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Obsolete("It happens that nobody touched this setting", true)]
|
||||
public float FontGammaLevel { get; set; } = 1.4f;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,159 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Dalamud.Interface.GameFonts;
|
||||
|
||||
/// <summary>
|
||||
/// Reference member view of a .fdt file data.
|
||||
/// </summary>
|
||||
internal readonly unsafe struct FdtFileView
|
||||
{
|
||||
private readonly byte* ptr;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FdtFileView"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to the data.</param>
|
||||
/// <param name="length">Length of the data.</param>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file header.
|
||||
/// </summary>
|
||||
public ref FdtReader.FdtHeader FileHeader => ref *(FdtReader.FdtHeader*)this.ptr;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font header.
|
||||
/// </summary>
|
||||
public ref FdtReader.FontTableHeader FontHeader =>
|
||||
ref *(FdtReader.FontTableHeader*)((nint)this.ptr + this.FileHeader.FontTableHeaderOffset);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the glyphs.
|
||||
/// </summary>
|
||||
public Span<FdtReader.FontTableEntry> Glyphs => new(this.GlyphsUnsafe, this.FontHeader.FontTableEntryCount);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the kerning header.
|
||||
/// </summary>
|
||||
public ref FdtReader.KerningTableHeader KerningHeader =>
|
||||
ref *(FdtReader.KerningTableHeader*)((nint)this.ptr + this.FileHeader.KerningTableHeaderOffset);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of kerning entries.
|
||||
/// </summary>
|
||||
public int KerningEntryCount => Math.Min(this.FontHeader.KerningTableEntryCount, this.KerningHeader.Count);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the kerning entries.
|
||||
/// </summary>
|
||||
public Span<FdtReader.KerningTableEntry> PairAdjustments => new(
|
||||
this.ptr + this.FileHeader.KerningTableHeaderOffset + sizeof(FdtReader.KerningTableHeader),
|
||||
this.KerningEntryCount);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum texture index.
|
||||
/// </summary>
|
||||
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));
|
||||
|
||||
/// <summary>
|
||||
/// Finds the glyph index for the corresponding codepoint.
|
||||
/// </summary>
|
||||
/// <param name="codepoint">Unicode codepoint (UTF-32 value).</param>
|
||||
/// <returns>Corresponding index, or a negative number according to <see cref="List{T}.BinarySearch(int,int,T,System.Collections.Generic.IComparer{T}?)"/>.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a glyph range for use with <see cref="Interface.ManagedFontAtlas.SafeFontConfig.GlyphRanges"/>.
|
||||
/// </summary>
|
||||
/// <param name="mergeDistance">Merge two ranges into one if distance is below the value specified in this parameter.</param>
|
||||
/// <returns>Glyph ranges.</returns>
|
||||
public ushort[] ToGlyphRanges(int mergeDistance = 8)
|
||||
{
|
||||
var glyphs = this.Glyphs;
|
||||
var ranges = new List<ushort>(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();
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ namespace Dalamud.Interface.GameFonts;
|
|||
/// <summary>
|
||||
/// Enum of available game fonts in specific sizes.
|
||||
/// </summary>
|
||||
public enum GameFontFamilyAndSize
|
||||
public enum GameFontFamilyAndSize : int
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/AXIS_96.fdt", "common/font/font{0}.tex", -1)]
|
||||
Axis96,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -23,7 +22,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/AXIS_12.fdt", "common/font/font{0}.tex", -1)]
|
||||
Axis12,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -31,7 +29,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/AXIS_14.fdt", "common/font/font{0}.tex", -1)]
|
||||
Axis14,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -39,7 +36,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/AXIS_18.fdt", "common/font/font{0}.tex", -1)]
|
||||
Axis18,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -47,7 +43,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/AXIS_36.fdt", "common/font/font{0}.tex", -4)]
|
||||
Axis36,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -55,7 +50,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Serif font. Contains mostly ASCII range. Used in game for job names.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/Jupiter_16.fdt", "common/font/font{0}.tex", -1)]
|
||||
Jupiter16,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -63,7 +57,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Serif font. Contains mostly ASCII range. Used in game for job names.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/Jupiter_20.fdt", "common/font/font{0}.tex", -1)]
|
||||
Jupiter20,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -71,7 +64,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Serif font. Contains mostly ASCII range. Used in game for job names.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/Jupiter_23.fdt", "common/font/font{0}.tex", -1)]
|
||||
Jupiter23,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -79,7 +71,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Serif font. Contains mostly numbers. Used in game for flying texts.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/Jupiter_45.fdt", "common/font/font{0}.tex", -2)]
|
||||
Jupiter45,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -87,7 +78,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Serif font. Contains mostly ASCII range. Used in game for job names.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/Jupiter_46.fdt", "common/font/font{0}.tex", -2)]
|
||||
Jupiter46,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -95,7 +85,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Serif font. Contains mostly numbers. Used in game for flying texts.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/Jupiter_90.fdt", "common/font/font{0}.tex", -4)]
|
||||
Jupiter90,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -103,7 +92,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/Meidinger_16.fdt", "common/font/font{0}.tex", -1)]
|
||||
Meidinger16,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -111,7 +99,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/Meidinger_20.fdt", "common/font/font{0}.tex", -1)]
|
||||
Meidinger20,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -119,7 +106,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/Meidinger_40.fdt", "common/font/font{0}.tex", -4)]
|
||||
Meidinger40,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -127,7 +113,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Horizontally wide. Contains mostly ASCII range.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/MiedingerMid_10.fdt", "common/font/font{0}.tex", -1)]
|
||||
MiedingerMid10,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -135,7 +120,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Horizontally wide. Contains mostly ASCII range.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/MiedingerMid_12.fdt", "common/font/font{0}.tex", -1)]
|
||||
MiedingerMid12,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -143,7 +127,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Horizontally wide. Contains mostly ASCII range.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/MiedingerMid_14.fdt", "common/font/font{0}.tex", -1)]
|
||||
MiedingerMid14,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -151,7 +134,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Horizontally wide. Contains mostly ASCII range.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/MiedingerMid_18.fdt", "common/font/font{0}.tex", -1)]
|
||||
MiedingerMid18,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -159,7 +141,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Horizontally wide. Contains mostly ASCII range.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/MiedingerMid_36.fdt", "common/font/font{0}.tex", -2)]
|
||||
MiedingerMid36,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -167,7 +148,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Horizontally narrow. Contains mostly ASCII range. Used for addon titles.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/TrumpGothic_184.fdt", "common/font/font{0}.tex", -1)]
|
||||
TrumpGothic184,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -175,7 +155,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Horizontally narrow. Contains mostly ASCII range. Used for addon titles.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/TrumpGothic_23.fdt", "common/font/font{0}.tex", -1)]
|
||||
TrumpGothic23,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -183,7 +162,6 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Horizontally narrow. Contains mostly ASCII range. Used for addon titles.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/TrumpGothic_34.fdt", "common/font/font{0}.tex", -1)]
|
||||
TrumpGothic34,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -191,6 +169,5 @@ public enum GameFontFamilyAndSize
|
|||
///
|
||||
/// Horizontally narrow. Contains mostly ASCII range. Used for addon titles.
|
||||
/// </summary>
|
||||
[GameFontFamilyAndSize("common/font/TrumpGothic_68.fdt", "common/font/font{0}.tex", -3)]
|
||||
TrumpGothic68,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
namespace Dalamud.Interface.GameFonts;
|
||||
|
||||
/// <summary>
|
||||
/// Marks the path for an enum value.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
internal class GameFontFamilyAndSizeAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameFontFamilyAndSizeAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="path">Inner path of the file.</param>
|
||||
/// <param name="texPathFormat">the file path format for the relevant .tex files.</param>
|
||||
/// <param name="horizontalOffset">Horizontal offset of the corresponding font.</param>
|
||||
public GameFontFamilyAndSizeAttribute(string path, string texPathFormat, int horizontalOffset)
|
||||
{
|
||||
this.Path = path;
|
||||
this.TexPathFormat = texPathFormat;
|
||||
this.HorizontalOffset = horizontalOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path.
|
||||
/// </summary>
|
||||
public string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file path format for the relevant .tex files.<br />
|
||||
/// Used for <see cref="string.Format(string,object?)"/>(<see cref="TexPathFormat"/>, <see cref="int"/>).
|
||||
/// </summary>
|
||||
public string TexPathFormat { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the horizontal offset of the corresponding font.
|
||||
/// </summary>
|
||||
public int HorizontalOffset { get; }
|
||||
}
|
||||
|
|
@ -1,76 +1,75 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.GameFonts;
|
||||
|
||||
/// <summary>
|
||||
/// ABI-compatible wrapper for <see cref="IFontHandle"/>.
|
||||
/// Prepare and keep game font loaded for use in OnDraw.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameFontHandle"/> class.
|
||||
/// </summary>
|
||||
/// <param name="fontHandle">The wrapped <see cref="IFontHandle"/>.</param>
|
||||
/// <param name="fontAtlasFactory">An instance of <see cref="FontAtlasFactory"/>.</param>
|
||||
internal GameFontHandle(IFontHandle.IInternal fontHandle, FontAtlasFactory fontAtlasFactory)
|
||||
/// <param name="manager">GameFontManager instance.</param>
|
||||
/// <param name="font">Font to use.</param>
|
||||
internal GameFontHandle(GameFontManager manager, GameFontStyle font)
|
||||
{
|
||||
this.fontHandle = fontHandle;
|
||||
this.fontAtlasFactory = fontAtlasFactory;
|
||||
this.manager = manager;
|
||||
this.fontStyle = font;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Exception? LoadException => this.fontHandle.LoadException;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Available => this.fontHandle.Available;
|
||||
|
||||
/// <inheritdoc cref="IFontHandle.IInternal.ImFont"/>
|
||||
[Obsolete($"Use {nameof(Push)}, and then use {nameof(ImGui.GetFont)} instead.", false)]
|
||||
public ImFontPtr ImFont => this.fontHandle.ImFont;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font style. Only applicable for <see cref="GameFontHandle"/>.
|
||||
/// Gets the font style.
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relevant <see cref="FdtReader"/>.<br />
|
||||
/// <br />
|
||||
/// Only applicable for game fonts. Otherwise it will throw.
|
||||
/// Gets a value indicating whether this font is ready for use.
|
||||
/// </summary>
|
||||
[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)!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => this.fontHandle.Dispose();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDisposable Push() => this.fontHandle.Push();
|
||||
public bool Available
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return this.manager.GetFont(this.fontStyle).GetValueOrDefault(null).NativePtr != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="GameFontLayoutPlan.Builder"/>.<br />
|
||||
/// <br />
|
||||
/// Only applicable for game fonts. Otherwise it will throw.
|
||||
/// Gets the font.
|
||||
/// </summary>
|
||||
public ImFontPtr ImFont => this.manager.GetFont(this.fontStyle).Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the FdtReader.
|
||||
/// </summary>
|
||||
public FdtReader FdtReader => this.manager.GetFdtReader(this.fontStyle.FamilyAndSize);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GameFontLayoutPlan.Builder.
|
||||
/// </summary>
|
||||
/// <param name="text">Text.</param>
|
||||
/// <returns>A new builder for GameFontLayoutPlan.</returns>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose() => this.manager.DecreaseFontRef(this.fontStyle);
|
||||
|
||||
/// <summary>
|
||||
/// Draws text.
|
||||
/// </summary>
|
||||
/// <param name="text">Text to draw.</param>
|
||||
[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
|
|||
/// </summary>
|
||||
/// <param name="col">Color.</param>
|
||||
/// <param name="text">Text to draw.</param>
|
||||
[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.
|
||||
/// </summary>
|
||||
/// <param name="text">Text to draw.</param>
|
||||
[Obsolete("If you use this, let the fact that you use this be known at Dalamud Discord.", false)]
|
||||
public void TextDisabled(string text)
|
||||
{
|
||||
unsafe
|
||||
|
|
|
|||
507
Dalamud/Interface/GameFonts/GameFontManager.cs
Normal file
507
Dalamud/Interface/GameFonts/GameFontManager.cs
Normal file
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Loads game font for use in ImGui.
|
||||
/// </summary>
|
||||
[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<byte[]> texturePixels;
|
||||
private readonly Dictionary<GameFontStyle, ImFontPtr> fonts = new();
|
||||
private readonly Dictionary<GameFontStyle, int> fontUseCounter = new();
|
||||
private readonly Dictionary<GameFontStyle, Dictionary<char, Tuple<int, FdtReader.FontTableEntry>>> 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<TexFile>($"common/font/font{x}.tex")!)
|
||||
.Select(x => new Task<byte[]>(Timings.AttachTimingHandle(() => x.ImageData!)))
|
||||
.ToArray();
|
||||
foreach (var task in texTasks)
|
||||
task.Start();
|
||||
this.texturePixels = texTasks.Select(x => x.GetAwaiter().GetResult()).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describe font into a string.
|
||||
/// </summary>
|
||||
/// <param name="font">Font to describe.</param>
|
||||
/// <returns>A string in a form of "FontName (NNNpt)".</returns>
|
||||
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"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a font should be able to display most of stuff.
|
||||
/// </summary>
|
||||
/// <param name="font">Font to check.</param>
|
||||
/// <returns>True if it can.</returns>
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unscales fonts after they have been rendered onto atlas.
|
||||
/// </summary>
|
||||
/// <param name="fontPtr">Font to unscale.</param>
|
||||
/// <param name="fontScale">Scale factor.</param>
|
||||
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||
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<ImFontGlyphHotDataReal>(i).AdvanceX /= fontScale;
|
||||
font->IndexedHotData.Ref<ImFontGlyphHotDataReal>(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<ImFontKerningPair>(i).AdvanceXAdjustment /= fontScale;
|
||||
for (int i = 0, i_ = font->FrequentKerningPairs.Size; i < i_; i++)
|
||||
font->FrequentKerningPairs.Ref<float>(i) /= fontScale;
|
||||
}
|
||||
|
||||
if (rebuildLookupTable && fontPtr.Glyphs.Size > 0)
|
||||
fontPtr.BuildLookupTableNonstandard();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a glyph range for use with ImGui AddFont.
|
||||
/// </summary>
|
||||
/// <param name="family">Font family and size.</param>
|
||||
/// <param name="mergeDistance">Merge two ranges into one if distance is below the value specified in this parameter.</param>
|
||||
/// <returns>Glyph ranges.</returns>
|
||||
public GCHandle ToGlyphRanges(GameFontFamilyAndSize family, int mergeDistance = 8)
|
||||
{
|
||||
var fdt = this.fdts[(int)family]!;
|
||||
var ranges = new List<ushort>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="style">Font to use.</param>
|
||||
/// <returns>Handle to game font that may or may not be ready yet.</returns>
|
||||
public GameFontHandle NewFontRef(GameFontStyle style)
|
||||
{
|
||||
var interfaceManager = Service<InterfaceManager>.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<Framework>.GetAsync()
|
||||
.ContinueWith(task => task.Result.RunOnTick(() => interfaceManager.RebuildFonts()));
|
||||
}
|
||||
|
||||
return new(this, style);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font.
|
||||
/// </summary>
|
||||
/// <param name="style">Font to get.</param>
|
||||
/// <returns>Corresponding font or null.</returns>
|
||||
public ImFontPtr? GetFont(GameFontStyle style) => this.fonts.GetValueOrDefault(style, null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding FdtReader.
|
||||
/// </summary>
|
||||
/// <param name="family">Font to get.</param>
|
||||
/// <returns>Corresponding FdtReader or null.</returns>
|
||||
public FdtReader? GetFdtReader(GameFontFamilyAndSize family) => this.fdts[(int)family];
|
||||
|
||||
/// <summary>
|
||||
/// Fills missing glyphs in target font from source font, if both are not null.
|
||||
/// </summary>
|
||||
/// <param name="source">Source font.</param>
|
||||
/// <param name="target">Target font.</param>
|
||||
/// <param name="missingOnly">Whether to copy missing glyphs only.</param>
|
||||
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||
public void CopyGlyphsAcrossFonts(ImFontPtr? source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable)
|
||||
{
|
||||
ImGuiHelpers.CopyGlyphsAcrossFonts(source ?? default, this.fonts[target], missingOnly, rebuildLookupTable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills missing glyphs in target font from source font, if both are not null.
|
||||
/// </summary>
|
||||
/// <param name="source">Source font.</param>
|
||||
/// <param name="target">Target font.</param>
|
||||
/// <param name="missingOnly">Whether to copy missing glyphs only.</param>
|
||||
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||
public void CopyGlyphsAcrossFonts(GameFontStyle source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable)
|
||||
{
|
||||
ImGuiHelpers.CopyGlyphsAcrossFonts(this.fonts[source], target ?? default, missingOnly, rebuildLookupTable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills missing glyphs in target font from source font, if both are not null.
|
||||
/// </summary>
|
||||
/// <param name="source">Source font.</param>
|
||||
/// <param name="target">Target font.</param>
|
||||
/// <param name="missingOnly">Whether to copy missing glyphs only.</param>
|
||||
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||
public void CopyGlyphsAcrossFonts(GameFontStyle source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable)
|
||||
{
|
||||
ImGuiHelpers.CopyGlyphsAcrossFonts(this.fonts[source], this.fonts[target], missingOnly, rebuildLookupTable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build fonts before plugins do something more. To be called from InterfaceManager.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Record that ImGui.GetIO().Fonts.Build() has been called.
|
||||
/// </summary>
|
||||
public void AfterIoFontsBuild()
|
||||
{
|
||||
this.isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether GameFontMamager owns an ImFont.
|
||||
/// </summary>
|
||||
/// <param name="fontPtr">ImFontPtr to check.</param>
|
||||
/// <returns>Whether it owns.</returns>
|
||||
public bool OwnsFont(ImFontPtr fontPtr) => this.fonts.ContainsValue(fontPtr);
|
||||
|
||||
/// <summary>
|
||||
/// Post-build fonts before plugins do something more. To be called from InterfaceManager.
|
||||
/// </summary>
|
||||
public unsafe void AfterBuildFonts()
|
||||
{
|
||||
var interfaceManager = Service<InterfaceManager>.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<ImFontGlyphHotDataReal>(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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrease font reference counter.
|
||||
/// </summary>
|
||||
/// <param name="style">Font to release.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ public struct GameFontStyle
|
|||
/// </summary>
|
||||
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
|
|||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font family.
|
||||
/// </summary>
|
||||
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
|
|||
/// <summary>
|
||||
/// Gets the corresponding GameFontFamilyAndSize but with minimum possible font sizes.
|
||||
/// </summary>
|
||||
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
|
|||
/// <summary>
|
||||
/// Gets the base font size in point unit.
|
||||
/// </summary>
|
||||
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
|
|||
/// <summary>
|
||||
/// Gets the base font size in pixel unit.
|
||||
/// </summary>
|
||||
public readonly float BaseSizePx => this.BaseSizePt * 4 / 3;
|
||||
public float BaseSizePx => this.BaseSizePt * 4 / 3;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this font is bold.
|
||||
/// </summary>
|
||||
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
|
|||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -233,26 +233,13 @@ public struct GameFontStyle
|
|||
_ => GameFontFamilyAndSize.Undefined,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new scaled instance of <see cref="GameFontStyle"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="scale">The scale.</param>
|
||||
/// <returns>The scaled instance.</returns>
|
||||
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,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the adjustment to width resulting fron Weight and SkewStrength.
|
||||
/// </summary>
|
||||
/// <param name="header">Font header.</param>
|
||||
/// <param name="glyph">Glyph.</param>
|
||||
/// <returns>Width adjustment in pixel unit.</returns>
|
||||
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
|
|||
/// <param name="reader">Font information.</param>
|
||||
/// <param name="glyph">Glyph.</param>
|
||||
/// <returns>Width adjustment in pixel unit.</returns>
|
||||
public readonly int CalculateBaseWidthAdjustment(FdtReader reader, FdtReader.FontTableEntry glyph) =>
|
||||
public int CalculateBaseWidthAdjustment(FdtReader reader, FdtReader.FontTableEntry glyph) =>
|
||||
this.CalculateBaseWidthAdjustment(reader.FontHeader, glyph);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override readonly string ToString()
|
||||
public override string ToString()
|
||||
{
|
||||
return $"GameFontStyle({this.FamilyAndSize}, {this.SizePt}pt, skew={this.SkewStrength}, weight={this.Weight})";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<FontAtlasFactory>.Get()
|
||||
?.GetFdtReader(GameFontFamilyAndSize.Axis12)
|
||||
.FindGlyph(chr) is null)
|
||||
if (Service<GameFontManager>.Get()
|
||||
.GetFdtReader(GameFontFamilyAndSize.Axis12)
|
||||
?.FindGlyph(chr) is null)
|
||||
{
|
||||
if (!this.EncounteredHan)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<IDisposable>().AggregateToDisposable().Dispose();
|
||||
this.WindowSystem.RemoveAllWindows();
|
||||
|
||||
this.changelogWindow.Dispose();
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||
|
|
@ -35,12 +34,6 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
|||
|
||||
private readonly TitleScreenMenuWindow tsmWindow;
|
||||
|
||||
private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new();
|
||||
private readonly IFontAtlas privateAtlas;
|
||||
private readonly Lazy<IFontHandle> bannerFont;
|
||||
private readonly Lazy<IDalamudTextureWrap> apiBumpExplainerTexture;
|
||||
private readonly Lazy<IDalamudTextureWrap> logoTexture;
|
||||
|
||||
private readonly InOutCubic windowFade = new(TimeSpan.FromSeconds(2.5f))
|
||||
{
|
||||
Point1 = Vector2.Zero,
|
||||
|
|
@ -53,6 +46,10 @@ 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;
|
||||
|
|
@ -61,28 +58,15 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
|||
/// Initializes a new instance of the <see cref="ChangelogWindow"/> class.
|
||||
/// </summary>
|
||||
/// <param name="tsmWindow">TSM window.</param>
|
||||
/// <param name="fontAtlasFactory">An instance of <see cref="FontAtlasFactory"/>.</param>
|
||||
/// <param name="assets">An instance of <see cref="DalamudAssetManager"/>.</param>
|
||||
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<GameFontManager>.GetAsync().ContinueWith(t => this.MakeFont(t.Result));
|
||||
}
|
||||
|
||||
private enum State
|
||||
|
|
@ -113,13 +97,21 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
|||
Service<DalamudInterface>.Get().SetCreditsDarkeningAnimation(true);
|
||||
this.tsmWindow.AllowDrawing = false;
|
||||
|
||||
_ = this.bannerFont;
|
||||
this.MakeFont(Service<GameFontManager>.Get());
|
||||
|
||||
this.state = State.WindowFadeIn;
|
||||
this.windowFade.Reset();
|
||||
this.bodyFade.Reset();
|
||||
this.needFadeRestart = true;
|
||||
|
||||
if (this.apiBumpExplainerTexture == null)
|
||||
{
|
||||
var dalamud = Service<Dalamud>.Get();
|
||||
var tm = Service<TextureManager>.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<DalamudAssetManager>.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)
|
||||
{
|
||||
|
|
@ -281,10 +276,8 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
|||
|
||||
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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
|||
/// <summary>
|
||||
/// Class responsible for drawing the data/debug window.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose() => this.modules.OfType<IDisposable>().AggregateToDisposable().Dispose();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnOpen()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for testing game prebaked fonts.
|
||||
/// </summary>
|
||||
internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable
|
||||
{
|
||||
private ImVectorWrapper<byte> testStringBuffer;
|
||||
private IFontAtlas? privateAtlas;
|
||||
private IReadOnlyDictionary<GameFontFamily, (GameFontStyle Size, Lazy<IFontHandle> Handle)[]>? fontHandles;
|
||||
private bool useGlobalScale;
|
||||
private bool useWordWrap;
|
||||
private bool useItalic;
|
||||
private bool useBold;
|
||||
private bool useMinimumBuild;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string[]? CommandShortcuts { get; init; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string DisplayName { get; init; } = "Game Prebaked Fonts";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load() => this.Ready = true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<FontAtlasFactory>.Get().CreateFontAtlas(
|
||||
nameof(GamePrebakedFontsTestWidget),
|
||||
FontAtlasAutoRebuildMode.Async,
|
||||
this.useGlobalScale);
|
||||
this.fontHandles ??=
|
||||
Enum.GetValues<GameFontFamilyAndSize>()
|
||||
.Where(x => x.GetAttribute<GameFontFamilyAndSizeAttribute>() 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<IFontHandle>(
|
||||
() => 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
|||
/// </summary>
|
||||
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
|
|||
/// <inheritdoc/>
|
||||
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<DalamudConfiguration>.Get();
|
||||
var interfaceManager = Service<InterfaceManager>.Get();
|
||||
var fontAtlasFactory = Service<FontAtlasFactory>.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();
|
||||
|
|
|
|||
|
|
@ -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<FontAtlasFactory>
|
||||
.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<GameFontManager>.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
|
|||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public override void Dispose() => this.privateAtlas.Dispose();
|
||||
public override void Dispose()
|
||||
{
|
||||
this.logoTexture?.Dispose();
|
||||
this.thankYouFont?.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<FontAtlasFactory>.Get().UseAxisOverride = v;
|
||||
Service<InterfaceManager>.Get().RebuildFonts();
|
||||
var im = Service<InterfaceManager>.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<InterfaceManager>.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"))
|
||||
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))
|
||||
{
|
||||
foreach (var e in fontBuildTask.Exception.InnerExceptions)
|
||||
ImGui.TextUnformatted(e.ToString());
|
||||
}
|
||||
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<DalamudConfiguration>.Get().GlobalUiScale;
|
||||
this.fontGamma = Service<DalamudConfiguration>.Get().FontGammaLevel;
|
||||
|
||||
base.Load();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<IFontHandle> myFontHandle;
|
||||
private readonly Lazy<IDalamudTextureWrap> shadeTexture;
|
||||
|
||||
private readonly Dictionary<Guid, InOutCubic> shadeEasings = new();
|
||||
private readonly Dictionary<Guid, InOutQuint> moveEasings = new();
|
||||
private readonly Dictionary<Guid, InOutCubic> logoEasings = new();
|
||||
private readonly Dictionary<string, InterfaceManager.SpecialGlyphRequest> specialGlyphRequests = new();
|
||||
|
||||
private InOutCubic? fadeOutEasing;
|
||||
|
||||
|
|
@ -52,7 +48,6 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
|||
/// <param name="clientState">An instance of <see cref="ClientState"/>.</param>
|
||||
/// <param name="configuration">An instance of <see cref="DalamudConfiguration"/>.</param>
|
||||
/// <param name="dalamudAssetManager">An instance of <see cref="DalamudAssetManager"/>.</param>
|
||||
/// <param name="fontAtlasFactory">An instance of <see cref="FontAtlasFactory"/>.</param>
|
||||
/// <param name="framework">An instance of <see cref="Framework"/>.</param>
|
||||
/// <param name="titleScreenMenu">An instance of <see cref="TitleScreenMenu"/>.</param>
|
||||
/// <param name="gameGui">An instance of <see cref="gameGui"/>.</param>
|
||||
|
|
@ -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
|
|||
/// </summary>
|
||||
public bool AllowDrawing { get; set; } = true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose() => this.scopedFinalizer.Dispose();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void PreDraw()
|
||||
{
|
||||
|
|
@ -133,6 +109,12 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
|||
base.PostDraw();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.framework.Update -= this.FrameworkOnUpdate;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<InterfaceManager>.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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
namespace Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
/// <summary>
|
||||
/// How to rebuild <see cref="IFontAtlas"/>.
|
||||
/// </summary>
|
||||
public enum FontAtlasAutoRebuildMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Do not rebuild.
|
||||
/// </summary>
|
||||
Disable,
|
||||
|
||||
/// <summary>
|
||||
/// Rebuild on new frame.
|
||||
/// </summary>
|
||||
OnNewFrame,
|
||||
|
||||
/// <summary>
|
||||
/// Rebuild asynchronously.
|
||||
/// </summary>
|
||||
Async,
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
/// <summary>
|
||||
/// Build step for <see cref="IFontAtlas"/>.
|
||||
/// </summary>
|
||||
public enum FontAtlasBuildStep
|
||||
{
|
||||
/// <summary>
|
||||
/// An invalid value. This should never be passed through event callbacks.
|
||||
/// </summary>
|
||||
Invalid,
|
||||
|
||||
/// <summary>
|
||||
/// Called before calling <see cref="ImFontAtlasPtr.Build"/>.<br />
|
||||
/// Expect <see cref="IFontAtlasBuildToolkitPreBuild"/> to be passed.
|
||||
/// </summary>
|
||||
PreBuild,
|
||||
|
||||
/// <summary>
|
||||
/// Called after calling <see cref="ImFontAtlasPtr.Build"/>.<br />
|
||||
/// Expect <see cref="IFontAtlasBuildToolkitPostBuild"/> to be passed.<br />
|
||||
/// <br />
|
||||
/// This callback is not guaranteed to happen after <see cref="PreBuild"/>,
|
||||
/// but it will never happen on its own.
|
||||
/// </summary>
|
||||
PostBuild,
|
||||
|
||||
/// <summary>
|
||||
/// Called after promoting staging font atlas to the actual atlas for <see cref="IFontAtlas"/>.<br />
|
||||
/// Expect <see cref="PostBuild"/> to be passed.<br />
|
||||
/// <br />
|
||||
/// This callback is not guaranteed to happen after <see cref="IFontAtlasBuildToolkitPostPromotion"/>,
|
||||
/// but it will never happen on its own.
|
||||
/// </summary>
|
||||
PostPromotion,
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
namespace Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate to be called when a font needs to be built.
|
||||
/// </summary>
|
||||
/// <param name="toolkit">A toolkit that may help you for font building steps.</param>
|
||||
/// <remarks>
|
||||
/// An implementation of <see cref="IFontAtlasBuildToolkit"/> may implement all of
|
||||
/// <see cref="IFontAtlasBuildToolkitPreBuild"/>, <see cref="IFontAtlasBuildToolkitPostBuild"/>, and
|
||||
/// <see cref="IFontAtlasBuildToolkitPostPromotion"/>.<br />
|
||||
/// Either use <see cref="IFontAtlasBuildToolkit.BuildStep"/> to identify the build step, or use
|
||||
/// <see cref="FontAtlasBuildToolkitUtilities.OnPreBuild"/>, <see cref="FontAtlasBuildToolkitUtilities.OnPostBuild"/>,
|
||||
/// and <see cref="FontAtlasBuildToolkitUtilities.OnPostPromotion"/> for routing.
|
||||
/// </remarks>
|
||||
public delegate void FontAtlasBuildStepDelegate(IFontAtlasBuildToolkit toolkit);
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Dalamud.Interface.Utility;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
/// <summary>
|
||||
/// Convenience function for building fonts through <see cref="IFontAtlas"/>.
|
||||
/// </summary>
|
||||
public static class FontAtlasBuildToolkitUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Compiles given <see cref="char"/>s into an array of <see cref="ushort"/> containing ImGui glyph ranges.
|
||||
/// </summary>
|
||||
/// <param name="enumerable">The chars.</param>
|
||||
/// <param name="addFallbackCodepoints">Add fallback codepoints to the range.</param>
|
||||
/// <param name="addEllipsisCodepoints">Add ellipsis codepoints to the range.</param>
|
||||
/// <returns>The compiled range.</returns>
|
||||
public static ushort[] ToGlyphRange(
|
||||
this IEnumerable<char> enumerable,
|
||||
bool addFallbackCodepoints = true,
|
||||
bool addEllipsisCodepoints = true)
|
||||
{
|
||||
using var builderScoped = ImGuiHelpers.NewFontGlyphRangeBuilderPtrScoped(out var builder);
|
||||
foreach (var c in enumerable)
|
||||
builder.AddChar(c);
|
||||
return builder.BuildRangesToArray(addFallbackCodepoints, addEllipsisCodepoints);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles given <see cref="char"/>s into an array of <see cref="ushort"/> containing ImGui glyph ranges.
|
||||
/// </summary>
|
||||
/// <param name="span">The chars.</param>
|
||||
/// <param name="addFallbackCodepoints">Add fallback codepoints to the range.</param>
|
||||
/// <param name="addEllipsisCodepoints">Add ellipsis codepoints to the range.</param>
|
||||
/// <returns>The compiled range.</returns>
|
||||
public static ushort[] ToGlyphRange(
|
||||
this ReadOnlySpan<char> span,
|
||||
bool addFallbackCodepoints = true,
|
||||
bool addEllipsisCodepoints = true)
|
||||
{
|
||||
using var builderScoped = ImGuiHelpers.NewFontGlyphRangeBuilderPtrScoped(out var builder);
|
||||
foreach (var c in span)
|
||||
builder.AddChar(c);
|
||||
return builder.BuildRangesToArray(addFallbackCodepoints, addEllipsisCodepoints);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles given string into an array of <see cref="ushort"/> containing ImGui glyph ranges.
|
||||
/// </summary>
|
||||
/// <param name="string">The string.</param>
|
||||
/// <param name="addFallbackCodepoints">Add fallback codepoints to the range.</param>
|
||||
/// <param name="addEllipsisCodepoints">Add ellipsis codepoints to the range.</param>
|
||||
/// <returns>The compiled range.</returns>
|
||||
public static ushort[] ToGlyphRange(
|
||||
this string @string,
|
||||
bool addFallbackCodepoints = true,
|
||||
bool addEllipsisCodepoints = true) =>
|
||||
@string.AsSpan().ToGlyphRange(addFallbackCodepoints, addEllipsisCodepoints);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the corresponding <see cref="ImFontConfigPtr"/> in
|
||||
/// <see cref="IFontAtlasBuildToolkit.NewImAtlas"/>.<see cref="ImFontAtlasPtr.ConfigData"/> that corresponds to the
|
||||
/// specified font <paramref name="fontPtr"/>.
|
||||
/// </summary>
|
||||
/// <param name="toolkit">The toolkit.</param>
|
||||
/// <param name="fontPtr">The font.</param>
|
||||
/// <returns>The relevant config pointer, or empty config pointer if not found.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes <paramref name="action"/>
|
||||
/// if <see cref="IFontAtlasBuildToolkit.BuildStep"/> of <paramref name="toolkit"/>
|
||||
/// is <see cref="FontAtlasBuildStep.PreBuild"/>.
|
||||
/// </summary>
|
||||
/// <param name="toolkit">The toolkit.</param>
|
||||
/// <param name="action">The action.</param>
|
||||
/// <returns>This, for method chaining.</returns>
|
||||
public static IFontAtlasBuildToolkit OnPreBuild(
|
||||
this IFontAtlasBuildToolkit toolkit,
|
||||
Action<IFontAtlasBuildToolkitPreBuild> action)
|
||||
{
|
||||
if (toolkit.BuildStep is FontAtlasBuildStep.PreBuild)
|
||||
action.Invoke((IFontAtlasBuildToolkitPreBuild)toolkit);
|
||||
return toolkit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes <paramref name="action"/>
|
||||
/// if <see cref="IFontAtlasBuildToolkit.BuildStep"/> of <paramref name="toolkit"/>
|
||||
/// is <see cref="FontAtlasBuildStep.PostBuild"/>.
|
||||
/// </summary>
|
||||
/// <param name="toolkit">The toolkit.</param>
|
||||
/// <param name="action">The action.</param>
|
||||
/// <returns>toolkit, for method chaining.</returns>
|
||||
public static IFontAtlasBuildToolkit OnPostBuild(
|
||||
this IFontAtlasBuildToolkit toolkit,
|
||||
Action<IFontAtlasBuildToolkitPostBuild> action)
|
||||
{
|
||||
if (toolkit.BuildStep is FontAtlasBuildStep.PostBuild)
|
||||
action.Invoke((IFontAtlasBuildToolkitPostBuild)toolkit);
|
||||
return toolkit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes <paramref name="action"/>
|
||||
/// if <see cref="IFontAtlasBuildToolkit.BuildStep"/> of <paramref name="toolkit"/>
|
||||
/// is <see cref="FontAtlasBuildStep.PostPromotion"/>.
|
||||
/// </summary>
|
||||
/// <param name="toolkit">The toolkit.</param>
|
||||
/// <param name="action">The action.</param>
|
||||
/// <returns>toolkit, for method chaining.</returns>
|
||||
public static IFontAtlasBuildToolkit OnPostPromotion(
|
||||
this IFontAtlasBuildToolkit toolkit,
|
||||
Action<IFontAtlasBuildToolkitPostPromotion> action)
|
||||
{
|
||||
if (toolkit.BuildStep is FontAtlasBuildStep.PostPromotion)
|
||||
action.Invoke((IFontAtlasBuildToolkitPostPromotion)toolkit);
|
||||
return toolkit;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Interface.Utility;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for <see cref="ImFontAtlasPtr"/>.
|
||||
/// </summary>
|
||||
public interface IFontAtlas : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Event to be called on build step changes.<br />
|
||||
/// <see cref="IFontAtlasBuildToolkit.Font"/> is meaningless for this event.
|
||||
/// </summary>
|
||||
event FontAtlasBuildStepDelegate? BuildStepChange;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when a font rebuild operation is recommended.<br />
|
||||
/// This event will be invoked from the main thread.<br />
|
||||
/// <br />
|
||||
/// Reasons for the event include changes in <see cref="ImGuiHelpers.GlobalScale"/> and
|
||||
/// initialization of new associated font handles.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You should call <see cref="BuildFontsAsync"/> or <see cref="BuildFontsOnNextFrame"/>
|
||||
/// if <see cref="AutoRebuildMode"/> is not set to <c>true</c>.<br />
|
||||
/// Avoid calling <see cref="BuildFontsImmediately"/> here; it will block the main thread.
|
||||
/// </remarks>
|
||||
event Action? RebuildRecommend;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the atlas. For logging and debugging purposes.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value how the atlas should be rebuilt when the relevant Dalamud Configuration changes.
|
||||
/// </summary>
|
||||
FontAtlasAutoRebuildMode AutoRebuildMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font atlas. Might be empty.
|
||||
/// </summary>
|
||||
ImFontAtlasPtr ImAtlas { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the task that represents the current font rebuild state.
|
||||
/// </summary>
|
||||
Task BuildTask { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether there exists any built atlas, regardless of <see cref="BuildTask"/>.
|
||||
/// </summary>
|
||||
bool HasBuiltAtlas { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this font atlas is under the effect of global scale.
|
||||
/// </summary>
|
||||
bool IsGlobalScaled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Suppresses automatically rebuilding fonts for the scope.
|
||||
/// </summary>
|
||||
/// <returns>An instance of <see cref="IDisposable"/> that will release the suppression.</returns>
|
||||
/// <remarks>
|
||||
/// 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 <see cref="AutoRebuildMode"/> is set to
|
||||
/// <see cref="FontAtlasAutoRebuildMode.Disable"/>.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// using (atlas.SuppressBuild()) {
|
||||
/// this.font1 = atlas.NewGameFontHandle(...);
|
||||
/// this.font2 = atlas.NewDelegateFontHandle(...);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public IDisposable SuppressAutoRebuild();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="IFontHandle"/> from game's built-in fonts.
|
||||
/// </summary>
|
||||
/// <param name="style">Font to use.</param>
|
||||
/// <returns>Handle to a font that may or may not be ready yet.</returns>
|
||||
public IFontHandle NewGameFontHandle(GameFontStyle style);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new IFontHandle using your own callbacks.
|
||||
/// </summary>
|
||||
/// <param name="buildStepDelegate">Callback for <see cref="IFontAtlas.BuildStepChange"/>.</param>
|
||||
/// <returns>Handle to a font that may or may not be ready yet.</returns>
|
||||
/// <example>
|
||||
/// <b>On initialization</b>:
|
||||
/// <code>
|
||||
/// 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)));
|
||||
/// </code>
|
||||
/// <br />
|
||||
/// <b>On use</b>:
|
||||
/// <code>
|
||||
/// using (this.fontHandle.Push())
|
||||
/// ImGui.TextUnformatted("Example");
|
||||
/// </code>
|
||||
/// </example>
|
||||
public IFontHandle NewDelegateFontHandle(FontAtlasBuildStepDelegate buildStepDelegate);
|
||||
|
||||
/// <summary>
|
||||
/// Queues rebuilding fonts, on the main thread.<br />
|
||||
/// Note that <see cref="BuildTask"/> would not necessarily get changed from calling this function.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">If <see cref="AutoRebuildMode"/> is <see cref="FontAtlasAutoRebuildMode.Async"/>.</exception>
|
||||
void BuildFontsOnNextFrame();
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds fonts immediately, on the current thread.<br />
|
||||
/// Even the callback for <see cref="FontAtlasBuildStep.PostPromotion"/> will be called on the same thread.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">If <see cref="AutoRebuildMode"/> is <see cref="FontAtlasAutoRebuildMode.Async"/>.</exception>
|
||||
void BuildFontsImmediately();
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds fonts asynchronously, on any thread.
|
||||
/// </summary>
|
||||
/// <param name="callPostPromotionOnMainThread">Call <see cref="FontAtlasBuildStep.PostPromotion"/> on the main thread.</param>
|
||||
/// <returns>The task.</returns>
|
||||
/// <exception cref="InvalidOperationException">If <see cref="AutoRebuildMode"/> is <see cref="FontAtlasAutoRebuildMode.OnNewFrame"/>.</exception>
|
||||
Task BuildFontsAsync(bool callPostPromotionOnMainThread = true);
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Interface.Utility;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
/// <summary>
|
||||
/// Common stuff for <see cref="IFontAtlasBuildToolkitPreBuild"/> and <see cref="IFontAtlasBuildToolkitPostBuild"/>.
|
||||
/// </summary>
|
||||
public interface IFontAtlasBuildToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the font relevant to the call.
|
||||
/// </summary>
|
||||
ImFontPtr Font { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current scale this font atlas is being built with.
|
||||
/// </summary>
|
||||
float Scale { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current build operation is asynchronous.
|
||||
/// </summary>
|
||||
bool IsAsyncBuildOperation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current build step.
|
||||
/// </summary>
|
||||
FontAtlasBuildStep BuildStep { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font atlas being built.
|
||||
/// </summary>
|
||||
ImFontAtlasPtr NewImAtlas { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the wrapper for <see cref="ImFontAtlas.Fonts"/> of <see cref="NewImAtlas"/>.<br />
|
||||
/// This does not need to be disposed. Calling <see cref="IDisposable.Dispose"/> does nothing.-
|
||||
/// <br />
|
||||
/// Modification of this vector may result in undefined behaviors.
|
||||
/// </summary>
|
||||
ImVectorWrapper<ImFontPtr> Fonts { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Queues an item to be disposed after the native atlas gets disposed, successful or not.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Disposable type.</typeparam>
|
||||
/// <param name="disposable">The disposable.</param>
|
||||
/// <returns>The same <paramref name="disposable"/>.</returns>
|
||||
T DisposeWithAtlas<T>(T disposable) where T : IDisposable;
|
||||
|
||||
/// <summary>
|
||||
/// Queues an item to be disposed after the native atlas gets disposed, successful or not.
|
||||
/// </summary>
|
||||
/// <param name="gcHandle">The gc handle.</param>
|
||||
/// <returns>The same <paramref name="gcHandle"/>.</returns>
|
||||
GCHandle DisposeWithAtlas(GCHandle gcHandle);
|
||||
|
||||
/// <summary>
|
||||
/// Queues an item to be disposed after the native atlas gets disposed, successful or not.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to run on dispose.</param>
|
||||
void DisposeWithAtlas(Action action);
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
using Dalamud.Interface.Internal;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
/// <summary>
|
||||
/// Toolkit for use when the build state is <see cref="FontAtlasBuildStep.PostBuild"/>.
|
||||
/// </summary>
|
||||
public interface IFontAtlasBuildToolkitPostBuild : IFontAtlasBuildToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets whether global scaling is ignored for the given font.
|
||||
/// </summary>
|
||||
/// <param name="fontPtr">The font.</param>
|
||||
/// <returns>True if ignored.</returns>
|
||||
bool IsGlobalScaleIgnored(ImFontPtr fontPtr);
|
||||
|
||||
/// <summary>
|
||||
/// Stores a texture to be managed with the atlas.
|
||||
/// </summary>
|
||||
/// <param name="textureWrap">The texture wrap.</param>
|
||||
/// <param name="disposeOnError">Dispose the wrap on error.</param>
|
||||
/// <returns>The texture index.</returns>
|
||||
int StoreTexture(IDalamudTextureWrap textureWrap, bool disposeOnError);
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
/// <summary>
|
||||
/// Toolkit for use when the build state is <see cref="FontAtlasBuildStep.PostPromotion"/>.
|
||||
/// </summary>
|
||||
public interface IFontAtlasBuildToolkitPostPromotion : IFontAtlasBuildToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// Copies glyphs across fonts, in a safer way.<br />
|
||||
/// If the font does not belong to the current atlas, this function is a no-op.
|
||||
/// </summary>
|
||||
/// <param name="source">Source font.</param>
|
||||
/// <param name="target">Target font.</param>
|
||||
/// <param name="missingOnly">Whether to copy missing glyphs only.</param>
|
||||
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||
/// <param name="rangeLow">Low codepoint range to copy.</param>
|
||||
/// <param name="rangeHigh">High codepoing range to copy.</param>
|
||||
void CopyGlyphsAcrossFonts(
|
||||
ImFontPtr source,
|
||||
ImFontPtr target,
|
||||
bool missingOnly,
|
||||
bool rebuildLookupTable = true,
|
||||
char rangeLow = ' ',
|
||||
char rangeHigh = '\uFFFE');
|
||||
|
||||
/// <summary>
|
||||
/// Calls <see cref="ImFontPtr.BuildLookupTable"/>, with some fixups.
|
||||
/// </summary>
|
||||
/// <param name="font">The font.</param>
|
||||
void BuildLookupTable(ImFontPtr font);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Toolkit for use when the build state is <see cref="FontAtlasBuildStep.PreBuild"/>.<br />
|
||||
/// <br />
|
||||
/// After <see cref="FontAtlasBuildStepDelegate"/> returns,
|
||||
/// either <see cref="IFontAtlasBuildToolkit.Font"/> must be set,
|
||||
/// or at least one font must have been added to the atlas using one of AddFont... functions.
|
||||
/// </summary>
|
||||
public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// Queues an item to be disposed after the whole build process gets complete, successful or not.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Disposable type.</typeparam>
|
||||
/// <param name="disposable">The disposable.</param>
|
||||
/// <returns>The same <paramref name="disposable"/>.</returns>
|
||||
T DisposeAfterBuild<T>(T disposable) where T : IDisposable;
|
||||
|
||||
/// <summary>
|
||||
/// Queues an item to be disposed after the whole build process gets complete, successful or not.
|
||||
/// </summary>
|
||||
/// <param name="gcHandle">The gc handle.</param>
|
||||
/// <returns>The same <paramref name="gcHandle"/>.</returns>
|
||||
GCHandle DisposeAfterBuild(GCHandle gcHandle);
|
||||
|
||||
/// <summary>
|
||||
/// Queues an item to be disposed after the whole build process gets complete, successful or not.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to run on dispose.</param>
|
||||
void DisposeAfterBuild(Action action);
|
||||
|
||||
/// <summary>
|
||||
/// Excludes given font from global scaling.
|
||||
/// </summary>
|
||||
/// <param name="fontPtr">The font.</param>
|
||||
/// <returns>Same <see cref="ImFontPtr"/> with <paramref name="fontPtr"/>.</returns>
|
||||
ImFontPtr IgnoreGlobalScale(ImFontPtr fontPtr);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether global scaling is ignored for the given font.
|
||||
/// </summary>
|
||||
/// <param name="fontPtr">The font.</param>
|
||||
/// <returns>True if ignored.</returns>
|
||||
bool IsGlobalScaleIgnored(ImFontPtr fontPtr);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a font from memory region allocated using <see cref="ImGuiHelpers.AllocateMemory"/>.<br />
|
||||
/// <strong>It WILL crash if you try to use a memory pointer allocated in some other way.</strong><br />
|
||||
/// <strong>
|
||||
/// Do NOT call <see cref="ImGuiNative.igMemFree"/> on the <paramref name="dataPointer"/> once this function has
|
||||
/// been called, unless <paramref name="freeOnException"/> is set and the function has thrown an error.
|
||||
/// </strong>
|
||||
/// </summary>
|
||||
/// <param name="dataPointer">Memory address for the data allocated using <see cref="ImGuiHelpers.AllocateMemory"/>.</param>
|
||||
/// <param name="dataSize">The size of the font file..</param>
|
||||
/// <param name="fontConfig">The font config.</param>
|
||||
/// <param name="freeOnException">Free <paramref name="dataPointer"/> if an exception happens.</param>
|
||||
/// <param name="debugTag">A debug tag.</param>
|
||||
/// <returns>The newly added font.</returns>
|
||||
unsafe ImFontPtr AddFontFromImGuiHeapAllocatedMemory(
|
||||
nint dataPointer,
|
||||
int dataSize,
|
||||
in SafeFontConfig fontConfig,
|
||||
bool freeOnException,
|
||||
string debugTag)
|
||||
=> this.AddFontFromImGuiHeapAllocatedMemory(
|
||||
(void*)dataPointer,
|
||||
dataSize,
|
||||
fontConfig,
|
||||
freeOnException,
|
||||
debugTag);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a font from memory region allocated using <see cref="ImGuiHelpers.AllocateMemory"/>.<br />
|
||||
/// <strong>It WILL crash if you try to use a memory pointer allocated in some other way.</strong><br />
|
||||
/// <strong>Do NOT call <see cref="ImGuiNative.igMemFree"/> on the <paramref name="dataPointer"/> once this
|
||||
/// function has been called.</strong>
|
||||
/// </summary>
|
||||
/// <param name="dataPointer">Memory address for the data allocated using <see cref="ImGuiHelpers.AllocateMemory"/>.</param>
|
||||
/// <param name="dataSize">The size of the font file..</param>
|
||||
/// <param name="fontConfig">The font config.</param>
|
||||
/// <param name="freeOnException">Free <paramref name="dataPointer"/> if an exception happens.</param>
|
||||
/// <param name="debugTag">A debug tag.</param>
|
||||
/// <returns>The newly added font.</returns>
|
||||
unsafe ImFontPtr AddFontFromImGuiHeapAllocatedMemory(
|
||||
void* dataPointer,
|
||||
int dataSize,
|
||||
in SafeFontConfig fontConfig,
|
||||
bool freeOnException,
|
||||
string debugTag);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a font from a file.
|
||||
/// </summary>
|
||||
/// <param name="path">The file path to create a new font from.</param>
|
||||
/// <param name="fontConfig">The font config.</param>
|
||||
/// <returns>The newly added font.</returns>
|
||||
ImFontPtr AddFontFromFile(string path, in SafeFontConfig fontConfig);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a font from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to create a new font from.</param>
|
||||
/// <param name="fontConfig">The font config.</param>
|
||||
/// <param name="leaveOpen">Dispose when this function returns or throws.</param>
|
||||
/// <param name="debugTag">A debug tag.</param>
|
||||
/// <returns>The newly added font.</returns>
|
||||
ImFontPtr AddFontFromStream(Stream stream, in SafeFontConfig fontConfig, bool leaveOpen, string debugTag);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a font from memory.
|
||||
/// </summary>
|
||||
/// <param name="span">The span to create from.</param>
|
||||
/// <param name="fontConfig">The font config.</param>
|
||||
/// <param name="debugTag">A debug tag.</param>
|
||||
/// <returns>The newly added font.</returns>
|
||||
ImFontPtr AddFontFromMemory(ReadOnlySpan<byte> span, in SafeFontConfig fontConfig, string debugTag);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the default font known to the current font atlas.<br />
|
||||
/// <br />
|
||||
/// Includes <see cref="AddFontAwesomeIconFont"/> and <see cref="AttachExtraGlyphsForDalamudLanguage"/>.<br />
|
||||
/// As this involves adding multiple fonts, calling this function will set <see cref="IFontAtlasBuildToolkit.Font"/>
|
||||
/// as the return value of this function, if it was empty before.
|
||||
/// </summary>
|
||||
/// <param name="sizePx">Font size in pixels.</param>
|
||||
/// <param name="glyphRanges">The glyph ranges. Use <see cref="FontAtlasBuildToolkitUtilities"/>.ToGlyphRange to build.</param>
|
||||
/// <returns>A font returned from <see cref="ImFontAtlasPtr.AddFont"/>.</returns>
|
||||
ImFontPtr AddDalamudDefaultFont(float sizePx, ushort[]? glyphRanges = null);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a font that is shipped with Dalamud.<br />
|
||||
/// <br />
|
||||
/// 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 <paramref name="fontConfig"/>
|
||||
/// will be ignored but <see cref="SafeFontConfig.SizePx"/>, <see cref="SafeFontConfig.MergeFont"/>,
|
||||
/// and <see cref="SafeFontConfig.GlyphRanges"/>.
|
||||
/// </summary>
|
||||
/// <param name="asset">The font type.</param>
|
||||
/// <param name="fontConfig">The font config.</param>
|
||||
/// <returns>The added font.</returns>
|
||||
ImFontPtr AddDalamudAssetFont(DalamudAsset asset, in SafeFontConfig fontConfig);
|
||||
|
||||
/// <summary>
|
||||
/// Same with <see cref="AddDalamudAssetFont"/>(<see cref="DalamudAsset.FontAwesomeFreeSolid"/>, ...),
|
||||
/// but using only FontAwesome icon ranges.<br />
|
||||
/// <see cref="SafeFontConfig.GlyphRanges"/> will be ignored.
|
||||
/// </summary>
|
||||
/// <param name="fontConfig">The font config.</param>
|
||||
/// <returns>The added font.</returns>
|
||||
ImFontPtr AddFontAwesomeIconFont(in SafeFontConfig fontConfig);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the game's symbols into the provided font.<br />
|
||||
/// <see cref="SafeFontConfig.GlyphRanges"/> will be ignored.<br />
|
||||
/// If the game symbol font file is unavailable, only <see cref="SafeFontConfig.SizePx"/> will be honored.
|
||||
/// </summary>
|
||||
/// <param name="fontConfig">The font config.</param>
|
||||
/// <returns>The added font.</returns>
|
||||
ImFontPtr AddGameSymbol(in SafeFontConfig fontConfig);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the game glyphs to the font.
|
||||
/// </summary>
|
||||
/// <param name="gameFontStyle">The font style.</param>
|
||||
/// <param name="glyphRanges">The glyph ranges.</param>
|
||||
/// <param name="mergeFont">The font to merge to. If empty, then a new font will be created.</param>
|
||||
/// <returns>The added font.</returns>
|
||||
ImFontPtr AddGameGlyphs(GameFontStyle gameFontStyle, ushort[]? glyphRanges, ImFontPtr mergeFont);
|
||||
|
||||
/// <summary>
|
||||
/// Adds glyphs of extra languages into the provided font, depending on Dalamud Configuration.<br />
|
||||
/// <see cref="SafeFontConfig.GlyphRanges"/> will be ignored.
|
||||
/// </summary>
|
||||
/// <param name="fontConfig">The font config.</param>
|
||||
void AttachExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig);
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a reference counting handle for fonts.
|
||||
/// </summary>
|
||||
public interface IFontHandle : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a reference counting handle for fonts. Dalamud internal use only.
|
||||
/// </summary>
|
||||
internal interface IInternal : IFontHandle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the font.<br />
|
||||
/// Use of this properly is safe only from the UI thread.<br />
|
||||
/// Use <see cref="IFontHandle.Push"/> if the intended purpose of this property is <see cref="ImGui.PushFont"/>.<br />
|
||||
/// Futures changes may make simple <see cref="ImGui.PushFont"/> not enough.
|
||||
/// </summary>
|
||||
ImFontPtr ImFont { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the load exception, if it failed to load. Otherwise, it is null.
|
||||
/// </summary>
|
||||
Exception? LoadException { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this font is ready for use.<br />
|
||||
/// Use <see cref="Push"/> directly if you want to keep the current ImGui font if the font is not ready.
|
||||
/// </summary>
|
||||
bool Available { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Pushes the current font into ImGui font stack using <see cref="ImGui.PushFont"/>, if available.<br />
|
||||
/// Use <see cref="ImGui.GetFont"/> to access the current font.<br />
|
||||
/// You may not access the font once you dispose this object.
|
||||
/// </summary>
|
||||
/// <returns>A disposable object that will call <see cref="ImGui.PopFont"/>(1) on dispose.</returns>
|
||||
IDisposable Push();
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// A font handle representing a user-callback generated font.
|
||||
/// </summary>
|
||||
internal class DelegateFontHandle : IFontHandle.IInternal
|
||||
{
|
||||
private IFontHandleManager? manager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DelegateFontHandle"/> class.
|
||||
/// </summary>
|
||||
/// <param name="manager">An instance of <see cref="IFontHandleManager"/>.</param>
|
||||
/// <param name="callOnBuildStepChange">Callback for <see cref="IFontAtlas.BuildStepChange"/>.</param>
|
||||
public DelegateFontHandle(IFontHandleManager manager, FontAtlasBuildStepDelegate callOnBuildStepChange)
|
||||
{
|
||||
this.manager = manager;
|
||||
this.CallOnBuildStepChange = callOnBuildStepChange;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the function to be called on build step changes.
|
||||
/// </summary>
|
||||
public FontAtlasBuildStepDelegate CallOnBuildStepChange { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Exception? LoadException => this.ManagerNotDisposed.Substance?.GetBuildException(this);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Available => this.ImFont.IsNotNullAndLoaded();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontPtr ImFont => this.ManagerNotDisposed.Substance?.GetFontPtr(this) ?? default;
|
||||
|
||||
private IFontHandleManager ManagerNotDisposed =>
|
||||
this.manager ?? throw new ObjectDisposedException(nameof(GamePrebakedFontHandle));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.manager?.FreeFontHandle(this);
|
||||
this.manager = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDisposable Push() => ImRaii.PushFont(this.ImFont, this.Available);
|
||||
|
||||
/// <summary>
|
||||
/// Manager for <see cref="DelegateFontHandle"/>s.
|
||||
/// </summary>
|
||||
internal sealed class HandleManager : IFontHandleManager
|
||||
{
|
||||
private readonly HashSet<DelegateFontHandle> handles = new();
|
||||
private readonly object syncRoot = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandleManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="atlasName">The name of the owner atlas.</param>
|
||||
public HandleManager(string atlasName) => this.Name = $"{atlasName}:{nameof(DelegateFontHandle)}:Manager";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action? RebuildRecommend;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandleSubstance? Substance { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
lock (this.syncRoot)
|
||||
{
|
||||
this.handles.Clear();
|
||||
this.Substance?.Dispose();
|
||||
this.Substance = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IFontAtlas.NewDelegateFontHandle"/>
|
||||
public IFontHandle NewFontHandle(FontAtlasBuildStepDelegate buildStepDelegate)
|
||||
{
|
||||
var key = new DelegateFontHandle(this, buildStepDelegate);
|
||||
lock (this.syncRoot)
|
||||
this.handles.Add(key);
|
||||
this.RebuildRecommend?.Invoke();
|
||||
return key;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void FreeFontHandle(IFontHandle handle)
|
||||
{
|
||||
if (handle is not DelegateFontHandle cgfh)
|
||||
return;
|
||||
|
||||
lock (this.syncRoot)
|
||||
this.handles.Remove(cgfh);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandleSubstance NewSubstance()
|
||||
{
|
||||
lock (this.syncRoot)
|
||||
return new HandleSubstance(this, this.handles.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Substance from <see cref="HandleManager"/>.
|
||||
/// </summary>
|
||||
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<DelegateFontHandle, ImFontPtr> fonts = new();
|
||||
private readonly Dictionary<DelegateFontHandle, Exception?> buildExceptions = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandleSubstance"/> class.
|
||||
/// </summary>
|
||||
/// <param name="manager">The manager.</param>
|
||||
/// <param name="relevantHandles">The relevant handles.</param>
|
||||
public HandleSubstance(IFontHandleManager manager, DelegateFontHandle[] relevantHandles)
|
||||
{
|
||||
this.Manager = manager;
|
||||
this.relevantHandles = relevantHandles;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandleManager Manager { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.fonts.Clear();
|
||||
this.buildExceptions.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontPtr GetFontPtr(IFontHandle handle) =>
|
||||
handle is DelegateFontHandle cgfh ? this.fonts.GetValueOrDefault(cgfh) : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Exception? GetBuildException(IFontHandle handle) =>
|
||||
handle is DelegateFontHandle cgfh ? this.buildExceptions.GetValueOrDefault(cgfh) : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnPreBuildCleanup(IFontAtlasBuildToolkitPreBuild toolkitPreBuild)
|
||||
{
|
||||
// irrelevant
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Standalone font atlas.
|
||||
/// </summary>
|
||||
internal sealed partial class FontAtlasFactory
|
||||
{
|
||||
private static readonly Dictionary<ulong, List<(char Left, char Right, float Distance)>> PairAdjustmentsCache =
|
||||
new();
|
||||
|
||||
/// <summary>
|
||||
/// Implementations for <see cref="IFontAtlasBuildToolkitPreBuild"/> and
|
||||
/// <see cref="IFontAtlasBuildToolkitPostBuild"/>.
|
||||
/// </summary>
|
||||
private class BuildToolkit : IFontAtlasBuildToolkitPreBuild, IFontAtlasBuildToolkitPostBuild, IDisposable
|
||||
{
|
||||
private static readonly ushort FontAwesomeIconMin =
|
||||
(ushort)Enum.GetValues<FontAwesomeIcon>().Where(x => x > 0).Min();
|
||||
|
||||
private static readonly ushort FontAwesomeIconMax =
|
||||
(ushort)Enum.GetValues<FontAwesomeIcon>().Where(x => x > 0).Max();
|
||||
|
||||
private readonly DisposeSafety.ScopedFinalizer disposeAfterBuild = new();
|
||||
private readonly GamePrebakedFontHandle.HandleSubstance gameFontHandleSubstance;
|
||||
private readonly FontAtlasFactory factory;
|
||||
private readonly FontAtlasBuiltData data;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BuildToolkit"/> class.
|
||||
/// </summary>
|
||||
/// <param name="factory">An instance of <see cref="FontAtlasFactory"/>.</param>
|
||||
/// <param name="data">New atlas.</param>
|
||||
/// <param name="gameFontHandleSubstance">An instance of <see cref="GamePrebakedFontHandle.HandleSubstance"/>.</param>
|
||||
/// <param name="isAsync">Specify whether the current build operation is an asynchronous one.</param>
|
||||
public BuildToolkit(
|
||||
FontAtlasFactory factory,
|
||||
FontAtlasBuiltData data,
|
||||
GamePrebakedFontHandle.HandleSubstance gameFontHandleSubstance,
|
||||
bool isAsync)
|
||||
{
|
||||
this.data = data;
|
||||
this.gameFontHandleSubstance = gameFontHandleSubstance;
|
||||
this.IsAsyncBuildOperation = isAsync;
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IFontAtlasBuildToolkit.Font"/>
|
||||
public ImFontPtr Font { get; set; }
|
||||
|
||||
/// <inheritdoc cref="IFontAtlasBuildToolkit.Font"/>
|
||||
public float Scale => this.data.Scale;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsAsyncBuildOperation { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FontAtlasBuildStep BuildStep { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontAtlasPtr NewImAtlas => this.data.Atlas;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImVectorWrapper<ImFontPtr> Fonts => this.data.Fonts;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of fonts to ignore global scale.
|
||||
/// </summary>
|
||||
public List<ImFontPtr> GlobalScaleExclusions { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose() => this.disposeAfterBuild.Dispose();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T2 DisposeAfterBuild<T2>(T2 disposable) where T2 : IDisposable =>
|
||||
this.disposeAfterBuild.Add(disposable);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public GCHandle DisposeAfterBuild(GCHandle gcHandle) => this.disposeAfterBuild.Add(gcHandle);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void DisposeAfterBuild(Action action) => this.disposeAfterBuild.Add(action);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T DisposeWithAtlas<T>(T disposable) where T : IDisposable => this.data.Garbage.Add(disposable);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public GCHandle DisposeWithAtlas(GCHandle gcHandle) => this.data.Garbage.Add(gcHandle);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void DisposeWithAtlas(Action action) => this.data.Garbage.Add(action);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontPtr IgnoreGlobalScale(ImFontPtr fontPtr)
|
||||
{
|
||||
this.GlobalScaleExclusions.Add(fontPtr);
|
||||
return fontPtr;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IFontAtlasBuildToolkitPreBuild.IsGlobalScaleIgnored"/>
|
||||
public bool IsGlobalScaleIgnored(ImFontPtr fontPtr) =>
|
||||
this.GlobalScaleExclusions.Contains(fontPtr);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int StoreTexture(IDalamudTextureWrap textureWrap, bool disposeOnError) =>
|
||||
this.data.AddNewTexture(textureWrap, disposeOnError);
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontPtr AddFontFromFile(string path, in SafeFontConfig fontConfig)
|
||||
{
|
||||
return this.AddFontFromStream(
|
||||
File.OpenRead(path),
|
||||
fontConfig,
|
||||
false,
|
||||
$"{nameof(this.AddFontFromFile)}({path})");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe ImFontPtr AddFontFromMemory(
|
||||
ReadOnlySpan<byte> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontPtr AddFontAwesomeIconFont(in SafeFontConfig fontConfig) => this.AddDalamudAssetFont(
|
||||
DalamudAsset.FontAwesomeFreeSolid,
|
||||
fontConfig with
|
||||
{
|
||||
GlyphRanges = new ushort[] { FontAwesomeIconMin, FontAwesomeIconMax, 0 },
|
||||
});
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontPtr AddGameSymbol(in SafeFontConfig fontConfig) =>
|
||||
this.AddDalamudAssetFont(
|
||||
DalamudAsset.LodestoneGameSymbol,
|
||||
fontConfig with
|
||||
{
|
||||
GlyphRanges = new ushort[]
|
||||
{
|
||||
GamePrebakedFontHandle.SeIconCharMin,
|
||||
GamePrebakedFontHandle.SeIconCharMax,
|
||||
0,
|
||||
},
|
||||
});
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontPtr AddGameGlyphs(GameFontStyle gameFontStyle, ushort[]? glyphRanges, ImFontPtr mergeFont) =>
|
||||
this.gameFontHandleSubstance.AttachGameGlyphs(this, mergeFont, gameFontStyle, glyphRanges);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AttachExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig)
|
||||
{
|
||||
var dalamudConfiguration = Service<DalamudConfiguration>.Get();
|
||||
if (dalamudConfiguration.EffectiveLanguage == "ko"
|
||||
|| Service<DalamudIme>.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<DalamudConfiguration>.Get().EffectiveLanguage == "tw")
|
||||
{
|
||||
this.AddFontFromFile(fontPathCht, fontConfig with
|
||||
{
|
||||
GlyphRanges = ImGuiHelpers.CreateImGuiRangesFrom(
|
||||
UnicodeRanges.CjkUnifiedIdeographs,
|
||||
UnicodeRanges.CjkUnifiedIdeographsExtensionA),
|
||||
});
|
||||
}
|
||||
else if (fontPathChs != null && (Service<DalamudConfiguration>.Get().EffectiveLanguage == "zh"
|
||||
|| Service<DalamudIme>.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<byte>();
|
||||
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<byte>.Shared.Return(buf);
|
||||
buf = ArrayPool<byte>.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<byte>.Shared.Return(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementations for <see cref="IFontAtlasBuildToolkitPostPromotion"/>.
|
||||
/// </summary>
|
||||
private class BuildToolkitPostPromotion : IFontAtlasBuildToolkitPostPromotion
|
||||
{
|
||||
private readonly FontAtlasBuiltData builtData;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BuildToolkitPostPromotion"/> class.
|
||||
/// </summary>
|
||||
/// <param name="builtData">The built data.</param>
|
||||
public BuildToolkitPostPromotion(FontAtlasBuiltData builtData) => this.builtData = builtData;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontPtr Font { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float Scale => this.builtData.Scale;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsAsyncBuildOperation => true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FontAtlasBuildStep BuildStep => FontAtlasBuildStep.PostPromotion;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontAtlasPtr NewImAtlas => this.builtData.Atlas;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe ImVectorWrapper<ImFontPtr> Fonts => new(
|
||||
&this.NewImAtlas.NativePtr->Fonts,
|
||||
x => ImGuiNative.ImFont_destroy(x->NativePtr));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T DisposeWithAtlas<T>(T disposable) where T : IDisposable => this.builtData.Garbage.Add(disposable);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public GCHandle DisposeWithAtlas(GCHandle gcHandle) => this.builtData.Garbage.Add(gcHandle);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void DisposeWithAtlas(Action action) => this.builtData.Garbage.Add(action);
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Standalone font atlas.
|
||||
/// </summary>
|
||||
internal sealed partial class FontAtlasFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Fallback codepoints for ImFont.
|
||||
/// </summary>
|
||||
public const string FallbackCodepoints = "\u3013\uFFFD?-";
|
||||
|
||||
/// <summary>
|
||||
/// Ellipsis codepoints for ImFont.
|
||||
/// </summary>
|
||||
public const string EllipsisCodepoints = "\u2026\u0085";
|
||||
|
||||
/// <summary>
|
||||
/// If set, disables concurrent font build operation.
|
||||
/// </summary>
|
||||
private static readonly object? NoConcurrentBuildOperationLock = null; // new();
|
||||
|
||||
private static readonly ModuleLog Log = new(nameof(FontAtlasFactory));
|
||||
|
||||
private static readonly Task<FontAtlasBuiltData> 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<IDalamudTextureWrap>? wraps;
|
||||
private readonly List<IFontHandleSubstance>? substances;
|
||||
private readonly DisposeSafety.ScopedFinalizer? garbage;
|
||||
|
||||
public unsafe FontAtlasBuiltData(
|
||||
DalamudFontAtlas owner,
|
||||
IEnumerable<IFontHandleSubstance> 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<ImFontPtr> Fonts => this.Atlas.FontsWrapped();
|
||||
|
||||
public readonly ImVectorWrapper<ImFontConfig> ConfigData => this.Atlas.ConfigDataWrapped();
|
||||
|
||||
public readonly ImVectorWrapper<ImFontAtlasTexture> ImTextures => this.Atlas.TexturesWrapped();
|
||||
|
||||
public readonly IReadOnlyList<IDalamudTextureWrap> Wraps =>
|
||||
(IReadOnlyList<IDalamudTextureWrap>?)this.wraps ?? Array.Empty<IDalamudTextureWrap>();
|
||||
|
||||
public readonly IReadOnlyList<IFontHandleSubstance> Substances =>
|
||||
(IReadOnlyList<IFontHandleSubstance>?)this.substances ?? Array.Empty<IFontHandleSubstance>();
|
||||
|
||||
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<GamePrebakedFontHandle.HandleSubstance>().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<FontAtlasBuiltData> buildTask = EmptyTask;
|
||||
private FontAtlasBuiltData builtData;
|
||||
|
||||
private int buildSuppressionCounter;
|
||||
private bool buildSuppressionSuppressed;
|
||||
|
||||
private int buildIndex;
|
||||
private bool buildQueued;
|
||||
private bool disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DalamudFontAtlas"/> class.
|
||||
/// </summary>
|
||||
/// <param name="factory">The factory.</param>
|
||||
/// <param name="atlasName">Name of atlas, for debugging and logging purposes.</param>
|
||||
/// <param name="autoRebuildMode">Specify how to auto rebuild.</param>
|
||||
/// <param name="isGlobalScaled">Whether the fonts in the atlas are under the effect of global scale.</param>
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="DalamudFontAtlas"/> class.
|
||||
/// </summary>
|
||||
~DalamudFontAtlas()
|
||||
{
|
||||
lock (this.syncRoot)
|
||||
{
|
||||
this.buildTask.ToDisposableIgnoreExceptions().Dispose();
|
||||
this.builtData.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event FontAtlasBuildStepDelegate? BuildStepChange;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action? RebuildRecommend;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<DisposeSafety.IDisposeCallback>? BeforeDispose;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<DisposeSafety.IDisposeCallback, Exception?>? AfterDispose;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FontAtlasAutoRebuildMode AutoRebuildMode { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontAtlasPtr ImAtlas
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (this.syncRoot)
|
||||
return this.builtData.Atlas;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task BuildTask => this.buildTask;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool HasBuiltAtlas => !this.builtData.Atlas.IsNull();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsGlobalScaled { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDisposable SuppressAutoRebuild()
|
||||
{
|
||||
this.buildSuppressionCounter++;
|
||||
return Disposable.Create(
|
||||
() =>
|
||||
{
|
||||
this.buildSuppressionCounter--;
|
||||
if (this.buildSuppressionSuppressed)
|
||||
this.OnRebuildRecommend();
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandle NewGameFontHandle(GameFontStyle style) => this.gameFontHandleManager.NewFontHandle(style);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandle NewDelegateFontHandle(FontAtlasBuildStepDelegate buildStepDelegate) =>
|
||||
this.delegateFontHandleManager.NewFontHandle(buildStepDelegate);
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<FontAtlasBuiltData>();
|
||||
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));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<FontAtlasBuiltData> BuildInner(Task<FontAtlasBuiltData> 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<FontAtlasBuiltData> RebuildFontsPrivate(bool isAsync, float scale)
|
||||
{
|
||||
if (NoConcurrentBuildOperationLock is null)
|
||||
return this.RebuildFontsPrivateReal(isAsync, scale);
|
||||
lock (NoConcurrentBuildOperationLock)
|
||||
return this.RebuildFontsPrivateReal(isAsync, scale);
|
||||
}
|
||||
|
||||
private async Task<FontAtlasBuiltData> 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Factory for the implementation of <see cref="IFontAtlas"/>.
|
||||
/// </summary>
|
||||
[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<GameFontFamilyAndSize, Task<byte[]>> fdtFiles;
|
||||
private readonly IReadOnlyDictionary<string, Task<Task<TexFile>[]>> texFiles;
|
||||
private readonly IReadOnlyDictionary<string, Task<IDalamudTextureWrap?[]>> prebakedTextureWraps;
|
||||
private readonly Task<ushort[]> 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<InterfaceManager.InterfaceManagerWithScene>
|
||||
.GetAsync()
|
||||
.ContinueWith(r => r.Result.Manager.Scene);
|
||||
|
||||
var gffasInfo = Enum.GetValues<GameFontFamilyAndSize>()
|
||||
.Select(
|
||||
x =>
|
||||
(
|
||||
Font: x,
|
||||
Attr: x.GetAttribute<GameFontFamilyAndSizeAttribute>()))
|
||||
.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<TexFile>(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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to override configuration for UseAxis.
|
||||
/// </summary>
|
||||
public bool? UseAxisOverride { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to use AXIS fonts.
|
||||
/// </summary>
|
||||
public bool UseAxis => this.UseAxisOverride ?? Service<DalamudConfiguration>.Get().UseAxisFontsFromGame;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the service instance of <see cref="Framework"/>.
|
||||
/// </summary>
|
||||
public Framework Framework { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the service instance of <see cref="InterfaceManager"/>.<br />
|
||||
/// <see cref="Internal.InterfaceManager.Scene"/> may not yet be available.
|
||||
/// </summary>
|
||||
public InterfaceManager InterfaceManager { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the async task for <see cref="RawDX11Scene"/> inside <see cref="InterfaceManager"/>.
|
||||
/// </summary>
|
||||
public Task<RawDX11Scene> SceneTask { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default glyph ranges (glyph ranges of <see cref="GameFontFamilyAndSize.Axis12"/>).
|
||||
/// </summary>
|
||||
public ushort[] DefaultGlyphRanges => ExtractResult(this.defaultGlyphRanges);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether game symbol font file is available.
|
||||
/// </summary>
|
||||
public bool HasGameSymbolsFontFile =>
|
||||
this.dalamudAssetManager.IsStreamImmediatelyAvailable(DalamudAsset.LodestoneGameSymbol);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.cancellationTokenSource.Cancel();
|
||||
this.scopedFinalizer.Dispose();
|
||||
this.cancellationTokenSource.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a class that implements the <see cref="IFontAtlas"/> interface.
|
||||
/// </summary>
|
||||
/// <param name="atlasName">Name of atlas, for debugging and logging purposes.</param>
|
||||
/// <param name="autoRebuildMode">Specify how to auto rebuild.</param>
|
||||
/// <param name="isGlobalScaled">Whether the fonts in the atlas is global scaled.</param>
|
||||
/// <returns>The new font atlas.</returns>
|
||||
public IFontAtlas CreateFontAtlas(
|
||||
string atlasName,
|
||||
FontAtlasAutoRebuildMode autoRebuildMode,
|
||||
bool isGlobalScaled = true) =>
|
||||
new DalamudFontAtlas(this, atlasName, autoRebuildMode, isGlobalScaled);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the font from Dalamud Assets.
|
||||
/// </summary>
|
||||
/// <param name="toolkitPreBuild">The toolkitPostBuild.</param>
|
||||
/// <param name="asset">The font.</param>
|
||||
/// <param name="fontConfig">The font config.</param>
|
||||
/// <returns>The address and size.</returns>
|
||||
public ImFontPtr AddFont(
|
||||
IFontAtlasBuildToolkitPreBuild toolkitPreBuild,
|
||||
DalamudAsset asset,
|
||||
in SafeFontConfig fontConfig) =>
|
||||
toolkitPreBuild.AddFontFromStream(
|
||||
this.dalamudAssetManager.CreateStream(asset),
|
||||
fontConfig,
|
||||
false,
|
||||
$"Asset({asset})");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="FdtReader"/> for the <see cref="GameFontFamilyAndSize"/>.
|
||||
/// </summary>
|
||||
/// <param name="gffas">The font family and size.</param>
|
||||
/// <returns>The <see cref="FdtReader"/>.</returns>
|
||||
public FdtReader GetFdtReader(GameFontFamilyAndSize gffas) => new(ExtractResult(this.fdtFiles[gffas]));
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int GetFontTextureCount(string texPathFormat) =>
|
||||
ExtractResult(this.prebakedTextureWraps[texPathFormat]).Length;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TexFile GetTexFile(string texPathFormat, int index) =>
|
||||
ExtractResult(ExtractResult(this.texFiles[texPathFormat])[index]);
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<T>(Task<T> t) => t.IsCompleted ? t.Result : t.GetAwaiter().GetResult();
|
||||
|
||||
private static unsafe void ExtractChannelFromB8G8R8A8(
|
||||
Span<byte> target,
|
||||
ReadOnlySpan<byte> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clones a texture wrap, by getting a new reference to the underlying <see cref="ShaderResourceView"/> and the
|
||||
/// texture behind.
|
||||
/// </summary>
|
||||
/// <param name="wrap">The <see cref="IDalamudTextureWrap"/> to clone from.</param>
|
||||
/// <returns>The cloned <see cref="IDalamudTextureWrap"/>.</returns>
|
||||
private static IDalamudTextureWrap CloneTextureWrap(IDalamudTextureWrap wrap)
|
||||
{
|
||||
var srv = CppObject.FromPointer<ShaderResourceView>(wrap.ImGuiHandle);
|
||||
using var res = srv.Resource;
|
||||
using var tex2D = res.QueryInterface<Texture2D>();
|
||||
var description = tex2D.Description;
|
||||
return new DalamudTextureWrap(
|
||||
new D3DTextureWrap(
|
||||
srv.QueryInterface<ShaderResourceView>(),
|
||||
description.Width,
|
||||
description.Height));
|
||||
}
|
||||
|
||||
private static unsafe void ExtractChannelFromB4G4R4A4(
|
||||
Span<byte> target,
|
||||
ReadOnlySpan<byte> 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<InterfaceManager.InterfaceManagerWithScene>.Get();
|
||||
var targetIsB4G4R4A4 = this.InterfaceManager.SupportsDxgiFormat(Format.B4G4R4A4_UNorm);
|
||||
var bpp = targetIsB4G4R4A4 ? 2 : 4;
|
||||
var buffer = ArrayPool<byte>.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<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// A font handle that uses the game's built-in fonts, optionally with some styling.
|
||||
/// </summary>
|
||||
internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
||||
{
|
||||
/// <summary>
|
||||
/// The smallest value of <see cref="SeIconChar"/>.
|
||||
/// </summary>
|
||||
public static readonly char SeIconCharMin = (char)Enum.GetValues<SeIconChar>().Min();
|
||||
|
||||
/// <summary>
|
||||
/// The largest value of <see cref="SeIconChar"/>.
|
||||
/// </summary>
|
||||
public static readonly char SeIconCharMax = (char)Enum.GetValues<SeIconChar>().Max();
|
||||
|
||||
private IFontHandleManager? manager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GamePrebakedFontHandle"/> class.
|
||||
/// </summary>
|
||||
/// <param name="manager">An instance of <see cref="IFontHandleManager"/>.</param>
|
||||
/// <param name="style">Font to use.</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provider for <see cref="IDalamudTextureWrap"/> for `common/font/fontNN.tex`.
|
||||
/// </summary>
|
||||
public interface IGameFontTextureProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the <see cref="FdtFileView"/> for the <see cref="GameFontFamilyAndSize"/>.<br />
|
||||
/// <strong>Dispose after use.</strong>
|
||||
/// </summary>
|
||||
/// <param name="gffas">The font family and size.</param>
|
||||
/// <param name="fdtFileView">The view.</param>
|
||||
/// <returns>Dispose this after use..</returns>
|
||||
public MemoryHandle CreateFdtFileView(GameFontFamilyAndSize gffas, out FdtFileView fdtFileView);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of font textures.
|
||||
/// </summary>
|
||||
/// <param name="texPathFormat">Format of .tex path.</param>
|
||||
/// <returns>The number of textures.</returns>
|
||||
public int GetFontTextureCount(string texPathFormat);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="TexFile"/> for the given index of a font.
|
||||
/// </summary>
|
||||
/// <param name="texPathFormat">Format of .tex path.</param>
|
||||
/// <param name="index">The index of .tex file.</param>
|
||||
/// <returns>The <see cref="TexFile"/>.</returns>
|
||||
public TexFile GetTexFile(string texPathFormat, int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a new reference of the font texture.
|
||||
/// </summary>
|
||||
/// <param name="texPathFormat">Format of .tex path.</param>
|
||||
/// <param name="textureIndex">Texture index.</param>
|
||||
/// <returns>The texture.</returns>
|
||||
public IDalamudTextureWrap NewFontTextureRef(string texPathFormat, int textureIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font style.
|
||||
/// </summary>
|
||||
public GameFontStyle FontStyle { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Exception? LoadException => this.ManagerNotDisposed.Substance?.GetBuildException(this);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Available => this.ImFont.IsNotNullAndLoaded();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontPtr ImFont => this.ManagerNotDisposed.Substance?.GetFontPtr(this) ?? default;
|
||||
|
||||
private IFontHandleManager ManagerNotDisposed =>
|
||||
this.manager ?? throw new ObjectDisposedException(nameof(GamePrebakedFontHandle));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.manager?.FreeFontHandle(this);
|
||||
this.manager = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDisposable Push() => ImRaii.PushFont(this.ImFont, this.Available);
|
||||
|
||||
/// <summary>
|
||||
/// Manager for <see cref="GamePrebakedFontHandle"/>s.
|
||||
/// </summary>
|
||||
internal sealed class HandleManager : IFontHandleManager
|
||||
{
|
||||
private readonly Dictionary<GameFontStyle, int> gameFontsRc = new();
|
||||
private readonly object syncRoot = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandleManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="atlasName">The name of the owner atlas.</param>
|
||||
/// <param name="gameFontTextureProvider">An instance of <see cref="IGameFontTextureProvider"/>.</param>
|
||||
public HandleManager(string atlasName, IGameFontTextureProvider gameFontTextureProvider)
|
||||
{
|
||||
this.GameFontTextureProvider = gameFontTextureProvider;
|
||||
this.Name = $"{atlasName}:{nameof(GamePrebakedFontHandle)}:Manager";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action? RebuildRecommend;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandleSubstance? Substance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance of <see cref="IGameFontTextureProvider"/>.
|
||||
/// </summary>
|
||||
public IGameFontTextureProvider GameFontTextureProvider { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Substance?.Dispose();
|
||||
this.Substance = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IFontAtlas.NewGameFontHandle"/>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandleSubstance NewSubstance()
|
||||
{
|
||||
lock (this.syncRoot)
|
||||
return new HandleSubstance(this, this.gameFontsRc.Keys);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Substance from <see cref="HandleManager"/>.
|
||||
/// </summary>
|
||||
internal sealed class HandleSubstance : IFontHandleSubstance
|
||||
{
|
||||
private readonly HandleManager handleManager;
|
||||
private readonly HashSet<GameFontStyle> gameFontStyles;
|
||||
|
||||
// Owned by this class, but ImFontPtr values still do not belong to this.
|
||||
private readonly Dictionary<GameFontStyle, FontDrawPlan> fonts = new();
|
||||
private readonly Dictionary<GameFontStyle, Exception?> buildExceptions = new();
|
||||
private readonly List<(ImFontPtr Font, GameFontStyle Style, ushort[]? Ranges)> attachments = new();
|
||||
|
||||
private readonly HashSet<ImFontPtr> templatedFonts = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandleSubstance"/> class.
|
||||
/// </summary>
|
||||
/// <param name="manager">The manager.</param>
|
||||
/// <param name="gameFontStyles">The game font styles.</param>
|
||||
public HandleSubstance(HandleManager manager, IEnumerable<GameFontStyle> gameFontStyles)
|
||||
{
|
||||
this.handleManager = manager;
|
||||
Service<InterfaceManager>.Get();
|
||||
this.gameFontStyles = new(gameFontStyles);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandleManager Manager => this.handleManager;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches game symbols to the given font. If font is null, it will be created.
|
||||
/// </summary>
|
||||
/// <param name="toolkitPreBuild">The toolkitPostBuild.</param>
|
||||
/// <param name="font">The font to attach to.</param>
|
||||
/// <param name="style">The game font style.</param>
|
||||
/// <param name="glyphRanges">The intended glyph ranges.</param>
|
||||
/// <returns><paramref name="font"/> if it is not empty; otherwise a new font.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates or gets a relevant <see cref="ImFontPtr"/> for the given <see cref="GameFontStyle"/>.
|
||||
/// </summary>
|
||||
/// <param name="style">The game font style.</param>
|
||||
/// <param name="toolkitPreBuild">The toolkitPostBuild.</param>
|
||||
/// <returns>The font.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontPtr GetFontPtr(IFontHandle handle) =>
|
||||
handle is GamePrebakedFontHandle ggfh
|
||||
? this.fonts.GetValueOrDefault(ggfh.FontStyle)?.FullRangeFont ?? default
|
||||
: default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Exception? GetBuildException(IFontHandle handle) =>
|
||||
handle is GamePrebakedFontHandle ggfh ? this.buildExceptions.GetValueOrDefault(ggfh.FontStyle) : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe void OnPostBuild(IFontAtlasBuildToolkitPostBuild toolkitPostBuild)
|
||||
{
|
||||
var allTextureIndices = new Dictionary<string, int[]>();
|
||||
var allTexFiles = new Dictionary<string, TexFile[]>();
|
||||
using var rentReturn = Disposable.Create(
|
||||
() =>
|
||||
{
|
||||
foreach (var x in allTextureIndices.Values)
|
||||
ArrayPool<int>.Shared.Return(x);
|
||||
foreach (var x in allTexFiles.Values)
|
||||
ArrayPool<TexFile>.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnPostPromotion(IFontAtlasBuildToolkitPostPromotion toolkitPostPromotion)
|
||||
{
|
||||
// Irrelevant
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new template font.
|
||||
/// </summary>
|
||||
/// <param name="toolkitPreBuild">The toolkitPostBuild.</param>
|
||||
/// <param name="sizePx">The size of the font.</param>
|
||||
/// <returns>The font.</returns>
|
||||
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<ImFontPtr, BitArray> 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<GameFontFamilyAndSizeAttribute>()!;
|
||||
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<ImGuiHelpers.ImFontGlyphHotDataReal>(
|
||||
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<ImGuiHelpers.ImFontGlyphHotDataReal>(
|
||||
fallbackCharCandidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void SetFullRangeFontGlyphs(
|
||||
IFontAtlasBuildToolkitPostBuild toolkitPostBuild,
|
||||
Dictionary<string, TexFile[]> allTexFiles,
|
||||
Dictionary<string, int[]> 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<TexFile>.Shared.Rent(this.TexCount));
|
||||
}
|
||||
|
||||
if (!allTextureIndices.TryGetValue(this.BaseAttr.TexPathFormat, out var textureIndices))
|
||||
{
|
||||
allTextureIndices.Add(
|
||||
this.BaseAttr.TexPathFormat,
|
||||
textureIndices = ArrayPool<int>.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
|
||||
/// <summary>
|
||||
/// Manager for <see cref="IFontHandle"/>.
|
||||
/// </summary>
|
||||
internal interface IFontHandleManager : IDisposable
|
||||
{
|
||||
/// <inheritdoc cref="IFontAtlas.RebuildRecommend"/>
|
||||
event Action? RebuildRecommend;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the font handle manager. For logging and debugging purposes.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the active font handle substance.
|
||||
/// </summary>
|
||||
IFontHandleSubstance? Substance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Decrease font reference counter.
|
||||
/// </summary>
|
||||
/// <param name="handle">Handle being released.</param>
|
||||
void FreeFontHandle(IFontHandle handle);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new substance of the font atlas.
|
||||
/// </summary>
|
||||
/// <returns>The new substance.</returns>
|
||||
IFontHandleSubstance NewSubstance();
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
|
||||
/// <summary>
|
||||
/// Substance of a font.
|
||||
/// </summary>
|
||||
internal interface IFontHandleSubstance : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the manager relevant to this instance of <see cref="IFontHandleSubstance"/>.
|
||||
/// </summary>
|
||||
IFontHandleManager Manager { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle to get from.</param>
|
||||
/// <returns>Corresponding font or null.</returns>
|
||||
ImFontPtr GetFontPtr(IFontHandle handle);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the exception happened while loading for the font.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle to get from.</param>
|
||||
/// <returns>Corresponding font or null.</returns>
|
||||
Exception? GetBuildException(IFontHandle handle);
|
||||
|
||||
/// <summary>
|
||||
/// Called before <see cref="ImFontAtlasPtr.Build"/> call.
|
||||
/// </summary>
|
||||
/// <param name="toolkitPreBuild">The toolkit.</param>
|
||||
void OnPreBuild(IFontAtlasBuildToolkitPreBuild toolkitPreBuild);
|
||||
|
||||
/// <summary>
|
||||
/// Called between <see cref="OnPreBuild"/> and <see cref="ImFontAtlasPtr.Build"/> calls.<br />
|
||||
/// Any further modification to <see cref="IFontAtlasBuildToolkit.Fonts"/> will result in undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="toolkitPreBuild">The toolkit.</param>
|
||||
void OnPreBuildCleanup(IFontAtlasBuildToolkitPreBuild toolkitPreBuild);
|
||||
|
||||
/// <summary>
|
||||
/// Called after <see cref="ImFontAtlasPtr.Build"/> call.
|
||||
/// </summary>
|
||||
/// <param name="toolkitPostBuild">The toolkit.</param>
|
||||
void OnPostBuild(IFontAtlasBuildToolkitPostBuild toolkitPostBuild);
|
||||
|
||||
/// <summary>
|
||||
/// Called on the specific thread depending on <see cref="IFontAtlasBuildToolkit.IsAsyncBuildOperation"/> after
|
||||
/// promoting the staging atlas to direct use with <see cref="IFontAtlas"/>.
|
||||
/// </summary>
|
||||
/// <param name="toolkitPostPromotion">The toolkit.</param>
|
||||
void OnPostPromotion(IFontAtlasBuildToolkitPostPromotion toolkitPostPromotion);
|
||||
}
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
using System.Buffers.Binary;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
|
||||
/// <summary>
|
||||
/// Deals with TrueType.
|
||||
/// </summary>
|
||||
internal static partial class TrueTypeUtils
|
||||
{
|
||||
private struct Fixed : IComparable<Fixed>
|
||||
{
|
||||
public ushort Major;
|
||||
public ushort Minor;
|
||||
|
||||
public Fixed(ushort major, ushort minor)
|
||||
{
|
||||
this.Major = major;
|
||||
this.Minor = minor;
|
||||
}
|
||||
|
||||
public Fixed(PointerSpan<byte> 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<KerningPair>
|
||||
{
|
||||
public ushort Left;
|
||||
public ushort Right;
|
||||
public short Value;
|
||||
|
||||
public KerningPair(PointerSpan<byte> 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<byte> 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<byte> 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<TagStruct>, IComparable<TagStruct>
|
||||
{
|
||||
[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<byte> 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<byte> 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]}\"";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
|
||||
/// <summary>
|
||||
/// Deals with TrueType.
|
||||
/// </summary>
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Deals with TrueType.
|
||||
/// </summary>
|
||||
[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<TagStruct, PointerSpan<byte>>
|
||||
{
|
||||
// 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<byte> Memory;
|
||||
public readonly int OffsetInCollection;
|
||||
public readonly ushort TableCount;
|
||||
|
||||
public SfntFile(PointerSpan<byte> 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<TagStruct> Keys => this.Select(x => x.Key);
|
||||
|
||||
public IEnumerable<PointerSpan<byte>> Values => this.Select(x => x.Value);
|
||||
|
||||
public PointerSpan<byte> this[TagStruct key] => this.First(x => x.Key == key).Value;
|
||||
|
||||
public IEnumerator<KeyValuePair<TagStruct, PointerSpan<byte>>> 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<DirectoryTableEntry>();
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
public bool ContainsKey(TagStruct key) => this.Any(x => x.Key == key);
|
||||
|
||||
public bool TryGetValue(TagStruct key, out PointerSpan<byte> 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<byte> Memory;
|
||||
|
||||
public DirectoryTableEntry(PointerSpan<byte> 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<SfntFile>
|
||||
{
|
||||
public static readonly TagStruct FileTag = new('t', 't', 'c', 'f');
|
||||
|
||||
public readonly PointerSpan<byte> Memory;
|
||||
public readonly TagStruct Tag;
|
||||
public readonly ushort MajorVersion;
|
||||
public readonly ushort MinorVersion;
|
||||
public readonly int FontCount;
|
||||
|
||||
public TtcFile(PointerSpan<byte> 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<SfntFile> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < this.FontCount; i++)
|
||||
yield return this[i];
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Deals with TrueType.
|
||||
/// </summary>
|
||||
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<byte> Memory;
|
||||
|
||||
public ClassDefTable(PointerSpan<byte> 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<byte> Memory;
|
||||
|
||||
public Format1ClassArray(PointerSpan<byte> 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<ushort> ClassValueArray => new(
|
||||
this.Memory[6..].As<ushort>(this.GlyphCount),
|
||||
BinaryPrimitives.ReverseEndianness);
|
||||
}
|
||||
|
||||
public readonly struct Format2ClassRanges
|
||||
{
|
||||
public readonly PointerSpan<byte> Memory;
|
||||
|
||||
public Format2ClassRanges(PointerSpan<byte> memory) => this.Memory = memory;
|
||||
|
||||
public ushort ClassRangeCount => this.Memory.ReadU16Big(2);
|
||||
|
||||
public BigEndianPointerSpan<ClassRangeRecord> ClassValueArray => new(
|
||||
this.Memory[4..].As<ClassRangeRecord>(this.ClassRangeCount),
|
||||
ClassRangeRecord.ReverseEndianness);
|
||||
|
||||
public struct ClassRangeRecord : IComparable<ClassRangeRecord>
|
||||
{
|
||||
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<byte> Memory;
|
||||
|
||||
public CoverageTable(PointerSpan<byte> memory) => this.Memory = memory;
|
||||
|
||||
public enum CoverageFormat : ushort
|
||||
{
|
||||
Glyphs = 1,
|
||||
RangeRecords = 2,
|
||||
}
|
||||
|
||||
public CoverageFormat Format => this.Memory.ReadEnumBig<CoverageFormat>(0);
|
||||
|
||||
public ushort Count => this.Memory.ReadU16Big(2);
|
||||
|
||||
public BigEndianPointerSpan<ushort> Glyphs =>
|
||||
this.Format == CoverageFormat.Glyphs
|
||||
? new(this.Memory[4..].As<ushort>(this.Count), BinaryPrimitives.ReverseEndianness)
|
||||
: default(BigEndianPointerSpan<ushort>);
|
||||
|
||||
public BigEndianPointerSpan<RangeRecord> RangeRecords =>
|
||||
this.Format == CoverageFormat.RangeRecords
|
||||
? new(this.Memory[4..].As<RangeRecord>(this.Count), RangeRecord.ReverseEndianness)
|
||||
: default(BigEndianPointerSpan<RangeRecord>);
|
||||
|
||||
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<PointerSpan<byte>>
|
||||
{
|
||||
public readonly PointerSpan<byte> Memory;
|
||||
|
||||
public LookupTable(PointerSpan<byte> memory) => this.Memory = memory;
|
||||
|
||||
public LookupType Type => this.Memory.ReadEnumBig<LookupType>(0);
|
||||
|
||||
public byte MarkAttachmentType => this.Memory[2];
|
||||
|
||||
public LookupFlags Flags => (LookupFlags)this.Memory[3];
|
||||
|
||||
public ushort SubtableCount => this.Memory.ReadU16Big(4);
|
||||
|
||||
public BigEndianPointerSpan<ushort> SubtableOffsets => new(
|
||||
this.Memory[6..].As<ushort>(this.SubtableCount),
|
||||
BinaryPrimitives.ReverseEndianness);
|
||||
|
||||
public PointerSpan<byte> this[int index] => this.Memory[this.SubtableOffsets[this.EnsureIndex(index)] ..];
|
||||
|
||||
public IEnumerator<PointerSpan<byte>> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Deals with TrueType.
|
||||
/// </summary>
|
||||
internal static partial class TrueTypeUtils
|
||||
{
|
||||
private delegate int BinarySearchComparer<T>(in T value);
|
||||
|
||||
private static IDisposable CreatePointerSpan<T>(this T[] data, out PointerSpan<T> 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<T>(this IReadOnlyList<T> span, in T value)
|
||||
where T : unmanaged, IComparable<T>
|
||||
{
|
||||
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<T>(this IReadOnlyList<T> span, BinarySearchComparer<T> 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<byte> ps, int offset) =>
|
||||
BinaryPrimitives.ReadInt16BigEndian(ps.Span[offset..]);
|
||||
|
||||
private static int ReadI32Big(this PointerSpan<byte> ps, int offset) =>
|
||||
BinaryPrimitives.ReadInt32BigEndian(ps.Span[offset..]);
|
||||
|
||||
private static long ReadI64Big(this PointerSpan<byte> ps, int offset) =>
|
||||
BinaryPrimitives.ReadInt64BigEndian(ps.Span[offset..]);
|
||||
|
||||
private static ushort ReadU16Big(this PointerSpan<byte> ps, int offset) =>
|
||||
BinaryPrimitives.ReadUInt16BigEndian(ps.Span[offset..]);
|
||||
|
||||
private static uint ReadU32Big(this PointerSpan<byte> ps, int offset) =>
|
||||
BinaryPrimitives.ReadUInt32BigEndian(ps.Span[offset..]);
|
||||
|
||||
private static ulong ReadU64Big(this PointerSpan<byte> ps, int offset) =>
|
||||
BinaryPrimitives.ReadUInt64BigEndian(ps.Span[offset..]);
|
||||
|
||||
private static Half ReadF16Big(this PointerSpan<byte> ps, int offset) =>
|
||||
BinaryPrimitives.ReadHalfBigEndian(ps.Span[offset..]);
|
||||
|
||||
private static float ReadF32Big(this PointerSpan<byte> ps, int offset) =>
|
||||
BinaryPrimitives.ReadSingleBigEndian(ps.Span[offset..]);
|
||||
|
||||
private static double ReadF64Big(this PointerSpan<byte> ps, int offset) =>
|
||||
BinaryPrimitives.ReadDoubleBigEndian(ps.Span[offset..]);
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, int offset, out short value) =>
|
||||
value = BinaryPrimitives.ReadInt16BigEndian(ps.Span[offset..]);
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, int offset, out int value) =>
|
||||
value = BinaryPrimitives.ReadInt32BigEndian(ps.Span[offset..]);
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, int offset, out long value) =>
|
||||
value = BinaryPrimitives.ReadInt64BigEndian(ps.Span[offset..]);
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, int offset, out ushort value) =>
|
||||
value = BinaryPrimitives.ReadUInt16BigEndian(ps.Span[offset..]);
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, int offset, out uint value) =>
|
||||
value = BinaryPrimitives.ReadUInt32BigEndian(ps.Span[offset..]);
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, int offset, out ulong value) =>
|
||||
value = BinaryPrimitives.ReadUInt64BigEndian(ps.Span[offset..]);
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, int offset, out Half value) =>
|
||||
value = BinaryPrimitives.ReadHalfBigEndian(ps.Span[offset..]);
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, int offset, out float value) =>
|
||||
value = BinaryPrimitives.ReadSingleBigEndian(ps.Span[offset..]);
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, int offset, out double value) =>
|
||||
value = BinaryPrimitives.ReadDoubleBigEndian(ps.Span[offset..]);
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, ref int offset, out short value)
|
||||
{
|
||||
ps.ReadBig(offset, out value);
|
||||
offset += 2;
|
||||
}
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, ref int offset, out int value)
|
||||
{
|
||||
ps.ReadBig(offset, out value);
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, ref int offset, out long value)
|
||||
{
|
||||
ps.ReadBig(offset, out value);
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, ref int offset, out ushort value)
|
||||
{
|
||||
ps.ReadBig(offset, out value);
|
||||
offset += 2;
|
||||
}
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, ref int offset, out uint value)
|
||||
{
|
||||
ps.ReadBig(offset, out value);
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, ref int offset, out ulong value)
|
||||
{
|
||||
ps.ReadBig(offset, out value);
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, ref int offset, out Half value)
|
||||
{
|
||||
ps.ReadBig(offset, out value);
|
||||
offset += 2;
|
||||
}
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, ref int offset, out float value)
|
||||
{
|
||||
ps.ReadBig(offset, out value);
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
private static void ReadBig(this PointerSpan<byte> ps, ref int offset, out double value)
|
||||
{
|
||||
ps.ReadBig(offset, out value);
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
private static unsafe T ReadEnumBig<T>(this PointerSpan<byte> ps, int offset) where T : unmanaged, Enum
|
||||
{
|
||||
switch (Marshal.SizeOf(Enum.GetUnderlyingType(typeof(T))))
|
||||
{
|
||||
case 1:
|
||||
var b1 = ps.Span[offset];
|
||||
return *(T*)&b1;
|
||||
case 2:
|
||||
var b2 = ps.ReadU16Big(offset);
|
||||
return *(T*)&b2;
|
||||
case 4:
|
||||
var b4 = ps.ReadU32Big(offset);
|
||||
return *(T*)&b4;
|
||||
case 8:
|
||||
var b8 = ps.ReadU64Big(offset);
|
||||
return *(T*)&b8;
|
||||
default:
|
||||
throw new ArgumentException("Enum is not of size 1, 2, 4, or 8.", nameof(T), null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReadBig<T>(this PointerSpan<byte> ps, int offset, out T value) where T : unmanaged, Enum =>
|
||||
value = ps.ReadEnumBig<T>(offset);
|
||||
|
||||
private static void ReadBig<T>(this PointerSpan<byte> ps, ref int offset, out T value) where T : unmanaged, Enum
|
||||
{
|
||||
value = ps.ReadEnumBig<T>(offset);
|
||||
offset += Unsafe.SizeOf<T>();
|
||||
}
|
||||
|
||||
private readonly unsafe struct PointerSpan<T> : IList<T>, IReadOnlyList<T>, ICollection
|
||||
where T : unmanaged
|
||||
{
|
||||
public readonly T* Pointer;
|
||||
|
||||
public PointerSpan(T* pointer, int count)
|
||||
{
|
||||
this.Pointer = pointer;
|
||||
this.Count = count;
|
||||
}
|
||||
|
||||
public PointerSpan(nint pointer, int count)
|
||||
: this((T*)pointer, count)
|
||||
{
|
||||
}
|
||||
|
||||
public Span<T> Span => new(this.Pointer, this.Count);
|
||||
|
||||
public bool IsEmpty => this.Count == 0;
|
||||
|
||||
public int Count { get; }
|
||||
|
||||
public int Length => this.Count;
|
||||
|
||||
public int ByteCount => sizeof(T) * this.Count;
|
||||
|
||||
bool ICollection.IsSynchronized => false;
|
||||
|
||||
object ICollection.SyncRoot => this;
|
||||
|
||||
bool ICollection<T>.IsReadOnly => false;
|
||||
|
||||
public ref T this[int index] => ref this.Pointer[this.EnsureIndex(index)];
|
||||
|
||||
public PointerSpan<T> this[Range range] => this.Slice(range.GetOffsetAndLength(this.Count));
|
||||
|
||||
T IList<T>.this[int index]
|
||||
{
|
||||
get => this.Pointer[this.EnsureIndex(index)];
|
||||
set => this.Pointer[this.EnsureIndex(index)] = value;
|
||||
}
|
||||
|
||||
T IReadOnlyList<T>.this[int index] => this.Pointer[this.EnsureIndex(index)];
|
||||
|
||||
public bool ContainsPointer<T2>(T2* obj) where T2 : unmanaged =>
|
||||
(T*)obj >= this.Pointer && (T*)(obj + 1) <= this.Pointer + this.Count;
|
||||
|
||||
public PointerSpan<T> Slice(int offset, int count) => new(this.Pointer + offset, count);
|
||||
|
||||
public PointerSpan<T> Slice((int Offset, int Count) offsetAndCount)
|
||||
=> this.Slice(offsetAndCount.Offset, offsetAndCount.Count);
|
||||
|
||||
public PointerSpan<T2> As<T2>(int count)
|
||||
where T2 : unmanaged =>
|
||||
count > this.Count / sizeof(T2)
|
||||
? throw new ArgumentOutOfRangeException(
|
||||
nameof(count),
|
||||
count,
|
||||
$"Wanted {count} items; had {this.Count / sizeof(T2)} items")
|
||||
: new((T2*)this.Pointer, count);
|
||||
|
||||
public PointerSpan<T2> As<T2>()
|
||||
where T2 : unmanaged =>
|
||||
new((T2*)this.Pointer, this.Count / sizeof(T2));
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < this.Count; i++)
|
||||
yield return this[i];
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item) => throw new NotSupportedException();
|
||||
|
||||
void ICollection<T>.Clear() => throw new NotSupportedException();
|
||||
|
||||
bool ICollection<T>.Contains(T item)
|
||||
{
|
||||
for (var i = 0; i < this.Count; i++)
|
||||
{
|
||||
if (Equals(this.Pointer[i], item))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ICollection<T>.CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
if (array.Length < this.Count)
|
||||
throw new ArgumentException(null, nameof(array));
|
||||
|
||||
if (array.Length < arrayIndex + this.Count)
|
||||
throw new ArgumentException(null, nameof(arrayIndex));
|
||||
|
||||
for (var i = 0; i < this.Count; i++)
|
||||
array[arrayIndex + i] = this.Pointer[i];
|
||||
}
|
||||
|
||||
bool ICollection<T>.Remove(T item) => throw new NotSupportedException();
|
||||
|
||||
int IList<T>.IndexOf(T item)
|
||||
{
|
||||
for (var i = 0; i < this.Count; i++)
|
||||
{
|
||||
if (Equals(this.Pointer[i], item))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void IList<T>.Insert(int index, T item) => throw new NotSupportedException();
|
||||
|
||||
void IList<T>.RemoveAt(int index) => throw new NotSupportedException();
|
||||
|
||||
void ICollection.CopyTo(Array array, int arrayIndex)
|
||||
{
|
||||
if (array.Length < this.Count)
|
||||
throw new ArgumentException(null, nameof(array));
|
||||
|
||||
if (array.Length < arrayIndex + this.Count)
|
||||
throw new ArgumentException(null, nameof(arrayIndex));
|
||||
|
||||
for (var i = 0; i < this.Count; i++)
|
||||
array.SetValue(this.Pointer[i], arrayIndex + i);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
private int EnsureIndex(int index) =>
|
||||
index >= 0 && index < this.Count ? index : throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
private readonly unsafe struct BigEndianPointerSpan<T>
|
||||
: IList<T>, IReadOnlyList<T>, ICollection
|
||||
where T : unmanaged
|
||||
{
|
||||
public readonly T* Pointer;
|
||||
|
||||
private readonly Func<T, T> reverseEndianness;
|
||||
|
||||
public BigEndianPointerSpan(PointerSpan<T> pointerSpan, Func<T, T> reverseEndianness)
|
||||
{
|
||||
this.reverseEndianness = reverseEndianness;
|
||||
this.Pointer = pointerSpan.Pointer;
|
||||
this.Count = pointerSpan.Count;
|
||||
}
|
||||
|
||||
public int Count { get; }
|
||||
|
||||
public int Length => this.Count;
|
||||
|
||||
public int ByteCount => sizeof(T) * this.Count;
|
||||
|
||||
public bool IsSynchronized => true;
|
||||
|
||||
public object SyncRoot => this;
|
||||
|
||||
public bool IsReadOnly => true;
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get =>
|
||||
BitConverter.IsLittleEndian
|
||||
? this.reverseEndianness(this.Pointer[this.EnsureIndex(index)])
|
||||
: this.Pointer[this.EnsureIndex(index)];
|
||||
set => this.Pointer[this.EnsureIndex(index)] =
|
||||
BitConverter.IsLittleEndian
|
||||
? this.reverseEndianness(value)
|
||||
: value;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < this.Count; i++)
|
||||
yield return this[i];
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item) => throw new NotSupportedException();
|
||||
|
||||
void ICollection<T>.Clear() => throw new NotSupportedException();
|
||||
|
||||
bool ICollection<T>.Contains(T item) => throw new NotSupportedException();
|
||||
|
||||
void ICollection<T>.CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
if (array.Length < this.Count)
|
||||
throw new ArgumentException(null, nameof(array));
|
||||
|
||||
if (array.Length < arrayIndex + this.Count)
|
||||
throw new ArgumentException(null, nameof(arrayIndex));
|
||||
|
||||
for (var i = 0; i < this.Count; i++)
|
||||
array[arrayIndex + i] = this[i];
|
||||
}
|
||||
|
||||
bool ICollection<T>.Remove(T item) => throw new NotSupportedException();
|
||||
|
||||
int IList<T>.IndexOf(T item)
|
||||
{
|
||||
for (var i = 0; i < this.Count; i++)
|
||||
{
|
||||
if (Equals(this[i], item))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void IList<T>.Insert(int index, T item) => throw new NotSupportedException();
|
||||
|
||||
void IList<T>.RemoveAt(int index) => throw new NotSupportedException();
|
||||
|
||||
void ICollection.CopyTo(Array array, int arrayIndex)
|
||||
{
|
||||
if (array.Length < this.Count)
|
||||
throw new ArgumentException(null, nameof(array));
|
||||
|
||||
if (array.Length < arrayIndex + this.Count)
|
||||
throw new ArgumentException(null, nameof(arrayIndex));
|
||||
|
||||
for (var i = 0; i < this.Count; i++)
|
||||
array.SetValue(this[i], arrayIndex + i);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
private int EnsureIndex(int index) =>
|
||||
index >= 0 && index < this.Count ? index : throw new IndexOutOfRangeException();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,135 +0,0 @@
|
|||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Interface.Utility;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
|
||||
/// <summary>
|
||||
/// Deals with TrueType.
|
||||
/// </summary>
|
||||
internal static partial class TrueTypeUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks whether the given <paramref name="fontConfig"/> will fail in <see cref="ImFontAtlasPtr.Build"/>,
|
||||
/// and throws an appropriate exception if it is the case.
|
||||
/// </summary>
|
||||
/// <param name="fontConfig">The font config.</param>
|
||||
public static unsafe void CheckImGuiCompatibleOrThrow(in ImFontConfig fontConfig)
|
||||
{
|
||||
var ranges = fontConfig.GlyphRanges;
|
||||
var sfnt = AsSfntFile(fontConfig);
|
||||
var cmap = new Cmap(sfnt);
|
||||
if (cmap.UnicodeTable is not { } unicodeTable)
|
||||
throw new NotSupportedException("The font does not have a compatible Unicode character mapping table.");
|
||||
if (unicodeTable.All(x => !ImGuiHelpers.IsCodepointInSuppliedGlyphRangesUnsafe(x.Key, ranges)))
|
||||
throw new NotSupportedException("The font does not have any glyph that falls under the requested range.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates through horizontal pair adjustments of a kern and gpos tables.
|
||||
/// </summary>
|
||||
/// <param name="fontConfig">The font config.</param>
|
||||
/// <returns>The enumerable of pair adjustments. Distance values need to be multiplied by font size in pixels.</returns>
|
||||
public static IEnumerable<(char Left, char Right, float Distance)> ExtractHorizontalPairAdjustments(
|
||||
ImFontConfig fontConfig)
|
||||
{
|
||||
float multiplier;
|
||||
Dictionary<ushort, char[]> glyphToCodepoints;
|
||||
Gpos gpos = default;
|
||||
Kern kern = default;
|
||||
|
||||
try
|
||||
{
|
||||
var sfnt = AsSfntFile(fontConfig);
|
||||
var head = new Head(sfnt);
|
||||
multiplier = 3f / 4 / head.UnitsPerEm;
|
||||
|
||||
if (new Cmap(sfnt).UnicodeTable is not { } table)
|
||||
yield break;
|
||||
|
||||
if (sfnt.ContainsKey(Kern.DirectoryTableTag))
|
||||
kern = new(sfnt);
|
||||
else if (sfnt.ContainsKey(Gpos.DirectoryTableTag))
|
||||
gpos = new(sfnt);
|
||||
else
|
||||
yield break;
|
||||
|
||||
glyphToCodepoints = table
|
||||
.GroupBy(x => x.Value, x => x.Key)
|
||||
.OrderBy(x => x.Key)
|
||||
.ToDictionary(
|
||||
x => x.Key,
|
||||
x => x.Where(y => y <= ushort.MaxValue)
|
||||
.Select(y => (char)y)
|
||||
.ToArray());
|
||||
}
|
||||
catch
|
||||
{
|
||||
// don't care; give up
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (kern.Memory.Count != 0)
|
||||
{
|
||||
foreach (var pair in kern.EnumerateHorizontalPairs())
|
||||
{
|
||||
if (!glyphToCodepoints.TryGetValue(pair.Left, out var leftChars))
|
||||
continue;
|
||||
if (!glyphToCodepoints.TryGetValue(pair.Right, out var rightChars))
|
||||
continue;
|
||||
|
||||
foreach (var l in leftChars)
|
||||
{
|
||||
foreach (var r in rightChars)
|
||||
yield return (l, r, pair.Value * multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (gpos.Memory.Count != 0)
|
||||
{
|
||||
foreach (var pair in gpos.ExtractAdvanceX())
|
||||
{
|
||||
if (!glyphToCodepoints.TryGetValue(pair.Left, out var leftChars))
|
||||
continue;
|
||||
if (!glyphToCodepoints.TryGetValue(pair.Right, out var rightChars))
|
||||
continue;
|
||||
|
||||
foreach (var l in leftChars)
|
||||
{
|
||||
foreach (var r in rightChars)
|
||||
yield return (l, r, pair.Value * multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe SfntFile AsSfntFile(in ImFontConfig fontConfig)
|
||||
{
|
||||
var memory = new PointerSpan<byte>((byte*)fontConfig.FontData, fontConfig.FontDataSize);
|
||||
if (memory.Length < 4)
|
||||
throw new NotSupportedException("File is too short to even have a magic.");
|
||||
|
||||
var magic = memory.ReadU32Big(0);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
magic = BinaryPrimitives.ReverseEndianness(magic);
|
||||
|
||||
if (magic == SfntFile.FileTagTrueType1.NativeValue)
|
||||
return new(memory);
|
||||
if (magic == SfntFile.FileTagType1.NativeValue)
|
||||
return new(memory);
|
||||
if (magic == SfntFile.FileTagOpenTypeWithCff.NativeValue)
|
||||
return new(memory);
|
||||
if (magic == SfntFile.FileTagOpenType1_0.NativeValue)
|
||||
return new(memory);
|
||||
if (magic == SfntFile.FileTagTrueTypeApple.NativeValue)
|
||||
return new(memory);
|
||||
if (magic == TtcFile.FileTag.NativeValue)
|
||||
return new TtcFile(memory)[fontConfig.FontNo];
|
||||
|
||||
throw new NotSupportedException($"The given file with the magic 0x{magic:X08} is not supported.");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,306 +0,0 @@
|
|||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
/// <summary>
|
||||
/// Managed version of <see cref="ImFontConfig"/>, to avoid unnecessary heap allocation and use of unsafe blocks.
|
||||
/// </summary>
|
||||
public struct SafeFontConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The raw config.
|
||||
/// </summary>
|
||||
public ImFontConfig Raw;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SafeFontConfig"/> struct.
|
||||
/// </summary>
|
||||
public SafeFontConfig()
|
||||
{
|
||||
this.OversampleH = 1;
|
||||
this.OversampleV = 1;
|
||||
this.PixelSnapH = true;
|
||||
this.GlyphMaxAdvanceX = float.MaxValue;
|
||||
this.RasterizerMultiply = 1f;
|
||||
this.RasterizerGamma = 1.4f;
|
||||
this.EllipsisChar = unchecked((char)-1);
|
||||
this.Raw.FontDataOwnedByAtlas = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SafeFontConfig"/> struct,
|
||||
/// copying applicable values from an existing instance of <see cref="ImFontConfigPtr"/>.
|
||||
/// </summary>
|
||||
/// <param name="config">Config to copy from.</param>
|
||||
public unsafe SafeFontConfig(ImFontConfigPtr config)
|
||||
: this()
|
||||
{
|
||||
if (config.NativePtr is not null)
|
||||
{
|
||||
this.Raw = *config.NativePtr;
|
||||
this.Raw.GlyphRanges = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of font within a TTF/OTF file.
|
||||
/// </summary>
|
||||
public int FontNo
|
||||
{
|
||||
get => this.Raw.FontNo;
|
||||
set => this.Raw.FontNo = EnsureRange(value, 0, int.MaxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the desired size of the new font, in pixels.<br />
|
||||
/// Effectively, this is the line height.<br />
|
||||
/// Value is tied with <see cref="SizePt"/>.
|
||||
/// </summary>
|
||||
public float SizePx
|
||||
{
|
||||
get => this.Raw.SizePixels;
|
||||
set => this.Raw.SizePixels = EnsureRange(value, float.Epsilon, float.MaxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the desired size of the new font, in points.<br />
|
||||
/// Effectively, this is the line height.<br />
|
||||
/// Value is tied with <see cref="SizePx"/>.
|
||||
/// </summary>
|
||||
public float SizePt
|
||||
{
|
||||
get => (this.Raw.SizePixels * 3) / 4;
|
||||
set => this.Raw.SizePixels = EnsureRange((value * 4) / 3, float.Epsilon, float.MaxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the horizontal oversampling pixel count.<br />
|
||||
/// Rasterize at higher quality for sub-pixel positioning.<br />
|
||||
/// Note the difference between 2 and 3 is minimal so you can reduce this to 2 to save memory.<br />
|
||||
/// Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details.
|
||||
/// </summary>
|
||||
public int OversampleH
|
||||
{
|
||||
get => this.Raw.OversampleH;
|
||||
set => this.Raw.OversampleH = EnsureRange(value, 1, int.MaxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the vertical oversampling pixel count.<br />
|
||||
/// Rasterize at higher quality for sub-pixel positioning.<br />
|
||||
/// This is not really useful as we don't use sub-pixel positions on the Y axis.
|
||||
/// </summary>
|
||||
public int OversampleV
|
||||
{
|
||||
get => this.Raw.OversampleV;
|
||||
set => this.Raw.OversampleV = EnsureRange(value, 1, int.MaxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to align every glyph to pixel boundary.<br />
|
||||
/// Useful e.g. if you are merging a non-pixel aligned font with the default font.<br />
|
||||
/// If enabled, you can set <see cref="OversampleH"/> and <see cref="OversampleV"/> to 1.
|
||||
/// </summary>
|
||||
public bool PixelSnapH
|
||||
{
|
||||
get => this.Raw.PixelSnapH != 0;
|
||||
set => this.Raw.PixelSnapH = value ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the extra spacing (in pixels) between glyphs.<br />
|
||||
/// Only X axis is supported for now.<br />
|
||||
/// Effectively, it is the letter spacing.
|
||||
/// </summary>
|
||||
public Vector2 GlyphExtraSpacing
|
||||
{
|
||||
get => this.Raw.GlyphExtraSpacing;
|
||||
set => this.Raw.GlyphExtraSpacing = new(
|
||||
EnsureRange(value.X, float.MinValue, float.MaxValue),
|
||||
EnsureRange(value.Y, float.MinValue, float.MaxValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the offset all glyphs from this font input.<br />
|
||||
/// Use this to offset fonts vertically when merging multiple fonts.
|
||||
/// </summary>
|
||||
public Vector2 GlyphOffset
|
||||
{
|
||||
get => this.Raw.GlyphOffset;
|
||||
set => this.Raw.GlyphOffset = new(
|
||||
EnsureRange(value.X, float.MinValue, float.MaxValue),
|
||||
EnsureRange(value.Y, float.MinValue, float.MaxValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the glyph ranges, which is a user-provided list of Unicode range.
|
||||
/// Each range has 2 values, and values are inclusive.<br />
|
||||
/// The list must be zero-terminated.<br />
|
||||
/// If empty or null, then all the glyphs from the font that is in the range of UCS-2 will be added.
|
||||
/// </summary>
|
||||
public ushort[]? GlyphRanges { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum AdvanceX for glyphs.<br />
|
||||
/// Set only <see cref="GlyphMinAdvanceX"/> to align font icons.<br />
|
||||
/// Set both <see cref="GlyphMinAdvanceX"/>/<see cref="GlyphMaxAdvanceX"/> to enforce mono-space font.
|
||||
/// </summary>
|
||||
public float GlyphMinAdvanceX
|
||||
{
|
||||
get => this.Raw.GlyphMinAdvanceX;
|
||||
set => this.Raw.GlyphMinAdvanceX =
|
||||
float.IsFinite(value)
|
||||
? value
|
||||
: throw new ArgumentOutOfRangeException(
|
||||
nameof(value),
|
||||
value,
|
||||
$"{nameof(this.GlyphMinAdvanceX)} must be a finite number.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum AdvanceX for glyphs.
|
||||
/// </summary>
|
||||
public float GlyphMaxAdvanceX
|
||||
{
|
||||
get => this.Raw.GlyphMaxAdvanceX;
|
||||
set => this.Raw.GlyphMaxAdvanceX =
|
||||
float.IsFinite(value)
|
||||
? value
|
||||
: throw new ArgumentOutOfRangeException(
|
||||
nameof(value),
|
||||
value,
|
||||
$"{nameof(this.GlyphMaxAdvanceX)} must be a finite number.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that either brightens (>1.0f) or darkens (<1.0f) the font output.<br />
|
||||
/// Brightening small fonts may be a good workaround to make them more readable.
|
||||
/// </summary>
|
||||
public float RasterizerMultiply
|
||||
{
|
||||
get => this.Raw.RasterizerMultiply;
|
||||
set => this.Raw.RasterizerMultiply = EnsureRange(value, float.Epsilon, float.MaxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the gamma value for fonts.
|
||||
/// </summary>
|
||||
public float RasterizerGamma
|
||||
{
|
||||
get => this.Raw.RasterizerGamma;
|
||||
set => this.Raw.RasterizerGamma = EnsureRange(value, float.Epsilon, float.MaxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value explicitly specifying unicode codepoint of the ellipsis character.<br />
|
||||
/// When fonts are being merged first specified ellipsis will be used.
|
||||
/// </summary>
|
||||
public char EllipsisChar
|
||||
{
|
||||
get => (char)this.Raw.EllipsisChar;
|
||||
set => this.Raw.EllipsisChar = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the desired name of the new font. Names longer than 40 bytes will be partially lost.
|
||||
/// </summary>
|
||||
public unsafe string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (void* pName = this.Raw.Name)
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>(pName, 40);
|
||||
var firstNull = span.IndexOf((byte)0);
|
||||
if (firstNull != -1)
|
||||
span = span[..firstNull];
|
||||
return Encoding.UTF8.GetString(span);
|
||||
}
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
fixed (void* pName = this.Raw.Name)
|
||||
{
|
||||
var span = new Span<byte>(pName, 40);
|
||||
Encoding.UTF8.GetBytes(value, span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the desired font to merge with, if set.
|
||||
/// </summary>
|
||||
public unsafe ImFontPtr MergeFont
|
||||
{
|
||||
get => this.Raw.DstFont is not null ? this.Raw.DstFont : default;
|
||||
set
|
||||
{
|
||||
this.Raw.MergeMode = value.NativePtr is null ? (byte)0 : (byte)1;
|
||||
this.Raw.DstFont = value.NativePtr is null ? default : value.NativePtr;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws <see cref="ArgumentException"/> with appropriate messages,
|
||||
/// if this <see cref="SafeFontConfig"/> has invalid values.
|
||||
/// </summary>
|
||||
public readonly void ThrowOnInvalidValues()
|
||||
{
|
||||
if (!(this.Raw.FontNo >= 0))
|
||||
throw new ArgumentException($"{nameof(this.FontNo)} must not be a negative number.");
|
||||
|
||||
if (!(this.Raw.SizePixels > 0))
|
||||
throw new ArgumentException($"{nameof(this.SizePx)} must be a positive number.");
|
||||
|
||||
if (!(this.Raw.OversampleH >= 1))
|
||||
throw new ArgumentException($"{nameof(this.OversampleH)} must be a negative number.");
|
||||
|
||||
if (!(this.Raw.OversampleV >= 1))
|
||||
throw new ArgumentException($"{nameof(this.OversampleV)} must be a negative number.");
|
||||
|
||||
if (!float.IsFinite(this.Raw.GlyphMinAdvanceX))
|
||||
throw new ArgumentException($"{nameof(this.GlyphMinAdvanceX)} must be a finite number.");
|
||||
|
||||
if (!float.IsFinite(this.Raw.GlyphMaxAdvanceX))
|
||||
throw new ArgumentException($"{nameof(this.GlyphMaxAdvanceX)} must be a finite number.");
|
||||
|
||||
if (!(this.Raw.RasterizerMultiply > 0))
|
||||
throw new ArgumentException($"{nameof(this.RasterizerMultiply)} must be a positive number.");
|
||||
|
||||
if (!(this.Raw.RasterizerGamma > 0))
|
||||
throw new ArgumentException($"{nameof(this.RasterizerGamma)} must be a positive number.");
|
||||
|
||||
if (this.GlyphRanges is { Length: > 0 } ranges)
|
||||
{
|
||||
if (ranges[0] == 0)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Font ranges cannot start with 0.",
|
||||
nameof(this.GlyphRanges));
|
||||
}
|
||||
|
||||
if (ranges[(ranges.Length - 1) & ~1] != 0)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Font ranges must terminate with a zero at even indices.",
|
||||
nameof(this.GlyphRanges));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static T EnsureRange<T>(T value, T min, T max, [CallerMemberName] string callerName = "")
|
||||
where T : INumber<T>
|
||||
{
|
||||
if (value < min)
|
||||
throw new ArgumentOutOfRangeException(callerName, value, $"{callerName} cannot be less than {min}.");
|
||||
if (value > max)
|
||||
throw new ArgumentOutOfRangeException(callerName, value, $"{callerName} cannot be more than {max}.");
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -11,8 +12,6 @@ using Dalamud.Interface.GameFonts;
|
|||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Internal.ManagedAsserts;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
using ImGuiScene;
|
||||
|
|
@ -31,13 +30,11 @@ public sealed class UiBuilder : IDisposable
|
|||
private readonly HitchDetector hitchDetector;
|
||||
private readonly string namespaceName;
|
||||
private readonly InterfaceManager interfaceManager = Service<InterfaceManager>.Get();
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
private readonly GameFontManager gameFontManager = Service<GameFontManager>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new();
|
||||
|
||||
private bool hasErrorWindow = false;
|
||||
private bool lastFrameUiHideState = false;
|
||||
|
||||
|
|
@ -47,33 +44,15 @@ public sealed class UiBuilder : IDisposable
|
|||
/// </summary>
|
||||
/// <param name="namespaceName">The plugin namespace.</param>
|
||||
internal UiBuilder(string namespaceName)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.stopwatch = new Stopwatch();
|
||||
this.hitchDetector = new HitchDetector($"UiBuilder({namespaceName})", this.configuration.UiBuilderHitch);
|
||||
this.namespaceName = namespaceName;
|
||||
|
||||
this.interfaceManager.Draw += this.OnDraw;
|
||||
this.scopedFinalizer.Add(() => this.interfaceManager.Draw -= this.OnDraw);
|
||||
|
||||
this.interfaceManager.BuildFonts += this.OnBuildFonts;
|
||||
this.interfaceManager.AfterBuildFonts += this.OnAfterBuildFonts;
|
||||
this.interfaceManager.ResizeBuffers += this.OnResizeBuffers;
|
||||
this.scopedFinalizer.Add(() => this.interfaceManager.ResizeBuffers -= this.OnResizeBuffers);
|
||||
|
||||
this.FontAtlas =
|
||||
this.scopedFinalizer
|
||||
.Add(
|
||||
Service<FontAtlasFactory>
|
||||
.Get()
|
||||
.CreateFontAtlas(namespaceName, FontAtlasAutoRebuildMode.Disable));
|
||||
this.FontAtlas.BuildStepChange += this.PrivateAtlasOnBuildStepChange;
|
||||
this.FontAtlas.RebuildRecommend += this.RebuildFonts;
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.scopedFinalizer.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -101,19 +80,19 @@ public sealed class UiBuilder : IDisposable
|
|||
/// Gets or sets an action that is called any time ImGui fonts need to be rebuilt.<br/>
|
||||
/// Any ImFontPtr objects that you store <strong>can be invalidated</strong> when fonts are rebuilt
|
||||
/// (at any time), so you should both reload your custom fonts and restore those
|
||||
/// pointers inside this handler.
|
||||
/// pointers inside this handler.<br/>
|
||||
/// <strong>PLEASE remove this handler inside Dispose, or when you no longer need your fonts!</strong>
|
||||
/// </summary>
|
||||
[Obsolete($"Use {nameof(this.FontAtlas)} instead.", false)]
|
||||
public event Action? BuildFonts;
|
||||
public event Action BuildFonts;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an action that is called any time right after ImGui fonts are rebuilt.<br/>
|
||||
/// Any ImFontPtr objects that you store <strong>can be invalidated</strong> when fonts are rebuilt
|
||||
/// (at any time), so you should both reload your custom fonts and restore those
|
||||
/// pointers inside this handler.
|
||||
/// pointers inside this handler.<br/>
|
||||
/// <strong>PLEASE remove this handler inside Dispose, or when you no longer need your fonts!</strong>
|
||||
/// </summary>
|
||||
[Obsolete($"Use {nameof(this.FontAtlas)} instead.", false)]
|
||||
public event Action? AfterBuildFonts;
|
||||
public event Action AfterBuildFonts;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an action that is called when plugin UI or interface modifications are supposed to be shown.
|
||||
|
|
@ -128,57 +107,18 @@ public sealed class UiBuilder : IDisposable
|
|||
public event Action HideUi;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default Dalamud font size in points.
|
||||
/// Gets the default Dalamud font based on Noto Sans CJK Medium in 17pt - supporting all game languages and icons.
|
||||
/// </summary>
|
||||
public static float DefaultFontSizePt => InterfaceManager.DefaultFontSizePt;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default Dalamud font size in pixels.
|
||||
/// </summary>
|
||||
public static float DefaultFontSizePx => InterfaceManager.DefaultFontSizePx;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default Dalamud font - supporting all game languages and icons.<br />
|
||||
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A font handle corresponding to this font can be obtained with:
|
||||
/// <code>
|
||||
/// fontAtlas.NewDelegateFontHandle(
|
||||
/// e => e.OnPreBuild(
|
||||
/// tk => tk.AddDalamudDefaultFont(UiBuilder.DefaultFontSizePt)));
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public static ImFontPtr DefaultFont => InterfaceManager.DefaultFont;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default Dalamud icon font based on FontAwesome 5 Free solid.<br />
|
||||
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
|
||||
/// Gets the default Dalamud icon font based on FontAwesome 5 Free solid in 17pt.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A font handle corresponding to this font can be obtained with:
|
||||
/// <code>
|
||||
/// fontAtlas.NewDelegateFontHandle(
|
||||
/// e => e.OnPreBuild(
|
||||
/// tk => tk.AddFontAwesomeIconFont(new() { SizePt = UiBuilder.DefaultFontSizePt })));
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public static ImFontPtr IconFont => InterfaceManager.IconFont;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default Dalamud monospaced font based on Inconsolata Regular.<br />
|
||||
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
|
||||
/// Gets the default Dalamud monospaced font based on Inconsolata Regular in 16pt.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A font handle corresponding to this font can be obtained with:
|
||||
/// <code>
|
||||
/// fontAtlas.NewDelegateFontHandle(
|
||||
/// e => e.OnPreBuild(
|
||||
/// tk => tk.AddDalamudAssetFont(
|
||||
/// DalamudAsset.InconsolataRegular,
|
||||
/// new() { SizePt = UiBuilder.DefaultFontSizePt })));
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public static ImFontPtr MonoFont => InterfaceManager.MonoFont;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -250,11 +190,6 @@ public sealed class UiBuilder : IDisposable
|
|||
/// </summary>
|
||||
public bool UiPrepared => Service<InterfaceManager.InterfaceManagerWithScene>.GetNullable() != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin-private font atlas.
|
||||
/// </summary>
|
||||
public IFontAtlas FontAtlas { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether statistics about UI draw time should be collected.
|
||||
/// </summary>
|
||||
|
|
@ -384,7 +319,7 @@ public sealed class UiBuilder : IDisposable
|
|||
if (runInFrameworkThread)
|
||||
{
|
||||
return this.InterfaceManagerWithSceneAsync
|
||||
.ContinueWith(_ => this.framework.RunOnFrameworkThread(func))
|
||||
.ContinueWith(_ => Service<Framework>.Get().RunOnFrameworkThread(func))
|
||||
.Unwrap();
|
||||
}
|
||||
else
|
||||
|
|
@ -406,7 +341,7 @@ public sealed class UiBuilder : IDisposable
|
|||
if (runInFrameworkThread)
|
||||
{
|
||||
return this.InterfaceManagerWithSceneAsync
|
||||
.ContinueWith(_ => this.framework.RunOnFrameworkThread(func))
|
||||
.ContinueWith(_ => Service<Framework>.Get().RunOnFrameworkThread(func))
|
||||
.Unwrap();
|
||||
}
|
||||
else
|
||||
|
|
@ -422,49 +357,19 @@ public sealed class UiBuilder : IDisposable
|
|||
/// </summary>
|
||||
/// <param name="style">Font to get.</param>
|
||||
/// <returns>Handle to the game font which may or may not be available for use yet.</returns>
|
||||
[Obsolete($"Use {nameof(this.FontAtlas)}.{nameof(IFontAtlas.NewGameFontHandle)} instead.", false)]
|
||||
public GameFontHandle GetGameFontHandle(GameFontStyle style) => new(
|
||||
(IFontHandle.IInternal)this.FontAtlas.NewGameFontHandle(style),
|
||||
Service<FontAtlasFactory>.Get());
|
||||
public GameFontHandle GetGameFontHandle(GameFontStyle style) => this.gameFontManager.NewFontRef(style);
|
||||
|
||||
/// <summary>
|
||||
/// Call this to queue a rebuild of the font atlas.<br/>
|
||||
/// This will invoke any <see cref="BuildFonts"/> and <see cref="AfterBuildFonts"/> handlers and ensure that any
|
||||
/// loaded fonts are ready to be used on the next UI frame.
|
||||
/// This will invoke any <see cref="OnBuildFonts"/> handlers and ensure that any loaded fonts are
|
||||
/// ready to be used on the next UI frame.
|
||||
/// </summary>
|
||||
public void RebuildFonts()
|
||||
{
|
||||
Log.Verbose("[FONT] {0} plugin is initiating FONT REBUILD", this.namespaceName);
|
||||
if (this.AfterBuildFonts is null && this.BuildFonts is null)
|
||||
this.FontAtlas.BuildFontsAsync();
|
||||
else
|
||||
this.FontAtlas.BuildFontsOnNextFrame();
|
||||
this.interfaceManager.RebuildFonts();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an isolated <see cref="IFontAtlas"/>.
|
||||
/// </summary>
|
||||
/// <param name="autoRebuildMode">Specify when and how to rebuild this atlas.</param>
|
||||
/// <param name="isGlobalScaled">Whether the fonts in the atlas is global scaled.</param>
|
||||
/// <param name="debugName">Name for debugging purposes.</param>
|
||||
/// <returns>A new instance of <see cref="IFontAtlas"/>.</returns>
|
||||
/// <remarks>
|
||||
/// Use this to create extra font atlases, if you want to create and dispose fonts without having to rebuild all
|
||||
/// other fonts together.<br />
|
||||
/// If <paramref name="autoRebuildMode"/> is not <see cref="FontAtlasAutoRebuildMode.OnNewFrame"/>,
|
||||
/// the font rebuilding functions must be called manually.
|
||||
/// </remarks>
|
||||
public IFontAtlas CreateFontAtlas(
|
||||
FontAtlasAutoRebuildMode autoRebuildMode,
|
||||
bool isGlobalScaled = true,
|
||||
string? debugName = null) =>
|
||||
this.scopedFinalizer.Add(Service<FontAtlasFactory>
|
||||
.Get()
|
||||
.CreateFontAtlas(
|
||||
this.namespaceName + ":" + (debugName ?? "custom"),
|
||||
autoRebuildMode,
|
||||
isGlobalScaled));
|
||||
|
||||
/// <summary>
|
||||
/// Add a notification to the notification queue.
|
||||
/// </summary>
|
||||
|
|
@ -487,7 +392,12 @@ public sealed class UiBuilder : IDisposable
|
|||
/// <summary>
|
||||
/// Unregister the UiBuilder. Do not call this in plugin code.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose() => this.scopedFinalizer.Dispose();
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
this.interfaceManager.Draw -= this.OnDraw;
|
||||
this.interfaceManager.BuildFonts -= this.OnBuildFonts;
|
||||
this.interfaceManager.ResizeBuffers -= this.OnResizeBuffers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open the registered configuration UI, if it exists.
|
||||
|
|
@ -553,12 +463,8 @@ public sealed class UiBuilder : IDisposable
|
|||
this.ShowUi?.InvokeSafely();
|
||||
}
|
||||
|
||||
// just in case, if something goes wrong, prevent drawing; otherwise it probably will crash.
|
||||
if (!this.FontAtlas.BuildTask.IsCompletedSuccessfully
|
||||
&& (this.BuildFonts is not null || this.AfterBuildFonts is not null))
|
||||
{
|
||||
if (!this.interfaceManager.FontsReady)
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.PushID(this.namespaceName);
|
||||
if (DoStats)
|
||||
|
|
@ -620,28 +526,14 @@ public sealed class UiBuilder : IDisposable
|
|||
this.hitchDetector.Stop();
|
||||
}
|
||||
|
||||
private unsafe void PrivateAtlasOnBuildStepChange(IFontAtlasBuildToolkit e)
|
||||
private void OnBuildFonts()
|
||||
{
|
||||
if (e.IsAsyncBuildOperation)
|
||||
return;
|
||||
|
||||
e.OnPreBuild(
|
||||
_ =>
|
||||
{
|
||||
var prev = ImGui.GetIO().NativePtr->Fonts;
|
||||
ImGui.GetIO().NativePtr->Fonts = e.NewImAtlas.NativePtr;
|
||||
this.BuildFonts?.InvokeSafely();
|
||||
ImGui.GetIO().NativePtr->Fonts = prev;
|
||||
});
|
||||
}
|
||||
|
||||
e.OnPostBuild(
|
||||
_ =>
|
||||
private void OnAfterBuildFonts()
|
||||
{
|
||||
var prev = ImGui.GetIO().NativePtr->Fonts;
|
||||
ImGui.GetIO().NativePtr->Fonts = e.NewImAtlas.NativePtr;
|
||||
this.AfterBuildFonts?.InvokeSafely();
|
||||
ImGui.GetIO().NativePtr->Fonts = prev;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnResizeBuffers()
|
||||
|
|
|
|||
|
|
@ -1,15 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Unicode;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using ImGuiNET;
|
||||
using ImGuiScene;
|
||||
|
|
@ -36,7 +31,8 @@ public static class ImGuiHelpers
|
|||
/// This does not necessarily mean you can call drawing functions.
|
||||
/// </summary>
|
||||
public static unsafe bool IsImGuiInitialized =>
|
||||
ImGui.GetCurrentContext() != nint.Zero && ImGui.GetIO().NativePtr is not null;
|
||||
ImGui.GetCurrentContext() is not (nint)0 // KW: IDEs get mad without the cast, despite being unnecessary
|
||||
&& ImGui.GetIO().NativePtr is not null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the global Dalamud scale; even available before drawing is ready.<br />
|
||||
|
|
@ -202,7 +198,7 @@ public static class ImGuiHelpers
|
|||
/// <param name="round">If a positive number is given, numbers will be rounded to this.</param>
|
||||
public static unsafe void AdjustGlyphMetrics(this ImFontPtr fontPtr, float scale, float round = 0f)
|
||||
{
|
||||
Func<float, float> rounder = round > 0 ? x => MathF.Round(x / round) * round : x => x;
|
||||
Func<float, float> rounder = round > 0 ? x => MathF.Round(x * round) / round : x => x;
|
||||
|
||||
var font = fontPtr.NativePtr;
|
||||
font->FontSize = rounder(font->FontSize * scale);
|
||||
|
|
@ -314,7 +310,6 @@ public static class ImGuiHelpers
|
|||
glyph->U1,
|
||||
glyph->V1,
|
||||
glyph->AdvanceX * scale);
|
||||
target.Mark4KPageUsedAfterGlyphAdd((ushort)glyph->Codepoint);
|
||||
changed = true;
|
||||
}
|
||||
else if (!missingOnly)
|
||||
|
|
@ -348,6 +343,14 @@ public static class ImGuiHelpers
|
|||
}
|
||||
|
||||
if (changed && rebuildLookupTable)
|
||||
target.BuildLookupTableNonstandard();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call ImFont::BuildLookupTable, after attempting to fulfill some preconditions.
|
||||
/// </summary>
|
||||
/// <param name="font">The font.</param>
|
||||
public static unsafe void BuildLookupTableNonstandard(this ImFontPtr font)
|
||||
{
|
||||
// ImGui resolves ' ' with FindGlyph, which uses FallbackGlyph.
|
||||
// FallbackGlyph is resolved after resolving ' '.
|
||||
|
|
@ -356,10 +359,9 @@ public static class ImGuiHelpers
|
|||
// On our secondary calls of BuildLookupTable, FallbackGlyph is set to some value that is not null,
|
||||
// making ImGui attempt to treat whatever was there as a ' '.
|
||||
// This may cause random glyphs to be sized randomly, if not an access violation exception.
|
||||
target.NativePtr->FallbackGlyph = null;
|
||||
font.NativePtr->FallbackGlyph = null;
|
||||
|
||||
target.BuildLookupTable();
|
||||
}
|
||||
font.BuildLookupTable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -405,103 +407,6 @@ public static class ImGuiHelpers
|
|||
public static void CenterCursorFor(float itemWidth) =>
|
||||
ImGui.SetCursorPosX((int)((ImGui.GetWindowWidth() - itemWidth) / 2));
|
||||
|
||||
/// <summary>
|
||||
/// Allocates memory on the heap using <see cref="ImGuiNative.igMemAlloc"/><br />
|
||||
/// Memory must be freed using <see cref="ImGuiNative.igMemFree"/>.
|
||||
/// <br />
|
||||
/// Note that null is a valid return value when <paramref name="length"/> is 0.
|
||||
/// </summary>
|
||||
/// <param name="length">The length of allocated memory.</param>
|
||||
/// <returns>The allocated memory.</returns>
|
||||
/// <exception cref="OutOfMemoryException">If <see cref="ImGuiNative.igMemAlloc"/> returns null.</exception>
|
||||
public static unsafe void* AllocateMemory(int length)
|
||||
{
|
||||
// TODO: igMemAlloc takes size_t, which is nint; ImGui.NET apparently interpreted that as uint.
|
||||
// fix that in ImGui.NET.
|
||||
switch (length)
|
||||
{
|
||||
case 0:
|
||||
return null;
|
||||
case < 0:
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(length),
|
||||
length,
|
||||
$"{nameof(length)} cannot be a negative number.");
|
||||
default:
|
||||
var memory = ImGuiNative.igMemAlloc((uint)length);
|
||||
if (memory is null)
|
||||
{
|
||||
throw new OutOfMemoryException(
|
||||
$"Failed to allocate {length} bytes using {nameof(ImGuiNative.igMemAlloc)}");
|
||||
}
|
||||
|
||||
return memory;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="ImFontGlyphRangesBuilderPtr"/> with a natively backed memory.
|
||||
/// </summary>
|
||||
/// <param name="builder">The created instance.</param>
|
||||
/// <returns>Disposable you can call.</returns>
|
||||
public static unsafe IDisposable NewFontGlyphRangeBuilderPtrScoped(out ImFontGlyphRangesBuilderPtr builder)
|
||||
{
|
||||
builder = new(ImGuiNative.ImFontGlyphRangesBuilder_ImFontGlyphRangesBuilder());
|
||||
var ptr = builder.NativePtr;
|
||||
return Disposable.Create(() =>
|
||||
{
|
||||
if (ptr != null)
|
||||
ImGuiNative.ImFontGlyphRangesBuilder_destroy(ptr);
|
||||
ptr = null;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds ImGui Glyph Ranges for use with <see cref="SafeFontConfig.GlyphRanges"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder.</param>
|
||||
/// <param name="addFallbackCodepoints">Add fallback codepoints to the range.</param>
|
||||
/// <param name="addEllipsisCodepoints">Add ellipsis codepoints to the range.</param>
|
||||
/// <returns>When disposed, the resource allocated for the range will be freed.</returns>
|
||||
public static unsafe ushort[] BuildRangesToArray(
|
||||
this ImFontGlyphRangesBuilderPtr builder,
|
||||
bool addFallbackCodepoints = true,
|
||||
bool addEllipsisCodepoints = true)
|
||||
{
|
||||
if (addFallbackCodepoints)
|
||||
builder.AddText(FontAtlasFactory.FallbackCodepoints);
|
||||
if (addEllipsisCodepoints)
|
||||
{
|
||||
builder.AddText(FontAtlasFactory.EllipsisCodepoints);
|
||||
builder.AddChar('.');
|
||||
}
|
||||
|
||||
builder.BuildRanges(out var vec);
|
||||
return new ReadOnlySpan<ushort>((void*)vec.Data, vec.Size).ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CreateImGuiRangesFrom(IEnumerable{UnicodeRange})"/>
|
||||
public static ushort[] CreateImGuiRangesFrom(params UnicodeRange[] ranges)
|
||||
=> CreateImGuiRangesFrom((IEnumerable<UnicodeRange>)ranges);
|
||||
|
||||
/// <summary>
|
||||
/// Creates glyph ranges from <see cref="UnicodeRange"/>.<br />
|
||||
/// Use values from <see cref="UnicodeRanges"/>.
|
||||
/// </summary>
|
||||
/// <param name="ranges">The unicode ranges.</param>
|
||||
/// <returns>The range array that can be used for <see cref="SafeFontConfig.GlyphRanges"/>.</returns>
|
||||
public static ushort[] CreateImGuiRangesFrom(IEnumerable<UnicodeRange> ranges) =>
|
||||
ranges
|
||||
.Where(x => x.FirstCodePoint <= ushort.MaxValue)
|
||||
.SelectMany(
|
||||
x => new[]
|
||||
{
|
||||
(ushort)Math.Min(x.FirstCodePoint, ushort.MaxValue),
|
||||
(ushort)Math.Min(x.FirstCodePoint + x.Length, ushort.MaxValue),
|
||||
})
|
||||
.Append((ushort)0)
|
||||
.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether <paramref name="ptr"/> is empty.
|
||||
/// </summary>
|
||||
|
|
@ -510,7 +415,7 @@ public static class ImGuiHelpers
|
|||
public static unsafe bool IsNull(this ImFontPtr ptr) => ptr.NativePtr == null;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether <paramref name="ptr"/> is empty.
|
||||
/// Determines whether <paramref name="ptr"/> is not null and loaded.
|
||||
/// </summary>
|
||||
/// <param name="ptr">The pointer.</param>
|
||||
/// <returns>Whether it is empty.</returns>
|
||||
|
|
@ -523,27 +428,6 @@ public static class ImGuiHelpers
|
|||
/// <returns>Whether it is empty.</returns>
|
||||
public static unsafe bool IsNull(this ImFontAtlasPtr ptr) => ptr.NativePtr == null;
|
||||
|
||||
/// <summary>
|
||||
/// If <paramref name="self"/> is default, then returns <paramref name="other"/>.
|
||||
/// </summary>
|
||||
/// <param name="self">The self.</param>
|
||||
/// <param name="other">The other.</param>
|
||||
/// <returns><paramref name="self"/> if it is not default; otherwise, <paramref name="other"/>.</returns>
|
||||
public static unsafe ImFontPtr OrElse(this ImFontPtr self, ImFontPtr other) =>
|
||||
self.NativePtr is null ? other : self;
|
||||
|
||||
/// <summary>
|
||||
/// Mark 4K page as used, after adding a codepoint to a font.
|
||||
/// </summary>
|
||||
/// <param name="font">The font.</param>
|
||||
/// <param name="codepoint">The codepoint.</param>
|
||||
internal static unsafe void Mark4KPageUsedAfterGlyphAdd(this ImFontPtr font, ushort codepoint)
|
||||
{
|
||||
// Mark 4K page as used
|
||||
var pageIndex = unchecked((ushort)(codepoint / 4096));
|
||||
font.NativePtr->Used4kPagesMap[pageIndex >> 3] |= unchecked((byte)(1 << (pageIndex & 7)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the corresponding ImGui viewport ID for the given window handle.
|
||||
/// </summary>
|
||||
|
|
@ -564,89 +448,6 @@ public static class ImGuiHelpers
|
|||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to validate that <paramref name="fontPtr"/> is valid.
|
||||
/// </summary>
|
||||
/// <param name="fontPtr">The font pointer.</param>
|
||||
/// <returns>The exception, if any occurred during validation.</returns>
|
||||
internal static unsafe Exception? ValidateUnsafe(this ImFontPtr fontPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
var font = fontPtr.NativePtr;
|
||||
if (font is null)
|
||||
throw new NullReferenceException("The font is null.");
|
||||
|
||||
_ = Marshal.ReadIntPtr((nint)font);
|
||||
if (font->IndexedHotData.Data != 0)
|
||||
_ = Marshal.ReadIntPtr(font->IndexedHotData.Data);
|
||||
if (font->FrequentKerningPairs.Data != 0)
|
||||
_ = Marshal.ReadIntPtr(font->FrequentKerningPairs.Data);
|
||||
if (font->IndexLookup.Data != 0)
|
||||
_ = Marshal.ReadIntPtr(font->IndexLookup.Data);
|
||||
if (font->Glyphs.Data != 0)
|
||||
_ = Marshal.ReadIntPtr(font->Glyphs.Data);
|
||||
if (font->KerningPairs.Data != 0)
|
||||
_ = Marshal.ReadIntPtr(font->KerningPairs.Data);
|
||||
if (font->ConfigDataCount == 0 && font->ConfigData is not null)
|
||||
throw new InvalidOperationException("ConfigDataCount == 0 but ConfigData is not null?");
|
||||
if (font->ConfigDataCount != 0 && font->ConfigData is null)
|
||||
throw new InvalidOperationException("ConfigDataCount != 0 but ConfigData is null?");
|
||||
if (font->ConfigData is not null)
|
||||
_ = Marshal.ReadIntPtr((nint)font->ConfigData);
|
||||
if (font->FallbackGlyph is not null
|
||||
&& ((nint)font->FallbackGlyph < font->Glyphs.Data || (nint)font->FallbackGlyph >= font->Glyphs.Data))
|
||||
throw new InvalidOperationException("FallbackGlyph is not in range of Glyphs.Data");
|
||||
if (font->FallbackHotData is not null
|
||||
&& ((nint)font->FallbackHotData < font->IndexedHotData.Data
|
||||
|| (nint)font->FallbackHotData >= font->IndexedHotData.Data))
|
||||
throw new InvalidOperationException("FallbackGlyph is not in range of Glyphs.Data");
|
||||
if (font->ContainerAtlas is not null)
|
||||
_ = Marshal.ReadIntPtr((nint)font->ContainerAtlas);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the fallback char of <paramref name="font"/>.
|
||||
/// </summary>
|
||||
/// <param name="font">The font.</param>
|
||||
/// <param name="c">The fallback character.</param>
|
||||
internal static unsafe void UpdateFallbackChar(this ImFontPtr font, char c)
|
||||
{
|
||||
font.FallbackChar = c;
|
||||
font.NativePtr->FallbackHotData =
|
||||
(ImFontGlyphHotData*)((ImFontGlyphHotDataReal*)font.IndexedHotData.Data + font.FallbackChar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the supplied codepoint is inside the given range,
|
||||
/// in format of <see cref="ImFontConfig.GlyphRanges"/>.
|
||||
/// </summary>
|
||||
/// <param name="codepoint">The codepoint.</param>
|
||||
/// <param name="rangePtr">The ranges.</param>
|
||||
/// <returns>Whether it is the case.</returns>
|
||||
internal static unsafe bool IsCodepointInSuppliedGlyphRangesUnsafe(int codepoint, ushort* rangePtr)
|
||||
{
|
||||
if (codepoint is <= 0 or >= ushort.MaxValue)
|
||||
return false;
|
||||
|
||||
while (*rangePtr != 0)
|
||||
{
|
||||
var from = *rangePtr++;
|
||||
var to = *rangePtr++;
|
||||
if (from <= codepoint && codepoint <= to)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get data needed for each new frame.
|
||||
/// </summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue