hb-subset-instancer-solver.cc (13861B)
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 { return {-v.maximum, -v.middle, -v.minimum}; } 40 41 42 static inline double supportScalar (double coord, const Triple &tent) 43 { 44 /* Copied from VarRegionAxis::evaluate() */ 45 double 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 void 66 _solve (Triple tent, Triple axisLimit, rebase_tent_result_t &out, bool negative = false) 67 { 68 out.reset(); 69 double axisMin = axisLimit.minimum; 70 double axisDef = axisLimit.middle; 71 double axisMax = axisLimit.maximum; 72 double lower = tent.minimum; 73 double peak = tent.middle; 74 double upper = tent.maximum; 75 76 // Mirror the problem such that axisDef <= peak 77 if (axisDef > peak) 78 { 79 _solve (_reverse_negate (tent), _reverse_negate (axisLimit), out, !negative); 80 81 for (auto &p : out) 82 p = hb_pair (p.first, _reverse_negate (p.second)); 83 84 return; 85 } 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 if (axisMax <= lower && axisMax < peak) 100 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 if (axisMax < peak) 131 { 132 double mult = supportScalar (axisMax, tent); 133 tent = Triple{lower, axisMax, axisMax}; 134 135 _solve (tent, axisLimit, out); 136 137 for (auto &p : out) 138 p = hb_pair (p.first * mult, p.second); 139 140 return; 141 } 142 143 // lower <= axisDef <= peak <= axisMax 144 145 double gain = supportScalar (axisDef, tent); 146 out.push(hb_pair (gain, Triple{})); 147 148 // First, the positive side 149 150 // outGain is the scalar of axisMax at the tent. 151 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 if (gain >= outGain) 171 { 172 // Note that this is the branch taken if both gain and outGain are 0. 173 174 // Crossing point on the axis. 175 double crossing = peak + (1 - gain) * (upper - peak); 176 177 Triple loc{hb_max (lower, axisDef), peak, crossing}; 178 double scalar = 1.0; 179 180 // The part before the crossing point. 181 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 if (upper >= axisMax) 191 { 192 Triple loc {crossing, axisMax, axisMax}; 193 double scalar = outGain; 194 195 out.push (hb_pair (scalar - gain, loc)); 196 } 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 else 216 { 217 // A tent's peak cannot fall on axis default. Nudge it. 218 if (upper == axisDef) 219 upper += EPSILON; 220 221 // Downslope. 222 Triple loc1 {crossing, upper, axisMax}; 223 double scalar1 = 0.0; 224 225 // Eternity justify. 226 Triple loc2 {upper, axisMax, axisMax}; 227 double scalar2 = 0.0; 228 229 out.push (hb_pair (scalar1 - gain, loc1)); 230 out.push (hb_pair (scalar2 - gain, loc2)); 231 } 232 } 233 234 else 235 { 236 // Special-case if peak is at axisMax. 237 if (axisMax == peak) 238 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 double newUpper = peak + (1 - gain) * (upper - peak); 257 assert (axisMax <= newUpper); // Because outGain > gain 258 /* Disabled because ots doesn't like us: 259 * https://github.com/fonttools/fonttools/issues/3350 */ 260 261 if (false && (newUpper <= axisDef + (axisMax - axisDef) * 2)) 262 { 263 upper = newUpper; 264 if (!negative && axisDef + (axisMax - axisDef) * MAX_F2DOT14 < upper) 265 { 266 // we clamp +2.0 to the max F2Dot14 (~1.99994) for convenience 267 upper = axisDef + (axisMax - axisDef) * MAX_F2DOT14; 268 assert (peak < upper); 269 } 270 271 Triple loc {hb_max (axisDef, lower), peak, upper}; 272 double scalar = 1.0; 273 274 out.push (hb_pair (scalar - gain, loc)); 275 } 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 else 294 { 295 Triple loc1 {hb_max (axisDef, lower), peak, axisMax}; 296 double scalar1 = 1.0; 297 298 Triple loc2 {peak, axisMax, axisMax}; 299 double scalar2 = outGain; 300 301 out.push (hb_pair (scalar1 - gain, loc1)); 302 // Don't add a dirac delta! 303 if (peak < axisMax) 304 out.push (hb_pair (scalar2 - gain, loc2)); 305 } 306 } 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 if (lower <= axisMin) 325 { 326 Triple loc {axisMin, axisMin, axisDef}; 327 double scalar = supportScalar (axisMin, tent); 328 329 out.push (hb_pair (scalar - gain, loc)); 330 } 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 else 348 { 349 // A tent's peak cannot fall on axis default. Nudge it. 350 if (lower == axisDef) 351 lower -= EPSILON; 352 353 // Downslope. 354 Triple loc1 {axisMin, lower, axisDef}; 355 double scalar1 = 0.0; 356 357 // Eternity justify. 358 Triple loc2 {axisMin, axisMin, lower}; 359 double scalar2 = 0.0; 360 361 out.push (hb_pair (scalar1 - gain, loc1)); 362 out.push (hb_pair (scalar2 - gain, loc2)); 363 } 364 } 365 366 static inline TripleDistances _reverse_triple_distances (const TripleDistances &v) 367 { return TripleDistances (v.positive, v.negative); } 368 369 double renormalizeValue (double v, const Triple &triple, 370 const TripleDistances &triple_distances, bool extrapolate) 371 { 372 double lower = triple.minimum, def = triple.middle, upper = triple.maximum; 373 assert (lower <= def && def <= upper); 374 375 if (!extrapolate) 376 v = hb_clamp (v, lower, upper); 377 378 if (v == def) 379 return 0.0; 380 381 if (def < 0.0) 382 return -renormalizeValue (-v, _reverse_negate (triple), 383 _reverse_triple_distances (triple_distances), extrapolate); 384 385 /* default >= 0 and v != default */ 386 if (v > def) 387 return (v - def) / (upper - def); 388 389 /* v < def */ 390 if (lower >= 0.0) 391 return (v - def) / (def - lower); 392 393 /* lower < 0 and v < default */ 394 double total_distance = triple_distances.negative * (-lower) + triple_distances.positive * def; 395 396 double v_distance; 397 if (v >= 0.0) 398 v_distance = (def - v) * triple_distances.positive; 399 else 400 v_distance = (-v) * triple_distances.negative + triple_distances.positive * def; 401 402 return (-v_distance) /total_distance; 403 } 404 405 void 406 rebase_tent (Triple tent, Triple axisLimit, TripleDistances axis_triple_distances, 407 rebase_tent_result_t &out, 408 rebase_tent_result_t &scratch) 409 { 410 assert (-1.0 <= axisLimit.minimum && axisLimit.minimum <= axisLimit.middle && axisLimit.middle <= axisLimit.maximum && axisLimit.maximum <= +1.0); 411 assert (-2.0 <= tent.minimum && tent.minimum <= tent.middle && tent.middle <= tent.maximum && tent.maximum <= +2.0); 412 assert (tent.middle != 0.0); 413 414 rebase_tent_result_t &sols = scratch; 415 _solve (tent, axisLimit, sols); 416 417 auto n = [&axisLimit, &axis_triple_distances] (double v) { return renormalizeValue (v, axisLimit, axis_triple_distances); }; 418 419 out.reset(); 420 for (auto &p : sols) 421 { 422 if (!p.first) continue; 423 if (p.second == Triple{}) 424 { 425 out.push (p); 426 continue; 427 } 428 Triple t = p.second; 429 out.push (hb_pair (p.first, 430 Triple{n (t.minimum), n (t.middle), n (t.maximum)})); 431 } 432 }