Skip to content

Commit 3407fe4

Browse files
committed
Quantize normalized coordinates to the F2Dot14 grid
An animated axis sweeps through arbitrary float user values; without quantization every frame produced a fresh normalized position and a fresh clone in the per-source variation cache, growing it without bound across repeated sweeps. Snap each coordinate to 1/16384 after avar correction. That is the resolution the font binary itself stores - gvar, avar and the item variation stores speak F2Dot14 - so no representable position is lost, while a swept axis lands on at most 32769 distinct cache entries and sub-resolution user values collapse to one clone (pinned by tests at the position and clone-identity level).
1 parent 2611aac commit 3407fe4

3 files changed

Lines changed: 42 additions & 0 deletions

File tree

src/Avalonia.Base/Media/GlyphTypeface.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,6 +1616,13 @@ internal NormalizedVariationPosition CreateNormalizedPosition(
16161616
normalizedValue = _avarTable.Remap(i, normalizedValue);
16171617
}
16181618

1619+
// Quantize to the F2Dot14 grid (1/16384) the font binary itself uses for
1620+
// normalized coordinates — gvar, avar and the item variation stores cannot
1621+
// represent a finer position, so this loses nothing. It also bounds the
1622+
// per-source variation-clone cache under animation: a swept axis lands on
1623+
// at most 32769 distinct positions instead of one per float progress value.
1624+
normalizedValue = MathF.Round(normalizedValue * 16384f) / 16384f;
1625+
16191626
if (normalizedValue != 0f)
16201627
{
16211628
normalized ??= new Dictionary<OpenTypeTag, float>(axes.Length);

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,24 @@ public void CreateNormalizedPosition_Ignores_Unknown_Axes()
161161
Assert.False(position.TryGetCoordinate(bogus, out _));
162162
}
163163

164+
[Fact]
165+
public void CreateNormalizedPosition_Quantizes_To_The_F2Dot14_Grid()
166+
{
167+
// Normalized coordinates are quantized to the 1/16384 grid the font binary
168+
// uses, so user values that differ below that resolution produce the SAME
169+
// position — this is what keeps the variation-clone cache bounded when an
170+
// axis is animated through arbitrary float values.
171+
var typeface = LoadTypeface(InterVariableAsset);
172+
173+
var a = typeface.CreateNormalizedPosition(Settings((s_wghtTag, 700)));
174+
var b = typeface.CreateNormalizedPosition(Settings((s_wghtTag, 700.001)));
175+
176+
Assert.Equal(a, b);
177+
178+
Assert.True(a.TryGetCoordinate(s_wghtTag, out var wght));
179+
Assert.Equal(MathF.Round(wght * 16384f) / 16384f, wght);
180+
}
181+
164182
[Fact]
165183
public void CreateNormalizedPosition_Uses_Named_Instance_Coordinates()
166184
{

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,23 @@ public void WithVariations_Shares_Clones_Between_Settings_That_Normalize_Equal()
270270
Assert.Same(atMax, clamped);
271271
}
272272

273+
[Fact]
274+
public void WithVariations_Shares_Clones_Within_A_Quantization_Cell()
275+
{
276+
// Under animation an axis sweeps through arbitrary float user values; the
277+
// F2Dot14 quantization in CreateNormalizedPosition collapses sub-resolution
278+
// differences so the sweep reuses clones instead of growing the cache
279+
// per progress value.
280+
var gt = LoadTypeface(InterVariableAsset);
281+
282+
var a = gt.WithVariations(new FontVariationSettings(
283+
new[] { new FontVariation(s_wghtTag, 700) }));
284+
var b = gt.WithVariations(new FontVariationSettings(
285+
new[] { new FontVariation(s_wghtTag, 700.001) }));
286+
287+
Assert.Same(a, b);
288+
}
289+
273290
[Fact]
274291
public void WithVariations_Selects_Named_Instance_By_Index()
275292
{

0 commit comments

Comments
 (0)