Coverage Report

Created: 2026-11-06 11:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/work/workdir/UnpackedTarball/harfbuzz/src/hb-subset-cff2.cc
Line
Count
Source
1
/*
2
 * Copyright © 2018 Adobe Inc.
3
 *
4
 *  This is part of HarfBuzz, a text shaping library.
5
 *
6
 * Permission is hereby granted, without written agreement and without
7
 * license or royalty fees, to use, copy, modify, and distribute this
8
 * software and its documentation for any purpose, provided that the
9
 * above copyright notice and the following two paragraphs appear in
10
 * all copies of this software.
11
 *
12
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
13
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
14
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
15
 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
16
 * DAMAGE.
17
 *
18
 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
19
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20
 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
21
 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
22
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23
 *
24
 * Adobe Author(s): Michiharu Ariza
25
 */
26
27
#include "hb.hh"
28
29
#ifndef HB_NO_SUBSET_CFF
30
31
#include "hb-open-type.hh"
32
#include "hb-ot-cff2-table.hh"
33
#include "hb-ot-var-common.hh"
34
#include "hb-set.h"
35
#include "hb-subset-plan.hh"
36
#include "hb-subset-cff-common.hh"
37
#include "hb-cff2-interp-cs.hh"
38
#include "hb-subset-cff2-round.hh"
39
#include "hb-subset-cff2-to-cff1.hh"
40
41
using namespace CFF;
42
43
namespace CFF {
44
45
/*
46
 * Partial instancing (some axes pinned or limited, others kept).
47
 *
48
 * This is a port of fontTools' instantiateCFF2 (fonttools/fonttools#3506),
49
 * with one simplification: the variation-store instancing solver acts on
50
 * regions independently of the per-blend delta values, so instead of lifting
51
 * every blend's deltas into the store, instancing it, and reading the rows
52
 * back, we instance each VarData once with identity delta rows.  The
53
 * instantiated tuples then give, per VarData, a fixed linear map from old
54
 * per-region deltas to new per-region deltas.  The contribution that folds
55
 * into each blend's default value is the old store's region scalars
56
 * evaluated at the new default location (plan->normalized_coords); the
57
 * surviving rebased regions all evaluate to zero there.
58
 *
59
 * Use: create (), then note_use () for every blend the retained charstrings
60
 * and private dicts hold, then finalize (); only VarDatas that are both
61
 * still variable and actually used survive into the new store.
62
 */
63
struct cff2_instancing_plan_t
64
{
65
  struct vardata_transform_t
66
  {
67
    /* new_deltas[j] = Σ_i matrix[j][i] * old_deltas[i] */
68
    hb_vector_t<hb_vector_t<float>> matrix;
69
    /* Default-value fold-in scalars, one per old region: the old store's
70
     * region scalars at the new default location. */
71
    hb_vector_t<float> gains;
72
    /* Surviving regions, parallel to matrix rows. */
73
    hb_vector_t<hb_hashmap_t<hb_tag_t, Triple>> regions;
74
    /* Index into the new global region list, per new region (finalize). */
75
    hb_vector_t<unsigned> region_indices;
76
    unsigned new_major = 0;
77
    bool alive_ = false;
78
0
    bool alive () const { return alive_; }
79
80
    /* One value's delta for new region j, from its old per-region deltas. */
81
    double transform_delta (unsigned j, hb_array_t<const number_t> deltas) const
82
0
    {
83
0
      const hb_vector_t<float> &col = matrix.arrayZ[j];
84
0
      double d = 0.;
85
0
      unsigned count = hb_min (col.length, deltas.length);
86
0
      for (unsigned c = 0; c < count; c++)
87
0
  d += (double) col.arrayZ[c] * deltas.arrayZ[c].to_real ();
88
0
      return d;
89
0
    }
90
  };
91
92
  hb_vector_t<vardata_transform_t> transforms;  /* per old VarData (vsindex) */
93
  hb_vector_t<hb_hashmap_t<hb_tag_t, Triple>> new_regions;  /* global region list */
94
  hb_set_t used_majors;
95
  unsigned new_major_count = 0;
96
97
0
  bool has_variations () const { return new_major_count; }
98
99
  const vardata_transform_t *get_transform (unsigned major) const
100
0
  { return major < transforms.length ? &transforms.arrayZ[major] : nullptr; }
101
102
  /* Record that a retained blend references this VarData. */
103
0
  void note_use (unsigned major) { used_majors.add (major); }
104
105
  /* The new vsindex a private dict's (or charstring's) old ivs maps to;
106
   * dead VarDatas fall back to 0, their blends having become static. */
107
  unsigned remap_ivs (unsigned old_ivs) const
108
0
  {
109
0
    const vardata_transform_t *t = get_transform (old_ivs);
110
0
    return (t && t->alive ()) ? t->new_major : 0;
111
0
  }
112
113
  double fold_deltas (unsigned old_ivs, hb_array_t<const number_t> deltas) const
114
0
  {
115
0
    const vardata_transform_t *t = get_transform (old_ivs);
116
0
    if (unlikely (!t || t->gains.length != deltas.length)) return 0.;
117
0
    double v = 0.;
118
0
    for (unsigned i = 0; i < deltas.length; i++)
119
0
      v += (double) t->gains.arrayZ[i] * deltas.arrayZ[i].to_real ();
120
0
    return v;
121
0
  }
122
123
  bool create (const CFF2ItemVariationStore *cff2VarStore,
124
         const hb_subset_plan_t *plan)
125
0
  {
126
0
    const OT::ItemVariationStore &varStore = cff2VarStore->varStore;
127
0
    unsigned major_count = varStore.get_sub_table_count ();
128
0
    if (!transforms.resize (major_count)) return false;
129
130
0
    if (!varStore.get_region_list ().get_var_regions (plan->axes_old_index_tag_map, orig_regions))
131
0
      return false;
132
133
    /* Instance each VarData with identity delta rows; the surviving tuples'
134
     * delta columns are the old-region → new-region transform. */
135
0
    OT::optimize_scratch_t scratch;
136
0
    for (unsigned m = 0; m < major_count; m++)
137
0
    {
138
0
      const OT::VarData &var_data = varStore.get_sub_table (m);
139
0
      unsigned k = var_data.get_region_index_count ();
140
0
      vardata_transform_t &t = transforms.arrayZ[m];
141
142
0
      if (!t.gains.resize_exact (k)) return false;
143
0
      varStore.get_region_scalars (m, plan->normalized_coords.arrayZ,
144
0
           plan->normalized_coords.length,
145
0
           t.gains.arrayZ, k);
146
147
0
      OT::tuple_variations_t tuples;
148
0
      for (unsigned r = 0; r < k; r++)
149
0
      {
150
0
  OT::tuple_delta_t tuple;
151
0
  if (!tuple.deltas_x.resize (k) ||
152
0
      !tuple.indices.resize (k))
153
0
    return false;
154
0
  for (unsigned i = 0; i < k; i++)
155
0
    tuple.indices.arrayZ[i] = true;
156
0
  tuple.deltas_x.arrayZ[r] = 1.f;
157
0
  unsigned region_index = var_data.get_region_index (r);
158
0
  if (unlikely (region_index >= orig_regions.length)) return false;
159
0
  tuple.axis_tuples = orig_regions.arrayZ[region_index];
160
0
  if (unlikely (tuple.axis_tuples.in_error ())) return false;
161
0
  tuples.tuple_vars.push (std::move (tuple));
162
0
      }
163
0
      if (unlikely (tuples.tuple_vars.in_error ())) return false;
164
165
0
      if (!tuples.instantiate (plan->axes_location, plan->axes_triple_distances, scratch))
166
0
  return false;
167
168
0
      for (auto &tuple : tuples.tuple_vars)
169
0
      {
170
0
  if (unlikely (tuple.deltas_x.length != k)) return false;
171
0
  hb_vector_t<float> col;
172
0
  if (unlikely (!col.resize_exact (k))) return false;
173
0
  for (unsigned i = 0; i < k; i++)
174
0
    col.arrayZ[i] = tuple.deltas_x.arrayZ[i];
175
0
  t.matrix.push (std::move (col));
176
0
  t.regions.push (std::move (tuple.axis_tuples));
177
0
      }
178
0
      if (unlikely (t.matrix.in_error () || t.regions.in_error ())) return false;
179
0
    }
180
181
0
    return true;
182
0
  }
183
184
  /* Decide which VarDatas survive, number them, and build the new global
185
   * region list: surviving original regions first, in their original order,
186
   * then solver-split new regions in order of appearance (fontTools'
187
   * rebuildRegions + prune_regions). */
188
  bool finalize ()
189
0
  {
190
0
    for (unsigned m = 0; m < transforms.length; m++)
191
0
    {
192
0
      vardata_transform_t &t = transforms.arrayZ[m];
193
0
      t.alive_ = t.matrix.length && used_majors.has (m);
194
0
      if (t.alive_)
195
0
  t.new_major = new_major_count++;
196
0
    }
197
198
    /* Keys hash by content, so pointers into different owners compare fine. */
199
0
    hb_hashmap_t<const hb_hashmap_t<hb_tag_t, Triple>*, unsigned> region_idx_map;
200
0
    hb_vector_t<const hb_hashmap_t<hb_tag_t, Triple>*> region_ptrs;
201
0
    for (const vardata_transform_t &t : transforms)
202
0
    {
203
0
      if (!t.alive ()) continue;
204
0
      for (const auto &region : t.regions)
205
0
  if (!region_idx_map.has (&region))
206
0
    if (unlikely (!region_idx_map.set (&region, (unsigned) -1)))
207
0
      return false;
208
0
    }
209
0
    for (const auto &r : orig_regions)
210
0
    {
211
0
      unsigned *idx;
212
0
      if (region_idx_map.has (&r, &idx) && *idx == (unsigned) -1)
213
0
      {
214
0
  if (unlikely (!region_idx_map.set (&r, region_ptrs.length))) return false;
215
0
  region_ptrs.push (&r);
216
0
      }
217
0
    }
218
0
    for (const vardata_transform_t &t : transforms)
219
0
    {
220
0
      if (!t.alive ()) continue;
221
0
      for (const auto &region : t.regions)
222
0
      {
223
0
  unsigned *idx;
224
0
  if (region_idx_map.has (&region, &idx) && *idx == (unsigned) -1)
225
0
  {
226
0
    if (unlikely (!region_idx_map.set (&region, region_ptrs.length))) return false;
227
0
    region_ptrs.push (&region);
228
0
  }
229
0
      }
230
0
    }
231
0
    if (unlikely (region_ptrs.in_error ())) return false;
232
233
0
    for (vardata_transform_t &t : transforms)
234
0
    {
235
0
      if (!t.alive ()) continue;
236
0
      for (const auto &region : t.regions)
237
0
      {
238
0
  unsigned *idx;
239
0
  if (unlikely (!region_idx_map.has (&region, &idx))) return false;
240
0
  t.region_indices.push (*idx);
241
0
      }
242
0
      if (unlikely (t.region_indices.in_error ())) return false;
243
0
    }
244
245
0
    if (!new_regions.resize (region_ptrs.length)) return false;
246
0
    for (unsigned i = 0; i < region_ptrs.length; i++)
247
0
    {
248
0
      new_regions.arrayZ[i] = *region_ptrs.arrayZ[i];
249
0
      if (unlikely (new_regions.arrayZ[i].in_error ())) return false;
250
0
    }
251
252
0
    return true;
253
0
  }
254
255
  /* Serialize the instanced store: a CFF2ItemVariationStore whose VarDatas
256
   * carry no items, just region indexes (blend deltas live in charstrings).
257
   * Laid out manually since all sizes are known up front. */
258
  bool serialize_var_store (hb_serialize_context_t *c,
259
          const hb_vector_t<hb_tag_t> &axis_tags) const
260
0
  {
261
0
    unsigned data_count = new_major_count;
262
0
    unsigned header_size = 2 + 4 + 2 + 4 * data_count;
263
264
0
    uint64_t offset = header_size;
265
0
    hb_vector_t<unsigned> data_offsets;
266
0
    for (const vardata_transform_t &t : transforms)
267
0
    {
268
0
      if (!t.alive ()) continue;
269
0
      data_offsets.push ((unsigned) offset);
270
0
      offset += 6 + 2 * (uint64_t) t.region_indices.length;
271
0
    }
272
0
    if (unlikely (data_offsets.in_error () || data_offsets.length != data_count))
273
0
      return false;
274
0
    unsigned regions_offset = (unsigned) offset;
275
0
    uint64_t total = offset + 4 + (uint64_t) new_regions.length * axis_tags.length * 6;
276
0
    if (unlikely (total > 0xFFFFFFFFu)) return false;
277
278
0
    HBUINT16 *size_field = c->allocate_size<HBUINT16> (HBUINT16::static_size);
279
0
    if (unlikely (!size_field)) return false;
280
    /* OFF 2026 5.3.3.11.2 permits larger stores with length=65535. */
281
0
    *size_field = hb_min (total, (uint64_t) 0xFFFFu);
282
283
0
    char *header = c->allocate_size<char> (header_size);
284
0
    if (unlikely (!header)) return false;
285
0
    * (HBUINT16 *) (header + 0) = 1;      /* format */
286
0
    * (HBUINT32 *) (header + 2) = regions_offset;
287
0
    * (HBUINT16 *) (header + 6) = data_count;
288
0
    for (unsigned i = 0; i < data_count; i++)
289
0
      * (HBUINT32 *) (header + 8 + 4 * i) = data_offsets.arrayZ[i];
290
291
0
    for (const vardata_transform_t &t : transforms)
292
0
    {
293
0
      if (!t.alive ()) continue;
294
0
      unsigned k = t.region_indices.length;
295
0
      HBUINT16 *v = (HBUINT16 *) c->allocate_size<char> (6 + 2 * k);
296
0
      if (unlikely (!v)) return false;
297
0
      v[0] = 0;           /* itemCount */
298
0
      v[1] = 0;           /* wordSizeCount */
299
0
      v[2] = k;           /* regionIndices.len */
300
0
      for (unsigned j = 0; j < k; j++)
301
0
  v[3 + j] = t.region_indices.arrayZ[j];
302
0
    }
303
304
0
    hb_vector_t<const hb_hashmap_t<hb_tag_t, Triple>*> region_ptrs;
305
0
    if (unlikely (!region_ptrs.alloc_exact (new_regions.length))) return false;
306
0
    for (const auto &r : new_regions)
307
0
      region_ptrs.push (&r);
308
0
    auto *region_list = c->start_embed<OT::VarRegionList> ();
309
0
    return region_list->serialize (c, axis_tags, region_ptrs);
310
0
  }
311
312
  private:
313
  hb_vector_t<hb_hashmap_t<hb_tag_t, Triple>> orig_regions;
314
};
315
316
/* Usage-collection pass for partial instancing: record which VarDatas the
317
 * retained charstrings' and private dicts' blends reference.  Mirrors the
318
 * emission pass: blends consumed by dropped hint ops don't count. */
319
struct cff2_usage_param_t
320
{
321
0
  void init () {}
322
323
  cff2_instancing_plan_t *instancing = nullptr;
324
  bool drop_hints = false;
325
  bool has_blend = false;
326
  bool has_flex1 = false;
327
  unsigned ivs = 0; /* dict-side current vsindex */
328
};
329
330
struct cff2_cs_opset_usage_t : cff2_cs_opset_t<cff2_cs_opset_usage_t, cff2_usage_param_t, blend_arg_t>
331
{
332
  static void flush_args_and_op (op_code_t op, cff2_cs_interp_env_t<blend_arg_t> &env, cff2_usage_param_t& param)
333
0
  {
334
0
    param.has_flex1 |= op == OpCode_flex1;
335
0
    bool counts = true;
336
0
    switch (op)
337
0
    {
338
0
      case OpCode_return:
339
0
      case OpCode_endchar:
340
0
  counts = false;
341
0
  break;
342
343
0
      case OpCode_hstem:
344
0
      case OpCode_hstemhm:
345
0
      case OpCode_vstem:
346
0
      case OpCode_vstemhm:
347
0
      case OpCode_hintmask:
348
0
      case OpCode_cntrmask:
349
0
  if (param.drop_hints)
350
0
    counts = false;
351
0
  break;
352
353
0
      default:
354
0
  break;
355
0
    }
356
357
0
    if (counts)
358
0
      for (unsigned int i = 0; i < env.argStack.get_count (); i++)
359
0
  if (env.argStack[i].blending ())
360
0
  {
361
0
    param.has_blend = true;
362
0
    if (param.instancing) param.instancing->note_use (env.get_ivs ());
363
0
    break;
364
0
  }
365
366
0
    env.clear_args ();
367
0
  }
368
};
369
370
struct cff2_private_dict_usage_opset_t : dict_opset_t
371
{
372
  static void process_op (op_code_t op, cff2_priv_dict_interp_env_t& env, cff2_usage_param_t& param)
373
0
  {
374
0
    switch (op) {
375
0
      case OpCode_vsindexdict:
376
0
  env.process_vsindex ();
377
0
  param.ivs = env.get_ivs ();
378
0
  env.clear_args ();
379
0
  return;
380
0
      case OpCode_blenddict:
381
0
  param.instancing->note_use (param.ivs);
382
0
  env.clear_args ();
383
0
  return;
384
0
      default:
385
0
  dict_opset_t::process_op (op, env);
386
0
  if (!env.argStack.is_empty ()) return;
387
0
  env.clear_args ();
388
0
  return;
389
0
    }
390
0
  }
391
};
392
393
} /* namespace CFF */
394
395
struct cff2_sub_table_info_t : cff_sub_table_info_t
396
{
397
  cff2_sub_table_info_t ()
398
0
    : cff_sub_table_info_t (),
399
0
      var_store_link (0)
400
0
  {}
401
402
  objidx_t  var_store_link;
403
};
404
405
struct cff2_top_dict_op_serializer_t : cff_top_dict_op_serializer_t<>
406
{
407
  bool serialize (hb_serialize_context_t *c,
408
      const op_str_t &opstr,
409
      const cff2_sub_table_info_t &info) const
410
0
  {
411
0
    TRACE_SERIALIZE (this);
412
413
0
    switch (opstr.op)
414
0
    {
415
0
      case OpCode_vstore:
416
0
        if (info.var_store_link)
417
0
    return_trace (FontDict::serialize_link4_op(c, opstr.op, info.var_store_link));
418
0
  else
419
0
    return_trace (true);
420
421
0
      default:
422
0
  return_trace (cff_top_dict_op_serializer_t<>::serialize (c, opstr, info));
423
0
    }
424
0
  }
425
};
426
427
struct cff2_cs_opset_flatten_t : cff2_cs_opset_t<cff2_cs_opset_flatten_t, flatten_param_t, blend_arg_t>
428
{
429
  static void flush_args_and_op (op_code_t op, cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param)
430
0
  {
431
0
    bool skip_command = false;
432
0
    switch (op)
433
0
    {
434
0
      case OpCode_return:
435
0
      case OpCode_endchar:
436
0
  skip_command = true;
437
0
  break;
438
439
0
      case OpCode_hstem:
440
0
      case OpCode_hstemhm:
441
0
      case OpCode_vstem:
442
0
      case OpCode_vstemhm:
443
0
      case OpCode_hintmask:
444
0
      case OpCode_cntrmask:
445
0
  skip_command = param.drop_hints;
446
0
  break;
447
448
0
      default:
449
0
  break;
450
0
    }
451
452
0
    if (!skip_command && param.rounding && unlikely (!round_args (op, env, param)))
453
0
    {
454
0
      env.set_error ();
455
0
      return;
456
0
    }
457
458
    /* Capture resolved commands for CFF2-to-CFF1 specialization. */
459
0
    if (param.commands && !skip_command)
460
0
    {
461
0
      cs_command_t cmd (op);
462
0
      for (unsigned int i = 0; i < env.argStack.get_count ();)
463
0
      {
464
0
  const blend_arg_t &arg = env.argStack[i];
465
0
  if (arg.blending ())
466
0
  {
467
0
    cmd.args.push (arg);
468
0
    i += arg.numValues;
469
0
  }
470
0
  else
471
0
  {
472
0
    cmd.args.push (arg);
473
0
    i++;
474
0
  }
475
0
      }
476
0
      param.commands->push (cmd);
477
0
    }
478
479
0
    switch (op)
480
0
    {
481
0
      case OpCode_return:
482
0
      case OpCode_endchar:
483
  /* dummy opcodes in CFF2. ignore */
484
0
  if (op == OpCode_endchar && param.instancer)
485
0
    emit_vsindex_prefix (env, param);
486
0
  break;
487
488
0
      case OpCode_hstem:
489
0
      case OpCode_hstemhm:
490
0
      case OpCode_vstem:
491
0
      case OpCode_vstemhm:
492
0
      case OpCode_hintmask:
493
0
      case OpCode_cntrmask:
494
0
  if (param.drop_hints)
495
0
  {
496
0
    env.clear_args ();
497
0
    return;
498
0
  }
499
0
  HB_FALLTHROUGH;
500
501
0
      default:
502
0
  SUPER::flush_args_and_op (op, env, param);
503
0
  break;
504
0
    }
505
0
  }
506
507
  static void flush_args (cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param)
508
0
  {
509
0
    if (param.rounding)
510
0
    {
511
0
      encode_rounded_args (env, param);
512
0
      SUPER::flush_args (env, param);
513
0
      return;
514
0
    }
515
0
    for (unsigned int i = 0; i < env.argStack.get_count ();)
516
0
    {
517
0
      const blend_arg_t &arg = env.argStack[i];
518
0
      if (arg.blending ())
519
0
      {
520
0
  if (unlikely (!((arg.numValues > 0) && (env.argStack.get_count () >= arg.numValues))))
521
0
  {
522
0
    env.set_error ();
523
0
    return;
524
0
  }
525
0
  if (param.instancer)
526
0
    rewrite_blends (arg, i, env, param);
527
0
  else
528
0
    flatten_blends (arg, i, env, param);
529
0
  i += arg.numValues;
530
0
      }
531
0
      else
532
0
      {
533
0
  str_encoder_t  encoder (param.flatStr);
534
0
  encoder.encode_num_cs (arg);
535
0
  i++;
536
0
      }
537
0
    }
538
0
    SUPER::flush_args (env, param);
539
0
  }
540
541
  static bool round_args (op_code_t op, cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param)
542
0
  {
543
0
    unsigned k = 0;
544
0
    if (param.instancer)
545
0
    {
546
0
      unsigned ivs = env.get_ivs ();
547
0
      const auto *t = param.instancer->get_transform (ivs);
548
0
      k = t && t->alive () ? t->matrix.length : 0;
549
0
      for (unsigned i = 0; i < env.argStack.get_count (); i++)
550
0
      {
551
0
  auto &arg = env.argStack[i];
552
0
  if (!arg.blending ()) continue;
553
0
  if (unlikely (!t || arg.deltas.length != t->gains.length)) return false;
554
0
  double value = arg.to_real () + param.instancer->fold_deltas (ivs, arg.deltas.as_array ());
555
0
  hb_vector_t<number_t> deltas = std::move (arg.deltas);
556
0
  arg.set_real (value);
557
0
  if (unlikely (!arg.deltas.resize (k))) return false;
558
0
  for (unsigned r = 0; r < k; r++)
559
0
    arg.deltas.arrayZ[r].set_real (t->transform_delta (r, deltas.as_array ()));
560
0
      }
561
0
    }
562
0
    return param.rounding->round (op, hb_array (env.argStack.arrayZ, env.argStack.get_count ()), k);
563
0
  }
564
565
  static void encode_rounded_args (cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param)
566
0
  {
567
0
    str_encoder_t encoder (param.flatStr);
568
0
    unsigned count = env.argStack.get_count ();
569
0
    for (unsigned i = 0; i < count;)
570
0
    {
571
0
      const auto &arg = env.argStack[i];
572
0
      if (!arg.blending ())
573
0
      {
574
0
  encoder.encode_num_cs (arg);
575
0
  i++;
576
0
  continue;
577
0
      }
578
0
      unsigned k = arg.deltas.length;
579
0
      unsigned n = 1;
580
0
      while (i + n < count && env.argStack[i + n].deltas.length == k) n++;
581
      /* Previously emitted operands remain on the stack during each blend. */
582
0
      unsigned chunk = hb_min (n, (512 - i) / (k + 1));
583
0
      if (unlikely (!chunk))
584
0
      {
585
0
  env.set_error ();
586
0
  return;
587
0
      }
588
0
      for (unsigned j = 0; j < chunk; j++)
589
0
  encoder.encode_num_cs (env.argStack[i + j]);
590
0
      for (unsigned j = 0; j < chunk; j++)
591
0
  for (const auto &d : env.argStack[i + j].deltas)
592
0
    encoder.encode_num_cs (d);
593
0
      encoder.encode_int (chunk);
594
0
      encoder.encode_op (OpCode_blendcs);
595
0
      param.emitted_blend = true;
596
0
      i += chunk;
597
0
    }
598
0
  }
599
600
  static void flatten_blends (const blend_arg_t &arg, unsigned int i, cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param)
601
0
  {
602
    /* flatten the default values */
603
0
    str_encoder_t  encoder (param.flatStr);
604
0
    for (unsigned int j = 0; j < arg.numValues; j++)
605
0
    {
606
0
      const blend_arg_t &arg1 = env.argStack[i + j];
607
0
      if (unlikely (!((arg1.blending () && (arg.numValues == arg1.numValues) && (arg1.valueIndex == j) &&
608
0
        (arg1.deltas.length == env.get_region_count ())))))
609
0
      {
610
0
  env.set_error ();
611
0
  return;
612
0
      }
613
0
      encoder.encode_num_cs (arg1);
614
0
    }
615
    /* flatten deltas for each value */
616
0
    for (unsigned int j = 0; j < arg.numValues; j++)
617
0
    {
618
0
      const blend_arg_t &arg1 = env.argStack[i + j];
619
0
      for (unsigned int k = 0; k < arg1.deltas.length; k++)
620
0
  encoder.encode_num_cs (arg1.deltas[k]);
621
0
    }
622
    /* flatten the number of values followed by blend operator */
623
0
    encoder.encode_int (arg.numValues);
624
0
    encoder.encode_op (OpCode_blendcs);
625
0
  }
626
627
  /* Partial instancing: rewrite one blend group against the instanced store.
628
   * Each value's default absorbs the folded (pinned-away) contribution, and
629
   * its deltas are mapped onto the new region list. */
630
  static void rewrite_blends (const blend_arg_t &arg, unsigned int i, cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param)
631
0
  {
632
0
    const cff2_instancing_plan_t &instancer = *param.instancer;
633
0
    unsigned ivs = env.get_ivs ();
634
0
    const cff2_instancing_plan_t::vardata_transform_t *t = instancer.get_transform (ivs);
635
0
    if (unlikely (!t))
636
0
    {
637
0
      env.set_error ();
638
0
      return;
639
0
    }
640
641
0
    unsigned n = arg.numValues;
642
0
    for (unsigned int j = 0; j < n; j++)
643
0
    {
644
0
      const blend_arg_t &arg1 = env.argStack[i + j];
645
0
      if (unlikely (!(arg1.blending () && (arg1.numValues == n) && (arg1.valueIndex == j) &&
646
0
          (arg1.deltas.length == env.get_region_count ()) &&
647
0
          (arg1.deltas.length == t->gains.length))))
648
0
      {
649
0
  env.set_error ();
650
0
  return;
651
0
      }
652
0
    }
653
654
0
    str_encoder_t encoder (param.flatStr);
655
0
    unsigned k_new = t->alive () ? t->matrix.length : 0;
656
657
0
    if (!k_new)
658
0
    {
659
      /* All of this VarData's regions died; blends dissolve into statics. */
660
0
      for (unsigned int j = 0; j < n; j++)
661
0
      {
662
0
  const blend_arg_t &arg1 = env.argStack[i + j];
663
0
  number_t v;
664
0
  v.set_real (arg1.to_real () + round (instancer.fold_deltas (ivs, arg1.deltas.as_array ())));
665
0
  encoder.encode_num_cs (v);
666
0
      }
667
0
      return;
668
0
    }
669
670
    /* Emit in chunks so the output charstring stays within the argument
671
     * stack limit: values already emitted for this op, plus this chunk's
672
     * defaults, deltas and count, must not exceed 513. */
673
0
    unsigned done = 0;
674
0
    while (done < n)
675
0
    {
676
0
      unsigned avail = 512 > (i + done) ? 512 - (i + done) : 0;
677
0
      unsigned chunk = hb_min (avail / (k_new + 1), n - done);
678
0
      if (unlikely (!chunk))
679
0
      {
680
0
  env.set_error ();
681
0
  return;
682
0
      }
683
684
0
      for (unsigned int j = done; j < done + chunk; j++)
685
0
      {
686
0
  const blend_arg_t &arg1 = env.argStack[i + j];
687
0
  number_t v;
688
0
  v.set_real (arg1.to_real () + round (instancer.fold_deltas (ivs, arg1.deltas.as_array ())));
689
0
  encoder.encode_num_cs (v);
690
0
      }
691
0
      for (unsigned int j = done; j < done + chunk; j++)
692
0
      {
693
0
  const blend_arg_t &arg1 = env.argStack[i + j];
694
0
  for (unsigned r = 0; r < k_new; r++)
695
0
  {
696
0
    number_t v;
697
0
    v.set_int (hb_clamp_to<int> (round (t->transform_delta (r, arg1.deltas.as_array ()))));
698
0
    encoder.encode_num_cs (v);
699
0
  }
700
0
      }
701
0
      encoder.encode_int (chunk);
702
0
      encoder.encode_op (OpCode_blendcs);
703
0
      done += chunk;
704
0
    }
705
0
    param.emitted_blend = true;
706
0
  }
707
708
  /* Partial instancing: prepend a vsindex op when the charstring's
709
   * (remapped) ivs differs from its FD's (remapped) private-dict ivs. */
710
  static void emit_vsindex_prefix (cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param)
711
0
  {
712
0
    if (!param.emitted_blend) return;
713
0
    const cff2_instancing_plan_t &instancer = *param.instancer;
714
0
    unsigned new_ivs = instancer.remap_ivs (env.get_ivs ());
715
0
    unsigned fd_ivs = instancer.remap_ivs (env.get_orig_ivs ());
716
0
    if (new_ivs == fd_ivs) return;
717
718
0
    str_buff_t prefix;
719
0
    str_encoder_t enc (prefix);
720
0
    enc.encode_int ((int) new_ivs);
721
0
    enc.encode_op (OpCode_vsindexcs);
722
723
0
    str_buff_t &buf = param.flatStr;
724
0
    unsigned old_len = buf.length;
725
0
    if (unlikely (prefix.in_error () || !buf.resize (old_len + prefix.length)))
726
0
    {
727
0
      env.set_error ();
728
0
      return;
729
0
    }
730
0
    memmove (buf.arrayZ + prefix.length, buf.arrayZ, old_len);
731
0
    hb_memcpy (buf.arrayZ, prefix.arrayZ, prefix.length);
732
0
  }
733
734
  static void flush_op (op_code_t op, cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param)
735
0
  {
736
0
    switch (op)
737
0
    {
738
0
      case OpCode_return:
739
0
      case OpCode_endchar:
740
0
  return;
741
0
      default:
742
0
  str_encoder_t  encoder (param.flatStr);
743
0
  encoder.encode_op (op);
744
0
    }
745
0
  }
746
747
  static void flush_hintmask (op_code_t op, cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param)
748
0
  {
749
0
    SUPER::flush_hintmask (op, env, param);
750
    /* Preserve hintmask payload in captured commands for specializer re-encoding. */
751
0
    if (param.commands && !param.drop_hints && param.commands->length > 0)
752
0
    {
753
0
      auto &cmd = param.commands->tail ();
754
0
      if (cmd.op == op)
755
0
      {
756
0
        cmd.mask_bytes.resize (env.hintmask_size);
757
0
        if (unlikely (cmd.mask_bytes.in_error ()))
758
0
        {
759
0
          env.set_error ();
760
0
          return;
761
0
        }
762
0
        for (unsigned int i = 0; i < env.hintmask_size; i++)
763
0
          cmd.mask_bytes[i] = env.str_ref[i];
764
0
      }
765
0
    }
766
0
    if (!param.drop_hints)
767
0
    {
768
0
      str_encoder_t  encoder (param.flatStr);
769
0
      for (unsigned int i = 0; i < env.hintmask_size; i++)
770
0
  encoder.encode_byte (env.str_ref[i]);
771
0
    }
772
0
  }
773
774
  private:
775
  typedef cff2_cs_opset_t<cff2_cs_opset_flatten_t, flatten_param_t, blend_arg_t> SUPER;
776
  typedef cs_opset_t<blend_arg_t, cff2_cs_opset_flatten_t, cff2_cs_opset_flatten_t, cff2_cs_interp_env_t<blend_arg_t>, flatten_param_t> CSOPSET;
777
};
778
779
struct cff2_cs_opset_subr_subset_t : cff2_cs_opset_t<cff2_cs_opset_subr_subset_t, subr_subset_param_t, blend_arg_t>
780
{
781
  static void process_op (op_code_t op, cff2_cs_interp_env_t<blend_arg_t> &env, subr_subset_param_t& param)
782
0
  {
783
0
    switch (op) {
784
785
0
      case OpCode_return:
786
0
  param.current_parsed_str->flush_coalesced (env.str_ref);
787
0
  param.current_parsed_str->set_parsed ();
788
0
  env.return_from_subr ();
789
0
  param.set_current_str (env, false);
790
0
  break;
791
792
0
      case OpCode_endchar:
793
0
  param.current_parsed_str->flush_coalesced (env.str_ref);
794
0
  param.current_parsed_str->set_parsed ();
795
0
  SUPER::process_op (op, env, param);
796
0
  break;
797
798
0
      case OpCode_callsubr:
799
0
  process_call_subr (op, CSType_LocalSubr, env, param, env.localSubrs, param.local_closure);
800
0
  break;
801
802
0
      case OpCode_callgsubr:
803
0
  process_call_subr (op, CSType_GlobalSubr, env, param, env.globalSubrs, param.global_closure);
804
0
  break;
805
806
0
      default:
807
0
  SUPER::process_op (op, env, param);
808
0
  param.current_parsed_str->add_op (op, env.str_ref);
809
0
  break;
810
0
    }
811
0
  }
812
813
  protected:
814
  static void process_call_subr (op_code_t op, cs_type_t type,
815
         cff2_cs_interp_env_t<blend_arg_t> &env, subr_subset_param_t& param,
816
         cff2_biased_subrs_t& subrs, hb_set_t *closure)
817
0
  {
818
0
    if (unlikely (!param.current_parsed_str->has_immediate_subr_number ()))
819
0
    {
820
0
      env.set_error ();
821
0
      return;
822
0
    }
823
0
    byte_str_ref_t    str_ref = env.str_ref;
824
0
    env.call_subr (subrs, type);
825
0
    param.current_parsed_str->add_call_op (op, str_ref, env.context.subr_num);
826
0
    closure->add (env.context.subr_num);
827
0
    param.set_current_str (env, true);
828
0
  }
829
830
  private:
831
  typedef cff2_cs_opset_t<cff2_cs_opset_subr_subset_t, subr_subset_param_t, blend_arg_t> SUPER;
832
};
833
834
struct cff2_subr_subsetter_t : subr_subsetter_t<cff2_subr_subsetter_t, CFF2Subrs, const OT::cff2::accelerator_subset_t, cff2_cs_interp_env_t<blend_arg_t>, cff2_cs_opset_subr_subset_t>
835
{
836
  cff2_subr_subsetter_t (const OT::cff2::accelerator_subset_t &acc_, const hb_subset_plan_t *plan_)
837
0
    : subr_subsetter_t (acc_, plan_) {}
838
839
  static void complete_parsed_str (cff2_cs_interp_env_t<blend_arg_t> &env, subr_subset_param_t& param, parsed_cs_str_t &charstring)
840
0
  {
841
    /* vsindex is inserted at the beginning of the charstring as necessary */
842
0
    if (env.seen_vsindex ())
843
0
    {
844
0
      number_t  ivs;
845
0
      ivs.set_int ((int)env.get_ivs ());
846
0
      charstring.set_prefix (ivs, OpCode_vsindexcs);
847
0
    }
848
0
  }
849
};
850
851
struct cff2_private_blend_encoder_param_t
852
{
853
  cff2_private_blend_encoder_param_t (hb_serialize_context_t *c,
854
              const CFF2ItemVariationStore *varStore,
855
              hb_array_t<int> normalized_coords) :
856
0
    c (c), varStore (varStore), normalized_coords (normalized_coords) {}
857
858
0
  void init () {}
859
860
  void process_blend ()
861
0
  {
862
0
    if (!seen_blend)
863
0
    {
864
0
      region_count = varStore->varStore.get_region_index_count (ivs);
865
0
      if (unlikely (!scalars.resize_exact (region_count)))
866
0
      {
867
0
  if (c) c->err (HB_SERIALIZE_ERROR_OTHER);
868
0
  seen_blend = true;
869
0
  return;
870
0
      }
871
0
      varStore->varStore.get_region_scalars (ivs, normalized_coords.arrayZ, normalized_coords.length,
872
0
               &scalars[0], region_count);
873
0
      seen_blend = true;
874
0
    }
875
0
  }
876
877
  double blend_deltas (hb_array_t<const number_t> deltas) const
878
0
  {
879
0
    double v = 0;
880
0
    if (likely (scalars.length == deltas.length))
881
0
    {
882
0
      unsigned count = scalars.length;
883
0
      for (unsigned i = 0; i < count; i++)
884
0
  v += (double) scalars.arrayZ[i] * deltas.arrayZ[i].to_real ();
885
0
    }
886
0
    return v;
887
0
  }
888
889
890
  hb_serialize_context_t *c = nullptr;
891
  bool seen_blend = false;
892
  unsigned ivs = 0;
893
  unsigned region_count = 0;
894
  hb_vector_t<float> scalars;
895
  const  CFF2ItemVariationStore *varStore = nullptr;
896
  hb_array_t<int> normalized_coords;
897
898
  /* CFF2 partial instancing: when set, blends are rewritten against the
899
   * instanced variation store instead of being flattened. */
900
  const cff2_instancing_plan_t *instancer = nullptr;
901
  /* Error-compensated fold rounding state, per dict operator. */
902
  double fold_exact = 0.;
903
  double fold_emitted = 0.;
904
};
905
906
struct cff2_private_dict_blend_opset_t : dict_opset_t
907
{
908
  static void process_arg_blend (cff2_private_blend_encoder_param_t& param,
909
         number_t &arg,
910
         const hb_array_t<const number_t> blends,
911
         unsigned n, unsigned i)
912
0
  {
913
0
    arg.set_int (round (arg.to_real () + param.blend_deltas (blends)));
914
0
  }
915
916
  /* The fold added to a value, error-compensated across the operator: most
917
   * blended private-dict values (BlueValues, StemSnap*, ...) are
918
   * delta-encoded, so independently rounded folds would accumulate in the
919
   * decoded absolutes; keeping the emitted cumulative fold equal to the
920
   * rounded exact cumulative fold bounds every absolute within ±0.5. */
921
  static double fold (cff2_private_blend_encoder_param_t& param,
922
          const hb_array_t<const number_t> blends)
923
0
  {
924
0
    double f = param.blend_deltas (blends);
925
0
    double emit = round (param.fold_exact + f) - param.fold_emitted;
926
0
    param.fold_exact += f;
927
0
    param.fold_emitted += emit;
928
0
    return emit;
929
0
  }
930
931
  /* Partial instancing: emit the args preceding this blend group, then the
932
   * group itself rewritten against the instanced store, and clear the
933
   * stack.  The op's remaining args are flushed at process_op time. */
934
  static void rewrite_blends (cff2_priv_dict_interp_env_t& env,
935
            cff2_private_blend_encoder_param_t& param,
936
            unsigned start, unsigned n, unsigned k)
937
0
  {
938
0
    const cff2_instancing_plan_t &instancer = *param.instancer;
939
0
    const cff2_instancing_plan_t::vardata_transform_t *t = instancer.get_transform (param.ivs);
940
0
    if (unlikely (!t || t->gains.length != k))
941
0
    {
942
0
      env.set_error ();
943
0
      return;
944
0
    }
945
946
0
    str_buff_t str;
947
0
    str_encoder_t encoder (str);
948
949
0
    for (unsigned idx = 0; idx < start; idx++)
950
0
      encoder.encode_num_tp (env.argStack[idx]);
951
952
0
    unsigned k_new = t->alive () ? t->matrix.length : 0;
953
0
    if (!k_new)
954
0
    {
955
      /* This VarData died (or is unused by any retained charstring, which
956
       * can happen for FDs kept only for retain-gids holes); blends
957
       * dissolve into statics. */
958
0
      for (unsigned j = 0; j < n; j++)
959
0
      {
960
0
  const hb_array_t<const number_t> blends = env.argStack.sub_array (start + n + (j * k), k);
961
0
  number_t v;
962
0
  v.set_real (env.argStack[start + j].to_real () + fold (param, blends));
963
0
  encoder.encode_num_tp (v);
964
0
      }
965
0
    }
966
0
    else
967
0
    {
968
0
      unsigned done = 0;
969
0
      while (done < n)
970
0
      {
971
0
  unsigned avail = 512 > (start + done) ? 512 - (start + done) : 0;
972
0
  unsigned chunk = hb_min (avail / (k_new + 1), n - done);
973
0
  if (unlikely (!chunk))
974
0
  {
975
0
    env.set_error ();
976
0
    return;
977
0
  }
978
979
0
  for (unsigned j = done; j < done + chunk; j++)
980
0
  {
981
0
    const hb_array_t<const number_t> blends = env.argStack.sub_array (start + n + (j * k), k);
982
0
    number_t v;
983
0
    v.set_real (env.argStack[start + j].to_real () + fold (param, blends));
984
0
    encoder.encode_num_tp (v);
985
0
  }
986
0
  for (unsigned j = done; j < done + chunk; j++)
987
0
  {
988
0
    const hb_array_t<const number_t> blends = env.argStack.sub_array (start + n + (j * k), k);
989
0
    for (unsigned r = 0; r < k_new; r++)
990
0
    {
991
0
      number_t v;
992
0
      v.set_int (hb_clamp_to<int> (round (t->transform_delta (r, blends))));
993
0
      encoder.encode_num_tp (v);
994
0
    }
995
0
  }
996
0
  encoder.encode_int (chunk);
997
0
  encoder.encode_op (OpCode_blenddict);
998
0
  done += chunk;
999
0
      }
1000
0
    }
1001
1002
0
    auto bytes = str.as_bytes ();
1003
0
    param.c->embed (&bytes, bytes.length);
1004
1005
0
    env.clear_args ();
1006
0
  }
1007
1008
  static void process_blend (cff2_priv_dict_interp_env_t& env, cff2_private_blend_encoder_param_t& param)
1009
0
  {
1010
0
    unsigned int n, k;
1011
1012
0
    param.process_blend ();
1013
0
    k = param.region_count;
1014
0
    n = env.argStack.pop_uint ();
1015
1016
0
    unsigned count = env.argStack.get_count ();
1017
0
    unsigned int total;
1018
0
    if (unlikely (hb_unsigned_mul_overflows (k + 1, n, &total) || total > count))
1019
0
    {
1020
0
      env.set_error ();
1021
0
      return;
1022
0
    }
1023
1024
    /* copy the blend values into blend array of the default values */
1025
0
    unsigned int start = count - total;
1026
    /* let an obvious error case fail, but note CFF2 spec doesn't forbid n==0 */
1027
0
    if (unlikely (start > count))
1028
0
    {
1029
0
      env.set_error ();
1030
0
      return;
1031
0
    }
1032
1033
0
    if (param.instancer && k)
1034
0
    {
1035
0
      rewrite_blends (env, param, start, n, k);
1036
0
      return;
1037
0
    }
1038
1039
0
    for (unsigned int i = 0; i < n; i++)
1040
0
    {
1041
0
      const hb_array_t<const number_t> blends = env.argStack.sub_array (start + n + (i * k), k);
1042
0
      process_arg_blend (param, env.argStack[start + i], blends, n, i);
1043
0
    }
1044
1045
    /* pop off blend values leaving default values now adorned with blend values */
1046
0
    env.argStack.pop (k * n);
1047
0
  }
1048
1049
  static void process_op (op_code_t op, cff2_priv_dict_interp_env_t& env, cff2_private_blend_encoder_param_t& param)
1050
0
  {
1051
0
    switch (op) {
1052
0
      case OpCode_StdHW:
1053
0
      case OpCode_StdVW:
1054
0
      case OpCode_BlueScale:
1055
0
      case OpCode_BlueShift:
1056
0
      case OpCode_BlueFuzz:
1057
0
      case OpCode_ExpansionFactor:
1058
0
      case OpCode_LanguageGroup:
1059
0
      case OpCode_BlueValues:
1060
0
      case OpCode_OtherBlues:
1061
0
      case OpCode_FamilyBlues:
1062
0
      case OpCode_FamilyOtherBlues:
1063
0
      case OpCode_StemSnapH:
1064
0
      case OpCode_StemSnapV:
1065
0
  break;
1066
0
      case OpCode_vsindexdict:
1067
0
  env.process_vsindex ();
1068
0
  param.ivs = env.get_ivs ();
1069
0
  if (param.instancer)
1070
0
  {
1071
    /* Re-emit with the remapped vsindex; 0 is the dict default. */
1072
0
    unsigned new_ivs = param.instancer->remap_ivs (param.ivs);
1073
0
    if (new_ivs)
1074
0
    {
1075
0
      str_buff_t str;
1076
0
      str_encoder_t encoder (str);
1077
0
      encoder.encode_int ((int) new_ivs);
1078
0
      encoder.encode_op (OpCode_vsindexdict);
1079
0
      auto bytes = str.as_bytes ();
1080
0
      param.c->embed (&bytes, bytes.length);
1081
0
    }
1082
0
  }
1083
0
  env.clear_args ();
1084
0
  return;
1085
0
      case OpCode_blenddict:
1086
0
  process_blend (env, param);
1087
0
  return;
1088
1089
0
      default:
1090
0
  dict_opset_t::process_op (op, env);
1091
0
  if (!env.argStack.is_empty ()) return;
1092
0
  break;
1093
0
    }
1094
1095
0
    if (unlikely (env.in_error ())) return;
1096
1097
    // Write args then op
1098
1099
0
    str_buff_t str;
1100
0
    str_encoder_t encoder (str);
1101
1102
0
    unsigned count = env.argStack.get_count ();
1103
0
    for (unsigned i = 0; i < count; i++)
1104
0
      encoder.encode_num_tp (env.argStack[i]);
1105
1106
0
    encoder.encode_op (op);
1107
1108
0
    auto bytes = str.as_bytes ();
1109
0
    param.c->embed (&bytes, bytes.length);
1110
1111
0
    env.clear_args ();
1112
0
    param.fold_exact = param.fold_emitted = 0.;
1113
0
  }
1114
};
1115
1116
struct cff2_private_dict_op_serializer_t : op_serializer_t
1117
{
1118
  cff2_private_dict_op_serializer_t (bool desubroutinize_, bool drop_hints_, bool pinned_,
1119
             const CFF::CFF2ItemVariationStore* varStore_,
1120
             hb_array_t<int> normalized_coords_,
1121
             const cff2_instancing_plan_t *instancer_ = nullptr)
1122
0
    : desubroutinize (desubroutinize_), drop_hints (drop_hints_), pinned (pinned_),
1123
0
      param (nullptr, varStore_, normalized_coords_)
1124
0
  { param.instancer = instancer_; }
1125
1126
  bool serialize (hb_serialize_context_t *c,
1127
      const op_str_t &opstr,
1128
      objidx_t subrs_link) const
1129
0
  {
1130
0
    TRACE_SERIALIZE (this);
1131
1132
0
    if (drop_hints && dict_opset_t::is_hint_op (opstr.op))
1133
0
      return_trace (true);
1134
1135
0
    if (opstr.op == OpCode_Subrs)
1136
0
    {
1137
0
      if (desubroutinize || !subrs_link)
1138
0
  return_trace (true);
1139
0
      else
1140
0
  return_trace (FontDict::serialize_link2_op (c, opstr.op, subrs_link));
1141
0
    }
1142
1143
0
    if (pinned || param.instancer)
1144
0
    {
1145
      /* Reinterpret opstr and process blends.  The param persists across
1146
       * this dict's opstrs, so that an ivs set by a vsindexdict opstr is
1147
       * still in effect when a later opstr's blends are processed. */
1148
0
      cff2_priv_dict_interp_env_t env {hb_ubytes_t (opstr.ptr, opstr.length)};
1149
0
      param.c = c;
1150
0
      dict_interpreter_t<cff2_private_dict_blend_opset_t, cff2_private_blend_encoder_param_t, cff2_priv_dict_interp_env_t> interp (env);
1151
0
      return_trace (interp.interpret (param));
1152
0
    }
1153
1154
0
    return_trace (copy_opstr (c, opstr));
1155
0
  }
1156
1157
  protected:
1158
  const bool desubroutinize;
1159
  const bool drop_hints;
1160
  const bool pinned;
1161
  mutable cff2_private_blend_encoder_param_t param;
1162
};
1163
1164
1165
1166
namespace OT {
1167
struct cff2_subset_plan
1168
{
1169
  bool create (const OT::cff2::accelerator_subset_t &acc,
1170
        hb_subset_plan_t *plan)
1171
0
  {
1172
    /* make sure notdef is first */
1173
0
    hb_codepoint_t old_glyph;
1174
0
    if (!plan->old_gid_for_new_gid (0, &old_glyph) || (old_glyph != 0)) return false;
1175
1176
0
    num_glyphs = plan->num_output_glyphs ();
1177
0
    orig_fdcount = acc.fdArray->count;
1178
1179
0
    drop_hints = plan->flags & HB_SUBSET_FLAGS_NO_HINTING;
1180
0
    bool instancing_requested = (bool) plan->normalized_coords;
1181
0
    pinned = instancing_requested && plan->all_axes_pinned;
1182
0
    normalized_coords = plan->normalized_coords;
1183
0
    head_maxp_info = plan->head_maxp_info;
1184
0
    hmtx_map = &plan->hmtx_map;
1185
0
    desubroutinize = plan->flags & HB_SUBSET_FLAGS_DESUBROUTINIZE ||
1186
0
         instancing_requested; // For instancing we need this path
1187
1188
    /* Partial instancing: some axes pinned or limited, others kept. */
1189
0
    partial_instancing = instancing_requested && !pinned &&
1190
0
       acc.varStore != &Null (CFF2ItemVariationStore);
1191
0
    if (partial_instancing &&
1192
0
  unlikely (!instancing.create (acc.varStore, plan)))
1193
0
      return false;
1194
0
    axis_tags = &plan->axis_tags;
1195
1196
    /* Enable command capture for CFF2→CFF1 conversion (for specialization) */
1197
0
    capture_commands = pinned;
1198
1199
 #ifdef HB_EXPERIMENTAL_API
1200
    min_charstrings_off_size = (plan->flags & HB_SUBSET_FLAGS_IFTB_REQUIREMENTS) ? 4 : 0;
1201
 #else
1202
0
    min_charstrings_off_size = 0;
1203
0
 #endif
1204
1205
0
    if (desubroutinize)
1206
0
    {
1207
0
      if (instancing_requested)
1208
0
      {
1209
  /* Identify charstrings that can round absolute coordinates.  Conservatively
1210
   * keep relative rounding for flex1, whose implicit direction can vary.
1211
   * Also collect the VarDatas referenced by partial instances. */
1212
0
  hb_vector_t<bool> round_charstrings;
1213
0
  if (unlikely (!round_charstrings.resize (num_glyphs))) return false;
1214
0
  hb_set_t used_fds;
1215
0
  for (unsigned int i = 0; i < num_glyphs; i++)
1216
0
  {
1217
0
    hb_codepoint_t glyph;
1218
0
    if (!plan->old_gid_for_new_gid (i, &glyph))
1219
0
      continue;
1220
0
    const hb_ubytes_t str = (*acc.charStrings)[glyph];
1221
0
    unsigned int fd = CFF::subset_get_fd (acc, glyph);
1222
0
    if (unlikely (fd >= acc.fdCount))
1223
0
      return false;
1224
0
    used_fds.add (fd);
1225
1226
0
    cff2_cs_interp_env_t<blend_arg_t> env (str, acc, fd);
1227
0
    cs_interpreter_t<cff2_cs_interp_env_t<blend_arg_t>, cff2_cs_opset_usage_t, cff2_usage_param_t> interp (env);
1228
0
    cff2_usage_param_t param;
1229
0
    param.instancing = partial_instancing ? &instancing : nullptr;
1230
0
    param.drop_hints = drop_hints;
1231
0
    if (unlikely (!interp.interpret (param)))
1232
0
      return false;
1233
0
    round_charstrings.arrayZ[i] = param.has_blend && !param.has_flex1;
1234
0
  }
1235
0
  if (partial_instancing)
1236
0
  {
1237
0
    for (unsigned fd : used_fds)
1238
0
    {
1239
0
      cff2_usage_param_t param;
1240
0
      param.instancing = &instancing;
1241
0
      unsigned count = acc.privateDicts[fd].get_count ();
1242
0
      for (unsigned j = 0; j < count; j++)
1243
0
      {
1244
0
        const op_str_t &opstr = acc.privateDicts[fd][j];
1245
0
        if (drop_hints && dict_opset_t::is_hint_op (opstr.op))
1246
0
          continue;
1247
0
        cff2_priv_dict_interp_env_t env {hb_ubytes_t (opstr.ptr, opstr.length)};
1248
0
        dict_interpreter_t<cff2_private_dict_usage_opset_t, cff2_usage_param_t, cff2_priv_dict_interp_env_t> interp (env);
1249
0
        if (unlikely (!interp.interpret (param)))
1250
0
          return false;
1251
0
      }
1252
0
    }
1253
0
    if (unlikely (!instancing.finalize ()))
1254
0
      return false;
1255
0
  }
1256
1257
  /* Keep full-instance blends unrounded until their absolute positions
1258
   * are known; partial-instance blends stay symbolic for rewriting. */
1259
0
  if (!subset_charstrings.resize_exact (num_glyphs))
1260
0
    return false;
1261
0
  if (capture_commands && !charstring_commands.resize_exact (num_glyphs))
1262
0
    return false;
1263
0
  for (unsigned int i = 0; i < num_glyphs; i++)
1264
0
  {
1265
0
    hb_codepoint_t glyph;
1266
0
    if (!plan->old_gid_for_new_gid (i, &glyph))
1267
0
      continue;
1268
0
    const hb_ubytes_t str = (*acc.charStrings)[glyph];
1269
0
    unsigned int fd = CFF::subset_get_fd (acc, glyph);
1270
0
    if (unlikely (fd >= acc.fdCount))
1271
0
      return false;
1272
1273
0
    cff2_cs_interp_env_t<blend_arg_t> env (str, acc, fd,
1274
0
                 pinned ? plan->normalized_coords.arrayZ : nullptr,
1275
0
                 pinned ? plan->normalized_coords.length : 0);
1276
0
    env.round_blends = !round_charstrings.arrayZ[i];
1277
0
    cs_interpreter_t<cff2_cs_interp_env_t<blend_arg_t>, cff2_cs_opset_flatten_t, flatten_param_t> interp (env);
1278
0
    flatten_param_t param = {
1279
0
      subset_charstrings.arrayZ[i],
1280
0
      (bool) (plan->flags & HB_SUBSET_FLAGS_NO_HINTING),
1281
0
      plan,
1282
0
      capture_commands ? &charstring_commands.arrayZ[i] : nullptr
1283
0
    };
1284
0
    cff2_cs_rounding_t rounding;
1285
0
    if (round_charstrings.arrayZ[i]) param.rounding = &rounding;
1286
0
    param.instancer = partial_instancing ? &instancing : nullptr;
1287
0
    if (unlikely (!interp.interpret (param)))
1288
0
      return false;
1289
0
  }
1290
0
      }
1291
0
      else
1292
0
      {
1293
      /* Flatten global & local subrs */
1294
0
      subr_flattener_t<const OT::cff2::accelerator_subset_t, cff2_cs_interp_env_t<blend_arg_t>, cff2_cs_opset_flatten_t>
1295
0
        flattener(acc, plan);
1296
1297
      /* Enable command capture if requested (for specialization) */
1298
0
      if (capture_commands)
1299
0
      {
1300
0
  if (!charstring_commands.resize_exact (num_glyphs))
1301
0
    return false;
1302
1303
0
  if (!flattener.flatten (subset_charstrings, &charstring_commands))
1304
0
    return false;
1305
0
      }
1306
0
      else
1307
0
      {
1308
0
  if (!flattener.flatten (subset_charstrings))
1309
0
    return false;
1310
0
      }
1311
0
      }
1312
0
    }
1313
0
    else
1314
0
    {
1315
0
      cff2_subr_subsetter_t subr_subsetter (acc, plan);
1316
1317
      /* Subset subrs: collect used subroutines, leaving all unused ones behind */
1318
0
      if (!subr_subsetter.subset ())
1319
0
  return false;
1320
1321
      /* encode charstrings, global subrs, local subrs with new subroutine numbers */
1322
0
      if (!subr_subsetter.encode_charstrings (subset_charstrings, !pinned))
1323
0
  return false;
1324
1325
0
      if (!subr_subsetter.encode_globalsubrs (subset_globalsubrs))
1326
0
  return false;
1327
1328
      /* local subrs */
1329
0
      if (!subset_localsubrs.resize (orig_fdcount))
1330
0
  return false;
1331
0
      for (unsigned int fd = 0; fd < orig_fdcount; fd++)
1332
0
      {
1333
0
  subset_localsubrs[fd].init ();
1334
0
  if (!subr_subsetter.encode_localsubrs (fd, subset_localsubrs[fd]))
1335
0
    return false;
1336
0
      }
1337
0
    }
1338
1339
    /* FDSelect */
1340
0
    if (acc.fdSelect != &Null (CFF2FDSelect))
1341
0
    {
1342
0
      if (unlikely (!hb_plan_subset_cff_fdselect (plan,
1343
0
              orig_fdcount,
1344
0
              *(const FDSelect *)acc.fdSelect,
1345
0
              acc.num_glyphs,
1346
0
              subset_fdcount,
1347
0
              subset_fdselect_size,
1348
0
              subset_fdselect_format,
1349
0
              subset_fdselect_ranges,
1350
0
              fdmap)))
1351
0
  return false;
1352
0
    }
1353
0
    else
1354
0
      fdmap.identity (1);
1355
1356
0
    return true;
1357
0
  }
1358
1359
  cff2_sub_table_info_t info;
1360
1361
  unsigned int    num_glyphs;
1362
  unsigned int    orig_fdcount = 0;
1363
  unsigned int    subset_fdcount = 1;
1364
  unsigned int    subset_fdselect_size = 0;
1365
  unsigned int    subset_fdselect_format = 0;
1366
  bool            pinned = false;
1367
  hb_vector_t<code_pair_t>   subset_fdselect_ranges;
1368
1369
  hb_inc_bimap_t   fdmap;
1370
1371
  str_buff_vec_t      subset_charstrings;
1372
  str_buff_vec_t      subset_globalsubrs;
1373
  hb_vector_t<str_buff_vec_t> subset_localsubrs;
1374
1375
  bool      drop_hints = false;
1376
  bool      desubroutinize = false;
1377
1378
  bool      partial_instancing = false;
1379
  cff2_instancing_plan_t instancing;
1380
  const hb_vector_t<hb_tag_t> *axis_tags = nullptr;
1381
1382
  unsigned  min_charstrings_off_size = 0;
1383
1384
  hb_array_t<int> normalized_coords; // For instantiation
1385
  head_maxp_info_t head_maxp_info;  // For FontBBox
1386
  const hb_hashmap_t<hb_codepoint_t, hb_pair_t<unsigned, int>> *hmtx_map; // For widths
1387
1388
  // Width optimization results (for CFF1 conversion)
1389
  unsigned default_width = 0;
1390
  unsigned nominal_width = 0;
1391
1392
  // Command capture for specialization (CFF2→CFF1 conversion)
1393
  bool capture_commands = false;
1394
  hb_vector_t<hb_vector_t<cs_command_t>> charstring_commands;
1395
};
1396
} // namespace OT
1397
1398
/*
1399
 * CFF2 to CFF1 Converter Implementation
1400
 */
1401
1402
#include "hb-cff-width-optimizer.hh"
1403
#include "hb-cff-specializer.hh"
1404
1405
/* Serialize charstrings using CFF1 format with widths */
1406
static bool
1407
_serialize_cff1_charstrings (hb_serialize_context_t *c,
1408
                             OT::cff2_subset_plan &plan,
1409
                             unsigned default_width,
1410
                             unsigned nominal_width)
1411
0
{
1412
0
  c->push ();
1413
1414
  // CFF1 requires:
1415
  // 1. Width at the beginning (if != defaultWidthX)
1416
  // 2. endchar at the end
1417
0
  str_buff_vec_t cff1_charstrings;
1418
0
  if (unlikely (!cff1_charstrings.resize (plan.subset_charstrings.length)))
1419
0
  {
1420
0
    c->pop_discard ();
1421
0
    return false;
1422
0
  }
1423
1424
0
  for (unsigned i = 0; i < plan.subset_charstrings.length; i++)
1425
0
  {
1426
    // Get width for this glyph from hmtx_map
1427
0
    unsigned width = 0;
1428
0
    if (plan.hmtx_map->has (i))
1429
0
      width = plan.hmtx_map->get (i).first;
1430
1431
    // Encode width if different from default
1432
0
    str_encoder_t encoder (cff1_charstrings[i]);
1433
0
    if (width != default_width)
1434
0
    {
1435
0
      int delta = (int) width - (int) nominal_width;
1436
0
      encoder.encode_int (delta);
1437
0
    }
1438
1439
    // Use specialized commands if available, otherwise use binary
1440
0
    if (plan.capture_commands && i < plan.charstring_commands.length &&
1441
0
        plan.charstring_commands[i].length > 0)
1442
0
    {
1443
      // Specialize and encode commands
1444
0
      auto &commands = plan.charstring_commands[i];
1445
0
      CFF::specialize_commands (commands, 48);  /* maxstack=48 for CFF1 */
1446
0
      if (unlikely (!CFF::encode_commands (commands, cff1_charstrings[i])))
1447
0
      {
1448
0
        c->pop_discard ();
1449
0
        return false;
1450
0
      }
1451
0
    }
1452
0
    else
1453
0
    {
1454
      // Use binary CharString
1455
0
      const str_buff_t &cs = plan.subset_charstrings[i];
1456
0
      for (unsigned j = 0; j < cs.length; j++)
1457
0
        cff1_charstrings[i].push (cs[j]);
1458
0
    }
1459
1460
    // Check if it already ends with endchar (0x0e) or return (0x0b)
1461
0
    if (cff1_charstrings[i].length == 0 ||
1462
0
        (cff1_charstrings[i].tail () != 0x0e && cff1_charstrings[i].tail () != 0x0b))
1463
0
    {
1464
      // Append endchar operator
1465
0
      if (unlikely (!cff1_charstrings[i].push_or_fail (0x0e)))
1466
0
      {
1467
0
        c->pop_discard ();
1468
0
        return false;
1469
0
      }
1470
0
    }
1471
0
  }
1472
1473
0
  unsigned data_size = 0;
1474
0
  unsigned total_size = CFF1CharStrings::total_size (cff1_charstrings, &data_size, plan.min_charstrings_off_size);
1475
0
  if (unlikely (!c->start_zerocopy (total_size)))
1476
0
  {
1477
0
    c->pop_discard ();
1478
0
    return false;
1479
0
  }
1480
1481
0
  auto *cs = c->start_embed<CFF1CharStrings> ();
1482
0
  if (unlikely (!cs->serialize (c, cff1_charstrings, &data_size, plan.min_charstrings_off_size)))
1483
0
  {
1484
0
    c->pop_discard ();
1485
0
    return false;
1486
0
  }
1487
1488
0
  plan.info.char_strings_link = c->pop_pack (false);
1489
0
  return true;
1490
0
}
1491
1492
/* Serialize CID Charset (format 2 range: gid 0-N -> cid 0-N) */
1493
static bool
1494
_serialize_cff1_charset (hb_serialize_context_t *c,
1495
                         unsigned int num_glyphs,
1496
                         objidx_t &charset_link)
1497
0
{
1498
  // For CID fonts, create a simple identity charset
1499
  // Format 2: one range covering all glyphs (except .notdef)
1500
0
  c->push ();
1501
1502
0
  auto *charset = c->start_embed<Charset> ();
1503
0
  if (unlikely (!charset))
1504
0
  {
1505
0
    c->pop_discard ();
1506
0
    return false;
1507
0
  }
1508
1509
  // Create a single range for CID 1 to num_glyphs-1
1510
0
  hb_vector_t<code_pair_t> ranges;
1511
0
  if (num_glyphs > 1)
1512
0
  {
1513
0
    code_pair_t range;
1514
0
    range.code = 1;  // first CID
1515
0
    range.glyph = num_glyphs - 2;  // nLeft (covers glyphs 1 to num_glyphs-1)
1516
0
    ranges.push (range);
1517
0
  }
1518
1519
0
  if (unlikely (!charset->serialize (c, 2, num_glyphs, ranges)))
1520
0
  {
1521
0
    c->pop_discard ();
1522
0
    return false;
1523
0
  }
1524
1525
0
  charset_link = c->pop_pack ();
1526
0
  return true;
1527
0
}
1528
1529
/* CFF2 to CFF1 serialization */
1530
namespace CFF {
1531
1532
bool
1533
serialize_cff2_to_cff1 (hb_serialize_context_t *c,
1534
                        OT::cff2_subset_plan &plan,
1535
                        const cff2_top_dict_values_t &cff2_topDict,
1536
                        const OT::cff2::accelerator_subset_t &acc)
1537
0
{
1538
0
  TRACE_SERIALIZE (this);
1539
1540
  /*
1541
   * CFF1 Serialization Order (reverse, as HarfBuzz packs from end):
1542
   * 1. CharStrings
1543
   * 2. Private DICs & Local Subrs
1544
   * 3. FDArray
1545
   * 4. FDSelect
1546
   * 5. Charset
1547
   * 6. Global Subrs
1548
   * 7. String INDEX
1549
   * 8. Top DICT INDEX
1550
   * 9. Name INDEX
1551
   * 10. Header
1552
   */
1553
1554
  // 0. Optimize width encoding (for all FDs)
1555
0
  {
1556
    // Collect widths from hmtx_map
1557
0
    hb_vector_t<unsigned> widths;
1558
0
    widths.alloc (plan.num_glyphs);
1559
1560
0
    for (unsigned gid = 0; gid < plan.num_glyphs; gid++)
1561
0
    {
1562
0
      unsigned width = 0;
1563
0
      if (plan.hmtx_map->has (gid))
1564
0
        width = plan.hmtx_map->get (gid).first;
1565
0
      widths.push (width);
1566
0
    }
1567
1568
    // Optimize defaultWidthX and nominalWidthX
1569
0
    CFF::optimize_widths (widths, plan.default_width, plan.nominal_width);
1570
0
  }
1571
1572
  // 1. CharStrings (with widths prepended)
1573
0
  if (!_serialize_cff1_charstrings (c, plan, plan.default_width, plan.nominal_width))
1574
0
    return_trace (false);
1575
1576
  // 2. Private DICs & Local Subrs (same as CFF2)
1577
0
  hb_vector_t<table_info_t> private_dict_infos;
1578
0
  if (unlikely (!private_dict_infos.resize (plan.subset_fdcount)))
1579
0
    return_trace (false);
1580
1581
0
  for (int i = (int)acc.privateDicts.length; --i >= 0;)
1582
0
  {
1583
0
    if (plan.fdmap.has (i))
1584
0
    {
1585
0
      objidx_t subrs_link = 0;
1586
1587
0
      if (plan.subset_localsubrs[i].length > 0)
1588
0
      {
1589
0
        auto *dest = c->push<CFF1Subrs> ();
1590
0
        if (likely (dest->serialize (c, plan.subset_localsubrs[i])))
1591
0
          subrs_link = c->pop_pack (false);
1592
0
        else
1593
0
        {
1594
0
          c->pop_discard ();
1595
0
          return_trace (false);
1596
0
        }
1597
0
      }
1598
1599
0
      auto *pd = c->push<PrivateDict> ();
1600
      // Use the CFF2 Private DICT serializer which instantiates blends when pinned=true
1601
0
      cff2_private_dict_op_serializer_t privSzr (plan.desubroutinize, plan.drop_hints, plan.pinned,
1602
0
                                                  acc.varStore, plan.normalized_coords);
1603
0
      if (likely (pd->serialize (c, acc.privateDicts[i], privSzr, subrs_link)))
1604
0
      {
1605
        // Add defaultWidthX and nominalWidthX for CFF1
1606
0
        str_buff_t width_ops;
1607
0
        str_encoder_t encoder (width_ops);
1608
0
        encoder.encode_int (plan.default_width);
1609
0
        encoder.encode_op (OpCode_defaultWidthX);
1610
0
        encoder.encode_int (plan.nominal_width);
1611
0
        encoder.encode_op (OpCode_nominalWidthX);
1612
1613
0
        if (!encoder.in_error () && c->embed (width_ops.as_bytes ().arrayZ, width_ops.length))
1614
0
        {
1615
0
          unsigned fd = plan.fdmap[i];
1616
0
          private_dict_infos[fd].size = c->length ();
1617
0
          private_dict_infos[fd].link = c->pop_pack ();
1618
0
        }
1619
0
        else
1620
0
        {
1621
0
          c->pop_discard ();
1622
0
          return_trace (false);
1623
0
        }
1624
0
      }
1625
0
      else
1626
0
      {
1627
0
        c->pop_discard ();
1628
0
        return_trace (false);
1629
0
      }
1630
0
    }
1631
0
  }
1632
1633
  // 3. FDArray - serialize CFF2 font dicts as CFF1
1634
0
  {
1635
0
    auto *fda = c->push<FDArray<HBUINT16>> ();
1636
0
    cff_font_dict_op_serializer_t fontSzr;
1637
0
    auto it =
1638
0
    + hb_zip (+ hb_iter (acc.fontDicts)
1639
0
              | hb_filter ([&] (const cff2_font_dict_values_t &_)
1640
0
                { return plan.fdmap.has (&_ - &acc.fontDicts[0]); }),
1641
0
              hb_iter (private_dict_infos))
1642
0
    ;
1643
    // Explicitly specify template parameters: DICTVAL, INFO
1644
0
    bool success = fda->serialize<cff2_font_dict_values_t, table_info_t> (c, it, fontSzr);
1645
0
    if (success)
1646
0
      plan.info.fd_array_link = c->pop_pack (false);
1647
0
    else
1648
0
    {
1649
0
      c->pop_discard ();
1650
0
      return_trace (false);
1651
0
    }
1652
0
  }
1653
1654
  // 4. FDSelect (required in CFF1 CID-keyed fonts)
1655
  // CFF1 requires FDSelect for all CID-keyed fonts, even with just one FD
1656
  // CFF2 makes it optional when there's only one FD
1657
0
  if (acc.fdSelect != &Null (CFF2FDSelect))
1658
0
  {
1659
0
    c->push ();
1660
0
    if (likely (hb_serialize_cff_fdselect (c, plan.num_glyphs,
1661
0
                                          *(const FDSelect *)acc.fdSelect,
1662
0
                                          plan.orig_fdcount,
1663
0
                                          plan.subset_fdselect_format,
1664
0
                                          plan.subset_fdselect_size,
1665
0
                                          plan.subset_fdselect_ranges)))
1666
0
      plan.info.fd_select.link = c->pop_pack ();
1667
0
    else
1668
0
    {
1669
0
      c->pop_discard ();
1670
0
      return_trace (false);
1671
0
    }
1672
0
  }
1673
0
  else
1674
0
  {
1675
    // Create a range-based FDSelect3 mapping all glyphs to FD 0
1676
    // Format: format(1) + nRanges(2) + range(3) + sentinel(2) = 8 bytes
1677
0
    c->push ();
1678
1679
    // Format byte
1680
0
    HBUINT8 format;
1681
0
    format = 3;
1682
0
    if (unlikely (!c->embed (format)))
1683
0
    {
1684
0
      c->pop_discard ();
1685
0
      return_trace (false);
1686
0
    }
1687
1688
    // nRanges
1689
0
    HBUINT16 nRanges;
1690
0
    nRanges = 1;
1691
0
    if (unlikely (!c->embed (nRanges)))
1692
0
    {
1693
0
      c->pop_discard ();
1694
0
      return_trace (false);
1695
0
    }
1696
1697
    // Single range: {first: 0, fd: 0}
1698
0
    FDSelect3_Range range;
1699
0
    range.first = 0;
1700
0
    range.fd = 0;
1701
0
    if (unlikely (!c->embed (range)))
1702
0
    {
1703
0
      c->pop_discard ();
1704
0
      return_trace (false);
1705
0
    }
1706
1707
    // Sentinel (number of glyphs)
1708
0
    HBUINT16 sentinel;
1709
0
    sentinel = plan.num_glyphs;
1710
0
    if (unlikely (!c->embed (sentinel)))
1711
0
    {
1712
0
      c->pop_discard ();
1713
0
      return_trace (false);
1714
0
    }
1715
1716
0
    plan.info.fd_select.link = c->pop_pack ();
1717
0
  }
1718
1719
  // 5. Charset (CID charset for identity mapping)
1720
0
  objidx_t charset_link;
1721
0
  if (!_serialize_cff1_charset (c, plan.num_glyphs, charset_link))
1722
0
    return_trace (false);
1723
1724
  // 6. Global Subrs
1725
0
  {
1726
0
    auto *dest = c->push<CFF1Subrs> ();
1727
0
    if (likely (dest->serialize (c, plan.subset_globalsubrs)))
1728
0
      c->pop_pack (false);
1729
0
    else
1730
0
    {
1731
0
      c->pop_discard ();
1732
0
      return_trace (false);
1733
0
    }
1734
0
  }
1735
1736
  // 7. String INDEX - Add "Adobe" and "Identity" for ROS operator
1737
0
  {
1738
0
    const char *adobe_str = "Adobe";
1739
0
    const char *identity_str = "Identity";
1740
0
    unsigned adobe_len = 5;  // strlen("Adobe")
1741
0
    unsigned identity_len = 8;  // strlen("Identity")
1742
1743
    // Build strings array
1744
0
    hb_vector_t<hb_ubytes_t> strings;
1745
0
    strings.alloc (2);
1746
0
    strings.push (hb_ubytes_t ((const unsigned char *) adobe_str, adobe_len));
1747
0
    strings.push (hb_ubytes_t ((const unsigned char *) identity_str, identity_len));
1748
1749
    // Serialize as CFF INDEX
1750
0
    auto *dest = c->push<CFF1Index> ();
1751
0
    if (likely (dest->serialize (c, strings)))
1752
0
      c->pop_pack (false);
1753
0
    else
1754
0
    {
1755
0
      c->pop_discard ();
1756
0
      return_trace (false);
1757
0
    }
1758
0
  }
1759
1760
  // 8. CFF Header
1761
0
  OT::cff1 *cff = c->allocate_min<OT::cff1> ();
1762
0
  if (unlikely (!cff)) return_trace (false);
1763
1764
  /* header */
1765
0
  cff->version.major = 0x01;
1766
0
  cff->version.minor = 0x00;
1767
0
  cff->nameIndex = cff->min_size;
1768
0
  cff->offSize = 4; /* unused? */
1769
1770
  // 9. Name INDEX (single entry)
1771
0
  {
1772
0
    unsigned name_len = strlen (CFF1_DEFAULT_FONT_NAME);
1773
1774
0
    CFF1Index *idx = c->start_embed<CFF1Index> ();
1775
0
    if (unlikely (!idx)) return_trace (false);
1776
1777
0
    if (unlikely (!idx->serialize_header (c, hb_iter (&name_len, 1), name_len)))
1778
0
      return_trace (false);
1779
1780
0
    if (unlikely (!c->embed (CFF1_DEFAULT_FONT_NAME, name_len)))
1781
0
      return_trace (false);
1782
0
  }
1783
1784
  // 10. Top DICT INDEX
1785
0
  {
1786
    // Serialize the Top DICT data first
1787
0
    c->push<TopDict> ();
1788
0
    cff1_from_cff2_top_dict_op_serializer_t topSzr;
1789
1790
    // Serialize ROS first
1791
0
    if (unlikely (!topSzr.serialize_ros (c)))
1792
0
    {
1793
0
      c->pop_discard ();
1794
0
      return_trace (false);
1795
0
    }
1796
1797
    // Serialize FontBBox from head table
1798
0
    {
1799
0
      str_buff_t bbox_buff;
1800
0
      str_encoder_t encoder (bbox_buff);
1801
1802
0
      encoder.encode_int (plan.head_maxp_info.xMin);
1803
0
      encoder.encode_int (plan.head_maxp_info.yMin);
1804
0
      encoder.encode_int (plan.head_maxp_info.xMax);
1805
0
      encoder.encode_int (plan.head_maxp_info.yMax);
1806
0
      encoder.encode_op (OpCode_FontBBox);
1807
1808
0
      if (encoder.in_error () || !c->embed (bbox_buff.as_bytes ().arrayZ, bbox_buff.length))
1809
0
      {
1810
0
        c->pop_discard ();
1811
0
        return_trace (false);
1812
0
      }
1813
0
    }
1814
1815
    // Serialize charset operator
1816
0
    if (charset_link && unlikely (!FontDict::serialize_link4_op (c, OpCode_charset, charset_link, whence_t::Absolute)))
1817
0
    {
1818
0
      c->pop_discard ();
1819
0
      return_trace (false);
1820
0
    }
1821
1822
    // Serialize FDSelect operator (required for CID-keyed CFF1 fonts)
1823
0
    if (plan.info.fd_select.link && unlikely (!FontDict::serialize_link4_op (c, OpCode_FDSelect, plan.info.fd_select.link, whence_t::Absolute)))
1824
0
    {
1825
0
      c->pop_discard ();
1826
0
      return_trace (false);
1827
0
    }
1828
1829
    // Serialize FDArray operator (required for CID-keyed CFF1 fonts)
1830
0
    if (plan.info.fd_array_link && unlikely (!FontDict::serialize_link4_op (c, OpCode_FDArray, plan.info.fd_array_link, whence_t::Absolute)))
1831
0
    {
1832
0
      c->pop_discard ();
1833
0
      return_trace (false);
1834
0
    }
1835
1836
    // Serialize other operators from CFF2 TopDict
1837
0
    for (const auto &opstr : cff2_topDict.values)
1838
0
    {
1839
0
      if (unlikely (!topSzr.serialize (c, opstr, plan.info)))
1840
0
      {
1841
0
        c->pop_discard ();
1842
0
        return_trace (false);
1843
0
      }
1844
0
    }
1845
1846
0
    unsigned top_size = c->length ();
1847
0
    c->pop_pack (false);
1848
1849
    // Serialize INDEX header
1850
0
    auto *dest = c->start_embed<CFF1Index> ();
1851
0
    if (unlikely (!dest->serialize_header (c, hb_iter (&top_size, 1), top_size)))
1852
0
      return_trace (false);
1853
0
  }
1854
1855
0
  return_trace (true);
1856
0
}
1857
1858
} /* namespace CFF */
1859
1860
static bool _serialize_cff2_charstrings (hb_serialize_context_t *c,
1861
           cff2_subset_plan &plan,
1862
           const OT::cff2::accelerator_subset_t  &acc)
