Created
September 27, 2016 10:04
-
-
Save simoncozens/b3b7d132b0ad2427924cbddc1f28ef35 to your computer and use it in GitHub Desktop.
OpenType Variation Instance Generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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