Coverage Report

Created: 2026-03-31 11:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/work/workdir/UnpackedTarball/harfbuzz/src/hb-subset-instancer-solver.cc
Line
Count
Source
1
/*
2
 * Copyright © 2023  Behdad Esfahbod
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-solver.hh"
26
27
/* This file is a straight port of the following:
28
 *
29
 * https://github.com/fonttools/fonttools/blob/f73220816264fc383b8a75f2146e8d69e455d398/Lib/fontTools/varLib/instancer/solver.py
30
 *
31
 * Where that file returns None for a triple, we return Triple{}.
32
 * This should be safe.
33
 */
34
35
constexpr static double EPSILON = 1.0 / (1 << 14);
36
constexpr static double MAX_F2DOT14 = double (0x7FFF) / (1 << 14);
37
38
static inline Triple _reverse_negate(const Triple &v)
39
0
{ return {-v.maximum, -v.middle, -v.minimum}; }
40
41
42
static inline double supportScalar (double coord, const Triple &tent)
43
0
{
44
  /* Copied from VarRegionAxis::evaluate() */
45
0
  double start = tent.minimum, peak = tent.middle, end = tent.maximum;
46
47
0
  if (unlikely (start > peak || peak > end))
48
0
    return 1.;
49
0
  if (unlikely (start < 0 && end > 0 && peak != 0))
50
0
    return 1.;
51
52
0
  if (peak == 0 || coord == peak)
53
0
    return 1.;
54
55
0
  if (coord <= start || end <= coord)
56
0
    return 0.;
57
58
  /* Interpolate */
59
0
  if (coord < peak)
60
0
    return (coord - start) / (peak - start);
61
0
  else
62
0
    return  (end - coord) / (end - peak);
63
0
}
64
65
static inline void
66
_solve (Triple tent, Triple axisLimit, rebase_tent_result_t &out, bool negative = false)
67
0
{
68
0
  out.reset();
69
0
  double axisMin = axisLimit.minimum;
70
0
  double axisDef = axisLimit.middle;
71
0
  double axisMax = axisLimit.maximum;
72
0
  double lower = tent.minimum;
73
0
  double peak  = tent.middle;
74
0
  double upper = tent.maximum;
75
76
  // Mirror the problem such that axisDef <= peak
77
0
  if (axisDef > peak)
78
0
  {
79
0
    _solve (_reverse_negate (tent), _reverse_negate (axisLimit), out, !negative);
80
81
0
    for (auto &p : out)
82
0
      p = hb_pair (p.first, _reverse_negate (p.second));
83
84
0
    return;
85
0
  }
86
  // axisDef <= peak
87
88
  /* case 1: The whole deltaset falls outside the new limit; we can drop it
89
   *
90
   *                                          peak
91
   *  1.........................................o..........
92
   *                                           / \
93
   *                                          /   \
94
   *                                         /     \
95
   *                                        /       \
96
   *  0---|-----------|----------|-------- o         o----1
97
   *    axisMin     axisDef    axisMax   lower     upper
98
   */
99
0
  if (axisMax <= lower && axisMax < peak)
100
0
      return;  // No overlap
101
102
  /* case 2: Only the peak and outermost bound fall outside the new limit;
103
   * we keep the deltaset, update peak and outermost bound and scale deltas
104
   * by the scalar value for the restricted axis at the new limit, and solve
105
   * recursively.
106
   *
107
   *                                  |peak
108
   *  1...............................|.o..........
109
   *                                  |/ \
110
   *                                  /   \
111
   *                                 /|    \
112
   *                                / |     \
113
   *  0--------------------------- o  |      o----1
114
   *                           lower  |      upper
115
   *                                  |
116
   *                                axisMax
117
   *
118
   * Convert to:
119
   *
120
   *  1............................................
121
   *                                  |
122
   *                                  o peak
123
   *                                 /|
124
   *                                /x|
125
   *  0--------------------------- o  o upper ----1
126
   *                           lower  |
127
   *                                  |
128
   *                                axisMax
129
   */
130
0
  if (axisMax < peak)
131
0
  {
132
0
    double mult = supportScalar (axisMax, tent);
133
0
    tent = Triple{lower, axisMax, axisMax};
134
135
0
    _solve (tent, axisLimit, out);
136
137
0
    for (auto &p : out)
138
0
      p = hb_pair (p.first * mult, p.second);
139
140
0
    return;
141
0
  }
142
143
  // lower <= axisDef <= peak <= axisMax
144
145
0
  double gain = supportScalar (axisDef, tent);
146
0
  out.push(hb_pair (gain, Triple{}));
147
148
  // First, the positive side
149
150
  // outGain is the scalar of axisMax at the tent.
151
0
  double outGain = supportScalar (axisMax, tent);
152
153
  /* Case 3a: Gain is more than outGain. The tent down-slope crosses
154
   * the axis into negative. We have to split it into multiples.
155
   *
156
   *                      | peak  |
157
   *  1...................|.o.....|..............
158
   *                      |/x\_   |
159
   *  gain................+....+_.|..............
160
   *                     /|    |y\|
161
   *  ................../.|....|..+_......outGain
162
   *                   /  |    |  | \
163
   *  0---|-----------o   |    |  |  o----------1
164
   *    axisMin    lower  |    |  |   upper
165
   *                      |    |  |
166
   *                axisDef    |  axisMax
167
   *                           |
168
   *                      crossing
169
   */
170
0
  if (gain >= outGain)
171
0
  {
172
    // Note that this is the branch taken if both gain and outGain are 0.
173
174
    // Crossing point on the axis.
175
0
    double crossing = peak + (1 - gain) * (upper - peak);
176
177
0
    Triple loc{hb_max (lower, axisDef), peak, crossing};
178
0
    double scalar = 1.0;
179
180
    // The part before the crossing point.
181
0
    out.push (hb_pair (scalar - gain, loc));
182
183
    /* The part after the crossing point may use one or two tents,
184
     * depending on whether upper is before axisMax or not, in one
185
     * case we need to keep it down to eternity.
186
     *
187
     * Case 3a1, similar to case 1neg; just one tent needed, as in
188
     * the drawing above.
189
     */
190
0
    if (upper >= axisMax)
191
0
    {
192
0
      Triple loc {crossing, axisMax, axisMax};
193
0
      double scalar = outGain;
194
195
0
      out.push (hb_pair (scalar - gain, loc));
196
0
    }
197
198
    /* Case 3a2: Similar to case 2neg; two tents needed, to keep
199
     * down to eternity.
200
     *
201
     *                      | peak             |
202
     *  1...................|.o................|...
203
     *                      |/ \_              |
204
     *  gain................+....+_............|...
205
     *                     /|    | \xxxxxxxxxxy|
206
     *                    / |    |  \_xxxxxyyyy|
207
     *                   /  |    |    \xxyyyyyy|
208
     *  0---|-----------o   |    |     o-------|--1
209
     *    axisMin    lower  |    |      upper  |
210
     *                      |    |             |
211
     *                axisDef    |             axisMax
212
     *                           |
213
     *                      crossing
214
     */
215
0
    else
216
0
    {
217
      // A tent's peak cannot fall on axis default. Nudge it.
218
0
      if (upper == axisDef)
219
0
  upper += EPSILON;
220
221
      // Downslope.
222
0
      Triple loc1 {crossing, upper, axisMax};
223
0
      double scalar1 = 0.0;
224
225
      // Eternity justify.
226
0
      Triple loc2 {upper, axisMax, axisMax};
227
0
      double scalar2 = 0.0;
228
229
0
      out.push (hb_pair (scalar1 - gain, loc1));
230
0
      out.push (hb_pair (scalar2 - gain, loc2));
231
0
    }
232
0
  }
233
234
0
  else
235
0
  {
236
    // Special-case if peak is at axisMax.
237
0
    if (axisMax == peak)
238
0
  upper = peak;
239
240
    /* Case 3:
241
     * we keep deltas as is and only scale the axis upper to achieve
242
     * the desired new tent if feasible.
243
     *
244
     *                        peak
245
     *  1.....................o....................
246
     *                       / \_|
247
     *  ..................../....+_.........outGain
248
     *                     /     | \
249
     *  gain..............+......|..+_.............
250
     *                   /|      |  | \
251
     *  0---|-----------o |      |  |  o----------1
252
     *    axisMin    lower|      |  |   upper
253
     *                    |      |  newUpper
254
     *              axisDef      axisMax
255
     */
256
0
    double newUpper = peak + (1 - gain) * (upper - peak);
257
0
    assert (axisMax <= newUpper);  // Because outGain > gain
258
    /* Disabled because ots doesn't like us:
259
     * https://github.com/fonttools/fonttools/issues/3350 */
260
261
0
    if (false && (newUpper <= axisDef + (axisMax - axisDef) * 2))
262
0
    {
263
0
      upper = newUpper;
264
0
      if (!negative && axisDef + (axisMax - axisDef) * MAX_F2DOT14 < upper)
265
0
      {
266
  // we clamp +2.0 to the max F2Dot14 (~1.99994) for convenience
267
0
  upper = axisDef + (axisMax - axisDef) * MAX_F2DOT14;
268
0
  assert (peak < upper);
269
0
      }
270
271
0
      Triple loc {hb_max (axisDef, lower), peak, upper};
272
0
      double scalar = 1.0;
273
274
0
      out.push (hb_pair (scalar - gain, loc));
275
0
    }
276
277
    /* Case 4: New limit doesn't fit; we need to chop into two tents,
278
     * because the shape of a triangle with part of one side cut off
279
     * cannot be represented as a triangle itself.
280
     *
281
     *            |   peak |
282
     *  1.........|......o.|....................
283
     *  ..........|...../x\|.............outGain
284
     *            |    |xxy|\_
285
     *            |   /xxxy|  \_
286
     *            |  |xxxxy|    \_
287
     *            |  /xxxxy|      \_
288
     *  0---|-----|-oxxxxxx|        o----------1
289
     *    axisMin | lower  |        upper
290
     *            |        |
291
     *          axisDef  axisMax
292
     */
293
0
    else
294
0
    {
295
0
      Triple loc1 {hb_max (axisDef, lower), peak, axisMax};
296
0
      double scalar1 = 1.0;
297
298
0
      Triple loc2 {peak, axisMax, axisMax};
299
0
      double scalar2 = outGain;
300
301
0
      out.push (hb_pair (scalar1 - gain, loc1));
302
      // Don't add a dirac delta!
303
0
      if (peak < axisMax)
304
0
  out.push (hb_pair (scalar2 - gain, loc2));
305
0
    }
306
0
  }
307
308
  /* Now, the negative side
309
   *
310
   * Case 1neg: Lower extends beyond axisMin: we chop. Simple.
311
   *
312
   *                     |   |peak
313
   *  1..................|...|.o.................
314
   *                     |   |/ \
315
   *  gain...............|...+...\...............
316
   *                     |x_/|    \
317
   *                     |/  |     \
318
   *                   _/|   |      \
319
   *  0---------------o  |   |       o----------1
320
   *              lower  |   |       upper
321
   *                     |   |
322
   *               axisMin   axisDef
323
   */
324
0
  if (lower <= axisMin)
325
0
  {
326
0
    Triple loc {axisMin, axisMin, axisDef};
327
0
    double scalar = supportScalar (axisMin, tent);
328
329
0
    out.push (hb_pair (scalar - gain, loc));
330
0
  }
331
332
  /* Case 2neg: Lower is betwen axisMin and axisDef: we add two
333
   * tents to keep it down all the way to eternity.
334
   *
335
   *      |               |peak
336
   *  1...|...............|.o.................
337
   *      |               |/ \
338
   *  gain|...............+...\...............
339
   *      |yxxxxxxxxxxxxx/|    \
340
   *      |yyyyyyxxxxxxx/ |     \
341
   *      |yyyyyyyyyyyx/  |      \
342
   *  0---|-----------o   |       o----------1
343
   *    axisMin    lower  |       upper
344
   *                      |
345
   *                    axisDef
346
   */
347
0
  else
348
0
  {
349
    // A tent's peak cannot fall on axis default. Nudge it.
350
0
    if (lower == axisDef)
351
0
      lower -= EPSILON;
352
353
    // Downslope.
354
0
    Triple loc1 {axisMin, lower, axisDef};
355
0
    double scalar1 = 0.0;
356
357
    // Eternity justify.
358
0
    Triple loc2 {axisMin, axisMin, lower};
359
0
    double scalar2 = 0.0;
360
361
0
    out.push (hb_pair (scalar1 - gain, loc1));
362
0
    out.push (hb_pair (scalar2 - gain, loc2));
363
0
  }
364
0
}
365
366
static inline TripleDistances _reverse_triple_distances (const TripleDistances &v)
367
0
{ return TripleDistances (v.positive, v.negative); }
368
369
double renormalizeValue (double v, const Triple &triple,
370
                         const TripleDistances &triple_distances, bool extrapolate)
