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-plan-var.cc
Line
Count
Source
1
/*
2
 * Copyright © 2023  Google, 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
 * Google Author(s): Garret Rieger, Qunxin Liu, Roderick Sheeter
25
 */
26
27
 #include "hb-ot-layout-common.hh"
28
#include "hb-subset-plan.hh"
29
30
 #include "hb-ot-var-common.hh"
31
 #include "hb-ot-layout-base-table.hh"
32
 #include "hb-ot-glyf-table.hh"
33
 #include "hb-ot-var-fvar-table.hh"
34
 #include "hb-ot-var-avar-table.hh"
35
 #include "hb-ot-cff2-table.hh"
36
37
 #ifndef HB_NO_VAR
38
39
 void
40
 generate_varstore_inner_maps (const hb_set_t& varidx_set,
41
                               unsigned subtable_count,
42
                               hb_vector_t<hb_inc_bimap_t> &inner_maps /* OUT */)
43
0
 {
44
0
   if (varidx_set.is_empty () || subtable_count == 0) return;
45
46
0
   if (unlikely (!inner_maps.resize (subtable_count))) return;
47
0
   for (unsigned idx : varidx_set)
48
0
   {
49
0
     uint16_t major = idx >> 16;
50
0
     uint16_t minor = idx & 0xFFFF;
51
52
0
     if (major >= subtable_count)
53
0
       continue;
54
0
     inner_maps[major].add (minor);
55
0
   }
56
0
 }
57
58
 /* Evaluate a variation-region tent at v, mirroring VarRegionAxis::evaluate
59
  * (invalid regions evaluate as constant 1). */
60
 static double
61
 _tent_eval (const Triple &tent, double v)
62
0
 {
63
0
   double start = (double) tent.minimum;
64
0
   double peak = (double) tent.middle;
65
0
   double end = (double) tent.maximum;
66
0
   if (unlikely (start > peak || peak > end)) return 1.0;
67
0
   if (unlikely (start < 0.0 && end > 0.0 && peak != 0.0)) return 1.0;
68
0
   if (peak == 0.0 || v == peak) return 1.0;
69
0
   if (v <= start || end <= v) return 0.0;
70
0
   if (v < peak) return (v - start) / (peak - start);
71
0
   return (end - v) / (end - peak);
72
0
 }
73
74
 /* Range of a tent's scalar over an input interval [lo, hi]. The tent is
75
  * unimodal, so the extremes are at the interval endpoints, plus the peak
76
  * when it falls inside. */
77
 static void
78
 _tent_value_range (const Triple &tent, double lo, double hi,
79
                    double *tmin, double *tmax)
80
0
 {
81
0
   double a = _tent_eval (tent, lo);
82
0
   double b = _tent_eval (tent, hi);
83
0
   *tmin = hb_min (a, b);
84
0
   *tmax = hb_max (a, b);
85
0
   if (lo <= (double) tent.middle && (double) tent.middle <= hi)
86
0
     *tmax = hb_max (*tmax, 1.0);
87
0
 }
88
89
 /* Compute conservative reachable old-space final-coord ranges per axis for
90
  * avar2 partial instancing. With offset compensation, the instanced font's
91
  * final coordinates equal the original font's over the retained user box,
92
  * so the reachable range of axis i's final coordinate is the range of
93
  *   final_i = intermediate_i + delta_i(intermediates)
94
  * over the box of retained old-intermediate ranges: restricted axes span
95
  * their retained [a, b], free public axes [-1, +1], pinned axes sit at
96
  * their d_i, and hidden (private) axes at 0. Interval arithmetic over the
97
  * avar2 VarStore regions bounds delta_i. Mirrors fontTools'
98
  * _computeReachableRangesForAvar2, but computed from the original store at
99
  * plan time (the same quantity fontTools bounds via getExtremes on the
100
  * instanced store). Only constraining ranges (narrower than [-1, +1]) are
101
  * recorded. */
102
 static bool
103
 _compute_avar2_reachable_ranges (hb_subset_plan_t *plan,
104
                                  hb_array_t<const OT::AxisRecord> axes,
105
                                  const OT::avar *avar_table,
106
                                  bool detect_self_contained)
107
0
 {
108
0
   const OT::ItemVariationStore *var_store;
109
0
   const OT::DeltaSetIndexMap *varidx_map;
110
0
   if (!avar_table->get_v2_store_and_map (&var_store, &varidx_map))
111
0
     return false;
112
113
   /* Old-intermediate input box per axis. */
114
0
   hb_hashmap_t<hb_tag_t, hb_pair_t<double, double>> box;
115
0
   for (const auto &axis : axes)
116
0
   {
117
0
     hb_tag_t tag = axis.get_axis_tag ();
118
0
     double lo = -1.0, hi = +1.0;
119
0
     Triple *old_int;
120
0
     if (plan->user_axes_location.has (tag))
121
0
     {
122
0
       if (!plan->old_intermediates.has (tag, &old_int)) return false;
123
0
       lo = (double) old_int->minimum;
124
0
       hi = (double) old_int->maximum;
125
0
     }
126
0
     else if (axis.is_hidden ())
127
0
       lo = hi = 0.0; /* private axis: intermediate is always 0 */
128
0
     if (!box.set (tag, hb_pair (lo, hi))) return false;
129
0
   }
130
131
0
   hb_vector_t<hb_hashmap_t<hb_tag_t, Triple>> regions;
132
0
   if (!var_store->get_region_list ().get_var_regions (plan->axes_old_index_tag_map, regions))
133
0
     return false;
134
135
0
   hb_hashmap_t<hb_tag_t, unsigned> grid_pos;
136
0
   hb_vector_t<hb_vector_t<double>> grid_points;
137
0
   for (unsigned i = 0; i < axes.length; i++)
138
0
   {
139
0
     grid_pos.reset ();
140
0
     grid_points.reset ();
141
142
0
     hb_tag_t tag = axes[i].get_axis_tag ();
143
0
     hb_pair_t<double, double> *identity;
144
0
     if (!box.has (tag, &identity)) return false;
145
146
     /* Collect the row's active regions (non-zero delta). A varidx (or the
147
      * implicit identity mapping) that doesn't resolve to a real store row
148
      * behaves as "no variation" at runtime, so it contributes no regions;
149
      * this also covers a NULL VarStore. */
150
0
     hb_vector_t<hb_pair_t<int, unsigned>> active; /* (delta, region index) */
151
0
     uint32_t varidx = varidx_map->map (i);
152
     /* Runtime treats out-of-range delta-set indices as zero. */
153
0
     if (varidx != HB_OT_LAYOUT_NO_VARIATIONS_INDEX &&
154
0
   !var_store->has_delta_set (varidx))
155
0
       varidx = HB_OT_LAYOUT_NO_VARIATIONS_INDEX;
156
0
     if (varidx != HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
157
0
     {
158
0
       unsigned outer = varidx >> 16;
159
0
       unsigned inner = varidx & 0xFFFF;
160
0
       const OT::VarData &vd = var_store->get_sub_table (outer);
161
0
       const OT::HBUINT8 *delta_bytes = vd.get_delta_bytes ();
162
0
       unsigned row_size = vd.get_row_size ();
163
0
       unsigned region_count = vd.get_region_index_count ();
164
0
       for (unsigned r = 0; r < region_count; r++)
165
0
       {
166
0
   int delta = vd.get_item_delta_fast (inner, r, delta_bytes, row_size);
167
0
   if (!delta) continue;
168
0
   unsigned region_idx = vd.get_region_index (r);
169
0
   if (region_idx >= regions.length) return false;
170
0
   active.push (hb_pair (delta, region_idx));
171
0
       }
172
0
       if (active.in_error ()) return false;
173
0
     }
174
175
     /* The delta is piecewise multilinear in the axis coordinates (each
176
      * region scalar is a product of independent per-axis tents), so over
177
      * the box its exact extremes are attained at vertices of the grid of
178
      * per-axis tent breakpoints. Enumerate that grid when it is small
179
      * (real fonts); otherwise fall back to per-region interval
180
      * arithmetic, which ignores correlations between regions and is
181
      * looser but still conservative. Mirrors fontTools'
182
      * VarStore.getExtremes. */
183
0
     constexpr unsigned MAX_GRID = 1u << 14;
184
0
     bool exact = true;
185
0
     double dmin = 0.0, dmax = 0.0; /* delta extremes, F2Dot14 units */
186
0
     double vmin = 0.0, vmax = 0.0; /* identity + delta extremes, coord units */
187
188
     /* Grid axes: the target axis (identity term), plus every axis a valid
189
      * tent of an active region references. */
190
0
     auto add_grid_axis = [&] (hb_tag_t t) -> int
191
0
     {
192
0
       unsigned *pos;
193
0
       if (grid_pos.has (t, &pos)) return (int) *pos;
194
0
       unsigned new_pos = grid_pos.get_population ();
195
0
       hb_pair_t<double, double> *input;
196
0
       double blo = -1.0, bhi = +1.0;
197
0
       if (box.has (t, &input)) { blo = input->first; bhi = input->second; }
198
0
       grid_points.push (hb_vector_t<double> ());
199
0
       if (grid_points.in_error ()) return -1;
200
0
       auto &points = grid_points[new_pos];
201
0
       points.push (blo);
202
0
       points.push (bhi);
203
0
       if (points.in_error () || !grid_pos.set (t, new_pos)) return -1;
204
0
       return (int) new_pos;
205
0
     };
206
207
0
     if (add_grid_axis (tag) < 0) return false;
208
     /* Per active region: (grid axis position, tent) for its valid tents. */
209
0
     hb_vector_t<hb_vector_t<hb_pair_t<unsigned, Triple>>> region_tents;
210
0
     for (const auto &ar : active)
211
0
     {
212
0
       region_tents.push (hb_vector_t<hb_pair_t<unsigned, Triple>> ());
213
0
       if (region_tents.in_error ()) return false;
214
0
       auto &tents = region_tents[region_tents.length - 1];
215
0
       for (const auto &_ : regions[ar.second])
216
0
       {
217
0
   const Triple &tent = _.second;
218
   /* Tents the runtime ignores (constant scalar 1) add no breakpoints. */
219
0
   if (tent.middle == 0.f ||
220
0
       tent.minimum > tent.middle || tent.middle > tent.maximum ||
221
0
       (tent.minimum < 0.f && tent.maximum > 0.f))
222
0
     continue;
223
0
   int pos = add_grid_axis (_.first);
224
0
   if (pos < 0) return false;
225
0
   auto &points = grid_points[pos];
226
0
   double blo = points[0], bhi = points[1];
227
0
   points.push (hb_clamp ((double) tent.minimum, blo, bhi));
228
0
   points.push (hb_clamp ((double) tent.middle, blo, bhi));
229
0
   points.push (hb_clamp ((double) tent.maximum, blo, bhi));
230
0
   tents.push (hb_pair ((unsigned) pos, tent));
231
0
   if (points.in_error () || tents.in_error ()) return false;
232
0
       }
233
0
     }
234
235
0
     unsigned grid_size = 1;
236
0
     for (auto &points : grid_points)
237
0
     {
238
0
       points.qsort ([] (const double &a, const double &b) -> int
239
0
         { return a < b ? -1 : a > b ? +1 : 0; });
240
0
       unsigned n = 0;
241
0
       for (unsigned j = 0; j < points.length; j++)
242
0
   if (!n || points.arrayZ[j] != points.arrayZ[n - 1])
243
0
     points.arrayZ[n++] = points.arrayZ[j];
244
0
       points.shrink (n);
245
0
       if (grid_size > MAX_GRID / points.length) { exact = false; break; }
246
0
       grid_size *= points.length;
247
0
     }
248
249
0
     if (exact)
250
0
     {
251
0
       unsigned target_pos = 0; /* the target axis was added first */
252
0
       hb_vector_t<unsigned> odometer;
253
0
       if (!odometer.resize (grid_pos.get_population ())) return false;
254
0
       bool first = true;
255
0
       while (true)
256
0
       {
257
0
   double delta_sum = 0.0;
258
0
   for (unsigned r = 0; r < active.length; r++)
259
0
   {
260
0
     double scalar = 1.0;
261
0
     for (const auto &pt : region_tents[r])
262
0
     {
263
0
       double v = grid_points[pt.first][odometer[pt.first]];
264
0
       scalar *= _tent_eval (pt.second, v);
265
0
       if (scalar == 0.0) break;
266
0
     }
267
0
     delta_sum += scalar * active[r].first;
268
0
   }
269
0
   double coord = grid_points[target_pos][odometer[target_pos]];
270
0
   double value = coord + delta_sum / 16384.0;
271
0
   if (first)
272
0
   {
273
0
     dmin = dmax = delta_sum;
274
0
     vmin = vmax = value;
275
0
     first = false;
276
0
   }
277
0
   else
278
0
   {
279
0
     dmin = hb_min (dmin, delta_sum); dmax = hb_max (dmax, delta_sum);
280
0
     vmin = hb_min (vmin, value);     vmax = hb_max (vmax, value);
281
0
   }
282
0
   unsigned k = 0;
283
0
   for (; k < odometer.length; k++)
284
0
   {
285
0
     if (++odometer[k] < grid_points[k].length) break;
286
0
     odometer[k] = 0;
287
0
   }
288
0
   if (k == odometer.length) break;
289
0
       }
290
0
     }
291
0
     else
292
0
     {
293
0
       for (const auto &ar : active)
294
0
       {
295
0
   int delta = ar.first;
296
0
   double smin = 1.0, smax = 1.0;
297
0
   for (const auto &_ : regions[ar.second])
298
0
   {
299
0
     hb_pair_t<double, double> *input;
300
0
     double blo = -1.0, bhi = +1.0;
301
0
     if (box.has (_.first, &input)) { blo = input->first; bhi = input->second; }
302
0
     double tmin, tmax;
303
0
     _tent_value_range (_.second, blo, bhi, &tmin, &tmax);
304
0
     smin *= tmin;
305
0
     smax *= tmax;
306
0
     if (smax == 0.0) break;
307
0
   }
308
0
   if (delta > 0) { dmin += smin * delta; dmax += smax * delta; }
309
0
   else           { dmin += smax * delta; dmax += smin * delta; }
310
0
       }
311
0
       vmin = identity->first + dmin / 16384.0;
312
0
       vmax = identity->second + dmax / 16384.0;
313
0
     }
314
     /* A pinned axis whose delta is constant over the box is self-contained:
315
      * its final coordinate is a constant. It can be removed from fvar/avar
316
      * and its contribution baked into the variation tables like an ordinary
317
      * pin at that coordinate (in old final space). Round the delta alone
318
      * and add it to the exact quantized intermediate, matching the runtime
319
      * (map_coords_2_14) and fontTools. */
320
0
     Triple *user;
321
0
     if (detect_self_contained && dmin == dmax &&
322
0
   plan->user_axes_location.has (tag, &user) && user->is_point ())
323
0
     {
324
0
       int v_int = (int) roundf ((float) identity->first * 16384.f) +
325
0
       (int) roundf ((float) dmin);
326
0
       v_int = hb_clamp (v_int, -(1 << 14), +(1 << 14));
327
0
       if (!plan->avar2_self_contained.set (tag, v_int / 16384.0)) return false;
328
0
       continue; /* axis is dropped; no region can reference it afterwards */
329
0
     }
330
331
     /* Pad by one F2Dot14 unit against rounding differences with the
332
      * runtime's fixed-point evaluation. */
333
0
     double lo = hb_clamp (vmin - 1.0 / 16384.0, -1.0, +1.0);
334
0
     double hi = hb_clamp (vmax + 1.0 / 16384.0, -1.0, +1.0);
335
0
     lo = floor (lo * 16384.0) / 16384.0;
336
0
     hi = ceil (hi * 16384.0) / 16384.0;
337
0
     if (lo <= -1.0 && hi >= +1.0)
338
0
       continue; /* not constraining */
339
0
     if (!plan->avar2_reachable_ranges.set (tag, Triple (lo, hb_clamp (0.0, lo, hi), hi)))
340
0
       return false;
341
0
   }
342
0
   return true;
343
0
 }
344
345
#ifndef HB_NO_OT_FONT_CFF
346
 static inline hb_font_t*
347
 _get_hb_font_with_variations (const hb_subset_plan_t *plan)
348
0
 {
349
0
   hb_font_t *font = hb_font_create (plan->source);
350
351
0
   if (plan->has_avar2)
352
0
   {
353
     /* Under avar2, instancing applies only to the self-contained pins,
354
      * whose constant final coordinates the plan holds in normalized_coords.
355
      * Setting user-space variations would run the full avar2 mapping and
356
      * bake contributions that remain live in the instance's variations. */
357
0
     hb_font_set_var_coords_normalized (font, plan->normalized_coords.arrayZ,
358
0
                                        plan->normalized_coords.length);
359
0
     return font;
360
0
   }
361
362
0
   hb_vector_t<hb_variation_t> vars;
363
0
   if (!vars.alloc (plan->user_axes_location.get_population ())) {
364
0
     hb_font_destroy (font);
365
0
     return nullptr;
366
0
   }
367
368
0
   for (auto _ : plan->user_axes_location)
369
0
   {
370
0
     hb_variation_t var;
371
0
     var.tag = _.first;
372
0
     var.value = _.second.middle;
373
0
     vars.push (var);
374
0
   }
375
376
0
   hb_font_set_variations (font, vars.arrayZ, plan->user_axes_location.get_population ());
377
0
   return font;
378
0
 }
379
#endif
380
381
 template<typename ItemVarStore>
382
 void
383
 remap_variation_indices (const ItemVarStore &var_store,
384
                          const hb_set_t &variation_indices,
385
                          const hb_vector_t<int>& normalized_coords,
386
                          bool calculate_delta, /* not pinned at default */
387
                          bool no_variations, /* all axes pinned */
388
                          hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> &variation_idx_delta_map /* OUT */)
389
0
 {
390
0
   if (&var_store == &Null (OT::ItemVariationStore)) return;
391
0
   unsigned subtable_count = var_store.get_sub_table_count ();
392
0
   auto *store_cache = var_store.create_cache ();
393
394
0
   unsigned new_major = 0, new_minor = 0;
395
0
   unsigned last_major = (variation_indices.get_min ()) >> 16;
396
0
   for (unsigned idx : variation_indices)
397
0
   {
398
0
     int delta = 0;
399
0
     if (calculate_delta)
400
0
       delta = roundf (var_store.get_delta (idx, normalized_coords.arrayZ,
401
0
                                            normalized_coords.length, store_cache));
402
403
0
     if (no_variations)
404
0
     {
405
0
       variation_idx_delta_map.set (idx, hb_pair_t<unsigned, int> (HB_OT_LAYOUT_NO_VARIATIONS_INDEX, delta));
406
0
       continue;
407
0
     }
408
409
0
     uint16_t major = idx >> 16;
410
0
     if (major >= subtable_count) break;
411
0
     if (major != last_major)
412
0
     {
413
0
       new_minor = 0;
414
0
       ++new_major;
415
0
     }
416
417
0
     unsigned new_idx = (new_major << 16) + new_minor;
418
0
     variation_idx_delta_map.set (idx, hb_pair_t<unsigned, int> (new_idx, delta));
419
0
     ++new_minor;
420
0
     last_major = major;
421
0
   }
422
0
   var_store.destroy_cache (store_cache);
423
0
 }
424
425
 template
426
 void
427
 remap_variation_indices<OT::ItemVariationStore> (const OT::ItemVariationStore &var_store,
428
                          const hb_set_t &variation_indices,
429
                          const hb_vector_t<int>& normalized_coords,
430
                          bool calculate_delta, /* not pinned at default */
431
                          bool no_variations, /* all axes pinned */
432
                          hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> &variation_idx_delta_map /* OUT */);
433
434
 #ifndef HB_NO_BASE
435
 void
436
 collect_base_variation_indices (hb_subset_plan_t* plan)
437
0
 {
438
0
   hb_blob_ptr_t<OT::BASE> base = plan->source_table<OT::BASE> ();
439
0
   if (!base->has_var_store ())
440
0
   {
441
0
     base.destroy ();
442
0
     return;
443
0
   }
444
445
0
   hb_set_t varidx_set;
446
0
   base->collect_variation_indices (plan, varidx_set);
447
0
   const OT::ItemVariationStore &var_store = base->get_var_store ();
448
0
   unsigned subtable_count = var_store.get_sub_table_count ();
449
450
451
0
   remap_variation_indices (var_store, varidx_set,
452
0
                             plan->normalized_coords,
453
0
                             !plan->pinned_at_default,
454
0
                             plan->all_axes_pinned,
455
0
                             plan->base_variation_idx_map);
456
0
   generate_varstore_inner_maps (varidx_set, subtable_count, plan->base_varstore_inner_maps);
457
458
0
   base.destroy ();
459
0
 }
460
461
 #endif
462
463
bool
464
normalize_axes_location (hb_face_t *face, hb_subset_plan_t *plan)
465
20.3k
{
466
20.3k
  if (plan->user_axes_location.is_empty ())
467
20.3k
    return true;
468
469
0
  hb_array_t<const OT::AxisRecord> axes = face->table.fvar->get_axes ();
470
0
  for (const auto &axis : axes)
471
0
  {
472
0
    hb_tag_t tag = axis.get_axis_tag ();
473
0
    const Triple *range;
474
0
    float minimum, middle, maximum;
475
0
    axis.get_coordinates (minimum, middle, maximum);
476
0
    if (plan->user_axes_location.has (tag, &range) && !range->is_point () &&
477
0
        range->minimum == minimum &&
478
0
        range->middle == middle &&
479
0
        range->maximum == maximum)
480
0
      plan->user_axes_location.del (tag);
481
0
  }
482
0
  if (plan->user_axes_location.is_empty ())
483
0
    return true;
484
485
0
  if (!plan->check_success (plan->normalized_coords.resize (axes.length)))
486
0
    return false;
487
488
0
  bool has_avar = face->table.avar->has_data ();
489
0
  hb_vector_t<float> normalized_mins;
490
0
  hb_vector_t<float> normalized_defaults;
491
0
  hb_vector_t<float> normalized_maxs;
492
0
  if (has_avar)
493
0
  {
494
0
    if (!plan->check_success (normalized_mins.resize (axes.length)) ||
495
0
        !plan->check_success (normalized_defaults.resize (axes.length)) ||
496
0
        !plan->check_success (normalized_maxs.resize (axes.length)))
497
0
      return false;
498
0
  }
499
500
0
  bool axis_not_pinned = false;
501
0
  unsigned new_axis_idx = 0;
502
0
  unsigned last_idx = 0;
503
0
  for (const auto& _ : + hb_enumerate (axes))
504
0
  {
505
0
    unsigned i = _.first;
506
0
    const OT::AxisRecord &axis = _.second;
507
0
    hb_tag_t axis_tag = axis.get_axis_tag ();
508
0
    plan->axes_old_index_tag_map.set (i, axis_tag);
509
510
0
    if (!plan->user_axes_location.has (axis_tag) ||
511
0
        !plan->user_axes_location.get (axis_tag).is_point ())
512
0
    {
513
0
      axis_not_pinned = true;
514
0
      plan->axes_index_map.set (i, new_axis_idx);
515
0
      plan->axis_tags.push (axis_tag);
516
0
      new_axis_idx++;
517
0
    }
518
519
0
    Triple *axis_range;
520
0
    if (plan->user_axes_location.has (axis_tag, &axis_range))
521
0
    {
522
0
      plan->axes_triple_distances.set (axis_tag, axis.get_triple_distances ());
523
524
0
      float normalized_min = axis.normalize_axis_value (axis_range->minimum);
525
0
      float normalized_default = axis.normalize_axis_value (axis_range->middle);
526
0
      float normalized_max = axis.normalize_axis_value (axis_range->maximum);
527
528
      // TODO(behdad): Spec says axis normalization should be done in 16.16;
529
      // We used to do it in 2.14, but that's not correct.  I fixed this in
530
      // the fvar/avar code, but keeping 2.14 here for now to keep tests
531
      // happy. We might need to adjust fonttools as well.
532
      // I'm only fairly confident in the above statement. Anyway,
533
      // we should look deeper into this, and also update fonttools if
534
      // needed.
535
536
      // Round to 2.14
537
0
      normalized_min = roundf (normalized_min * 16384.f) / 16384.f;
538
0
      normalized_default = roundf (normalized_default * 16384.f) / 16384.f;
539
0
      normalized_max = roundf (normalized_max * 16384.f) / 16384.f;
540
541
0
      if (has_avar)
542
0
      {
543
0
        normalized_mins[i] = normalized_min;
544
0
        normalized_defaults[i] = normalized_default;
545
0
        normalized_maxs[i] = normalized_max;
546
0
        last_idx = i;
547
0
      }
548
0
      else
549
0
      {
550
0
        plan->axes_location.set (axis_tag, Triple ((double) normalized_min,
551
0
                                                   (double) normalized_default,
552
0
                                                   (double) normalized_max));
553
0
        if (normalized_default == -0.f)
554
0
          normalized_default = 0.f; // Normalize -0 to 0
555
0
        if (normalized_default != 0.f)
556
0
          plan->pinned_at_default = false;
557
558
0
        plan->normalized_coords[i] = roundf (normalized_default * 16384.f);
559
0
      }
560
0
    }
561
0
  }
562
0
  plan->all_axes_pinned = !axis_not_pinned;
563
564
  // TODO: use avar map_coords_16_16() when normalization is changed to 16.16
565
  // in fonttools
566
0
  if (has_avar)
567
0
  {
568
0
    const OT::avar* avar_table = face->table.avar;
569
0
    unsigned coords_len = last_idx + 1;
570
571
0
    if (avar_table->has_v2_data () && !plan->all_axes_pinned)
572
0
    {
573
      /* avar2 partial instancing: use v1-only mapping to get intermediate-space
574
       * coords. These are used for IVS rebasing and offset compensation. */
575
0
      plan->has_avar2 = true;
576
577
0
      if (!plan->check_success (avar_table->map_coords_2_14 (normalized_mins.arrayZ, coords_len, true)) ||
578
0
          !plan->check_success (avar_table->map_coords_2_14 (normalized_defaults.arrayZ, coords_len, true)) ||
579
0
          !plan->check_success (avar_table->map_coords_2_14 (normalized_maxs.arrayZ, coords_len, true)))
580
0
        return false;
581
582
0
      for (const auto& _ : + hb_enumerate (axes))
583
0
      {
584
0
        unsigned i = _.first;
585
0
        hb_tag_t axis_tag = _.second.get_axis_tag ();
586
0
        if (plan->user_axes_location.has (axis_tag))
587
0
        {
588
          /* Store intermediate-space coords for offset compensation,
589
           * segment-map renormalization, and IVS rebasing. */
590
0
          plan->old_intermediates.set (axis_tag, Triple ((double) normalized_mins[i],
591
0
                                                         (double) normalized_defaults[i],
592
0
                                                         (double) normalized_maxs[i]));
593
0
        }
594
0
      }
595
596
      /* Reachable-range computation also detects self-contained pinned
597
       * axes: pinned axes whose final coordinate is constant. Those are
598
       * removed from fvar, so suppress the detection when the face has
599
       * tables that cannot follow: CFF2 has no partial-pin instancing path
600
       * (its 'pinned' path flattens ALL blends; lift this once
601
       * https://github.com/harfbuzz/harfbuzz/pull/4710 lands, porting
602
       * fontTools' instantiateCFF2 from
603
       * https://github.com/fonttools/fonttools/pull/3506). Such axes stay
604
       * in fvar as ordinary hidden pins. VARC remaps its axis indices and
605
       * rejects instancing of axes it references. */
606
0
      hb_blob_t *cff2_blob = hb_face_reference_table (face, HB_TAG ('C','F','F','2'));
607
0
      bool detect_self_contained = !hb_blob_get_length (cff2_blob);
608
0
      hb_blob_destroy (cff2_blob);
609
0
      if (!_compute_avar2_reachable_ranges (plan, axes, avar_table,
610
0
                                            detect_self_contained))
611
0
        return false;
612
613
0
#ifndef HB_NO_VAR_COMPOSITES
614
      /* VARC overrides can reach coordinates outside the font-level
615
       * ranges. Do not cull variation data using those ranges. */
616
0
      if (!plan->drop_tables.has (HB_TAG ('V','A','R','C')))
617
0
      {
618
0
        hb_blob_t *varc_blob = hb_face_reference_table (face, HB_TAG ('V','A','R','C'));
619
0
        if (hb_blob_get_length (varc_blob))
620
0
          plan->avar2_reachable_ranges.reset ();
621
0
        hb_blob_destroy (varc_blob);
622
0
      }
623
0
#endif
624
625
      /* Keep all axes in fvar (pinned ones as hidden), EXCEPT self-contained
626
       * pinned axes, which are removed entirely. */
627
0
      plan->axes_index_map.reset ();
628
0
      plan->axis_tags.reset ();
629
0
      unsigned retained_axis_idx = 0;
630
0
      for (const auto& _ : + hb_enumerate (axes))
631
0
      {
632
0
        unsigned i = _.first;
633
0
        hb_tag_t axis_tag = _.second.get_axis_tag ();
634
0
        if (plan->avar2_self_contained.has (axis_tag))
635
0
          continue;
636
0
        plan->axes_index_map.set (i, retained_axis_idx++);
637
0
        plan->axis_tags.push (axis_tag);
638
0
      }
639
      /* The other tables see ONLY the self-contained pins, as an ordinary
640
       * partial instancing in old final-coordinate space; the standard
641
       * instancing machinery bakes those axes' contributions in. The
642
       * remaining restriction is carried entirely by avar. */
643
0
      plan->axes_location.reset ();
644
0
      if (plan->avar2_self_contained.get_population ())
645
0
      {
646
0
        bool sc_pinned_at_default = true;
647
0
        for (auto _ : plan->avar2_self_contained)
648
0
        {
649
0
          plan->axes_location.set (_.first, Triple (_.second, _.second, _.second));
650
0
          if (_.second != 0.0)
651
0
            sc_pinned_at_default = false;
652
0
        }
653
0
        for (const auto& _ : + hb_enumerate (axes))
654
0
        {
655
0
          double *v;
656
0
          plan->normalized_coords[_.first] =
657
0
              plan->avar2_self_contained.has (_.second.get_axis_tag (), &v)
658
0
              ? (int) roundf ((float) (*v * 16384.0)) : 0;
659
0
        }
660
0
        plan->pinned_at_default = sc_pinned_at_default;
661
0
      }
662
0
      else
663
0
        plan->normalized_coords.shrink (0);
664
0
    }
665
0
    else
666
0
    {
667
      /* Standard avar v1 (or v1+v2 with all axes pinned) */
668
0
      if (!plan->check_success (avar_table->map_coords_2_14 (normalized_mins.arrayZ, coords_len)) ||
669
0
          !plan->check_success (avar_table->map_coords_2_14 (normalized_defaults.arrayZ, coords_len)) ||
670
0
          !plan->check_success (avar_table->map_coords_2_14 (normalized_maxs.arrayZ, coords_len)))
671
0
        return false;
672
673
0
      for (const auto& _ : + hb_enumerate (axes))
674
0
      {
675
0
        unsigned i = _.first;
676
0
        hb_tag_t axis_tag = _.second.get_axis_tag ();
677
0
        if (plan->user_axes_location.has (axis_tag))
678
0
        {
679
0
          plan->axes_location.set (axis_tag, Triple ((double) normalized_mins[i],
680
0
                                                     (double) normalized_defaults[i],
681
0
                                                     (double) normalized_maxs[i]));
682
0
          float normalized_default = normalized_defaults[i];
683
0
          if (normalized_default == -0.f)
684
0
            normalized_default = 0.f;
685
0
          if (normalized_default != 0.f)
686
0
            plan->pinned_at_default = false;
687
688
0
          plan->normalized_coords[i] = roundf (normalized_default * 16384.f);
689
0
        }
690
0
      }
691
0
    }
692
0
  }
693
0
  return true;
694
0
}
695
696
#ifndef HB_NO_OT_FONT_CFF
697
void
698
update_instance_metrics_map_from_cff2 (hb_subset_plan_t *plan)
699
20.3k
{
700
20.3k
  if (!plan->normalized_coords) return;
701
0
  OT::cff2::accelerator_t cff2 (plan->source);
702
0
  if (!cff2.is_valid ()) return;
703
704
0
  hb_font_t *font = _get_hb_font_with_variations (plan);
705
0
  if (unlikely (!plan->check_success (font != nullptr)))
706
0
  {
707
0
    hb_font_destroy (font);
708
0
    return;
709
0
  }
710
711
0
  hb_glyph_extents_t extents = {0x7FFF, -0x7FFF};
712
0
  OT::hmtx_accelerator_t _hmtx (plan->source);
713
0
  OT::hb_scalar_cache_t *hvar_store_cache = nullptr;
714
0
  if (_hmtx.has_data () && _hmtx.var_table.get_length ())
715
0
    hvar_store_cache = _hmtx.var_table->get_var_store ().create_cache ();
716
717
0
  OT::vmtx_accelerator_t _vmtx (plan->source);
718
0
  OT::hb_scalar_cache_t *vvar_store_cache = nullptr;
719
0
  if (_vmtx.has_data () && _vmtx.var_table.get_length ())
720
0
    vvar_store_cache = _vmtx.var_table->get_var_store ().create_cache ();
721
722
0
  for (auto p : *plan->glyph_map)
723
0
  {
724
0
    hb_codepoint_t old_gid = p.first;
725
0
    hb_codepoint_t new_gid = p.second;
726
0
    if (!cff2.get_extents (font, old_gid, &extents)) continue;
727
0
    bool has_bounds_info = true;
728
0
    if (extents.x_bearing == 0 && extents.width == 0 &&
729
0
        extents.height == 0 && extents.y_bearing == 0)
730
0
      has_bounds_info = false;
731
732
0
    if (has_bounds_info)
733
0
    {
734
0
      plan->head_maxp_info.xMin = hb_min (plan->head_maxp_info.xMin, extents.x_bearing);
735
0
      plan->head_maxp_info.xMax = hb_max (plan->head_maxp_info.xMax,
736
0
             hb_saturate_add (extents.x_bearing, extents.width));
737
0
      plan->head_maxp_info.yMax = hb_max (plan->head_maxp_info.yMax, extents.y_bearing);
738
0
      plan->head_maxp_info.yMin = hb_min (plan->head_maxp_info.yMin,
739
0
             hb_saturate_add (extents.y_bearing, extents.height));
740
0
    }
741
742
0
    if (_hmtx.has_data ())
743
0
    {
744
0
      double hori_aw = _hmtx.get_advance_without_var_unscaled (old_gid);
745
0
      if (_hmtx.var_table.get_length ())
746
0
        hori_aw += (double) roundf (_hmtx.var_table->get_advance_delta_unscaled (old_gid, font->coords, font->num_coords,
747
0
                   hvar_store_cache));
748
      /* Malicious deltas can take the advance out of the UFWORD range that
749
       * hmtx serialization stores.  Clamp, to keep downstream consumers
750
       * (e.g. CFF width optimizer) bounded and consistent with hmtx. */
751
0
      hori_aw = hb_clamp (hori_aw, 0., 65535.);
752
0
      int lsb = extents.x_bearing;
753
0
      if (!has_bounds_info)
754
0
      {
755
0
        _hmtx.get_leading_bearing_without_var_unscaled (old_gid, &lsb);
756
0
      }
757
0
      plan->hmtx_map.set (new_gid, hb_pair ((unsigned) hori_aw, lsb));
758
0
      plan->bounds_width_vec[new_gid] = extents.width;
759
0
    }
760
761
0
    if (_vmtx.has_data ())
762
0
    {
763
0
      double vert_aw = _vmtx.get_advance_without_var_unscaled (old_gid);
764
0
      if (_vmtx.var_table.get_length ())
765
0
        vert_aw += (double) roundf (_vmtx.var_table->get_advance_delta_unscaled (old_gid, font->coords, font->num_coords,
766
0
                   vvar_store_cache));
767
0
      vert_aw = hb_clamp (vert_aw, 0., 65535.);
768
0
      hb_position_t vorg_x = 0;
769
0
      hb_position_t vorg_y = 0;
770
0
      int tsb = 0;
771
0
      if (has_bounds_info &&
772
0
           hb_font_get_glyph_v_origin (font, old_gid, &vorg_x, &vorg_y))
773
0
      {
774
0
        tsb = hb_saturate_sub (vorg_y, extents.y_bearing);
775
0
      } else {
776
0
        _vmtx.get_leading_bearing_without_var_unscaled (old_gid, &tsb);
777
0
      }
778
779
0
      plan->vmtx_map.set (new_gid, hb_pair ((unsigned) vert_aw, tsb));
780
0
      plan->bounds_height_vec[new_gid] = extents.height;
781
0
    }
782
0
  }
783
  /* No glyph contributed bounds: do not serialize the accumulator's
784
   * inverted sentinels into head (or a downgraded CFF FontBBox). */
785
0
  if (plan->head_maxp_info.xMin > plan->head_maxp_info.xMax)
786
0
  {
787
0
    plan->head_maxp_info.xMin = plan->head_maxp_info.xMax = 0;
788
0
    plan->head_maxp_info.yMin = plan->head_maxp_info.yMax = 0;
789
0
  }
790
0
  hb_font_destroy (font);
791
0
  if (hvar_store_cache)
792
0
    _hmtx.var_table->get_var_store ().destroy_cache (hvar_store_cache);
793
0
  if (vvar_store_cache)
794
0
    _vmtx.var_table->get_var_store ().destroy_cache (vvar_store_cache);
795
0
}
796
#endif
797
798
bool
799
get_instance_glyphs_contour_points (hb_subset_plan_t *plan)
800
20.3k
{
801
  /* contour_points vector only needed for updating gvar table (infer delta and
802
   * iup delta optimization) during partial instancing */
803
20.3k
  if (plan->user_axes_location.is_empty () || plan->all_axes_pinned)
804
20.3k
    return true;
805
806
0
  OT::glyf_accelerator_t glyf (plan->source);
807
808
0
  for (auto &_ : plan->new_to_old_gid_list)
809
0
  {
810
0
    hb_codepoint_t new_gid = _.first;
811
0
    contour_point_vector_t all_points;
812
0
    if (new_gid == 0 && !(plan->flags & HB_SUBSET_FLAGS_NOTDEF_OUTLINE))
813
0
    {
814
0
      if (unlikely (!plan->new_gid_contour_points_map.set (new_gid, all_points)))
815
0
        return false;
816
0
      continue;
817
0
    }
818
819
0
    hb_codepoint_t old_gid = _.second;
820
0
    auto glyph = glyf.glyph_for_gid (old_gid);
821
0
    if (unlikely (!glyph.get_all_points_without_var (plan->source, all_points)))
822
0
      return false;
823
0
    if (unlikely (!plan->new_gid_contour_points_map.set (new_gid, all_points)))
824
0
      return false;
825
826
    /* composite new gids are only needed by iup delta optimization */
827
0
    if ((plan->flags & HB_SUBSET_FLAGS_OPTIMIZE_IUP_DELTAS) && glyph.is_composite ())
828
0
      plan->composite_new_gids.add (new_gid);
829
0
  }
830
0
  return true;
831
0
}
832
833
template<typename DeltaSetIndexMap>
834
void
835
remap_colrv1_delta_set_index_indices (const DeltaSetIndexMap &index_map,
836
                                      const hb_set_t &delta_set_idxes,
837
                                      hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> &variation_idx_delta_map, /* IN/OUT */
838
                                      hb_map_t &new_deltaset_idx_varidx_map /* OUT */)
839
0
{
840
0
  if (!index_map.get_map_count ())
841
0
    return;
842
843
0
  hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> delta_set_idx_delta_map;
844
0
  unsigned new_delta_set_idx = 0;
845
0
  for (unsigned delta_set_idx : delta_set_idxes)
846
0
  {
847
0
    unsigned var_idx = index_map.map (delta_set_idx);
848
0
    unsigned new_varidx = HB_OT_LAYOUT_NO_VARIATIONS_INDEX;
849
0
    int delta = 0;
850
851
0
    if (var_idx != HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
852
0
    {
853
0
      hb_pair_t<unsigned, int> *new_varidx_delta;
854
0
      if (!variation_idx_delta_map.has (var_idx, &new_varidx_delta)) continue;
855
856
0
      new_varidx = hb_first (*new_varidx_delta);
857
0
      delta = hb_second (*new_varidx_delta);
858
0
    }
859
860
0
    new_deltaset_idx_varidx_map.set (new_delta_set_idx, new_varidx);
861
0
    delta_set_idx_delta_map.set (delta_set_idx, hb_pair_t<unsigned, int> (new_delta_set_idx, delta));
862
0
    new_delta_set_idx++;
863
0
  }
864
0
  variation_idx_delta_map = std::move (delta_set_idx_delta_map);
865
0
}
866
867
template void
868
remap_colrv1_delta_set_index_indices<OT::DeltaSetIndexMap> (const OT::DeltaSetIndexMap &index_map,
869
                                      const hb_set_t &delta_set_idxes,
870
                                      hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> &variation_idx_delta_map, /* IN/OUT */
871
                                      hb_map_t &new_deltaset_idx_varidx_map /* OUT */);
872
873
 #endif