Coverage Report

Created: 2026-06-30 11:14

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
#ifndef HB_NO_OT_FONT_CFF
59
 static inline hb_font_t*
60
 _get_hb_font_with_variations (const hb_subset_plan_t *plan)
61
0
 {
62
0
   hb_font_t *font = hb_font_create (plan->source);
63
64
0
   hb_vector_t<hb_variation_t> vars;
65
0
   if (!vars.alloc (plan->user_axes_location.get_population ())) {
66
0
     hb_font_destroy (font);
67
0
     return nullptr;
68
0
   }
69
70
0
   for (auto _ : plan->user_axes_location)
71
0
   {
72
0
     hb_variation_t var;
73
0
     var.tag = _.first;
74
0
     var.value = _.second.middle;
75
0
     vars.push (var);
76
0
   }
77
78
0
   hb_font_set_variations (font, vars.arrayZ, plan->user_axes_location.get_population ());
79
0
   return font;
80
0
 }
81
#endif
82
83
 template<typename ItemVarStore>
84
 void
85
 remap_variation_indices (const ItemVarStore &var_store,
86
                          const hb_set_t &variation_indices,
87
                          const hb_vector_t<int>& normalized_coords,
88
                          bool calculate_delta, /* not pinned at default */
89
                          bool no_variations, /* all axes pinned */
90
                          hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> &variation_idx_delta_map /* OUT */)
91
0
 {
92
0
   if (&var_store == &Null (OT::ItemVariationStore)) return;
93
0
   unsigned subtable_count = var_store.get_sub_table_count ();
94
0
   auto *store_cache = var_store.create_cache ();
95
96
0
   unsigned new_major = 0, new_minor = 0;
97
0
   unsigned last_major = (variation_indices.get_min ()) >> 16;
98
0
   for (unsigned idx : variation_indices)
99
0
   {
100
0
     int delta = 0;
101
0
     if (calculate_delta)
102
0
       delta = roundf (var_store.get_delta (idx, normalized_coords.arrayZ,
103
0
                                            normalized_coords.length, store_cache));
104
105
0
     if (no_variations)
106
0
     {
107
0
       variation_idx_delta_map.set (idx, hb_pair_t<unsigned, int> (HB_OT_LAYOUT_NO_VARIATIONS_INDEX, delta));
108
0
       continue;
109
0
     }
110
111
0
     uint16_t major = idx >> 16;
112
0
     if (major >= subtable_count) break;
113
0
     if (major != last_major)
114
0
     {
115
0
       new_minor = 0;
116
0
       ++new_major;
117
0
     }
118
119
0
     unsigned new_idx = (new_major << 16) + new_minor;
120
0
     variation_idx_delta_map.set (idx, hb_pair_t<unsigned, int> (new_idx, delta));
121
0
     ++new_minor;
122
0
     last_major = major;
123
0
   }
124
0
   var_store.destroy_cache (store_cache);
125
0
 }
126
127
 template
128
 void
129
 remap_variation_indices<OT::ItemVariationStore> (const OT::ItemVariationStore &var_store,
130
                          const hb_set_t &variation_indices,
131
                          const hb_vector_t<int>& normalized_coords,
132
                          bool calculate_delta, /* not pinned at default */
133
                          bool no_variations, /* all axes pinned */
134
                          hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> &variation_idx_delta_map /* OUT */);
135
136
 #ifndef HB_NO_BASE
137
 void
138
 collect_base_variation_indices (hb_subset_plan_t* plan)
139
0
 {
140
0
   hb_blob_ptr_t<OT::BASE> base = plan->source_table<OT::BASE> ();
141
0
   if (!base->has_var_store ())
142
0
   {
143
0
     base.destroy ();
144
0
     return;
145
0
   }
146
147
0
   hb_set_t varidx_set;
148
0
   base->collect_variation_indices (plan, varidx_set);
149
0
   const OT::ItemVariationStore &var_store = base->get_var_store ();
150
0
   unsigned subtable_count = var_store.get_sub_table_count ();
151
152
153
0
   remap_variation_indices (var_store, varidx_set,
154
0
                             plan->normalized_coords,
155
0
                             !plan->pinned_at_default,
156
0
                             plan->all_axes_pinned,
157
0
                             plan->base_variation_idx_map);
158
0
   generate_varstore_inner_maps (varidx_set, subtable_count, plan->base_varstore_inner_maps);
159
160
0
   base.destroy ();
161
0
 }
162
163
 #endif
164
165
bool
166
normalize_axes_location (hb_face_t *face, hb_subset_plan_t *plan)
167
6.42k
{
168
6.42k
  if (plan->user_axes_location.is_empty ())
169
6.42k
    return true;
170
171
0
  hb_array_t<const OT::AxisRecord> axes = face->table.fvar->get_axes ();
172
0
  if (!plan->check_success (plan->normalized_coords.resize (axes.length)))
173
0
    return false;
174
175
0
  bool has_avar = face->table.avar->has_data ();
176
0
  hb_vector_t<float> normalized_mins;
177
0
  hb_vector_t<float> normalized_defaults;
178
0
  hb_vector_t<float> normalized_maxs;
179
0
  if (has_avar)
180
0
  {
181
0
    if (!plan->check_success (normalized_mins.resize (axes.length)) ||
182
0
        !plan->check_success (normalized_defaults.resize (axes.length)) ||
183
0
        !plan->check_success (normalized_maxs.resize (axes.length)))
184
0
      return false;
185
0
  }
186
187
0
  bool axis_not_pinned = false;
188
0
  unsigned new_axis_idx = 0;
189
0
  unsigned last_idx = 0;
190
0
  for (const auto& _ : + hb_enumerate (axes))
191
0
  {
192
0
    unsigned i = _.first;
193
0
    const OT::AxisRecord &axis = _.second;
194
0
    hb_tag_t axis_tag = axis.get_axis_tag ();
195
0
    plan->axes_old_index_tag_map.set (i, axis_tag);
196
197
0
    if (!plan->user_axes_location.has (axis_tag) ||
198
0
        !plan->user_axes_location.get (axis_tag).is_point ())
199
0
    {
200
0
      axis_not_pinned = true;
201
0
      plan->axes_index_map.set (i, new_axis_idx);
202
0
      plan->axis_tags.push (axis_tag);
203
0
      new_axis_idx++;
204
0
    }
205
206
0
    Triple *axis_range;
207
0
    if (plan->user_axes_location.has (axis_tag, &axis_range))
208
0
    {
209
0
      plan->axes_triple_distances.set (axis_tag, axis.get_triple_distances ());
210
211
0
      float normalized_min = axis.normalize_axis_value (axis_range->minimum);
212
0
      float normalized_default = axis.normalize_axis_value (axis_range->middle);
213
0
      float normalized_max = axis.normalize_axis_value (axis_range->maximum);
214
215
      // TODO(behdad): Spec says axis normalization should be done in 16.16;
216
      // We used to do it in 2.14, but that's not correct.  I fixed this in
217
      // the fvar/avar code, but keeping 2.14 here for now to keep tests
218
      // happy. We might need to adjust fonttools as well.
219
      // I'm only fairly confident in the above statement. Anyway,
220
      // we should look deeper into this, and also update fonttools if
221
      // needed.
222
223
      // Round to 2.14
224
0
      normalized_min = roundf (normalized_min * 16384.f) / 16384.f;
225
0
      normalized_default = roundf (normalized_default * 16384.f) / 16384.f;
226
0
      normalized_max = roundf (normalized_max * 16384.f) / 16384.f;
227
228
0
      if (has_avar)
229
0
      {
230
0
        normalized_mins[i] = normalized_min;
231
0
        normalized_defaults[i] = normalized_default;
232
0
        normalized_maxs[i] = normalized_max;
233
0
        last_idx = i;
234
0
      }
235
0
      else
236
0
      {
237
0
        plan->axes_location.set (axis_tag, Triple ((double) normalized_min,
238
0
                                                   (double) normalized_default,
239
0
                                                   (double) normalized_max));
240
0
        if (normalized_default == -0.f)
241
0
          normalized_default = 0.f; // Normalize -0 to 0
242
0
        if (normalized_default != 0.f)
243
0
          plan->pinned_at_default = false;
244
245
0
        plan->normalized_coords[i] = roundf (normalized_default * 16384.f);
246
0
      }
247
0
    }
248
0
  }
249
0
  plan->all_axes_pinned = !axis_not_pinned;
250
251
  // TODO: use avar map_coords_16_16() when normalization is changed to 16.16
252
  // in fonttools
253
0
  if (has_avar)
254
0
  {
255
0
    const OT::avar* avar_table = face->table.avar;
256
0
    if (avar_table->has_v2_data () && !plan->all_axes_pinned)
257
0
    {
258
0
      DEBUG_MSG (SUBSET, nullptr, "Partial-instancing avar2 table is not supported.");
259
0
      return false;
260
0
    }
261
262
0
    unsigned coords_len = last_idx + 1;
263
0
    if (!plan->check_success (avar_table->map_coords_2_14 (normalized_mins.arrayZ, coords_len)) ||
264
0
        !plan->check_success (avar_table->map_coords_2_14 (normalized_defaults.arrayZ, coords_len)) ||
265
0
        !plan->check_success (avar_table->map_coords_2_14 (normalized_maxs.arrayZ, coords_len)))
266
0
      return false;
267
268
0
    for (const auto& _ : + hb_enumerate (axes))
269
0
    {
270
0
      unsigned i = _.first;
271
0
      hb_tag_t axis_tag = _.second.get_axis_tag ();
272
0
      if (plan->user_axes_location.has (axis_tag))
273
0
      {
274
0
        plan->axes_location.set (axis_tag, Triple ((double) normalized_mins[i],
275
0
                                                   (double) normalized_defaults[i],
276
0
                                                   (double) normalized_maxs[i]));
277
0
        float normalized_default = normalized_defaults[i];
278
0
        if (normalized_default == -0.f)
279
0
          normalized_default = 0.f; // Normalize -0 to 0
280
0
        if (normalized_default != 0.f)
281
0
          plan->pinned_at_default = false;
282
283
0
        plan->normalized_coords[i] = roundf (normalized_default * 16384.f);
284
0
      }
285
0
    }
286
0
  }
287
0
  return true;
288
0
}
289
290
#ifndef HB_NO_OT_FONT_CFF
291
void
292
update_instance_metrics_map_from_cff2 (hb_subset_plan_t *plan)
293
6.42k
{
294
6.42k
  if (!plan->normalized_coords) return;
295
0
  OT::cff2::accelerator_t cff2 (plan->source);
296
0
  if (!cff2.is_valid ()) return;
297
298
0
  hb_font_t *font = _get_hb_font_with_variations (plan);
299
0
  if (unlikely (!plan->check_success (font != nullptr)))
300
0
  {
301
0
    hb_font_destroy (font);
302
0
    return;
303
0
  }
304
305
0
  hb_glyph_extents_t extents = {0x7FFF, -0x7FFF};
306
0
  OT::hmtx_accelerator_t _hmtx (plan->source);
307
0
  OT::hb_scalar_cache_t *hvar_store_cache = nullptr;
308
0
  if (_hmtx.has_data () && _hmtx.var_table.get_length ())
309
0
    hvar_store_cache = _hmtx.var_table->get_var_store ().create_cache ();
310
311
0
  OT::vmtx_accelerator_t _vmtx (plan->source);
312
0
  OT::hb_scalar_cache_t *vvar_store_cache = nullptr;
313
0
  if (_vmtx.has_data () && _vmtx.var_table.get_length ())
314
0
    vvar_store_cache = _vmtx.var_table->get_var_store ().create_cache ();
315
316
0
  for (auto p : *plan->glyph_map)
317
0
  {
318
0
    hb_codepoint_t old_gid = p.first;
319
0
    hb_codepoint_t new_gid = p.second;
320
0
    if (!cff2.get_extents (font, old_gid, &extents)) continue;
321
0
    bool has_bounds_info = true;
322
0
    if (extents.x_bearing == 0 && extents.width == 0 &&
323
0
        extents.height == 0 && extents.y_bearing == 0)
324
0
      has_bounds_info = false;
325
326
0
    if (has_bounds_info)
327
0
    {
328
0
      plan->head_maxp_info.xMin = hb_min (plan->head_maxp_info.xMin, extents.x_bearing);
329
0
      plan->head_maxp_info.xMax = hb_max (plan->head_maxp_info.xMax, extents.x_bearing + extents.width);
330
0
      plan->head_maxp_info.yMax = hb_max (plan->head_maxp_info.yMax, extents.y_bearing);
331
0
      plan->head_maxp_info.yMin = hb_min (plan->head_maxp_info.yMin, extents.y_bearing + extents.height);
332
0
    }
333
334
0
    if (_hmtx.has_data ())
335
0
    {
336
0
      int hori_aw = _hmtx.get_advance_without_var_unscaled (old_gid);
337
0
      if (_hmtx.var_table.get_length ())
338
0
        hori_aw += (int) roundf (_hmtx.var_table->get_advance_delta_unscaled (old_gid, font->coords, font->num_coords,
339
0
                                                                              hvar_store_cache));
340
0
      int lsb = extents.x_bearing;
341
0
      if (!has_bounds_info)
342
0
      {
343
0
        _hmtx.get_leading_bearing_without_var_unscaled (old_gid, &lsb);
344
0
      }
345
0
      plan->hmtx_map.set (new_gid, hb_pair ((unsigned) hori_aw, lsb));
346
0
      plan->bounds_width_vec[new_gid] = extents.width;
347
0
    }
348
349
0
    if (_vmtx.has_data ())
350
0
    {
351
0
      int vert_aw = _vmtx.get_advance_without_var_unscaled (old_gid);
352
0
      if (_vmtx.var_table.get_length ())
353
0
        vert_aw += (int) roundf (_vmtx.var_table->get_advance_delta_unscaled (old_gid, font->coords, font->num_coords,
354
0
                                                                              vvar_store_cache));
355
0
      hb_position_t vorg_x = 0;
356
0
      hb_position_t vorg_y = 0;
357
0
      int tsb = 0;
358
0
      if (has_bounds_info &&
359
0
           hb_font_get_glyph_v_origin (font, old_gid, &vorg_x, &vorg_y))
360
0
      {
361
0
        tsb = vorg_y - extents.y_bearing;
362
0
      } else {
363
0
        _vmtx.get_leading_bearing_without_var_unscaled (old_gid, &tsb);
364
0
      }
365
366
0
      plan->vmtx_map.set (new_gid, hb_pair ((unsigned) vert_aw, tsb));
367
0
      plan->bounds_height_vec[new_gid] = extents.height;
368
0
    }
369
0
  }
370
0
  hb_font_destroy (font);
371
0
  if (hvar_store_cache)
372
0
    _hmtx.var_table->get_var_store ().destroy_cache (hvar_store_cache);
373
0
  if (vvar_store_cache)
374
0
    _vmtx.var_table->get_var_store ().destroy_cache (vvar_store_cache);
375
0
}
376
#endif
377
378
bool
379
get_instance_glyphs_contour_points (hb_subset_plan_t *plan)
380
6.42k
{
381
  /* contour_points vector only needed for updating gvar table (infer delta and
382
   * iup delta optimization) during partial instancing */
383
6.42k
  if (plan->user_axes_location.is_empty () || plan->all_axes_pinned)
384
6.42k
    return true;
385
386
0
  OT::glyf_accelerator_t glyf (plan->source);
387
388
0
  for (auto &_ : plan->new_to_old_gid_list)
389
0
  {
390
0
    hb_codepoint_t new_gid = _.first;
391
0
    contour_point_vector_t all_points;
392
0
    if (new_gid == 0 && !(plan->flags & HB_SUBSET_FLAGS_NOTDEF_OUTLINE))
393
0
    {
394
0
      if (unlikely (!plan->new_gid_contour_points_map.set (new_gid, all_points)))
395
0
        return false;
396
0
      continue;
397
0
    }
398
399
0
    hb_codepoint_t old_gid = _.second;
400
0
    auto glyph = glyf.glyph_for_gid (old_gid);
401
0
    if (unlikely (!glyph.get_all_points_without_var (plan->source, all_points)))
402
0
      return false;
403
0
    if (unlikely (!plan->new_gid_contour_points_map.set (new_gid, all_points)))
404
0
      return false;
405
406
    /* composite new gids are only needed by iup delta optimization */
407
0
    if ((plan->flags & HB_SUBSET_FLAGS_OPTIMIZE_IUP_DELTAS) && glyph.is_composite ())
408
0
      plan->composite_new_gids.add (new_gid);
409
0
  }
410
0
  return true;
411
0
}
412
413
template<typename DeltaSetIndexMap>
414
void
415
remap_colrv1_delta_set_index_indices (const DeltaSetIndexMap &index_map,
416
                                      const hb_set_t &delta_set_idxes,
417
                                      hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> &variation_idx_delta_map, /* IN/OUT */
418
                                      hb_map_t &new_deltaset_idx_varidx_map /* OUT */)
