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;
|
public bool UseAxisFontsFromGame { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
[Obsolete("It happens that nobody touched this setting", true)]
|
|
||||||
public float FontGammaLevel { get; set; } = 1.4f;
|
public float FontGammaLevel { get; set; } = 1.4f;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// <summary>
|
||||||
/// Enum of available game fonts in specific sizes.
|
/// Enum of available game fonts in specific sizes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum GameFontFamilyAndSize
|
public enum GameFontFamilyAndSize : int
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Placeholder meaning unused.
|
/// 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.
|
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/AXIS_96.fdt", "common/font/font{0}.tex", -1)]
|
|
||||||
Axis96,
|
Axis96,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -23,7 +22,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
|
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/AXIS_12.fdt", "common/font/font{0}.tex", -1)]
|
|
||||||
Axis12,
|
Axis12,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -31,7 +29,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
|
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/AXIS_14.fdt", "common/font/font{0}.tex", -1)]
|
|
||||||
Axis14,
|
Axis14,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -39,7 +36,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
|
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/AXIS_18.fdt", "common/font/font{0}.tex", -1)]
|
|
||||||
Axis18,
|
Axis18,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -47,7 +43,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
|
/// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/AXIS_36.fdt", "common/font/font{0}.tex", -4)]
|
|
||||||
Axis36,
|
Axis36,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -55,7 +50,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Serif font. Contains mostly ASCII range. Used in game for job names.
|
/// Serif font. Contains mostly ASCII range. Used in game for job names.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/Jupiter_16.fdt", "common/font/font{0}.tex", -1)]
|
|
||||||
Jupiter16,
|
Jupiter16,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -63,7 +57,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Serif font. Contains mostly ASCII range. Used in game for job names.
|
/// Serif font. Contains mostly ASCII range. Used in game for job names.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/Jupiter_20.fdt", "common/font/font{0}.tex", -1)]
|
|
||||||
Jupiter20,
|
Jupiter20,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -71,7 +64,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Serif font. Contains mostly ASCII range. Used in game for job names.
|
/// Serif font. Contains mostly ASCII range. Used in game for job names.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/Jupiter_23.fdt", "common/font/font{0}.tex", -1)]
|
|
||||||
Jupiter23,
|
Jupiter23,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -79,7 +71,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Serif font. Contains mostly numbers. Used in game for flying texts.
|
/// Serif font. Contains mostly numbers. Used in game for flying texts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/Jupiter_45.fdt", "common/font/font{0}.tex", -2)]
|
|
||||||
Jupiter45,
|
Jupiter45,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -87,7 +78,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Serif font. Contains mostly ASCII range. Used in game for job names.
|
/// Serif font. Contains mostly ASCII range. Used in game for job names.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/Jupiter_46.fdt", "common/font/font{0}.tex", -2)]
|
|
||||||
Jupiter46,
|
Jupiter46,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -95,7 +85,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Serif font. Contains mostly numbers. Used in game for flying texts.
|
/// Serif font. Contains mostly numbers. Used in game for flying texts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/Jupiter_90.fdt", "common/font/font{0}.tex", -4)]
|
|
||||||
Jupiter90,
|
Jupiter90,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -103,7 +92,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff.
|
/// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/Meidinger_16.fdt", "common/font/font{0}.tex", -1)]
|
|
||||||
Meidinger16,
|
Meidinger16,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -111,7 +99,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff.
|
/// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/Meidinger_20.fdt", "common/font/font{0}.tex", -1)]
|
|
||||||
Meidinger20,
|
Meidinger20,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -119,7 +106,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff.
|
/// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/Meidinger_40.fdt", "common/font/font{0}.tex", -4)]
|
|
||||||
Meidinger40,
|
Meidinger40,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -127,7 +113,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Horizontally wide. Contains mostly ASCII range.
|
/// Horizontally wide. Contains mostly ASCII range.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/MiedingerMid_10.fdt", "common/font/font{0}.tex", -1)]
|
|
||||||
MiedingerMid10,
|
MiedingerMid10,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -135,7 +120,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Horizontally wide. Contains mostly ASCII range.
|
/// Horizontally wide. Contains mostly ASCII range.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/MiedingerMid_12.fdt", "common/font/font{0}.tex", -1)]
|
|
||||||
MiedingerMid12,
|
MiedingerMid12,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -143,7 +127,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Horizontally wide. Contains mostly ASCII range.
|
/// Horizontally wide. Contains mostly ASCII range.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/MiedingerMid_14.fdt", "common/font/font{0}.tex", -1)]
|
|
||||||
MiedingerMid14,
|
MiedingerMid14,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -151,7 +134,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Horizontally wide. Contains mostly ASCII range.
|
/// Horizontally wide. Contains mostly ASCII range.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/MiedingerMid_18.fdt", "common/font/font{0}.tex", -1)]
|
|
||||||
MiedingerMid18,
|
MiedingerMid18,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -159,7 +141,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Horizontally wide. Contains mostly ASCII range.
|
/// Horizontally wide. Contains mostly ASCII range.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/MiedingerMid_36.fdt", "common/font/font{0}.tex", -2)]
|
|
||||||
MiedingerMid36,
|
MiedingerMid36,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -167,7 +148,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Horizontally narrow. Contains mostly ASCII range. Used for addon titles.
|
/// Horizontally narrow. Contains mostly ASCII range. Used for addon titles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/TrumpGothic_184.fdt", "common/font/font{0}.tex", -1)]
|
|
||||||
TrumpGothic184,
|
TrumpGothic184,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -175,7 +155,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Horizontally narrow. Contains mostly ASCII range. Used for addon titles.
|
/// Horizontally narrow. Contains mostly ASCII range. Used for addon titles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/TrumpGothic_23.fdt", "common/font/font{0}.tex", -1)]
|
|
||||||
TrumpGothic23,
|
TrumpGothic23,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -183,7 +162,6 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Horizontally narrow. Contains mostly ASCII range. Used for addon titles.
|
/// Horizontally narrow. Contains mostly ASCII range. Used for addon titles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/TrumpGothic_34.fdt", "common/font/font{0}.tex", -1)]
|
|
||||||
TrumpGothic34,
|
TrumpGothic34,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -191,6 +169,5 @@ public enum GameFontFamilyAndSize
|
||||||
///
|
///
|
||||||
/// Horizontally narrow. Contains mostly ASCII range. Used for addon titles.
|
/// Horizontally narrow. Contains mostly ASCII range. Used for addon titles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameFontFamilyAndSize("common/font/TrumpGothic_68.fdt", "common/font/font{0}.tex", -3)]
|
|
||||||
TrumpGothic68,
|
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 System.Numerics;
|
||||||
|
|
||||||
using Dalamud.Interface.ManagedFontAtlas;
|
|
||||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
|
||||||
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
namespace Dalamud.Interface.GameFonts;
|
namespace Dalamud.Interface.GameFonts;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ABI-compatible wrapper for <see cref="IFontHandle"/>.
|
/// Prepare and keep game font loaded for use in OnDraw.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class GameFontHandle : IFontHandle
|
public class GameFontHandle : IDisposable
|
||||||
{
|
{
|
||||||
private readonly IFontHandle.IInternal fontHandle;
|
private readonly GameFontManager manager;
|
||||||
private readonly FontAtlasFactory fontAtlasFactory;
|
private readonly GameFontStyle fontStyle;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="GameFontHandle"/> class.
|
/// Initializes a new instance of the <see cref="GameFontHandle"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fontHandle">The wrapped <see cref="IFontHandle"/>.</param>
|
/// <param name="manager">GameFontManager instance.</param>
|
||||||
/// <param name="fontAtlasFactory">An instance of <see cref="FontAtlasFactory"/>.</param>
|
/// <param name="font">Font to use.</param>
|
||||||
internal GameFontHandle(IFontHandle.IInternal fontHandle, FontAtlasFactory fontAtlasFactory)
|
internal GameFontHandle(GameFontManager manager, GameFontStyle font)
|
||||||
{
|
{
|
||||||
this.fontHandle = fontHandle;
|
this.manager = manager;
|
||||||
this.fontAtlasFactory = fontAtlasFactory;
|
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>
|
/// <summary>
|
||||||
/// Gets the font style. Only applicable for <see cref="GameFontHandle"/>.
|
/// Gets the font style.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("If you use this, let the fact that you use this be known at Dalamud Discord.", false)]
|
public GameFontStyle Style => this.fontStyle;
|
||||||
public GameFontStyle Style => ((GamePrebakedFontHandle)this.fontHandle).FontStyle;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the relevant <see cref="FdtReader"/>.<br />
|
/// Gets a value indicating whether this font is ready for use.
|
||||||
/// <br />
|
|
||||||
/// Only applicable for game fonts. Otherwise it will throw.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("If you use this, let the fact that you use this be known at Dalamud Discord.", false)]
|
public bool Available
|
||||||
public FdtReader FdtReader => this.fontAtlasFactory.GetFdtReader(this.Style.FamilyAndSize)!;
|
{
|
||||||
|
get
|
||||||
/// <inheritdoc />
|
{
|
||||||
public void Dispose() => this.fontHandle.Dispose();
|
unsafe
|
||||||
|
{
|
||||||
/// <inheritdoc/>
|
return this.manager.GetFont(this.fontStyle).GetValueOrDefault(null).NativePtr != null;
|
||||||
public IDisposable Push() => this.fontHandle.Push();
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="GameFontLayoutPlan.Builder"/>.<br />
|
/// Gets the font.
|
||||||
/// <br />
|
/// </summary>
|
||||||
/// Only applicable for game fonts. Otherwise it will throw.
|
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>
|
/// </summary>
|
||||||
/// <param name="text">Text.</param>
|
/// <param name="text">Text.</param>
|
||||||
/// <returns>A new builder for GameFontLayoutPlan.</returns>
|
/// <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)
|
||||||
public GameFontLayoutPlan.Builder LayoutBuilder(string text) => new(this.ImFont, this.FdtReader, text);
|
{
|
||||||
|
return new GameFontLayoutPlan.Builder(this.ImFont, this.FdtReader, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose() => this.manager.DecreaseFontRef(this.fontStyle);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Draws text.
|
/// Draws text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text">Text to draw.</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 Text(string text)
|
public void Text(string text)
|
||||||
{
|
{
|
||||||
if (!this.Available)
|
if (!this.Available)
|
||||||
|
|
@ -94,7 +93,6 @@ public sealed class GameFontHandle : IFontHandle
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="col">Color.</param>
|
/// <param name="col">Color.</param>
|
||||||
/// <param name="text">Text to draw.</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)
|
public void TextColored(Vector4 col, string text)
|
||||||
{
|
{
|
||||||
ImGui.PushStyleColor(ImGuiCol.Text, col);
|
ImGui.PushStyleColor(ImGuiCol.Text, col);
|
||||||
|
|
@ -106,7 +104,6 @@ public sealed class GameFontHandle : IFontHandle
|
||||||
/// Draws disabled text.
|
/// Draws disabled text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text">Text to draw.</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 TextDisabled(string text)
|
public void TextDisabled(string text)
|
||||||
{
|
{
|
||||||
unsafe
|
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>
|
/// </summary>
|
||||||
public float SizePt
|
public float SizePt
|
||||||
{
|
{
|
||||||
readonly get => this.SizePx * 3 / 4;
|
get => this.SizePx * 3 / 4;
|
||||||
set => this.SizePx = value * 4 / 3;
|
set => this.SizePx = value * 4 / 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,14 +73,14 @@ public struct GameFontStyle
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float BaseSkewStrength
|
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;
|
set => this.SkewStrength = value * this.SizePx / this.BaseSizePx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the font family.
|
/// Gets the font family.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly GameFontFamily Family => this.FamilyAndSize switch
|
public GameFontFamily Family => this.FamilyAndSize switch
|
||||||
{
|
{
|
||||||
GameFontFamilyAndSize.Undefined => GameFontFamily.Undefined,
|
GameFontFamilyAndSize.Undefined => GameFontFamily.Undefined,
|
||||||
GameFontFamilyAndSize.Axis96 => GameFontFamily.Axis,
|
GameFontFamilyAndSize.Axis96 => GameFontFamily.Axis,
|
||||||
|
|
@ -112,7 +112,7 @@ public struct GameFontStyle
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the corresponding GameFontFamilyAndSize but with minimum possible font sizes.
|
/// Gets the corresponding GameFontFamilyAndSize but with minimum possible font sizes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly GameFontFamilyAndSize FamilyWithMinimumSize => this.Family switch
|
public GameFontFamilyAndSize FamilyWithMinimumSize => this.Family switch
|
||||||
{
|
{
|
||||||
GameFontFamily.Axis => GameFontFamilyAndSize.Axis96,
|
GameFontFamily.Axis => GameFontFamilyAndSize.Axis96,
|
||||||
GameFontFamily.Jupiter => GameFontFamilyAndSize.Jupiter16,
|
GameFontFamily.Jupiter => GameFontFamilyAndSize.Jupiter16,
|
||||||
|
|
@ -126,7 +126,7 @@ public struct GameFontStyle
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the base font size in point unit.
|
/// Gets the base font size in point unit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly float BaseSizePt => this.FamilyAndSize switch
|
public float BaseSizePt => this.FamilyAndSize switch
|
||||||
{
|
{
|
||||||
GameFontFamilyAndSize.Undefined => 0,
|
GameFontFamilyAndSize.Undefined => 0,
|
||||||
GameFontFamilyAndSize.Axis96 => 9.6f,
|
GameFontFamilyAndSize.Axis96 => 9.6f,
|
||||||
|
|
@ -158,14 +158,14 @@ public struct GameFontStyle
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the base font size in pixel unit.
|
/// Gets the base font size in pixel unit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly float BaseSizePx => this.BaseSizePt * 4 / 3;
|
public float BaseSizePx => this.BaseSizePt * 4 / 3;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this font is bold.
|
/// Gets or sets a value indicating whether this font is bold.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Bold
|
public bool Bold
|
||||||
{
|
{
|
||||||
readonly get => this.Weight > 0f;
|
get => this.Weight > 0f;
|
||||||
set => this.Weight = value ? 1f : 0f;
|
set => this.Weight = value ? 1f : 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,8 +174,8 @@ public struct GameFontStyle
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Italic
|
public bool Italic
|
||||||
{
|
{
|
||||||
readonly get => this.SkewStrength != 0;
|
get => this.SkewStrength != 0;
|
||||||
set => this.SkewStrength = value ? this.SizePx / 6 : 0;
|
set => this.SkewStrength = value ? this.SizePx / 7 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -233,26 +233,13 @@ public struct GameFontStyle
|
||||||
_ => GameFontFamilyAndSize.Undefined,
|
_ => 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>
|
/// <summary>
|
||||||
/// Calculates the adjustment to width resulting fron Weight and SkewStrength.
|
/// Calculates the adjustment to width resulting fron Weight and SkewStrength.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="header">Font header.</param>
|
/// <param name="header">Font header.</param>
|
||||||
/// <param name="glyph">Glyph.</param>
|
/// <param name="glyph">Glyph.</param>
|
||||||
/// <returns>Width adjustment in pixel unit.</returns>
|
/// <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;
|
var widthDelta = this.Weight;
|
||||||
switch (this.BaseSkewStrength)
|
switch (this.BaseSkewStrength)
|
||||||
|
|
@ -276,11 +263,11 @@ public struct GameFontStyle
|
||||||
/// <param name="reader">Font information.</param>
|
/// <param name="reader">Font information.</param>
|
||||||
/// <param name="glyph">Glyph.</param>
|
/// <param name="glyph">Glyph.</param>
|
||||||
/// <returns>Width adjustment in pixel unit.</returns>
|
/// <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);
|
this.CalculateBaseWidthAdjustment(reader.FontHeader, glyph);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override readonly string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"GameFontStyle({this.FamilyAndSize}, {this.SizePt}pt, skew={this.SkewStrength}, weight={this.Weight})";
|
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.Game.Text;
|
||||||
using Dalamud.Hooking.WndProcHook;
|
using Dalamud.Hooking.WndProcHook;
|
||||||
using Dalamud.Interface.GameFonts;
|
using Dalamud.Interface.GameFonts;
|
||||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
|
|
||||||
using ImGuiNET;
|
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 (HanRange.Any(x => x.FirstCodePoint <= chr && chr < x.FirstCodePoint + x.Length))
|
||||||
{
|
{
|
||||||
if (Service<FontAtlasFactory>.Get()
|
if (Service<GameFontManager>.Get()
|
||||||
?.GetFdtReader(GameFontFamilyAndSize.Axis12)
|
.GetFdtReader(GameFontFamilyAndSize.Axis12)
|
||||||
.FindGlyph(chr) is null)
|
?.FindGlyph(chr) is null)
|
||||||
{
|
{
|
||||||
if (!this.EncounteredHan)
|
if (!this.EncounteredHan)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ using Dalamud.Interface.Internal.Windows.PluginInstaller;
|
||||||
using Dalamud.Interface.Internal.Windows.SelfTest;
|
using Dalamud.Interface.Internal.Windows.SelfTest;
|
||||||
using Dalamud.Interface.Internal.Windows.Settings;
|
using Dalamud.Interface.Internal.Windows.Settings;
|
||||||
using Dalamud.Interface.Internal.Windows.StyleEditor;
|
using Dalamud.Interface.Internal.Windows.StyleEditor;
|
||||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
|
||||||
using Dalamud.Interface.Style;
|
using Dalamud.Interface.Style;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
|
@ -94,8 +93,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
private DalamudInterface(
|
private DalamudInterface(
|
||||||
Dalamud dalamud,
|
Dalamud dalamud,
|
||||||
DalamudConfiguration configuration,
|
DalamudConfiguration configuration,
|
||||||
FontAtlasFactory fontAtlasFactory,
|
InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene,
|
||||||
InterfaceManager interfaceManager,
|
|
||||||
PluginImageCache pluginImageCache,
|
PluginImageCache pluginImageCache,
|
||||||
DalamudAssetManager dalamudAssetManager,
|
DalamudAssetManager dalamudAssetManager,
|
||||||
Game.Framework framework,
|
Game.Framework framework,
|
||||||
|
|
@ -105,7 +103,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
this.dalamud = dalamud;
|
this.dalamud = dalamud;
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
this.interfaceManager = interfaceManager;
|
this.interfaceManager = interfaceManagerWithScene.Manager;
|
||||||
|
|
||||||
this.WindowSystem = new WindowSystem("DalamudCore");
|
this.WindowSystem = new WindowSystem("DalamudCore");
|
||||||
|
|
||||||
|
|
@ -124,14 +122,10 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
clientState,
|
clientState,
|
||||||
configuration,
|
configuration,
|
||||||
dalamudAssetManager,
|
dalamudAssetManager,
|
||||||
fontAtlasFactory,
|
|
||||||
framework,
|
framework,
|
||||||
gameGui,
|
gameGui,
|
||||||
titleScreenMenu) { IsOpen = false };
|
titleScreenMenu) { IsOpen = false };
|
||||||
this.changelogWindow = new ChangelogWindow(
|
this.changelogWindow = new ChangelogWindow(this.titleScreenMenuWindow) { IsOpen = false };
|
||||||
this.titleScreenMenuWindow,
|
|
||||||
fontAtlasFactory,
|
|
||||||
dalamudAssetManager) { IsOpen = false };
|
|
||||||
this.profilerWindow = new ProfilerWindow() { IsOpen = false };
|
this.profilerWindow = new ProfilerWindow() { IsOpen = false };
|
||||||
this.branchSwitcherWindow = new BranchSwitcherWindow() { IsOpen = false };
|
this.branchSwitcherWindow = new BranchSwitcherWindow() { IsOpen = false };
|
||||||
this.hitchSettingsWindow = new HitchSettingsWindow() { IsOpen = false };
|
this.hitchSettingsWindow = new HitchSettingsWindow() { IsOpen = false };
|
||||||
|
|
@ -213,7 +207,6 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
this.interfaceManager.Draw -= this.OnDraw;
|
this.interfaceManager.Draw -= this.OnDraw;
|
||||||
|
|
||||||
this.WindowSystem.Windows.OfType<IDisposable>().AggregateToDisposable().Dispose();
|
|
||||||
this.WindowSystem.RemoveAllWindows();
|
this.WindowSystem.RemoveAllWindows();
|
||||||
|
|
||||||
this.changelogWindow.Dispose();
|
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.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
|
|
@ -6,8 +7,6 @@ using Dalamud.Interface.Animation.EasingFunctions;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Components;
|
using Dalamud.Interface.Components;
|
||||||
using Dalamud.Interface.GameFonts;
|
using Dalamud.Interface.GameFonts;
|
||||||
using Dalamud.Interface.ManagedFontAtlas;
|
|
||||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
|
|
@ -32,14 +31,8 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
||||||
• Plugins can now add tooltips and interaction to the server info bar
|
• Plugins can now add tooltips and interaction to the server info bar
|
||||||
• The Dalamud/plugin installer UI has been refreshed
|
• The Dalamud/plugin installer UI has been refreshed
|
||||||
";
|
";
|
||||||
|
|
||||||
private readonly TitleScreenMenuWindow tsmWindow;
|
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))
|
private readonly InOutCubic windowFade = new(TimeSpan.FromSeconds(2.5f))
|
||||||
{
|
{
|
||||||
|
|
@ -53,36 +46,27 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
||||||
Point2 = Vector2.One,
|
Point2 = Vector2.One,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private IDalamudTextureWrap? apiBumpExplainerTexture;
|
||||||
|
private IDalamudTextureWrap? logoTexture;
|
||||||
|
private GameFontHandle? bannerFont;
|
||||||
|
|
||||||
private State state = State.WindowFadeIn;
|
private State state = State.WindowFadeIn;
|
||||||
|
|
||||||
private bool needFadeRestart = false;
|
private bool needFadeRestart = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ChangelogWindow"/> class.
|
/// Initializes a new instance of the <see cref="ChangelogWindow"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tsmWindow">TSM window.</param>
|
/// <param name="tsmWindow">TSM window.</param>
|
||||||
/// <param name="fontAtlasFactory">An instance of <see cref="FontAtlasFactory"/>.</param>
|
public ChangelogWindow(TitleScreenMenuWindow tsmWindow)
|
||||||
/// <param name="assets">An instance of <see cref="DalamudAssetManager"/>.</param>
|
|
||||||
public ChangelogWindow(
|
|
||||||
TitleScreenMenuWindow tsmWindow,
|
|
||||||
FontAtlasFactory fontAtlasFactory,
|
|
||||||
DalamudAssetManager assets)
|
|
||||||
: base("What's new in Dalamud?##ChangelogWindow", ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse, true)
|
: base("What's new in Dalamud?##ChangelogWindow", ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse, true)
|
||||||
{
|
{
|
||||||
this.tsmWindow = tsmWindow;
|
this.tsmWindow = tsmWindow;
|
||||||
this.Namespace = "DalamudChangelogWindow";
|
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 we are going to show a changelog, make sure we have the font ready, otherwise it will hitch
|
||||||
if (WarrantsChangelog())
|
if (WarrantsChangelog())
|
||||||
_ = this.bannerFont.Value;
|
Service<GameFontManager>.GetAsync().ContinueWith(t => this.MakeFont(t.Result));
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum State
|
private enum State
|
||||||
|
|
@ -113,12 +97,20 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
||||||
Service<DalamudInterface>.Get().SetCreditsDarkeningAnimation(true);
|
Service<DalamudInterface>.Get().SetCreditsDarkeningAnimation(true);
|
||||||
this.tsmWindow.AllowDrawing = false;
|
this.tsmWindow.AllowDrawing = false;
|
||||||
|
|
||||||
_ = this.bannerFont;
|
this.MakeFont(Service<GameFontManager>.Get());
|
||||||
|
|
||||||
this.state = State.WindowFadeIn;
|
this.state = State.WindowFadeIn;
|
||||||
this.windowFade.Reset();
|
this.windowFade.Reset();
|
||||||
this.bodyFade.Reset();
|
this.bodyFade.Reset();
|
||||||
this.needFadeRestart = true;
|
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();
|
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));
|
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)))
|
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();
|
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 (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)
|
switch (this.state)
|
||||||
{
|
{
|
||||||
|
|
@ -280,11 +275,9 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
||||||
ImGui.TextWrapped("If some plugins are displayed with a red cross in the 'Installed Plugins' tab, they may not yet be available.");
|
ImGui.TextWrapped("If some plugins are displayed with a red cross in the 'Installed Plugins' tab, they may not yet be available.");
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(15);
|
ImGuiHelpers.ScaledDummy(15);
|
||||||
|
|
||||||
ImGuiHelpers.CenterCursorFor(this.apiBumpExplainerTexture.Value.Width);
|
ImGuiHelpers.CenterCursorFor(this.apiBumpExplainerTexture!.Width);
|
||||||
ImGui.Image(
|
ImGui.Image(this.apiBumpExplainerTexture.ImGuiHandle, this.apiBumpExplainerTexture.Size);
|
||||||
this.apiBumpExplainerTexture.Value.ImGuiHandle,
|
|
||||||
this.apiBumpExplainerTexture.Value.Size);
|
|
||||||
|
|
||||||
DrawNextButton(State.Links);
|
DrawNextButton(State.Links);
|
||||||
break;
|
break;
|
||||||
|
|
@ -384,4 +377,7 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
||||||
public void Dispose()
|
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.Internal.Windows.Data.Widgets;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Utility;
|
|
||||||
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
|
|
@ -16,7 +14,7 @@ namespace Dalamud.Interface.Internal.Windows.Data;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class responsible for drawing the data/debug window.
|
/// Class responsible for drawing the data/debug window.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class DataWindow : Window, IDisposable
|
internal class DataWindow : Window
|
||||||
{
|
{
|
||||||
private readonly IDataWindowWidget[] modules =
|
private readonly IDataWindowWidget[] modules =
|
||||||
{
|
{
|
||||||
|
|
@ -36,7 +34,6 @@ internal class DataWindow : Window, IDisposable
|
||||||
new FlyTextWidget(),
|
new FlyTextWidget(),
|
||||||
new FontAwesomeTestWidget(),
|
new FontAwesomeTestWidget(),
|
||||||
new GameInventoryTestWidget(),
|
new GameInventoryTestWidget(),
|
||||||
new GamePrebakedFontsTestWidget(),
|
|
||||||
new GamepadWidget(),
|
new GamepadWidget(),
|
||||||
new GaugeWidget(),
|
new GaugeWidget(),
|
||||||
new HookWidget(),
|
new HookWidget(),
|
||||||
|
|
@ -79,9 +76,6 @@ internal class DataWindow : Window, IDisposable
|
||||||
this.Load();
|
this.Load();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose() => this.modules.OfType<IDisposable>().AggregateToDisposable().Dispose();
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void OnOpen()
|
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.Configuration.Internal;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Internal.Windows.Settings.Tabs;
|
using Dalamud.Interface.Internal.Windows.Settings.Tabs;
|
||||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
|
using Dalamud.Plugin.Internal;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
|
|
@ -19,7 +19,14 @@ namespace Dalamud.Interface.Internal.Windows.Settings;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class SettingsWindow : Window
|
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;
|
private string searchInput = string.Empty;
|
||||||
|
|
||||||
|
|
@ -42,15 +49,6 @@ internal class SettingsWindow : Window
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void OnOpen()
|
public override void OnOpen()
|
||||||
{
|
{
|
||||||
this.tabs ??= new SettingsTab[]
|
|
||||||
{
|
|
||||||
new SettingsTabGeneral(),
|
|
||||||
new SettingsTabLook(),
|
|
||||||
new SettingsTabDtr(),
|
|
||||||
new SettingsTabExperimental(),
|
|
||||||
new SettingsTabAbout(),
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var settingsTab in this.tabs)
|
foreach (var settingsTab in this.tabs)
|
||||||
{
|
{
|
||||||
settingsTab.Load();
|
settingsTab.Load();
|
||||||
|
|
@ -66,12 +64,15 @@ internal class SettingsWindow : Window
|
||||||
{
|
{
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
var configuration = Service<DalamudConfiguration>.Get();
|
||||||
var interfaceManager = Service<InterfaceManager>.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;
|
ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale;
|
||||||
fontAtlasFactory.UseAxisOverride = null;
|
interfaceManager.FontGammaOverride = null;
|
||||||
|
interfaceManager.UseAxisOverride = null;
|
||||||
|
|
||||||
if (rebuildFont)
|
if (rebuildFont)
|
||||||
interfaceManager.RebuildFonts();
|
interfaceManager.RebuildFonts();
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
using System.Diagnostics;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
using CheapLoc;
|
using CheapLoc;
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
using Dalamud.Interface.GameFonts;
|
using Dalamud.Interface.GameFonts;
|
||||||
using Dalamud.Interface.ManagedFontAtlas;
|
|
||||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
|
|
@ -15,6 +15,7 @@ using Dalamud.Storage.Assets;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using ImGuiScene;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.Windows.Settings.Tabs;
|
namespace Dalamud.Interface.Internal.Windows.Settings.Tabs;
|
||||||
|
|
||||||
|
|
@ -172,21 +173,16 @@ Contribute at: https://github.com/goatcorp/Dalamud
|
||||||
";
|
";
|
||||||
|
|
||||||
private readonly Stopwatch creditsThrottler;
|
private readonly Stopwatch creditsThrottler;
|
||||||
private readonly IFontAtlas privateAtlas;
|
|
||||||
|
|
||||||
private string creditsText;
|
private string creditsText;
|
||||||
|
|
||||||
private bool resetNow = false;
|
private bool resetNow = false;
|
||||||
private IDalamudTextureWrap? logoTexture;
|
private IDalamudTextureWrap? logoTexture;
|
||||||
private IFontHandle? thankYouFont;
|
private GameFontHandle? thankYouFont;
|
||||||
|
|
||||||
public SettingsTabAbout()
|
public SettingsTabAbout()
|
||||||
{
|
{
|
||||||
this.creditsThrottler = new();
|
this.creditsThrottler = new();
|
||||||
|
|
||||||
this.privateAtlas = Service<FontAtlasFactory>
|
|
||||||
.Get()
|
|
||||||
.CreateFontAtlas(nameof(SettingsTabAbout), FontAtlasAutoRebuildMode.Async);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SettingsEntry[] Entries { get; } = { };
|
public override SettingsEntry[] Entries { get; } = { };
|
||||||
|
|
@ -211,7 +207,11 @@ Contribute at: https://github.com/goatcorp/Dalamud
|
||||||
|
|
||||||
this.creditsThrottler.Restart();
|
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;
|
this.resetNow = true;
|
||||||
|
|
||||||
|
|
@ -269,12 +269,14 @@ Contribute at: https://github.com/goatcorp/Dalamud
|
||||||
|
|
||||||
if (this.thankYouFont != null)
|
if (this.thankYouFont != null)
|
||||||
{
|
{
|
||||||
using var fontPush = this.thankYouFont.Push();
|
ImGui.PushFont(this.thankYouFont.ImFont);
|
||||||
var thankYouLenX = ImGui.CalcTextSize(ThankYouText).X;
|
var thankYouLenX = ImGui.CalcTextSize(ThankYouText).X;
|
||||||
|
|
||||||
ImGui.Dummy(new Vector2((windowX / 2) - (thankYouLenX / 2), 0f));
|
ImGui.Dummy(new Vector2((windowX / 2) - (thankYouLenX / 2), 0f));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted(ThankYouText);
|
ImGui.TextUnformatted(ThankYouText);
|
||||||
|
|
||||||
|
ImGui.PopFont();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(0, windowSize.Y + 50f);
|
ImGuiHelpers.ScaledDummy(0, windowSize.Y + 50f);
|
||||||
|
|
@ -303,5 +305,9 @@ Contribute at: https://github.com/goatcorp/Dalamud
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposes of managed and unmanaged resources.
|
/// Disposes of managed and unmanaged resources.
|
||||||
/// </summary>
|
/// </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.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
using CheapLoc;
|
using CheapLoc;
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Internal.Windows.PluginInstaller;
|
using Dalamud.Interface.Internal.Windows.PluginInstaller;
|
||||||
using Dalamud.Interface.Internal.Windows.Settings.Widgets;
|
using Dalamud.Interface.Internal.Windows.Settings.Widgets;
|
||||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
@ -30,6 +28,7 @@ public class SettingsTabLook : SettingsTab
|
||||||
};
|
};
|
||||||
|
|
||||||
private float globalUiScale;
|
private float globalUiScale;
|
||||||
|
private float fontGamma;
|
||||||
|
|
||||||
public override SettingsEntry[] Entries { get; } =
|
public override SettingsEntry[] Entries { get; } =
|
||||||
{
|
{
|
||||||
|
|
@ -42,8 +41,9 @@ public class SettingsTabLook : SettingsTab
|
||||||
(v, c) => c.UseAxisFontsFromGame = v,
|
(v, c) => c.UseAxisFontsFromGame = v,
|
||||||
v =>
|
v =>
|
||||||
{
|
{
|
||||||
Service<FontAtlasFactory>.Get().UseAxisOverride = v;
|
var im = Service<InterfaceManager>.Get();
|
||||||
Service<InterfaceManager>.Get().RebuildFonts();
|
im.UseAxisOverride = v;
|
||||||
|
im.RebuildFonts();
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new GapSettingsEntry(5, true),
|
new GapSettingsEntry(5, true),
|
||||||
|
|
@ -145,7 +145,6 @@ public class SettingsTabLook : SettingsTab
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
var interfaceManager = Service<InterfaceManager>.Get();
|
var interfaceManager = Service<InterfaceManager>.Get();
|
||||||
var fontBuildTask = interfaceManager.FontBuildTask;
|
|
||||||
|
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global Font Scale"));
|
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;
|
var globalUiScaleInPt = 12f * this.globalUiScale;
|
||||||
if (ImGui.DragFloat("##DalamudSettingsGlobalUiScaleDrag", ref globalUiScaleInPt, 0.1f, 9.6f, 36f, "%.1fpt", ImGuiSliderFlags.AlwaysClamp))
|
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."));
|
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(
|
this.fontGamma = 1.4f;
|
||||||
ImGuiColors.DalamudRed,
|
interfaceManager.FontGammaOverride = this.fontGamma;
|
||||||
Loc.Localize("DalamudSettingsFontBuildFaulted", "Failed to load fonts as requested."));
|
interfaceManager.RebuildFonts();
|
||||||
if (fontBuildTask.Exception is not null
|
|
||||||
&& ImGui.CollapsingHeader("##DalamudSetingsFontBuildFaultReason"))
|
|
||||||
{
|
|
||||||
foreach (var e in fontBuildTask.Exception.InnerExceptions)
|
|
||||||
ImGui.TextUnformatted(e.ToString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui.DragFloat("##DalamudSettingsFontGammaDrag", ref this.fontGamma, 0.005f, 0.3f, 3f, "%.2f", ImGuiSliderFlags.AlwaysClamp))
|
||||||
|
{
|
||||||
|
interfaceManager.FontGammaOverride = this.fontGamma;
|
||||||
|
interfaceManager.RebuildFonts();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsFontGammaHint", "Changes the thickness of text."));
|
||||||
|
|
||||||
base.Draw();
|
base.Draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Load()
|
public override void Load()
|
||||||
{
|
{
|
||||||
this.globalUiScale = Service<DalamudConfiguration>.Get().GlobalUiScale;
|
this.globalUiScale = Service<DalamudConfiguration>.Get().GlobalUiScale;
|
||||||
|
this.fontGamma = Service<DalamudConfiguration>.Get().FontGammaLevel;
|
||||||
|
|
||||||
base.Load();
|
base.Load();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,11 @@ using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState;
|
using Dalamud.Game.ClientState;
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
using Dalamud.Interface.Animation.EasingFunctions;
|
using Dalamud.Interface.Animation.EasingFunctions;
|
||||||
using Dalamud.Interface.ManagedFontAtlas;
|
|
||||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Storage.Assets;
|
using Dalamud.Storage.Assets;
|
||||||
using Dalamud.Utility;
|
|
||||||
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
|
|
@ -30,17 +27,16 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
|
|
||||||
private readonly ClientState clientState;
|
private readonly ClientState clientState;
|
||||||
private readonly DalamudConfiguration configuration;
|
private readonly DalamudConfiguration configuration;
|
||||||
|
private readonly Framework framework;
|
||||||
private readonly GameGui gameGui;
|
private readonly GameGui gameGui;
|
||||||
private readonly TitleScreenMenu titleScreenMenu;
|
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 Lazy<IDalamudTextureWrap> shadeTexture;
|
||||||
|
|
||||||
private readonly Dictionary<Guid, InOutCubic> shadeEasings = new();
|
private readonly Dictionary<Guid, InOutCubic> shadeEasings = new();
|
||||||
private readonly Dictionary<Guid, InOutQuint> moveEasings = new();
|
private readonly Dictionary<Guid, InOutQuint> moveEasings = new();
|
||||||
private readonly Dictionary<Guid, InOutCubic> logoEasings = new();
|
private readonly Dictionary<Guid, InOutCubic> logoEasings = new();
|
||||||
|
private readonly Dictionary<string, InterfaceManager.SpecialGlyphRequest> specialGlyphRequests = new();
|
||||||
|
|
||||||
private InOutCubic? fadeOutEasing;
|
private InOutCubic? fadeOutEasing;
|
||||||
|
|
||||||
|
|
@ -52,7 +48,6 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
/// <param name="clientState">An instance of <see cref="ClientState"/>.</param>
|
/// <param name="clientState">An instance of <see cref="ClientState"/>.</param>
|
||||||
/// <param name="configuration">An instance of <see cref="DalamudConfiguration"/>.</param>
|
/// <param name="configuration">An instance of <see cref="DalamudConfiguration"/>.</param>
|
||||||
/// <param name="dalamudAssetManager">An instance of <see cref="DalamudAssetManager"/>.</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="framework">An instance of <see cref="Framework"/>.</param>
|
||||||
/// <param name="titleScreenMenu">An instance of <see cref="TitleScreenMenu"/>.</param>
|
/// <param name="titleScreenMenu">An instance of <see cref="TitleScreenMenu"/>.</param>
|
||||||
/// <param name="gameGui">An instance of <see cref="gameGui"/>.</param>
|
/// <param name="gameGui">An instance of <see cref="gameGui"/>.</param>
|
||||||
|
|
@ -60,7 +55,6 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
ClientState clientState,
|
ClientState clientState,
|
||||||
DalamudConfiguration configuration,
|
DalamudConfiguration configuration,
|
||||||
DalamudAssetManager dalamudAssetManager,
|
DalamudAssetManager dalamudAssetManager,
|
||||||
FontAtlasFactory fontAtlasFactory,
|
|
||||||
Framework framework,
|
Framework framework,
|
||||||
GameGui gameGui,
|
GameGui gameGui,
|
||||||
TitleScreenMenu titleScreenMenu)
|
TitleScreenMenu titleScreenMenu)
|
||||||
|
|
@ -71,6 +65,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
{
|
{
|
||||||
this.clientState = clientState;
|
this.clientState = clientState;
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
|
this.framework = framework;
|
||||||
this.gameGui = gameGui;
|
this.gameGui = gameGui;
|
||||||
this.titleScreenMenu = titleScreenMenu;
|
this.titleScreenMenu = titleScreenMenu;
|
||||||
|
|
||||||
|
|
@ -82,25 +77,9 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
this.PositionCondition = ImGuiCond.Always;
|
this.PositionCondition = ImGuiCond.Always;
|
||||||
this.RespectCloseHotkey = false;
|
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));
|
this.shadeTexture = new(() => dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.TitleScreenMenuShade));
|
||||||
|
|
||||||
framework.Update += this.FrameworkOnUpdate;
|
framework.Update += this.FrameworkOnUpdate;
|
||||||
this.scopedFinalizer.Add(() => framework.Update -= this.FrameworkOnUpdate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum State
|
private enum State
|
||||||
|
|
@ -115,9 +94,6 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AllowDrawing { get; set; } = true;
|
public bool AllowDrawing { get; set; } = true;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose() => this.scopedFinalizer.Dispose();
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void PreDraw()
|
public override void PreDraw()
|
||||||
{
|
{
|
||||||
|
|
@ -133,6 +109,12 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
base.PostDraw();
|
base.PostDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.framework.Update -= this.FrameworkOnUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
|
|
@ -264,12 +246,33 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
break;
|
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(
|
private bool DrawEntry(
|
||||||
TitleScreenMenuEntry entry, bool inhibitFadeout, bool showText, bool isFirst, bool overrideAlpha, bool interactable)
|
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;
|
var scale = ImGui.GetIO().FontGlobalScale;
|
||||||
|
|
||||||
|
|
@ -380,6 +383,8 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
initialCursor.Y += entry.Texture.Height * scale;
|
initialCursor.Y += entry.Texture.Height * scale;
|
||||||
ImGui.SetCursorPos(initialCursor);
|
ImGui.SetCursorPos(initialCursor);
|
||||||
|
|
||||||
|
ImGui.PopFont();
|
||||||
|
|
||||||
return isHover;
|
return isHover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -396,6 +401,4 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
if (charaMake != IntPtr.Zero || charaSelect != IntPtr.Zero || titleDcWorldMap != IntPtr.Zero)
|
if (charaMake != IntPtr.Zero || charaSelect != IntPtr.Zero || titleDcWorldMap != IntPtr.Zero)
|
||||||
this.IsOpen = false;
|
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.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -11,8 +12,6 @@ using Dalamud.Interface.GameFonts;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.Interface.Internal.ManagedAsserts;
|
using Dalamud.Interface.Internal.ManagedAsserts;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using Dalamud.Interface.ManagedFontAtlas;
|
|
||||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using ImGuiScene;
|
using ImGuiScene;
|
||||||
|
|
@ -31,13 +30,11 @@ public sealed class UiBuilder : IDisposable
|
||||||
private readonly HitchDetector hitchDetector;
|
private readonly HitchDetector hitchDetector;
|
||||||
private readonly string namespaceName;
|
private readonly string namespaceName;
|
||||||
private readonly InterfaceManager interfaceManager = Service<InterfaceManager>.Get();
|
private readonly InterfaceManager interfaceManager = Service<InterfaceManager>.Get();
|
||||||
private readonly Framework framework = Service<Framework>.Get();
|
private readonly GameFontManager gameFontManager = Service<GameFontManager>.Get();
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
||||||
private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new();
|
|
||||||
|
|
||||||
private bool hasErrorWindow = false;
|
private bool hasErrorWindow = false;
|
||||||
private bool lastFrameUiHideState = false;
|
private bool lastFrameUiHideState = false;
|
||||||
|
|
||||||
|
|
@ -48,32 +45,14 @@ public sealed class UiBuilder : IDisposable
|
||||||
/// <param name="namespaceName">The plugin namespace.</param>
|
/// <param name="namespaceName">The plugin namespace.</param>
|
||||||
internal UiBuilder(string namespaceName)
|
internal UiBuilder(string namespaceName)
|
||||||
{
|
{
|
||||||
try
|
this.stopwatch = new Stopwatch();
|
||||||
{
|
this.hitchDetector = new HitchDetector($"UiBuilder({namespaceName})", this.configuration.UiBuilderHitch);
|
||||||
this.stopwatch = new Stopwatch();
|
this.namespaceName = namespaceName;
|
||||||
this.hitchDetector = new HitchDetector($"UiBuilder({namespaceName})", this.configuration.UiBuilderHitch);
|
|
||||||
this.namespaceName = namespaceName;
|
|
||||||
|
|
||||||
this.interfaceManager.Draw += this.OnDraw;
|
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.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>
|
/// <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/>
|
/// 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
|
/// 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
|
/// (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>
|
/// </summary>
|
||||||
[Obsolete($"Use {nameof(this.FontAtlas)} instead.", false)]
|
public event Action BuildFonts;
|
||||||
public event Action? BuildFonts;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets an action that is called any time right after ImGui fonts are rebuilt.<br/>
|
/// 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
|
/// 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
|
/// (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>
|
/// </summary>
|
||||||
[Obsolete($"Use {nameof(this.FontAtlas)} instead.", false)]
|
public event Action AfterBuildFonts;
|
||||||
public event Action? AfterBuildFonts;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets an action that is called when plugin UI or interface modifications are supposed to be shown.
|
/// 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;
|
public event Action HideUi;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </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;
|
public static ImFontPtr DefaultFont => InterfaceManager.DefaultFont;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the default Dalamud icon font based on FontAwesome 5 Free solid.<br />
|
/// Gets the default Dalamud icon font based on FontAwesome 5 Free solid in 17pt.
|
||||||
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
|
|
||||||
/// </summary>
|
/// </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;
|
public static ImFontPtr IconFont => InterfaceManager.IconFont;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the default Dalamud monospaced font based on Inconsolata Regular.<br />
|
/// Gets the default Dalamud monospaced font based on Inconsolata Regular in 16pt.
|
||||||
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
|
|
||||||
/// </summary>
|
/// </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;
|
public static ImFontPtr MonoFont => InterfaceManager.MonoFont;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -250,11 +190,6 @@ public sealed class UiBuilder : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UiPrepared => Service<InterfaceManager.InterfaceManagerWithScene>.GetNullable() != null;
|
public bool UiPrepared => Service<InterfaceManager.InterfaceManagerWithScene>.GetNullable() != null;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the plugin-private font atlas.
|
|
||||||
/// </summary>
|
|
||||||
public IFontAtlas FontAtlas { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether statistics about UI draw time should be collected.
|
/// Gets or sets a value indicating whether statistics about UI draw time should be collected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -384,7 +319,7 @@ public sealed class UiBuilder : IDisposable
|
||||||
if (runInFrameworkThread)
|
if (runInFrameworkThread)
|
||||||
{
|
{
|
||||||
return this.InterfaceManagerWithSceneAsync
|
return this.InterfaceManagerWithSceneAsync
|
||||||
.ContinueWith(_ => this.framework.RunOnFrameworkThread(func))
|
.ContinueWith(_ => Service<Framework>.Get().RunOnFrameworkThread(func))
|
||||||
.Unwrap();
|
.Unwrap();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -406,7 +341,7 @@ public sealed class UiBuilder : IDisposable
|
||||||
if (runInFrameworkThread)
|
if (runInFrameworkThread)
|
||||||
{
|
{
|
||||||
return this.InterfaceManagerWithSceneAsync
|
return this.InterfaceManagerWithSceneAsync
|
||||||
.ContinueWith(_ => this.framework.RunOnFrameworkThread(func))
|
.ContinueWith(_ => Service<Framework>.Get().RunOnFrameworkThread(func))
|
||||||
.Unwrap();
|
.Unwrap();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -422,49 +357,19 @@ public sealed class UiBuilder : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="style">Font to get.</param>
|
/// <param name="style">Font to get.</param>
|
||||||
/// <returns>Handle to the game font which may or may not be available for use yet.</returns>
|
/// <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) => this.gameFontManager.NewFontRef(style);
|
||||||
public GameFontHandle GetGameFontHandle(GameFontStyle style) => new(
|
|
||||||
(IFontHandle.IInternal)this.FontAtlas.NewGameFontHandle(style),
|
|
||||||
Service<FontAtlasFactory>.Get());
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Call this to queue a rebuild of the font atlas.<br/>
|
/// 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
|
/// This will invoke any <see cref="OnBuildFonts"/> handlers and ensure that any loaded fonts are
|
||||||
/// loaded fonts are ready to be used on the next UI frame.
|
/// ready to be used on the next UI frame.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void RebuildFonts()
|
public void RebuildFonts()
|
||||||
{
|
{
|
||||||
Log.Verbose("[FONT] {0} plugin is initiating FONT REBUILD", this.namespaceName);
|
Log.Verbose("[FONT] {0} plugin is initiating FONT REBUILD", this.namespaceName);
|
||||||
if (this.AfterBuildFonts is null && this.BuildFonts is null)
|
this.interfaceManager.RebuildFonts();
|
||||||
this.FontAtlas.BuildFontsAsync();
|
|
||||||
else
|
|
||||||
this.FontAtlas.BuildFontsOnNextFrame();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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>
|
/// <summary>
|
||||||
/// Add a notification to the notification queue.
|
/// Add a notification to the notification queue.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -487,7 +392,12 @@ public sealed class UiBuilder : IDisposable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unregister the UiBuilder. Do not call this in plugin code.
|
/// Unregister the UiBuilder. Do not call this in plugin code.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Open the registered configuration UI, if it exists.
|
/// Open the registered configuration UI, if it exists.
|
||||||
|
|
@ -553,12 +463,8 @@ public sealed class UiBuilder : IDisposable
|
||||||
this.ShowUi?.InvokeSafely();
|
this.ShowUi?.InvokeSafely();
|
||||||
}
|
}
|
||||||
|
|
||||||
// just in case, if something goes wrong, prevent drawing; otherwise it probably will crash.
|
if (!this.interfaceManager.FontsReady)
|
||||||
if (!this.FontAtlas.BuildTask.IsCompletedSuccessfully
|
|
||||||
&& (this.BuildFonts is not null || this.AfterBuildFonts is not null))
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.PushID(this.namespaceName);
|
ImGui.PushID(this.namespaceName);
|
||||||
if (DoStats)
|
if (DoStats)
|
||||||
|
|
@ -620,28 +526,14 @@ public sealed class UiBuilder : IDisposable
|
||||||
this.hitchDetector.Stop();
|
this.hitchDetector.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void PrivateAtlasOnBuildStepChange(IFontAtlasBuildToolkit e)
|
private void OnBuildFonts()
|
||||||
{
|
{
|
||||||
if (e.IsAsyncBuildOperation)
|
this.BuildFonts?.InvokeSafely();
|
||||||
return;
|
}
|
||||||
|
|
||||||
e.OnPreBuild(
|
private void OnAfterBuildFonts()
|
||||||
_ =>
|
{
|
||||||
{
|
this.AfterBuildFonts?.InvokeSafely();
|
||||||
var prev = ImGui.GetIO().NativePtr->Fonts;
|
|
||||||
ImGui.GetIO().NativePtr->Fonts = e.NewImAtlas.NativePtr;
|
|
||||||
this.BuildFonts?.InvokeSafely();
|
|
||||||
ImGui.GetIO().NativePtr->Fonts = prev;
|
|
||||||
});
|
|
||||||
|
|
||||||
e.OnPostBuild(
|
|
||||||
_ =>
|
|
||||||
{
|
|
||||||
var prev = ImGui.GetIO().NativePtr->Fonts;
|
|
||||||
ImGui.GetIO().NativePtr->Fonts = e.NewImAtlas.NativePtr;
|
|
||||||
this.AfterBuildFonts?.InvokeSafely();
|
|
||||||
ImGui.GetIO().NativePtr->Fonts = prev;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnResizeBuffers()
|
private void OnResizeBuffers()
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,10 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Reactive.Disposables;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text.Unicode;
|
|
||||||
|
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Game.ClientState.Keys;
|
using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Interface.ManagedFontAtlas;
|
|
||||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using ImGuiScene;
|
using ImGuiScene;
|
||||||
|
|
@ -36,7 +31,8 @@ public static class ImGuiHelpers
|
||||||
/// This does not necessarily mean you can call drawing functions.
|
/// This does not necessarily mean you can call drawing functions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static unsafe bool IsImGuiInitialized =>
|
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>
|
/// <summary>
|
||||||
/// Gets the global Dalamud scale; even available before drawing is ready.<br />
|
/// 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>
|
/// <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)
|
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;
|
var font = fontPtr.NativePtr;
|
||||||
font->FontSize = rounder(font->FontSize * scale);
|
font->FontSize = rounder(font->FontSize * scale);
|
||||||
|
|
@ -314,7 +310,6 @@ public static class ImGuiHelpers
|
||||||
glyph->U1,
|
glyph->U1,
|
||||||
glyph->V1,
|
glyph->V1,
|
||||||
glyph->AdvanceX * scale);
|
glyph->AdvanceX * scale);
|
||||||
target.Mark4KPageUsedAfterGlyphAdd((ushort)glyph->Codepoint);
|
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
else if (!missingOnly)
|
else if (!missingOnly)
|
||||||
|
|
@ -348,18 +343,25 @@ public static class ImGuiHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed && rebuildLookupTable)
|
if (changed && rebuildLookupTable)
|
||||||
{
|
target.BuildLookupTableNonstandard();
|
||||||
// ImGui resolves ' ' with FindGlyph, which uses FallbackGlyph.
|
}
|
||||||
// FallbackGlyph is resolved after resolving ' '.
|
|
||||||
// On the first call of BuildLookupTable, called from BuildFonts, FallbackGlyph is set to null,
|
|
||||||
// making FindGlyph return nullptr.
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
target.BuildLookupTable();
|
/// <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 ' '.
|
||||||
|
// On the first call of BuildLookupTable, called from BuildFonts, FallbackGlyph is set to null,
|
||||||
|
// making FindGlyph return nullptr.
|
||||||
|
// 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.
|
||||||
|
font.NativePtr->FallbackGlyph = null;
|
||||||
|
|
||||||
|
font.BuildLookupTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -405,103 +407,6 @@ public static class ImGuiHelpers
|
||||||
public static void CenterCursorFor(float itemWidth) =>
|
public static void CenterCursorFor(float itemWidth) =>
|
||||||
ImGui.SetCursorPosX((int)((ImGui.GetWindowWidth() - itemWidth) / 2));
|
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>
|
/// <summary>
|
||||||
/// Determines whether <paramref name="ptr"/> is empty.
|
/// Determines whether <paramref name="ptr"/> is empty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -510,7 +415,7 @@ public static class ImGuiHelpers
|
||||||
public static unsafe bool IsNull(this ImFontPtr ptr) => ptr.NativePtr == null;
|
public static unsafe bool IsNull(this ImFontPtr ptr) => ptr.NativePtr == null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether <paramref name="ptr"/> is empty.
|
/// Determines whether <paramref name="ptr"/> is not null and loaded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ptr">The pointer.</param>
|
/// <param name="ptr">The pointer.</param>
|
||||||
/// <returns>Whether it is empty.</returns>
|
/// <returns>Whether it is empty.</returns>
|
||||||
|
|
@ -522,27 +427,6 @@ public static class ImGuiHelpers
|
||||||
/// <param name="ptr">The pointer.</param>
|
/// <param name="ptr">The pointer.</param>
|
||||||
/// <returns>Whether it is empty.</returns>
|
/// <returns>Whether it is empty.</returns>
|
||||||
public static unsafe bool IsNull(this ImFontAtlasPtr ptr) => ptr.NativePtr == null;
|
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>
|
/// <summary>
|
||||||
/// Finds the corresponding ImGui viewport ID for the given window handle.
|
/// Finds the corresponding ImGui viewport ID for the given window handle.
|
||||||
|
|
@ -564,89 +448,6 @@ public static class ImGuiHelpers
|
||||||
return -1;
|
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>
|
/// <summary>
|
||||||
/// Get data needed for each new frame.
|
/// Get data needed for each new frame.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue