Substitution Lookups
Start with the direct substitution types, then use them as the actions behind more complex behavior. Each example below assumes that every named glyph exists. The examples are independent: choose the rule appropriate to the task rather than pasting competing alternatives into one feature.
Features, Lookups, and Rules in FEA
A feature block can contain rules directly:
feature ss01 {
sub a by a.ss01;
} ss01;
The compiler places that rule in an implicit lookup. It is still a lookup in the compiled table, even though you did not give it a name in the source.
Use explicit named lookups when order or reuse needs to be clear. For example:
lookup ChangeA {
sub a by a.alt;
} ChangeA;
lookup RestoreA {
sub a.alt by a;
} RestoreA;
feature ss01 {
lookup ChangeA;
lookup RestoreA;
} ss01;
With both lookups applied in that order, an input a becomes a.alt and then becomes a again. The absence of a visible change does not mean the first substitution failed.
Within a shaping stage, lookup order in the compiled table, not the order of feature definitions in source code, governs processing. Recall the feature/lookup/rule hierarchy in The Process Architecture: a feature associates a typographic purpose with lookups; it does not create a separate execution schedule merely by appearing earlier in a file or menu.
Define named lookups in a deliberate processing order. Put lookups for earlier stages, such as localized forms in locl, before those for later stages, such as ligatures in liga. This canonical-stage discipline makes dependencies visible and supports reuse without duplicating rule text. The shaping engine still determines the stages appropriate to the script; arranging the source is not a way to override that script model.
The example above makes one dependency explicit. ChangeA creates the input that RestoreA expects. When both apply in that order, the second operation sees the result of the first. A later lookup cannot assume it will still receive the original character-mapped glyph if an earlier lookup has replaced it. The same issue occurs when two different features refer to related operations.
An implicit lookup is useful for a simple feature whose rules form one operation. A named lookup is useful when you need to identify that operation, place it among dependent operations, or reference it from more than one feature. Naming is not itself a correctness check: the input and output glyphs, feature associations, language systems, and compiled order still need to agree.
Avoid sharing a lookup across different shaping stages without understanding when it will be invoked. A lookup used by several features within one stage is not necessarily applied once for each feature name. Inspect the actual sequence rather than counting source references as independent executions.
Compile and inspect an actual glyph run whenever several features can act on the same glyphs. The input to a lookup is the output available at that point, not necessarily the original character-mapped glyph.
Single Substitution
Single substitution replaces one glyph with one other glyph. It is the basic pattern for small caps and many stylistic variants:
feature smcp {
sub a by a.sc;
} smcp;
A class on the input side can send several alternatives to the same result:
feature smcp {
sub [a a.alt] by a.sc;
} smcp;
Here either source glyph becomes a.sc. This is useful when an earlier feature can produce a.alt, but both versions should reach the same small cap.
For several corresponding replacements, use equal-length classes in the intended order:
@Lowercase = [a b c];
@SmallCaps = [a.sc b.sc c.sc];
feature smcp {
sub @Lowercase by @SmallCaps;
} smcp;
This maps a to a.sc, b to b.sc, and c to c.sc. It does not search the target class for similar-looking names. Explicit class order or the verified name-sorting order of generated tag classes determines the mapping; recall Glyph Classes.
[a z] is a two-member list. It is not the same notation as a range such as [a-z]. Use explicit lists when you want the correspondence to be reviewable without depending on range expansion or assumptions about the available names.
Ligature Substitution
Ligature substitution replaces a sequence of glyphs with one glyph:
feature liga {
sub f i by f_i;
} liga;
The rule recognizes the glyph sequence f i; the underscore in f_i is a useful source naming convention, not the runtime operation. A ligature drawing with that name does not become active until suitable layout data reaches it.
An input class can expand a family of sequences at compile time:
feature liga {
sub [f f.alt] i by f_i;
} liga;
Both f i and f.alt i can then produce f_i. This is a rule for two-glyph sequences, not a class-to-class positional mapping.
When ligatures overlap, consider the longer sequences as well as the shorter ones. Within one contiguous set of explicit ligature rules in a lookup, the FEA compiler sorts candidates; you need not write the longest sequence first. This does not make separate lookups order-independent. A lookup that has already changed f f can prevent a later f f i rule from seeing its original input. Define and test the intended longest and shortest cases, including repeated letters.
Use liga for standard ligatures and dlig for discretionary ones. Use rlig where the script requires the behavior. Standard default-on behavior is not the same as a required feature that the font needs for correct script rendering.
Recall underscore naming from Managing Glyph Names and Codepoints and ligature families from Substitution Features. After shaping, inspect the ligature's advance, kerning, mark attachments, and caret anchors from Anchors for Positioning, stored in GDEF as described in The Process Architecture. Replacing two drawings with one changes more than the visible overlap between those drawings.
Multiple and Alternate Substitution
Multiple substitution replaces one glyph with a sequence. A decomposition for mark attachment can use:
feature ccmp {
sub aacute by a acutecomb;
} ccmp;
The resulting glyphs still need appropriate metrics and attachment data. The rule does not read a FontLab component recipe or automatically transfer its offsets into the text run.
The standard multiple-substitution form uses one explicit source glyph and an explicit replacement sequence; glyph classes are not permitted in that form. It is not the class-to-class pairing operation used by single substitution. If several glyphs need different decompositions, give each its own rule and check the generated sequences.
Alternate substitution offers a choice of replacements with the from keyword:
feature salt {
sub a from [a.alt a.ss01];
} salt;
The alternate list does not mean “replace a with both glyphs.” The application or feature value chooses a member. Test the available choices in an interface that exposes them; a simple on/off control may not offer the full list.
Alternate access can expose unencoded designs through salt or an access-all-alternates workflow. For a specific coherent design choice that should be a simple toggle, a stylistic set with single substitutions may be easier for users to select. Choose the feature behavior before choosing the lookup syntax.
Editing Substitutions Visually
The Lookups panel lets you work with the feature, lookup, and rule relationships directly. This is useful when inspecting an imported font or comparing the visible inputs and outputs of a rule. When the font has stored binary layout tables, the panel starts from those tables. It does not need to compile the reconstructed Features text to inspect the existing layout.
For the full panel workflow and debugger controls, see The Lookups Panel. Open Window > Panels > Lookups. Choose the Script and Language, then select a feature to narrow the lookup list. Select a lookup to see its rules and the preview below them. The substitution and positioning filter buttons let you concentrate on one kind of operation. A glyph-name filter finds lookups involving a glyph; the filter beside the rule buttons narrows the rules within the selected lookup. The lookup glyph filter accepts wildcard patterns such as A.sc*. The rule filter matches partial text in the rule summary and its glyph names, without case sensitivity.
The Lookups document and Features-panel code are separate editable representations. To bring code into the visual document, import it explicitly as described in The Features Panel. Merely opening the panel, selecting a feature, or compiling Features does not replace an existing Lookups document.
Build a ligature rule
With f, i, and f_i already present in the font:
- Select the
ligafeature in the intended script and language. If it is absent, use + beside the feature list to add it. - Use + beside the lookup list and choose Ligature substitution. Give the new lookup a name that describes its purpose.
- Fill the first empty rule created with the lookup: choose
fandias the input sequence andf_ias its output. You can also double-click the substitution text and enterf i -> f_i. Use + beside the rule list when you need another rule. - Inspect the two rule previews. The left shows the input; the right shows the replacement. The glyph names beneath them identify the samples being displayed.
- Test the rule in a text run using the debugger described in The Lookups Panel. When the document is ready for use in the Glyph window, Preview panel, and export, choose Compile from the Lookups panel menu.
The glyph and class picker lets you choose actual font glyphs or a class. Blue glyphs in a rule preview are samples from a class: click one to choose another member without changing the class. Open the Classes panel when you want to change membership. Test several members when the rule depends on corresponding class order, as in the small-cap example above.
Rule-order buttons move the selected rule within its lookup. Keep the distinction between rule order, lookup order, and shaping stages: moving a rule in this list does not schedule its lookup before another shaping stage.
Testing the Substitution
Use the debugger described in The Lookups Panel to trace the rule with Script shaping on and the intended feature checked. Test ordinary fi for the ligature example, or /f/i to request those exact glyphs without inserting a space.
For the ChangeA/RestoreA example at the start of this chapter, check ss01 and inspect both steps: the first produces a.alt, and the second restores a, even though the final Result contains the original glyph. This makes the reversal visible.
Contextual use of these lookups is taught in Contextual Substitution and autogeneration in Generated and Conditional Substitution. Positioning counterparts are developed in Kerning, GPOS, and the kern Table and Mark Features and Export.