Exemplo n.º 1
0
        protected override void OnWidthExceeded()
        {
            if (addingEllipsis)
            {
                return;
            }

            addingEllipsis = true;

            try
            {
                if (string.IsNullOrEmpty(ellipsisString))
                {
                    return;
                }

                // Characters is re-used to reduce allocations, but must be reset after use
                int startIndex = Characters.Count;

                // Compute the ellipsis to find out the size required
                var builder = new TextBuilder(store, font, float.MaxValue, useFontSizeAsHeight, Vector2.Zero, spacing, Characters, neverFixedWidthCharacters, fallbackCharacter);
                builder.AddText(ellipsisString);

                float            ellipsisWidth      = builder.Bounds.X;
                TextBuilderGlyph firstEllipsisGlyph = builder.Characters[startIndex];

                // Reset the characters list by removing all ellipsis characters
                Characters.RemoveRange(startIndex, Characters.Count - startIndex);

                while (true)
                {
                    RemoveLastCharacter();

                    if (Characters.Count == 0)
                    {
                        break;
                    }

                    if (Characters[Characters.Count - 1].IsWhiteSpace())
                    {
                        continue;
                    }

                    if (base.HasAvailableSpace(firstEllipsisGlyph.GetKerning(Characters[Characters.Count - 1]) + spacing.X + ellipsisWidth))
                    {
                        break;
                    }
                }

                AddText(ellipsisString);
            }
            finally
            {
                addingEllipsis = false;
                ellipsisAdded  = true;
            }
        }
Exemplo n.º 2
0
        /// <summary>
        /// Removes the last character added to this <see cref="TextBuilder"/>.
        /// If the character is the first character on a new line, the new line is also removed.
        /// </summary>
        public void RemoveLastCharacter()
        {
            if (Characters.Count == 0)
            {
                return;
            }

            TextBuilderGlyph removedCharacter  = Characters[Characters.Count - 1];
            TextBuilderGlyph?previousCharacter = Characters.Count == 1 ? null : (TextBuilderGlyph?)Characters[Characters.Count - 2];

            Characters.RemoveAt(Characters.Count - 1);

            // For each character that is removed:
            // 1. Calculate the line height of the last line.
            // 2. If the character is the first on a new line, move the current position upwards by the calculated line height and to the end of the previous line.
            //    The position at the end of the line is the post-XAdvanced position.
            // 3. If the character is not the first on a new line, move the current position backwards by the XAdvance and the kerning from the previous glyph.
            //    This brings the current position to the post-XAdvanced position of the previous glyph.

            currentLineHeight = 0;

            // This is O(n^2) for removing all characters within a line, but is generally not used in such a case
            for (int i = Characters.Count - 1; i >= 0; i--)
            {
                var character = Characters[i];

                currentLineHeight = Math.Max(currentLineHeight, getGlyphHeight(ref character));

                if (character.OnNewLine)
                {
                    break;
                }
            }

            if (removedCharacter.OnNewLine)
            {
                // Move up to the previous line
                currentPos.Y -= currentLineHeight;

                // If this is the first line (ie. there are no characters remaining) we shouldn't be removing the spacing,
                // as there is no spacing applied to the first line.
                if (Characters.Count > 0)
                {
                    currentPos.Y -= spacing.Y;
                }

                currentPos.X = 0;

                if (previousCharacter != null)
                {
                    // The character's draw rectangle is the only marker that keeps a constant state for the position, but it has the glyph's XOffset added into it
                    // So the post-kerned position can be retrieved by taking the XOffset away, and the post-XAdvanced position is retrieved by adding the XAdvance back in
                    currentPos.X = previousCharacter.Value.DrawRectangle.Left - previousCharacter.Value.XOffset + previousCharacter.Value.XAdvance;
                }
            }
            else
            {
                // Move back within the current line, reversing the operations in AddCharacter()
                currentPos.X -= removedCharacter.XAdvance;

                if (previousCharacter != null)
                {
                    currentPos.X -= removedCharacter.GetKerning(previousCharacter.Value) + spacing.X;
                }
            }

            Bounds = Vector2.Zero;

            for (int i = 0; i < Characters.Count; i++)
            {
                // As above, the bounds are calculated through the character draw rectangles
                Bounds = Vector2.ComponentMax(
                    Bounds,
                    new Vector2(
                        Characters[i].DrawRectangle.Left - Characters[i].XOffset + Characters[i].XAdvance,
                        Characters[i].DrawRectangle.Top - Characters[i].YOffset + currentLineHeight)
                    );
            }

            // The new line is removed when the first character on the line is removed, thus the current position is never on a new line
            // after a character is removed except when there are no characters remaining in the builder
            if (Characters.Count == 0)
            {
                currentNewLine = true;
            }
        }