Coverage Report

Created: 2026-08-11 07:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/3rdparty/harfbuzz-ng/src/hb-ot-var-avar-table.hh
Line
Count
Source
1
/*
2
 * Copyright © 2017  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): Behdad Esfahbod
25
 */
26
27
#ifndef HB_OT_VAR_AVAR_TABLE_HH
28
#define HB_OT_VAR_AVAR_TABLE_HH
29
30
#include "hb-open-type.hh"
31
#include "hb-ot-var-common.hh"
32
#include "hb-ot-var-fvar-table.hh"
33
34
35
/*
36
 * avar -- Axis Variations
37
 * https://docs.microsoft.com/en-us/typography/opentype/spec/avar
38
 */
39
40
#define HB_OT_TAG_avar HB_TAG('a','v','a','r')
41
42
43
namespace OT {
44
45
46
/* "Spec": https://github.com/be-fonts/boring-expansion-spec/issues/14 */
47
struct avarV2Tail
48
{
49
  friend struct avar;
50
51
  bool sanitize (hb_sanitize_context_t *c,
52
     const void *base) const
53
0
  {
54
0
    TRACE_SANITIZE (this);
55
0
    return_trace (varIdxMap.sanitize (c, base) &&
56
0
      varStore.sanitize (c, base));
57
0
  }
58
59
  protected:
60
  Offset32To<DeltaSetIndexMap>  varIdxMap;  /* Offset from the beginning of 'avar' table. */
61
  Offset32To<ItemVariationStore>  varStore; /* Offset from the beginning of 'avar' table. */
62
63
  public:
64
  DEFINE_SIZE_STATIC (8);
65
};
66
67
68
struct AxisValueMap
69
{
70
  bool sanitize (hb_sanitize_context_t *c) const
71
0
  {
72
0
    TRACE_SANITIZE (this);
73
0
    return_trace (c->check_struct (this));
74
0
  }
75
76
  void set_mapping (float from_coord, float to_coord)
77
0
  {
78
0
    coords[0].set_float (from_coord);
79
0
    coords[1].set_float (to_coord);
80
0
  }
81
82
  bool is_outside_axis_range (const Triple& axis_range) const
83
0
  {
84
0
    double from_coord = (double) coords[0].to_float ();
85
0
    return !axis_range.contains (from_coord);
86
0
  }
87
88
  bool must_include () const
89
0
  {
90
0
    float from_coord = coords[0].to_float ();
91
0
    float to_coord = coords[1].to_float ();
92
0
    return (from_coord == -1.f && to_coord == -1.f) ||
93
0
           (from_coord == 0.f && to_coord == 0.f) ||
94
0
           (from_coord == 1.f && to_coord == 1.f);
95
0
  }
96
97
  void instantiate (const Triple& axis_range,
98
                    const Triple& unmapped_range,
99
                    const TripleDistances& triple_distances)
100
0
  {
101
0
    float from_coord = coords[0].to_float ();
102
0
    float to_coord = coords[1].to_float ();
103
0
104
0
    from_coord = renormalizeValue ((double) from_coord, unmapped_range, triple_distances);
105
0
    to_coord = renormalizeValue ((double) to_coord, axis_range, triple_distances);
106
0
107
0
    coords[0].set_float (from_coord);
108
0
    coords[1].set_float (to_coord);
109
0
  }
110
111
  HB_INTERNAL static int cmp (const void *pa, const void *pb)
112
0
  {
113
0
    const AxisValueMap *a = (const AxisValueMap *) pa;
114
0
    const AxisValueMap *b = (const AxisValueMap *) pb;
115
0
116
0
    int a_from = a->coords[0].to_int ();
117
0
    int b_from = b->coords[0].to_int ();
118
0
    if (a_from != b_from)
119
0
      return a_from - b_from;
120
0
121
0
    /* this should never be reached. according to the spec, all of the axis
122
0
     * value map records for a given axis must have different fromCoord values
123
0
     * */
124
0
    int a_to = a->coords[1].to_int ();
125
0
    int b_to = b->coords[1].to_int ();
126
0
    return a_to - b_to;
127
0
  }
128
129
  bool serialize (hb_serialize_context_t *c) const
130
0
  {
131
0
    TRACE_SERIALIZE (this);
132
0
    return_trace (c->embed (this));
133
0
  }
134
135
  public:
136
  F2DOT14 coords[2];
137
//   F2DOT14  fromCoord;  /* A normalized coordinate value obtained using
138
//         * default normalization. */
139
//   F2DOT14  toCoord;  /* The modified, normalized coordinate value. */
140
141
  public:
142
  DEFINE_SIZE_STATIC (4);
143
};
144
145
struct SegmentMaps : Array16Of<AxisValueMap>
146
{
147
  float map_float (float value, unsigned int from_offset = 0, unsigned int to_offset = 1) const
148
0
  {
149
0
#define fromCoord coords[from_offset].to_float ()
150
0
#define toCoord coords[to_offset].to_float ()
151
152
0
    const auto *map = arrayZ;
153
154
    /* The following special-cases are not part of OpenType, which requires
155
     * that at least -1, 0, and +1 must be mapped. But we include these as
156
     * part of a better error recovery scheme. */
157
0
    if (len < 2)
158
0
    {
159
0
      if (!len)
160
0
  return value;
161
0
      else /* len == 1*/
162
0
  return value - map[0].fromCoord + map[0].toCoord;
163
0
    }
164
165
    // At least two mappings now.
166
167
    /* CoreText is wild...
168
     * PingFangUI avar needs all this special-casing...
169
     * So we implement an extended version of the spec here,
170
     * which is more robust and more likely to be compatible with
171
     * the wild. */
172
173
0
    unsigned start = 0;
174
0
    unsigned end = len;
175
0
    if (map[start].fromCoord == -1 && map[start].toCoord == -1 && map[start+1].fromCoord == -1)
176
0
      start++;
177
0
    if (map[end-1].fromCoord == +1 && map[end-1].toCoord == +1 && map[end-2].fromCoord == +1)
178
0
      end--;
179
180
    /* Look for exact match first, and do lots of special-casing. */
181
0
    unsigned i;
182
0
    for (i = start; i < end; i++)
183
0
      if (value == map[i].fromCoord)
184
0
  break;
185
0
    if (i < end)
186
0
    {
187
      // There's at least one exact match. See if there are more.
188
0
      unsigned j = i;
189
0
      for (; j + 1 < end; j++)
190
0
  if (value != map[j + 1].fromCoord)
191
0
    break;
192
193
      // [i,j] inclusive are all exact matches:
194
195
      // If there's only one, return it. This is the only spec-compliant case.
196
0
      if (i == j)
197
0
  return map[i].toCoord;
198
      // If there's exactly three, return the middle one.
199
0
      if (i + 2 == j)
200
0
  return map[i + 1].toCoord;
201
202
      // Ignore the middle ones. Return the one mapping closer to 0.
203
0
      if (value < 0) return map[j].toCoord;
204
0
      if (value > 0) return map[i].toCoord;
205
206
      // Mapping 0? CoreText seems confused. It seems to prefer 0 here...
207
      // So we'll just return the smallest one. lol
208
0
      return fabsf (map[i].toCoord) < fabsf (map[j].toCoord) ? map[i].toCoord : map[j].toCoord;
209
210
      // Mapping 0? Return one not mapping to 0.
211
0
      if (map[i].toCoord == 0)
212
0
  return map[j].toCoord;
213
0
      else
214
0
  return map[i].toCoord;
215
0
    }
216
217
    /* There's at least two and we're not an exact match. Prepare to lerp. */
218
219
    // Find the segment we're in.
220
0
    for (i = start; i < end; i++)
221
0
      if (value < map[i].fromCoord)
222
0
  break;
223
224
0
    if (i == 0)
225
0
    {
226
      // Value before all segments; Shift.
227
0
      return value - map[0].fromCoord + map[0].toCoord;
228
0
    }
229
0
    if (i == end)
230
0
    {
231
      // Value after all segments; Shift.
232
0
      return value - map[end - 1].fromCoord + map[end - 1].toCoord;
233
0
    }
234
235
    // Actually interpolate.
236
0
    auto &before = map[i-1];
237
0
    auto &after = map[i];
238
0
    float denom = after.fromCoord - before.fromCoord; // Can't be zero by now.
239
0
    return before.toCoord + ((after.toCoord - before.toCoord) * (value - before.fromCoord)) / denom;
240
241
0
#undef toCoord
242
0
#undef fromCoord
243
0
  }
244
245
0
  float unmap_float (float value) const { return map_float (value, 1, 0); }
246
247
248
  // TODO Kill this.
249
  Triple unmap_axis_range (const Triple& axis_range) const
250
0
  {
251
0
    float unmapped_min = unmap_float (axis_range.minimum);
252
0
    float unmapped_middle = unmap_float (axis_range.middle);
253
0
    float unmapped_max = unmap_float (axis_range.maximum);
254
0
255
0
    return Triple{(double) unmapped_min, (double) unmapped_middle, (double) unmapped_max};
256
0
  }
257
258
  bool subset (hb_subset_context_t *c, hb_tag_t axis_tag,
259
               hb_vector_t<AxisValueMap> *out_mappings = nullptr) const
260
0
  {
261
0
    TRACE_SUBSET (this);
262
0
263
0
    /* This function cannot work on avar2 table (and currently doesn't).
264
0
     * We should instead keep the design coords in the shape plan and use
265
0
     * those. unmap_axis_range needs to be killed. */
266
0
267
0
    /* avar mapped normalized axis range. Under avar2, axes_location holds
268
0
     * only the self-contained pins for the other tables; avar itself uses
269
0
     * the intermediate-space ranges of all restricted axes. */
270
0
    const auto &axes_location = c->plan->has_avar2
271
0
        ? c->plan->old_intermediates
272
0
        : c->plan->axes_location;
273
0
    Triple *axis_range;
274
0
    if (!axes_location.has (axis_tag, &axis_range))
275
0
      return c->serializer->embed (*this);
276
0
277
0
    TripleDistances *axis_triple_distances;
278
0
    if (!c->plan->axes_triple_distances.has (axis_tag, &axis_triple_distances))
279
0
      return_trace (false);
280
0
281
0
    auto *out = c->serializer->start_embed (this);
282
0
    if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
283
0
284
0
    Triple unmapped_range = unmap_axis_range (*axis_range);
285
0
286
0
    /* create a vector of retained mappings and sort */
287
0
    hb_vector_t<AxisValueMap> value_mappings;
288
0
    for (const auto& _ : as_array ())
289
0
    {
290
0
      if (_.is_outside_axis_range (unmapped_range))
291
0
        continue;
292
0
      AxisValueMap mapping;
293
0
      mapping = _;
294
0
      mapping.instantiate (*axis_range, unmapped_range, *axis_triple_distances);
295
0
      /* (-1, -1), (0, 0), (1, 1) mappings will be added later, so avoid
296
0
       * duplicates here */
297
0
      if (mapping.must_include ())
298
0
        continue;
299
0
      value_mappings.push (mapping);
300
0
    }
301
0
302
0
    AxisValueMap m;
303
0
    m.set_mapping (-1.f, -1.f);
304
0
    value_mappings.push (m);
305
0
306
0
    m.set_mapping (0.f, 0.f);
307
0
    value_mappings.push (m);
308
0
309
0
    m.set_mapping (1.f, 1.f);
310
0
    value_mappings.push (m);
311
0
312
0
    value_mappings.qsort ();
313
0
314
0
    if (unlikely (value_mappings.in_error ()))
315
0
      return_trace (false);
316
0
317
0
    for (const auto& _ : value_mappings)
318
0
    {
319
0
      if (!_.serialize (c->serializer))
320
0
        return_trace (false);
321
0
    }
322
0
    if (!c->serializer->check_assign (out->len, value_mappings.length, HB_SERIALIZE_ERROR_INT_OVERFLOW))
323
0
      return_trace (false);
324
0
    /* Hand the instantiated mappings to the caller; avar2 offset
325
0
     * compensation needs them to locate the new mapping's kinks. */
326
0
    if (out_mappings)
327
0
      *out_mappings = std::move (value_mappings);
328
0
    return_trace (true);
329
0
  }
330
331
  public:
332
  DEFINE_SIZE_ARRAY (2, *this);
333
};
334
335
/* One knot of the avar2 offset-compensation function
336
 * offset(z) = inv_renorm(z) - z, in new intermediate space. */
337
struct avar2_offset_knot_t
338
{
339
  double z;
340
  double offset;
341
};
342
343
/* Insert a knot keeping the vector sorted by z; first insertion wins on
344
 * duplicate z. */
345
static inline void
346
_avar2_add_knot (hb_vector_t<avar2_offset_knot_t> &knots, double z, double offset)
347
0
{
348
0
  unsigned i = 0;
349
0
  while (i < knots.length && knots.arrayZ[i].z < z) i++;
350
0
  if (i < knots.length && knots.arrayZ[i].z == z) return;
351
0
  knots.push (avar2_offset_knot_t {0.0, 0.0});
352
0
  if (unlikely (knots.in_error ())) return;
353
0
  for (unsigned j = knots.length - 1; j > i; j--)
354
0
    knots.arrayZ[j] = knots.arrayZ[j - 1];
355
0
  knots.arrayZ[i] = avar2_offset_knot_t {z, offset};
356
0
}
Unexecuted instantiation: hb-font.cc:OT::_avar2_add_knot(hb_vector_t<OT::avar2_offset_knot_t, false>&, double, double)
Unexecuted instantiation: hb-ot-var.cc:OT::_avar2_add_knot(hb_vector_t<OT::avar2_offset_knot_t, false>&, double, double)
357
358
/* Piecewise-linear evaluation over the sorted knots. Inputs are always
359
 * within [-1, +1] and anchor knots at -1/0/+1 always exist. */
360
static inline double
361
_avar2_eval_offset (const hb_vector_t<avar2_offset_knot_t> &knots, double z)
362
0
{
363
0
  unsigned len = knots.length;
364
0
  for (unsigned i = 0; i < len; i++)
365
0
  {
366
0
    if (z == knots.arrayZ[i].z) return knots.arrayZ[i].offset;
367
0
    if (z < knots.arrayZ[i].z)
368
0
    {
369
0
      if (!i) return knots.arrayZ[0].offset;
370
0
      const auto &before = knots.arrayZ[i - 1];
371
0
      const auto &after = knots.arrayZ[i];
372
0
      double denom = after.z - before.z;
373
0
      return before.offset + (after.offset - before.offset) * (z - before.z) / denom;
374
0
    }
375
0
  }
376
0
  return len ? knots.arrayZ[len - 1].offset : 0.0;
377
0
}
Unexecuted instantiation: hb-font.cc:OT::_avar2_eval_offset(hb_vector_t<OT::avar2_offset_knot_t, false> const&, double)
Unexecuted instantiation: hb-ot-var.cc:OT::_avar2_eval_offset(hb_vector_t<OT::avar2_offset_knot_t, false> const&, double)
378
379
/* Piecewise-linear evaluation over instantiated avar v1 mappings, as
380
 * produced by SegmentMaps::subset (sorted, with -1/0/+1 anchors).
381
 * Matches SegmentMaps::map_float for such well-formed mappings. */
382
static inline double
383
_avar2_map_new_mapping (const hb_vector_t<AxisValueMap> &mappings, double v)
384
0
{
385
0
  unsigned len = mappings.length;
386
0
  if (!len) return v;
387
0
388
0
  for (unsigned i = 0; i < len; i++)
389
0
  {
390
0
    double from = (double) mappings.arrayZ[i].coords[0].to_float ();
391
0
    if (v == from) return (double) mappings.arrayZ[i].coords[1].to_float ();
392
0
    if (v < from)
393
0
    {
394
0
      double to = (double) mappings.arrayZ[i].coords[1].to_float ();
395
0
      if (!i) return v - from + to;
396
0
      double prev_from = (double) mappings.arrayZ[i - 1].coords[0].to_float ();
397
0
      double prev_to = (double) mappings.arrayZ[i - 1].coords[1].to_float ();
398
0
      double denom = from - prev_from;
399
0
      if (denom == 0.0) return prev_to;
400
0
      return prev_to + (to - prev_to) * (v - prev_from) / denom;
401
0
    }
402
0
  }
403
0
  return v - (double) mappings.arrayZ[len - 1].coords[0].to_float ()
404
0
           + (double) mappings.arrayZ[len - 1].coords[1].to_float ();
405
0
}
Unexecuted instantiation: hb-font.cc:OT::_avar2_map_new_mapping(hb_vector_t<OT::AxisValueMap, false> const&, double)
Unexecuted instantiation: hb-ot-var.cc:OT::_avar2_map_new_mapping(hb_vector_t<OT::AxisValueMap, false> const&, double)
406
407
/* Plain (non-avar) fvar-style normalization of a user value against a
408
 * user-space (min, default, max) triple. */
409
static inline double
410
_avar2_normalize_value (double v, double min, double def, double max)
411
0
{
412
0
  v = hb_clamp (v, min, max);
413
0
  if (v == def) return 0.0;
414
0
  if (v < def) return def == min ? 0.0 : (v - def) / (def - min);
415
0
  return def == max ? 0.0 : (v - def) / (max - def);
416
0
}
Unexecuted instantiation: hb-font.cc:OT::_avar2_normalize_value(double, double, double, double)
Unexecuted instantiation: hb-ot-var.cc:OT::_avar2_normalize_value(double, double, double, double)
417
418
/* Inverse of the above: map a normalized value back to user space. */
419
static inline double
420
_avar2_denormalize_value (double v, double min, double def, double max)
421
0
{
422
0
  if (v == 0.0) return def;
423
0
  return v < 0.0 ? def + v * (def - min) : def + v * (max - def);
424
0
}
Unexecuted instantiation: hb-font.cc:OT::_avar2_denormalize_value(double, double, double, double)
Unexecuted instantiation: hb-ot-var.cc:OT::_avar2_denormalize_value(double, double, double, double)
425
426
/* Estimate the residual offset-compensation error for one restricted axis:
427
 * the max |old-avar1-final - (new-avar1 + offset)| over the retained user
428
 * range, in F2Dot14 units. offset(z) is the piecewise-linear function
429
 * through the knots. The residual is dominated by F2Dot14 requantization of
430
 * a steep retained avar v1 segment (e.g. a moved default compressing part
431
 * of the axis into a narrow z band); offset compensation cannot remove it.
432
 * Used only to pick the better knot set and decide whether to warn, so a
433
 * coarse uniform sample suffices. */
434
static inline unsigned
435
_avar2_estimate_offset_error (const SegmentMaps &old_seg,
436
                              const hb_vector_t<AxisValueMap> &new_mapping,
437
                              double old_min, double old_def, double old_max,
438
                              double new_min, double new_def, double new_max,
439
                              const hb_vector_t<avar2_offset_knot_t> &knots)
440
0
{
441
0
  unsigned max_err = 0;
442
0
  constexpr unsigned samples = 257;
443
0
  for (unsigned i = 0; i < samples; i++)
444
0
  {
445
0
    double u = new_min + (new_max - new_min) * i / (samples - 1);
446
0
    double n_old = _avar2_normalize_value (u, old_min, old_def, old_max);
447
0
    double old_final = (double) old_seg.map_float ((float) n_old);
448
0
    double n_new = _avar2_normalize_value (u, new_min, new_def, new_max);
449
0
    double z = _avar2_map_new_mapping (new_mapping, n_new);
450
0
    double new_final = z + _avar2_eval_offset (knots, z);
451
0
    int err = abs ((int) roundf ((float) (old_final * 16384.0)) -
452
0
                   (int) roundf ((float) (new_final * 16384.0)));
453
0
    if ((unsigned) err > max_err) max_err = err;
454
0
  }
455
0
  return max_err;
456
0
}
Unexecuted instantiation: hb-font.cc:OT::_avar2_estimate_offset_error(OT::SegmentMaps const&, hb_vector_t<OT::AxisValueMap, false> const&, double, double, double, double, double, double, hb_vector_t<OT::avar2_offset_knot_t, false> const&)
Unexecuted instantiation: hb-ot-var.cc:OT::_avar2_estimate_offset_error(OT::SegmentMaps const&, hb_vector_t<OT::AxisValueMap, false> const&, double, double, double, double, double, double, hb_vector_t<OT::avar2_offset_knot_t, false> const&)
457
458
struct avar
459
{
460
  static constexpr hb_tag_t tableTag = HB_OT_TAG_avar;
461
462
0
  bool has_data () const { return version.to_int (); }
463
464
  const SegmentMaps* get_segment_maps () const
465
0
  { return &firstAxisSegmentMaps; }
466
467
  unsigned get_axis_count () const
468
0
  { return axisCount; }
469
470
  bool sanitize (hb_sanitize_context_t *c) const
471
0
  {
472
0
    TRACE_SANITIZE (this);
473
0
    if (!(version.sanitize (c) &&
474
0
    hb_barrier () &&
475
0
    (version.major == 1
476
0
#ifndef HB_NO_AVAR2
477
0
     || version.major == 2
478
0
#endif
479
0
     ) &&
480
0
    c->check_struct (this)))
481
0
      return_trace (false);
482
483
0
    const SegmentMaps *map = &firstAxisSegmentMaps;
484
0
    unsigned int count = axisCount;
485
0
    for (unsigned int i = 0; i < count; i++)
486
0
    {
487
0
      if (unlikely (!map->sanitize (c)))
488
0
  return_trace (false);
489
0
      map = &StructAfter<SegmentMaps> (*map);
490
0
    }
491
492
0
#ifndef HB_NO_AVAR2
493
0
    if (version.major < 2)
494
0
      return_trace (true);
495
0
    hb_barrier ();
496
497
0
    const auto &v2 = * (const avarV2Tail *) map;
498
0
    if (unlikely (!v2.sanitize (c, this)))
499
0
      return_trace (false);
500
0
#endif
501
502
0
    return_trace (true);
503
0
  }
504
505
  void map_coords_16_16 (int *coords, unsigned int coords_length) const
506
0
  {
507
0
    unsigned int count = hb_min (coords_length, axisCount);
508
509
0
    const SegmentMaps *map = &firstAxisSegmentMaps;
510
0
    for (unsigned int i = 0; i < count; i++)
511
0
    {
512
0
      coords[i] = roundf (map->map_float (coords[i] / 65536.f) * 65536.f);
513
0
      map = &StructAfter<SegmentMaps> (*map);
514
0
    }
515
516
0
#ifndef HB_NO_AVAR2
517
0
    if (version.major < 2)
518
0
      return;
519
0
    hb_barrier ();
520
521
0
    for (; count < axisCount; count++)
522
0
      map = &StructAfter<SegmentMaps> (*map);
523
524
0
    const auto &v2 = * (const avarV2Tail *) map;
525
526
0
    const auto &varidx_map = this+v2.varIdxMap;
527
0
    const auto &var_store = this+v2.varStore;
528
0
    auto *var_store_cache = var_store.create_cache ();
529
530
0
    hb_vector_t<int> coords_2_14;
531
0
    coords_2_14.resize (coords_length);
532
0
    for (unsigned i = 0; i < coords_length; i++)
533
0
      coords_2_14[i] = roundf (coords[i] / 4.f); // 16.16 -> 2.14
534
535
0
    hb_vector_t<int> out;
536
0
    out.alloc (coords_length);
537
0
    for (unsigned i = 0; i < coords_length; i++)
538
0
    {
539
0
      int v = coords[i];
540
0
      uint32_t varidx = varidx_map.map (i);
541
0
      float delta = var_store.get_delta (varidx, coords_2_14.arrayZ, coords_2_14.length, var_store_cache);
542
      /* Apply the delta unclamped and clamp only the result to [-1, +1],
543
       * matching fontTools. Since inputs and results are in [-1, +1],
544
       * deltas beyond ±2 are equivalent to ±2; clamp to that range only
545
       * to keep the float->int conversion safe. */
546
0
      float d = hb_clamp (delta * 4, -(float) (1<<17), +(float) (1<<17)); // 2.14 -> 16.16
547
0
      v += (int) roundf (d);
548
0
      v = hb_clamp (v, -(1<<16), +(1<<16));
549
0
      out.push (v);
550
0
    }
551
0
    for (unsigned i = 0; i < coords_length; i++)
552
0
      coords[i] = out[i];
553
554
0
    OT::ItemVariationStore::destroy_cache (var_store_cache);
555
0
#endif
556
0
  }
557
558
0
  bool has_v2_data () const { return version.major > 1; }
559
560
  /* Resolve the avar2 VarStore and VarIdxMap, for the subset planner. */
561
  bool get_v2_store_and_map (const ItemVariationStore **store,
562
                             const DeltaSetIndexMap **varidx_map) const
563
0
  {
564
0
#ifndef HB_NO_AVAR2
565
0
    if (version.major < 2) return false;
566
0
    const SegmentMaps *map = &firstAxisSegmentMaps;
567
0
    for (unsigned i = 0; i < axisCount; i++)
568
0
      map = &StructAfter<SegmentMaps> (*map);
569
0
    const auto &v2 = * (const avarV2Tail *) map;
570
0
    *store = &(this+v2.varStore);
571
0
    *varidx_map = &(this+v2.varIdxMap);
572
0
    return true;
573
0
#else
574
0
    return false;
575
0
#endif
576
0
  }
577
578
  // axis normalization is done in 2.14 here
579
  // TODO: deprecate this API once fonttools is updated to use 16.16 normalization
580
  bool map_coords_2_14 (float *coords, unsigned int coords_length,
581
      bool v1_only = false) const
582
0
  {
583
0
    hb_vector_t<int> coords_2_14;
584
0
    if (!v1_only && !coords_2_14.resize (coords_length)) return false;
585
0
    unsigned int count = hb_min (coords_length, axisCount);
586
0
587
0
    const SegmentMaps *map = &firstAxisSegmentMaps;
588
0
    for (unsigned int i = 0; i < count; i++)
589
0
    {
590
0
      int v = roundf (map->map_float (coords[i]) * 16384.f);
591
0
      if (!v1_only)
592
0
  coords_2_14[i] = v;
593
0
      coords[i] = v / 16384.f;
594
0
      map = &StructAfter<SegmentMaps> (*map);
595
0
    }
596
0
597
0
    if (v1_only)
598
0
      return true;
599
0
600
0
#ifndef HB_NO_AVAR2
601
0
    if (version.major < 2)
602
0
      return true;
603
0
    hb_barrier ();
604
0
605
0
    for (; count < axisCount; count++)
606
0
      map = &StructAfter<SegmentMaps> (*map);
607
0
608
0
    const auto &v2 = * (const avarV2Tail *) map;
609
0
610
0
    const auto &varidx_map = this+v2.varIdxMap;
611
0
    const auto &var_store = this+v2.varStore;
612
0
    auto *var_store_cache = var_store.create_cache ();
613
0
614
0
    for (unsigned i = 0; i < coords_length; i++)
615
0
    {
616
0
      int v = coords_2_14[i];
617
0
      uint32_t varidx = varidx_map.map (i);
618
0
      float delta = var_store.get_delta (varidx, coords_2_14.arrayZ, coords_2_14.length, var_store_cache);
619
0
      /* As above: apply the delta unclamped (±2 covers every useful case)
620
0
       * and clamp the result to [-1, +1], matching fontTools. */
621
0
      v += (int) hb_clamp (roundf (delta), -(float) (1<<15), +(float) (1<<15));
622
0
      v = hb_clamp (v, -(1<<14), +(1<<14));
623
0
      coords[i] = v / 16384.f;
624
0
    }
625
0
626
0
    OT::ItemVariationStore::destroy_cache (var_store_cache);
627
0
    return true;
628
0
#else
629
0
    return version.major < 2;
630
0
#endif
631
0
  }
632
633
  bool subset (hb_subset_context_t *c) const
634
0
  {
635
0
    TRACE_SUBSET (this);
636
0
    unsigned retained_axis_count = c->plan->axes_index_map.get_population ();
637
0
    if (!retained_axis_count) //all axes are pinned/dropped
638
0
      return_trace (false);
639
0
640
0
    avar *out = c->serializer->allocate_min<avar> ();
641
0
    if (unlikely (!out)) return_trace (false);
642
0
643
0
    out->version.major = c->plan->has_avar2 ? 2 : 1;
644
0
    out->version.minor = 0;
645
0
    if (!c->serializer->check_assign (out->axisCount, retained_axis_count, HB_SERIALIZE_ERROR_INT_OVERFLOW))
646
0
      return_trace (false);
647
0
648
0
    /* For avar2, keep the instantiated v1 mappings around; offset
649
0
     * compensation needs them to locate the new mappings' kinks. */
650
0
    hb_vector_t<hb_vector_t<AxisValueMap>> new_mappings;
651
0
    if (c->plan->has_avar2 && !new_mappings.resize (axisCount))
652
0
      return_trace (false);
653
0
654
0
    const hb_map_t& axes_index_map = c->plan->axes_index_map;
655
0
    const SegmentMaps *map = &firstAxisSegmentMaps;
656
0
    unsigned count = axisCount;
657
0
    for (unsigned int i = 0; i < count; i++)
658
0
    {
659
0
      if (axes_index_map.has (i))
660
0
      {
661
0
        hb_tag_t *axis_tag;
662
0
        if (!c->plan->axes_old_index_tag_map.has (i, &axis_tag))
663
0
          return_trace (false);
664
0
665
0
        Triple *axis_location;
666
0
        if (c->plan->has_avar2 &&
667
0
            c->plan->user_axes_location.has (*axis_tag, &axis_location) &&
668
0
            axis_location->is_point ())
669
0
        {
670
0
          /* Pinned axis in avar2 mode: serialize identity segment map
671
0
           * {-1->-1, 0->0, 1->1}. The axis is kept in fvar as hidden,
672
0
           * so avar needs a segment map entry for it. */
673
0
          auto *identity_map = c->serializer->start_embed<SegmentMaps> ();
674
0
          if (unlikely (!c->serializer->extend_min (identity_map)))
675
0
            return_trace (false);
676
0
          AxisValueMap m;
677
0
          m.set_mapping (-1.f, -1.f);
678
0
          if (!m.serialize (c->serializer)) return_trace (false);
679
0
          m.set_mapping (0.f, 0.f);
680
0
          if (!m.serialize (c->serializer)) return_trace (false);
681
0
          m.set_mapping (1.f, 1.f);
682
0
          if (!m.serialize (c->serializer)) return_trace (false);
683
0
          if (!c->serializer->check_assign (identity_map->len, 3u,
684
0
                                            HB_SERIALIZE_ERROR_INT_OVERFLOW))
685
0
            return_trace (false);
686
0
        }
687
0
        else
688
0
        {
689
0
          /* Restricted or free axis: use standard SegmentMaps::subset() */
690
0
          if (!map->subset (c, *axis_tag,
691
0
                            c->plan->has_avar2 ? &new_mappings[i] : nullptr))
692
0
            return_trace (false);
693
0
        }
694
0
      }
695
0
      map = &StructAfter<SegmentMaps> (*map);
696
0
    }
697
0
698
0
    if (c->plan->has_avar2)
699
0
      return_trace (_subset_avar2 (c, new_mappings));
700
0
701
0
    return_trace (true);
702
0
  }
703
704
  private:
705
  struct avar2_index_map_plan_t
706
  {
707
    bool init (const hb_vector_t<uint32_t> &varidx_mapping,
708
         const hb_map_t &axes_index_map,
709
         unsigned axis_count)
710
0
    {
711
0
      if (!output_map.alloc (axes_index_map.get_population ()))
712
0
  return false;
713
0
714
0
      bool has_no_variation = false;
715
0
      unsigned max_outer = 0, max_inner = 0;
716
0
      for (unsigned i = 0; i < axis_count; i++)
717
0
      {
718
0
  if (!axes_index_map.has (i)) continue;
719
0
  uint32_t varidx = varidx_mapping[i];
720
0
  output_map.push (varidx);
721
0
  if (varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
722
0
  {
723
0
    has_no_variation = true;
724
0
    continue;
725
0
  }
726
0
  max_outer = hb_max (max_outer, varidx >> 16);
727
0
  max_inner = hb_max (max_inner, varidx & 0xFFFF);
728
0
      }
729
0
      if (output_map.in_error () ||
730
0
    output_map.length != axes_index_map.get_population ())
731
0
  return false;
732
0
733
0
      if (has_no_variation)
734
0
      {
735
0
  width = 4;
736
0
  inner_bit_count = 16;
737
0
      }
738
0
      else
739
0
      {
740
0
  inner_bit_count = hb_max (1u, hb_bit_storage (max_inner));
741
0
  unsigned outer_bit_count = hb_max (1u, hb_bit_storage (max_outer));
742
0
  width = hb_clamp ((inner_bit_count + outer_bit_count + 7) / 8, 1u, 4u);
743
0
  if (inner_bit_count + outer_bit_count > width * 8)
744
0
    inner_bit_count = width * 8 - outer_bit_count;
745
0
      }
746
0
      return true;
747
0
    }
748
749
0
    unsigned get_inner_bit_count () const { return inner_bit_count; }
750
0
    unsigned get_width () const { return width; }
751
0
    hb_array_t<const uint32_t> get_output_map () const { return output_map.as_array (); }
752
753
    unsigned inner_bit_count = 1;
754
    unsigned width = 1;
755
    hb_vector_t<uint32_t> output_map;
756
  };
757
758
  bool _subset_avar2 (hb_subset_context_t *c,
759
                      const hb_vector_t<hb_vector_t<AxisValueMap>> &new_mappings) const
760
0
  {
761
0
#if defined (HB_NO_VAR) || defined (HB_NO_AVAR2)
762
0
    /* Not reachable: the plan never sets has_avar2 in these configurations. */
763
0
    return false;
764
0
#else
765
0
766
0
    /* 1. Locate original avar2 data, keeping per-axis old segment maps */
767
0
    hb_vector_t<const SegmentMaps *> old_seg_maps;
768
0
    if (!old_seg_maps.alloc (axisCount)) return false;
769
0
    const SegmentMaps *map = &firstAxisSegmentMaps;
770
0
    for (unsigned i = 0; i < axisCount; i++)
771
0
    {
772
0
      old_seg_maps.push (map);
773
0
      map = &StructAfter<SegmentMaps> (*map);
774
0
    }
775
0
776
0
    const auto &v2 = * (const avarV2Tail *) map;
777
0
    const auto &varidx_map = this+v2.varIdxMap;
778
0
    const auto &var_store = this+v2.varStore;
779
0
780
0
    auto fvar_axes = c->plan->source->table.fvar->get_axes ();
781
0
782
0
    /* 2. Compute default deltas by evaluating VarStore at old defaults */
783
0
    hb_vector_t<int> default_coords;
784
0
    if (!default_coords.resize (axisCount)) return false;
785
0
    for (unsigned i = 0; i < axisCount; i++)
786
0
    {
787
0
      hb_tag_t *axis_tag;
788
0
      if (c->plan->axes_old_index_tag_map.has (i, &axis_tag) &&
789
0
          c->plan->old_intermediates.has (*axis_tag))
790
0
      {
791
0
        float d_i = (float) c->plan->old_intermediates.get (*axis_tag).middle;
792
0
        default_coords[i] = roundf (d_i * 16384.f);
793
0
      }
794
0
      else
795
0
        default_coords[i] = 0;
796
0
    }
797
0
798
0
    auto *store_cache = var_store.create_cache ();
799
0
    hb_vector_t<float> default_deltas;
800
0
    if (!default_deltas.resize (axisCount)) {
801
0
      ItemVariationStore::destroy_cache (store_cache);
802
0
      return false;
803
0
    }
804
0
    for (unsigned i = 0; i < axisCount; i++)
805
0
    {
806
0
      uint32_t varidx = varidx_map.map (i);
807
0
      if (varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
808
0
        default_deltas[i] = 0.f;
809
0
      else
810
0
        default_deltas[i] = var_store.get_delta (varidx, default_coords.arrayZ,
811
0
                                                  default_coords.length, store_cache);
812
0
    }
813
0
    ItemVariationStore::destroy_cache (store_cache);
814
0
815
0
    /* 3. Rebase IVS regions */
816
0
    item_variations_t item_vars;
817
0
    if (!item_vars.create_from_item_varstore (var_store, c->plan->axes_old_index_tag_map))
818
0
      return false;
819
0
    if (!item_vars.instantiate_tuple_vars (c->plan->old_intermediates,
820
0
                                           c->plan->axes_triple_distances,
821
0
                                           false))
822
0
      return false;
823
0
824
0
    /* 4. Self-contained pinned axes (whose final coordinate is constant over
825
0
     * the retained box) were detected at plan time
826
0
     * (_compute_avar2_reachable_ranges) and removed from axes_index_map.
827
0
     * They are skipped below; their constant contribution is baked into the
828
0
     * other variation tables by standard instancing at the plan's
829
0
     * axes_location/normalized_coords. */
830
0
831
0
    /* 5. Build per-axis varIdx mapping (may create new VarDatas) */
832
0
    hb_vector_t<uint32_t> new_varidx_mapping;
833
0
    if (!new_varidx_mapping.resize (axisCount)) return false;
834
0
    for (unsigned i = 0; i < axisCount; i++)
835
0
      new_varidx_mapping[i] = varidx_map.map (i);
836
0
837
0
    /* 5.5. Privatize shared varIdx delta rows before adding offset
838
0
     * compensation. avar2's VarIdxMap may map several fvar axes to the SAME
839
0
     * IVS delta row. Writing one axis's offset-compensation deltas into a
840
0
     * shared row would corrupt every other axis that reads that row. So give
841
0
     * each offset-receiving axis whose row is shared its own private copy of
842
0
     * the row (identical contents, preserving the rebased deltas), then
843
0
     * repoint its varIdx. Sharers keep the clean row; the varstore
844
0
     * optimization pass re-merges identical rows afterwards. */
845
0
    hb_hashmap_t<uint32_t, unsigned> varidx_ref_count;
846
0
    for (unsigned i = 0; i < axisCount; i++)
847
0
    {
848
0
      if (!c->plan->axes_index_map.has (i)) continue; /* self-contained: dropped */
849
0
      uint32_t varidx = new_varidx_mapping[i];
850
0
      if (varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX) continue;
851
0
      unsigned *count;
852
0
      if (varidx_ref_count.has (varidx, &count))
853
0
        (*count)++;
854
0
      else if (!varidx_ref_count.set (varidx, 1))
855
0
        return false;
856
0
    }
857
0
    for (unsigned i = 0; i < axisCount; i++)
858
0
    {
859
0
      hb_tag_t *axis_tag_ptr;
860
0
      if (!c->plan->axes_old_index_tag_map.has (i, &axis_tag_ptr))
861
0
        return false;
862
0
      /* Only axes that will receive offset compensation (restricted or
863
0
       * pinned) can contaminate a shared row. */
864
0
      if (!c->plan->axes_index_map.has (i) ||
865
0
          !c->plan->user_axes_location.has (*axis_tag_ptr))
866
0
        continue;
867
0
      uint32_t varidx = new_varidx_mapping[i];
868
0
      if (varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
869
0
        continue; /* gets a fresh, private VarData in the offset loop below */
870
0
      unsigned *count;
871
0
      if (!varidx_ref_count.has (varidx, &count) || *count <= 1)
872
0
        continue; /* sole owner: safe to write offsets in place */
873
0
      unsigned outer = varidx >> 16;
874
0
      unsigned new_inner = item_vars.duplicate_row (outer, varidx & 0xFFFF);
875
0
      if (unlikely (new_inner == (unsigned) -1)) return false;
876
0
      new_varidx_mapping[i] = (outer << 16) | new_inner;
877
0
      (*count)--;
878
0
    }
879
0
880
0
    /* 6. Add offset compensation tuples.
881
0
     * Track processed (outer,inner) pairs to avoid adding duplicate biases
882
0
     * when multiple axes share the same varIdx. */
883
0
    hb_set_t processed_varidxes;
884
0
    for (unsigned i = 0; i < axisCount; i++)
885
0
    {
886
0
      hb_tag_t *axis_tag_ptr;
887
0
      if (!c->plan->axes_old_index_tag_map.has (i, &axis_tag_ptr))
888
0
        return false;
889
0
      hb_tag_t axis_tag = *axis_tag_ptr;
890
0
891
0
      /* Self-contained pinned axes are removed from fvar/avar; their
892
0
       * contribution is baked into the variation tables instead. */
893
0
      if (!c->plan->axes_index_map.has (i))
894
0
        continue;
895
0
896
0
      Triple *new_user;
897
0
      if (c->plan->user_axes_location.has (axis_tag, &new_user))
898
0
      {
899
0
        /* This axis is being restricted or pinned */
900
0
        Triple *old_int;
901
0
        if (!c->plan->old_intermediates.has (axis_tag, &old_int))
902
0
          return false;
903
0
904
0
        float a_i = (float) old_int->minimum;
905
0
        float d_i = (float) old_int->middle;
906
0
        float b_i = (float) old_int->maximum;
907
0
908
0
        int d_int = roundf (d_i * 16384.f);
909
0
910
0
        bool is_pinned = new_user->is_point ();
911
0
912
0
        uint32_t varidx = new_varidx_mapping[i];
913
0
        unsigned outer, inner, item_count;
914
0
915
0
        if (varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
916
0
        {
917
0
          /* No existing avar2 mapping. Create new VarData. */
918
0
          outer = item_vars.add_vardata (1);
919
0
          inner = 0;
920
0
          item_count = 1;
921
0
          new_varidx_mapping[i] = (outer << 16) | inner;
922
0
          default_deltas[i] = 0.f; /* no prior default delta */
923
0
        }
924
0
        else
925
0
        {
926
0
          outer = varidx >> 16;
927
0
          inner = varidx & 0xFFFF;
928
0
          item_count = item_vars.get_item_count (outer);
929
0
        }
930
0
931
0
        /* Empty-region bias: d_int + round(defaultDelta) */
932
0
        int bias = d_int + (int) roundf (default_deltas[i]);
933
0
        if (bias != 0)
934
0
        {
935
0
          hb_hashmap_t<hb_tag_t, Triple> empty_region;
936
0
          item_vars.add_tuple (outer, std::move (empty_region),
937
0
                               inner, bias, item_count);
938
0
        }
939
0
940
0
        if (!is_pinned)
941
0
        {
942
0
          /* Offset compensation encodes, as avar2 deltas on this axis, the
943
0
           * piecewise-linear function offset(z) = inv_renorm(z) - z, where
944
0
           * inv_renorm maps a new intermediate coordinate z back to the old
945
0
           * intermediate coordinate. It is known at these knots in the new
946
0
           * intermediate space:
947
0
           *   z = -1  ->  a_i + 1   (new minimum)
948
0
           *   z =  0  ->  d_i       (new default)
949
0
           *   z = +1  ->  b_i - 1   (new maximum)
950
0
           */
951
0
          hb_vector_t<avar2_offset_knot_t> knots;
952
0
          _avar2_add_knot (knots, -1.0, (double) a_i + 1.0);
953
0
          _avar2_add_knot (knots, 0.0, (double) d_i);
954
0
          _avar2_add_knot (knots, 1.0, (double) b_i - 1.0);
955
0
956
0
          const hb_vector_t<AxisValueMap> &new_mapping = new_mappings[i];
957
0
958
0
          float old_min = 0.f, old_def = 0.f, old_max = 0.f;
959
0
          if (likely (i < fvar_axes.length))
960
0
            fvar_axes[i].get_coordinates (old_min, old_def, old_max);
961
0
962
0
          /* If the axis default MOVED, inv_renorm also kinks where the OLD
963
0
           * default lands in the new space (the old intermediate coordinate
964
0
           * crosses 0 there), at
965
0
           *   z = z_old  ->  -z_old
966
0
           * Omitting that knot (as a plain two-tent encoding would) makes
967
0
           * interior coordinates wrong. */
968
0
          double z_old = _avar2_normalize_value ((double) old_def,
969
0
                                                 new_user->minimum,
970
0
                                                 new_user->middle,
971
0
                                                 new_user->maximum);
972
0
          z_old = _avar2_map_new_mapping (new_mapping, z_old);
973
0
          z_old = (double) roundf ((float) (z_old * 16384.0)) / 16384.0;
974
0
          if (-1.0 < z_old && z_old < 1.0)
975
0
            _avar2_add_knot (knots, z_old, -z_old);
976
0
977
0
          /* Interior avar v1 breakpoints inside the retained range each put
978
0
           * a kink in offset(z). Sampling only {-1, 0, +1, z_old} would
979
0
           * linearly interpolate across those kinks. The instantiated
980
0
           * mapping keeps exactly the in-range old breakpoints, and the new
981
0
           * mapping kinks at each one's output coordinate; add that z with
982
0
           * its old intermediate value so offset(z) is reproduced at every
983
0
           * kink. */
984
0
          hb_vector_t<avar2_offset_knot_t> with_breakpoints (knots);
985
0
          for (const auto &m : new_mapping)
986
0
          {
987
0
            double from = (double) m.coords[0].to_float ();
988
0
            if (from == -1.0 || from == 0.0 || from == 1.0)
989
0
              continue; /* anchors already seeded */
990
0
            double z = (double) m.coords[1].to_float ();
991
0
            if (!(-1.0 < z && z < 1.0))
992
0
              continue;
993
0
            double user = _avar2_denormalize_value (from,
994
0
                                                    new_user->minimum,
995
0
                                                    new_user->middle,
996
0
                                                    new_user->maximum);
997
0
            double n_old = _avar2_normalize_value (user, old_min, old_def, old_max);
998
0
            double x_old = (double) old_seg_maps[i]->map_float ((float) n_old);
999
0
            x_old = (double) roundf ((float) (x_old * 16384.0)) / 16384.0;
1000
0
            _avar2_add_knot (with_breakpoints, z, x_old - z);
1001
0
          }
1002
0
1003
0
          if (unlikely (knots.in_error () || with_breakpoints.in_error ()))
1004
0
            return false;
1005
0
1006
0
          /* Extra tents cost F2Dot14 rounding, so for a steep segment they
1007
0
           * can add more quantization noise than the structural error they
1008
0
           * remove. Keep the interior breakpoints only when they do not
1009
0
           * increase the estimated residual; this makes the collection a
1010
0
           * strict (never-worse) improvement over the {-1, 0, +1, z_old}
1011
0
           * anchors. Warn when even the better choice is not bit-exact (a
1012
0
           * steep retained segment that cannot be reproduced in F2Dot14). */
1013
0
          unsigned err = _avar2_estimate_offset_error (*old_seg_maps[i], new_mapping,
1014
0
                                                       old_min, old_def, old_max,
1015
0
                                                       new_user->minimum,
1016
0
                                                       new_user->middle,
1017
0
                                                       new_user->maximum,
1018
0
                                                       knots);
1019
0
          if (with_breakpoints.length != knots.length)
1020
0
          {
1021
0
            unsigned err_with = _avar2_estimate_offset_error (*old_seg_maps[i], new_mapping,
1022
0
                                                              old_min, old_def, old_max,
1023
0
                                                              new_user->minimum,
1024
0
                                                              new_user->middle,
1025
0
                                                              new_user->maximum,
1026
0
                                                              with_breakpoints);
1027
0
            if (err_with <= err)
1028
0
            {
1029
0
              knots = std::move (with_breakpoints);
1030
0
              err = err_with;
1031
0
            }
1032
0
          }
1033
0
          if (err > 8)
1034
0
            DEBUG_MSG (SUBSET, nullptr,
1035
0
                       "avar2 offset compensation is approximate for axis %c%c%c%c: "
1036
0
                       "max residual %u F2Dot14 units",
1037
0
                       HB_UNTAG (axis_tag), err);
1038
0
1039
0
          /* Synthesize tents. Adjacent tents evaluate to zero at each
1040
0
           * other's peaks, so each knot's delta is offset(z) - offset(0);
1041
0
           * the base value offset(0) = d_i is carried by the empty-region
1042
0
           * bias above. This reduces to the classic pair of tents
1043
0
           * (-1,-1,0) / (0,+1,+1) when the default is unchanged and there
1044
0
           * are no interior knots. */
1045
0
          for (unsigned k = 0; k < knots.length; k++)
1046
0
          {
1047
0
            double z = knots.arrayZ[k].z;
1048
0
            if (z == 0.0) continue;
1049
0
            int delta = (int) roundf ((float) ((knots.arrayZ[k].offset - (double) d_i) * 16384.0));
1050
0
            if (!delta) continue;
1051
0
            double lower, upper;
1052
0
            if (z > 0.0)
1053
0
            {
1054
0
              lower = knots.arrayZ[k - 1].z;
1055
0
              upper = k + 1 < knots.length ? knots.arrayZ[k + 1].z : z;
1056
0
            }
1057
0
            else
1058
0
            {
1059
0
              upper = knots.arrayZ[k + 1].z;
1060
0
              lower = k > 0 ? knots.arrayZ[k - 1].z : z;
1061
0
            }
1062
0
            hb_hashmap_t<hb_tag_t, Triple> region;
1063
0
            if (unlikely (!region.set (axis_tag, Triple (lower, z, upper))))
1064
0
              return false;
1065
0
            item_vars.add_tuple (outer, std::move (region),
1066
0
                                 inner, delta, item_count);
1067
0
          }
1068
0
        }
1069
0
      }
1070
0
      else
1071
0
      {
1072
0
        /* Free or private axis — not being restricted.
1073
0
         * If it has a non-zero default delta, add it back as a bias.
1074
0
         * Skip if this (outer,inner) was already processed (shared varIdx). */
1075
0
        uint32_t varidx = new_varidx_mapping[i];
1076
0
        if (varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
1077
0
          continue;
1078
0
1079
0
        if (processed_varidxes.has (varidx))
1080
0
          continue;
1081
0
        processed_varidxes.add (varidx);
1082
0
1083
0
        unsigned outer = varidx >> 16;
1084
0
        unsigned inner = varidx & 0xFFFF;
1085
0
        int dd = (int) roundf (default_deltas[i]);
1086
0
        if (dd != 0)
1087
0
        {
1088
0
          unsigned item_count = item_vars.get_item_count (outer);
1089
0
          hb_hashmap_t<hb_tag_t, Triple> empty_region;
1090
0
          item_vars.add_tuple (outer, std::move (empty_region),
1091
0
                               inner, dd, item_count);
1092
0
        }
1093
0
      }
1094
0
    }
1095
0
1096
0
    /* 7. Finalize: build region list + convert to varstore */
1097
0
    if (!item_vars.build_region_list ()) return false;
1098
0
    if (!item_vars.as_item_varstore (true /* optimize */,
1099
0
                                     false /* use_no_variation_idx */))
1100
0
      return false;
1101
0
1102
0
    /* 8. Apply varidx_map optimization remapping */
1103
0
    const auto &opt_varidx_map = item_vars.get_varidx_map ();
1104
0
    for (unsigned i = 0; i < axisCount; i++)
1105
0
    {
1106
0
      uint32_t varidx = new_varidx_mapping[i];
1107
0
      if (varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
1108
0
        continue;
1109
0
      uint32_t *new_idx;
1110
0
      if (opt_varidx_map.has (varidx, &new_idx))
1111
0
        new_varidx_mapping[i] = *new_idx;
1112
0
    }
1113
0
1114
0
    /* 9. Serialize avarV2Tail. Entries cover the retained axes only;
1115
0
     * self-contained pinned axes are removed from fvar. */
1116
0
    avar2_index_map_plan_t index_map_plan;
1117
0
    if (!index_map_plan.init (new_varidx_mapping,
1118
0
            c->plan->axes_index_map,
1119
0
            axisCount))
1120
0
      return false;
1121
0
1122
0
    auto *tail = c->serializer->allocate_size<avarV2Tail> (avarV2Tail::static_size);
1123
0
    if (unlikely (!tail)) return false;
1124
0
    if (!tail->varIdxMap.serialize_serialize (c->serializer, index_map_plan))
1125
0
      return false;
1126
0
    if (!tail->varStore.serialize_serialize (c->serializer,
1127
0
               item_vars.has_long_word (),
1128
0
               c->plan->axis_tags,
1129
0
               item_vars.get_region_list (),
1130
0
               item_vars.get_vardata_encodings ()))
1131
0
      return false;
1132
0
1133
0
    return true;
1134
0
#endif
1135
0
  }
1136
1137
  public:
1138
1139
  protected:
1140
  FixedVersion<>version;  /* Version of the avar table
1141
         * initially set to 0x00010000u */
1142
  HBUINT16  reserved; /* This field is permanently reserved. Set to 0. */
1143
  HBUINT16  axisCount;  /* The number of variation axes in the font. This
1144
         * must be the same number as axisCount in the
1145
         * 'fvar' table. */
1146
  SegmentMaps firstAxisSegmentMaps;
1147
1148
  public:
1149
  DEFINE_SIZE_MIN (8);
1150
};
1151
1152
} /* namespace OT */
1153
1154
1155
#endif /* HB_OT_VAR_AVAR_TABLE_HH */