| # Introduction | |
| **⚠️ WARNING: This API is highly experimental and subject to change. It is not | |
| enabled by default and should not be used in production without understanding | |
| the stability implications. The API may change significantly in future releases.** | |
| The dependency API provides access to a glyph dependency graph that represents | |
| how glyphs in an OpenType font reference or produce other glyphs. The graph | |
| maps input glyphs to output glyphs through various OpenType mechanisms: | |
| - **Glyph substitution (GSUB)**: Tracks which glyphs can be substituted for | |
| other glyphs through OpenType Layout features | |
| - **Composite glyphs (glyf, CFF)**: Records component glyphs used in composite | |
| glyph construction (TrueType glyf composites and CFF1 SEAC) | |
| - **Color layers (COLR)**: Identifies glyphs used as color layers | |
| - **Math variants (MATH)**: Tracks mathematical variant glyphs | |
| **Note**: Unicode Variation Sequences (UVS) are not included in the dependency | |
| graph. Handle UVS separately using `hb_font_get_variation_glyph()`. | |
| The dependency graph enables finding the transitive closure of all glyphs | |
| reachable from a given input set, which is useful for font subsetting, | |
| analyzing glyph coverage, and optimizing font delivery. | |
| # Building with the Depend API | |
| The dependency API is guarded by `HB_NO_SUBSET_DEPEND`, which is defined by | |
| default. To enable it, either enable the `experimental_api` build option or | |
| define `HB_SUBSET_DEPEND_API` to opt in to just this API. | |
| ## Enabling via build option | |
| ```bash | |
| meson setup build -Dexperimental_api=true | |
| meson compile -C build | |
| ``` | |
| In code, check for availability: | |
| ```c | |
| #ifdef HB_SUBSET_DEPEND_API | |
| // Use dependency API | |
| #endif | |
| ``` | |
| ## Enabling just the depend API | |
| Define `HB_SUBSET_DEPEND_API` on your compiler command line or in a config | |
| header to enable the depend API without enabling all experimental APIs: | |
| ```bash | |
| cc ... -DHB_SUBSET_DEPEND_API ... | |
| ``` | |
| ## Discovering available options | |
| To see all available HarfBuzz build options, including `experimental_api`: | |
| ```bash | |
| # After setting up a build directory | |
| meson configure build | |
| # Or to see just the option values | |
| meson introspect build --buildoptions | jq '.[] | select(.section == "user")' | |
| ``` | |
| You can also view all available options in the source file `meson_options.txt`. | |
| **Important:** This API is experimental and may change without notice. Do not | |
| use in production applications without being prepared for breaking changes in | |
| future HarfBuzz releases. | |
| # Usage | |
| ## Basic Example | |
| ```c | |
| #include <hb.h> | |
| hb_blob_t *blob = hb_blob_create_from_file("font.ttf"); | |
| hb_face_t *face = hb_face_create(blob, 0); | |
| // Extract dependency graph | |
| hb_subset_depend_t *depend = hb_subset_depend_from_face_or_fail(face); | |
| if (!depend) { | |
| // Handle error | |
| return; | |
| } | |
| // Query dependencies for a specific glyph | |
| hb_codepoint_t gid = 42; // Glyph to query | |
| unsigned int total = hb_subset_depend_lookup_glyph(depend, gid, 0, NULL, NULL); | |
| printf("Dependencies for glyph %u:\n", gid); | |
| for (unsigned int i = 0; i < total; i++) { | |
| hb_subset_depend_entry_t entry; | |
| unsigned int count = 1; | |
| hb_subset_depend_lookup_glyph(depend, gid, i, &count, &entry); | |
| printf(" -> glyph %u via %c%c%c%c\n", | |
| entry.dependent, HB_UNTAG(entry.table_tag)); | |
| } | |
| // Clean up | |
| hb_subset_depend_destroy(depend); | |
| hb_face_destroy(face); | |
| hb_blob_destroy(blob); | |
| ``` | |
| ## Iterating Dependencies | |
| To programmatically access dependency information for a specific glyph: | |
| ```c | |
| hb_codepoint_t gid = 42; // Glyph ID to query | |
| // Get total count, then iterate | |
| unsigned int total = hb_subset_depend_lookup_glyph(depend, gid, 0, NULL, NULL); | |
| for (unsigned int i = 0; i < total; i++) { | |
| hb_subset_depend_entry_t entry; | |
| unsigned int count = 1; | |
| hb_subset_depend_lookup_glyph(depend, gid, i, &count, &entry); | |
| // Process dependency information | |
| if (entry.table_tag == HB_OT_TAG_GSUB) { | |
| // GSUB dependency: layout_tag contains feature tag | |
| printf("GID %u -> %u via GSUB feature '%c%c%c%c'\n", | |
| gid, entry.dependent, HB_UNTAG(entry.layout_tag)); | |
| } else { | |
| // Other dependencies (glyf, CFF, COLR, MATH, VARC) | |
| printf("GID %u -> %u via %c%c%c%c\n", | |
| gid, entry.dependent, HB_UNTAG(entry.table_tag)); | |
| } | |
| } | |
| ``` | |
| ## Finding Reachable Glyphs | |
| To compute all glyphs reachable from a starting set (transitive closure): | |
| ```c | |
| void find_reachable_glyphs(hb_subset_depend_t *depend, hb_set_t *reachable) | |
| { | |
| hb_set_t *to_process = hb_set_create(); | |
| hb_set_union(to_process, reachable); | |
| // Process glyphs until none remain | |
| while (!hb_set_is_empty(to_process)) { | |
| hb_codepoint_t gid = hb_set_get_min(to_process); | |
| hb_set_del(to_process, gid); | |
| unsigned int total = hb_subset_depend_lookup_glyph(depend, gid, 0, NULL, NULL); | |
| for (unsigned int i = 0; i < total; i++) { | |
| hb_subset_depend_entry_t entry; | |
| unsigned int count = 1; | |
| hb_subset_depend_lookup_glyph(depend, gid, i, &count, &entry); | |
| if (!hb_set_has(reachable, entry.dependent)) { | |
| hb_set_add(reachable, entry.dependent); | |
| hb_set_add(to_process, entry.dependent); | |
| } | |
| } | |
| } | |
| hb_set_destroy(to_process); | |
| } | |
| // Usage: | |
| hb_set_t *reachable = hb_set_create(); | |
| hb_set_add(reachable, 100); | |
| hb_set_add(reachable, 101); | |
| find_reachable_glyphs(depend, reachable); | |
| printf("Found %u reachable glyphs\n", hb_set_get_population(reachable)); | |
| hb_set_destroy(reachable); | |
| ``` | |
| **Note:** This simple algorithm doesn't handle ligature dependencies or context sets | |
| correctly. Ligatures should only be added when all component glyphs in their ligature | |
| set are present, and contextual dependencies should only be followed when their positional | |
| requirements are satisfied. For a production-quality implementation that correctly handles | |
| ligatures, context filtering, and feature filtering to compute closures similar to | |
| HarfBuzz's subset API, see `compute_depend_closure()` in `test/fuzzing/hb-depend-closure-parity.cc` | |
| and `docs/depend-for-closure.md`. | |
| ## Working with Ligature Sets | |
| When a dependency entry belongs to a ligature set, you can find all other members | |
| of that set using `hb_subset_depend_lookup_set()`: | |
| ```c | |
| // After finding entry.ligature_set_index != HB_CODEPOINT_INVALID... | |
| // Get all glyphs in this ligature set | |
| hb_set_t *ligature_glyphs = hb_set_create(); | |
| if (hb_subset_depend_lookup_set(depend, entry.ligature_set_index, ligature_glyphs)) { | |
| hb_codepoint_t lig_gid = HB_SET_VALUE_INVALID; | |
| printf("Members of ligature set %u:\n", entry.ligature_set_index); | |
| while (hb_set_next(ligature_glyphs, &lig_gid)) { | |
| // Find the dependency entry index for this glyph in this ligature set | |
| unsigned int lig_total = hb_subset_depend_lookup_glyph(depend, lig_gid, 0, NULL, NULL); | |
| for (unsigned int j = 0; j < lig_total; j++) { | |
| hb_subset_depend_entry_t lig_entry; | |
| unsigned int count = 1; | |
| hb_subset_depend_lookup_glyph(depend, lig_gid, j, &count, &lig_entry); | |
| if (lig_entry.ligature_set_index == entry.ligature_set_index) { | |
| printf(" gid=%u, index=%u\n", lig_gid, j); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| hb_set_destroy(ligature_glyphs); | |
| ``` | |
| ## Working with Context Sets | |
| Context and ChainContext GSUB rules apply lookups only when specific backtrack and/or | |
| lookahead glyphs are present. The depend API records these requirements in | |
| `context_set_index` fields as optional information that can refine dependency traversal. | |
| **Note:** Context sets are optional refinement information. Uses of the dependency graph | |
| that ignore `context_set_index` (treating it as always satisfied) work fine - they produce a | |
| conservative over-approximation. Context sets allow more precise closure computation when | |
| desired. | |
| ### Understanding Context Sets | |
| When `entry.context_set_index != HB_CODEPOINT_INVALID`, the edge includes positional | |
| requirements from a Context or ChainContext rule. Context set elements use two encodings: | |
| 1. **Direct GID reference** (value < 0x80000000): A specific glyph ID | |
| 2. **Indirect set reference** (value >= 0x80000000): An index into the sets array | |
| (mask off high bit with `value & 0x7FFFFFFF` to get the set index) | |
| The high bit (0x80000000) distinguishes between direct and indirect references. For an | |
| edge to apply, ALL elements in the context set must be satisfied: | |
| - Direct references: that specific glyph must be present in the closure | |
| - Indirect references: at least ONE glyph from the referenced set must be present (disjunction) | |
| ### Checking Context Requirements | |
| ```c | |
| bool check_context_satisfied(hb_subset_depend_t *depend, | |
| hb_codepoint_t context_set_idx, | |
| hb_set_t *current_closure) | |
| { | |
| if (context_set_idx == HB_CODEPOINT_INVALID) | |
| return true; // No context requirements | |
| hb_set_t *context_elements = hb_set_create(); | |
| if (!hb_subset_depend_lookup_set(depend, context_set_idx, context_elements)) | |
| { | |
| hb_set_destroy(context_elements); | |
| return false; // Error retrieving context | |
| } | |
| bool satisfied = true; | |
| hb_codepoint_t elem = HB_SET_VALUE_INVALID; | |
| while (hb_set_next(context_elements, &elem)) | |
| { | |
| if (elem < 0x80000000) | |
| { | |
| // Direct reference: check if specific glyph is in closure | |
| if (!hb_set_has(current_closure, elem)) | |
| { | |
| satisfied = false; | |
| break; | |
| } | |
| } | |
| else | |
| { | |
| // Indirect reference: check if ANY glyph from set is in closure | |
| hb_codepoint_t set_idx = elem & 0x7FFFFFFF; | |
| hb_set_t *required_set = hb_set_create(); | |
| if (hb_subset_depend_lookup_set(depend, set_idx, required_set)) | |
| { | |
| // Check if ANY element from required_set is in current_closure | |
| bool any_found = false; | |
| hb_codepoint_t glyph = HB_SET_VALUE_INVALID; | |
| while (hb_set_next(required_set, &glyph)) | |
| { | |
| if (hb_set_has(current_closure, glyph)) | |
| { | |
| any_found = true; | |
| break; | |
| } | |
| } | |
| if (!any_found) | |
| satisfied = false; | |
| } | |
| else | |
| { | |
| satisfied = false; // Error retrieving set | |
| } | |
| hb_set_destroy(required_set); | |
| if (!satisfied) | |
| break; | |
| } | |
| } | |
| hb_set_destroy(context_elements); | |
| return satisfied; | |
| } | |
| ``` | |
| For a complete implementation of context-aware closure computation, see the | |
| `compute_depend_closure()` function in `test/fuzzing/hb-depend-closure-parity.cc`. | |
| # API Details | |
| ## Dependency Entry Fields | |
| Each `hb_subset_depend_entry_t` filled by `hb_subset_depend_lookup_glyph()` contains: | |
| - **table_tag**: Source table (e.g., `HB_OT_TAG_GSUB`, | |
| `HB_TAG('g','l','y','f')`, `HB_TAG('C','F','F',' ')`, `HB_TAG('C','O','L','R')`, | |
| `HB_TAG('M','A','T','H')`, `HB_TAG('V','A','R','C')`) | |
| - **dependent**: The dependent glyph ID | |
| - **layout_tag**: | |
| - For GSUB: the feature tag (e.g., `HB_TAG('l','i','g','a')`) | |
| - Otherwise: `HB_CODEPOINT_INVALID` | |
| - **ligature_set_index**: For ligatures, identifies which ligature set; otherwise | |
| `HB_CODEPOINT_INVALID` | |
| - **context_set_index**: Optional refinement information for Context and ChainContext GSUB | |
| rules. When not `HB_CODEPOINT_INVALID`, identifies positional requirements | |
| (backtrack/lookahead) for this dependency. Can be ignored (treated as always satisfied) | |
| for conservative over-approximation, or checked for more precise closure computation. | |
| See "Working with Context Sets" section for details. | |
| - **flags**: Edge metadata flags (`hb_subset_depend_edge_flags_t`). Currently defined: | |
| - `HB_SUBSET_DEPEND_EDGE_FLAG_FROM_CONTEXT_POSITION` (0x01): Edge created from a | |
| multi-position contextual rule (Context or ChainContext with inputCount > 1). Depend | |
| extraction records edges based on what glyphs COULD be at each position according to | |
| the static input coverage/class. But during closure computation, lookups within the | |
| rule are applied sequentially: lookups at earlier positions may transform glyphs at | |
| later positions, and multiple lookups at the same position may interact (one produces | |
| a glyph, another immediately consumes it as an "intermediate"). So a glyph matching | |
| the coverage might not actually persist at that position when closure traverses the | |
| rule. These edges may cause over-approximation. | |
| - `HB_SUBSET_DEPEND_EDGE_FLAG_FROM_NESTED_CONTEXT` (0x02): Edge created from a lookup | |
| that was called from within another contextual lookup (nested context). The outer | |
| context's requirements are not preserved on this edge, so it may over-approximate. | |
| These flags help distinguish between "true" over-approximation (a bug) and "expected" | |
| over-approximation (a known limitation of the static dependency analysis). Closure | |
| implementations can use these flags to report which type of over-approximation occurred. | |
| # Implementation Notes | |
| **⚠️ Warning: Graph Cycles in Invalid Fonts** | |
| The dependency graph may contain cycles when processing invalid or malicious fonts. | |
| While the OpenType specification requires COLR paint graphs to be directed acyclic | |
| graphs (DAGs), the depend API faithfully reports the graph structure as it exists | |
| in the font, including any cycles that may be present. Implementations traversing | |
| the depend graph should implement cycle detection to protect against invalid fonts. | |
| For details on how cycles can occur and how PaintGlyph self-references are filtered, | |
| see the "COLR Cycles and Self-References" section in `docs/depend-implementation.md`. | |
| For detailed information about the depend API implementation, including memory | |
| management, data structures, and performance characteristics, see | |
| `docs/depend-implementation.md`. | |
| # 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. | |
| ## Coverage Analysis | |
| Analyze which features or scripts require which glyphs, useful for font | |
| optimization and planning. | |
| ## 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. |