Merge pull request #4221 from googlefonts/user_glyph_map

[subset] Add API method to allow a custom glyph map to be specified.
This commit is contained in:
Behdad Esfahbod 2023-05-08 09:12:34 -06:00 committed by GitHub
commit 62bc2841d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 174 additions and 9 deletions

View file

@ -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

View file

@ -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:

View file

@ -119,6 +119,7 @@ struct hb_subset_input_t
bool force_long_loca = false;
hb_hashmap_t<hb_tag_t, float> axes_location;
hb_map_t glyph_map;
#ifdef HB_EXPERIMENTAL_API
hb_hashmap_t<hb_ot_name_record_ids_t, hb_bytes_t> name_table_overrides;
#endif

View file

@ -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<hb_codepoint_t, hb_codepoint_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,

View file

@ -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);

View file

@ -0,0 +1,2 @@
--gid-map=37:1,39:2,69:3,74:4,77:5
--layout-features+=c2sc

View file

@ -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

View file

@ -0,0 +1,9 @@
FONTS:
Roboto-Regular.ttf
PROFILES:
glyph_map_roboto.txt
glyph_map_roboto_retain_gids.txt
SUBSETS:
ACafi

View file

@ -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)

View file

@ -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:
// <entry 1>,<entry 2>,...,<entry n>
// <entry> = <old gid>:<new gid>
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 <GOptionArgFunc line_parser, bool allow_comments=true>
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 *"},