예제 #1
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:
            {
                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;
            }

            default:
                throw new ArgumentOutOfRangeException();
            }

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

            var collapsingProperties = collapsingPropertiesList[0];

            var collapsedRuns = collapsingProperties.Collapse(this);

            if (collapsedRuns is null)
            {
                return(this);
            }

            var collapsedLine = new TextLineImpl(collapsedRuns, FirstTextSourceIndex, Length, _paragraphWidth, _paragraphProperties,
                                                 _flowDirection, TextLineBreak, true);

            if (collapsedRuns.Count > 0)
            {
                collapsedLine.FinalizeLine();
            }

            return(collapsedLine);
        }
예제 #3
0
        /// <inheritdoc/>
        public override TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList)
        {
            if (collapsingPropertiesList.Length == 0)
            {
                return(this);
            }

            var collapsingProperties = collapsingPropertiesList[0];

            var collapsedRuns = collapsingProperties.Collapse(this);

            if (collapsedRuns is List <ShapedTextCharacters> shapedRuns)
            {
                var collapsedLine = new TextLineImpl(shapedRuns, TextRange, _paragraphWidth, _paragraphProperties, _flowDirection, TextLineBreak, true);

                if (shapedRuns.Count > 0)
                {
                    collapsedLine.FinalizeLine();
                }

                return(collapsedLine);
            }

            return(this);
        }
예제 #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;
            FlowDirection flowDirection;
            TextLineBreak?nextLineBreak = null;
            List <ShapedTextCharacters> shapedRuns;

            var textRuns = FetchTextRuns(textSource, firstTextSourceIndex,
                                         out var textEndOfLine, out var textRange);

            if (previousLineBreak?.RemainingCharacters != null)
            {
                flowDirection = previousLineBreak.FlowDirection;
                shapedRuns    = previousLineBreak.RemainingCharacters.ToList();
                nextLineBreak = previousLineBreak;
            }
            else
            {
                shapedRuns = ShapeTextRuns(textRuns, paragraphProperties.FlowDirection, out flowDirection);

                if (nextLineBreak == null && textEndOfLine != null)
                {
                    nextLineBreak = new TextLineBreak(textEndOfLine, flowDirection);
                }
            }

            TextLineImpl textLine;

            switch (textWrapping)
            {
            case TextWrapping.NoWrap:
            {
                TextLineImpl.SortRuns(shapedRuns);

                textLine = new TextLineImpl(shapedRuns, textRange, paragraphWidth, paragraphProperties,
                                            flowDirection, nextLineBreak);

                textLine.FinalizeLine();

                break;
            }

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

            default:
                throw new ArgumentOutOfRangeException(nameof(textWrapping));
            }

            return(textLine);
        }
예제 #5
0
        /// <inheritdoc cref="TextFormatter.FormatLine"/>
        public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
                                            TextParagraphProperties paragraphProperties, TextLineBreak?previousLineBreak = null)
        {
            var                    textWrapping = paragraphProperties.TextWrapping;
            FlowDirection          resolvedFlowDirection;
            TextLineBreak?         nextLineBreak = null;
            List <DrawableTextRun> drawableTextRuns;

            var textRuns = FetchTextRuns(textSource, firstTextSourceIndex,
                                         out var textEndOfLine, out var textSourceLength);

            if (previousLineBreak?.RemainingRuns != null)
            {
                resolvedFlowDirection = previousLineBreak.FlowDirection;
                drawableTextRuns      = previousLineBreak.RemainingRuns.ToList();
                nextLineBreak         = previousLineBreak;
            }
            else
            {
                drawableTextRuns = ShapeTextRuns(textRuns, paragraphProperties, out resolvedFlowDirection);

                if (nextLineBreak == null && textEndOfLine != null)
                {
                    nextLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection);
                }
            }

            TextLineImpl textLine;

            switch (textWrapping)
            {
            case TextWrapping.NoWrap:
            {
                textLine = new TextLineImpl(drawableTextRuns, firstTextSourceIndex, textSourceLength,
                                            paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak);

                textLine.FinalizeLine();

                break;
            }

            case TextWrapping.WrapWithOverflow:
            case TextWrapping.Wrap:
            {
                textLine = PerformTextWrapping(drawableTextRuns, firstTextSourceIndex, paragraphWidth, paragraphProperties,
                                               resolvedFlowDirection, nextLineBreak);
                break;
            }

            default:
                throw new ArgumentOutOfRangeException(nameof(textWrapping));
            }

            return(textLine);
        }
예제 #6
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);
        }
예제 #7
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());
        }
예제 #8
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);
        }
        public override IReadOnlyList <TextRun>?Collapse(TextLine textLine)
        {
            var shapedTextRuns = textLine.TextRuns as List <ShapedTextCharacters>;

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

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

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

            // Overview of ellipsis structure
            // Prefix length run | Ellipsis symbol | Post split run growing from the end |
            var availableWidth = Width - shapedSymbol.Size.Width;

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

                currentWidth += currentRun.Size.Width;

                if (currentWidth > availableWidth)
                {
                    currentRun.TryMeasureCharacters(availableWidth, out var measuredLength);

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

                    if (measuredLength > 0)
                    {
                        List <ShapedTextCharacters>?preSplitRuns  = null;
                        List <ShapedTextCharacters>?postSplitRuns = null;

                        if (_prefixLength > 0)
                        {
                            var splitResult = TextFormatterImpl.SplitShapedRuns(shapedTextRuns, Math.Min(_prefixLength, measuredLength));

                            shapedTextCharacters.AddRange(splitResult.First);

                            TextLineImpl.SortRuns(shapedTextCharacters);

                            preSplitRuns  = splitResult.First;
                            postSplitRuns = splitResult.Second;
                        }
                        else
                        {
                            postSplitRuns = shapedTextRuns;
                        }

                        shapedTextCharacters.Add(shapedSymbol);

                        if (measuredLength > _prefixLength && postSplitRuns is not null)
                        {
                            var availableSuffixWidth = availableWidth;

                            if (preSplitRuns is not null)
                            {
                                foreach (var run in preSplitRuns)
                                {
                                    availableSuffixWidth -= run.Size.Width;
                                }
                            }

                            for (int i = postSplitRuns.Count - 1; i >= 0; i--)
                            {
                                var run = postSplitRuns[i];

                                if (run.TryMeasureCharactersBackwards(availableSuffixWidth, out int suffixCount, out double suffixWidth))
                                {
                                    availableSuffixWidth -= suffixWidth;

                                    if (suffixCount > 0)
                                    {
                                        var splitSuffix = run.Split(run.TextSourceLength - suffixCount);

                                        shapedTextCharacters.Add(splitSuffix.Second !);
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        shapedTextCharacters.Add(shapedSymbol);
                    }

                    return(shapedTextCharacters);
                }

                availableWidth -= currentRun.Size.Width;

                runIndex++;
            }

            return(null);
        }
예제 #10
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);
        }