Measure and Draw SpriteFont Character without any space

Hi,

I want to draw a single character to a specific position.
But if I draw that character to a position, it already contains some margin space. It is not aligned to the position.
The measurement of the character, which I got through the MeasureString-method, also gaves me a larger
measurement with some space.

What I need is a way to get the real character size without any space or margin.
I’ve tried it with and without kerning but it doesn’t solve the problem. The spacing value of my spritefont is 0.
I already looked into the SpriteFont.Glyph Structure but it only gave me some information about left and right margin. Did I miss anything?

Thanks for your help.
Regards
Ronny

Nobody has an answer for that?

I’ve had this issue as well, and would also be interested in additional information/solutions. In the past I’ve just compensated for the “extra padding” by adding a fixed amount to the height measurements that I use to place the text.

I want to draw a single character to a specific position.

You can get the glyphs for a spritefont by copying out the glyph dictionary then use regular draw.
spriteBatch.Draw(myfont.Texture, position , Dict[letter].Glyph.BoundsInTexture…ect… , … ect…
A spritefont is really just a souped up spritesheet.

What I need is a way to get the real character size without any space or margin.

Im not sure if i fully understand what is meant by the real character size here without space or margin.

For the character size each character has a glyph which has a Bounds Rectangle this is a raw rectangle to the sprite in the fonts spritesheet texture for that character that includes its raw width and height rectangle.

This however doesn’t tell you how to line up the characters or space them if that character is in the middle of a string all the other values do including all the previous characters cumulatively.
Spaces for instance just move the offset with the right side bering as they don’t need to actually draw anything for a blank space. They just affect were the next letter is drawn. The left and right side bering can alter were a character is drawn as well as the croping values.

The position of a character is based on the position that is passed to drawstring as if that is a origin and the current offset add to it. The offset itself is only affected by the left and right sidebering and the characters width and padding.
It is calculated in steps in a loop.
Each characters left side bering is added to the offset at the start then this is copied to a temp value were the glyphs cropping are added and that determines the destination rectangle for the character.
However the offset varys it is dependent on the last characters glyph width and right side bering as well which is added to the offset after the destination has been calculated for the next character and the cropping is not actually added to that offset.

in the below you will find and notice the following lines which are very important.

The newline code block at
636
offset.X = 0; // each newline resets the x offset
offset.Y += spriteFont.LineSpacing; // and moves the y offset down

657 offset.X += spriteFont.Spacing + pCurrentGlyph->LeftSideBearing;

660 var p = offset; // this demarks a temporary variable copied from the offset to be used

// this adjust the offset for the next character such as when the next character will be after a space character.
683 offset.X += pCurrentGlyph->Width + pCurrentGlyph->RightSideBearing;

1 Like

The issue here, assuming @ronnycsharp has been dealing with the same issue I have, is that MeasureString gives seemingly off values for certain fonts. In the example below, you can see that MeasureString has given a height that is quite a bit taller than the rendered text. That is, the actual text doesn’t appear for about 5-10 pixels down from the set position to draw it. I presume this is a misunderstanding about how it’s measuring characters in certain fonts, or that the font itself is malformed, because I’ve only observed this with specific ones. It does cause some issue though. i.e. I was writing a menu system and wanted to center some text in a padded box, similar to how it’s done in this example code, but had to manually add extra padding to the bottom to get the actual rendered text to appear centered.

Result:

Code:

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;


namespace DrawString
{
	public class Game1 : Game
	{
		GraphicsDeviceManager graphics;
		SpriteBatch spriteBatch;
		SpriteFont font;
		Texture2D pixelTexture;
		string msg = "Testing";
		Vector2 msgSize;
		Vector2 msgPos = new Vector2(20, 20);
		Vector2 msgPos2 = new Vector2(20, 100);
		Vector2 msgPadding = new Vector2(10, 5);
		Rectangle noPadExampleBack, paddedExampleBack;


		public Game1()
		{
			graphics = new GraphicsDeviceManager(this);
			Content.RootDirectory = "Content";
		}


		protected override void LoadContent()
		{
			spriteBatch = new SpriteBatch(GraphicsDevice);
			font = Content.Load<SpriteFont>("40_reg");

			pixelTexture = new Texture2D(graphics.GraphicsDevice, 1, 1);
			pixelTexture.SetData(new Color[] { Color.White });

			msgSize = font.MeasureString(msg);
			noPadExampleBack = new Rectangle(
				x: (int)Math.Round(msgPos.X),
				y: (int)Math.Round(msgPos.Y),
				width: (int)Math.Round(msgSize.X),
				height: (int)Math.Round(msgSize.Y)
			);
			paddedExampleBack = new Rectangle(
				x: (int)Math.Round(msgPos2.X),
				y: (int)Math.Round(msgPos2.Y),
				width: (int)Math.Round(msgSize.X + msgPadding.X * 2),
				height: (int)Math.Round(msgSize.Y + msgPadding.Y * 2)
			);
		}


		protected override void Draw(GameTime gameTime)
		{
			GraphicsDevice.Clear(Color.Black);

			spriteBatch.Begin();

			// no padding example
			spriteBatch.Draw(pixelTexture, noPadExampleBack, Color.Blue);
			spriteBatch.DrawString(font, msg, msgPos, Color.White);

			// padded example
			spriteBatch.Draw(pixelTexture, paddedExampleBack, Color.Blue);
			spriteBatch.DrawString(font, msg, msgPos2 + msgPadding, Color.White);

			spriteBatch.End();

			base.Draw(gameTime);
		}
	}
}

AFAIK the problem with MeasureString is that it doesn’t really “measure” the string. If your fontsize is e.g. 10 then “Testing”(7 letters) is (70,10) in size. So MeasureString really is only useful for monospace fonts

Im not following it seems to handle kerning fine as far as i can tell.

Other then it is taking cropping into account for the y size which may not be what is expected.

the red outline represents the measured string returned width and height in relation to the text position for the tahoma font here with kerning on.

string hellomsg = "hello This is some"+"\n"+"_measured Texttiii";
Vector2 hellomsgpos = new Vector2(200, 400);
Vector2 hellomsgsize = new Vector2(0, 0);

hellomsgsize = Gu.currentFont.MeasureString(hellomsg);

Gu.spriteBatch.DrawString(Gu.currentFont,hellomsg , hellomsgpos, Color.White);
            Gu.DrawSquareBorder(new Rectangle(hellomsgpos.ToPoint(), hellomsgsize.ToPoint() ), 1, Color.Red);

That said.
You can which is what i normally do make your own measure string using the spritebatch.DrawString itself (mostly just copy paste it to your own method) you have to yank out the fonts data aka line height glyphs ect as stated previously.
You can check if the offset.X at the very end is larger then the saved largest amount in the loop if it is you save it.
As for the y at the end of the loop just set the largest y value to the offset.Y + the line Height, when the method returns return the saved largest x y in a point or vector.
I do it like that because i have my own drawstring like utility methods and they measure as they calculate bounds as i do a lot of string manipulation.

If you want ill make a generic measure string but its pretty straight forward.

1 Like

Thanks a lot for the help.
I solved it with the BoundsInTexture-Property from the SpriteFont.Glyph.
But it doesn’t work well in some cases outside A-Z.

Greetings
Ronny

    public static void DrawGlyph (
                            char c,
                            SpriteBatch batch,
                            Vector2 position,
                            Color color,
                            float scale,
                            Matrix transform) {

        var glyph           = Fonts.Glyphs [c];
        var measuredChar    = MeasureGlyph (c, scale);
        var offsetX         = -(glyph.Cropping.Width - glyph.Width) * scale / 2f;
        var offsetY         = -glyph.Cropping.Y * scale;
        var offset          = new Vector2 (offsetX, offsetY);

        batch.Begin (
            SpriteSortMode.Deferred,
            BlendState.AlphaBlend,
            SamplerState.AnisotropicClamp,
            DepthStencilState.None,
            ScissorEnabled,
            null,
            transform);
            
        batch.DrawString (
            Fonts.ModernMath,
            c.ToString (),
            position + offset,
            color,
            0, Vector2.Zero, scale, SpriteEffects.None, 0);

        batch.End ();
    }

    /// <summary>
    /// Measures a character without any space
    /// </summary>
    /// <returns>The glyph.</returns>
    /// <param name="c">C.</param>
    /// <param name="scale">Scale.</param>
    public static Vector2 MeasureGlyph (char c, float scale) {
        var glyph = Fonts.Glyphs [c];
        return new Vector2 (
            glyph.Width,
            glyph.BoundsInTexture.Height + 2) * scale;  // TODO Why we need the margin here? Perhaps through SpriteFont-Spacing
    }