371
0
{
372
0
  double lower = triple.minimum, def = triple.middle, upper = triple.maximum;
373
0
  if (unlikely (!(lower <= def && def <= upper)))
374
0
    return hb_clamp (v, -1.0, +1.0);
375
376
0
  if (!extrapolate)
377
0
    v = hb_clamp (v, lower, upper);
378
379
0
  if (v == def)
380
0
    return 0.0;
381
382
0
  if (def < 0.0)
383
0
    return -renormalizeValue (-v, _reverse_negate (triple),
384
0
                              _reverse_triple_distances (triple_distances), extrapolate);
385
386
  /* default >= 0 and v != default */
387
0
  if (v > def)
388
0
  {
389
0
    double positive_range = upper - def;
390
0
    if (unlikely (!positive_range))
391
0
      return +1.0;
392
0
    return (v - def) / positive_range;
393
0
  }
394
395
  /* v < def */
396
0
  if (lower >= 0.0)
397
0
  {
398
0
    double negative_range = def - lower;
399
0
    if (unlikely (!negative_range))
400
0
      return -1.0;
401
0
    return (v - def) / negative_range;
402
0
  }
403
404
  /* lower < 0 and v < default */
405
0
  double total_distance = triple_distances.negative * (-lower) + triple_distances.positive * def;
406
0
  if (unlikely (!total_distance))
407
0
    return 0.0;
408
409
0
  double v_distance;
410
0
  if (v >= 0.0)
411
0
    v_distance = (def - v) * triple_distances.positive;
412
0
  else
413
0
    v_distance = (-v) * triple_distances.negative + triple_distances.positive * def;
414
415
0
  return (-v_distance) /total_distance;
416
0
}
417
418
void
419
rebase_tent (Triple tent, Triple axisLimit, TripleDistances axis_triple_distances,
420
       rebase_tent_result_t &out,
421
       rebase_tent_result_t &scratch)
422
0
{
423
0
  if (unlikely (!(-1.0 <= axisLimit.minimum &&
424
0
                  axisLimit.minimum <= axisLimit.middle &&
425
0
                  axisLimit.middle <= axisLimit.maximum &&
426
0
                  axisLimit.maximum <= +1.0) ||
427
0
                !(-2.0 <= tent.minimum &&
428
0
                  tent.minimum <= tent.middle &&
429
0
                  tent.middle <= tent.maximum &&
430
0
                  tent.maximum <= +2.0) ||
431
0
                tent.middle == 0.0))
432
0
  {
433
0
    out.reset ();
434
0
    return;
435
0
  }
436
437
0
  rebase_tent_result_t &sols = scratch;
438
0
  _solve (tent, axisLimit, sols);
439
440
0
  auto n = [&axisLimit, &axis_triple_distances] (double v) { return renormalizeValue (v, axisLimit, axis_triple_distances); };
441
442
0
  out.reset();
443
0
  for (auto &p : sols)
444
0
  {
445
0
    if (!p.first) continue;
446
0
    if (p.second == Triple{})
447
0
    {
448
0
      out.push (p);
449
0
      continue;
450
0
    }
451
0
    Triple t = p.second;
452
0
    out.push (hb_pair (p.first,
453
0
           Triple{n (t.minimum), n (t.middle), n (t.maximum)}));
454
0
  }
455
0
}