Coverage Report

Created: 2025-11-11 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/harfbuzz/src/hb-subset-instancer-iup.cc
Line
Count
Source
1
/*
2
 * Copyright © 2024  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
25
#include "hb-subset-instancer-iup.hh"
26
27
#include "hb-bit-page.hh"
28
29
using hb_iup_set_t = hb_bit_page_t;
30
31
/* This file is a straight port of the following:
32
 *
33
 * https://github.com/fonttools/fonttools/blob/main/Lib/fontTools/varLib/iup.py
34
 *
35
 * Where that file returns optimzied deltas vector, we return optimized
36
 * referenced point indices.
37
 */
38
39
constexpr static unsigned MAX_LOOKBACK = 8;
40
41
static void _iup_contour_bound_forced_set (const hb_array_t<const contour_point_t> contour_points,
42
                                           const hb_array_t<const int> x_deltas,
43
                                           const hb_array_t<const int> y_deltas,
44
                                           hb_iup_set_t& forced_set, /* OUT */
45
                                           double tolerance = 0.0)
46
0
{
47
0
  unsigned len = contour_points.length;
48
0
  unsigned next_i = 0;
49
0
  for (int i = len - 1; i >= 0; i--)
50
0
  {
51
0
    unsigned last_i = (len + i -1) % len;
52
0
    for (unsigned j = 0; j < 2; j++)
53
0
    {
54
0
      double cj, lcj, ncj;
55
0
      int dj, ldj, ndj;
56
0
      if (j == 0)
57
0
      {
58
0
        cj = static_cast<double> (contour_points.arrayZ[i].x);
59
0
        dj = x_deltas.arrayZ[i];
60
0
        lcj = static_cast<double> (contour_points.arrayZ[last_i].x);
61
0
        ldj = x_deltas.arrayZ[last_i];
62
0
        ncj = static_cast<double> (contour_points.arrayZ[next_i].x);
63
0
        ndj = x_deltas.arrayZ[next_i];
64
0
      }
65
0
      else
66
0
      {
67
0
        cj = static_cast<double> (contour_points.arrayZ[i].y);
68
0
        dj = y_deltas.arrayZ[i];
69
0
        lcj = static_cast<double> (contour_points.arrayZ[last_i].y);
70
0
        ldj = y_deltas.arrayZ[last_i];
71
0
        ncj = static_cast<double> (contour_points.arrayZ[next_i].y);
72
0
        ndj = y_deltas.arrayZ[next_i];
73
0
      }
74
75
0
      double c1, c2;
76
0
      int d1, d2;
77
0
      if (lcj <= ncj)
78
0
      {
79
0
        c1 = lcj;
80
0
        c2 = ncj;
81
0
        d1 = ldj;
82
0
        d2 = ndj;
83
0
      }
84
0
      else
85
0
      {
86
0
        c1 = ncj;
87
0
        c2 = lcj;
88
0
        d1 = ndj;
89
0
        d2 = ldj;
90
0
      }
91
92
0
      bool force = false;
93
0
      if (c1 == c2)
94
0
      {
95
0
        if (abs (d1 - d2) > tolerance && abs (dj) > tolerance)
96
0
          force = true;
97
0
      }
98
0
      else if (c1 <= cj && cj <= c2)
99
0
      {
100
0
        if (!(hb_min (d1, d2) - tolerance <= dj &&
101
0
              dj <= hb_max (d1, d2) + tolerance))
102
0
          force = true;
103
0
      }
104
0
      else
105
0
      {
106
0
        if (d1 != d2)
107
0
        {
108
0
          if (cj < c1)
109
0
          {
110
0
            if (abs (dj) > tolerance &&
111
0
                abs (dj - d1) > tolerance &&
112
0
                ((dj - tolerance < d1) != (d1 < d2)))
113
0
                force = true;
114
0
          }
115
0
          else
116
0
          {
117
0
            if (abs (dj) > tolerance &&
118
0
                abs (dj - d2) > tolerance &&
119
0
                ((d2 < dj + tolerance) != (d1 < d2)))
120
0
              force = true;
121
0
          }
122
0
        }
123
0
      }
124
125
0
      if (force)
126
0
      {
127
0
        forced_set.add (i);
128
0
        break;
129
0
      }
130
0
    }
131
0
    next_i = i;
132
0
  }
133
0
}
134
135
template <typename T,
136
          hb_enable_if (hb_is_trivially_copyable (T))>
