Fix GDI zero-ink glyph handling

This commit is contained in:
gamer147
2026-08-15 10:30:32 -04:00
parent d38511665a
commit 3289948ce7
5 changed files with 107 additions and 8 deletions

View File

@@ -7,6 +7,36 @@ using Xunit;
public class DebugSceneLaunchTests
{
private sealed class ReachedDebugMapPage8ContinuationException : Exception { }
private sealed class LaunchDebugMapAndStopAfterPage8Sink : ITraceSink
{
public VirtualMachine Vm = null!;
public bool TracingSteps => true;
public void Emit(in TraceEvent e)
{
if (e.Kind == TraceEventKind.FrameEnter
&& e.Name?.Equals("TITLE.BIN", StringComparison.OrdinalIgnoreCase) == true)
{
DebugFrameSnapshot frame = Assert.IsType<DebugFrameSnapshot>(Vm.DebugFrame);
Assert.True(Vm.TryRequestDebugFrameReturn(frame.FrameId, new Dictionary<int, long>
{
[0] = 1,
[0xaba5c] = -1,
[0x62ccf] = 0,
[0x699] = 0x338c,
}));
}
if (e.Kind == TraceEventKind.Step
&& e.Ins?.Offset == 0x5b25
&& Vm.DebugFrame?.CurrentScript.Equals(
"SC0270.BIN", StringComparison.OrdinalIgnoreCase) == true)
throw new ReachedDebugMapPage8ContinuationException();
}
}
private static Operand I(long value) => new(0, value);
private static Operand G(long address) => new(3, address);
private static (int, Operand[]) Call(Operand id) => (0x3, new[] { id });
@@ -15,6 +45,24 @@ public class DebugSceneLaunchTests
private static (int, Operand[]) Sleep() => (0xc8, new[] { I(1) });
private static (int, Operand[]) Exit() => (0x2, Array.Empty<Operand>());
[Fact]
[Trait("Category", "Workspace")]
public void RealDebugMapLaunchContinuesPastSc0270Page8Wait()
{
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
var scripts = Sys4ScriptProvider.Load(table);
var sink = new LaunchDebugMapAndStopAfterPage8Sink();
var vm = new VirtualMachine(
scripts.RequireByName("SYSTEM4.BIN"), table, new CaptureHost(),
new VmOptions(MaxSteps: 1_000_000), scripts, sink);
sink.Vm = vm;
Exception? exception = Record.Exception(() => vm.Run());
Assert.IsType<ReachedDebugMapPage8ContinuationException>(exception);
Assert.Null(vm.HaltReason);
}
[Fact]
public async Task ParkedTitleFrameReturnsToCoordinatorWhichDispatchesSelectedScript()
{

View File

@@ -60,6 +60,19 @@ public class WindowsGdiGlyphMaskRasterizerTests
Assert.All(actual.Coverage.ToArray(), value => Assert.InRange(value, (byte)0, (byte)16));
}
[Fact]
public void IdeographicSpacePreservesAdvanceWithTransparentCoverage()
{
if (!WindowsGdiGlyphMaskRasterizer.TryGetAvailability(out _)) return;
using var rasterizer = new WindowsGdiGlyphMaskRasterizer();
GlyphMask space = rasterizer.Rasterize(
NativeRequest(" 明朝", 24, -12, 0, 0x3000));
Assert.True(space.CellAdvanceX > 0);
Assert.All(space.Coverage.ToArray(), value => Assert.Equal(0, value));
}
[Fact]
public void FontHandlesUseTheSharedBoundedLruAndDisposeCleanly()
{

View File

@@ -124,7 +124,11 @@ public sealed class WindowsGdiGlyphMaskRasterizer
int height = checked((int)metrics.BlackBoxY);
int stride = checked((width + 3) & ~3);
int expectedBytes = checked(stride * height);
if (size != expectedBytes)
// CP932 0x8140 (U+3000 IDEOGRAPHIC SPACE) is a zero-ink spacing glyph. GDI reports
// its placement as a nominal 1x1 black box but returns a zero-byte required buffer.
// Preserve those metrics and materialize the implied transparent mask; a nonzero
// short/oversized payload still means that the bitmap contract is inconsistent.
if (size != 0 && size != expectedBytes)
throw new InvalidOperationException(
$"GDI gray-4 buffer size {size} disagrees with {width}x{height}, stride {stride}.");