| /* |
| * Copyright © 2023 Behdad Esfahbod |
| * |
| * This is part of HarfBuzz, a text shaping library. |
| * |
| * Permission is hereby granted, without written agreement and without |
| * license or royalty fees, to use, copy, modify, and distribute this |
| * software and its documentation for any purpose, provided that the |
| * above copyright notice and the following two paragraphs appear in |
| * all copies of this software. |
| * |
| * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR |
| * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES |
| * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN |
| * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| * |
| * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, |
| * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
| * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS |
| * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO |
| * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
| */ |
| |
| #include "hb.hh" |
| |
| /* This file is a straight port of the following: |
| * |
| * https://github.com/fonttools/fonttools/blob/f73220816264fc383b8a75f2146e8d69e455d398/Lib/fontTools/varLib/instancer/solver.py |
| * |
| * Where that file returns None for a triple, we return Triple{}. |
| * This should be safe. |
| */ |
| |
| constexpr static float EPSILON = 1.f / (1 << 14); |
| constexpr static float MAX_F2DOT14 = float (0x7FFF) / (1 << 14); |
| |
| struct Triple { |
| |
| Triple () : |
| minimum (0.f), middle (0.f), maximum (0.f) {} |
| |
| Triple (float minimum_, float middle_, float maximum_) : |
| minimum (minimum_), middle (middle_), maximum (maximum_) {} |
| |
| bool operator == (const Triple &o) |
| { |
| return minimum == o.minimum && |
| middle == o.middle && |
| maximum == o.maximum; |
| } |
| |
| float minimum; |
| float middle; |
| float maximum; |
| }; |
| |
| static inline Triple _reverse_negate(const Triple &v) |
| { return {-v.maximum, -v.middle, -v.minimum}; } |
| |
| |
| static inline float supportScalar (float coord, const Triple &tent) |
| { |
| /* Copied from VarRegionAxis::evaluate() */ |
| float start = tent.minimum, peak = tent.middle, end = tent.maximum; |
| |
| if (unlikely (start > peak || peak > end)) |
| return 1.; |
| if (unlikely (start < 0 && end > 0 && peak != 0)) |
| return 1.; |
| |
| if (peak == 0 || coord == peak) |
| return 1.; |
| |
| if (coord <= start || end <= coord) |
| return 0.; |
| |
| /* Interpolate */ |
| if (coord < peak) |
| return (coord - start) / (peak - start); |
| else |
| return (end - coord) / (end - peak); |
| } |
| |
| |
| using result_item_t = hb_pair_t<float, Triple>; |
| using result_t = hb_vector_t<result_item_t>; |
| |
| static inline result_t |
| _solve (Triple tent, Triple axisLimit, bool negative = false) |
| { |
| float axisMin = axisLimit.minimum; |
| float axisDef = axisLimit.middle; |
| float axisMax = axisLimit.maximum; |
| float lower = tent.minimum; |
| float peak = tent.middle; |
| float upper = tent.maximum; |
| |
| // Mirror the problem such that axisDef <= peak |
| if (axisDef > peak) |
| { |
| result_t vec = _solve (_reverse_negate (tent), |
| _reverse_negate (axisLimit), |
| !negative); |
| |
| for (auto &p : vec) |
| p = hb_pair (p.first, _reverse_negate (p.second)); |
| |
| return vec; |
| } |
| // axisDef <= peak |
| |
| /* case 1: The whole deltaset falls outside the new limit; we can drop it |
| * |
| * peak |
| * 1.........................................o.......... |
| * / \ |
| * / \ |
| * / \ |
| * / \ |
| * 0---|-----------|----------|-------- o o----1 |
| * axisMin axisDef axisMax lower upper |
| */ |
| if (axisMax <= lower && axisMax < peak) |
| return result_t{}; // No overlap |
| |
| /* case 2: Only the peak and outermost bound fall outside the new limit; |
| * we keep the deltaset, update peak and outermost bound and and scale deltas |
| * by the scalar value for the restricted axis at the new limit, and solve |
| * recursively. |
| * |
| * |peak |
| * 1...............................|.o.......... |
| * |/ \ |
| * / \ |
| * /| \ |
| * / | \ |
| * 0--------------------------- o | o----1 |
| * lower | upper |
| * | |
| * axisMax |
| * |
| * Convert to: |
| * |
| * 1............................................ |
| * | |
| * o peak |
| * /| |
| * /x| |
| * 0--------------------------- o o upper ----1 |
| * lower | |
| * | |
| * axisMax |
| */ |
| if (axisMax < peak) |
| { |
| float mult = supportScalar (axisMax, tent); |
| tent = Triple{lower, axisMax, axisMax}; |
| |
| result_t vec = _solve (tent, axisLimit); |
| |
| for (auto &p : vec) |
| p = hb_pair (p.first * mult, p.second); |
| |
| return vec; |
| } |
| |
| // lower <= axisDef <= peak <= axisMax |
| |
| float gain = supportScalar (axisDef, tent); |
| result_t out {hb_pair (gain, Triple{})}; |
| |
| // First, the positive side |
| |
| // outGain is the scalar of axisMax at the tent. |
| float outGain = supportScalar (axisMax, tent); |
| |
| /* Case 3a: Gain is more than outGain. The tent down-slope crosses |
| * the axis into negative. We have to split it into multiples. |
| * |
| * | peak | |
| * 1...................|.o.....|.............. |
| * |/x\_ | |
| * gain................+....+_.|.............. |
| * /| |y\| |
| * ................../.|....|..+_......outGain |
| * / | | | \ |
| * 0---|-----------o | | | o----------1 |
| * axisMin lower | | | upper |
| * | | | |
| * axisDef | axisMax |
| * | |
| * crossing |
| */ |
| if (gain > outGain) |
| { |
| // Crossing point on the axis. |
| float crossing = peak + ((1 - gain) * (upper - peak) / (1 - outGain)); |
| |
| Triple loc{peak, peak, crossing}; |
| float scalar = 1.f; |
| |
| // The part before the crossing point. |
| out.push (hb_pair (scalar - gain, loc)); |
| |
| /* The part after the crossing point may use one or two tents, |
| * depending on whether upper is before axisMax or not, in one |
| * case we need to keep it down to eternity. |
| * |
| * Case 3a1, similar to case 1neg; just one tent needed, as in |
| * the drawing above. |
| */ |
| if (upper >= axisMax) |
| { |
| Triple loc {crossing, axisMax, axisMax}; |
| float scalar = supportScalar (axisMax, tent); |
| |
| out.push (hb_pair (scalar - gain, loc)); |
| } |
| |
| /* Case 3a2: Similar to case 2neg; two tents needed, to keep |
| * down to eternity. |
| * |
| * | peak | |
| * 1...................|.o................|... |
| * |/ \_ | |
| * gain................+....+_............|... |
| * /| | \xxxxxxxxxxy| |
| * / | | \_xxxxxyyyy| |
| * / | | \xxyyyyyy| |
| * 0---|-----------o | | o-------|--1 |
| * axisMin lower | | upper | |
| * | | | |
| * axisDef | axisMax |
| * | |
| * crossing |
| */ |
| else |
| { |
| // A tent's peak cannot fall on axis default. Nudge it. |
| if (upper == axisDef) |
| upper += EPSILON; |
| |
| // Downslope. |
| Triple loc1 {crossing, upper, axisMax}; |
| float scalar1 = 0.f; |
| |
| // Eternity justify. |
| Triple loc2 {upper, axisMax, axisMax}; |
| float scalar2 = 1.f; // supportScalar({"tag": axisMax}, {"tag": tent}) |
| |
| out.push (hb_pair (scalar1 - gain, loc1)); |
| out.push (hb_pair (scalar2 - gain, loc2)); |
| } |
| } |
| |
| /* Case 3: Outermost limit still fits within F2Dot14 bounds; |
| * we keep deltas as is and only scale the axes bounds. Deltas beyond -1.0 |
| * or +1.0 will never be applied as implementations must clamp to that range. |
| * |
| * A second tent is needed for cases when gain is positive, though we add it |
| * unconditionally and it will be dropped because scalar ends up 0. |
| * |
| * TODO: See if we can just move upper closer to adjust the slope, instead of |
| * second tent. |
| * |
| * | peak | |
| * 1.........|............o...|.................. |
| * | /x\ | |
| * | /xxx\ | |
| * | /xxxxx\| |
| * | /xxxxxxx+ |
| * | /xxxxxxxx|\ |
| * 0---|-----|------oxxxxxxxxx|xo---------------1 |
| * axisMin | lower | upper |
| * | | |
| * axisDef axisMax |
| */ |
| else if (axisDef + (axisMax - axisDef) * 2 >= upper) |
| { |
| if (!negative && axisDef + (axisMax - axisDef) * MAX_F2DOT14 < upper) |
| { |
| // we clamp +2.0 to the max F2Dot14 (~1.99994) for convenience |
| upper = axisDef + (axisMax - axisDef) * MAX_F2DOT14; |
| assert (peak < upper); |
| } |
| |
| // Special-case if peak is at axisMax. |
| if (axisMax == peak) |
| upper = peak; |
| |
| Triple loc1 {hb_max (axisDef, lower), peak, upper}; |
| float scalar1 = 1.f; |
| |
| Triple loc2 {peak, upper, upper}; |
| float scalar2 = 0.f; |
| |
| // Don't add a dirac delta! |
| if (axisDef < upper) |
| out.push (hb_pair (scalar1 - gain, loc1)); |
| if (peak < upper) |
| out.push (hb_pair (scalar2 - gain, loc2)); |
| } |
| |
| /* Case 4: New limit doesn't fit; we need to chop into two tents, |
| * because the shape of a triangle with part of one side cut off |
| * cannot be represented as a triangle itself. |
| * |
| * | peak | |
| * 1.........|......o.|................... |
| * | /x\| |
| * | |xxy|\_ |
| * | /xxxy| \_ |
| * | |xxxxy| \_ |
| * | /xxxxy| \_ |
| * 0---|-----|-oxxxxxx| o----------1 |
| * axisMin | lower | upper |
| * | | |
| * axisDef axisMax |
| */ |
| else |
| { |
| Triple loc1 {hb_max (axisDef, lower), peak, axisMax}; |
| float scalar1 = 1.f; |
| |
| Triple loc2 {peak, axisMax, axisMax}; |
| float scalar2 = supportScalar (axisMax, tent); |
| |
| out.push (hb_pair (scalar1 - gain, loc1)); |
| // Don't add a dirac delta! |
| if (peak < axisMax) |
| out.push (hb_pair (scalar2 - gain, loc2)); |
| } |
| |
| /* Now, the negative side |
| * |
| * Case 1neg: Lower extends beyond axisMin: we chop. Simple. |
| * |
| * | |peak |
| * 1..................|...|.o................. |
| * | |/ \ |
| * gain...............|...+...\............... |
| * |x_/| \ |
| * |/ | \ |
| * _/| | \ |
| * 0---------------o | | o----------1 |
| * lower | | upper |
| * | | |
| * axisMin axisDef |
| */ |
| if (lower <= axisMin) |
| { |
| Triple loc {axisMin, axisMin, axisDef}; |
| float scalar = supportScalar (axisMin, tent); |
| |
| out.push (hb_pair (scalar - gain, loc)); |
| } |
| |
| /* Case 2neg: Lower is betwen axisMin and axisDef: we add two |
| * tents to keep it down all the way to eternity. |
| * |
| * | |peak |
| * 1...|...............|.o................. |
| * | |/ \ |
| * gain|...............+...\............... |
| * |yxxxxxxxxxxxxx/| \ |
| * |yyyyyyxxxxxxx/ | \ |
| * |yyyyyyyyyyyx/ | \ |
| * 0---|-----------o | o----------1 |
| * axisMin lower | upper |
| * | |
| * axisDef |
| */ |
| else |
| { |
| // A tent's peak cannot fall on axis default. Nudge it. |
| if (lower == axisDef) |
| lower -= EPSILON; |
| |
| // Downslope. |
| Triple loc1 {axisMin, lower, axisDef}; |
| float scalar1 = 0.f; |
| |
| // Eternity justify. |
| Triple loc2 {axisMin, axisMin, lower}; |
| float scalar2 = 0.f; |
| |
| out.push (hb_pair (scalar1 - gain, loc1)); |
| out.push (hb_pair (scalar2 - gain, loc2)); |
| } |
| |
| return out; |
| } |
| |
| /* Normalizes value based on a min/default/max triple. */ |
| static inline float normalizeValue (float v, const Triple &triple, bool extrapolate = false) |
| { |
| /* |
| >>> normalizeValue(400, (100, 400, 900)) |
| 0.0 |
| >>> normalizeValue(100, (100, 400, 900)) |
| -1.0 |
| >>> normalizeValue(650, (100, 400, 900)) |
| 0.5 |
| */ |
| float lower = triple.minimum, def = triple.middle, upper = triple.maximum; |
| assert (lower <= def && def <= upper); |
| |
| if (!extrapolate) |
| v = hb_max (hb_min (v, upper), lower); |
| |
| if ((v == def) || (lower == upper)) |
| return 0.f; |
| |
| if ((v < def && lower != def) || (v > def && upper == def)) |
| return (v - def) / (def - lower); |
| else |
| { |
| assert ((v > def && upper != def) || |
| (v < def && lower == def)); |
| return (v - def) / (upper - def); |
| } |
| } |
| |
| /* Given a tuple (lower,peak,upper) "tent" and new axis limits |
| * (axisMin,axisDefault,axisMax), solves how to represent the tent |
| * under the new axis configuration. All values are in normalized |
| * -1,0,+1 coordinate system. Tent values can be outside this range. |
| * |
| * Return value: a list of tuples. Each tuple is of the form |
| * (scalar,tent), where scalar is a multipler to multiply any |
| * delta-sets by, and tent is a new tent for that output delta-set. |
| * If tent value is Triple{}, that is a special deltaset that should |
| * be always-enabled (called "gain"). |
| */ |
| HB_INTERNAL result_t rebase_tent (Triple tent, Triple axisLimit); |
| |
| result_t |
| rebase_tent (Triple tent, Triple axisLimit) |
| { |
| float axisMin = axisLimit.minimum; |
| float axisDef = axisLimit.middle; |
| float axisMax = axisLimit.maximum; |
| float lower = tent.minimum; |
| float peak = tent.middle; |
| float upper = tent.maximum; |
| |
| assert (-1.f <= axisMin && axisMin <= axisDef && axisDef <= axisMax && axisMax <= +1.f); |
| assert (-2.f <= lower && lower <= peak && peak <= upper && upper <= +2.f); |
| assert (peak != 0.f); |
| |
| result_t sols = _solve (tent, axisLimit); |
| |
| auto n = [&axisLimit] (float v) { return normalizeValue (v, axisLimit, true); }; |
| |
| result_t out; |
| for (auto &p : sols) |
| { |
| if (!p.first) continue; |
| if (p.second == Triple{}) |
| { |
| out.push (p); |
| continue; |
| } |
| Triple t = p.second; |
| out.push (hb_pair (p.first, |
| Triple{n (t.minimum), n (t.middle), n (t.maximum)})); |
| } |
| |
| return sols; |
| } |