Move some FreeType operations to Font itself, add lazy loading for Glyph data, remove IDisposable from base Resource.

This commit is contained in:
2025-06-29 14:28:39 +02:00
parent 552e05d498
commit e0e8d6e9ff
3 changed files with 73 additions and 62 deletions

View File

@@ -1,5 +1,9 @@
using System.Numerics; using System.Numerics;
using FreeTypeSharp;
using static FreeTypeSharp.FT;
using static FreeTypeSharp.FT_LOAD;
namespace Voile; namespace Voile;
public struct Glyph public struct Glyph
@@ -16,7 +20,7 @@ public struct Glyph
/// <summary> /// <summary>
/// Represents font data. /// Represents font data.
/// </summary> /// </summary>
public class Font : Resource public class Font : Resource, IDisposable
{ {
/// <summary> /// <summary>
/// Internal handle for the font. If it got successfully loaded into the GPU, the value will be other than -1. /// Internal handle for the font. If it got successfully loaded into the GPU, the value will be other than -1.
@@ -27,11 +31,37 @@ public class Font : Resource
public byte[]? Buffer { get; private set; } public byte[]? Buffer { get; private set; }
public long BufferSize { get; set; } public long BufferSize { get; set; }
internal nint FacePtr;
internal nint LibraryPtr;
public Font(string path, byte[] buffer) : base(path) public Font(string path, byte[] buffer) : base(path)
{ {
Buffer = buffer; Buffer = buffer;
} }
public void Dispose()
{
unsafe
{
if (FacePtr != IntPtr.Zero)
FT_Done_Face((FT_FaceRec_*)FacePtr);
if (LibraryPtr != IntPtr.Zero)
FT_Done_FreeType((FT_LibraryRec_*)LibraryPtr);
}
}
/// <summary>
/// Loads a basic ASCII charset for this font.
/// </summary>
public void LoadAsciiData()
{
for (char c = ' '; c < 127; c++)
{
GetGlyph(c);
}
}
public void Measure(string text) public void Measure(string text)
{ {
foreach (char c in text) foreach (char c in text)
@@ -40,9 +70,14 @@ public class Font : Resource
} }
} }
internal void AddGlyph(char c, Glyph glyph) public Glyph GetGlyph(char character)
{ {
_glyphs.Add(c, glyph); if (_glyphs.TryGetValue(character, out var glyph))
return glyph;
var loaded = LoadGlyph(character);
_glyphs[character] = loaded;
return loaded;
} }
internal void GetGlyphBoundingBox(Glyph glyph) internal void GetGlyphBoundingBox(Glyph glyph)
@@ -50,5 +85,26 @@ public class Font : Resource
} }
private unsafe Glyph LoadGlyph(char character)
{
var face = (FT_FaceRec_*)FacePtr;
// TODO: for now, we're loading glyphs for metrics, but when implementing WebGPU rendering, we want to somehow render them to SDF or bitmap.
FT_Error error = FT_Load_Char(face, character, FT_LOAD_NO_BITMAP);
if (error != 0)
throw new Exception($"Failed to load glyph for '{character}'");
var glyph = face->glyph;
var bitmap = glyph->bitmap;
return new Glyph
{
Width = bitmap.width,
Height = bitmap.rows,
Bearing = new Vector2(glyph->bitmap_left, glyph->bitmap_top),
Advance = (int)glyph->advance.x >> 6,
};
}
private Dictionary<char, Glyph> _glyphs = new(); private Dictionary<char, Glyph> _glyphs = new();
} }

View File

@@ -1,12 +1,10 @@
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices;
using FreeTypeSharp; using FreeTypeSharp;
using Voile.VFS; using Voile.VFS;
using static FreeTypeSharp.FT; using static FreeTypeSharp.FT;
using static FreeTypeSharp.FT_LOAD; using static FreeTypeSharp.FT_LOAD;
using static FreeTypeSharp.FT_Render_Mode_;
namespace Voile.Resources; namespace Voile.Resources;
@@ -28,67 +26,28 @@ public class FontLoader : ResourceLoader<Font>
result.BufferSize = bytesRead; result.BufferSize = bytesRead;
LoadFaceData(result); LoadFaceData(result);
result.LoadAsciiData();
return result; return result;
} }
private unsafe void LoadFaceData(Font font) private unsafe void LoadFaceData(Font font)
{ {
LoadFreeType(); FT_LibraryRec_* lib;
FT_Error error = FT_Init_FreeType(&lib);
if (error != 0)
throw new Exception("Failed to init FreeType");
fixed (FT_LibraryRec_** lib = &_lib) font.LibraryPtr = (nint)lib;
fixed (byte* data = font.Buffer)
{ {
fixed (FT_FaceRec_* face = &_face) FT_FaceRec_* face;
{ error = FT_New_Memory_Face(lib, data, (nint)font.BufferSize, 0, &face);
FT_Error error; if (error != 0)
throw new Exception("Failed to load font face");
var buffer = new Memory<byte>(font.Buffer); font.FacePtr = (nint)face;
var handle = buffer.Pin();
error = FT_New_Memory_Face(*lib, (byte*)handle.Pointer, (nint)font.BufferSize, 0, &face);
error = FT_Set_Pixel_Sizes(face, 0, (uint)font.Size);
error = FT_Select_Charmap(face, FT_Encoding_.FT_ENCODING_UNICODE);
uint gid;
var charcode = FT_Get_First_Char(face, &gid);
while (gid != 0)
{
Console.WriteLine($"Codepoint: {(char)charcode}, gid {gid}");
charcode = FT_Get_Next_Char(face, charcode, &gid);
}
error = FT_Load_Char(face, 'F', FT_LOAD_NO_BITMAP);
var metrics = face->glyph->metrics;
font.AddGlyph('F', new Glyph()
{
Width = metrics.width >> 6,
Height = metrics.height >> 6,
Bearing = new Vector2(metrics.horiBearingX >> 6, metrics.horiBearingY >> 6),
Advance = (int)metrics.horiAdvance >> 6,
});
FT_Done_Face(face);
FT_Done_FreeType(*lib);
}
} }
} }
private unsafe void LoadFreeType()
{
fixed (FT_LibraryRec_** lib = &_lib)
{
FT_Error error;
if (_lib == null)
{
error = FT_Init_FreeType(lib);
}
}
}
private unsafe FT_LibraryRec_* _lib;
private unsafe FT_FaceRec_ _face;
} }

View File

@@ -57,7 +57,7 @@ namespace Voile
/// <summary> /// <summary>
/// Represents data usable by Voile. /// Represents data usable by Voile.
/// </summary> /// </summary>
public abstract class Resource : IDisposable public abstract class Resource
{ {
/// <summary> /// <summary>
/// Path to this resource. /// Path to this resource.
@@ -68,9 +68,5 @@ namespace Voile
{ {
Path = path; Path = path;
} }
public void Dispose()
{
}
} }
} }