Dashboard/Quik/Media/Font/FontAtlas.cs

187 lines
5.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using Quik.Media.Color;
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<AtlasPage> atlases = new List<AtlasPage>();
private readonly Dictionary<int, FontAtlasGlyphInfo> glyphs = new Dictionary<int, FontAtlasGlyphInfo>();
private int index = 0;
private AtlasPage last = null;
private bool isSdf = false;
private int expansion;
public bool IsSdf
{
get => isSdf;
set
{
foreach (AtlasPage page in atlases)
{
(page.Image as QImageBuffer).SetSdf(value);
}
isSdf = value;
}
}
public FontAtlas(int width, int height, bool isSdf, int expansion = 4)
{
this.width = width;
this.height = height;
IsSdf = isSdf;
this.expansion = expansion;
}
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.WouldFit(source))
{
AddPage();
}
last.PutGlyph(source, ref info);
}
private void AddPage()
{
index++;
if (index < atlases.Count)
{
last = atlases[index];
}
else
{
last = new AtlasPage(width, height, expansion);
(last.Image as QImageBuffer).SetSdf(IsSdf);
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 int Expansion;
public bool IsFull => PointerX > Image.Width || PointerY > Image.Height;
public AtlasPage(int width, int height, int expansion)
{
Image = new QImageBuffer(QImageFormat.AlphaU8, width, height);
Expansion = expansion;
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((float)PointerX/Image.Width, (float)PointerY/Image.Height);
QVec2 size = new QVec2((float)src.Width/Image.Width, (float)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 + Expansion;
RowHeight = 0;
}
public void AdvanceColumn(int width, int height)
{
RowHeight = Math.Max(RowHeight, height);
PointerX += width + Expansion;
if (PointerX > Image.Width)
{
AdvanceRow();
}
}
private bool isDisposed = false;
public void Dispose()
{
if (isDisposed)
return;
Image?.Dispose();
isDisposed = true;
}
internal bool WouldFit(QImageLock source)
{
return !IsFull || PointerX + source.Width > Image.Width || PointerY + source.Height > Image.Height;
}
}
}
}