public void BasicLatinTest()
        {
            var lineBreaker = new LineBreakEnumerator("Hello World\r\nThis is a test.".AsMemory());

            Assert.True(lineBreaker.MoveNext());
            Assert.Equal(6, lineBreaker.Current.PositionWrap);
            Assert.False(lineBreaker.Current.Required);

            Assert.True(lineBreaker.MoveNext());
            Assert.Equal(13, lineBreaker.Current.PositionWrap);
            Assert.True(lineBreaker.Current.Required);

            Assert.True(lineBreaker.MoveNext());
            Assert.Equal(18, lineBreaker.Current.PositionWrap);
            Assert.False(lineBreaker.Current.Required);

            Assert.True(lineBreaker.MoveNext());
            Assert.Equal(21, lineBreaker.Current.PositionWrap);
            Assert.False(lineBreaker.Current.Required);

            Assert.True(lineBreaker.MoveNext());
            Assert.Equal(23, lineBreaker.Current.PositionWrap);
            Assert.False(lineBreaker.Current.Required);

            Assert.True(lineBreaker.MoveNext());
            Assert.Equal(28, lineBreaker.Current.PositionWrap);
            Assert.False(lineBreaker.Current.Required);

            Assert.False(lineBreaker.MoveNext());
        }
Exemple #2
0
        private static bool TryGetLineBreak(TextRun textRun, out LineBreak lineBreak)
        {
            lineBreak = default;

            if (textRun.Text.IsEmpty)
            {
                return(false);
            }

            var lineBreakEnumerator = new LineBreakEnumerator(textRun.Text);

            while (lineBreakEnumerator.MoveNext())
            {
                if (!lineBreakEnumerator.Current.Required)
                {
                    continue;
                }

                lineBreak = lineBreakEnumerator.Current;

                return(lineBreak.PositionWrap >= textRun.Text.Length || true);
            }

            return(false);
        }
Exemple #3
0
        public void Should_Enumerate_LineBreaks(string text, int expectedLength)
        {
            var textMemory = text.AsMemory();

            var enumerator = new LineBreakEnumerator(textMemory);

            Assert.True(enumerator.MoveNext());

            Assert.Equal(expectedLength, enumerator.Current.PositionWrap);
        }
        private static List <LineBreak> GetBreaks(LineBreakEnumerator lineBreaker)
        {
            var breaks = new List <LineBreak>();

            while (lineBreaker.MoveNext())
            {
                breaks.Add(lineBreaker.Current);
            }

            return(breaks);
        }
        public void Should_Wrap(string text, string familyName, int numberOfCharactersPerLine)
        {
            using (Start())
            {
                var lineBreaker = new LineBreakEnumerator(text.AsMemory());

                var expected = new List <int>();

                while (lineBreaker.MoveNext())
                {
                    expected.Add(lineBreaker.Current.PositionWrap - 1);
                }

                var typeface = new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#" +
                                            familyName);

                var defaultProperties = new GenericTextRunProperties(Typeface.Default);

                var textSource = new SingleBufferTextSource(text, defaultProperties);

                var formatter = new TextFormatterImpl();

                var glyph = typeface.GlyphTypeface.GetGlyph('a');

                var advance = typeface.GlyphTypeface.GetGlyphAdvance(glyph) *
                              (12.0 / typeface.GlyphTypeface.DesignEmHeight);

                var paragraphWidth = advance * numberOfCharactersPerLine;

                var currentPosition = 0;

                while (currentPosition < text.Length)
                {
                    var textLine =
                        formatter.FormatLine(textSource, currentPosition, paragraphWidth,
                                             new GenericTextParagraphProperties(defaultProperties, textWrap: TextWrapping.Wrap));

                    var end = textLine.FirstTextSourceIndex + textLine.Length - 1;

                    Assert.True(expected.Contains(end));

                    var index = expected.IndexOf(end);

                    for (var i = 0; i <= index; i++)
                    {
                        expected.RemoveAt(0);
                    }

                    currentPosition += textLine.Length;
                }
            }
        }
        public void ForwardTest()
        {
            var lineBreaker = new LineBreakEnumerator("Apples Pears Bananas".AsMemory());

            var positionsF = GetBreaks(lineBreaker);

            Assert.Equal(7, positionsF[0].PositionWrap);
            Assert.Equal(6, positionsF[0].PositionMeasure);
            Assert.Equal(13, positionsF[1].PositionWrap);
            Assert.Equal(12, positionsF[1].PositionMeasure);
            Assert.Equal(20, positionsF[2].PositionWrap);
            Assert.Equal(20, positionsF[2].PositionMeasure);
        }
        public void ForwardTextWithOuterWhitespace()
        {
            var lineBreaker = new LineBreakEnumerator(" Apples Pears Bananas   ".AsMemory());
            var positionsF  = GetBreaks(lineBreaker);

            Assert.Equal(1, positionsF[0].PositionWrap);
            Assert.Equal(0, positionsF[0].PositionMeasure);
            Assert.Equal(8, positionsF[1].PositionWrap);
            Assert.Equal(7, positionsF[1].PositionMeasure);
            Assert.Equal(14, positionsF[2].PositionWrap);
            Assert.Equal(13, positionsF[2].PositionMeasure);
            Assert.Equal(24, positionsF[3].PositionWrap);
            Assert.Equal(21, positionsF[3].PositionMeasure);
        }
        public void ShouldFindBreaks(int lineNumber, int[] codePoints, int[] breakPoints)
        {
            var text = string.Join(null, codePoints.Select(char.ConvertFromUtf32));

            var lineBreaker = new LineBreakEnumerator(text.AsMemory());

            var foundBreaks = new List <int>();

            while (lineBreaker.MoveNext())
            {
                foundBreaks.Add(lineBreaker.Current.PositionWrap);
            }

            // Check the same
            var pass = true;

            if (foundBreaks.Count != breakPoints.Length)
            {
                pass = false;
            }
            else
            {
                for (var i = 0; i < foundBreaks.Count; i++)
                {
                    if (foundBreaks[i] != breakPoints[i])
                    {
                        pass = false;
                    }
                }
            }

            if (!pass)
            {
                _outputHelper.WriteLine($"Failed test on line {lineNumber}");
                _outputHelper.WriteLine("");
                _outputHelper.WriteLine($"    Code Points: {string.Join(" ", codePoints)}");
                _outputHelper.WriteLine($"Expected Breaks: {string.Join(" ", breakPoints)}");
                _outputHelper.WriteLine($"  Actual Breaks: {string.Join(" ", foundBreaks)}");
                _outputHelper.WriteLine($"           Text: {text}");
                _outputHelper.WriteLine($"     Char Props: {string.Join(" ", codePoints.Select(x => new Codepoint(x).LineBreakClass))}");
                _outputHelper.WriteLine("");
            }

            Assert.True(pass);
        }
