Expand description
§fea-rs-ast
A Rust port of Python’s fontTools.feaLib.ast
library, providing a fontTools-compatible AST (Abstract Syntax Tree) for OpenType Feature Files.
This crate builds on top of the fea-rs
parser, providing a higher-level, more ergonomic interface that matches the familiar fontTools API
while leveraging Rust’s type safety and performance.
§Overview
OpenType Feature Files (.fea) define advanced typographic features for fonts using a domain-specific language. This crate provides:
- Parsing: Load and parse feature files into a structured AST using
fea-rs. - Construction: Programmatically build feature file structures
- Serialization: Convert AST back to valid feature file syntax via the
AsFeatrait - Transformation: Modify AST using the visitor pattern
§Architecture
The crate provides two main statement enums:
Statement: All possible statements in a feature file, regardless of contextToplevelItem: Only statements valid at the top level of a feature file
Both implement the AsFea trait for serialization back to .fea syntax.
§Examples
§Loading an Existing Feature File
Parse a feature file from a string:
use fea_rs_ast::{FeatureFile, AsFea};
let fea_code = r#"
languagesystem DFLT dflt;
feature smcp {
sub a by a.smcp;
sub b by b.smcp;
} smcp;
"#;
// Simple parsing without glyph name resolution
let feature_file = FeatureFile::try_from(fea_code).unwrap();
// Or with full resolution support
let feature_file = FeatureFile::new_from_fea(
fea_code,
Some(&["a", "a.smcp", "b", "b.smcp"]), // Glyph names
None::<&str>, // Project root for includes
).unwrap();
// Serialize back to .fea syntax
let output = feature_file.as_fea("");
println!("{}", output);§Constructing New Statements
Build feature file structures programmatically:
use fea_rs_ast::*;
// Create a glyph class definition
let lowercase = GlyphClassDefinition::new(
"lowercase".to_string(),
GlyphClass::new(vec![
GlyphContainer::GlyphName(GlyphName::new("a")),
GlyphContainer::GlyphName(GlyphName::new("b")),
GlyphContainer::GlyphName(GlyphName::new("c")),
], 0..0),
0..0, // location range
);
// Create a single substitution statement
let subst = SingleSubstStatement::new(
vec![GlyphContainer::GlyphName(GlyphName::new("a"))],
vec![GlyphContainer::GlyphName(GlyphName::new("a.smcp"))],
vec![], // prefix
vec![], // suffix
0..0, // location
false, // force_chain
);
// Create a feature block
let feature = FeatureBlock::new(
"smcp".into(),
vec![Statement::SingleSubst(subst)],
false, // use_extension
0..0, // location
);
// Build the complete feature file
let feature_file = FeatureFile::new(vec![
ToplevelItem::GlyphClassDefinition(lowercase),
ToplevelItem::Feature(feature),
]);
// Serialize to .fea syntax
let output = feature_file.as_fea("");
assert!(output.contains("@lowercase = [a b c];"));
assert!(output.contains("feature smcp"));
assert!(output.contains("sub a by a.smcp;"));§Using the Visitor Pattern
Transform AST structures by implementing the LayoutVisitor trait:
use fea_rs_ast::*;
// Create a visitor that renames all features
struct FeatureRenamer {
old_name: String,
new_name: String,
}
impl LayoutVisitor for FeatureRenamer {
fn visit_statement(&mut self, statement: &mut Statement) -> bool {
match statement {
Statement::FeatureBlock(feature) => {
if feature.name == self.old_name.as_str() {
feature.name = self.new_name.as_str().into();
}
}
_ => {}
}
true // Continue visiting
}
}
// Use the visitor
let fea_code = r#"
feature liga {
sub f i by fi;
} liga;
"#;
let mut feature_file = FeatureFile::try_from(fea_code).unwrap();
let mut visitor = FeatureRenamer {
old_name: "liga".to_string(),
new_name: "dlig".to_string(),
};
visitor.visit(&mut feature_file).unwrap();
let output = feature_file.as_fea("");
assert!(output.contains("feature dlig"));§More Complex Visitor: Glyph Name Substitution
use fea_rs_ast::*;
use std::collections::HashMap;
// Visitor that replaces glyph names throughout the AST
struct GlyphNameReplacer {
replacements: HashMap<String, String>,
}
impl LayoutVisitor for GlyphNameReplacer {
fn visit_statement(&mut self, statement: &mut Statement) -> bool {
// Replace glyph names in various statement types
match statement {
Statement::SingleSubst(subst) => {
for container in &mut subst.glyphs {
self.replace_in_container(container);
}
for container in &mut subst.replacement {
self.replace_in_container(container);
}
}
Statement::GlyphClassDefinition(gcd) => {
for container in &mut gcd.glyphs.glyphs {
self.replace_in_container(container);
}
}
_ => {}
}
true
}
}
impl GlyphNameReplacer {
fn replace_in_container(&self, container: &mut GlyphContainer) {
match container {
GlyphContainer::GlyphName(gn) => {
if let Some(new_name) = self.replacements.get(gn.name.as_str()) {
gn.name = new_name.as_str().into();
}
}
GlyphContainer::GlyphClass(gc) => {
for glyph_container in &mut gc.glyphs {
self.replace_in_container(glyph_container);
}
}
_ => {}
}
}
}§Feature Coverage
This crate supports most OpenType feature file constructs:
- GSUB: Single, Multiple, Alternate, Ligature, Contextual, and Reverse Chaining substitutions
- GPOS: Single, Pair, Cursive, Mark-to-Base, Mark-to-Ligature, and Mark-to-Mark positioning
- Tables: GDEF, BASE, head, hhea, name, OS/2, STAT, vhea
- Contextual Rules: Chaining context and ignore statements
- Variable Fonts: Conditionsets and variation blocks
- Lookups: Lookup blocks with flags and references
- Features: Feature blocks with useExtension
Features which fea-rs parses which this crate does not currently support:
- Glyphs number variables in value records
- CID-keyed glyph names
§Compatibility
The API closely mirrors fontTools’ Python API where practical, making it easier to port existing Python code to Rust. Key differences:
- Rust’s type system provides compile-time guarantees about statement validity
- The
Statementenum distinguishes between all possible statements - The
ToplevelItemenum ensures only valid top-level constructs - Location tracking uses byte ranges (
Range<usize>) instead of line/column numbers
§Re-exports
This crate re-exports the underlying fea_rs parser for advanced use cases where
direct access to the parse tree is needed.
Re-exports§
pub use fea_rs;
Structs§
- Alternate
Subst Statement - An alternate substitution (GSUB type 3) statement
- Anchor
- An
Anchorelement, used inside aposrule. - Anchor
Definition - A named anchor definition. (2.e.viii)
- Attach
Statement - A
GDEFtableAttachstatement - Chained
Context Statement - A chained contextual substitution statement, either GSUB or GPOS.
- Comment
- A comment in a feature file
- Condition
Set - A variable layout conditionset.
- Cursive
PosStatement - A cursive positioning rule (GPOS type 3)
- Feature
Block - A named feature block. (
feature foo { ... } foo;) - Feature
File - A complete OpenType Feature File.
- Feature
Reference Statement - Example:
feature salt; - Font
Revision Statement - A
headtableFontRevisionstatement. - Gdef
- The
GDEFtable - Glyph
Class - A glyph class literal, such as
[a b c]or[a-z A-Z]. - Glyph
Class DefStatement - A
GDEFtableGlyphClassDefstatement - Glyph
Class Definition - A glyph class definition
- Glyph
Name - A single glyph name, such as
cedilla. - Glyph
Range - A glyph range, such as
a-zorA01-A05. - Head
- The
headtable - Hhea
- The
hheatable - Hhea
Statement - A statement in the
hheatable - Ignore
Statement - Either a GPOS or GSUB ignore statement.
- Language
Statement - A
languagestatement within a feature - Language
System Statement - A top-level
languagesystemstatement. - Ligature
Caret ByIndex Statement - A
GDEFtableLigatureCaretByIndexstatement - Ligature
Caret ByPos Statement - A
GDEFtableLigatureCaretByPosstatement - Ligature
Subst Statement - A ligature substitution (GSUB type 4) statement
- Lookup
Block - A named lookup block. (
lookup foo { ... } foo;) - Lookup
Flag Statement - A
lookupflagstatement - Lookup
Reference Statement - Represents a
lookup ...;statement to include a lookup in a feature. - Mark
Base PosStatement - A mark-to-base positioning rule (GPOS type 4)
- Mark
Class - The name of a mark class
- Mark
Class Definition - A definition of a glyph in a mark class, associating it with an anchor point.
- Mark
LigPos Statement - A mark-to-ligature positioning rule (GPOS type 5)
- Mark
Mark PosStatement - A mark-to-mark positioning rule (GPOS type 6)
- Multiple
Subst Statement - A multiple substitution (GSUB type 2) statement
- Name
- The
nametable - Name
Record - A name record statement in a name table.
- Nested
Block - A nested block containing statements (e.g.,
featureNames { ... };) - Pair
PosStatement - A pair positioning rule (GPOS type 2)
- Pos
- Type marker that the statement is a positioning statement
- Reverse
Chain Single Subst Statement - A reverse chaining substitution statement
- Script
Statement - A
scriptstatement - Single
PosStatement - A single positioning rule (GPOS type 1)
- Single
Subst Statement - A single substitution (GSUB type 1) statement
- Size
Parameters - A
parametersstatement for thesizefeature. - Stat
- The
STATtable - Subst
- Type marker that the statement is a substitution statement
- Subtable
Statement - Represents a subtable break
- Table
- A table in a feature file, parameterized by the type of statements it contains.
- Value
Record - A
ValueRecordelement, used inside aposrule to change a glyph’s position - Value
Record Definition - Represents a named value record definition.
- Variation
Block - A variable layout variation block.
- Vhea
- The
vheatable - Vhea
Statement - A statement in the
vheatable
Enums§
- Error
- Errors that can occur in fea-rs-ast operations.
- Gdef
Statement - A statement in a
table GDEF { ... } GDEFblock - Glyph
Container - A container for glyphs in various forms: single glyph names, glyph classes, glyph ranges, or glyph name/range literals.
- Head
Statement - A statement in the
headtable - Hhea
Field - Fields in the
hheatable - Metric
- A metric, which is potentially variable.
- Name
Record Kind - Kind of name record.
- Name
Statement - A statement in the
nametable - Statement
- An AST node representing a single statement in a feature file.
- Toplevel
Item - Statements that can appear at the top level of a feature file.
- Vhea
Field - A field in the
vheatable
Traits§
- AsFea
- Trait for converting AST nodes back to feature file syntax.
- FeaTable
- A helper for constructing tables which hold statements of a particular type.
- Layout
Visitor - A visitor trait for traversing and potentially modifying the AST.
- SubOr
Pos - A trait implemented by both Pos and Subst to allow generic handling