tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }