Skip to content

Commit 3198835

Browse files
committed
Add support for tracking (letter-spacing)
Introduces a Tracking property to TextOptions to allow uniform adjustment of spacing between characters, measured in em units.
1 parent 1ff7661 commit 3198835

File tree

3 files changed

+74
-0
lines changed

3 files changed

+74
-0
lines changed

src/SixLabors.Fonts/TextLayout.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,22 @@ VerticalOrientationType.Rotate or
11951195
// Work out the scaled metrics for the glyph.
11961196
GlyphMetrics metric = metrics[i];
11971197

1198+
// Adjust the advance for the last decomposed glyph to add
1199+
// tracking if applicable.
1200+
if (options.Tracking != 0 && decomposedAdvance > 0 && i == decomposedAdvances.Length - 1)
1201+
{
1202+
if (isHorizontalLayout || shouldRotate)
1203+
{
1204+
float scaleAX = pointSize / glyph.ScaleFactor.X;
1205+
decomposedAdvance += options.Tracking * metric.FontMetrics.UnitsPerEm * scaleAX;
1206+
}
1207+
else
1208+
{
1209+
float scaleAY = pointSize / glyph.ScaleFactor.Y;
1210+
decomposedAdvance += options.Tracking * metric.FontMetrics.UnitsPerEm * scaleAY;
1211+
}
1212+
}
1213+
11981214
// Convert design-space units to pixels based on the target point size.
11991215
// ScaleFactor.Y represents the vertical UPEM scaling factor for this glyph.
12001216
float scaleY = pointSize / metric.ScaleFactor.Y;

src/SixLabors.Fonts/TextOptions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,13 @@ public float LineSpacing
170170
/// </summary>
171171
public KerningMode KerningMode { get; set; }
172172

173+
/// <summary>
174+
/// Gets or sets the tracking (letter-spacing) value.
175+
/// Tracking adjusts the spacing between all characters uniformly and is measured in em.
176+
/// Positive values increase spacing, negative values decrease spacing, and zero applies no adjustment.
177+
/// </summary>
178+
public float Tracking { get; set; }
179+
173180
/// <summary>
174181
/// Gets or sets the positioning mode used for rendering decorations.
175182
/// </summary>

tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,57 @@ public void TrueTypeHinting_CanHintSmallOpenSans(char c, FontRectangle expected)
967967
Assert.Equal(expected, actual, Comparer);
968968
}
969969

970+
[Theory]
971+
[InlineData("aaaa", 0, 134.0)]
972+
[InlineData("awwa", 0, 162.1)]
973+
[InlineData("aaaa", 0.1, 153.3)]
974+
[InlineData("awwa", 0.1, 181.4)]
975+
[InlineData("aaaa", 1, 326.1)]
976+
[InlineData("awwa", 1, 354.1)]
977+
public void FontTracking_SpaceCharacters(string text, float tracking, float width)
978+
{
979+
Font font = new FontCollection().Add(TestFonts.OpenSansFile).CreateFont(64);
980+
TextOptions options = new(font)
981+
{
982+
Tracking = tracking,
983+
};
984+
985+
FontRectangle actual = TextMeasurer.MeasureSize(text, options);
986+
Assert.Equal(new FontRectangle(0, 0, width, 35.4f), actual, Comparer);
987+
}
988+
989+
[Theory]
990+
[InlineData("\u1B3C", 1, 83.8)]
991+
[InlineData("\u1B3C\u1B3C", 1, 83.8)]
992+
public void FontTracking_DoNotAddSpacingAfterCharacterThatDidNotAdvance(string text, float tracking, float width)
993+
{
994+
Font font = new FontCollection().Add(TestFonts.NotoSansBalineseRegular).CreateFont(64);
995+
TextOptions options = new(font)
996+
{
997+
Tracking = tracking,
998+
};
999+
1000+
FontRectangle actual = TextMeasurer.MeasureSize(text, options);
1001+
Assert.Equal(new FontRectangle(0, 0, width, 103.3f), actual, Comparer);
1002+
}
1003+
1004+
[Theory]
1005+
[InlineData("\u093f", 1, 48.4)]
1006+
[InlineData("\u0930\u094D\u0915\u093F", 1, 225.6)]
1007+
[InlineData("\u0930\u094D\u0915\u093F\u0930\u094D\u0915\u093F", 1, 419)]
1008+
[InlineData("\u093fa", 1, 145.5f)]
1009+
public void FontTracking_CorrectlyAddSpacingForComposedCharacter(string text, float tracking, float width)
1010+
{
1011+
Font font = new FontCollection().Add(TestFonts.NotoSansDevanagariRegular).CreateFont(64);
1012+
TextOptions options = new(font)
1013+
{
1014+
Tracking = tracking,
1015+
};
1016+
1017+
FontRectangle actual = TextMeasurer.MeasureSize(text, options);
1018+
Assert.Equal(width, actual.Width, Comparer);
1019+
}
1020+
9701021
[Fact]
9711022
public void CanMeasureTextAdvance()
9721023
{

0 commit comments

Comments
 (0)