Exemple #9
0
        public void Should_Split_Text_By_Explicit_Breaks()
        {
            //ABC [0 3]
            //DEF\r[4 7]
            //\r[8]
            //Hello\r\n[9 15]
            const string text = "ABC DEF\r\rHELLO\r\n";

            var buffer = new ReadOnlySlice <char>(text.AsMemory());

            var lineBreaker = new LineBreakEnumerator(buffer);

            var current = 0;

            Assert.True(lineBreaker.MoveNext());

            var a = text.Substring(current, lineBreaker.Current.PositionMeasure - current + 1);

            Assert.Equal("ABC ", a);

            current += a.Length;

            Assert.True(lineBreaker.MoveNext());

            var b = text.Substring(current, lineBreaker.Current.PositionMeasure - current + 1);

            Assert.Equal("DEF\r", b);

            current += b.Length;

            Assert.True(lineBreaker.MoveNext());

            var c = text.Substring(current, lineBreaker.Current.PositionMeasure - current + 1);

            Assert.Equal("\r", c);

            current += c.Length;

            Assert.True(lineBreaker.MoveNext());

            var d = text.Substring(current, text.Length - current);

            Assert.Equal("HELLO\r\n", d);
        }
Exemple #10
0
        private static bool TryGetLineBreak(TextRun textRun, out LineBreak lineBreak)
        {
            lineBreak = default;

            if (textRun.Text.IsEmpty)
            {
                return(false);
            }

            var lineBreakEnumerator = new LineBreakEnumerator(textRun.Text);

            while (lineBreakEnumerator.MoveNext())
            {
                if (!lineBreakEnumerator.Current.Required)
                {
                    continue;
                }

                lineBreak = lineBreakEnumerator.Current;

                if (lineBreak.PositionWrap >= textRun.Text.Length)
                {
                    return(true);
                }

                //The line breaker isn't treating \n\r as a pair so we have to fix that here.
                if (textRun.Text[lineBreak.PositionMeasure] == '\n' &&
                    textRun.Text[lineBreak.PositionWrap] == '\r')
                {
                    lineBreak = new LineBreak(lineBreak.PositionMeasure, lineBreak.PositionWrap + 1,
                                              lineBreak.Required);
                }

                return(true);
            }

            return(false);
        }
        private static int FindPositionForTextWrapping(StringRange range, int maxIndex)
        {
            if (maxIndex > range.Length - 1)
            {
                maxIndex = range.Length - 1;
            }

            LineBreakEnumerator lineBreakEnumerator = new LineBreakEnumerator(
                new ReadOnlySlice <char>(range.String.AsMemory().Slice(range.OffsetToFirstChar, range.Length)));

            LineBreak?lineBreak = null;

            while (lineBreakEnumerator.MoveNext())
            {
                if (lineBreakEnumerator.Current.PositionWrap > maxIndex)
                {
                    break;
                }

                lineBreak = lineBreakEnumerator.Current;
            }

            return(lineBreak.HasValue ? lineBreak.Value.PositionWrap : maxIndex);
        }
