SpriteFontPlus

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

Hello.
I’m not sure if the thread is still alive but I’m having problems to implement this feature.
I think I{m following all the instructions correctly.

First

TtfFontBakerResult generator = TtfFontBaker.Bake(File.ReadAllBytes(path), 25, 1024, 1024, characters);

And then

SpriteFont font = generator.CreateSpriteFont(device);

But when I try to draw any text, I get no results on the screen.
I used the SpriteFont.MeasureString method to see if something was happening there and I got a really high size, 2220x1024 pixels.

I noticed that the last version listed on github is 0.85 and the lastest version available at NuGet I think is 0.72.
I´’m not sure if I’m using the wrong version.

Sorry if I’m not providing any more details, but I’m not even sure how to debug this problem.
Everything seems to be working fine.

The discord for both spritefontplus and fontstash is here in the myra server.

you might try fontstash im not sure which one of these are newer.