using System; using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace FontStashSharp { internal class FontSystem { private readonly Dictionary> _glyphs = new Dictionary>(); private readonly List _fonts = new List(); private float _ith; private float _itw; private FontAtlas _currentAtlas; private Point _size; private int _fontSize; public int FontSize { get { return _fontSize; } set { if (value == _fontSize) { return; } _fontSize = value; foreach (var f in _fonts) { f.Recalculate(_fontSize); } } } public readonly int Blur; public float Spacing; public Vector2 Scale; public bool UseKernings = true; public int? DefaultCharacter = ' '; public FontAtlas CurrentAtlas { get { if (_currentAtlas == null) { _currentAtlas = new FontAtlas(_size.X, _size.Y, 256); Atlases.Add(_currentAtlas); } return _currentAtlas; } } public List Atlases { get; } = new List(); public event EventHandler CurrentAtlasFull; public FontSystem(int width, int height, int blur = 0) { if (width <= 0) { throw new ArgumentOutOfRangeException(nameof(width)); } if (height <= 0) { throw new ArgumentOutOfRangeException(nameof(height)); } if (blur < 0 || blur > 20) { throw new ArgumentOutOfRangeException(nameof(blur)); } Blur = blur; _size = new Point(width, height); _itw = 1.0f / _size.X; _ith = 1.0f / _size.Y; ClearState(); } public void ClearState() { FontSize = 12; Spacing = 0; } public void AddFontMem(byte[] data) { var font = Font.FromMemory(data); font.Recalculate(FontSize); _fonts.Add(font); } private Dictionary GetGlyphsCollection(int size) { Dictionary result; if (_glyphs.TryGetValue(size, out result)) { return result; } result = new Dictionary(); _glyphs[size] = result; return result; } private void PreDraw(string str, out Dictionary glyphs, out float ascent, out float lineHeight) { glyphs = GetGlyphsCollection(FontSize); // Determine ascent and lineHeight from first character ascent = 0; lineHeight = 0; for (int i = 0; i < str.Length; i += char.IsSurrogatePair(str, i) ? 2 : 1) { var codepoint = char.ConvertToUtf32(str, i); var glyph = GetGlyph(null, glyphs, codepoint); if (glyph == null) { continue; } ascent = glyph.Font.Ascent; lineHeight = glyph.Font.LineHeight; break; } } public float DrawText(SpriteBatch batch, float x, float y, string str, Color color, float depth) { if (string.IsNullOrEmpty(str)) return 0.0f; Dictionary glyphs; float ascent, lineHeight; PreDraw(str, out glyphs, out ascent, out lineHeight); float originX = 0.0f; float originY = 0.0f; originY += ascent; FontGlyph prevGlyph = null; var q = new FontGlyphSquad(); for (int i = 0; i < str.Length; i += char.IsSurrogatePair(str, i) ? 2 : 1) { var codepoint = char.ConvertToUtf32(str, i); if (codepoint == '\n') { originX = 0.0f; originY += lineHeight; prevGlyph = null; continue; } var glyph = GetGlyph(batch.GraphicsDevice, glyphs, codepoint); if (glyph == null) { continue; } GetQuad(glyph, prevGlyph, Spacing, ref originX, ref originY, ref q); if (!glyph.IsEmpty) { q.X0 = (int)(q.X0 * Scale.X); q.X1 = (int)(q.X1 * Scale.X); q.Y0 = (int)(q.Y0 * Scale.Y); q.Y1 = (int)(q.Y1 * Scale.Y); var destRect = new Rectangle((int)(x + q.X0), (int)(y + q.Y0), (int)(q.X1 - q.X0), (int)(q.Y1 - q.Y0)); var sourceRect = new Rectangle((int)(q.S0 * _size.X), (int)(q.T0 * _size.Y), (int)((q.S1 - q.S0) * _size.X), (int)((q.T1 - q.T0) * _size.Y)); batch.Draw(glyph.Atlas.Texture, destRect, sourceRect, color, 0f, Vector2.Zero, SpriteEffects.None, depth); } prevGlyph = glyph; } return x; } public float DrawText(SpriteBatch batch, float x, float y, string str, Color[] glyphColors, float depth) { if (string.IsNullOrEmpty(str)) return 0.0f; Dictionary glyphs; float ascent, lineHeight; PreDraw(str, out glyphs, out ascent, out lineHeight); float originX = 0.0f; float originY = 0.0f; originY += ascent; FontGlyph prevGlyph = null; var pos = 0; var q = new FontGlyphSquad(); for (int i = 0; i < str.Length; i += char.IsSurrogatePair(str, i) ? 2 : 1) { var codepoint = char.ConvertToUtf32(str, i); if (codepoint == '\n') { originX = 0.0f; originY += lineHeight; prevGlyph = null; ++pos; continue; } var glyph = GetGlyph(batch.GraphicsDevice, glyphs, codepoint); if (glyph == null) { ++pos; continue; } GetQuad(glyph, prevGlyph, Spacing, ref originX, ref originY, ref q); if (!glyph.IsEmpty) { q.X0 = (int)(q.X0 * Scale.X); q.X1 = (int)(q.X1 * Scale.X); q.Y0 = (int)(q.Y0 * Scale.Y); q.Y1 = (int)(q.Y1 * Scale.Y); var destRect = new Rectangle((int)(x + q.X0), (int)(y + q.Y0), (int)(q.X1 - q.X0), (int)(q.Y1 - q.Y0)); var sourceRect = new Rectangle((int)(q.S0 * _size.X), (int)(q.T0 * _size.Y), (int)((q.S1 - q.S0) * _size.X), (int)((q.T1 - q.T0) * _size.Y)); batch.Draw(glyph.Atlas.Texture, destRect, sourceRect, glyphColors[pos], 0f, Vector2.Zero, SpriteEffects.None, depth); } prevGlyph = glyph; ++pos; } return x; } private void PreDraw(StringBuilder str, out Dictionary glyphs, out float ascent, out float lineHeight) { glyphs = GetGlyphsCollection(FontSize); // Determine ascent and lineHeight from first character ascent = 0; lineHeight = 0; for (int i = 0; i < str.Length; i += StringBuilderIsSurrogatePair(str, i) ? 2 : 1) { var codepoint = StringBuilderConvertToUtf32(str, i); var glyph = GetGlyph(null, glyphs, codepoint); if (glyph == null) { continue; } ascent = glyph.Font.Ascent; lineHeight = glyph.Font.LineHeight; break; } } public float DrawText(SpriteBatch batch, float x, float y, StringBuilder str, Color color, float depth) { if (str == null || str.Length == 0) return 0.0f; Dictionary glyphs; float ascent, lineHeight; PreDraw(str, out glyphs, out ascent, out lineHeight); float originX = 0.0f; float originY = 0.0f; originY += ascent; FontGlyph prevGlyph = null; var q = new FontGlyphSquad(); for (int i = 0; i < str.Length; i += StringBuilderIsSurrogatePair(str, i) ? 2 : 1) { var codepoint = StringBuilderConvertToUtf32(str, i); if (codepoint == '\n') { originX = 0.0f; originY += lineHeight; prevGlyph = null; continue; } var glyph = GetGlyph(batch.GraphicsDevice, glyphs, codepoint); if (glyph == null) { continue; } GetQuad(glyph, prevGlyph, Spacing, ref originX, ref originY, ref q); if (!glyph.IsEmpty) { q.X0 = (int)(q.X0 * Scale.X); q.X1 = (int)(q.X1 * Scale.X); q.Y0 = (int)(q.Y0 * Scale.Y); q.Y1 = (int)(q.Y1 * Scale.Y); var destRect = new Rectangle((int)(x + q.X0), (int)(y + q.Y0), (int)(q.X1 - q.X0), (int)(q.Y1 - q.Y0)); var sourceRect = new Rectangle((int)(q.S0 * _size.X), (int)(q.T0 * _size.Y), (int)((q.S1 - q.S0) * _size.X), (int)((q.T1 - q.T0) * _size.Y)); batch.Draw(glyph.Atlas.Texture, destRect, sourceRect, color, 0f, Vector2.Zero, SpriteEffects.None, depth); } prevGlyph = glyph; } return x; } public float DrawText(SpriteBatch batch, float x, float y, StringBuilder str, Color[] glyphColors, float depth) { if (str == null || str.Length == 0) return 0.0f; Dictionary glyphs; float ascent, lineHeight; PreDraw(str, out glyphs, out ascent, out lineHeight); float originX = 0.0f; float originY = 0.0f; originY += ascent; FontGlyph prevGlyph = null; var pos = 0; var q = new FontGlyphSquad(); for (int i = 0; i < str.Length; i += StringBuilderIsSurrogatePair(str, i) ? 2 : 1) { var codepoint = StringBuilderConvertToUtf32(str, i); if (codepoint == '\n') { originX = 0.0f; originY += lineHeight; prevGlyph = null; ++pos; continue; } var glyph = GetGlyph(batch.GraphicsDevice, glyphs, codepoint); if (glyph == null) { ++pos; continue; } GetQuad(glyph, prevGlyph, Spacing, ref originX, ref originY, ref q); if (!glyph.IsEmpty) { q.X0 = (int)(q.X0 * Scale.X); q.X1 = (int)(q.X1 * Scale.X); q.Y0 = (int)(q.Y0 * Scale.Y); q.Y1 = (int)(q.Y1 * Scale.Y); var destRect = new Rectangle((int)(x + q.X0), (int)(y + q.Y0), (int)(q.X1 - q.X0), (int)(q.Y1 - q.Y0)); var sourceRect = new Rectangle((int)(q.S0 * _size.X), (int)(q.T0 * _size.Y), (int)((q.S1 - q.S0) * _size.X), (int)((q.T1 - q.T0) * _size.Y)); batch.Draw(glyph.Atlas.Texture, destRect, sourceRect, glyphColors[pos], 0f, Vector2.Zero, SpriteEffects.None, depth); } prevGlyph = glyph; ++pos; } return x; } public float TextBounds(float x, float y, string str, ref Bounds bounds) { if (string.IsNullOrEmpty(str)) return 0.0f; Dictionary glyphs; float ascent, lineHeight; PreDraw(str, out glyphs, out ascent, out lineHeight); var q = new FontGlyphSquad(); y += ascent; float minx, maxx, miny, maxy; minx = maxx = x; miny = maxy = y; float startx = x; FontGlyph prevGlyph = null; for (int i = 0; i < str.Length; i += char.IsSurrogatePair(str, i) ? 2 : 1) { var codepoint = char.ConvertToUtf32(str, i); if (codepoint == '\n') { x = startx; y += lineHeight; prevGlyph = null; continue; } var glyph = GetGlyph(null, glyphs, codepoint); if (glyph == null) { continue; } GetQuad(glyph, prevGlyph, Spacing, ref x, ref y, ref q); if (q.X0 < minx) minx = q.X0; if (x > maxx) maxx = x; if (q.Y0 < miny) miny = q.Y0; if (q.Y1 > maxy) maxy = q.Y1; prevGlyph = glyph; } float advance = x - startx; bounds.X = minx; bounds.Y = miny; bounds.X2 = maxx; bounds.Y2 = maxy; return advance; } public float TextBounds(float x, float y, StringBuilder str, ref Bounds bounds) { if (str == null || str.Length == 0) return 0.0f; Dictionary glyphs; float ascent, lineHeight; PreDraw(str, out glyphs, out ascent, out lineHeight); var q = new FontGlyphSquad(); y += ascent; float minx, maxx, miny, maxy; minx = maxx = x; miny = maxy = y; float startx = x; FontGlyph prevGlyph = null; for (int i = 0; i < str.Length; i += StringBuilderIsSurrogatePair(str, i) ? 2 : 1) { var codepoint = StringBuilderConvertToUtf32(str, i); if (codepoint == '\n') { x = startx; y += lineHeight; prevGlyph = null; continue; } var glyph = GetGlyph(null, glyphs, codepoint); if (glyph == null) { continue; } GetQuad(glyph, prevGlyph, Spacing, ref x, ref y, ref q); if (q.X0 < minx) minx = q.X0; if (x > maxx) maxx = x; if (q.Y0 < miny) miny = q.Y0; if (q.Y1 > maxy) maxy = q.Y1; prevGlyph = glyph; } float advance = x - startx; bounds.X = minx; bounds.Y = miny; bounds.X2 = maxx; bounds.Y2 = maxy; return advance; } public List GetGlyphRects(float x, float y, string str){ List Rects = new List(); if (string.IsNullOrEmpty(str)) return Rects; Dictionary glyphs; float ascent, lineHeight; PreDraw(str, out glyphs, out ascent, out lineHeight); var q = new FontGlyphSquad(); y += ascent; float minx, maxx, miny, maxy; minx = maxx = x; miny = maxy = y; float startx = x; FontGlyph prevGlyph = null; for (int i = 0; i < str.Length; i += char.IsSurrogatePair(str, i) ? 2 : 1) { var codepoint = char.ConvertToUtf32(str, i); if (codepoint == '\n') { x = startx; y += lineHeight; prevGlyph = null; continue; } var glyph = GetGlyph(null, glyphs, codepoint); if (glyph == null) { continue; } GetQuad(glyph, prevGlyph, Spacing, ref x, ref y, ref q); Rects.Add(new Rectangle((int)q.X0, (int)q.Y0, (int)(q.X1-q.X0), (int)(q.Y1-q.Y0))); prevGlyph = glyph; } return Rects; } public List GetGlyphRects(float x, float y, StringBuilder str){ List Rects = new List(); if (str == null || str.Length == 0) return Rects; Dictionary glyphs; float ascent, lineHeight; PreDraw(str, out glyphs, out ascent, out lineHeight); var q = new FontGlyphSquad(); y += ascent; float minx, maxx, miny, maxy; minx = maxx = x; miny = maxy = y; float startx = x; FontGlyph prevGlyph = null; for (int i = 0; i < str.Length; i += StringBuilderIsSurrogatePair(str, i) ? 2 : 1) { var codepoint = StringBuilderConvertToUtf32(str, i); if (codepoint == '\n') { x = startx; y += lineHeight; prevGlyph = null; continue; } var glyph = GetGlyph(null, glyphs, codepoint); if (glyph == null) { continue; } GetQuad(glyph, prevGlyph, Spacing, ref x, ref y, ref q); Rects.Add(new Rectangle((int)q.X0, (int)q.Y0, (int)(q.X1-q.X0), (int)(q.Y1-q.Y0))); prevGlyph = glyph; } return Rects; } bool StringBuilderIsSurrogatePair(StringBuilder sb, int index) { if (index + 1 < sb.Length) return char.IsSurrogatePair(sb[index], sb[index + 1]); return false; } int StringBuilderConvertToUtf32(StringBuilder sb, int index) { if (!char.IsHighSurrogate(sb[index])) return sb[index]; return char.ConvertToUtf32(sb[index], sb[index + 1]); } public void Reset(int width, int height) { Atlases.Clear(); _glyphs.Clear(); if (width == _size.X && height == _size.Y) return; _size = new Point(width, height); _itw = 1.0f / _size.X; _ith = 1.0f / _size.Y; } public void Reset() { Reset(_size.X, _size.Y); } private int GetCodepointIndex(int codepoint, out Font font) { font = null; int g = 0; foreach (var f in _fonts) { g = f.GetGlyphIndex(codepoint); if (g != 0) { font = f; break; } } return g; } private FontGlyph GetGlyphWithoutBitmap(Dictionary glyphs, int codepoint) { FontGlyph glyph = null; if (glyphs.TryGetValue(codepoint, out glyph)) { return glyph; } Font font; var g = GetCodepointIndex(codepoint, out font); if (g == 0) { return null; } int advance = 0, lsb = 0, x0 = 0, y0 = 0, x1 = 0, y1 = 0; font.BuildGlyphBitmap(g, FontSize, font.Scale, ref advance, ref lsb, ref x0, ref y0, ref x1, ref y1); var gw = x1 - x0; var gh = y1 - y0; glyph = new FontGlyph { Font = font, Codepoint = codepoint, Size = FontSize, Index = g, Bounds = new Rectangle(0, 0, gw, gh), XAdvance = (int)(font.Scale * advance * 10.0f), XOffset = x0, YOffset = y0 }; glyphs[codepoint] = glyph; return glyph; } private FontGlyph GetGlyphInternal(GraphicsDevice graphicsDevice, Dictionary glyphs, int codepoint) { var glyph = GetGlyphWithoutBitmap(glyphs, codepoint); if (glyph == null) { return null; } if (graphicsDevice == null || glyph.Atlas != null || glyph.IsEmpty) return glyph; var currentAtlas = CurrentAtlas; int gx = 0, gy = 0; var gw = glyph.Bounds.Width; var gh = glyph.Bounds.Height; if (!currentAtlas.AddRect(gw, gh, ref gx, ref gy)) { CurrentAtlasFull?.Invoke(this, EventArgs.Empty); // This code will force creation of new atlas _currentAtlas = null; currentAtlas = CurrentAtlas; // Try to add again if (!currentAtlas.AddRect(gw, gh, ref gx, ref gy)) { throw new Exception(string.Format("Could not add rect to the newly created atlas. gw={0}, gh={1}", gw, gh)); } } glyph.Bounds.X = gx; glyph.Bounds.Y = gy; currentAtlas.RenderGlyph(graphicsDevice, glyph); glyph.Atlas = currentAtlas; return glyph; } private FontGlyph GetGlyph(GraphicsDevice graphicsDevice, Dictionary glyphs, int codepoint) { var result = GetGlyphInternal(graphicsDevice, glyphs, codepoint); if (result == null && DefaultCharacter != null) { result = GetGlyphInternal(graphicsDevice, glyphs, DefaultCharacter.Value); } return result; } private void GetQuad(FontGlyph glyph, FontGlyph prevGlyph, float spacing, ref float x, ref float y, ref FontGlyphSquad q) { if (prevGlyph != null) { float adv = 0; if (UseKernings && glyph.Font == prevGlyph.Font) { adv = prevGlyph.GetKerning(glyph) * glyph.Font.Scale; } x += (int)(adv + spacing + 0.5f); } float rx = x + glyph.XOffset; float ry = y + glyph.YOffset; q.X0 = rx; q.Y0 = ry; q.X1 = rx + glyph.Bounds.Width; q.Y1 = ry + glyph.Bounds.Height; q.S0 = glyph.Bounds.X * _itw; q.T0 = glyph.Bounds.Y * _ith; q.S1 = glyph.Bounds.Right * _itw; q.T1 = glyph.Bounds.Bottom * _ith; x += (int)(glyph.XAdvance / 10.0f + 0.5f); } } }