Account for character width of 0 when drawing individual characters

I’ve written my own extension method to draw characters from a font individually with SpriteBatch, which uses most of MonoGame’s own implementation. I did this is for a flexible dialogue system in one of my projects.

I’m now using this method in another project, but I noticed there’s an error with scaling a character that has a glyph with 0 width, such as a space. Since the width is 0, the space this character occupies is the same with all scales. I couldn’t find how MonoGame handles this in their method, so I suspect the spacing at different scales is happening naturally when rendering the characters. Is there a way I can handle this in my method?

I just so happen to be working on my own altered drawstring methods.

Edit

I thought that might of been a bug in the framework but you’re alteration is a little off.
It’s you’re right side bearing calculation i think. Ill dig out one of my old full methods and post it as well.

Breifly though.

Dont pre scale the spacing in the offset itself.
Pre-scaling makes it harder to fit in rotation later efficiently if you plan to add that.
If you are really dead set on doing so you need to also scale the left side bearing here.

       offset.X += (font.Spacing * finalScale.X) + glyph.LeftSideBearing;

All of p needs to be scaled not just the cropping p is the temporary position which is also the current offset position. Unless you prescale everything. Cropping can’t be added to the offset though so you cant concatenate it and scale so you should probably revise this.

     Vector2 p = offset;
     p.X += glyph.Cropping.X * finalScale.X;
     p.Y += glyph.Cropping.Y * finalScale.Y;

It is also requisite to scale the width and height of the destination bounds,
That appears to be missing in your method but it will visibly screw things up if you dont.
see the example i posted at the end.

     (int)(currentGlyph.BoundsInTexture.Width * scale.X),
     (int)(currentGlyph.BoundsInTexture.Height * scale.Y)

This is improper without the whole of the offset being scaled and right side bearing is added at the end so if you insisted on scaling all of it before hand then this has to be scaled and added to the offset at the end basically. I favor post scaling i think its easier.
If you wanted to prescale though then this.

    offset.X += (glyph.Width * finalScale.X) + glyph.RightSideBearing;

Would be the below …

Which is were you could band aid fix your problem atm as the “space” character only has a right side bearing typically…

    offset.X += (glyph.Width + glyph.RightSideBearing) * finalScale.X;

Beware in general i don’t think your version as is will match spritebatch and you will need to fix it later anyways when you start to notice its not right.

Edit…
Eh i think most of my old full methods are well i dunno were i put them. The only full ones i can find are super raw to were i bypass everything even the matrixs and blit right into the viewport which are probably not gonna help.

Here is a striped down method from a current function that should help i got rid of all the extra stuff as im doing basically a bunch of complicated crap right now and most of it will prolly get thrown away by the time im done.

I added some comments.

        public static void MinimalDrawStringFunction(MgStringBuilder text, Vector2 scale)
        {
            var letterWidthSpacing = tsf.Spacing;
            var lineHeightSpacing = tsf.LineSpacing; // * scale.Y; // might of been testing a precomputation get rid of that scale here.
            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 (tsf.DefaultCharacter.HasValue)
                        currentGlyph = defaultGlyph;
                    else
                        throw new ArgumentException("Text Contains a Unresolvable Character");
                }

                if (firstGlyphOfLine)
                {
                    // Ensures offset.X starts at zero unless the left side bearing is positive.
                    offset.X = Math.Max(currentGlyph.LeftSideBearing, 0);
                    firstGlyphOfLine = false;
                }
                else
                    offset.X += letterWidthSpacing + currentGlyph.LeftSideBearing;

                // matrix calculations unrolled rotation is excluded for this version

                // Note this line below.
                // I did it this way so that offset is unchanged by any transformations.
                // Cropping is not something that is a saved translation so its done in the temporary calculation.
                var m = offset;

                // Offset from here to nearly the end is not altered.     
                // m is a throw away variable.            
                //_____________________________________________________

                m.X += currentGlyph.Cropping.X;
                m.Y += currentGlyph.Cropping.Y;

                // once the temporary translation has been calculated here you can then just scale the whole thing.
                // you can even do this with rotation but in a bit more of a roundabout way.

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

                //_____________________________________________________

                offset.X += currentGlyph.Width + currentGlyph.RightSideBearing;
            }
        }

You can of course just translate it to any position you want as you are doing already.
The source rectangle is of course the bounds in texture i didn’t show drawing these out.
You could spriteBatch.Draw in the function to see it work and compare it to a regular spritebatch drawn underneath it. This handles the kerning just like spritebatch.

I just wanted to update this, as I ended up fixing it. I tested with various scales, and the output is now identical to SpriteBatch.DrawString.