1*e1fe3e4aSElliott Hughes#! /usr/bin/env python3 2*e1fe3e4aSElliott Hughes 3*e1fe3e4aSElliott Hughes# Illustrates how a fonttools script can construct variable fonts. 4*e1fe3e4aSElliott Hughes# 5*e1fe3e4aSElliott Hughes# This script reads Roboto-Thin.ttf, Roboto-Regular.ttf, and 6*e1fe3e4aSElliott Hughes# Roboto-Black.ttf from /tmp/Roboto, and writes a Multiple Master GX 7*e1fe3e4aSElliott Hughes# font named "Roboto.ttf" into the current working directory. 8*e1fe3e4aSElliott Hughes# This output font supports interpolation along the Weight axis, 9*e1fe3e4aSElliott Hughes# and it contains named instances for "Thin", "Light", "Regular", 10*e1fe3e4aSElliott Hughes# "Bold", and "Black". 11*e1fe3e4aSElliott Hughes# 12*e1fe3e4aSElliott Hughes# All input fonts must contain the same set of glyphs, and these glyphs 13*e1fe3e4aSElliott Hughes# need to have the same control points in the same order. Note that this 14*e1fe3e4aSElliott Hughes# is *not* the case for the normal Roboto fonts that can be downloaded 15*e1fe3e4aSElliott Hughes# from Google. This demo script prints a warning for any problematic 16*e1fe3e4aSElliott Hughes# glyphs; in the resulting font, these glyphs will not be interpolated 17*e1fe3e4aSElliott Hughes# and get rendered in the "Regular" weight. 18*e1fe3e4aSElliott Hughes# 19*e1fe3e4aSElliott Hughes# Usage: 20*e1fe3e4aSElliott Hughes# $ mkdir /tmp/Roboto && cp Roboto-*.ttf /tmp/Roboto 21*e1fe3e4aSElliott Hughes# $ ./interpolate.py && open Roboto.ttf 22*e1fe3e4aSElliott Hughes 23*e1fe3e4aSElliott Hughes 24*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import TTFont 25*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables._n_a_m_e import NameRecord 26*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis, NamedInstance 27*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables._g_v_a_r import table__g_v_a_r, TupleVariation 28*e1fe3e4aSElliott Hughesimport logging 29*e1fe3e4aSElliott Hughes 30*e1fe3e4aSElliott Hughes 31*e1fe3e4aSElliott Hughesdef AddFontVariations(font): 32*e1fe3e4aSElliott Hughes assert "fvar" not in font 33*e1fe3e4aSElliott Hughes fvar = font["fvar"] = table__f_v_a_r() 34*e1fe3e4aSElliott Hughes 35*e1fe3e4aSElliott Hughes weight = Axis() 36*e1fe3e4aSElliott Hughes weight.axisTag = "wght" 37*e1fe3e4aSElliott Hughes weight.nameID = AddName(font, "Weight").nameID 38*e1fe3e4aSElliott Hughes weight.minValue, weight.defaultValue, weight.maxValue = (100, 400, 900) 39*e1fe3e4aSElliott Hughes fvar.axes.append(weight) 40*e1fe3e4aSElliott Hughes 41*e1fe3e4aSElliott Hughes # https://www.microsoft.com/typography/otspec/os2.htm#wtc 42*e1fe3e4aSElliott Hughes for name, wght in ( 43*e1fe3e4aSElliott Hughes ("Thin", 100), 44*e1fe3e4aSElliott Hughes ("Light", 300), 45*e1fe3e4aSElliott Hughes ("Regular", 400), 46*e1fe3e4aSElliott Hughes ("Bold", 700), 47*e1fe3e4aSElliott Hughes ("Black", 900), 48*e1fe3e4aSElliott Hughes ): 49*e1fe3e4aSElliott Hughes inst = NamedInstance() 50*e1fe3e4aSElliott Hughes inst.nameID = AddName(font, name).nameID 51*e1fe3e4aSElliott Hughes inst.coordinates = {"wght": wght} 52*e1fe3e4aSElliott Hughes fvar.instances.append(inst) 53*e1fe3e4aSElliott Hughes 54*e1fe3e4aSElliott Hughes 55*e1fe3e4aSElliott Hughesdef AddName(font, name): 56*e1fe3e4aSElliott Hughes """(font, "Bold") --> NameRecord""" 57*e1fe3e4aSElliott Hughes nameTable = font.get("name") 58*e1fe3e4aSElliott Hughes namerec = NameRecord() 59*e1fe3e4aSElliott Hughes namerec.nameID = 1 + max([n.nameID for n in nameTable.names] + [256]) 60*e1fe3e4aSElliott Hughes namerec.string = name.encode("mac_roman") 61*e1fe3e4aSElliott Hughes namerec.platformID, namerec.platEncID, namerec.langID = (1, 0, 0) 62*e1fe3e4aSElliott Hughes nameTable.names.append(namerec) 63*e1fe3e4aSElliott Hughes return namerec 64*e1fe3e4aSElliott Hughes 65*e1fe3e4aSElliott Hughes 66*e1fe3e4aSElliott Hughesdef AddGlyphVariations(font, thin, regular, black): 67*e1fe3e4aSElliott Hughes assert "gvar" not in font 68*e1fe3e4aSElliott Hughes gvar = font["gvar"] = table__g_v_a_r() 69*e1fe3e4aSElliott Hughes gvar.version = 1 70*e1fe3e4aSElliott Hughes gvar.reserved = 0 71*e1fe3e4aSElliott Hughes gvar.variations = {} 72*e1fe3e4aSElliott Hughes for glyphName in regular.getGlyphOrder(): 73*e1fe3e4aSElliott Hughes regularCoord = GetCoordinates(regular, glyphName) 74*e1fe3e4aSElliott Hughes thinCoord = GetCoordinates(thin, glyphName) 75*e1fe3e4aSElliott Hughes blackCoord = GetCoordinates(black, glyphName) 76*e1fe3e4aSElliott Hughes if not regularCoord or not blackCoord or not thinCoord: 77*e1fe3e4aSElliott Hughes logging.warning("glyph %s not present in all input fonts", glyphName) 78*e1fe3e4aSElliott Hughes continue 79*e1fe3e4aSElliott Hughes if len(regularCoord) != len(blackCoord) or len(regularCoord) != len(thinCoord): 80*e1fe3e4aSElliott Hughes logging.warning( 81*e1fe3e4aSElliott Hughes "glyph %s has not the same number of " 82*e1fe3e4aSElliott Hughes "control points in all input fonts", 83*e1fe3e4aSElliott Hughes glyphName, 84*e1fe3e4aSElliott Hughes ) 85*e1fe3e4aSElliott Hughes continue 86*e1fe3e4aSElliott Hughes thinDelta = [] 87*e1fe3e4aSElliott Hughes blackDelta = [] 88*e1fe3e4aSElliott Hughes for (regX, regY), (blackX, blackY), (thinX, thinY) in zip( 89*e1fe3e4aSElliott Hughes regularCoord, blackCoord, thinCoord 90*e1fe3e4aSElliott Hughes ): 91*e1fe3e4aSElliott Hughes thinDelta.append(((thinX - regX, thinY - regY))) 92*e1fe3e4aSElliott Hughes blackDelta.append((blackX - regX, blackY - regY)) 93*e1fe3e4aSElliott Hughes thinVar = TupleVariation({"wght": (-1.0, -1.0, 0.0)}, thinDelta) 94*e1fe3e4aSElliott Hughes blackVar = TupleVariation({"wght": (0.0, 1.0, 1.0)}, blackDelta) 95*e1fe3e4aSElliott Hughes gvar.variations[glyphName] = [thinVar, blackVar] 96*e1fe3e4aSElliott Hughes 97*e1fe3e4aSElliott Hughes 98*e1fe3e4aSElliott Hughesdef GetCoordinates(font, glyphName): 99*e1fe3e4aSElliott Hughes """font, glyphName --> glyph coordinates as expected by "gvar" table 100*e1fe3e4aSElliott Hughes 101*e1fe3e4aSElliott Hughes The result includes four "phantom points" for the glyph metrics, 102*e1fe3e4aSElliott Hughes as mandated by the "gvar" spec. 103*e1fe3e4aSElliott Hughes """ 104*e1fe3e4aSElliott Hughes glyphTable = font["glyf"] 105*e1fe3e4aSElliott Hughes glyph = glyphTable.glyphs.get(glyphName) 106*e1fe3e4aSElliott Hughes if glyph is None: 107*e1fe3e4aSElliott Hughes return None 108*e1fe3e4aSElliott Hughes glyph.expand(glyphTable) 109*e1fe3e4aSElliott Hughes glyph.recalcBounds(glyphTable) 110*e1fe3e4aSElliott Hughes if glyph.isComposite(): 111*e1fe3e4aSElliott Hughes coord = [c.getComponentInfo()[1][-2:] for c in glyph.components] 112*e1fe3e4aSElliott Hughes else: 113*e1fe3e4aSElliott Hughes coord = [c for c in glyph.getCoordinates(glyphTable)[0]] 114*e1fe3e4aSElliott Hughes # Add phantom points for (left, right, top, bottom) positions. 115*e1fe3e4aSElliott Hughes horizontalAdvanceWidth, leftSideBearing = font["hmtx"].metrics[glyphName] 116*e1fe3e4aSElliott Hughes 117*e1fe3e4aSElliott Hughes leftSideX = glyph.xMin - leftSideBearing 118*e1fe3e4aSElliott Hughes rightSideX = leftSideX + horizontalAdvanceWidth 119*e1fe3e4aSElliott Hughes 120*e1fe3e4aSElliott Hughes # XXX these are incorrect. Load vmtx and fix. 121*e1fe3e4aSElliott Hughes topSideY = glyph.yMax 122*e1fe3e4aSElliott Hughes bottomSideY = -glyph.yMin 123*e1fe3e4aSElliott Hughes 124*e1fe3e4aSElliott Hughes coord.extend([(leftSideX, 0), (rightSideX, 0), (0, topSideY), (0, bottomSideY)]) 125*e1fe3e4aSElliott Hughes return coord 126*e1fe3e4aSElliott Hughes 127*e1fe3e4aSElliott Hughes 128*e1fe3e4aSElliott Hughesdef main(): 129*e1fe3e4aSElliott Hughes logging.basicConfig(format="%(levelname)s: %(message)s") 130*e1fe3e4aSElliott Hughes thin = TTFont("/tmp/Roboto/Roboto-Thin.ttf") 131*e1fe3e4aSElliott Hughes regular = TTFont("/tmp/Roboto/Roboto-Regular.ttf") 132*e1fe3e4aSElliott Hughes black = TTFont("/tmp/Roboto/Roboto-Black.ttf") 133*e1fe3e4aSElliott Hughes out = regular 134*e1fe3e4aSElliott Hughes AddFontVariations(out) 135*e1fe3e4aSElliott Hughes AddGlyphVariations(out, thin, regular, black) 136*e1fe3e4aSElliott Hughes out.save("./Roboto.ttf") 137*e1fe3e4aSElliott Hughes 138*e1fe3e4aSElliott Hughes 139*e1fe3e4aSElliott Hughesif __name__ == "__main__": 140*e1fe3e4aSElliott Hughes import sys 141*e1fe3e4aSElliott Hughes 142*e1fe3e4aSElliott Hughes sys.exit(main()) 143