Merge pull request #309 from brawer/interpol
[GX] Code snippet for building interpolated fonts
This commit is contained in:
commit
f83337dca3
135
Snippets/interpolate.py
Executable file
135
Snippets/interpolate.py
Executable file
@ -0,0 +1,135 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
# Illustrates how a fonttools script can construct variable fonts.
|
||||
#
|
||||
# This script reads Roboto-Thin.ttf, Roboto-Regular.ttf, and
|
||||
# Roboto-Black.ttf from /tmp/Roboto, and writes a Multiple Master GX
|
||||
# font named "Roboto.ttf" into the current working directory.
|
||||
# This output font supports interpolation along the Weight axis,
|
||||
# and it contains named instances for "Thin", "Light", "Regular",
|
||||
# "Bold", and "Black".
|
||||
#
|
||||
# All input fonts must contain the same set of glyphs, and these glyphs
|
||||
# need to have the same control points in the same order. Note that this
|
||||
# is *not* the case for the normal Roboto fonts that can be downloaded
|
||||
# from Google. This demo script prints a warning for any problematic
|
||||
# glyphs; in the resulting font, these glyphs will not be interpolated
|
||||
# and get rendered in the "Regular" weight.
|
||||
#
|
||||
# Usage:
|
||||
# $ mkdir /tmp/Roboto && cp Roboto-*.ttf /tmp/Roboto
|
||||
# $ ./interpolate.py && open Roboto.ttf
|
||||
|
||||
|
||||
from __future__ import print_function, division, absolute_import
|
||||
from fontTools.misc.py23 import *
|
||||
from fontTools.ttLib import TTFont
|
||||
from fontTools.ttLib.tables._n_a_m_e import NameRecord
|
||||
from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis, NamedInstance
|
||||
from fontTools.ttLib.tables._g_v_a_r import table__g_v_a_r, GlyphVariation
|
||||
import warnings
|
||||
|
||||
|
||||
def AddFontVariations(font):
|
||||
assert "fvar" not in font
|
||||
fvar = font["fvar"] = table__f_v_a_r()
|
||||
|
||||
weight = Axis()
|
||||
weight.axisTag = "wght"
|
||||
weight.nameID = AddName(font, "Weight").nameID
|
||||
weight.minValue, weight.defaultValue, weight.maxValue = (0.48, 1.0, 3.2)
|
||||
fvar.axes.append(weight)
|
||||
|
||||
for name, wght in (
|
||||
("Thin", 0.48),
|
||||
("Light", 0.8),
|
||||
("Regular", 1.0),
|
||||
("Bold", 1.95),
|
||||
("Black", 3.2)):
|
||||
inst = NamedInstance()
|
||||
inst.nameID = AddName(font, name).nameID
|
||||
inst.coordinates = {"wght": wght}
|
||||
fvar.instances.append(inst)
|
||||
|
||||
|
||||
def AddName(font, name):
|
||||
"""(font, "Bold") --> NameRecord"""
|
||||
nameTable = font.get("name")
|
||||
namerec = NameRecord()
|
||||
namerec.nameID = 1 + max([n.nameID for n in nameTable.names] + [256])
|
||||
namerec.string = name.encode("mac_roman")
|
||||
namerec.platformID, namerec.platEncID, namerec.langID = (1, 0, 0)
|
||||
nameTable.names.append(namerec)
|
||||
return namerec
|
||||
|
||||
|
||||
def AddGlyphVariations(font, thin, regular, black):
|
||||
assert "gvar" not in font
|
||||
gvar = font["gvar"] = table__g_v_a_r()
|
||||
gvar.version = 1
|
||||
gvar.reserved = 0
|
||||
gvar.variations = {}
|
||||
for glyphName in regular.getGlyphOrder():
|
||||
regularCoord = GetCoordinates(regular, glyphName)
|
||||
thinCoord = GetCoordinates(thin, glyphName)
|
||||
blackCoord = GetCoordinates(black, glyphName)
|
||||
if not regularCoord or not blackCoord or not thinCoord:
|
||||
warnings.warn("glyph %s not present in all input fonts" %
|
||||
glyphName)
|
||||
continue
|
||||
if (len(regularCoord) != len(blackCoord) or
|
||||
len(regularCoord) != len(thinCoord)):
|
||||
warnings.warn("glyph %s has not the same number of "
|
||||
"control points in all input fonts" % glyphName)
|
||||
continue
|
||||
thinDelta = []
|
||||
blackDelta = []
|
||||
for ((regX, regY), (blackX, blackY), (thinX, thinY)) in \
|
||||
zip(regularCoord, blackCoord, thinCoord):
|
||||
thinDelta.append(((thinX - regX, thinY - regY)))
|
||||
blackDelta.append((blackX - regX, blackY - regY))
|
||||
thinVar = GlyphVariation({"wght": (-1.0, -1.0, 0.0)}, thinDelta)
|
||||
blackVar = GlyphVariation({"wght": (0.0, 1.0, 1.0)}, blackDelta)
|
||||
gvar.variations[glyphName] = [thinVar, blackVar]
|
||||
|
||||
|
||||
def GetCoordinates(font, glyphName):
|
||||
"""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.
|
||||
"""
|
||||
glyphTable = font["glyf"]
|
||||
glyph = glyphTable.glyphs.get(glyphName)
|
||||
if glyph is None:
|
||||
return None
|
||||
glyph.expand(glyphTable)
|
||||
glyph.recalcBounds(glyphTable)
|
||||
if glyph.isComposite():
|
||||
coord = [c.getComponentInfo()[1][-2:] for c in glyph.components]
|
||||
else:
|
||||
coord = [c for c in glyph.getCoordinates(glyphTable)[0]]
|
||||
# Add phantom points for (left, right, top, bottom) side bearing.
|
||||
horizontalAdvanceWidth, leftSideBearing = font["hmtx"].metrics[glyphName]
|
||||
rightSideBearing = (
|
||||
horizontalAdvanceWidth - leftSideBearing - (glyph.xMax - glyph.xMin))
|
||||
# TODO: Not sure if top and bottom side bearing are correct.
|
||||
topSideBearing = glyph.yMax
|
||||
bottomSideBearing = -glyph.yMin
|
||||
coord.extend([(leftSideBearing, 0), (rightSideBearing, 0),
|
||||
(0, topSideBearing), (0, bottomSideBearing)])
|
||||
return coord
|
||||
|
||||
|
||||
def main():
|
||||
thin = TTFont("/tmp/Roboto/Roboto-Thin.ttf")
|
||||
regular = TTFont("/tmp/Roboto/Roboto-Regular.ttf")
|
||||
black = TTFont("/tmp/Roboto/Roboto-Black.ttf")
|
||||
out = regular
|
||||
AddFontVariations(out)
|
||||
AddGlyphVariations(out, thin, regular, black)
|
||||
out.save("./Roboto.ttf")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
x
Reference in New Issue
Block a user