oxideav-scribe 0.1.9

Pure-Rust vector font shaper + layout for the oxideav framework — TrueType / OTF outline access, GSUB ligatures, GPOS kerning, mark attachment, CBDT colour bitmaps. Pixel pipeline lives in oxideav-raster.
Documentation

oxideav-scribe

Pure-Rust vector font shaper + line layout for the oxideav framework. Parses TTF / OTF tables (via oxideav-ttf

Scribe contains no pixel kernel: outline flattening, scanline AA, alpha compositing, synthetic bold and stroke dilation all live in oxideav-raster. Producing a rasterised text run is a two-step pipeline:

use oxideav_core::{Group, Node, VectorFrame};
use oxideav_raster::Renderer;
use oxideav_scribe::{Face, FaceChain, Shaper};

let bytes  = std::fs::read("DejaVuSans.ttf")?;
let face   = Face::from_ttf_bytes(bytes)?;
let chain  = FaceChain::new(face);

// 1. Shape: emit positioned vector glyph nodes.
let placed = Shaper::shape_to_paths(&chain, "Hello, world!", 16.0);

// 2. Wrap into a VectorFrame + render via oxideav-raster.
let mut root = Group::default();
for (_face_idx, glyph_node, transform) in placed {
    root.children.push(Node::Group(Group {
        transform,
        children: vec![glyph_node],
        ..Group::default()
    }));
}
let mut frame = VectorFrame::new(400.0, 80.0);
frame.root = root;
let rgba: oxideav_core::VideoFrame = Renderer::new(400, 80).render(&frame);

Capabilities

Outlines and rendering

  • Outline accessFace::glyph_path(gid) returns a Y-up oxideav_core::Path (MoveTo / LineTo / QuadCurveTo / CubicCurveTo / Close). TT outlines decode quadratics; CFF charstrings decode cubics 1:1. Face::glyph_node(gid, size_px) bakes the Y-flip + scale into a render-ready Node::Path (or Node::Image for CBDT colour glyphs).
  • Vector text APIShaper::shape_to_paths returns one (face_idx, Node, Transform2D) per visible glyph. Each node is wrapped in an oxideav_core::Group { cache_key: Some(_), .. } so the downstream rasterizer's bitmap cache memoises the rendered glyph across renders, frames, and renderer instances.
  • Italic synthesisstyle.italic synthesises a 12° forward shear when the face is upright; falls back to the font's own slant when one is present. Bold synthesis is deferred to consumer code (or a real Bold face).
  • Face chain — multi-face fallback for missing codepoints; per-glyph face_idx tells the consumer which face owns each glyph.
  • CBDT/CBLC colour bitmaps — Noto Color Emoji and friends decode to Node::Image carrying a VideoFrame; resampling to the requested size happens in scribe (bilinear, straight-alpha).

Shaping (GSUB / GPOS)

  • GSUB substitution — LookupType 1 (single), 2 (multiple), 3 (alternate), 4 (ligature) are applied through the caller-driven Face::shape_text(text, features) surface; contextual / chained / reverse-chained (types 5 / 6 / 8) flow through Shaper::shape / FaceChain::shape. The required-feature ccmp runs as a pre-ligature pass and calt as a post-ligature pass for every Latin / Cyrillic / Greek / DFLT run. Worked examples: face.shape_text("fi", &[*b"liga"]) returns a single fi-ligature glyph; face.shape_text("Hi", &[*b"smcp"]) returns small-caps where the font ships them.
  • GPOS positioning — single adjustment (type 1), pair kerning (type 2), cursive attachment (type 3, flag-clear semantics), mark-to-base (type 4), mark-to-ligature (type 5), and mark-to-mark stacking (type 6). Together this is enough for Latin / Cyrillic / Greek / basic CJK / Vietnamese / polytonic Greek.
  • Feature-tag introspectionFace::gsub_features_for_script / has_gsub_feature report the feature tags the active face publishes under an OpenType script tag, for higher-level APIs that gate on feature presence. (A GPOS introspection mirror is a follow-up.)
  • Explicit-script + alternate-index shapingshape_text_with_script resolves features against one named script tag (no priority walk, avoiding cross-script collisions like liga under both latn and arab); shape_text_with_alternates / shape_text_with_script_and_alternates name the AlternateSet entry the Type-3 walker picks. The auto-probe shape_text walks a broad script-tag priority list (Latin / Cyrillic / Greek / DFLT / Arabic / Hebrew / Thai / Lao / the Indic v1+v2 scripts / Khmer / Myanmar / Hangul / Han / Kana).

Complex-script shaping

  • Arabic contextual joiningshaping::arabic picks isol / init / medi / fina per character via the joining-class state machine; FaceChain::shape rewrites Arabic letters into their Presentation Forms-B equivalents before cmap so cmap-only fonts render the correct contextual shapes (including LAM-ALEF ligatures).
  • Indic + Brahmic non-Indic shapingshaping::indic classifies Devanagari, Bengali, Tamil, Gurmukhi, Gujarati, Telugu, Kannada, Malayalam, Oriya, Sinhala, Khmer, Thai, Lao, and Myanmar / Burmese, segments runs into orthographic clusters, applies per-script pre-base matra reorder, identifies reph (or the Burmese kinzi), rewrites the leading RA to its reph form via rphf, and wires the cluster-position-aware GSUB features (half, pref / blwf / abvf / pstf, the presentation features pres / psts / abvs / blws, and the context-aware locl / nukt / akhn / cjct / init / haln). Per-script reorder rules are exposed as DEVANAGARI_RULES / BENGALI_RULES / … / BURMESE_RULES for callers reusing the cluster machine. Coverage misses pass through unchanged.

Variable fonts

  • Outline interpolationFace::set_variation_coords / variation_axes / named_instances / is_variable surface the fvar declarations and let callers shape against a custom axis-coord vector. Shaper::with_variation_coords(..) is the per-call override path. Glyph outlines flow through the gvar interpolator so the emitted Path carries the blended deltas. CFF2 variable charstrings (the blend operator) are not yet emitted — scribe parses the CFF2 INDEX for table presence / axis count / glyph count via Face::cff2() only.
  • Metric-variation tablesFace::mvar() / metric_delta(tag) (global metrics), Face::hvar() / h_advance_delta(gid) (horizontal advance), Face::vvar() / v_advance_delta(gid) (vertical), and Face::stat() / stat_axes() / stat_axis_values() (Style Attributes) all resolve at the current variation coords. They share an ItemVariationStore + DeltaSetIndexMap parser in crate::variations.
  • name-id resolutionFace::name_id(nid) returns the highest-ranked Unicode string for a name-table id, resolving axis_name_id / subfamily_name_id / value_name_id.

Layout and bidirectional text

  • Line layout — line measurement + word-wrap.
  • High-level bidi bridgelayout::reorder_line_visual(text, base_level) -> VisualLine drives the complete UAX #9 pipeline over one display line (class assignment → P → X → W → N0 → N1/N2 → I → L1 → L2 → L3 → L4) and returns the characters in left-to-right visual order ready to feed glyph-by-glyph into the shaper. VisualLine publishes visual: Vec<char> (L4-mirrored, render order), the logical_to_visual / visual_to_logical permutation pair (the latter precomputed for O(1) cursor hit-testing), and the resolved base_level. base_level: Option<u8> is the HL1 override.
  • Whole-text / paragraph driversbidi::process_text(text, base_level) -> TextBidi splits a document into paragraphs (P1) and resolves each independently; bidi::process_paragraph(text, base_level) and process_paragraph_with_brackets(..) (N0 wired in between W7 and N1) compose the per-rule passes into one ParagraphBidi carrier with reorder_paragraph() / reorder_line_range(start..end) helpers.
  • Per-rule UAX #9 surface — the complete rule pipeline is also exposed as individual public functions for callers needing finer control: bidi_class (full Unicode 16.0 DerivedBidiClass.txt coverage, data-driven from the UCD snapshots under docs/text/unicode-bidi/), paragraph_level / split_paragraphs (P1/P2/P3), resolve_explicit_levels (X1..X9 stack), level_runs / isolating_run_sequences (X10 BD7/BD13 partition + sos/eos), resolve_weak_types (W1..W7), paired_bracket / bracket_pairs / resolve_bracket_pairs (N0, full BidiBrackets.txt), resolve_neutral_types (N1/N2), resolve_implicit_levels (I1/I2), reset_trailing_levels / reorder_line (L1/L2), reorder_combining_marks (L3), and mirrored_glyph / apply_mirroring (L4, full BidiMirroring.txt).

Out of scope

  • Pixel work — bitmap rasterisation, alpha compositing, synthetic bold dilation, stroke dilation. All in oxideav-raster.
  • Bidi HL1..HL6 higher-level-protocol overrides — the rule pipeline itself is complete; HL overrides remain caller responsibility.
  • CFF2 variable charstrings — the blend operator is not yet emitted (the INDEX walker is parsed for table metadata only).
  • TrueType bytecode hinting, subpixel LCD filtering, and the GPOS cursive attachment RIGHT_TO_LEFT flag-set variant (needs lookup-flag exposure in oxideav-ttf's public GPOS API) — deferred.

Test fixtures

Reuses crates/oxideav-ttf/tests/fixtures/DejaVuSans.ttf plus DejaVuSansMono.ttf (Bitstream Vera license), crates/oxideav-otf/tests/fixtures/SourceSans3-Regular.otf (SIL OFL), and a vendored copy of InterVariable.ttf (SIL OFL — see tests/fixtures/INTER-OFL-LICENSE.txt) for the variable-font suite. Network-gated emoji/CJK fixtures fetch on demand; see tests/font_fixtures/ and run with OXIDEAV_NETWORK_TESTS=1.

License

MIT — see LICENSE.