I have made a small algorithm that essentially is just tacked on the end of the SpriteBatch DrawString method. That as the title suggest word wraps text.
Basically its drawstring but it takes a bounding Rectangle. It can do this in real time without a huge performance hit. It can measure the text to the sides the top and bottom or both i use the full rectangle atm but it can do either or both.
How im doing it at the moment is actually to reformat a stringbuilder by inserting newlines using a mocked up renamed drawstring with the extra wrapping code, then get the altered stringbuilder back, and then draw it. I was thinking this alone would probably just as useful if not more so then measurestring which is also a part of spritebatch..
So anyways i think it would be faster in place in drawstring without the extra step im doing but it would probably require a character array or list in place for a word buffer. It's surprisingly simple, it basically marks and sweeps words.
I think it would be a nice addition to spritebatch in either form to have a drawstring(string s, rectangle textbounds ...) Or a method that reformats the text of a stringbuilder and returns it. I think it would be nice convenience to have at least the stringbuilder altering version in monogame if not a actual drawstring.
Question is... What do you guys think ?
If you guys want to take a look, i can post up my current method write up a draft for a spritebatch version or just work on it and submit a pull request, but i wanted to know what you guys think of the idea first.

reformat a stringbuilder
I would be curious to see the code, because manipulating strings/stringbuilders is likely to generate garbage. Which is something we should avoid.
You don't have to reformat a string or use MeasureString, you only have to play with the offset within DrawString to simulate a \n character when the next word offset is out of the bound of the rectangle.
I like the proposition though. It's typically the kind of thing that we end up writing anyway when making a game.
Yes that is what i do currently with a couple of caveats.
Atm i use my own StringBuilders wrapper as it has a function that replaces string builders Insert but its not efficient that way, so i would have to modify the current function if i wanted to really speed it up.
StringBuilders insert seems to generate collected garbage as a rule which is a big pain so that has to be worked around for any string wrapping manipulations.
Right a actual drawstring that directly wraps words doesn't need to insert characters, as it can just re-position the text on the fly. However it would require a buffer so as to spit out a loop of item.Sets(...) after the calculations were completed in the first pass. Those would re-position the words after rewinds occur so a overwrite-able buffer would be needed.
Anyways here is my current prototype just to get a idea of the algorithm with comments, the insert and buffer are the only two real hurtles to overcome. Basically im reformating a stringbuilder here.
The idea is like so... rewinding words that will end up violating a boundry.
I mark the start of a word, if it will end up going out of bounds or does.
I just back up back to the start of the word, re-position the draw to line down or insert a newline and then continue.
If a word is going to break the height part of the boundry.
Then i set the buffer length to that words starting position and end the function.
pretty simple idea.
The logic is basically 3 layers deep at most shown by the relevant wrapping logic block meaning... at most 3 conditional checks deep and that represents were a word intersects the bounding area.
public static void WordWrapTextWithinBounds(MgStringBuilder text, Vector2 scale, Rectangle boundRect)
{
// AdHock to just use the currently set font and make the rest of the method a little more readable.
var letterWidthSpacing = tsf.Spacing;
var lineHeightSpacing = tsf.LineSpacing * scale.Y;
Vector2 offset = Vector2.Zero;
float yextent = offset.Y + lineHeightSpacing;
Vector2 redrawOffset = Vector2.Zero;
Rectangle dest = new Rectangle();
var currentGlyph = SpriteFont.Glyph.Empty;
var firstGlyphOfLine = true;
int lastWordBreakCharPos = 0;
bool firstwordonline = true;
Vector2 rewindOffset = Vector2.Zero;
// Basically Drawstring here
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
if (c == '\r')
continue;
if (c == '\n')
{
offset.X = 0;
offset.Y += lineHeightSpacing;
yextent = offset.Y + lineHeightSpacing;
firstGlyphOfLine = true;
firstwordonline = true; // <<< note.
continue;
}
if (_glyphs.ContainsKey(c))
currentGlyph = _glyphs[c];
else
if (!tsf.DefaultCharacter.HasValue)
throw new ArgumentException("Text Contains a Unresolvable Character");
else
currentGlyph = defaultGlyph;
if (firstGlyphOfLine)
{
// Removes left side bearing for the first character on a line
offset.X = Math.Max(currentGlyph.LeftSideBearing, 0);
firstGlyphOfLine = false;
}
else
offset.X += letterWidthSpacing + currentGlyph.LeftSideBearing;
// matrix calculations unrolled rotation is excluded for this version
var m = offset;
m.X += currentGlyph.Cropping.X;
m.Y += currentGlyph.Cropping.Y;
dest = new Rectangle
(
(int)(m.X * scale.X),
(int)(m.Y * scale.Y),
(int)(currentGlyph.BoundsInTexture.Width * scale.X),
(int)(currentGlyph.BoundsInTexture.Height * scale.Y)
);
// Prep for word wrapping operations here.
if (c == ' ')
{
// if char is a word break character e.g. white space we mark the words start position as a rewind canidate.
lastWordBreakCharPos = i;
rewindOffset = offset;
if (firstwordonline)
firstwordonline = false;
}
// Wrapping logic sequence.
if (yextent >= boundRect.Height)
{
// Termination due to height boundry reached. Set sb length to the current read position.
text.Length = i;
i = text.Length;
}
else
{
if (dest.Right > boundRect.Width && text.Length > (i + 1))
{
if (firstwordonline == false && text[lastWordBreakCharPos + 1] != '\n')
{
text.AppendAt(lastWordBreakCharPos + 1, '\n'); //<< Note, Stringbuilders insert here is was a gc-collection problem... fixed this by using my own char sliding loop could be done better in other ways.
if (text[lastWordBreakCharPos + 1] != ' ')
{
offset = rewindOffset;
i = lastWordBreakCharPos;
}
}
else // When first word on the line is true
{
if (text[i + 1] != '\n') // If its not already a new line char
text.AppendAt(i + 1, '\n'); // << see earlier note.
}
}
}
offset.X += currentGlyph.Width + currentGlyph.RightSideBearing;
}
}
I suppose a static buffer of some type that can grow is the simplest way to make it work without garbage using stringbuilder is a problem for that as the insert method is literally garbage.
Conversely pre-batching the rectangles to draw that represented a word, at least to the point i was sure a word would fit... Is what id be looking to do for a actual Drawstring.
I think that drawing wrapped text is useful feature. Though I'd address mentioned garbage issue with different approach: I'd add class WrappedString that internally stores formatted text and draws it on demand.
It would look like following:
```c#
public class WrappedString
{
public int Width
{
get;
}
public int Height
{
get;
}
public string Text
{
get;
}
public void Draw(SpriteBatch spriteBatch, Point position, Color color);
/// <summary>
/// This method does all wrapping
/// </summary>
/// <param name="text"></param>
/// <param name="width"></param>
/// <param name="height">height is nullable as user might not want to have height constraint</param>
/// <returns></returns>
public static WrappedText Create(string text, int width, int? height);
}
```
Before i reply i want to point out that were really talking about the reformatting aspect.
Not just drawing out text in a method.
The problems for a actual DrawString method are much less severe in relation to garbage collections.
For that you just need a mutable buffer that hangs around privately or internally i.e. at class scope or static. That is reused so it can constantly buffer up how to draw what is passed, until it is drawn out.
You don't need to hold or keep any immutable data you are drawing and a mutable buffer is just held as a constantly reusable buffer as long as it's reference doesn't go out of application scope, it will not need to be collected. You also want a fast insert or some type of way to track were to insert as you go so its fast.
In consideration of a wrapped class for holding onto wrapped text.
A separate class is a option, for placing a mutable buffer into it and it looks nice. While this approach does encapsulate the idea of holding onto a buffer for re-use instead of returning a immutable string to the user.
Using a regular string internally as the buffer itself won't solve any issues with garbage collections, subsequent to its first use, as a string is immutable.
So some type of mutable buffer would be required for subsequent changes i.e. a stringbuilder or some mutable character array buffer in the class.
Wrapping the buffer up in a class is the proper way to go but... You could argue that stringbuilder is a wrapper and a buffer and you should just return that, removing a unnecessary step.
So the problem at hand for reformatting text and returning it to the user, is ...
1) The need to have a grow-able buffer that can be mutated. (_also a concern to a drawstring method_)
2) Making it clear that it needs to be held on to, so it will not go out of scope, so it is not collected.
3) The speed of inserting line-breaks. (_also a concern to a drawstring's algorithm that simulates this at runtime_)
4) Not generating collected garbage when inserting a line-break which unfortunately the commonly used character buffer for c# StringBuilder's Insert() method seems to .... always do.
There are also problems for mutable containers under c#. Currently even stringbuilders insert makes garbage that gets collected on stuff it shouldn't its just a garbage collection monster and it is requisite for reformatting that you have a insert.
I get around that atm in a terribly inefficient way by using the below function in my stringbuilder wrapper.
Since it only fires for one word per line i can still get away with it running in real time.
But there is no doubt that it's Not a fast way to insert line break characters.
/// <summary>
/// Functions as a insert, existing text will be moved over. Notes are left in this method overload.
/// </summary>
public void AppendAt(int index, String s)
{
// Ensure that the Capacity of the stingbuilder is increased if needed.
int reqcapacity = (index + 1 + s.Length) - this.StringBuilder.Capacity;
if (reqcapacity > 0)
this.StringBuilder.Capacity += reqcapacity + 32;
// Ensure that the Length of the stingbuilder is increased to mark the end.
int initialLength = StringBuilder.Length;
StringBuilder.Length = initialLength + s.Length;
// Now we will wind from back to front the current characters in the stringbuilder to make room for this append.
// Yes this will be a expensive operation however this must be done if we want a proper AppendAt.
// Chunks or no chunks stringbuilders insert is piss poor.
int insertedsbLength = s.Length;
for (int j = StringBuilder.Length - 1; j >= index + insertedsbLength; j--)
StringBuilder[j] = StringBuilder[j - insertedsbLength];
// perform the append
for (int i = 0; i < insertedsbLength; i++)
{
this.StringBuilder[index + i] = s[i];
}
}
Rules of thumb here...
_Taking a string or mutable from the user in a method is ok. but...
Mutating a immutable is going to be a collection.
Copying it to a mutable will make more garbage but that is not the same as a collection.
If the mutable buffer goes out of scope it will be collected.
Returning a immutable to the user is just asking for a collection later.
Holding a created immutable as a buffer is just asking for someone to call a function on it that mutates it and of course creates a garbage collection (so might as well just return a altered string in that case).
Passing a immutable to a function that doesn't directly alter it or just copys it is fine as it is passed by a value reference.
Copying to a buffer in a function means if the buffer goes out of scope on return that buffer will become a collection.
Copying a immutable to a mutable then working on and or returning the mutable is the rule in all cases. Wth the exception that you would not ever want to return a drawstrings internal mutable._
Now ... for a sprite batch that directly draws out word wrapped text you would have to buffer up a the values that are passed to item.Set(...) until you were sure that a word fits on the line.
That would be done in this segment of the logic with a couple extra lines and would then dump those values to the item.Set(,,,) call in a small loop for the word.
if (c == ' ')
{
lastWordBreakCharPos = i;
rewindOffset = offset;
if (firstwordonline)
firstwordonline = false;
}
In the case were it did not...
The saved buffered values for the current word would get cleared.
The index would rewind.
The offset would be moved to the beginning of the first line and and the algorithm would continue buffering.
Keeping in mind of course the /n would be getting replaced by offset changes instead.
var wordStartIndex = lastWordBreakCharPos + 1;
if (dest.Right > boundRect.Width && text.Length > (i + 1))
{
if (firstwordonline == false && text[wordStartIndex ] != '\n')
{
text.AppendAt(wordStartIndex , '\n');
if (text[wordStartIndex ] != ' ')
{
offset = rewindOffset;
i = lastWordBreakCharPos;
}
}
else
{
if (text[i + 1] != '\n')
text.AppendAt(i + 1, '\n');
}
}
You could alternately dump the entire current buffered line up till now at this point before clearing.
If you also marked the ending position of the last valid word that fit on the line.
For reference / comparison, here is my own...
https://stackoverflow.com/a/25337147/168235
Most helpful comment
I would be curious to see the code, because manipulating strings/stringbuilders is likely to generate garbage. Which is something we should avoid.
You don't have to reformat a string or use MeasureString, you only have to play with the offset within DrawString to simulate a
\ncharacter when the next word offset is out of the bound of the rectangle.I like the proposition though. It's typically the kind of thing that we end up writing anyway when making a game.