Skip to content

Commit

Permalink
Merge pull request #398 from gerdus/FixCharacterBounds
Browse files Browse the repository at this point in the history
Fix character bounds returned by TryMeasureCharacterBounds
  • Loading branch information
JimBobSquarePants authored Mar 28, 2024
2 parents 30c8f15 + 91405e3 commit e546be3
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 14 deletions.
29 changes: 16 additions & 13 deletions src/SixLabors.Fonts/GlyphLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,15 @@ internal GlyphLayout(

internal FontRectangle BoundingBox(float dpi)
{
Vector2 origin = (this.PenLocation + this.Offset) * dpi;
FontRectangle box = this.Glyph.BoundingBox(this.LayoutMode, this.BoxLocation, dpi);
// Same logic as in TrueTypeGlyphMetrics.RenderTo
Vector2 location = this.PenLocation;
Vector2 offset = this.Offset;

location *= dpi;
offset *= dpi;
Vector2 renderLocation = location + offset;

FontRectangle box = this.Glyph.BoundingBox(this.LayoutMode, renderLocation, dpi);

if (this.IsWhiteSpace())
{
Expand All @@ -110,33 +117,29 @@ internal FontRectangle BoundingBox(float dpi)
if (this.LayoutMode == GlyphLayoutMode.Vertical)
{
return new FontRectangle(
box.X + origin.X,
box.Y + origin.Y,
box.X,
box.Y,
box.Width,
this.AdvanceY * dpi);
}

if (this.LayoutMode == GlyphLayoutMode.VerticalRotated)
{
return new FontRectangle(
box.X + origin.X,
box.Y + origin.Y,
box.X,
box.Y,
0,
this.AdvanceY * dpi);
}

return new FontRectangle(
box.X + origin.X,
box.Y + origin.Y,
box.X,
box.Y,
this.AdvanceX * dpi,
box.Height);
}

return new FontRectangle(
box.X + origin.X,
box.Y + origin.Y,
box.Width,
box.Height);
return box;
}

/// <inheritdoc/>
Expand Down
2 changes: 1 addition & 1 deletion tests/SixLabors.Fonts.Tests/GlyphTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public void EmojiWidthIsComputedCorrectlyWithSubstitutionOnZwj()
FontRectangle size = TextMeasurer.MeasureSize(text, new TextOptions(font));
FontRectangle size2 = TextMeasurer.MeasureSize(text2, new TextOptions(font));

Assert.Equal(52f, size.Width);
Assert.Equal(51f, size.Width);
Assert.Equal(51f, size2.Width);
}

Expand Down
101 changes: 101 additions & 0 deletions tests/SixLabors.Fonts.Tests/TextLayoutTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,107 @@ public void DoesMeasureCharacterLayoutIncludeStringIndex()
Assert.Equal(graphemeCount - 1, lastBound.GraphemeIndex);
}

[Fact]
public void DoesMeasureCharacterBoundsExtendForAdvanceMultipliers()
{
FontFamily family = new FontCollection().Add(TestFonts.OpenSansFile);
family.TryGetMetrics(FontStyle.Regular, out FontMetrics metrics);

TextOptions options = new(family.CreateFont(metrics.UnitsPerEm))
{
TabWidth = 8
};

const string text = "H\tH";

IReadOnlyList<FontRectangle> glyphsToRender = CaptureGlyphBoundBuilder.GenerateGlyphsBoxes(text, options);
TextMeasurer.TryMeasureCharacterBounds(text, options, out ReadOnlySpan<GlyphBounds> bounds);

IReadOnlyList<GlyphLayout> glyphLayouts = TextLayout.GenerateLayout(text, options);

Assert.Equal(glyphsToRender.Count, bounds.Length);
Assert.Equal(glyphsToRender.Count, glyphsToRender.Count);

for (int glyphIndex = 0; glyphIndex < glyphsToRender.Count; glyphIndex++)
{
FontRectangle renderGlyph = glyphsToRender[glyphIndex];
FontRectangle measureGlyph = bounds[glyphIndex].Bounds;
GlyphLayout glyphLayout = glyphLayouts[glyphIndex];

if (glyphLayout.IsWhiteSpace())
{
Assert.Equal(renderGlyph.X, measureGlyph.X);
Assert.Equal(renderGlyph.Y, measureGlyph.Y);
Assert.Equal(glyphLayout.AdvanceX * options.Dpi, measureGlyph.Width);
Assert.Equal(renderGlyph.Height, measureGlyph.Height);
}
else
{
Assert.Equal(renderGlyph, measureGlyph);
}
}
}

