2016-04-27 01:30:19 -07:00
|
|
|
"""
|
2016-04-27 01:41:48 -07:00
|
|
|
Instantiate a variation font. Run, eg:
|
|
|
|
|
2017-03-03 14:49:11 -08:00
|
|
|
$ python mutator.py ./NotoSansArabic-VF.ttf wght=140 wdth=85
|
2016-04-27 01:30:19 -07:00
|
|
|
"""
|
|
|
|
from __future__ import print_function, division, absolute_import
|
|
|
|
from fontTools.misc.py23 import *
|
|
|
|
from fontTools.ttLib import TTFont
|
|
|
|
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
|
2016-08-15 13:56:13 -07:00
|
|
|
from fontTools.varLib import _GetCoordinates, _SetCoordinates
|
2017-08-09 21:43:44 -07:00
|
|
|
from fontTools.varLib.models import supportScalar, normalizeLocation
|
2016-04-27 01:30:19 -07:00
|
|
|
import os.path
|
|
|
|
|
2017-05-20 22:13:51 -07:00
|
|
|
|
|
|
|
def _iup_segment(coords, rc1, rd1, rc2, rd2):
|
|
|
|
# rc1 = reference coord 1
|
|
|
|
# rd1 = reference delta 1
|
|
|
|
out_arrays = [None, None]
|
|
|
|
for j in 0,1:
|
|
|
|
out_arrays[j] = out = []
|
|
|
|
x1, x2, d1, d2 = rc1[j], rc2[j], rd1[j], rd2[j]
|
|
|
|
|
|
|
|
|
|
|
|
if x1 == x2:
|
|
|
|
n = len(coords)
|
|
|
|
if d1 == d2:
|
|
|
|
out.extend([d1]*n)
|
|
|
|
else:
|
|
|
|
out.extend([0]*n)
|
|
|
|
continue
|
|
|
|
|
|
|
|
if x1 > x2:
|
|
|
|
x1, x2 = x2, x1
|
|
|
|
d1, d2 = d2, d1
|
|
|
|
|
|
|
|
# x1 < x2
|
|
|
|
scale = (d2 - d1) / (x2 - x1)
|
|
|
|
for pair in coords:
|
|
|
|
x = pair[j]
|
|
|
|
|
|
|
|
if x <= x1:
|
|
|
|
d = d1
|
|
|
|
elif x >= x2:
|
|
|
|
d = d2
|
|
|
|
else:
|
|
|
|
# Interpolate
|
|
|
|
d = d1 + (x - x1) * scale
|
|
|
|
|
|
|
|
out.append(d)
|
|
|
|
|
|
|
|
return zip(*out_arrays)
|
|
|
|
|
|
|
|
def _iup_contour(delta, coords):
|
|
|
|
assert len(delta) == len(coords)
|
|
|
|
if None not in delta:
|
|
|
|
return delta
|
|
|
|
|
|
|
|
n = len(delta)
|
|
|
|
# indices of points with explicit deltas
|
|
|
|
indices = [i for i,v in enumerate(delta) if v is not None]
|
|
|
|
if not indices:
|
|
|
|
# All deltas are None. Return 0,0 for all.
|
|
|
|
return [(0,0)]*n
|
|
|
|
|
|
|
|
out = []
|
|
|
|
it = iter(indices)
|
|
|
|
start = next(it)
|
|
|
|
if start != 0:
|
|
|
|
# Initial segment that wraps around
|
|
|
|
i1, i2, ri1, ri2 = 0, start, start, indices[-1]
|
|
|
|
out.extend(_iup_segment(coords[i1:i2], coords[ri1], delta[ri1], coords[ri2], delta[ri2]))
|
|
|
|
out.append(delta[start])
|
|
|
|
for end in it:
|
|
|
|
if end - start > 1:
|
|
|
|
i1, i2, ri1, ri2 = start+1, end, start, end
|
|
|
|
out.extend(_iup_segment(coords[i1:i2], coords[ri1], delta[ri1], coords[ri2], delta[ri2]))
|
|
|
|
out.append(delta[end])
|
|
|
|
start = end
|
|
|
|
if start != n-1:
|
|
|
|
# Final segment that wraps around
|
|
|
|
i1, i2, ri1, ri2 = start+1, n, start, indices[0]
|
|
|
|
out.extend(_iup_segment(coords[i1:i2], coords[ri1], delta[ri1], coords[ri2], delta[ri2]))
|
|
|
|
|
|
|
|
assert len(delta) == len(out), (len(delta), len(out))
|
|
|
|
return out
|
|
|
|
|
|
|
|
def _iup_delta(delta, coords, ends):
|
|
|
|
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:
|
|
|
|
end += 1
|
|
|
|
contour = _iup_contour(delta[start:end], coords[start:end])
|
|
|
|
out.extend(contour)
|
|
|
|
start = end
|
|
|
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
2016-04-27 01:30:19 -07:00
|
|
|
def main(args=None):
|
|
|
|
|
|
|
|
if args is None:
|
2016-08-15 16:29:21 -07:00
|
|
|
import sys
|
2016-04-27 01:30:19 -07:00
|
|
|
args = sys.argv[1:]
|
|
|
|
|
|
|
|
varfilename = args[0]
|
|
|
|
locargs = args[1:]
|
|
|
|
outfile = os.path.splitext(varfilename)[0] + '-instance.ttf'
|
|
|
|
|
|
|
|
loc = {}
|
|
|
|
for arg in locargs:
|
2016-08-15 16:16:03 -07:00
|
|
|
tag,val = arg.split('=')
|
2016-05-11 14:56:08 +02:00
|
|
|
assert len(tag) <= 4
|
2016-08-15 16:16:03 -07:00
|
|
|
loc[tag.ljust(4)] = float(val)
|
2016-04-27 01:30:19 -07:00
|
|
|
print("Location:", loc)
|
|
|
|
|
2017-03-03 14:49:11 -08:00
|
|
|
print("Loading variable font")
|
2016-04-27 01:30:19 -07:00
|
|
|
varfont = TTFont(varfilename)
|
|
|
|
|
|
|
|
fvar = varfont['fvar']
|
2016-08-15 16:13:58 -07:00
|
|
|
axes = {a.axisTag:(a.minValue,a.defaultValue,a.maxValue) for a in fvar.axes}
|
2017-05-22 19:40:20 -07:00
|
|
|
# TODO Apply avar
|
2016-08-15 16:13:58 -07:00
|
|
|
# TODO Round to F2Dot14?
|
|
|
|
loc = normalizeLocation(loc, axes)
|
2016-04-27 01:30:19 -07:00
|
|
|
# Location is normalized now
|
|
|
|
print("Normalized location:", loc)
|
|
|
|
|
|
|
|
gvar = varfont['gvar']
|
2017-05-04 12:08:04 +02:00
|
|
|
glyf = varfont['glyf']
|
|
|
|
# get list of glyph names in gvar sorted by component depth
|
|
|
|
glyphnames = sorted(
|
|
|
|
gvar.variations.keys(),
|
|
|
|
key=lambda name: (
|
2017-05-04 12:28:02 +02:00
|
|
|
glyf[name].getCompositeMaxpValues(glyf).maxComponentDepth
|
2017-05-04 12:08:04 +02:00
|
|
|
if glyf[name].isComposite() else 0,
|
|
|
|
name))
|
2017-05-03 19:13:49 +02:00
|
|
|
for glyphname in glyphnames:
|
|
|
|
variations = gvar.variations[glyphname]
|
2016-04-27 01:30:19 -07:00
|
|
|
coordinates,_ = _GetCoordinates(varfont, glyphname)
|
2017-05-20 21:08:11 -07:00
|
|
|
origCoords, endPts = None, None
|
2016-04-27 01:30:19 -07:00
|
|
|
for var in variations:
|
2017-08-07 17:04:42 -07:00
|
|
|
scalar = supportScalar(loc, var.axes, ot=True)
|
2016-04-27 01:30:19 -07:00
|
|
|
if not scalar: continue
|
2017-05-20 21:08:11 -07:00
|
|
|
delta = var.coordinates
|
|
|
|
if None in delta:
|
|
|
|
if origCoords is None:
|
|
|
|
origCoords,control = _GetCoordinates(varfont, glyphname)
|
|
|
|
endPts = control[1] if control[0] >= 1 else list(range(len(control[1])))
|
2017-05-20 22:13:51 -07:00
|
|
|
delta = _iup_delta(delta, origCoords, endPts)
|
2017-05-20 21:08:11 -07:00
|
|
|
coordinates += GlyphCoordinates(delta) * scalar
|
2016-04-27 01:30:19 -07:00
|
|
|
_SetCoordinates(varfont, glyphname, coordinates)
|
|
|
|
|
2017-10-05 13:32:06 +02:00
|
|
|
# Interpolate cvt
|
|
|
|
|
|
|
|
if 'cvar' in varfont:
|
|
|
|
cvar = varfont['cvar']
|
|
|
|
cvt = varfont['cvt ']
|
|
|
|
deltas = {}
|
|
|
|
for var in cvar.variations:
|
|
|
|
scalar = supportScalar(loc, var.axes)
|
|
|
|
if not scalar: continue
|
|
|
|
for i, c in enumerate(var.coordinates):
|
|
|
|
if c is not None:
|
|
|
|
deltas[i] = deltas.get(i, 0) + scalar * c
|
|
|
|
for i, delta in deltas.items():
|
|
|
|
cvt[i] += int(round(delta))
|
|
|
|
|
2017-03-04 23:30:37 -08:00
|
|
|
print("Removing variable tables")
|
|
|
|
for tag in ('avar','cvar','fvar','gvar','HVAR','MVAR','VVAR','STAT'):
|
2016-04-27 01:30:19 -07:00
|
|
|
if tag in varfont:
|
|
|
|
del varfont[tag]
|
|
|
|
|
|
|
|
print("Saving instance font", outfile)
|
|
|
|
varfont.save(outfile)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
import sys
|
|
|
|
if len(sys.argv) > 1:
|
2017-01-11 12:10:58 +00:00
|
|
|
sys.exit(main())
|
2017-01-11 12:24:04 +00:00
|
|
|
import doctest
|
2016-04-27 01:30:19 -07:00
|
|
|
sys.exit(doctest.testmod().failed)
|