Skip to content

Commit 87226fb

Browse files
committed
Add vertical glyph advance API to GlyphTypeface
Mirror the existing TryGetHorizontalGlyphAdvance / ...Advances pair with vertical equivalents. Until now a vertical-layout caller (CJK, Mongolian) had no advance-only path on GlyphTypeface and had to go through TryGetGlyphMetrics, which after PR2b reads the glyf bounding box per glyph — pure waste when only the advance is needed. VerticalMetricsTable already exposes the per-glyph and batch readers internally (used by TryGetGlyphMetrics(batch)); this is a thin wrapper over them. Tests cover the Latin (no vmtx → false) and CJK (vmtx → positive advances) cases and batch/single parity using Inter and MiSans-Normal. Audit note: HorizontalMetricsTable and VerticalMetricsTable batch readers already span-cache (each fetches _data.Span once per call, reusing a BigEndianBinaryReader across glyphs). No metrics-table refactor is needed — the speculative Part 2 of the plan collapses to nothing.
1 parent 3e19d6a commit 87226fb

2 files changed

Lines changed: 111 additions & 0 deletions

File tree

src/Avalonia.Base/Media/GlyphTypeface.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,53 @@ public bool TryGetHorizontalGlyphAdvances(ReadOnlySpan<ushort> glyphIndices, Spa
713713
return _hmTable.TryGetAdvances(glyphIndices, advances);
714714
}
715715

716+
/// <summary>
717+
/// Attempts to retrieve the vertical advance height for the specified glyph.
718+
/// </summary>
719+
/// <remarks>Returns false if vertical metrics are not available (the font has no
720+
/// <c>vmtx</c> table — the common case for Latin fonts) or if the specified glyph
721+
/// is not present in the metrics table.</remarks>
722+
/// <param name="glyphIndex">The identifier of the glyph for which to obtain the vertical advance height.</param>
723+
/// <param name="advance">When this method returns, contains the vertical advance height of the glyph if found; otherwise, zero. This
724+
/// parameter is passed uninitialized.</param>
725+
/// <returns>true if the vertical advance height was successfully retrieved; otherwise, false.</returns>
726+
public bool TryGetVerticalGlyphAdvance(ushort glyphIndex, out ushort advance)
727+
{
728+
advance = default;
729+
730+
if (!_hasVerticalMetrics || _vmTable is null)
731+
{
732+
return false;
733+
}
734+
735+
if (!_vmTable.TryGetAdvance(glyphIndex, out advance))
736+
{
737+
return false;
738+
}
739+
740+
return true;
741+
}
742+
743+
/// <summary>
744+
/// Attempts to retrieve vertical advance heights for multiple glyphs in a single operation.
745+
/// </summary>
746+
/// <remarks>This method is significantly more efficient than calling <see cref="TryGetVerticalGlyphAdvance"/>
747+
/// multiple times as it minimizes memory access overhead and exploits data locality. This is the preferred method
748+
/// for batch vertical-layout scenarios (CJK, Mongolian). Returns false if vertical metrics
749+
/// are not available.</remarks>
750+
/// <param name="glyphIndices">Read-only span of glyph identifiers for which to retrieve advance heights.</param>
751+
/// <param name="advances">Output span to write the advance heights. Must be at least as long as <paramref name="glyphIndices"/>.</param>
752+
/// <returns>true if vertical metrics are available and all advances were successfully retrieved; otherwise, false.</returns>
753+
public bool TryGetVerticalGlyphAdvances(ReadOnlySpan<ushort> glyphIndices, Span<ushort> advances)
754+
{
755+
if (!_hasVerticalMetrics || _vmTable is null)
756+
{
757+
return false;
758+
}
759+
760+
return _vmTable.TryGetAdvances(glyphIndices, advances);
761+
}
762+
716763
/// <summary>
717764
/// Attempts to retrieve the metrics for the specified glyph.
718765
/// </summary>

