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 {}