TrueType/OTF(CFF)/WOFF/WOFF2 font parser for MoonBit
Dependencies
let data : Bytes = ... // TTF, OTF, WOFF, WOFF2, or TTC file bytes
let font = @font.parse_font(data).unwrap()
// Get glyph outline as SVG path commands
let cmds = font.scaled_outline('A'.to_int(), 48.0)
// Get glyph metrics
let gid = font.glyph_index('A'.to_int())
let metrics = font.glyph_metrics(gid)
// Text layout with kerning
let positions = font.layout_text("Hello", 48.0)
let width = font.measure_text("Hello", 48.0)
// Variable font with axis values
let cmds = font.char_outline_at('A'.to_int(), { "wght": 700.0 })
// Vertical text layout
let vpositions = font.layout_text_vertical("縦書き", 48.0)
let vheight = font.measure_text_vertical("縦書き", 48.0)
// Font subsetting (glyf-based TrueType only)
let subset = @font.subset_font(data, [0x41, 0x42, 0x43]) // A, B, C| Format | Notes | |
|---|---|---|
| TTF (TrueType) | ✅ | sfnt flavor 0x00010000 and 0x74727565 |
| OTF (OpenType/CFF) | ✅ | sfnt flavor OTTO |
| WOFF1 | ✅ | zlib decompression |
| WOFF2 | ✅ | Brotli decompression with glyf/loca/hmtx transforms |
| TTC/OTC (Font Collections) | ✅ | Index-based or first-font access |
| Table | Description |
|---|---|
| head | units_per_em, indexToLocFormat |
| maxp | numGlyphs |
| hhea | ascent, descent, lineGap, numOfLongHorMetrics |
| hmtx | advanceWidth, leftSideBearing per glyph |
| cmap | Format 0, 4 (BMP), 6, and 12 (full Unicode) |
| loca | Short and long formats |
| glyf | Simple and compound glyphs |
| CFF | Type 2 charstrings with subroutines |
| CFF2 | Variable font support with blend/vsindex |
| fvar | Axis definitions (wght, wdth, opsz, etc.) |
| avar | Piecewise linear axis value mapping |
| gvar | TrueType delta interpolation with IUP |
| kern | Format 0 horizontal pairs |
| name | Windows Unicode, Unicode platform, Mac Roman |
| OS/2 | Weight/width class, PANOSE, x-height, cap-height |
| post | Italic angle, fixed pitch, glyph names (v2.0) |
| vhea/vmtx | Vertical metrics — advance heights and top side bearings |
| gasp | Grid-fitting and scan-conversion ranges |
| VORG | Vertical origin Y coordinates for CFF glyphs |
| Table | Description |
|---|---|
| GSUB | Glyph substitution — ligatures, contextual alternates, localized forms |
| GPOS | Glyph positioning — pair adjustment, mark-to-base, mark-to-mark |
| GDEF | Glyph definition — glyph classes, ligature caret positions, mark sets |
| BASE | Baseline offsets for mixing scripts (e.g. Latin + CJK in one line) |
| JSTF | Justification alternatives for full-justified text |
| Table | Description |
|---|---|
| COLR/CPAL | Color layers with palettes (COLRv0/v1 color fonts) |
| SVG | SVG glyph documents (SVG-in-OpenType color fonts) |
| CBDT/CBLC | Color bitmap glyphs (Google/Android emoji) |
| sbix | Apple bitmap glyphs (Apple emoji) |
| EBDT/EBLC | Monochrome/grayscale embedded bitmaps (legacy low-res screens) |
| Table | Description |
|---|---|
| MATH | Math layout constants and glyph assembly — only for TeX-like math typesetting engines |
| cvt/fpgm/prep | TrueType hinting programs — grid-fitting instructions for low-DPI rasterization. Irrelevant for SVG/vector output |
| hdmx | Pre-computed device widths for specific PPEMs — legacy optimization for bitmap rendering |
| Table | Description |
|---|---|
| morx/mort | Apple Advanced Typography — state-machine-based shaping |
| Table | Description |
|---|---|
| DSIG | Digital signature — font authenticity verification, does not affect rendering |
| cvar | CVT variations — hinting value adjustments in variable fonts, only relevant if hinting is executed |
| Category | Operators | |
|---|---|---|
| Move | ✅ | rmoveto, hmoveto, vmoveto |
| Line | ✅ | rlineto, hlineto, vlineto |
| Curve | ✅ | rrcurveto, hhcurveto, vvcurveto, hvcurveto, vhcurveto |
| Mixed | ✅ | rcurveline, rlinecurve |
| Flex | ✅ | flex, hflex, vflex, hflex1, flex1 |
| Hint | ✅ | hstem, vstem, hstemhm, vstemhm, hintmask, cntrmask |
| Subroutine | ✅ | callsubr, callgsubr, return |
| Arithmetic | ✅ | add, sub, mul, div, neg, abs, sqrt, eq |
| Logic | ✅ | and, or, not, ifelse |
| Stack | ✅ | dup, exch, drop, index, roll, put, get |
| CFF2 Variation | ✅ | blend, vsindex |
| Control | ✅ | endchar |
| Deprecated | ➖ | seac, dotsection — removed from modern specs |
| Feature | Notes | |
|---|---|---|
| Simple glyphs (TrueType) | ✅ | |
| Compound glyphs (TrueType) | ✅ | Scale, 2x2 matrix, XY offset transforms |
| CFF1 charstrings | ✅ | Local/global subroutines |
| CFF2 charstrings | ✅ | Blend interpolation |
| TrueType variations (gvar) | ✅ | Shared tuples, IUP, delta unpacking |
| CFF2 variations (ItemVariationStore) | ✅ | Region scalars, blend deltas |
| Hinting / instruction execution | ❌ | Not needed for vector/SVG output |
| Format | Coverage | |
|---|---|---|
| Format 0 | ✅ | Mac Roman 256-char |
| Format 4 | ✅ | BMP (U+0000–U+FFFF) |
| Format 6 | ✅ | Trimmed table |
| Format 12 | ✅ | Full Unicode (preferred when available) |
| Format 2 | ❌ | CJK mixed 8/16-bit — obsolete encoding, replaced by Format 12 |
| Format 14 | ❌ | Unicode Variation Sequences — needed for CJK glyph variants (e.g. JP vs CN forms) |
| Feature | Notes | |
|---|---|---|
| kern table Format 0 (flat pairs) | ✅ | Covers most Latin fonts |
| kern table Format 1 (Apple state machine) | ❌ | Apple-only, rare in cross-platform fonts |
| GPOS pair adjustment (PairPos) | ❌ | More precise than kern, used by modern fonts |
| GPOS contextual kerning | ❌ | Context-dependent spacing adjustments |
| Feature | Notes | |
|---|---|---|
| Glyf-based TrueType subsetting | ✅ | |
| Compound glyph dependency resolution | ✅ | |
| Glyph ID remapping | ✅ | |
| cmap Format 12 rebuild | ✅ | |
| Table copy (head, hhea, hmtx, maxp, name, OS/2, post) | ✅ | |
| CFF/CFF2 subsetting | ❌ | CFF charstring rewriting is complex |
| WOFF/WOFF2 output | ❌ | Outputs raw sfnt — wrap with external compressor |
| Layout table subsetting (GSUB/GPOS) | ❌ | Requires lookup/coverage rewriting |
| Feature | Notes | |
|---|---|---|
| Horizontal advance widths | ✅ | |
| Pair kerning (kern table) | ✅ | |
| UTF-16 surrogate pair handling | ✅ | |
| Text width measurement | ✅ | |
| OpenType shaping (GSUB/GPOS) | ❌ | Full shaping engine — needed for Arabic, Devanagari, ligatures |
| Bidirectional text | ❌ | Unicode BiDi algorithm — needed for mixed LTR/RTL text |
| Vertical layout | ✅ | Top-to-bottom CJK typesetting (vhea/vmtx/VORG) |
| Line breaking | ❌ | Text wrapping — typically handled by the application layer |
| Function | Returns | Description |
|---|---|---|
| loadFont(data) | JSON string | Parse font, return metrics |
| getFontInfo() | JSON string | Get cached font info |
| glyphToSvgPath(codepoint, fontSize) | SVG path string | Scaled glyph outline |
| glyphAdvance(codepoint, fontSize) | Double | Scaled advance width |
| fontName(nameId) | String | Name table lookup |
| kernAdvance(cp1, cp2, fontSize) | Double | Scaled kerning value |
| layoutText(text, fontSize) | JSON string | Glyph positions with kerning |
| measureText(text, fontSize) | Double | Total text width |
| fontWeightClass() | Int | OS/2 weight class |
| isFixedPitch() | Int (0/1) | Post table fixed-pitch flag |
| codepointCoverage() | JSON string | All supported codepoints |
| glyphIds() | JSON string | All glyph IDs (0 to numGlyphs-1) |
| tableSizes() | JSON string | Table tag to byte size |
# Download test fixtures and run tests
just test
# Or manually
bash scripts/download-fixtures.sh
moon test
# Type check
just check
# Format code
just fmtpub(all) struct AtlasPageInfo {
page_id : Int
width : Int
height : Int
dirty : Bool
}impl Show for AtlasPageInfopub(all) struct BinaryReader {
data : Bytes
pos : Int
}pub(all) struct CFFData {
charstrings : CFFIndex
global_subrs : CFFIndex
local_subrs : CFFIndex
default_width_x : Int
nominal_width_x : Int
is_cff2 : Bool
ivs : ItemVariationStore?
}pub(all) struct FontKey {
family : String
weight : Int
italic : Bool
}pub struct GlyphAtlas {
cache : GlyphCache
width : Int
height : Int
pixels : Array[Int]
dirty : Bool
last_used_generation : Int
}fn GlyphAtlas::rasterize_glyph(self : GlyphAtlas, font : TTFont, codepoint : Int, size_px : Double) -> GlyphCacheEntry?impl Show for GlyphBitmappub struct GlyphCache {
entries : Map[Int, GlyphCacheEntry]
next_x : Double
next_y : Double
row_height : Double
atlas_width : Double
atlas_height : Double
atlas_page_id : Int
}fn GlyphCache::allocate(self : GlyphCache, glyph_id : Int, width : Double, height : Double) -> GlyphCacheEntry?pub(all) struct GlyphCacheEntry {
glyph_id : Int
atlas_x : Double
atlas_y : Double
atlas_w : Double
atlas_h : Double
atlas_page_id : Int
}impl Show for GlyphCacheEntrypub(all) struct GlyphPosition {
codepoint : Int
glyph_id : Int
x_offset : Double
y_offset : Double
x_advance : Double
}pub(all) struct GlyphQuad {
glyph_id : Int
atlas_x : Double
atlas_y : Double
atlas_w : Double
atlas_h : Double
dst_x : Double
dst_y : Double
dst_w : Double
dst_h : Double
}pub(all) struct OS2Table {
version : Int
us_weight_class : Int
us_width_class : Int
fs_type : Int
panose : FixedArray[Int]
ul_unicode_range1 : Int
ul_unicode_range2 : Int
ul_unicode_range3 : Int
ul_unicode_range4 : Int
fs_selection : Int
s_typo_ascender : Int
s_typo_descender : Int
s_typo_line_gap : Int
us_win_ascent : Int
us_win_descent : Int
sx_height : Int
s_cap_height : Int
}pub(all) struct PostTable {
italic_angle : Double
underline_position : Int
underline_thickness : Int
is_fixed_pitch : Bool
glyph_names : Array[String]
}pub(all) struct TTFont {
units_per_em : Int
index_to_loc_format : Int
num_glyphs : Int
ascent : Int
descent : Int
line_gap : Int
num_h_metrics : Int
glyf_offset : Int
data : Bytes
cmap : Map[Int, Int]
loca : Array[Int]
advance_widths : Array[Int]
left_side_bearings : Array[Int]
cff : CFFData?
gvar : GvarData?
var_axes : Array[VarAxis]
kern_pairs : Map[Int, Int]
avar_segments : Array[Array[(Double, Double)]]
name_records : Map[Int, String]
os2 : OS2Table?
post : PostTable?
vertical : VerticalMetrics?
gasp : Array[GaspRange]
vorg : VorgData?
}fn TTFont::char_outline_at(self : TTFont, codepoint : Int, axis_values : Map[String, Double]) -> Array[PathCommand]fn TTFont::glyph_metrics_at(self : TTFont, glyph_id : Int, axis_values : Map[String, Double]) -> GlyphMetricsfn TTFont::glyph_outline_at(self : TTFont, glyph_id : Int, axis_values : Map[String, Double]) -> Array[PathCommand]fn TTFont::layout_text_vertical(self : TTFont, text : String, font_size : Double) -> Array[GlyphPosition]pub(all) struct TextMetrics {
width : Double
height : Double
baseline : Double
}impl Show for TextMetricspub struct TextRenderer {
pages : Array[GlyphAtlas]
font : TTFont
pipeline_id : Int
atlas_size : Int
base_page_id : Int
max_pages : Int
generation : Int
}fn TextRenderer::ensure_glyph(self : TextRenderer, codepoint : Int, size_px : Double) -> (Int, GlyphCacheEntry)?fn TextRenderer::new(font : TTFont, atlas_size : Int, base_page_id : Int, pipeline_id : Int) -> TextRendererfn TextRenderer::new_with_options(font : TTFont, atlas_size : Int, base_page_id : Int, pipeline_id : Int, max_pages : Int) -> TextRendererpub(all) struct TextStyle {
font : FontKey
size_px : Double
line_height : Double
letter_spacing : Double
}pub(all) struct VarAxis {
tag : String
min_value : Double
default_value : Double
max_value : Double
}pub(all) struct VarRegionAxis {
start_coord : Double
peak_coord : Double
end_coord : Double
}fn blit_to_atlas(atlas_pixels : Array[Int], atlas_width : Int, src : GlyphBitmap, dst_x : Int, dst_y : Int) -> Unitfn bytes_slice(data : Bytes, offset : Int, length : Int) -> Bytesfn calc_table_checksum(data : Bytes) -> Intfn make_glyph_cache_key(glyph_id : Int, size_px : Double) -> Intfn parse_avar(reader : BinaryReader, offset : Int, axis_count : Int) -> Array[Array[(Double, Double)]]fn parse_hmtx(reader : BinaryReader, offset : Int, num_glyphs : Int, num_h_metrics : Int) -> (Array[Int], Array[Int])fn parse_vmtx(reader : BinaryReader, offset : Int, num_glyphs : Int, num_v_metrics : Int) -> (Array[Int], Array[Int])fn scale_commands(commands : Array[PathCommand], scale : Double, flip_y : Bool) -> Array[PathCommand]TrueType/OTF(CFF)/WOFF/WOFF2 font parser for MoonBit
Dependencies