tests/Avalonia.Base.UnitTests/Media/GlyphTypefaceTests.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class GlyphTypefaceTests
1616
private const string InterFontUri = "resm:Avalonia.Base.UnitTests.Assets.Inter-Regular.ttf?assembly=Avalonia.Base.UnitTests";
1717
private const string BlankFontUri = "resm:Avalonia.Base.UnitTests.Assets.AdobeBlank2VF.ttf?assembly=Avalonia.Base.UnitTests";
1818
private const string GB18030FontUri = "resm:Avalonia.Base.UnitTests.Assets.NISC18030.ttf?assembly=Avalonia.Base.UnitTests";
19+
private const string MiSansFontUri = "resm:Avalonia.Base.UnitTests.Assets.MiSans-Normal.ttf?assembly=Avalonia.Base.UnitTests";
1920

2021
[Fact]
2122
public void Should_Load_Inter_Font()
@@ -342,6 +343,69 @@ public void TryGetGlyphMetrics_Batch_Matches_Single()
342343
}
343344
}
344345

346+
[Fact]
347+
public void TryGetVerticalGlyphAdvance_Returns_False_For_Latin_Font()
348+
{
349+
var assetLoader = new StandardAssetLoader();
350+
using var stream = assetLoader.Open(new Uri(InterFontUri));
351+
var typeface = new GlyphTypeface(new CustomPlatformTypeface(stream));
352+
353+
var glyphIndex = typeface.CharacterToGlyphMap['A'];
354+
355+
// Latin fonts typically carry no vmtx table — the call returns false and
356+
// leaves the advance at zero.
357+
Assert.False(typeface.TryGetVerticalGlyphAdvance(glyphIndex, out var advance));
358+
Assert.Equal((ushort)0, advance);
359+
}
360+
361+
[Fact]
362+
public void TryGetVerticalGlyphAdvances_Batch_Returns_False_For_Latin_Font()
363+
{
364+
var assetLoader = new StandardAssetLoader();
365+
using var stream = assetLoader.Open(new Uri(InterFontUri));
366+
var typeface = new GlyphTypeface(new CustomPlatformTypeface(stream));
367+
368+
var map = typeface.CharacterToGlyphMap;
369+
var glyphIndices = new ushort[] { map['A'], map['B'], map['g'] };
370+
var advances = new ushort[glyphIndices.Length];
371+
372+
Assert.False(typeface.TryGetVerticalGlyphAdvances(glyphIndices, advances));
373+
}
374+
375+
[Fact]
376+
public void TryGetVerticalGlyphAdvance_Returns_True_For_CJK_Font()
377+
{
378+
var assetLoader = new StandardAssetLoader();
379+
using var stream = assetLoader.Open(new Uri(MiSansFontUri));
380+
var typeface = new GlyphTypeface(new CustomPlatformTypeface(stream));
381+
382+
// CJK glyph: U+4E2D ("中"). MiSans is a CJK font with a vmtx table.
383+
var glyphIndex = typeface.CharacterToGlyphMap['中'];
384+
385+
Assert.True(typeface.TryGetVerticalGlyphAdvance(glyphIndex, out var advance));
386+
Assert.True(advance > 0, "Expected a positive vertical advance for a CJK glyph.");
387+
}
388+
389+
[Fact]
390+
public void TryGetVerticalGlyphAdvances_Batch_Matches_Single_For_CJK_Font()
391+
{
392+
var assetLoader = new StandardAssetLoader();
393+
using var stream = assetLoader.Open(new Uri(MiSansFontUri));
394+
var typeface = new GlyphTypeface(new CustomPlatformTypeface(stream));
395+
396+
var map = typeface.CharacterToGlyphMap;
397+
var glyphIndices = new ushort[] { map['中'], map['文'], map['字'], map[' '] };
398+
399+
var batch = new ushort[glyphIndices.Length];
400+
Assert.True(typeface.TryGetVerticalGlyphAdvances(glyphIndices, batch));
401+
402+
for (var i = 0; i < glyphIndices.Length; i++)
403+
{
404+
Assert.True(typeface.TryGetVerticalGlyphAdvance(glyphIndices[i], out var single));
405+
Assert.Equal(single, batch[i]);
406+
}
407+
}
408+
345409
[Fact]
346410
public void Should_Have_Valid_PlatformTypeface()
347411
{

0 commit comments

Comments
 (0)