oxideav-scribe
Pure-Rust font rasterizer + simple shaper + line layout for the
oxideav framework. Sits on top of
oxideav-ttf (outlines,
GSUB/GPOS lookups) and oxideav-pixfmt
(Porter-Duff alpha compositing) to turn a UTF-8 string + a face into an
RGBA bitmap suitable for a subtitle track or a scene compositor.
Round-2 scope (this release)
Round 2 closes the four deferrals flagged by the subtitle integration:
- Synthesised italic —
Style { italic: bool, weight: u16 }plumbed through shaping + rasterisation. When the requested style is italic AND the underlying face'spost.italicAngleis ~0 (upright), the rasterizer applies a 12° horizontal shear at outline-flatten time; when the face is already italic, no double-shear happens. - Per-run colour — verified end-to-end: cached glyph bitmaps are alpha-only, the run colour mixes in at compose time. Two back-to-back calls with different colours produce different RGB at glyph pixels but identical alpha shape.
- Font fallback —
FaceChainwalks an ordered list of faces. For each codepoint, the first face whoseglyph_indexreturns a real (non-.notdef) glyph wins; if no face has it, the primary's.notdefis used.PositionedGlyph::face_idxtells the rasterizer which face to fetch the outline from. - Text outline / stroke —
compose_run_with_strokepaints a dilated alpha-mask underneath the fill, matching the\bordsemantics that mpv / libass and the ffmpegsubtitlesfilter ship. Round 2 uses circular max-filter dilation (fast, looks identical to ffmpeg at typical 1–3 px bord values); true offset-curve geometry is a round-3 lift.
Round-1 scope (still in)
- Outline flattening — quadratic-Bezier subdivision via the classic de Casteljau split (chord tolerance 0.5 px). Implements the TrueType implicit-on-curve rule for adjacent off-curve handles, plus the Apple "all-off-curve" synthetic-start fallback. Now optionally honours a horizontal shear for synthetic italic.
- Scanline rasterisation — active-edge-list fill with 4× vertical supersampling for anti-aliasing. Even-odd fill rule.
- Shaper —
cmapmapping with.notdeffallback for unmapped codepoints. Ligature substitution via GSUB type 4. Pair kerning via GPOS type 2 with legacykerntable fallback. - Composer — Porter-Duff "over" via
oxideav_pixfmt::blit_alpha_maskwith straight-alpha destinations. - Layout — line measurement + word-wrap. Tokenises on whitespace and falls back to character-level breaks if a single word overflows.
- LRU cache — glyph bitmap reuse keyed by
(face_id, glyph_id, size_q8, shear_q14); default capacity 256 covers a typical subtitle session at >95% hit rate. Shear key component keeps synthesised-italic glyphs out of the upright slot.
Public API
use ;
let bytes = read?;
let face = from_ttf_bytes?;
// Round-1 entry point — defaults to upright Regular.
let bitmap: RgbaBitmap = render_text?;
// Round-2: italic + weight via Style.
let italic = render_text_styled?;
// Word-wrap to a max width; one bitmap per output line.
let lines = render_text_wrapped?;
// Lower-level: shape once, then compose into a destination you allocate.
let glyphs = shape?;
let mut dst = new;
let mut composer = new;
composer.compose_run?;
// Multi-face fallback chain.
let cjk_face = from_ttf_bytes?;
let chain = new.push_fallback;
let glyphs = chain.shape?;
// Stroked subtitle text (\bord 2 + white fill on black border).
let stroke = new;
composer.compose_run_with_stroke?;
Out of scope (round 3+)
- Bidi (UAX #9) — left-to-right only; bidi resolution is round 3.
- Arabic shaping — joining types, connection tables, mandatory ligatures; round 3.
- Indic conjunct formation — reordering + half-form selection; round 3.
- Variable fonts —
fvar/gvar/MVAR; round 3. - TrueType bytecode hinting — modern AA at ≥ 16 px does not need it.
- CFF / Type 2 charstrings —
oxideav-otfcarries the cubic-Bezier outline pipeline. - Mark-to-base / mark-to-mark attachment (GPOS types 4/5/6) —
reserved by
PositionedGlyph::y_offset; round 3. - Subpixel positioning — round 3 once we wire up RGB / BGR LCD filtering.
- Synthetic bold —
Style.weightis carried through the cache key but no synthesis pass runs yet; round 3 will dilate the alpha mask in proportion to the(weight - 400)delta. - True offset-curve stroke geometry — current stroke uses alpha-mask dilation; round 3 may add geometric Minkowski-sum mode for cases where exact corners matter at large bord widths.
Test fixture
Reuses crates/oxideav-ttf/tests/fixtures/DejaVuSans.ttf plus
DejaVuSansMono.ttf (Bitstream Vera license). The integration tests
check rasterised output dimensions, shaper glyph counts, kerning
shrinkage on AVATAR, the fi ligature on office, italic-shear
widening of upright glyphs, per-run colour preservation, font-fallback
routing, and stroke dilation. A two-script-coverage fallback test
(Latin primary + CJK fallback) is deferred until a small CJK fixture
lands in oxideav-ttf — Noto Sans CJK at 10 MB is too big to vendor
for one test.
License
MIT — see LICENSE.