From a4b7033d0159b6372e631927b98b1963838bcc54 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sun, 19 Feb 2023 17:48:02 -0700 Subject: [PATCH] [cff2-subset] Blend Private values when instancing --- src/hb-serialize.hh | 7 ++ src/hb-subset-cff-common.hh | 80 ++++++++++++++++++ src/hb-subset-cff2.cc | 159 ++++++++++++++++++++++++++++++++++-- 3 files changed, 239 insertions(+), 7 deletions(-) diff --git a/src/hb-serialize.hh b/src/hb-serialize.hh index 0c2f64cc4..61ec0253a 100644 --- a/src/hb-serialize.hh +++ b/src/hb-serialize.hh @@ -629,6 +629,13 @@ struct hb_serialize_context_t template Type *embed (const Type &obj) { return embed (std::addressof (obj)); } + char *embed (const char *obj, unsigned size) + { + char *ret = this->allocate_size (size, false); + if (unlikely (!ret)) return nullptr; + hb_memcpy (ret, obj, size); + return ret; + } template auto _copy (const Type &src, hb_priority<1>, Ts&&... ds) HB_RETURN diff --git a/src/hb-subset-cff-common.hh b/src/hb-subset-cff-common.hh index ed9e0125a..6351720e6 100644 --- a/src/hb-subset-cff-common.hh +++ b/src/hb-subset-cff-common.hh @@ -81,6 +81,7 @@ struct str_encoder_t } } + // Encode number for CharString void encode_num_cs (const number_t& n) { if (n.in_int_range ()) @@ -98,6 +99,85 @@ struct str_encoder_t } } + // Encode number for TopDict / Private + void encode_num_tp (const number_t& n) + { + if (n.in_int_range ()) + { + // TODO longint + encode_int (n.to_int ()); + } + else + { + // Sigh. BCD + // https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-5-nibble-definitions + double v = n.to_real (); + encode_byte (OpCode_BCD); + + // Based on: + // https://github.com/fonttools/fonttools/blob/97ed3a61cde03e17b8be36f866192fbd56f1d1a7/Lib/fontTools/misc/psCharStrings.py#L265-L294 + + char buf[16]; + /* FontTools has the following comment: + * + * # Note: 14 decimal digits seems to be the limitation for CFF real numbers + * # in macOS. However, we use 8 here to match the implementation of AFDKO. + * + * We use 8 here to match FontTools X-). + */ + snprintf (buf, sizeof (buf), "%.8G", v); // XXX This is locale-sensitive; Ugh + char *s = buf; + if (s[0] == '0' && s[1] == '.') + s++; + else if (s[0] == '-' && s[1] == '0' && s[2] == '.') + { + s[1] = '-'; + s++; + } + hb_vector_t nibbles; + while (*s) + { + char c = s[0]; + s++; + + switch (c) + { + case 'E': + { + char c2 = *s; + if (c2 == '-') + { + s++; + nibbles.push (0x0C); // E- + continue; + } + if (c2 == '+') + s++; + nibbles.push (0x0B); // E + continue; + } + + case '.': case ',': // Comma for some European locales!!! + nibbles.push (0x0A); // . + continue; + + case '-': + nibbles.push (0x0E); // . + continue; + } + + nibbles.push (c - '0'); + } + nibbles.push (0x0F); + if (nibbles.length % 2) + nibbles.push (0x0F); + + unsigned count = nibbles.length; + for (unsigned i = 0; i < count; i += 2) + encode_byte ((nibbles[i] << 4) | nibbles[i+1]); + } + } + void encode_op (op_code_t op) { if (Is_OpCode_ESC (op)) diff --git a/src/hb-subset-cff2.cc b/src/hb-subset-cff2.cc index 5150d0109..6c5f69061 100644 --- a/src/hb-subset-cff2.cc +++ b/src/hb-subset-cff2.cc @@ -246,10 +246,143 @@ struct cff2_subr_subsetter_t : subr_subsetter_t normalized_coords) : + c (c), varStore (varStore), normalized_coords (normalized_coords) {} + + void init () {} + + void process_blend () + { + if (!seen_blend) + { + region_count = varStore->varStore.get_region_index_count (ivs); + scalars.resize_exact (region_count); + varStore->varStore.get_region_scalars (ivs, normalized_coords.arrayZ, normalized_coords.length, + &scalars[0], region_count); + seen_blend = true; + } + } + + double blend_deltas (hb_array_t deltas) const + { + double v = 0; + if (likely (scalars.length == deltas.length)) + { + unsigned count = scalars.length; + for (unsigned i = 0; i < count; i++) + v += (double) scalars.arrayZ[i] * deltas.arrayZ[i].to_real (); + } + return v; + } + + + hb_serialize_context_t *c = nullptr; + bool seen_blend = false; + unsigned ivs = 0; + unsigned region_count = 0; + hb_vector_t scalars; + const CFF2VariationStore *varStore = nullptr; + hb_array_t normalized_coords; +}; + +struct cff2_private_dict_blend_opset_t : dict_opset_t +{ + static void process_arg_blend (cff2_private_blend_encoder_param_t& param, + number_t &arg, + const hb_array_t blends, + unsigned n, unsigned i) + { + arg.set_real (arg.to_real () + param.blend_deltas (blends)); + } + + static void process_blend (cff2_priv_dict_interp_env_t& env, cff2_private_blend_encoder_param_t& param) + { + unsigned int n, k; + + param.process_blend (); + k = param.region_count; + n = env.argStack.pop_uint (); + /* copy the blend values into blend array of the default values */ + unsigned int start = env.argStack.get_count () - ((k+1) * n); + /* let an obvious error case fail, but note CFF2 spec doesn't forbid n==0 */ + if (unlikely (start > env.argStack.get_count ())) + { + env.set_error (); + return; + } + for (unsigned int i = 0; i < n; i++) + { + const hb_array_t blends = env.argStack.sub_array (start + n + (i * k), k); + process_arg_blend (param, env.argStack[start + i], blends, n, i); + } + + /* pop off blend values leaving default values now adorned with blend values */ + env.argStack.pop (k * n); + } + + static void process_op (op_code_t op, cff2_priv_dict_interp_env_t& env, cff2_private_blend_encoder_param_t& param) + { + switch (op) { + case OpCode_StdHW: + case OpCode_StdVW: + case OpCode_BlueScale: + case OpCode_BlueShift: + case OpCode_BlueFuzz: + case OpCode_ExpansionFactor: + case OpCode_LanguageGroup: + case OpCode_BlueValues: + case OpCode_OtherBlues: + case OpCode_FamilyBlues: + case OpCode_FamilyOtherBlues: + case OpCode_StemSnapH: + case OpCode_StemSnapV: + break; + case OpCode_vsindexdict: + env.process_vsindex (); + param.ivs = env.get_ivs (); + env.clear_args (); + return; + case OpCode_blenddict: + process_blend (env, param); + return; + + default: + dict_opset_t::process_op (op, env); + if (!env.argStack.is_empty ()) return; + break; + } + + if (unlikely (env.in_error ())) return; + + // Write args then op + + str_buff_t str; + str_encoder_t encoder (str); + + unsigned count = env.argStack.get_count (); + for (unsigned i = 0; i < count; i++) + encoder.encode_num_tp (env.argStack[i]); + + encoder.encode_op (op); + + auto bytes = str.as_bytes (); + param.c->embed (&bytes, bytes.length); + + env.clear_args (); + } +}; + struct cff2_private_dict_op_serializer_t : op_serializer_t { - cff2_private_dict_op_serializer_t (bool desubroutinize_, bool drop_hints_, bool pinned_ = false) - : desubroutinize (desubroutinize_), drop_hints (drop_hints_), pinned (pinned_) {} + cff2_private_dict_op_serializer_t (bool desubroutinize_, bool drop_hints_, bool pinned_, + const CFF::CFF2VariationStore* varStore_, + hb_array_t normalized_coords_) + : desubroutinize (desubroutinize_), drop_hints (drop_hints_), pinned (pinned_), + varStore (varStore_), normalized_coords (normalized_coords_) {} bool serialize (hb_serialize_context_t *c, const op_str_t &opstr, @@ -259,8 +392,6 @@ struct cff2_private_dict_op_serializer_t : op_serializer_t if (drop_hints && dict_opset_t::is_hint_op (opstr.op)) return_trace (true); - if (pinned && opstr.op == OpCode_vsindexdict) - return_trace (true); if (opstr.op == OpCode_Subrs) { @@ -270,6 +401,15 @@ struct cff2_private_dict_op_serializer_t : op_serializer_t return_trace (FontDict::serialize_link2_op (c, opstr.op, subrs_link)); } + if (pinned) + { + // Reinterpret opstr and process blends. + cff2_priv_dict_interp_env_t env {hb_ubytes_t (opstr.ptr, opstr.length)}; + cff2_private_blend_encoder_param_t param (c, varStore, normalized_coords); + dict_interpreter_t interp (env); + return_trace (interp.interpret (param)); + } + return_trace (copy_opstr (c, opstr)); } @@ -277,6 +417,8 @@ struct cff2_private_dict_op_serializer_t : op_serializer_t const bool desubroutinize; const bool drop_hints; const bool pinned; + const CFF::CFF2VariationStore* varStore; + hb_array_t normalized_coords; }; @@ -367,7 +509,8 @@ struct cff2_subset_plan static bool _serialize_cff2 (hb_serialize_context_t *c, cff2_subset_plan &plan, const OT::cff2::accelerator_subset_t &acc, - unsigned int num_glyphs) + unsigned int num_glyphs, + hb_array_t normalized_coords) { /* private dicts & local subrs */ hb_vector_t private_dict_infos; @@ -395,7 +538,8 @@ static bool _serialize_cff2 (hb_serialize_context_t *c, PrivateDict *pd = c->start_embed (); if (unlikely (!pd)) return false; c->push (); - cff2_private_dict_op_serializer_t privSzr (plan.desubroutinize, plan.drop_hints, plan.pinned); + cff2_private_dict_op_serializer_t privSzr (plan.desubroutinize, plan.drop_hints, plan.pinned, + acc.varStore, normalized_coords); if (likely (pd->serialize (c, acc.privateDicts[i], privSzr, subrs_link))) { unsigned fd = plan.fdmap[i]; @@ -503,7 +647,8 @@ _hb_subset_cff2 (const OT::cff2::accelerator_subset_t &acc, cff2_subset_plan cff2_plan; if (unlikely (!cff2_plan.create (acc, c->plan))) return false; - return _serialize_cff2 (c->serializer, cff2_plan, acc, c->plan->num_output_glyphs ()); + return _serialize_cff2 (c->serializer, cff2_plan, acc, c->plan->num_output_glyphs (), + c->plan->normalized_coords.as_array ()); } bool