Dashboard/Quik.StbTrueType/StbFont.cs

355 lines
10 KiB
C#

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<stbtt_kerningentry> _kerningTable;
public IntPtr FontBuffer => _buffer;
public ref stbtt_fontinfo FontInfo => ref *_info;
public IReadOnlyList<stbtt_kerningentry> KerningTable
{
get
{
if (_kerningTable != null)
return _kerningTable;
int count = Stbtt.GetKerningTableLength(_info);
if (count == 0)
{
return _kerningTable = new List<stbtt_kerningentry>();
}
else
{
stbtt_kerningentry[] array = new stbtt_kerningentry[count];
fixed (stbtt_kerningentry *ptr = array)
Stbtt.GetKerningTable(_info, ptr, count);
return _kerningTable = new List<stbtt_kerningentry>(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<byte>(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<IntPtr> Destroy;
public Bitmap(IntPtr buffer, int width, int height, Action<IntPtr> 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;
}
}
}
}