Merge pull request #1019 from fonttools/varlib-iup-optimal
Varlib iup optimal
This commit is contained in:
commit
39ffa74128
@ -202,34 +202,221 @@ def _SetCoordinates(font, glyphName, coord):
|
||||
font["hmtx"].metrics[glyphName] = horizontalAdvanceWidth, leftSideBearing
|
||||
|
||||
|
||||
def _optimize_contour(delta, coords, tolerance=0.):
|
||||
def _all_interpolatable_in_between(deltas, coords, i, j, tolerance):
|
||||
assert j - i >= 2
|
||||
from fontTools.varLib.mutator import _iup_segment
|
||||
interp = list(_iup_segment(coords[i+1:j], coords[i], deltas[i], coords[j], deltas[j]))
|
||||
deltas = deltas[i+1:j]
|
||||
|
||||
assert len(deltas) == len(interp)
|
||||
|
||||
return all(abs(complex(x-p, y-q)) <= tolerance for (x,y),(p,q) in zip(deltas, interp))
|
||||
|
||||
def _iup_contour_bound_forced_set(delta, coords, tolerance=0):
|
||||
"""The forced set is a conservative set of points on the contour that must be encoded
|
||||
explicitly (ie. cannot be interpolated). Calculating this set allows for significantly
|
||||
speeding up the dynamic-programming, as well as resolve circularity in DP.
|
||||
|
||||
The set is precise; that is, if an index is in the returned set, then there is no way
|
||||
that IUP can generate delta for that point, given coords and delta.
|
||||
"""
|
||||
assert len(delta) == len(coords)
|
||||
|
||||
forced = set()
|
||||
# Track "last" and "next" points on the contour as we sweep.
|
||||
nd, nc = delta[0], coords[0]
|
||||
ld, lc = delta[-1], coords[-1]
|
||||
for i in range(len(delta)-1, -1, -1):
|
||||
d, c = ld, lc
|
||||
ld, lc = delta[i-1], coords[i-1]
|
||||
|
||||
for j in (0,1): # For X and for Y
|
||||
cj = c[j]
|
||||
dj = d[j]
|
||||
lcj = lc[j]
|
||||
ldj = ld[j]
|
||||
ncj = nc[j]
|
||||
ndj = nd[j]
|
||||
|
||||
if lcj <= ncj:
|
||||
c1, c2 = lcj, ncj
|
||||
d1, d2 = ldj, ndj
|
||||
else:
|
||||
c1, c2 = ncj, lcj
|
||||
d1, d2 = ndj, ldj
|
||||
|
||||
# If coordinate for current point is between coordinate of adjacent
|
||||
# points on the two sides, but the delta for current point is NOT
|
||||
# between delta for those adjacent points (considering tolerance
|
||||
# allowance), then there is no way that current point can be IUP-ed.
|
||||
# Mark it forced.
|
||||
force = False
|
||||
if c1 <= cj <= c2:
|
||||
if not (min(d1,d2)-tolerance <= dj <= max(d1,d2)+tolerance):
|
||||
force = True
|
||||
else: # cj < c1 or c2 < cj
|
||||
if c1 == c2:
|
||||
if d1 == d2:
|
||||
if abs(dj - d1) > tolerance:
|
||||
force = True
|
||||
else:
|
||||
if abs(dj) > tolerance:
|
||||
# Disabled the following because the "d1 == d2" does
|
||||
# check does not take tolerance into consideration...
|
||||
pass # force = True
|
||||
elif d1 != d2:
|
||||
if cj < c1:
|
||||
if dj != d1 and ((dj-tolerance < d1) != (d1 < d2)):
|
||||
force = True
|
||||
else: # c2 < cj
|
||||
if d2 != dj and ((d2 < dj+tolerance) != (d1 < d2)):
|
||||
force = True
|
||||
|
||||
if force:
|
||||
forced.add(i)
|
||||
break
|
||||
|
||||
nd, nc = d, c
|
||||
|
||||
return forced
|
||||
|
||||
def _iup_contour_optimize_dp(delta, coords, forced={}, tolerance=0, lookback=None):
|
||||
"""Straightforward Dynamic-Programming. For each index i, find least-costly encoding of
|
||||
points i to n-1 where i is explicitly encoded. We find this by considering all next
|
||||
explicit points j and check whether interpolation can fill points between i and j.
|
||||
|
||||
Note that solution always encodes last point explicitly. Higher-level is responsible
|
||||
for removing that restriction.
|
||||
|
||||
As major speedup, we stop looking further whenever we see a "forced" point."""
|
||||
|
||||
n = len(delta)
|
||||
if all(abs(x) <= tolerance >= abs(y) for x,y in delta):
|
||||
if lookback is None:
|
||||
lookback = n
|
||||
costs = {-1:0}
|
||||
chain = {-1:None}
|
||||
for i in range(0, n):
|
||||
best_cost = costs[i-1] + 1
|
||||
|
||||
costs[i] = best_cost
|
||||
chain[i] = i - 1
|
||||
|
||||
if i - 1 in forced:
|
||||
continue
|
||||
|
||||
for j in range(i-2, max(i-lookback, -2), -1):
|
||||
|
||||
cost = costs[j] + 1
|
||||
|
||||
if cost < best_cost and _all_interpolatable_in_between(delta, coords, j, i, tolerance):
|
||||
costs[i] = best_cost = cost
|
||||
chain[i] = j
|
||||
|
||||
if j in forced:
|
||||
break
|
||||
|
||||
return chain, costs
|
||||
|
||||
def _rot_list(l, k):
|
||||
"""Rotate list by k items forward. Ie. item at position 0 will be
|
||||
at position k in returned list. Negative k is allowed."""
|
||||
n = len(l)
|
||||
k %= n
|
||||
if not k: return l
|
||||
return l[n-k:] + l[:n-k]
|
||||
|
||||
def _rot_set(s, k, n):
|
||||
k %= n
|
||||
if not k: return s
|
||||
return {(v + k) % n for v in s}
|
||||
|
||||
def _iup_contour_optimize(delta, coords, tolerance=0.):
|
||||
n = len(delta)
|
||||
|
||||
# Get the easy cases out of the way:
|
||||
|
||||
# If all are within tolerance distance of 0, encode nothing:
|
||||
if all(abs(complex(*p)) <= tolerance for p in delta):
|
||||
return [None] * n
|
||||
|
||||
# If there's exactly one point, return it:
|
||||
if n == 1:
|
||||
return delta
|
||||
|
||||
# If all deltas are exactly the same, return just one (the first one):
|
||||
d0 = delta[0]
|
||||
if all(d0 == d for d in delta):
|
||||
return [d0] + [None] * (n-1)
|
||||
|
||||
# TODO
|
||||
# Else, solve the general problem using Dynamic Programming.
|
||||
|
||||
forced = _iup_contour_bound_forced_set(delta, coords, tolerance)
|
||||
# The _iup_contour_optimize_dp() routine returns the optimal encoding
|
||||
# solution given the constraint that the last point is always encoded.
|
||||
# To remove this constraint, we use two different methods, depending on
|
||||
# whether forced set is non-empty or not:
|
||||
|
||||
if forced:
|
||||
# Forced set is non-empty: rotate the contour start point
|
||||
# such that the last point in the list is a forced point.
|
||||
k = (n-1) - max(forced)
|
||||
assert k >= 0
|
||||
|
||||
delta = _rot_list(delta, k)
|
||||
coords = _rot_list(coords, k)
|
||||
forced = _rot_set(forced, k, n)
|
||||
|
||||
chain, costs = _iup_contour_optimize_dp(delta, coords, forced, tolerance)
|
||||
|
||||
# Assemble solution.
|
||||
solution = set()
|
||||
i = n - 1
|
||||
while i is not None:
|
||||
solution.add(i)
|
||||
i = chain[i]
|
||||
assert forced <= solution, (forced, solution)
|
||||
delta = [delta[i] if i in solution else None for i in range(n)]
|
||||
|
||||
delta = _rot_list(delta, -k)
|
||||
else:
|
||||
# Repeat the contour an extra time, solve the 2*n case, then look for solutions of the
|
||||
# circular n-length problem in the solution for 2*n linear case. I cannot prove that
|
||||
# this always produces the optimal solution...
|
||||
chain, costs = _iup_contour_optimize_dp(delta+delta, coords+coords, forced, tolerance, n)
|
||||
best_sol, best_cost = None, n+1
|
||||
|
||||
for start in range(n-1, 2*n-1):
|
||||
# Assemble solution.
|
||||
solution = set()
|
||||
i = start
|
||||
while i > start - n:
|
||||
solution.add(i % n)
|
||||
i = chain[i]
|
||||
if i == start - n:
|
||||
cost = costs[start] - costs[start - n]
|
||||
if cost <= best_cost:
|
||||
best_sol, best_cost = solution, cost
|
||||
|
||||
delta = [delta[i] if i in best_sol else None for i in range(n)]
|
||||
|
||||
|
||||
return delta
|
||||
|
||||
def _optimize_delta(delta, coords, ends, tolerance=0.):
|
||||
def _iup_delta_optimize(delta, coords, ends, tolerance=0.):
|
||||
assert sorted(ends) == ends and len(coords) == (ends[-1]+1 if ends else 0) + 4
|
||||
n = len(coords)
|
||||
ends = ends + [n-4, n-3, n-2, n-1]
|
||||
out = []
|
||||
start = 0
|
||||
for end in ends:
|
||||
contour = _optimize_contour(delta[start:end+1], coords[start:end+1], tolerance)
|
||||
contour = _iup_contour_optimize(delta[start:end+1], coords[start:end+1], tolerance)
|
||||
assert len(contour) == end - start + 1
|
||||
out.extend(contour)
|
||||
start = end+1
|
||||
|
||||
return out
|
||||
|
||||
def _add_gvar(font, model, master_ttfs, tolerance=.5, optimize=True):
|
||||
def _add_gvar(font, model, master_ttfs, tolerance=0.5, optimize=True):
|
||||
|
||||
assert tolerance >= 0
|
||||
|
||||
@ -266,7 +453,7 @@ def _add_gvar(font, model, master_ttfs, tolerance=.5, optimize=True):
|
||||
continue
|
||||
var = TupleVariation(support, delta)
|
||||
if optimize:
|
||||
delta_opt = _optimize_delta(delta, origCoords, endPts, tolerance=tolerance)
|
||||
delta_opt = _iup_delta_optimize(delta, origCoords, endPts, tolerance=tolerance)
|
||||
|
||||
if None in delta_opt:
|
||||
# Use "optimized" version only if smaller...
|
||||
|
@ -561,41 +561,26 @@
|
||||
<delta pt="4" x="-20" y="18"/>
|
||||
<delta pt="5" x="-10" y="26"/>
|
||||
<delta pt="6" x="-6" y="26"/>
|
||||
<delta pt="7" x="8" y="26"/>
|
||||
<delta pt="8" x="8" y="12"/>
|
||||
<delta pt="9" x="8" y="-1"/>
|
||||
<delta pt="10" x="-6" y="-3"/>
|
||||
<delta pt="11" x="-12" y="-3"/>
|
||||
<delta pt="12" x="-22" y="4"/>
|
||||
<delta pt="13" x="-22" y="12"/>
|
||||
<delta pt="14" x="-22" y="17"/>
|
||||
<delta pt="15" x="-12" y="25"/>
|
||||
<delta pt="16" x="-6" y="25"/>
|
||||
<delta pt="17" x="8" y="25"/>
|
||||
<delta pt="18" x="8" y="12"/>
|
||||
<delta pt="19" x="8" y="-3"/>
|
||||
<delta pt="20" x="-6" y="-5"/>
|
||||
<delta pt="21" x="-10" y="-5"/>
|
||||
<delta pt="22" x="-20" y="3"/>
|
||||
<delta pt="23" x="-20" y="9"/>
|
||||
<delta pt="24" x="-20" y="16"/>
|
||||
<delta pt="25" x="-10" y="23"/>
|
||||
<delta pt="26" x="-6" y="23"/>
|
||||
<delta pt="27" x="8" y="23"/>
|
||||
<delta pt="28" x="8" y="9"/>
|
||||
<delta pt="29" x="8" y="-5"/>
|
||||
<delta pt="30" x="-7" y="-1"/>
|
||||
<delta pt="31" x="-13" y="-1"/>
|
||||
<delta pt="32" x="-23" y="6"/>
|
||||
<delta pt="33" x="-23" y="12"/>
|
||||
<delta pt="34" x="-23" y="19"/>
|
||||
<delta pt="35" x="-13" y="27"/>
|
||||
<delta pt="36" x="-7" y="27"/>
|
||||
<delta pt="37" x="-2" y="27"/>
|
||||
<delta pt="38" x="8" y="19"/>
|
||||
<delta pt="39" x="8" y="12"/>
|
||||
<delta pt="40" x="8" y="-1"/>
|
||||
<delta pt="41" x="-7" y="-5"/>
|
||||
<delta pt="42" x="-13" y="-5"/>
|
||||
<delta pt="43" x="-23" y="2"/>
|
||||
<delta pt="44" x="-23" y="9"/>
|
||||
@ -605,49 +590,28 @@
|
||||
<delta pt="48" x="-2" y="23"/>
|
||||
<delta pt="49" x="8" y="14"/>
|
||||
<delta pt="50" x="8" y="9"/>
|
||||
<delta pt="51" x="8" y="-5"/>
|
||||
<delta pt="52" x="-8" y="-1"/>
|
||||
<delta pt="53" x="-13" y="-1"/>
|
||||
<delta pt="54" x="-22" y="8"/>
|
||||
<delta pt="55" x="-22" y="14"/>
|
||||
<delta pt="56" x="-22" y="20"/>
|
||||
<delta pt="57" x="-13" y="27"/>
|
||||
<delta pt="58" x="-8" y="27"/>
|
||||
<delta pt="59" x="6" y="27"/>
|
||||
<delta pt="60" x="6" y="14"/>
|
||||
<delta pt="61" x="6" y="-1"/>
|
||||
<delta pt="62" x="-8" y="-5"/>
|
||||
<delta pt="63" x="-13" y="-5"/>
|
||||
<delta pt="64" x="-22" y="3"/>
|
||||
<delta pt="65" x="-22" y="10"/>
|
||||
<delta pt="66" x="-22" y="16"/>
|
||||
<delta pt="67" x="-13" y="22"/>
|
||||
<delta pt="68" x="-8" y="22"/>
|
||||
<delta pt="69" x="6" y="22"/>
|
||||
<delta pt="70" x="6" y="10"/>
|
||||
<delta pt="71" x="6" y="-5"/>
|
||||
<delta pt="72" x="-9" y="-1"/>
|
||||
<delta pt="73" x="-15" y="-1"/>
|
||||
<delta pt="74" x="-25" y="6"/>
|
||||
<delta pt="75" x="-25" y="12"/>
|
||||
<delta pt="76" x="-25" y="19"/>
|
||||
<delta pt="77" x="-15" y="27"/>
|
||||
<delta pt="78" x="-9" y="27"/>
|
||||
<delta pt="79" x="-3" y="27"/>
|
||||
<delta pt="80" x="7" y="19"/>
|
||||
<delta pt="81" x="7" y="12"/>
|
||||
<delta pt="82" x="7" y="-1"/>
|
||||
<delta pt="83" x="-9" y="-5"/>
|
||||
<delta pt="84" x="-15" y="-5"/>
|
||||
<delta pt="85" x="-25" y="1"/>
|
||||
<delta pt="86" x="-25" y="9"/>
|
||||
<delta pt="87" x="-25" y="15"/>
|
||||
<delta pt="88" x="-15" y="24"/>
|
||||
<delta pt="89" x="-9" y="24"/>
|
||||
<delta pt="90" x="-3" y="24"/>
|
||||
<delta pt="91" x="7" y="15"/>
|
||||
<delta pt="92" x="7" y="9"/>
|
||||
<delta pt="93" x="7" y="-5"/>
|
||||
<delta pt="94" x="-8" y="-1"/>
|
||||
<delta pt="95" x="-16" y="-1"/>
|
||||
<delta pt="96" x="-25" y="4"/>
|
||||
@ -655,29 +619,19 @@
|
||||
<delta pt="98" x="-25" y="18"/>
|
||||
<delta pt="99" x="-16" y="26"/>
|
||||
<delta pt="100" x="-8" y="26"/>
|
||||
<delta pt="101" x="6" y="26"/>
|
||||
<delta pt="102" x="6" y="12"/>
|
||||
<delta pt="103" x="6" y="-1"/>
|
||||
<delta pt="104" x="-10" y="-3"/>
|
||||
<delta pt="105" x="-25" y="-3"/>
|
||||
<delta pt="106" x="-25" y="12"/>
|
||||
<delta pt="107" x="-25" y="25"/>
|
||||
<delta pt="108" x="-10" y="25"/>
|
||||
<delta pt="109" x="-3" y="25"/>
|
||||
<delta pt="110" x="5" y="17"/>
|
||||
<delta pt="111" x="5" y="12"/>
|
||||
<delta pt="112" x="5" y="-3"/>
|
||||
<delta pt="113" x="-8" y="-4"/>
|
||||
<delta pt="114" x="-16" y="-4"/>
|
||||
<delta pt="115" x="-25" y="3"/>
|
||||
<delta pt="116" x="-25" y="10"/>
|
||||
<delta pt="117" x="-25" y="17"/>
|
||||
<delta pt="118" x="-16" y="24"/>
|
||||
<delta pt="119" x="-8" y="24"/>
|
||||
<delta pt="120" x="6" y="24"/>
|
||||
<delta pt="121" x="6" y="10"/>
|
||||
<delta pt="122" x="6" y="-4"/>
|
||||
<delta pt="123" x="0" y="0"/>
|
||||
<delta pt="124" x="-18" y="0"/>
|
||||
<delta pt="125" x="0" y="22"/>
|
||||
<delta pt="126" x="0" y="1"/>
|
||||
@ -717,32 +671,18 @@
|
||||
<glyphVariations glyph="uni0308">
|
||||
<tuple>
|
||||
<coord axis="wght" value="1.0"/>
|
||||
<delta pt="0" x="-28" y="-40"/>
|
||||
<delta pt="1" x="-49" y="-40"/>
|
||||
<delta pt="2" x="-76" y="-12"/>
|
||||
<delta pt="3" x="-76" y="8"/>
|
||||
<delta pt="4" x="-76" y="28"/>
|
||||
<delta pt="5" x="-49" y="56"/>
|
||||
<delta pt="6" x="-28" y="56"/>
|
||||
<delta pt="7" x="-7" y="56"/>
|
||||
<delta pt="8" x="20" y="28"/>
|
||||
<delta pt="9" x="20" y="8"/>
|
||||
<delta pt="10" x="20" y="-12"/>
|
||||
<delta pt="11" x="-7" y="-40"/>
|
||||
<delta pt="12" x="28" y="-40"/>
|
||||
<delta pt="13" x="7" y="-40"/>
|
||||
<delta pt="14" x="-20" y="-12"/>
|
||||
<delta pt="15" x="-20" y="8"/>
|
||||
<delta pt="16" x="-20" y="28"/>
|
||||
<delta pt="17" x="7" y="56"/>
|
||||
<delta pt="18" x="28" y="56"/>
|
||||
<delta pt="19" x="49" y="56"/>
|
||||
<delta pt="20" x="76" y="28"/>
|
||||
<delta pt="21" x="76" y="8"/>
|
||||
<delta pt="22" x="76" y="-12"/>
|
||||
<delta pt="23" x="49" y="-40"/>
|
||||
<delta pt="24" x="0" y="0"/>
|
||||
<delta pt="25" x="0" y="0"/>
|
||||
<delta pt="26" x="0" y="56"/>
|
||||
<delta pt="27" x="0" y="40"/>
|
||||
</tuple>
|
||||
|
Loading…
x
Reference in New Issue
Block a user