using System.Collections.Generic; using Amphora; using Glass.Typesetting; using UnitTestSharp; using LineBreakTestFile = Glass.UnitTests.Typesetting.LineBreakIteratorTests.LineBreakTestFile; namespace Glass.UnitTests.Typesetting { public partial class LineWrapperTests { public static LineWrapper WrapFaced(StringView text, int maxWidth, IFontMetrics baseFont, FaceRuns faces, TextOverflow overflow = TextOverflow.Clip, int? maxLines = null, TrailingSpaceMode trailingSpaces = TrailingSpaceMode.Hang) { var returnMe = default(LineWrapper); returnMe.Initialize(new MeasuredWords(baseFont, text, faces), maxWidth, maxHeight: null, overflow, maxLines, trailingSpaces); return returnMe; } public static List FacedLines(StringView text, int maxWidth, IFontMetrics baseFont, params (int start, int end, IFontMetrics font)[] runs) { return Lines(WrapFaced(text, maxWidth, baseFont, FacedMeasuringTests.Runs(baseFont, runs))); } public partial class MoveNextTests { private static readonly SpriteFontDefinition Narrow = FacedMeasuringTests.Narrow; private static readonly SpriteFontDefinition Wide = FacedMeasuringTests.Wide; public void FacedWords_WrapByTheirOwnWidths() { CheckEqual(new[] { "0..3 width 20", "3..6 width 28", "6..8 width 20" }, FacedLines("aa bb cc", maxWidth: 50, Narrow, (3, 5, Wide))); } public void FacedWordWiderThanTheLine_SplitsByEachGlyphsWidth() { CheckEqual(new[] { "0..2 width 20", "2..4 width 28", "4..6 width 20" }, FacedLines("abcdef", maxWidth: 30, Narrow, (2, 4, Wide))); } public void FacedWordWiderThanTheLine_KeepsAGlyphThatAloneIsTooWide() { CheckEqual(new[] { "0..1 width 14", "1..2 width 14", "2..3 width 14" }, FacedLines("bbb", maxWidth: 6, Narrow, (0, 3, Wide))); } public void FacedSoftHyphen_IsMeasuredInTheFontTheSoftHyphenIsSetIn() { string text = $"ab{LineBreakIterator.SoftHyphen}cd"; CheckEqual(new[] { "0..3 width 25 EndsWithHyphen", "3..5 width 20" }, FacedLines(text, maxWidth: 28, Narrow, (2, 3, Wide))); } public void SoftHyphenInTheBaseFont_IsMeasuredInIt() { string text = $"ab{LineBreakIterator.SoftHyphen}cd"; CheckEqual(new[] { "0..3 width 23 EndsWithHyphen", "3..5 width 28" }, FacedLines(text, maxWidth: 28, Narrow, (3, 5, Wide))); } public void FacedSoftHyphen_NeedsRoomInItsOwnFontToJoinALine() { string text = $"aa bb{LineBreakIterator.SoftHyphen}cc"; CheckEqual(new[] { "0..3 width 20", "3..8 width 40" }, FacedLines(text, maxWidth: 48, Narrow, (5, 6, Wide))); CheckEqual(new[] { "0..6 width 47 EndsWithHyphen", "6..8 width 20" }, FacedLines(text, maxWidth: 48, Narrow, (20, 21, Wide))); } public void FacedEllipsis_IsMeasuredInTheFontOfTheGlyphBeforeIt() { CheckEqual(new[] { "0..1 width 22 EndsWithEllipsis" }, Lines(WrapFaced("aaa bbb", maxWidth: 34, Narrow, FacedMeasuringTests.Runs(Narrow, (0, 3, Wide)), TextOverflow.Ellipsis, maxLines: 1))); } public void EllipsisAfterGlyphsInTheBaseFont_IsMeasuredInIt() { CheckEqual(new[] { "0..2 width 25 EndsWithEllipsis" }, Lines(WrapFaced("aaa bbb", maxWidth: 34, Narrow, FacedMeasuringTests.Runs(Narrow, (3, 7, Wide)), TextOverflow.Ellipsis, maxLines: 1))); } public void FacedEllipsisThatKeepsNothing_IsMeasuredInTheFontOfTheTextItReplaces() { CheckEqual(new[] { "0..0 width 8 EndsWithEllipsis" }, Lines(WrapFaced("bbb", maxWidth: 6, Narrow, FacedMeasuringTests.Runs(Narrow, (0, 3, Wide)), TextOverflow.Ellipsis, maxLines: 1))); } public void FacedEllipsis_KernsAgainstTheGlyphBeforeIt() { CheckEqual(new[] { "0..1 width 14 EndsWithEllipsis" }, Lines(WrapFaced("bbb bbb", maxWidth: 14, FacedMeasuringTests.Kerned, FacedMeasuringTests.Runs(FacedMeasuringTests.Kerned, (7, 8, Wide)), TextOverflow.Ellipsis, maxLines: 1))); } public void FacedTrailingSpaces_HangPastTheLineByTheirOwnWidths() { CheckEqual(new[] { "0..3 width 10", "3..4 width 10" }, FacedLines("a b", maxWidth: 12, Narrow, (2, 3, Wide))); } public void FacedTrailingSpaces_SpillOntoLinesByTheirOwnWidths() { var wrapper = WrapFaced("a b", maxWidth: 14, Narrow, FacedMeasuringTests.Runs(Narrow, (2, 4, Wide)), trailingSpaces: TrailingSpaceMode.Wrap); CheckEqual(new[] { "0..2 width 14", "2..4 width 12", "4..6 width 14" }, Lines(wrapper)); } public void FacedLeadingWordThatDrawsNothing_DoesNotStartTheLine() { string text = $"{LineBreakIterator.SoftHyphen}ab"; CheckEqual(Lines(text, maxWidth: 30, Narrow), FacedLines(text, maxWidth: 30, Narrow, (7, 8, Wide))); } private static readonly MeasuredWords AllocationWords = new MeasuredWords(Narrow, "aa bb cc dd ee ff gg hh", FacedMeasuringTests.Runs(Narrow, (3, 5, Wide), (9, 11, Wide))); [AllocationTest] public void FacedWords_AllocateNothing() { var wrapper = default(LineWrapper); wrapper.Initialize(AllocationWords, maxWidth: 50, maxHeight: null, TextOverflow.Ellipsis, maxLines: 2); int lines = 0; while (wrapper.MoveNext()) { ++lines; } CheckNoAllocations(); CheckEqual(2, lines); } private static List LinesInEveryMode(StringView text, IFontMetrics font, FaceRuns faces) { var returnMe = new List(); foreach (int width in new[] { 3, 8 }) { foreach ((TextOverflow overflow, int? maxLines, TrailingSpaceMode trailing) in new[] { (TextOverflow.Clip, (int?)null, TrailingSpaceMode.Hang), (TextOverflow.Ellipsis, (int?)2, TrailingSpaceMode.Hang), (TextOverflow.Clip, (int?)null, TrailingSpaceMode.Wrap), }) { LineWrapper wrapper = faces == null ? Wrap(text, width, font, null, overflow, maxLines, trailing) : WrapFaced(text, width, font, faces, overflow, maxLines, trailing); returnMe.AddRange(Lines(wrapper)); } } return returnMe; } private static List WordsOf(MeasuredWords words) { var returnMe = new List(); while (words.MoveNext()) { returnMe.Add(FacedMeasuringTests.Describe(words.CurrentStart, words.Current)); } return returnMe; } // Mismatches are collected rather than checked one by one, since the file holds thousands of texts. public void FacedWordsInACopyOfTheFont_WrapLikeThePlainWordsOnEveryLineBreakTestShape() { IFontMetrics font = LineBreakTestFont; IFontMetrics copy = new ProbeFont(); var mismatches = new List(); foreach (LineBreakTestFile.Case testCase in LineBreakTestFile.Shapes) { var faces = new FaceRuns(font); for (int start = 0; start < testCase.Text.Length; start += 4) { faces.Add(start, start + 2, copy); } var plain = new MeasuredWords(font, testCase.Text); var faced = new MeasuredWords(font, testCase.Text, faces); bool same = SameSequence(WordsOf(plain), WordsOf(faced)) && SameSequence(LinesInEveryMode(testCase.Text, font, null), LinesInEveryMode(testCase.Text, font, faces)); if (!same && mismatches.Count < 10) { mismatches.Add(LineBreakIteratorTests.CodePointsOf(testCase.Text)); } } CheckEqual("", string.Join("; ", mismatches)); } public void KernedTextAllInACopyOfTheFont_WrapsLikeThePlainWordsOnEveryLineBreakTestShape() { IFontMetrics font = WordMeasurerTests.ConformanceFont(); IFontMetrics copy = WordMeasurerTests.ConformanceFont(); var mismatches = new List(); foreach (LineBreakTestFile.Case testCase in LineBreakTestFile.Shapes) { var faces = new FaceRuns(font); faces.Add(0, testCase.Text.Length, copy); var plain = new MeasuredWords(font, testCase.Text); var faced = new MeasuredWords(font, testCase.Text, faces); bool same = SameSequence(WordsOf(plain), WordsOf(faced)) && SameSequence(LinesInEveryMode(testCase.Text, font, null), LinesInEveryMode(testCase.Text, font, faces)); if (!same && mismatches.Count < 10) { mismatches.Add(LineBreakIteratorTests.CodePointsOf(testCase.Text)); } } CheckEqual("", string.Join("; ", mismatches)); } private static bool SameSequence(List expected, List actual) { return string.Join("|", expected) == string.Join("|", actual); } } } }