#region Copyright & License Information /* * Copyright 2007-2019 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.InteropServices; using OpenRA.Primitives; 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 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) { 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) { 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 { Advance = glyphAdvance, Offset = new int2(bitmapLeft, -bitmapTop), Size = glyphSize, Data = new byte[glyphSize.Width * glyphSize.Height] }; unsafe { var p = (byte*)bitmapBuffer; 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 += bitmapPitch; } } return g; } public void Dispose() { if (!disposed) { if (faceHandle.IsAllocated) { FreeType.FT_Done_Face(face); faceHandle.Free(); disposed = true; } } } } }