Source code
Revision control
Copy as Markdown
Other Tools
/*
* 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);
}
}
}
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,
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_indexes (&feature_indices, features);
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_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_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