Beispiel #1
0
        /// <inheritdoc cref="TextFormatter.FormatLine"/>
        public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
                                            TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak = null)
        {
            var      textTrimming = paragraphProperties.TextTrimming;
            var      textWrapping = paragraphProperties.TextWrapping;
            TextLine textLine     = null;

            var textRuns = FetchTextRuns(textSource, firstTextSourceIndex, previousLineBreak, out var nextLineBreak);

            var textRange = GetTextRange(textRuns);

            if (textTrimming != TextTrimming.None)
            {
                textLine = PerformTextTrimming(textRuns, textRange, paragraphWidth, paragraphProperties);
            }
            else
            {
                switch (textWrapping)
                {
                case TextWrapping.NoWrap:
                {
                    var textLineMetrics =
                        TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties);

                    textLine = new TextLineImpl(textRuns, textLineMetrics, nextLineBreak);
                    break;
                }

                case TextWrapping.WrapWithOverflow:
                case TextWrapping.Wrap:
                {
                    textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties);
                    break;
                }
                }
            }

            return(textLine);
        }
Beispiel #2
0
        /// <summary>
        /// Fetches text runs.
        /// </summary>
        /// <param name="textSource">The text source.</param>
        /// <param name="firstTextSourceIndex">The first text source index.</param>
        /// <param name="previousLineBreak">Previous line break. Can be null.</param>
        /// <param name="nextLineBreak">Next line break. Can be null.</param>
        /// <returns>
        /// The formatted text runs.
        /// </returns>
        private static List <ShapedTextCharacters> FetchTextRuns(ITextSource textSource,
                                                                 int firstTextSourceIndex, TextLineBreak previousLineBreak, out TextLineBreak nextLineBreak)
        {
            nextLineBreak = default;

            var currentLength = 0;

            var textRuns = new List <ShapedTextCharacters>();

            if (previousLineBreak != null)
            {
                for (var index = 0; index < previousLineBreak.RemainingCharacters.Count; index++)
                {
                    var shapedCharacters = previousLineBreak.RemainingCharacters[index];

                    if (shapedCharacters == null)
                    {
                        continue;
                    }

                    textRuns.Add(shapedCharacters);

                    if (TryGetLineBreak(shapedCharacters, out var runLineBreak))
                    {
                        var splitResult = SplitTextRuns(textRuns, currentLength + runLineBreak.PositionWrap);

                        if (++index < previousLineBreak.RemainingCharacters.Count)
                        {
                            for (; index < previousLineBreak.RemainingCharacters.Count; index++)
                            {
                                splitResult.Second.Add(previousLineBreak.RemainingCharacters[index]);
                            }
                        }

                        nextLineBreak = new TextLineBreak(splitResult.Second);

                        return(splitResult.First);
                    }

                    currentLength += shapedCharacters.Text.Length;
                }
            }

            firstTextSourceIndex += currentLength;

            var textRunEnumerator = new TextRunEnumerator(textSource, firstTextSourceIndex);

            while (textRunEnumerator.MoveNext())
            {
                var textRun = textRunEnumerator.Current;

                switch (textRun)
                {
                case TextCharacters textCharacters:
                {
                    var shapeableRuns = textCharacters.GetShapeableCharacters();

                    foreach (var run in shapeableRuns)
                    {
                        var glyphRun = TextShaper.Current.ShapeText(run.Text, run.Properties.Typeface,
                                                                    run.Properties.FontRenderingEmSize, run.Properties.CultureInfo);

                        var shapedCharacters = new ShapedTextCharacters(glyphRun, run.Properties);

                        textRuns.Add(shapedCharacters);
                    }

                    break;
                }

                case TextEndOfLine textEndOfLine:
                    nextLineBreak = new TextLineBreak(textEndOfLine);
                    break;
                }

                if (TryGetLineBreak(textRun, out var runLineBreak))
                {
                    var splitResult = SplitTextRuns(textRuns, currentLength + runLineBreak.PositionWrap);

                    nextLineBreak = new TextLineBreak(splitResult.Second);

                    return(splitResult.First);
                }

                currentLength += textRun.Text.Length;
            }

            return(textRuns);
        }
Beispiel #3
0
        /// <summary>
        /// Performs text wrapping returns a list of text lines.
        /// </summary>
        /// <param name="textRuns"></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="flowDirection"></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 <ShapedTextCharacters> textRuns, TextRange textRange,
                                                        double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection flowDirection,
                                                        TextLineBreak?currentLineBreak)
        {
            var measuredLength = MeasureLength(textRuns, textRange, paragraphWidth);

            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 = SplitShapedRuns(textRuns, measuredLength);

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

            var remainingCharacters = splitResult.Second;

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

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

            TextLineImpl.SortRuns(splitResult.First);

            var textLine = new TextLineImpl(splitResult.First, textRange, paragraphWidth, paragraphProperties, flowDirection,
                                            lineBreak);

            return(textLine.FinalizeLine());
        }
Beispiel #4
0
        /// <inheritdoc cref="TextFormatter.FormatLine"/>
        public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
                                            TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak = null)
        {
            var textWrapping = paragraphProperties.TextWrapping;

            var textRuns = FetchTextRuns(textSource, firstTextSourceIndex, previousLineBreak,
                                         out var nextLineBreak);

            var textRange = GetTextRange(textRuns);

            TextLine textLine;

            switch (textWrapping)
            {
            case TextWrapping.NoWrap:
            {
                textLine = new TextLineImpl(textRuns, textRange, paragraphWidth, paragraphProperties,
                                            nextLineBreak);
                break;
            }

            case TextWrapping.WrapWithOverflow:
            case TextWrapping.Wrap:
            {
                textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties,
                                               nextLineBreak);
                break;
            }

            default:
                throw new ArgumentOutOfRangeException();
            }

            return(textLine);
        }
Beispiel #5
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>
        /// <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;

            if (currentLineBreak?.RemainingCharacters != null)
            {
                if (remainingCharacters != null)
                {
                    remainingCharacters.AddRange(currentLineBreak.RemainingCharacters);
                }
                else
                {
                    remainingCharacters = new List <ShapedTextCharacters>(currentLineBreak.RemainingCharacters);
                }
            }

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

            return(new TextLineImpl(splitResult.First, textRange, paragraphWidth, paragraphProperties,
                                    lineBreak));
        }
Beispiel #6
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>
        /// <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 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 remainingCharacters = splitResult.Second;

                    if (currentLineBreak?.RemainingCharacters != null)
                    {
                        if (remainingCharacters != null)
                        {
                            remainingCharacters.AddRange(currentLineBreak.RemainingCharacters);
                        }
                        else
                        {
                            remainingCharacters = new List <ShapedTextCharacters>(currentLineBreak.RemainingCharacters);
                        }
                    }

                    var lineBreak = remainingCharacters != null && remainingCharacters.Count > 0 ?
                                    new TextLineBreak(remainingCharacters) :
                                    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),
                                    currentLineBreak?.RemainingCharacters != null ?
                                    new TextLineBreak(currentLineBreak.RemainingCharacters) :
                                    null));
        }
 /// <summary>
 /// Formats a text line.
 /// </summary>
 /// <param name="textSource">The text source.</param>
 /// <param name="firstTextSourceIndex">The first character index to start the text line from.</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>
 /// <param name="previousLineBreak">A <see cref="TextLineBreak"/> value that specifies the text formatter state,
 /// in terms of where the previous line in the paragraph was broken by the text formatting process.</param>
 /// <returns>The formatted line.</returns>
 public abstract TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
                                     TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak = null);