Sorry for the late reply i didn’t see this.
How would you take advance of a SpriteBatch that lets you batch primitives to draw text faster?
I have quite a few methods for cutting text and manipulating text on the fly. Including a couple extra classes that do a little more advanced manipulation on words or paragraphs and colors.
Most of these rely on being able to directly define vertice edges of the quads the colors of the quads, command text within the strings passed themselves avoiding MeasureString in the drawString command precomputing rotations on the fly ect.
For example in one method i call this in my own drawstring.
spriteBatch.Draw(tsf.Texture, dest, currentGlyph.BoundsInTexture, color, rotation, Vector2.Zero, SpriteEffects.None, depth);
If i am need of a overload that allows me to set the vertices directly.
Then i already know what the 2d representation of the quad is on screen 3 dimensionally.
All of the below bolded feilds are then redundant in the draw method but will be recomputed anyways.
spriteBatch.Draw(tsf.Texture, dest, currentGlyph.BoundsInTexture, color, rotation, Vector2.Zero, SpriteEffects.None, depth);
Further the dest and bounds will be recalculated to floats in that draw but i have just calculated the integers from floats to pass to that method. I also know the color per glyph, i may even know the color per vertice i want for the text to give off a cool cheap effect or highlight some words. I may want to simulate italicized text by drawing a quadrilateral quad instead of a directly square one ect.
Spritebatcher is very efficient at this point and its limited to squares that are all of the same color.
However this limitation is in fact completely redundant and not really neccessary, from its wrapper class Spritebatch’s and its Draw overloads this can actually be inefficient in some cases. It also is pointless reduced functionality there is no reason Draw should only take squares it is certainly not for performance.
I don’t have a way to efficiently bypass the extra junk in draw to basically give a quad to spriteBatcher which would be nice.
Anyways…
To keep on topic of the post i made this little class for cutting strings today.
I figured id post it to share or for posterity.
This just cuts down and returns a string or gives the index for a string for were it is to get cut off by a bounding rectangles width. This should work with scaling as well, its very simple it uses the same algorithm that DrawString itself uses though this might be a bit simplified.
I didn’t include cutting it off for the bounds height though its easy to add it yourself if you want.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
namespace Microsoft.Xna.Framework
{
public static class TextMeasure
{
private static SpriteFont tsf;
private static Dictionary<char, SpriteFont.Glyph> _glyphs;
private static SpriteFont.Glyph defaultGlyph;
private static char defaultfontchar = ' ';
public static void SetSpriteFont(SpriteFont s)
{
tsf = s;
_glyphs = tsf.GetGlyphs();
defaultGlyph = new SpriteFont.Glyph();
if (tsf.DefaultCharacter.HasValue)
{
defaultfontchar = (char)(tsf.DefaultCharacter.Value);
defaultGlyph = _glyphs[defaultfontchar];
}
}
public static string CutStringByBounds(string text, Rectangle boundRect)
{
var i = CutStringsEndingCharIndex(text, Vector2.One, boundRect);
return text.Substring(0, i);
}
public static string CutStringByBounds(SpriteFont sf, string text, Rectangle boundRect)
{
SetSpriteFont(sf);
var i = CutStringsEndingCharIndex(text, Vector2.One, boundRect);
return text.Substring(0, i);
}
public static string CutStringByBounds(SpriteFont sf, string text, Vector2 scale, Rectangle boundRect)
{
SetSpriteFont(sf);
var i = CutStringsEndingCharIndex(text, scale, boundRect);
return text.Substring(0, i);
}
public static string CutStringByBounds(string text, Vector2 scale, Rectangle boundRect)
{
var i = CutStringsEndingCharIndex(text, scale, boundRect);
return text.Substring(0, i);
}
public static int CutStringsEndingCharIndex(string text, Rectangle boundRect)
{
return CutStringsEndingCharIndex(text, Vector2.One, boundRect);
}
public static int CutStringsEndingCharIndex(SpriteFont sf, string text, Rectangle boundRect)
{
SetSpriteFont(sf);
return CutStringsEndingCharIndex(text, Vector2.One, boundRect);
}
public static int CutStringsEndingCharIndex(SpriteFont sf, string text, Vector2 scale, Rectangle boundRect)
{
SetSpriteFont(sf);
return CutStringsEndingCharIndex(text, scale, boundRect);
}
public static int CutStringsEndingCharIndex(string text, Vector2 scale, Rectangle boundRect)
{
var lineHeight = (float)tsf.LineSpacing;
var Spacing = tsf.Spacing;
Vector2 offset = Vector2.Zero;
Rectangle dest = new Rectangle();
var currentGlyph = SpriteFont.Glyph.Empty;
var firstGlyphOfLine = true;
int result = 0;
for (var i = 0; i < text.Length; i++)
{
var c = text[i];
if (c == '\r')
continue;
if (c == '\n')
{
offset.X = 0;
offset.Y += lineHeight;
firstGlyphOfLine = true;
continue;
}
if (_glyphs.ContainsKey(c))
currentGlyph = _glyphs[c];
else
if (!tsf.DefaultCharacter.HasValue)
throw new ArgumentException("Text Contains a Unresolvable Character");
else
currentGlyph = defaultGlyph;
// Solves the problem- the first character on a line with a negative left side bearing.
if (firstGlyphOfLine)
{
offset.X = Math.Max(currentGlyph.LeftSideBearing, 0);
firstGlyphOfLine = false;
}
else
offset.X += Spacing + currentGlyph.LeftSideBearing;
// matrix calculations unrolled removed un-needed here
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)
);
if (dest.Right < boundRect.Width)
{
result = i + 1;
}
else
{
return result;
}
offset.X += currentGlyph.Width + currentGlyph.RightSideBearing;
}
return result;
}
}
}
Its used like so…
text = TextMeasure.CutStringByBounds(font, text, textBoundingRectangle);