355 lines
10 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
} |