Exemple #12
0
        /// <summary>
        /// Updates the layout and applies specified text style overrides.
        /// </summary>
        private void UpdateLayout()
        {
            if (_text.IsEmpty || Math.Abs(MaxWidth) < double.Epsilon || Math.Abs(MaxHeight) < double.Epsilon)
            {
                var textLine = CreateEmptyTextLine(0);

                TextLines = new List <TextLine> {
                    textLine
                };

                Bounds = new Rect(textLine.LineMetrics.BaselineOrigin.X, 0, 0, textLine.LineMetrics.Size.Height);
            }
            else
            {
                var textLines = new List <TextLine>();

                double left = 0.0, right = 0.0, bottom = 0.0;

                var lineBreaker = new LineBreakEnumerator(_text);

                var currentPosition = 0;

                while (currentPosition < _text.Length && (MaxLines == 0 || textLines.Count < MaxLines))
                {
                    int length;

                    if (lineBreaker.MoveNext())
                    {
                        if (!lineBreaker.Current.Required)
                        {
                            continue;
                        }

                        length = lineBreaker.Current.PositionWrap - currentPosition;

                        if (currentPosition + length < _text.Length)
                        {
                            //The line breaker isn't treating \n\r as a pair so we have to fix that here.
                            if (_text[lineBreaker.Current.PositionMeasure] == '\n' &&
                                _text[lineBreaker.Current.PositionWrap] == '\r')
                            {
                                length++;
                            }
                        }
                    }
                    else
                    {
                        length = _text.Length - currentPosition;
                    }

                    var remainingLength = length;

                    while (remainingLength > 0 && (MaxLines == 0 || textLines.Count < MaxLines))
                    {
                        var textSlice = _text.AsSlice(currentPosition, remainingLength);

                        var textSource = new FormattedTextSource(textSlice, _paragraphProperties.DefaultTextStyle, _textStyleOverrides);

                        var textLine = TextFormatter.Current.FormatLine(textSource, 0, MaxWidth, _paragraphProperties);

                        UpdateBounds(textLine, ref left, ref right, ref bottom);

                        textLines.Add(textLine);

                        if (!double.IsPositiveInfinity(MaxHeight) && bottom + textLine.LineMetrics.Size.Height > MaxHeight)
                        {
                            currentPosition = _text.Length;
                            break;
                        }

                        if (_paragraphProperties.TextTrimming != TextTrimming.None)
                        {
                            currentPosition += remainingLength;

                            break;
                        }

                        remainingLength -= textLine.Text.Length;

                        currentPosition += textLine.Text.Length;
                    }
                }

                if (lineBreaker.Current.Required && currentPosition == _text.Length)
                {
                    var emptyTextLine = CreateEmptyTextLine(currentPosition);

                    UpdateBounds(emptyTextLine, ref left, ref right, ref bottom);

                    textLines.Add(emptyTextLine);
                }

                Bounds = new Rect(left, 0, right, bottom);

                TextLines = textLines;
            }
        }
Exemple #13
0
        /// <summary>
        /// Performs text wrapping returns a list of text lines.
        /// </summary>
        /// <param name="textRuns">The text run's.</param>
        /// <param name="textRange">The text range that is covered by the text runs.</param>
        /// <param name="paragraphWidth">The paragraph width.</param>
        /// <param name="paragraphProperties">The text paragraph properties.</param>
        /// <returns>The wrapped text line.</returns>
        private static TextLine PerformTextWrapping(IReadOnlyList <ShapedTextCharacters> textRuns, TextRange textRange,
                                                    double paragraphWidth, TextParagraphProperties paragraphProperties)
        {
            var availableWidth = paragraphWidth;
            var currentWidth   = 0.0;
            var runIndex       = 0;
            var currentLength  = 0;

            while (runIndex < textRuns.Count)
            {
                var currentRun = textRuns[runIndex];

                if (currentWidth + currentRun.GlyphRun.Bounds.Width > availableWidth)
                {
                    var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth);

                    var breakFound = false;

                    var currentBreakPosition = 0;

                    if (measuredLength < currentRun.Text.Length)
                    {
                        var lineBreaker = new LineBreakEnumerator(currentRun.Text);

                        while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
                        {
                            var nextBreakPosition = lineBreaker.Current.PositionWrap;

                            if (nextBreakPosition == 0 || nextBreakPosition > measuredLength)
                            {
                                break;
                            }

                            breakFound = lineBreaker.Current.Required ||
                                         lineBreaker.Current.PositionWrap != currentRun.Text.Length;

                            currentBreakPosition = nextBreakPosition;
                        }
                    }

                    if (breakFound)
                    {
                        measuredLength = currentBreakPosition;
                    }
                    else
                    {
                        if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow)
                        {
                            var lineBreaker = new LineBreakEnumerator(currentRun.Text.Skip(currentBreakPosition));

                            if (lineBreaker.MoveNext())
                            {
                                measuredLength = currentBreakPosition + lineBreaker.Current.PositionWrap;
                            }
                        }
                    }

                    currentLength += measuredLength;

                    var splitResult = SplitTextRuns(textRuns, currentLength);

                    var textLineMetrics = TextLineMetrics.Create(splitResult.First,
                                                                 new TextRange(textRange.Start, currentLength), paragraphWidth, paragraphProperties);

                    var lineBreak = splitResult.Second != null && splitResult.Second.Count > 0 ?
                                    new TextLineBreak(splitResult.Second) :
                                    null;

                    return(new TextLineImpl(splitResult.First, textLineMetrics, lineBreak));
                }

                currentWidth += currentRun.GlyphRun.Bounds.Width;

                currentLength += currentRun.GlyphRun.Characters.Length;

                runIndex++;
            }

            return(new TextLineImpl(textRuns,
                                    TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties)));
        }
