2016-04-15 13:56:37 -07:00
|
|
|
"""
|
|
|
|
Module for dealing with 'gvar'-style font variations, also known as run-time
|
|
|
|
interpolation.
|
2016-04-12 23:52:03 -07:00
|
|
|
|
2016-04-15 13:56:37 -07:00
|
|
|
The ideas here are very similar to MutatorMath. There is even code to read
|
2016-08-15 11:59:53 -07:00
|
|
|
MutatorMath .designspace files in the varLib.designspace module.
|
2016-04-15 13:56:37 -07:00
|
|
|
|
|
|
|
For now, if you run this file on a designspace file, it tries to find
|
|
|
|
ttf-interpolatable files for the masters and build a GX variation font from
|
|
|
|
them. Such ttf-interpolatable and designspace files can be generated from
|
|
|
|
a Glyphs source, eg., using noto-source as an example:
|
|
|
|
|
|
|
|
$ fontmake -o ttf-interpolatable -g NotoSansArabic-MM.glyphs
|
|
|
|
|
|
|
|
Then you can make a GX font this way:
|
|
|
|
|
|
|
|
$ python fonttools/Lib/fontTools/varLib/__init__.py master_ufo/NotoSansArabic.designspace
|
|
|
|
|
|
|
|
API *will* change in near future.
|
|
|
|
"""
|
2016-04-12 23:52:03 -07:00
|
|
|
from __future__ import print_function, division, absolute_import
|
|
|
|
from fontTools.misc.py23 import *
|
2016-07-01 15:31:00 -07:00
|
|
|
from fontTools.ttLib import TTFont, newTable
|
2016-04-14 18:27:44 -07:00
|
|
|
from fontTools.ttLib.tables._n_a_m_e import NameRecord
|
2016-07-01 15:31:00 -07:00
|
|
|
from fontTools.ttLib.tables._f_v_a_r import Axis, NamedInstance
|
2016-04-27 00:21:46 -07:00
|
|
|
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
|
2016-07-01 15:31:00 -07:00
|
|
|
from fontTools.ttLib.tables._g_v_a_r import GlyphVariation
|
|
|
|
from fontTools.ttLib.tables import otTables as ot
|
2016-08-15 13:56:13 -07:00
|
|
|
from fontTools.varLib import designspace, models, builder
|
2016-04-18 16:48:02 -07:00
|
|
|
import warnings
|
2016-04-14 18:27:44 -07:00
|
|
|
import os.path
|
2016-04-12 23:52:03 -07:00
|
|
|
|
2016-04-14 18:27:44 -07:00
|
|
|
#
|
|
|
|
# Creation routines
|
|
|
|
#
|
|
|
|
|
|
|
|
# TODO: Move to name table proper; also, is mac_roman ok for ASCII names?
|
|
|
|
def _AddName(font, name):
|
|
|
|
"""(font, "Bold") --> NameRecord"""
|
2016-09-02 17:33:25 -07:00
|
|
|
name = tounicode(name)
|
|
|
|
|
2016-04-14 18:27:44 -07:00
|
|
|
nameTable = font.get("name")
|
|
|
|
namerec = NameRecord()
|
|
|
|
namerec.nameID = 1 + max([n.nameID for n in nameTable.names] + [256])
|
2016-08-15 13:56:13 -07:00
|
|
|
namerec.string = name
|
2016-09-02 17:33:25 -07:00
|
|
|
namerec.platformID, namerec.platEncID, namerec.langID = (3, 1, 0x409)
|
2016-04-14 18:27:44 -07:00
|
|
|
nameTable.names.append(namerec)
|
|
|
|
return namerec
|
|
|
|
|
|
|
|
# Move to fvar table proper?
|
2016-08-17 17:08:58 -07:00
|
|
|
def _add_fvar(font, axes, instances, axis_map):
|
2016-09-02 17:29:22 -07:00
|
|
|
"""
|
|
|
|
Add 'fvar' table to font.
|
|
|
|
|
|
|
|
axes is a dictionary mapping axis-id to axis (min,default,max)
|
|
|
|
coordinate values.
|
|
|
|
|
|
|
|
instances is list of dictionary objects with 'location', 'stylename',
|
|
|
|
and possibly 'postscriptfontname' entries.
|
|
|
|
|
|
|
|
axisMap is dictionary mapping axis-id to (axis-tag, axis-name).
|
|
|
|
"""
|
|
|
|
|
2016-04-14 18:27:44 -07:00
|
|
|
assert "fvar" not in font
|
2016-07-01 15:31:00 -07:00
|
|
|
font['fvar'] = fvar = newTable('fvar')
|
2016-04-14 18:27:44 -07:00
|
|
|
|
2016-08-17 17:08:58 -07:00
|
|
|
for iden in sorted(axes.keys()):
|
2016-04-14 18:27:44 -07:00
|
|
|
axis = Axis()
|
2016-08-17 17:08:58 -07:00
|
|
|
axis.axisTag = Tag(axis_map[iden][0])
|
|
|
|
axis.minValue, axis.defaultValue, axis.maxValue = axes[iden]
|
2016-09-02 18:12:14 -07:00
|
|
|
axis.axisNameID = _AddName(font, axis_map[iden][1]).nameID
|
2016-04-14 18:27:44 -07:00
|
|
|
fvar.axes.append(axis)
|
|
|
|
|
2016-09-02 17:29:22 -07:00
|
|
|
for instance in instances:
|
|
|
|
coordinates = instance['location']
|
|
|
|
name = instance['stylename']
|
2016-09-02 18:12:14 -07:00
|
|
|
psname = instance.get('postscriptfontname')
|
2016-09-02 17:29:22 -07:00
|
|
|
|
2016-04-14 18:27:44 -07:00
|
|
|
inst = NamedInstance()
|
2016-09-02 18:12:14 -07:00
|
|
|
inst.subfamilyNameID = _AddName(font, name).nameID
|
|
|
|
if psname:
|
2016-09-06 13:46:13 -07:00
|
|
|
inst.postscriptNameID = _AddName(font, psname).nameID
|
2016-08-17 17:08:58 -07:00
|
|
|
inst.coordinates = {axis_map[k][0]:v for k,v in coordinates.items()}
|
2016-04-14 18:27:44 -07:00
|
|
|
fvar.instances.append(inst)
|
|
|
|
|
2016-04-14 23:55:11 -07:00
|
|
|
# TODO Move to glyf or gvar table proper
|
|
|
|
def _GetCoordinates(font, glyphName):
|
2016-04-14 18:27:44 -07:00
|
|
|
"""font, glyphName --> glyph coordinates as expected by "gvar" table
|
|
|
|
|
|
|
|
The result includes four "phantom points" for the glyph metrics,
|
|
|
|
as mandated by the "gvar" spec.
|
|
|
|
"""
|
2016-04-14 23:55:11 -07:00
|
|
|
glyf = font["glyf"]
|
|
|
|
if glyphName not in glyf.glyphs: return None
|
|
|
|
glyph = glyf[glyphName]
|
2016-04-14 18:27:44 -07:00
|
|
|
if glyph.isComposite():
|
2016-07-29 14:44:02 -07:00
|
|
|
coord = GlyphCoordinates([(getattr(c, 'x', 0),getattr(c, 'y', 0)) for c in glyph.components])
|
2016-04-14 23:55:11 -07:00
|
|
|
control = [c.glyphName for c in glyph.components]
|
2016-04-14 18:27:44 -07:00
|
|
|
else:
|
2016-04-14 23:55:11 -07:00
|
|
|
allData = glyph.getCoordinates(glyf)
|
|
|
|
coord = allData[0]
|
|
|
|
control = allData[1:]
|
|
|
|
|
2016-04-14 18:27:44 -07:00
|
|
|
# Add phantom points for (left, right, top, bottom) positions.
|
|
|
|
horizontalAdvanceWidth, leftSideBearing = font["hmtx"].metrics[glyphName]
|
|
|
|
if not hasattr(glyph, 'xMin'):
|
2016-04-14 23:55:11 -07:00
|
|
|
glyph.recalcBounds(glyf)
|
2016-04-14 18:27:44 -07:00
|
|
|
leftSideX = glyph.xMin - leftSideBearing
|
|
|
|
rightSideX = leftSideX + horizontalAdvanceWidth
|
|
|
|
# XXX these are incorrect. Load vmtx and fix.
|
|
|
|
topSideY = glyph.yMax
|
|
|
|
bottomSideY = -glyph.yMin
|
2016-04-27 00:25:31 -07:00
|
|
|
coord = coord.copy()
|
2016-04-14 18:27:44 -07:00
|
|
|
coord.extend([(leftSideX, 0),
|
|
|
|
(rightSideX, 0),
|
|
|
|
(0, topSideY),
|
|
|
|
(0, bottomSideY)])
|
2016-04-14 23:55:11 -07:00
|
|
|
|
|
|
|
return coord, control
|
2016-04-14 18:27:44 -07:00
|
|
|
|
2016-04-27 01:17:09 -07:00
|
|
|
# TODO Move to glyf or gvar table proper
|
|
|
|
def _SetCoordinates(font, glyphName, coord):
|
|
|
|
glyf = font["glyf"]
|
|
|
|
assert glyphName in glyf.glyphs
|
|
|
|
glyph = glyf[glyphName]
|
|
|
|
|
|
|
|
# Handle phantom points for (left, right, top, bottom) positions.
|
|
|
|
assert len(coord) >= 4
|
|
|
|
if not hasattr(glyph, 'xMin'):
|
|
|
|
glyph.recalcBounds(glyf)
|
|
|
|
leftSideX = coord[-4][0]
|
|
|
|
rightSideX = coord[-3][0]
|
|
|
|
topSideY = coord[-2][1]
|
|
|
|
bottomSideY = coord[-1][1]
|
|
|
|
|
|
|
|
for _ in range(4):
|
|
|
|
del coord[-1]
|
|
|
|
|
|
|
|
if glyph.isComposite():
|
|
|
|
assert len(coord) == len(glyph.components)
|
|
|
|
for p,comp in zip(coord, glyph.components):
|
2016-07-29 14:44:02 -07:00
|
|
|
if hasattr(comp, 'x'):
|
|
|
|
comp.x,comp.y = p
|
2016-04-27 01:17:09 -07:00
|
|
|
elif glyph.numberOfContours is 0:
|
|
|
|
assert len(coord) == 0
|
|
|
|
else:
|
|
|
|
assert len(coord) == len(glyph.coordinates)
|
|
|
|
glyph.coordinates = coord
|
|
|
|
|
2016-06-07 15:51:54 -07:00
|
|
|
glyph.recalcBounds(glyf)
|
|
|
|
|
|
|
|
horizontalAdvanceWidth = rightSideX - leftSideX
|
|
|
|
leftSideBearing = glyph.xMin - leftSideX
|
|
|
|
# XXX Handle vertical
|
|
|
|
# XXX Remove the round when https://github.com/behdad/fonttools/issues/593 is fixed
|
|
|
|
font["hmtx"].metrics[glyphName] = int(round(horizontalAdvanceWidth)), int(round(leftSideBearing))
|
|
|
|
|
2016-08-09 20:53:19 -07:00
|
|
|
|
|
|
|
def _add_gvar(font, model, master_ttfs):
|
|
|
|
|
2016-04-14 23:55:11 -07:00
|
|
|
print("Generating gvar")
|
|
|
|
assert "gvar" not in font
|
2016-07-01 15:31:00 -07:00
|
|
|
gvar = font["gvar"] = newTable('gvar')
|
2016-04-14 18:27:44 -07:00
|
|
|
gvar.version = 1
|
|
|
|
gvar.reserved = 0
|
|
|
|
gvar.variations = {}
|
|
|
|
|
2016-04-14 23:55:11 -07:00
|
|
|
for glyph in font.getGlyphOrder():
|
2016-04-14 18:27:44 -07:00
|
|
|
|
2016-04-14 23:55:11 -07:00
|
|
|
allData = [_GetCoordinates(m, glyph) for m in master_ttfs]
|
|
|
|
allCoords = [d[0] for d in allData]
|
|
|
|
allControls = [d[1] for d in allData]
|
|
|
|
control = allControls[0]
|
|
|
|
if (any(c != control for c in allControls)):
|
|
|
|
warnings.warn("glyph %s has incompatible masters; skipping" % glyph)
|
2016-04-14 18:27:44 -07:00
|
|
|
continue
|
2016-04-14 23:55:11 -07:00
|
|
|
del allControls
|
2016-04-14 18:27:44 -07:00
|
|
|
|
2016-07-01 15:31:00 -07:00
|
|
|
# Update gvar
|
2016-04-14 18:27:44 -07:00
|
|
|
gvar.variations[glyph] = []
|
2016-04-15 08:56:04 -07:00
|
|
|
deltas = model.getDeltas(allCoords)
|
|
|
|
supports = model.supports
|
|
|
|
assert len(deltas) == len(supports)
|
2016-07-01 15:31:00 -07:00
|
|
|
for i,(delta,support) in enumerate(zip(deltas[1:], supports[1:])):
|
2016-04-15 08:56:04 -07:00
|
|
|
var = GlyphVariation(support, delta)
|
|
|
|
gvar.variations[glyph].append(var)
|
2016-04-14 18:27:44 -07:00
|
|
|
|
2016-08-10 01:48:09 -07:00
|
|
|
def _add_HVAR(font, model, master_ttfs, axes):
|
2016-07-01 15:31:00 -07:00
|
|
|
|
2016-08-09 20:53:19 -07:00
|
|
|
print("Generating HVAR")
|
|
|
|
|
|
|
|
hAdvanceDeltas = {}
|
|
|
|
metricses = [m["hmtx"].metrics for m in master_ttfs]
|
|
|
|
for glyph in font.getGlyphOrder():
|
|
|
|
hAdvances = [metrics[glyph][0] for metrics in metricses]
|
2016-08-15 16:25:35 -07:00
|
|
|
# TODO move round somewhere else?
|
|
|
|
hAdvanceDeltas[glyph] = tuple(round(d) for d in model.getDeltas(hAdvances)[1:])
|
2016-07-01 15:31:00 -07:00
|
|
|
|
|
|
|
# We only support the direct mapping right now.
|
|
|
|
|
|
|
|
supports = model.supports[1:]
|
2016-08-10 01:48:09 -07:00
|
|
|
varTupleList = builder.buildVarRegionList(supports, axes.keys())
|
2016-07-01 15:31:00 -07:00
|
|
|
varTupleIndexes = list(range(len(supports)))
|
|
|
|
n = len(supports)
|
|
|
|
items = []
|
|
|
|
zeroes = [0]*n
|
|
|
|
for glyphName in font.getGlyphOrder():
|
|
|
|
items.append(hAdvanceDeltas.get(glyphName, zeroes))
|
|
|
|
while items and items[-1] is zeroes:
|
|
|
|
del items[-1]
|
2016-08-11 01:02:52 -07:00
|
|
|
|
2016-08-11 01:35:56 -07:00
|
|
|
advanceMapping = None
|
|
|
|
# Add indirect mapping to save on duplicates
|
|
|
|
uniq = set(items)
|
|
|
|
# TODO Improve heuristic
|
|
|
|
if (len(items) - len(uniq)) * len(varTupleIndexes) > len(items):
|
|
|
|
newItems = sorted(uniq)
|
|
|
|
mapper = {v:i for i,v in enumerate(newItems)}
|
|
|
|
mapping = [mapper[item] for item in items]
|
2016-08-11 21:42:31 -07:00
|
|
|
while len(mapping) > 1 and mapping[-1] == mapping[-2]:
|
|
|
|
del mapping[-1]
|
2016-08-11 01:35:56 -07:00
|
|
|
advanceMapping = builder.buildVarIdxMap(mapping)
|
|
|
|
items = newItems
|
|
|
|
del mapper, mapping, newItems
|
|
|
|
del uniq
|
2016-08-11 01:02:52 -07:00
|
|
|
|
2016-08-10 03:15:38 -07:00
|
|
|
varData = builder.buildVarData(varTupleIndexes, items)
|
|
|
|
varStore = builder.buildVarStore(varTupleList, [varData])
|
2016-07-01 15:31:00 -07:00
|
|
|
|
|
|
|
assert "HVAR" not in font
|
|
|
|
HVAR = font["HVAR"] = newTable('HVAR')
|
|
|
|
hvar = HVAR.table = ot.HVAR()
|
2016-09-04 20:58:46 -07:00
|
|
|
hvar.Version = 0x00010000
|
2016-07-01 15:31:00 -07:00
|
|
|
hvar.VarStore = varStore
|
2016-08-11 01:35:56 -07:00
|
|
|
hvar.AdvWidthMap = advanceMapping
|
|
|
|
hvar.LsbMap = hvar.RsbMap = None
|
2016-08-10 01:17:45 -07:00
|
|
|
|
2016-07-01 15:31:00 -07:00
|
|
|
|
2016-08-13 03:09:11 -07:00
|
|
|
def _all_equal(lst):
|
|
|
|
it = iter(lst)
|
|
|
|
v0 = next(it)
|
|
|
|
for v in it:
|
|
|
|
if v0 != v:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
def buildVarDevTable(store, master_values):
|
|
|
|
if _all_equal(master_values):
|
|
|
|
return None
|
|
|
|
deltas = master_values
|
|
|
|
return builder.buildVarDevTable(0xdeadbeef)
|
|
|
|
|
2016-08-13 03:14:49 -07:00
|
|
|
def _merge_OTL(font, model, master_ttfs, axes, base_idx):
|
2016-08-13 03:09:11 -07:00
|
|
|
|
|
|
|
print("Merging OpenType Layout tables")
|
|
|
|
|
|
|
|
GDEFs = [m['GDEF'].table for m in master_ttfs]
|
|
|
|
GPOSs = [m['GPOS'].table for m in master_ttfs]
|
|
|
|
GSUBs = [m['GSUB'].table for m in master_ttfs]
|
|
|
|
|
2016-08-13 03:14:49 -07:00
|
|
|
# Reuse the base font's tables
|
|
|
|
for tag in 'GDEF', 'GPOS', 'GSUB':
|
|
|
|
font[tag] = master_ttfs[base_idx][tag]
|
|
|
|
|
2016-08-13 03:09:11 -07:00
|
|
|
GPOS = font['GPOS'].table
|
|
|
|
|
|
|
|
getAnchor = lambda GPOS: GPOS.LookupList.Lookup[4].SubTable[0].MarkArray.MarkRecord[28].MarkAnchor
|
2016-08-15 11:14:52 -07:00
|
|
|
store_builder = builder.OnlineVarStoreBuilder(axes.keys())
|
|
|
|
store_builder.setModel(model)
|
2016-08-13 03:09:11 -07:00
|
|
|
|
|
|
|
anchors = [getAnchor(G) for G in GPOSs]
|
|
|
|
anchor = getAnchor(GPOS)
|
|
|
|
|
|
|
|
XDeviceTable = buildVarDevTable(store_builder, [a.XCoordinate for a in anchors])
|
|
|
|
YDeviceTable = buildVarDevTable(store_builder, [a.YCoordinate for a in anchors])
|
|
|
|
if XDeviceTable or YDeviceTable:
|
|
|
|
anchor.Format = 3
|
|
|
|
anchor.XDeviceTable = XDeviceTable
|
|
|
|
anchor.YDeviceTable = YDeviceTable
|
|
|
|
|
2016-08-15 11:14:52 -07:00
|
|
|
store = store_builder.finish()
|
|
|
|
# TODO insert in GDEF
|
|
|
|
|
2016-08-13 03:09:11 -07:00
|
|
|
|
2016-09-02 17:10:16 -07:00
|
|
|
def build(designspace_filename, master_finder=lambda s:s, axisMap=None):
|
|
|
|
"""
|
|
|
|
Build variation font from a designspace file.
|
2016-04-14 00:31:17 -07:00
|
|
|
|
2016-09-02 17:10:16 -07:00
|
|
|
If master_finder is set, it should be a callable that takes master
|
|
|
|
filename as found in designspace file and map it to master font
|
|
|
|
binary as to be opened (eg. .ttf or .otf).
|
2016-04-14 00:31:17 -07:00
|
|
|
|
2016-09-02 17:29:22 -07:00
|
|
|
If axisMap is set, it should be dictionary mapping axis-id to
|
|
|
|
(axis-tag, axis-name).
|
2016-09-02 17:10:16 -07:00
|
|
|
"""
|
2016-04-14 18:27:44 -07:00
|
|
|
|
2016-08-15 11:59:53 -07:00
|
|
|
masters, instances = designspace.load(designspace_filename)
|
|
|
|
base_idx = None
|
|
|
|
for i,m in enumerate(masters):
|
|
|
|
if 'info' in m and m['info']['copy']:
|
|
|
|
assert base_idx is None
|
|
|
|
base_idx = i
|
2016-04-17 11:53:20 -07:00
|
|
|
assert base_idx is not None, "Cannot find 'base' master; Add <info> element to one of the masters in the .designspace document."
|
2016-04-14 00:31:17 -07:00
|
|
|
|
2016-04-15 13:46:52 -07:00
|
|
|
from pprint import pprint
|
2016-04-14 18:27:44 -07:00
|
|
|
print("Index of base master:", base_idx)
|
|
|
|
|
|
|
|
print("Building GX")
|
|
|
|
print("Loading TTF masters")
|
|
|
|
basedir = os.path.dirname(designspace_filename)
|
2016-09-02 17:10:16 -07:00
|
|
|
master_ttfs = [master_finder(os.path.join(basedir, m['filename'])) for m in masters]
|
2016-04-14 18:27:44 -07:00
|
|
|
master_fonts = [TTFont(ttf_path) for ttf_path in master_ttfs]
|
|
|
|
|
|
|
|
standard_axis_map = {
|
|
|
|
'weight': ('wght', 'Weight'),
|
|
|
|
'width': ('wdth', 'Width'),
|
|
|
|
'slant': ('slnt', 'Slant'),
|
|
|
|
'optical': ('opsz', 'Optical Size'),
|
2016-06-15 18:46:59 +04:00
|
|
|
'custom': ('xxxx', 'Custom'),
|
2016-04-14 18:27:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
axis_map = standard_axis_map
|
|
|
|
if axisMap:
|
|
|
|
axis_map = axis_map.copy()
|
|
|
|
axis_map.update(axisMap)
|
|
|
|
|
|
|
|
# TODO: For weight & width, use OS/2 values and setup 'avar' mapping.
|
|
|
|
|
2016-08-17 17:08:58 -07:00
|
|
|
master_locs = [o['location'] for o in masters]
|
2016-04-14 18:27:44 -07:00
|
|
|
|
|
|
|
axis_tags = set(master_locs[0].keys())
|
|
|
|
assert all(axis_tags == set(m.keys()) for m in master_locs)
|
|
|
|
|
|
|
|
# Set up axes
|
|
|
|
axes = {}
|
|
|
|
for tag in axis_tags:
|
|
|
|
default = master_locs[base_idx][tag]
|
|
|
|
lower = min(m[tag] for m in master_locs)
|
|
|
|
upper = max(m[tag] for m in master_locs)
|
2016-08-15 13:56:13 -07:00
|
|
|
axes[tag] = (lower, default, upper)
|
2016-04-14 18:27:44 -07:00
|
|
|
print("Axes:")
|
|
|
|
pprint(axes)
|
|
|
|
|
2016-09-05 19:14:40 -07:00
|
|
|
print("Master locations:")
|
|
|
|
pprint(master_locs)
|
|
|
|
|
2016-08-13 03:14:49 -07:00
|
|
|
# We can use the base font straight, but it's faster to load it again since
|
|
|
|
# then we won't be recompiling the existing ('glyf', 'hmtx', ...) tables.
|
|
|
|
#gx = master_fonts[base_idx]
|
2016-04-14 18:27:44 -07:00
|
|
|
gx = TTFont(master_ttfs[base_idx])
|
|
|
|
|
2016-09-02 17:29:22 -07:00
|
|
|
# TODO append masters as named-instances as well; needs .designspace change.
|
|
|
|
_add_fvar(gx, axes, instances, axis_map)
|
2016-04-14 18:27:44 -07:00
|
|
|
|
2016-08-15 16:29:21 -07:00
|
|
|
# Normalize master locations
|
|
|
|
master_locs = [models.normalizeLocation(m, axes) for m in master_locs]
|
2016-09-05 19:14:40 -07:00
|
|
|
|
|
|
|
print("Normalized master locations:")
|
|
|
|
pprint(master_locs)
|
2016-08-15 16:29:21 -07:00
|
|
|
|
|
|
|
# Assume single-model for now.
|
|
|
|
model = models.VariationModel(master_locs)
|
|
|
|
assert 0 == model.mapping[base_idx]
|
2016-08-09 20:53:19 -07:00
|
|
|
|
|
|
|
print("Building variations tables")
|
2016-09-06 13:48:23 -07:00
|
|
|
if 'glyf' in gx:
|
|
|
|
_add_gvar(gx, model, master_fonts)
|
2016-08-10 01:48:09 -07:00
|
|
|
_add_HVAR(gx, model, master_fonts, axes)
|
2016-08-13 03:14:49 -07:00
|
|
|
#_merge_OTL(gx, model, master_fonts, axes, base_idx)
|
2016-04-14 18:27:44 -07:00
|
|
|
|
2016-09-02 17:10:16 -07:00
|
|
|
return gx, model, master_ttfs
|
|
|
|
|
|
|
|
|
|
|
|
def main(args=None):
|
|
|
|
|
|
|
|
if args is None:
|
|
|
|
import sys
|
|
|
|
args = sys.argv[1:]
|
|
|
|
|
|
|
|
(designspace_filename,) = args
|
|
|
|
finder = lambda s: s.replace('master_ufo', 'master_ttf_interpolatable').replace('.ufo', '.ttf')
|
|
|
|
outfile = os.path.splitext(designspace_filename)[0] + '-GX.ttf'
|
|
|
|
|
|
|
|
gx, model, master_ttfs = build(designspace_filename, finder)
|
|
|
|
|
|
|
|
print("Saving variation font", outfile)
|
2016-04-14 18:27:44 -07:00
|
|
|
gx.save(outfile)
|
2016-04-14 00:31:17 -07:00
|
|
|
|
2016-04-13 23:51:54 -07:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2016-04-14 00:31:17 -07:00
|
|
|
import sys
|
|
|
|
if len(sys.argv) > 1:
|
|
|
|
main()
|
2016-04-15 13:46:52 -07:00
|
|
|
#sys.exit(0)
|
2016-04-13 23:51:54 -07:00
|
|
|
import doctest, sys
|
|
|
|
sys.exit(doctest.testmod().failed)
|