Reimplement FreeTypeFont against FreeType directly.
This commit is contained in:
@@ -9,75 +9,140 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenRA.Primitives;
|
||||
using SharpFont;
|
||||
|
||||
namespace OpenRA.Platforms.Default
|
||||
{
|
||||
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter",
|
||||
Justification = "C-style naming is kept for consistency with the underlying native API.")]
|
||||
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1310:FieldNamesMustNotContainUnderscore",
|
||||
Justification = "C-style naming is kept for consistency with the underlying native API.")]
|
||||
internal static class FreeType
|
||||
{
|
||||
internal const uint OK = 0x00;
|
||||
internal const int FT_LOAD_RENDER = 0x04;
|
||||
|
||||
internal static readonly int FaceRecGlyphOffset = IntPtr.Size == 8 ? 152 : 84; // offsetof(FT_FaceRec, glyph)
|
||||
internal static readonly int GlyphSlotMetricsOffset = IntPtr.Size == 8 ? 48 : 24; // offsetof(FT_GlyphSlotRec, metrics)
|
||||
internal static readonly int GlyphSlotBitmapOffset = IntPtr.Size == 8 ? 152 : 76; // offsetof(FT_GlyphSlotRec, bitmap)
|
||||
internal static readonly int GlyphSlotBitmapLeftOffset = IntPtr.Size == 8 ? 192 : 100; // offsetof(FT_GlyphSlotRec, bitmap_left)
|
||||
internal static readonly int GlyphSlotBitmapTopOffset = IntPtr.Size == 8 ? 196 : 104; // offsetof(FT_GlyphSlotRec, bitmap_top)
|
||||
internal static readonly int MetricsWidthOffset = 0; // offsetof(FT_Glyph_Metrics, width)
|
||||
internal static readonly int MetricsHeightOffset = IntPtr.Size == 8 ? 8 : 4; // offsetof(FT_Glyph_Metrics, height)
|
||||
internal static readonly int MetricsAdvanceOffset = IntPtr.Size == 8 ? 32 : 16; // offsetof(FT_Glyph_Metrics, horiAdvance)
|
||||
internal static readonly int BitmapPitchOffset = 8; // offsetof(FT_Bitmap, pitch)
|
||||
internal static readonly int BitmapBufferOffset = IntPtr.Size == 8 ? 16 : 12; // offsetof(FT_Bitmap, buffer)
|
||||
|
||||
[DllImport("freetype6", CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern uint FT_Init_FreeType(out IntPtr library);
|
||||
|
||||
[DllImport("freetype6", CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern uint FT_New_Memory_Face(IntPtr library, IntPtr file_base, int file_size, int face_index, out IntPtr aface);
|
||||
|
||||
[DllImport("freetype6", CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern uint FT_Done_Face(IntPtr face);
|
||||
|
||||
[DllImport("freetype6", CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern uint FT_Set_Pixel_Sizes(IntPtr face, uint pixel_width, uint pixel_height);
|
||||
|
||||
[DllImport("freetype6", CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern uint FT_Load_Char(IntPtr face, uint char_code, int load_flags);
|
||||
}
|
||||
|
||||
public sealed class FreeTypeFont : IFont
|
||||
{
|
||||
static readonly Library Library = new Library();
|
||||
readonly Face face;
|
||||
static readonly FontGlyph EmptyGlyph = new FontGlyph
|
||||
{
|
||||
Offset = int2.Zero,
|
||||
Size = new Size(0, 0),
|
||||
Advance = 0,
|
||||
Data = null
|
||||
};
|
||||
|
||||
static IntPtr library = IntPtr.Zero;
|
||||
readonly GCHandle faceHandle;
|
||||
readonly IntPtr face;
|
||||
bool disposed;
|
||||
|
||||
public FreeTypeFont(byte[] data)
|
||||
{
|
||||
face = new Face(Library, data, 0);
|
||||
if (library == IntPtr.Zero && FreeType.FT_Init_FreeType(out library) != FreeType.OK)
|
||||
throw new InvalidOperationException("Failed to initialize FreeType");
|
||||
|
||||
faceHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
|
||||
if (FreeType.FT_New_Memory_Face(library, faceHandle.AddrOfPinnedObject(), data.Length, 0, out face) != FreeType.OK)
|
||||
throw new InvalidDataException("Failed to initialize font");
|
||||
}
|
||||
|
||||
public FontGlyph CreateGlyph(char c, int size, float deviceScale)
|
||||
{
|
||||
try
|
||||
var scaledSize = (uint)(size * deviceScale);
|
||||
if (FreeType.FT_Set_Pixel_Sizes(face, scaledSize, scaledSize) != FreeType.OK)
|
||||
return EmptyGlyph;
|
||||
|
||||
if (FreeType.FT_Load_Char(face, c, FreeType.FT_LOAD_RENDER) != FreeType.OK)
|
||||
return EmptyGlyph;
|
||||
|
||||
// Extract the glyph data we care about
|
||||
// HACK: This uses raw pointer offsets to avoid defining structs and types that are 95% unnecessary
|
||||
var glyph = Marshal.ReadIntPtr(IntPtr.Add(face, FreeType.FaceRecGlyphOffset)); // face->glyph
|
||||
|
||||
var metrics = IntPtr.Add(glyph, FreeType.GlyphSlotMetricsOffset); // face->glyph->metrics
|
||||
var metricsWidth = Marshal.ReadIntPtr(IntPtr.Add(metrics, FreeType.MetricsWidthOffset)); // face->glyph->metrics.width
|
||||
var metricsHeight = Marshal.ReadIntPtr(IntPtr.Add(metrics, FreeType.MetricsHeightOffset)); // face->glyph->metrics.width
|
||||
var metricsAdvance = Marshal.ReadIntPtr(IntPtr.Add(metrics, FreeType.MetricsAdvanceOffset)); // face->glyph->metrics.horiAdvance
|
||||
|
||||
var bitmap = IntPtr.Add(glyph, FreeType.GlyphSlotBitmapOffset); // face->glyph->bitmap
|
||||
var bitmapPitch = Marshal.ReadInt32(IntPtr.Add(bitmap, FreeType.BitmapPitchOffset)); // face->glyph->bitmap.pitch
|
||||
var bitmapBuffer = Marshal.ReadIntPtr(IntPtr.Add(bitmap, FreeType.BitmapBufferOffset)); // face->glyph->bitmap.buffer
|
||||
|
||||
var bitmapLeft = Marshal.ReadInt32(IntPtr.Add(glyph, FreeType.GlyphSlotBitmapLeftOffset)); // face->glyph.bitmap_left
|
||||
var bitmapTop = Marshal.ReadInt32(IntPtr.Add(glyph, FreeType.GlyphSlotBitmapTopOffset)); // face->glyph.bitmap_top
|
||||
|
||||
// Convert FreeType's 26.6 fixed point format to integers by discarding fractional bits
|
||||
var glyphSize = new Size((int)metricsWidth >> 6, (int)metricsHeight >> 6);
|
||||
var glyphAdvance = (int)metricsAdvance >> 6;
|
||||
|
||||
var g = new FontGlyph
|
||||
{
|
||||
var scaledSize = (uint) (size * deviceScale);
|
||||
face.SetPixelSizes(scaledSize, scaledSize);
|
||||
|
||||
face.LoadChar(c, LoadFlags.Default, LoadTarget.Normal);
|
||||
face.Glyph.RenderGlyph(RenderMode.Normal);
|
||||
Advance = glyphAdvance,
|
||||
Offset = new int2(bitmapLeft, -bitmapTop),
|
||||
Size = glyphSize,
|
||||
Data = new byte[glyphSize.Width * glyphSize.Height]
|
||||
};
|
||||
|
||||
var glyphSize = new Size((int)face.Glyph.Metrics.Width, (int)face.Glyph.Metrics.Height);
|
||||
|
||||
var g = new FontGlyph
|
||||
unsafe
|
||||
{
|
||||
var p = (byte*)bitmapBuffer;
|
||||
var k = 0;
|
||||
for (var j = 0; j < glyphSize.Height; j++)
|
||||
{
|
||||
Advance = (float)face.Glyph.Metrics.HorizontalAdvance,
|
||||
Offset = new int2(face.Glyph.BitmapLeft, -face.Glyph.BitmapTop),
|
||||
Size = glyphSize,
|
||||
Data = new byte[glyphSize.Width * glyphSize.Height]
|
||||
};
|
||||
for (var i = 0; i < glyphSize.Width; i++)
|
||||
g.Data[k++] = p[i];
|
||||
|
||||
// A new bitmap is generated each time this property is accessed, so we do need to dispose it.
|
||||
using (var bitmap = face.Glyph.Bitmap)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var p = (byte*)bitmap.Buffer;
|
||||
var k = 0;
|
||||
for (var j = 0; j < glyphSize.Height; j++)
|
||||
{
|
||||
for (var i = 0; i < glyphSize.Width; i++)
|
||||
g.Data[k++] = p[i];
|
||||
|
||||
p += bitmap.Pitch;
|
||||
}
|
||||
}
|
||||
p += bitmapPitch;
|
||||
}
|
||||
}
|
||||
|
||||
return g;
|
||||
}
|
||||
catch (FreeTypeException)
|
||||
{
|
||||
return new FontGlyph
|
||||
{
|
||||
Offset = int2.Zero,
|
||||
Size = new Size(0, 0),
|
||||
Advance = 0,
|
||||
Data = null
|
||||
};
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
face.Dispose();
|
||||
if (!disposed)
|
||||
{
|
||||
if (faceHandle.IsAllocated)
|
||||
{
|
||||
FreeType.FT_Done_Face(face);
|
||||
|
||||
faceHandle.Free();
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user