Merge pull request #3933 from googlefonts/cff

[subset] Fix infinite loop when instancing CFF fonts
This commit is contained in:
Behdad Esfahbod 2022-12-12 14:07:44 -07:00 committed by GitHub
commit 51d3ce39ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 55 additions and 50 deletions

View File

@ -403,6 +403,9 @@ hb_subset_input_get_user_data (const hb_subset_input_t *input,
* *
* Pin an axis to its default location in the given subset input object. * Pin an axis to its default location in the given subset input object.
* *
* Currently only works for fonts with 'glyf' tables. CFF and CFF2 is not
* yet supported. Additionally all axes in a font must be pinned.
*
* Return value: `true` if success, `false` otherwise * Return value: `true` if success, `false` otherwise
* *
* Since: REPLACEME * Since: REPLACEME
@ -427,6 +430,9 @@ hb_subset_input_pin_axis_to_default (hb_subset_input_t *input,
* *
* Pin an axis to a fixed location in the given subset input object. * Pin an axis to a fixed location in the given subset input object.
* *
* Currently only works for fonts with 'glyf' tables. CFF and CFF2 is not
* yet supported. Additionally all axes in a font must be pinned.
*
* Return value: `true` if success, `false` otherwise * Return value: `true` if success, `false` otherwise
* *
* Since: REPLACEME * Since: REPLACEME

View File

@ -413,20 +413,14 @@ _passthrough (hb_subset_plan_t *plan, hb_tag_t tag)
static bool static bool
_dependencies_satisfied (hb_subset_plan_t *plan, hb_tag_t tag, _dependencies_satisfied (hb_subset_plan_t *plan, hb_tag_t tag,
hb_set_t &visited_set, hb_set_t &revisit_set) const hb_set_t &subsetted_tags,
const hb_set_t &pending_subset_tags)
{ {
switch (tag) switch (tag)
{ {
case HB_OT_TAG_hmtx: case HB_OT_TAG_hmtx:
case HB_OT_TAG_vmtx: case HB_OT_TAG_vmtx:
if (!plan->pinned_at_default && return plan->pinned_at_default || !pending_subset_tags.has (HB_OT_TAG_glyf);
!visited_set.has (HB_OT_TAG_glyf))
{
revisit_set.add (tag);
return false;
}
return true;
default: default:
return true; return true;
} }
@ -572,41 +566,58 @@ hb_subset_plan_execute_or_fail (hb_subset_plan_t *plan)
return nullptr; return nullptr;
} }
hb_set_t tags_set, revisit_set;
bool success = true;
hb_tag_t table_tags[32]; hb_tag_t table_tags[32];
unsigned offset = 0, num_tables = ARRAY_LENGTH (table_tags); unsigned offset = 0, num_tables = ARRAY_LENGTH (table_tags);
hb_vector_t<char> buf;
buf.alloc (4096 - 16);
hb_set_t subsetted_tags, pending_subset_tags;
while (((void) _get_table_tags (plan, offset, &num_tables, table_tags), num_tables)) while (((void) _get_table_tags (plan, offset, &num_tables, table_tags), num_tables))
{ {
for (unsigned i = 0; i < num_tables; ++i) for (unsigned i = 0; i < num_tables; ++i)
{ {
hb_tag_t tag = table_tags[i]; hb_tag_t tag = table_tags[i];
if (_should_drop_table (plan, tag) && !tags_set.has (tag)) continue; if (_should_drop_table (plan, tag)) continue;
if (!_dependencies_satisfied (plan, tag, tags_set, revisit_set)) continue; pending_subset_tags.add (tag);
tags_set.add (tag); }
offset += num_tables;
}
hb_vector_t<char> buf;
buf.alloc (4096 - 16);
bool success = true;
while (!pending_subset_tags.is_empty ())
{
bool made_changes = false;
for (hb_tag_t tag : pending_subset_tags)
{
if (!_dependencies_satisfied (plan, tag,
subsetted_tags,
pending_subset_tags))
{
// delayed subsetting for some tables since they might have dependency on other tables
// in some cases: e.g: during instantiating glyf tables, hmetrics/vmetrics are updated
// and saved in subset plan, hmtx/vmtx subsetting need to use these updated metrics values
continue;
}
pending_subset_tags.del (tag);
subsetted_tags.add (tag);
made_changes = true;
success = _subset_table (plan, buf, tag); success = _subset_table (plan, buf, tag);
if (unlikely (!success)) goto end; if (unlikely (!success)) goto end;
} }
/*delayed subsetting for some tables since they might have dependency on other tables in some cases: if (!made_changes)
e.g: during instantiating glyf tables, hmetrics/vmetrics are updated and saved in subset plan,
hmtx/vmtx subsetting need to use these updated metrics values*/
while (!revisit_set.is_empty ())
{ {
hb_set_t revisit_temp; DEBUG_MSG (SUBSET, nullptr, "Table dependencies unable to be satisfied. Subset failed.");
for (hb_tag_t tag : revisit_set) success = false;
{ goto end;
if (!_dependencies_satisfied (plan, tag, tags_set, revisit_temp)) continue;
tags_set.add (tag);
success = _subset_table (plan, buf, tag);
if (unlikely (!success)) goto end;
}
revisit_set = revisit_temp;
} }
offset += num_tables;
} }
if (success && plan->attach_accelerator_data) { if (success && plan->attach_accelerator_data) {

View File

@ -47,6 +47,10 @@ TESTS = \
tests/math.tests \ tests/math.tests \
tests/math_coverage_offset.tests \ tests/math_coverage_offset.tests \
tests/post.tests \ tests/post.tests \
tests/full_instance.tests \
tests/instance_feature_variations.tests \
tests/instantiate_glyf.tests \
tests/pin_all_at_default.tests \
$(NULL) $(NULL)
# TODO: re-enable once colrv1 subsetting is stabilized. # TODO: re-enable once colrv1 subsetting is stabilized.
@ -59,8 +63,4 @@ XFAIL_TESTS = \
# Disabled because instancing is only available w/ experimental API on. # Disabled because instancing is only available w/ experimental API on.
DISABLED_TESTS = \ DISABLED_TESTS = \
tests/full_instance.tests \
tests/instance_feature_variations.tests \
tests/instantiate_glyf.tests \
tests/pin_all_at_default.tests \
$(NULL) $(NULL)

View File

@ -35,7 +35,6 @@ def generate_expected_output(input_file, unicodes, profile_flags, instance_flags
"--no-overlap-flag", "--no-overlap-flag",
"--no-recalc-bounds", "--no-recalc-bounds",
"--no-recalc-timestamp", "--no-recalc-timestamp",
"--no-harfbuzz-repacker", # disable harfbuzz repacker so we aren't comparing to ourself.
"--output=%s" % instance_path, "--output=%s" % instance_path,
input_file] input_file]
args.extend(instance_flags) args.extend(instance_flags)

View File

@ -49,18 +49,12 @@ tests = [
'glyph_names', 'glyph_names',
'post', 'post',
'32bit_var_store', '32bit_var_store',
'pin_all_at_default',
'instantiate_glyf',
'full_instance',
'instance_feature_variations',
] ]
if get_option('experimental_api')
# instacing tests, only supported in experimental api
tests += [
'pin_all_at_default',
'instantiate_glyf',
'full_instance',
'instance_feature_variations',
]
endif
repack_tests = [ repack_tests = [
'basic', 'basic',
'prioritization', 'prioritization',
@ -70,7 +64,6 @@ repack_tests = [
'space_splitting', 'space_splitting',
] ]
run_test = find_program('run-tests.py') run_test = find_program('run-tests.py')
foreach t : tests foreach t : tests

View File

@ -660,7 +660,6 @@ parse_drop_tables (const char *name,
return true; return true;
} }
#ifdef HB_EXPERIMENTAL_API
#ifndef HB_NO_VAR #ifndef HB_NO_VAR
static gboolean static gboolean
parse_instance (const char *name, parse_instance (const char *name,
@ -725,7 +724,6 @@ parse_instance (const char *name,
return true; return true;
} }
#endif #endif
#endif
template <GOptionArgFunc line_parser, bool allow_comments=true> template <GOptionArgFunc line_parser, bool allow_comments=true>
static gboolean static gboolean
@ -897,7 +895,6 @@ 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, 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 *"},
{"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 #ifndef HB_NO_VAR
{"instance", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_instance, {"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" "(Partially|Fully) Instantiate a variable font. A location consists of the tag of a variation axis, followed by '=', followed by a\n"
@ -906,7 +903,6 @@ subset_main_t::add_options ()
"For example: --instance=\"wdth=100 wght=200\" or --instance=\"wdth=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", "Note: currently only fully instancing to the default location is supported\n",
"list of comma separated axis-locations"}, "list of comma separated axis-locations"},
#endif
#endif #endif
{nullptr} {nullptr}
}; };