137
static bool rotate_array (const hb_array_t<const T>& org_array,
138
                          int k,
139
                          hb_vector_t<T>& out)
140
0
{
141
0
  unsigned n = org_array.length;
142
0
  if (!n) return true;
143
0
  if (unlikely (!out.resize_dirty  (n)))
144
0
    return false;
145
146
0
  unsigned item_size = hb_static_size (T);
147
0
  if (k < 0)
148
0
    k = n - (-k) % n;
149
0
  else
150
0
    k %= n;
151
152
0
  hb_memcpy ((void *) out.arrayZ, (const void *) (org_array.arrayZ + n - k), k * item_size);
153
0
  hb_memcpy ((void *) (out.arrayZ + k), (const void *) org_array.arrayZ, (n - k) * item_size);
154
0
  return true;
155
0
}
Unexecuted instantiation: hb-subset-instancer-iup.cc:_ZL12rotate_arrayI15contour_point_tTnPN12hb_enable_ifIXsr3std21is_trivially_copyableIT_EE5valueEvE4typeELPv0EEbRK10hb_array_tIKS2_EiR11hb_vector_tIS2_Lb0EE
Unexecuted instantiation: hb-subset-instancer-iup.cc:_ZL12rotate_arrayIiTnPN12hb_enable_ifIXsr3std21is_trivially_copyableIT_EE5valueEvE4typeELPv0EEbRK10hb_array_tIKS1_EiR11hb_vector_tIS1_Lb0EE
Unexecuted instantiation: hb-subset-instancer-iup.cc:_ZL12rotate_arrayIbTnPN12hb_enable_ifIXsr3std21is_trivially_copyableIT_EE5valueEvE4typeELPv0EEbRK10hb_array_tIKS1_EiR11hb_vector_tIS1_Lb0EE
156
157
static bool rotate_set (const hb_iup_set_t& org_set,
158
                        int k,
159
                        unsigned n,
160
                        hb_iup_set_t& out)
161
0
{
162
0
  if (!n) return false;
163
0
  k %= n;
164
0
  if (k < 0)
165
0
    k = n + k;
166
167
0
  if (k == 0)
168
0
  {
169
0
    out = org_set;
170
0
  }
171
0
  else
172
0
  {
173
0
    for (unsigned v : org_set)
174
0
      out.add ((v + k) % n);
175
0
  }
176
0
  return true;
177
0
}
178
179
/* Given two reference coordinates (start and end of contour_points array),
180
 * output interpolated deltas for points in between */
181
static bool _iup_segment (const hb_array_t<const contour_point_t> contour_points,
182
                          const hb_array_t<const int> x_deltas,
183
                          const hb_array_t<const int> y_deltas,
184
                          const contour_point_t& p1, const contour_point_t& p2,
185
                          int p1_dx, int p2_dx,
186
                          int p1_dy, int p2_dy,
187
        double tolerance_sq,
188
                          hb_vector_t<double>& interp_x_deltas, /* OUT */
189
                          hb_vector_t<double>& interp_y_deltas /* OUT */)
