/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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()); }
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); }
/// <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); }