This module provides utilities for defining and managing OpenType font features. It includes tools for creating stylistic sets, character variants, ligatures, and more.
The feature/ module is designed to simplify the creation of OpenType font features. It uses an abstract syntax tree (AST) approach to define and manage features programmatically.
ast.py: Core utilities for defining OpenType features.regular.py: Entry file for regular features.italic.py: Entry file for italic features.base/: Foundational classes and features (e.g., numbers, cases, localized forms).calt/: Default ligatures.cv/: Character variants.ss/: Stylistic sets.
The calt/tag.py file provides utilities for creating custom tags.
There are many built-in tags with full-round border in the font, you can use subst_liga function to custom trigger text. Following example shows how to convert TODO: to (TODO) (Cons: the first letter before the tag will be overlapped)
subst_liga(
source="TODO:",
target="tag_todo.liga",
lookup_name="todo_colon"
)If you want to get more tags, use tag_custom function:
tag_custom(
[
(":attention:", "[attention]"),
("_noqa_", "(noqa)"),
# ("_alter_", "<alter>"),
],
bg_cls_dict,
)This converts:
:attention: _noqa_
into a styled tag:
- Built-in tags are optimized for spacing; custom tags are not.
- Tags may split if letter spacing > 0. See #381.
- Tags inherit the original text color. See #381.
There are two strategies to freeze a feature:
- For lookups implementing new ligatures (e.g.,
ss08), move rules intocalt. - For glyph replacements (e.g.,
cv01), substitute glyphs directly with their originals.
Currently, the second approach cannot be implemented in variable format, so in the V7.0 release all variable format variants are identical except for the family name. The build script provides an escape hatch with the --apply-fea-file flag.
Since the feature-loading logic was refactored to Python, features are loaded dynamically. The logic in common.py moves rules from the target feature to calt (method 1). Enabling the calt feature will yield styling equivalent to the static version.
So, please ENABLE FONT LIGATURE to make all features work if you are using variable font (NOT recommended).
The ast.py file provides classes and functions to define OpenType features. Below are some key utilities:
Represents a class of glyphs.
from source.py.feature.ast import Clazz, subst
cls_digit = Clazz("Digit", ["zero", "one", "two", "three"])
cls_digit.state()
subst(cls_digit.use(), "a", "b", "c")Generated fea string:
@Digit = [zero, one, two, three];
sub @Digit a' b by c;Defines a lookup block for substitutions.
from source.py.feature.ast import Lookup, subst
lookup_example = Lookup(
name="example_lookup",
desc="Example substitution",
content=[
subst("a", "b", None, "c"),
],
)Generated fea string:
# Example substitution
lookup example_lookup {
sub a b' by c;
} example_lookup;Represents an OpenType feature.
from source.py.feature.ast import Feature
feature_example = Feature(
tag="calt",
content=[
lookup_example,
],
)Generated fea string:
feature calt {
# Example substitution
lookup example_lookup {
sub a b' by c;
} example_lookup;
}Generates the final OpenType feature file content.
from source.py.feature.ast import create
fea_content = create([feature_example])
print(fea_content)In most of time, you don't need to update the fea files. The generated fea string will be automatically applied at build time without using --apply-fea-file flag.
To update existing fea files, you can run:
uv run task.py feaHere is an example to show how to use the generate_fea_string function to generate feature files
from source.py.feature import generate_fea_string
fea_string = generate_fea_string(italic=False, cn=True)
print(fea_string)