| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009 |
- using System;
- using System.Collections.Generic;
- using System.Globalization;
- using System.Linq;
- using System.Runtime.InteropServices;
- using Avalonia.Media;
- using Avalonia.Media.TextFormatting;
- using Avalonia.UnitTests;
- using Avalonia.Utilities;
- using Xunit;
- namespace Avalonia.Skia.UnitTests.Media.TextFormatting
- {
- public class TextLineTests
- {
- private const string s_multiLineText = "012345678\r\r0123456789";
- [Fact]
- public void Should_Get_First_CharacterHit()
- {
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new SingleBufferTextSource(s_multiLineText, defaultProperties);
- var formatter = new TextFormatterImpl();
- var currentIndex = 0;
- while (currentIndex < s_multiLineText.Length)
- {
- var textLine =
- formatter.FormatLine(textSource, currentIndex, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- var firstCharacterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(int.MinValue));
- Assert.Equal(textLine.FirstTextSourceIndex, firstCharacterHit.FirstCharacterIndex);
- currentIndex += textLine.Length;
- }
- }
- }
- [Fact]
- public void Should_Get_Last_CharacterHit()
- {
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new SingleBufferTextSource(s_multiLineText, defaultProperties);
- var formatter = new TextFormatterImpl();
- var currentIndex = 0;
- while (currentIndex < s_multiLineText.Length)
- {
- var textLine =
- formatter.FormatLine(textSource, currentIndex, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- var lastCharacterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(int.MaxValue));
- Assert.Equal(textLine.FirstTextSourceIndex + textLine.Length,
- lastCharacterHit.FirstCharacterIndex + lastCharacterHit.TrailingLength);
- currentIndex += textLine.Length;
- }
- }
- }
- [Fact]
- public void Should_Get_Next_Caret_CharacterHit_Bidi()
- {
- const string text = "אבג 1 ABC";
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new SingleBufferTextSource(text, defaultProperties);
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- var clusters = new List<int>();
- foreach (var textRun in textLine.TextRuns.OrderBy(x => TextTestHelper.GetStartCharIndex(x.Text)))
- {
- var shapedRun = (ShapedTextRun)textRun;
- var runClusters = shapedRun.ShapedBuffer.GlyphInfos.Select(glyph => glyph.GlyphCluster);
- clusters.AddRange(shapedRun.IsReversed ? runClusters.Reverse() : runClusters);
- }
- var nextCharacterHit = new CharacterHit(0, clusters[1] - clusters[0]);
- foreach (var cluster in clusters)
- {
- Assert.Equal(cluster, nextCharacterHit.FirstCharacterIndex);
- nextCharacterHit = textLine.GetNextCaretCharacterHit(nextCharacterHit);
- }
- var lastCharacterHit = nextCharacterHit;
- nextCharacterHit = textLine.GetNextCaretCharacterHit(lastCharacterHit);
- Assert.Equal(lastCharacterHit.FirstCharacterIndex, nextCharacterHit.FirstCharacterIndex);
- Assert.Equal(lastCharacterHit.TrailingLength, nextCharacterHit.TrailingLength);
- }
- }
- [Fact]
- public void Should_Get_Previous_Caret_CharacterHit_Bidi()
- {
- const string text = "אבג 1 ABC";
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new SingleBufferTextSource(text, defaultProperties);
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- var clusters = new List<int>();
- foreach (var textRun in textLine.TextRuns.OrderBy(x => TextTestHelper.GetStartCharIndex(x.Text)))
- {
- var shapedRun = (ShapedTextRun)textRun;
- var runClusters = shapedRun.ShapedBuffer.GlyphInfos.Select(glyph => glyph.GlyphCluster);
- clusters.AddRange(shapedRun.IsReversed ? runClusters.Reverse() : runClusters);
- }
- clusters.Reverse();
- var nextCharacterHit = new CharacterHit(text.Length - 1);
- foreach (var cluster in clusters)
- {
- var currentCaretIndex = nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength;
- Assert.Equal(cluster, currentCaretIndex);
- nextCharacterHit = textLine.GetPreviousCaretCharacterHit(nextCharacterHit);
- }
- var lastCharacterHit = nextCharacterHit;
- nextCharacterHit = textLine.GetPreviousCaretCharacterHit(lastCharacterHit);
- Assert.Equal(lastCharacterHit.FirstCharacterIndex, nextCharacterHit.FirstCharacterIndex);
- Assert.Equal(lastCharacterHit.TrailingLength, nextCharacterHit.TrailingLength);
- }
- }
- [InlineData("𐐷𐐷𐐷𐐷𐐷")]
- [InlineData("01234567🎉\n")]
- [InlineData("𐐷1234")]
- [Theory]
- public void Should_Get_Next_Caret_CharacterHit(string text)
- {
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new SingleBufferTextSource(text, defaultProperties);
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- var clusters = BuildGlyphClusters(textLine);
- var nextCharacterHit = new CharacterHit(0);
- for (var i = 0; i < clusters.Count; i++)
- {
- var expectedCluster = clusters[i];
- var actualCluster = nextCharacterHit.FirstCharacterIndex;
- Assert.Equal(expectedCluster, actualCluster);
- nextCharacterHit = textLine.GetNextCaretCharacterHit(nextCharacterHit);
- }
- var lastCharacterHit = nextCharacterHit;
- nextCharacterHit = textLine.GetNextCaretCharacterHit(lastCharacterHit);
- Assert.Equal(lastCharacterHit.FirstCharacterIndex, nextCharacterHit.FirstCharacterIndex);
- Assert.Equal(lastCharacterHit.TrailingLength, nextCharacterHit.TrailingLength);
- nextCharacterHit = new CharacterHit(0, clusters[1] - clusters[0]);
- foreach (var cluster in clusters)
- {
- Assert.Equal(cluster, nextCharacterHit.FirstCharacterIndex);
- nextCharacterHit = textLine.GetNextCaretCharacterHit(nextCharacterHit);
- }
- lastCharacterHit = nextCharacterHit;
- nextCharacterHit = textLine.GetNextCaretCharacterHit(lastCharacterHit);
- Assert.Equal(lastCharacterHit.FirstCharacterIndex, nextCharacterHit.FirstCharacterIndex);
- Assert.Equal(lastCharacterHit.TrailingLength, nextCharacterHit.TrailingLength);
- }
- }
- [InlineData("𐐷𐐷𐐷𐐷𐐷")]
- [InlineData("01234567🎉\n")]
- [InlineData("𐐷1234")]
- [Theory]
- public void Should_Get_Previous_Caret_CharacterHit(string text)
- {
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new SingleBufferTextSource(text, defaultProperties);
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- var clusters = textLine.TextRuns
- .Cast<ShapedTextRun>()
- .SelectMany(x => x.ShapedBuffer.GlyphInfos, (_, glyph) => glyph.GlyphCluster)
- .ToArray();
- var previousCharacterHit = new CharacterHit(text.Length);
- for (var i = clusters.Length - 1; i >= 0; i--)
- {
- previousCharacterHit = textLine.GetPreviousCaretCharacterHit(previousCharacterHit);
- Assert.Equal(clusters[i],
- previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength);
- }
- var firstCharacterHit = previousCharacterHit;
- previousCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit);
- Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex);
- Assert.Equal(0, previousCharacterHit.TrailingLength);
- previousCharacterHit = new CharacterHit(clusters[^1], text.Length - clusters[^1]);
- for (var i = clusters.Length - 1; i > 0; i--)
- {
- previousCharacterHit = textLine.GetPreviousCaretCharacterHit(previousCharacterHit);
- Assert.Equal(clusters[i],
- previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength);
- }
- firstCharacterHit = previousCharacterHit;
- firstCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit);
- previousCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit);
- Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex);
- Assert.Equal(0, previousCharacterHit.TrailingLength);
- }
- }
- [Fact]
- public void Should_Get_Distance_From_CharacterHit()
- {
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new SingleBufferTextSource(s_multiLineText, defaultProperties);
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- var currentDistance = 0.0;
- foreach (var run in textLine.TextRuns)
- {
- var textRun = (ShapedTextRun)run;
- var glyphRun = textRun.GlyphRun;
- for (var i = 0; i < glyphRun.GlyphInfos.Count; i++)
- {
- var cluster = glyphRun.GlyphInfos[i].GlyphCluster;
- var advance = glyphRun.GlyphInfos[i].GlyphAdvance;
- var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(cluster));
- Assert.Equal(currentDistance, distance);
- currentDistance += advance;
- }
- }
- var actualDistance = textLine.GetDistanceFromCharacterHit(new CharacterHit(s_multiLineText.Length));
- Assert.Equal(currentDistance, actualDistance);
- }
- }
- [InlineData("ABC012345")] //LeftToRight
- [InlineData("זה כיף סתם לשמוע איך תנצח קרפד עץ טוב בגן")] //RightToLeft
- [Theory]
- public void Should_Get_CharacterHit_From_Distance(string text)
- {
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new SingleBufferTextSource(text, defaultProperties);
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- var isRightToLeft = IsRightToLeft(textLine);
- var rects = BuildRects(textLine);
- var glyphClusters = BuildGlyphClusters(textLine);
- for (var i = 0; i < rects.Count; i++)
- {
- var cluster = glyphClusters[i];
- var rect = rects[i];
- var characterHit = textLine.GetCharacterHitFromDistance(rect.Left);
- Assert.Equal(isRightToLeft ? cluster + 1 : cluster,
- characterHit.FirstCharacterIndex + characterHit.TrailingLength);
- }
- }
- }
- public static IEnumerable<object[]> CollapsingData
- {
- get
- {
- yield return CreateData("01234 01234 01234", 120, TextTrimming.PrefixCharacterEllipsis, "01234 01\u20264 01234");
- yield return CreateData("01234 01234", 58, TextTrimming.CharacterEllipsis, "01234 0\u2026");
- yield return CreateData("01234 01234", 58, TextTrimming.WordEllipsis, "01234\u2026");
- yield return CreateData("01234", 9, TextTrimming.CharacterEllipsis, "\u2026");
- yield return CreateData("01234", 2, TextTrimming.CharacterEllipsis, "");
- object[] CreateData(string text, double width, TextTrimming mode, string expected)
- {
- return new object[]
- {
- text, width, mode, expected
- };
- }
- }
- }
- [MemberData(nameof(CollapsingData))]
- [Theory]
- public void Should_Collapse_Line(string text, double width, TextTrimming trimming, string expected)
- {
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new SingleBufferTextSource(text, defaultProperties);
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- Assert.False(textLine.HasCollapsed);
- TextCollapsingProperties collapsingProperties = trimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, defaultProperties));
- var collapsedLine = textLine.Collapse(collapsingProperties);
- Assert.True(collapsedLine.HasCollapsed);
- var trimmedText = collapsedLine.TextRuns.SelectMany(x => x.Text.ToString()).ToArray();
- Assert.Equal(expected.Length, trimmedText.Length);
- for (var i = 0; i < expected.Length; i++)
- {
- Assert.Equal(expected[i], trimmedText[i]);
- }
- }
- }
- [Fact]
- public void Should_Get_Next_CharacterHit_For_Drawable_Runs()
- {
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new DrawableRunTextSource();
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- Assert.Equal(4, textLine.TextRuns.Count);
- var currentHit = textLine.GetNextCaretCharacterHit(new CharacterHit(0));
- Assert.Equal(1, currentHit.FirstCharacterIndex);
- Assert.Equal(0, currentHit.TrailingLength);
- currentHit = textLine.GetNextCaretCharacterHit(currentHit);
- Assert.Equal(2, currentHit.FirstCharacterIndex);
- Assert.Equal(0, currentHit.TrailingLength);
- currentHit = textLine.GetNextCaretCharacterHit(currentHit);
- Assert.Equal(3, currentHit.FirstCharacterIndex);
- Assert.Equal(0, currentHit.TrailingLength);
- currentHit = textLine.GetNextCaretCharacterHit(currentHit);
- Assert.Equal(4, currentHit.FirstCharacterIndex);
- Assert.Equal(0, currentHit.TrailingLength);
- }
- }
- [Fact]
- public void Should_Get_Previous_CharacterHit_For_Drawable_Runs()
- {
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new DrawableRunTextSource();
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- Assert.Equal(4, textLine.TextRuns.Count);
- var currentHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(3, 1));
- Assert.Equal(2, currentHit.FirstCharacterIndex);
- Assert.Equal(1, currentHit.TrailingLength);
- currentHit = textLine.GetPreviousCaretCharacterHit(currentHit);
- Assert.Equal(1, currentHit.FirstCharacterIndex);
- Assert.Equal(1, currentHit.TrailingLength);
- currentHit = textLine.GetPreviousCaretCharacterHit(currentHit);
- Assert.Equal(0, currentHit.FirstCharacterIndex);
- Assert.Equal(1, currentHit.TrailingLength);
- currentHit = textLine.GetPreviousCaretCharacterHit(currentHit);
- Assert.Equal(0, currentHit.FirstCharacterIndex);
- Assert.Equal(0, currentHit.TrailingLength);
- }
- }
- [Fact]
- public void Should_Get_CharacterHit_From_Distance_For_Drawable_Runs()
- {
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new DrawableRunTextSource();
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- var characterHit = textLine.GetCharacterHitFromDistance(50);
- Assert.Equal(5, characterHit.FirstCharacterIndex);
- Assert.Equal(1, characterHit.TrailingLength);
- characterHit = textLine.GetCharacterHitFromDistance(32);
- Assert.Equal(3, characterHit.FirstCharacterIndex);
- Assert.Equal(0, characterHit.TrailingLength);
- }
- }
- [Fact]
- public void Should_Get_Distance_From_CharacterHit_Drawable_Runs()
- {
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new DrawableRunTextSource();
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(1));
- Assert.Equal(14, distance);
- distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(2));
- Assert.True(distance > 14);
- }
- }
- [Fact]
- public void Should_Get_Distance_From_CharacterHit_Mixed_TextBuffer()
- {
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new MixedTextBufferTextSource();
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(10));
- Assert.Equal(72.01171875, distance);
- distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(20));
- Assert.Equal(144.0234375, distance);
- distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(30));
- Assert.Equal(216.03515625, distance);
- distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(40));
- Assert.Equal(textLine.WidthIncludingTrailingWhitespace, distance);
- }
- }
- [Fact]
- public void Should_Get_TextBounds_From_Mixed_TextBuffer()
- {
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new MixedTextBufferTextSource();
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- var textBounds = textLine.GetTextBounds(0, 10);
- Assert.Equal(1, textBounds.Count);
- Assert.Equal(72.01171875, textBounds[0].Rectangle.Width);
- textBounds = textLine.GetTextBounds(0, 20);
- Assert.Equal(2, textBounds.Count);
- Assert.Equal(144.0234375, textBounds.Sum(x => x.Rectangle.Width));
- textBounds = textLine.GetTextBounds(0, 30);
- Assert.Equal(3, textBounds.Count);
- Assert.Equal(216.03515625, textBounds.Sum(x => x.Rectangle.Width));
- textBounds = textLine.GetTextBounds(0, 40);
- Assert.Equal(4, textBounds.Count);
- Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
- }
- }
- [Fact]
- public void Should_GetTextRange()
- {
- var text = "שדגככעיחדגכAישדגשדגחייטYDASYWIWחיחלדשSAטויליHUHIUHUIDWKLאא'ק'קחליק/'וקןגגגלךשף'/קפוכדגכשדגשיח'/קטאגשד";
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new SingleBufferTextSource(text, defaultProperties);
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- var textRuns = textLine.TextRuns.Cast<ShapedTextRun>().ToList();
- var lineWidth = textLine.WidthIncludingTrailingWhitespace;
- var textBounds = textLine.GetTextBounds(0, text.Length);
- TextBounds lastBounds = null;
- var runBounds = textBounds.SelectMany(x => x.TextRunBounds).ToList();
- Assert.Equal(textRuns.Count, runBounds.Count);
- for (var i = 0; i < textRuns.Count; i++)
- {
- var run = textRuns[i];
- var bounds = runBounds[i];
- Assert.Equal(TextTestHelper.GetStartCharIndex(run.Text), bounds.TextSourceCharacterIndex);
- Assert.Equal(run, bounds.TextRun);
- Assert.Equal(run.Size.Width, bounds.Rectangle.Width);
- }
- for (var i = 0; i < textBounds.Count; i++)
- {
- var currentBounds = textBounds[i];
- if (lastBounds != null)
- {
- Assert.Equal(lastBounds.Rectangle.Right, currentBounds.Rectangle.Left);
- }
- var sumOfRunWidth = currentBounds.TextRunBounds.Sum(x => x.Rectangle.Width);
- Assert.Equal(sumOfRunWidth, currentBounds.Rectangle.Width);
- lastBounds = currentBounds;
- }
- var sumOfBoundsWidth = textBounds.Sum(x => x.Rectangle.Width);
- Assert.Equal(lineWidth, sumOfBoundsWidth);
- }
- }
- private class MixedTextBufferTextSource : ITextSource
- {
- public TextRun? GetTextRun(int textSourceIndex)
- {
- switch (textSourceIndex)
- {
- case 0:
- return new TextCharacters("aaaaaaaaaa", new GenericTextRunProperties(Typeface.Default));
- case 10:
- return new TextCharacters("bbbbbbbbbb", new GenericTextRunProperties(Typeface.Default));
- case 20:
- return new TextCharacters("cccccccccc", new GenericTextRunProperties(Typeface.Default));
- case 30:
- return new TextCharacters("dddddddddd", new GenericTextRunProperties(Typeface.Default));
- default:
- return null;
- }
- }
- }
- private class DrawableRunTextSource : ITextSource
- {
- private const string Text = "_A_A";
- public TextRun GetTextRun(int textSourceIndex)
- {
- switch (textSourceIndex)
- {
- case 0:
- return new CustomDrawableRun();
- case 1:
- return new TextCharacters(Text, new GenericTextRunProperties(Typeface.Default));
- case 5:
- return new CustomDrawableRun();
- case 6:
- return new TextCharacters(Text, new GenericTextRunProperties(Typeface.Default));
- default:
- return null;
- }
- }
- }
- private class CustomDrawableRun : DrawableTextRun
- {
- public override Size Size => new(14, 14);
- public override double Baseline => 14;
- public override void Draw(DrawingContext drawingContext, Point origin)
- {
- }
- }
- private static bool IsRightToLeft(TextLine textLine)
- {
- return textLine.TextRuns.Cast<ShapedTextRun>().Any(x => !x.ShapedBuffer.IsLeftToRight);
- }
- private static List<int> BuildGlyphClusters(TextLine textLine)
- {
- var glyphClusters = new List<int>();
- var shapedTextRuns = textLine.TextRuns.Cast<ShapedTextRun>().ToList();
- var lastCluster = -1;
- foreach (var textRun in shapedTextRuns)
- {
- var shapedBuffer = textRun.ShapedBuffer;
- var currentClusters = shapedBuffer.GlyphInfos.Select(glyph => glyph.GlyphCluster).ToList();
- foreach (var currentCluster in currentClusters)
- {
- if (lastCluster == currentCluster)
- {
- continue;
- }
- glyphClusters.Add(currentCluster);
- lastCluster = currentCluster;
- }
- }
- return glyphClusters;
- }
- private static List<Rect> BuildRects(TextLine textLine)
- {
- var rects = new List<Rect>();
- var height = textLine.Height;
- var currentX = 0d;
- var lastCluster = -1;
- var shapedTextRuns = textLine.TextRuns.Cast<ShapedTextRun>().ToList();
- foreach (var textRun in shapedTextRuns)
- {
- var shapedBuffer = textRun.ShapedBuffer;
- for (var index = 0; index < shapedBuffer.GlyphInfos.Length; index++)
- {
- var currentCluster = shapedBuffer.GlyphInfos[index].GlyphCluster;
- var advance = shapedBuffer.GlyphInfos[index].GlyphAdvance;
- if (lastCluster != currentCluster)
- {
- rects.Add(new Rect(currentX, 0, advance, height));
- }
- else
- {
- var rect = rects[index - 1];
- rects.Remove(rect);
- rect = rect.WithWidth(rect.Width + advance);
- rects.Add(rect);
- }
- currentX += advance;
- lastCluster = currentCluster;
- }
- }
- return rects;
- }
- [Fact]
- public void Should_Get_TextBounds_Mixed()
- {
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var text = "0123";
- var shaperOption = new TextShaperOptions(Typeface.Default.GlyphTypeface, 10, 0, CultureInfo.CurrentCulture);
- var firstRun = new ShapedTextRun(TextShaper.Current.ShapeText(text, shaperOption), defaultProperties);
- var textRuns = new List<TextRun>
- {
- new CustomDrawableRun(),
- firstRun,
- new CustomDrawableRun(),
- new ShapedTextRun(TextShaper.Current.ShapeText(text, shaperOption), defaultProperties),
- new CustomDrawableRun(),
- new ShapedTextRun(TextShaper.Current.ShapeText(text, shaperOption), defaultProperties)
- };
- var textSource = new FixedRunsTextSource(textRuns);
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, double.PositiveInfinity,
- new GenericTextParagraphProperties(defaultProperties));
- var textBounds = textLine.GetTextBounds(0, textLine.Length);
- Assert.Equal(6, textBounds.Count);
- Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
- textBounds = textLine.GetTextBounds(0, 1);
- Assert.Equal(1, textBounds.Count);
- Assert.Equal(14, textBounds[0].Rectangle.Width);
- textBounds = textLine.GetTextBounds(0, firstRun.Length + 1);
- Assert.Equal(2, textBounds.Count);
- Assert.Equal(firstRun.Size.Width + 14, textBounds.Sum(x => x.Rectangle.Width));
- textBounds = textLine.GetTextBounds(1, firstRun.Length);
- Assert.Equal(1, textBounds.Count);
- Assert.Equal(firstRun.Size.Width, textBounds[0].Rectangle.Width);
- textBounds = textLine.GetTextBounds(0, 1 + firstRun.Length);
- Assert.Equal(2, textBounds.Count);
- Assert.Equal(firstRun.Size.Width + 14, textBounds.Sum(x => x.Rectangle.Width));
- }
- }
- [Fact]
- public void Should_Get_TextBounds_BiDi_LeftToRight()
- {
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var text = "אאא AAA";
- var textSource = new SingleBufferTextSource(text, defaultProperties);
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, 200,
- new GenericTextParagraphProperties(FlowDirection.LeftToRight, TextAlignment.Left,
- true, true, defaultProperties, TextWrapping.NoWrap, 0, 0, 0));
- var textBounds = textLine.GetTextBounds(0, 3);
- var firstRun = textLine.TextRuns[0] as ShapedTextRun;
- Assert.Equal(1, textBounds.Count);
- Assert.Equal(firstRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
- textBounds = textLine.GetTextBounds(3, 4);
- var secondRun = textLine.TextRuns[1] as ShapedTextRun;
- Assert.Equal(1, textBounds.Count);
- Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
- textBounds = textLine.GetTextBounds(0, 4);
- Assert.Equal(2, textBounds.Count);
- Assert.Equal(firstRun.Size.Width, textBounds[0].Rectangle.Width);
- Assert.Equal(7.201171875, textBounds[1].Rectangle.Width);
- Assert.Equal(firstRun.Size.Width, textBounds[1].Rectangle.Left);
- textBounds = textLine.GetTextBounds(0, text.Length);
- Assert.Equal(2, textBounds.Count);
- Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
- }
- }
- [Fact]
- public void Should_Get_TextBounds_BiDi_RightToLeft()
- {
- using (Start())
- {
- var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var text = "אאא AAA";
- var textSource = new SingleBufferTextSource(text, defaultProperties);
- var formatter = new TextFormatterImpl();
- var textLine =
- formatter.FormatLine(textSource, 0, 200,
- new GenericTextParagraphProperties(FlowDirection.RightToLeft, TextAlignment.Left,
- true, true, defaultProperties, TextWrapping.NoWrap, 0, 0, 0));
- var textBounds = textLine.GetTextBounds(0, 4);
- var secondRun = textLine.TextRuns[1] as ShapedTextRun;
- Assert.Equal(1, textBounds.Count);
- Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
- textBounds = textLine.GetTextBounds(4, 3);
- var firstRun = textLine.TextRuns[0] as ShapedTextRun;
- Assert.Equal(1, textBounds.Count);
- Assert.Equal(3, textBounds[0].TextRunBounds.Sum(x => x.Length));
- Assert.Equal(firstRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
- textBounds = textLine.GetTextBounds(0, 5);
- Assert.Equal(2, textBounds.Count);
- Assert.Equal(5, textBounds.Sum(x => x.TextRunBounds.Sum(x => x.Length)));
- Assert.Equal(secondRun.Size.Width, textBounds[1].Rectangle.Width);
- Assert.Equal(7.201171875, textBounds[0].Rectangle.Width);
- Assert.Equal(textLine.Start + 7.201171875, textBounds[0].Rectangle.Right);
- Assert.Equal(textLine.Start + firstRun.Size.Width, textBounds[1].Rectangle.Left);
- textBounds = textLine.GetTextBounds(0, text.Length);
- Assert.Equal(2, textBounds.Count);
- Assert.Equal(7, textBounds.Sum(x => x.TextRunBounds.Sum(x => x.Length)));
- Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
- }
- }
- private class FixedRunsTextSource : ITextSource
- {
- private readonly IReadOnlyList<TextRun> _textRuns;
- public FixedRunsTextSource(IReadOnlyList<TextRun> textRuns)
- {
- _textRuns = textRuns;
- }
- public TextRun GetTextRun(int textSourceIndex)
- {
- var currentPosition = 0;
- foreach (var textRun in _textRuns)
- {
- if (currentPosition == textSourceIndex)
- {
- return textRun;
- }
- currentPosition += textRun.Length;
- }
- return null;
- }
- }
- private static IDisposable Start()
- {
- var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface
- .With(renderInterface: new PlatformRenderInterface(null),
- textShaperImpl: new TextShaperImpl(),
- fontManagerImpl: new CustomFontManagerImpl()));
- return disposable;
- }
- }
- }
|