csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
185 lines
6.0 KiB
185 lines
6.0 KiB
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using Avalonia.Media.Fonts;
|
|
using Xunit;
|
|
|
|
namespace Avalonia.Base.UnitTests.Media.Fonts
|
|
{
|
|
public class UnmanagedFontMemoryTests
|
|
{
|
|
private static byte[] BuildFont(OpenTypeTag tag, byte[] tableData)
|
|
{
|
|
const int recordsStart = 12;
|
|
const int numTables = 1;
|
|
var directoryBytes = recordsStart + numTables * 16; // 12 + 16 = 28
|
|
var offset = directoryBytes;
|
|
var result = new byte[offset + tableData.Length];
|
|
|
|
// Simple SFNT header (version 0x00010000)
|
|
result[0] = 0;
|
|
result[1] = 1;
|
|
result[2] = 0;
|
|
result[3] = 0;
|
|
// numTables (big-endian)
|
|
result[4] = 0;
|
|
result[5] = 1;
|
|
// rest of header (6 bytes) left as zero
|
|
|
|
// Table record at offset 12
|
|
uint v = tag;
|
|
result[12] = (byte)(v >> 24);
|
|
result[13] = (byte)(v >> 16);
|
|
result[14] = (byte)(v >> 8);
|
|
result[15] = (byte)v;
|
|
|
|
// checksum (4 bytes) left as zero
|
|
|
|
// offset (big-endian) at bytes 20..23
|
|
result[20] = (byte)(offset >> 24);
|
|
result[21] = (byte)(offset >> 16);
|
|
result[22] = (byte)(offset >> 8);
|
|
result[23] = (byte)offset;
|
|
|
|
// length (big-endian) at bytes 24..27
|
|
var len = tableData.Length;
|
|
result[24] = (byte)(len >> 24);
|
|
result[25] = (byte)(len >> 16);
|
|
result[26] = (byte)(len >> 8);
|
|
result[27] = (byte)len;
|
|
|
|
Buffer.BlockCopy(tableData, 0, result, offset, len);
|
|
|
|
return result;
|
|
}
|
|
|
|
[Fact]
|
|
public unsafe void TryGetTable_ReturnsTableData_WhenExists()
|
|
{
|
|
var tag = OpenTypeTag.Parse("test");
|
|
var data = new byte[] { 1, 2, 3, 4, 5 };
|
|
var font = BuildFont(tag, data);
|
|
|
|
using var ms = new MemoryStream(font);
|
|
using var mem = UnmanagedFontMemory.LoadFromStream(ms);
|
|
|
|
Assert.True(mem.TryGetTable(tag, out var table));
|
|
Assert.Equal(data, table.ToArray());
|
|
|
|
// Second call should also succeed (cache path)
|
|
Assert.True(mem.TryGetTable(tag, out var table2));
|
|
Assert.Equal(table.Length, table2.Length);
|
|
|
|
// Ensure both ReadOnlyMemory instances reference the same underlying memory
|
|
ref byte r1 = ref MemoryMarshal.GetReference(table.Span);
|
|
ref byte r2 = ref MemoryMarshal.GetReference(table2.Span);
|
|
|
|
fixed (byte* p1 = &r1)
|
|
fixed (byte* p2 = &r2)
|
|
{
|
|
Assert.Equal((IntPtr)p1, (IntPtr)p2);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void TryGetTable_ReturnsFalse_ForUnknownTag()
|
|
{
|
|
var tag = OpenTypeTag.Parse("TEST");
|
|
var other = OpenTypeTag.Parse("OTHR");
|
|
var data = new byte[] { 9, 8, 7 };
|
|
var font = BuildFont(tag, data);
|
|
|
|
using var ms = new MemoryStream(font);
|
|
using var mem = UnmanagedFontMemory.LoadFromStream(ms);
|
|
|
|
Assert.False(mem.TryGetTable(other, out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void TryGetTable_ReturnsFalse_ForInvalidFont()
|
|
{
|
|
// Too short to be a valid SFNT
|
|
var shortData = new byte[8];
|
|
|
|
using var ms = new MemoryStream(shortData);
|
|
using var mem = UnmanagedFontMemory.LoadFromStream(ms);
|
|
|
|
Assert.False(mem.TryGetTable(OpenTypeTag.Parse("test"), out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetSpan_ReturnsUnderlyingData()
|
|
{
|
|
var tag = OpenTypeTag.Parse("span");
|
|
var tableData = Enumerable.Range(0, 64).Select(i => (byte)i).ToArray();
|
|
var font = BuildFont(tag, tableData);
|
|
|
|
using var ms = new MemoryStream(font);
|
|
using var mem = UnmanagedFontMemory.LoadFromStream(ms);
|
|
|
|
var span = mem.GetSpan();
|
|
Assert.Equal(font.Length, span.Length);
|
|
Assert.Equal(font, span.ToArray());
|
|
}
|
|
|
|
[Fact]
|
|
public void Pin_IncrementsPinCount_And_Dispose_Throws_WhenPinned()
|
|
{
|
|
var tag = OpenTypeTag.Parse("pin ");
|
|
var data = new byte[] { 1, 2, 3 };
|
|
var font = BuildFont(tag, data);
|
|
|
|
using var ms = new MemoryStream(font);
|
|
UnmanagedFontMemory mem = UnmanagedFontMemory.LoadFromStream(ms);
|
|
UnmanagedFontMemory? fresh = null;
|
|
|
|
try
|
|
{
|
|
var handle = mem.Pin();
|
|
|
|
try
|
|
{
|
|
// Attempting to dispose while pinned should throw
|
|
Assert.Throws<InvalidOperationException>(() => mem.Dispose());
|
|
}
|
|
finally
|
|
{
|
|
// Release the pin via the handle. After the failed Dispose the original
|
|
// instance may be in an invalid state, so prefer releasing the pin
|
|
// through the handle rather than calling methods on the possibly corrupted instance.
|
|
try
|
|
{
|
|
handle.Dispose();
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
// After the exception the original instance may be unusable; construct a new instance
|
|
// for further operations and assertions.
|
|
fresh = UnmanagedFontMemory.LoadFromStream(new MemoryStream(font));
|
|
|
|
// Now disposing the fresh instance should not throw
|
|
fresh.Dispose();
|
|
}
|
|
finally
|
|
{
|
|
// Ensure final cleanup if something went wrong
|
|
try
|
|
{
|
|
mem.Dispose();
|
|
}
|
|
catch { }
|
|
|
|
if (fresh != null)
|
|
{
|
|
try
|
|
{
|
|
fresh.Dispose();
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|