3

I have one long string and I want to split it in a few pieces so that every line of the text is always in the given rectangle. The text should not exceed the border of the rectangle.

The height of the rectangle isn't the problem. The text will never touch the bottom of the rectangle because the rectangle is very tall. But the rectangle isn't very wide.

How can I calculate which parts of the string should be drawn in each line? I don't want to split a word. If a word exceeds the border of the rectangle, then the word should be drawn in the next line.

For example, drawing the string should look like this:

Cyberpunk 2077 is an upcoming role-playing video game
developed and published by CD Projekt, releasing for
Google Stadia, Microsoft Windows, PlayStation 4, and
...

and not like that:

Cyberpunk 2077 is an upcoming role-playing video game devel
oped and published by CD Projekt, releasing for Google Stad
ia, Microsoft Windows, PlayStation 4, and Xbox One on 16
...

Right now, spriteBatch is drawing the full long string like that in just one line and the text exceeds the border of the rectangle. How can I split it up correctly in a few lines?

Cyberpunk 2077 is an upcoming role-playing video game developed and published by CD Projekt, releasing for Google Stadia, Microsoft Windows, PlayStation 4, and Xbox One on 16 April 2020. Adapted from the 1988 tabletop game Cyberpunk 2020, it is set fifty-seven years later in dystopian Night City, an open world with six distinct regions. In a first-person perspective, players assume the role of the customisable mercenary V, who can reach prominence in hacking, machinery, and combat. V has an arsenal of ranged weapons and options for melee combat.

I use spriteBatch.DrawString to draw the string:

string Text = "Cyberpunk 2077 is an upcoming role-playing video game developed and published by CD Projekt, releasing for Google Stadia, Microsoft Windows, PlayStation 4, and Xbox One on 16 April 2020. Adapted from the 1988 tabletop game Cyberpunk 2020, it is set fifty-seven years later in dystopian Night City, an open world with six distinct regions. In a first-person perspective, players assume the role of the customisable mercenary V, who can reach prominence in hacking, machinery, and combat. V has an arsenal of ranged weapons and options for melee combat.";

protected override void Draw(GameTime gameTime)
{                
    graphics.GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.CornflowerBlue);
    spriteBatch.Begin();
    spriteBatch.DrawString(Font, Text, new Vector2(200, 300), Microsoft.Xna.Framework.Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 0f);
    spriteBatch.End();

    base.Draw(gameTime);
}

UPDATE: You need to add this line of code at the end:

yield return buffer;

    string[] lines = Split(Text, 400, Font).ToArray();

    public static IEnumerable<string> Split(string text, double rectangleWidth, SpriteFont font)
    {
        var words = text.Split(' ');
        string buffer = string.Empty;

        foreach (var word in words)
        {
            var newBuffer = buffer + " " + word;
            if (word == words[0])
              newBuffer = word;
            else
              newBuffer = buffer + " " + word;

            Vector2 FontMeasurements = font.MeasureString(newBuffer);

            if (FontMeasurements.X >= rectangleWidth)
            {
                yield return buffer;
                buffer = word;
            }
            else
            {
                buffer = newBuffer;
            }
        }
        yield return buffer;
    }

Drawing:

 for (int i = 0; i <= lines.Count() - 1; i++)
   spriteBatch.DrawString(Font, lines[i], new Vector2(300, 500 + i * 30), Microsoft.Xna.Framework.Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 0f);

or:

string boxedText = string.Join('\n', Split(Text, 400, Font));
spriteBatch.DrawString(Font, boxedText, new Vector2(300, 500), Microsoft.Xna.Framework.Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 0f);
Hobbit7
  • 313
  • 3
  • 16
  • 1
    Did you see [this](https://gamedev.stackexchange.com/questions/81177/how-do-i-measure-a-string-that-will-be-drawn-with-spritebatch) ? You would split into a list of words and add them up until the sum of their widths fills the line.. – TaW Aug 29 '19 at 18:25
  • Could you explain your solution in detail? Should I add all the words of the text one after another to a list? For example: WordsList.Add("Cyberpunk"); WordsList.Add("2077"); WordsList.Add("is"); ... And then creating a new string with the words of WordsList until the width of the new string almost touches the border(width) of the rectangle? – Hobbit7 Aug 29 '19 at 20:20
  • Yes, pretty much like the given answer. But putting them in a number of lists first and also keeping a list with the lengths could help adding in a few spaces to make a better block alignment. – TaW Aug 29 '19 at 20:23

1 Answers1

2

Assuming you have a Method Measure(string text) which returns the actual visual width of the text (not needed if you use a monospaced font), you could use this method to split the text into lines:

public static IEnumerable<string> Split(string text, double rectangleWidth)
{
    var words = text.Split(' ');
    string buffer = string.Empty;

    foreach (var word in words)
    {
       var newBuffer = buffer + " " + word;

       if (Measure(newBuffer) >= rectangleWidth)
       {
           yield return buffer;
           buffer = word;
       }
       else
       {
           buffer = newBuffer;
       }
    }

    yield return buffer;
}

To get the lines as an array, use string[] lines = Split(text, Rectangle.Width).ToArray().

If you want a single string separated by newlines, use string boxedText = string.Join('\n', Split(text, Rectangle.Width)).

In your case you would use it like this:

string Text = "Cyberpunk 2077 is an upcoming role-playing video game developed and published by CD Projekt, releasing for Google Stadia, Microsoft Windows, PlayStation 4, and Xbox One on 16 April 2020. Adapted from the 1988 tabletop game Cyberpunk 2020, it is set fifty-seven years later in dystopian Night City, an open world with six distinct regions. In a first-person perspective, players assume the role of the customisable mercenary V, who can reach prominence in hacking, machinery, and combat. V has an arsenal of ranged weapons and options for melee combat.";

protected override void Draw(GameTime gameTime)
{                
    graphics.GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.CornflowerBlue);
    spriteBatch.Begin();
    spriteBatch.DrawString(
        Font, 
        string.Join(' ', Split(Text, Rectangle.Width)), 
        new Vector2(200, 300), 
        Microsoft.Xna.Framework.Color.White, 
        0, 
        Vector2.Zero,
        1f,
        SpriteEffects.None,
        0f);
    spriteBatch.End();

    base.Draw(gameTime);
}
blenderfreaky
  • 738
  • 7
  • 26
  • 2
    A valid assumption. See my coment :-) – TaW Aug 29 '19 at 19:04
  • 1
    I don't understand how to use your code. How can I draw the text if use your code? – Hobbit7 Aug 29 '19 at 20:11
  • The last line of the long string is always missing. This part is missing: "for melee combat." How can I add the missing part to the array? See my question, I have updated it. – Hobbit7 Aug 29 '19 at 21:25
  • 1
    @blenderfreaky You need to add this line of code at the end of your code to fix the problem with the missing part of the long string: yield return buffer; – Hobbit7 Aug 30 '19 at 07:48