From 9aef994c510ba0c59845442f5210cfe43842945f Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Tue, 12 Oct 2021 03:23:35 +0200 Subject: [PATCH] Added GenericFont SplitStringSeparate which differentiates between existing newline characters and splits due to maximum width --- CHANGELOG.md | 1 + MLEM/Font/GenericFont.cs | 36 +++++++++++++++++++++++++++--------- Tests/FontTests.cs | 10 +++++----- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f251d8..33263eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/MLEM/Font/GenericFont.cs b/MLEM/Font/GenericFont.cs index 9d2c055..097d8a3 100644 --- a/MLEM/Font/GenericFont.cs +++ b/MLEM/Font/GenericFont.cs @@ -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 { /// /// 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 . + /// See for a method that differentiates between 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, containing newline characters at each new line public string SplitString(string text, float width, float scale) { - var ret = new StringBuilder(); + 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. + /// + /// 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) { 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) { diff --git a/Tests/FontTests.cs b/Tests/FontTests.cs index b1e7599..6ac1366 100644 --- a/Tests/FontTests.cs +++ b/Tests/FontTests.cs @@ -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' source code for more in-depth explanations of their functionality or the website 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_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"); 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_",