Exemple #14
0
        public static List <DrawableTextRun>?Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis)
        {
            if (textLine.TextRuns is not List <DrawableTextRun> textRuns || textRuns.Count == 0)
            {
                return(null);
            }

            var runIndex        = 0;
            var currentWidth    = 0.0;
            var collapsedLength = 0;
            var shapedSymbol    = TextFormatterImpl.CreateSymbol(properties.Symbol, FlowDirection.LeftToRight);

            if (properties.Width < shapedSymbol.GlyphRun.Size.Width)
            {
                //Not enough space to fit in the symbol
                return(new List <DrawableTextRun>(0));
            }

            var availableWidth = properties.Width - shapedSymbol.Size.Width;

            while (runIndex < textRuns.Count)
            {
                var currentRun = textRuns[runIndex];

                switch (currentRun)
                {
                case ShapedTextCharacters shapedRun:
                {
                    currentWidth += shapedRun.Size.Width;

                    if (currentWidth > availableWidth)
                    {
                        if (shapedRun.TryMeasureCharacters(availableWidth, out var measuredLength))
                        {
                            if (isWordEllipsis && measuredLength < textLine.Length)
                            {
                                var currentBreakPosition = 0;

                                var lineBreaker = new LineBreakEnumerator(currentRun.Text);

                                while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
                                {
                                    var nextBreakPosition = lineBreaker.Current.PositionMeasure;

                                    if (nextBreakPosition == 0)
                                    {
                                        break;
                                    }

                                    if (nextBreakPosition >= measuredLength)
                                    {
                                        break;
                                    }

                                    currentBreakPosition = nextBreakPosition;
                                }

                                measuredLength = currentBreakPosition;
                            }
                        }

                        collapsedLength += measuredLength;

                        var collapsedRuns = new List <DrawableTextRun>(textRuns.Count);

                        if (collapsedLength > 0)
                        {
                            var splitResult = TextFormatterImpl.SplitDrawableRuns(textRuns, collapsedLength);

                            collapsedRuns.AddRange(splitResult.First);
                        }

                        collapsedRuns.Add(shapedSymbol);

                        return(collapsedRuns);
                    }

                    availableWidth -= currentRun.Size.Width;


                    break;
                }

                case { } drawableRun:
                {
                    //The whole run needs to fit into available space
                    if (currentWidth + drawableRun.Size.Width > availableWidth)
                    {
                        var collapsedRuns = new List <DrawableTextRun>(textRuns.Count);

                        if (collapsedLength > 0)
                        {
                            var splitResult = TextFormatterImpl.SplitDrawableRuns(textRuns, collapsedLength);

                            collapsedRuns.AddRange(splitResult.First);
                        }

                        collapsedRuns.Add(shapedSymbol);

                        return(collapsedRuns);
                    }

                    break;
                }
                }

                collapsedLength += currentRun.TextSourceLength;

                runIndex++;
            }

            return(null);
        }
        /// <summary>
        /// Performs text wrapping returns a list of text lines.
        /// </summary>
        /// <param name="textRuns">The text run's.</param>
        /// <param name="textRange">The text range that is covered by the text runs.</param>
        /// <param name="paragraphWidth">The paragraph width.</param>
        /// <param name="paragraphProperties">The text paragraph properties.</param>
        /// <param name="currentLineBreak">The current line break if the line was explicitly broken.</param>
        /// <returns>The wrapped text line.</returns>
        private static TextLine PerformTextWrapping(List <ShapedTextCharacters> textRuns, TextRange textRange,
                                                    double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak?currentLineBreak)
        {
            var availableWidth = paragraphWidth;
            var currentWidth   = 0.0;
            var measuredLength = 0;

            foreach (var currentRun in textRuns)
            {
                if (currentWidth + currentRun.Size.Width > availableWidth)
                {
                    if (TryMeasureCharacters(currentRun, paragraphWidth - currentWidth, out var count))
                    {
                        measuredLength += count;
                    }

                    break;
                }

                currentWidth += currentRun.Size.Width;

                measuredLength += currentRun.Text.Length;
            }

            var currentLength = 0;

            var lastWrapPosition = 0;

            var currentPosition = 0;

            if (measuredLength == 0 && paragraphProperties.TextWrapping != TextWrapping.WrapWithOverflow)
            {
                measuredLength = 1;
            }
            else
            {
                for (var index = 0; index < textRuns.Count; index++)
                {
                    var currentRun = textRuns[index];

                    var lineBreaker = new LineBreakEnumerator(currentRun.Text);

                    var breakFound = false;

                    while (lineBreaker.MoveNext())
                    {
                        if (lineBreaker.Current.Required &&
                            currentLength + lineBreaker.Current.PositionMeasure <= measuredLength)
                        {
                            breakFound = true;

                            currentPosition = currentLength + lineBreaker.Current.PositionWrap;

                            break;
                        }

                        if ((paragraphProperties.TextWrapping != TextWrapping.WrapWithOverflow || lastWrapPosition != 0) &&
                            currentLength + lineBreaker.Current.PositionMeasure > measuredLength)
                        {
                            if (lastWrapPosition > 0)
                            {
                                currentPosition = lastWrapPosition;
                            }
                            else
                            {
                                currentPosition = currentLength + measuredLength;
                            }

                            breakFound = true;

                            break;
                        }

                        if (currentLength + lineBreaker.Current.PositionWrap >= measuredLength)
                        {
                            currentPosition = currentLength + lineBreaker.Current.PositionWrap;

                            if (index < textRuns.Count - 1 &&
                                lineBreaker.Current.PositionWrap == currentRun.Text.Length)
                            {
                                var nextRun = textRuns[index + 1];

                                lineBreaker = new LineBreakEnumerator(nextRun.Text);

                                if (lineBreaker.MoveNext() &&
                                    lineBreaker.Current.PositionMeasure == 0)
                                {
                                    currentPosition += lineBreaker.Current.PositionWrap;
                                }
                            }

                            breakFound = true;

                            break;
                        }

                        lastWrapPosition = currentLength + lineBreaker.Current.PositionWrap;
                    }

                    if (!breakFound)
                    {
                        currentLength += currentRun.Text.Length;

                        continue;
                    }

                    measuredLength = currentPosition;

                    break;
                }
            }

            var splitResult = SplitTextRuns(textRuns, measuredLength);

            textRange = new TextRange(textRange.Start, measuredLength);

            var remainingCharacters = splitResult.Second;

            var lineBreak = remainingCharacters?.Count > 0 ? new TextLineBreak(remainingCharacters) : null;

            if (lineBreak is null && currentLineBreak?.TextEndOfLine != null)
            {
                lineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine);
            }

            return(new TextLineImpl(splitResult.First, textRange, paragraphWidth, paragraphProperties, lineBreak));
        }
        /// <summary>
        /// Performs text wrapping returns a list of text lines.
        /// </summary>
        /// <param name="paragraphProperties">The text paragraph properties.</param>
        /// <param name="textRuns">The text run'S.</param>
        /// <param name="text">The text to analyze for break opportunities.</param>
        /// <param name="paragraphWidth"></param>
        /// <returns></returns>
        private static TextLine PerformTextWrapping(TextPointer text, IReadOnlyList <ShapedTextRun> textRuns,
                                                    double paragraphWidth, TextParagraphProperties paragraphProperties)
        {
            var availableWidth = paragraphWidth;
            var currentWidth   = 0.0;
            var runIndex       = 0;
            var length         = 0;

            while (runIndex < textRuns.Count)
            {
                var currentRun = textRuns[runIndex];

                if (currentWidth + currentRun.GlyphRun.Bounds.Width > availableWidth)
                {
                    var measuredLength = MeasureText(currentRun, paragraphWidth - currentWidth);

                    if (measuredLength < currentRun.Text.Length)
                    {
                        var currentBreakPosition = -1;

                        var lineBreaker = new LineBreakEnumerator(currentRun.Text);

                        while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
                        {
                            var nextBreakPosition = lineBreaker.Current.PositionWrap;

                            if (nextBreakPosition == 0)
                            {
                                break;
                            }

                            if (nextBreakPosition > measuredLength)
                            {
                                break;
                            }

                            currentBreakPosition = nextBreakPosition;
                        }

                        if (currentBreakPosition != -1)
                        {
                            measuredLength = currentBreakPosition;
                        }
                    }

                    length += measuredLength;

                    var splitResult = SplitTextRuns(textRuns, length);

                    var textLineMetrics =
                        TextLineMetrics.Create(splitResult.First, paragraphWidth, paragraphProperties.TextAlignment);

                    return(new SimpleTextLine(text.Take(length), splitResult.First, textLineMetrics));
                }

                currentWidth += currentRun.GlyphRun.Bounds.Width;

                length += currentRun.GlyphRun.Characters.Length;

                runIndex++;
            }

            return(new SimpleTextLine(text, textRuns,
                                      TextLineMetrics.Create(textRuns, paragraphWidth, paragraphProperties.TextAlignment)));
        }
