Contextual Substitution

A contextual rule changes a glyph only when the surrounding glyph sequence matches. Use it when a form depends on neighboring shapes, not simply on a feature being enabled. The examples below are independent; replace a trial feature when testing an alternative rather than adding a second definition with the same tag.

Marked Glyphs and Chaining

Contextual substitution distinguishes the target from the context around it. An apostrophe marks the input glyph position to which the substitution applies:

feature calt {
    sub f a' by a.alt;
} calt;

This example requires f, a, and a.alt. It changes the a after f; f supplies the preceding context and is not replaced. An a after another glyph remains unchanged by this rule.

The match is against the current glyph sequence. If an earlier feature has already replaced f with another glyph, the literal f context no longer matches unless the contextual rule also accounts for that alternate.

You can express the same relationship by calling a named direct lookup at the marked position:

lookup AlternateA {
    sub a by a.alt;
} AlternateA;

feature calt {
    sub f a' lookup AlternateA;
} calt;

The contextual rule decides where the operation is allowed; the named lookup supplies the replacement. This is useful when one action is reused under several contexts. Define the called lookup before the contextual rule that references it.

A contextual ligature marks the consecutive glyphs that will combine, leaving the required surroundings unmarked:

feature clig {
    sub f' i' a by f_i;
} clig;

With f, i, a, and f_i present, this turns f i a into f_i a: the two marked glyphs combine, while a supplies the following context and remains unchanged. Test f i o as a negative case. Disable any separate liga rule for f i during this isolated test so it cannot hide the contextual difference.

For more complex input, mark the positions intentionally and keep the surrounding unmarked context readable. Test one input case at a time before broadening it with classes. A broad class can admit glyphs produced by other features that were not in the original example.

Contextual substitution is different from designspace conditional substitution. Context tests neighboring glyphs in text. A designspace condition tests axis locations. A font can use both, but an apostrophe does not express a weight threshold and a conditional tag does not describe a preceding letter.

Exceptions and Word Boundaries

Use ignore sub to exclude a sequence that would otherwise match a broader contextual rule. Place the exception before the affected rule within the same contextual lookup.

feature calt {
    ignore sub b a' c;
    sub a' c by a.alt;
} calt;

Here a c can become a.alt c, but b a c is exempt. The apostrophe identifies the same target position in the exception and the substitution. An ignore rule does not disable every other lookup that might later change a.

Keep exceptions close to the rules they qualify. Moving an exception into another lookup can change its scope, even if the feature still compiles.

Approximate an end position with a class

OpenType contextual matching does not provide a universal linguistic “end of word” token. You can instead describe the following glyphs that should prevent a swash.

With FontLab's Include Classes > Virtual Tags enabled, this example uses the generated @noblank set:

feature cswh {
    ignore sub a' @noblank;
    sub a' by a.swsh;
} cswh;

The substitution is excluded when a following glyph belongs to @noblank. It can occur when the glyph is at the end of the run or followed by a glyph outside that set, such as an ordinary blank space. The replacement a.swsh must exist.

This is a content-based boundary approximation. Punctuation, zero-width glyphs, visible space symbols, or intervening marks can make it differ from a reader's idea of a word boundary. Inspect the actual class membership and test those cases. Use a deliberately authored letter or boundary class when that better expresses the language and design requirements.

Using @ALL instead makes the condition stricter:

feature swsh {
    ignore sub a' @ALL;
    sub a' by a.swsh;
} swsh;

Because @ALL includes ordinary space glyphs, that version normally leaves only an end-of-run opportunity; it is not the same as the previous space-sensitive behavior. The font's .notdef and any overridden or combined class memberships also need consideration.

Treat these as alternative examples, not a pair to enable together without a design reason. Test the same text at the beginning, middle, and end of a run, before punctuation and spaces, and with marks. Then test feature combinations that can change either the target or its context.

