// XNA BMFont renderer by Jacob 'proj' Repp. // // Feel free to use this in your code with no limitations, modify and tweak to your desire. // No warranties implied or granted. // // Send comments or suggestions to jacobrepp@gmail.com // // Usage: // List buffer = new List(); // // Font font = Font.LoadFont(content, "Assets", "main_font.fnt"); // font.GeneratePrimitives(buffer, "My String", X, Y, Font.LineHeight, Color.White); // // Render (customize to your shader invokation): // GraphicsDevice gd; // using (VertexDeclaration decl = new VertexDeclaration(_gd, VertexPositionColorTexture.VertexElements)) // { // gd.VertexDeclaration = decl; // gd.Vertices[0].SetSource(buffer, 0, VertexPositionColorTexture.SizeInBytes); // ... Bind an ortho ModelView matrix and the font.Texture to your texture on the shader // gd.DrawPimitives(PrimitiveType.TriangleList, 0, buffer.Count / 3); // } using System; using System.IO; using System.Collections.Generic; using System.Text; using System.Xml; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content; namespace UnderGroundHeroesXNA { class Font : IDisposable { public float LineHeight; public float Base; public int Pages; public float ScaleW; public float ScaleH; public Texture2D Texture; struct Kerning { public int Next; public float Ammount; public Kerning(int next, float ammount) { Next = next; Ammount = ammount; } } struct Glyph { public float U; public float V; public float Width; public float Height; public float XOffset; public float YOffset; public float XAdvance; public List Kernings; } Glyph []_glyphs = new Glyph[256]; public void GeneratePrimitives( List buffer, string str, float x, float y, float lineHeight, Color color) { float lx = x; for(int i = 0, c = str.Length; i < c; ++i) { if (str[i] == '\n') { lx = x; y += lineHeight; } Glyph g = _glyphs[(byte)str[i]]; float ox = lx + g.XOffset; float oy = y + g.YOffset; float uw = g.Width / ScaleW; float uh = g.Height / ScaleH; Vector3 upperLeft = new Vector3(ox, oy, 0); Vector2 upperLeftUV = new Vector2(g.U, g.V); Vector3 lowerLeft = new Vector3(ox, oy + g.Height, 0); Vector2 lowerLeftUV = new Vector2(g.U, g.V + uh); Vector3 upperRight = new Vector3(ox + g.Width, oy, 0); Vector2 upperRightUV = new Vector2(g.U + uw, g.V); Vector3 lowerRight = new Vector3(ox + g.Width, oy + g.Height, 0); Vector2 lowerRightUV = new Vector2(g.U + uw, g.V + uh); // tri 1 // upper left buffer.Add(new VertexPositionColorTexture(upperLeft, color, upperLeftUV)); // lower left buffer.Add(new VertexPositionColorTexture(lowerLeft, color, lowerLeftUV)); // lower right buffer.Add(new VertexPositionColorTexture(lowerRight, color, lowerRightUV)); // tri 2 // lower right buffer.Add(new VertexPositionColorTexture(lowerRight, color, lowerRightUV)); // upper right buffer.Add(new VertexPositionColorTexture(upperRight, color, upperRightUV)); // upper left buffer.Add(new VertexPositionColorTexture(upperLeft, color, upperLeftUV)); lx += GetAdvanceAmmount(g, str, i); } } private float GetAdvanceAmmount(Glyph g, string str, int index) { float ammount = g.XAdvance; if (index < str.Length - 1 && null != g.Kernings) { int next = (int)str[index + 1]; for (int i = 0, c = g.Kernings.Count; i < c; ++i) { if (g.Kernings[i].Next == next) { ammount += g.Kernings[i].Ammount; break; } } } return ammount; } public BoundingBox CalculateBounds(string str, float x, float y, float lineHeight) { BoundingBox bounds = new BoundingBox(); bounds.Min.X = x; bounds.Min.Y = y; bounds.Max.Y = y + lineHeight; float lx = x; for(int i = 0, c = str.Length; i < c; ++i) { if (str[i] == '\n') { bounds.Max.Y += lineHeight; lx = x; } else { Glyph g = _glyphs[(byte)str[i]]; lx += GetAdvanceAmmount(g, str, i); if (lx > bounds.Max.X) { bounds.Max.X = lx; } } } return bounds; } public static Font LoadFont(ContentManager contentManager, string path, string fontfile) { Font font = new Font(); StreamReader reader = new StreamReader(path + "\\" + fontfile); String line; while ((line = reader.ReadLine()) != null) { String []tokens = line.Split(new char[]{' '}, StringSplitOptions.RemoveEmptyEntries); if(tokens[0] == "info") { } else if(tokens[0] == "common") { for(int i = 1; i < tokens.Length; ++i) { String []kv = tokens[i].Split('='); switch(kv[0]) { case "lineHeight": font.LineHeight = Convert.ToSingle(kv[1]); break; case "base": font.Base = Convert.ToSingle(kv[1]); break; case "pages": font.Pages = Convert.ToInt32(kv[1]); if(font.Pages > 1) { throw new ApplicationException("Can't handle fonts with more than 1 page currently"); } break; case "scaleW": font.ScaleW = Convert.ToSingle(kv[1]); break; case "scaleH": font.ScaleH = Convert.ToSingle(kv[1]); break; } } } else if(tokens[0] == "page") { for(int i = 1; i < tokens.Length; ++i) { String []kv = tokens[i].Split('='); switch(kv[0]) { case "id": break; case "file": kv[1] = kv[1].TrimStart('\"'); kv[1] = kv[1].TrimEnd('\"'); kv[1] = kv[1].Substring(0, kv[1].IndexOf('.')); font.Texture = contentManager.Load(path + "\\" + kv[1]) as Texture2D; break; case "pages": font.Pages = Convert.ToInt32(kv[1]); break; } } } else if(tokens[0] == "char") { int id = -1; Glyph g = new Glyph(); for(int i = 1; i < tokens.Length; ++i) { String []kv = tokens[i].Split('='); switch(kv[0]) { case "id": id = Convert.ToInt32(kv[1]); break; case "x": g.U = Convert.ToSingle(kv[1]) / font.ScaleW; break; case "y": g.V = Convert.ToSingle(kv[1]) / font.ScaleH; break; case "width": g.Width = Convert.ToSingle(kv[1]); break; case "height": g.Height = Convert.ToSingle(kv[1]); break; case "xoffset": g.XOffset = Convert.ToSingle(kv[1]); break; case "yoffset": g.YOffset = Convert.ToSingle(kv[1]); break; case "xadvance": g.XAdvance = Convert.ToSingle(kv[1]); break; } } if(id < 0 || id >= font._glyphs.Length) { throw new ApplicationException("Invalid character ID: " + id); } font._glyphs[id] = g; } else if(tokens[0] == "kerning") { int first = -1; int second = -1; float ammount = 0.0f; for(int i = 1; i < tokens.Length; ++i) { string []kv = tokens[i].Split('='); switch(kv[0]) { case "first": first = Convert.ToInt32(kv[1]); break; case "second": second = Convert.ToInt32(kv[1]); break; case "ammount": ammount = Convert.ToSingle(kv[1]); break; } } if(first > 0 && second > 0 && ammount > 0.0f) { Glyph g = font._glyphs[first]; if(null == g.Kernings) { g.Kernings = new List(); } g.Kernings.Add(new Kerning(second, ammount)); } } } reader.Close(); return font; } public void Dispose() { if (Texture != null) { Texture.Dispose(); } Texture = null; } } }