mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-19 14:27:45 +01:00
Implement FontChooserDialog (#1637)
* Implement FontChooserDialog * Minor fixes * Fixes 2 * Add Reset default font button * Add failsafe * reduce uninteresting exception message * Add remarks to use AttachExtraGlyphsForDalamudLanguage * Support advanced font configuration options * fixes * Shift ui elements * more fixes * Add To(Localized)String for IFontSpec * Untie GlobalFontScale from default font size * Layout fixes * Make UiBuilder.DefaultFontSize point to user configured value * Update example for NewDelegateFontHandle * Font interfaces: write notes on not intended for plugins to implement * Update default gamma to 1.7 to match closer to prev behavior (1.4**2) * Fix console window layout
This commit is contained in:
parent
3b3823d4e6
commit
34daa73612
31 changed files with 2478 additions and 81 deletions
|
|
@ -0,0 +1,87 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Storage.Assets;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Dalamud.Interface.FontIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a font from Dalamud assets.
|
||||
/// </summary>
|
||||
public sealed class DalamudAssetFontAndFamilyId : IFontFamilyId, IFontId
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DalamudAssetFontAndFamilyId"/> class.
|
||||
/// </summary>
|
||||
/// <param name="asset">The font asset.</param>
|
||||
public DalamudAssetFontAndFamilyId(DalamudAsset asset)
|
||||
{
|
||||
if (asset.GetPurpose() != DalamudAssetPurpose.Font)
|
||||
throw new ArgumentOutOfRangeException(nameof(asset), asset, "The specified asset is not a font asset.");
|
||||
this.Asset = asset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font asset.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public DalamudAsset Asset { get; init; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public string EnglishName => $"Dalamud: {this.Asset}";
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public IReadOnlyDictionary<string, string>? LocaleNames => null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public IReadOnlyList<IFontId> Fonts => new List<IFontId> { this }.AsReadOnly();
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public IFontFamilyId Family => this;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public int Weight => (int)DWRITE_FONT_WEIGHT.DWRITE_FONT_WEIGHT_NORMAL;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public int Stretch => (int)DWRITE_FONT_STRETCH.DWRITE_FONT_STRETCH_NORMAL;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public int Style => (int)DWRITE_FONT_STYLE.DWRITE_FONT_STYLE_NORMAL;
|
||||
|
||||
public static bool operator ==(DalamudAssetFontAndFamilyId? left, DalamudAssetFontAndFamilyId? right) =>
|
||||
Equals(left, right);
|
||||
|
||||
public static bool operator !=(DalamudAssetFontAndFamilyId? left, DalamudAssetFontAndFamilyId? right) =>
|
||||
!Equals(left, right);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj) => obj is DalamudAssetFontAndFamilyId other && this.Equals(other);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => (int)this.Asset;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => $"{nameof(DalamudAssetFontAndFamilyId)}:{this.Asset}";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int FindBestMatch(int weight, int stretch, int style) => 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontPtr AddToBuildToolkit(IFontAtlasBuildToolkitPreBuild tk, in SafeFontConfig config) =>
|
||||
tk.AddDalamudAssetFont(this.Asset, config);
|
||||
|
||||
private bool Equals(DalamudAssetFontAndFamilyId other) => this.Asset == other.Asset;
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Dalamud.Interface.FontIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the default Dalamud font.
|
||||
/// </summary>
|
||||
public sealed class DalamudDefaultFontAndFamilyId : IFontId, IFontFamilyId
|
||||
{
|
||||
/// <summary>
|
||||
/// The shared instance of <see cref="DalamudDefaultFontAndFamilyId"/>.
|
||||
/// </summary>
|
||||
public static readonly DalamudDefaultFontAndFamilyId Instance = new();
|
||||
|
||||
private DalamudDefaultFontAndFamilyId()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public string EnglishName => "(Default)";
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public IReadOnlyDictionary<string, string>? LocaleNames => null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public IFontFamilyId Family => this;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public int Weight => (int)DWRITE_FONT_WEIGHT.DWRITE_FONT_WEIGHT_NORMAL;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public int Stretch => (int)DWRITE_FONT_STRETCH.DWRITE_FONT_STRETCH_NORMAL;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public int Style => (int)DWRITE_FONT_STYLE.DWRITE_FONT_STYLE_NORMAL;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public IReadOnlyList<IFontId> Fonts => new List<IFontId> { this }.AsReadOnly();
|
||||
|
||||
public static bool operator ==(DalamudDefaultFontAndFamilyId? left, DalamudDefaultFontAndFamilyId? right) =>
|
||||
left is null == right is null;
|
||||
|
||||
public static bool operator !=(DalamudDefaultFontAndFamilyId? left, DalamudDefaultFontAndFamilyId? right) =>
|
||||
left is null != right is null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj) => obj is DalamudDefaultFontAndFamilyId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => 12345678;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => nameof(DalamudDefaultFontAndFamilyId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontPtr AddToBuildToolkit(IFontAtlasBuildToolkitPreBuild tk, in SafeFontConfig config)
|
||||
=> tk.AddDalamudDefaultFont(config.SizePx, config.GlyphRanges);
|
||||
// TODO: mergeFont
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int FindBestMatch(int weight, int stretch, int style) => 0;
|
||||
}
|
||||
81
Dalamud/Interface/FontIdentifier/GameFontAndFamilyId.cs
Normal file
81
Dalamud/Interface/FontIdentifier/GameFontAndFamilyId.cs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Dalamud.Interface.FontIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a font from the game.
|
||||
/// </summary>
|
||||
public sealed class GameFontAndFamilyId : IFontId, IFontFamilyId
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameFontAndFamilyId"/> class.
|
||||
/// </summary>
|
||||
/// <param name="family">The game font family.</param>
|
||||
public GameFontAndFamilyId(GameFontFamily family) => this.GameFontFamily = family;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the game font family.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public GameFontFamily GameFontFamily { get; init; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public string EnglishName => $"Game: {Enum.GetName(this.GameFontFamily) ?? throw new NotSupportedException()}";
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public IReadOnlyDictionary<string, string>? LocaleNames => null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public IFontFamilyId Family => this;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public int Weight => (int)DWRITE_FONT_WEIGHT.DWRITE_FONT_WEIGHT_NORMAL;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public int Stretch => (int)DWRITE_FONT_STRETCH.DWRITE_FONT_STRETCH_NORMAL;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public int Style => (int)DWRITE_FONT_STYLE.DWRITE_FONT_STYLE_NORMAL;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public IReadOnlyList<IFontId> Fonts => new List<IFontId> { this }.AsReadOnly();
|
||||
|
||||
public static bool operator ==(GameFontAndFamilyId? left, GameFontAndFamilyId? right) => Equals(left, right);
|
||||
|
||||
public static bool operator !=(GameFontAndFamilyId? left, GameFontAndFamilyId? right) => !Equals(left, right);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj) =>
|
||||
ReferenceEquals(this, obj) || (obj is GameFontAndFamilyId other && this.Equals(other));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => (int)this.GameFontFamily;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int FindBestMatch(int weight, int stretch, int style) => 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => $"{nameof(GameFontAndFamilyId)}:{this.GameFontFamily}";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontPtr AddToBuildToolkit(IFontAtlasBuildToolkitPreBuild tk, in SafeFontConfig config) =>
|
||||
tk.AddGameGlyphs(new(this.GameFontFamily, config.SizePx), config.GlyphRanges, config.MergeFont);
|
||||
|
||||
private bool Equals(GameFontAndFamilyId other) => this.GameFontFamily == other.GameFontFamily;
|
||||
}
|
||||
102
Dalamud/Interface/FontIdentifier/IFontFamilyId.cs
Normal file
102
Dalamud/Interface/FontIdentifier/IFontFamilyId.cs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Dalamud.Interface.FontIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a font family identifier.<br />
|
||||
/// Not intended for plugins to implement.
|
||||
/// </summary>
|
||||
public interface IFontFamilyId : IObjectWithLocalizableName
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the list of fonts under this family.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
IReadOnlyList<IFontId> Fonts { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Finds the index of the font inside <see cref="Fonts"/> that best matches the given parameters.
|
||||
/// </summary>
|
||||
/// <param name="weight">The weight of the font.</param>
|
||||
/// <param name="stretch">The stretch of the font.</param>
|
||||
/// <param name="style">The style of the font.</param>
|
||||
/// <returns>The index of the font. Guaranteed to be a valid index.</returns>
|
||||
int FindBestMatch(int weight, int stretch, int style);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of Dalamud-provided fonts.
|
||||
/// </summary>
|
||||
/// <returns>The list of fonts.</returns>
|
||||
public static List<IFontFamilyId> ListDalamudFonts() =>
|
||||
new()
|
||||
{
|
||||
new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansJpMedium),
|
||||
new DalamudAssetFontAndFamilyId(DalamudAsset.InconsolataRegular),
|
||||
new DalamudAssetFontAndFamilyId(DalamudAsset.FontAwesomeFreeSolid),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of Game-provided fonts.
|
||||
/// </summary>
|
||||
/// <returns>The list of fonts.</returns>
|
||||
public static List<IFontFamilyId> ListGameFonts() => new()
|
||||
{
|
||||
new GameFontAndFamilyId(GameFontFamily.Axis),
|
||||
new GameFontAndFamilyId(GameFontFamily.Jupiter),
|
||||
new GameFontAndFamilyId(GameFontFamily.JupiterNumeric),
|
||||
new GameFontAndFamilyId(GameFontFamily.Meidinger),
|
||||
new GameFontAndFamilyId(GameFontFamily.MiedingerMid),
|
||||
new GameFontAndFamilyId(GameFontFamily.TrumpGothic),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of System-provided fonts.
|
||||
/// </summary>
|
||||
/// <param name="refresh">If <c>true</c>, try to refresh the list.</param>
|
||||
/// <returns>The list of fonts.</returns>
|
||||
public static unsafe List<IFontFamilyId> ListSystemFonts(bool refresh)
|
||||
{
|
||||
using var dwf = default(ComPtr<IDWriteFactory>);
|
||||
fixed (Guid* piid = &IID.IID_IDWriteFactory)
|
||||
{
|
||||
DirectX.DWriteCreateFactory(
|
||||
DWRITE_FACTORY_TYPE.DWRITE_FACTORY_TYPE_SHARED,
|
||||
piid,
|
||||
(IUnknown**)dwf.GetAddressOf()).ThrowOnError();
|
||||
}
|
||||
|
||||
using var sfc = default(ComPtr<IDWriteFontCollection>);
|
||||
dwf.Get()->GetSystemFontCollection(sfc.GetAddressOf(), refresh).ThrowOnError();
|
||||
|
||||
var count = (int)sfc.Get()->GetFontFamilyCount();
|
||||
var result = new List<IFontFamilyId>(count);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
using var ff = default(ComPtr<IDWriteFontFamily>);
|
||||
if (sfc.Get()->GetFontFamily((uint)i, ff.GetAddressOf()).FAILED)
|
||||
{
|
||||
// Ignore errors, if any
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
result.Add(SystemFontFamilyId.FromDWriteFamily(ff));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
40
Dalamud/Interface/FontIdentifier/IFontId.cs
Normal file
40
Dalamud/Interface/FontIdentifier/IFontId.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.FontIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a font identifier.<br />
|
||||
/// Not intended for plugins to implement.
|
||||
/// </summary>
|
||||
public interface IFontId : IObjectWithLocalizableName
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the associated font family.
|
||||
/// </summary>
|
||||
IFontFamilyId Family { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font weight, ranging from 1 to 999.
|
||||
/// </summary>
|
||||
int Weight { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font stretch, ranging from 1 to 9.
|
||||
/// </summary>
|
||||
int Stretch { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font style. Treat as an opaque value.
|
||||
/// </summary>
|
||||
int Style { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds this font to the given font build toolkit.
|
||||
/// </summary>
|
||||
/// <param name="tk">The font build toolkit.</param>
|
||||
/// <param name="config">The font configuration. Some parameters may be ignored.</param>
|
||||
/// <returns>The added font.</returns>
|
||||
ImFontPtr AddToBuildToolkit(IFontAtlasBuildToolkitPreBuild tk, in SafeFontConfig config);
|
||||
}
|
||||
50
Dalamud/Interface/FontIdentifier/IFontSpec.cs
Normal file
50
Dalamud/Interface/FontIdentifier/IFontSpec.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.FontIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a user's choice of font(s).<br />
|
||||
/// Not intended for plugins to implement.
|
||||
/// </summary>
|
||||
public interface IFontSpec
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the font size in pixels.
|
||||
/// </summary>
|
||||
float SizePx { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font size in points.
|
||||
/// </summary>
|
||||
float SizePt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the line height in pixels.
|
||||
/// </summary>
|
||||
float LineHeightPx { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a font handle corresponding to this font specification.
|
||||
/// </summary>
|
||||
/// <param name="atlas">The atlas to bind this font handle to.</param>
|
||||
/// <param name="callback">Optional callback to be called after creating the font handle.</param>
|
||||
/// <returns>The new font handle.</returns>
|
||||
IFontHandle CreateFontHandle(IFontAtlas atlas, FontAtlasBuildStepDelegate? callback = null);
|
||||
|
||||
/// <summary>
|
||||
/// Adds this font to the given font build toolkit.
|
||||
/// </summary>
|
||||
/// <param name="tk">The font build toolkit.</param>
|
||||
/// <param name="mergeFont">The font to merge to.</param>
|
||||
/// <returns>The added font.</returns>
|
||||
ImFontPtr AddToBuildToolkit(IFontAtlasBuildToolkitPreBuild tk, ImFontPtr mergeFont = default);
|
||||
|
||||
/// <summary>
|
||||
/// Represents this font specification, preferrably in the requested locale.
|
||||
/// </summary>
|
||||
/// <param name="localeCode">The locale code. Must be in lowercase(invariant).</param>
|
||||
/// <returns>The value.</returns>
|
||||
string ToLocalizedString(string localeCode);
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Utility;
|
||||
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Dalamud.Interface.FontIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an object with localizable names.
|
||||
/// </summary>
|
||||
public interface IObjectWithLocalizableName
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name, preferrably in English.
|
||||
/// </summary>
|
||||
string EnglishName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the names per locales.
|
||||
/// </summary>
|
||||
IReadOnlyDictionary<string, string>? LocaleNames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name in the requested locale if available; otherwise, <see cref="EnglishName"/>.
|
||||
/// </summary>
|
||||
/// <param name="localeCode">The locale code. Must be in lowercase(invariant).</param>
|
||||
/// <returns>The value.</returns>
|
||||
string GetLocalizedName(string localeCode)
|
||||
{
|
||||
if (this.LocaleNames is null)
|
||||
return this.EnglishName;
|
||||
if (this.LocaleNames.TryGetValue(localeCode, out var v))
|
||||
return v;
|
||||
foreach (var (a, b) in this.LocaleNames)
|
||||
{
|
||||
if (a.StartsWith(localeCode))
|
||||
return b;
|
||||
}
|
||||
|
||||
return this.EnglishName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves all names per locales.
|
||||
/// </summary>
|
||||
/// <param name="fn">The names.</param>
|
||||
/// <returns>A new dictionary mapping from locale code to localized names.</returns>
|
||||
internal static unsafe IReadOnlyDictionary<string, string> GetLocaleNames(IDWriteLocalizedStrings* fn)
|
||||
{
|
||||
var count = fn->GetCount();
|
||||
var maxStrLen = 0u;
|
||||
for (var i = 0u; i < count; i++)
|
||||
{
|
||||
var length = 0u;
|
||||
fn->GetStringLength(i, &length).ThrowOnError();
|
||||
maxStrLen = Math.Max(maxStrLen, length);
|
||||
fn->GetLocaleNameLength(i, &length).ThrowOnError();
|
||||
maxStrLen = Math.Max(maxStrLen, length);
|
||||
}
|
||||
|
||||
maxStrLen++;
|
||||
var buf = stackalloc char[(int)maxStrLen];
|
||||
var result = new Dictionary<string, string>((int)count);
|
||||
for (var i = 0u; i < count; i++)
|
||||
{
|
||||
fn->GetLocaleName(i, (ushort*)buf, maxStrLen).ThrowOnError();
|
||||
var key = new string(buf);
|
||||
fn->GetString(i, (ushort*)buf, maxStrLen).ThrowOnError();
|
||||
var value = new string(buf);
|
||||
result[key.ToLowerInvariant()] = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
155
Dalamud/Interface/FontIdentifier/SingleFontSpec.cs
Normal file
155
Dalamud/Interface/FontIdentifier/SingleFontSpec.cs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.Utility;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Interface.FontIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a user's choice of a single font.
|
||||
/// </summary>
|
||||
[SuppressMessage(
|
||||
"StyleCop.CSharp.OrderingRules",
|
||||
"SA1206:Declaration keywords should follow order",
|
||||
Justification = "public required")]
|
||||
public record SingleFontSpec : IFontSpec
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the font id.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public required IFontId FontId { get; init; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonProperty]
|
||||
public float SizePx { get; init; } = 16;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public float SizePt
|
||||
{
|
||||
get => (this.SizePx * 3) / 4;
|
||||
init => this.SizePx = (value * 4) / 3;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public float LineHeightPx => MathF.Round(this.SizePx * this.LineHeight);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the line height ratio to the font size.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public float LineHeight { get; init; } = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the glyph offset in pixels.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public Vector2 GlyphOffset { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the letter spacing in pixels.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public float LetterSpacing { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the glyph ranges.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public ushort[]? GlyphRanges { get; init; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string ToLocalizedString(string localeCode)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(this.FontId.Family.GetLocalizedName(localeCode));
|
||||
sb.Append($"({this.FontId.GetLocalizedName(localeCode)}, {this.SizePt}pt");
|
||||
if (Math.Abs(this.LineHeight - 1f) > 0.000001f)
|
||||
sb.Append($", LH={this.LineHeight:0.##}");
|
||||
if (this.GlyphOffset != default)
|
||||
sb.Append($", O={this.GlyphOffset.X:0.##},{this.GlyphOffset.Y:0.##}");
|
||||
if (this.LetterSpacing != 0f)
|
||||
sb.Append($", LS={this.LetterSpacing:0.##}");
|
||||
sb.Append(')');
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => this.ToLocalizedString("en");
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandle CreateFontHandle(IFontAtlas atlas, FontAtlasBuildStepDelegate? callback = null) =>
|
||||
atlas.NewDelegateFontHandle(tk =>
|
||||
{
|
||||
tk.OnPreBuild(e => e.Font = this.AddToBuildToolkit(e));
|
||||
callback?.Invoke(tk);
|
||||
});
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontPtr AddToBuildToolkit(IFontAtlasBuildToolkitPreBuild tk, ImFontPtr mergeFont = default)
|
||||
{
|
||||
var font = this.FontId.AddToBuildToolkit(
|
||||
tk,
|
||||
new()
|
||||
{
|
||||
SizePx = this.SizePx,
|
||||
GlyphRanges = this.GlyphRanges,
|
||||
MergeFont = mergeFont,
|
||||
});
|
||||
|
||||
tk.RegisterPostBuild(
|
||||
() =>
|
||||
{
|
||||
var roundUnit = tk.IsGlobalScaleIgnored(font) ? 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;
|
||||
|
||||
font.Ascent = newAscent;
|
||||
font.FontSize = newFontSize;
|
||||
font.Descent = newFontSize - font.Ascent;
|
||||
|
||||
var lookup = new BitArray(ushort.MaxValue + 1, this.GlyphRanges is null);
|
||||
if (this.GlyphRanges is not null)
|
||||
{
|
||||
for (var i = 0; i < this.GlyphRanges.Length && this.GlyphRanges[i] != 0; i += 2)
|
||||
{
|
||||
var to = (int)this.GlyphRanges[i + 1];
|
||||
for (var j = this.GlyphRanges[i]; j <= to; j++)
|
||||
lookup[j] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// `/ roundUnit` = `* scale`
|
||||
var dax = MathF.Round(this.LetterSpacing / roundUnit / roundUnit) * roundUnit;
|
||||
var dxy0 = this.GlyphOffset / roundUnit;
|
||||
|
||||
dxy0 /= roundUnit;
|
||||
dxy0.X = MathF.Round(dxy0.X);
|
||||
dxy0.Y = MathF.Round(dxy0.Y);
|
||||
dxy0 *= roundUnit;
|
||||
|
||||
dxy0.Y += shiftDown;
|
||||
var dxy = new Vector4(dxy0, dxy0.X, dxy0.Y);
|
||||
foreach (ref var glyphReal in font.GlyphsWrapped().DataSpan)
|
||||
{
|
||||
if (!lookup[glyphReal.Codepoint])
|
||||
continue;
|
||||
|
||||
glyphReal.XY += dxy;
|
||||
glyphReal.AdvanceX += dax;
|
||||
}
|
||||
});
|
||||
|
||||
return font;
|
||||
}
|
||||
}
|
||||
181
Dalamud/Interface/FontIdentifier/SystemFontFamilyId.cs
Normal file
181
Dalamud/Interface/FontIdentifier/SystemFontFamilyId.cs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Dalamud.Interface.FontIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a font from system.
|
||||
/// </summary>
|
||||
public sealed class SystemFontFamilyId : IFontFamilyId
|
||||
{
|
||||
[JsonIgnore]
|
||||
private IReadOnlyList<IFontId>? fontsLazy;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemFontFamilyId"/> class.
|
||||
/// </summary>
|
||||
/// <param name="englishName">The font name in English.</param>
|
||||
/// <param name="localeNames">The localized font name for display purposes.</param>
|
||||
[JsonConstructor]
|
||||
internal SystemFontFamilyId(string englishName, IReadOnlyDictionary<string, string> localeNames)
|
||||
{
|
||||
this.EnglishName = englishName;
|
||||
this.LocaleNames = localeNames;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemFontFamilyId"/> class.
|
||||
/// </summary>
|
||||
/// <param name="localeNames">The localized font name for display purposes.</param>
|
||||
internal SystemFontFamilyId(IReadOnlyDictionary<string, string> localeNames)
|
||||
{
|
||||
if (localeNames.TryGetValue("en-us", out var name))
|
||||
this.EnglishName = name;
|
||||
else if (localeNames.TryGetValue("en", out name))
|
||||
this.EnglishName = name;
|
||||
else
|
||||
this.EnglishName = localeNames.Values.First();
|
||||
this.LocaleNames = localeNames;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonProperty]
|
||||
public string EnglishName { get; init; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonProperty]
|
||||
public IReadOnlyDictionary<string, string>? LocaleNames { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public IReadOnlyList<IFontId> Fonts => this.fontsLazy ??= this.GetFonts();
|
||||
|
||||
public static bool operator ==(SystemFontFamilyId? left, SystemFontFamilyId? right) => Equals(left, right);
|
||||
|
||||
public static bool operator !=(SystemFontFamilyId? left, SystemFontFamilyId? right) => !Equals(left, right);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int FindBestMatch(int weight, int stretch, int style)
|
||||
{
|
||||
using var matchingFont = default(ComPtr<IDWriteFont>);
|
||||
|
||||
var candidates = this.Fonts.ToList();
|
||||
var minGap = int.MaxValue;
|
||||
foreach (var c in candidates)
|
||||
minGap = Math.Min(minGap, Math.Abs(c.Weight - weight));
|
||||
candidates.RemoveAll(c => Math.Abs(c.Weight - weight) != minGap);
|
||||
|
||||
minGap = int.MaxValue;
|
||||
foreach (var c in candidates)
|
||||
minGap = Math.Min(minGap, Math.Abs(c.Stretch - stretch));
|
||||
candidates.RemoveAll(c => Math.Abs(c.Stretch - stretch) != minGap);
|
||||
|
||||
if (candidates.Any(x => x.Style == style))
|
||||
candidates.RemoveAll(x => x.Style != style);
|
||||
else if (candidates.Any(x => x.Style == (int)DWRITE_FONT_STYLE.DWRITE_FONT_STYLE_NORMAL))
|
||||
candidates.RemoveAll(x => x.Style != (int)DWRITE_FONT_STYLE.DWRITE_FONT_STYLE_NORMAL);
|
||||
|
||||
if (!candidates.Any())
|
||||
return 0;
|
||||
|
||||
for (var i = 0; i < this.Fonts.Count; i++)
|
||||
{
|
||||
if (Equals(this.Fonts[i], candidates[0]))
|
||||
return i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => $"{nameof(SystemFontFamilyId)}:{this.EnglishName}";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj) =>
|
||||
ReferenceEquals(this, obj) || (obj is SystemFontFamilyId other && this.Equals(other));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => this.EnglishName.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance of <see cref="SystemFontFamilyId"/> from an <see cref="IDWriteFontFamily"/>.
|
||||
/// </summary>
|
||||
/// <param name="family">The family.</param>
|
||||
/// <returns>The new instance.</returns>
|
||||
internal static unsafe SystemFontFamilyId FromDWriteFamily(ComPtr<IDWriteFontFamily> family)
|
||||
{
|
||||
using var fn = default(ComPtr<IDWriteLocalizedStrings>);
|
||||
family.Get()->GetFamilyNames(fn.GetAddressOf()).ThrowOnError();
|
||||
return new(IObjectWithLocalizableName.GetLocaleNames(fn));
|
||||
}
|
||||
|
||||
private unsafe IReadOnlyList<IFontId> GetFonts()
|
||||
{
|
||||
using var dwf = default(ComPtr<IDWriteFactory>);
|
||||
fixed (Guid* piid = &IID.IID_IDWriteFactory)
|
||||
{
|
||||
DirectX.DWriteCreateFactory(
|
||||
DWRITE_FACTORY_TYPE.DWRITE_FACTORY_TYPE_SHARED,
|
||||
piid,
|
||||
(IUnknown**)dwf.GetAddressOf()).ThrowOnError();
|
||||
}
|
||||
|
||||
using var sfc = default(ComPtr<IDWriteFontCollection>);
|
||||
dwf.Get()->GetSystemFontCollection(sfc.GetAddressOf(), false).ThrowOnError();
|
||||
|
||||
var familyIndex = 0u;
|
||||
BOOL exists = false;
|
||||
fixed (void* pName = this.EnglishName)
|
||||
sfc.Get()->FindFamilyName((ushort*)pName, &familyIndex, &exists).ThrowOnError();
|
||||
if (!exists)
|
||||
throw new FileNotFoundException($"Font \"{this.EnglishName}\" not found.");
|
||||
|
||||
using var family = default(ComPtr<IDWriteFontFamily>);
|
||||
sfc.Get()->GetFontFamily(familyIndex, family.GetAddressOf()).ThrowOnError();
|
||||
|
||||
var fontCount = (int)family.Get()->GetFontCount();
|
||||
var fonts = new List<IFontId>(fontCount);
|
||||
for (var i = 0; i < fontCount; i++)
|
||||
{
|
||||
using var font = default(ComPtr<IDWriteFont>);
|
||||
if (family.Get()->GetFont((uint)i, font.GetAddressOf()).FAILED)
|
||||
{
|
||||
// Ignore errors, if any
|
||||
continue;
|
||||
}
|
||||
|
||||
if (font.Get()->GetSimulations() != DWRITE_FONT_SIMULATIONS.DWRITE_FONT_SIMULATIONS_NONE)
|
||||
{
|
||||
// No simulation support
|
||||
continue;
|
||||
}
|
||||
|
||||
fonts.Add(new SystemFontId(this, font));
|
||||
}
|
||||
|
||||
fonts.Sort(
|
||||
(a, b) =>
|
||||
{
|
||||
var comp = a.Weight.CompareTo(b.Weight);
|
||||
if (comp != 0)
|
||||
return comp;
|
||||
|
||||
comp = a.Stretch.CompareTo(b.Stretch);
|
||||
if (comp != 0)
|
||||
return comp;
|
||||
|
||||
return a.Style.CompareTo(b.Style);
|
||||
});
|
||||
return fonts;
|
||||
}
|
||||
|
||||
private bool Equals(SystemFontFamilyId other) => this.EnglishName == other.EnglishName;
|
||||
}
|
||||
163
Dalamud/Interface/FontIdentifier/SystemFontId.cs
Normal file
163
Dalamud/Interface/FontIdentifier/SystemFontId.cs
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Dalamud.Interface.FontIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a font installed in the system.
|
||||
/// </summary>
|
||||
public sealed class SystemFontId : IFontId
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemFontId"/> class.
|
||||
/// </summary>
|
||||
/// <param name="family">The parent font family.</param>
|
||||
/// <param name="font">The font.</param>
|
||||
internal unsafe SystemFontId(SystemFontFamilyId family, ComPtr<IDWriteFont> font)
|
||||
{
|
||||
this.Family = family;
|
||||
this.Weight = (int)font.Get()->GetWeight();
|
||||
this.Stretch = (int)font.Get()->GetStretch();
|
||||
this.Style = (int)font.Get()->GetStyle();
|
||||
|
||||
using var fn = default(ComPtr<IDWriteLocalizedStrings>);
|
||||
font.Get()->GetFaceNames(fn.GetAddressOf()).ThrowOnError();
|
||||
this.LocaleNames = IObjectWithLocalizableName.GetLocaleNames(fn);
|
||||
if (this.LocaleNames.TryGetValue("en-us", out var name))
|
||||
this.EnglishName = name;
|
||||
else if (this.LocaleNames.TryGetValue("en", out name))
|
||||
this.EnglishName = name;
|
||||
else
|
||||
this.EnglishName = this.LocaleNames.Values.First();
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
private SystemFontId(string englishName, IReadOnlyDictionary<string, string> localeNames, IFontFamilyId family)
|
||||
{
|
||||
this.EnglishName = englishName;
|
||||
this.LocaleNames = localeNames;
|
||||
this.Family = family;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonProperty]
|
||||
public string EnglishName { get; init; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonProperty]
|
||||
public IReadOnlyDictionary<string, string>? LocaleNames { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonProperty]
|
||||
public IFontFamilyId Family { get; init; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonProperty]
|
||||
public int Weight { get; init; } = (int)DWRITE_FONT_WEIGHT.DWRITE_FONT_WEIGHT_NORMAL;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonProperty]
|
||||
public int Stretch { get; init; } = (int)DWRITE_FONT_STRETCH.DWRITE_FONT_STRETCH_NORMAL;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonProperty]
|
||||
public int Style { get; init; } = (int)DWRITE_FONT_STYLE.DWRITE_FONT_STYLE_NORMAL;
|
||||
|
||||
public static bool operator ==(SystemFontId? left, SystemFontId? right) => Equals(left, right);
|
||||
|
||||
public static bool operator !=(SystemFontId? left, SystemFontId? right) => !Equals(left, right);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj) =>
|
||||
ReferenceEquals(this, obj) || (obj is SystemFontId other && this.Equals(other));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => HashCode.Combine(this.Family, this.Weight, this.Stretch, this.Style);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() =>
|
||||
$"{nameof(SystemFontId)}:{this.Weight}:{this.Stretch}:{this.Style}:{this.Family}";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImFontPtr AddToBuildToolkit(IFontAtlasBuildToolkitPreBuild tk, in SafeFontConfig config)
|
||||
{
|
||||
var (path, index) = this.GetFileAndIndex();
|
||||
return tk.AddFontFromFile(path, config with { FontNo = index });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file containing this font, and the font index within.
|
||||
/// </summary>
|
||||
/// <returns>The path and index.</returns>
|
||||
public unsafe (string Path, int Index) GetFileAndIndex()
|
||||
{
|
||||
using var dwf = default(ComPtr<IDWriteFactory>);
|
||||
fixed (Guid* piid = &IID.IID_IDWriteFactory)
|
||||
{
|
||||
DirectX.DWriteCreateFactory(
|
||||
DWRITE_FACTORY_TYPE.DWRITE_FACTORY_TYPE_SHARED,
|
||||
piid,
|
||||
(IUnknown**)dwf.GetAddressOf()).ThrowOnError();
|
||||
}
|
||||
|
||||
using var sfc = default(ComPtr<IDWriteFontCollection>);
|
||||
dwf.Get()->GetSystemFontCollection(sfc.GetAddressOf(), false).ThrowOnError();
|
||||
|
||||
var familyIndex = 0u;
|
||||
BOOL exists = false;
|
||||
fixed (void* name = this.Family.EnglishName)
|
||||
sfc.Get()->FindFamilyName((ushort*)name, &familyIndex, &exists).ThrowOnError();
|
||||
if (!exists)
|
||||
throw new FileNotFoundException($"Font \"{this.Family.EnglishName}\" not found.");
|
||||
|
||||
using var family = default(ComPtr<IDWriteFontFamily>);
|
||||
sfc.Get()->GetFontFamily(familyIndex, family.GetAddressOf()).ThrowOnError();
|
||||
|
||||
using var font = default(ComPtr<IDWriteFont>);
|
||||
family.Get()->GetFirstMatchingFont(
|
||||
(DWRITE_FONT_WEIGHT)this.Weight,
|
||||
(DWRITE_FONT_STRETCH)this.Stretch,
|
||||
(DWRITE_FONT_STYLE)this.Style,
|
||||
font.GetAddressOf()).ThrowOnError();
|
||||
|
||||
using var fface = default(ComPtr<IDWriteFontFace>);
|
||||
font.Get()->CreateFontFace(fface.GetAddressOf()).ThrowOnError();
|
||||
var fileCount = 0;
|
||||
fface.Get()->GetFiles((uint*)&fileCount, null).ThrowOnError();
|
||||
if (fileCount != 1)
|
||||
throw new NotSupportedException();
|
||||
|
||||
using var ffile = default(ComPtr<IDWriteFontFile>);
|
||||
fface.Get()->GetFiles((uint*)&fileCount, ffile.GetAddressOf()).ThrowOnError();
|
||||
void* refKey;
|
||||
var refKeySize = 0u;
|
||||
ffile.Get()->GetReferenceKey(&refKey, &refKeySize).ThrowOnError();
|
||||
|
||||
using var floader = default(ComPtr<IDWriteFontFileLoader>);
|
||||
ffile.Get()->GetLoader(floader.GetAddressOf()).ThrowOnError();
|
||||
|
||||
using var flocal = default(ComPtr<IDWriteLocalFontFileLoader>);
|
||||
floader.As(&flocal).ThrowOnError();
|
||||
|
||||
var pathSize = 0u;
|
||||
flocal.Get()->GetFilePathLengthFromKey(refKey, refKeySize, &pathSize).ThrowOnError();
|
||||
|
||||
var path = stackalloc char[(int)pathSize + 1];
|
||||
flocal.Get()->GetFilePathFromKey(refKey, refKeySize, (ushort*)path, pathSize + 1).ThrowOnError();
|
||||
return (new(path, 0, (int)pathSize), (int)fface.Get()->GetIndex());
|
||||
}
|
||||
|
||||
private bool Equals(SystemFontId other) => this.Family.Equals(other.Family) && this.Weight == other.Weight &&
|
||||
this.Stretch == other.Stretch && this.Style == other.Style;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue