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