Fix oversized GDI glyph buffers
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Age.Engine.Model;
|
||||
using Age.Engine.Text;
|
||||
using Age.Engine.Text.Windows;
|
||||
|
||||
@@ -73,6 +74,48 @@ public class WindowsGdiGlyphMaskRasterizerTests
|
||||
Assert.All(space.Coverage.ToArray(), value => Assert.Equal(0, value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Sc0000OpeningNarrationLineRasterizesEveryGlyph()
|
||||
{
|
||||
if (!WindowsGdiGlyphMaskRasterizer.TryGetAvailability(out _)) return;
|
||||
|
||||
const string text =
|
||||
"だが怨嗟に満ちた言葉は、呪いのように彼らの心に暗い影を落とした。";
|
||||
var style = AdvTextStyle.Default with
|
||||
{
|
||||
PrimaryFontSize = 24,
|
||||
Bold = true,
|
||||
FontFace = "MS 明朝",
|
||||
RenderMode = 3,
|
||||
LineSpacing = 8,
|
||||
};
|
||||
|
||||
using var rasterizer = new WindowsGdiGlyphMaskRasterizer();
|
||||
foreach (GlyphRasterRequest request in
|
||||
ImmediateSurfaceTextRenderer.CreateRequests(text, style))
|
||||
{
|
||||
Exception? error = Record.Exception(() => rasterizer.Rasterize(request));
|
||||
Assert.True(
|
||||
error == null,
|
||||
$"U+{request.UnicodeScalar:X4} CP932 0x{request.Cp932Code:X4}: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gray4StorageBeyondMetricRowsIsTrimmed()
|
||||
{
|
||||
if (!WindowsGdiGlyphMaskRasterizer.TryGetAvailability(out _)) return;
|
||||
|
||||
GlyphRasterRequest request = NativeRequest("MS 明朝", 24, -12, 700, 'p');
|
||||
DirectGlyph direct = RasterizeUnicodeDirect("MS 明朝", 24, -12, 700, 'p');
|
||||
using var rasterizer = new WindowsGdiGlyphMaskRasterizer();
|
||||
GlyphMask actual = rasterizer.Rasterize(request);
|
||||
|
||||
Assert.True(direct.RawBufferSize > direct.Coverage.Length);
|
||||
Assert.Equal(direct.Coverage, actual.Coverage.ToArray());
|
||||
Assert.Equal(actual.Stride * actual.Height, actual.Coverage.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FontHandlesUseTheSharedBoundedLruAndDisposeCleanly()
|
||||
{
|
||||
@@ -135,17 +178,19 @@ public class WindowsGdiGlyphMaskRasterizerTests
|
||||
int glyphWidth = checked((int)metrics.BlackBoxX);
|
||||
int glyphHeight = checked((int)metrics.BlackBoxY);
|
||||
int stride = checked((glyphWidth + 3) & ~3);
|
||||
Assert.Equal(checked(stride * glyphHeight), (int)size);
|
||||
byte[] coverage = new byte[size];
|
||||
int expectedBytes = checked(stride * glyphHeight);
|
||||
Assert.True(size >= expectedBytes);
|
||||
byte[] rawCoverage = new byte[size];
|
||||
if (size > 0)
|
||||
{
|
||||
identity = Mat2.Identity;
|
||||
uint written = Native.GetGlyphOutlineW(
|
||||
dc, (uint)scalar, GgoGray4Bitmap,
|
||||
out GlyphMetrics second, size, coverage, ref identity);
|
||||
out GlyphMetrics second, size, rawCoverage, ref identity);
|
||||
Assert.Equal(size, written);
|
||||
Assert.Equal(metrics, second);
|
||||
}
|
||||
byte[] coverage = rawCoverage.AsSpan(0, expectedBytes).ToArray();
|
||||
|
||||
string text = char.ConvertFromUtf32(scalar);
|
||||
if (!Native.GetTextExtentPoint32W(dc, text, text.Length, out NativeSize cell))
|
||||
@@ -154,7 +199,7 @@ public class WindowsGdiGlyphMaskRasterizerTests
|
||||
glyphWidth, glyphHeight, stride,
|
||||
metrics.GlyphOrigin.X, metrics.GlyphOrigin.Y,
|
||||
metrics.CellIncrementX, metrics.CellIncrementY,
|
||||
cell.Width, cell.Height, coverage);
|
||||
cell.Width, cell.Height, coverage, checked((int)size));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -178,7 +223,8 @@ public class WindowsGdiGlyphMaskRasterizerTests
|
||||
int CellAdvanceY,
|
||||
int CellWidth,
|
||||
int CellHeight,
|
||||
byte[] Coverage);
|
||||
byte[] Coverage,
|
||||
int RawBufferSize);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct NativePoint
|
||||
|
||||
@@ -126,22 +126,31 @@ public sealed class WindowsGdiGlyphMaskRasterizer
|
||||
int expectedBytes = checked(stride * height);
|
||||
// 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)
|
||||
// Preserve those metrics and materialize the implied transparent mask. GDI can also
|
||||
// return storage beyond the metric-defined rows (for example, bold 24px MS Mincho 'p'
|
||||
// reports 12x15 but requests 192 bytes rather than 180). Treat the queried size as the
|
||||
// allocation contract and the GLYPHMETRICS box as the raster-consumption contract: read
|
||||
// exactly the requested buffer, then retain only the rows described by the metrics. Bytes
|
||||
// outside that box are not drawable coverage and are not required to be initialized to zero.
|
||||
if (size != 0 && size < expectedBytes)
|
||||
throw new InvalidOperationException(
|
||||
$"GDI gray-4 buffer size {size} disagrees with {width}x{height}, stride {stride}.");
|
||||
$"GDI gray-4 buffer size {size} for CP932 0x{code:x4} " +
|
||||
$"('{request.FontFace}', {request.PixelHeight}px, width {request.RequestedWidth}, " +
|
||||
$"weight {request.Weight}) is smaller than its {width}x{height} metric box, " +
|
||||
$"stride {stride} ({expectedBytes} bytes).");
|
||||
|
||||
byte[] coverage = new byte[expectedBytes];
|
||||
if (size > 0)
|
||||
{
|
||||
byte[] gdiBuffer = new byte[checked((int)size)];
|
||||
identity = Mat2.Identity;
|
||||
uint written = NativeMethods.GetGlyphOutlineA(
|
||||
_displayIc.DangerousGetHandle(), code, GgoGray4Bitmap,
|
||||
out GlyphMetrics secondMetrics, size, coverage, ref identity);
|
||||
out GlyphMetrics secondMetrics, size, gdiBuffer, ref identity);
|
||||
if (written == GdiError) ThrowWin32($"GetGlyphOutlineA read failed for CP932 0x{code:x4}");
|
||||
if (written != size || !metrics.Equals(secondMetrics))
|
||||
throw new InvalidOperationException("GDI glyph metrics changed between query and read.");
|
||||
gdiBuffer.AsSpan(0, expectedBytes).CopyTo(coverage);
|
||||
}
|
||||
|
||||
byte[] encoded = EncodeCp932Code(code);
|
||||
|
||||
Reference in New Issue
Block a user