oxideav-ttf
Pure-Rust TrueType font parser for the oxideav framework. Implements the sfnt container, the core OpenType tables, and just enough of GSUB / GPOS to do Latin/Cyrillic/Greek/CJK shaping with ligatures and kerning.
Round-1 scope (this release)
- sfnt + table directory walker.
head,hhea,maxp,cmap(ALL base formats — 0, 2, 4, 6, 8, 10, 12, 13 — plus format 14 Unicode Variation Sequences as a sidecar. Format 2 is the legacy mixed-8-/16-bit "high-byte mapping through table" layout for pre-Unicode CJK fonts, format 13 the "many-to-one range mappings" layout used by last-resort fonts. Format 8 is the discouraged mixed-16-/32-bit UTF-16 layout: its 8 KiBis32lead-word bit array is enforced as a validity filter in both directions on top of the format-12-style sequential group search. Format 10 is the 32-bit trimmed-array analog of format 6 for fonts covering one contiguous supplementary-plane window),name,OS/2,hmtx,loca,glyf(simple + composite — the composite path handles both component-placement forms from the "Composite glyph description" section: theARGS_ARE_XY_VALUESoffset-vector form and the point-matching form whereargument1/argument2are parent / child point numbers aligned after the child's 2×2 transform is applied, withSCALED_COMPONENT_OFFSET/UNSCALED_COMPONENT_OFFSEThonoured so a scaled offset is transformed into the parent grid while the default / unscaled offset is left raw; a reference to an unresolved phantom point degrades to zero-offset placement),post(ISO/IEC 14496-22:2019 §5.2.10 — full structural decode of v1.0 / v2.0 / v2.5 / v3.0: 32-byte common header withitalicAngle/ underline geometry /isFixedPitch/ four PostScript memory hints; v2.0numGlyphs+glyphNameIndex[]+ Pascal-formatstringData[]with the §5.2.10.2 ASCII allow-set + 63-byte recommendation surfaced throughhas_oversize_glyph_name/has_non_conformant_glyph_namediagnostic flags; v2.5int8 offset[numGlyphs]resolved per §5.2.10.3 to a standard Macintosh index; v3.0 accepted as the names-absent form (required for CFF v1 outlines per §5.2.10.4); Apple v4.0 rejected as out-of-scope.Font::glyph_name_ref(gid)exposes both theCustom(&str)(Pascal-string) andStandardMac { index }branches;Font::glyph_name(gid)is the convenience accessor returningSome(&str)for the Pascal-string branch. The 258-name standard Macintosh glyph table itself is pending docs gap #1277 — the decoder publishes the index today and the convenience accessor returnsNonefor theStandardMacbranch until the list lands),VORG(vertical origin table, ISO/IEC 14496-22:2019 §5.4.4 — header fieldsdefaultVertOriginY+ the optional sortedvertOriginYMetricsarray of per-glyph overrides, with the §5.4.4 "must be sorted by increasing glyphIndex" + "must not have more than one element with the same glyphIndex" invariants enforced at parse time; per-glyph lookup is a binary search. The §5.4.4 "If present in TrueType OFF fonts it must be ignored by font clients" rule is honoured at theFontlayer —vert_origin_y_from_vorgreturnsNonewheneverglyfis present, even when the table itself parses cleanly throughvorg_table()for tooling that wants to introspect it),vhea+vmtx(vertical-layout metrics for CJK / Mongolian fonts, ISO/IEC 14496-22:2019 §5.7.9 / §5.7.10 — bothvheaversions parse: v1.0 with the centre-line-relativeascent/descentfields and v1.1 with the ideographic-em-box typographicvertTypoAscender/vertTypoDescenderrename;vmtxcovers the long-pair array plus the §5.7.10 monospaced top-side-bearing tail),BASE(baseline table, ISO/IEC 14496-22:2019 §6.3.1 — both v1.0 and v1.1 headers, with the v1.1 trailingitemVarStoreOffsetbounds- checked and the IVS bytes surfaced for the sharedItemVariationStoredecoder; HorizAxis and VertAxis trees each decode the BaseTagList + BaseScriptList; per script, BaseScript carries an optional BaseValues table giving one BaseCoord per baseline tag plus an optional default MinMax table with per-featureFeatMinMaxRecordoverrides and an array ofBaseLangSysRecordlanguage-specific MinMax overrides; BaseCoord covers all three §6.3.1.3 formats — design-unit-only, design-unit + reference-glyph/contour-point pair, and design-unit + Device-table / VariationIndex offset for size- and instance-dependent adjustment.Font::base_horiz_y_for_script_baseline(script_tag, baseline_tag)walks the HorizAxis Y coordinate for a (script, baseline) pair; the mirrorbase_vert_x_for_script_baselinewalks the VertAxis X coordinate),gasp(grid-fitting and scan-conversion procedure table, ISO/IEC 14496-22:2019 §5.3.7 — both version 0 (pre-ClearType) and version 1 (adds the twoGASP_SYMMETRIC_*ClearType bits) parse identically; per-record(rangeMaxPPEM, rangeGaspBehavior)decoded with the four defined flags (GASP_GRIDFIT,GASP_DOGRAY,GASP_SYMMETRIC_GRIDFIT,GASP_SYMMETRIC_SMOOTHING); strictly-increasing-rangeMaxPPEMinvariant enforced at parse time so a malformed array does not shadow later records; reserved bits0xFFF0tolerated and surfaced throughGaspRange::reserved_bits();Font::gasp_behavior_for_ppem(ppem)picks the first record whoserangeMaxPPEMis at least the requested ppem, returningNonefor fonts withoutgaspor when every limit sits below the request — caller falls back to rasteriser default per §5.3.7;GaspTable::covers_all_sizes()flags the single-0xFFFF- sentinel shortcut. MVAR coupling to thegsp0..gsp9value tags is documented for variable-font interpolation ofrangeMaxPPEM),LTSH(linear-threshold table, ISO/IEC 14496-22:2019 §5.7.4 — the 4-byte header (uint16 version,uint16 numGlyphs) plus theuint8 yPels[numGlyphs]array publishing the lowest ppem at which each glyph's grid-fitted advance has converged on the rounded linear advance per §5.7.4 criterion (a)ppem ≥ 50 ∧ |Δ| ≤ 2 %or (b) exact equality; the §5.7.4 sentinelyPels = 1marks glyphs without sidebearing instructions as "always scales linearly".numGlyphsis cross-checked againstmaxpat parse time so a mismatch is rejected asBadStructureinstead of silently truncating per-glyph lookups; trailing 4-byte sfnt padding is tolerated.Font::ltsh_threshold(gid)returns the recorded ppem,Font::ltsh_linearly_scales_at_ppem(gid, ppem)honours theppem ≥ yPels[gid]inequality and falls through tofalsefor fonts withoutLTSH— §5.7.4's prescription is to grid-fit in that case, which is what the predicate signals;LtshTable:: all_always_linear()short-circuits the "every glyph carries the sentinel" common case for rasterisers that want to skip per-glyph probing. §5.7.4 nameshdmxandvdmxas the complementary precomputed-advance methods;hdmxlanded alongside this round (see below),VDMXalso landed alongside this round (see below)),hdmx(horizontal device metrics, ISO/IEC 14496-22:2019 §5.7.2 — the 8-byte header (uint16 version,int16 numRecords,int32 sizeDeviceRecord) plusnumRecordsdevice records, each carryinguint8 pixelSize+uint8 maxWidth+uint8 widths[numGlyphs]and padded to the long-word-aligned per-record stride. Each record publishes the grid-fitted integer-pixel advance widths of every glyph at a single recorded ppem so a rasteriser can short-circuit scan-converting at one of those sizes. Per-recordwidths[]length is cross-checked againstmaxp.numGlyphsat parse time (under- sizedsizeDeviceRecordrejected asBadStructure); the §5.7.2 "sorted by pixel size" invariant is enforced as strict-monotonic increase so a corrupted record cannot shadow later ones;sizeDeviceRecordis honoured as the stride so writers that long-align past the minimum body still decode with the trailing bytes ignored.Font::hdmx_advance_pixels(gid, ppem)is the per-(glyph, ppem)accessor; §5.7.2 has no "nearest neighbour" rule so an unrecorded ppem returnsNoneand the caller falls back to scan-converting.Font::hdmx_recorded_ppem_sizes()lists the recorded sizes in ascending order. §7.3.5 forbidshdmxin variable fonts — we parse it whenever present and leave the cross-check to the caller),VDMX(vertical device metrics, ISO/IEC 14496-22:2019 §5.7.8 — the 6-byte header (uint16 version,uint16 numRecs,uint16 numRatios) plus aRatioRange[numRatios]aspect-ratio selector array, a parallelOffset16[numRatios]array pointing at one VDMX group per ratio, and the VDMX groups themselves: each group is a 4-byte(recs, startsz, endsz)header followed by a sortedvTable[recs]array of(yPelHeight, yMax, yMin)tuples giving the font-wide vertical pel envelope at each recorded ppem. Both versions 0 and 1 parse identically —bCharSetsemantics differ between them but the numeric layout doesn't, so the raw byte is surfaced for the caller. The §5.7.8 "sorted by yPelHeight, need not be continuous" rule is enforced as strict-monotonic increase per group so a corrupted vTable cannot shadow later records; the(xRatio=0, yStartRatio=0, yEndRatio=0)catch-all sentinel is range-validated as the last RatioRange entry per §5.7.8 ("if present, this must be the last Ratio group in the table"); shared groups (two RatioRange entries pointing at one on-wire group) deduplicate to one parsedVdmxGroupwhile the per-ratio mapping is preserved so both ratios still resolve to the shared records.Font::vdmx_y_extent_for_device(ppem, deviceXRatio, deviceYRatio)runs the §5.7.8 "once a match is found, the search stops" first-match RatioRange walk and returns the matched group's(yMax, yMin)at the requested ppem, orNonefor a non-matching device (no sentinel) or an unrecorded ppem (no nearest-neighbour fallback); thevdmx_y_extent_squareshortcut hard-codes the common 1:1 lookup. yPelHeight isuint16so the spec's note about per-record ppem reaching 65535 is honoured even when RatioRange'suint8bracketing caps at 255. §7.3.5 forbidsVDMXin variable fonts — we parse it whenever present and leave the cross-check to the caller, matching thehdmxpolicy),meta(metadata table, ISO/IEC 14496-22:2019 §5.7.6 — the 16-byte header (uint32 version,uint32 flags,uint32 reserved,uint32 dataMapsCount) plus a sorted-by-disk-orderDataMap[dataMapsCount]array of(Tag, Offset32 dataOffset, uint32 dataLength)records and the payload bytes themselves. The table is the OpenType-level grab-bag for font-wide key/value metadata pairs keyed by four-character ASCII tags. §5.7.6.2 reserves two registered tags —'dlng'(design languages) and'slng'(supported languages), both UTF-8 ASCII text with §5.7.6.3-grammar comma-separated ScriptLangTag values — and two Apple-reserved tags ('appl','bild'); vendor-private tags follow the §5.7.6.2 paragraph 4 uppercase + digit grammar. The parser enforcesversion == 1per §5.7.6.1,flags == 0per the spec's "currently unused" mandate, the §5.7.6.2 tag character class (letter-led, letters / digits / trailing spaces only) at every DataMap.tag, the in-bounds invariant on everydataOffset + dataLengthslice (out-of-range payload rejected asBadStructure), and capsdataMapsCountat 1024 to bound worst-case allocation. Thereservedfield is surfaced rather than validated — §5.7.6.1's NOTE acknowledges that legacy Apple TrueType fonts may carry a non-zero data offset there. Document order is preserved inMetaTable::records()so tooling can round-trip the table without re-sorting.Font::has_meta()andFont::meta_table()expose the parsed table;Font::meta_record(tag)returns the first record matching a tag (honouring §5.7.6.1's "any instances after the first may be ignored" rule for the registered single-record tags);Font::meta_design_languages()andFont::meta_supported_languages()are convenience accessors that decode'dlng'/'slng'payloads as UTF-8 text. A free-functionscript_lang_tags(payload)splits a'dlng'/'slng'value into the §5.7.6.3 ScriptLangTag fragments — comma-separated, trimmed, non-empty, ASCII-only, and rejecting leading / trailing / doubled hyphens per the spec's BNF; deeper validation against the IANA Language Subtag Registry and ISO 15924 stays in the caller),PCLT(PCL 5 table, ISO/IEC 14496-22:2019 §5.7.7 — the fixed 54-byte struct of PCL 5 font-selection attributes, "strongly discouraged for OFF fonts with TrueType outlines" per the spec but still shipped by legacy faces. Every packed word decodes through typed accessors: FontNumber splits into the native-vs-converted MSB, the 7-bit HP-assigned vendor letter, and the 24-bit vendor-assigned id; Style splits into structure (bits 5–9) / appearance width (bits 2–4) / posture (bits 0–1) with the reserved top 6 bits surfaced; TypeFamily splits into the 4-bit HP vendor code + 12-bit family code; SymbolSet follows the §5.7.7 rule "the least significant 5 bits, when added to 64, is the ASCII value of the symbol set ID field" (all eight spec example values round-trip, e.g. 629 → 19U). The 16-byte Typeface and 6-byte FileName ASCII fields trim trailing pad to&str(with raw-byte fallbacks), the 8-byte CharacterComplement decodes to a big-endian u64 withprovides_collection(bit)honouring the cleared-bit-means- provided polarity established by the spec's worked examples andis_unicode_indexed()reading bit 0 per "Bit 0 must always be cleared when the font elements are provided in Unicode order"; StrokeWeight / WidthType surface raw with*_is_valid()range checks against the §5.7.7 "-7 to 7" / "-5 to 5" validity sentences, and SerifStyle splits into the 6-bit serif value + 2-bit serif/contrast class.majorVersion != 1is rejected per "The current PCLT table version is 1.0";minorVersionand the trailing Reserved pad byte are surfaced raw.Font::has_pclt()/Font::pclt_table()expose the parsed table),nametable: full accessor API beyond family / full name — the registered nameID registry (name_idconstants), typed accessors (subfamily, PostScript, version, copyright, trademark, manufacturer, designer, description, vendor / designer / licence URLs, typographic family / subfamily with the TN5149 §1.4 fallback), exact-locale lookup (name_string_for(id, platform, language)), and full record enumeration (name_records()→NameRecordwith the(platform, encoding, language, name)locator tuple). Macintosh non-Roman scripts surface their locator + raw bytes but decode toNone(legacy codepage tables not staged).- Legacy
kerntable (format 0). GSUBLookupType 1 (single substitution: positional forms, small-caps, vertical alternates), LookupType 2 (multiple substitution — split one input glyph into N), LookupType 3 (alternate substitution —aalt/saltper-coverage alternates), LookupType 4 (ligature substitution — exposed both as a "walk every lookup" helper and as a lookup-index-specific apply path for feature-driven shaping ofliga/rlig/dlig), LookupType 5 (contextual substitution — formats 1 / 2 / 3, predecessor of LT6 minus backtrack/lookahead), LookupType 6 (chained contexts substitution — formats 1 / 2 / 3, with recursive dispatch into nested LookupType 1 / 2 / 3 / 4 / 5 / 6 sub-lookups), and LookupType 8 (reverse chained context single substitution — used by some Arabic fonts). All sit behind a ScriptList / FeatureList walk so callers can ask "which lookup indices implement featureinitfor scriptarab?"GSUBFeatureVariations (ISO/IEC 14496-22:2019 §6.2.9) — the version-1.1 header'sfeatureVariationsOffsetis decoded so a variable font can swap the lookups behind a feature for an alternate set at the current variation instance (the canonical use is optical-size- or weight-conditional substitution). The sharedFeatureVariations/ConditionSet/ConditionTableFormat1(font variation axis range, the only defined condition format) /FeatureTableSubstitutionsubstructure evaluates each record's AND-ed condition set against the avar-bent normalised coordinate vector and applies the §6.2.9 first-match rule (universal-match on a zero condition-set offset; unrecognised condition formats and unsupported substitution-table versions both fail the record so a later record can win, the spec's forward-compatibility behaviour).Font::gsub_features_for_script_at_instance(script, lang)returns the per-feature lookup lists for the current instance — identical togsub_features_for_scriptfor static fonts, v1.0 headers, or instances matching no condition set;Font::gsub_has_feature_variations()gates. Set the instance withset_variation_coordsfirst. Alternate feature tables keep the default feature's tag per §6.2.9.GPOSLookupType 1 (single positioning — formats 1 + 2), LookupType 2 (pair-adjustment / kerning), LookupType 3 (cursive attachment — entry/exit anchor pairs for Arabic Nastaliq + script-font cursive chaining), LookupType 4 (mark-to-base attachment), LookupType 5 (mark-to-ligature attachment — closes thefi-ligature + above-mark gap), LookupType 6 (mark-to-mark stacking), LookupType 7 (contextual positioning —SequenceContextformats 1 / 2 / 3, the non-chained sibling of LT 8, with nested LT 1 / 2 / 3 / 4 / 6 / 7 / 8 dispatch), and LookupType 8 (chained-context positioning — formats 1 / 2 / 3, with nested LT 1 / 2 / 3 / 4 / 6 / 8 dispatch). ExtensionPos (LookupType 9) is unwrapped transparently — both at the sub-table level (a LT-9 sub-table inside any lookup) and at the lookup level (a whole lookup whoselookupTypeis 9 wrapping any of the supported inner types).Font::gpos_lookup_list()+Font::gsub_lookup_list()enumerate every lookup as(index, effective_type, subtable_count)for shapers that need to find e.g. every chained-context lookup without probing each index.GDEFglyph-definition table — v1.0 / v1.2 / v1.3 headers, withglyphClassDef(skip-mark filter for GPOS / GSUB),AttachList(per-glyph contour-point indices),LigCaretList(per-ligature caret coordinates asCaretValue::DesignUnits/ContourPoint/DesignUnitsWithDevice),MarkAttachClassDef(the class compared againstlookupFlag.markAttachmentType),MarkGlyphSetsDef(the Offset32 Coverage arrays consulted bylookupFlag.useMarkFilteringSet), and anitem_var_store_bytes()raw slice for the v1.3 ItemVariationStore feeding CaretValueFormat3 VariationIndex references through the same IVS decoder shared with MVAR / HVAR / VVAR.- sbix
'dupe'indirection chasing (sbix_glyph_resolved): walks the per-strike indirection chain up toSBIX_MAX_DUPE_DEPTH(= 8) hops with explicit cycle detection (two-glyph, self-loop, and forward-chain overflow all bail toNone). The rawsbix_glyphaccessor still surfaces the'dupe'sentinel untouched for byte-level consumers. EBDT/EBLCembedded monochrome + grayscale bitmaps (ISO/IEC 14496-22:2019 §5.6.2 / §5.6.3) — the location side (EBLC) reuses the sameCblcTablewalker that drivesCBLC(it already accepts themajorVersion == 2header and all five IndexSubTable formats 1–5), so this round adds theEBDTimage-data decoder for the five bit-packed §5.6.2.2 pixel formats: format 1 (small metrics, byte-aligned), 2 (small metrics, bit-aligned), 5 (bit-aligned data only, metrics lifted from the EBLC IndexSubTable 2/5BigGlyphMetrics), 6 (big metrics, byte-aligned) and 7 (big metrics, bit-aligned). EachbitDepthof 1 / 2 / 4 / 8 (§5.6.3.1) is unpacked MSB-first, left-to-right, top-to-bottom into awidth × heightrow-major grid of one alpha-coverage byte per pixel, thebitDepth-bit sample scaled to the full 0..=255 range (1-bit "1 = black" →0xFF). The byte-aligned formats pad each row up to a byte boundary; the bit-aligned formats pack the whole glyph contiguously.Font:: glyph_gray_bitmap(gid, target_ppem)returns aGrayBitmapfrom the closest strike (same closest-ppem-with-larger-wins tie-break as the colour path);Font::has_gray_bitmaps()/Font::gray_strike_sizes()gate and enumerate. Format 4 (compressed) and formats 8 / 9 (composite) decode toNone;bitDepth == 32(BGRA) routes to theCBDTcolour path instead.EBSC(scaled-strike substitution, §5.6.4) is not yet decoded.- Adobe Glyph List (AGL) glyph-name → Unicode resolution
(
glyph_name_to_codepoints/glyph_name_to_char). Direct table lookup against the embedded AGL 2.0 data: a PostScript glyph name (e.g. from apostv2 table or a CFF charset) maps to its Unicode scalar-value sequence; ligature / base+points names yield a short sequence. The AGL Specification's algorithmic fallback (suffix stripping,uniXXXXsynthetic names) is intentionally out of scope — only the stagedglyphlist.txtdata drives it. MVAR(font-wide Metrics Variations, ISO/IEC 14496-22:2019 §7.3.6) with the sharedItemVariationStoresubstructure (§7.2.3) decoded inline:Font::metric_variation_delta(tag)returns the interpolated signed adjustment for any §7.3.6.3 metric tag ('xhgt', 'cpht', 'stro', 'unds', 'hasc', 'hdsc', 'gsp0'…'gsp9', …) at the current variation coordinates. Region scalars are computed per §7.1 (peak-0 axes ignored, opposite-sign coords zero, linear interpolation on rising / falling edges),avaris honoured so wght=700 with a non-identity axis-value map produces the bent normalised value the spec mandates, and thevalueRecordSizefield is treated as the record stride so minor-version bumps that grow ValueRecord (per the §7.3.6.1 note) decode correctly with the unknown trailing bytes ignored.HVAR(per-glyph horizontal-metrics variations, ISO/IEC 14496-22:2019 §7.3.5) with the sameItemVariationStoresubstructure shared with MVAR:Font::advance_width_variation_delta(gid)returns the interpolated advance-width adjustment forglyph_idat the current variation coordinates, andFont::lsb_variation_delta(gid)/Font::rsb_variation_delta(gid)cover the optional side-bearing mappings. The optionalDeltaSetIndexMapsub-table (§7.3.5.2) is decoded for all four supported entry sizes (1 / 2 / 3 / 4 bytes per entry, 1..16 inner-index bits) with the §7.3.5.2 "glyph IDs beyond mapCount-1 use the last entry" clamp; whenadvanceWidthMappingOffsetis zero, the §7.3.5.3 implicit form (outer = 0, inner = glyph ID) is used instead.VVAR(per-glyph vertical-metrics variations, ISO/IEC 14496-22:2019 §7.3.8) reusing the sameItemVariationStore+DeltaSetIndexMapsubstructures as HVAR.Font::advance_height_variation_delta(gid)returns the interpolated advance-height adjustment forglyph_id;Font::tsb_variation_delta(gid)andFont::bsb_variation_delta(gid)cover the optional top- and bottom-side-bearing mappings; andFont::vorg_variation_delta(gid)covers the CFF2-only vertical- origin Y mapping (§7.3.8.2 final paragraph: "Mappings and variation data for vertical origins are not used in fonts with TrueType outlines"). The implicit "outer=0, inner=gid" form applies to advance heights whenadvanceHeightMappingOffsetis zero, matching the §7.3.8.2 cross-reference back to §7.3.5.3.STAT(style attributes table, ISO/IEC 14496-22:2019 §7.3.7) — v1.0 / v1.1 / v1.2 headers (the v1.0 deprecated form is parsed and its missingelidedFallbackNameIDdefaulted to the conventional name ID 2 = "Regular").Font::stat_axes()exposes the §7.3.7.2 design-axis records (axisTag/axisNameID/axisOrdering) with thedesignAxisSizestride honoured so future minor-version growth decodes transparently.Font::stat_axis_values()exposes every §7.3.7.3 axis value record — format 1 (single value), format 2 (nominal +[rangeMin, rangeMax]with the0x80000000/0x7FFFFFFF±∞ sentinels surfaced asSTAT_RANGE_MIN_NEG_INFINITY/STAT_RANGE_MAX_POS_INFINITY), format 3 (single +linkedValuefor style-linked Bold/Italic UI), and format 4 (multi-axis combinations for non-analytic instance names with the spec's "different axisIndex per record" rule enforced). TheOLDER_SIBLING_FONT_ATTRIBUTE/ELIDABLE_AXIS_VALUE_NAMEflag bits are decoded intois_older_sibling_font_attribute()/is_elidable()convenience accessors, andFont::stat_axis_values_for_tagfilters the array down to a single axis (resolving format-4 contributors that touch it).
The companion oxideav-scribe
crate consumes the outlines + shaping output to rasterise text to RGBA
bitmaps for subtitles and the scene compositor.
Public API
use Font;
let bytes = read?;
let font = from_bytes?;
// Metadata.
let _ = font.family_name; // Some("DejaVu Sans Mono")
let _ = font.units_per_em; // 2048
let _ = font.glyph_count;
let _ = font.ascent;
let _ = font.descent;
let _ = font.line_gap;
// name-table strings — typed accessors for the well-known nameIDs.
let _ = font.subfamily_name; // Some("Book")
let _ = font.postscript_name; // Some("DejaVuSans")
let _ = font.version_string; // Some("Version 2.37")
let _ = font.copyright;
let _ = font.license_url;
let _ = font.vendor_url;
// Exact locale (no ranking): the Japanese family name, if the font
// ships one. (name_id::FAMILY, platform::WINDOWS, 0x0411 = ja-JP)
use ;
let _ = font.name_string_for;
// Enumerate every name record with its (platform, encoding, language,
// nameID) locator tuple.
for rec in font.name_records
// Glyph lookup.
let gid_a = font.glyph_index.unwrap;
let _ = font.glyph_advance; // i16 advance width in font units
let _ = font.glyph_lsb;
let _ = font.glyph_bounding_box;
let _ = font.glyph_outline?; // contours of i16 points
// Vertical-layout metrics (vhea + vmtx). Present only on fonts that
// support top-to-bottom writing — typically CJK faces. The accessors
// return None on horizontal-only fonts.
if font.has_vertical_metrics
// VORG — vertical origin (§5.4.4). CFF-flavoured CJK sfnts ship this
// to give the canonical Y of each glyph's vertical origin without the
// caller having to compute a bbox; the spec restricts the table to
// CFF sfnts so `vert_origin_y_from_vorg` returns None for any font
// with a `glyf` table (which is the §5.4.4 ignore policy).
if font.has_vorg
// Shaping helpers.
let gid_f = font.glyph_index.unwrap;
let gid_i = font.glyph_index.unwrap;
if let Some = font.lookup_ligature
let gid_v = font.glyph_index.unwrap;
let _ = font.lookup_kerning; // negative i16 in font units
// GSUB feature-tagged lookups (Arabic positional forms, small-caps, …).
// Discover which lookup indices implement `init` for script `arab`,
// then apply LookupType 1 to a single glyph id.
for feat in font.gsub_features_for_script
// LookupType 4 — ligature substitution dispatched per-feature.
// Resolve the `liga` feature for `latn` and apply each of its
// LookupType-4 lookups to a glyph run; the apply method returns
// (replacement_gid, consumed_count) on a hit.
for feat in font.gsub_features_for_script
// LookupType 6 — chained-context substitution. Returns the rewritten
// run starting at `pos` (or None when no chain rule matches the
// (backtrack, input, lookahead) window). Formats 1 (glyph sequence),
// 2 (class-based) and 3 (coverage-based) are all supported.
for feat in font.gsub_features_for_script
// LookupType 2 — multiple substitution. Splits one input glyph into a
// sequence of replacement glyphs (e.g. some script normalisations that
// expand a precomposed glyph into base + mark cluster).
let some_gid = 42u16;
if let Some = font.gsub_apply_lookup_type_2
// LookupType 3 — alternate substitution. Each covered glyph carries an
// AlternateSet of alternates; the caller picks an index. Used by `aalt`
// / `salt` features.
for feat in font.gsub_features_for_script
// LookupType 5 — contextual substitution (LT6 minus backtrack/lookahead).
// Same return shape as LookupType 6.
for feat in font.gsub_features_for_script
// LookupType 8 — reverse chained context single substitution. Returns
// the replacement gid for `gids[pos]` when the (backtrack, input,
// lookahead) coverage triple matches. The spec mandates reverse-text
// processing: a higher-level shaper walks `pos` from right to left.
for feat in font.gsub_features_for_script
// GPOS LookupType 1 — single-glyph positioning. Returns four signed
// i16 deltas: x_placement / y_placement / x_advance / y_advance. Used
// by features like `cpsp` (capital spacing).
for in font.gpos_lookup_list
// GPOS LookupType 3 — cursive attachment. Returns a CursiveAttachment
// with (entry, exit) anchor points (each Option). Chain glyph N+1's
// entry onto glyph N's exit: per-glyph delta = prev.exit - this.entry.
if let Some = font.lookup_cursive_attachment
// GPOS LookupType 5 — mark-to-ligature attachment. Pick the ligature
// component the mark sits over (0-indexed: 0 = first component, etc.).
// Returns (dx, dy) to shift the mark's pen origin.
if let Some = font.glyph_index
// GPOS LookupType 8 — chained-context positioning. Returns a Vec of
// PosRecord(absolute glyph index, four-field PosValue). The shaper
// folds these deltas into its own glyph-position state.
for in font.gpos_lookup_list
// LookupList enumeration — find every lookup of a given (effective,
// post-extension-unwrap) type without probing each index in turn.
let chain_pos_lookups: = font
.gpos_lookup_list
.into_iter
.filter_map
.collect;
let _ = chain_pos_lookups;
// Unicode Variation Sequences (cmap format 14). Used by emoji
// presentation selectors and registered IVS for CJK.
let _ = font.lookup_variation; // grinning face + VS-16
// Colour glyphs — three families covered:
//
// COLR/CPAL: vector layer stack (Microsoft Segoe UI Emoji, Twemoji-Mozilla, …)
// CBDT/CBLC: PNG-payload bitmap strikes (Noto Color Emoji and friends)
// sbix: Apple-style PNG/JPEG bitmap strikes (Apple Color Emoji)
//
if font.has_color_layers
if font.has_color_bitmaps
if font.has_sbix
// TTC (TrueType Collection) — pick one subfont from a `.ttc` file.
let _ = is_collection;
let _ = from_collection_bytes;
// Variable fonts (fvar / avar / gvar). Pick a coord vector in
// user-space units (e.g. wght 100..900); glyph_outline() then
// returns the gvar-deltad outline.
let mut vfont = from_bytes?;
if vfont.is_variable
Out of scope (round 2+)
- CFF / Type 2 charstrings — moves to a sibling
oxideav-otfcrate. - Bidi, Arabic shaping, Indic conjuncts, complex contextual GSUB/GPOS.
- TrueType bytecode hinting (modern AA at ≥ 16 px does not need it).
cmap formats 8 and 10landed in r276 — every base cmap subtable format (0, 2, 4, 6, 8, 10, 12, 13) plus the format-14 UVS sidecar is now decoded; see above.- All GPOS lookup types except LookupType 7 (the now-fully-handled LookupType 9 ExtensionPos wrapper plays its role) are implemented: 1 (single), 2 (pair), 3 (cursive attachment), 4 (mark-to-base), 5 (mark-to-ligature), 6 (mark-to-mark), 8 (chained context with nested LT 1/2/3/4/6/8 dispatch). All seven public GSUB lookup types (1 single, 2 multiple, 3 alternate, 4 ligature, 5 contextual, 6 chained context, 8 reverse chained context) are implemented; ExtensionSubst LookupType 7 (GSUB) and ExtensionPos LookupType 9 (GPOS) are unwrapped transparently for every type both at the sub-table and lookup level.
- COLR v1 paint graph (gradients, transforms, composites) — only the v0 flat layer stack is supported.
- avar v2 delta-set index map (variable-axis remap).
- gvar delta propagation into composite-glyph component offsets and the four phantom points.
- STAT (style attributes) landed in r217 — see above. The format-2 overlapping-range tie-break (§7.3.7.3) is documented as caller policy; we expose the full document-order record array unchanged.
- GSUB FeatureVariations (§6.2.9) landed this round — see above. The
identical substructure on the GPOS v1.1 header is not yet wired
(GPOS has no
features_for_scriptwalker today); the sharedtables::feature_variationsdecoder is ready to drive it once that walker exists.
Test fixtures
tests/fixtures/DejaVuSansMono.ttfis the upstream DejaVu Sans Mono 2.37 under the Bitstream Vera license (seetests/fixtures/DEJAVU-LICENSE).tests/fixtures/InterVariable.ttfis Inter 4.0 (variable font,wght+opszaxes) under the SIL Open Font License 1.1 (seetests/fixtures/INTER-OFL-LICENSE.txt).tests/fixtures/NotoSansArabic-Regular.ttfis Noto Sans Arabic 2022 (used to exercise GSUB feature-tagged single substitution for thearabscript's positional joining forms) under the SIL Open Font License 1.1 (seetests/fixtures/NOTO-ARABIC-OFL-LICENSE.txt).
License
MIT — see LICENSE.