A cross-platform UI framework for .NET
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.
 
 
 

145 lines
4.9 KiB

using System;
using Avalonia.Media;
using Avalonia.Media.Fonts.Tables;
using Avalonia.Platform;
using Xunit;
namespace Avalonia.Base.UnitTests.Media.Fonts.Tables
{
public class LocaTableTests
{
private const string InterFontUri =
"resm:Avalonia.Base.UnitTests.Assets.Inter-Regular.ttf?assembly=Avalonia.Base.UnitTests";
private static GlyphTypeface LoadInter()
{
var assetLoader = new StandardAssetLoader();
using var stream = assetLoader.Open(new Uri(InterFontUri));
return new GlyphTypeface(new CustomPlatformTypeface(stream));
}
private static LocaTable LoadLoca(GlyphTypeface typeface)
{
Assert.True(HeadTable.TryLoad(typeface, out var head));
var maxp = MaxpTable.Load(typeface);
var loca = LocaTable.Load(typeface, head!, maxp);
Assert.NotNull(loca);
return loca!;
}
[Fact]
public void Load_Returns_Table_With_Glyphs_For_Inter()
{
var loca = LoadLoca(LoadInter());
Assert.True(loca.GlyphCount > 0);
}
[Fact]
public void GlyphCount_Matches_Maxp()
{
var typeface = LoadInter();
var maxp = MaxpTable.Load(typeface);
var loca = LoadLoca(typeface);
Assert.Equal(maxp.NumGlyphs, loca.GlyphCount);
}
[Theory]
[InlineData(-1)]
[InlineData(int.MaxValue)]
public void TryGetOffsets_Returns_False_For_Out_Of_Range(int glyphIndex)
{
var loca = LoadLoca(LoadInter());
Assert.False(loca.TryGetOffsets(glyphIndex, out _, out _));
}
[Fact]
public void TryGetOffsets_Returns_False_At_GlyphCount()
{
var loca = LoadLoca(LoadInter());
// GlyphCount is one past the last valid glyph index.
Assert.False(loca.TryGetOffsets(loca.GlyphCount, out _, out _));
}
[Fact]
public void TryGetOffsets_Yields_Ascending_Ranges_For_Every_Glyph()
{
var loca = LoadLoca(LoadInter());
for (var i = 0; i < loca.GlyphCount; i++)
{
Assert.True(loca.TryGetOffsets(i, out var start, out var end));
// A glyph's data range must be non-negative in length; equal start/end
// marks an empty glyph.
Assert.True(end >= start, $"Glyph {i}: end {end} < start {start}");
}
}
[Fact]
public void Empty_Glyph_Has_Equal_Offsets()
{
var typeface = LoadInter();
var map = typeface.CharacterToGlyphMap;
Assert.True(map.ContainsGlyph(' '));
var spaceGlyph = map[' '];
var loca = LoadLoca(typeface);
Assert.True(loca.TryGetOffsets(spaceGlyph, out var start, out var end));
// The space glyph carries no outline, so loca gives it a zero-length range.
Assert.Equal(start, end);
}
[Fact]
public void Letter_Glyph_Has_NonEmpty_Range()
{
var typeface = LoadInter();
var map = typeface.CharacterToGlyphMap;
Assert.True(map.ContainsGlyph('A'));
var letterGlyph = map['A'];
var loca = LoadLoca(typeface);
Assert.True(loca.TryGetOffsets(letterGlyph, out var start, out var end));
Assert.True(end > start, "'A' should have a non-empty glyf range.");
}
[Fact]
public void Glyph_Count_Is_Clamped_To_What_The_Table_Covers()
{
// Three short-format entries (6 bytes) cover two glyphs (entries = glyphCount + 1),
// however many glyphs maxp declares. Lookups past the covered range must fail the
// index check instead of reading a truncated entry.
var loca = CreateLoca(new byte[6], glyphCount: 10, isShortFormat: true);
Assert.Equal(2, loca.GlyphCount);
Assert.True(loca.TryGetOffsets(1, out _, out _));
Assert.False(loca.TryGetOffsets(2, out _, out _));
}
/// <summary>
/// Builds a <see cref="LocaTable"/> directly from raw bytes via its internal constructor,
/// bypassing the full font-load path (which would require a complete TTF).
/// </summary>
private static LocaTable CreateLoca(byte[] locaData, int glyphCount, bool isShortFormat)
{
var ctor = typeof(LocaTable).GetConstructor(
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic,
binder: null,
new[] { typeof(ReadOnlyMemory<byte>), typeof(int), typeof(bool) },
modifiers: null);
Assert.NotNull(ctor);
return (LocaTable)ctor!.Invoke(new object[] { (ReadOnlyMemory<byte>)locaData, glyphCount, isShortFormat });
}
}
}