Added GenericFont SplitStringSeparate which differentiates between existing newline characters and splits due to maximum width

This commit is contained in:
Ell 2021-10-12 03:23:35 +02:00
parent 1d462b0252
commit 9aef994c51
3 changed files with 33 additions and 14 deletions

View File

@ -10,6 +10,7 @@ Jump to version:
### MLEM
Additions
- Added a strikethrough formatting code
- Added GenericFont SplitStringSeparate which differentiates between existing newline characters and splits due to maximum width
### MLEM.Ui
Additions

View File

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
@ -160,22 +161,36 @@ namespace MLEM.Font {
/// <summary>
/// Splits a string to a given maximum width, adding newline characters between each line.
/// Also splits long words and supports zero-width spaces.
/// Also splits long words and supports zero-width spaces and takes into account existing newline characters in the passed <paramref name="text"/>.
/// See <see cref="SplitStringSeparate"/> for a method that differentiates between existing newline characters and splits due to maximum width.
/// </summary>
/// <param name="text">The text to split into multiple lines</param>
/// <param name="width">The maximum width that each line should have</param>
/// <param name="scale">The scale to use for width measurements</param>
/// <returns>The split string, containing newline characters at each new line</returns>
public string SplitString(string text, float width, float scale) {
var ret = new StringBuilder();
return string.Join("\n", this.SplitStringSeparate(text, width, scale));
}
/// <summary>
/// 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 <see cref="SplitString"/> in that it differentiates between pre-existing newline characters and splits due to maximum width.
/// </summary>
/// <param name="text">The text to split into multiple lines</param>
/// <param name="width">The maximum width that each line should have</param>
/// <param name="scale">The scale to use for width measurements</param>
/// <returns>The split string as an enumerable of split sections</returns>
public IEnumerable<string> SplitStringSeparate(string text, float width, float scale) {
var currWidth = 0F;
var lastSpaceIndex = -1;
var widthSinceLastSpace = 0F;
var curr = new StringBuilder();
for (var i = 0; i < text.Length; i++) {
var c = text[i];
if (c == '\n') {
// split at pre-defined new lines
ret.Append(c);
// fake split at pre-defined new lines
curr.Append(c);
lastSpaceIndex = -1;
widthSinceLastSpace = 0;
currWidth = 0;
@ -183,17 +198,19 @@ namespace MLEM.Font {
var cWidth = this.MeasureString(c.ToCachedString()).X * scale;
if (c == ' ' || c == OneEmSpace || c == Zwsp) {
// remember the location of this (breaking!) space
lastSpaceIndex = ret.Length;
lastSpaceIndex = curr.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');
yield return curr.ToString();
currWidth = 0;
curr.Clear();
} else {
// split after the last space
ret.Insert(lastSpaceIndex + 1, '\n');
yield return curr.ToString().Substring(0, lastSpaceIndex + 1);
curr.Remove(0, lastSpaceIndex + 1);
// we need to restore the width accumulated since the last space for the new line
currWidth = widthSinceLastSpace;
}
@ -204,10 +221,11 @@ namespace MLEM.Font {
// add current character
currWidth += cWidth;
widthSinceLastSpace += cWidth;
ret.Append(c);
curr.Append(c);
}
}
return ret.ToString();
if (curr.Length > 0)
yield return curr.ToString();
}
private static bool IsTrailingSpace(string s, int index) {

View File

@ -29,9 +29,9 @@ namespace Tests {
[Test]
public void TestRegularSplit() {
Assert.AreEqual(this.font.SplitString(
Assert.AreEqual(this.font.SplitStringSeparate(
"Note that the default style does not contain any textures or font files and, as such, is quite bland. However, the default style is quite easy to override, as can be seen in the code for this demo.",
65, 0.1F), "Note that the default style does \nnot contain any textures or font \nfiles and, as such, is quite \nbland. However, the default \nstyle is quite easy to override, \nas can be seen in the code for \nthis demo.");
65, 0.1F), new[] {"Note that the default style does ", "not contain any textures or font ", "files and, as such, is quite ", "bland. However, the default ", "style is quite easy to override, ", "as can be seen in the code for ", "this demo."});
var formatted = this.formatter.Tokenize(this.font,
"Select the demo you want to see below using your mouse, touch input, your keyboard or a controller. Check the demos' <c CornflowerBlue><l https://github.com/Ellpeck/MLEM/tree/main/Demos>source code</l></c> for more in-depth explanations of their functionality or the <c CornflowerBlue><l https://mlem.ellpeck.de/>website</l></c> for tutorials and API documentation.");
@ -55,16 +55,16 @@ namespace Tests {
[Test]
public void TestLongLineSplit() {
const string expectedDisplay = "This_is_a_really_long_line_to_s\nee_if_splitting_without_spaces_\nworks_properly._I_also_want_to_\nsee_if_it_works_across_multiple\n_lines_or_just_on_the_first_one. \nBut after this, I want the text to \ncontinue normally before \nchanging_back_to_being_really_\nlong_oh_yes";
var expectedDisplay = new[] {"This_is_a_really_long_line_to_s", "ee_if_splitting_without_spaces_", "works_properly._I_also_want_to_", "see_if_it_works_across_multiple", "_lines_or_just_on_the_first_one. ", "But after this, I want the text to ", "continue normally before ", "changing_back_to_being_really_", "long_oh_yes"};
Assert.AreEqual(this.font.SplitString(
Assert.AreEqual(this.font.SplitStringSeparate(
"This_is_a_really_long_line_to_see_if_splitting_without_spaces_works_properly._I_also_want_to_see_if_it_works_across_multiple_lines_or_just_on_the_first_one. But after this, I want the text to continue normally before changing_back_to_being_really_long_oh_yes",
65, 0.1F), expectedDisplay);
var formatted = this.formatter.Tokenize(this.font,
"This_is_a_really_long_line_to_see_if_<c Blue>splitting</c>_without_spaces_works_properly._I_also_want_to_see_if_it_works_across_multiple_<c Yellow>lines</c>_or_just_on_the_first_one. But after this, I want the <b>text</b> to continue normally before changing_back_<i>to</i>_being_really_long_oh_yes");
formatted.Split(this.font, 65, 0.1F);
Assert.AreEqual(formatted.DisplayString, expectedDisplay);
Assert.AreEqual(formatted.DisplayString, string.Join('\n', expectedDisplay));
var tokens = new[] {
"This_is_a_really_long_line_to_s\nee_if_",