SpriteFontPlus

SpriteFontPlus is library that extends functionality of the SpriteFont. For now it has only one feature - ability to create SpiteFont dynamically from ttf.

It could be used if one doesnt know exactly what character ranges are going to be used(it happens in games that use Asian languages).

It’s worth to note that SpriteFontPlus produces quite different result than stock MonoGame pipeline font baker.
I.e. this is MG:

And this is SpriteFontPlus:

SpriteFontPlus Project Site: https://github.com/rds1983/SpriteFontPlus
I would be grateful for any feedback.

4 Likes

I have a high level of interest in this. As well as i known that others do, as this is something that has been talked about being added to mono game.

I have some questions.

1)
Can this import multibyte unicode characters that equate to the loaded glyphs data ?

To say does it represent glyphs with the equivalent possibly 2 character surrogate strings or utf32 character codes that might represent a surrogate pair unicode glyph.
For example c# uses utf16 be to represent characters and can read high and low surrogates to represent them for a total of 32bits. However are current spritefont only reads in and represents glyphs with only a single 16bit character in its glyph dictionary meaning that the range of characters can only represent up to \u+FFFF while this is still a large range and even allows some emojis Hot Beverage :coffee: :coffee: U+2615. It omits much of what c# itself can actually store in a string and represent as a single glyph.
:speech_balloon: 0xD83D 0xDCAC > &#128172; // :speech_balloon: < can be pasted into visual studio in a string.
However spritefonts glyph dictionary only stores the character as a c# character not as a string so you would end up with 0xD83D only https://www.fileformat.info/info/unicode/char/d83d/index.htm

2)
How does it handle point size ?

3)
Is that being drawn with SpriteBatch or with your own routine?

In your second image the one with spritefont plus it appears that the text is not being kerned. There is space between many letters that appears to be a bit to much such as between the q u in quick and b r o in brown.

1, 2)
Frankly I dont know answers to those questions. As SpriteFontPlus uses port of stb_truetype internally and I have poor knowledge about stb_truetype capabilities.
Basically I did two things: ported stb_truetype to C# and wrote code that uses it to bake font into SpriteFont:
https://github.com/rds1983/SpriteFontPlus/blob/master/src/SpriteFontPlus/TtfFontBaker.cs

It creates SpriteFont and renders it with ordinary SpriteBatch.DrawString.
There could be bug with kernings, yes.

It appears to not take into account high or low surrogates.
var glyphs = new Dictionary<char, GlyphInfo>();
this would need to be of the type.

var glyphs = new Dictionary<string, GlyphInfo>();

or

Preferably read into a string or char pair out of the font, then converted with char.ConvertToUtf32(char high, char low); To be also stored in a glyph dictionary of the <int, GlyphInfo> type the characters should be stored in the ttf i think in high low order.

var glyphs = new Dictionary<int, GlyphInfo>();

I suppose that it is necessary if it will be used with the current spritebatch.

It would be great however to have a option to create a glyph with a string or uft32 equivalent of a glyphs high low surrogate pair. As well as the dictionary look up for it. Rewriting spritebatch at that point is no more then a couple lines of change and would allow access to all the higher unicode characters in a font including emojis naturally.

1 Like

Thanks for the feedback. I’ve created a few issues based on it: https://github.com/rds1983/SpriteFontPlus/issues

Unfortunately, MonoGame SpriteFont doesnt support utf-32. As it accepts List<char> in constructor:

However eventually SpriteFontPlus will gain its own font class, which will support utf-32.

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. :muscle: :page_with_curl: :pushpin:

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];

@rds1983 In case you’re interested in adding UTF16 4-byte glyph support to MonoGame, I have a branch that has most work done. The thing to fix is importing characters from SpriteFontDescription: https://github.com/Jjagg/MonoGame/tree/unicode

I skimmed over it breifly jjag i just didn’t see what needed to be done on that end i have no idea were to even look for the font description. I really have avoided the content importing classes for the most part so im not familiar at all with it.