419
0
{
420
0
  if (!index_map.get_map_count ())
421
0
    return;
422
423
0
  hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> delta_set_idx_delta_map;
424
0
  unsigned new_delta_set_idx = 0;
425
0
  for (unsigned delta_set_idx : delta_set_idxes)
426
0
  {
427
0
    unsigned var_idx = index_map.map (delta_set_idx);
428
0
    unsigned new_varidx = HB_OT_LAYOUT_NO_VARIATIONS_INDEX;
429
0
    int delta = 0;
430
431
0
    if (var_idx != HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
432
0
    {
433
0
      hb_pair_t<unsigned, int> *new_varidx_delta;
434
0
      if (!variation_idx_delta_map.has (var_idx, &new_varidx_delta)) continue;
435
436
0
      new_varidx = hb_first (*new_varidx_delta);
437
0
      delta = hb_second (*new_varidx_delta);
438
0
    }
439
440
0
    new_deltaset_idx_varidx_map.set (new_delta_set_idx, new_varidx);
441
0
    delta_set_idx_delta_map.set (delta_set_idx, hb_pair_t<unsigned, int> (new_delta_set_idx, delta));
442
0
    new_delta_set_idx++;
443
0
  }
444
0
  variation_idx_delta_map = std::move (delta_set_idx_delta_map);
445
0
}
446
447
template void
448
remap_colrv1_delta_set_index_indices<OT::DeltaSetIndexMap> (const OT::DeltaSetIndexMap &index_map,
449
                                      const hb_set_t &delta_set_idxes,
450
                                      hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> &variation_idx_delta_map, /* IN/OUT */
451
                                      hb_map_t &new_deltaset_idx_varidx_map /* OUT */);
452
453
 #endif