1/*
2 * Copyright © 2018 Google, Inc.
3 *
4 * This is part of HarfBuzz, a text shaping library.
5 *
6 * Permission is hereby granted, without written agreement and without
7 * license or royalty fees, to use, copy, modify, and distribute this
8 * software and its documentation for any purpose, provided that the
9 * above copyright notice and the following two paragraphs appear in
10 * all copies of this software.
11 *
12 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
13 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
14 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
15 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
16 * DAMAGE.
17 *
18 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
19 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20 * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
21 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
22 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23 *
24 * Google Author(s): Garret Rieger, Roderick Sheeter
25 */
26
27#include "hb-subset-plan.hh"
28#include "hb-subset-accelerator.hh"
29#include "hb-map.hh"
30#include "hb-multimap.hh"
31#include "hb-set.hh"
32
33#include "hb-ot-cmap-table.hh"
34#include "hb-ot-glyf-table.hh"
35#include "hb-ot-layout-gdef-table.hh"
36#include "hb-ot-layout-gpos-table.hh"
37#include "hb-ot-layout-gsub-table.hh"
38#include "hb-ot-cff1-table.hh"
39#include "hb-ot-cff2-table.hh"
40#include "OT/Color/COLR/COLR.hh"
41#include "OT/Color/COLR/colrv1-closure.hh"
42#include "OT/Color/CPAL/CPAL.hh"
43#include "hb-ot-var-fvar-table.hh"
44#include "hb-ot-var-avar-table.hh"
45#include "hb-ot-stat-table.hh"
46#include "hb-ot-math-table.hh"
47
48using OT::Layout::GSUB;
49using OT::Layout::GPOS;
50
51
52hb_subset_accelerator_t::~hb_subset_accelerator_t ()
53{
54 if (cmap_cache && destroy_cmap_cache)
55 destroy_cmap_cache ((void*) cmap_cache);
56
57#ifndef HB_NO_SUBSET_CFF
58 cff1_accel.fini ();
59 cff2_accel.fini ();
60#endif
61 hb_face_destroy (source);
62}
63
64
65typedef hb_hashmap_t<unsigned, hb::unique_ptr<hb_set_t>> script_langsys_map;
66#ifndef HB_NO_SUBSET_CFF
67static inline bool
68_add_cff_seac_components (const OT::cff1::accelerator_subset_t &cff,
69 hb_codepoint_t gid,
70 hb_set_t *gids_to_retain)
71{
72 hb_codepoint_t base_gid, accent_gid;
73 if (cff.get_seac_components (gid, &base_gid, &accent_gid))
74 {
75 gids_to_retain->add (base_gid);
76 gids_to_retain->add (accent_gid);
77 return true;
78 }
79 return false;
80}
81#endif
82
83static void
84_remap_palette_indexes (const hb_set_t *palette_indexes,
85 hb_map_t *mapping /* OUT */)
86{
87 unsigned new_idx = 0;
88 for (unsigned palette_index : palette_indexes->iter ())
89 {
90 if (palette_index == 0xFFFF)
91 {
92 mapping->set (palette_index, palette_index);
93 continue;
94 }
95 mapping->set (palette_index, new_idx);
96 new_idx++;
97 }
98}
99
100static void
101_remap_indexes (const hb_set_t *indexes,
102 hb_map_t *mapping /* OUT */)
103{
104 for (auto _ : + hb_enumerate (indexes->iter ()))
105 mapping->set (_.second, _.first);
106
107}
108
109#ifndef HB_NO_SUBSET_LAYOUT
110
111/*
112 * Removes all tags from 'tags' that are not in filter. Additionally eliminates any duplicates.
113 * Returns true if anything was removed (not including duplicates).
114 */
115static bool _filter_tag_list(hb_vector_t<hb_tag_t>* tags, /* IN/OUT */
116 const hb_set_t* filter)
117{
118 hb_vector_t<hb_tag_t> out;
119 out.alloc (tags->get_size() + 1); // +1 is to allocate room for the null terminator.
120
121 bool removed = false;
122 hb_set_t visited;
123
124 for (hb_tag_t tag : *tags)
125 {
126 if (!tag) continue;
127 if (visited.has (tag)) continue;
128
129 if (!filter->has (tag))
130 {
131 removed = true;
132 continue;
133 }
134
135 visited.add (tag);
136 out.push (tag);
137 }
138
139 // The collect function needs a null element to signal end of the array.
140 out.push (HB_TAG_NONE);
141
142 hb_swap (out, *tags);
143 return removed;
144}
145
146template <typename T>
147static void _collect_layout_indices (hb_subset_plan_t *plan,
148 const T& table,
149 hb_set_t *lookup_indices, /* OUT */
150 hb_set_t *feature_indices, /* OUT */
151 hb_hashmap_t<unsigned, hb::shared_ptr<hb_set_t>> *feature_record_cond_idx_map, /* OUT */
152 hb_hashmap_t<unsigned, const OT::Feature*> *feature_substitutes_map, /* OUT */
153 bool& insert_catch_all_feature_variation_record)
154{
155 unsigned num_features = table.get_feature_count ();
156 hb_vector_t<hb_tag_t> features;
157 if (!plan->check_success (features.resize (num_features))) return;
158 table.get_feature_tags (0, &num_features, features.arrayZ);
159 bool retain_all_features = !_filter_tag_list (&features, &plan->layout_features);
160
161 unsigned num_scripts = table.get_script_count ();
162 hb_vector_t<hb_tag_t> scripts;
163 if (!plan->check_success (scripts.resize (num_scripts))) return;
164 table.get_script_tags (0, &num_scripts, scripts.arrayZ);
165 bool retain_all_scripts = !_filter_tag_list (&scripts, &plan->layout_scripts);
166
167 if (!plan->check_success (!features.in_error ()) || !features
168 || !plan->check_success (!scripts.in_error ()) || !scripts)
169 return;
170
171 hb_ot_layout_collect_features (plan->source,
172 T::tableTag,
173 retain_all_scripts ? nullptr : scripts.arrayZ,
174 nullptr,
175 retain_all_features ? nullptr : features.arrayZ,
176 feature_indices);
177
178#ifndef HB_NO_VAR
179 // collect feature substitutes with variations
180 if (!plan->user_axes_location.is_empty ())
181 {
182 hb_hashmap_t<hb::shared_ptr<hb_map_t>, unsigned> conditionset_map;
183 OT::hb_collect_feature_substitutes_with_var_context_t c =
184 {
185 &plan->axes_old_index_tag_map,
186 &plan->axes_location,
187 feature_record_cond_idx_map,
188 feature_substitutes_map,
189 insert_catch_all_feature_variation_record,
190 feature_indices,
191 false,
192 false,
193 false,
194 0,
195 &conditionset_map
196 };
197 table.collect_feature_substitutes_with_variations (&c);
198 }
199#endif
200
201 for (unsigned feature_index : *feature_indices)
202 {
203 const OT::Feature* f = &(table.get_feature (feature_index));
204 const OT::Feature **p = nullptr;
205 if (feature_substitutes_map->has (feature_index, &p))
206 f = *p;
207
208 f->add_lookup_indexes_to (lookup_indices);
209 }
210
211 // If all axes are pinned then all feature variations will be dropped so there's no need
212 // to collect lookups from them.
213 if (!plan->all_axes_pinned)
214 {
215 // TODO(qxliu76): this collection doesn't work correctly for feature variations that are dropped
216 // but not applied. The collection will collect and retain the lookup indices
217 // associated with those dropped but not activated rules. Since partial instancing
218 // isn't yet supported this isn't an issue yet but will need to be fixed for
219 // partial instancing.
220 table.feature_variation_collect_lookups (feature_indices, feature_substitutes_map, lookup_indices);
221 }
222}
223
224
225static inline void
226_GSUBGPOS_find_duplicate_features (const OT::GSUBGPOS &g,
227 const hb_map_t *lookup_indices,
228 const hb_set_t *feature_indices,
229 const hb_hashmap_t<unsigned, const OT::Feature*> *feature_substitutes_map,
230 hb_map_t *duplicate_feature_map /* OUT */)
231{
232 if (feature_indices->is_empty ()) return;
233 hb_hashmap_t<hb_tag_t, hb::unique_ptr<hb_set_t>> unique_features;
234 //find out duplicate features after subset
235 for (unsigned i : feature_indices->iter ())
236 {
237 hb_tag_t t = g.get_feature_tag (i);
238 if (t == HB_MAP_VALUE_INVALID) continue;
239 if (!unique_features.has (t))
240 {
241 if (unlikely (!unique_features.set (t, hb::unique_ptr<hb_set_t> {hb_set_create ()})))
242 return;
243 if (unique_features.has (t))
244 unique_features.get (t)->add (i);
245 duplicate_feature_map->set (i, i);
246 continue;
247 }
248
249 bool found = false;
250
251 hb_set_t* same_tag_features = unique_features.get (t);
252 for (unsigned other_f_index : same_tag_features->iter ())
253 {
254 const OT::Feature* f = &(g.get_feature (i));
255 const OT::Feature **p = nullptr;
256 if (feature_substitutes_map->has (i, &p))
257 f = *p;
258
259 const OT::Feature* other_f = &(g.get_feature (other_f_index));
260 if (feature_substitutes_map->has (other_f_index, &p))
261 other_f = *p;
262
263 auto f_iter =
264 + hb_iter (f->lookupIndex)
265 | hb_filter (lookup_indices)
266 ;
267
268 auto other_f_iter =
269 + hb_iter (other_f->lookupIndex)
270 | hb_filter (lookup_indices)
271 ;
272
273 bool is_equal = true;
274 for (; f_iter && other_f_iter; f_iter++, other_f_iter++)
275 {
276 unsigned a = *f_iter;
277 unsigned b = *other_f_iter;
278 if (a != b) { is_equal = false; break; }
279 }
280
281 if (is_equal == false || f_iter || other_f_iter) continue;
282
283 found = true;
284 duplicate_feature_map->set (i, other_f_index);
285 break;
286 }
287
288 if (found == false)
289 {
290 same_tag_features->add (i);
291 duplicate_feature_map->set (i, i);
292 }
293 }
294}
295
296template <typename T>
297static inline void
298_closure_glyphs_lookups_features (hb_subset_plan_t *plan,
299 hb_set_t *gids_to_retain,
300 hb_map_t *lookups,
301 hb_map_t *features,
302 script_langsys_map *langsys_map,
303 hb_hashmap_t<unsigned, hb::shared_ptr<hb_set_t>> *feature_record_cond_idx_map,
304 hb_hashmap_t<unsigned, const OT::Feature*> *feature_substitutes_map,
305 bool& insert_catch_all_feature_variation_record)
306{
307 hb_blob_ptr_t<T> table = plan->source_table<T> ();
308 hb_tag_t table_tag = table->tableTag;
309 hb_set_t lookup_indices, feature_indices;
310 _collect_layout_indices<T> (plan,
311 *table,
312 &lookup_indices,
313 &feature_indices,
314 feature_record_cond_idx_map,
315 feature_substitutes_map,
316 insert_catch_all_feature_variation_record);
317
318 if (table_tag == HB_OT_TAG_GSUB && !(plan->flags & HB_SUBSET_FLAGS_NO_LAYOUT_CLOSURE))
319 hb_ot_layout_lookups_substitute_closure (plan->source,
320 &lookup_indices,
321 gids_to_retain);
322 table->closure_lookups (plan->source,
323 gids_to_retain,
324 &lookup_indices);
325 _remap_indexes (&lookup_indices, lookups);
326
327 // prune features
328 table->prune_features (lookups,
329 plan->user_axes_location.is_empty () ? nullptr : feature_record_cond_idx_map,
330 feature_substitutes_map,
331 &feature_indices);
332 hb_map_t duplicate_feature_map;
333 _GSUBGPOS_find_duplicate_features (*table, lookups, &feature_indices, feature_substitutes_map, &duplicate_feature_map);
334
335 feature_indices.clear ();
336 table->prune_langsys (&duplicate_feature_map, &plan->layout_scripts, langsys_map, &feature_indices);
337 _remap_indexes (&feature_indices, features);
338
339 table.destroy ();
340}
341
342#endif
343
344#ifndef HB_NO_VAR
345static inline void
346_generate_varstore_inner_maps (const hb_set_t& varidx_set,
347 unsigned subtable_count,
348 hb_vector_t<hb_inc_bimap_t> &inner_maps /* OUT */)
349{
350 if (varidx_set.is_empty () || subtable_count == 0) return;
351
352 if (unlikely (!inner_maps.resize (subtable_count))) return;
353 for (unsigned idx : varidx_set)
354 {
355 uint16_t major = idx >> 16;
356 uint16_t minor = idx & 0xFFFF;
357
358 if (major >= subtable_count)
359 continue;
360 inner_maps[major].add (minor);
361 }
362}
363
364static inline hb_font_t*
365_get_hb_font_with_variations (const hb_subset_plan_t *plan)
366{
367 hb_font_t *font = hb_font_create (plan->source);
368
369 hb_vector_t<hb_variation_t> vars;
370 if (!vars.alloc (plan->user_axes_location.get_population ())) {
371 hb_font_destroy (font);
372 return nullptr;
373 }
374
375 for (auto _ : plan->user_axes_location)
376 {
377 hb_variation_t var;
378 var.tag = _.first;
379 var.value = _.second.middle;
380 vars.push (var);
381 }
382
383#ifndef HB_NO_VAR
384 hb_font_set_variations (font, vars.arrayZ, plan->user_axes_location.get_population ());
385#endif
386 return font;
387}
388
389static inline void
390_collect_layout_variation_indices (hb_subset_plan_t* plan)
391{
392 hb_blob_ptr_t<OT::GDEF> gdef = plan->source_table<OT::GDEF> ();
393 hb_blob_ptr_t<GPOS> gpos = plan->source_table<GPOS> ();
394
395 if (!gdef->has_data ())
396 {
397 gdef.destroy ();
398 gpos.destroy ();
399 return;
400 }
401
402 const OT::VariationStore *var_store = nullptr;
403 hb_set_t varidx_set;
404 float *store_cache = nullptr;
405 bool collect_delta = plan->pinned_at_default ? false : true;
406 if (collect_delta)
407 {
408 if (gdef->has_var_store ())
409 {
410 var_store = &(gdef->get_var_store ());
411 store_cache = var_store->create_cache ();
412 }
413 }
414
415 OT::hb_collect_variation_indices_context_t c (&varidx_set,
416 &plan->layout_variation_idx_delta_map,
417 plan->normalized_coords ? &(plan->normalized_coords) : nullptr,
418 var_store,
419 &plan->_glyphset_gsub,
420 &plan->gpos_lookups,
421 store_cache);
422 gdef->collect_variation_indices (&c);
423
424 if (hb_ot_layout_has_positioning (plan->source))
425 gpos->collect_variation_indices (&c);
426
427 var_store->destroy_cache (store_cache);
428
429 gdef->remap_layout_variation_indices (&varidx_set, &plan->layout_variation_idx_delta_map);
430
431 unsigned subtable_count = gdef->has_var_store () ? gdef->get_var_store ().get_sub_table_count () : 0;
432 _generate_varstore_inner_maps (varidx_set, subtable_count, plan->gdef_varstore_inner_maps);
433
434 gdef.destroy ();
435 gpos.destroy ();
436}
437#endif
438
439static inline void
440_cmap_closure (hb_face_t *face,
441 const hb_set_t *unicodes,
442 hb_set_t *glyphset)
443{
444 OT::cmap::accelerator_t cmap (face);
445 cmap.table->closure_glyphs (unicodes, glyphset);
446}
447
448static void _colr_closure (hb_face_t *face,
449 hb_map_t *layers_map,
450 hb_map_t *palettes_map,
451 hb_set_t *glyphs_colred)
452{
453 OT::COLR::accelerator_t colr (face);
454 if (!colr.is_valid ()) return;
455
456 hb_set_t palette_indices, layer_indices;
457 // Collect all glyphs referenced by COLRv0
458 hb_set_t glyphset_colrv0;
459 for (hb_codepoint_t gid : *glyphs_colred)
460 colr.closure_glyphs (gid, &glyphset_colrv0);
461
462 glyphs_colred->union_ (glyphset_colrv0);
463
464 //closure for COLRv1
465 colr.closure_forV1 (glyphs_colred, &layer_indices, &palette_indices);
466
467 colr.closure_V0palette_indices (glyphs_colred, &palette_indices);
468 _remap_indexes (&layer_indices, layers_map);
469 _remap_palette_indexes (&palette_indices, palettes_map);
470}
471
472static inline void
473_math_closure (hb_subset_plan_t *plan,
474 hb_set_t *glyphset)
475{
476 hb_blob_ptr_t<OT::MATH> math = plan->source_table<OT::MATH> ();
477 if (math->has_data ())
478 math->closure_glyphs (glyphset);
479 math.destroy ();
480}
481
482
483static inline void
484_remove_invalid_gids (hb_set_t *glyphs,
485 unsigned int num_glyphs)
486{
487 glyphs->del_range (num_glyphs, HB_SET_VALUE_INVALID);
488}
489
490static void
491_populate_unicodes_to_retain (const hb_set_t *unicodes,
492 const hb_set_t *glyphs,
493 hb_subset_plan_t *plan)
494{
495 OT::cmap::accelerator_t cmap (plan->source);
496 unsigned size_threshold = plan->source->get_num_glyphs ();
497 if (glyphs->is_empty () && unicodes->get_population () < size_threshold)
498 {
499
500 const hb_map_t* unicode_to_gid = nullptr;
501 if (plan->accelerator)
502 unicode_to_gid = &plan->accelerator->unicode_to_gid;
503
504 // This is approach to collection is faster, but can only be used if glyphs
505 // are not being explicitly added to the subset and the input unicodes set is
506 // not excessively large (eg. an inverted set).
507 plan->unicode_to_new_gid_list.alloc (unicodes->get_population ());
508 if (!unicode_to_gid) {
509 for (hb_codepoint_t cp : *unicodes)
510 {
511 hb_codepoint_t gid;
512 if (!cmap.get_nominal_glyph (cp, &gid))
513 {
514 DEBUG_MSG(SUBSET, nullptr, "Drop U+%04X; no gid", cp);
515 continue;
516 }
517
518 plan->codepoint_to_glyph->set (cp, gid);
519 plan->unicode_to_new_gid_list.push (hb_pair (cp, gid));
520 }
521 } else {
522 // Use in memory unicode to gid map it's faster then looking up from
523 // the map. This code is mostly duplicated from above to avoid doing
524 // conditionals on the presence of the unicode_to_gid map each
525 // iteration.
526 for (hb_codepoint_t cp : *unicodes)
527 {
528 hb_codepoint_t gid = unicode_to_gid->get (cp);
529 if (gid == HB_MAP_VALUE_INVALID)
530 {
531 DEBUG_MSG(SUBSET, nullptr, "Drop U+%04X; no gid", cp);
532 continue;
533 }
534
535 plan->codepoint_to_glyph->set (cp, gid);
536 plan->unicode_to_new_gid_list.push (hb_pair (cp, gid));
537 }
538 }
539 }
540 else
541 {
542 // This approach is slower, but can handle adding in glyphs to the subset and will match
543 // them with cmap entries.
544
545 hb_map_t unicode_glyphid_map_storage;
546 hb_set_t cmap_unicodes_storage;
547 const hb_map_t* unicode_glyphid_map = &unicode_glyphid_map_storage;
548 const hb_set_t* cmap_unicodes = &cmap_unicodes_storage;
549
550 if (!plan->accelerator) {
551 cmap.collect_mapping (&cmap_unicodes_storage, &unicode_glyphid_map_storage);
552 plan->unicode_to_new_gid_list.alloc (hb_min(unicodes->get_population ()
553 + glyphs->get_population (),
554 cmap_unicodes->get_population ()));
555 } else {
556 unicode_glyphid_map = &plan->accelerator->unicode_to_gid;
557 cmap_unicodes = &plan->accelerator->unicodes;
558 }
559
560 if (plan->accelerator &&
561 unicodes->get_population () < cmap_unicodes->get_population () &&
562 glyphs->get_population () < cmap_unicodes->get_population ())
563 {
564 plan->codepoint_to_glyph->alloc (unicodes->get_population () + glyphs->get_population ());
565
566 auto &gid_to_unicodes = plan->accelerator->gid_to_unicodes;
567 for (hb_codepoint_t gid : *glyphs)
568 {
569 auto unicodes = gid_to_unicodes.get (gid);
570
571 for (hb_codepoint_t cp : unicodes)
572 {
573 plan->codepoint_to_glyph->set (cp, gid);
574 plan->unicode_to_new_gid_list.push (hb_pair (cp, gid));
575 }
576 }
577 for (hb_codepoint_t cp : *unicodes)
578 {
579 /* Don't double-add entry. */
580 if (plan->codepoint_to_glyph->has (cp))
581 continue;
582
583 hb_codepoint_t *gid;
584 if (!unicode_glyphid_map->has(cp, &gid))
585 continue;
586
587 plan->codepoint_to_glyph->set (cp, *gid);
588 plan->unicode_to_new_gid_list.push (hb_pair (cp, *gid));
589 }
590 plan->unicode_to_new_gid_list.qsort ();
591 }
592 else
593 {
594 plan->codepoint_to_glyph->alloc (cmap_unicodes->get_population ());
595 for (hb_codepoint_t cp : *cmap_unicodes)
596 {
597 hb_codepoint_t gid = (*unicode_glyphid_map)[cp];
598 if (!unicodes->has (cp) && !glyphs->has (gid))
599 continue;
600
601 plan->codepoint_to_glyph->set (cp, gid);
602 plan->unicode_to_new_gid_list.push (hb_pair (cp, gid));
603 }
604 }
605
606 /* Add gids which where requested, but not mapped in cmap */
607 unsigned num_glyphs = plan->source->get_num_glyphs ();
608 hb_codepoint_t first = HB_SET_VALUE_INVALID, last = HB_SET_VALUE_INVALID;
609 for (; glyphs->next_range (&first, &last); )
610 {
611 if (first >= num_glyphs)
612 break;
613 if (last >= num_glyphs)
614 last = num_glyphs - 1;
615 plan->_glyphset_gsub.add_range (first, last);
616 }
617 }
618
619 auto &arr = plan->unicode_to_new_gid_list;
620 if (arr.length)
621 {
622 plan->unicodes.add_sorted_array (&arr.arrayZ->first, arr.length, sizeof (*arr.arrayZ));
623 plan->_glyphset_gsub.add_array (&arr.arrayZ->second, arr.length, sizeof (*arr.arrayZ));
624 }
625}
626
627#ifndef HB_COMPOSITE_OPERATIONS_PER_GLYPH
628#define HB_COMPOSITE_OPERATIONS_PER_GLYPH 64
629#endif
630
631static unsigned
632_glyf_add_gid_and_children (const OT::glyf_accelerator_t &glyf,
633 hb_codepoint_t gid,
634 hb_set_t *gids_to_retain,
635 int operation_count,
636 unsigned depth = 0)
637{
638 /* Check if is already visited */
639 if (gids_to_retain->has (gid)) return operation_count;
640
641 gids_to_retain->add (gid);
642
643 if (unlikely (depth++ > HB_MAX_NESTING_LEVEL)) return operation_count;
644 if (unlikely (--operation_count < 0)) return operation_count;
645
646 auto glyph = glyf.glyph_for_gid (gid);
647
648 for (auto &item : glyph.get_composite_iterator ())
649 operation_count =
650 _glyf_add_gid_and_children (glyf,
651 item.get_gid (),
652 gids_to_retain,
653 operation_count,
654 depth);
655
656#ifndef HB_NO_VAR_COMPOSITES
657 for (auto &item : glyph.get_var_composite_iterator ())
658 {
659 operation_count =
660 _glyf_add_gid_and_children (glyf,
661 item.get_gid (),
662 gids_to_retain,
663 operation_count,
664 depth);
665 }
666#endif
667
668 return operation_count;
669}
670
671static void
672_nameid_closure (hb_subset_plan_t* plan,
673 hb_set_t* drop_tables)
674{
675#ifndef HB_NO_STYLE
676 plan->source->table.STAT->collect_name_ids (&plan->user_axes_location, &plan->name_ids);
677#endif
678#ifndef HB_NO_VAR
679 if (!plan->all_axes_pinned)
680 plan->source->table.fvar->collect_name_ids (&plan->user_axes_location, &plan->axes_old_index_tag_map, &plan->name_ids);
681#endif
682#ifndef HB_NO_COLOR
683 if (!drop_tables->has (HB_OT_TAG_CPAL))
684 plan->source->table.CPAL->collect_name_ids (&plan->colr_palettes, &plan->name_ids);
685#endif
686
687#ifndef HB_NO_SUBSET_LAYOUT
688 if (!drop_tables->has (HB_OT_TAG_GPOS))
689 {
690 hb_blob_ptr_t<GPOS> gpos = plan->source_table<GPOS> ();
691 gpos->collect_name_ids (&plan->gpos_features, &plan->name_ids);
692 gpos.destroy ();
693 }
694 if (!drop_tables->has (HB_OT_TAG_GSUB))
695 {
696 hb_blob_ptr_t<GSUB> gsub = plan->source_table<GSUB> ();
697 gsub->collect_name_ids (&plan->gsub_features, &plan->name_ids);
698 gsub.destroy ();
699 }
700#endif
701}
702
703static void
704_populate_gids_to_retain (hb_subset_plan_t* plan,
705 hb_set_t* drop_tables)
706{
707 OT::glyf_accelerator_t glyf (plan->source);
708#ifndef HB_NO_SUBSET_CFF
709 // Note: we cannot use inprogress_accelerator here, since it has not been
710 // created yet. So in case of preprocessed-face (and otherwise), we do an
711 // extra sanitize pass here, which is not ideal.
712 OT::cff1::accelerator_subset_t stack_cff (plan->accelerator ? nullptr : plan->source);
713 const OT::cff1::accelerator_subset_t *cff (plan->accelerator ? plan->accelerator->cff1_accel.get () : &stack_cff);
714#endif
715
716 plan->_glyphset_gsub.add (0); // Not-def
717
718 _cmap_closure (plan->source, &plan->unicodes, &plan->_glyphset_gsub);
719
720#ifndef HB_NO_SUBSET_LAYOUT
721 if (!drop_tables->has (HB_OT_TAG_GSUB))
722 // closure all glyphs/lookups/features needed for GSUB substitutions.
723 _closure_glyphs_lookups_features<GSUB> (
724 plan,
725 &plan->_glyphset_gsub,
726 &plan->gsub_lookups,
727 &plan->gsub_features,
728 &plan->gsub_langsys,
729 &plan->gsub_feature_record_cond_idx_map,
730 &plan->gsub_feature_substitutes_map,
731 plan->gsub_insert_catch_all_feature_variation_rec);
732
733 if (!drop_tables->has (HB_OT_TAG_GPOS))
734 _closure_glyphs_lookups_features<GPOS> (
735 plan,
736 &plan->_glyphset_gsub,
737 &plan->gpos_lookups,
738 &plan->gpos_features,
739 &plan->gpos_langsys,
740 &plan->gpos_feature_record_cond_idx_map,
741 &plan->gpos_feature_substitutes_map,
742 plan->gpos_insert_catch_all_feature_variation_rec);
743#endif
744 _remove_invalid_gids (&plan->_glyphset_gsub, plan->source->get_num_glyphs ());
745
746 plan->_glyphset_mathed = plan->_glyphset_gsub;
747 if (!drop_tables->has (HB_OT_TAG_MATH))
748 {
749 _math_closure (plan, &plan->_glyphset_mathed);
750 _remove_invalid_gids (&plan->_glyphset_mathed, plan->source->get_num_glyphs ());
751 }
752
753 hb_set_t cur_glyphset = plan->_glyphset_mathed;
754 if (!drop_tables->has (HB_OT_TAG_COLR))
755 {
756 _colr_closure (plan->source, &plan->colrv1_layers, &plan->colr_palettes, &cur_glyphset);
757 _remove_invalid_gids (&cur_glyphset, plan->source->get_num_glyphs ());
758 }
759
760 plan->_glyphset_colred = cur_glyphset;
761
762 _nameid_closure (plan, drop_tables);
763 /* Populate a full set of glyphs to retain by adding all referenced
764 * composite glyphs. */
765 if (glyf.has_data ())
766 for (hb_codepoint_t gid : cur_glyphset)
767 _glyf_add_gid_and_children (glyf, gid, &plan->_glyphset,
768 cur_glyphset.get_population () * HB_COMPOSITE_OPERATIONS_PER_GLYPH);
769 else
770 plan->_glyphset.union_ (cur_glyphset);
771#ifndef HB_NO_SUBSET_CFF
772 if (!plan->accelerator || plan->accelerator->has_seac)
773 {
774 bool has_seac = false;
775 if (cff->is_valid ())
776 for (hb_codepoint_t gid : cur_glyphset)
777 if (_add_cff_seac_components (*cff, gid, &plan->_glyphset))
778 has_seac = true;
779 plan->has_seac = has_seac;
780 }
781#endif
782
783 _remove_invalid_gids (&plan->_glyphset, plan->source->get_num_glyphs ());
784
785#ifndef HB_NO_VAR
786 if (!drop_tables->has (HB_OT_TAG_GDEF))
787 _collect_layout_variation_indices (plan);
788#endif
789}
790
791static void
792_create_glyph_map_gsub (const hb_set_t* glyph_set_gsub,
793 const hb_map_t* glyph_map,
794 hb_map_t* out)
795{
796 out->alloc (glyph_set_gsub->get_population ());
797 + hb_iter (glyph_set_gsub)
798 | hb_map ([&] (hb_codepoint_t gid) {
799 return hb_codepoint_pair_t (gid, glyph_map->get (gid));
800 })
801 | hb_sink (out)
802 ;
803}
804
805static bool
806_create_old_gid_to_new_gid_map (const hb_face_t *face,
807 bool retain_gids,
808 const hb_set_t *all_gids_to_retain,
809 const hb_map_t *requested_glyph_map,
810 hb_map_t *glyph_map, /* OUT */
811 hb_map_t *reverse_glyph_map, /* OUT */
812 hb_sorted_vector_t<hb_codepoint_pair_t> *new_to_old_gid_list /* OUT */,
813 unsigned int *num_glyphs /* OUT */)
814{
815 unsigned pop = all_gids_to_retain->get_population ();
816 reverse_glyph_map->alloc (pop);
817 glyph_map->alloc (pop);
818 new_to_old_gid_list->alloc (pop);
819
820 if (*requested_glyph_map)
821 {
822 hb_set_t new_gids(requested_glyph_map->values());
823 if (new_gids.get_population() != requested_glyph_map->get_population())
824 {
825 DEBUG_MSG (SUBSET, nullptr, "The provided custom glyph mapping is not unique.");
826 return false;
827 }
828
829 if (retain_gids)
830 {
831 DEBUG_MSG (SUBSET, nullptr,
832 "HB_SUBSET_FLAGS_RETAIN_GIDS cannot be set if "
833 "a custom glyph mapping has been provided.");
834 return false;
835 }
836
837 hb_codepoint_t max_glyph = 0;
838 hb_set_t remaining;
839 for (auto old_gid : all_gids_to_retain->iter ())
840 {
841 if (old_gid == 0) {
842 new_to_old_gid_list->push (hb_pair<hb_codepoint_t, hb_codepoint_t> (0u, 0u));
843 continue;
844 }
845
846 hb_codepoint_t* new_gid;
847 if (!requested_glyph_map->has (old_gid, &new_gid))
848 {
849 remaining.add(old_gid);
850 continue;
851 }
852
853 if (*new_gid > max_glyph)
854 max_glyph = *new_gid;
855 new_to_old_gid_list->push (hb_pair (*new_gid, old_gid));
856 }
857 new_to_old_gid_list->qsort ();
858
859 // Anything that wasn't mapped by the requested mapping should
860 // be placed after the requested mapping.
861 for (auto old_gid : remaining)
862 new_to_old_gid_list->push (hb_pair (++max_glyph, old_gid));
863
864 *num_glyphs = max_glyph + 1;
865 }
866 else if (!retain_gids)
867 {
868 + hb_enumerate (hb_iter (all_gids_to_retain), (hb_codepoint_t) 0)
869 | hb_sink (new_to_old_gid_list)
870 ;
871 *num_glyphs = new_to_old_gid_list->length;
872 }
873 else
874 {
875 + hb_iter (all_gids_to_retain)
876 | hb_map ([] (hb_codepoint_t _) {
877 return hb_codepoint_pair_t (_, _);
878 })
879 | hb_sink (new_to_old_gid_list)
880 ;
881
882 hb_codepoint_t max_glyph = HB_SET_VALUE_INVALID;
883 hb_set_previous (all_gids_to_retain, &max_glyph);
884
885 *num_glyphs = max_glyph + 1;
886 }
887
888 + hb_iter (new_to_old_gid_list)
889 | hb_sink (reverse_glyph_map)
890 ;
891 + hb_iter (new_to_old_gid_list)
892 | hb_map (&hb_codepoint_pair_t::reverse)
893 | hb_sink (glyph_map)
894 ;
895
896 return true;
897}
898
899#ifndef HB_NO_VAR
900static void
901_normalize_axes_location (hb_face_t *face, hb_subset_plan_t *plan)
902{
903 if (plan->user_axes_location.is_empty ())
904 return;
905
906 hb_array_t<const OT::AxisRecord> axes = face->table.fvar->get_axes ();
907 plan->normalized_coords.resize (axes.length);
908
909 bool has_avar = face->table.avar->has_data ();
910 const OT::SegmentMaps *seg_maps = nullptr;
911 unsigned avar_axis_count = 0;
912 if (has_avar)
913 {
914 seg_maps = face->table.avar->get_segment_maps ();
915 avar_axis_count = face->table.avar->get_axis_count();
916 }
917
918 bool axis_not_pinned = false;
919 unsigned old_axis_idx = 0, new_axis_idx = 0;
920 for (const auto& axis : axes)
921 {
922 hb_tag_t axis_tag = axis.get_axis_tag ();
923 plan->axes_old_index_tag_map.set (old_axis_idx, axis_tag);
924
925 if (!plan->user_axes_location.has (axis_tag) ||
926 !plan->user_axes_location.get (axis_tag).is_point ())
927 {
928 axis_not_pinned = true;
929 plan->axes_index_map.set (old_axis_idx, new_axis_idx);
930 new_axis_idx++;
931 }
932
933 Triple *axis_range;
934 if (plan->user_axes_location.has (axis_tag, &axis_range))
935 {
936 plan->axes_triple_distances.set (axis_tag, axis.get_triple_distances ());
937
938 int normalized_min = axis.normalize_axis_value (axis_range->minimum);
939 int normalized_default = axis.normalize_axis_value (axis_range->middle);
940 int normalized_max = axis.normalize_axis_value (axis_range->maximum);
941
942 if (has_avar && old_axis_idx < avar_axis_count)
943 {
944 normalized_min = seg_maps->map (normalized_min);
945 normalized_default = seg_maps->map (normalized_default);
946 normalized_max = seg_maps->map (normalized_max);
947 }
948 plan->axes_location.set (axis_tag, Triple (static_cast<float> (normalized_min / 16384.f),
949 static_cast<float> (normalized_default / 16384.f),
950 static_cast<float> (normalized_max / 16384.f)));
951
952 if (normalized_default != 0)
953 plan->pinned_at_default = false;
954
955 plan->normalized_coords[old_axis_idx] = normalized_default;
956 }
957
958 old_axis_idx++;
959
960 if (has_avar && old_axis_idx < avar_axis_count)
961 seg_maps = &StructAfter<OT::SegmentMaps> (*seg_maps);
962 }
963 plan->all_axes_pinned = !axis_not_pinned;
964}
965
966static void
967_update_instance_metrics_map_from_cff2 (hb_subset_plan_t *plan)
968{
969 if (!plan->normalized_coords) return;
970 OT::cff2::accelerator_t cff2 (plan->source);
971 if (!cff2.is_valid ()) return;
972
973 hb_font_t *font = nullptr;
974 if (unlikely (!plan->check_success (font = _get_hb_font_with_variations (plan))))
975 {
976 hb_font_destroy (font);
977 return;
978 }
979
980 hb_glyph_extents_t extents = {0x7FFF, -0x7FFF};
981 OT::hmtx_accelerator_t _hmtx (plan->source);
982 float *hvar_store_cache = nullptr;
983 if (_hmtx.has_data () && _hmtx.var_table.get_length ())
984 hvar_store_cache = _hmtx.var_table->get_var_store ().create_cache ();
985
986 OT::vmtx_accelerator_t _vmtx (plan->source);
987 float *vvar_store_cache = nullptr;
988 if (_vmtx.has_data () && _vmtx.var_table.get_length ())
989 vvar_store_cache = _vmtx.var_table->get_var_store ().create_cache ();
990
991 for (auto p : *plan->glyph_map)
992 {
993 hb_codepoint_t old_gid = p.first;
994 hb_codepoint_t new_gid = p.second;
995 if (!cff2.get_extents (font, old_gid, &extents)) continue;
996 bool has_bounds_info = true;
997 if (extents.x_bearing == 0 && extents.width == 0 &&
998 extents.height == 0 && extents.y_bearing == 0)
999 has_bounds_info = false;
1000
1001 if (has_bounds_info)
1002 {
1003 plan->head_maxp_info.xMin = hb_min (plan->head_maxp_info.xMin, extents.x_bearing);
1004 plan->head_maxp_info.xMax = hb_max (plan->head_maxp_info.xMax, extents.x_bearing + extents.width);
1005 plan->head_maxp_info.yMax = hb_max (plan->head_maxp_info.yMax, extents.y_bearing);
1006 plan->head_maxp_info.yMin = hb_min (plan->head_maxp_info.yMin, extents.y_bearing + extents.height);
1007 }
1008
1009 if (_hmtx.has_data ())
1010 {
1011 int hori_aw = _hmtx.get_advance_without_var_unscaled (old_gid);
1012 if (_hmtx.var_table.get_length ())
1013 hori_aw += (int) roundf (_hmtx.var_table->get_advance_delta_unscaled (old_gid, font->coords, font->num_coords,
1014 hvar_store_cache));
1015 int lsb = extents.x_bearing;
1016 if (!has_bounds_info)
1017 {
1018 if (!_hmtx.get_leading_bearing_without_var_unscaled (old_gid, &lsb))
1019 continue;
1020 }
1021 plan->hmtx_map.set (new_gid, hb_pair ((unsigned) hori_aw, lsb));
1022 plan->bounds_width_vec[new_gid] = extents.width;
1023 }
1024
1025 if (_vmtx.has_data ())
1026 {
1027 int vert_aw = _vmtx.get_advance_without_var_unscaled (old_gid);
1028 if (_vmtx.var_table.get_length ())
1029 vert_aw += (int) roundf (_vmtx.var_table->get_advance_delta_unscaled (old_gid, font->coords, font->num_coords,
1030 vvar_store_cache));
1031
1032 int tsb = extents.y_bearing;
1033 if (!has_bounds_info)
1034 {
1035 if (!_vmtx.get_leading_bearing_without_var_unscaled (old_gid, &tsb))
1036 continue;
1037 }
1038 plan->vmtx_map.set (new_gid, hb_pair ((unsigned) vert_aw, tsb));
1039 plan->bounds_height_vec[new_gid] = extents.height;
1040 }
1041 }
1042 hb_font_destroy (font);
1043 if (hvar_store_cache)
1044 _hmtx.var_table->get_var_store ().destroy_cache (hvar_store_cache);
1045 if (vvar_store_cache)
1046 _vmtx.var_table->get_var_store ().destroy_cache (vvar_store_cache);
1047}
1048#endif
1049
1050hb_subset_plan_t::hb_subset_plan_t (hb_face_t *face,
1051 const hb_subset_input_t *input)
1052{
1053 successful = true;
1054 flags = input->flags;
1055
1056 unicode_to_new_gid_list.init ();
1057
1058 name_ids = *input->sets.name_ids;
1059 name_languages = *input->sets.name_languages;
1060 layout_features = *input->sets.layout_features;
1061 layout_scripts = *input->sets.layout_scripts;
1062 glyphs_requested = *input->sets.glyphs;
1063 drop_tables = *input->sets.drop_tables;
1064 no_subset_tables = *input->sets.no_subset_tables;
1065 source = hb_face_reference (face);
1066 dest = hb_face_builder_create ();
1067
1068 codepoint_to_glyph = hb_map_create ();
1069 glyph_map = hb_map_create ();
1070 reverse_glyph_map = hb_map_create ();
1071
1072 gsub_insert_catch_all_feature_variation_rec = false;
1073 gpos_insert_catch_all_feature_variation_rec = false;
1074 gdef_varstore_inner_maps.init ();
1075
1076 user_axes_location = input->axes_location;
1077 all_axes_pinned = false;
1078 pinned_at_default = true;
1079
1080#ifdef HB_EXPERIMENTAL_API
1081 for (auto _ : input->name_table_overrides)
1082 {
1083 hb_bytes_t name_bytes = _.second;
1084 unsigned len = name_bytes.length;
1085 char *name_str = (char *) hb_malloc (len);
1086 if (unlikely (!check_success (name_str)))
1087 break;
1088
1089 hb_memcpy (name_str, name_bytes.arrayZ, len);
1090 name_table_overrides.set (_.first, hb_bytes_t (name_str, len));
1091 }
1092#endif
1093
1094 void* accel = hb_face_get_user_data(face, hb_subset_accelerator_t::user_data_key());
1095
1096 attach_accelerator_data = input->attach_accelerator_data;
1097 force_long_loca = input->force_long_loca;
1098 if (accel)
1099 accelerator = (hb_subset_accelerator_t*) accel;
1100
1101 if (unlikely (in_error ()))
1102 return;
1103
1104#ifndef HB_NO_VAR
1105 _normalize_axes_location (face, this);
1106#endif
1107
1108 _populate_unicodes_to_retain (input->sets.unicodes, input->sets.glyphs, this);
1109
1110 _populate_gids_to_retain (this, input->sets.drop_tables);
1111 if (unlikely (in_error ()))
1112 return;
1113
1114 if (!check_success(_create_old_gid_to_new_gid_map(
1115 face,
1116 input->flags & HB_SUBSET_FLAGS_RETAIN_GIDS,
1117 &_glyphset,
1118 &input->glyph_map,
1119 glyph_map,
1120 reverse_glyph_map,
1121 &new_to_old_gid_list,
1122 &_num_output_glyphs))) {
1123 return;
1124 }
1125
1126 _create_glyph_map_gsub (
1127 &_glyphset_gsub,
1128 glyph_map,
1129 &glyph_map_gsub);
1130
1131 // Now that we have old to new gid map update the unicode to new gid list.
1132 for (unsigned i = 0; i < unicode_to_new_gid_list.length; i++)
1133 {
1134 // Use raw array access for performance.
1135 unicode_to_new_gid_list.arrayZ[i].second =
1136 glyph_map->get(unicode_to_new_gid_list.arrayZ[i].second);
1137 }
1138
1139 bounds_width_vec.resize (_num_output_glyphs, false);
1140 for (auto &v : bounds_width_vec)
1141 v = 0xFFFFFFFF;
1142 bounds_height_vec.resize (_num_output_glyphs, false);
1143 for (auto &v : bounds_height_vec)
1144 v = 0xFFFFFFFF;
1145
1146 if (unlikely (in_error ()))
1147 return;
1148
1149#ifndef HB_NO_VAR
1150 _update_instance_metrics_map_from_cff2 (this);
1151#endif
1152
1153 if (attach_accelerator_data)
1154 {
1155 inprogress_accelerator =
1156 hb_subset_accelerator_t::create (source,
1157 *codepoint_to_glyph,
1158 unicodes,
1159 has_seac);
1160
1161 check_success (inprogress_accelerator);
1162 }
1163
1164#define HB_SUBSET_PLAN_MEMBER(Type, Name) check_success (!Name.in_error ());
1165#include "hb-subset-plan-member-list.hh"
1166#undef HB_SUBSET_PLAN_MEMBER
1167}
1168
1169hb_subset_plan_t::~hb_subset_plan_t()
1170{
1171 hb_face_destroy (dest);
1172
1173 hb_map_destroy (codepoint_to_glyph);
1174 hb_map_destroy (glyph_map);
1175 hb_map_destroy (reverse_glyph_map);
1176#ifndef HB_NO_SUBSET_CFF
1177 cff1_accel.fini ();
1178 cff2_accel.fini ();
1179#endif
1180 hb_face_destroy (source);
1181
1182#ifdef HB_EXPERIMENTAL_API
1183 for (auto _ : name_table_overrides.iter_ref ())
1184 _.second.fini ();
1185#endif
1186
1187 if (inprogress_accelerator)
1188 hb_subset_accelerator_t::destroy ((void*) inprogress_accelerator);
1189}
1190
1191
1192/**
1193 * hb_subset_plan_create_or_fail:
1194 * @face: font face to create the plan for.
1195 * @input: a #hb_subset_input_t input.
1196 *
1197 * Computes a plan for subsetting the supplied face according
1198 * to a provided input. The plan describes
1199 * which tables and glyphs should be retained.
1200 *
1201 * Return value: (transfer full): New subset plan. Destroy with
1202 * hb_subset_plan_destroy(). If there is a failure creating the plan
1203 * nullptr will be returned.
1204 *
1205 * Since: 4.0.0
1206 **/
1207hb_subset_plan_t *
1208hb_subset_plan_create_or_fail (hb_face_t *face,
1209 const hb_subset_input_t *input)
1210{
1211 hb_subset_plan_t *plan;
1212 if (unlikely (!(plan = hb_object_create<hb_subset_plan_t> (face, input))))
1213 return nullptr;
1214
1215 if (unlikely (plan->in_error ()))
1216 {
1217 hb_subset_plan_destroy (plan);
1218 return nullptr;
1219 }
1220
1221 return plan;
1222}
1223
1224/**
1225 * hb_subset_plan_destroy:
1226 * @plan: a #hb_subset_plan_t
1227 *
1228 * Decreases the reference count on @plan, and if it reaches zero, destroys
1229 * @plan, freeing all memory.
1230 *
1231 * Since: 4.0.0
1232 **/
1233void
1234hb_subset_plan_destroy (hb_subset_plan_t *plan)
1235{
1236 if (!hb_object_destroy (plan)) return;
1237
1238 hb_free (plan);
1239}
1240
1241/**
1242 * hb_subset_plan_old_to_new_glyph_mapping:
1243 * @plan: a subsetting plan.
1244 *
1245 * Returns the mapping between glyphs in the original font to glyphs in the
1246 * subset that will be produced by @plan
1247 *
1248 * Return value: (transfer none):
1249 * A pointer to the #hb_map_t of the mapping.
1250 *
1251 * Since: 4.0.0
1252 **/
1253hb_map_t *
1254hb_subset_plan_old_to_new_glyph_mapping (const hb_subset_plan_t *plan)
1255{
1256 return plan->glyph_map;
1257}
1258
1259/**
1260 * hb_subset_plan_new_to_old_glyph_mapping:
1261 * @plan: a subsetting plan.
1262 *
1263 * Returns the mapping between glyphs in the subset that will be produced by
1264 * @plan and the glyph in the original font.
1265 *
1266 * Return value: (transfer none):
1267 * A pointer to the #hb_map_t of the mapping.
1268 *
1269 * Since: 4.0.0
1270 **/
1271hb_map_t *
1272hb_subset_plan_new_to_old_glyph_mapping (const hb_subset_plan_t *plan)
1273{
1274 return plan->reverse_glyph_map;
1275}
1276
1277/**
1278 * hb_subset_plan_unicode_to_old_glyph_mapping:
1279 * @plan: a subsetting plan.
1280 *
1281 * Returns the mapping between codepoints in the original font and the
1282 * associated glyph id in the original font.
1283 *
1284 * Return value: (transfer none):
1285 * A pointer to the #hb_map_t of the mapping.
1286 *
1287 * Since: 4.0.0
1288 **/
1289hb_map_t *
1290hb_subset_plan_unicode_to_old_glyph_mapping (const hb_subset_plan_t *plan)
1291{
1292 return plan->codepoint_to_glyph;
1293}
1294
1295/**
1296 * hb_subset_plan_reference: (skip)
1297 * @plan: a #hb_subset_plan_t object.
1298 *
1299 * Increases the reference count on @plan.
1300 *
1301 * Return value: @plan.
1302 *
1303 * Since: 4.0.0
1304 **/
1305hb_subset_plan_t *
1306hb_subset_plan_reference (hb_subset_plan_t *plan)
1307{
1308 return hb_object_reference (plan);
1309}
1310
1311/**
1312 * hb_subset_plan_set_user_data: (skip)
1313 * @plan: a #hb_subset_plan_t object.
1314 * @key: The user-data key to set
1315 * @data: A pointer to the user data
1316 * @destroy: (nullable): A callback to call when @data is not needed anymore
1317 * @replace: Whether to replace an existing data with the same key
1318 *
1319 * Attaches a user-data key/data pair to the given subset plan object.
1320 *
1321 * Return value: `true` if success, `false` otherwise
1322 *
1323 * Since: 4.0.0
1324 **/
1325hb_bool_t
1326hb_subset_plan_set_user_data (hb_subset_plan_t *plan,
1327 hb_user_data_key_t *key,
1328 void *data,
1329 hb_destroy_func_t destroy,
1330 hb_bool_t replace)
1331{
1332 return hb_object_set_user_data (plan, key, data, destroy, replace);
1333}
1334
1335/**
1336 * hb_subset_plan_get_user_data: (skip)
1337 * @plan: a #hb_subset_plan_t object.
1338 * @key: The user-data key to query
1339 *
1340 * Fetches the user data associated with the specified key,
1341 * attached to the specified subset plan object.
1342 *
1343 * Return value: (transfer none): A pointer to the user data
1344 *
1345 * Since: 4.0.0
1346 **/
1347void *
1348hb_subset_plan_get_user_data (const hb_subset_plan_t *plan,
1349 hb_user_data_key_t *key)
1350{
1351 return hb_object_get_user_data (plan, key);
1352}
1353