From 2d8b71c647e1b9ccea9f8746146fd8eaafee2791 Mon Sep 17 00:00:00 2001 From: srkizer Date: Sun, 18 Feb 2024 23:08:07 +0900 Subject: [PATCH] Add SetFontScaleMode(ImFontPtr, FontScaleMode) (#1666) * Add SetFontScaleMode(ImFontPtr, FontScaleMode) `IgnoreGlobalScale` was advertised as "excludes the given font from global scaling", but the intent I had in mind was "excludes the given font from being scaled in any manner". As the latter functionality is needed, obsoleted `IgnoreGlobalScale` and added `SetFontScaleMode`. * Make it correct * Name consistency --- Dalamud/Interface/FontIdentifier/IFontSpec.cs | 2 + .../FontIdentifier/SingleFontSpec.cs | 13 ++-- .../SingleFontChooserDialog.cs | 6 +- .../Widgets/GamePrebakedFontsTestWidget.cs | 68 +++++++++++++------ .../ManagedFontAtlas/FontScaleMode.cs | 33 +++++++++ .../IFontAtlasBuildToolkitPostBuild.cs | 14 ++-- .../IFontAtlasBuildToolkitPreBuild.cs | 28 +++++++- .../FontAtlasFactory.BuildToolkit.cs | 22 +++--- .../Internals/GamePrebakedFontHandle.cs | 46 +++++++++---- 9 files changed, 166 insertions(+), 66 deletions(-) create mode 100644 Dalamud/Interface/ManagedFontAtlas/FontScaleMode.cs diff --git a/Dalamud/Interface/FontIdentifier/IFontSpec.cs b/Dalamud/Interface/FontIdentifier/IFontSpec.cs index e4d931605..4d0719d4e 100644 --- a/Dalamud/Interface/FontIdentifier/IFontSpec.cs +++ b/Dalamud/Interface/FontIdentifier/IFontSpec.cs @@ -31,6 +31,8 @@ public interface IFontSpec /// The atlas to bind this font handle to. /// Optional callback to be called after creating the font handle. /// The new font handle. + /// will be set when is invoked. + /// IFontHandle CreateFontHandle(IFontAtlas atlas, FontAtlasBuildStepDelegate? callback = null); /// diff --git a/Dalamud/Interface/FontIdentifier/SingleFontSpec.cs b/Dalamud/Interface/FontIdentifier/SingleFontSpec.cs index 0604b22ea..946215b85 100644 --- a/Dalamud/Interface/FontIdentifier/SingleFontSpec.cs +++ b/Dalamud/Interface/FontIdentifier/SingleFontSpec.cs @@ -109,7 +109,9 @@ public record SingleFontSpec : IFontSpec tk.RegisterPostBuild( () => { - var roundUnit = tk.IsGlobalScaleIgnored(font) ? 1 : 1 / tk.Scale; + // Multiplication by scale will be done with global scale, outside of this handling. + var scale = tk.GetFontScaleMode(font) == FontScaleMode.UndoGlobalScale ? 1 / tk.Scale : 1; + var roundUnit = tk.GetFontScaleMode(font) == FontScaleMode.SkipHandling ? 1 : 1 / tk.Scale; var newAscent = MathF.Round((font.Ascent * this.LineHeight) / roundUnit) * roundUnit; var newFontSize = MathF.Round((font.FontSize * this.LineHeight) / roundUnit) * roundUnit; var shiftDown = MathF.Round((newFontSize - font.FontSize) / 2f / roundUnit) * roundUnit; @@ -129,13 +131,10 @@ public record SingleFontSpec : IFontSpec } } - // `/ roundUnit` = `* scale` - var dax = MathF.Round(this.LetterSpacing / roundUnit / roundUnit) * roundUnit; - var dxy0 = this.GlyphOffset / roundUnit; - + var dax = MathF.Round((this.LetterSpacing * scale) / roundUnit) * roundUnit; + var dxy0 = this.GlyphOffset * scale; dxy0 /= roundUnit; - dxy0.X = MathF.Round(dxy0.X); - dxy0.Y = MathF.Round(dxy0.Y); + dxy0 = new(MathF.Round(dxy0.X), MathF.Round(dxy0.Y)); dxy0 *= roundUnit; dxy0.Y += shiftDown; diff --git a/Dalamud/Interface/ImGuiFontChooserDialog/SingleFontChooserDialog.cs b/Dalamud/Interface/ImGuiFontChooserDialog/SingleFontChooserDialog.cs index 410bf7d18..ca75e5ce0 100644 --- a/Dalamud/Interface/ImGuiFontChooserDialog/SingleFontChooserDialog.cs +++ b/Dalamud/Interface/ImGuiFontChooserDialog/SingleFontChooserDialog.cs @@ -342,9 +342,7 @@ public sealed class SingleFontChooserDialog : IDisposable { this.fontHandle ??= this.selectedFont.CreateFontHandle( this.atlas, - tk => - tk.OnPreBuild(e => e.IgnoreGlobalScale(e.Font)) - .OnPostBuild(e => e.Font.AdjustGlyphMetrics(1f / e.Scale))); + tk => tk.OnPreBuild(e => e.SetFontScaleMode(e.Font, FontScaleMode.UndoGlobalScale))); } else { @@ -837,7 +835,7 @@ public sealed class SingleFontChooserDialog : IDisposable var changed = false; if (!ImGui.BeginTable("##advancedOptions", 4)) - return changed; + return false; var labelWidth = ImGui.CalcTextSize("Letter Spacing:").X; labelWidth = Math.Max(labelWidth, ImGui.CalcTextSize("Offset:").X); diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/GamePrebakedFontsTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/GamePrebakedFontsTestWidget.cs index 84682e7c2..8bb999557 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/GamePrebakedFontsTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/GamePrebakedFontsTestWidget.cs @@ -25,12 +25,20 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable { + private static readonly string[] FontScaleModes = + { + nameof(FontScaleMode.Default), + nameof(FontScaleMode.SkipHandling), + nameof(FontScaleMode.UndoGlobalScale), + }; + private ImVectorWrapper testStringBuffer; private IFontAtlas? privateAtlas; private SingleFontSpec fontSpec = new() { FontId = DalamudDefaultFontAndFamilyId.Instance }; private IFontHandle? fontDialogHandle; private IReadOnlyDictionary Handle)[]>? fontHandles; - private bool useGlobalScale; + private bool atlasScaleMode = true; + private int fontScaleMode = (int)FontScaleMode.UndoGlobalScale; private bool useWordWrap; private bool useItalic; private bool useBold; @@ -52,12 +60,14 @@ internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable public unsafe void Draw() { ImGui.AlignTextToFramePadding(); - fixed (byte* labelPtr = "Global Scale"u8) + if (ImGui.Combo("Global Scale per Font", ref this.fontScaleMode, FontScaleModes, FontScaleModes.Length)) + this.ClearAtlas(); + fixed (byte* labelPtr = "Global Scale for Atlas"u8) { - var v = (byte)(this.useGlobalScale ? 1 : 0); + var v = (byte)(this.atlasScaleMode ? 1 : 0); if (ImGuiNative.igCheckbox(labelPtr, &v) != 0) { - this.useGlobalScale = v != 0; + this.atlasScaleMode = v != 0; this.ClearAtlas(); } } @@ -124,7 +134,7 @@ internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable $"{nameof(GamePrebakedFontsTestWidget)}:EditorFont", FontAtlasAutoRebuildMode.Async)); fcd.SelectedFont = this.fontSpec; - fcd.IgnorePreviewGlobalScale = !this.useGlobalScale; + fcd.IgnorePreviewGlobalScale = !this.atlasScaleMode; Service.Get().Draw += fcd.Draw; fcd.ResultTask.ContinueWith( r => Service.Get().RunOnFrameworkThread( @@ -148,12 +158,14 @@ internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable Service.Get().CreateFontAtlas( nameof(GamePrebakedFontsTestWidget), FontAtlasAutoRebuildMode.Async, - this.useGlobalScale); - this.fontDialogHandle ??= this.fontSpec.CreateFontHandle(this.privateAtlas); + this.atlasScaleMode); + this.fontDialogHandle ??= this.fontSpec.CreateFontHandle( + this.privateAtlas, + e => e.OnPreBuild(tk => tk.SetFontScaleMode(tk.Font, (FontScaleMode)this.fontScaleMode))); fixed (byte* labelPtr = "Test Input"u8) { - if (!this.useGlobalScale) + if (!this.atlasScaleMode) ImGuiNative.igSetWindowFontScale(1 / ImGuiHelpers.GlobalScale); using (this.fontDialogHandle.Push()) { @@ -180,7 +192,7 @@ internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable } } - if (!this.useGlobalScale) + if (!this.atlasScaleMode) ImGuiNative.igSetWindowFontScale(1); } @@ -192,17 +204,29 @@ internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable .ToImmutableDictionary( x => x.Key, x => x.Select( - y => (y, new Lazy( - () => this.useMinimumBuild - ? this.privateAtlas.NewDelegateFontHandle( - e => - e.OnPreBuild( - tk => tk.AddGameGlyphs( - y, - Encoding.UTF8.GetString( - this.testStringBuffer.DataSpan).ToGlyphRange(), - default))) - : this.privateAtlas.NewGameFontHandle(y)))) + y => + { + var range = Encoding.UTF8.GetString(this.testStringBuffer.DataSpan).ToGlyphRange(); + + Lazy l; + if (this.useMinimumBuild + || (this.atlasScaleMode && this.fontScaleMode != (int)FontScaleMode.Default)) + { + l = new( + () => this.privateAtlas!.NewDelegateFontHandle( + e => + e.OnPreBuild( + tk => tk.SetFontScaleMode( + tk.AddGameGlyphs(y, range, default), + (FontScaleMode)this.fontScaleMode)))); + } + else + { + l = new(() => this.privateAtlas!.NewGameFontHandle(y)); + } + + return (y, l); + }) .ToArray()); var offsetX = ImGui.CalcTextSize("99.9pt").X + (ImGui.GetStyle().FramePadding.X * 2); @@ -230,7 +254,7 @@ internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable } else { - if (!this.useGlobalScale) + if (!this.atlasScaleMode) ImGuiNative.igSetWindowFontScale(1 / ImGuiHelpers.GlobalScale); if (counter++ % 2 == 0) { @@ -251,8 +275,8 @@ internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable } finally { - ImGuiNative.igPopTextWrapPos(); ImGuiNative.igSetWindowFontScale(1); + ImGuiNative.igPopTextWrapPos(); } } } diff --git a/Dalamud/Interface/ManagedFontAtlas/FontScaleMode.cs b/Dalamud/Interface/ManagedFontAtlas/FontScaleMode.cs new file mode 100644 index 000000000..b30d5c26c --- /dev/null +++ b/Dalamud/Interface/ManagedFontAtlas/FontScaleMode.cs @@ -0,0 +1,33 @@ +using Dalamud.Interface.Utility; + +using ImGuiNET; + +namespace Dalamud.Interface.ManagedFontAtlas; + +/// +/// Specifies how should global font scale affect a font. +/// +public enum FontScaleMode +{ + /// + /// Do the default handling. Dalamud will load the sufficienty large font that will accomodate the global scale, + /// and stretch the loaded glyphs so that they look pixel-perfect after applying global scale on drawing. + /// Note that bitmap fonts and game fonts will always look blurry if they're not in their original sizes. + /// + Default, + + /// + /// Do nothing with the font. Dalamud will load the font with the size that is exactly as specified. + /// On drawing, the font will look blurry due to stretching. + /// Intended for use with custom scale handling. + /// + SkipHandling, + + /// + /// Stretch the glyphs of the loaded font by the inverse of the global scale. + /// On drawing, the font will always render exactly as the requested size without blurring, as long as + /// and do not affect the scale any + /// further. Note that bitmap fonts and game fonts will always look blurry if they're not in their original sizes. + /// + UndoGlobalScale, +} diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostBuild.cs b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostBuild.cs index d824eca52..827187063 100644 --- a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostBuild.cs +++ b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostBuild.cs @@ -1,4 +1,5 @@ using Dalamud.Interface.Internal; +using Dalamud.Utility; using ImGuiNET; @@ -10,12 +11,13 @@ namespace Dalamud.Interface.ManagedFontAtlas; /// public interface IFontAtlasBuildToolkitPostBuild : IFontAtlasBuildToolkit { - /// - /// Gets whether global scaling is ignored for the given font. - /// - /// The font. - /// True if ignored. - bool IsGlobalScaleIgnored(ImFontPtr fontPtr); + /// + [Obsolete($"Use {nameof(this.GetFontScaleMode)}")] + [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] + bool IsGlobalScaleIgnored(ImFontPtr fontPtr) => this.GetFontScaleMode(fontPtr) == FontScaleMode.UndoGlobalScale; + + /// + FontScaleMode GetFontScaleMode(ImFontPtr fontPtr); /// /// Stores a texture to be managed with the atlas. diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPreBuild.cs b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPreBuild.cs index 9ab480374..9b80d27ff 100644 --- a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPreBuild.cs +++ b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPreBuild.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using Dalamud.Interface.FontIdentifier; using Dalamud.Interface.GameFonts; using Dalamud.Interface.Utility; +using Dalamud.Utility; using ImGuiNET; @@ -45,14 +46,37 @@ public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit /// /// The font. /// Same with . - ImFontPtr IgnoreGlobalScale(ImFontPtr fontPtr); + [Obsolete( + $"Use {nameof(this.SetFontScaleMode)} with {nameof(FontScaleMode)}.{nameof(FontScaleMode.UndoGlobalScale)}")] + [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] + ImFontPtr IgnoreGlobalScale(ImFontPtr fontPtr) => this.SetFontScaleMode(fontPtr, FontScaleMode.UndoGlobalScale); /// /// Gets whether global scaling is ignored for the given font. /// /// The font. /// True if ignored. - bool IsGlobalScaleIgnored(ImFontPtr fontPtr); + [Obsolete($"Use {nameof(this.GetFontScaleMode)}")] + [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] + bool IsGlobalScaleIgnored(ImFontPtr fontPtr) => this.GetFontScaleMode(fontPtr) == FontScaleMode.UndoGlobalScale; + + /// + /// Sets the scaling mode for the given font. + /// + /// The font, returned from and alike. + /// Note that property is not guaranteed to be automatically updated upon + /// calling font adding functions. Pass the return value from font adding functions, not + /// property. + /// The scaling mode. + /// . + ImFontPtr SetFontScaleMode(ImFontPtr fontPtr, FontScaleMode mode); + + /// + /// Gets the scaling mode for the given font. + /// + /// The font. + /// The scaling mode. + FontScaleMode GetFontScaleMode(ImFontPtr fontPtr); /// /// Registers a function to be run after build. diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs index a57e6d036..55af20329 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs @@ -83,9 +83,9 @@ internal sealed partial class FontAtlasFactory public ImVectorWrapper Fonts => this.data.Fonts; /// - /// Gets the list of fonts to ignore global scale. + /// Gets the font scale modes. /// - public List GlobalScaleExclusions { get; } = new(); + private Dictionary FontScaleModes { get; } = new(); /// public void Dispose() => this.disposeAfterBuild.Dispose(); @@ -151,15 +151,15 @@ internal sealed partial class FontAtlasFactory } /// - public ImFontPtr IgnoreGlobalScale(ImFontPtr fontPtr) + public ImFontPtr SetFontScaleMode(ImFontPtr fontPtr, FontScaleMode scaleMode) { - this.GlobalScaleExclusions.Add(fontPtr); + this.FontScaleModes[fontPtr] = scaleMode; return fontPtr; } - /// - public bool IsGlobalScaleIgnored(ImFontPtr fontPtr) => - this.GlobalScaleExclusions.Contains(fontPtr); + /// + public FontScaleMode GetFontScaleMode(ImFontPtr fontPtr) => + this.FontScaleModes.GetValueOrDefault(fontPtr, FontScaleMode.Default); /// public int StoreTexture(IDalamudTextureWrap textureWrap, bool disposeOnError) => @@ -496,17 +496,17 @@ internal sealed partial class FontAtlasFactory var configData = this.data.ConfigData; foreach (ref var config in configData.DataSpan) { - if (this.GlobalScaleExclusions.Contains(new(config.DstFont))) + if (this.GetFontScaleMode(config.DstFont) != FontScaleMode.Default) continue; config.SizePixels *= this.Scale; config.GlyphMaxAdvanceX *= this.Scale; - if (float.IsInfinity(config.GlyphMaxAdvanceX)) + if (float.IsInfinity(config.GlyphMaxAdvanceX) || float.IsNaN(config.GlyphMaxAdvanceX)) config.GlyphMaxAdvanceX = config.GlyphMaxAdvanceX > 0 ? float.MaxValue : -float.MaxValue; config.GlyphMinAdvanceX *= this.Scale; - if (float.IsInfinity(config.GlyphMinAdvanceX)) + if (float.IsInfinity(config.GlyphMinAdvanceX) || float.IsNaN(config.GlyphMinAdvanceX)) config.GlyphMinAdvanceX = config.GlyphMinAdvanceX > 0 ? float.MaxValue : -float.MaxValue; config.GlyphOffset *= this.Scale; @@ -536,7 +536,7 @@ internal sealed partial class FontAtlasFactory var scale = this.Scale; foreach (ref var font in this.Fonts.DataSpan) { - if (!this.GlobalScaleExclusions.Contains(font)) + if (this.GetFontScaleMode(font) != FontScaleMode.SkipHandling) font.AdjustGlyphMetrics(1 / scale, 1 / scale); foreach (var c in FallbackCodepoints) diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/GamePrebakedFontHandle.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/GamePrebakedFontHandle.cs index b6c9817aa..1101e7119 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/GamePrebakedFontHandle.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/GamePrebakedFontHandle.cs @@ -345,17 +345,36 @@ internal class GamePrebakedFontHandle : FontHandle { 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)); + switch (toolkitPreBuild.GetFontScaleMode(font)) + { + case FontScaleMode.Default: + default: + plan = new( + style, + toolkitPreBuild.Scale, + this.handleManager.GameFontTextureProvider, + this.CreateTemplateFont(toolkitPreBuild, style.SizePx)); + break; + + case FontScaleMode.SkipHandling: + plan = new( + style, + 1f, + this.handleManager.GameFontTextureProvider, + this.CreateTemplateFont(toolkitPreBuild, style.SizePx)); + break; + + case FontScaleMode.UndoGlobalScale: + plan = new( + style.Scale(1 / toolkitPreBuild.Scale), + toolkitPreBuild.Scale, + this.handleManager.GameFontTextureProvider, + this.CreateTemplateFont(toolkitPreBuild, style.SizePx)); + break; + } + this.fonts[style] = plan; } @@ -620,15 +639,14 @@ internal class GamePrebakedFontHandle : FontHandle 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 fontScaleMode = toolkitPostBuild.GetFontScaleMode(font); + var round = fontScaleMode == FontScaleMode.SkipHandling ? 1 : 1 / toolkitPostBuild.Scale; var lookup = font.IndexLookupWrapped(); var glyphs = font.GlyphsWrapped(); @@ -649,7 +667,7 @@ internal class GamePrebakedFontHandle : FontHandle ref var g = ref glyphs[glyphIndex]; g = sourceGlyph; - if (noGlobalScale) + if (fontScaleMode == FontScaleMode.SkipHandling) { g.XY *= scale; g.AdvanceX *= scale; @@ -673,7 +691,7 @@ internal class GamePrebakedFontHandle : FontHandle continue; if (!rangeBits[leftInt] || !rangeBits[rightInt]) continue; - if (noGlobalScale) + if (fontScaleMode == FontScaleMode.SkipHandling) { font.AddKerningPair((ushort)leftInt, (ushort)rightInt, k.RightOffset * scale); }