[subset] Add unfinished port of fonttools instancer solver
This commit is contained in:
parent
167443e9fc
commit
223abd72b9
|
@ -353,6 +353,7 @@ HB_SUBSET_sources = \
|
||||||
hb-subset-cff2.hh \
|
hb-subset-cff2.hh \
|
||||||
hb-subset-input.cc \
|
hb-subset-input.cc \
|
||||||
hb-subset-input.hh \
|
hb-subset-input.hh \
|
||||||
|
hb-subset-instancer-solver.cc \
|
||||||
hb-subset-accelerator.hh \
|
hb-subset-accelerator.hh \
|
||||||
hb-subset-plan.cc \
|
hb-subset-plan.cc \
|
||||||
hb-subset-plan.hh \
|
hb-subset-plan.hh \
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
#include "hb-subset-cff1.cc"
|
#include "hb-subset-cff1.cc"
|
||||||
#include "hb-subset-cff2.cc"
|
#include "hb-subset-cff2.cc"
|
||||||
#include "hb-subset-input.cc"
|
#include "hb-subset-input.cc"
|
||||||
|
#include "hb-subset-instancer-solver.cc"
|
||||||
#include "hb-subset-plan.cc"
|
#include "hb-subset-plan.cc"
|
||||||
#include "hb-subset-repacker.cc"
|
#include "hb-subset-repacker.cc"
|
||||||
#include "hb-subset.cc"
|
#include "hb-subset.cc"
|
||||||
|
|
|
@ -0,0 +1,405 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* See XXX markers for unfinished parts.
|
||||||
|
*
|
||||||
|
* 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_) {}
|
||||||
|
|
||||||
|
float minimum;
|
||||||
|
float middle;
|
||||||
|
float maximum;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline Triple _reverse_negate(const Triple &v)
|
||||||
|
{ return {-v.maximum, -v.middle, -v.minimum}; }
|
||||||
|
|
||||||
|
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 = 1.f; //XXX supportScalar({"tag": axisMax}, {"tag": 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 = 1.0f; // XXX supportScalar({"tag": axisDef}, {"tag": tent})
|
||||||
|
result_t out {hb_pair (gain, Triple{})};
|
||||||
|
|
||||||
|
// First, the positive side
|
||||||
|
|
||||||
|
// outGain is the scalar of axisMax at the tent.
|
||||||
|
float outGain = 1.f; // XXX supportScalar({"tag": axisMax}, {"tag": 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 = 1.f; // XXX supportScalar({"tag": axisMax}, {"tag": 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 = 1.f; // XXX supportScalar({"tag": axisMax}, {"tag": 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 = 1.f; // XXX supportScalar({"tag": axisMin}, {"tag": 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// XXX
|
||||||
|
n = lambda v: normalizeValue(v, axisLimit, extrapolate=True)
|
||||||
|
sols = [
|
||||||
|
(scalar, (n(v[0]), n(v[1]), n(v[2])) if v is not None else None)
|
||||||
|
for scalar, v in sols
|
||||||
|
if scalar
|
||||||
|
]
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return sols;
|
||||||
|
}
|
|
@ -353,6 +353,7 @@ hb_subset_sources = files(
|
||||||
'hb-subset-cff2.hh',
|
'hb-subset-cff2.hh',
|
||||||
'hb-subset-input.cc',
|
'hb-subset-input.cc',
|
||||||
'hb-subset-input.hh',
|
'hb-subset-input.hh',
|
||||||
|
'hb-subset-instancer-solver.cc',
|
||||||
'hb-subset-plan.cc',
|
'hb-subset-plan.cc',
|
||||||
'hb-subset-plan.hh',
|
'hb-subset-plan.hh',
|
||||||
'hb-subset-repacker.cc',
|
'hb-subset-repacker.cc',
|
||||||
|
|
Loading…
Reference in New Issue