1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-25 14:08:34 +01:00

improved performance of SplitString and re-added Zwsp compatibility

This commit is contained in:
Ell 2021-04-14 23:13:19 +02:00
parent 538fd08d8a
commit e7ab8fefe8
3 changed files with 67 additions and 30 deletions

View file

@ -1,3 +1,4 @@
using System;
using System.Text; using System.Text;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
@ -21,6 +22,11 @@ namespace MLEM.Font {
/// Whereas a regular <see cref="SpriteFont"/> would have to explicitly support this character for width calculations, generic fonts implicitly support it in <see cref="MeasureString"/>. /// Whereas a regular <see cref="SpriteFont"/> would have to explicitly support this character for width calculations, generic fonts implicitly support it in <see cref="MeasureString"/>.
/// </summary> /// </summary>
public const char Nbsp = '\u00A0'; public const char Nbsp = '\u00A0';
/// <summary>
/// This field holds the unicode representation of a zero-width space.
/// Whereas a regular <see cref="SpriteFont"/> would have to explicitly support this character for width calculations and string splitting, generic fonts implicitly support it in <see cref="MeasureString"/> and <see cref="SplitString"/>.
/// </summary>
public const char Zwsp = '\u200B';
/// <summary> /// <summary>
/// The bold version of this font. /// The bold version of this font.
@ -108,6 +114,9 @@ namespace MLEM.Font {
case Nbsp: case Nbsp:
xOffset += this.MeasureChar(' ').X; xOffset += this.MeasureChar(' ').X;
break; break;
case Zwsp:
// don't add width for a zero-width space
break;
default: default:
xOffset += this.MeasureChar(c).X; xOffset += this.MeasureChar(c).X;
break; break;
@ -161,38 +170,47 @@ namespace MLEM.Font {
/// <param name="scale">The scale to use for width measurements</param> /// <param name="scale">The scale to use for width measurements</param>
/// <returns>The split string, containing newline characters at each new line</returns> /// <returns>The split string, containing newline characters at each new line</returns>
public string SplitString(string text, float width, float scale) { public string SplitString(string text, float width, float scale) {
var total = new StringBuilder(); var ret = new StringBuilder();
foreach (var line in text.Split('\n')) { var currWidth = 0F;
var curr = new StringBuilder(); var lastSpaceIndex = -1;
foreach (var word in line.Split(' ')) { var widthSinceLastSpace = 0F;
if (this.MeasureString(word).X * scale >= width) { for (var i = 0; i < text.Length; i++) {
if (curr.Length > 0) { var c = text[i];
total.Append(curr).Append('\n'); if (c == '\n') {
curr.Clear(); // split at pre-defined new lines
} ret.Append(c);
var wordBuilder = new StringBuilder(); lastSpaceIndex = -1;
for (var i = 0; i < word.Length; i++) { widthSinceLastSpace = 0;
wordBuilder.Append(word[i]); currWidth = 0;
if (this.MeasureString(wordBuilder.ToString()).X * scale >= width) { } else {
total.Append(wordBuilder.ToString(0, wordBuilder.Length - 1)).Append('\n'); var cWidth = this.MeasureChar(c).X * scale;
wordBuilder.Remove(0, wordBuilder.Length - 1); if (c == ' ' || c == OneEmSpace || c == Zwsp) {
} // remember the location of this space
} lastSpaceIndex = ret.Length;
curr.Append(wordBuilder).Append(' '); widthSinceLastSpace = 0;
} else { } else if (currWidth + cWidth >= width) {
curr.Append(word).Append(' '); // check if this line contains a space
if (this.MeasureString(curr.ToString()).X * scale >= width) { if (lastSpaceIndex < 0) {
var len = curr.Length - word.Length - 1; // if there is no last space, the word is longer than a line so we split here
if (len > 0) { ret.Append('\n');
total.Append(curr.ToString(0, len)).Append('\n'); currWidth = 0;
curr.Remove(0, len); } else {
} // split after the last space
ret.Insert(lastSpaceIndex + 1, '\n');
// we need to restore the width accumulated since the last space for the new line
currWidth = widthSinceLastSpace;
} }
widthSinceLastSpace = 0;
lastSpaceIndex = -1;
} }
// add current character
currWidth += cWidth;
widthSinceLastSpace += cWidth;
ret.Append(c);
} }
total.Append(curr).Append('\n');
} }
return total.ToString(0, total.Length - 2); return ret.ToString();
} }
} }

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -72,8 +73,8 @@ namespace MLEM.Formatting {
// if we're within the bounds of the token's substring, append to the new substring // if we're within the bounds of the token's substring, append to the new substring
if (index >= token.Index) if (index >= token.Index)
ret.Append(this.splitString[i]); ret.Append(this.splitString[i]);
// if the current char is not a newline, we simulate length increase // if the current char is not an added newline, we simulate length increase
if (this.splitString[i] != '\n') { if (this.splitString[i] != '\n' || this.String[index] == '\n') {
if (index >= token.Index) if (index >= token.Index)
length++; length++;
index++; index++;

View file

@ -79,6 +79,24 @@ namespace Tests {
Assert.AreEqual(formatted.Tokens[i].DisplayString, tokens[i]); Assert.AreEqual(formatted.Tokens[i].DisplayString, tokens[i]);
} }
[Test]
public void TestNewlineSplit() {
var formatted = this.formatter.Tokenize(this.font,
"This is a pretty long line with regular <c Blue>content</c> that will be split.\nNow this is a new line with additional regular <c Blue>content</c> that is forced into a new line.");
formatted.Split(this.font, 65, 0.1F);
Assert.AreEqual(formatted.DisplayString, "This is a pretty long line with \nregular content that will be \nsplit.\nNow this is a new line with \nadditional regular content that \nis forced into a new line.");
var tokens = new[] {
"This is a pretty long line with \nregular ",
"content",
" that will be \nsplit.\nNow this is a new line with \nadditional regular ",
"content",
" that \nis forced into a new line."
};
for (var i = 0; i < tokens.Length; i++)
Assert.AreEqual(formatted.Tokens[i].DisplayString, tokens[i]);
}
[Test] [Test]
public void TestMacros() { public void TestMacros() {
this.formatter.Macros.Add(new Regex("<testmacro>"), (f, m, r) => "<test1>"); this.formatter.Macros.Add(new Regex("<testmacro>"), (f, m, r) => "<test1>");