/// <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); }
public static bool Run() { Console.WriteLine("Bidi Character Tests"); Console.WriteLine("--------------------"); Console.WriteLine(); // Read the test file var location = System.IO.Path.GetDirectoryName(typeof(BidiCharacterTest).Assembly.Location); var lines = System.IO.File.ReadAllLines(System.IO.Path.Combine(location, System.IO.Path.Combine("TestData", "BidiCharacterTest.txt"))); // Parse lines var tests = new List <Test>(); for (int lineNumber = 1; lineNumber < lines.Length + 1; lineNumber++) { // Get the line, remove comments var line = lines[lineNumber - 1].Split('#')[0].Trim(); // Ignore blank/comment only lines if (string.IsNullOrWhiteSpace(line)) { continue; } // Split into fields var fields = line.Split(';'); var test = new Test(); test.LineNumber = lineNumber; // Parse field 0 - code points test.CodePoints = fields[0].Split(' ').Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x)).Select(x => Convert.ToInt32(x, 16)).ToArray(); // Parse field 1 - paragraph level test.ParagraphLevel = sbyte.Parse(fields[1]); // Parse field 2 - resolved paragraph level test.ResolvedParagraphLevel = sbyte.Parse(fields[2]); // Parse field 3 - resolved levels test.ResolvedLevels = fields[3].Split(' ').Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x)).Select(x => x == "x" ? (sbyte)-1 : Convert.ToSByte(x)).ToArray(); // Parse field 4 - resolved levels test.ResolvedOrder = fields[4].Split(' ').Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x)).Select(x => Convert.ToInt32(x)).ToArray(); tests.Add(test); } Console.WriteLine($"Test data loaded: {tests.Count} test cases"); var bidi = new Bidi(); var bidiData = new BidiData(); // Run tests... var tr = new TestResults(); for (int testNumber = 0; testNumber < tests.Count; testNumber++) { var t = tests[testNumber]; // Arrange bidiData.Init(new Slice <int>(t.CodePoints), t.ParagraphLevel); // Act tr.EnterTest(); for (int i = 0; i < 10; i++) { bidi.Process(bidiData); } tr.LeaveTest(); var resultLevels = bidi.ResolvedLevels; int resultParagraphLevel = bidi.ResolvedParagraphEmbeddingLevel; // Assert bool passed = true; if (t.ResolvedParagraphLevel != resultParagraphLevel) { passed = false; } for (int i = 0; i < t.ResolvedLevels.Length; i++) { if (t.ResolvedLevels[i] == -1) { continue; } if (t.ResolvedLevels[i] != resultLevels[i]) { passed = false; break; } } /* * if (!passed) * { * Console.WriteLine($"Failed line {t.LineNumber}"); * Console.WriteLine(); * Console.WriteLine($" Code Points: {string.Join(" ", t.CodePoints.Select(x => x.ToString("X4")))}"); * Console.WriteLine($" Pair Bracket Types: {string.Join(" ", bidiData.PairedBracketTypes.Select(x => " " + x.ToString()))}"); * Console.WriteLine($" Pair Bracket Values: {string.Join(" ", bidiData.PairedBracketValues.Select(x => x.ToString("X4")))}"); * Console.WriteLine($" Embed Level: {t.ParagraphLevel}"); * Console.WriteLine($" Expected Embed Level: {t.ResolvedParagraphLevel}"); * Console.WriteLine($" Actual Embed Level: {resultParagraphLevel}"); * Console.WriteLine($" Directionality: {string.Join(" ", bidiData.Types)}"); * Console.WriteLine($" Expected Levels: {string.Join(" ", t.ResolvedLevels)}"); * Console.WriteLine($" Actual Levels: {string.Join(" ", resultLevels)}"); * Console.WriteLine(); * return false; * } */ // Record it tr.TestPassed(passed); } tr.Dump(); return(tr.AllPassed); }
/// <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); }