diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs
index 5359861cf..7930f5c79 100644
--- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs
+++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs
@@ -130,9 +130,9 @@ namespace Dalamud.Configuration.Internal
public float GlobalUiScale { get; set; } = 1.0f;
///
- /// Gets or sets the game font to use for Dalamud UI.
+ /// Gets or sets a value indicating whether to use AXIS fonts from the game.
///
- public GameFont DefaultFontFromGame { get; set; } = GameFont.Undefined;
+ public bool UseAxisFontsFromGame { get; set; } = false;
///
/// Gets or sets a value indicating whether or not plugin UI should be hidden.
diff --git a/Dalamud/Interface/GameFonts/GameFontFamily.cs b/Dalamud/Interface/GameFonts/GameFontFamily.cs
new file mode 100644
index 000000000..2aa836927
--- /dev/null
+++ b/Dalamud/Interface/GameFonts/GameFontFamily.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Dalamud.Interface.GameFonts
+{
+ ///
+ /// Enum of available game font families.
+ ///
+ public enum GameFontFamily
+ {
+ ///
+ /// Placeholder meaning unused.
+ ///
+ Undefined,
+
+ ///
+ /// Sans-serif fonts used for the whole UI. Contains Japanese characters in addition to Latin characters.
+ ///
+ Axis,
+
+ ///
+ /// Serif fonts used for job names. Contains Latin characters.
+ ///
+ Jupiter,
+
+ ///
+ /// Digit-only serif fonts used for flying texts. Contains numbers.
+ ///
+ JupiterNumeric,
+
+ ///
+ /// Digit-only sans-serif horizontally wide fonts used for HP/MP/IL numbers.
+ ///
+ Meidinger,
+
+ ///
+ /// Sans-serif horizontally wide font used for names of gauges. Contains Latin characters.
+ ///
+ MiedingerMid,
+
+ ///
+ /// Sans-serif horizontally narrow font used for addon titles. Contains Latin characters.
+ ///
+ TrumpGothic,
+ }
+}
diff --git a/Dalamud/Interface/GameFonts/GameFont.cs b/Dalamud/Interface/GameFonts/GameFontFamilyAndSize.cs
similarity index 97%
rename from Dalamud/Interface/GameFonts/GameFont.cs
rename to Dalamud/Interface/GameFonts/GameFontFamilyAndSize.cs
index f095b6fcc..1cbdf210f 100644
--- a/Dalamud/Interface/GameFonts/GameFont.cs
+++ b/Dalamud/Interface/GameFonts/GameFontFamilyAndSize.cs
@@ -1,9 +1,9 @@
namespace Dalamud.Interface.GameFonts
{
///
- /// Enum of available game fonts.
+ /// Enum of available game fonts in specific sizes.
///
- public enum GameFont : int
+ public enum GameFontFamilyAndSize : int
{
///
/// Placeholder meaning unused.
diff --git a/Dalamud/Interface/GameFonts/GameFontHandle.cs b/Dalamud/Interface/GameFonts/GameFontHandle.cs
index 6d9274ac0..a50941883 100644
--- a/Dalamud/Interface/GameFonts/GameFontHandle.cs
+++ b/Dalamud/Interface/GameFonts/GameFontHandle.cs
@@ -1,5 +1,5 @@
using System;
-
+using System.Numerics;
using ImGuiNET;
namespace Dalamud.Interface.GameFonts
@@ -10,26 +10,92 @@ namespace Dalamud.Interface.GameFonts
public class GameFontHandle : IDisposable
{
private readonly GameFontManager manager;
- private readonly GameFont font;
+ private readonly GameFontStyle fontStyle;
///
/// Initializes a new instance of the class.
///
/// GameFontManager instance.
/// Font to use.
- internal GameFontHandle(GameFontManager manager, GameFont font)
+ internal GameFontHandle(GameFontManager manager, GameFontStyle font)
{
this.manager = manager;
- this.font = font;
+ this.fontStyle = font;
}
+ ///
+ /// Gets the font style.
+ ///
+ public GameFontStyle Style => this.fontStyle;
+
+ ///
+ /// Gets a value indicating whether this font is ready for use.
+ ///
+ public bool Available => this.manager.GetFont(this.fontStyle) != null;
+
///
/// Gets the font.
///
- /// Corresponding font or null.
- public ImFontPtr? Get() => this.manager.GetFont(this.font);
+ public ImFontPtr ImFont => this.manager.GetFont(this.fontStyle).Value;
+
+ ///
+ /// Gets the FdtReader.
+ ///
+ public FdtReader FdtReader => this.manager.GetFdtReader(this.fontStyle.FamilyAndSize);
+
+ ///
+ /// Creates a new GameFontLayoutPlan.Builder.
+ ///
+ /// Text.
+ /// A new builder for GameFontLayoutPlan.
+ public GameFontLayoutPlan.Builder LayoutBuilder(string text)
+ {
+ return new GameFontLayoutPlan.Builder(this.ImFont, this.FdtReader, text);
+ }
///
- public void Dispose() => this.manager.DecreaseFontRef(this.font);
+ public void Dispose() => this.manager.DecreaseFontRef(this.fontStyle);
+
+ ///
+ /// Draws text.
+ ///
+ /// Text to draw.
+ public void Text(string text)
+ {
+ if (!this.Available)
+ {
+ ImGui.TextUnformatted(text);
+ }
+ else
+ {
+ this.LayoutBuilder(text)
+ .Build()
+ .Draw(ImGui.GetWindowDrawList(), ImGui.GetWindowPos() + ImGui.GetCursorPos(), ImGui.GetColorU32(ImGuiCol.Text));
+ }
+ }
+
+ ///
+ /// Draws text in given color.
+ ///
+ /// Color.
+ /// Text to draw.
+ public void TextColored(Vector4 col, string text)
+ {
+ ImGui.PushStyleColor(ImGuiCol.Text, col);
+ this.Text(text);
+ ImGui.PopStyleColor();
+ }
+
+ ///
+ /// Draws disabled text.
+ ///
+ /// Text to draw.
+ public void TextDisabled(string text)
+ {
+ unsafe
+ {
+ this.TextColored(*ImGui.GetStyleColorVec4(ImGuiCol.TextDisabled), text);
+ }
+ }
}
}
diff --git a/Dalamud/Interface/GameFonts/GameFontLayoutPlan.cs b/Dalamud/Interface/GameFonts/GameFontLayoutPlan.cs
new file mode 100644
index 000000000..dc2d5f380
--- /dev/null
+++ b/Dalamud/Interface/GameFonts/GameFontLayoutPlan.cs
@@ -0,0 +1,414 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+using System.Threading.Tasks;
+
+using ImGuiNET;
+
+namespace Dalamud.Interface.GameFonts
+{
+ ///
+ /// Plan on how glyphs will be rendered.
+ ///
+ public class GameFontLayoutPlan
+ {
+ ///
+ /// Horizontal alignment.
+ ///
+ public enum HorizontalAlignment
+ {
+ ///
+ /// Align to left.
+ ///
+ Left,
+
+ ///
+ /// Align to center.
+ ///
+ Center,
+
+ ///
+ /// Align to right.
+ ///
+ Right,
+ }
+
+ ///
+ /// Gets the associated ImFontPtr.
+ ///
+ public ImFontPtr ImFontPtr { get; internal set; }
+
+ ///
+ /// Gets the size in points of the text.
+ ///
+ public float Size { get; internal set; }
+
+ ///
+ /// Gets the x offset of the leftmost glyph.
+ ///
+ public float X { get; internal set; }
+
+ ///
+ /// Gets the width of the text.
+ ///
+ public float Width { get; internal set; }
+
+ ///
+ /// Gets the height of the text.
+ ///
+ public float Height { get; internal set; }
+
+ ///
+ /// Gets the list of plannen elements.
+ ///
+ public IList Elements { get; internal set; }
+
+ ///
+ /// Draws font to ImGui.
+ ///
+ /// Target ImDrawList.
+ /// Position.
+ /// Color.
+ public void Draw(ImDrawListPtr drawListPtr, Vector2 pos, uint col)
+ {
+ ImGui.Dummy(new Vector2(this.Width, this.Height));
+ foreach (var element in this.Elements)
+ {
+ if (element.IsControl)
+ continue;
+
+ this.ImFontPtr.RenderChar(
+ drawListPtr,
+ this.Size,
+ new Vector2(
+ this.X + pos.X + element.X,
+ pos.Y + element.Y),
+ col,
+ element.Glyph.Char);
+ }
+ }
+
+ ///
+ /// Plan on how each glyph will be rendered.
+ ///
+ public class Element
+ {
+ ///
+ /// Gets the original codepoint.
+ ///
+ public int Codepoint { get; init; }
+
+ ///
+ /// Gets the corresponding or fallback glyph.
+ ///
+ public FdtReader.FontTableEntry Glyph { get; init; }
+
+ ///
+ /// Gets the X offset of this glyph.
+ ///
+ public float X { get; internal set; }
+
+ ///
+ /// Gets the Y offset of this glyph.
+ ///
+ public float Y { get; internal set; }
+
+ ///
+ /// Gets a value indicating whether whether this codepoint is a control character.
+ ///
+ public bool IsControl
+ {
+ get
+ {
+ return this.Codepoint < 0x10000 && char.IsControl((char)this.Codepoint);
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether whether this codepoint is a space.
+ ///
+ public bool IsSpace
+ {
+ get
+ {
+ return this.Codepoint < 0x10000 && char.IsWhiteSpace((char)this.Codepoint);
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether whether this codepoint is a line break character.
+ ///
+ public bool IsLineBreak
+ {
+ get
+ {
+ return this.Codepoint == '\n' || this.Codepoint == '\r';
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether whether this codepoint is a chinese character.
+ ///
+ public bool IsChineseCharacter
+ {
+ get
+ {
+ // CJK Symbols and Punctuation(〇)
+ if (this.Codepoint >= 0x3007 && this.Codepoint <= 0x3007)
+ return true;
+
+ // CJK Unified Ideographs Extension A
+ if (this.Codepoint >= 0x3400 && this.Codepoint <= 0x4DBF)
+ return true;
+
+ // CJK Unified Ideographs
+ if (this.Codepoint >= 0x4E00 && this.Codepoint <= 0x9FFF)
+ return true;
+
+ // CJK Unified Ideographs Extension B
+ if (this.Codepoint >= 0x20000 && this.Codepoint <= 0x2A6DF)
+ return true;
+
+ // CJK Unified Ideographs Extension C
+ if (this.Codepoint >= 0x2A700 && this.Codepoint <= 0x2B73F)
+ return true;
+
+ // CJK Unified Ideographs Extension D
+ if (this.Codepoint >= 0x2B740 && this.Codepoint <= 0x2B81F)
+ return true;
+
+ // CJK Unified Ideographs Extension E
+ if (this.Codepoint >= 0x2B820 && this.Codepoint <= 0x2CEAF)
+ return true;
+
+ // CJK Unified Ideographs Extension F
+ if (this.Codepoint >= 0x2CEB0 && this.Codepoint <= 0x2EBEF)
+ return true;
+
+ return false;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether whether this codepoint is a good position to break word after.
+ ///
+ public bool IsWordBreakPoint
+ {
+ get
+ {
+ if (this.IsChineseCharacter)
+ return true;
+
+ if (this.Codepoint >= 0x10000)
+ return false;
+
+ // TODO: Whatever
+ switch (char.GetUnicodeCategory((char)this.Codepoint))
+ {
+ case System.Globalization.UnicodeCategory.SpaceSeparator:
+ case System.Globalization.UnicodeCategory.LineSeparator:
+ case System.Globalization.UnicodeCategory.ParagraphSeparator:
+ case System.Globalization.UnicodeCategory.Control:
+ case System.Globalization.UnicodeCategory.Format:
+ case System.Globalization.UnicodeCategory.Surrogate:
+ case System.Globalization.UnicodeCategory.PrivateUse:
+ case System.Globalization.UnicodeCategory.ConnectorPunctuation:
+ case System.Globalization.UnicodeCategory.DashPunctuation:
+ case System.Globalization.UnicodeCategory.OpenPunctuation:
+ case System.Globalization.UnicodeCategory.ClosePunctuation:
+ case System.Globalization.UnicodeCategory.InitialQuotePunctuation:
+ case System.Globalization.UnicodeCategory.FinalQuotePunctuation:
+ case System.Globalization.UnicodeCategory.OtherPunctuation:
+ case System.Globalization.UnicodeCategory.MathSymbol:
+ case System.Globalization.UnicodeCategory.ModifierSymbol:
+ case System.Globalization.UnicodeCategory.OtherSymbol:
+ case System.Globalization.UnicodeCategory.OtherNotAssigned:
+ return true;
+ }
+
+ return false;
+ }
+ }
+ }
+
+ ///
+ /// Build a GameFontLayoutPlan.
+ ///
+ public class Builder
+ {
+ private readonly ImFontPtr fontPtr;
+ private readonly FdtReader fdt;
+ private readonly string text;
+ private int maxWidth = int.MaxValue;
+ private float size;
+ private HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Corresponding ImFontPtr.
+ /// FDT file to base on.
+ /// Text.
+ public Builder(ImFontPtr fontPtr, FdtReader fdt, string text)
+ {
+ this.fontPtr = fontPtr;
+ this.fdt = fdt;
+ this.text = text;
+ this.size = fdt.FontHeader.LineHeight;
+ }
+
+ ///
+ /// Sets the size of resulting text.
+ ///
+ /// Size in pixels.
+ /// This.
+ public Builder WithSize(float size)
+ {
+ this.size = size;
+ return this;
+ }
+
+ ///
+ /// Sets the maximum width of the text.
+ ///
+ /// Maximum width in pixels.
+ /// This.
+ public Builder WithMaxWidth(int maxWidth)
+ {
+ this.maxWidth = maxWidth;
+ return this;
+ }
+
+ ///
+ /// Sets the horizontal alignment of the text.
+ ///
+ /// Horizontal alignment.
+ /// This.
+ public Builder WithHorizontalAlignment(HorizontalAlignment horizontalAlignment)
+ {
+ this.horizontalAlignment = horizontalAlignment;
+ return this;
+ }
+
+ ///
+ /// Builds the layout plan.
+ ///
+ /// Newly created layout plan.
+ public GameFontLayoutPlan Build()
+ {
+ var scale = this.size / this.fdt.FontHeader.LineHeight;
+ var unscaledMaxWidth = (float)Math.Ceiling(this.maxWidth / scale);
+ var elements = new List();
+ foreach (var c in this.text)
+ elements.Add(new() { Codepoint = c, Glyph = this.fdt.GetGlyph(c), });
+
+ var lastBreakIndex = 0;
+ List lineBreakIndices = new() { 0 };
+ for (var i = 1; i < elements.Count; i++)
+ {
+ var prev = elements[i - 1];
+ var curr = elements[i];
+
+ if (prev.IsLineBreak)
+ {
+ curr.X = 0;
+ curr.Y = prev.Y + this.fdt.FontHeader.LineHeight;
+ lineBreakIndices.Add(i);
+ }
+ else
+ {
+ curr.X = prev.X + prev.Glyph.NextOffsetX + prev.Glyph.BoundingWidth + this.fdt.GetDistance(prev.Codepoint, curr.Codepoint);
+ curr.Y = prev.Y;
+ }
+
+ if (prev.IsWordBreakPoint)
+ lastBreakIndex = i;
+
+ if (curr.IsSpace)
+ continue;
+
+ if (curr.X + curr.Glyph.BoundingWidth < unscaledMaxWidth)
+ continue;
+
+ if (!prev.IsSpace && elements[lastBreakIndex].X > 0)
+ {
+ prev = elements[lastBreakIndex - 1];
+ curr = elements[lastBreakIndex];
+ i = lastBreakIndex;
+ }
+ else
+ {
+ lastBreakIndex = i;
+ }
+
+ curr.X = 0;
+ curr.Y = prev.Y + this.fdt.FontHeader.LineHeight;
+ lineBreakIndices.Add(i);
+ }
+
+ lineBreakIndices.Add(elements.Count);
+
+ var targetX = 0f;
+ var targetWidth = 0f;
+ var targetHeight = 0f;
+ for (var i = 1; i < lineBreakIndices.Count; i++)
+ {
+ var from = lineBreakIndices[i - 1];
+ var to = lineBreakIndices[i];
+ while (to > from && elements[to - 1].IsSpace)
+ {
+ to--;
+ }
+
+ if (from >= to)
+ continue;
+
+ var right = 0f;
+ for (var j = from; j < to; j++)
+ {
+ var e = elements[j];
+ right = Math.Max(right, e.X + Math.Max(e.Glyph.BoundingWidth, e.Glyph.AdvanceWidth));
+ targetHeight = Math.Max(targetHeight, e.Y + e.Glyph.BoundingHeight);
+ }
+
+ targetWidth = Math.Max(targetWidth, right - elements[from].X);
+ float offsetX;
+ if (this.horizontalAlignment == HorizontalAlignment.Center)
+ offsetX = (unscaledMaxWidth - right) / 2;
+ else if (this.horizontalAlignment == HorizontalAlignment.Right)
+ offsetX = unscaledMaxWidth - right;
+ else if (this.horizontalAlignment == HorizontalAlignment.Left)
+ offsetX = 0;
+ else
+ throw new ArgumentException("Invalid horizontal alignment");
+ for (var j = from; j < to; j++)
+ elements[j].X += offsetX;
+ targetX = i == 1 ? elements[from].X : Math.Min(targetX, elements[from].X);
+ }
+
+ targetHeight = Math.Max(targetHeight, this.fdt.FontHeader.LineHeight * (lineBreakIndices.Count - 1));
+
+ targetWidth *= scale;
+ targetHeight *= scale;
+ targetX *= scale;
+ foreach (var e in elements)
+ {
+ e.X *= scale;
+ e.Y *= scale;
+ }
+
+ return new GameFontLayoutPlan()
+ {
+ ImFontPtr = this.fontPtr,
+ Size = this.size,
+ X = targetX,
+ Width = targetWidth,
+ Height = targetHeight,
+ Elements = elements,
+ };
+ }
+ }
+ }
+}
diff --git a/Dalamud/Interface/GameFonts/GameFontManager.cs b/Dalamud/Interface/GameFonts/GameFontManager.cs
index 9cca00236..44772bc48 100644
--- a/Dalamud/Interface/GameFonts/GameFontManager.cs
+++ b/Dalamud/Interface/GameFonts/GameFontManager.cs
@@ -33,10 +33,9 @@ namespace Dalamud.Interface.GameFonts
private readonly FdtReader?[] fdts;
private readonly List texturePixels;
- private readonly ImFontPtr?[] fonts = new ImFontPtr?[FontNames.Length];
-
- private readonly int[] fontUseCounter = new int[FontNames.Length];
- private readonly List>> glyphRectIds = new();
+ private readonly Dictionary fonts = new();
+ private readonly Dictionary fontUseCounter = new();
+ private readonly Dictionary>> glyphRectIds = new();
///
/// Initializes a new instance of the class.
@@ -60,34 +59,34 @@ namespace Dalamud.Interface.GameFonts
///
/// Font to describe.
/// A string in a form of "FontName (NNNpt)".
- public static string DescribeFont(GameFont font)
+ public static string DescribeFont(GameFontFamilyAndSize font)
{
return font switch
{
- GameFont.Undefined => "-",
- GameFont.Axis96 => "AXIS (9.6pt)",
- GameFont.Axis12 => "AXIS (12pt)",
- GameFont.Axis14 => "AXIS (14pt)",
- GameFont.Axis18 => "AXIS (18pt)",
- GameFont.Axis36 => "AXIS (36pt)",
- GameFont.Jupiter16 => "Jupiter (16pt)",
- GameFont.Jupiter20 => "Jupiter (20pt)",
- GameFont.Jupiter23 => "Jupiter (23pt)",
- GameFont.Jupiter45 => "Jupiter Numeric (45pt)",
- GameFont.Jupiter46 => "Jupiter (46pt)",
- GameFont.Jupiter90 => "Jupiter Numeric (90pt)",
- GameFont.Meidinger16 => "Meidinger Numeric (16pt)",
- GameFont.Meidinger20 => "Meidinger Numeric (20pt)",
- GameFont.Meidinger40 => "Meidinger Numeric (40pt)",
- GameFont.MiedingerMid10 => "MiedingerMid (10pt)",
- GameFont.MiedingerMid12 => "MiedingerMid (12pt)",
- GameFont.MiedingerMid14 => "MiedingerMid (14pt)",
- GameFont.MiedingerMid18 => "MiedingerMid (18pt)",
- GameFont.MiedingerMid36 => "MiedingerMid (36pt)",
- GameFont.TrumpGothic184 => "Trump Gothic (18.4pt)",
- GameFont.TrumpGothic23 => "Trump Gothic (23pt)",
- GameFont.TrumpGothic34 => "Trump Gothic (34pt)",
- GameFont.TrumpGothic68 => "Trump Gothic (68pt)",
+ 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"),
};
}
@@ -97,15 +96,15 @@ namespace Dalamud.Interface.GameFonts
///
/// Font to check.
/// True if it can.
- public static bool IsGenericPurposeFont(GameFont font)
+ public static bool IsGenericPurposeFont(GameFontFamilyAndSize font)
{
return font switch
{
- GameFont.Axis96 => true,
- GameFont.Axis12 => true,
- GameFont.Axis14 => true,
- GameFont.Axis18 => true,
- GameFont.Axis36 => true,
+ GameFontFamilyAndSize.Axis96 => true,
+ GameFontFamilyAndSize.Axis12 => true,
+ GameFontFamilyAndSize.Axis14 => true,
+ GameFontFamilyAndSize.Axis18 => true,
+ GameFontFamilyAndSize.Axis36 => true,
_ => false,
};
}
@@ -174,32 +173,38 @@ namespace Dalamud.Interface.GameFonts
///
/// Creates a new GameFontHandle, and increases internal font reference counter, and if it's first time use, then the font will be loaded on next font building process.
///
- /// Font to use.
+ /// Font to use.
/// Handle to game font that may or may not be ready yet.
- public GameFontHandle NewFontRef(GameFont gameFont)
+ public GameFontHandle NewFontRef(GameFontStyle style)
{
- var fontIndex = (int)gameFont;
var needRebuild = false;
lock (this.syncRoot)
{
- var prev = this.fontUseCounter[fontIndex] == 0;
- this.fontUseCounter[fontIndex] += 1;
- needRebuild = prev != (this.fontUseCounter[fontIndex] == 0);
+ var prevValue = this.fontUseCounter.GetValueOrDefault(style, 0);
+ var newValue = this.fontUseCounter[style] = prevValue + 1;
+ needRebuild = (prevValue == 0) != (newValue == 0) && !this.fonts.ContainsKey(style);
}
if (needRebuild)
this.interfaceManager.RebuildFonts();
- return new(this, gameFont);
+ return new(this, style);
}
///
/// Gets the font.
///
- /// Font to get.
+ /// Font to get.
/// Corresponding font or null.
- public ImFontPtr? GetFont(GameFont gameFont) => this.fonts[(int)gameFont];
+ public ImFontPtr? GetFont(GameFontStyle style) => this.fonts.GetValueOrDefault(style, null);
+
+ ///
+ /// Gets the corresponding FdtReader.
+ ///
+ /// Font to get.
+ /// Corresponding FdtReader or null.
+ public FdtReader? GetFdtReader(GameFontFamilyAndSize family) => this.fdts[(int)family];
///
/// Fills missing glyphs in target font from source font, if both are not null.
@@ -208,9 +213,9 @@ namespace Dalamud.Interface.GameFonts
/// Target font.
/// Whether to copy missing glyphs only.
/// Whether to call target.BuildLookupTable().
- public void CopyGlyphsAcrossFonts(ImFontPtr? source, GameFont target, bool missingOnly, bool rebuildLookupTable)
+ public void CopyGlyphsAcrossFonts(ImFontPtr? source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable)
{
- GameFontManager.CopyGlyphsAcrossFonts(source, this.fonts[(int)target], missingOnly, rebuildLookupTable);
+ GameFontManager.CopyGlyphsAcrossFonts(source, this.fonts[target], missingOnly, rebuildLookupTable);
}
///
@@ -220,9 +225,9 @@ namespace Dalamud.Interface.GameFonts
/// Target font.
/// Whether to copy missing glyphs only.
/// Whether to call target.BuildLookupTable().
- public void CopyGlyphsAcrossFonts(GameFont source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable)
+ public void CopyGlyphsAcrossFonts(GameFontStyle source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable)
{
- GameFontManager.CopyGlyphsAcrossFonts(this.fonts[(int)source], target, missingOnly, rebuildLookupTable);
+ GameFontManager.CopyGlyphsAcrossFonts(this.fonts[source], target, missingOnly, rebuildLookupTable);
}
///
@@ -232,9 +237,9 @@ namespace Dalamud.Interface.GameFonts
/// Target font.
/// Whether to copy missing glyphs only.
/// Whether to call target.BuildLookupTable().
- public void CopyGlyphsAcrossFonts(GameFont source, GameFont target, bool missingOnly, bool rebuildLookupTable)
+ public void CopyGlyphsAcrossFonts(GameFontStyle source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable)
{
- GameFontManager.CopyGlyphsAcrossFonts(this.fonts[(int)source], this.fonts[(int)target], missingOnly, rebuildLookupTable);
+ GameFontManager.CopyGlyphsAcrossFonts(this.fonts[source], this.fonts[target], missingOnly, rebuildLookupTable);
}
///
@@ -242,30 +247,38 @@ namespace Dalamud.Interface.GameFonts
///
public void BuildFonts()
{
- this.glyphRectIds.Clear();
var io = ImGui.GetIO();
io.Fonts.TexDesiredWidth = 4096;
- for (var i = 0; i < FontNames.Length; i++)
- {
- this.fonts[i] = null;
- this.glyphRectIds.Add(new());
+ this.glyphRectIds.Clear();
+ this.fonts.Clear();
- var fdt = this.fdts[i];
- if (this.fontUseCounter[i] == 0 || fdt == null)
+ foreach (var style in this.fontUseCounter.Keys)
+ {
+ var rectIds = this.glyphRectIds[style] = new();
+
+ var fdt = this.fdts[(int)style.FamilyAndSize];
+ if (fdt == null)
continue;
- Log.Information($"GameFontManager BuildFont: {FontNames[i]}");
-
var font = io.Fonts.AddFontDefault();
- this.fonts[i] = font;
+ this.fonts[style] = font;
foreach (var glyph in fdt.Glyphs)
{
var c = glyph.Char;
if (c < 32 || c >= 0xFFFF)
continue;
- this.glyphRectIds[i][c] = Tuple.Create(io.Fonts.AddCustomRectFontGlyph(font, c, glyph.BoundingWidth + 1, glyph.BoundingHeight + 1, glyph.BoundingWidth + glyph.NextOffsetX, new Vector2(0, glyph.CurrentOffsetY)), glyph);
+ var widthAdjustment = style.CalculateWidthAdjustment(fdt, glyph);
+ rectIds[c] = Tuple.Create(
+ io.Fonts.AddCustomRectFontGlyph(
+ font,
+ c,
+ glyph.BoundingWidth + widthAdjustment + 1,
+ glyph.BoundingHeight + 1,
+ glyph.AdvanceWidth,
+ new Vector2(0, glyph.CurrentOffsetY)),
+ glyph);
}
}
}
@@ -279,13 +292,9 @@ namespace Dalamud.Interface.GameFonts
io.Fonts.GetTexDataAsRGBA32(out byte* pixels8, out var width, out var height);
var pixels32 = (uint*)pixels8;
- for (var i = 0; i < this.fonts.Length; i++)
+ foreach (var (style, font) in this.fonts)
{
- if (!this.fonts[i].HasValue)
- continue;
-
- var font = this.fonts[i]!.Value;
- var fdt = this.fdts[i];
+ var fdt = this.fdts[(int)style.FamilyAndSize];
var fontPtr = font.NativePtr;
fontPtr->ConfigData->SizePixels = fontPtr->FontSize = fdt.FontHeader.LineHeight;
fontPtr->Ascent = fdt.FontHeader.Ascent;
@@ -301,75 +310,83 @@ namespace Dalamud.Interface.GameFonts
}
}
- fixed (char* c = FontNames[i])
+ fixed (char* c = FontNames[(int)style.FamilyAndSize])
{
for (var j = 0; j < 40; j++)
fontPtr->ConfigData->Name[j] = 0;
- Encoding.UTF8.GetBytes(c, FontNames[i].Length, fontPtr->ConfigData->Name, 40);
+ Encoding.UTF8.GetBytes(c, FontNames[(int)style.FamilyAndSize].Length, fontPtr->ConfigData->Name, 40);
}
- foreach (var (c, (rectId, glyph)) in this.glyphRectIds[i])
+ foreach (var (c, (rectId, glyph)) in this.glyphRectIds[style])
{
var rc = io.Fonts.GetCustomRectByIndex(rectId);
var sourceBuffer = this.texturePixels[glyph.TextureFileIndex];
var sourceBufferDelta = glyph.TextureChannelByteIndex;
- for (var y = 0; y < glyph.BoundingHeight; y++)
+ var widthAdjustment = style.CalculateWidthAdjustment(fdt, glyph);
+ if (widthAdjustment == 0)
{
- for (var x = 0; x < glyph.BoundingWidth; x++)
+ for (var y = 0; y < glyph.BoundingHeight; y++)
{
- 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;
+ 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.SkewStrength > 0)
+ xDelta += style.SkewStrength * (fdt.FontHeader.LineHeight - glyph.CurrentOffsetY - y) / fdt.FontHeader.LineHeight;
+ else if (style.SkewStrength < 0)
+ xDelta -= style.SkewStrength * (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));
+ }
+ }
}
}
}
}
- this.CopyGlyphsAcrossFonts(InterfaceManager.DefaultFont, GameFont.Axis96, true, false);
- this.CopyGlyphsAcrossFonts(InterfaceManager.DefaultFont, GameFont.Axis12, true, false);
- this.CopyGlyphsAcrossFonts(InterfaceManager.DefaultFont, GameFont.Axis14, true, false);
- this.CopyGlyphsAcrossFonts(InterfaceManager.DefaultFont, GameFont.Axis18, true, false);
- this.CopyGlyphsAcrossFonts(InterfaceManager.DefaultFont, GameFont.Axis36, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis18, GameFont.Jupiter16, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis36, GameFont.Jupiter20, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis36, GameFont.Jupiter23, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis36, GameFont.Jupiter45, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis36, GameFont.Jupiter46, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis36, GameFont.Jupiter90, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis18, GameFont.Meidinger16, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis36, GameFont.Meidinger20, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis36, GameFont.Meidinger40, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis96, GameFont.MiedingerMid10, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis12, GameFont.MiedingerMid12, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis14, GameFont.MiedingerMid14, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis18, GameFont.MiedingerMid18, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis36, GameFont.MiedingerMid36, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis18, GameFont.TrumpGothic184, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis36, GameFont.TrumpGothic23, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis36, GameFont.TrumpGothic34, true, false);
- this.CopyGlyphsAcrossFonts(GameFont.Axis36, GameFont.TrumpGothic68, true, false);
-
- foreach (var font in this.fonts)
- font?.BuildLookupTable();
+ foreach (var font in this.fonts.Values)
+ {
+ CopyGlyphsAcrossFonts(InterfaceManager.DefaultFont, font, true, false);
+ font.BuildLookupTable();
+ }
}
///
- /// Decrease font reference counter and release if nobody is using it.
+ /// Decrease font reference counter.
///
- /// Font to release.
- internal void DecreaseFontRef(GameFont gameFont)
+ /// Font to release.
+ internal void DecreaseFontRef(GameFontStyle style)
{
- var fontIndex = (int)gameFont;
- var needRebuild = false;
-
lock (this.syncRoot)
{
- var prev = this.fontUseCounter[fontIndex] == 0;
- this.fontUseCounter[fontIndex] -= 1;
- needRebuild = prev != (this.fontUseCounter[fontIndex] == 0);
+ if ((this.fontUseCounter[style] -= 1) == 0)
+ this.fontUseCounter.Remove(style);
}
-
- if (needRebuild)
- this.interfaceManager.RebuildFonts();
}
private struct ImFontGlyphReal
diff --git a/Dalamud/Interface/GameFonts/GameFontStyle.cs b/Dalamud/Interface/GameFonts/GameFontStyle.cs
new file mode 100644
index 000000000..8a713f1cf
--- /dev/null
+++ b/Dalamud/Interface/GameFonts/GameFontStyle.cs
@@ -0,0 +1,235 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Dalamud.Interface.GameFonts
+{
+ ///
+ /// Describes a font based on game resource file.
+ ///
+ public struct GameFontStyle
+ {
+ ///
+ /// Font family of the font.
+ ///
+ public GameFontFamilyAndSize FamilyAndSize;
+
+ ///
+ /// Weight of the font.
+ ///
+ /// 0 is unaltered.
+ /// Any value greater than 0 will make it bolder.
+ ///
+ public float Weight;
+
+ ///
+ /// Skewedness of the font.
+ ///
+ /// 0 is unaltered.
+ /// Greater than 1 will make upper part go rightwards.
+ /// Less than 1 will make lower part go rightwards.
+ ///
+ public float SkewStrength;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Font family.
+ /// Size in points.
+ public GameFontStyle(GameFontFamily family, float size)
+ {
+ this.FamilyAndSize = GetRecommendedFamilyAndSize(family, size);
+ this.Weight = this.SkewStrength = 0f;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Font family and size.
+ public GameFontStyle(GameFontFamilyAndSize familyAndSize)
+ {
+ this.FamilyAndSize = familyAndSize;
+ this.Weight = this.SkewStrength = 0f;
+ }
+
+ ///
+ /// Gets the font family.
+ ///
+ public GameFontFamily Family => this.FamilyAndSize switch
+ {
+ GameFontFamilyAndSize.Undefined => GameFontFamily.Undefined,
+ GameFontFamilyAndSize.Axis96 => GameFontFamily.Axis,
+ GameFontFamilyAndSize.Axis12 => GameFontFamily.Axis,
+ GameFontFamilyAndSize.Axis14 => GameFontFamily.Axis,
+ GameFontFamilyAndSize.Axis18 => GameFontFamily.Axis,
+ GameFontFamilyAndSize.Axis36 => GameFontFamily.Axis,
+ GameFontFamilyAndSize.Jupiter16 => GameFontFamily.Jupiter,
+ GameFontFamilyAndSize.Jupiter20 => GameFontFamily.Jupiter,
+ GameFontFamilyAndSize.Jupiter23 => GameFontFamily.Jupiter,
+ GameFontFamilyAndSize.Jupiter45 => GameFontFamily.JupiterNumeric,
+ GameFontFamilyAndSize.Jupiter46 => GameFontFamily.Jupiter,
+ GameFontFamilyAndSize.Jupiter90 => GameFontFamily.JupiterNumeric,
+ GameFontFamilyAndSize.Meidinger16 => GameFontFamily.Meidinger,
+ GameFontFamilyAndSize.Meidinger20 => GameFontFamily.Meidinger,
+ GameFontFamilyAndSize.Meidinger40 => GameFontFamily.Meidinger,
+ GameFontFamilyAndSize.MiedingerMid10 => GameFontFamily.MiedingerMid,
+ GameFontFamilyAndSize.MiedingerMid12 => GameFontFamily.MiedingerMid,
+ GameFontFamilyAndSize.MiedingerMid14 => GameFontFamily.MiedingerMid,
+ GameFontFamilyAndSize.MiedingerMid18 => GameFontFamily.MiedingerMid,
+ GameFontFamilyAndSize.MiedingerMid36 => GameFontFamily.MiedingerMid,
+ GameFontFamilyAndSize.TrumpGothic184 => GameFontFamily.TrumpGothic,
+ GameFontFamilyAndSize.TrumpGothic23 => GameFontFamily.TrumpGothic,
+ GameFontFamilyAndSize.TrumpGothic34 => GameFontFamily.TrumpGothic,
+ GameFontFamilyAndSize.TrumpGothic68 => GameFontFamily.TrumpGothic,
+ _ => throw new InvalidOperationException(),
+ };
+
+ ///
+ /// Gets the font size.
+ ///
+ public float Size => this.FamilyAndSize switch
+ {
+ GameFontFamilyAndSize.Undefined => 0,
+ GameFontFamilyAndSize.Axis96 => 9.6f,
+ GameFontFamilyAndSize.Axis12 => 12,
+ GameFontFamilyAndSize.Axis14 => 14,
+ GameFontFamilyAndSize.Axis18 => 18,
+ GameFontFamilyAndSize.Axis36 => 36,
+ GameFontFamilyAndSize.Jupiter16 => 16,
+ GameFontFamilyAndSize.Jupiter20 => 20,
+ GameFontFamilyAndSize.Jupiter23 => 23,
+ GameFontFamilyAndSize.Jupiter45 => 45,
+ GameFontFamilyAndSize.Jupiter46 => 46,
+ GameFontFamilyAndSize.Jupiter90 => 90,
+ GameFontFamilyAndSize.Meidinger16 => 16,
+ GameFontFamilyAndSize.Meidinger20 => 20,
+ GameFontFamilyAndSize.Meidinger40 => 40,
+ GameFontFamilyAndSize.MiedingerMid10 => 10,
+ GameFontFamilyAndSize.MiedingerMid12 => 12,
+ GameFontFamilyAndSize.MiedingerMid14 => 14,
+ GameFontFamilyAndSize.MiedingerMid18 => 18,
+ GameFontFamilyAndSize.MiedingerMid36 => 36,
+ GameFontFamilyAndSize.TrumpGothic184 => 18.4f,
+ GameFontFamilyAndSize.TrumpGothic23 => 23,
+ GameFontFamilyAndSize.TrumpGothic34 => 34,
+ GameFontFamilyAndSize.TrumpGothic68 => 8,
+ _ => throw new InvalidOperationException(),
+ };
+
+ ///
+ /// Gets or sets a value indicating whether this font is bold.
+ ///
+ public bool Bold
+ {
+ get => this.Weight > 0f;
+ set => this.Weight = value ? 1f : 0f;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether this font is italic.
+ ///
+ public bool Italic
+ {
+ get => this.SkewStrength != 0;
+ set => this.SkewStrength = value ? 4 : 0;
+ }
+
+ ///
+ /// Gets the recommend GameFontFamilyAndSize given family and size.
+ ///
+ /// Font family.
+ /// Font size in points.
+ /// Recommended GameFontFamilyAndSize.
+ public static GameFontFamilyAndSize GetRecommendedFamilyAndSize(GameFontFamily family, float size)
+ {
+ if (size <= 0)
+ return GameFontFamilyAndSize.Undefined;
+
+ switch (family)
+ {
+ case GameFontFamily.Undefined:
+ return GameFontFamilyAndSize.Undefined;
+
+ case GameFontFamily.Axis:
+ if (size <= 9.6)
+ return GameFontFamilyAndSize.Axis96;
+ else if (size <= 12)
+ return GameFontFamilyAndSize.Axis12;
+ else if (size <= 14)
+ return GameFontFamilyAndSize.Axis14;
+ else if (size <= 18)
+ return GameFontFamilyAndSize.Axis18;
+ else
+ return GameFontFamilyAndSize.Axis36;
+
+ case GameFontFamily.Jupiter:
+ if (size <= 16)
+ return GameFontFamilyAndSize.Jupiter16;
+ else if (size <= 20)
+ return GameFontFamilyAndSize.Jupiter20;
+ else if (size <= 23)
+ return GameFontFamilyAndSize.Jupiter23;
+ else
+ return GameFontFamilyAndSize.Jupiter46;
+
+ case GameFontFamily.JupiterNumeric:
+ if (size <= 45)
+ return GameFontFamilyAndSize.Jupiter45;
+ else
+ return GameFontFamilyAndSize.Jupiter90;
+
+ case GameFontFamily.Meidinger:
+ if (size <= 16)
+ return GameFontFamilyAndSize.Meidinger16;
+ else if (size <= 20)
+ return GameFontFamilyAndSize.Meidinger20;
+ else
+ return GameFontFamilyAndSize.Meidinger40;
+
+ case GameFontFamily.MiedingerMid:
+ if (size <= 10)
+ return GameFontFamilyAndSize.MiedingerMid10;
+ else if (size <= 12)
+ return GameFontFamilyAndSize.MiedingerMid12;
+ else if (size <= 14)
+ return GameFontFamilyAndSize.MiedingerMid14;
+ else if (size <= 18)
+ return GameFontFamilyAndSize.MiedingerMid18;
+ else
+ return GameFontFamilyAndSize.MiedingerMid36;
+
+ case GameFontFamily.TrumpGothic:
+ if (size <= 18.4)
+ return GameFontFamilyAndSize.TrumpGothic184;
+ else if (size <= 23)
+ return GameFontFamilyAndSize.TrumpGothic23;
+ else if (size <= 34)
+ return GameFontFamilyAndSize.TrumpGothic34;
+ else
+ return GameFontFamilyAndSize.TrumpGothic68;
+
+ default:
+ return GameFontFamilyAndSize.Undefined;
+ }
+ }
+
+ ///
+ /// Calculates the adjustment to width resulting fron Weight and SkewStrength.
+ ///
+ /// Font information.
+ /// Glyph.
+ /// Width adjustment in pixel unit.
+ public int CalculateWidthAdjustment(FdtReader reader, FdtReader.FontTableEntry glyph)
+ {
+ var widthDelta = this.Weight;
+ if (this.SkewStrength > 0)
+ widthDelta += 1f * this.SkewStrength * (reader.FontHeader.LineHeight - glyph.CurrentOffsetY) / reader.FontHeader.LineHeight;
+ else if (this.SkewStrength < 0)
+ widthDelta -= 1f * this.SkewStrength * (glyph.CurrentOffsetY + glyph.BoundingHeight) / reader.FontHeader.LineHeight;
+
+ return (int)Math.Ceiling(widthDelta);
+ }
+ }
+}
diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs
index 1e9654dc0..58f18bf70 100644
--- a/Dalamud/Interface/Internal/InterfaceManager.cs
+++ b/Dalamud/Interface/Internal/InterfaceManager.cs
@@ -57,8 +57,7 @@ namespace Dalamud.Interface.Internal
private readonly SwapChainVtableResolver address;
private RawDX11Scene? scene;
- private GameFont overwriteDefaultFontFromGameFont = GameFont.Undefined;
- private GameFontHandle? overwriteDefaultFontFromGameFontHandle;
+ private GameFontHandle? axisFontHandle;
// can't access imgui IO before first present call
private bool lastWantCapture = false;
@@ -313,16 +312,7 @@ namespace Dalamud.Interface.Internal
if (!this.isRebuildingFonts)
{
Log.Verbose("[FONT] RebuildFonts() trigger");
- var configuration = Service.Get();
- if (this.overwriteDefaultFontFromGameFont != configuration.DefaultFontFromGame)
- {
- this.overwriteDefaultFontFromGameFont = configuration.DefaultFontFromGame;
- this.overwriteDefaultFontFromGameFontHandle?.Dispose();
- if (configuration.DefaultFontFromGame == GameFont.Undefined)
- this.overwriteDefaultFontFromGameFontHandle = null;
- else
- this.overwriteDefaultFontFromGameFontHandle = Service.Get().NewFontRef(configuration.DefaultFontFromGame);
- }
+ this.SetAxisFonts();
this.isRebuildingFonts = true;
this.scene.OnNewRenderFrame += this.RebuildFontsInternal;
@@ -342,6 +332,26 @@ namespace Dalamud.Interface.Internal
Util.Fatal($"One or more files required by XIVLauncher were not found.\nPlease restart and report this error if it occurs again.\n\n{path}", "Error");
}
+ private void SetAxisFonts()
+ {
+ var configuration = Service.Get();
+ if (configuration.UseAxisFontsFromGame)
+ {
+ var currentFamilyAndSize = GameFontStyle.GetRecommendedFamilyAndSize(GameFontFamily.Axis, this.axisFontHandle?.Style.Size ?? 0f);
+ var expectedFamilyAndSize = GameFontStyle.GetRecommendedFamilyAndSize(GameFontFamily.Axis, 12 * ImGui.GetIO().FontGlobalScale);
+ if (currentFamilyAndSize == expectedFamilyAndSize)
+ return;
+
+ this.axisFontHandle?.Dispose();
+ this.axisFontHandle = Service.Get().NewFontRef(new(expectedFamilyAndSize));
+ }
+ else
+ {
+ this.axisFontHandle?.Dispose();
+ this.axisFontHandle = null;
+ }
+ }
+
/*
* NOTE(goat): When hooking ReShade DXGISwapChain::runtime_present, this is missing the syncInterval arg.
* Seems to work fine regardless, I guess, so whatever.
@@ -403,15 +413,7 @@ namespace Dalamud.Interface.Internal
this.scene.OnBuildUI += this.Display;
this.scene.OnNewInputFrame += this.OnNewInputFrame;
- if (this.overwriteDefaultFontFromGameFont != configuration.DefaultFontFromGame)
- {
- this.overwriteDefaultFontFromGameFont = configuration.DefaultFontFromGame;
- this.overwriteDefaultFontFromGameFontHandle?.Dispose();
- if (configuration.DefaultFontFromGame == GameFont.Undefined)
- this.overwriteDefaultFontFromGameFontHandle = null;
- else
- this.overwriteDefaultFontFromGameFontHandle = Service.Get().NewFontRef(configuration.DefaultFontFromGame);
- }
+ this.SetAxisFonts();
this.SetupFonts();
@@ -591,7 +593,7 @@ namespace Dalamud.Interface.Internal
ImGui.GetIO().Fonts.Build();
gameFontManager.AfterBuildFonts();
- GameFontManager.CopyGlyphsAcrossFonts(this.overwriteDefaultFontFromGameFontHandle?.Get(), DefaultFont, false, true);
+ GameFontManager.CopyGlyphsAcrossFonts(this.axisFontHandle?.ImFont, DefaultFont, false, true);
Log.Verbose("[FONT] Invoke OnAfterBuildFonts");
this.AfterBuildFonts?.Invoke();
diff --git a/Dalamud/Interface/Internal/Windows/SettingsWindow.cs b/Dalamud/Interface/Internal/Windows/SettingsWindow.cs
index 6fa6df808..ac01392a7 100644
--- a/Dalamud/Interface/Internal/Windows/SettingsWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/SettingsWindow.cs
@@ -28,8 +28,6 @@ namespace Dalamud.Interface.Internal.Windows
private const float MinScale = 0.3f;
private const float MaxScale = 2.0f;
- private readonly List validFontChoices;
- private readonly string[] validFontNames;
private readonly string[] languages;
private readonly string[] locLanguages;
private int langIndex;
@@ -40,6 +38,7 @@ namespace Dalamud.Interface.Internal.Windows
private bool doCfChatMessage;
private float globalUiScale;
+ private bool doUseAxisFontsFromGame;
private bool doToggleUiHide;
private bool doToggleUiHideDuringCutscenes;
private bool doToggleUiHideDuringGpose;
@@ -69,8 +68,6 @@ namespace Dalamud.Interface.Internal.Windows
private bool doButtonsSystemMenu;
private bool disableRmtFiltering;
- private int validFontIndex;
-
#region Experimental
private bool doPluginTest;
@@ -94,6 +91,7 @@ namespace Dalamud.Interface.Internal.Windows
this.doCfChatMessage = configuration.DutyFinderChatMessage;
this.globalUiScale = configuration.GlobalUiScale;
+ this.doUseAxisFontsFromGame = configuration.UseAxisFontsFromGame;
this.doToggleUiHide = configuration.ToggleUiHide;
this.doToggleUiHideDuringCutscenes = configuration.ToggleUiHideDuringCutscenes;
this.doToggleUiHideDuringGpose = configuration.ToggleUiHideDuringGpose;
@@ -116,10 +114,6 @@ namespace Dalamud.Interface.Internal.Windows
this.doButtonsSystemMenu = configuration.DoButtonsSystemMenu;
this.disableRmtFiltering = configuration.DisableRmtFiltering;
- this.validFontChoices = Enum.GetValues().Where(x => x == GameFont.Undefined || GameFontManager.IsGenericPurposeFont(x)).ToList();
- this.validFontNames = this.validFontChoices.Select(x => GameFontManager.DescribeFont(x)).ToArray();
- this.validFontIndex = Math.Max(0, this.validFontChoices.IndexOf(configuration.DefaultFontFromGame));
-
this.languages = Localization.ApplicableLangCodes.Prepend("en").ToArray();
try
{
@@ -286,20 +280,19 @@ namespace Dalamud.Interface.Internal.Windows
{
this.globalUiScale = 1.0f;
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
+ Service.Get().RebuildFonts();
}
if (ImGui.DragFloat("##DalamudSettingsGlobalUiScaleDrag", ref this.globalUiScale, 0.005f, MinScale, MaxScale, "%.2f"))
+ {
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
+ Service.Get().RebuildFonts();
+ }
ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsGlobalUiScaleHint", "Scale all XIVLauncher UI elements - useful for 4K displays."));
ImGuiHelpers.ScaledDummy(10, 16);
- ImGui.Text(Loc.Localize("DalamudSettingsGlobalFont", "Global Font"));
- ImGui.Combo("##DalamudSettingsGlobalFontDrag", ref this.validFontIndex, this.validFontNames, this.validFontNames.Length);
-
- ImGuiHelpers.ScaledDummy(10, 16);
-
if (ImGui.Button(Loc.Localize("DalamudSettingsOpenStyleEditor", "Open Style Editor")))
{
Service.Get().OpenStyleEditor();
@@ -311,6 +304,9 @@ namespace Dalamud.Interface.Internal.Windows
ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideOptOutNote", "Plugins may independently opt out of the settings below."));
+ ImGui.Checkbox(Loc.Localize("DalamudSettingToggleAxisFonts", "Use AXIS fonts as default Dalamud font"), ref this.doUseAxisFontsFromGame);
+ ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiAxisFontsHint", "Use AXIS fonts (the game's main UI fonts) as default Dalamud font."));
+
ImGui.Checkbox(Loc.Localize("DalamudSettingToggleUiHide", "Hide plugin UI when the game UI is toggled off"), ref this.doToggleUiHide);
ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideHint", "Hide any open windows by plugins when toggling the game overlay."));
@@ -814,7 +810,7 @@ namespace Dalamud.Interface.Internal.Windows
configuration.IsFocusManagementEnabled = this.doFocus;
configuration.ShowTsm = this.doTsm;
- configuration.DefaultFontFromGame = this.validFontChoices[this.validFontIndex];
+ configuration.UseAxisFontsFromGame = this.doUseAxisFontsFromGame;
// This is applied every frame in InterfaceManager::CheckViewportState()
configuration.IsDisableViewport = !this.doViewport;
@@ -858,9 +854,8 @@ namespace Dalamud.Interface.Internal.Windows
configuration.Save();
- Service.Get().RebuildFonts();
-
_ = Service.Get().ReloadPluginMastersAsync();
+ Service.Get().RebuildFonts();
}
}
}
diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs
index c57a130b4..b4c089fbe 100644
--- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
@@ -8,6 +8,7 @@ using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.Gui;
using Dalamud.Interface.Animation.EasingFunctions;
+using Dalamud.Interface.GameFonts;
using Dalamud.Interface.Windowing;
using ImGuiNET;
using ImGuiScene;
@@ -19,6 +20,7 @@ namespace Dalamud.Interface.Internal.Windows
///
internal class TitleScreenMenuWindow : Window, IDisposable
{
+ private const float TargetFontSize = 16.2f;
private readonly TextureWrap shadeTexture;
private readonly Dictionary shadeEasings = new();
@@ -27,6 +29,8 @@ namespace Dalamud.Interface.Internal.Windows
private InOutCubic? fadeOutEasing;
+ private GameFontHandle? axisFontHandle;
+
private State state = State.Hide;
///
@@ -67,14 +71,19 @@ namespace Dalamud.Interface.Internal.Windows
///
public override void PreDraw()
{
+ this.SetAxisFonts();
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, 0));
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0));
+ if (this.axisFontHandle?.Available ?? false)
+ ImGui.PushFont(this.axisFontHandle.ImFont);
base.PreDraw();
}
///
public override void PostDraw()
{
+ if (this.axisFontHandle?.Available ?? false)
+ ImGui.PopFont();
ImGui.PopStyleVar(2);
base.PostDraw();
}
@@ -90,128 +99,143 @@ namespace Dalamud.Interface.Internal.Windows
///
public override void Draw()
{
- ImGui.SetWindowFontScale(1.3f);
+ ImGui.SetWindowFontScale(TargetFontSize / ImGui.GetFont().FontSize * 4 / 3);
var tsm = Service.Get();
switch (this.state)
{
case State.Show:
- {
- for (var i = 0; i < tsm.Entries.Count; i++)
{
- var entry = tsm.Entries[i];
-
- if (!this.moveEasings.TryGetValue(entry.Id, out var moveEasing))
+ for (var i = 0; i < tsm.Entries.Count; i++)
{
- moveEasing = new InOutQuint(TimeSpan.FromMilliseconds(400));
- this.moveEasings.Add(entry.Id, moveEasing);
+ var entry = tsm.Entries[i];
+
+ if (!this.moveEasings.TryGetValue(entry.Id, out var moveEasing))
+ {
+ moveEasing = new InOutQuint(TimeSpan.FromMilliseconds(400));
+ this.moveEasings.Add(entry.Id, moveEasing);
+ }
+
+ if (!moveEasing.IsRunning && !moveEasing.IsDone)
+ {
+ moveEasing.Restart();
+ }
+
+ if (moveEasing.IsDone)
+ {
+ moveEasing.Stop();
+ }
+
+ moveEasing.Update();
+
+ var finalPos = (i + 1) * this.shadeTexture.Height;
+ var pos = moveEasing.Value * finalPos;
+
+ // FIXME(goat): Sometimes, easings can overshoot and bring things out of alignment.
+ if (moveEasing.IsDone)
+ {
+ pos = finalPos;
+ }
+
+ this.DrawEntry(entry, moveEasing.IsRunning && i != 0, true, i == 0, true);
+
+ var cursor = ImGui.GetCursorPos();
+ cursor.Y = (float)pos;
+ ImGui.SetCursorPos(cursor);
}
- if (!moveEasing.IsRunning && !moveEasing.IsDone)
+ if (!ImGui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows |
+ ImGuiHoveredFlags.AllowWhenOverlapped |
+ ImGuiHoveredFlags.AllowWhenBlockedByActiveItem))
{
- moveEasing.Restart();
+ this.state = State.FadeOut;
}
- if (moveEasing.IsDone)
- {
- moveEasing.Stop();
- }
-
- moveEasing.Update();
-
- var finalPos = (i + 1) * this.shadeTexture.Height;
- var pos = moveEasing.Value * finalPos;
-
- // FIXME(goat): Sometimes, easings can overshoot and bring things out of alignment.
- if (moveEasing.IsDone)
- {
- pos = finalPos;
- }
-
- this.DrawEntry(entry, moveEasing.IsRunning && i != 0, true, i == 0, true);
-
- var cursor = ImGui.GetCursorPos();
- cursor.Y = (float)pos;
- ImGui.SetCursorPos(cursor);
+ break;
}
- if (!ImGui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows |
- ImGuiHoveredFlags.AllowWhenOverlapped |
- ImGuiHoveredFlags.AllowWhenBlockedByActiveItem))
- {
- this.state = State.FadeOut;
- }
-
- break;
- }
-
case State.FadeOut:
- {
- this.fadeOutEasing ??= new InOutCubic(TimeSpan.FromMilliseconds(400))
{
- IsInverse = true,
- };
+ this.fadeOutEasing ??= new InOutCubic(TimeSpan.FromMilliseconds(400))
+ {
+ IsInverse = true,
+ };
- if (!this.fadeOutEasing.IsRunning && !this.fadeOutEasing.IsDone)
- {
- this.fadeOutEasing.Restart();
+ if (!this.fadeOutEasing.IsRunning && !this.fadeOutEasing.IsDone)
+ {
+ this.fadeOutEasing.Restart();
+ }
+
+ if (this.fadeOutEasing.IsDone)
+ {
+ this.fadeOutEasing.Stop();
+ }
+
+ this.fadeOutEasing.Update();
+
+ ImGui.PushStyleVar(ImGuiStyleVar.Alpha, (float)this.fadeOutEasing.Value);
+
+ for (var i = 0; i < tsm.Entries.Count; i++)
+ {
+ var entry = tsm.Entries[i];
+
+ var finalPos = (i + 1) * this.shadeTexture.Height;
+
+ this.DrawEntry(entry, i != 0, true, i == 0, false);
+
+ var cursor = ImGui.GetCursorPos();
+ cursor.Y = finalPos;
+ ImGui.SetCursorPos(cursor);
+ }
+
+ ImGui.PopStyleVar();
+
+ var isHover = ImGui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows |
+ ImGuiHoveredFlags.AllowWhenOverlapped |
+ ImGuiHoveredFlags.AllowWhenBlockedByActiveItem);
+
+ if (!isHover && this.fadeOutEasing!.IsDone)
+ {
+ this.state = State.Hide;
+ this.fadeOutEasing = null;
+ }
+ else if (isHover)
+ {
+ this.state = State.Show;
+ this.fadeOutEasing = null;
+ }
+
+ break;
}
- if (this.fadeOutEasing.IsDone)
- {
- this.fadeOutEasing.Stop();
- }
-
- this.fadeOutEasing.Update();
-
- ImGui.PushStyleVar(ImGuiStyleVar.Alpha, (float)this.fadeOutEasing.Value);
-
- for (var i = 0; i < tsm.Entries.Count; i++)
- {
- var entry = tsm.Entries[i];
-
- var finalPos = (i + 1) * this.shadeTexture.Height;
-
- this.DrawEntry(entry, i != 0, true, i == 0, false);
-
- var cursor = ImGui.GetCursorPos();
- cursor.Y = finalPos;
- ImGui.SetCursorPos(cursor);
- }
-
- ImGui.PopStyleVar();
-
- var isHover = ImGui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows |
- ImGuiHoveredFlags.AllowWhenOverlapped |
- ImGuiHoveredFlags.AllowWhenBlockedByActiveItem);
-
- if (!isHover && this.fadeOutEasing!.IsDone)
- {
- this.state = State.Hide;
- this.fadeOutEasing = null;
- }
- else if (isHover)
- {
- this.state = State.Show;
- this.fadeOutEasing = null;
- }
-
- break;
- }
-
case State.Hide:
- {
- if (this.DrawEntry(tsm.Entries[0], true, false, true, true))
{
- this.state = State.Show;
- }
+ if (this.DrawEntry(tsm.Entries[0], true, false, true, true))
+ {
+ this.state = State.Show;
+ }
- this.moveEasings.Clear();
- this.logoEasings.Clear();
- this.shadeEasings.Clear();
- break;
- }
+ this.moveEasings.Clear();
+ this.logoEasings.Clear();
+ this.shadeEasings.Clear();
+ break;
+ }
+ }
+ }
+
+ private void SetAxisFonts()
+ {
+ var configuration = Service.Get();
+ if (configuration.UseAxisFontsFromGame)
+ {
+ if (this.axisFontHandle == null)
+ this.axisFontHandle = Service.Get().NewFontRef(new(GameFontFamily.Axis, TargetFontSize));
+ }
+ else
+ {
+ this.axisFontHandle?.Dispose();
+ this.axisFontHandle = null;
}
}
diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs
index 35c82102b..e3f85426c 100644
--- a/Dalamud/Interface/UiBuilder.cs
+++ b/Dalamud/Interface/UiBuilder.cs
@@ -215,9 +215,9 @@ namespace Dalamud.Interface
///
/// Gets a game font.
///
- /// Font to get.
+ /// Font to get.
/// Handle to the game font which may or may not be available for use yet.
- public GameFontHandle GetGameFontHandle(GameFont gameFont) => Service.Get().NewFontRef(gameFont);
+ public GameFontHandle GetGameFontHandle(GameFontStyle style) => Service.Get().NewFontRef(style);
///
/// Call this to queue a rebuild of the font atlas.