I made a test project that while it of course lacks a actual spritefont with a int glyph dictionary to read from.
It has a two classes that might be of interest everything of real relevance is in game1.
One that shows a regular font just being rendered with draw instead of drawstring to say its a striped down simplistic version of spritebatch its a buffer without the batching it will draw a stringbuilders text with spritefont or your spritefont you generated exactly like drawstring with kerning ect…
The other class is nearly the exact same except i dropped in some notes and code.
That shows how you would convert the previous class to read a spritefont with a dictionary of utf32 converted <int, glyphs> and then draw them if it of had one which would be on your end.
The classes are very small compared to the whole of spritebatch and are basically buffered simplistic versions of spritebatch e.g. you set the text and just keep calling draw on it over and over its really for a specific use but its good enough to demonstrate whats missing and the same changes applied to that second class can be applied directly to the actual monogame spritebatch.
I.E. if you manage to make a spritefont with the int glyph layout were the int represents the surrogate pair glyphs. Then you nearly have a working test draw function in that second class, i say nearly cause of course i cant test it to make sure i didn’t muck anything up.
I put the unifont in there but it doesn’t actually hold any double char surrogates i think even if it is huge.
You would have to find a font that does i think some of the microsoft fonts actually do.
basically this would have to change…
public void TextToDraw(SpriteFont sf, StringBuilder text, Rectangle bounds, Vector2 scale, Vector2 scroll, Vector2 rotationOrigin, bool usebounds)
{
if (sf != currentFont)
SetFont(sf);
var letterWidthSpacing = currentFont.Spacing * scale.X;
var lineHeightSpacing = currentFont.LineSpacing * scale.Y;
Vector2 offset = Vector2.Zero;
Vector2 redrawOffset = Vector2.Zero;
Rectangle dest = new Rectangle();
var currentGlyph = SpriteFont.Glyph.Empty;
var firstGlyphOfLine = true;
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;
firstGlyphOfLine = true;
continue;
}
if (glyphs.ContainsKey(c))
currentGlyph = glyphs[c];
else
{
if (currentFont.DefaultCharacter.HasValue)
currentGlyph = defaultGlyph;
else
throw new ArgumentException("Text Contains a Unresolvable Character");
}
if (firstGlyphOfLine)
{
// Removes left side bearing for the first character on a line
offset.X = Math.Max(currentGlyph.LeftSideBearing * scale.X, 0);
firstGlyphOfLine = false;
}
else
offset.X += letterWidthSpacing * scale.X + currentGlyph.LeftSideBearing * scale.X;
// Matrix calculations unrolled. Only transformation and scaling occurs here the world matrix is set to handle all rotations proper.
// This is a little test to see what ill later need to do if i want to actually buffer and batch like this. There can be some upsides to this and down as well.
var m = offset;
m.X += currentGlyph.Cropping.X * scale.X;
m.Y += currentGlyph.Cropping.Y * scale.Y;
dest = new Rectangle
(
(int)(m.X - scroll.X),
(int)(m.Y - scroll.Y),
(int)(currentGlyph.BoundsInTexture.Width * scale.X),
(int)(currentGlyph.BoundsInTexture.Height * scale.Y)
);
if (usebounds == false)
{
dest.Location += bounds.Location + scroll.ToPoint();
// rotation origin is passed and handled within if desired
buffer.AddCharacter(dest, currentGlyph.BoundsInTexture, Color.White);
}
else
if (dest.X > 0 && dest.Y > 0 && dest.Right < bounds.Right && dest.Bottom < bounds.Bottom)
{
dest.Location += bounds.Location + scroll.ToPoint();
// rotation origin is passed and handled within if desired
buffer.AddCharacter(dest, currentGlyph.BoundsInTexture, Color.White);
}
offset.X += (currentGlyph.Width + currentGlyph.RightSideBearing) * scale.X;
}
}
To be something like this to read a full unicode range string 0x FFFF FFFF. A dynamic font that stored a index to proper glyph character data that the ttf stored as surrogate pairs would also allow emoji’s across the full range that a c# string can support.
public void UnicodeTextToDraw(SpriteFont sf, StringBuilder text, Rectangle bounds, Vector2 scale, Vector2 scroll, Vector2 rotationOrigin, bool usebounds)
{
if (sf != currentFont)
SetFont(sf);
var letterWidthSpacing = currentFont.Spacing * scale.X;
var lineHeightSpacing = currentFont.LineSpacing * scale.Y;
Vector2 offset = Vector2.Zero;
Vector2 redrawOffset = Vector2.Zero;
Rectangle dest = new Rectangle();
var currentGlyph = SpriteFont.Glyph.Empty;
var firstGlyphOfLine = true;
if (text.Length > 0 && char.IsHighSurrogate(text[text.Length - 1]) )
throw new Exception("improper string format the last character in the string is a defind as a high surrogate pair but there is no following character"); // probably will never happen.
for (int i = 0; i < text.Length; i++)
{
char cs = text[i];
int charval = 0;
if (char.IsHighSurrogate(cs))
charval = char.ConvertToUtf32(cs, text[i + 1]);
else
charval = cs;
if (charval == '\r')
continue;
if (charval == '\n')
{
offset.X = 0;
offset.Y += lineHeightSpacing;
firstGlyphOfLine = true;
continue;
}
// in this way the glyphs can be indexed in a new type of spritefont that has loaded the ttf characters correctly. With a proper <int, glyph> dictionary to reference the glphys from.
if (glyphs.ContainsKey(charval))
currentGlyph = glyphs[charval];
else
{
if (currentFont.DefaultCharacter.HasValue)
currentGlyph = defaultGlyph;
else
throw new ArgumentException("Text Contains a Unresolvable Character");
}
if (firstGlyphOfLine)
{
// Removes left side bearing for the first character on a line
offset.X = Math.Max(currentGlyph.LeftSideBearing * scale.X, 0);
firstGlyphOfLine = false;
}
else
offset.X += letterWidthSpacing * scale.X + currentGlyph.LeftSideBearing * scale.X;
// Matrix calculations unrolled. Only transformation and scaling occurs here the world matrix is set to handle all rotations proper.
// This is a little test to see what ill later need to do if i want to actually buffer and batch like this. There can be some upsides to this and down as well.
var m = offset;
m.X += currentGlyph.Cropping.X * scale.X;
m.Y += currentGlyph.Cropping.Y * scale.Y;
dest = new Rectangle
(
(int)(m.X - scroll.X),
(int)(m.Y - scroll.Y),
(int)(currentGlyph.BoundsInTexture.Width * scale.X),
(int)(currentGlyph.BoundsInTexture.Height * scale.Y)
);
if (usebounds == false)
{
dest.Location += bounds.Location + scroll.ToPoint();
buffer.AddCharacter(dest, currentGlyph.BoundsInTexture, Color.White);
}
else
if (dest.X > 0 && dest.Y > 0 && dest.Right < bounds.Right && dest.Bottom < bounds.Bottom)
{
dest.Location += bounds.Location + scroll.ToPoint();
buffer.AddCharacter(dest, currentGlyph.BoundsInTexture, Color.White);
}
offset.X += (currentGlyph.Width + currentGlyph.RightSideBearing) * scale.X;
}
}
As shown this is the primary change that spritebatch needs but we don’t have a spritefont type at the moment that is available to enable it.
if (glyphs.ContainsKey(charval))
currentGlyph = glyphs[charval];