From 2a4773e43d528343a1b4a305905d275ba5eda829 Mon Sep 17 00:00:00 2001 From: Qunxin Liu Date: Tue, 21 Jun 2022 19:29:52 -0700 Subject: [PATCH] add option "--instance", store axes_location in subset_plan and drop all variation tables when all axes are pinned at default --- src/gen-def.py | 4 +- src/hb-map.hh | 24 +++++++++++ src/hb-ot-var-avar-table.hh | 8 ++++ src/hb-ot-var-fvar-table.hh | 2 + src/hb-subset-input.cc | 65 +++++++++++++++++++++++++++- src/hb-subset-input.hh | 4 +- src/hb-subset-plan.cc | 84 +++++++++++++++++++++++++------------ src/hb-subset-plan.hh | 3 ++ src/hb-subset.cc | 10 +++++ src/hb-subset.h | 15 +++++++ util/hb-subset.cc | 78 ++++++++++++++++++++++++++++++++++ 11 files changed, 267 insertions(+), 30 deletions(-) diff --git a/src/gen-def.py b/src/gen-def.py index 205ed7ebd..47b7b479d 100755 --- a/src/gen-def.py +++ b/src/gen-def.py @@ -19,7 +19,9 @@ symbols = sorted (re.findall (r"^hb_\w+(?= \()", "\n".join (headers_content), re if '--experimental-api' not in sys.argv: # Move these to harfbuzz-sections.txt when got stable experimental_symbols = \ -"""hb_subset_repack_or_fail""".splitlines () +"""hb_subset_repack_or_fail +hb_subset_input_pin_axis_location +hb_subset_input_pin_axis_to_default""".splitlines () symbols = [x for x in symbols if x not in experimental_symbols] symbols = "\n".join (symbols) diff --git a/src/hb-map.hh b/src/hb-map.hh index df215fce5..71d45c106 100644 --- a/src/hb-map.hh +++ b/src/hb-map.hh @@ -455,4 +455,28 @@ struct hb_map_t : hb_hashmap_t +static inline +hb_hashmap_t* hb_hashmap_create () +{ + using hashmap = hb_hashmap_t; + hashmap* map; + if (!(map = hb_object_create ())) + return nullptr; + + map->init_shallow (); + + return map; +} + +template +static inline +void hb_hashmap_destroy (hb_hashmap_t* map) +{ + if (!hb_object_destroy (map)) + return; + map->fini_shallow (); + hb_free (map); +} + #endif /* HB_MAP_HH */ diff --git a/src/hb-ot-var-avar-table.hh b/src/hb-ot-var-avar-table.hh index 65f26c1d2..71d767e30 100644 --- a/src/hb-ot-var-avar-table.hh +++ b/src/hb-ot-var-avar-table.hh @@ -106,6 +106,14 @@ struct avar { static constexpr hb_tag_t tableTag = HB_OT_TAG_avar; + bool has_data () const { return version.to_int (); } + + const SegmentMaps* get_segment_maps () const + { return &firstAxisSegmentMaps; } + + unsigned get_axis_count () const + { return axisCount; } + bool sanitize (hb_sanitize_context_t *c) const { TRACE_SANITIZE (this); diff --git a/src/hb-ot-var-fvar-table.hh b/src/hb-ot-var-fvar-table.hh index c5c476bc0..6300a42b8 100644 --- a/src/hb-ot-var-fvar-table.hh +++ b/src/hb-ot-var-fvar-table.hh @@ -96,6 +96,8 @@ struct AxisRecord info->reserved = 0; } + hb_tag_t get_axis_tag () const { return axisTag; } + int normalize_axis_value (float v) const { float min_value, default_value, max_value; diff --git a/src/hb-subset-input.cc b/src/hb-subset-input.cc index 70c33f407..06f9f9844 100644 --- a/src/hb-subset-input.cc +++ b/src/hb-subset-input.cc @@ -48,7 +48,10 @@ hb_subset_input_create_or_fail (void) for (auto& set : input->sets_iter ()) set = hb_set_create (); - if (input->in_error ()) + if ((input->axes_location = hb_object_create> ())) + input->axes_location->init_shallow (); + + if (!input->axes_location || input->in_error ()) { hb_subset_input_destroy (input); return nullptr; @@ -246,6 +249,13 @@ hb_subset_input_destroy (hb_subset_input_t *input) for (hb_set_t* set : input->sets_iter ()) hb_set_destroy (set); + if (input->axes_location) + { + hb_object_destroy (input->axes_location); + input->axes_location->fini_shallow (); + hb_free (input->axes_location); + } + hb_free (input); } @@ -376,3 +386,56 @@ hb_subset_input_get_user_data (const hb_subset_input_t *input, { return hb_object_get_user_data (input, key); } + +#ifdef HB_EXPERIMENTAL_API +#ifndef HB_NO_VAR +/** + * hb_subset_input_pin_axis_to_default: (skip) + * @input: a #hb_subset_input_t object. + * @axis_tag: Tag of the axis to be pinned + * + * Pin an axis to its default location in the given subset input object. + * + * Return value: `true` if success, `false` otherwise + * + * Since: REPLACEME + **/ +hb_bool_t +hb_subset_input_pin_axis_to_default (hb_subset_input_t *input, + hb_face_t *face, + hb_tag_t axis_tag) +{ + hb_ot_var_axis_info_t axis_info; + if (!hb_ot_var_find_axis_info (face, axis_tag, &axis_info)) + return false; + + return input->axes_location->set (axis_tag, axis_info.default_value); +} + +/** + * hb_subset_input_pin_axis_location: (skip) + * @input: a #hb_subset_input_t object. + * @axis_tag: Tag of the axis to be pinned + * @axis_value: Location on the axis to be pinned at + * + * Pin an axis to a fixed location in the given subset input object. + * + * Return value: `true` if success, `false` otherwise + * + * Since: REPLACEME + **/ +hb_bool_t +hb_subset_input_pin_axis_location (hb_subset_input_t *input, + hb_face_t *face, + hb_tag_t axis_tag, + float axis_value) +{ + hb_ot_var_axis_info_t axis_info; + if (!hb_ot_var_find_axis_info (face, axis_tag, &axis_info)) + return false; + + float val = hb_clamp(axis_value, axis_info.min_value, axis_info.max_value); + return input->axes_location->set (axis_tag, val); +} +#endif +#endif diff --git a/src/hb-subset-input.hh b/src/hb-subset-input.hh index a21a6f352..2335f0634 100644 --- a/src/hb-subset-input.hh +++ b/src/hb-subset-input.hh @@ -59,6 +59,7 @@ struct hb_subset_input_t }; unsigned flags; + hb_hashmap_t *axes_location; inline unsigned num_sets () const { @@ -77,7 +78,8 @@ struct hb_subset_input_t if (unlikely (set_ptrs[i]->in_error ())) return true; } - return false; + + return axes_location->in_error (); } }; diff --git a/src/hb-subset-plan.cc b/src/hb-subset-plan.cc index 8bda4b515..e405a4611 100644 --- a/src/hb-subset-plan.cc +++ b/src/hb-subset-plan.cc @@ -37,6 +37,7 @@ #include "hb-ot-color-colr-table.hh" #include "hb-ot-color-colrv1-closure.hh" #include "hb-ot-var-fvar-table.hh" +#include "hb-ot-var-avar-table.hh" #include "hb-ot-stat-table.hh" #include "hb-ot-math-table.hh" @@ -585,6 +586,48 @@ _nameid_closure (hb_face_t *face, #endif } + +static void +_normalize_axes_location (hb_face_t *face, + const hb_hashmap_t *user_axes_location, + hb_hashmap_t *normalized_axes_location, /* OUT */ + bool &all_axes_pinned) +{ + if (user_axes_location->is_empty ()) + return; + + hb_array_t axes = face->table.fvar->get_axes (); + + bool has_avar = face->table.avar->has_data (); + const OT::SegmentMaps *seg_maps = nullptr; + if (has_avar) + seg_maps = face->table.avar->get_segment_maps (); + + bool axis_not_pinned = false; + unsigned axis_count = 0; + for (const auto& axis : axes) + { + hb_tag_t axis_tag = axis.get_axis_tag (); + if (!user_axes_location->has (axis_tag)) + { + axis_not_pinned = true; + } + else + { + int normalized_v = axis.normalize_axis_value (user_axes_location->get (axis_tag)); + if (has_avar && axis_count < face->table.avar->get_axis_count ()) + { + normalized_v = seg_maps->map (normalized_v); + } + normalized_axes_location->set (axis_tag, normalized_v); + } + if (has_avar) + seg_maps = &StructAfter (*seg_maps); + + axis_count++; + } + all_axes_pinned = !axis_not_pinned; +} /** * hb_subset_plan_create_or_fail: * @face: font face to create the plan for. @@ -636,10 +679,8 @@ hb_subset_plan_create_or_fail (hb_face_t *face, plan->gsub_lookups = hb_map_create (); plan->gpos_lookups = hb_map_create (); - if (plan->check_success (plan->gsub_langsys = hb_object_create ())) - plan->gsub_langsys->init_shallow (); - if (plan->check_success (plan->gpos_langsys = hb_object_create ())) - plan->gpos_langsys->init_shallow (); + plan->check_success (plan->gsub_langsys = hb_hashmap_create> ()); + plan->check_success (plan->gpos_langsys = hb_hashmap_create> ()); plan->gsub_features = hb_map_create (); plan->gpos_features = hb_map_create (); @@ -648,10 +689,9 @@ hb_subset_plan_create_or_fail (hb_face_t *face, plan->layout_variation_indices = hb_set_create (); plan->layout_variation_idx_map = hb_map_create (); - - if ((plan->sanitized_table_cache = hb_object_create>> ())) { - plan->sanitized_table_cache->init_shallow (); - } + plan->check_success (plan->sanitized_table_cache = hb_hashmap_create> ()); + plan->check_success (plan->axes_location = hb_hashmap_create ()); + plan->all_axes_pinned = false; if (unlikely (plan->in_error ())) { hb_subset_plan_destroy (plan); @@ -685,6 +725,11 @@ hb_subset_plan_create_or_fail (hb_face_t *face, plan->glyph_map->get(plan->unicode_to_new_gid_list.arrayZ[i].second); } + _normalize_axes_location (face, + input->axes_location, + plan->axes_location, + plan->all_axes_pinned); + if (unlikely (plan->in_error ())) { hb_subset_plan_destroy (plan); return nullptr; @@ -734,25 +779,10 @@ hb_subset_plan_destroy (hb_subset_plan_t *plan) hb_set_destroy (plan->layout_variation_indices); hb_map_destroy (plan->layout_variation_idx_map); - if (plan->gsub_langsys) - { - hb_object_destroy (plan->gsub_langsys); - plan->gsub_langsys->fini_shallow (); - hb_free (plan->gsub_langsys); - } - - if (plan->gpos_langsys) - { - hb_object_destroy (plan->gpos_langsys); - plan->gpos_langsys->fini_shallow (); - hb_free (plan->gpos_langsys); - } - - if (plan->sanitized_table_cache) { - hb_object_destroy (plan->sanitized_table_cache); - plan->sanitized_table_cache->fini (); - hb_free (plan->sanitized_table_cache); - } + hb_hashmap_destroy (plan->gsub_langsys); + hb_hashmap_destroy (plan->gpos_langsys); + hb_hashmap_destroy (plan->axes_location); + hb_hashmap_destroy (plan->sanitized_table_cache); hb_free (plan); } diff --git a/src/hb-subset-plan.hh b/src/hb-subset-plan.hh index c631ea02e..49edacc5f 100644 --- a/src/hb-subset-plan.hh +++ b/src/hb-subset-plan.hh @@ -107,6 +107,9 @@ struct hb_subset_plan_t hb_map_t *layout_variation_idx_map; hb_hashmap_t>* sanitized_table_cache; + //normalized axes location map + hb_hashmap_t *axes_location; + bool all_axes_pinned; public: diff --git a/src/hb-subset.cc b/src/hb-subset.cc index 34ef923ba..bcbe73df0 100644 --- a/src/hb-subset.cc +++ b/src/hb-subset.cc @@ -361,6 +361,8 @@ _should_drop_table (hb_subset_plan_t *plan, hb_tag_t tag) switch (tag) { case HB_TAG ('c','v','a','r'): /* hint table, fallthrough */ + return plan->all_axes_pinned || (plan->flags & HB_SUBSET_FLAGS_NO_HINTING); + case HB_TAG ('c','v','t',' '): /* hint table, fallthrough */ case HB_TAG ('f','p','g','m'): /* hint table, fallthrough */ case HB_TAG ('p','r','e','p'): /* hint table, fallthrough */ @@ -380,6 +382,14 @@ _should_drop_table (hb_subset_plan_t *plan, hb_tag_t tag) return true; #endif + case HB_TAG ('a','v','a','r'): + case HB_TAG ('f','v','a','r'): + case HB_TAG ('g','v','a','r'): + case HB_OT_TAG_HVAR: + case HB_OT_TAG_VVAR: + case HB_TAG ('M','V','A','R'): + return plan->all_axes_pinned; + default: return false; } diff --git a/src/hb-subset.h b/src/hb-subset.h index d0064cbfe..f4346c70c 100644 --- a/src/hb-subset.h +++ b/src/hb-subset.h @@ -154,6 +154,21 @@ HB_EXTERN void hb_subset_input_set_flags (hb_subset_input_t *input, unsigned value); +#ifdef HB_EXPERIMENTAL_API +#ifndef HB_NO_VAR +HB_EXTERN hb_bool_t +hb_subset_input_pin_axis_to_default (hb_subset_input_t *input, + hb_face_t *face, + hb_tag_t axis_tag); + +HB_EXTERN hb_bool_t +hb_subset_input_pin_axis_location (hb_subset_input_t *input, + hb_face_t *face, + hb_tag_t axis_tag, + float axis_value); +#endif +#endif + HB_EXTERN hb_face_t * hb_subset_or_fail (hb_face_t *source, const hb_subset_input_t *input); diff --git a/util/hb-subset.cc b/util/hb-subset.cc index 0592bfada..bca714015 100644 --- a/util/hb-subset.cc +++ b/util/hb-subset.cc @@ -648,6 +648,73 @@ parse_drop_tables (const char *name, return true; } +#ifdef HB_EXPERIMENTAL_API +#ifndef HB_NO_VAR +static gboolean +parse_instance (const char *name, + const char *arg, + gpointer data, + GError **error) +{ + subset_main_t *subset_main = (subset_main_t *) data; + + char *s = strtok((char *) arg, "="); + while (s) + { + unsigned len = strlen (s); + if (len > 4) //Axis tags are 4 bytes. + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Failed parsing axis tag at: '%s'", s); + return false; + } + + hb_tag_t axis_tag = hb_tag_from_string (s, len); + + s = strtok(nullptr, ", "); + if (!s) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Value not specified for axis: %c%c%c%c", HB_UNTAG (axis_tag)); + return false; + } + + if (strcmp (s, "drop") == 0) + { + if (!hb_subset_input_pin_axis_to_default (subset_main->input, subset_main->face, axis_tag)) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag)); + return false; + } + } + else + { + errno = 0; + char *p; + float axis_value = strtof (s, &p); + if (errno || s == p) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Failed parsing axis value at: '%s'", s); + return false; + } + + if (!hb_subset_input_pin_axis_location (subset_main->input, subset_main->face, axis_tag, axis_value)) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag)); + return false; + } + } + s = strtok(nullptr, "="); + } + + return true; +} +#endif +#endif + template static gboolean parse_file_for (const char *name, @@ -818,7 +885,18 @@ subset_main_t::add_options () {"drop-tables", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_drop_tables, "Drop the specified tables. Use --drop-tables-=... to subtract from the current set.", "list of string table tags or *"}, {"drop-tables+", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_drop_tables, "Drop the specified tables.", "list of string table tags or *"}, {"drop-tables-", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_drop_tables, "Drop the specified tables.", "list of string table tags or *"}, +#ifdef HB_EXPERIMENTAL_API +#ifndef HB_NO_VAR + {"instance", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_instance, + "(Partially|Fully) Instantiate a variable font. A location consists of the tag of a variation axis, followed by '=', followed by a\n" + "number or the literal string 'drop'\n" + " " + "For example: --instance=\"wdth=100 wght=200\" or --instance=\"wdth=drop\"\n" + "Note: currently only fully instancing to the default location is supported\n", + "list of comma separated axis-locations"}, {nullptr} +#endif +#endif }; add_group (other_entries, "subset-other",