From f5adf50823426e481a15b82134cc5c28031e8f1a Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 22 Dec 2021 13:00:41 +0100 Subject: [PATCH] Added StringBuilder overloads to GenericFont --- CHANGELOG.md | 3 + MLEM.Extended/Font/GenericBitmapFont.cs | 4 +- MLEM.Extended/Font/GenericStashFont.cs | 4 +- MLEM/Font/GenericFont.cs | 183 ++++++++++++++---------- MLEM/Font/GenericSpriteFont.cs | 4 +- MLEM/Formatting/TokenizedString.cs | 8 +- 6 files changed, 123 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c020eca..4a912b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ Jump to version: ## 5.3.0 (Unreleased) ### MLEM +Additions +- Added StringBuilder overloads to GenericFont + Improvements - Generify GenericFont's string drawing diff --git a/MLEM.Extended/Font/GenericBitmapFont.cs b/MLEM.Extended/Font/GenericBitmapFont.cs index db785b4..8b9a494 100644 --- a/MLEM.Extended/Font/GenericBitmapFont.cs +++ b/MLEM.Extended/Font/GenericBitmapFont.cs @@ -38,8 +38,8 @@ namespace MLEM.Extended.Font { } /// - protected override void DrawChar(SpriteBatch batch, char c, string cString, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { - batch.DrawString(this.Font, cString, position, color, rotation, origin, scale, effects, layerDepth); + protected override void DrawChar(SpriteBatch batch, string cString, Vector2 position, Color color, float rotation, Vector2 scale, SpriteEffects effects, float layerDepth) { + batch.DrawString(this.Font, cString, position, color, rotation, Vector2.Zero, scale, effects, layerDepth); } } diff --git a/MLEM.Extended/Font/GenericStashFont.cs b/MLEM.Extended/Font/GenericStashFont.cs index 4afb137..e36d280 100644 --- a/MLEM.Extended/Font/GenericStashFont.cs +++ b/MLEM.Extended/Font/GenericStashFont.cs @@ -38,8 +38,8 @@ namespace MLEM.Extended.Font { } /// - protected override void DrawChar(SpriteBatch batch, char c, string cString, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { - this.Font.DrawText(batch, cString, position, color, scale, rotation, origin, layerDepth); + protected override void DrawChar(SpriteBatch batch,string cString, Vector2 position, Color color, float rotation, Vector2 scale, SpriteEffects effects, float layerDepth) { + this.Font.DrawText(batch, cString, position, color, scale, rotation, Vector2.Zero, layerDepth); } } diff --git a/MLEM/Font/GenericFont.cs b/MLEM/Font/GenericFont.cs index 8ea788b..fa060a6 100644 --- a/MLEM/Font/GenericFont.cs +++ b/MLEM/Font/GenericFont.cs @@ -59,20 +59,23 @@ namespace MLEM.Font { /// Note that this method is only called internally. /// /// The sprite batch to draw with. - /// The character which will be drawn. /// A string representation of the character which will be drawn. /// The drawing location on screen. /// A color mask. /// A rotation of this character. - /// Center of the rotation. 0,0 by default. /// A scaling of this character. /// Modificators for drawing. Can be combined. /// A depth of the layer of this character. - protected abstract void DrawChar(SpriteBatch batch, char c, string cString, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth); + protected abstract void DrawChar(SpriteBatch batch, string cString, Vector2 position, Color color, float rotation, Vector2 scale, SpriteEffects effects, float layerDepth); + + /// + public void DrawString(SpriteBatch batch, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { + this.DrawString(batch, new CharSource(text), position, color, rotation, origin, scale, effects, layerDepth); + } /// public void DrawString(SpriteBatch batch, StringBuilder text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { - this.DrawString(batch, text.ToString(), position, color, rotation, origin, scale, effects, layerDepth); + this.DrawString(batch, new CharSource(text), position, color, rotation, origin, scale, effects, layerDepth); } /// @@ -95,64 +98,6 @@ namespace MLEM.Font { this.DrawString(batch, text, position, color, 0, Vector2.Zero, Vector2.One, SpriteEffects.None, 0); } - /// - public void DrawString(SpriteBatch batch, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { - var (flipX, flipY) = Vector2.Zero; - var flippedV = (effects & SpriteEffects.FlipVertically) != 0; - var flippedH = (effects & SpriteEffects.FlipHorizontally) != 0; - if (flippedV || flippedH) { - var (w, h) = this.MeasureString(text); - if (flippedH) { - origin.X *= -1; - flipX = -w; - } - if (flippedV) { - origin.Y *= -1; - flipY = this.LineHeight - h; - } - } - - var trans = Matrix.Identity; - if (rotation == 0) { - trans.M11 = flippedH ? -scale.X : scale.X; - trans.M22 = flippedV ? -scale.Y : scale.Y; - trans.M41 = (flipX - origin.X) * trans.M11 + position.X; - trans.M42 = (flipY - origin.Y) * trans.M22 + position.Y; - } else { - var sin = (float) Math.Sin(rotation); - var cos = (float) Math.Cos(rotation); - trans.M11 = (flippedH ? -scale.X : scale.X) * cos; - trans.M12 = (flippedH ? -scale.X : scale.X) * sin; - trans.M21 = (flippedV ? -scale.Y : scale.Y) * -sin; - trans.M22 = (flippedV ? -scale.Y : scale.Y) * cos; - trans.M41 = (flipX - origin.X) * trans.M11 + (flipY - origin.Y) * trans.M21 + position.X; - trans.M42 = (flipX - origin.X) * trans.M12 + (flipY - origin.Y) * trans.M22 + position.Y; - } - - var offset = Vector2.Zero; - for (var i = 0; i < text.Length; i++) { - var c = text[i]; - if (c == '\n') { - offset.X = 0; - offset.Y += this.LineHeight; - continue; - } - - var cString = c.ToCachedString(); - var (cW, cH) = this.MeasureString(cString); - - var charPos = offset; - if (flippedH) - charPos.X += cW; - if (flippedV) - charPos.Y += cH - this.LineHeight; - Vector2.Transform(ref charPos, ref trans, out charPos); - - this.DrawChar(batch, c, cString, charPos, color, rotation, Vector2.Zero, scale, effects, layerDepth); - offset.X += cW; - } - } - /// /// Measures the width of the given string when drawn with this font's underlying font. /// This method uses internally to calculate the size of known characters and calculates additional characters like , and . @@ -162,7 +107,7 @@ namespace MLEM.Font { /// Whether trailing whitespace should be ignored in the returned size, causing the end of each line to be effectively trimmed /// The size of the string when drawn with this font public Vector2 MeasureString(string text, bool ignoreTrailingSpaces = false) { - return this.MeasureString(text, ignoreTrailingSpaces, null); + return this.MeasureString(new CharSource(text), ignoreTrailingSpaces, null); } /// @@ -176,7 +121,12 @@ namespace MLEM.Font { /// The characters to add to the end of the string if it is too long /// The truncated string, or the same string if it is shorter than the maximum width public string TruncateString(string text, float width, float scale, bool fromBack = false, string ellipsis = "") { - return this.TruncateString(text, width, scale, fromBack, ellipsis, null); + return this.TruncateString(new CharSource(text), width, scale, fromBack, ellipsis, null).ToString(); + } + + /// + public StringBuilder TruncateString(StringBuilder text, float width, float scale, bool fromBack = false, string ellipsis = "") { + return this.TruncateString(new CharSource(text), width, scale, fromBack, ellipsis, null); } /// @@ -192,20 +142,30 @@ namespace MLEM.Font { return string.Join("\n", this.SplitStringSeparate(text, width, scale)); } + /// + public string SplitString(StringBuilder text, float width, float scale) { + return string.Join("\n", this.SplitStringSeparate(text, width, scale)); + } + /// /// Splits a string to a given maximum width and returns each split section as a separate string. /// Note that existing new lines are taken into account for line length, but not split in the resulting strings. - /// This method differs from in that it differentiates between pre-existing newline characters and splits due to maximum width. + /// This method differs from in that it differentiates between pre-existing newline characters and splits due to maximum width. /// /// The text to split into multiple lines /// The maximum width that each line should have /// The scale to use for width measurements /// The split string as an enumerable of split sections public IEnumerable SplitStringSeparate(string text, float width, float scale) { - return this.SplitStringSeparate(text, width, scale, null); + return this.SplitStringSeparate(new CharSource(text), width, scale, null); } - internal Vector2 MeasureString(string text, bool ignoreTrailingSpaces, Func fontFunction) { + /// + public IEnumerable SplitStringSeparate(StringBuilder text, float width, float scale) { + return this.SplitStringSeparate(new CharSource(text), width, scale, null); + } + + internal Vector2 MeasureString(CharSource text, bool ignoreTrailingSpaces, Func fontFunction) { var size = Vector2.Zero; if (text.Length <= 0) return size; @@ -247,7 +207,7 @@ namespace MLEM.Font { return size; } - internal string TruncateString(string text, float width, float scale, bool fromBack, string ellipsis, Func fontFunction) { + internal StringBuilder TruncateString(CharSource text, float width, float scale, bool fromBack, string ellipsis, Func fontFunction) { var total = new StringBuilder(); for (var i = 0; i < text.Length; i++) { if (fromBack) { @@ -259,16 +219,16 @@ namespace MLEM.Font { var font = fontFunction?.Invoke(i) ?? this; if (font.MeasureString(total + ellipsis).X * scale >= width) { if (fromBack) { - return total.Remove(0, 1).Insert(0, ellipsis).ToString(); + return total.Remove(0, 1).Insert(0, ellipsis); } else { - return total.Remove(total.Length - 1, 1).Append(ellipsis).ToString(); + return total.Remove(total.Length - 1, 1).Append(ellipsis); } } } - return total.ToString(); + return total; } - internal IEnumerable SplitStringSeparate(string text, float width, float scale, Func fontFunction) { + internal IEnumerable SplitStringSeparate(CharSource text, float width, float scale, Func fontFunction) { var currWidth = 0F; var lastSpaceIndex = -1; var widthSinceLastSpace = 0F; @@ -316,7 +276,64 @@ namespace MLEM.Font { yield return curr.ToString(); } - private static bool IsTrailingSpace(string s, int index) { + private void DrawString(SpriteBatch batch, CharSource text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { + var (flipX, flipY) = Vector2.Zero; + var flippedV = (effects & SpriteEffects.FlipVertically) != 0; + var flippedH = (effects & SpriteEffects.FlipHorizontally) != 0; + if (flippedV || flippedH) { + var (w, h) = this.MeasureString(text, false, null); + if (flippedH) { + origin.X *= -1; + flipX = -w; + } + if (flippedV) { + origin.Y *= -1; + flipY = this.LineHeight - h; + } + } + + var trans = Matrix.Identity; + if (rotation == 0) { + trans.M11 = flippedH ? -scale.X : scale.X; + trans.M22 = flippedV ? -scale.Y : scale.Y; + trans.M41 = (flipX - origin.X) * trans.M11 + position.X; + trans.M42 = (flipY - origin.Y) * trans.M22 + position.Y; + } else { + var sin = (float) Math.Sin(rotation); + var cos = (float) Math.Cos(rotation); + trans.M11 = (flippedH ? -scale.X : scale.X) * cos; + trans.M12 = (flippedH ? -scale.X : scale.X) * sin; + trans.M21 = (flippedV ? -scale.Y : scale.Y) * -sin; + trans.M22 = (flippedV ? -scale.Y : scale.Y) * cos; + trans.M41 = (flipX - origin.X) * trans.M11 + (flipY - origin.Y) * trans.M21 + position.X; + trans.M42 = (flipX - origin.X) * trans.M12 + (flipY - origin.Y) * trans.M22 + position.Y; + } + + var offset = Vector2.Zero; + for (var i = 0; i < text.Length; i++) { + var c = text[i]; + if (c == '\n') { + offset.X = 0; + offset.Y += this.LineHeight; + continue; + } + + var cString = c.ToCachedString(); + var (cW, cH) = this.MeasureString(cString); + + var charPos = offset; + if (flippedH) + charPos.X += cW; + if (flippedV) + charPos.Y += cH - this.LineHeight; + Vector2.Transform(ref charPos, ref trans, out charPos); + + this.DrawChar(batch, cString, charPos, color, rotation, scale, effects, layerDepth); + offset.X += cW; + } + } + + private static bool IsTrailingSpace(CharSource s, int index) { for (var i = index + 1; i < s.Length; i++) { if (s[i] != ' ') return false; @@ -324,5 +341,25 @@ namespace MLEM.Font { return true; } + internal readonly struct CharSource { + + private readonly string strg; + private readonly StringBuilder builder; + + public int Length => this.strg?.Length ?? this.builder.Length; + public char this[int index] => this.strg?[index] ?? this.builder[index]; + + public CharSource(string strg) { + this.strg = strg; + this.builder = null; + } + + public CharSource(StringBuilder builder) { + this.strg = null; + this.builder = builder; + } + + } + } } \ No newline at end of file diff --git a/MLEM/Font/GenericSpriteFont.cs b/MLEM/Font/GenericSpriteFont.cs index 9f29567..64b69aa 100644 --- a/MLEM/Font/GenericSpriteFont.cs +++ b/MLEM/Font/GenericSpriteFont.cs @@ -37,8 +37,8 @@ namespace MLEM.Font { } /// - protected override void DrawChar(SpriteBatch batch, char c, string cString, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { - batch.DrawString(this.Font, cString, position, color, rotation, origin, scale, effects, layerDepth); + protected override void DrawChar(SpriteBatch batch, string cString, Vector2 position, Color color, float rotation, Vector2 scale, SpriteEffects effects, float layerDepth) { + batch.DrawString(this.Font, cString, position, color, rotation, Vector2.Zero, scale, effects, layerDepth); } private static SpriteFont SetDefaults(SpriteFont font) { diff --git a/MLEM/Formatting/TokenizedString.cs b/MLEM/Formatting/TokenizedString.cs index 0b2bf88..9dd2cef 100644 --- a/MLEM/Formatting/TokenizedString.cs +++ b/MLEM/Formatting/TokenizedString.cs @@ -7,6 +7,7 @@ using MLEM.Extensions; using MLEM.Font; using MLEM.Formatting.Codes; using MLEM.Misc; +using static MLEM.Font.GenericFont; namespace MLEM.Formatting { /// @@ -51,7 +52,6 @@ namespace MLEM.Formatting { /// /// Splits this tokenized string, inserting newline characters if the width of the string is bigger than the maximum width. /// Note that a tokenized string can be re-split without losing any of its actual data, as this operation merely modifies the . - /// /// /// The font to use for width calculations /// The maximum width, in display pixels based on the font and scale @@ -59,7 +59,7 @@ namespace MLEM.Formatting { /// The text alignment that should be used for width calculations public void Split(GenericFont font, float width, float scale, TextAlignment alignment = TextAlignment.Left) { // a split string has the same character count as the input string but with newline characters added - this.modifiedString = string.Join("\n", font.SplitStringSeparate(this.String, width, scale, i => this.GetFontForIndex(font, i))); + this.modifiedString = string.Join("\n", font.SplitStringSeparate(new CharSource(this.String), width, scale, i => this.GetFontForIndex(font, i))); this.StoreModifiedSubstrings(font, alignment); } @@ -74,13 +74,13 @@ namespace MLEM.Formatting { /// The characters to add to the end of the string if it is too long /// The text alignment that should be used for width calculations public void Truncate(GenericFont font, float width, float scale, string ellipsis = "", TextAlignment alignment = TextAlignment.Left) { - this.modifiedString = font.TruncateString(this.String, width, scale, false, ellipsis, i => this.GetFontForIndex(font, i)); + this.modifiedString = font.TruncateString(new CharSource(this.String), width, scale, false, ellipsis, i => this.GetFontForIndex(font, i)).ToString(); this.StoreModifiedSubstrings(font, alignment); } /// public Vector2 Measure(GenericFont font) { - return font.MeasureString(this.DisplayString, false, i => this.GetFontForIndex(font, i)); + return font.MeasureString(new CharSource(this.DisplayString), false, i => this.GetFontForIndex(font, i)); } ///