/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 | } |