| # 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, VARC) | |
| - 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 |