Exemple #17
0
        public static List <ShapedTextCharacters>?Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis)
        {
            var shapedTextRuns = textLine.TextRuns as List <ShapedTextCharacters>;

            if (shapedTextRuns is null)
            {
                return(null);
            }

            var runIndex        = 0;
            var currentWidth    = 0.0;
            var collapsedLength = 0;
            var textRange       = textLine.TextRange;
            var shapedSymbol    = TextFormatterImpl.CreateSymbol(properties.Symbol, FlowDirection.LeftToRight);

            if (properties.Width < shapedSymbol.GlyphRun.Size.Width)
            {
                return(new List <ShapedTextCharacters>(0));
            }

            var availableWidth = properties.Width - shapedSymbol.Size.Width;

            while (runIndex < shapedTextRuns.Count)
            {
                var currentRun = shapedTextRuns[runIndex];

                currentWidth += currentRun.Size.Width;

                if (currentWidth > availableWidth)
                {
                    if (currentRun.TryMeasureCharacters(availableWidth, out var measuredLength))
                    {
                        if (isWordEllipsis && measuredLength < textRange.End)
                        {
                            var currentBreakPosition = 0;

                            var lineBreaker = new LineBreakEnumerator(currentRun.Text);

                            while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
                            {
                                var nextBreakPosition = lineBreaker.Current.PositionMeasure;

                                if (nextBreakPosition == 0)
                                {
                                    break;
                                }

                                if (nextBreakPosition >= measuredLength)
                                {
                                    break;
                                }

                                currentBreakPosition = nextBreakPosition;
                            }

                            measuredLength = currentBreakPosition;
                        }
                    }

                    collapsedLength += measuredLength;

                    var shapedTextCharacters = new List <ShapedTextCharacters>(shapedTextRuns.Count);

                    if (collapsedLength > 0)
                    {
                        var splitResult = TextFormatterImpl.SplitShapedRuns(shapedTextRuns, collapsedLength);

                        shapedTextCharacters.AddRange(splitResult.First);

                        TextLineImpl.SortRuns(shapedTextCharacters);
                    }

                    shapedTextCharacters.Add(shapedSymbol);

                    return(shapedTextCharacters);
                }

                availableWidth -= currentRun.Size.Width;

                collapsedLength += currentRun.GlyphRun.Characters.Length;

                runIndex++;
            }

            return(null);
        }
