Yes, I have written my own sprite renderer that handles both sprite sheets and fonts.
It handles both because if you handle fonts this way, it basically works the same way as sprite sheets.
Each font glyph is a sprite and they are all stored within the same texture, which is your sprite sheet.
You need to know where on the texture each glyph is located.
Tools such as FontBuilder provide this information for you in an additional file.
You need to parse this information and create some way to map single characters to glyph location within your code. (C# dictionary, c++ map...)
Then create a simple function that draws text.
Just split the string you want to draw into individual characters, look them up in your map, and render a screen quad using this data.
Here's some old hlsl code I had lying around:
[source lang="cpp"]Texture2D SpriteSheet : register(t0);SamplerState SpriteSampler : register(s0);cbuffer CBText : register (b0){ float2 TextureSize : packoffset(c0.x); float2 ViewportSize : packoffset(c0.z);}struct VSInput{ // Vertex Data float2 Position : POSITION0; // Instance Data float4 Color : COLOR0; float4 Source : SRCRECT; float4 Destination : DSTRECT;};struct PSInput{ float4 Position : SV_Position; float2 TexCoord : TEXCOORD; float4 Color : COLOR;};PSInput VSText(VSInput input){ PSInput output = (PSInput)0; float4 SourceRect = float4(input.Source.xy / TextureSize, input.Source.zw / TextureSize); float4 DestinationRect = float4(input.Destination.xy / ViewportSize, input.Destination.zw / ViewportSize); float2 OutPos = input.Position * DestinationRect.zw + DestinationRect.xy; OutPos = OutPos * 2.0 - 1.0; OutPos.y = -OutPos.y; output.Position = float4(OutPos, 0, 1); output.TexCoord = input.Position * SourceRect.zw + SourceRect.xy; output.Color = input.Color; return output;}float4 PSText(PSInput input) : SV_TARGET{ float4 Color = SpriteSheet.Sample(SpriteSampler, input.TexCoord); Color = Color * input.Color; Color.rgb *= Color.a; return Color;}[/source]
You render a simple fullscreen quad with these shaders. (from (0, 0) to (1, 1))
Source, Destination, TextureSize and ViewportSize are provided in pixels.
If you've never used hardware instancing before you might want to create an implementation without it first.
Just put the Color/Source/Destination instance data I have into the constant buffer for now, and make 1 draw call per character.
Remember to add some distance between characters so they're not all rendered in the same spot.
To be honest, doing this without hardware instancing is probably fine, unless you render a lot of text.
I'd only look into it if you think that it is affecting your performance, or if you want to learn a very efficient way of doing it.
Once you have this running, changing it to use hardware instancing is pretty straight forward and reduces your draw calls to 1.
Cheers,
Hyu
Edit: Don't use a texture for each font glyph. That's a really bad idea.

Think about it, for each character you draw you would not only issue a draw call, but make a state change as well (change texture register).
This isn't horribly expensive, but the overhead of doing so quickly builds up if you're rendering a lot of characters.
Edited by Hyunkel, 06 September 2012 - 05:03 PM.