Using SpriteFont to draw text at an exact position and pixel height?

I’m trying to draw text using SpriteBatch.DrawString but having trouble understanding how the position and the height (in pixels, not points) of the text is being determined.

I made a SpriteFont file from the Arial font family:

<?xml version="1.0" encoding="utf-8"?>
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
  <Asset Type="Graphics:FontDescription">
    <FontName>Arial</FontName>
    <Size>72</Size>
    <Spacing>0</Spacing>
    <UseKerning>true</UseKerning>
    <Style>Regular</Style>
    <DefaultCharacter>*</DefaultCharacter>
    <CharacterRegions>
      <CharacterRegion>
        <Start>&#32;</Start>
        <End>&#126;</End>
      </CharacterRegion>
    </CharacterRegions>
  </Asset>
</XnaContent>

Then used SpriteBatch to draw it with no scaling at Position=Vector2.Zero and got this result:

arial 72pt

My first confusion is why the top of the text isn’t at Y=0. You probably can’t see it in the image but I drew a little yellow pixel at 0,0 to compare with. It seems like the top of the text is around Y=17 or so.

My second confusion is the height of the text. In the SpriteFont file, I specified the FontSize as 72. My internet searches tell me that to convert between points and pixels, you multiply the points by 1.33333. So I expected the height from the top of the A, to the bottom of the J to be roughly 72*1.3333=96px. But instead it looks like it’s about 90px? (not counting the 17 or so empty pixels from Y=0 to the top of the ‘A’. Also, SpriteFont.MeasureString(“Aj”) is returning (87, 110). How is it getting 110px from 72pt font? If I change the SpriteFont to ‘Segoe UI’, measuring the same string at the same FontSize is giving me (85, 128). So it seems like the measurements aren’t just dependent on the FontSize?

How can I render text such that the top of the text is exactly at the Y-coordinate I specify for the Position parameter, and the height of the text is exactly a desired height in pixels, and MeasureString gives me the same height value?

It’s basically a BitmapFont generated from a Font File. That means, every letter of the font is rendered onto a texture and this texture is then used as an atlas to draw several quads per letter. In the exat size that letter has in Pixels in that texture. I can’t tell rn how the basic SpriteFont Generator is handling this. I personally switched to a different one with more options long ago. It may be easier to work with others which give more options. (I recently switched to FontStashSharp for several reasons)

There is no such thing as “pt” in the process, as the Resolution your game runs in is not necessarily known at the time the Bitmap Font is created.

You see, as this text comes from a TextureAtlas you always need a bit of spacing around each letter to not bleed pixels from another letter or cut off pixels due to Sampling. Your best bet is, decide for a font, find the offset you’d need to make and manually add it so it will line up exactly where you want

Each font has it’s own spacing metrics set by the designer

How is it getting 110px from 72pt font?

The height is calculated from the VerticalLineSpacing metric stored in the font.

The “extra space” above the A allows for diacritics(~ above N and circle at the top of the A…)

How can I render text such that the top of the text is exactly at the Y-coordinate I specify for the Position parameter, and the height of the text is exactly a desired height in pixels…

You would not want to with almost any truetype/opentype font; Your letters would be top aligned making them nearly unreadable.

… I specify for the Position parameter, and the height of the text is exactly a desired height in pixels…

Make a fixed sized single line Bitmap texture with ASCII placement starting with the space(32) at 0,0.

Then: using 16x16 characters, max 127 for width < 2048

public Vector2 DrawString(SpriteBatch, sb, Texture2D fontTexture,String text, Vector2 position, Color color)
{
    foreach(var c in text)
        sb.Draw(fontTexture,position, new Rectangle(((int)c - 32) * 16,0,16,16),color);

    return new Vector2(16 * text.Length,16);
}