Exemple #18
0
        /// <summary>
        /// Performs text trimming and returns a trimmed line.
        /// </summary>
        /// <param name="textRuns">The text runs to perform the trimming on.</param>
        /// <param name="textRange">The text range that is covered by the text runs.</param>
        /// <param name="paragraphWidth">A <see cref="double"/> value that specifies the width of the paragraph that the line fills.</param>
        /// <param name="paragraphProperties">A <see cref="TextParagraphProperties"/> value that represents paragraph properties,
        /// such as TextWrapping, TextAlignment, or TextStyle.</param>
        /// <returns></returns>
        private static TextLine PerformTextTrimming(IReadOnlyList <ShapedTextCharacters> textRuns, TextRange textRange,
                                                    double paragraphWidth, TextParagraphProperties paragraphProperties)
        {
            var textTrimming   = paragraphProperties.TextTrimming;
            var availableWidth = paragraphWidth;
            var currentWidth   = 0.0;
            var runIndex       = 0;

            while (runIndex < textRuns.Count)
            {
                var currentRun = textRuns[runIndex];

                currentWidth += currentRun.GlyphRun.Bounds.Width;

                if (currentWidth > availableWidth)
                {
                    var ellipsisRun = CreateEllipsisRun(currentRun.Properties);

                    var measuredLength = MeasureText(currentRun, availableWidth - ellipsisRun.GlyphRun.Bounds.Width);

                    if (textTrimming == TextTrimming.WordEllipsis)
                    {
                        if (measuredLength < textRange.End)
                        {
                            var currentBreakPosition = 0;

                            var lineBreaker = new LineBreakEnumerator(currentRun.Text);

                            while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
                            {
                                var nextBreakPosition = lineBreaker.Current.PositionWrap;

                                if (nextBreakPosition == 0)
                                {
                                    break;
                                }

                                if (nextBreakPosition > measuredLength)
                                {
                                    break;
                                }

                                currentBreakPosition = nextBreakPosition;
                            }

                            measuredLength = currentBreakPosition;
                        }
                    }

                    var splitResult = SplitTextRuns(textRuns, measuredLength);

                    var trimmedRuns = new List <ShapedTextCharacters>(splitResult.First.Count + 1);

                    trimmedRuns.AddRange(splitResult.First);

                    trimmedRuns.Add(ellipsisRun);

                    var textLineMetrics =
                        TextLineMetrics.Create(trimmedRuns, textRange, paragraphWidth, paragraphProperties);

                    return(new TextLineImpl(trimmedRuns, textLineMetrics));
                }

                availableWidth -= currentRun.GlyphRun.Bounds.Width;

                runIndex++;
            }

            return(new TextLineImpl(textRuns,
                                    TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties)));
        }
        /// <inheritdoc/>
        public override TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList)
        {
            if (collapsingPropertiesList == null || collapsingPropertiesList.Length == 0)
            {
                return(this);
            }

            var             collapsingProperties = collapsingPropertiesList[0];
            var             runIndex             = 0;
            var             currentWidth         = 0.0;
            var             textRange            = TextRange;
            var             collapsedLength      = 0;
            TextLineMetrics textLineMetrics;

            var shapedSymbol = CreateShapedSymbol(collapsingProperties.Symbol);

            var availableWidth = collapsingProperties.Width - shapedSymbol.Size.Width;

            while (runIndex < _textRuns.Count)
            {
                var currentRun = _textRuns[runIndex];

                currentWidth += currentRun.Size.Width;

                if (currentWidth > availableWidth)
                {
                    if (TextFormatterImpl.TryMeasureCharacters(currentRun, availableWidth, out var measuredLength))
                    {
                        if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord && measuredLength < textRange.End)
                        {
                            var currentBreakPosition = 0;

                            var lineBreaker = new LineBreakEnumerator(currentRun.Text);

                            while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
                            {
                                var nextBreakPosition = lineBreaker.Current.PositionWrap;

                                if (nextBreakPosition == 0)
                                {
                                    break;
                                }

                                if (nextBreakPosition > measuredLength)
                                {
                                    break;
                                }

                                currentBreakPosition = nextBreakPosition;
                            }

                            measuredLength = currentBreakPosition;
                        }
                    }

                    collapsedLength += measuredLength;

                    var splitResult = TextFormatterImpl.SplitTextRuns(_textRuns, collapsedLength);

                    var shapedTextCharacters = new List <ShapedTextCharacters>(splitResult.First.Count + 1);

                    shapedTextCharacters.AddRange(splitResult.First);

                    shapedTextCharacters.Add(shapedSymbol);

                    textRange = new TextRange(textRange.Start, collapsedLength);

                    var shapedWidth = GetShapedWidth(shapedTextCharacters);

                    textLineMetrics = new TextLineMetrics(new Size(shapedWidth, LineMetrics.Size.Height),
                                                          LineMetrics.TextBaseline, textRange, false);

                    return(new TextLineImpl(shapedTextCharacters, textLineMetrics, TextLineBreak, true));
                }

                availableWidth -= currentRun.Size.Width;

                collapsedLength += currentRun.GlyphRun.Characters.Length;

                runIndex++;
            }

            textLineMetrics =
                new TextLineMetrics(LineMetrics.Size.WithWidth(LineMetrics.Size.Width + shapedSymbol.Size.Width),
                                    LineMetrics.TextBaseline, TextRange, LineMetrics.HasOverflowed);

            return(new TextLineImpl(new List <ShapedTextCharacters>(_textRuns)
            {
                shapedSymbol
            }, textLineMetrics, null,
                                    true));
        }
        public override void Justify(TextLine textLine)
        {
            var paragraphWidth = Width;

            if (double.IsInfinity(paragraphWidth))
            {
                return;
            }

            if (textLine.NewLineLength > 0)
            {
                return;
            }

            var textLineBreak = textLine.TextLineBreak;

            if (textLineBreak is not null && textLineBreak.TextEndOfLine is not null)
            {
                if (textLineBreak.RemainingRuns is null || textLineBreak.RemainingRuns.Count == 0)
                {
                    return;
                }
            }

            var breakOportunities = new Queue <int>();

            foreach (var textRun in textLine.TextRuns)
            {
                var text = textRun.Text;

                if (text.IsEmpty)
                {
                    continue;
                }

                var start = text.Start;

                var lineBreakEnumerator = new LineBreakEnumerator(text);

                while (lineBreakEnumerator.MoveNext())
                {
                    var currentBreak = lineBreakEnumerator.Current;

                    if (!currentBreak.Required && currentBreak.PositionWrap != text.Length)
                    {
                        breakOportunities.Enqueue(start + currentBreak.PositionMeasure);
                    }
                }
            }

            if (breakOportunities.Count == 0)
            {
                return;
            }

            var remainingSpace = Math.Max(0, paragraphWidth - textLine.WidthIncludingTrailingWhitespace);
            var spacing        = remainingSpace / breakOportunities.Count;

            foreach (var textRun in textLine.TextRuns)
            {
                var text = textRun.Text;

                if (text.IsEmpty)
                {
                    continue;
                }

                if (textRun is ShapedTextCharacters shapedText)
                {
                    var glyphRun        = shapedText.GlyphRun;
                    var shapedBuffer    = shapedText.ShapedBuffer;
                    var currentPosition = text.Start;

                    while (breakOportunities.Count > 0)
                    {
                        var characterIndex = breakOportunities.Dequeue();

                        if (characterIndex < currentPosition)
                        {
                            continue;
                        }

                        var glyphIndex = glyphRun.FindGlyphIndex(characterIndex);
                        var glyphInfo  = shapedBuffer.GlyphInfos[glyphIndex];

                        shapedBuffer.GlyphInfos[glyphIndex] = new GlyphInfo(glyphInfo.GlyphIndex, glyphInfo.GlyphCluster, glyphInfo.GlyphAdvance + spacing);
                    }

                    glyphRun.GlyphAdvances = shapedBuffer.GlyphAdvances;
                }
            }
        }