190
0
{
191
0
  unsigned n = contour_points.length;
192
0
  if (unlikely (!interp_x_deltas.resize_dirty  (n) ||
193
0
                !interp_y_deltas.resize_dirty  (n)))
194
0
    return false;
195
196
0
  for (unsigned j = 0; j < 2; j++)
197
0
  {
198
0
    float contour_point_t::* xp;
199
0
    double x1, x2, d1, d2;
200
0
    const int *in;
201
0
    double *out;
202
0
    if (j == 0)
203
0
    {
204
0
      xp = &contour_point_t::x;
205
0
      x1 = static_cast<double> (p1.x);
206
0
      x2 = static_cast<double> (p2.x);
207
0
      d1 = p1_dx;
208
0
      d2 = p2_dx;
209
0
      in = x_deltas.arrayZ;
210
0
      out = interp_x_deltas.arrayZ;
211
0
    }
212
0
    else
213
0
    {
214
0
      xp = &contour_point_t::y;
215
0
      x1 = static_cast<double> (p1.y);
216
0
      x2 = static_cast<double> (p2.y);
217
0
      d1 = p1_dy;
218
0
      d2 = p2_dy;
219
0
      in = y_deltas.arrayZ;
220
0
      out = interp_y_deltas.arrayZ;
221
0
    }
222
223
0
    if (x1 == x2)
224
0
    {
225
0
      if (d1 == d2)
226
0
      {
227
0
        for (unsigned i = 0; i < n; i++)
228
0
          out[i] = d1;
229
0
      }
230
0
      else
231
0
      {
232
0
        for (unsigned i = 0; i < n; i++)
233
0
          out[i] = 0.0;
234
0
      }
235
0
      continue;
236
0
    }
237
238
0
    if (x1 > x2)
239
0
    {
240
0
      hb_swap (x1, x2);
241
0
      hb_swap (d1, d2);
242
0
    }
243
244
0
    double scale = (d2 - d1) / (x2 - x1);
245
0
    for (unsigned i = 0; i < n; i++)
246
0
    {
247
0
      double x = (double) (contour_points.arrayZ[i].*xp);
248
0
      double d;
249
0
      if (x <= x1)
250
0
        d = d1;
251
0
      else if (x >= x2)
252
0
        d = d2;
253
0
      else
254
0
        d = d1 + (x - x1) * scale;
255
256
0
      out[i] = d;
257
0
      double err = d - in[i];
258
0
      if (err * err > tolerance_sq)
259
0
  return false;
260
0
    }
261
0
  }
262
0
  return true;
263
0
}
264
265
static bool _can_iup_in_between (const hb_array_t<const contour_point_t> contour_points,
266
                                 const hb_array_t<const int> x_deltas,
267
                                 const hb_array_t<const int> y_deltas,
268
                                 const contour_point_t& p1, const contour_point_t& p2,
269
                                 int p1_dx, int p2_dx,
270
                                 int p1_dy, int p2_dy,
271
                                 double tolerance_sq,
272
         hb_vector_t<double> &interp_x_deltas, /* scratch */
273
         hb_vector_t<double> &interp_y_deltas /* scratch */)
274
0
{
275
0
  if (!_iup_segment (contour_points, x_deltas, y_deltas,
276
0
                     p1, p2, p1_dx, p2_dx, p1_dy, p2_dy,
277
0
         tolerance_sq,
278
0
                     interp_x_deltas, interp_y_deltas))
279
0
    return false;
280
281
0
  unsigned num = contour_points.length;
282
283
0
  for (unsigned i = 0; i < num; i++)
284
0
  {
285
0
    double dx = static_cast<double> (x_deltas.arrayZ[i]) - interp_x_deltas.arrayZ[i];
286
0
    double dy = static_cast<double> (y_deltas.arrayZ[i]) - interp_y_deltas.arrayZ[i];
287
288
0
    if (dx * dx + dy * dy > tolerance_sq)
289
0
      return false;
290
0
  }
291
0
  return true;
292
0
}
293
294
static bool _iup_contour_optimize_dp (const contour_point_vector_t& contour_points,
295
                                      const hb_vector_t<int>& x_deltas,
296
                                      const hb_vector_t<int>& y_deltas,
297
                                      const hb_iup_set_t& forced_set,
298
                                      double tolerance_sq,
299
                                      unsigned lookback,
300
                                      hb_vector_t<unsigned>& costs, /* OUT */
301
                                      hb_vector_t<int>& chain, /* OUT */
302
              hb_vector_t<double> &interp_x_deltas_scratch,
303
              hb_vector_t<double> &interp_y_deltas_scratch)
