internal SplitResult <ShapedTextCharacters> Split(int length) { if (IsReversed) { Reverse(); } #if DEBUG if (length == 0) { throw new ArgumentOutOfRangeException(nameof(length), "length must be greater than zero."); } #endif var splitBuffer = ShapedBuffer.Split(length); var first = new ShapedTextCharacters(splitBuffer.First, Properties); #if DEBUG if (first.Text.Length != length) { throw new InvalidOperationException("Split length mismatch."); } #endif var second = new ShapedTextCharacters(splitBuffer.Second !, Properties); return(new SplitResult <ShapedTextCharacters>(first, second)); }
/// <summary> /// Measures the number of characters that fits into available width. /// </summary> /// <param name="textCharacters">The text run.</param> /// <param name="availableWidth">The available width.</param> /// <returns></returns> private static int MeasureText(ShapedTextCharacters textCharacters, double availableWidth) { var glyphRun = textCharacters.GlyphRun; var characterHit = glyphRun.GetCharacterHitFromDistance(availableWidth, out _); return(characterHit.FirstCharacterIndex + characterHit.TrailingLength - textCharacters.Text.Start); }
/// <summary> /// Splits the <see cref="TextRun"/> at specified length. /// </summary> /// <param name="length">The length.</param> /// <returns>The split result.</returns> public SplitTextCharactersResult Split(int length) { var glyphCount = 0; var firstCharacters = GlyphRun.Characters.Take(length); var codepointEnumerator = new CodepointEnumerator(firstCharacters); while (codepointEnumerator.MoveNext()) { glyphCount++; } if (GlyphRun.Characters.Length == length) { return(new SplitTextCharactersResult(this, null)); } if (GlyphRun.GlyphIndices.Length == glyphCount) { return(new SplitTextCharactersResult(this, null)); } var firstGlyphRun = new GlyphRun( Properties.Typeface.GlyphTypeface, Properties.FontRenderingEmSize, GlyphRun.GlyphIndices.Take(glyphCount), GlyphRun.GlyphAdvances.Take(glyphCount), GlyphRun.GlyphOffsets.Take(glyphCount), GlyphRun.Characters.Take(length), GlyphRun.GlyphClusters.Take(glyphCount)); var firstTextRun = new ShapedTextCharacters(firstGlyphRun, Properties); var secondGlyphRun = new GlyphRun( Properties.Typeface.GlyphTypeface, Properties.FontRenderingEmSize, GlyphRun.GlyphIndices.Skip(glyphCount), GlyphRun.GlyphAdvances.Skip(glyphCount), GlyphRun.GlyphOffsets.Skip(glyphCount), GlyphRun.Characters.Skip(length), GlyphRun.GlyphClusters.Skip(glyphCount)); var secondTextRun = new ShapedTextCharacters(secondGlyphRun, Properties); return(new SplitTextCharactersResult(firstTextRun, secondTextRun)); }
/// <summary> /// Splits the <see cref="TextRun"/> at specified length. /// </summary> /// <param name="length">The length.</param> /// <returns>The split result.</returns> public SplitTextCharactersResult Split(int length) { var glyphCount = GlyphRun.FindGlyphIndex(GlyphRun.Characters.Start + length); if (GlyphRun.Characters.Length == length) { return(new SplitTextCharactersResult(this, null)); } if (GlyphRun.GlyphIndices.Length == glyphCount) { return(new SplitTextCharactersResult(this, null)); } var firstGlyphRun = new GlyphRun( Properties.Typeface.GlyphTypeface, Properties.FontRenderingEmSize, GlyphRun.GlyphIndices.Take(glyphCount), GlyphRun.GlyphAdvances.Take(glyphCount), GlyphRun.GlyphOffsets.Take(glyphCount), GlyphRun.Characters.Take(length), GlyphRun.GlyphClusters.Take(glyphCount), GlyphRun.BiDiLevel); var firstTextRun = new ShapedTextCharacters(firstGlyphRun, Properties); var secondGlyphRun = new GlyphRun( Properties.Typeface.GlyphTypeface, Properties.FontRenderingEmSize, GlyphRun.GlyphIndices.Skip(glyphCount), GlyphRun.GlyphAdvances.Skip(glyphCount), GlyphRun.GlyphOffsets.Skip(glyphCount), GlyphRun.Characters.Skip(length), GlyphRun.GlyphClusters.Skip(glyphCount), GlyphRun.BiDiLevel); var secondTextRun = new ShapedTextCharacters(secondGlyphRun, Properties); return(new SplitTextCharactersResult(firstTextRun, secondTextRun)); }
/// <summary> /// Shape specified text runs with specified paragraph embedding. /// </summary> /// <param name="textRuns">The text runs to shape.</param> /// <param name="flowDirection">The paragraph embedding level.</param> /// <param name="resolvedFlowDirection">The resolved flow direction.</param> /// <returns> /// A list of shaped text characters. /// </returns> private static List <ShapedTextCharacters> ShapeTextRuns(List <TextCharacters> textRuns, FlowDirection flowDirection, out FlowDirection resolvedFlowDirection) { var shapedTextCharacters = new List <ShapedTextCharacters>(); var biDiData = new BidiData((sbyte)flowDirection); foreach (var textRun in textRuns) { biDiData.Append(textRun.Text); } var biDi = BidiAlgorithm.Instance.Value !; biDi.Process(biDiData); var resolvedEmbeddingLevel = biDi.ResolveEmbeddingLevel(biDiData.Classes); resolvedFlowDirection = (resolvedEmbeddingLevel & 1) == 0 ? FlowDirection.LeftToRight : FlowDirection.RightToLeft; foreach (var shapeableRuns in CoalesceLevels(textRuns, biDi.ResolvedLevels)) { for (var index = 0; index < shapeableRuns.Count; index++) { var currentRun = shapeableRuns[index]; var shapedBuffer = TextShaper.Current.ShapeText(currentRun.Text, currentRun.Properties.Typeface.GlyphTypeface, currentRun.Properties.FontRenderingEmSize, currentRun.Properties.CultureInfo, currentRun.BidiLevel); var shapedCharacters = new ShapedTextCharacters(shapedBuffer, currentRun.Properties); shapedTextCharacters.Add(shapedCharacters); } } return(shapedTextCharacters); }
/// <summary> /// Measures the number of characters that fits into available width. /// </summary> /// <param name="textCharacters">The text run.</param> /// <param name="availableWidth">The available width.</param> /// <returns></returns> internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth) { var glyphRun = textCharacters.GlyphRun; if (glyphRun.Bounds.Width < availableWidth) { return(glyphRun.Characters.Length); } var glyphCount = 0; var currentWidth = 0.0; if (glyphRun.GlyphAdvances.IsEmpty) { var glyphTypeface = glyphRun.GlyphTypeface; for (var i = 0; i < glyphRun.GlyphClusters.Length; i++) { var glyph = glyphRun.GlyphIndices[i]; var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale; if (currentWidth + advance > availableWidth) { break; } currentWidth += advance; glyphCount++; } } else { foreach (var advance in glyphRun.GlyphAdvances) { if (currentWidth + advance > availableWidth) { break; } currentWidth += advance; glyphCount++; } } if (glyphCount == glyphRun.GlyphIndices.Length) { return(glyphRun.Characters.Length); } if (glyphRun.GlyphClusters.IsEmpty) { return(glyphCount); } var firstCluster = glyphRun.GlyphClusters[0]; var lastCluster = glyphRun.GlyphClusters[glyphCount]; return(lastCluster - firstCluster); }
/// <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 IReadOnlyList <ShapedTextCharacters> FetchTextRuns(ITextSource textSource, int firstTextSourceIndex, TextLineBreak previousLineBreak, out TextLineBreak nextLineBreak) { nextLineBreak = default; var currentLength = 0; var textRuns = new List <ShapedTextCharacters>(); if (previousLineBreak != null) { foreach (var shapedCharacters in previousLineBreak.RemainingCharacters) { if (shapedCharacters == null) { continue; } textRuns.Add(shapedCharacters); if (TryGetLineBreak(shapedCharacters, out var runLineBreak)) { var splitResult = SplitTextRuns(textRuns, currentLength + runLineBreak.PositionWrap); 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; } } 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); }
/// <summary> /// Split a sequence of runs into two segments at specified length. /// </summary> /// <param name="textRuns">The text run's.</param> /// <param name="length">The length to split at.</param> /// <returns>The split text runs.</returns> internal static SplitTextRunsResult SplitTextRuns(IReadOnlyList <ShapedTextCharacters> textRuns, int length) { var currentLength = 0; for (var i = 0; i < textRuns.Count; i++) { var currentRun = textRuns[i]; if (currentLength + currentRun.GlyphRun.Characters.Length < length) { currentLength += currentRun.GlyphRun.Characters.Length; continue; } var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i; var first = new ShapedTextCharacters[firstCount]; if (firstCount > 1) { for (var j = 0; j < i; j++) { first[j] = textRuns[j]; } } var secondCount = textRuns.Count - firstCount; if (currentLength + currentRun.GlyphRun.Characters.Length == length) { var second = new ShapedTextCharacters[secondCount]; var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0; if (secondCount > 0) { for (var j = 0; j < secondCount; j++) { second[j] = textRuns[i + j + offset]; } } first[i] = currentRun; return(new SplitTextRunsResult(first, second)); } else { secondCount++; var second = new ShapedTextCharacters[secondCount]; if (secondCount > 0) { for (var j = 1; j < secondCount; j++) { second[j] = textRuns[i + j]; } } var split = currentRun.Split(length - currentLength); first[i] = split.First; second[0] = split.Second; return(new SplitTextRunsResult(first, second)); } } return(new SplitTextRunsResult(textRuns, null)); }
public SplitTextCharactersResult(ShapedTextCharacters first, ShapedTextCharacters second) { First = first; Second = second; }
/// <summary> /// Measures the number of characters that fit into available width. /// </summary> /// <param name="textCharacters">The text run.</param> /// <param name="availableWidth">The available width.</param> /// <param name="count">The count of fitting characters.</param> /// <returns> /// <c>true</c> if characters fit into the available width; otherwise, <c>false</c>. /// </returns> internal static bool TryMeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth, out int count) { var glyphRun = textCharacters.GlyphRun; if (glyphRun.Size.Width < availableWidth) { count = glyphRun.Characters.Length; return(true); } var glyphCount = 0; var currentWidth = 0.0; if (glyphRun.GlyphAdvances.IsEmpty) { var glyphTypeface = glyphRun.GlyphTypeface; for (var i = 0; i < glyphRun.GlyphClusters.Length; i++) { var glyph = glyphRun.GlyphIndices[i]; var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale; if (currentWidth + advance > availableWidth) { break; } currentWidth += advance; glyphCount++; } } else { foreach (var advance in glyphRun.GlyphAdvances) { if (currentWidth + advance > availableWidth) { break; } currentWidth += advance; glyphCount++; } } if (glyphCount == 0) { count = 0; return(false); } if (glyphCount == glyphRun.GlyphIndices.Length) { count = glyphRun.Characters.Length; return(true); } if (glyphRun.GlyphClusters.IsEmpty) { count = glyphCount; return(true); } var firstCluster = glyphRun.GlyphClusters[0]; var lastCluster = glyphRun.GlyphClusters[glyphCount]; if (glyphRun.IsLeftToRight) { count = lastCluster - firstCluster; } else { count = firstCluster - lastCluster; } return(count > 0); }
public OrderedBidiRun(ShapedTextCharacters run) => Run = run;