blob: e971f1929776e885d6587a4ca24ab7e9044b8a35 [file] [view] [edit]
# Using Depend for Closure Computation
The depend API can be used to compute glyph closures - determining which glyphs
are reachable from a given set of input codepoints and active features. This
document explains how to compute closures using the depend graph and provides a
reference implementation.
## Computing Closure with Depend
To compute a closure using the depend graph:
1. **Map input codepoints to starting glyphs** using `hb_font_get_nominal_glyph()`
2. **Handle Unicode Variation Sequences** using `hb_font_get_variation_glyph()` for
UVS-based glyph expansion (not in depend graph)
3. **Initialize the reachable set** with these starting glyphs
4. **Expand through dependencies** by following edges in the depend graph
5. **Filter by active features** to only follow edges for enabled GSUB features
6. **Handle ligatures correctly** - only add ligature outputs when all component
glyphs are present
7. **Check context requirements** - only follow edges when positional requirements
(backtrack/lookahead) are satisfied
### Context Set Filtering
Context and ChainContext GSUB rules have positional requirements encoded in the
context_set field. Each context_set specifies which backtrack and/or lookahead
glyphs must be present for a dependency edge to apply.
When context_set filtering is implemented, recent testing has not generated any
over-approximation cases - the depend-based closure matches the subset-based
closure in all tested scenarios.
**With context_set filtering:** Recent testing shows no over-approximation
**Without context_set filtering:** Conservative over-approximation (safe but may include extra glyphs)
See `docs/depend-api.md` section "Working with Context Sets" for details on the
context_set encoding and how to check context requirements.
### Reference Implementation
The test suite includes a production-quality implementation of depend-based
closure in `test/fuzzing/hb-depend-closure-parity.cc`. The `compute_depend_closure()`
function demonstrates proper handling of:
- Feature filtering (only following edges for active GSUB features)
- Ligature sets (only adding ligature outputs when all components present)
- Context sets (only following edges when positional requirements satisfied)
- Non-GSUB dependencies (glyf, CFF, COLR, MATH)
- UVS handling (via `hb_font_get_variation_glyph()`, separate from depend graph)
Key aspects of the implementation:
```c++
// entry is hb_subset_depend_entry_t
// Feature filtering - only follow GSUB edges for active features
if (entry.table_tag == HB_OT_TAG_GSUB) {
if (active_features && !hb_set_has(active_features, entry.layout_tag))
continue; // Skip edge if feature not active
}
// Context filtering - only follow edges when requirements satisfied
if (entry.context_set_index != HB_CODEPOINT_INVALID) {
if (!check_context_satisfied(depend, entry.context_set_index, glyphs))
continue; // Skip edge if context not satisfied
}
// Ligature handling - only add ligature when all components present
if (entry.ligature_set_index != HB_CODEPOINT_INVALID) {
hb_set_t *ligature_glyphs = hb_set_create();
hb_subset_depend_lookup_set(depend, entry.ligature_set_index, ligature_glyphs);
// Check if all component glyphs are in reachable set
if (!hb_set_is_subset(ligature_glyphs, glyphs)) {
hb_set_destroy(ligature_glyphs);
continue; // Skip ligature if not all components present
}
hb_set_destroy(ligature_glyphs);
}
// Add dependent glyph to closure
if (!hb_set_has(glyphs, entry.dependent)) {
hb_set_add(glyphs, entry.dependent);
hb_set_add(to_process, entry.dependent);
}
```
## Table-Specific Closure Behavior
### MATH Closure
MATH edges should be followed in a **single pass**: follow MATH edges for glyphs
that are in the set before the MATH stage, but do NOT follow MATH edges for
glyphs that the MATH stage itself added. This matches the subsetter's behavior,
which retains MATH-closure glyphs (their outlines are needed for drawing assembly
parts) but strips their own MATH constructions from the subset.
Math layout engines do not recursively stretch assembly parts, so the
second-hop constructions are not functionally needed.
### GSUB Self-Feeding Chains
The depend graph captures all GSUB substitution edges. When computing transitive
closure, a lookup's output glyph may itself be in the same lookup's coverage,
creating a chain: A → B → C → ... through the same lookup. No shaper actually
re-applies a lookup to its own output within a single feature application, so
these chains are over-approximation relative to real shaping behavior.
The subsetter's iterative closure (`HB_CLOSURE_MAX_STAGES`) truncates these
chains. The depend closure, following edges to a fixed point, may go further.
To detect this condition (only relevant when unexpected over-approximation is
found without flagged edges):
1. For each active GSUB feature, collect source glyphs and dependent glyphs
2. If those sets intersect for any feature, the font has **self-feeding potential**
3. Trace extra glyphs through self-feeding feature edges from the subset closure
4. If all extra glyphs are reachable through self-feeding chains, the
over-approximation is a known artifact of self-feeding, not a bug
This detection only runs on the failure path and is O(edges), so real fonts pay
no cost. In practice, well-formed fonts do not have self-feeding lookups.
## Edge Flags and Over-Approximation Detection
Each dependency edge has an optional `flags` field that indicates potential over-approximation:
- **`HB_SUBSET_DEPEND_EDGE_FLAG_FROM_CONTEXT_POSITION` (0x01)**: Edge from a multi-position
contextual rule
- **`HB_SUBSET_DEPEND_EDGE_FLAG_FROM_NESTED_CONTEXT` (0x02)**: Edge from a nested contextual
lookup
When computing closure, track whether any flagged edges contributed to the result:
```c++
bool hit_flagged_edge = false;
// When following an edge (entry is hb_subset_depend_entry_t)...
if (!hb_set_has(closure, entry.dependent)) {
hb_set_add(closure, entry.dependent);
if (entry.flags & (HB_SUBSET_DEPEND_EDGE_FLAG_FROM_CONTEXT_POSITION |
HB_SUBSET_DEPEND_EDGE_FLAG_FROM_NESTED_CONTEXT))
hit_flagged_edge = true;
}
// After closure computation...
if (depend_closure.is_superset_of(subset_closure)) {
if (hit_flagged_edge)
printf("Expected over-approximation (hit flagged edge)\n");
else
printf("UNEXPECTED over-approximation (bug)\n");
}
```
## Optional Over-Approximation
You can choose to skip context_set checking for:
- **Simpler implementation** - Fewer steps, less complex code
- **Coverage analysis** - Finding all glyphs that participate in a feature, regardless
of whether they're reachable for specific input
- **Conservative estimates** - Ensuring no glyphs are missed
Skipping context_set produces a safe over-approximation - all needed glyphs are
included, possibly with some additional glyphs that wouldn't be reached for the
specific input.
## Use Cases
### Font Subsetting
Determine which glyphs must be retained to properly render a specific set of
characters, accounting for all OpenType substitutions and compositions.
With context_set filtering, depend-based subsetting produces results that match
subset-based subsetting in recent testing.
### Coverage Analysis
Analyze which features or scripts require which glyphs. For this use case,
skipping context_set filtering may be desirable - it shows all glyphs that
participate in a feature, not just those reachable for specific input.
### Font Segmentation
Partition a font into smaller subsets where each segment contains glyphs reachable
from a specific set of input characters, enabling more efficient font delivery
for web applications.
### Testing
Verify that font modifications haven't inadvertently broken glyph references or
substitution chains. The depend graph provides a complete view of font structure
that can be compared before and after modifications.
## Implementation Notes
### Non-GSUB Dependencies
Dependencies from non-GSUB tables do not require context filtering:
- **Composite glyphs (glyf, CFF)**: Structural component relationships
- **Color layers (COLR)**: Layer composition
- **Math variants (MATH)**: Size variant relationships
These dependencies should always be followed during closure computation.
**Note**: Unicode Variation Sequences (UVS) are handled separately via
`hb_font_get_variation_glyph()` and are not part of the depend graph.
### Feature Filtering
GSUB edges should be filtered by active features. The layout_tag field contains
the feature tag for GSUB dependencies. Only follow edges where the feature is
active in your shaping configuration.
### Ligature Filtering
Ligature dependencies should only be followed when ALL component glyphs in the
ligature_set are present in the current closure. This prevents adding ligature
outputs prematurely.
## Further Reading
For detailed API documentation:
- `docs/depend-api.md` - API usage guide with examples
- `docs/depend-implementation.md` - Implementation details and architecture
For reference implementation:
- `test/fuzzing/hb-depend-closure-parity.cc` - Production-quality closure computation
- `test/api/test-ot-depend.c` - Unit tests for depend API