Skip to content

Add FreeType loader for TTF/OTF rendering#18

Draft
SadPencil wants to merge 5 commits into
masterfrom
freetype
Draft

Add FreeType loader for TTF/OTF rendering#18
SadPencil wants to merge 5 commits into
masterfrom
freetype

Conversation

@SadPencil
Copy link
Copy Markdown
Member

No description provided.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an optional FreeType-backed TrueType font loader path to Rampastring.XNAUI, allowing Fonts.ini to select between the default StbTrueType loader and new FreeType modes for glyph rasterization.

Changes:

  • Add a FreeType-based IFontLoader/IFontSource implementation and supporting enums.
  • Extend FontManager Fonts.ini parsing to support a per-font Loader= setting for TTF fonts.
  • Add the FreeTypeSharp NuGet dependency.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
Rampastring.XNAUI.csproj Adds FreeTypeSharp package reference.
FontManagement/TTFFontLoader.cs Adds enum for selecting the TTF loader via Fonts.ini.
FontManagement/FreeTypeRenderMode.cs Adds enum for FreeType render modes.
FontManagement/FreeTypeFontSource.cs Implements FreeType-backed IFontSource with custom struct layout/offset logic.
FontManagement/FreeTypeFontLoader.cs Implements IFontLoader that instantiates FreeTypeFontSource.
FontManagement/FontManager.cs Adds Fonts.ini Loader parsing and wires selected loader into FontSystemSettings.FontLoader.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

#region FreeType constants

private const int FT_LOAD_DEFAULT = 0x0;
private const int FT_LOAD_COLOR = 0x20;
Comment on lines +146 to +153
public int GetGlyphKernAdvance(int previousGlyphId, int glyphId, float fontSize)
{
int err = FT_Get_Kerning(_face, (uint)previousGlyphId, (uint)glyphId, FT_KERNING_DEFAULT, out FT_Vector kerning);
if (err != 0)
return 0;

return kerning.x >> 6;
}
_ => throw new InvalidOperationException($"Unsupported render mode: {RenderMode}"),
};

FT_Render_Glyph(slotPtr, renderMode);
Comment on lines +7 to +12
/// <summary>
/// IFontSource implementation backed by FreeType with correct Windows P/Invoke struct layouts.
/// Unlike FreeTypeSharp, this correctly maps FT_Pos/FT_Fixed (C 'long') to 'int' on Windows,
/// where C 'long' is always 4 bytes regardless of 32/64-bit.
/// </summary>
public sealed class FreeTypeFontSource : IFontSource
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for @SadPencil: I think I mentioned this issue in Discord a while back. They used some sort of automatic tool for the C# bindings in FreeTypeSharp. It's pulled through the wrong size for one of the fields, hence this code's hacky workaround. It could be easily fixed if we fork FreeTypeSharp (or PR them), or if you're OK with this workaround then just keep it and wrap in a If isWindows then.... The field's size is correct for Linux but not Windows IIRC.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer a fork (and a pr for upstream perhaps). This freetype is not in a hurry and does not block the client's release

Comment on lines +107 to +143
int renderMode = RenderMode switch
{
FreeTypeRenderMode.Normal => (int)FT_Render_Mode_.FT_RENDER_MODE_NORMAL,
FreeTypeRenderMode.Light => (int)FT_Render_Mode_.FT_RENDER_MODE_LIGHT,
FreeTypeRenderMode.Mono => (int)FT_Render_Mode_.FT_RENDER_MODE_MONO,
FreeTypeRenderMode.LCD => (int)FT_Render_Mode_.FT_RENDER_MODE_LCD,
FreeTypeRenderMode.LCDV => (int)FT_Render_Mode_.FT_RENDER_MODE_LCD_V,
FreeTypeRenderMode.SDF => (int)FT_Render_Mode_.FT_RENDER_MODE_SDF,
_ => throw new InvalidOperationException($"Unsupported render mode: {RenderMode}"),
};

FT_Render_Glyph(slotPtr, renderMode);

FT_Bitmap bitmap = ReadGlyphSlotBitmap(slotPtr);

for (int y = 0; y < outHeight; y++)
{
int dstPos = (y * outStride) + startIndex;
IntPtr srcRow = bitmap.buffer + (y * bitmap.pitch);

if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY)
{
Marshal.Copy(srcRow, buffer, dstPos, Math.Min(outWidth, Math.Abs(bitmap.pitch)));
}
else if (bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
{
for (int x = 0; x < outWidth; x += 8)
{
byte bits = Marshal.ReadByte(srcRow, x / 8);
int count = Math.Min(8, outWidth - x);
for (int b = 0; b < count; b++)
{
buffer[dstPos + x + b] = ((bits >> (7 - b)) & 1) != 0 ? (byte)255 : (byte)0;
}
}
}
}
Comment thread Rampastring.XNAUI.csproj
@SadPencil SadPencil marked this pull request as draft May 4, 2026 13:44
@SadPencil SadPencil marked this pull request as draft May 4, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants