[cff2-subset] Blend Private values when instancing

This commit is contained in:
Behdad Esfahbod 2023-02-19 17:48:02 -07:00
parent f10a4c9d6a
commit a4b7033d01
3 changed files with 239 additions and 7 deletions

View File

@ -629,6 +629,13 @@ struct hb_serialize_context_t
template <typename Type> template <typename Type>
Type *embed (const Type &obj) Type *embed (const Type &obj)
{ return embed (std::addressof (obj)); } { return embed (std::addressof (obj)); }
char *embed (const char *obj, unsigned size)
{
char *ret = this->allocate_size<char> (size, false);
if (unlikely (!ret)) return nullptr;
hb_memcpy (ret, obj, size);
return ret;
}
template <typename Type, typename ...Ts> auto template <typename Type, typename ...Ts> auto
_copy (const Type &src, hb_priority<1>, Ts&&... ds) HB_RETURN _copy (const Type &src, hb_priority<1>, Ts&&... ds) HB_RETURN

View File

@ -81,6 +81,7 @@ struct str_encoder_t
} }
} }
// Encode number for CharString
void encode_num_cs (const number_t& n) void encode_num_cs (const number_t& n)
{ {
if (n.in_int_range ()) 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<char> 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) void encode_op (op_code_t op)
{ {
if (Is_OpCode_ESC (op)) if (Is_OpCode_ESC (op))

View File

@ -246,10 +246,143 @@ struct cff2_subr_subsetter_t : subr_subsetter_t<cff2_subr_subsetter_t, CFF2Subrs
} }
}; };
struct cff2_private_blend_encoder_param_t
{
cff2_private_blend_encoder_param_t (hb_serialize_context_t *c,
const CFF2VariationStore *varStore,
hb_array_t<int> 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<const number_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<float> scalars;
const CFF2VariationStore *varStore = nullptr;
hb_array_t<int> 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<const number_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<const number_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 struct cff2_private_dict_op_serializer_t : op_serializer_t
{ {
cff2_private_dict_op_serializer_t (bool desubroutinize_, bool drop_hints_, bool pinned_ = false) cff2_private_dict_op_serializer_t (bool desubroutinize_, bool drop_hints_, bool pinned_,
: desubroutinize (desubroutinize_), drop_hints (drop_hints_), pinned (pinned_) {} const CFF::CFF2VariationStore* varStore_,
hb_array_t<int> normalized_coords_)
: desubroutinize (desubroutinize_), drop_hints (drop_hints_), pinned (pinned_),
varStore (varStore_), normalized_coords (normalized_coords_) {}
bool serialize (hb_serialize_context_t *c, bool serialize (hb_serialize_context_t *c,
const op_str_t &opstr, 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)) if (drop_hints && dict_opset_t::is_hint_op (opstr.op))
return_trace (true); return_trace (true);
if (pinned && opstr.op == OpCode_vsindexdict)
return_trace (true);
if (opstr.op == OpCode_Subrs) 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)); 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<cff2_private_dict_blend_opset_t, cff2_private_blend_encoder_param_t, cff2_priv_dict_interp_env_t> interp (env);
return_trace (interp.interpret (param));
}
return_trace (copy_opstr (c, opstr)); 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 desubroutinize;
const bool drop_hints; const bool drop_hints;
const bool pinned; const bool pinned;
const CFF::CFF2VariationStore* varStore;
hb_array_t<int> normalized_coords;
}; };
@ -367,7 +509,8 @@ struct cff2_subset_plan
static bool _serialize_cff2 (hb_serialize_context_t *c, static bool _serialize_cff2 (hb_serialize_context_t *c,
cff2_subset_plan &plan, cff2_subset_plan &plan,
const OT::cff2::accelerator_subset_t &acc, const OT::cff2::accelerator_subset_t &acc,
unsigned int num_glyphs) unsigned int num_glyphs,
hb_array_t<int> normalized_coords)
{ {
/* private dicts & local subrs */ /* private dicts & local subrs */
hb_vector_t<table_info_t> private_dict_infos; hb_vector_t<table_info_t> private_dict_infos;
@ -395,7 +538,8 @@ static bool _serialize_cff2 (hb_serialize_context_t *c,
PrivateDict *pd = c->start_embed<PrivateDict> (); PrivateDict *pd = c->start_embed<PrivateDict> ();
if (unlikely (!pd)) return false; if (unlikely (!pd)) return false;
c->push (); 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))) if (likely (pd->serialize (c, acc.privateDicts[i], privSzr, subrs_link)))
{ {
unsigned fd = plan.fdmap[i]; unsigned fd = plan.fdmap[i];
@ -503,7 +647,8 @@ _hb_subset_cff2 (const OT::cff2::accelerator_subset_t &acc,
cff2_subset_plan cff2_plan; cff2_subset_plan cff2_plan;
if (unlikely (!cff2_plan.create (acc, c->plan))) return false; 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 bool