Skip to content

Instantly share code, notes, and snippets.

@simoncozens
Created September 27, 2016 10:04
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save simoncozens/b3b7d132b0ad2427924cbddc1f28ef35 to your computer and use it in GitHub Desktop.
Save simoncozens/b3b7d132b0ad2427924cbddc1f28ef35 to your computer and use it in GitHub Desktop.
OpenType Variation Instance Generator
#!/usr/bin/python
from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont
import sys
import argparse
parser = argparse.ArgumentParser(description="OpenType Variation Instance Generator")
parser.add_argument('font', help="font file")
parser.add_argument('-a', '--all', action="store_true", help="Generate all instances")
parser.add_argument('-o', '--output', help="Output directory", default = ".")
args = parser.parse_args()
infont = TTFont(args.font)
if "CFF2" in infont:
print("Can't deal with CFF2 fonts yet.")
sys.exit(1)
if not ("fvar" in infont):
print("That's not a variable font.")
sys.exit(1)
fvar = infont["fvar"]
def normalizeCoord(coord, axis):
# XXX There might be an avar table which will ruin your day
if coord < axis.minValue: coord = axis.minValue
if coord > axis.maxValue: coord = axis.maxValue
if coord < axis.defaultValue:
return -(axis.defaultValue - coord) / (axis.defaultValue - axis.minValue)
if (coord > axis.defaultValue):
return (coord - axis.defaultValue) / (axis.maxValue - axis.defaultValue)
return 0
def interpolate(font, coords):
gvar = font["gvar"]
for glyphName in font.getGlyphOrder():
# if glyphName != "H": continue
# print("Glyph: %s" % glyphName)
glyph = font["glyf"].glyphs.get(glyphName)
variations = gvar.variations.get(glyphName)
if glyph.numberOfContours < 1: continue
netAdjustments = [None] * (len(glyph.coordinates)+4)
if not variations: continue
for v in variations:
S = 1
for tag, inst in coords.items():
vRange = v.axes.get(tag)
AS = 1
if not vRange: continue
minValue, peakValue, maxValue = vRange
if inst < minValue or inst > maxValue: AS =0
elif inst == peakValue: AS = 1
elif inst < peakValue:
AS = (inst - minValue) / (peakValue - minValue)
else:
AS = (maxValue - inst) / (maxValue - peakValue)
S *= AS
for i, point in enumerate(v.coordinates):
if point is not None:
if not netAdjustments[i]: netAdjustments[i] = {"x": 0, "y": 0}
netAdjustments[i]["x"] += point[0] * S
netAdjustments[i]["y"] += point[1] * S
for index in range(len(glyph.coordinates)):
cX = glyph.coordinates[index][0] + netAdjustments[index]["x"]
cY = glyph.coordinates[index][1] + netAdjustments[index]["y"]
glyph.coordinates[index] = (cX,cY)
# print(glyph.coordinates[index])
# XXX Watch out for components
# Normally this should be done with HVAR, rather than phantom points
# This isn't correct, anyway. LSBs are all wrong
metric = font["hmtx"].metrics[glyphName]
advanceAdjustment = netAdjustments[len(glyph.coordinates)+1]["x"]
font["hmtx"].metrics[glyphName] = (int(metric[0] + advanceAdjustment), metric[1])
# XXX Now apply OS/2 etc. variations
for instance in fvar.instances:
name = infont["name"].getDebugName(instance.nameID)
origname = infont["name"].getDebugName(1)
for instance in fvar.instances:
name = infont["name"].getDebugName(instance.nameID)
# if name != "Light Condensed": continue
coords = {}
output = name + " ("
for axis in fvar.axes:
axisName = infont["name"].getDebugName(axis.nameID)
coord = instance.coordinates[axis.axisTag]
coords[axis.axisTag] = normalizeCoord(coord, axis)
output += ("%s=%s, "% (axisName, coords[axis.axisTag]))
outfont = TTFont(args.font)
interpolate(outfont, coords)
outname = (args.output+"/"+origname+" "+name+".ttf").replace(" ","-")
output=output[:-2] + ") -> %s" % outname
print output
outfont.save(outname)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment