[subset] Add HB_SUBSET_FLAGS_IFTB_REQUIREMENTS flag.

When enabled the output subset conforms to the requirements for a base font to be patched by IFTB patches. IFTB is a proposed incremental font transfer patch. This new flag is set as experimental. Currently, setting this flag causes the output subset to also use long offsets for outline data in loca/glyf, gvar, CFF, and CFF2.

This change is a version of 3ae2fe2084 rebased onto head w/ tests added.
This commit is contained in:
Garret Rieger 2023-11-06 20:21:46 +00:00 committed by Behdad Esfahbod
parent c3c32b0176
commit c6884377ec
23 changed files with 223 additions and 42 deletions

View file

@ -78,7 +78,8 @@ struct CFFIndex
hb_requires (hb_is_iterable (Iterable))>
bool serialize (hb_serialize_context_t *c,
const Iterable &iterable,
const unsigned *p_data_size = nullptr)
const unsigned *p_data_size = nullptr,
unsigned min_off_size = 0)
{
TRACE_SERIALIZE (this);
unsigned data_size;
@ -88,7 +89,7 @@ struct CFFIndex
total_size (iterable, &data_size);
auto it = hb_iter (iterable);
if (unlikely (!serialize_header (c, +it, data_size))) return_trace (false);
if (unlikely (!serialize_header (c, +it, data_size, min_off_size))) return_trace (false);
unsigned char *ret = c->allocate_size<unsigned char> (data_size, false);
if (unlikely (!ret)) return_trace (false);
for (const auto &_ : +it)
@ -111,11 +112,13 @@ struct CFFIndex
hb_requires (hb_is_iterator (Iterator))>
bool serialize_header (hb_serialize_context_t *c,
Iterator it,
unsigned data_size)
unsigned data_size,
unsigned min_off_size = 0)
{
TRACE_SERIALIZE (this);
unsigned off_size = (hb_bit_storage (data_size + 1) + 7) / 8;
off_size = hb_max(min_off_size, off_size);
/* serialize CFFIndex header */
if (unlikely (!c->extend_min (this))) return_trace (false);
@ -195,7 +198,7 @@ struct CFFIndex
template <typename Iterable,
hb_requires (hb_is_iterable (Iterable))>
static unsigned total_size (const Iterable &iterable, unsigned *data_size = nullptr)
static unsigned total_size (const Iterable &iterable, unsigned *data_size = nullptr, unsigned min_off_size = 0)
{
auto it = + hb_iter (iterable);
if (!it)
@ -211,6 +214,7 @@ struct CFFIndex
if (data_size) *data_size = total;
unsigned off_size = (hb_bit_storage (total + 1) + 7) / 8;
off_size = hb_max(min_off_size, off_size);
return min_size + HBUINT8::static_size + (hb_len (it) + 1) * off_size + total;
}

View file

@ -428,7 +428,10 @@ struct gvar
subset_data_size += get_glyph_var_data_bytes (c->source_blob, glyph_count, old_gid).length;
}
bool long_offset = subset_data_size & ~0xFFFFu;
bool long_offset = (subset_data_size & ~0xFFFFu);
#ifdef HB_EXPERIMENTAL_API
long_offset = long_offset || (c->plan->flags & HB_SUBSET_FLAGS_IFTB_REQUIREMENTS);
#endif
out->flags = long_offset ? 1 : 0;
HBUINT8 *subset_offsets = c->serializer->allocate_size<HBUINT8> ((long_offset ? 4 : 2) * (num_glyphs + 1), false);
@ -446,6 +449,8 @@ struct gvar
hb_memcpy (tuples, this+sharedTuples, shared_tuple_size);
}
/* This ordering relative to the shared tuples array, which puts the glyphVariationData
last in the table, is required when HB_SUBSET_FLAGS_IFTB_REQUIREMENTS is set */
char *subset_data = c->serializer->allocate_size<char> (subset_data_size, false);
if (!subset_data) return_trace (false);
out->dataZ = subset_data - (char *) out;

View file

@ -620,6 +620,14 @@ struct cff1_subset_plan
drop_hints = plan->flags & HB_SUBSET_FLAGS_NO_HINTING;
desubroutinize = plan->flags & HB_SUBSET_FLAGS_DESUBROUTINIZE;
#ifdef HB_EXPERIMENTAL_API
charstrings_last = plan->flags & HB_SUBSET_FLAGS_IFTB_REQUIREMENTS;
min_charstrings_off_size = (plan->flags & HB_SUBSET_FLAGS_IFTB_REQUIREMENTS) ? 4 : 0;
#else
charstrings_last = false;
min_charstrings_off_size = 0;
#endif
subset_charset = !acc.is_predef_charset ();
if (!subset_charset)
/* check whether the subset renumbers any glyph IDs */
@ -778,13 +786,42 @@ struct cff1_subset_plan
unsigned int topDictModSIDs[name_dict_values_t::ValCount];
bool desubroutinize = false;
bool charstrings_last = false;
unsigned min_charstrings_off_size = 0;
};
} // namespace OT
static bool _serialize_cff1_charstrings (hb_serialize_context_t *c,
struct OT::cff1_subset_plan &plan,
const OT::cff1::accelerator_subset_t &acc)
{
c->push<CFF1CharStrings> ();
unsigned data_size = 0;
unsigned total_size = CFF1CharStrings::total_size (plan.subset_charstrings, &data_size, plan.min_charstrings_off_size);
if (unlikely (!c->start_zerocopy (total_size)))
return false;
auto *cs = c->start_embed<CFF1CharStrings> ();
if (unlikely (!cs->serialize (c, plan.subset_charstrings, &data_size, plan.min_charstrings_off_size))) {
c->pop_discard ();
return false;
}
plan.info.char_strings_link = c->pop_pack (false);
return true;
}
bool
OT::cff1::accelerator_subset_t::serialize (hb_serialize_context_t *c,
struct OT::cff1_subset_plan &plan) const
{
if (plan.charstrings_last) {
if (!_serialize_cff1_charstrings(c, plan, *this))
return false;
}
/* private dicts & local subrs */
for (int i = (int) privateDicts.length; --i >= 0 ;)
{
@ -823,23 +860,9 @@ OT::cff1::accelerator_subset_t::serialize (hb_serialize_context_t *c,
if (!is_CID ())
plan.info.privateDictInfo = plan.fontdicts_mod[0].privateDictInfo;
/* CharStrings */
{
c->push<CFF1CharStrings> ();
unsigned data_size = 0;
unsigned total_size = CFF1CharStrings::total_size (plan.subset_charstrings, &data_size);
if (unlikely (!c->start_zerocopy (total_size)))
return false;
auto *cs = c->start_embed<CFF1CharStrings> ();
if (likely (cs->serialize (c, plan.subset_charstrings, &data_size)))
plan.info.char_strings_link = c->pop_pack (false);
else
{
c->pop_discard ();
if (!plan.charstrings_last) {
if (!_serialize_cff1_charstrings(c, plan, *this))
return false;
}
}
/* FDArray (FD Index) */

View file

@ -439,6 +439,14 @@ struct cff2_subset_plan
desubroutinize = plan->flags & HB_SUBSET_FLAGS_DESUBROUTINIZE ||
pinned; // For instancing we need this path
#ifdef HB_EXPERIMENTAL_API
charstrings_last = plan->flags & HB_SUBSET_FLAGS_IFTB_REQUIREMENTS;
min_charstrings_off_size = (plan->flags & HB_SUBSET_FLAGS_IFTB_REQUIREMENTS) ? 4 : 0;
#else
charstrings_last = false;
min_charstrings_off_size = 0;
#endif
if (desubroutinize)
{
/* Flatten global & local subrs */
@ -510,14 +518,44 @@ struct cff2_subset_plan
bool drop_hints = false;
bool desubroutinize = false;
bool charstrings_last = false;
unsigned min_charstrings_off_size = 0;
};
} // namespace OT
static bool _serialize_cff2_charstrings (hb_serialize_context_t *c,
cff2_subset_plan &plan,
const OT::cff2::accelerator_subset_t &acc)
{
c->push ();
unsigned data_size = 0;
unsigned total_size = CFF2CharStrings::total_size (plan.subset_charstrings, &data_size, plan.min_charstrings_off_size);
if (unlikely (!c->start_zerocopy (total_size)))
return false;
auto *cs = c->start_embed<CFF2CharStrings> ();
if (unlikely (!cs->serialize (c, plan.subset_charstrings, &data_size, plan.min_charstrings_off_size)))
{
c->pop_discard ();
return false;
}
plan.info.char_strings_link = c->pop_pack (false);
return true;
}
bool
OT::cff2::accelerator_subset_t::serialize (hb_serialize_context_t *c,
struct cff2_subset_plan &plan,
hb_array_t<int> normalized_coords) const
{
if (plan.charstrings_last) {
if (!_serialize_cff2_charstrings(c, plan, *this))
return false;
}
/* private dicts & local subrs */
hb_vector_t<table_info_t> private_dict_infos;
if (unlikely (!private_dict_infos.resize (plan.subset_fdcount))) return false;
@ -556,23 +594,9 @@ OT::cff2::accelerator_subset_t::serialize (hb_serialize_context_t *c,
}
}
/* CharStrings */
{
c->push ();
unsigned data_size = 0;
unsigned total_size = CFF2CharStrings::total_size (plan.subset_charstrings, &data_size);
if (unlikely (!c->start_zerocopy (total_size)))
return false;
auto *cs = c->start_embed<CFF2CharStrings> ();
if (likely (cs->serialize (c, plan.subset_charstrings, &data_size)))
plan.info.char_strings_link = c->pop_pack (false);
else
{
c->pop_discard ();
if (!plan.charstrings_last) {
if (!_serialize_cff2_charstrings(c, plan, *this))
return false;
}
}
/* FDSelect */

View file

@ -845,12 +845,12 @@ _create_old_gid_to_new_gid_map (const hb_face_t *face,
if (retain_gids)
{
DEBUG_MSG (SUBSET, nullptr,
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 ())
@ -1000,7 +1000,7 @@ _update_instance_metrics_map_from_cff2 (hb_subset_plan_t *plan)
float *hvar_store_cache = nullptr;
if (_hmtx.has_data () && _hmtx.var_table.get_length ())
hvar_store_cache = _hmtx.var_table->get_var_store ().create_cache ();
OT::vmtx_accelerator_t _vmtx (plan->source);
float *vvar_store_cache = nullptr;
if (_vmtx.has_data () && _vmtx.var_table.get_length ())
@ -1144,6 +1144,10 @@ hb_subset_plan_t::hb_subset_plan_t (hb_face_t *face,
attach_accelerator_data = input->attach_accelerator_data;
force_long_loca = input->force_long_loca;
#ifdef HB_EXPERIMENTAL_API
force_long_loca = force_long_loca || (flags & HB_SUBSET_FLAGS_IFTB_REQUIREMENTS);
#endif
if (accel)
accelerator = (hb_subset_accelerator_t*) accel;

View file

@ -73,6 +73,9 @@ typedef struct hb_subset_plan_t hb_subset_plan_t;
* OS/2 will not be recalculated.
* @HB_SUBSET_FLAGS_NO_LAYOUT_CLOSURE: If set don't perform glyph closure on layout
* substitution rules (GSUB). Since: 7.2.0.
* @HB_SUBSET_FLAGS_IFTB_REQUIREMENTS: If set enforce requirements on the output subset
* to allow it to be used with incremental font transfer IFTB patches. Primarily,
* this forces all outline data to use long (32 bit) offsets. Since: EXPERIMENTAL
*
* List of boolean properties that can be configured on the subset input.
*
@ -90,6 +93,9 @@ typedef enum { /*< flags >*/
HB_SUBSET_FLAGS_GLYPH_NAMES = 0x00000080u,
HB_SUBSET_FLAGS_NO_PRUNE_UNICODE_RANGES = 0x00000100u,
HB_SUBSET_FLAGS_NO_LAYOUT_CLOSURE = 0x00000200u,
#ifdef HB_EXPERIMENTAL_API
HB_SUBSET_FLAGS_IFTB_REQUIREMENTS = 0x00000400u,
#endif
} hb_subset_flags_t;
/**

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -336,6 +336,34 @@ test_subset_cff1_j_retaingids (void)
hb_face_destroy (face_41_4c2e);
}
#ifdef HB_EXPERIMENTAL_API
static void
test_subset_cff1_iftb_requirements (void)
{
hb_face_t *face_abc = hb_test_open_font_file ("fonts/SourceSansPro-Regular.abc.otf");
hb_face_t *face_long_off = hb_test_open_font_file ("fonts/SourceSansPro-Regular.abc.long_off.otf");
hb_set_t *codepoints = hb_set_create();
hb_face_t *face_abc_subset;
hb_set_add (codepoints, 97);
hb_set_add (codepoints, 98);
hb_set_add (codepoints, 99);
hb_subset_input_t *input = hb_subset_test_create_input (codepoints);
hb_subset_input_set_flags (input, HB_SUBSET_FLAGS_IFTB_REQUIREMENTS);
face_abc_subset = hb_subset_test_create_subset (face_abc, input);
hb_set_destroy (codepoints);
hb_subset_test_check (face_long_off, face_abc_subset, HB_TAG ('C','F','F', ' '));
hb_face_destroy (face_abc_subset);
hb_face_destroy (face_abc);
hb_face_destroy (face_long_off);
}
#endif
int
main (int argc, char **argv)
{
@ -356,5 +384,9 @@ main (int argc, char **argv)
hb_test_add (test_subset_cff1_retaingids);
hb_test_add (test_subset_cff1_j_retaingids);
#ifdef HB_EXPERIMENTAL_API
hb_test_add (test_subset_cff1_iftb_requirements);
#endif
return hb_test_run ();
}

View file

@ -161,6 +161,33 @@ test_subset_cff2_retaingids (void)
hb_face_destroy (face_ac);
}
#ifdef HB_EXPERIMENTAL_API
static void
test_subset_cff2_iftb_requirements (void)
{
hb_face_t *face_abc = hb_test_open_font_file ("fonts/AdobeVFPrototype.abc.otf");
hb_face_t *face_long_off = hb_test_open_font_file ("fonts/AdobeVFPrototype.abc.long_off.otf");
hb_set_t *codepoints = hb_set_create();
hb_face_t *face_abc_subset;
hb_set_add (codepoints, 97);
hb_set_add (codepoints, 98);
hb_set_add (codepoints, 99);
hb_subset_input_t *input = hb_subset_test_create_input (codepoints);
hb_subset_input_set_flags (input, HB_SUBSET_FLAGS_IFTB_REQUIREMENTS);
face_abc_subset = hb_subset_test_create_subset (face_abc, input);
hb_set_destroy (codepoints);
hb_subset_test_check (face_long_off, face_abc_subset, HB_TAG ('C','F','F', '2'));
hb_face_destroy (face_abc_subset);
hb_face_destroy (face_abc);
hb_face_destroy (face_long_off);
}
#endif
int
main (int argc, char **argv)
{
@ -173,5 +200,9 @@ main (int argc, char **argv)
hb_test_add (test_subset_cff2_desubr_strip_hints);
hb_test_add (test_subset_cff2_retaingids);
#ifdef HB_EXPERIMENTAL_API
hb_test_add (test_subset_cff2_iftb_requirements);
#endif
return hb_test_run ();
}

View file

@ -357,6 +357,35 @@ test_subset_glyf_retain_gids_truncates (void)
hb_face_destroy (face_a);
}
#ifdef HB_EXPERIMENTAL_API
static void
test_subset_glyf_iftb_requirements (void)
{
hb_face_t *face_abc = hb_test_open_font_file ("fonts/Roboto-Variable.abc.ttf");
hb_face_t *face_long_loca = hb_test_open_font_file ("fonts/Roboto-Variable.abc.long_loca.ttf");
hb_set_t *codepoints = hb_set_create();
hb_face_t *face_abc_subset;
hb_set_add (codepoints, 97);
hb_set_add (codepoints, 98);
hb_set_add (codepoints, 99);
hb_subset_input_t *input = hb_subset_test_create_input (codepoints);
hb_subset_input_set_flags (input, HB_SUBSET_FLAGS_IFTB_REQUIREMENTS);
face_abc_subset = hb_subset_test_create_subset (face_abc, input);
hb_set_destroy (codepoints);
hb_subset_test_check (face_long_loca, face_abc_subset, HB_TAG ('l','o','c', 'a'));
hb_subset_test_check (face_long_loca, face_abc_subset, HB_TAG ('g','l','y','f'));
hb_subset_test_check (face_long_loca, face_abc_subset, HB_TAG ('g','v','a','r'));
hb_face_destroy (face_abc_subset);
hb_face_destroy (face_abc);
hb_face_destroy (face_long_loca);
}
#endif
// TODO(grieger): test for long loca generation.
int
@ -377,5 +406,9 @@ main (int argc, char **argv)
hb_test_add (test_subset_glyf_retain_gids);
hb_test_add (test_subset_glyf_retain_gids_truncates);
#ifdef HB_EXPERIMENTAL_API
hb_test_add (test_subset_glyf_iftb_requirements);
#endif
return hb_test_run();
}

View file

@ -0,0 +1 @@
--iftb-requirements

View file

@ -0,0 +1,14 @@
FONTS:
Roboto-Variable.ttf
SourceSansPro-Regular.otf
AdobeVFPrototype.otf
PROFILES:
default.txt
iftb_requirements.txt
SUBSETS:
abc
OPTIONS:
no_fonttools

View file

@ -78,6 +78,7 @@ if get_option('experimental_api')
'gdef_partial_instance',
'value_format_partial_instance',
'feature_variation_instance_collect_lookups',
'iftb_requirements'
]
endif

View file

@ -778,7 +778,7 @@ parse_instance (const char *name,
"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,
@ -1024,7 +1024,7 @@ 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 *"},
#ifndef HB_NO_VAR
#ifndef HB_NO_VAR
{"variations", 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 number or the literal "
@ -1064,6 +1064,9 @@ subset_main_t::add_options ()
"Alternative name for --preprocess.", nullptr},
{"preprocess", 0, 0, G_OPTION_ARG_NONE, &this->preprocess,
"If set preprocesses the face with the add accelerator option before actually subsetting.", nullptr},
#ifdef HB_EXPERIMENTAL_API
{"iftb-requirements", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_IFTB_REQUIREMENTS>, "Enforce requirements needed to use the subset with incremental font transfer IFTB patches.", nullptr},
#endif
{nullptr}
};
add_group (flag_entries,