/* * Copyright © 2023 Google, Inc. * * This is part of HarfBuzz, a text shaping library. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the * above copyright notice and the following two paragraphs appear in * all copies of this software. * * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. * * Google Author(s): Garret Rieger, Qunxin Liu, Roderick Sheeter */ #include "hb-subset-plan.hh" #include "hb-ot-layout-gdef-table.hh" #include "hb-ot-layout-gpos-table.hh" #include "hb-ot-layout-gsub-table.hh" using OT::Layout::GSUB; using OT::Layout::GPOS; #ifndef HB_NO_SUBSET_LAYOUT void remap_used_mark_sets (hb_subset_plan_t *plan, hb_map_t& used_mark_sets_map) { hb_blob_ptr_t gdef = plan->source_table (); if (!gdef->has_data () || !gdef->has_mark_glyph_sets ()) { gdef.destroy (); return; } hb_set_t used_mark_sets; gdef->get_mark_glyph_sets ().collect_used_mark_sets (plan->_glyphset_gsub, used_mark_sets); gdef.destroy (); remap_indexes (&used_mark_sets, &used_mark_sets_map); } /* * Removes all tags from 'tags' that are not in filter. Additionally eliminates any duplicates. * Returns true if anything was removed (not including duplicates). */ static bool _filter_tag_list(hb_vector_t* tags, /* IN/OUT */ const hb_set_t* filter) { hb_vector_t out; out.alloc (tags->get_size() + 1); // +1 is to allocate room for the null terminator. bool removed = false; hb_set_t visited; for (hb_tag_t tag : *tags) { if (!tag) continue; if (visited.has (tag)) continue; if (!filter->has (tag)) { removed = true; continue; } visited.add (tag); out.push (tag); } // The collect function needs a null element to signal end of the array. out.push (HB_TAG_NONE); hb_swap (out, *tags); return removed; } template static void _collect_layout_indices (hb_subset_plan_t *plan, const T& table, hb_set_t *lookup_indices, /* OUT */ hb_set_t *feature_indices, /* OUT */ hb_hashmap_t> *feature_record_cond_idx_map, /* OUT */ hb_hashmap_t *feature_substitutes_map, /* OUT */ hb_set_t& catch_all_record_feature_idxes, /* OUT */ hb_hashmap_t>& catch_all_record_idx_feature_map /* OUT */) { unsigned num_features = table.get_feature_count (); hb_vector_t features; if (!plan->check_success (features.resize (num_features))) return; table.get_feature_tags (0, &num_features, features.arrayZ); bool retain_all_features = !_filter_tag_list (&features, &plan->layout_features); unsigned num_scripts = table.get_script_count (); hb_vector_t scripts; if (!plan->check_success (scripts.resize (num_scripts))) return; table.get_script_tags (0, &num_scripts, scripts.arrayZ); bool retain_all_scripts = !_filter_tag_list (&scripts, &plan->layout_scripts); if (!plan->check_success (!features.in_error ()) || !features || !plan->check_success (!scripts.in_error ()) || !scripts) return; hb_ot_layout_collect_features (plan->source, T::tableTag, retain_all_scripts ? nullptr : scripts.arrayZ, nullptr, retain_all_features ? nullptr : features.arrayZ, feature_indices); #ifndef HB_NO_VAR // collect feature substitutes with variations if (!plan->user_axes_location.is_empty ()) { hb_hashmap_t, unsigned> conditionset_map; OT::hb_collect_feature_substitutes_with_var_context_t c = { &plan->axes_old_index_tag_map, &plan->axes_location, feature_record_cond_idx_map, feature_substitutes_map, catch_all_record_feature_idxes, feature_indices, false, false, false, 0, &conditionset_map }; table.collect_feature_substitutes_with_variations (&c); } #endif for (unsigned feature_index : *feature_indices) { const OT::Feature* f = &(table.get_feature (feature_index)); const OT::Feature **p = nullptr; if (feature_substitutes_map->has (feature_index, &p)) f = *p; f->add_lookup_indexes_to (lookup_indices); } #ifndef HB_NO_VAR if (catch_all_record_feature_idxes) { for (unsigned feature_index : catch_all_record_feature_idxes) { const OT::Feature& f = table.get_feature (feature_index); f.add_lookup_indexes_to (lookup_indices); const void *tag = reinterpret_cast (&(table.get_feature_list ().get_tag (feature_index))); catch_all_record_idx_feature_map.set (feature_index, hb_pair (&f, tag)); } } // If all axes are pinned then all feature variations will be dropped so there's no need // to collect lookups from them. if (!plan->all_axes_pinned) table.feature_variation_collect_lookups (feature_indices, plan->user_axes_location.is_empty () ? nullptr: feature_record_cond_idx_map, lookup_indices); #endif } static inline void _GSUBGPOS_find_duplicate_features (const OT::GSUBGPOS &g, const hb_map_t *lookup_indices, const hb_set_t *feature_indices, const hb_hashmap_t *feature_substitutes_map, hb_map_t *duplicate_feature_map /* OUT */) { if (feature_indices->is_empty ()) return; hb_hashmap_t> unique_features; //find out duplicate features after subset for (unsigned i : feature_indices->iter ()) { hb_tag_t t = g.get_feature_tag (i); if (t == HB_MAP_VALUE_INVALID) continue; if (!unique_features.has (t)) { if (unlikely (!unique_features.set (t, hb::unique_ptr {hb_set_create ()}))) return; if (unique_features.has (t)) unique_features.get (t)->add (i); duplicate_feature_map->set (i, i); continue; } bool found = false; hb_set_t* same_tag_features = unique_features.get (t); for (unsigned other_f_index : same_tag_features->iter ()) { const OT::Feature* f = &(g.get_feature (i)); const OT::Feature **p = nullptr; if (feature_substitutes_map->has (i, &p)) f = *p; const OT::Feature* other_f = &(g.get_feature (other_f_index)); if (feature_substitutes_map->has (other_f_index, &p)) other_f = *p; auto f_iter = + hb_iter (f->lookupIndex) | hb_filter (lookup_indices) ; auto other_f_iter = + hb_iter (other_f->lookupIndex) | hb_filter (lookup_indices) ; bool is_equal = true; for (; f_iter && other_f_iter; f_iter++, other_f_iter++) { unsigned a = *f_iter; unsigned b = *other_f_iter; if (a != b) { is_equal = false; break; } } if (is_equal == false || f_iter || other_f_iter) continue; found = true; duplicate_feature_map->set (i, other_f_index); break; } if (found == false) { same_tag_features->add (i); duplicate_feature_map->set (i, i); } } } static void remap_feature_indices (const hb_set_t &feature_indices, const hb_map_t &duplicate_feature_map, const hb_hashmap_t>& catch_all_record_idx_feature_map, hb_map_t *mapping, /* OUT */ hb_map_t *mapping_w_duplicates /* OUT */) { unsigned i = 0; for (const auto _ : feature_indices) { // retain those features in case we need to insert a catch-all record to reinstate the old features if (catch_all_record_idx_feature_map.has (_)) { mapping->set (_, i); mapping_w_duplicates->set (_, i); i++; } else { uint32_t f_idx = duplicate_feature_map.get (_); uint32_t *new_idx; if (mapping-> has (f_idx, &new_idx)) { mapping_w_duplicates->set (_, *new_idx); } else { mapping->set (_, i); mapping_w_duplicates->set (_, i); i++; } } } } template static void _closure_glyphs_lookups_features (hb_subset_plan_t *plan, hb_set_t *gids_to_retain, hb_map_t *lookups, hb_map_t *features, hb_map_t *features_w_duplicates, script_langsys_map *langsys_map, hb_hashmap_t> *feature_record_cond_idx_map, hb_hashmap_t *feature_substitutes_map, hb_set_t &catch_all_record_feature_idxes, hb_hashmap_t>& catch_all_record_idx_feature_map) { hb_blob_ptr_t table = plan->source_table (); hb_tag_t table_tag = table->tableTag; hb_set_t lookup_indices, feature_indices; _collect_layout_indices (plan, *table, &lookup_indices, &feature_indices, feature_record_cond_idx_map, feature_substitutes_map, catch_all_record_feature_idxes, catch_all_record_idx_feature_map); if (table_tag == HB_OT_TAG_GSUB && !(plan->flags & HB_SUBSET_FLAGS_NO_LAYOUT_CLOSURE)) hb_ot_layout_lookups_substitute_closure (plan->source, &lookup_indices, gids_to_retain); table->closure_lookups (plan->source, gids_to_retain, &lookup_indices); remap_indexes (&lookup_indices, lookups); // prune features table->prune_features (lookups, plan->user_axes_location.is_empty () ? nullptr : feature_record_cond_idx_map, feature_substitutes_map, &feature_indices); hb_map_t duplicate_feature_map; _GSUBGPOS_find_duplicate_features (*table, lookups, &feature_indices, feature_substitutes_map, &duplicate_feature_map); feature_indices.clear (); table->prune_langsys (&duplicate_feature_map, &plan->layout_scripts, langsys_map, &feature_indices); remap_feature_indices (feature_indices, duplicate_feature_map, catch_all_record_idx_feature_map, features, features_w_duplicates); table.destroy (); } void layout_nameid_closure (hb_subset_plan_t* plan, hb_set_t* drop_tables) { if (!drop_tables->has (HB_OT_TAG_GPOS)) { hb_blob_ptr_t gpos = plan->source_table (); gpos->collect_name_ids (&plan->gpos_features, &plan->name_ids); gpos.destroy (); } if (!drop_tables->has (HB_OT_TAG_GSUB)) { hb_blob_ptr_t gsub = plan->source_table (); gsub->collect_name_ids (&plan->gsub_features, &plan->name_ids); gsub.destroy (); } } void layout_populate_gids_to_retain (hb_subset_plan_t* plan, hb_set_t* drop_tables) { if (!drop_tables->has (HB_OT_TAG_GSUB)) // closure all glyphs/lookups/features needed for GSUB substitutions. _closure_glyphs_lookups_features ( plan, &plan->_glyphset_gsub, &plan->gsub_lookups, &plan->gsub_features, &plan->gsub_features_w_duplicates, &plan->gsub_langsys, &plan->gsub_feature_record_cond_idx_map, &plan->gsub_feature_substitutes_map, plan->gsub_old_features, plan->gsub_old_feature_idx_tag_map); if (!drop_tables->has (HB_OT_TAG_GPOS)) _closure_glyphs_lookups_features ( plan, &plan->_glyphset_gsub, &plan->gpos_lookups, &plan->gpos_features, &plan->gpos_features_w_duplicates, &plan->gpos_langsys, &plan->gpos_feature_record_cond_idx_map, &plan->gpos_feature_substitutes_map, plan->gpos_old_features, plan->gpos_old_feature_idx_tag_map); } #ifndef HB_NO_VAR void collect_layout_variation_indices (hb_subset_plan_t* plan) { hb_blob_ptr_t gdef = plan->source_table (); hb_blob_ptr_t gpos = plan->source_table (); if (!gdef->has_data () || !gdef->has_var_store ()) { gdef.destroy (); gpos.destroy (); return; } hb_set_t varidx_set; OT::hb_collect_variation_indices_context_t c (&varidx_set, &plan->_glyphset_gsub, &plan->gpos_lookups); gdef->collect_variation_indices (&c); if (hb_ot_layout_has_positioning (plan->source)) gpos->collect_variation_indices (&c); remap_variation_indices (gdef->get_var_store (), varidx_set, plan->normalized_coords, !plan->pinned_at_default, plan->all_axes_pinned, plan->layout_variation_idx_delta_map); unsigned subtable_count = gdef->get_var_store ().get_sub_table_count (); generate_varstore_inner_maps (varidx_set, subtable_count, plan->gdef_varstore_inner_maps); gdef.destroy (); gpos.destroy (); } #endif #endif