hb-subset-plan-layout.cc (14300B)
1 /* 2 * Copyright © 2023 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, Qunxin Liu, Roderick Sheeter 25 */ 26 27 #include "hb-subset-plan.hh" 28 29 #include "hb-ot-layout-gdef-table.hh" 30 #include "hb-ot-layout-gpos-table.hh" 31 #include "hb-ot-layout-gsub-table.hh" 32 33 using OT::Layout::GSUB; 34 using OT::Layout::GPOS; 35 36 #ifndef HB_NO_SUBSET_LAYOUT 37 38 void 39 remap_used_mark_sets (hb_subset_plan_t *plan, 40 hb_map_t& used_mark_sets_map) 41 { 42 hb_blob_ptr_t<OT::GDEF> gdef = plan->source_table<OT::GDEF> (); 43 44 if (!gdef->has_data () || !gdef->has_mark_glyph_sets ()) 45 { 46 gdef.destroy (); 47 return; 48 } 49 50 hb_set_t used_mark_sets; 51 gdef->get_mark_glyph_sets ().collect_used_mark_sets (plan->_glyphset_gsub, used_mark_sets); 52 gdef.destroy (); 53 54 remap_indexes (&used_mark_sets, &used_mark_sets_map); 55 } 56 57 /* 58 * Removes all tags from 'tags' that are not in filter. Additionally eliminates any duplicates. 59 * Returns true if anything was removed (not including duplicates). 60 */ 61 static bool _filter_tag_list(hb_vector_t<hb_tag_t>* tags, /* IN/OUT */ 62 const hb_set_t* filter) 63 { 64 hb_vector_t<hb_tag_t> out; 65 out.alloc (tags->get_size() + 1); // +1 is to allocate room for the null terminator. 66 67 bool removed = false; 68 hb_set_t visited; 69 70 for (hb_tag_t tag : *tags) 71 { 72 if (!tag) continue; 73 if (visited.has (tag)) continue; 74 75 if (!filter->has (tag)) 76 { 77 removed = true; 78 continue; 79 } 80 81 visited.add (tag); 82 out.push (tag); 83 } 84 85 // The collect function needs a null element to signal end of the array. 86 out.push (HB_TAG_NONE); 87 88 hb_swap (out, *tags); 89 return removed; 90 } 91 92 template <typename T> 93 static void _collect_layout_indices (hb_subset_plan_t *plan, 94 const T& table, 95 hb_set_t *lookup_indices, /* OUT */ 96 hb_set_t *feature_indices, /* OUT */ 97 hb_hashmap_t<unsigned, hb::shared_ptr<hb_set_t>> *feature_record_cond_idx_map, /* OUT */ 98 hb_hashmap_t<unsigned, const OT::Feature*> *feature_substitutes_map, /* OUT */ 99 hb_set_t& catch_all_record_feature_idxes, /* OUT */ 100 hb_hashmap_t<unsigned, hb_pair_t<const void*, const void*>>& catch_all_record_idx_feature_map /* OUT */) 101 { 102 unsigned num_features = table.get_feature_count (); 103 hb_vector_t<hb_tag_t> features; 104 if (!plan->check_success (features.resize (num_features))) return; 105 table.get_feature_tags (0, &num_features, features.arrayZ); 106 bool retain_all_features = !_filter_tag_list (&features, &plan->layout_features); 107 108 unsigned num_scripts = table.get_script_count (); 109 hb_vector_t<hb_tag_t> scripts; 110 if (!plan->check_success (scripts.resize (num_scripts))) return; 111 table.get_script_tags (0, &num_scripts, scripts.arrayZ); 112 bool retain_all_scripts = !_filter_tag_list (&scripts, &plan->layout_scripts); 113 114 if (!plan->check_success (!features.in_error ()) || !features 115 || !plan->check_success (!scripts.in_error ()) || !scripts) 116 return; 117 118 hb_ot_layout_collect_features (plan->source, 119 T::tableTag, 120 retain_all_scripts ? nullptr : scripts.arrayZ, 121 nullptr, 122 retain_all_features ? nullptr : features.arrayZ, 123 feature_indices); 124 125 #ifndef HB_NO_VAR 126 // collect feature substitutes with variations 127 if (!plan->user_axes_location.is_empty ()) 128 { 129 hb_hashmap_t<hb::shared_ptr<hb_map_t>, unsigned> conditionset_map; 130 OT::hb_collect_feature_substitutes_with_var_context_t c = 131 { 132 &plan->axes_old_index_tag_map, 133 &plan->axes_location, 134 feature_record_cond_idx_map, 135 feature_substitutes_map, 136 catch_all_record_feature_idxes, 137 feature_indices, 138 false, 139 false, 140 false, 141 0, 142 &conditionset_map 143 }; 144 table.collect_feature_substitutes_with_variations (&c); 145 } 146 #endif 147 148 for (unsigned feature_index : *feature_indices) 149 { 150 const OT::Feature* f = &(table.get_feature (feature_index)); 151 const OT::Feature **p = nullptr; 152 if (feature_substitutes_map->has (feature_index, &p)) 153 f = *p; 154 155 f->add_lookup_indexes_to (lookup_indices); 156 } 157 158 #ifndef HB_NO_VAR 159 if (catch_all_record_feature_idxes) 160 { 161 for (unsigned feature_index : catch_all_record_feature_idxes) 162 { 163 const OT::Feature& f = table.get_feature (feature_index); 164 f.add_lookup_indexes_to (lookup_indices); 165 const void *tag = reinterpret_cast<const void*> (&(table.get_feature_list ().get_tag (feature_index))); 166 catch_all_record_idx_feature_map.set (feature_index, hb_pair (&f, tag)); 167 } 168 } 169 170 // If all axes are pinned then all feature variations will be dropped so there's no need 171 // to collect lookups from them. 172 if (!plan->all_axes_pinned) 173 table.feature_variation_collect_lookups (feature_indices, 174 plan->user_axes_location.is_empty () ? nullptr: feature_record_cond_idx_map, 175 lookup_indices); 176 #endif 177 } 178 179 180 static inline void 181 _GSUBGPOS_find_duplicate_features (const OT::GSUBGPOS &g, 182 const hb_map_t *lookup_indices, 183 const hb_set_t *feature_indices, 184 const hb_hashmap_t<unsigned, const OT::Feature*> *feature_substitutes_map, 185 hb_map_t *duplicate_feature_map /* OUT */) 186 { 187 if (feature_indices->is_empty ()) return; 188 hb_hashmap_t<hb_tag_t, hb::unique_ptr<hb_set_t>> unique_features; 189 //find out duplicate features after subset 190 for (unsigned i : feature_indices->iter ()) 191 { 192 hb_tag_t t = g.get_feature_tag (i); 193 if (t == HB_MAP_VALUE_INVALID) continue; 194 if (!unique_features.has (t)) 195 { 196 if (unlikely (!unique_features.set (t, hb::unique_ptr<hb_set_t> {hb_set_create ()}))) 197 return; 198 if (unique_features.has (t)) 199 unique_features.get (t)->add (i); 200 duplicate_feature_map->set (i, i); 201 continue; 202 } 203 204 bool found = false; 205 206 hb_set_t* same_tag_features = unique_features.get (t); 207 for (unsigned other_f_index : same_tag_features->iter ()) 208 { 209 const OT::Feature* f = &(g.get_feature (i)); 210 const OT::Feature **p = nullptr; 211 if (feature_substitutes_map->has (i, &p)) 212 f = *p; 213 214 const OT::Feature* other_f = &(g.get_feature (other_f_index)); 215 if (feature_substitutes_map->has (other_f_index, &p)) 216 other_f = *p; 217 218 auto f_iter = 219 + hb_iter (f->lookupIndex) 220 | hb_filter (lookup_indices) 221 ; 222 223 auto other_f_iter = 224 + hb_iter (other_f->lookupIndex) 225 | hb_filter (lookup_indices) 226 ; 227 228 bool is_equal = true; 229 for (; f_iter && other_f_iter; f_iter++, other_f_iter++) 230 { 231 unsigned a = *f_iter; 232 unsigned b = *other_f_iter; 233 if (a != b) { is_equal = false; break; } 234 } 235 236 if (is_equal == false || f_iter || other_f_iter) continue; 237 238 found = true; 239 duplicate_feature_map->set (i, other_f_index); 240 break; 241 } 242 243 if (found == false) 244 { 245 same_tag_features->add (i); 246 duplicate_feature_map->set (i, i); 247 } 248 } 249 } 250 251 static void 252 remap_feature_indices (const hb_set_t &feature_indices, 253 const hb_map_t &duplicate_feature_map, 254 const hb_hashmap_t<unsigned, hb_pair_t<const void*, const void*>>& catch_all_record_idx_feature_map, 255 hb_map_t *mapping, /* OUT */ 256 hb_map_t *mapping_w_duplicates /* OUT */) 257 { 258 unsigned i = 0; 259 for (const auto _ : feature_indices) 260 { 261 // retain those features in case we need to insert a catch-all record to reinstate the old features 262 if (catch_all_record_idx_feature_map.has (_)) 263 { 264 mapping->set (_, i); 265 mapping_w_duplicates->set (_, i); 266 i++; 267 } 268 else 269 { 270 uint32_t f_idx = duplicate_feature_map.get (_); 271 uint32_t *new_idx; 272 if (mapping-> has (f_idx, &new_idx)) 273 { 274 mapping_w_duplicates->set (_, *new_idx); 275 } 276 else 277 { 278 mapping->set (_, i); 279 mapping_w_duplicates->set (_, i); 280 i++; 281 } 282 } 283 } 284 } 285 286 template <typename T> 287 static void 288 _closure_glyphs_lookups_features (hb_subset_plan_t *plan, 289 hb_set_t *gids_to_retain, 290 hb_map_t *lookups, 291 hb_map_t *features, 292 hb_map_t *features_w_duplicates, 293 script_langsys_map *langsys_map, 294 hb_hashmap_t<unsigned, hb::shared_ptr<hb_set_t>> *feature_record_cond_idx_map, 295 hb_hashmap_t<unsigned, const OT::Feature*> *feature_substitutes_map, 296 hb_set_t &catch_all_record_feature_idxes, 297 hb_hashmap_t<unsigned, hb_pair_t<const void*, const void*>>& catch_all_record_idx_feature_map) 298 { 299 hb_blob_ptr_t<T> table = plan->source_table<T> (); 300 hb_tag_t table_tag = table->tableTag; 301 hb_set_t lookup_indices, feature_indices; 302 _collect_layout_indices<T> (plan, 303 *table, 304 &lookup_indices, 305 &feature_indices, 306 feature_record_cond_idx_map, 307 feature_substitutes_map, 308 catch_all_record_feature_idxes, 309 catch_all_record_idx_feature_map); 310 311 if (table_tag == HB_OT_TAG_GSUB && !(plan->flags & HB_SUBSET_FLAGS_NO_LAYOUT_CLOSURE)) 312 hb_ot_layout_lookups_substitute_closure (plan->source, 313 &lookup_indices, 314 gids_to_retain); 315 table->closure_lookups (plan->source, 316 gids_to_retain, 317 &lookup_indices); 318 remap_indexes (&lookup_indices, lookups); 319 320 // prune features 321 table->prune_features (lookups, 322 plan->user_axes_location.is_empty () ? nullptr : feature_record_cond_idx_map, 323 feature_substitutes_map, 324 &feature_indices); 325 hb_map_t duplicate_feature_map; 326 _GSUBGPOS_find_duplicate_features (*table, lookups, &feature_indices, feature_substitutes_map, &duplicate_feature_map); 327 328 feature_indices.clear (); 329 table->prune_langsys (&duplicate_feature_map, &plan->layout_scripts, langsys_map, &feature_indices); 330 remap_feature_indices (feature_indices, duplicate_feature_map, catch_all_record_idx_feature_map, features, features_w_duplicates); 331 332 table.destroy (); 333 } 334 335 void layout_nameid_closure (hb_subset_plan_t* plan, 336 hb_set_t* drop_tables) 337 { 338 if (!drop_tables->has (HB_OT_TAG_GPOS)) 339 { 340 hb_blob_ptr_t<GPOS> gpos = plan->source_table<GPOS> (); 341 gpos->collect_name_ids (&plan->gpos_features, &plan->name_ids); 342 gpos.destroy (); 343 } 344 if (!drop_tables->has (HB_OT_TAG_GSUB)) 345 { 346 hb_blob_ptr_t<GSUB> gsub = plan->source_table<GSUB> (); 347 gsub->collect_name_ids (&plan->gsub_features, &plan->name_ids); 348 gsub.destroy (); 349 } 350 } 351 352 void 353 layout_populate_gids_to_retain (hb_subset_plan_t* plan, 354 hb_set_t* drop_tables) { 355 if (!drop_tables->has (HB_OT_TAG_GSUB)) 356 // closure all glyphs/lookups/features needed for GSUB substitutions. 357 _closure_glyphs_lookups_features<GSUB> ( 358 plan, 359 &plan->_glyphset_gsub, 360 &plan->gsub_lookups, 361 &plan->gsub_features, 362 &plan->gsub_features_w_duplicates, 363 &plan->gsub_langsys, 364 &plan->gsub_feature_record_cond_idx_map, 365 &plan->gsub_feature_substitutes_map, 366 plan->gsub_old_features, 367 plan->gsub_old_feature_idx_tag_map); 368 369 if (!drop_tables->has (HB_OT_TAG_GPOS)) 370 _closure_glyphs_lookups_features<GPOS> ( 371 plan, 372 &plan->_glyphset_gsub, 373 &plan->gpos_lookups, 374 &plan->gpos_features, 375 &plan->gpos_features_w_duplicates, 376 &plan->gpos_langsys, 377 &plan->gpos_feature_record_cond_idx_map, 378 &plan->gpos_feature_substitutes_map, 379 plan->gpos_old_features, 380 plan->gpos_old_feature_idx_tag_map); 381 } 382 383 #ifndef HB_NO_VAR 384 void 385 collect_layout_variation_indices (hb_subset_plan_t* plan) 386 { 387 hb_blob_ptr_t<OT::GDEF> gdef = plan->source_table<OT::GDEF> (); 388 hb_blob_ptr_t<GPOS> gpos = plan->source_table<GPOS> (); 389 390 if (!gdef->has_data () || !gdef->has_var_store ()) 391 { 392 gdef.destroy (); 393 gpos.destroy (); 394 return; 395 } 396 397 hb_set_t varidx_set; 398 OT::hb_collect_variation_indices_context_t c (&varidx_set, 399 &plan->_glyphset_gsub, 400 &plan->gpos_lookups); 401 gdef->collect_variation_indices (&c); 402 403 if (hb_ot_layout_has_positioning (plan->source)) 404 gpos->collect_variation_indices (&c); 405 406 remap_variation_indices (gdef->get_var_store (), 407 varidx_set, plan->normalized_coords, 408 !plan->pinned_at_default, 409 plan->all_axes_pinned, 410 plan->layout_variation_idx_delta_map); 411 412 unsigned subtable_count = gdef->get_var_store ().get_sub_table_count (); 413 generate_varstore_inner_maps (varidx_set, subtable_count, plan->gdef_varstore_inner_maps); 414 415 gdef.destroy (); 416 gpos.destroy (); 417 } 418 #endif 419 420 #endif