Eliminate boxed production surface-override enumeration, retain vital modifier projections until the enchantment registry mutates, and measure DAT font widths without allocating a captured delegate. Preserve exact hashes, spell stacking, and glyph advances with warmed zero-allocation tests. Validated by the focused rendering, UI, and spell suites, a zero-error Release build, and 8,409 passing Release tests with five pre-existing skips. Co-authored-by: Codex <codex@openai.com>
115 lines
3.8 KiB
C#
115 lines
3.8 KiB
C#
using System.Collections.Generic;
|
|
using AcDream.App.UI;
|
|
using DatReaderWriter.Types;
|
|
|
|
namespace AcDream.App.Tests.UI;
|
|
|
|
/// <summary>
|
|
/// Pure pen-advance / MeasureWidth math for the retail dat font (no GL, no dat).
|
|
/// The advance per glyph is the retail
|
|
/// <c>HorizontalOffsetBefore + Width + HorizontalOffsetAfter</c>
|
|
/// (SurfaceWindow::DrawCharacter, acclient 0x00442c3a), accumulated across the
|
|
/// string the way the retail string loop does (0x00467ed4 edi_3 += var_98).
|
|
/// </summary>
|
|
public class UiDatFontTests
|
|
{
|
|
private static FontCharDesc Glyph(
|
|
ushort unicode, byte width,
|
|
sbyte before = 0, sbyte after = 0,
|
|
ushort offsetX = 0, ushort offsetY = 0, byte height = 16, sbyte vBefore = 0)
|
|
=> new()
|
|
{
|
|
Unicode = unicode,
|
|
Width = width,
|
|
Height = height,
|
|
OffsetX = offsetX,
|
|
OffsetY = offsetY,
|
|
HorizontalOffsetBefore = before,
|
|
HorizontalOffsetAfter = after,
|
|
VerticalOffsetBefore = vBefore,
|
|
};
|
|
|
|
[Fact]
|
|
public void GlyphAdvance_SumsBeforeWidthAfter()
|
|
{
|
|
var g = Glyph('A', width: 8, before: 1, after: 2);
|
|
Assert.Equal(11f, UiDatFont.GlyphAdvance(g));
|
|
}
|
|
|
|
[Fact]
|
|
public void GlyphAdvance_HandlesNegativeBearings()
|
|
{
|
|
// Kerned glyph: a negative left-bearing pulls it leftward; the advance
|
|
// still nets out to before + width + after.
|
|
var g = Glyph('j', width: 4, before: -1, after: 0);
|
|
Assert.Equal(3f, UiDatFont.GlyphAdvance(g));
|
|
}
|
|
|
|
[Fact]
|
|
public void MeasureWidth_SumsEachGlyphAdvance()
|
|
{
|
|
var table = new Dictionary<char, FontCharDesc>
|
|
{
|
|
['2'] = Glyph('2', width: 7, before: 1, after: 1), // advance 9
|
|
['9'] = Glyph('9', width: 7, before: 1, after: 1), // advance 9
|
|
['1'] = Glyph('1', width: 3, before: 2, after: 1), // advance 6
|
|
['/'] = Glyph('/', width: 4, before: 0, after: 1), // advance 5
|
|
};
|
|
FontCharDesc? Lookup(char c) => table.TryGetValue(c, out var g) ? g : null;
|
|
|
|
// "291/291" = 9 + 9 + 6 + 5 + 9 + 9 + 6 = 53
|
|
Assert.Equal(53f, UiDatFont.MeasureWidth("291/291", Lookup));
|
|
}
|
|
|
|
[Fact]
|
|
public void MeasureWidth_SkipsCharactersNotInFont()
|
|
{
|
|
var table = new Dictionary<char, FontCharDesc>
|
|
{
|
|
['5'] = Glyph('5', width: 6, before: 1, after: 1), // advance 8
|
|
};
|
|
FontCharDesc? Lookup(char c) => table.TryGetValue(c, out var g) ? g : null;
|
|
|
|
// 'X' has no glyph → contributes nothing; only the two '5's count.
|
|
Assert.Equal(16f, UiDatFont.MeasureWidth("5X5", Lookup));
|
|
}
|
|
|
|
[Fact]
|
|
public void MeasureWidth_EmptyOrNullIsZero()
|
|
{
|
|
FontCharDesc? Lookup(char c) => null;
|
|
Assert.Equal(0f, UiDatFont.MeasureWidth("", Lookup));
|
|
Assert.Equal(0f, UiDatFont.MeasureWidth(null, Lookup));
|
|
}
|
|
|
|
[Fact]
|
|
public void InstanceMeasureWidth_ReusesGlyphTableWithoutAllocating()
|
|
{
|
|
var glyphs = new Dictionary<char, FontCharDesc>
|
|
{
|
|
['A'] = Glyph('A', width: 8, before: 1, after: 2),
|
|
['B'] = Glyph('B', width: 7, before: 1, after: 1),
|
|
};
|
|
var font = new UiDatFont(
|
|
fgTex: 0,
|
|
fgW: 0,
|
|
fgH: 0,
|
|
bgTex: 0,
|
|
bgW: 0,
|
|
bgH: 0,
|
|
lineHeight: 16f,
|
|
baselineOffset: 12f,
|
|
glyphs);
|
|
const string Text = "ABBA";
|
|
float expected = font.MeasureWidth(Text);
|
|
float actual = 0f;
|
|
long before = GC.GetAllocatedBytesForCurrentThread();
|
|
|
|
for (int iteration = 0; iteration < 10_000; iteration++)
|
|
actual = font.MeasureWidth(Text);
|
|
|
|
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
|
|
Assert.Equal(expected, actual);
|
|
Assert.Equal(0, allocated);
|
|
}
|
|
}
|