Conversation
There was a problem hiding this comment.
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/IFontSourceimplementation and supporting enums. - Extend
FontManagerFonts.ini parsing to support a per-fontLoader=setting for TTF fonts. - Add the
FreeTypeSharpNuGet 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; |
| 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); |
| /// <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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
| 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; | ||
| } | ||
| } | ||
| } | ||
| } |
No description provided.