freetype

Pure MoonBit FreeType-compatible font engine

font
freetype
truetype
opentype
moon add bikallem/freetype@0.5.3
Download zip
Author
Version
0.5.3
License
Apache-2.0
Last updated
4 months ago
Downloads
68
README

#bikallem/freetype

MoonBit FreeType-compatible font engine with pure MoonBit drivers.

#Table of Contents

#Overview

  • Font loading from Bytes (in-memory, no file I/O)
  • Character-to-glyph mapping (cmap formats 0, 2, 4, 6, 8, 10, 12, 13)
  • Glyph outline loading (TrueType simple/composite, CFF charstrings)
  • Glyph rendering (render_glyph, LOAD_RENDER) in normal, light, mono, LCD, LCD_V, color BGRA, and SDF modes
  • Pixel size scaling with proper 16.16 fixed-point math
  • Kerning pair lookup
  • TrueType bytecode hinting (~160 opcodes: point movement, zones, projection vectors, rounding, deltas)
  • PostScript hinting (stem fitting, blue zone alignment, point interpolation)
  • Auto-hinter (script-aware segment/edge/stem detection, pixel grid fitting)
  • PostScript Type 2 charstring interpreter (path operators, hints, subroutines, flex)
  • PostScript Type 1 charstring interpreter (hsbw, closepath, callothersubr/Flex, seac)
  • Font variations API (set_var_design_coordinates, fvar/avar/gvar/HVAR)
  • WOFF1 decompression (zlib via bikallem/compress)
  • WOFF2 decompression (Brotli via bikallem/compress) with glyf/loca and hmtx transform reconstruction, including collections
  • Standalone CFF font loading (bare CFF without SFNT container)
  • Format auto-detection from file header bytes

#Features

Font format support:

FormatExtensionStatusDriver
TrueType.ttfFulltruetype/ — glyph loading, bytecode interpreter
OpenType/CFF (CFF1).otfFullcff/ — CFF INDEX/DICT parsing, Type 2 charstrings
OpenType/CFF2.otfFullcff/ + sfnt/ — pure MoonBit CFF2 parsing, charstrings, variations, and SFNT integration
TrueType Collection.ttcFullsfnt/ — collection header, per-face loading
WOFF1.woffFullsfnt/woff.mbt — zlib decompression via bikallem/compress
WOFF2.woff2Fullsfnt/woff2.mbt — Brotli decompression, glyf/loca + hmtx transform reconstruction, collections
Standalone CFF.cffFullcff_loader.mbt — bare CFF without SFNT container, PS hinting
Type 1 (PFB/PFA).pfb .pfaFulltype1/ — pure MoonBit single-master + Multiple Master parsing, hinting, and AFM/PFM-backed metrics/kerning
BDF (bitmap).bdfFullbdf/ — header + glyph bitmap extraction
PCF (bitmap).pcfFullpcf/ — TOC, properties, metrics, encodings, bitmaps
Color glyphsCOLR/CPAL, sbix, CBDT/CBLCFullcolor/ — MoonBit COLR/sbix/CBDT path

Obsolete formats not supported: CID-keyed (standalone), Type 42, PFR (Bitstream), Windows FNT/FON. CID-keyed fonts inside CFF/OpenType are supported through the CFF driver.

#What Is Not Ported

The following FreeType subsystems are excluded from this port:

SubsystemReason
File I/O (ftsystem.c file operations)Accepts Bytes instead of file paths; no I/O or OS dependency
FT_Library global stateEliminated — API is stateless, no initialization needed
Memory allocator (FT_ALLOC/FT_FREE/FT_Memory)Eliminated — GC handles memory
FT_Generic (user data hooks)Omitted — users wrap Face in their own struct
OT-SVG renderer (SVG table rendering)Not implemented — the runtime exposes SVG table metadata but does not ship an SVG rasterizer

#API

#Load a font

let font_data : Bytes = ... // font file contents
let face = @freetype.from_bytes(font_data)

// Face metadata
println(face.family_name()) // "DejaVu Sans"
println(face.num_glyphs()) // 6253
println(face.units_per_em()) // 2048
println(face.is_scalable()) // true
println(face.has_kerning()) // true