304
0
{
305
0
  unsigned n = contour_points.length;
306
0
  if (unlikely (!costs.resize_dirty  (n) ||
307
0
                !chain.resize_dirty  (n)))
308
0
    return false;
309
310
0
  lookback = hb_min (lookback, MAX_LOOKBACK);
311
312
0
  for (unsigned i = 0; i < n; i++)
313
0
  {
314
0
    unsigned best_cost = (i == 0 ? 1 : costs.arrayZ[i-1] + 1);
315
316
0
    costs.arrayZ[i] = best_cost;
317
0
    chain.arrayZ[i] = (i == 0 ? -1 : i - 1);
318
319
0
    if (i > 0 && forced_set.has (i - 1))
320
0
      continue;
321
322
0
    int lookback_index = hb_max ((int) i - (int) lookback + 1, -1);
323
0
    for (int j = i - 2; j >= lookback_index; j--)
324
0
    {
325
0
      unsigned cost = j == -1 ? 1 : costs.arrayZ[j] + 1;
326
      /* num points between i and j */
327
0
      unsigned num_points = i - j - 1;
328
0
      unsigned p1 = (j == -1 ? n - 1 : j);
329
0
      if (cost < best_cost &&
330
0
          _can_iup_in_between (contour_points.as_array ().sub_array (j + 1, num_points),
331
0
                               x_deltas.as_array ().sub_array (j + 1, num_points),
332
0
                               y_deltas.as_array ().sub_array (j + 1, num_points),
333
0
                               contour_points.arrayZ[p1], contour_points.arrayZ[i],
334
0
                               x_deltas.arrayZ[p1], x_deltas.arrayZ[i],
335
0
                               y_deltas.arrayZ[p1], y_deltas.arrayZ[i],
336
0
                               tolerance_sq,
337
0
             interp_x_deltas_scratch, interp_y_deltas_scratch))
338
0
      {
339
0
        best_cost = cost;
340
0
        costs.arrayZ[i] = best_cost;
341
0
        chain.arrayZ[i] = j;
342
0
      }
343
344
0
      if (j > 0 && forced_set.has (j))
345
0
        break;
346
0
    }
347
0
  }
348
0
  return true;
349
0
}
350
351
static bool _iup_contour_optimize (const hb_array_t<const contour_point_t> contour_points,
352
                                   const hb_array_t<const int> x_deltas,
353
                                   const hb_array_t<const int> y_deltas,
354
                                   hb_array_t<bool> opt_indices, /* OUT */
355
                                   double tolerance,
356
           iup_scratch_t &scratch)
