diff --git a/MLEM/Font/GenericFont.cs b/MLEM/Font/GenericFont.cs
index 094fca1..bf59eea 100644
--- a/MLEM/Font/GenericFont.cs
+++ b/MLEM/Font/GenericFont.cs
@@ -1,3 +1,4 @@
+using System;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
@@ -21,6 +22,11 @@ namespace MLEM.Font {
/// Whereas a regular would have to explicitly support this character for width calculations, generic fonts implicitly support it in .
///
public const char Nbsp = '\u00A0';
+ ///
+ /// This field holds the unicode representation of a zero-width space.
+ /// Whereas a regular would have to explicitly support this character for width calculations and string splitting, generic fonts implicitly support it in and .
+ ///
+ public const char Zwsp = '\u200B';
///
/// The bold version of this font.
@@ -108,6 +114,9 @@ namespace MLEM.Font {
case Nbsp:
xOffset += this.MeasureChar(' ').X;
break;
+ case Zwsp:
+ // don't add width for a zero-width space
+ break;
default:
xOffset += this.MeasureChar(c).X;
break;
@@ -161,38 +170,47 @@ namespace MLEM.Font {
/// The scale to use for width measurements
/// The split string, containing newline characters at each new line
public string SplitString(string text, float width, float scale) {
- var total = new StringBuilder();
- foreach (var line in text.Split('\n')) {
- var curr = new StringBuilder();
- foreach (var word in line.Split(' ')) {
- if (this.MeasureString(word).X * scale >= width) {
- if (curr.Length > 0) {
- total.Append(curr).Append('\n');
- curr.Clear();
- }
- var wordBuilder = new StringBuilder();
- for (var i = 0; i < word.Length; i++) {
- wordBuilder.Append(word[i]);
- if (this.MeasureString(wordBuilder.ToString()).X * scale >= width) {
- total.Append(wordBuilder.ToString(0, wordBuilder.Length - 1)).Append('\n');
- wordBuilder.Remove(0, wordBuilder.Length - 1);
- }
- }
- curr.Append(wordBuilder).Append(' ');
- } else {
- curr.Append(word).Append(' ');
- if (this.MeasureString(curr.ToString()).X * scale >= width) {
- var len = curr.Length - word.Length - 1;
- if (len > 0) {
- total.Append(curr.ToString(0, len)).Append('\n');
- curr.Remove(0, len);
- }
+ var ret = new StringBuilder();
+ var currWidth = 0F;
+ var lastSpaceIndex = -1;
+ var widthSinceLastSpace = 0F;
+ for (var i = 0; i < text.Length; i++) {
+ var c = text[i];
+ if (c == '\n') {
+ // split at pre-defined new lines
+ ret.Append(c);
+ lastSpaceIndex = -1;
+ widthSinceLastSpace = 0;
+ currWidth = 0;
+ } else {
+ var cWidth = this.MeasureChar(c).X * scale;
+ if (c == ' ' || c == OneEmSpace || c == Zwsp) {
+ // remember the location of this space
+ lastSpaceIndex = ret.Length;
+ widthSinceLastSpace = 0;
+ } else if (currWidth + cWidth >= width) {
+ // check if this line contains a space
+ if (lastSpaceIndex < 0) {
+ // if there is no last space, the word is longer than a line so we split here
+ ret.Append('\n');
+ currWidth = 0;
+ } 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();
}
}
diff --git a/MLEM/Formatting/TokenizedString.cs b/MLEM/Formatting/TokenizedString.cs
index 6c70d5f..bd70fb3 100644
--- a/MLEM/Formatting/TokenizedString.cs
+++ b/MLEM/Formatting/TokenizedString.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Linq;
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 (index >= token.Index)
ret.Append(this.splitString[i]);
- // if the current char is not a newline, we simulate length increase
- if (this.splitString[i] != '\n') {
+ // if the current char is not an added newline, we simulate length increase
+ if (this.splitString[i] != '\n' || this.String[index] == '\n') {
if (index >= token.Index)
length++;
index++;
diff --git a/Tests/FontTests.cs b/Tests/FontTests.cs
index 9d79221..68686c0 100644
--- a/Tests/FontTests.cs
+++ b/Tests/FontTests.cs
@@ -79,6 +79,24 @@ namespace Tests {
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 content that will be split.\nNow this is a new line with additional regular content 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]
public void TestMacros() {
this.formatter.Macros.Add(new Regex(""), (f, m, r) => "");