No Library object, no initialization, no cleanup. The API is stateless and concurrency-safe.

#Character to glyph mapping

let glyph_index = @freetype.get_char_index(face, 65U) // 'A' → glyph index

#Load glyph outlines

// Font units (no scaling)
@freetype.load_glyph(face, glyph_index, load_flags=@base.LOAD_NO_SCALE)
let outline = face.glyph().outline()
println(outline.n_points()) // number of outline points
println(outline.n_contours()) // number of contours
println(outline.points()[0].x()) // first point x coordinate

// Scaled to pixel size
@freetype.set_pixel_sizes(face, 0U, 16U) // 16 ppem
@freetype.load_glyph(face, glyph_index)
println(face.glyph().metrics().hori_advance()) // advance width in 26.6

#Render glyph bitmaps

@freetype.set_pixel_sizes(face, 0U, 16U)
@freetype.load_glyph(face, glyph_index, load_flags=@base.LOAD_RENDER)
println(face.glyph().bitmap().width())
println(face.glyph().bitmap().rows())

// Or render after a separate load_glyph call.
@freetype.load_glyph(face, glyph_index, load_flags=@base.LOAD_NO_HINTING)
@freetype.render_glyph(face)
println(face.glyph().bitmap_left())
println(face.glyph().bitmap_top())

// Explicit render modes.
@freetype.render_glyph(face, mode=Mono)
@freetype.render_glyph(face, mode=Lcd)
@freetype.render_glyph(face, mode=Sdf)

// LOAD_RENDER also honors FreeType-style load targets.
@freetype.load_glyph(
face,
glyph_index,
load_flags=@base.LOAD_RENDER | @base.LOAD_TARGET_LCD,
)

// Color glyphs opt in through LOAD_COLOR and render to BGRA.
@freetype.load_glyph(face, glyph_index, load_flags=@base.LOAD_COLOR)
println(face.glyph().bitmap().pixel_mode()) // Bgra

#Kerning

let (kern_x, kern_y) = @freetype.get_kerning(face, glyph_a, glyph_v)

#Format detection

let format = @base.detect_format(data)
// Returns: TrueType, CffOpenType, TrueTypeCollection, Woff1, Woff2,
// Type1Pfb, Type1Pfa, CffStandalone, Bdf, Pcf, WindowsFnt, Pfr, Unknown

#Project Structure

src/ lib.mbt # Public API: from_bytes, get_char_index, load_glyph, # set_pixel_sizes, get_kerning error/ # FTError enum (~80 error codes) fixed/ # Fixed-point math: 16.16, 26.6, 2.14, CORDIC trig, matrix ops types/ # Shared types: Vector, BBox, Outline, Bitmap, GlyphMetrics, tags stream/ # ByteReader: position-tracked Bytes reader base/ # FaceRec, GlyphSlot, GlyphLoader, outline ops, format detection smooth/ # Outline rasterizer: gray, mono, LCD, LCD_V bitmap emission color/ # Color font table parsing, BGRA surfaces, bitmap decode helpers sdf/ # Signed-distance-field rasterization nativeft/ # Legacy native bridge experiments (not used by the public runtime) sfnt/ # SFNT parsing: table directory, head, hhea, maxp, hmtx, name, # OS/2, post, cmap (formats 0/2/4/6/8/10/12/13/14), kern, # WOFF1, WOFF2 truetype/ # TrueType: glyph loading, loca, bytecode interpreter cff/ # CFF: INDEX/DICT parsing, charstring loading psaux/ # PostScript charstring interpreter (Type 2) pshinter/ # PostScript hinting: stem alignment, blue zones psnames/ # Glyph name ↔ Unicode mapping type1/ # Type 1 PFB: segment parsing, eexec decryption autofit/ # Auto-hinter: script detection, segment/edge analysis bdf/, pcf/ # Bitmap font drivers cache/ # Glyph/charmap LRU cache otvalid/, gxvalid/ # OpenType and GX/AAT table validation parity/ # Parity tests (read fonts from disk, compare with C FreeType) bench/ # C FreeType comparison benchmark + report benchmarks/ # MoonBit benchmarks (moon bench) blit/ # Fast byte-level blit operations (memcpy/memset FFI)

