using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Text; namespace Quik.Stb { public unsafe class StbFont : IDisposable { IntPtr _buffer; stbtt_fontinfo* _info; List _kerningTable; public IntPtr FontBuffer => _buffer; public ref stbtt_fontinfo FontInfo => ref *_info; public IReadOnlyList KerningTable { get { if (_kerningTable != null) return _kerningTable; int count = Stbtt.GetKerningTableLength(_info); if (count == 0) { return _kerningTable = new List(); } else { stbtt_kerningentry[] array = new stbtt_kerningentry[count]; fixed (stbtt_kerningentry *ptr = array) Stbtt.GetKerningTable(_info, ptr, count); return _kerningTable = new List(array); } } } public int Ascend { get; } public int Descend { get; } public int VerticalLineGap { get; } public int AscendOS2 { get; } public int DescendOS2 { get; } public int VerticalLineGapOS2 { get; } public Box BoundingBox { get; } private StbFont(IntPtr buffer, stbtt_fontinfo* info) { _buffer = buffer; _info = info; int a, b, c, d; Stbtt.GetFontVMetrics(_info, &a, &b, &c); Ascend = a; Descend = b; VerticalLineGap = c; Stbtt.GetFontVMetricsOS2(_info, &a, &b, &c); AscendOS2 = a; DescendOS2 = b; VerticalLineGapOS2 = c; Stbtt.GetFontBoundingBox(_info, &a, &b, &c, &d); BoundingBox = new Box(a, b, c, d); } ~StbFont() { Dispose(false); } public int FindGlyphIndex(int codepoint) { return Stbtt.FindGlyphIndex(_info, codepoint); } public int FindGlyphIndex(Rune codepoint) => FindGlyphIndex(codepoint.Value); public float ScaleForPixelHeight(float pixels) { return Stbtt.ScaleForPixelHeight(_info, pixels); } public float ScaleForMappingEmToPixels(float pixels) { return Stbtt.ScaleForMappingEmToPixels(_info, pixels); } public void GetCodepointHMetrics(int codepoint, out int advance, out int bearing) { int a, b; Stbtt.GetCodepointHMetrics(_info, codepoint, &a, &b); advance = a; bearing = b; } public void GetCodepointHMetrics(Rune codepoint, out int advance, out int bearing) => GetCodepointHMetrics(codepoint.Value, out advance, out bearing); public int GetCodepointKernAdvance(int cp1, int cp2) { return Stbtt.GetCodepointKernAdvance(_info, cp1, cp2); } public int GetCodepointKernAdvance(Rune cp1, Rune cp2) => GetCodepointKernAdvance(cp1.Value, cp2.Value); public int GetCodepointBox(int codepoint, out Box box) { int x0, y0; int x1, y1; int rval; rval = Stbtt.GetCodepointBox(_info, codepoint, &x0, &y0, &x1, &y1); box = new Box(x0, y0, x1, y1); return rval; } public void GetGlyphHMetrics(int glyph, out int advance, out int bearing) { int a, b; Stbtt.GetGlyphHMetrics(_info, glyph, &a, &b); advance = a; bearing = b; } public int GetGlyphKernAdvance(int gl1, int gl2) { return Stbtt.GetGlyphKernAdvance(_info, gl1, gl2); } public int GetGlyphBox(int glyph, out Box box) { int x0, y0; int x1, y1; int rval; rval = Stbtt.GetGlyphBox(_info, glyph, &x0, &y0, &x1, &y1); box = new Box(x0, y0, x1, y1); return rval; } public bool IsGlyphEmpty(int glyph) { return Stbtt.IsGlyphEmpty(_info, glyph) != 0; } public Bitmap GetCodepointBitmap(float scaleX, float scaleY, int codepoint, out int offsetX, out int offsetY) { int w, h, x, y; void* ptr = Stbtt.GetCodepointBitmap(_info, scaleX, scaleY, codepoint, &w, &h, &x, &y); offsetX = x; offsetY = y; return new Bitmap((IntPtr)ptr, w, h, FreeBitmap); } public Bitmap GetCodepointBitmap(float scaleX, float scaleY, Rune codepoint, out int offsetX, out int offsetY) => GetCodepointBitmap(scaleX, scaleY, codepoint.Value, out offsetX, out offsetY); public Bitmap GetCodepointBitmapSubpixel(float scaleX, float scaleY, float shiftX, float shiftY, int codepoint, out int offsetX, out int offsetY) { int w, h, x, y; void* ptr = Stbtt.GetCodepointBitmapSubpixel(_info, scaleX, scaleY, shiftX, shiftY, codepoint, &w, &h, &x, &y); offsetX = x; offsetY = y; return new Bitmap((IntPtr)ptr, w, h, FreeBitmap); } public Bitmap GetCodepointBitmapSubpixel(float scaleX, float scaleY, float shiftX, float shiftY, Rune codepoint, out int offsetX, out int offsetY) => GetCodepointBitmapSubpixel(scaleX, scaleY, shiftX, shiftY, codepoint.Value, out offsetX, out offsetY); public Bitmap GetGlyphBitmap(float scaleX, float scaleY, int glyph, out int offsetX, out int offsetY) { int w, h, x, y; void* ptr = Stbtt.GetGlyphBitmap(_info, scaleX, scaleY, glyph, &w, &h, &x, &y); offsetX = x; offsetY = y; return new Bitmap((IntPtr)ptr, w, h, FreeBitmap); } public Bitmap GetGlyphBitmapSubpixel(float scaleX, float scaleY, float shiftX, float shiftY, int glyph, out int offsetX, out int offsetY) { int w, h, x, y; void* ptr = Stbtt.GetGlyphBitmapSubpixel(_info, scaleX, scaleY, shiftX, shiftY, glyph, &w, &h, &x, &y); offsetX = x; offsetY = y; return new Bitmap((IntPtr)ptr, w, h, FreeBitmap); } public Bitmap GetGlyphSdf(float scale, int glyph, int padding, byte edgeValue, float pixelDistScale, out int offsetX, out int offsetY) { int w, h, x, y; void *ptr = Stbtt.GetGlyphSDF(_info, scale, glyph, padding, edgeValue, pixelDistScale, &w, &h, &x, &y); offsetX = x; offsetY = y; return new Bitmap((IntPtr)ptr, w, h, FreeSdf); } public Bitmap GetCodepointSdf(float scale, int codepoint, int padding, byte edgeValue, float pixelDistScale, out int offsetX, out int offsetY) { int w, h, x, y; void *ptr = Stbtt.GetCodepointSDF(_info, scale, codepoint, padding, edgeValue, pixelDistScale, &w, &h, &x, &y); offsetX = x; offsetY = y; return new Bitmap((IntPtr)ptr, w, h, FreeSdf); } public Bitmap GetCodepointSdf(float scale, Rune codepoint, int padding, byte edgeValue, float pixelDistScale, out int offsetX, out int offsetY) => GetCodepointSdf(scale, codepoint.Value, padding, edgeValue, pixelDistScale, out offsetX, out offsetY); public void Dispose() { Dispose(true); } bool isDisposed = false; private void Dispose(bool disposing) { if (isDisposed) return; if (disposing) { GC.SuppressFinalize(this); } Marshal.FreeHGlobal(_buffer); Marshal.FreeHGlobal((IntPtr)_info); isDisposed = true; } public static bool TryLoad(Stream stream, out StbFont font) { byte* buffer = (byte*)Marshal.AllocHGlobal((int)stream.Length); stbtt_fontinfo* fontInfo = (stbtt_fontinfo*)Marshal.AllocHGlobal(sizeof(stbtt_fontinfo)); stream.Read(new Span(buffer, (int)stream.Length)); int nfont = Stbtt.GetNumberOfFonts(buffer); if (nfont == 0) { font = null; return false; } int offset = Stbtt.GetFontOffsetForIndex(buffer, 0); if (Stbtt.InitFont(fontInfo, (byte*)buffer, offset) == 0) { Marshal.FreeHGlobal((IntPtr)buffer); Marshal.FreeHGlobal((IntPtr)fontInfo); font = null; return false; } font = new StbFont((IntPtr)buffer, fontInfo); return true; } public static StbFont Load(Stream stream) { if (TryLoad(stream, out StbFont font)) { return font; } throw new Exception("Could not load the font."); } private static void FreeBitmap(IntPtr buffer) { Stbtt.FreeBitmap((byte*)buffer, null); } private static void FreeSdf(IntPtr buffer) { Stbtt.FreeSDF((byte*)buffer, null); } public struct Box { public int X0; public int Y0; public int X1; public int Y1; public Box(int x0, int y0, int x1, int y1) { X0 = x0; Y0 = y0; X1 = x1; Y1 = y1; } } public class Bitmap : IDisposable { public IntPtr Buffer { get; } public int Width { get; } public int Height { get; } private readonly Action Destroy; public Bitmap(IntPtr buffer, int width, int height, Action destroy) { Buffer = buffer; Width = width; Height = height; Destroy = destroy; } ~Bitmap() { Dispose(false); } public void Dispose() => Dispose(true); private bool isDiposed = false; public void Dispose(bool disposing) { if (isDiposed) return; if (disposing) { GC.SuppressFinalize(this); } Destroy(Buffer); isDiposed = true; } } } }