github.com/OrelSokolov/freetype.cr
1.1.0 / published Oct 13, 2026 / repository
TrueType hinting and rasterization in pure Crystal: the ftgrays anti-aliasing rasterizer, the ttinterp bytecode-hinting VM and an SFNT loader, ported from FreeType 2.14.3 — pixel-exact hinted glyphs with no libfreetype dependency. The system FreeType is used as a test oracle only.
freetype.cr — TrueType & CFF glyph pipeline in pure Crystal
The logo above was rendered by this library itself — hinted glyph
rasterization + PNG packing, no FreeType, no FFI (spec/gen_logo.cr).
A self-contained Crystal port of FreeType's font pipeline: the
ftgrays.c anti-aliasing rasterizer (plus the ftraster.c
black-and-white one for FT_RENDER_MODE_MONO and the ftlcdfil.c FIR
filter for LCD), the ttinterp.c bytecode-hinting
VM, the SFNT loading glue and the CFF (Type 2 charstring) glyph loader
(ported from the FreeType 2.14.3 master snapshot). No dependency on
libfreetype — or on any other part of FreeType. Given a TTF/OTF and a
pixel size, it produces hinted or unhinted glyph bitmaps
pixel-for-pixel identical to what the system libfreetype would
render.
Why
- Drop the native dependency. No
libfreetype.so/freetype.dllto ship (the Windows DLL distribution problem disappears entirely). - Full control over the pipeline. Loading, hinting and rasterization are ordinary Crystal code you can step through, instrument and embed.
- Correct hinting without the library. TrueType hinting is bytecode
(
fpgm,prep, per-glyph instructions) that moves outline points; there is no heuristic that approximates it. This port executes it.
Components
src/ftgrays.cr— the rasterizer core:Ftgrays::Raster+Ftgrays::Outline(26.6 coordinates, y up). A port ofgray_render_line(64-bit variant), conics via DDA (gray_render_conic), cubics via bisection (gray_render_cubic), coverage cells and the sweep with fill rules. Bit-level fidelity is carried over explicitly: wrapping arithmetic (&+ &- &*), arithmetic shifts, C-style truncated divisiontdiv, an exact replica ofFT_UDIV. The cell pool / band bisection is replaced with dynamic arrays (area integration is independent of the band split).src/ftrender.cr— theftsmooth.c/ftobjs.crendering glue for all fourFT_Render_Modes: control box → pixel bbox (floor/ceil as inft_glyphslot_preset_bitmap) → translation → rasterization. NORMAL renders throughftgrays; LCD/LCD_V pad the cbox ±43/64, implode the buffer (x3 / y3) and apply the default five-tap FIR filter ([0x08, 0x4D, 0x56, 0x4D, 0x08], ported fromftlcdfil.c); MONO goes throughsrc/ftraster1.cr. The result is anFtrender::GlyphBitmap(8-bit coverage or 1-bit MSB-first packed, top-down;pitchandmodefields included).src/ftraster1.cr— a port of theftraster.cblack-and-white rasterizer (FreeType 2.13.3): profile pool with y-turns, line/Bezier up/down splitting, the two-pass sweep (vertical + horizontal, MSB-first bits) with dropout/stub/smart handling, sub-banding. Used for FT_RENDER_MODE_MONO; honoursFT_OUTLINE_HIGH_PRECISION(TrueType hinted outlines set it) and the dropout scan-mode flags in tags[0].src/fttrigon.cr— a port offttrigon.c(fixed-point trigonometry:FT_Vector_Rotate,FT_Vector_Norm_Len, etc. — needed for projections onto arbitrary axes in the bytecode).src/autofit/— the auto-hinter (src/autofit/*of FreeType): the shared point/segment/edge machinery (afhints.cr), the latin writing system (aflatin.cr— global metrics with blue zones, stem widths, digit check, segment linking with the demerit scoring, edge hinting; the dummynone_dfltfallback is folded intoLatinMetrics), the CJK writing system (afcjk.cr— per-dimension blue zones, the distance-based segment linking with serif de-linking, theaf_hint_normal_stemgrid fitter,AF_SCALER_FLAG_NO_ADVANCE), the style coverage scan with the lazily built per-style metrics (afglobal.cr— the hani fallback of CJK-enabled builds; a latin style without blue zones is disabled tonone_dflt', as inaf_latin_metrics_init_blues), and theaf_loader_load_glyphglue (afloader.cr) on top of both the TT and the CFF face (the CFF hook mirrors FT_LOAD_FORCE_AUTOHINT). Segment/edge indices replace the C pointer arithmetic;afblue_data.cr/afranges_data.cr` are generated from the FreeType sources.src/tt/ttgxvar.cr— GX font variations (a 1:1 port of ttgxvar.c):fvar'/avar' (+ avar v2 store) parsing with design ↔ normalized coordinate conversion (GXBlend.from_font'),gvar' glyph deltas with the packed point/delta decoding, tuple application and IUP-style interpolation (#apply_glyph_deltas'), andHVAR'/VVAR' advance adjustments through the ItemVariationStore/DeltaSetIndexMap machinery (#advance_delta'). The loader hooks (HintedFace#set_var_design', the HVAR-adjusted advance, the phantom/component delta calls in src/tt/loader.cr and the non-default-instance scaling from the unrounded 16.16 coordinates) are verified oracle-exact — hinted and unhinted — by spec/var_diff.cr.cvar' CVT variations (including theprep' rerun on coordinate changes and the GETVARIATION/GETDATA opcodes) are in place too (spec/var_cvar_diff.cr), as are CFF2 variations — the CFF2 table layout, theblend'/vsindex' operators in charstrings and Private DICTs (cff_blend_doBlend writes the results back into the operand stack), HVAR advances and the vstore parsing (spec/cff2_var_diff.cr, SourceSerif4Variable) — andMVAR' metric variations: the tagged deltas re-apply to the modelled font fields (typo/win/line-gap metrics, underline, strikeout, x-height) with FreeType's exact repeated-application semantics, including the derived ascender/descender/height adjustments of tt_apply_mvar (spec/var_mvar_diff.cr on a synthesized Liberation font); `Font#raw_table' hands this module the raw tables.src/tt/sfnt.cr— a minimal SFNT parser: head, maxp, hhea, hmtx, cmap (formats 4 and 12), loca, glyf, cvt, fpgm, prep, gasp, kern. Only what loading outlines and hinting need. WOFF1 wrappers are unwrapped into a plain SFNT before parsing (thewoff_open_font' approach, zlib via the Crystal stdlib) —TT::Font/CFF::Faceaccept.woffbuffers directly; WOFF2 (brotli) is unwrapped the same way (seesrc/tt/woff2.cr). TrueType/OpenType collections (.ttc/.otc) are handled by slicing out the requested face: passface_indextoTT::Font/TT::HintedFace/CFF::Face` (it reaches WOFF2 collections too).src/tt/woff2.cr— the WOFF2 counterpart (a port of FreeType'ssfwoff2.c): header/table-directory parsing, the 255UShort/base128 integer formats, brotli decompression of the table stream and the reconstruction of transformedglyf'/loca' (triplet decoding, bbox/nPoints/nContour substreams, composite glyphs) andhmtx'. Always compiled in:TT::Font/CFF::Faceaccept.woff2buffers directly (including WOFF2 collections, viaface_index). The brotli backend is selectable at compile time: the pure-Crystal decoder shard [brotli.cr](https://github.com/OrelSokolov/brotli.cr) (>= 0.2.0) by default, or libbrotlidec through FFI by adding-Dnative_brotli` (an all-C reference build; both pass the same WOFF2 acceptance diff).src/tt/loader.cr— a port of the hint glue fromttgload.c/ttobjs.c: scaling,exec.run_fpgm/run_prep(CVT in 26.6, as in C),backward_compatibility, simple and composite glyph loading, grid-fit advance (ft_glyphslot_grid_fit_metrics), point write-back, the scan-conversion flag in tags[0].src/tt/ttinterp.cr(~4 600 lines) — a port of thettinterp.cVM: stack, zones/points, CVT, storage, functions, ~190 opcodes; wrapping arithmetic (&+ &- &*), C semantics for division and shifts. A tracer is built in (TT_TRACE=1— per-instruction VM dump, zero cost when the flag is off).src/cff/cffload.cr— the CFF table parser: a port ofcffload.c(header, INDEX structures, charset — needed for seac, FDSelect + FDArray, the font-matrix reconciliation fromcffobjs.c) and the DICT operand decoding ofcffparse.c(integers, 16.16 fixed, binary-coded decimal reals verbatim). CFF2 is parsed too (32-bit INDEX counts, the FDArray-only layout, the VariationStore offset and maxstack), including the Private DICTblend'/vsindex' operators — `CFF.build_blend_vector' is the shared cff_blend_build_vector port.src/cff/cffinterp.cr— the Type 2 charstring interpreter: a port ofpsintrp.ccf2_interpT2CharString (typed int/fixed operand stack, width parsing, path/arithmetic operators, flex, seac via endchar, the xorshiftrandom) specialized to the unhinted no-stem-darkening mode, plus the unhinted cf2_glyphpath frompshints.cand the ps_builder outline callbacks frompsobjs.c. 32-bit wrapping arithmetic is carried over explicitly. A tracer is built in (CFF_TRACE=1, the TT_TRACE counterpart).src/cff/cffhints.cr— the Adobe hinting engine: a port ofpshints.c(CF2_Hint edge expansion with ghost/inverted pairs, the hint map — build/insert with initial-map recomputation, the two-pass optimum pixel positioning ofcf2_hintmap_adjustHints, the piecewise-linear CS→DS map) andpsblues.c(blue zones with family flat-edge matching, BlueScale cutoff, overshoot suppression / blue boost,cf2_blues_capture, the ideographic em-box heuristic) plus the HintMask object frompsintrp.c. The hinted GlyphPath (per-subpath/hint-substitution maps, first-map closes) lives incffinterp.cr; stem darkening (offsets, intersections, winding) is out — FT defaults to it off since 2.7.src/cff/face.cr— the CFF-flavoured OTF face: a port ofcff_slot_load(cffgload.c) — advance fromhmtx, FontMatrix → translate → FT_MulFix scale in the cffgload.c order — reusing the SFNT glue (cmap/hmtx) fromsrc/tt/sfnt.cr. Returns the sameTT::LoadedGlyphas the TrueType loader. CFF2 faces additionally support `set_var_design': the normalized coordinates drive the charstring/Private DICT blend machinery and the HVAR advance delta, and the Private DICTs are re-blended on coordinate changes.
Note: FreeType hands fonts without bytecode (empty fpgm, prep ≤ 7
bytes) to the auto-hinter (ftobjs.c:1016-1020) — so unhinted corpora
(e.g. Roboto) are covered by a separate unhinted acceptance run rather
than the hinted one.
Usage
require "freetype-cr"
face = TT::HintedFace.new(File.read("DejaVuSans.ttf").to_slice)
face.set_pixel_size(16)
gid = face.font.glyph_index('A'.ord)
g = face.load_glyph(gid) # hinted 26.6 outline + advance
bmp = Ftrender.render_glyph(
Ftgrays::Outline.new(g.xs, g.ys, g.tags, g.contours))
# bmp.width/height/left/top + bmp.buffer (8-bit coverage, top-down)
load_glyph(gid, hint: false) gives the FT_LOAD_NO_HINTING path;
advances come back in 26.6 fixed point alongside the outline.
CFF (OTF) fonts go through the same API:
require "freetype-cr"
face = CFF::Face.new(File.read("NimbusSans-Regular.otf").to_slice)
face.set_pixel_size(16)
gid = face.font.glyph_index('A'.ord)
g = face.load_glyph(gid) # unhinted 26.6 outline + advance
bmp = Ftrender.render_glyph(
Ftgrays::Outline.new(g.xs, g.ys, g.tags, g.contours))
CFF::Face#load_glyph returns the same TT::LoadedGlyph structure and
renders through the same Ftrender as the TrueType path. hint: true
(default in FT terms: FT_LOAD_DEFAULT) runs the ported Adobe hinting
engine — bit-exact against it.
WOFF2 (.woff2 webfonts) is supported out of the box: the pure-Crystal
brotli.cr decoder is always
compiled in (add -Dnative_brotli to use the libbrotlidec FFI backend
instead). WOFF1 (.woff) needs no flags either — it is always compiled
(stdlib zlib).
Accuracy
Verified with an oracle-diff against the system libfreetype (FFI in the specs; the library itself never calls it):
- Unhinted: 32 000 glyph rasterizations across 16 unhinted fonts (every Roboto weight/style, Noto Sans/Serif/Mono; sizes 12/13/16/24/37) — 0-pixel diff, including bitmap dimensions and offsets. Synthetic smoke tests: 5/5.
- Hinted (full pipeline — TTF → outline → hinting → rasterization, entirely in Crystal): 198 790 hinted glyphs (Liberation, DejaVu, Verdana, Times and others; 12/13/16/24/37 px) — 0 diff on outlines + bitmaps + advances, plus 80 997 unhinted glyphs — 0 diff. Two "version-sensitive" glyphs are registered (LiberationMono, 12/13 px) where the system libfreetype 2.13.3 disagrees with the FT master snapshot; this port matches the master (checked against a local build).
- CFF (OTF), unhinted: 632 875 glyphs across 140 OTF fonts
(URW base35 + Extra, TeX Gyre, Latin Modern; 12/13/16/24/37 px) —
0 diff on outlines + advances against the system libfreetype with
FT_LOAD_NO_HINTING and
no-stem-darkening=TRUE(the property is set through FFI in the spec; the library itself assumes darkening is off). A render smoke test (3 fonts × 16/24 px, full load → rasterize → bitmap compare) is also 0-diff. The CID path (FDArray/FDSelect) is verified on Noto Sans CJK JP (18 subfonts, every one in use): 327 675 glyphs, 0 diff. - CFF (OTF), hinted (Adobe engine): the port of
pshints.c/psblues.c(stem hints, hint maps with two-pass optimum positioning, blue zones with family matching, overshoot suppression and the BlueScale cutoff, cntrmask counter maps, ghost/inverted pairs) — 632 875 glyphs / 140 OTF fonts / 12-37 px, 0 diff against FT_LOAD_DEFAULT (Adobe engine, darkening off — the FT default since 2.7), plus the CID font above (327 675 glyphs, 0 diff). - WOFF1: 222 942 glyphs across 6 fonts (Liberation TTF, Roboto TTF,
three OTFs and the CID Noto above, all re-wrapped as WOFF1 with
zlib-compressed tables) — 0 diff against the system libfreetype
loading the same
.wofffiles. - WOFF2: 4 625 glyphs across 3 real webfonts
(Roboto and Open Sans — hinted, i.e. the reconstructed
glyf' feeds the full bytecode pipeline, outline+bitmap+advance diffed; Lora — unhinted) — **0 diff** against the system libfreetype loading the same.woff2files. Covers transformedglyf'/loca' (simple, composite, empty glyphs, explicit and computed bboxes), transformedhmtx' and short `loca' (indexToLocFormat 0). - MONO/LCD/LCD_V rendering: 749 370 glyph bitmaps across the
DejaVu + Liberation corpus (38 fonts, mono/lcd/lcdv, 13/24 px) —
0 diff against
FT_Render_Glyphof the system libfreetype (bit-exact buffers, dimensions and offsets). The mono mode runs the portedftraster.c; LCD/LCD_V go through the ftgrays path with the default FIR filter. NB: pixel-exact LCD output requires a libfreetype built with subpixel rendering enabled (SUBPIXEL_RENDERING), as the distro build here is. - TTC/OTC collections: all 30 faces of the system Noto CJK
collections (NotoSansCJK/NotoSerifCJK Regular+Bold, CFF-flavoured;
10+10 Sans + 5+5 Serif faces) loaded through
CFF::Face.new(data, face_index)' — outlines + advances, hinted (Adobe engine) and unhinted, **7 864 200 glyph comparisons, 0 diff** against the system libfreetype opening the same.ttc' with the same face index.
CFF limitations (honest)
- Adobe hinting is ported (
pshints.c/psblues.cand the hint ops ofpsintrp.c— seesrc/cff/cffhints.cr);load_glyph(gid, hint: true)reproduces FT_LOAD_DEFAULT bit for bit. Stem darkening is not ported and intentionally so: FreeType itself defaults tono-stem-darkening=TRUEsince 2.7, so the no-darkening output matches stock libfreetype (verified glyph-for-glyph). Enabling darkening viaFT_Property_Setwould diverge. Subfonts with a non-identity FontMatrix still load through the unhinted path. - Bare CFF (
.cfffiles with their own encoding/charset charmaps) is not parsed — only CFF1 and CFF2 in an SFNT/OTF and WOFF1 (which is unwrapped to SFNT transparently). WOFF2 is unwrapped too (the backend is the pure-Crystal brotli.cr shard, or libbrotlidec through FFI with-Dnative_brotli; the reconstruction itself, including the transformedglyf'/hmtx', is pure Crystal). CFF2 charstring transforms are rejected. - CID (FDArray/FDSelect) is implemented and oracle-verified on Noto Sans CJK JP; other CID fonts passed through the same code path, but that is the only CID font in the acceptance corpus.
- The
randomoperator seed is deterministic (FT's InitialRandomSeed); FreeType derives the default seed from a memory address, so glyphs usingrandommay differ in the low bits of perturbed coordinates.
Performance: pure Crystal vs C through FFI
Bottom line: the port is ~1.8x slower than the "Crystal calls libfreetype via FFI" path (~115k vs ~205k glyphs/s, machine variance ±5%). Both numbers are measured from Crystal, so the comparison is fair: the C side of the benchmark is the real FFI path of a text stack.
crystal run --release spec/bench_render.cr -- 100000 1000
renders 100 000 glyphs in batches of 1000 (batch = one font at one
size; the DejaVu/Liberation/Noto corpus, ppem 12/16/24/37), both sides
with a per-pixel checksum of the output. ONLY=c/ONLY=x runs one
side; spec/bench_diff.cr is a per-glyph debug diff of the same
workload.
Was porting worth it "to avoid hundreds of FFI calls"?
The FFI transition itself was measured (spec/bench_ffi.cr, 10M calls,
release). NB: the rows below measure DIFFERENT things and are not a
"Crystal vs C" comparison — the first is the cost of executing code
with no call at all (the floor), the second and third are the cost of
CALLING C from Crystal:
| what is measured | ns |
|---|---|
| executed Crystal code: two arithmetic ops, no call at all | ~0.9 |
| trivial C function call (strlen, ~0.5 ns of work) via FFI | ~3.6 |
| real FT_Get_Char_Index call (cmap lookup) via FFI | ~9 |
How to read it: the FFI toll itself is ~3 ns on top of a native
call (3.6 − strlen's work). Baking one glyph costs 4 700–8 500 ns and
makes 1–3 FFI calls (load, optionally kerning/metrics) — so FFI
overhead is <0.5 % of a glyph bake. Saving on FFI calls is NOT by
itself a reason for the port — it is negligible when baking into an
atlas. The real wins are removing the external
libfreetype.so/.dll dependency and full control of the pipeline;
the price is 1.8x in speed, which is immaterial for UI (a full bake of
every glyph of a font size is single-digit milliseconds).
But what if there really are hundreds of FFI calls per frame?
Checked on live egui.cr load (icons_browser: ~4000 icons, a grid with a texture-cache pool, text through the same stack):
- Text. After the first bake — zero FFI per frame:
glyph_index, kerning and glyphs are memoized (AtlasFonts), drawing goes from the atlas. FFI lives only in cache misses. - Icons. Baking = rasterizing an SVG into a texture, once per
(source, tint, size); the release bake in egui.cr goes through pure
Crystal (a NanoSVG port), the C shim is only in dev builds. A/B on
real icons (
bin/svg_rasterizer --headless, 128×128): C via FFI 0.23–0.47 ms vs Crystal 0.17–0.53 ms per icon, byte-identical output — on that workload the ports are already on par. - Worst-case arithmetic. Even if a frame makes 1000 direct FFI calls — 1000 × ~3.5 ns ≈ 3.5 µs per frame against a 16 600 µs budget (60 fps) — 0.02 %. The real hundreds of calls per frame in egui.cr are the sokol draw layer; it exists regardless of the rasterizer/font choice and costs a few microseconds total.
In every scenario the cost of the FFI transitions is negligible against the work the call performs; the choice between "C via FFI" and "a Crystal port" should be made on the cost of the work itself (for glyphs — 1.8x in favor of C; for SVG icons — parity) and on the price of the dependency, not on the number of calls.
Optimizations made (the port's output stayed bit-identical)
loader: reusable scratch buffers instead of ~10 allocations per glyph (parse buffers, cur/org/orus copies, phantom points, the composite-glyph chain Set);simple_glyph_intoparses a simple glyph directly into caller buffers; bulk outline copy into the accumulator.ftgrays: rasterizer cells are structs in per-row arrays reused between renders (previously: anheight+1array + a class object per touched cell);Ftrenderkeeps oneRasterper process so the pool stays warm.ttinterp: unsafe reads in the VM main loop (POP_PUSH_COUNT/code); the opcode counter behindTT_OPCOUNT=1(profiling).sfnt: unsafe reads when decoding simple-glyph coordinates (bounds already validated).
Measured profile after the optimizations: load + hint ~6.0 µs per glyph, rasterization ~2.6 µs. The remaining win would be systemic unsafe access to VM zones (~190 handlers), which would sharply increase the review cost of acceptance.
Tests
crystal run spec/ftgrays_smoke.cr— synthetic shapes: a square, a triangle, a circle of conics, a cubic, even-odd.crystal run --release spec/ftgrays_diff.cr [-- fonts...]— the rasterizer acceptance diff against the system libfreetype (FFI oracle). Fonts are auto-classified (unhinted = outlines match with and without hinting); hinted ones are skipped with a note. Runs on the unhinted corpus with a 0-pixel diff.DUMP_DIFFS=n— ASCII dump of the first n mismatches.crystal run --release spec/tt_unhinted_diff.cr [-- fonts...]— the full pipeline without hinting, a diff of outlines/bitmaps/advances against FT_LOAD_NO_HINTING (80 997 glyphs, 0 diff).crystal run --release spec/tt_hinted_diff.cr [-- fonts...]— the hinted acceptance diff: outline comparison (points + tags with the 0xE7 mask — TOUCH bits are VM internals and vary between FT versions), bitmaps and advances against FT_LOAD_DEFAULT (198 790 glyphs, 0 diff).crystal run --release spec/cff_unhinted_diff.cr [-- fonts...]— the CFF acceptance diff: outlines (points + tags + contours) and advances against FT_LOAD_NO_HINTING withno-stem-darkeningset via FFI (632 875 glyphs across 140 OTF fonts, 0 diff).crystal run --release spec/cff_hinted_diff.cr [-- fonts...]— the hinted CFF acceptance diff against FT_LOAD_DEFAULT (Adobe engine, darkening off): 632 875 glyphs across 140 OTF fonts, 0 diff.crystal run --release spec/woff2_diff.cr— the WOFF2 acceptance diff (see above). Skips itself with a note when no corpus (tmp_check/w2_*.woff2) is found, or the passed files are missing — so it is safe to invoke unconditionally; it is intentionally not part of CI (the CI oracle FreeType is built without brotli and the flag would drag libbrotlidec into the link).crystal run --release spec/ttc_diff.cr [-- fonts.ttc ...]— the collection acceptance diff: every face of a.ttc'/.otc' (default corpus: the system Noto CJK collections) diffed against the system libfreetype opened with the same face index, hinted and unhinted.crystal run --release spec/mono_lcd_diff.cr [-- fonts...]— the MONO/LCD/LCD_V acceptance diff: every glyph of the corpus rendered throughFtrender.render_glyphin each mode and compared bit-for-bit withFT_Render_Glyph(749 370 bitmaps, 0 diff). LCD modes expect the oracle libfreetype to be built with subpixel rendering.crystal run --release spec/bench_render.cr -- [total] [batch]— the performance benchmark (see above);spec/bench_ffi.cr— the FFI-call-cost microbenchmark;spec/bench_diff.cr -- [n]— a per-glyph diff of the same workload.spec/interp_probe.cr— manual VM probes on synthetic input.tmp_c/— a standalone build offtgrays.cfrom the snapshot (a second oracle for bisection: input is a text outline dump, output is coverage). NB: must be built withftint64_shim.h(theFT_INT64macro), otherwise it compiles the pre-2.13 rasterization path.spec/bisect_dbg.cr— a three-way glyph bisection (ours / C-ftgrays / system FT);spec/ffi_dbg.cr— a check of the FFI mirror of the FT structures;spec/circle_dbg.cr— synthetic cross-check against the C oracle.
Implementation notes
- FreeType applies
FT_Outline_Translatebefore outline decomposition, so conic midpoints (v_start/v_middle, C division/2truncating toward zero) round on shifted coordinates — the port applies the shift when reading points indecompose, not when upscaling. Found by debugging; kept as an invariant. - Rendering covers all four
FT_Render_Modes (NORMAL via ftgrays, MONO via ftraster1, LCD/LCD_V with the default FIR filter). Overlap handling (FT_OUTLINE_OVERLAP) is not ported. Pixel-exact LCD/LCD_V output assumes subpixel rendering is enabled in the reference libfreetype (SUBPIXEL_RENDERING); distro builds usually have it. - Square pixel sizes, grayscale (non-mono) rendering, non-tricky fonts — the scope the hint glue is specialized to (FT_LOAD_DEFAULT with v40 subpixel-hinting-minimal).
License
MIT — see shard.yml. The port is derived from the FreeType source
(FTL/MIT-licensed); the original notices live in the file headers.