Though i have been messing with his font plus. Making lots of changes…

Edit ok i have a rough draft alteration that pulls in full unicode support i tested this with the Symbolas font which supports most of the higher range unicode blocks. Here i pulled in the blocks shown under the image.

            var fontBakeResultUnicode = TtfFontBaker.BakeUnicode(File.ReadAllBytes("Fonts/Symbola.ttf"),
                25,
                FontBitmapWidth,
                FontBitmapHeight,
                new[]
                {
                    CharacterRange2.BasicLatin,
                    CharacterRange2.Latin1Supplement,
                    CharacterRange2.LatinExtendedA,
                    CharacterRange2.Cyrillic,
                    CharacterRange2.Dingbats,
                    CharacterRange2.MiscellaneousSymbolsAndPictographs
                }
            );

Ill post back the alterations i made to your project as soon as i clean it up at least a little bit.
It’s basically using the spritebatch mockup i posted before to draw.

Remarks…
Overall i think this pulls in and renders the glyphs better then the current importer spritefont uses.
Expanded they look really good and don’t have the bad alaising and transparency artifacts the current importer is producing on load.
The ability to load the ttf’s directly is really nice.
It also opens up the possibility of dynamically loading system fonts to pull in just a needed character or character block.
Which also leads me back to the idea that maybe a spritefont that holds multiple texture pages to cover a larger range of images per font and a drawstring that can use it, would be good.

1 Like

It also opens up the possibility of dynamically loading system fonts to pull in just a needed character or character block.

It’s already implemented in, say, this project: GitHub - memononen/fontstash: Light-weight online font texture atlas builder
It could be ported to C# and added to the SpriteFontPlus.

Well my little render here is just a hack in and its a little messed up but i did get the unicode working.

To tired to post a pic i zipped up the whole solution just about everything i added is marked Unicode in the classes or methods. Its mostly just alternates of your current classes. I also added probably about three quarters of all the the en character regions with some comment descriptions for intellisense. Mostly skipped the individual language regions early on.

SpriteFontPlus 0.5.5 is out.
I’ve ported fontstash to C# and integrated it into the SpriteFontPlus.
Which resulted in the new feature - DynamicSpriteFont:

SpriteFontPlus 0.6 is out.
Now DynamicSpriteFont supports utf-16:

2 Likes

SpriteFontPlus 0.6.4 is out.
List of changes:

  • Updated StbTrueTypeSharp to version 1.21
  • New Feature: Added ability to create SpriteFont from AngelCode BMFont(only XML with single texture is supported for now).
1 Like

SpriteFontPlus 0.7.0 is out.
Now DynamicSpriteFont supports multiple backing textures. When the current texture is full, new one is created.

Project Site: https://github.com/rds1983/SpriteFontPlus

3 Likes

Why don’t you dynamically scale the textures (maybe up to some upper limit)? Saves the texture switches when rendering.

1 Like

There’s obvious issue with such approach: maximum texture size is constrained by the hardware capabilities. Thus overflow exception will be thrown eventually. I want to avoid that.

It is worth to mention that while I’ve used small texture size(256x256) in the above animated gif to demonstrate the new feature. In reality a user can specify any texture size when creating the DynamicSpriteFont(i.e. 2048x2048).

Moreover there’s a special event(CurrentTextureFull) that is fired when the texture is full. A user might use it to reset the DynamicSpriteFont thus keeping it with only one texture(though glyphs would have to be rendered again).

SpriteFontPlus 0.7.5 is out: https://github.com/rds1983/SpriteFontPlus/releases/tag/0.7.5.26

2 Likes

SpriteFontPlus 0.8.0 is out: https://github.com/rds1983/SpriteFontPlus/releases/tag/0.8.0.36

2 Likes

SpriteFontPlus 0.8.5 is there: https://github.com/rds1983/SpriteFontPlus/releases/tag/0.8.5.43
It has one really breaking change.

2 Likes