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

            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 #6
0
        /// <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);
        }
Exemple #7
0
        /// <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);
        }
Exemple #8
0
        /// <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));
        }
Exemple #9
0
            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);
        }
Exemple #11
0
 public OrderedBidiRun(ShapedTextCharacters run) => Run = run;