[Fact]
public void IsMeasureCharacterBoundsSameAsRenderBounds()
{
FontFamily family = new FontCollection().Add(TestFonts.OpenSansFile);
family.TryGetMetrics(FontStyle.Regular, out FontMetrics metrics);

TextOptions options = new(family.CreateFont(metrics.UnitsPerEm))
{
};

const string text = "Hello WorLLd";

IReadOnlyList<FontRectangle> glyphsToRender = CaptureGlyphBoundBuilder.GenerateGlyphsBoxes(text, options);
TextMeasurer.TryMeasureCharacterBounds(text, options, out ReadOnlySpan<GlyphBounds> bounds);

Assert.Equal(glyphsToRender.Count, bounds.Length);

for (int glyphIndex = 0; glyphIndex < glyphsToRender.Count; glyphIndex++)
{
FontRectangle renderGlyph = glyphsToRender[glyphIndex];
FontRectangle measureGlyph = bounds[glyphIndex].Bounds;

Assert.Equal(renderGlyph.X, measureGlyph.X);
Assert.Equal(renderGlyph.Y, measureGlyph.Y);
Assert.Equal(renderGlyph.Width, measureGlyph.Width);
Assert.Equal(renderGlyph.Height, measureGlyph.Height);

Assert.Equal(renderGlyph, measureGlyph);
}
}

private class CaptureGlyphBoundBuilder : IGlyphRenderer
{
public static List<FontRectangle> GenerateGlyphsBoxes(string text, TextOptions options)
{
CaptureGlyphBoundBuilder glyphBuilder = new();
TextRenderer renderer = new(glyphBuilder);
renderer.RenderText(text, options);
return glyphBuilder.GlyphBounds;
}
public readonly List<FontRectangle> GlyphBounds = new();

Check warning on line 1146 in tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

View workflow job for this annotation

GitHub Actions / Build (false, macos-latest, net7.0, 7.0.x, true, -x64, false)

Check warning on line 1146 in tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

View workflow job for this annotation

GitHub Actions / Build (false, windows-latest, net6.0, 6.0.x, -x64, true)

Check warning on line 1146 in tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

View workflow job for this annotation

GitHub Actions / Build (false, windows-latest, net6.0, 6.0.x, -x64, true)

public CaptureGlyphBoundBuilder() { }

Check warning on line 1147 in tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

View workflow job for this annotation

GitHub Actions / Build (false, macos-latest, net7.0, 7.0.x, true, -x64, false)

Check warning on line 1147 in tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

View workflow job for this annotation

GitHub Actions / Build (false, windows-latest, net6.0, 6.0.x, -x64, true)

Check warning on line 1147 in tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

View workflow job for this annotation

GitHub Actions / Build (false, windows-latest, net6.0, 6.0.x, -x64, true)

bool IGlyphRenderer.BeginGlyph(in FontRectangle bounds, in GlyphRendererParameters parameters)

Check warning on line 1148 in tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

View workflow job for this annotation

GitHub Actions / Build (false, macos-latest, net7.0, 7.0.x, true, -x64, false)

Check warning on line 1148 in tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

View workflow job for this annotation

GitHub Actions / Build (false, windows-latest, net6.0, 6.0.x, -x64, true)

Check warning on line 1148 in tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

View workflow job for this annotation

GitHub Actions / Build (false, windows-latest, net6.0, 6.0.x, -x64, true)

{
this.GlyphBounds.Add(bounds);
return true;
}
public void BeginFigure() { }

Check warning on line 1153 in tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

View workflow job for this annotation

GitHub Actions / Build (false, macos-latest, net7.0, 7.0.x, true, -x64, false)

Check warning on line 1153 in tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

View workflow job for this annotation

GitHub Actions / Build (false, windows-latest, net6.0, 6.0.x, -x64, true)

Check warning on line 1153 in tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

View workflow job for this annotation

GitHub Actions / Build (false, windows-latest, net6.0, 6.0.x, -x64, true)

public void MoveTo(Vector2 point) { }

Check warning on line 1154 in tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

View workflow job for this annotation

GitHub Actions / Build (false, macos-latest, net7.0, 7.0.x, true, -x64, false)

Check warning on line 1154 in tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

View workflow job for this annotation

GitHub Actions / Build (false, windows-latest, net6.0, 6.0.x, -x64, true)

Check warning on line 1154 in tests/SixLabors.Fonts.Tests/TextLayoutTests.cs

View workflow job for this annotation

GitHub Actions / Build (false, windows-latest, net6.0, 6.0.x, -x64, true)

public void QuadraticBezierTo(Vector2 secondControlPoint, Vector2 point) { }
public void CubicBezierTo(Vector2 secondControlPoint, Vector2 thirdControlPoint, Vector2 point) { }
public void LineTo(Vector2 point) { }
public void EndFigure() { }
public void EndGlyph() { }
public void EndText() { }
void IGlyphRenderer.BeginText(in FontRectangle bounds) { }
public TextDecorations EnabledDecorations() => TextDecorations.None;
public void SetDecoration(TextDecorations textDecorations, Vector2 start, Vector2 end, float thickness) { }
}

private static readonly Font OpenSansTTF = new FontCollection().Add(TestFonts.OpenSansFile).CreateFont(10);
private static readonly Font OpenSansWoff = new FontCollection().Add(TestFonts.OpenSansFile).CreateFont(10);

Expand Down

0 comments on commit e546be3

Please sign in to comment.