| /* |
| * 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<OT::GDEF> gdef = plan->source_table<OT::GDEF> (); |
| |
| 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<hb_tag_t>* tags, /* IN/OUT */ |
| const hb_set_t* filter) |
| { |
| hb_vector_t<hb_tag_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 <typename T> |
| 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<unsigned, hb::shared_ptr<hb_set_t>> *feature_record_cond_idx_map, /* OUT */ |
| hb_hashmap_t<unsigned, const OT::Feature*> *feature_substitutes_map, /* OUT */ |
| hb_set_t& catch_all_record_feature_idxes, /* OUT */ |
| hb_hashmap_t<unsigned, hb_pair_t<const void*, const void*>>& catch_all_record_idx_feature_map /* OUT */) |
| { |
| unsigned num_features = table.get_feature_count (); |
| hb_vector_t<hb_tag_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<hb_tag_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<hb::shared_ptr<hb_map_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<const void*> (&(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<unsigned, const OT::Feature*> *feature_substitutes_map, |
| hb_map_t *duplicate_feature_map /* OUT */) |
| { |
| if (feature_indices->is_empty ()) return; |
| hb_hashmap_t<hb_tag_t, hb::unique_ptr<hb_set_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_t> {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<unsigned, hb_pair_t<const void*, const void*>>& 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 <typename T> |
| 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<unsigned, hb::shared_ptr<hb_set_t>> *feature_record_cond_idx_map, |
| hb_hashmap_t<unsigned, const OT::Feature*> *feature_substitutes_map, |
| hb_set_t &catch_all_record_feature_idxes, |
| hb_hashmap_t<unsigned, hb_pair_t<const void*, const void*>>& catch_all_record_idx_feature_map) |
| { |
| hb_blob_ptr_t<T> table = plan->source_table<T> (); |
| hb_tag_t table_tag = table->tableTag; |
| hb_set_t lookup_indices, feature_indices; |
| _collect_layout_indices<T> (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> gpos = plan->source_table<GPOS> (); |
| 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> gsub = plan->source_table<GSUB> (); |
| 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<GSUB> ( |
| 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<GPOS> ( |
| 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<OT::GDEF> gdef = plan->source_table<OT::GDEF> (); |
| hb_blob_ptr_t<GPOS> gpos = plan->source_table<GPOS> (); |
| |
| 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 |