#Build

make build # Compile make test # Run unit tests make parity # Run parity tests against C FreeType golden data make fmt # Format code + regenerate .mbti files make bench # Run C vs MoonBit benchmark comparison make clean # Remove build artifacts make all # build + fmt + test + parity

#Parity Testing

Parity tests verify that the MoonBit port produces identical results to the vendored C FreeType library across all supported font formats. The test pipeline:

  1. Golden file generation — A Python wrapper (test/golden/generate/generate.py) stages the font corpus, using the repo's MoonBit WOFF2 decompressor for .woff2 files, then invokes a C program (test/golden/generate/gen_golden.c) linked against vendored FreeType to dump JSON per font: face metadata, charmap entries, glyph outlines at multiple sizes and load flags, and kerning pairs.

  2. Test generation — A Python script (test/parity/gen_parity_tests.py) reads the golden JSON and generates MoonBit test files that load the same fonts through our port and compare every value.

  3. Font loading — Tests read fonts from disk at runtime via @fs.read_file_to_bytes() (native target).

Fonts tested (real corpus plus synthetic edge-case fixtures):

FontFormatGlyphsSource
DejaVu SansTrueType6,253dejavu-fonts
Roboto VariableTrueType1,326Google Fonts
Source Code ProCFF/OpenType1,568Adobe
Noto Sans JPCFF/OpenType17,936Google Noto
DejaVu SansTTC6,253 × 2Converted from TTF
DejaVu SansWOFF16,253Converted from TTF
Source Code ProWOFF11,568Converted from OTF
DejaVu SansWOFF26,253Converted from TTF
minimalWOFF2 (CFF)3Converted from OTF
minimal collectionWOFF2 TTC3 × 2Synthetic collection reconstruction fixture
Source Code ProStandalone CFF1,568Extracted CFF table
minimalStandalone CFF3Extracted CFF table
Nimbus SansType 1 PFB855URW base35
GNU UnifontBDF57,087unifoundry.com
NablaTrueType / COLR v13,655Google Fonts real-world COLR v1 gradient fixture
minimal SBIX / CBDT / COLRColor TrueType3-6Synthetic BGRA, COLR v0/v1, and bitmap-format edge-case fixtures
uvs / mvarTrueType4-5Synthetic UVS and metric-variation fixtures

What's verified per font:

  • Format detection
  • Font loading without error
  • Face metadata: family name, glyph count, units per em, ascender, descender, height, underline
  • Bounding box: xMin, yMin, xMax, yMax
  • Face flags: scalable, sfnt, horizontal, kerning
  • Charmap count and platform/encoding IDs
  • Character-to-glyph mapping (up to 50 charcodes per font)
  • Glyph outlines in font units: point count, contour count, point coordinates
  • Glyph metrics at 16 ppem: horizontal advance
  • Rendered bitmap parity: pixel mode, dimensions, bearings, buffer bytes
  • TTC multi-face loading
  • Kerning pair values (up to 20 pairs)

Run the parity report:

$ make parity PARITY REPORT: MoonBit FreeType Port vs C FreeType Total: 536 tests, 536 passed, 0 failed Format coverage: FULL TrueType .ttf FULL CFF/OpenType .otf FULL TrueType Collection .ttc FULL WOFF1 .woff FULL WOFF2 .woff2 FULL Standalone CFF .cff FULL Type 1 PFB .pfb FULL BDF Bitmap .bdf FULL PCF Bitmap .pcf

#Performance

The MoonBit port achieves near-parity or better performance compared to C FreeType across most font formats. Benchmarks run with moon bench --release --target native against C FreeType compiled with -O2.

FontFormatMoonBit / C Ratio
DejaVuSans.ttfTrueType0.5x (faster)
DejaVuSans.ttcTrueType Collection0.8x (faster)
SourceCodePro-Regular.otfCFF/OpenType0.7x (faster)
NotoSansJP-Regular.otfCFF/OpenType CJK0.1x (faster)
NimbusSans-Regular.pfbType 1 PFB0.5x (faster)
Roboto[wdth,wght].ttfVariable TrueType1.5x
unifont.bdfBDF Bitmap2.3x
DejaVuSans.woffWOFF19.9x (zlib overhead)

