using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Glass; using Azimuth; using UnitTestSharp; using static System.Net.Mime.MediaTypeNames; namespace Glass.UnitTests { public class TextLayoutTests : UnitTestSharp.TestFixture { public class MonospaceTestFont : IFont { public static readonly Scalar BaseGlyphWidth = 8; public virtual Scalar GlyphWidth(char glyph) { return BaseGlyphWidth; } public virtual Scalar SpaceBetweenGlyphs(char? leftGlyph, char? rightGlyph) { return 0; } public virtual Scalar LineAscent => 4; public virtual Scalar LineDescent => 4; public virtual Scalar LineGap => 4; } static MonospaceTestFont monospaceFont = new MonospaceTestFont(); public class CalculateTextWidth : UnitTestSharp.TestFixture { public void Basic() { var width = TextLayout.CalculateTextWidth(monospaceFont, "hello"); CheckEqual(5*MonospaceTestFont.BaseGlyphWidth, width); } public void Empty() { var width = TextLayout.CalculateTextWidth(monospaceFont, ""); CheckEqual(0, width); } public void StartingIndex() { char? previousGlyph = null; StringView text = "hello".GetView(1, null); var width = TextLayout.CalculateTextWidth(monospaceFont, text, ref previousGlyph); CheckEqual(4*MonospaceTestFont.BaseGlyphWidth, width); } public void EndingIndex() { char? previousGlyph = null; StringView text = "hello".GetView(null, 2); var width = TextLayout.CalculateTextWidth(monospaceFont, text, ref previousGlyph); CheckEqual(2*MonospaceTestFont.BaseGlyphWidth, width); } public class MonospaceWithSpaceAfterE : MonospaceTestFont { public override Scalar SpaceBetweenGlyphs(char? leftGlyph, char? rightGlyph) { if (leftGlyph == 'e') { return 7; } return 0; } } public void SpaceBetweenGlyphs() { var font = new MonospaceWithSpaceAfterE(); var width = TextLayout.CalculateTextWidth(font, "hello"); CheckEqual(5*MonospaceTestFont.BaseGlyphWidth + 7, width); } public void NullFont() { CheckThrow(typeof(ArgumentNullException)); TextLayout.CalculateTextWidth(null, "hello"); } } public class SplitOffWordAndSpace : TestFixture { readonly string text = "help I'm eating pizza"; public void Basic() { StringView remainder = TextLayout.SplitOffWordAndSpace( text, out StringView word, out StringView spaces); CheckEqual("help", word.ToString()); CheckEqual(" ", spaces.ToString()); CheckEqual("I'm eating pizza", remainder.ToString()); } public void EmptyString() { string text = ""; StringView remainder = TextLayout.SplitOffWordAndSpace( text, out StringView word, out StringView spaces); CheckEqual("", word.ToString()); CheckEqual("", spaces.ToString()); CheckEqual("", remainder.ToString()); } public void NoSpaces() { string text = "help"; StringView remainder = TextLayout.SplitOffWordAndSpace( text, out StringView word, out StringView spaces); CheckEqual("help", word.ToString()); CheckEqual("", spaces.ToString()); CheckEqual("", remainder.ToString()); } public void OnlySpaces() { string text = " "; StringView remainder = TextLayout.SplitOffWordAndSpace( text, out StringView word, out StringView spaces); CheckEqual("", word.ToString()); CheckEqual(" ", spaces.ToString()); CheckEqual("", remainder.ToString()); } public void StartingIndex() { StringView advancedText = text.GetView(5, null); StringView remainder = TextLayout.SplitOffWordAndSpace( advancedText, out StringView word, out StringView spaces); CheckEqual("I'm", word.ToString()); CheckEqual(" ", spaces.ToString()); CheckEqual("eating pizza", remainder.ToString()); } public void Midword() { StringView advancedText = text.GetView(1, null); StringView remainder = TextLayout.SplitOffWordAndSpace( advancedText, out StringView word, out StringView spaces); CheckEqual("elp", word.ToString()); CheckEqual(" ", spaces.ToString()); CheckEqual("I'm eating pizza", remainder.ToString()); } } public class SplitAtLineBreak : TestFixture { public void Basic() { string text = "test 1234"; (StringView lhs, StringView rhs) = TextLayout.SplitAtLineBreak( text, monospaceFont, null, 4 * MonospaceTestFont.BaseGlyphWidth, out Scalar widthUsed); CheckEqual(text, lhs.Text); CheckEqual(0, lhs.StartIndex); CheckEqual(4, lhs.EndIndex); CheckEqual(text, rhs.Text); CheckEqual(4, rhs.StartIndex); CheckEqual(9, rhs.EndIndex); CheckEqual(4 * MonospaceTestFont.BaseGlyphWidth, widthUsed); } class TestFont : IFont { public TestFont(Func glyphSpacing) { this.glyphSpacing = glyphSpacing; } public virtual Scalar GlyphWidth(char glyph) { return 1; } public virtual Scalar SpaceBetweenGlyphs(char? leftGlyph, char? rightGlyph) => glyphSpacing(leftGlyph, rightGlyph); public virtual Scalar LineAscent => 4; public virtual Scalar LineDescent => 4; public virtual Scalar LineGap => 4; public Func glyphSpacing; } public void ChecksInitialGlyphSpacing() { string text = "t"; var font = new TestFont((left, right) => (left ?? 0) - (right ?? 0)); TextLayout.SplitAtLineBreak(text, font, 'z', Scalar.PositiveInfinity, out Scalar widthUsed); CheckEqual('z' - 't' + 1, widthUsed); } public void ChecksSubsequentGlyphSpacing() { string text = "bc"; var font = new TestFont((left, right) => { if (left == 'a' && right == 'b') { return 3; } if (left == 'b' && right == 'c') { return 7; } throw new InvalidOperationException(); }); TextLayout.SplitAtLineBreak(text, font, 'a', Scalar.PositiveInfinity, out Scalar widthUsed); CheckEqual(3 + 7 + 2, widthUsed); } public void StopsAtMaxLineLength() { string text = "bcd"; var font = new TestFont((left, right) => { if (left == 'a' && right == 'b') { return 3; } if (left == 'b' && right == 'c') { return 7; } return 0; }); (StringView lhs, StringView rhs) = TextLayout.SplitAtLineBreak( text, font, 'a', 3 + 7 + 2, out Scalar widthUsed); CheckEqual(3 + 7 + 2, widthUsed); CheckEqual(text, lhs.Text); CheckEqual(0, lhs.StartIndex); CheckEqual(2, lhs.EndIndex); CheckEqual(text, rhs.Text); CheckEqual(2, rhs.StartIndex); CheckEqual(3, rhs.EndIndex); } public void NullFont() { CheckThrow(typeof(ArgumentNullException)); TextLayout.SplitAtLineBreak("test", null, null, Scalar.PositiveInfinity, out _); } public void NegativeWidthRemaining() { CheckThrow(typeof(ArgumentOutOfRangeException)); TextLayout.SplitAtLineBreak("test", monospaceFont, null, -1, out _); } public void ZeroWidthRemaining() { (StringView lhs, StringView rhs) = TextLayout.SplitAtLineBreak( "test", monospaceFont, null, 0, out Scalar widthUsed); CheckEqual(0, lhs.StartIndex); CheckEqual(0, lhs.EndIndex); CheckEqual(0, rhs.StartIndex); CheckEqual(4, rhs.EndIndex); CheckEqual(0, widthUsed); } } public class WordPlacementCalculatorTests : TestFixture { public class Newline : TestFixture { class TestFont : IFont { public virtual Scalar GlyphWidth(char glyph) => 1; public virtual Scalar SpaceBetweenGlyphs(char? leftGlyph, char? rightGlyph) => 0; public virtual Scalar LineAscent => 3; public virtual Scalar LineDescent => 5; public virtual Scalar LineGap => 7; } public void Basic() { var calculator = new TextLayout.WordPlacementCalculator( new TestFont(), maxWidth: 10, callback: null); calculator.LineWidth = 7; calculator.Newline(); CheckEqual(0, calculator.LineWidth); CheckEqual(18, calculator.CumulativeHeight); CheckEqual(7, calculator.MaxUsedWidth); } public void MaxUsedWidthUsesMax() { var calculator = new TextLayout.WordPlacementCalculator( monospaceFont, maxWidth: 10, callback: null); calculator.LineWidth = 7; calculator.Newline(); calculator.LineWidth = 8; calculator.Newline(); calculator.LineWidth = 3; calculator.Newline(); CheckEqual(8, calculator.MaxUsedWidth); } public void LineWidthGreaterThanMaxWidth() { var calculator = new TextLayout.WordPlacementCalculator( monospaceFont, maxWidth: 3, callback: null); calculator.LineWidth = 7; calculator.Newline(); calculator.LineWidth = 8; calculator.Newline(); calculator.LineWidth = 3; calculator.Newline(); CheckEqual(8, calculator.MaxUsedWidth); } } public class CharacterWrapWord : TestFixture { public void FitsOnLine() { var calculator = new TextLayout.WordPlacementCalculator( monospaceFont, maxWidth: 100, callback: null); string word = "test"; calculator.CharacterWrapWord(word, previousGlyph: null); CheckEqual(4 * MonospaceTestFont.BaseGlyphWidth, calculator.LineWidth); CheckEqual(monospaceFont.LineAscent, calculator.CumulativeHeight); CheckEqual(0, calculator.MaxUsedWidth); } public void EmptyString() { var calculator = new TextLayout.WordPlacementCalculator( monospaceFont, maxWidth: 100, callback: null); string word = ""; calculator.CharacterWrapWord(word, previousGlyph: null); CheckEqual(0, calculator.LineWidth); CheckEqual(monospaceFont.LineAscent, calculator.CumulativeHeight); CheckEqual(0, calculator.MaxUsedWidth); } public void SplitOverTwoLines() { var calculator = new TextLayout.WordPlacementCalculator( monospaceFont, maxWidth: 26, callback: null); string word = "test"; calculator.CharacterWrapWord(word, previousGlyph: null); CheckEqual(1 * MonospaceTestFont.BaseGlyphWidth, calculator.LineWidth); CheckEqual(2 * monospaceFont.LineAscent + monospaceFont.LineDescent + monospaceFont.LineGap, calculator.CumulativeHeight); CheckEqual(3 * MonospaceTestFont.BaseGlyphWidth, calculator.MaxUsedWidth); } public void ContinuesCurrentLineAndSplitsOverTwoLines() { var calculator = new TextLayout.WordPlacementCalculator( monospaceFont, maxWidth: 36, callback: null); calculator.CharacterWrapWord("bit", previousGlyph: null); string word = "test"; calculator.CharacterWrapWord(word, previousGlyph: null); CheckEqual(3 * MonospaceTestFont.BaseGlyphWidth, calculator.LineWidth); CheckEqual(2 * monospaceFont.LineAscent + monospaceFont.LineDescent + monospaceFont.LineGap, calculator.CumulativeHeight); CheckEqual(4 * MonospaceTestFont.BaseGlyphWidth, calculator.MaxUsedWidth); } public void ContinuesCurrentLineAndSplitsOverFourLines() { var calculator = new TextLayout.WordPlacementCalculator( monospaceFont, maxWidth: 36, callback: null); calculator.CharacterWrapWord("bit", previousGlyph: null); string word = "testNextWord"; calculator.CharacterWrapWord(word, previousGlyph: null); CheckEqual(3 * MonospaceTestFont.BaseGlyphWidth, calculator.LineWidth); CheckEqual( monospaceFont.LineAscent + 3 * (monospaceFont.LineAscent + monospaceFont.LineDescent + monospaceFont.LineGap), calculator.CumulativeHeight); CheckEqual(4 * MonospaceTestFont.BaseGlyphWidth, calculator.MaxUsedWidth); } public void CallsCallbackWithEachLineChunk() { var lineSize = monospaceFont.LineAscent + monospaceFont.LineDescent + monospaceFont.LineGap; var expectedString = new string[] { "bit", "t", "estN", "extW", "ord", }; var expectedPositions = new Vector[] { new Vector(0, monospaceFont.LineAscent), new Vector(3 * MonospaceTestFont.BaseGlyphWidth, monospaceFont.LineAscent), new Vector(0, monospaceFont.LineAscent + lineSize), new Vector(0, monospaceFont.LineAscent + 2 * lineSize), new Vector(0, monospaceFont.LineAscent + 3 * lineSize), }; int i = 0; var calculator = new TextLayout.WordPlacementCalculator( monospaceFont, maxWidth: 36, callback: (in StringView textIn, char? previousGlyph, Vector startingPosition) => { CheckEqual(textIn.ToString(), expectedString[i]); CheckNull(previousGlyph); CheckEqual(startingPosition, expectedPositions[i]); ++i; } ); calculator.CharacterWrapWord("bit", previousGlyph: null); string word = "testNextWord"; calculator.CharacterWrapWord(word, previousGlyph: null); CheckEqual(5, i); } public void UsesPreviousGlyphOnFirstLine() { int i = 0; var calculator = new TextLayout.WordPlacementCalculator( monospaceFont, maxWidth: 36, callback: (in StringView textIn, char? previousGlyph, Vector startingPosition) => { if (i == 0) { CheckEqual('Q', previousGlyph); } else if (i == 1) { CheckEqual('Z', previousGlyph); } else { CheckNull(previousGlyph); } ++i; } ); calculator.CharacterWrapWord("bit", previousGlyph: 'Q'); string word = "testNextWord"; calculator.CharacterWrapWord(word, previousGlyph: 'Z'); CheckEqual(5, i); } public void NoNewlineWhenTextExactlyFits() { var calculator = new TextLayout.WordPlacementCalculator( monospaceFont, maxWidth: 32, callback: null); string word = "test"; calculator.CharacterWrapWord(word, previousGlyph: null); CheckEqual(4 * MonospaceTestFont.BaseGlyphWidth, calculator.LineWidth); CheckEqual(monospaceFont.LineAscent, calculator.CumulativeHeight); CheckEqual(0, calculator.MaxUsedWidth); } public void ImmediateNewlineWhenAtEndOfLineToStartWith() { int i = 0; var calculator = new TextLayout.WordPlacementCalculator( monospaceFont, maxWidth: 32, callback: (in StringView textIn, char? previousGlyph, Vector startingPosition) => { if (i == 0) { CheckEqual('Q', previousGlyph); } else { CheckNull(previousGlyph); } ++i; } ); calculator.CharacterWrapWord("bite", previousGlyph: 'Q'); string word = "testNextWord"; calculator.CharacterWrapWord(word, previousGlyph: 'Z'); CheckEqual(4, i); } public void NoCallbackWhenEmpty() { int i = 0; var calculator = new TextLayout.WordPlacementCalculator( monospaceFont, maxWidth: 32, callback: (in StringView textIn, char? previousGlyph, Vector startingPosition) => { if (textIn.Empty) { throw new InvalidOperationException(); } ++i; } ); calculator.CharacterWrapWord("bite", previousGlyph: 'Q'); string word = "test"; calculator.CharacterWrapWord(word, previousGlyph: 'Z'); CheckEqual(2, i); } class TestFont : IFont { public virtual Scalar GlyphWidth(char glyph) => 7; public virtual Scalar SpaceBetweenGlyphs(char? leftGlyph, char? rightGlyph) => 1; public virtual Scalar LineAscent => 3; public virtual Scalar LineDescent => 5; public virtual Scalar LineGap => 11; } public void WhatHappensWhenCharacterIsTooWideForLine() { var font = new TestFont(); int i = 0; var calculator = new TextLayout.WordPlacementCalculator(font, maxWidth: 7, callback: (in StringView textIn, char? previousGlyph, Vector startingPosition) => { CheckEqual("A", textIn); CheckNull(previousGlyph); CheckEqual(new Vector(0, font.LineAscent), startingPosition); ++i; } ); calculator.CharacterWrapWord("A", previousGlyph: null); CheckEqual(1, i); CheckEqual(0, calculator.MaxUsedWidth); CheckEqual(8, calculator.LineWidth); } public void CurrentLineWidthTooWideForLine() { var font = new TestFont(); var calculator = new TextLayout.WordPlacementCalculator(font, maxWidth: 7, callback: null); calculator.LineWidth = 8; calculator.CharacterWrapWord("A", previousGlyph: null); CheckEqual(8, calculator.MaxUsedWidth); CheckEqual(8, calculator.LineWidth); CheckEqual(2 * font.LineAscent + font.LineDescent + font.LineGap, calculator.CumulativeHeight); } } public class AppendWord : TestFixture { public void Basic() { var calculator = new TextLayout.WordPlacementCalculator( monospaceFont, maxWidth: 100, callback: null); calculator.AppendWord("test", previousGlyph: null, wordWidth: 32); CheckEqual(32, calculator.LineWidth); CheckEqual(4, calculator.CumulativeHeight); CheckEqual(0, calculator.MaxUsedWidth); } public void LineAlreadyHasStuffOnIt() { var calculator = new TextLayout.WordPlacementCalculator( monospaceFont, maxWidth: 100, callback: null); calculator.AppendWord("test1", previousGlyph: null, wordWidth: 40); calculator.AppendWord("test2", previousGlyph: null, wordWidth: 40); CheckEqual(80, calculator.LineWidth); CheckEqual(4, calculator.CumulativeHeight); CheckEqual(0, calculator.MaxUsedWidth); } public void CallsCallback() { int i = 0; var calculator = new TextLayout.WordPlacementCalculator(monospaceFont, maxWidth: 100, callback: (in StringView text, char? previousGlyph, Vector startingPosition) => { if (i == 0) { CheckEqual("test1", text.ToString()); CheckEqual('a', previousGlyph); CheckEqual(startingPosition, new Vector(0, 4)); } else if (i == 1) { CheckEqual("test2", text.ToString()); CheckEqual('b', previousGlyph); CheckEqual(startingPosition, new Vector(40, 4)); } else { throw new InvalidOperationException(); } ++i; } ); calculator.AppendWord("test1", previousGlyph: 'a', wordWidth: 40); calculator.AppendWord("test2", previousGlyph: 'b', wordWidth: 40); CheckEqual(2, i); } public void WordWidthTooLarge() { var calculator = new TextLayout.WordPlacementCalculator( monospaceFont, maxWidth: 31, callback: null); CheckThrow(typeof(ArgumentException)); calculator.AppendWord("test", previousGlyph: null, wordWidth: 32); } public void NotEnoughSpaceLeft() { var calculator = new TextLayout.WordPlacementCalculator( monospaceFont, maxWidth: 40, callback: null); calculator.LineWidth = 9; CheckThrow(typeof(ArgumentException)); calculator.AppendWord("test", previousGlyph: null, wordWidth: 32); } } public class Finish : TestFixture { public class TestFont : IFont { public virtual Scalar GlyphWidth(char glyph) => 8; public virtual Scalar SpaceBetweenGlyphs(char? leftGlyph, char? rightGlyph) => 0; public virtual Scalar LineAscent => 3; public virtual Scalar LineDescent => 5; public virtual Scalar LineGap => 11; } public void Basic() { var font = new TestFont(); var calculator = new TextLayout.WordPlacementCalculator( font, maxWidth: 100, callback: null); calculator.CharacterWrapWord("test", previousGlyph: null); calculator.Finish(); CheckEqual(4 * MonospaceTestFont.BaseGlyphWidth, calculator.MaxUsedWidth); CheckEqual(0, calculator.LineWidth); CheckEqual(font.LineAscent + font.LineDescent, calculator.CumulativeHeight); } public void LineEmpty() { var font = new TestFont(); var calculator = new TextLayout.WordPlacementCalculator( font, maxWidth: 100, callback: null); calculator.Finish(); CheckEqual(0, calculator.MaxUsedWidth); CheckEqual(0, calculator.LineWidth); CheckEqual(-font.LineGap, calculator.CumulativeHeight); } } } public class WordWrapGlyphPlacements : TestFixture { class TestFont : IFont { public virtual Scalar GlyphWidth(char glyph) => 1; public virtual Scalar SpaceBetweenGlyphs(char? leftGlyph, char? rightGlyph) => 0; public virtual Scalar LineAscent => 3; public virtual Scalar LineDescent => 5; public virtual Scalar LineGap => 11; } public void SingleLine() { var font = new TestFont(); var size = TextLayout.WordWrap(font, "test"); CheckEqual(new Vector(4, font.LineAscent + font.LineDescent), size); } public void ZeroMaxWidth() { var font = new TestFont(); var size = TextLayout.WordWrap(font, "test", maxWidth: 0); CheckEqual(new Vector(1, 65), size); } public void VerySmallMaxWidth() { var font = new TestFont(); var size = TextLayout.WordWrap(font, "test", maxWidth: 0.1); CheckEqual(new Vector(1, 65), size); } public void SingleLineCallback() { var font = new TestFont(); bool callback = false; var size = TextLayout.WordWrap(font, "test", wordPlacementCallback: (in StringView text, char? previousGlyph, Vector startingPosition) => { callback = true; CheckEqual("test", text); CheckNull(previousGlyph); CheckEqual(new Vector(0, font.LineAscent), startingPosition); } ); Check(callback); } public void CallbackSplitsWordsAndSpaces() { var font = new TestFont(); var expectedStrings = new string[] { "this", " ", "is", " ", "a", " ", "test", " ", "string" }; var expectedOffsets = new Scalar[] { 0, 4, 5, 7, 9, 10, 11, 15, 18, 24, }; int i = 0; var size = TextLayout.WordWrap(font, "this is a test string", wordPlacementCallback: (in StringView text, char? previousGlyph, Vector startingPosition) => { CheckEqual(expectedStrings[i], text); if (i == 0) { CheckNull(previousGlyph); } else { CheckEqual(expectedStrings[i-1].Last(), previousGlyph); } CheckEqual(new Vector(expectedOffsets[i], font.LineAscent), startingPosition); ++i; } ); CheckEqual(expectedStrings.Length, i); CheckEqual(new Vector(expectedOffsets[i], font.LineAscent + font.LineDescent), size); } public void Empty() { var font = new TestFont(); var vector = TextLayout.WordWrap(font, ""); CheckEqual(Vector.Zero, vector); } public void NullString() { var font = new TestFont(); CheckThrow(typeof(ArgumentNullException)); TextLayout.WordWrap(font, null); } public void WordMovesToNextLine() { var font = new TestFont(); var expectedStrings = new string[] { "this", " ", "is", " ", }; var lineHeight = font.LineAscent + font.LineDescent + font.LineGap; var expectedPositions = new Vector[] { new Vector(0, font.LineAscent), new Vector(4, font.LineAscent), new Vector(0, font.LineAscent + lineHeight), new Vector(2, font.LineAscent + lineHeight), }; var expectedPreviousGlyphs = new char?[] { null, 's', null, 's', }; int i = 0; var size = TextLayout.WordWrap(font, "this is ", maxWidth: 6, wordPlacementCallback: (in StringView text, char? previousGlyph, Vector startingPosition) => { CheckEqual(expectedStrings[i], text); CheckEqual(expectedPreviousGlyphs[i], previousGlyph); CheckEqual(expectedPositions[i], startingPosition); ++i; } ); CheckEqual(expectedStrings.Length, i); CheckEqual(new Vector(5, 2 * lineHeight - font.LineGap), size); } public void LineDoesNotEndWithASpace() { var font = new TestFont(); var expectedStrings = new string[] { "this", " ", "is", " ", }; var lineHeight = font.LineAscent + font.LineDescent + font.LineGap; var expectedPositions = new Vector[] { new Vector(0, font.LineAscent), new Vector(4, font.LineAscent), new Vector(0, font.LineAscent + lineHeight), new Vector(2, font.LineAscent + lineHeight), }; var expectedPreviousGlyphs = new char?[] { null, 's', null, 's', }; int i = 0; var size = TextLayout.WordWrap(font, "this is ", maxWidth: 8, wordPlacementCallback: (in StringView text, char? previousGlyph, Vector startingPosition) => { CheckEqual(expectedStrings[i], text); CheckEqual(expectedPreviousGlyphs[i], previousGlyph); CheckEqual(expectedPositions[i], startingPosition); ++i; } ); CheckEqual(expectedStrings.Length, i); CheckEqual(new Vector(5, 2 * lineHeight - font.LineGap), size); } public void VeryLongWhitespaceForcesWordToFitOnLine() { var font = new TestFont(); var expectedStrings = new string[] { "this", " ", "is", " ", " ", }; var lineHeight = font.LineAscent + font.LineDescent + font.LineGap; var expectedPositions = new Vector[] { new Vector(0, font.LineAscent), new Vector(4, font.LineAscent), new Vector(5, font.LineAscent), new Vector(7, font.LineAscent), new Vector(0, font.LineAscent + lineHeight), }; var expectedPreviousGlyphs = new char?[] { null, 's', ' ', 's', null, }; int i = 0; var size = TextLayout.WordWrap(font, "this is ", maxWidth: 8, wordPlacementCallback: (in StringView text, char? previousGlyph, Vector startingPosition) => { CheckEqual(expectedStrings[i], text); CheckEqual(expectedPreviousGlyphs[i], previousGlyph); CheckEqual(expectedPositions[i], startingPosition); ++i; } ); CheckEqual(expectedStrings.Length, i); CheckEqual(new Vector(8, 2 * lineHeight - font.LineGap), size); } public void VeryLongWhitespaceAndWordWontFitOnLine() { var font = new TestFont(); var expectedStrings = new string[] { "this", " ", "armada", " ", " ", }; var lineHeight = font.LineAscent + font.LineDescent + font.LineGap; var expectedPositions = new Vector[] { new Vector(0, font.LineAscent), new Vector(4, font.LineAscent), new Vector(0, font.LineAscent + lineHeight), new Vector(6, font.LineAscent + lineHeight), new Vector(0, font.LineAscent + 2 * lineHeight), }; var expectedPreviousGlyphs = new char?[] { null, 's', null, 'a', null, }; int i = 0; var size = TextLayout.WordWrap(font, "this armada ", maxWidth: 8, wordPlacementCallback: (in StringView text, char? previousGlyph, Vector startingPosition) => { CheckEqual(expectedStrings[i], text); CheckEqual(expectedPreviousGlyphs[i], previousGlyph); CheckEqual(expectedPositions[i], startingPosition); ++i; } ); CheckEqual(expectedStrings.Length, i); CheckEqual(new Vector(8, 3 * lineHeight - font.LineGap), size); } public void WordWontFitOnLine() { var font = new TestFont(); var expectedStrings = new string[] { "this", " ", "ele", "phants", " ", " ", }; var lineHeight = font.LineAscent + font.LineDescent + font.LineGap; var expectedPositions = new Vector[] { new Vector(0, font.LineAscent), new Vector(4, font.LineAscent), new Vector(5, font.LineAscent), new Vector(0, font.LineAscent + lineHeight), new Vector(6, font.LineAscent + lineHeight), new Vector(0, font.LineAscent + 2 * lineHeight), }; var expectedPreviousGlyphs = new char?[] { null, 's', ' ', null, 's', null, }; int i = 0; var size = TextLayout.WordWrap(font, "this elephants ", maxWidth: 8, wordPlacementCallback: (in StringView text, char? previousGlyph, Vector startingPosition) => { CheckEqual(expectedStrings[i], text); CheckEqual(expectedPreviousGlyphs[i], previousGlyph); CheckEqual(expectedPositions[i], startingPosition); ++i; } ); CheckEqual(expectedStrings.Length, i); CheckEqual(new Vector(8, 3 * lineHeight - font.LineGap), size); } } } }