Exemple #1
0
        /// <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);
        }
Exemple #2
0
        /// <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);
        }
Exemple #3
0
        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);
        }
Exemple #5
0
        /// <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);
        }