420 lines
14 KiB
C++
420 lines
14 KiB
C++
/*
|
|
* 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
|