using System; using System.Collections.Generic; namespace Quik.Media.Font { public struct FontAtlasGlyphInfo { public int Codepoint; public QImage Image; public QRectangle UVs; } public class FontAtlas { private readonly int width, height; private readonly List atlases = new List(); private readonly Dictionary glyphs = new Dictionary(); private int index = 0; private AtlasPage last = null; public FontAtlas(int width, int height) { this.width = width; this.height = height; } public bool GetGlyph(int codepoint, out FontAtlasGlyphInfo info) { return glyphs.TryGetValue(codepoint, out info); } public void PutGlyph(int codepoint, QImageLock source, out FontAtlasGlyphInfo info) { info = new FontAtlasGlyphInfo() { Codepoint = codepoint }; if (last == null || last.IsFull) { AddPage(); } last.PutGlyph(source, ref info); } private void AddPage() { index++; if (index < atlases.Count) { last = atlases[index]; } else { last = new AtlasPage(width, height); atlases.Add(last); } } public void Clear() { // Trim any pages that were not used yet. for (int i = atlases.Count -1; i >= 0; i--) { if (atlases[i].PointerX != 0 && atlases[i].PointerY != 0) { for (int j = i + 1; j < atlases.Count; j++) { atlases[j].Dispose(); } if (i != atlases.Count - 1) atlases.RemoveRange(i+1, atlases.Count - i - 1); break; } } if (atlases.Count > 0) { last = atlases[0]; } else { last = null; } index = -1; glyphs.Clear(); } private class AtlasPage : IDisposable { public QImage Image; public int PointerX, PointerY; public int RowHeight; public bool IsFull => PointerX > Image.Width || PointerY > Image.Height; public AtlasPage(int width, int height) { Image = new QImageBuffer(QImageFormat.RedU8, width, height); Reset(); } public void PutGlyph(QImageLock src, ref FontAtlasGlyphInfo prototype) { if (IsFull) throw new Exception("Page is full!"); Image.LockBits2d(out QImageLock dst, QImageLockOptions.Default); src.CopyTo(dst, PointerX, PointerY); Image.UnlockBits(); QVec2 min = new QVec2(PointerX/Image.Width, PointerY/Image.Width); QVec2 size = new QVec2(src.Width/Image.Width, src.Height/Image.Height); prototype.Image = Image; prototype.UVs = new QRectangle(min + size, min); AdvanceColumn(src.Width, src.Height); } public void Reset() { RowHeight = PointerX = PointerY = 0; } public void AdvanceRow() { PointerX = 0; PointerY += RowHeight; RowHeight = 0; } public void AdvanceColumn(int width, int height) { RowHeight = Math.Max(RowHeight, height); PointerX += width; if (PointerX > Image.Width) { AdvanceRow(); } } private bool isDisposed = false; public void Dispose() { if (isDisposed) return; Image?.Dispose(); isDisposed = true; } } } }