Exemple #21
0
        /// <summary>
        /// Performs text wrapping returns a list of text lines.
        /// </summary>
        /// <param name="textRuns"></param>
        /// <param name="firstTextSourceIndex">The first text source index.</param>
        /// <param name="paragraphWidth">The paragraph width.</param>
        /// <param name="paragraphProperties">The text paragraph properties.</param>
        /// <param name="resolvedFlowDirection"></param>
        /// <param name="currentLineBreak">The current line break if the line was explicitly broken.</param>
        /// <returns>The wrapped text line.</returns>
        private static TextLineImpl PerformTextWrapping(List <DrawableTextRun> textRuns, int firstTextSourceIndex,
                                                        double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection,
                                                        TextLineBreak?currentLineBreak)
        {
            if (textRuns.Count == 0)
            {
                return(CreateEmptyTextLine(firstTextSourceIndex, paragraphWidth, paragraphProperties));
            }

            if (!TryMeasureLength(textRuns, paragraphWidth, out var measuredLength))
            {
                measuredLength = 1;
            }

            var currentLength = 0;

            var lastWrapPosition = 0;

            var currentPosition = 0;

            for (var index = 0; index < textRuns.Count; index++)
            {
                var currentRun = textRuns[index];

                var lineBreaker = new LineBreakEnumerator(currentRun.Text);

                var breakFound = false;

                while (lineBreaker.MoveNext())
                {
                    if (lineBreaker.Current.Required &&
                        currentLength + lineBreaker.Current.PositionMeasure <= measuredLength)
                    {
                        //Explicit break found
                        breakFound = true;

                        currentPosition = currentLength + lineBreaker.Current.PositionWrap;

                        break;
                    }

                    if (currentLength + lineBreaker.Current.PositionMeasure > measuredLength)
                    {
                        if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow)
                        {
                            if (lastWrapPosition > 0)
                            {
                                currentPosition = lastWrapPosition;

                                breakFound = true;

                                break;
                            }

                            //Find next possible wrap position (overflow)
                            if (index < textRuns.Count - 1)
                            {
                                if (lineBreaker.Current.PositionWrap != currentRun.Text.Length)
                                {
                                    //We already found the next possible wrap position.
                                    breakFound = true;

                                    currentPosition = currentLength + lineBreaker.Current.PositionWrap;

                                    break;
                                }

                                while (lineBreaker.MoveNext() && index < textRuns.Count)
                                {
                                    currentPosition += lineBreaker.Current.PositionWrap;

                                    if (lineBreaker.Current.PositionWrap != currentRun.Text.Length)
                                    {
                                        break;
                                    }

                                    index++;

                                    if (index >= textRuns.Count)
                                    {
                                        break;
                                    }

                                    currentRun = textRuns[index];

                                    lineBreaker = new LineBreakEnumerator(currentRun.Text);
                                }
                            }
                            else
                            {
                                currentPosition = currentLength + lineBreaker.Current.PositionWrap;
                            }

                            breakFound = true;

                            break;
                        }

                        //We overflowed so we use the last available wrap position.
                        currentPosition = lastWrapPosition == 0 ? measuredLength : lastWrapPosition;

                        breakFound = true;

                        break;
                    }

                    if (lineBreaker.Current.PositionMeasure != lineBreaker.Current.PositionWrap)
                    {
                        lastWrapPosition = currentLength + lineBreaker.Current.PositionWrap;
                    }
                }

                if (!breakFound)
                {
                    currentLength += currentRun.Text.Length;

                    continue;
                }

                measuredLength = currentPosition;

                break;
            }

            var splitResult = SplitDrawableRuns(textRuns, measuredLength);

            var remainingCharacters = splitResult.Second;

            var lineBreak = remainingCharacters?.Count > 0 ?
                            new TextLineBreak(currentLineBreak?.TextEndOfLine, resolvedFlowDirection, remainingCharacters) :
                            null;

            if (lineBreak is null && currentLineBreak?.TextEndOfLine != null)
            {
                lineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, resolvedFlowDirection);
            }

            var textLine = new TextLineImpl(splitResult.First, firstTextSourceIndex, measuredLength,
                                            paragraphWidth, paragraphProperties, resolvedFlowDirection,
                                            lineBreak);

            return(textLine.FinalizeLine());
        }