The application's segmentation matters too. A run can end because the font or script changes, not only because a word ends. A contextual rule observes the glyph context made available to it; it does not inspect an unlimited paragraph beyond the run boundary.

A start-of-word pattern

This independent example substitutes a swash ligature only when no member of the declared letter class precedes f i:

@WordLetters = [a b c f i];
lookup InitialFI {
    ignore sub @WordLetters f' i';
    sub f' i' by f_i.swash;
} InitialFI;
feature cswh {
    lookup InitialFI;
} cswh;

With cswh enabled, fi at the start of a run, or after space, can become f_i.swash; afi is excluded by the ignore rule. This deliberately small class is only a demonstration. Extend the letter and boundary policy for the intended repertoire; it is not Unicode word segmentation, and an omitted letter would be treated as a boundary here. Every named glyph, including the swash ligature, must exist.

Inspecting and Editing a Contextual Rule

The Lookups panel separates the input pattern, the surrounding context, and the action performed when the pattern matches. Read a summary such as @mark — apply mark_4 as: match a member of @mark, then apply mark_4 at that input position. The called lookup supplies the actual substitution or positioning operation.

Select a contextual lookup to inspect its input rows and action controls. An action's position starts at 1 and counts positions in the matched input, excluding the surrounding Before context. Choose the referenced lookup from the searchable selector. The chooser narrows the candidates to compatible lookups in the feature where possible. Use the compact + beside the action controls to add a call at the selected input position.

A feature's lookup list also includes the lookups called by its contextual rules, even when the feature does not reference those children directly. Muted names distinguish these dependencies in the browser. Open follows an action to its definition; Back to calling lookup restores the caller with the same script, language, feature, and browsing filters. If a reference names a group of imported parts, Open lets you choose the part to inspect.

A child used only through context should be referenced by the caller without also being assigned directly to the active feature. A direct assignment can run the substitution outside the intended context. For a directly assigned AlternateA, select its feature row, select AlternateA, and click − beside the lookup list to remove that assignment. Its definition and the caller’s reference remain, so it is still listed as a dependency.

Binary layout does not normally retain the author's lookup names. FontLab can generate names from the associated features. Names such as mark_3.1 and mark_3.2 identify editable parts of one imported lookup; the suffixes are part numbers, not revisions.

Set and sample the surrounding context

Expand Context to inspect or add conditions. Only when supplies a matching context; Except when excludes that context. The Before and After fields refer to logical text order, including for right-to-left scripts. An empty field leaves that side unrestricted.

Spaces separate positions. For example, @mark4_copy @calt_kern_mark_copy @mark3_copy describes three successive positions, each matched by a member of the named class. It does not describe three alternative class names for one position. Use a set such as {A V} for alternatives at one position. A class name's _copy suffix is simply part of its name; inspect the membership to see what the class actually contains.

Use the eye button beside either context field to preview the classes it mentions, then choose members for a representative sample. In the rule preview, surrounding glyphs are gray. The left canvas shows the input; the right shows the result of applying the referenced lookups. If the rule cannot be built, read the diagnostic instead of treating an empty result as a successful match.

To express the earlier AlternateA example visually, set the input to a, the Before condition to f, and an action applying AlternateA at position 1. The sample should change f a to f a.alt, leaving the context glyph unchanged.

Trace the sampled context

Click Use in debugger to insert the chosen context and input samples and run shaping immediately. For the example above, the generated input is /f/a, with no extra space glyph between the names. The command adds the current features to the debugger selection; review the checkboxes if other features were already enabled.

The debugger includes the contextual caller and any child lookups it actually invokes. A caller's output includes the completed work of its children; selecting a child isolates that invocation's output. The child row is not a second application added after the caller. If the context does not match, an uncalled child has no execution to show.

Test both the positive case and a negative case, such as b a where the condition requires f a. Then restore Script shaping and the intended feature combination to check the result in normal text. The general trace controls and metrics are described in The Lookups Panel.