/// <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> /// Shape specified text runs with specified paragraph embedding. /// </summary> /// <param name="textRuns">The text runs to shape.</param> /// <param name="paragraphProperties">The default paragraph properties.</param> /// <param name="resolvedFlowDirection">The resolved flow direction.</param> /// <returns> /// A list of shaped text characters. /// </returns> private static List <DrawableTextRun> ShapeTextRuns(List <TextRun> textRuns, TextParagraphProperties paragraphProperties, out FlowDirection resolvedFlowDirection) { var flowDirection = paragraphProperties.FlowDirection; var drawableTextRuns = new List <DrawableTextRun>(); var biDiData = new BidiData((sbyte)flowDirection); foreach (var textRun in textRuns) { if (textRun.Text.IsEmpty) { var text = new char[textRun.TextSourceLength]; biDiData.Append(text); } else { 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; var processedRuns = new List <TextRun>(textRuns.Count); foreach (var coalescedRuns in CoalesceLevels(textRuns, biDi.ResolvedLevels)) { processedRuns.AddRange(coalescedRuns); } for (var index = 0; index < processedRuns.Count; index++) { var currentRun = processedRuns[index]; switch (currentRun) { case DrawableTextRun drawableRun: { drawableTextRuns.Add(drawableRun); break; } case ShapeableTextCharacters shapeableRun: { var groupedRuns = new List <ShapeableTextCharacters>(2) { shapeableRun }; var text = currentRun.Text; var start = currentRun.Text.Start; var length = currentRun.Text.Length; var bufferOffset = currentRun.Text.BufferOffset; while (index + 1 < processedRuns.Count) { if (processedRuns[index + 1] is not ShapeableTextCharacters nextRun) { break; } if (shapeableRun.CanShapeTogether(nextRun)) { groupedRuns.Add(nextRun); length += nextRun.Text.Length; if (start > nextRun.Text.Start) { start = nextRun.Text.Start; } if (bufferOffset > nextRun.Text.BufferOffset) { bufferOffset = nextRun.Text.BufferOffset; } text = new ReadOnlySlice <char>(text.Buffer, start, length, bufferOffset); index++; shapeableRun = nextRun; continue; } break; } var shaperOptions = new TextShaperOptions(currentRun.Properties !.Typeface.GlyphTypeface, currentRun.Properties.FontRenderingEmSize, shapeableRun.BidiLevel, currentRun.Properties.CultureInfo, paragraphProperties.DefaultIncrementalTab); drawableTextRuns.AddRange(ShapeTogether(groupedRuns, text, shaperOptions)); break; } } } return(drawableTextRuns); }
private bool Run(BiDiClassData t) { var bidi = new BidiAlgorithm(); var bidiData = new BidiData(t.ParagraphLevel); var text = Encoding.UTF32.GetString(MemoryMarshal.Cast <int, byte>(t.CodePoints).ToArray()); // Append bidiData.Append(text.AsMemory()); // Act for (int i = 0; i < 10; i++) { bidi.Process(bidiData); } var resultLevels = bidi.ResolvedLevels; var resultParagraphLevel = bidi.ResolvedParagraphEmbeddingLevel; // Assert var passed = true; if (t.ResolvedParagraphLevel != resultParagraphLevel) { return(false); } for (var i = 0; i < t.ResolvedLevels.Length; i++) { if (t.ResolvedLevels[i] == -1) { continue; } if (t.ResolvedLevels[i] != resultLevels[i]) { passed = false; break; } } if (passed) { return(true); } _outputHelper.WriteLine($"Failed line {t.LineNumber}"); _outputHelper.WriteLine( $" Code Points: {string.Join(" ", t.CodePoints.Select(x => x.ToString("X4")))}"); _outputHelper.WriteLine( $" Pair Bracket Types: {string.Join(" ", bidiData.PairedBracketTypes.Select(x => " " + x.ToString()))}"); _outputHelper.WriteLine( $" Pair Bracket Values: {string.Join(" ", bidiData.PairedBracketValues.Select(x => x.ToString("X4")))}"); _outputHelper.WriteLine($" Embed Level: {t.ParagraphLevel}"); _outputHelper.WriteLine($" Expected Embed Level: {t.ResolvedParagraphLevel}"); _outputHelper.WriteLine($" Actual Embed Level: {resultParagraphLevel}"); _outputHelper.WriteLine($" Directionality: {string.Join(" ", bidiData.Classes)}"); _outputHelper.WriteLine($" Expected Levels: {string.Join(" ", t.ResolvedLevels)}"); _outputHelper.WriteLine($" Actual Levels: {string.Join(" ", resultLevels)}"); return(false); }
/// <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; var shapeableRuns = new List <ShapeableTextCharacters>(textRuns.Count); foreach (var coalescedRuns in CoalesceLevels(textRuns, biDi.ResolvedLevels)) { shapeableRuns.AddRange(coalescedRuns); } for (var index = 0; index < shapeableRuns.Count; index++) { var currentRun = shapeableRuns[index]; var groupedRuns = new List <ShapeableTextCharacters>(2) { currentRun }; var text = currentRun.Text; var start = currentRun.Text.Start; var length = currentRun.Text.Length; var bufferOffset = currentRun.Text.BufferOffset; while (index + 1 < shapeableRuns.Count) { var nextRun = shapeableRuns[index + 1]; if (currentRun.CanShapeTogether(nextRun)) { groupedRuns.Add(nextRun); length += nextRun.Text.Length; if (start > nextRun.Text.Start) { start = nextRun.Text.Start; } if (bufferOffset > nextRun.Text.BufferOffset) { bufferOffset = nextRun.Text.BufferOffset; } text = new ReadOnlySlice <char>(text.Buffer, start, length, bufferOffset); index++; currentRun = nextRun; continue; } break; } shapedTextCharacters.AddRange(ShapeTogether(groupedRuns, text)); } return(shapedTextCharacters); }