diff --git a/docs/harfbuzz-sections.txt b/docs/harfbuzz-sections.txt index d187f7001..dff9bac69 100644 --- a/docs/harfbuzz-sections.txt +++ b/docs/harfbuzz-sections.txt @@ -875,6 +875,7 @@ hb_subset_input_get_flags hb_subset_input_unicode_set hb_subset_input_glyph_set hb_subset_input_set +hb_subset_input_old_to_new_glyph_mapping hb_subset_input_pin_axis_location hb_subset_input_pin_axis_to_default hb_subset_or_fail diff --git a/src/hb-subset-input.cc b/src/hb-subset-input.cc index 5f001ac25..0b2de43ee 100644 --- a/src/hb-subset-input.cc +++ b/src/hb-subset-input.cc @@ -520,6 +520,37 @@ hb_subset_preprocess (hb_face_t *source) return new_source; } +/** + * hb_subset_input_old_to_new_glyph_mapping: + * @input: a #hb_subset_input_t object. + * + * Returns a map which can be used to provide an explicit mapping from old to new glyph + * id's in the produced subset. The caller should populate the map as desired. + * If this map is left empty then glyph ids will be automatically mapped to new + * values by the subsetter. If populated, the mapping must be unique. That + * is no two original glyph ids can be mapped to the same new id. + * Additionally, if a mapping is provided then the retain gids option cannot + * be enabled. + * + * Any glyphs that are retained in the subset which are not specified + * in this mapping will be assigned glyph ids after the highest glyph + * id in the mapping. + * + * Note: this will accept and apply non-monotonic mappings, however this + * may result in unsorted Coverage tables. Such fonts may not work for all + * use cases (for example ots will reject unsorted coverage tables). So it's + * recommended, if possible, to supply a monotonic mapping. + * + * Return value: (transfer none): pointer to the #hb_map_t of the custom glyphs ID map. + * + * XSince: REPLACEME + **/ +HB_EXTERN hb_map_t* +hb_subset_input_old_to_new_glyph_mapping (hb_subset_input_t *input) +{ + return &input->glyph_map; +} + #ifdef HB_EXPERIMENTAL_API /** * hb_subset_input_override_name_table: diff --git a/src/hb-subset-input.hh b/src/hb-subset-input.hh index 1550e8b2c..1970f795b 100644 --- a/src/hb-subset-input.hh +++ b/src/hb-subset-input.hh @@ -119,6 +119,7 @@ struct hb_subset_input_t bool force_long_loca = false; hb_hashmap_t axes_location; + hb_map_t glyph_map; #ifdef HB_EXPERIMENTAL_API hb_hashmap_t name_table_overrides; #endif diff --git a/src/hb-subset-plan.cc b/src/hb-subset-plan.cc index 1cbff1e0c..791f92d02 100644 --- a/src/hb-subset-plan.cc +++ b/src/hb-subset-plan.cc @@ -768,10 +768,11 @@ _create_glyph_map_gsub (const hb_set_t* glyph_set_gsub, ; } -static void +static bool _create_old_gid_to_new_gid_map (const hb_face_t *face, bool retain_gids, const hb_set_t *all_gids_to_retain, + const hb_map_t *requested_glyph_map, hb_map_t *glyph_map, /* OUT */ hb_map_t *reverse_glyph_map, /* OUT */ unsigned int *num_glyphs /* OUT */) @@ -780,7 +781,54 @@ _create_old_gid_to_new_gid_map (const hb_face_t *face, reverse_glyph_map->resize (pop); glyph_map->resize (pop); - if (!retain_gids) + if (*requested_glyph_map) + { + hb_set_t new_gids(requested_glyph_map->values()); + if (new_gids.get_population() != requested_glyph_map->get_population()) + { + DEBUG_MSG (SUBSET, nullptr, "The provided custom glyph mapping is not unique."); + return false; + } + + if (retain_gids) + { + DEBUG_MSG (SUBSET, nullptr, + "HB_SUBSET_FLAGS_RETAIN_GIDS cannot be set if " + "a custom glyph mapping has been provided."); + return false; + } + + hb_codepoint_t max_glyph = 0; + hb_set_t remaining; + for (auto old_gid : all_gids_to_retain->iter ()) + { + if (old_gid == 0) { + reverse_glyph_map->set(0, 0); + continue; + } + + hb_codepoint_t* new_gid; + if (!requested_glyph_map->has (old_gid, &new_gid)) + { + remaining.add(old_gid); + continue; + } + + if (*new_gid > max_glyph) + max_glyph = *new_gid; + reverse_glyph_map->set (*new_gid, old_gid); + } + + // Anything that wasn't mapped by the requested mapping should + // be placed after the requested mapping. + for (auto old_gid : remaining) + { + reverse_glyph_map->set(++max_glyph, old_gid); + } + + *num_glyphs = max_glyph + 1; + } + else if (!retain_gids) { + hb_enumerate (hb_iter (all_gids_to_retain), (hb_codepoint_t) 0) | hb_sink (reverse_glyph_map) @@ -806,6 +854,8 @@ _create_old_gid_to_new_gid_map (const hb_face_t *face, | hb_map (&hb_pair_t::reverse) | hb_sink (glyph_map) ; + + return true; } #ifndef HB_NO_VAR @@ -1008,12 +1058,16 @@ hb_subset_plan_t::hb_subset_plan_t (hb_face_t *face, if (unlikely (in_error ())) return; - _create_old_gid_to_new_gid_map (face, - input->flags & HB_SUBSET_FLAGS_RETAIN_GIDS, - &_glyphset, - glyph_map, - reverse_glyph_map, - &_num_output_glyphs); + if (!check_success(_create_old_gid_to_new_gid_map( + face, + input->flags & HB_SUBSET_FLAGS_RETAIN_GIDS, + &_glyphset, + &input->glyph_map, + glyph_map, + reverse_glyph_map, + &_num_output_glyphs))) { + return; + } _create_glyph_map_gsub ( &_glyphset_gsub, diff --git a/src/hb-subset.h b/src/hb-subset.h index 41d958705..6368ff93f 100644 --- a/src/hb-subset.h +++ b/src/hb-subset.h @@ -154,6 +154,9 @@ hb_subset_input_glyph_set (hb_subset_input_t *input); HB_EXTERN hb_set_t * hb_subset_input_set (hb_subset_input_t *input, hb_subset_sets_t set_type); +HB_EXTERN hb_map_t* +hb_subset_input_old_to_new_glyph_mapping (hb_subset_input_t *input); + HB_EXTERN hb_subset_flags_t hb_subset_input_get_flags (hb_subset_input_t *input); diff --git a/test/subset/data/expected/glyph_map/Roboto-Regular.glyph_map_roboto.41,43,61,66,69.ttf b/test/subset/data/expected/glyph_map/Roboto-Regular.glyph_map_roboto.41,43,61,66,69.ttf new file mode 100644 index 000000000..a4c82662f Binary files /dev/null and b/test/subset/data/expected/glyph_map/Roboto-Regular.glyph_map_roboto.41,43,61,66,69.ttf differ diff --git a/test/subset/data/expected/glyph_map/Roboto-Regular.glyph_map_roboto_retain_gids.41,43,61,66,69.ttf b/test/subset/data/expected/glyph_map/Roboto-Regular.glyph_map_roboto_retain_gids.41,43,61,66,69.ttf new file mode 100644 index 000000000..f63599984 Binary files /dev/null and b/test/subset/data/expected/glyph_map/Roboto-Regular.glyph_map_roboto_retain_gids.41,43,61,66,69.ttf differ diff --git a/test/subset/data/profiles/glyph_map_roboto.txt b/test/subset/data/profiles/glyph_map_roboto.txt new file mode 100644 index 000000000..2ee33b3cf --- /dev/null +++ b/test/subset/data/profiles/glyph_map_roboto.txt @@ -0,0 +1,2 @@ +--gid-map=37:1,39:2,69:3,74:4,77:5 +--layout-features+=c2sc diff --git a/test/subset/data/profiles/glyph_map_roboto_retain_gids.txt b/test/subset/data/profiles/glyph_map_roboto_retain_gids.txt new file mode 100644 index 000000000..77a513cbb --- /dev/null +++ b/test/subset/data/profiles/glyph_map_roboto_retain_gids.txt @@ -0,0 +1,4 @@ +--gid-map=37:37,39:39,69:69,74:74,77:77,444:444,446:446,561:561,563:563 +--gids=444,446,561,563 +--layout-features+=c2sc +--retain-gids diff --git a/test/subset/data/tests/glyph_map.tests b/test/subset/data/tests/glyph_map.tests new file mode 100644 index 000000000..d222aceee --- /dev/null +++ b/test/subset/data/tests/glyph_map.tests @@ -0,0 +1,9 @@ +FONTS: +Roboto-Regular.ttf + +PROFILES: +glyph_map_roboto.txt +glyph_map_roboto_retain_gids.txt + +SUBSETS: +ACafi diff --git a/test/subset/generate-expected-outputs.py b/test/subset/generate-expected-outputs.py index 201fe4a78..5f21f80c4 100755 --- a/test/subset/generate-expected-outputs.py +++ b/test/subset/generate-expected-outputs.py @@ -51,7 +51,9 @@ def generate_expected_output(input_file, unicodes, profile_flags, instance_flags if unicodes != "": args.extend(["--unicodes=%s" % unicodes,]) - args.extend(profile_flags) + # --gid-map is unsupported in fonttools so don't send it. Tests using + # it are crafted to work without fonttools knowing about the flag. + args.extend([f for f in profile_flags if not f.startswith("--gid-map")]) if not no_fonttools: check_call(args) diff --git a/util/hb-subset.cc b/util/hb-subset.cc index cdab3efde..599c7dc4e 100644 --- a/util/hb-subset.cc +++ b/util/hb-subset.cc @@ -742,6 +742,63 @@ parse_instance (const char *name, } #endif +static gboolean +parse_glyph_map (const char *name, + const char *arg, + gpointer data, + GError **error) +{ + // Glyph map has the following format: + // ,,..., + // = : + subset_main_t *subset_main = (subset_main_t *) data; + hb_subset_input_t* input = subset_main->input; + hb_set_t *glyphs = hb_subset_input_glyph_set(input); + + char *s = (char *) arg; + char *p; + + while (s && *s) + { + while (*s && strchr (", ", *s)) + s++; + if (!*s) + break; + + errno = 0; + hb_codepoint_t start_code = strtoul (s, &p, 10); + if (s[0] == '-' || errno || s == p) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Failed parsing glyph map at: '%s'", s); + return false; + } + + if (!p || p[0] != ':') // ranges + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Failed parsing glyph map at: '%s'", s); + return false; + } + + s = ++p; + hb_codepoint_t end_code = strtoul (s, &p, 10); + if (s[0] == '-' || errno || s == p) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Failed parsing glyph map at: '%s'", s); + return false; + } + + hb_set_add(glyphs, start_code); + hb_map_set (hb_subset_input_old_to_new_glyph_mapping (input), start_code, end_code); + + s = p; + } + + return true; +} + template static gboolean parse_file_for (const char *name, @@ -894,6 +951,7 @@ subset_main_t::add_options () GOptionEntry other_entries[] = { + {"gid-map", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_glyph_map, "Specify a glyph mapping to use, any unmapped gids will be automatically assigned.", "List of pairs old_gid1:new_gid1,old_gid2:new_gid2,..."}, {"name-IDs", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_nameids, "Subset specified nameids. Use --name-IDs-=... to subtract from the current set.", "list of int numbers or *"}, {"name-IDs-", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_nameids, "Subset specified nameids", "list of int numbers or *"}, {"name-IDs+", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_nameids, "Subset specified nameids", "list of int numbers or *"},