357
156
{
358
156
  unsigned n = contour_points.length;
359
156
  if (opt_indices.length != n ||
360
156
      x_deltas.length != n ||
361
156
      y_deltas.length != n)
362
0
    return false;
363
364
156
  if (unlikely (n > hb_iup_set_t::PAGE_BITS))
365
0
    return true; // Refuse to work
366
367
156
  bool all_within_tolerance = true;
368
156
  double tolerance_sq = tolerance * tolerance;
369
236
  for (unsigned i = 0; i < n; i++)
370
156
  {
371
156
    int dx = x_deltas.arrayZ[i];
372
156
    int dy = y_deltas.arrayZ[i];
373
156
    if ((double) dx * dx + (double) dy * dy > tolerance_sq)
374
76
    {
375
76
      all_within_tolerance = false;
376
76
      break;
377
76
    }
378
156
  }
379
380
  /* If all are within tolerance distance, do nothing, opt_indices is
381
   * initilized to false */
382
156
  if (all_within_tolerance)
383
80
    return true;
384
385
  /* If there's exactly one point, return it */
386
76
  if (n == 1)
387
76
  {
388
76
    opt_indices.arrayZ[0] = true;
389
76
    return true;
390
76
  }
391
392
  /* If all deltas are exactly the same, return just one (the first one) */
393
0
  bool all_deltas_are_equal = true;
394
0
  for (unsigned i = 1; i < n; i++)
395
0
    if (x_deltas.arrayZ[i] != x_deltas.arrayZ[0] ||
396
0
        y_deltas.arrayZ[i] != y_deltas.arrayZ[0])
397
0
    {
398
0
      all_deltas_are_equal = false;
399
0
      break;
400
0
    }
401
402
0
  if (all_deltas_are_equal)
403
0
  {
404
0
    opt_indices.arrayZ[0] = true;
405
0
    return true;
406
0
  }
407
408
  /* else, solve the general problem using Dynamic Programming */
409
0
  hb_iup_set_t forced_set;
410
0
  _iup_contour_bound_forced_set (contour_points, x_deltas, y_deltas, forced_set, tolerance);
411
412
0
  hb_vector_t<unsigned> &costs = scratch.costs.reset ();
413
0
  hb_vector_t<int> &chain = scratch.chain.reset ();
414
415
0
  if (!forced_set.is_empty ())
416
0
  {
417
0
    int k = n - 1 - forced_set.get_max ();
418
0
    if (k < 0)
419
0
      return false;
420
421
0
    hb_vector_t<int> &rot_x_deltas = scratch.rot_x_deltas.reset ();
422
0
    hb_vector_t<int> &rot_y_deltas = scratch.rot_y_deltas.reset ();
423
0
    contour_point_vector_t &rot_points = scratch.rot_points;
424
0
    rot_points.reset ();
425
0
    hb_iup_set_t rot_forced_set;
426
0
    if (!rotate_array (contour_points, k, rot_points) ||
427
0
        !rotate_array (x_deltas, k, rot_x_deltas) ||
428
0
        !rotate_array (y_deltas, k, rot_y_deltas) ||
429
0
        !rotate_set (forced_set, k, n, rot_forced_set))
430
0
      return false;
431
432
0
    if (!_iup_contour_optimize_dp (rot_points, rot_x_deltas, rot_y_deltas,
433
0
                                   rot_forced_set, tolerance_sq, n,
434
0
                                   costs, chain,
435
0
           scratch.interp_x_deltas, scratch.interp_y_deltas))
436
0
      return false;
437
438
0
    hb_iup_set_t solution;
439
0
    int index = n - 1;
440
0
    while (index != -1)
441
0
    {
442
0
      solution.add (index);
443
0
      index = chain.arrayZ[index];
444
0
    }
445
446
0
    if (solution.is_empty () ||
447
0
        forced_set.get_population () > solution.get_population ())
448
0
      return false;
449
450
0
    for (unsigned i : solution)
451
0
      opt_indices.arrayZ[i] = true;
452
453
0
    hb_vector_t<bool> &rot_indices = scratch.rot_indices.reset ();
454
0
    const hb_array_t<const bool> opt_indices_array (opt_indices.arrayZ, opt_indices.length);
455
0
    rotate_array (opt_indices_array, -k, rot_indices);
456
457
0
    for (unsigned i = 0; i < n; i++)
458
0
      opt_indices.arrayZ[i] = rot_indices.arrayZ[i];
459
0
  }
460
0
  else
461
0
  {
462
0
    hb_vector_t<int> repeat_x_deltas, repeat_y_deltas;
463
0
    contour_point_vector_t repeat_points;
464
465
0
    if (unlikely (!repeat_x_deltas.resize_dirty  (n * 2) ||
466
0
                  !repeat_y_deltas.resize_dirty  (n * 2) ||
467
0
                  !repeat_points.resize_dirty  (n * 2)))
468
0
      return false;
469
470
0
    unsigned contour_point_size = hb_static_size (contour_point_t);
471
0
    for (unsigned i = 0; i < n; i++)
472
0
    {
473
0
      hb_memcpy ((void *) repeat_x_deltas.arrayZ, (const void *) x_deltas.arrayZ, n * sizeof (repeat_x_deltas[0]));
474
0
      hb_memcpy ((void *) (repeat_x_deltas.arrayZ + n), (const void *) x_deltas.arrayZ, n * sizeof (repeat_x_deltas[0]));
475
476
0
      hb_memcpy ((void *) repeat_y_deltas.arrayZ, (const void *) y_deltas.arrayZ, n * sizeof (repeat_x_deltas[0]));
477
0
      hb_memcpy ((void *) (repeat_y_deltas.arrayZ + n), (const void *) y_deltas.arrayZ, n * sizeof (repeat_x_deltas[0]));
478
479
0
      hb_memcpy ((void *) repeat_points.arrayZ, (const void *) contour_points.arrayZ, n * contour_point_size);
480
0
      hb_memcpy ((void *) (repeat_points.arrayZ + n), (const void *) contour_points.arrayZ, n * contour_point_size);
481
0
    }
482
483
0
    if (!_iup_contour_optimize_dp (repeat_points, repeat_x_deltas, repeat_y_deltas,
484
0
                                   forced_set, tolerance_sq, n,
485
0
                                   costs, chain,
486
0
           scratch.interp_x_deltas, scratch.interp_y_deltas))
487
0
      return false;
488
489
0
    unsigned best_cost = n + 1;
490
0
    int len = costs.length;
491
0
    hb_iup_set_t best_sol;
492
0
    for (int start = n - 1; start < len; start++)
493
0
    {
494
0
      hb_iup_set_t solution;
495
0
      int i = start;
496
0
      int lookback = start - (int) n;
497
0
      while (i > lookback)
498
0
      {
499
0
        solution.add (i % n);
500
0
        i = chain.arrayZ[i];
501
0
      }
502
0
      if (i == lookback)
503
0
      {
504
0
        unsigned cost_i = i < 0 ? 0 : costs.arrayZ[i];
505
0
        unsigned cost = costs.arrayZ[start] - cost_i;
506
0
        if (cost <= best_cost)
507
0
        {
508
0
          best_sol = solution;
509
0
          best_cost = cost;
510
0
        }
511
0
      }
512
0
    }
513
514
0
    for (unsigned i = 0; i < n; i++)
515
0
      if (best_sol.has (i))
516
0
        opt_indices.arrayZ[i] = true;
517
0
  }
518
0
  return true;
519
0
}
520
521
bool iup_delta_optimize (const contour_point_vector_t& contour_points,
522
                         const hb_vector_t<int>& x_deltas,
523
                         const hb_vector_t<int>& y_deltas,
524
                         hb_vector_t<bool>& opt_indices, /* OUT */
525
       iup_scratch_t &scratch,
526
                         double tolerance)
