mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-01-03 14:23:40 +01:00
Make it possible to attach arbitrary game font from delegate font
This commit is contained in:
parent
3d576a0654
commit
d78667900f
8 changed files with 544 additions and 407 deletions
|
|
@ -6,7 +6,7 @@ namespace Dalamud.Interface.GameFonts;
|
|||
/// <summary>
|
||||
/// Reference member view of a .fdt file data.
|
||||
/// </summary>
|
||||
internal readonly unsafe ref struct FdtFileView
|
||||
internal readonly unsafe struct FdtFileView
|
||||
{
|
||||
private readonly byte* ptr;
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ public struct GameFontStyle
|
|||
/// </summary>
|
||||
public float SizePt
|
||||
{
|
||||
get => this.SizePx * 3 / 4;
|
||||
readonly get => this.SizePx * 3 / 4;
|
||||
set => this.SizePx = value * 4 / 3;
|
||||
}
|
||||
|
||||
|
|
@ -73,14 +73,14 @@ public struct GameFontStyle
|
|||
/// </summary>
|
||||
public float BaseSkewStrength
|
||||
{
|
||||
get => this.SkewStrength * this.BaseSizePx / this.SizePx;
|
||||
readonly get => this.SkewStrength * this.BaseSizePx / this.SizePx;
|
||||
set => this.SkewStrength = value * this.SizePx / this.BaseSizePx;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font family.
|
||||
/// </summary>
|
||||
public GameFontFamily Family => this.FamilyAndSize switch
|
||||
public readonly GameFontFamily Family => this.FamilyAndSize switch
|
||||
{
|
||||
GameFontFamilyAndSize.Undefined => GameFontFamily.Undefined,
|
||||
GameFontFamilyAndSize.Axis96 => GameFontFamily.Axis,
|
||||
|
|
@ -112,7 +112,7 @@ public struct GameFontStyle
|
|||
/// <summary>
|
||||
/// Gets the corresponding GameFontFamilyAndSize but with minimum possible font sizes.
|
||||
/// </summary>
|
||||
public GameFontFamilyAndSize FamilyWithMinimumSize => this.Family switch
|
||||
public readonly GameFontFamilyAndSize FamilyWithMinimumSize => this.Family switch
|
||||
{
|
||||
GameFontFamily.Axis => GameFontFamilyAndSize.Axis96,
|
||||
GameFontFamily.Jupiter => GameFontFamilyAndSize.Jupiter16,
|
||||
|
|
@ -126,7 +126,7 @@ public struct GameFontStyle
|
|||
/// <summary>
|
||||
/// Gets the base font size in point unit.
|
||||
/// </summary>
|
||||
public float BaseSizePt => this.FamilyAndSize switch
|
||||
public readonly float BaseSizePt => this.FamilyAndSize switch
|
||||
{
|
||||
GameFontFamilyAndSize.Undefined => 0,
|
||||
GameFontFamilyAndSize.Axis96 => 9.6f,
|
||||
|
|
@ -158,14 +158,14 @@ public struct GameFontStyle
|
|||
/// <summary>
|
||||
/// Gets the base font size in pixel unit.
|
||||
/// </summary>
|
||||
public float BaseSizePx => this.BaseSizePt * 4 / 3;
|
||||
public readonly float BaseSizePx => this.BaseSizePt * 4 / 3;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this font is bold.
|
||||
/// </summary>
|
||||
public bool Bold
|
||||
{
|
||||
get => this.Weight > 0f;
|
||||
readonly get => this.Weight > 0f;
|
||||
set => this.Weight = value ? 1f : 0f;
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ public struct GameFontStyle
|
|||
/// </summary>
|
||||
public bool Italic
|
||||
{
|
||||
get => this.SkewStrength != 0;
|
||||
readonly get => this.SkewStrength != 0;
|
||||
set => this.SkewStrength = value ? this.SizePx / 6 : 0;
|
||||
}
|
||||
|
||||
|
|
@ -233,13 +233,26 @@ public struct GameFontStyle
|
|||
_ => GameFontFamilyAndSize.Undefined,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new scaled instance of <see cref="GameFontStyle"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="scale">The scale.</param>
|
||||
/// <returns>The scaled instance.</returns>
|
||||
public readonly GameFontStyle Scale(float scale) => new()
|
||||
{
|
||||
FamilyAndSize = GetRecommendedFamilyAndSize(this.Family, this.SizePt * scale),
|
||||
SizePx = this.SizePx * scale,
|
||||
Weight = this.Weight,
|
||||
SkewStrength = this.SkewStrength * scale,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the adjustment to width resulting fron Weight and SkewStrength.
|
||||
/// </summary>
|
||||
/// <param name="header">Font header.</param>
|
||||
/// <param name="glyph">Glyph.</param>
|
||||
/// <returns>Width adjustment in pixel unit.</returns>
|
||||
public int CalculateBaseWidthAdjustment(in FdtReader.FontTableHeader header, in FdtReader.FontTableEntry glyph)
|
||||
public readonly int CalculateBaseWidthAdjustment(in FdtReader.FontTableHeader header, in FdtReader.FontTableEntry glyph)
|
||||
{
|
||||
var widthDelta = this.Weight;
|
||||
switch (this.BaseSkewStrength)
|
||||
|
|
@ -263,11 +276,11 @@ public struct GameFontStyle
|
|||
/// <param name="reader">Font information.</param>
|
||||
/// <param name="glyph">Glyph.</param>
|
||||
/// <returns>Width adjustment in pixel unit.</returns>
|
||||
public int CalculateBaseWidthAdjustment(FdtReader reader, FdtReader.FontTableEntry glyph) =>
|
||||
public readonly int CalculateBaseWidthAdjustment(FdtReader reader, FdtReader.FontTableEntry glyph) =>
|
||||
this.CalculateBaseWidthAdjustment(reader.FontHeader, glyph);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
public override readonly string ToString()
|
||||
{
|
||||
return $"GameFontStyle({this.FamilyAndSize}, {this.SizePt}pt, skew={this.SkewStrength}, weight={this.Weight})";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Interface.Utility;
|
||||
|
||||
using ImGuiNET;
|
||||
|
|
@ -44,6 +45,13 @@ public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit
|
|||
/// <returns>Same <see cref="ImFontPtr"/> with <paramref name="fontPtr"/>.</returns>
|
||||
ImFontPtr IgnoreGlobalScale(ImFontPtr fontPtr);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether global scaling is ignored for the given font.
|
||||
/// </summary>
|
||||
/// <param name="fontPtr">The font.</param>
|
||||
/// <returns>True if ignored.</returns>
|
||||
bool IsGlobalScaleIgnored(ImFontPtr fontPtr);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a font from memory region allocated using <see cref="ImGuiHelpers.AllocateMemory"/>.<br />
|
||||
/// <strong>It WILL crash if you try to use a memory pointer allocated in some other way.</strong><br />
|
||||
|
|
@ -120,7 +128,7 @@ public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit
|
|||
/// <summary>
|
||||
/// Adds the default font known to the current font atlas.<br />
|
||||
/// <br />
|
||||
/// Includes <see cref="AddFontAwesomeIconFont"/> and <see cref="AddExtraGlyphsForDalamudLanguage"/>.<br />
|
||||
/// Includes <see cref="AddFontAwesomeIconFont"/> and <see cref="AttachExtraGlyphsForDalamudLanguage"/>.<br />
|
||||
/// As this involves adding multiple fonts, calling this function will set <see cref="IFontAtlasBuildToolkit.Font"/>
|
||||
/// as the return value of this function, if it was empty before.
|
||||
/// </summary>
|
||||
|
|
@ -153,15 +161,26 @@ public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit
|
|||
|
||||
/// <summary>
|
||||
/// Adds the game's symbols into the provided font.<br />
|
||||
/// <see cref="SafeFontConfig.GlyphRanges"/> will be ignored.
|
||||
/// <see cref="SafeFontConfig.GlyphRanges"/> will be ignored.<br />
|
||||
/// If the game symbol font file is unavailable, only <see cref="SafeFontConfig.SizePx"/> will be honored.
|
||||
/// </summary>
|
||||
/// <param name="fontConfig">The font config.</param>
|
||||
void AddGameSymbol(in SafeFontConfig fontConfig);
|
||||
/// <returns>The added font.</returns>
|
||||
ImFontPtr AddGameSymbol(in SafeFontConfig fontConfig);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the game glyphs to the font.
|
||||
/// </summary>
|
||||
/// <param name="gameFontStyle">The font style.</param>
|
||||
/// <param name="glyphRanges">The glyph ranges.</param>
|
||||
/// <param name="mergeFont">The font to merge to. If empty, then a new font will be created.</param>
|
||||
/// <returns>The added font.</returns>
|
||||
ImFontPtr AddGameGlyphs(GameFontStyle gameFontStyle, ushort[]? glyphRanges, ImFontPtr mergeFont);
|
||||
|
||||
/// <summary>
|
||||
/// Adds glyphs of extra languages into the provided font, depending on Dalamud Configuration.<br />
|
||||
/// <see cref="SafeFontConfig.GlyphRanges"/> will be ignored.
|
||||
/// </summary>
|
||||
/// <param name="fontConfig">The font config.</param>
|
||||
void AddExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig);
|
||||
void AttachExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -271,6 +271,12 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnPreBuildCleanup(IFontAtlasBuildToolkitPreBuild toolkitPreBuild)
|
||||
{
|
||||
// irrelevant
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnPostBuild(IFontAtlasBuildToolkitPostBuild toolkitPostBuild)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -273,24 +273,21 @@ internal sealed partial class FontAtlasFactory
|
|||
/// <inheritdoc/>
|
||||
public ImFontPtr AddDalamudDefaultFont(float sizePx, ushort[]? glyphRanges)
|
||||
{
|
||||
ImFontPtr font;
|
||||
glyphRanges ??= this.factory.DefaultGlyphRanges;
|
||||
if (Service<InterfaceManager>.Get().UseAxis)
|
||||
{
|
||||
return this.gameFontHandleSubstance.GetOrCreateFont(
|
||||
new(GameFontFamily.Axis, sizePx),
|
||||
this);
|
||||
font = this.AddGameGlyphs(new(GameFontFamily.Axis, sizePx), glyphRanges, default);
|
||||
}
|
||||
else
|
||||
{
|
||||
font = this.AddDalamudAssetFont(
|
||||
DalamudAsset.NotoSansJpMedium,
|
||||
new() { SizePx = sizePx, GlyphRanges = glyphRanges });
|
||||
this.AddGameSymbol(new() { SizePx = sizePx, MergeFont = font });
|
||||
}
|
||||
|
||||
glyphRanges ??= this.factory.DefaultGlyphRanges;
|
||||
|
||||
var fontConfig = new SafeFontConfig
|
||||
{
|
||||
SizePx = sizePx,
|
||||
GlyphRanges = glyphRanges,
|
||||
};
|
||||
|
||||
var font = this.AddDalamudAssetFont(DalamudAsset.NotoSansJpMedium, fontConfig);
|
||||
this.AddExtraGlyphsForDalamudLanguage(fontConfig with { MergeFont = font });
|
||||
this.AddGameSymbol(fontConfig with { MergeFont = font });
|
||||
this.AttachExtraGlyphsForDalamudLanguage(new() { SizePx = sizePx, MergeFont = font });
|
||||
if (this.Font.IsNull())
|
||||
this.Font = font;
|
||||
return font;
|
||||
|
|
@ -315,11 +312,12 @@ internal sealed partial class FontAtlasFactory
|
|||
});
|
||||
|
||||
case DalamudAsset.LodestoneGameSymbol when !this.factory.HasGameSymbolsFontFile:
|
||||
return this.gameFontHandleSubstance.AttachGameSymbols(
|
||||
this,
|
||||
fontConfig.MergeFont,
|
||||
fontConfig.SizePx,
|
||||
fontConfig.GlyphRanges);
|
||||
{
|
||||
return this.AddGameGlyphs(
|
||||
new(GameFontFamily.Axis, fontConfig.SizePx),
|
||||
fontConfig.GlyphRanges,
|
||||
fontConfig.MergeFont);
|
||||
}
|
||||
|
||||
default:
|
||||
return this.factory.AddFont(
|
||||
|
|
@ -341,7 +339,8 @@ internal sealed partial class FontAtlasFactory
|
|||
});
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddGameSymbol(in SafeFontConfig fontConfig) => this.AddDalamudAssetFont(
|
||||
public ImFontPtr AddGameSymbol(in SafeFontConfig fontConfig) =>
|
||||
this.AddDalamudAssetFont(
|
||||
DalamudAsset.LodestoneGameSymbol,
|
||||
fontConfig with
|
||||
{
|
||||
|
|
@ -354,7 +353,11 @@ internal sealed partial class FontAtlasFactory
|
|||
});
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig)
|
||||
public ImFontPtr AddGameGlyphs(GameFontStyle gameFontStyle, ushort[]? glyphRanges, ImFontPtr mergeFont) =>
|
||||
this.gameFontHandleSubstance.AttachGameGlyphs(this, mergeFont, gameFontStyle, glyphRanges);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AttachExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig)
|
||||
{
|
||||
var dalamudConfiguration = Service<DalamudConfiguration>.Get();
|
||||
if (dalamudConfiguration.EffectiveLanguage == "ko")
|
||||
|
|
@ -377,6 +380,8 @@ internal sealed partial class FontAtlasFactory
|
|||
{
|
||||
foreach (var substance in this.data.Substances)
|
||||
substance.OnPreBuild(this);
|
||||
foreach (var substance in this.data.Substances)
|
||||
substance.OnPreBuildCleanup(this);
|
||||
}
|
||||
|
||||
public unsafe void PreBuild()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
|
||||
|
|
@ -207,15 +209,11 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
private readonly HashSet<GameFontStyle> gameFontStyles;
|
||||
|
||||
// Owned by this class, but ImFontPtr values still do not belong to this.
|
||||
private readonly Dictionary<GameFontStyle, ImFontPtr> fonts = new();
|
||||
private readonly Dictionary<GameFontStyle, FontDrawPlan> fonts = new();
|
||||
private readonly Dictionary<GameFontStyle, Exception?> buildExceptions = new();
|
||||
private readonly Dictionary<ImFontPtr, List<(ImFontPtr Font, ushort[]? Ranges)>> fontCopyTargets = new();
|
||||
private readonly List<(ImFontPtr Font, GameFontStyle Style, ushort[]? Ranges)> attachments = new();
|
||||
|
||||
private readonly HashSet<ImFontPtr> templatedFonts = new();
|
||||
private readonly Dictionary<ImFontPtr, List<(char From, char To)>> lateBuildRanges = new();
|
||||
|
||||
private readonly Dictionary<GameFontStyle, Dictionary<char, (int RectId, int FdtGlyphIndex)>> glyphRectIds =
|
||||
new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandleSubstance"/> class.
|
||||
|
|
@ -238,29 +236,22 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches game symbols to the given font.
|
||||
/// Attaches game symbols to the given font. If font is null, it will be created.
|
||||
/// </summary>
|
||||
/// <param name="toolkitPreBuild">The toolkitPostBuild.</param>
|
||||
/// <param name="font">The font to attach to.</param>
|
||||
/// <param name="sizePx">The font size in pixels.</param>
|
||||
/// <param name="style">The game font style.</param>
|
||||
/// <param name="glyphRanges">The intended glyph ranges.</param>
|
||||
/// <returns><paramref name="font"/> if it is not empty; otherwise a new font.</returns>
|
||||
public ImFontPtr AttachGameSymbols(
|
||||
public ImFontPtr AttachGameGlyphs(
|
||||
IFontAtlasBuildToolkitPreBuild toolkitPreBuild,
|
||||
ImFontPtr font,
|
||||
float sizePx,
|
||||
ushort[]? glyphRanges)
|
||||
GameFontStyle style,
|
||||
ushort[]? glyphRanges = null)
|
||||
{
|
||||
var style = new GameFontStyle(GameFontFamily.Axis, sizePx);
|
||||
var referenceFont = this.GetOrCreateFont(style, toolkitPreBuild);
|
||||
|
||||
if (font.IsNull())
|
||||
font = this.CreateTemplateFont(style, toolkitPreBuild);
|
||||
|
||||
if (!this.fontCopyTargets.TryGetValue(referenceFont, out var copyTargets))
|
||||
this.fontCopyTargets[referenceFont] = copyTargets = new();
|
||||
|
||||
copyTargets.Add((font, glyphRanges));
|
||||
font = this.CreateTemplateFont(toolkitPreBuild, style.SizePx);
|
||||
this.attachments.Add((font, style, glyphRanges));
|
||||
return font;
|
||||
}
|
||||
|
||||
|
|
@ -272,14 +263,20 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
/// <returns>The font.</returns>
|
||||
public ImFontPtr GetOrCreateFont(GameFontStyle style, IFontAtlasBuildToolkitPreBuild toolkitPreBuild)
|
||||
{
|
||||
if (this.fonts.TryGetValue(style, out var font))
|
||||
return font;
|
||||
|
||||
try
|
||||
{
|
||||
font = this.CreateFontPrivate(style, toolkitPreBuild, ' ', '\uFFFE', true);
|
||||
this.fonts.Add(style, font);
|
||||
return font;
|
||||
if (!this.fonts.TryGetValue(style, out var plan))
|
||||
{
|
||||
plan = new(
|
||||
style,
|
||||
toolkitPreBuild.Scale,
|
||||
this.handleManager.GameFontTextureProvider,
|
||||
this.CreateTemplateFont(toolkitPreBuild, style.SizePx));
|
||||
this.fonts[style] = plan;
|
||||
}
|
||||
|
||||
plan.AttachFont(plan.FullRangeFont);
|
||||
return plan.FullRangeFont;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -290,7 +287,9 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
|
||||
/// <inheritdoc/>
|
||||
public ImFontPtr GetFontPtr(IFontHandle handle) =>
|
||||
handle is GamePrebakedFontHandle ggfh ? this.fonts.GetValueOrDefault(ggfh.FontStyle) : default;
|
||||
handle is GamePrebakedFontHandle ggfh
|
||||
? this.fonts.GetValueOrDefault(ggfh.FontStyle)?.FullRangeFont ?? default
|
||||
: default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Exception? GetBuildException(IFontHandle handle) =>
|
||||
|
|
@ -315,6 +314,34 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnPreBuildCleanup(IFontAtlasBuildToolkitPreBuild toolkitPreBuild)
|
||||
{
|
||||
foreach (var (font, style, ranges) in this.attachments)
|
||||
{
|
||||
var effectiveStyle =
|
||||
toolkitPreBuild.IsGlobalScaleIgnored(font)
|
||||
? style.Scale(1 / toolkitPreBuild.Scale)
|
||||
: style;
|
||||
if (!this.fonts.TryGetValue(style, out var plan))
|
||||
{
|
||||
plan = new(
|
||||
effectiveStyle,
|
||||
toolkitPreBuild.Scale,
|
||||
this.handleManager.GameFontTextureProvider,
|
||||
this.CreateTemplateFont(toolkitPreBuild, style.SizePx));
|
||||
this.fonts[style] = plan;
|
||||
}
|
||||
|
||||
plan.AttachFont(font, ranges);
|
||||
}
|
||||
|
||||
foreach (var plan in this.fonts.Values)
|
||||
{
|
||||
plan.EnsureGlyphs(toolkitPreBuild.NewImAtlas);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe void OnPostBuild(IFontAtlasBuildToolkitPostBuild toolkitPostBuild)
|
||||
{
|
||||
|
|
@ -331,235 +358,19 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
|
||||
var pixels8Array = new byte*[toolkitPostBuild.NewImAtlas.Textures.Size];
|
||||
var widths = new int[toolkitPostBuild.NewImAtlas.Textures.Size];
|
||||
var heights = new int[toolkitPostBuild.NewImAtlas.Textures.Size];
|
||||
for (var i = 0; i < pixels8Array.Length; i++)
|
||||
toolkitPostBuild.NewImAtlas.GetTexDataAsAlpha8(i, out pixels8Array[i], out widths[i], out heights[i]);
|
||||
toolkitPostBuild.NewImAtlas.GetTexDataAsAlpha8(i, out pixels8Array[i], out widths[i], out _);
|
||||
|
||||
foreach (var (style, font) in this.fonts)
|
||||
foreach (var (style, plan) in this.fonts)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fas = GameFontStyle.GetRecommendedFamilyAndSize(
|
||||
style.Family,
|
||||
style.SizePt * toolkitPostBuild.Scale);
|
||||
var attr = fas.GetAttribute<GameFontFamilyAndSizeAttribute>();
|
||||
var horizontalOffset = attr?.HorizontalOffset ?? 0;
|
||||
var texCount = this.handleManager.GameFontTextureProvider.GetFontTextureCount(attr.TexPathFormat);
|
||||
using var handle = this.handleManager.GameFontTextureProvider.CreateFdtFileView(fas, out var fdt);
|
||||
ref var fdtFontHeader = ref fdt.FontHeader;
|
||||
var fdtGlyphs = fdt.Glyphs;
|
||||
var fontPtr = font.NativePtr;
|
||||
foreach (var font in plan.Ranges.Keys)
|
||||
this.PatchFontMetricsIfNecessary(style, font, toolkitPostBuild.Scale);
|
||||
|
||||
var glyphs = font.GlyphsWrapped();
|
||||
var scale = toolkitPostBuild.Scale * (style.SizePt / fdtFontHeader.Size);
|
||||
|
||||
fontPtr->FontSize = toolkitPostBuild.Scale * style.SizePx;
|
||||
if (fontPtr->ConfigData != null)
|
||||
fontPtr->ConfigData->SizePixels = fontPtr->FontSize;
|
||||
fontPtr->Ascent = fdtFontHeader.Ascent * scale;
|
||||
fontPtr->Descent = fdtFontHeader.Descent * scale;
|
||||
fontPtr->EllipsisChar = '…';
|
||||
|
||||
if (!allTexFiles.TryGetValue(attr.TexPathFormat, out var texFiles))
|
||||
allTexFiles.Add(attr.TexPathFormat, texFiles = ArrayPool<TexFile>.Shared.Rent(texCount));
|
||||
|
||||
if (this.glyphRectIds.TryGetValue(style, out var rectIdToGlyphs))
|
||||
{
|
||||
foreach (var (rectId, fdtGlyphIndex) in rectIdToGlyphs.Values)
|
||||
{
|
||||
ref var glyph = ref fdtGlyphs[fdtGlyphIndex];
|
||||
var rc = (ImGuiHelpers.ImFontAtlasCustomRectReal*)toolkitPostBuild.NewImAtlas
|
||||
.GetCustomRectByIndex(rectId)
|
||||
.NativePtr;
|
||||
var pixels8 = pixels8Array[rc->TextureIndex];
|
||||
var width = widths[rc->TextureIndex];
|
||||
texFiles[glyph.TextureFileIndex] ??=
|
||||
this.handleManager
|
||||
.GameFontTextureProvider
|
||||
.GetTexFile(attr.TexPathFormat, glyph.TextureFileIndex);
|
||||
var sourceBuffer = texFiles[glyph.TextureFileIndex].ImageData;
|
||||
var sourceBufferDelta = glyph.TextureChannelByteIndex;
|
||||
var widthAdjustment = style.CalculateBaseWidthAdjustment(fdtFontHeader, glyph);
|
||||
if (widthAdjustment == 0)
|
||||
{
|
||||
for (var y = 0; y < glyph.BoundingHeight; y++)
|
||||
{
|
||||
for (var x = 0; x < glyph.BoundingWidth; x++)
|
||||
{
|
||||
var a = sourceBuffer[
|
||||
sourceBufferDelta +
|
||||
(4 * (((glyph.TextureOffsetY + y) * fdtFontHeader.TextureWidth) +
|
||||
glyph.TextureOffsetX + x))];
|
||||
pixels8[((rc->Y + y) * width) + rc->X + x] = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var y = 0; y < glyph.BoundingHeight; y++)
|
||||
{
|
||||
for (var x = 0; x < glyph.BoundingWidth + widthAdjustment; x++)
|
||||
pixels8[((rc->Y + y) * width) + rc->X + x] = 0;
|
||||
}
|
||||
|
||||
for (int xbold = 0, xboldTo = Math.Max(1, (int)Math.Ceiling(style.Weight + 1));
|
||||
xbold < xboldTo;
|
||||
xbold++)
|
||||
{
|
||||
var boldStrength = Math.Min(1f, style.Weight + 1 - xbold);
|
||||
for (var y = 0; y < glyph.BoundingHeight; y++)
|
||||
{
|
||||
float xDelta = xbold;
|
||||
if (style.BaseSkewStrength > 0)
|
||||
{
|
||||
xDelta += style.BaseSkewStrength *
|
||||
(fdtFontHeader.LineHeight - glyph.CurrentOffsetY - y) /
|
||||
fdtFontHeader.LineHeight;
|
||||
}
|
||||
else if (style.BaseSkewStrength < 0)
|
||||
{
|
||||
xDelta -= style.BaseSkewStrength * (glyph.CurrentOffsetY + y) /
|
||||
fdtFontHeader.LineHeight;
|
||||
}
|
||||
|
||||
var xDeltaInt = (int)Math.Floor(xDelta);
|
||||
var xness = xDelta - xDeltaInt;
|
||||
for (var x = 0; x < glyph.BoundingWidth; x++)
|
||||
{
|
||||
var sourcePixelIndex =
|
||||
((glyph.TextureOffsetY + y) * fdtFontHeader.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] =
|
||||
Math.Max(pixels8[targetOffset], (byte)(boldStrength * n));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glyphs[rc->GlyphId].XY *= scale;
|
||||
glyphs[rc->GlyphId].AdvanceX *= scale;
|
||||
}
|
||||
}
|
||||
else if (this.lateBuildRanges.TryGetValue(font, out var buildRanges))
|
||||
{
|
||||
buildRanges.Sort();
|
||||
for (var i = 0; i < buildRanges.Count; i++)
|
||||
{
|
||||
var current = buildRanges[i];
|
||||
if (current.From > current.To)
|
||||
buildRanges[i] = (From: current.To, To: current.From);
|
||||
}
|
||||
|
||||
for (var i = 0; i < buildRanges.Count - 1; i++)
|
||||
{
|
||||
var current = buildRanges[i];
|
||||
var next = buildRanges[i + 1];
|
||||
if (next.From <= current.To)
|
||||
{
|
||||
buildRanges[i] = current with { To = next.To };
|
||||
buildRanges.RemoveAt(i + 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
var fdtTexSize = new Vector4(
|
||||
fdtFontHeader.TextureWidth,
|
||||
fdtFontHeader.TextureHeight,
|
||||
fdtFontHeader.TextureWidth,
|
||||
fdtFontHeader.TextureHeight);
|
||||
|
||||
if (!allTextureIndices.TryGetValue(attr.TexPathFormat, out var textureIndices))
|
||||
{
|
||||
allTextureIndices.Add(
|
||||
attr.TexPathFormat,
|
||||
textureIndices = ArrayPool<int>.Shared.Rent(texCount));
|
||||
textureIndices.AsSpan(0, texCount).Fill(-1);
|
||||
}
|
||||
|
||||
glyphs.EnsureCapacity(glyphs.Length + buildRanges.Sum(x => (x.To - x.From) + 1));
|
||||
foreach (var (rangeMin, rangeMax) in buildRanges)
|
||||
{
|
||||
var glyphIndex = fdt.FindGlyphIndex(rangeMin);
|
||||
if (glyphIndex < 0)
|
||||
glyphIndex = ~glyphIndex;
|
||||
var endIndex = fdt.FindGlyphIndex(rangeMax);
|
||||
if (endIndex < 0)
|
||||
endIndex = ~endIndex - 1;
|
||||
for (; glyphIndex <= endIndex; glyphIndex++)
|
||||
{
|
||||
var fdtg = fdtGlyphs[glyphIndex];
|
||||
|
||||
// If the glyph already exists in the target font, we do not overwrite.
|
||||
if (
|
||||
!(fdtg.Char == ' ' && this.templatedFonts.Contains(font))
|
||||
&& font.FindGlyphNoFallback(fdtg.Char).NativePtr is not null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ref var textureIndex = ref textureIndices[fdtg.TextureIndex];
|
||||
if (textureIndex == -1)
|
||||
{
|
||||
textureIndex = toolkitPostBuild.StoreTexture(
|
||||
this.handleManager
|
||||
.GameFontTextureProvider
|
||||
.NewFontTextureRef(attr.TexPathFormat, fdtg.TextureIndex),
|
||||
true);
|
||||
}
|
||||
|
||||
var glyph = new ImGuiHelpers.ImFontGlyphReal
|
||||
{
|
||||
AdvanceX = fdtg.AdvanceWidth,
|
||||
Codepoint = fdtg.Char,
|
||||
Colored = false,
|
||||
TextureIndex = textureIndex,
|
||||
Visible = true,
|
||||
X0 = horizontalOffset,
|
||||
Y0 = fdtg.CurrentOffsetY,
|
||||
U0 = fdtg.TextureOffsetX,
|
||||
V0 = fdtg.TextureOffsetY,
|
||||
U1 = fdtg.BoundingWidth,
|
||||
V1 = fdtg.BoundingHeight,
|
||||
};
|
||||
|
||||
glyph.XY1 = glyph.XY0 + glyph.UV1;
|
||||
glyph.UV1 += glyph.UV0;
|
||||
glyph.UV /= fdtTexSize;
|
||||
glyph.XY *= scale;
|
||||
glyph.AdvanceX *= scale;
|
||||
|
||||
glyphs.Add(glyph);
|
||||
}
|
||||
}
|
||||
|
||||
font.NativePtr->FallbackGlyph = null;
|
||||
|
||||
font.BuildLookupTable();
|
||||
}
|
||||
|
||||
foreach (var fallbackCharCandidate in FontAtlasFactory.FallbackCodepoints)
|
||||
{
|
||||
var glyph = font.FindGlyphNoFallback(fallbackCharCandidate);
|
||||
if ((IntPtr)glyph.NativePtr != IntPtr.Zero)
|
||||
{
|
||||
var ptr = font.NativePtr;
|
||||
ptr->FallbackChar = fallbackCharCandidate;
|
||||
ptr->FallbackGlyph = glyph.NativePtr;
|
||||
ptr->FallbackHotData =
|
||||
(ImFontGlyphHotData*)ptr->IndexedHotData.Address<ImGuiHelpers.ImFontGlyphHotDataReal>(
|
||||
fallbackCharCandidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
font.AdjustGlyphMetrics(1 / toolkitPostBuild.Scale, toolkitPostBuild.Scale);
|
||||
plan.SetFullRangeFontGlyphs(toolkitPostBuild, allTexFiles, allTextureIndices, pixels8Array, widths);
|
||||
plan.PostProcessFullRangeFont();
|
||||
plan.CopyGlyphsToRanges();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -567,32 +378,6 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
this.fonts[style] = default;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (source, targets) in this.fontCopyTargets)
|
||||
{
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (target.Ranges is null)
|
||||
{
|
||||
ImGuiHelpers.CopyGlyphsAcrossFonts(source, target.Font, missingOnly: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < target.Ranges.Length; i += 2)
|
||||
{
|
||||
if (target.Ranges[i] == 0)
|
||||
break;
|
||||
ImGuiHelpers.CopyGlyphsAcrossFonts(
|
||||
source,
|
||||
target.Font,
|
||||
true,
|
||||
true,
|
||||
target.Ranges[i],
|
||||
target.Ranges[i + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -601,103 +386,401 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
// Irrelevant
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a relevant <see cref="ImFontPtr"/> for the given <see cref="GameFontStyle"/>.
|
||||
/// </summary>
|
||||
/// <param name="style">The game font style.</param>
|
||||
/// <param name="toolkitPreBuild">The toolkitPostBuild.</param>
|
||||
/// <param name="minRange">Min range.</param>
|
||||
/// <param name="maxRange">Max range.</param>
|
||||
/// <param name="addExtraLanguageGlyphs">Add extra language glyphs.</param>
|
||||
/// <returns>The font.</returns>
|
||||
private ImFontPtr CreateFontPrivate(
|
||||
GameFontStyle style,
|
||||
IFontAtlasBuildToolkitPreBuild toolkitPreBuild,
|
||||
char minRange,
|
||||
char maxRange,
|
||||
bool addExtraLanguageGlyphs)
|
||||
{
|
||||
var font = toolkitPreBuild.IgnoreGlobalScale(this.CreateTemplateFont(style, toolkitPreBuild));
|
||||
|
||||
if (addExtraLanguageGlyphs)
|
||||
{
|
||||
var cfg = toolkitPreBuild.FindConfigPtr(font);
|
||||
toolkitPreBuild.AddExtraGlyphsForDalamudLanguage(new()
|
||||
{
|
||||
MergeFont = cfg.DstFont,
|
||||
SizePx = cfg.SizePixels,
|
||||
});
|
||||
}
|
||||
|
||||
var fas = GameFontStyle.GetRecommendedFamilyAndSize(style.Family, style.SizePt * toolkitPreBuild.Scale);
|
||||
var horizontalOffset = fas.GetAttribute<GameFontFamilyAndSizeAttribute>()?.HorizontalOffset ?? 0;
|
||||
using var handle = this.handleManager.GameFontTextureProvider.CreateFdtFileView(fas, out var fdt);
|
||||
ref var fdtFontHeader = ref fdt.FontHeader;
|
||||
var existing = new SortedSet<char>();
|
||||
|
||||
if (style is { Bold: false, Italic: false })
|
||||
{
|
||||
if (!this.lateBuildRanges.TryGetValue(font, out var ranges))
|
||||
this.lateBuildRanges[font] = ranges = new();
|
||||
|
||||
ranges.Add((minRange, maxRange));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.glyphRectIds.TryGetValue(style, out var rectIds))
|
||||
existing.UnionWith(rectIds.Keys);
|
||||
else
|
||||
rectIds = this.glyphRectIds[style] = new();
|
||||
|
||||
var glyphs = fdt.Glyphs;
|
||||
for (var fdtGlyphIndex = 0; fdtGlyphIndex < glyphs.Length; fdtGlyphIndex++)
|
||||
{
|
||||
ref var glyph = ref glyphs[fdtGlyphIndex];
|
||||
var cint = glyph.CharInt;
|
||||
if (cint < minRange || cint > maxRange)
|
||||
continue;
|
||||
|
||||
var c = (char)cint;
|
||||
if (existing.Contains(c))
|
||||
continue;
|
||||
|
||||
var widthAdjustment = style.CalculateBaseWidthAdjustment(fdtFontHeader, glyph);
|
||||
rectIds[c] = (
|
||||
toolkitPreBuild.NewImAtlas.AddCustomRectFontGlyph(
|
||||
font,
|
||||
c,
|
||||
glyph.BoundingWidth + widthAdjustment,
|
||||
glyph.BoundingHeight,
|
||||
glyph.AdvanceWidth,
|
||||
new(horizontalOffset, glyph.CurrentOffsetY)),
|
||||
fdtGlyphIndex);
|
||||
}
|
||||
}
|
||||
|
||||
var scale = toolkitPreBuild.Scale * (style.SizePt / fdt.FontHeader.Size);
|
||||
foreach (ref var kernPair in fdt.PairAdjustments)
|
||||
font.AddKerningPair(kernPair.Left, kernPair.Right, kernPair.RightOffset * scale);
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new template font.
|
||||
/// </summary>
|
||||
/// <param name="style">The game font style.</param>
|
||||
/// <param name="toolkitPreBuild">The toolkitPostBuild.</param>
|
||||
/// <param name="sizePx">The size of the font.</param>
|
||||
/// <returns>The font.</returns>
|
||||
private ImFontPtr CreateTemplateFont(GameFontStyle style, IFontAtlasBuildToolkitPreBuild toolkitPreBuild)
|
||||
private ImFontPtr CreateTemplateFont(IFontAtlasBuildToolkitPreBuild toolkitPreBuild, float sizePx)
|
||||
{
|
||||
var font = toolkitPreBuild.AddDalamudAssetFont(
|
||||
DalamudAsset.NotoSansJpMedium,
|
||||
new()
|
||||
{
|
||||
GlyphRanges = new ushort[] { ' ', ' ', '\0' },
|
||||
SizePx = style.SizePx * toolkitPreBuild.Scale,
|
||||
SizePx = sizePx,
|
||||
});
|
||||
this.templatedFonts.Add(font);
|
||||
return font;
|
||||
}
|
||||
|
||||
private unsafe void PatchFontMetricsIfNecessary(GameFontStyle style, ImFontPtr font, float atlasScale)
|
||||
{
|
||||
if (!this.templatedFonts.Contains(font))
|
||||
return;
|
||||
|
||||
var fas = style.Scale(atlasScale).FamilyAndSize;
|
||||
using var handle = this.handleManager.GameFontTextureProvider.CreateFdtFileView(fas, out var fdt);
|
||||
ref var fdtFontHeader = ref fdt.FontHeader;
|
||||
var fontPtr = font.NativePtr;
|
||||
|
||||
var scale = style.SizePt / fdtFontHeader.Size;
|
||||
fontPtr->Ascent = fdtFontHeader.Ascent * scale;
|
||||
fontPtr->Descent = fdtFontHeader.Descent * scale;
|
||||
fontPtr->EllipsisChar = '…';
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage(
|
||||
"StyleCop.CSharp.MaintainabilityRules",
|
||||
"SA1401:Fields should be private",
|
||||
Justification = "Internal")]
|
||||
private sealed class FontDrawPlan : IDisposable
|
||||
{
|
||||
public readonly GameFontStyle Style;
|
||||
public readonly GameFontStyle BaseStyle;
|
||||
public readonly GameFontFamilyAndSizeAttribute BaseAttr;
|
||||
public readonly int TexCount;
|
||||
public readonly Dictionary<ImFontPtr, BitArray> Ranges = new();
|
||||
public readonly List<(int RectId, int FdtGlyphIndex)> Rects = new();
|
||||
public readonly ushort[] RectLookup = new ushort[0x10000];
|
||||
public readonly FdtFileView Fdt;
|
||||
public readonly ImFontPtr FullRangeFont;
|
||||
|
||||
private readonly IDisposable fdtHandle;
|
||||
private readonly IGameFontTextureProvider gftp;
|
||||
|
||||
public FontDrawPlan(
|
||||
GameFontStyle style,
|
||||
float scale,
|
||||
IGameFontTextureProvider gameFontTextureProvider,
|
||||
ImFontPtr fullRangeFont)
|
||||
{
|
||||
this.Style = style;
|
||||
this.BaseStyle = style.Scale(scale);
|
||||
this.BaseAttr = this.BaseStyle.FamilyAndSize.GetAttribute<GameFontFamilyAndSizeAttribute>()!;
|
||||
this.gftp = gameFontTextureProvider;
|
||||
this.TexCount = this.gftp.GetFontTextureCount(this.BaseAttr.TexPathFormat);
|
||||
this.fdtHandle = this.gftp.CreateFdtFileView(this.BaseStyle.FamilyAndSize, out this.Fdt);
|
||||
this.RectLookup.AsSpan().Fill(ushort.MaxValue);
|
||||
this.FullRangeFont = fullRangeFont;
|
||||
this.Ranges[fullRangeFont] = new(0x10000);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.fdtHandle.Dispose();
|
||||
}
|
||||
|
||||
public void AttachFont(ImFontPtr font, ushort[]? glyphRanges = null)
|
||||
{
|
||||
if (!this.Ranges.TryGetValue(font, out var rangeBitArray))
|
||||
rangeBitArray = this.Ranges[font] = new(0x10000);
|
||||
|
||||
if (glyphRanges is null)
|
||||
{
|
||||
foreach (ref var g in this.Fdt.Glyphs)
|
||||
{
|
||||
var c = g.CharInt;
|
||||
if (c is >= 0x20 and <= 0xFFFE)
|
||||
rangeBitArray[c] = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < glyphRanges.Length - 1; i += 2)
|
||||
{
|
||||
if (glyphRanges[i] == 0)
|
||||
break;
|
||||
var from = (int)glyphRanges[i];
|
||||
var to = (int)glyphRanges[i + 1];
|
||||
for (var j = from; j <= to; j++)
|
||||
rangeBitArray[j] = true;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void EnsureGlyphs(ImFontAtlasPtr atlas)
|
||||
{
|
||||
var glyphs = this.Fdt.Glyphs;
|
||||
var ranges = this.Ranges[this.FullRangeFont];
|
||||
foreach (var (font, extraRange) in this.Ranges)
|
||||
{
|
||||
if (font.NativePtr != this.FullRangeFont.NativePtr)
|
||||
ranges.Or(extraRange);
|
||||
}
|
||||
|
||||
if (this.Style is not { Weight: 0, SkewStrength: 0 })
|
||||
{
|
||||
for (var fdtGlyphIndex = 0; fdtGlyphIndex < glyphs.Length; fdtGlyphIndex++)
|
||||
{
|
||||
ref var glyph = ref glyphs[fdtGlyphIndex];
|
||||
var cint = glyph.CharInt;
|
||||
if (cint > char.MaxValue)
|
||||
continue;
|
||||
if (!ranges[cint] || this.RectLookup[cint] != ushort.MaxValue)
|
||||
continue;
|
||||
|
||||
var widthAdjustment = this.BaseStyle.CalculateBaseWidthAdjustment(this.Fdt.FontHeader, glyph);
|
||||
this.RectLookup[cint] = (ushort)this.Rects.Count;
|
||||
this.Rects.Add(
|
||||
(
|
||||
atlas.AddCustomRectFontGlyph(
|
||||
this.FullRangeFont,
|
||||
(char)cint,
|
||||
glyph.BoundingWidth + widthAdjustment,
|
||||
glyph.BoundingHeight,
|
||||
glyph.AdvanceWidth,
|
||||
new(this.BaseAttr.HorizontalOffset, glyph.CurrentOffsetY)),
|
||||
fdtGlyphIndex));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var fdtGlyphIndex = 0; fdtGlyphIndex < glyphs.Length; fdtGlyphIndex++)
|
||||
{
|
||||
ref var glyph = ref glyphs[fdtGlyphIndex];
|
||||
var cint = glyph.CharInt;
|
||||
if (cint > char.MaxValue)
|
||||
continue;
|
||||
if (!ranges[cint] || this.RectLookup[cint] != ushort.MaxValue)
|
||||
continue;
|
||||
|
||||
this.RectLookup[cint] = (ushort)this.Rects.Count;
|
||||
this.Rects.Add((-1, fdtGlyphIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void PostProcessFullRangeFont()
|
||||
{
|
||||
var scale = this.Style.SizePt / this.Fdt.FontHeader.Size;
|
||||
foreach (ref var g in this.FullRangeFont.GlyphsWrapped().DataSpan)
|
||||
{
|
||||
g.XY *= scale;
|
||||
g.AdvanceX *= scale;
|
||||
}
|
||||
|
||||
var pfrf = this.FullRangeFont.NativePtr;
|
||||
ref var frf = ref *pfrf;
|
||||
pfrf->FallbackGlyph = null;
|
||||
ImGuiNative.ImFont_BuildLookupTable(pfrf);
|
||||
|
||||
foreach (var fallbackCharCandidate in FontAtlasFactory.FallbackCodepoints)
|
||||
{
|
||||
var glyph = ImGuiNative.ImFont_FindGlyphNoFallback(pfrf, fallbackCharCandidate);
|
||||
if ((nint)glyph == IntPtr.Zero)
|
||||
continue;
|
||||
frf.FallbackChar = fallbackCharCandidate;
|
||||
frf.FallbackGlyph = glyph;
|
||||
frf.FallbackHotData =
|
||||
(ImFontGlyphHotData*)frf.IndexedHotData.Address<ImGuiHelpers.ImFontGlyphHotDataReal>(
|
||||
fallbackCharCandidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void CopyGlyphsToRanges()
|
||||
{
|
||||
foreach (var (font, rangeBits) in this.Ranges)
|
||||
{
|
||||
if (font.NativePtr == this.FullRangeFont.NativePtr)
|
||||
continue;
|
||||
|
||||
var lookup = font.IndexLookupWrapped();
|
||||
var glyphs = font.GlyphsWrapped();
|
||||
foreach (ref var sourceGlyph in this.FullRangeFont.GlyphsWrapped().DataSpan)
|
||||
{
|
||||
if (!rangeBits[sourceGlyph.Codepoint])
|
||||
continue;
|
||||
|
||||
var glyphIndex = ushort.MaxValue;
|
||||
if (sourceGlyph.Codepoint < lookup.Length)
|
||||
glyphIndex = lookup[sourceGlyph.Codepoint];
|
||||
|
||||
if (glyphIndex == ushort.MaxValue)
|
||||
glyphs.Add(sourceGlyph);
|
||||
else
|
||||
glyphs[glyphIndex] = sourceGlyph;
|
||||
}
|
||||
|
||||
font.NativePtr->FallbackGlyph = null;
|
||||
font.BuildLookupTable();
|
||||
|
||||
foreach (var fallbackCharCandidate in FontAtlasFactory.FallbackCodepoints)
|
||||
{
|
||||
var glyph = font.FindGlyphNoFallback(fallbackCharCandidate).NativePtr;
|
||||
if ((nint)glyph == IntPtr.Zero)
|
||||
continue;
|
||||
|
||||
ref var frf = ref *font.NativePtr;
|
||||
frf.FallbackChar = fallbackCharCandidate;
|
||||
frf.FallbackGlyph = glyph;
|
||||
frf.FallbackHotData =
|
||||
(ImFontGlyphHotData*)frf.IndexedHotData.Address<ImGuiHelpers.ImFontGlyphHotDataReal>(
|
||||
fallbackCharCandidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void SetFullRangeFontGlyphs(
|
||||
IFontAtlasBuildToolkitPostBuild toolkitPostBuild,
|
||||
Dictionary<string, TexFile[]> allTexFiles,
|
||||
Dictionary<string, int[]> allTextureIndices,
|
||||
byte*[] pixels8Array,
|
||||
int[] widths)
|
||||
{
|
||||
var glyphs = this.FullRangeFont.GlyphsWrapped();
|
||||
var lookups = this.FullRangeFont.IndexLookupWrapped();
|
||||
|
||||
ref var fdtFontHeader = ref this.Fdt.FontHeader;
|
||||
var fdtGlyphs = this.Fdt.Glyphs;
|
||||
var fdtTexSize = new Vector4(
|
||||
this.Fdt.FontHeader.TextureWidth,
|
||||
this.Fdt.FontHeader.TextureHeight,
|
||||
this.Fdt.FontHeader.TextureWidth,
|
||||
this.Fdt.FontHeader.TextureHeight);
|
||||
|
||||
if (!allTexFiles.TryGetValue(this.BaseAttr.TexPathFormat, out var texFiles))
|
||||
{
|
||||
allTexFiles.Add(
|
||||
this.BaseAttr.TexPathFormat,
|
||||
texFiles = ArrayPool<TexFile>.Shared.Rent(this.TexCount));
|
||||
}
|
||||
|
||||
if (!allTextureIndices.TryGetValue(this.BaseAttr.TexPathFormat, out var textureIndices))
|
||||
{
|
||||
allTextureIndices.Add(
|
||||
this.BaseAttr.TexPathFormat,
|
||||
textureIndices = ArrayPool<int>.Shared.Rent(this.TexCount));
|
||||
textureIndices.AsSpan(0, this.TexCount).Fill(-1);
|
||||
}
|
||||
|
||||
var pixelWidth = Math.Max(1, (int)MathF.Ceiling(this.BaseStyle.Weight + 1));
|
||||
var pixelStrength = stackalloc byte[pixelWidth];
|
||||
for (var i = 0; i < pixelWidth; i++)
|
||||
pixelStrength[i] = (byte)(255 * Math.Min(1f, (this.BaseStyle.Weight + 1) - i));
|
||||
|
||||
var minGlyphY = 0;
|
||||
var maxGlyphY = 0;
|
||||
foreach (ref var g in fdtGlyphs)
|
||||
{
|
||||
minGlyphY = Math.Min(g.CurrentOffsetY, minGlyphY);
|
||||
maxGlyphY = Math.Max(g.BoundingHeight + g.CurrentOffsetY, maxGlyphY);
|
||||
}
|
||||
|
||||
var horzShift = stackalloc int[maxGlyphY - minGlyphY];
|
||||
var horzBlend = stackalloc byte[maxGlyphY - minGlyphY];
|
||||
horzShift -= minGlyphY;
|
||||
horzBlend -= minGlyphY;
|
||||
if (this.BaseStyle.BaseSkewStrength != 0)
|
||||
{
|
||||
for (var i = minGlyphY; i < maxGlyphY; i++)
|
||||
{
|
||||
float blend = this.BaseStyle.BaseSkewStrength switch
|
||||
{
|
||||
> 0 => fdtFontHeader.LineHeight - i,
|
||||
< 0 => -i,
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
blend *= this.BaseStyle.BaseSkewStrength / fdtFontHeader.LineHeight;
|
||||
horzShift[i] = (int)MathF.Floor(blend);
|
||||
horzBlend[i] = (byte)(255 * (blend - horzShift[i]));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (rectId, fdtGlyphIndex) in this.Rects)
|
||||
{
|
||||
ref var fdtGlyph = ref fdtGlyphs[fdtGlyphIndex];
|
||||
if (rectId == -1)
|
||||
{
|
||||
ref var textureIndex = ref textureIndices[fdtGlyph.TextureIndex];
|
||||
if (textureIndex == -1)
|
||||
{
|
||||
textureIndex = toolkitPostBuild.StoreTexture(
|
||||
this.gftp.NewFontTextureRef(this.BaseAttr.TexPathFormat, fdtGlyph.TextureIndex),
|
||||
true);
|
||||
}
|
||||
|
||||
var glyph = new ImGuiHelpers.ImFontGlyphReal
|
||||
{
|
||||
AdvanceX = fdtGlyph.AdvanceWidth,
|
||||
Codepoint = fdtGlyph.Char,
|
||||
Colored = false,
|
||||
TextureIndex = textureIndex,
|
||||
Visible = true,
|
||||
X0 = this.BaseAttr.HorizontalOffset,
|
||||
Y0 = fdtGlyph.CurrentOffsetY,
|
||||
U0 = fdtGlyph.TextureOffsetX,
|
||||
V0 = fdtGlyph.TextureOffsetY,
|
||||
U1 = fdtGlyph.BoundingWidth,
|
||||
V1 = fdtGlyph.BoundingHeight,
|
||||
};
|
||||
|
||||
glyph.XY1 = glyph.XY0 + glyph.UV1;
|
||||
glyph.UV1 += glyph.UV0;
|
||||
glyph.UV /= fdtTexSize;
|
||||
|
||||
glyphs.Add(glyph);
|
||||
}
|
||||
else
|
||||
{
|
||||
ref var rc = ref *(ImGuiHelpers.ImFontAtlasCustomRectReal*)toolkitPostBuild.NewImAtlas
|
||||
.GetCustomRectByIndex(rectId)
|
||||
.NativePtr;
|
||||
var widthAdjustment = this.BaseStyle.CalculateBaseWidthAdjustment(fdtFontHeader, fdtGlyph);
|
||||
|
||||
// Glyph is scaled at this point; undo that.
|
||||
ref var glyph = ref glyphs[lookups[rc.GlyphId]];
|
||||
glyph.X0 = this.BaseAttr.HorizontalOffset;
|
||||
glyph.Y0 = fdtGlyph.CurrentOffsetY;
|
||||
glyph.X1 = glyph.X0 + fdtGlyph.BoundingWidth + widthAdjustment;
|
||||
glyph.Y1 = glyph.Y0 + fdtGlyph.BoundingHeight;
|
||||
glyph.AdvanceX = fdtGlyph.AdvanceWidth;
|
||||
|
||||
var pixels8 = pixels8Array[rc.TextureIndex];
|
||||
var width = widths[rc.TextureIndex];
|
||||
texFiles[fdtGlyph.TextureFileIndex] ??=
|
||||
this.gftp.GetTexFile(this.BaseAttr.TexPathFormat, fdtGlyph.TextureFileIndex);
|
||||
var sourceBuffer = texFiles[fdtGlyph.TextureFileIndex].ImageData;
|
||||
var sourceBufferDelta = fdtGlyph.TextureChannelByteIndex;
|
||||
|
||||
for (var y = 0; y < fdtGlyph.BoundingHeight; y++)
|
||||
{
|
||||
var sourcePixelIndex =
|
||||
((fdtGlyph.TextureOffsetY + y) * fdtFontHeader.TextureWidth) + fdtGlyph.TextureOffsetX;
|
||||
sourcePixelIndex *= 4;
|
||||
sourcePixelIndex += sourceBufferDelta;
|
||||
var blend1 = horzBlend[fdtGlyph.CurrentOffsetY + y];
|
||||
|
||||
var targetOffset = ((rc.Y + y) * width) + rc.X;
|
||||
for (var x = 0; x < rc.Width; x++)
|
||||
pixels8[targetOffset + x] = 0;
|
||||
|
||||
targetOffset += horzShift[fdtGlyph.CurrentOffsetY + y];
|
||||
if (blend1 == 0)
|
||||
{
|
||||
for (var x = 0; x < fdtGlyph.BoundingWidth; x++, sourcePixelIndex += 4, targetOffset++)
|
||||
{
|
||||
var n = sourceBuffer[sourcePixelIndex + 4];
|
||||
for (var boldOffset = 0; boldOffset < pixelWidth; boldOffset++)
|
||||
{
|
||||
ref var p = ref pixels8[targetOffset + boldOffset];
|
||||
p = Math.Max(p, (byte)((pixelStrength[boldOffset] * n) / 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var blend2 = 255 - blend1;
|
||||
for (var x = 0; x < fdtGlyph.BoundingWidth; x++, sourcePixelIndex += 4, targetOffset++)
|
||||
{
|
||||
var a1 = sourceBuffer[sourcePixelIndex];
|
||||
var a2 = x == fdtGlyph.BoundingWidth - 1 ? 0 : sourceBuffer[sourcePixelIndex + 4];
|
||||
var n = (a1 * blend1) + (a2 * blend2);
|
||||
|
||||
for (var boldOffset = 0; boldOffset < pixelWidth; boldOffset++)
|
||||
{
|
||||
ref var p = ref pixels8[targetOffset + boldOffset];
|
||||
p = Math.Max(p, (byte)((pixelStrength[boldOffset] * n) / 255 / 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,13 @@ internal interface IFontHandleSubstance : IDisposable
|
|||
/// <param name="toolkitPreBuild">The toolkit.</param>
|
||||
void OnPreBuild(IFontAtlasBuildToolkitPreBuild toolkitPreBuild);
|
||||
|
||||
/// <summary>
|
||||
/// Called between <see cref="OnPreBuild"/> and <see cref="ImFontAtlasPtr.Build"/> calls.<br />
|
||||
/// Any further modification to <see cref="IFontAtlasBuildToolkit.Fonts"/> will result in undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="toolkitPreBuild">The toolkit.</param>
|
||||
void OnPreBuildCleanup(IFontAtlasBuildToolkitPreBuild toolkitPreBuild);
|
||||
|
||||
/// <summary>
|
||||
/// Called after <see cref="ImFontAtlasPtr.Build"/> call.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -37,10 +37,14 @@ public struct SafeFontConfig
|
|||
/// </summary>
|
||||
/// <param name="config">Config to copy from.</param>
|
||||
public unsafe SafeFontConfig(ImFontConfigPtr config)
|
||||
: this()
|
||||
{
|
||||
if (config.NativePtr is not null)
|
||||
{
|
||||
this.Raw = *config.NativePtr;
|
||||
this.Raw.GlyphRanges = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of font within a TTF/OTF file.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue