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; } }
/// <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; } }