Run benchmarks:

make bench # Runs C FreeType + MoonBit benchmarks and generates comparison report

#Dependencies

#C-to-MoonBit Design Decisions

C FreeTypeMoonBit Port
FT_Library (global state)Eliminated — stateless API
FT_ALLOC/FT_FREEGC handles memory
FT_Stream (file I/O)ByteReader over Bytes
FT_Fixed (32-bit signed long)Int64 newtype (avoids overflow)
FT_ListRec (linked list)Array[T]
FT_HashMap[K, V]
FT_Driver_ClassRec (fn ptr table)Closure-based SfntOps / Type1Ops
FT_CMap_ClassRec (fn ptr table)CmapLookup enum with per-format structs
FT_Generic (user data)Omitted — users wrap Face
src/gzip/, src/bzip2/, Brotlibikallem/compress (zlib + brotli)

#License

Apache 2.0

#
GradientLineSample

type GradientLineSample derive(Show)

#
GradientStopSample

type GradientStopSample derive(Show)

#
PaintTransform

type PaintTransform derive(Show)

#
RenderMode

pub(all) enum RenderMode {
Normal
Light
Mono
Lcd
LcdV
Sdf
} derive(Eq, Show)

Render mode for converting a loaded glyph slot to a bitmap.

#
attach_metrics

fn attach_metrics(face :
FaceRec
, data : Bytes) -> Unit raise
FTError

#
from_bytes

fn from_bytes(data : Bytes, face_index? : Int) ->
FaceRec
raise
FTError

Load a font face from in-memory data. This is the main entry point — replaces C's FT_New_Memory_Face. No Library object needed; API is stateless.

#
get_char_index

fn get_char_index(face :
FaceRec
, charcode : UInt) -> UInt

Map a character code to a glyph index using the active charmap.

#
get_char_variant_index

fn get_char_variant_index(face :
FaceRec
, charcode : UInt, variant_selector : UInt) -> UInt

#
get_char_variant_is_default

fn get_char_variant_is_default(face :
FaceRec
, charcode : UInt, variant_selector : UInt) -> Int

#
get_chars_of_variant

fn get_chars_of_variant(face :
FaceRec
, variant_selector : UInt) -> Array[UInt]

#
get_foreground_color

#
get_kerning

fn get_kerning(face :
FaceRec
, left_glyph : UInt, right_glyph : UInt) -> (Int64, Int64)

Get kerning between two glyphs. Returns (x, y) in font units.

#
get_palette

Return the currently active palette colors.

#
get_palette_data

Return palette metadata for color fonts, if present.

#
get_sdf_spread

fn get_sdf_spread(face :
FaceRec
) -> Int

#
get_variant_selectors

fn get_variant_selectors(face :
FaceRec
) -> Array[UInt]

#
get_variants_of_char

fn get_variants_of_char(face :
FaceRec
, charcode : UInt) -> Array[UInt]

#
load_glyph

fn load_glyph(face :
FaceRec
, glyph_index : UInt, load_flags? : Int) -> Unit raise
FTError

Load a glyph into face.glyph(). After this call, face.glyph().outline() and face.glyph().metrics() are populated.

#
render_glyph

#
select_palette

Select a CPAL palette by index and store it on the face.

#
set_foreground_color

fn set_foreground_color(face :
FaceRec
, color :
Color
) -> Unit

Override the foreground color used by COLR layers with palette index 0xFFFF.

#
set_pixel_sizes

fn set_pixel_sizes(face :
FaceRec
, pixel_width : UInt, pixel_height : UInt) -> Unit raise
FTError

Set the pixel size for subsequent glyph loading. Computes scale factors from ppem and units_per_em.

#
set_sdf_spread

fn set_sdf_spread(face :
FaceRec
, spread : Int) -> Unit raise
FTError

Configure the SDF spread in pixels. Must be positive.

#
set_var_design_coordinates

fn set_var_design_coordinates(face :
FaceRec
, coordinates : Array[(
Fixed
, UInt)]) -> Unit raise
FTError

Set variation design coordinates on a face. Validates and normalizes the coordinates, storing them for use during glyph loading.