Skip to main content

Crate fea_rs_ast

Crate fea_rs_ast 

Source
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 AsFea trait
  • 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 context
  • ToplevelItem: 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 Statement enum distinguishes between all possible statements
  • The ToplevelItem enum 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§

AlternateSubstStatement
An alternate substitution (GSUB type 3) statement
Anchor
An Anchor element, used inside a pos rule.
AnchorDefinition
A named anchor definition. (2.e.viii)
AttachStatement
A GDEF table Attach statement
ChainedContextStatement
A chained contextual substitution statement, either GSUB or GPOS.
Comment
A comment in a feature file
ConditionSet
A variable layout conditionset.
CursivePosStatement
A cursive positioning rule (GPOS type 3)
FeatureBlock
A named feature block. (feature foo { ... } foo;)
FeatureFile
A complete OpenType Feature File.
FeatureReferenceStatement
Example: feature salt;
FontRevisionStatement
A head table FontRevision statement.
Gdef
The GDEF table
GlyphClass
A glyph class literal, such as [a b c] or [a-z A-Z].
GlyphClassDefStatement
A GDEF table GlyphClassDef statement
GlyphClassDefinition
A glyph class definition
GlyphName
A single glyph name, such as cedilla.
GlyphRange
A glyph range, such as a-z or A01-A05.
Head
The head table
Hhea
The hhea table
HheaStatement
A statement in the hhea table
IgnoreStatement
Either a GPOS or GSUB ignore statement.
LanguageStatement
A language statement within a feature
LanguageSystemStatement
A top-level languagesystem statement.
LigatureCaretByIndexStatement
A GDEF table LigatureCaretByIndex statement
LigatureCaretByPosStatement
A GDEF table LigatureCaretByPos statement
LigatureSubstStatement
A ligature substitution (GSUB type 4) statement
LookupBlock
A named lookup block. (lookup foo { ... } foo;)
LookupFlagStatement
A lookupflag statement
LookupReferenceStatement
Represents a lookup ...; statement to include a lookup in a feature.
MarkBasePosStatement
A mark-to-base positioning rule (GPOS type 4)
MarkClass
The name of a mark class
MarkClassDefinition
A definition of a glyph in a mark class, associating it with an anchor point.
MarkLigPosStatement
A mark-to-ligature positioning rule (GPOS type 5)
MarkMarkPosStatement
A mark-to-mark positioning rule (GPOS type 6)
MultipleSubstStatement
A multiple substitution (GSUB type 2) statement
Name
The name table
NameRecord
A name record statement in a name table.
NestedBlock
A nested block containing statements (e.g., featureNames { ... };)
PairPosStatement
A pair positioning rule (GPOS type 2)
Pos
Type marker that the statement is a positioning statement
ReverseChainSingleSubstStatement
A reverse chaining substitution statement
ScriptStatement
A script statement
SinglePosStatement
A single positioning rule (GPOS type 1)
SingleSubstStatement
A single substitution (GSUB type 1) statement
SizeParameters
A parameters statement for the size feature.
Stat
The STAT table
Subst
Type marker that the statement is a substitution statement
SubtableStatement
Represents a subtable break
Table
A table in a feature file, parameterized by the type of statements it contains.
ValueRecord
A ValueRecord element, used inside a pos rule to change a glyph’s position
ValueRecordDefinition
Represents a named value record definition.
VariationBlock
A variable layout variation block.
Vhea
The vhea table
VheaStatement
A statement in the vhea table

Enums§

Error
Errors that can occur in fea-rs-ast operations.
GdefStatement
A statement in a table GDEF { ... } GDEF block
GlyphContainer
A container for glyphs in various forms: single glyph names, glyph classes, glyph ranges, or glyph name/range literals.
HeadStatement
A statement in the head table
HheaField
Fields in the hhea table
Metric
A metric, which is potentially variable.
NameRecordKind
Kind of name record.
NameStatement
A statement in the name table
Statement
An AST node representing a single statement in a feature file.
ToplevelItem
Statements that can appear at the top level of a feature file.
VheaField
A field in the vhea table

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.
LayoutVisitor
A visitor trait for traversing and potentially modifying the AST.
SubOrPos
A trait implemented by both Pos and Subst to allow generic handling