Skip to main content

oxideav_scribe/
lib.rs

1//! Pure-Rust font shaper + layout for the
2//! [oxideav](https://github.com/OxideAV) framework.
3//!
4//! Scribe is a **vector-only shaper**: parse TTF / OTF tables → emit
5//! positioned vector glyphs as [`oxideav_core::Node`]s. All pixel
6//! work — outline flattening, scanline anti-aliasing, alpha
7//! compositing — happens downstream in
8//! [`oxideav-raster`](https://github.com/OxideAV/oxideav-raster).
9//!
10//! Scope:
11//! - **Shaper** — `cmap` + GSUB type 4 (ligatures) + GPOS type 2
12//!   (pair kerning) + mark-to-base / mark-to-mark, enough for Latin /
13//!   Cyrillic / Greek / basic CJK / Vietnamese / polytonic Greek.
14//! - **Arabic contextual joining (round 7)** — `shaping::arabic`
15//!   computes the joining form per character using the Unicode joining
16//!   classes + an adjacency state machine; `FaceChain::shape` then
17//!   translates Arabic letters into their Arabic Presentation Forms-B
18//!   equivalents (U+FE70..U+FEFF) before cmap, so a font that ships
19//!   the PF-B block (DejaVuSans, Noto Sans Arabic, Amiri) renders
20//!   visually-correct contextual shapes — including LAM-ALEF
21//!   ligatures via the existing GSUB pass.
22//! - **Indic + Brahmic complex-script shaping (rounds 8 + 10 + 11 +
23//!   12)** — `shaping::indic` classifies Devanagari (U+0900..U+097F),
24//!   Bengali (U+0980..U+09FF), Tamil (U+0B80..U+0BFF), Gurmukhi
25//!   (U+0A00..U+0A7F), Gujarati (U+0A80..U+0AFF), Telugu
26//!   (U+0C00..U+0C7F), Kannada (U+0C80..U+0CFF), Malayalam
27//!   (U+0D00..U+0D7F), Oriya (U+0B00..U+0B7F), Sinhala
28//!   (U+0D80..U+0DFF), Khmer (U+1780..U+17FF), and Thai
29//!   (U+0E00..U+0E7F) codepoints into syllabic categories, segments
30//!   runs into orthographic clusters, and applies per-script cluster
31//!   transformations: pre-base matra reorder (a uniform mechanism
32//!   across all scripts that have one) plus reph identification (the
33//!   Indic core scripts; Tamil + Malayalam + Sinhala + Khmer + Thai
34//!   are reph-disabled). Khmer's halant role is played by U+17D2
35//!   COENG which stacks subjoined consonants underneath the base;
36//!   Thai has no halant and Thai pre-base vowels are already in
37//!   storage order before their consonant. The `FaceChain::shape`
38//!   pipeline applies the reorder before cmap so cmap-only fonts
39//!   render simple clusters with the matra in the correct visual
40//!   position. When the active face publishes a `rphf` GSUB lookup
41//!   for the script, identified reph clusters get the leading RA
42//!   glyph substituted to its reph-form and the halant glyph dropped
43//!   via `Font::gsub_apply_lookup_type_1`.
44//! - **`Face::glyph_path` / `glyph_node`** — TrueType + OTF (CFF)
45//!   outlines as `oxideav_core::Path`; CBDT/sbix colour bitmaps as
46//!   `Node::Image` carrying a `VideoFrame`.
47//! - **`Shaper::shape_to_paths`** — vector text API: positioned
48//!   `(face_idx, Node, Transform2D)` triples ready to compose into a
49//!   `VectorFrame`. Each glyph is wrapped in a cache-keyed `Group` so
50//!   the downstream rasterizer's bitmap cache reuses the same memoised
51//!   glyph across renders.
52//! - **Face chain** — multi-face fallback (primary → fallback chain),
53//!   per-codepoint resolution.
54//! - **Layout** — line measurement + word-wrap (no bidi).
55//!
56//! See `README.md` for a tour and the deferral list.
57
58#![deny(missing_debug_implementations)]
59#![warn(rust_2018_idioms)]
60
61pub mod color;
62pub mod color_glyph;
63pub mod face;
64pub mod face_chain;
65pub mod layout;
66pub mod shaper;
67pub mod shaping;
68pub mod style;
69
70pub use color::{Rgba, TRANSPARENT, WHITE};
71pub use color_glyph::ColorGlyphBitmap;
72pub use face::{Face, FaceKind};
73pub use face_chain::FaceChain;
74pub use layout::{run_width, wrap_lines};
75pub use oxideav_ttf::{NamedInstance, VariationAxis};
76pub use shaper::{PositionedGlyph, Shaper, ShaperBuilder};
77pub use shaping::{
78    bengali_category, bengali_feature_tags, cluster_boundaries, cluster_boundaries_with,
79    compute_forms, devanagari_category, devanagari_feature_tags, feature_tags_for_run,
80    gujarati_category, gujarati_feature_tags, gurmukhi_category, gurmukhi_feature_tags,
81    joining_class, kannada_category, kannada_feature_tags, khmer_category, khmer_feature_tags,
82    malayalam_category, malayalam_feature_tags, oriya_category, oriya_feature_tags,
83    presentation_form, reorder_cluster, reorder_cluster_with, script_indic_tags, sinhala_category,
84    sinhala_feature_tags, tamil_category, tamil_feature_tags, telugu_category, telugu_feature_tags,
85    thai_category, thai_feature_tags, ClusterFlags, IndicCategory, JoiningClass, JoiningForm,
86    ReorderRules, Script, BENGALI_RULES, DEVANAGARI_RULES, GUJARATI_RULES, GURMUKHI_RULES,
87    KANNADA_RULES, KHMER_RULES, MALAYALAM_RULES, ORIYA_RULES, SINHALA_RULES, TAMIL_RULES,
88    TELUGU_RULES, THAI_RULES,
89};
90pub use style::{
91    synthetic_italic_shear, Style, DEFAULT_SYNTHETIC_ITALIC_DEG, ITALIC_ANGLE_EPSILON_DEG,
92};
93
94/// Errors emitted by the scribe pipeline.
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub enum Error {
97    /// The underlying TTF parser rejected the bytes.
98    Ttf(oxideav_ttf::Error),
99    /// The underlying OTF (CFF) parser rejected the bytes.
100    Otf(oxideav_otf::Error),
101    /// `size_px` was non-positive (negative or NaN).
102    InvalidSize,
103    /// A `with_font` / `with_otf_font` call was made on a face of
104    /// the wrong flavour.
105    WrongFaceKind {
106        expected: FaceKind,
107        actual: FaceKind,
108    },
109}
110
111impl From<oxideav_ttf::Error> for Error {
112    fn from(e: oxideav_ttf::Error) -> Self {
113        Self::Ttf(e)
114    }
115}
116
117impl From<oxideav_otf::Error> for Error {
118    fn from(e: oxideav_otf::Error) -> Self {
119        Self::Otf(e)
120    }
121}
122
123impl core::fmt::Display for Error {
124    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
125        match self {
126            Self::Ttf(e) => write!(f, "ttf error: {e}"),
127            Self::Otf(e) => write!(f, "otf error: {e}"),
128            Self::InvalidSize => f.write_str("non-positive font size"),
129            Self::WrongFaceKind { expected, actual } => {
130                write!(f, "wrong face kind: expected {expected:?}, got {actual:?}")
131            }
132        }
133    }
134}
135
136impl std::error::Error for Error {}