1863
0
{
1864
0
  c->push ();
1865
1866
0
  unsigned data_size = 0;
1867
0
  unsigned total_size = CFF2CharStrings::total_size (plan.subset_charstrings, &data_size, plan.min_charstrings_off_size);
1868
0
  if (unlikely (!c->start_zerocopy (total_size)))
1869
0
    return false;
1870
1871
0
  auto *cs = c->start_embed<CFF2CharStrings> ();
1872
0
  if (unlikely (!cs->serialize (c, plan.subset_charstrings, &data_size, plan.min_charstrings_off_size)))
1873
0
  {
1874
0
    c->pop_discard ();
1875
0
    return false;
1876
0
  }
1877
1878
0
  plan.info.char_strings_link = c->pop_pack (false);
1879
0
  return true;
1880
0
}
1881
1882
bool
1883
OT::cff2::accelerator_subset_t::serialize (hb_serialize_context_t *c,
1884
             struct cff2_subset_plan &plan,
1885
             hb_array_t<int> normalized_coords) const
1886
0
{
1887
  /* push charstrings onto the object stack first which will ensure it packs as the last
1888
     object in the table. Keeping the chastrings last satisfies the requirements for patching
1889
     via IFTB. If this ordering needs to be changed in the future, charstrings should be left
1890
     at the end whenever HB_SUBSET_FLAGS_ITFB_REQUIREMENTS is enabled. */
1891
0
  if (!_serialize_cff2_charstrings(c, plan, *this))
1892
0
    return false;
1893
1894
  /* private dicts & local subrs */
1895
0
  hb_vector_t<table_info_t>  private_dict_infos;
1896
0
  if (unlikely (!private_dict_infos.resize (plan.subset_fdcount))) return false;
1897
1898
0
  for (int i = (int)privateDicts.length; --i >= 0 ;)
1899
0
  {
1900
0
    if (plan.fdmap.has (i))
1901
0
    {
1902
0
      objidx_t  subrs_link = 0;
1903
1904
0
      if (plan.subset_localsubrs[i].length > 0)
1905
0
      {
1906
0
  auto *dest = c->push <CFF2Subrs> ();
1907
0
  if (likely (dest->serialize (c, plan.subset_localsubrs[i])))
1908
0
    subrs_link = c->pop_pack (false);
1909
0
  else
1910
0
  {
1911
0
    c->pop_discard ();
1912
0
    return false;
1913
0
  }
1914
0
      }
1915
0
      auto *pd = c->push<PrivateDict> ();
1916
0
      cff2_private_dict_op_serializer_t privSzr (plan.desubroutinize, plan.drop_hints, plan.pinned,
1917
0
             varStore, normalized_coords,
1918
0
             plan.partial_instancing ? &plan.instancing : nullptr);
1919
0
      if (likely (pd->serialize (c, privateDicts[i], privSzr, subrs_link)))
1920
0
      {
1921
0
  unsigned fd = plan.fdmap[i];
1922
0
  private_dict_infos[fd].size = c->length ();
1923
0
  private_dict_infos[fd].link = c->pop_pack ();
1924
0
      }
1925
0
      else
1926
0
      {
1927
0
  c->pop_discard ();
1928
0
  return false;
1929
0
      }
1930
0
    }
1931
0
  }
1932
1933
  /* FDSelect */
1934
0
  if (fdSelect != &Null (CFF2FDSelect))
1935
0
  {
1936
0
    c->push ();
1937
0
    if (likely (hb_serialize_cff_fdselect (c, plan.num_glyphs, *(const FDSelect *)fdSelect,
1938
0
             plan.orig_fdcount,
1939
0
             plan.subset_fdselect_format, plan.subset_fdselect_size,
1940
0
             plan.subset_fdselect_ranges)))
1941
0
      plan.info.fd_select.link = c->pop_pack ();
1942
0
    else
1943
0
    {
1944
0
      c->pop_discard ();
1945
0
      return false;
1946
0
    }
1947
0
  }
1948
1949
  /* FDArray (FD Index) */
1950
0
  {
1951
0
    auto *fda = c->push<CFF2FDArray> ();
1952
0
    cff_font_dict_op_serializer_t fontSzr;
1953
0
    auto it =
1954
0
    + hb_zip (+ hb_iter (fontDicts)
1955
0
        | hb_filter ([&] (const cff2_font_dict_values_t &_)
1956
0
    { return plan.fdmap.has (&_ - &fontDicts[0]); }),
1957
0
        hb_iter (private_dict_infos))
1958
0
    ;
1959
0
    if (unlikely (!fda->serialize (c, it, fontSzr)))
1960
0
    {
1961
0
      c->pop_discard ();
1962
0
      return false;
1963
0
    }
1964
0
    plan.info.fd_array_link = c->pop_pack (false);
1965
0
  }
1966
1967
  /* variation store */
1968
0
  if (varStore != &Null (CFF2ItemVariationStore) &&
1969
0
      !plan.pinned)
1970
0
  {
1971
0
    if (plan.partial_instancing)
1972
0
    {
1973
0
      if (plan.instancing.has_variations ())
1974
0
      {
1975
0
  c->push ();
1976
0
  if (unlikely (!plan.instancing.serialize_var_store (c, *plan.axis_tags)))
1977
0
  {
1978
0
    c->pop_discard ();
1979
0
    return false;
1980
0
  }
1981
0
  plan.info.var_store_link = c->pop_pack (false);
1982
0
      }
1983
      /* else: the store emptied; the vstore op gets dropped along with it. */
1984
0
    }
1985
0
    else
1986
0
    {
1987
0
      auto *dest = c->push<CFF2ItemVariationStore> ();
1988
0
      if (unlikely (!dest->serialize (c, varStore)))
1989
0
      {
1990
0
  c->pop_discard ();
1991
0
  return false;
1992
0
      }
1993
0
      plan.info.var_store_link = c->pop_pack (false);
1994
0
    }
1995
0
  }
1996
1997
0
  OT::cff2 *cff2 = c->allocate_min<OT::cff2> ();
1998
0
  if (unlikely (!cff2)) return false;
1999
2000
  /* header */
2001
0
  cff2->version.major = 0x02;
2002
0
  cff2->version.minor = 0x00;
2003
0
  cff2->topDict = OT::cff2::static_size;
2004
2005
  /* top dict */
2006
0
  {
2007
0
    TopDict &dict = cff2 + cff2->topDict;
2008
0
    cff2_top_dict_op_serializer_t topSzr;
2009
0
    if (unlikely (!dict.serialize (c, topDict, topSzr, plan.info))) return false;
2010
0
    cff2->topDictSize = c->head - (const char *)&dict;
2011
0
  }
2012
2013
  /* global subrs */
2014
0
  {
2015
0
    auto *dest = c->start_embed <CFF2Subrs> ();
2016
0
    return dest->serialize (c, plan.subset_globalsubrs);
2017
0
  }
2018
0
}
2019
2020
bool
2021
OT::cff2::accelerator_subset_t::subset (hb_subset_context_t *c) const
2022
0
{
2023
0
  cff2_subset_plan cff2_plan;
2024
2025
0
  if (unlikely (!cff2_plan.create (*this, c->plan)))
2026
0
  {
2027
0
    c->serializer->err (HB_SERIALIZE_ERROR_OTHER);
2028
0
    return false;
2029
0
  }
2030
2031
  // If instantiating (pinned) and downgrade flag is set, convert to CFF1
2032
0
  if (cff2_plan.pinned && (c->plan->flags & HB_SUBSET_FLAGS_DOWNGRADE_CFF2))
2033
0
  {
2034
    // Serialize CFF1 to the subsetter's serializer
2035
    // If we run out of room, returning true will cause subsetter to retry with larger buffer
2036
0
    bool result = CFF::serialize_cff2_to_cff1 (c->serializer, cff2_plan, topDict, *this);
2037
2038
0
    if (c->serializer->ran_out_of_room ())
2039
0
      return true; // Subsetter will retry with larger buffer
2040
2041
0
    if (result && !c->serializer->in_error ())
2042
0
    {
2043
      // Success - end serialization to resolve links
2044
0
      c->serializer->end_serialize ();
2045
2046
      // Copy the serialized CFF1 data and add as CFF table
2047
0
      hb_blob_t *cff_blob = c->serializer->copy_blob ();
2048
0
      if (cff_blob)
2049
0
      {
2050
0
        c->plan->add_table (HB_TAG('C','F','F',' '), cff_blob);
2051
0
        hb_blob_destroy (cff_blob);
2052
2053
        // Return false to signal CFF2 table is not needed
2054
0
        return false;
2055
0
      }
2056
0
    }
2057
2058
    // Conversion failed - don't fall back, fail hard for debugging
2059
0
    return false;
2060
0
  }
2061
2062
0
  return serialize (c->serializer, cff2_plan,
2063
0
        c->plan->normalized_coords.as_array ());
2064
0
}
2065
2066
#endif