527
39
{
528
39
  if (!opt_indices.resize (contour_points.length))
529
0
      return false;
530
531
39
  hb_vector_t<unsigned> &end_points = scratch.end_points.reset ();
532
533
39
  unsigned count = contour_points.length;
534
39
  if (unlikely (!end_points.alloc (count)))
535
0
    return false;
536
537
39
  for (unsigned i = 0; i < count - 4; i++)
538
0
    if (contour_points.arrayZ[i].is_end_point)
539
0
      end_points.push (i);
540
541
  /* phantom points */
542
195
  for (unsigned i = count - 4; i < count; i++)
543
156
    end_points.push (i);
544
545
39
  if (end_points.in_error ()) return false;
546
547
39
  unsigned start = 0;
548
39
  for (unsigned end : end_points)
549
156
  {
550
156
    unsigned len = end - start + 1;
551
156
    if (!_iup_contour_optimize (contour_points.as_array ().sub_array (start, len),
552
156
                                x_deltas.as_array ().sub_array (start, len),
553
156
                                y_deltas.as_array ().sub_array (start, len),
554
156
                                opt_indices.as_array ().sub_array (start, len),
555
156
                                tolerance,
556
156
        scratch))
557
0
      return false;
558
156
    start = end + 1;
559
156
  }
560
39
  return true;
561
39
}