Exemple #22
0
        /// <inheritdoc/>
        public override TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList)
        {
            if (collapsingPropertiesList.Length == 0)
            {
                return(this);
            }

            var collapsingProperties = collapsingPropertiesList[0];

            var runIndex        = 0;
            var currentWidth    = 0.0;
            var textRange       = TextRange;
            var collapsedLength = 0;

            var shapedSymbol = TextFormatterImpl.CreateSymbol(collapsingProperties.Symbol, _paragraphProperties.FlowDirection);

            var availableWidth = collapsingProperties.Width - shapedSymbol.GlyphRun.Size.Width;

            while (runIndex < _textRuns.Count)
            {
                var currentRun = _textRuns[runIndex];

                currentWidth += currentRun.Size.Width;

                if (currentWidth > availableWidth)
                {
                    if (currentRun.TryMeasureCharacters(availableWidth, out var measuredLength))
                    {
                        if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord &&
                            measuredLength < textRange.End)
                        {
                            var currentBreakPosition = 0;

                            var lineBreaker = new LineBreakEnumerator(currentRun.Text);

                            while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
                            {
                                var nextBreakPosition = lineBreaker.Current.PositionMeasure;

                                if (nextBreakPosition == 0)
                                {
                                    break;
                                }

                                if (nextBreakPosition >= measuredLength)
                                {
                                    break;
                                }

                                currentBreakPosition = nextBreakPosition;
                            }

                            measuredLength = currentBreakPosition;
                        }
                    }

                    collapsedLength += measuredLength;

                    var splitResult = TextFormatterImpl.SplitShapedRuns(_textRuns, collapsedLength);

                    var shapedTextCharacters = new List <ShapedTextCharacters>(splitResult.First.Count + 1);

                    shapedTextCharacters.AddRange(splitResult.First);

                    SortRuns(shapedTextCharacters);

                    shapedTextCharacters.Add(shapedSymbol);

                    textRange = new TextRange(textRange.Start, collapsedLength);

                    var textLine = new TextLineImpl(shapedTextCharacters, textRange, _paragraphWidth, _paragraphProperties,
                                                    _flowDirection, TextLineBreak, true);

                    return(textLine.FinalizeLine());
                }

                availableWidth -= currentRun.Size.Width;

                collapsedLength += currentRun.GlyphRun.Characters.Length;

                runIndex++;
            }

            return(this);
        }