from __future__ import print_function, division, absolute_import
from __future__ import unicode_literals
import os
import shutil
import re
from fontTools.ttLib import TTFont
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.pens.t2CharStringPen import T2CharStringPen
from fontTools.fontBuilder import FontBuilder
from fontTools.ttLib.tables.TupleVariation import TupleVariation
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
from fontTools.misc.psCharStrings import T2CharString
def getTestData(fileName, mode="r"):
path = os.path.join(os.path.dirname(__file__), "data", fileName)
with open(path, mode) as f:
return f.read()
def strip_VariableItems(string):
# ttlib changes with the fontTools version
string = re.sub(' ttLibVersion=".*"', '', string)
# head table checksum and creation and mod date changes with each save.
string = re.sub('<checkSumAdjustment value="[^"]+"/>', '', string)
string = re.sub('<modified value="[^"]+"/>', '', string)
string = re.sub('<created value="[^"]+"/>', '', string)
return string
def drawTestGlyph(pen):
pen.moveTo((100, 100))
pen.lineTo((100, 1000))
pen.qCurveTo((200, 900), (400, 900), (500, 1000))
pen.lineTo((500, 100))
pen.closePath()
def _setupFontBuilder(isTTF, unitsPerEm=1024):
fb = FontBuilder(unitsPerEm, isTTF=isTTF)
fb.setupGlyphOrder([".notdef", ".null", "A", "a"])
fb.setupCharacterMap({65: "A", 97: "a"})
advanceWidths = {".notdef": 600, "A": 600, "a": 600, ".null": 600}
familyName = "HelloTestFont"
styleName = "TotallyNormal"
nameStrings = dict(familyName=dict(en="HelloTestFont", nl="HalloTestFont"),
styleName=dict(en="TotallyNormal", nl="TotaalNormaal"))
nameStrings['psName'] = familyName + "-" + styleName
return fb, advanceWidths, nameStrings
def _verifyOutput(outPath, tables=None):
f = TTFont(outPath)
f.saveXML(outPath + ".ttx", tables=tables)
with open(outPath + ".ttx") as f:
testData = strip_VariableItems(f.read())
refData = strip_VariableItems(getTestData(os.path.basename(outPath) + ".ttx"))
assert refData == testData
def test_build_ttf(tmpdir):
outPath = os.path.join(str(tmpdir), "test.ttf")
fb, advanceWidths, nameStrings = _setupFontBuilder(True)
pen = TTGlyphPen(None)
drawTestGlyph(pen)
glyph = pen.glyph()
glyphs = {".notdef": glyph, "A": glyph, "a": glyph, ".null": glyph}
fb.setupGlyf(glyphs)
metrics = {}
glyphTable = fb.font["glyf"]
for gn, advanceWidth in advanceWidths.items():
metrics[gn] = (advanceWidth, glyphTable[gn].xMin)
fb.setupHorizontalMetrics(metrics)
fb.setupHorizontalHeader(ascent=824, descent=200)
fb.setupNameTable(nameStrings)
fb.setupOS2()
fb.setupPost()
fb.setupDummyDSIG()
fb.save(outPath)
_verifyOutput(outPath)
def test_build_otf(tmpdir):
outPath = os.path.join(str(tmpdir), "test.otf")
fb, advanceWidths, nameStrings = _setupFontBuilder(False)
pen = T2CharStringPen(600, None)
drawTestGlyph(pen)
charString = pen.getCharString()
charStrings = {".notdef": charString, "A": charString, "a": charString, ".null": charString}
fb.setupCFF(nameStrings['psName'], {"FullName": nameStrings['psName']}, charStrings, {})
metrics = {}
for gn, advanceWidth in advanceWidths.items():
metrics[gn] = (advanceWidth, 100) # XXX lsb from glyph
fb.setupHorizontalMetrics(metrics)
fb.setupHorizontalHeader(ascent=824, descent=200)
fb.setupNameTable(nameStrings)
fb.setupOS2()
fb.setupPost()
fb.setupDummyDSIG()
fb.save(outPath)
_verifyOutput(outPath)
def test_build_var(tmpdir):
outPath = os.path.join(str(tmpdir), "test_var.ttf")
fb = FontBuilder(1024, isTTF=True)
fb.setupGlyphOrder([".notdef", ".null", "A", "a"])
fb.setupCharacterMap({65: "A", 97: "a"})
advanceWidths = {".notdef": 600, "A": 600, "a": 600, ".null": 600}
familyName = "HelloTestFont"
styleName = "TotallyNormal"
nameStrings = dict(familyName=dict(en="HelloTestFont", nl="HalloTestFont"),
styleName=dict(en="TotallyNormal", nl="TotaalNormaal"))
nameStrings['psName'] = familyName + "-" + styleName
pen = TTGlyphPen(None)
pen.moveTo((100, 0))
pen.lineTo((100, 400))
pen.lineTo((500, 400))
pen.lineTo((500, 000))
pen.closePath()
glyph = pen.glyph()
pen = TTGlyphPen(None)
emptyGlyph = pen.glyph()
glyphs = {".notdef": emptyGlyph, "A": glyph, "a": glyph, ".null": emptyGlyph}
fb.setupGlyf(glyphs)
metrics = {}
glyphTable = fb.font["glyf"]
for gn, advanceWidth in advanceWidths.items():
metrics[gn] = (advanceWidth, glyphTable[gn].xMin)
fb.setupHorizontalMetrics(metrics)
fb.setupHorizontalHeader(ascent=824, descent=200)
fb.setupNameTable(nameStrings)
axes = [
('LEFT', 0, 0, 100, "Left"),
('RGHT', 0, 0, 100, "Right"),
('UPPP', 0, 0, 100, "Up"),
('DOWN', 0, 0, 100, "Down"),
]
instances = [
dict(location=dict(LEFT=0, RGHT=0, UPPP=0, DOWN=0), stylename="TotallyNormal"),
dict(location=dict(LEFT=0, RGHT=100, UPPP=100, DOWN=0), stylename="Right Up"),
]
fb.setupFvar(axes, instances)
variations = {}
# Four (x, y) pairs and four phantom points:
leftDeltas = [(-200, 0), (-200, 0), (0, 0), (0, 0), None, None, None, None]
rightDeltas = [(0, 0), (0, 0), (200, 0), (200, 0), None, None, None, None]
upDeltas = [(0, 0), (0, 200), (0, 200), (0, 0), None, None, None, None]
downDeltas = [(0, -200), (0, 0), (0, 0), (0, -200), None, None, None, None]
variations['a'] = [
TupleVariation(dict(RGHT=(0, 1, 1)), rightDeltas),
TupleVariation(dict(LEFT=(0, 1, 1)), leftDeltas),
TupleVariation(dict(UPPP=(0, 1, 1)), upDeltas),
TupleVariation(dict(DOWN=(0, 1, 1)), downDeltas),
]
fb.setupGvar(variations)
fb.setupOS2()
fb.setupPost()
fb.setupDummyDSIG()
fb.save(outPath)
_verifyOutput(outPath)
def test_build_cff2(tmpdir):
outPath = os.path.join(str(tmpdir), "test_var.otf")
fb, advanceWidths, nameStrings = _setupFontBuilder(False, 1000)
fb.setupNameTable(nameStrings)
axes = [
('TEST', 0, 0, 100, "Test Axis"),
]
instances = [
dict(location=dict(TEST=0), stylename="TotallyNormal"),
dict(location=dict(TEST=100), stylename="TotallyTested"),
]
fb.setupFvar(axes, instances)
pen = T2CharStringPen(None, None, CFF2=True)
drawTestGlyph(pen)
charString = pen.getCharString()
program = [
200, 200, -200, -200, 2, "blend", "rmoveto",
400, 400, 1, "blend", "hlineto",
400, 400, 1, "blend", "vlineto",
-400, -400, 1, "blend", "hlineto"
]
charStringVariable = T2CharString(program=program)
charStrings = {".notdef": charString, "A": charString, "a": charStringVariable, ".null": charString}
fb.setupCFF2(charStrings, regions=[{"TEST": (0, 1, 1)}])
metrics = {gn: (advanceWidth, 0) for gn, advanceWidth in advanceWidths.items()}
fb.setupHorizontalMetrics(metrics)
fb.setupHorizontalHeader(ascent=824, descent=200)
fb.setupOS2(sTypoAscender=825, sTypoDescender=200, usWinAscent=824, usWinDescent=200)
fb.setupPost()
fb.save(outPath)
_verifyOutput(outPath)
def test_setupNameTable_no_mac():
fb, _, nameStrings = _setupFontBuilder(True)
fb.setupNameTable(nameStrings, mac=False)
assert all(n for n in fb.font["name"].names if n.platformID == 3)
assert not any(n for n in fb.font["name"].names if n.platformID == 1)
def test_setupNameTable_no_windows():
fb, _, nameStrings = _setupFontBuilder(True)
fb.setupNameTable(nameStrings, windows=False)
assert all(n for n in fb.font["name"].names if n.platformID == 1)
assert not any(n for n in fb.font["name"].names if n.platformID == 3)
def test_unicodeVariationSequences(tmpdir):
familyName = "UVSTestFont"
styleName = "Regular"
nameStrings = dict(familyName=familyName, styleName=styleName)
nameStrings['psName'] = familyName + "-" + styleName
glyphOrder = [".notdef", "space", "zero", "zero.slash"]
cmap = {ord(" "): "space", ord("0"): "zero"}
uvs = [
(0x0030, 0xFE00, "zero.slash"),
(0x0030, 0xFE01, None), # not an official sequence, just testing
]
metrics = {gn: (600, 0) for gn in glyphOrder}
pen = TTGlyphPen(None)
glyph = pen.glyph() # empty placeholder
glyphs = {gn: glyph for gn in glyphOrder}
fb = FontBuilder(1024, isTTF=True)
fb.setupGlyphOrder(glyphOrder)
fb.setupCharacterMap(cmap, uvs)
fb.setupGlyf(glyphs)
fb.setupHorizontalMetrics(metrics)
fb.setupHorizontalHeader(ascent=824, descent=200)
fb.setupNameTable(nameStrings)
fb.setupOS2()
fb.setupPost()
outPath = os.path.join(str(tmpdir), "test_uvs.ttf")
fb.save(outPath)
_verifyOutput(outPath, tables=["cmap"])
uvs = [
(0x0030, 0xFE00, "zero.slash"),
(0x0030, 0xFE01, "zero"), # should result in the exact same subtable data, due to cmap[0x0030] == "zero"
]
fb.setupCharacterMap(cmap, uvs)
fb.save(outPath)
_verifyOutput(outPath, tables=["cmap"])