• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from __future__ import print_function, division, absolute_import
2from __future__ import unicode_literals
3
4import os
5import shutil
6import re
7from fontTools.ttLib import TTFont
8from fontTools.pens.ttGlyphPen import TTGlyphPen
9from fontTools.pens.t2CharStringPen import T2CharStringPen
10from fontTools.fontBuilder import FontBuilder
11from fontTools.ttLib.tables.TupleVariation import TupleVariation
12from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
13from fontTools.misc.psCharStrings import T2CharString
14
15
16def getTestData(fileName, mode="r"):
17    path = os.path.join(os.path.dirname(__file__), "data", fileName)
18    with open(path, mode) as f:
19        return f.read()
20
21
22def strip_VariableItems(string):
23    # ttlib changes with the fontTools version
24    string = re.sub(' ttLibVersion=".*"', '', string)
25    # head table checksum and creation and mod date changes with each save.
26    string = re.sub('<checkSumAdjustment value="[^"]+"/>', '', string)
27    string = re.sub('<modified value="[^"]+"/>', '', string)
28    string = re.sub('<created value="[^"]+"/>', '', string)
29    return string
30
31
32def drawTestGlyph(pen):
33    pen.moveTo((100, 100))
34    pen.lineTo((100, 1000))
35    pen.qCurveTo((200, 900), (400, 900), (500, 1000))
36    pen.lineTo((500, 100))
37    pen.closePath()
38
39
40def _setupFontBuilder(isTTF, unitsPerEm=1024):
41    fb = FontBuilder(unitsPerEm, isTTF=isTTF)
42    fb.setupGlyphOrder([".notdef", ".null", "A", "a"])
43    fb.setupCharacterMap({65: "A", 97: "a"})
44
45    advanceWidths = {".notdef": 600, "A": 600, "a": 600, ".null": 600}
46
47    familyName = "HelloTestFont"
48    styleName = "TotallyNormal"
49    nameStrings = dict(familyName=dict(en="HelloTestFont", nl="HalloTestFont"),
50                       styleName=dict(en="TotallyNormal", nl="TotaalNormaal"))
51    nameStrings['psName'] = familyName + "-" + styleName
52
53    return fb, advanceWidths, nameStrings
54
55
56def _verifyOutput(outPath, tables=None):
57    f = TTFont(outPath)
58    f.saveXML(outPath + ".ttx", tables=tables)
59    with open(outPath + ".ttx") as f:
60        testData = strip_VariableItems(f.read())
61    refData = strip_VariableItems(getTestData(os.path.basename(outPath) + ".ttx"))
62    assert refData == testData
63
64
65def test_build_ttf(tmpdir):
66    outPath = os.path.join(str(tmpdir), "test.ttf")
67
68    fb, advanceWidths, nameStrings = _setupFontBuilder(True)
69
70    pen = TTGlyphPen(None)
71    drawTestGlyph(pen)
72    glyph = pen.glyph()
73    glyphs = {".notdef": glyph, "A": glyph, "a": glyph, ".null": glyph}
74    fb.setupGlyf(glyphs)
75    metrics = {}
76    glyphTable = fb.font["glyf"]
77    for gn, advanceWidth in advanceWidths.items():
78        metrics[gn] = (advanceWidth, glyphTable[gn].xMin)
79    fb.setupHorizontalMetrics(metrics)
80
81    fb.setupHorizontalHeader(ascent=824, descent=200)
82    fb.setupNameTable(nameStrings)
83    fb.setupOS2()
84    fb.setupPost()
85    fb.setupDummyDSIG()
86
87    fb.save(outPath)
88
89    _verifyOutput(outPath)
90
91
92def test_build_otf(tmpdir):
93    outPath = os.path.join(str(tmpdir), "test.otf")
94
95    fb, advanceWidths, nameStrings = _setupFontBuilder(False)
96
97    pen = T2CharStringPen(600, None)
98    drawTestGlyph(pen)
99    charString = pen.getCharString()
100    charStrings = {".notdef": charString, "A": charString, "a": charString, ".null": charString}
101    fb.setupCFF(nameStrings['psName'], {"FullName": nameStrings['psName']}, charStrings, {})
102    metrics = {}
103    for gn, advanceWidth in advanceWidths.items():
104        metrics[gn] = (advanceWidth, 100)  # XXX lsb from glyph
105    fb.setupHorizontalMetrics(metrics)
106
107    fb.setupHorizontalHeader(ascent=824, descent=200)
108    fb.setupNameTable(nameStrings)
109    fb.setupOS2()
110    fb.setupPost()
111    fb.setupDummyDSIG()
112
113    fb.save(outPath)
114
115    _verifyOutput(outPath)
116
117
118def test_build_var(tmpdir):
119    outPath = os.path.join(str(tmpdir), "test_var.ttf")
120
121    fb = FontBuilder(1024, isTTF=True)
122    fb.setupGlyphOrder([".notdef", ".null", "A", "a"])
123    fb.setupCharacterMap({65: "A", 97: "a"})
124
125    advanceWidths = {".notdef": 600, "A": 600, "a": 600, ".null": 600}
126
127    familyName = "HelloTestFont"
128    styleName = "TotallyNormal"
129    nameStrings = dict(familyName=dict(en="HelloTestFont", nl="HalloTestFont"),
130                       styleName=dict(en="TotallyNormal", nl="TotaalNormaal"))
131    nameStrings['psName'] = familyName + "-" + styleName
132
133    pen = TTGlyphPen(None)
134    pen.moveTo((100, 0))
135    pen.lineTo((100, 400))
136    pen.lineTo((500, 400))
137    pen.lineTo((500, 000))
138    pen.closePath()
139
140    glyph = pen.glyph()
141
142    pen = TTGlyphPen(None)
143    emptyGlyph = pen.glyph()
144
145    glyphs = {".notdef": emptyGlyph, "A": glyph, "a": glyph, ".null": emptyGlyph}
146    fb.setupGlyf(glyphs)
147    metrics = {}
148    glyphTable = fb.font["glyf"]
149    for gn, advanceWidth in advanceWidths.items():
150        metrics[gn] = (advanceWidth, glyphTable[gn].xMin)
151    fb.setupHorizontalMetrics(metrics)
152
153    fb.setupHorizontalHeader(ascent=824, descent=200)
154    fb.setupNameTable(nameStrings)
155
156    axes = [
157        ('LEFT', 0, 0, 100, "Left"),
158        ('RGHT', 0, 0, 100, "Right"),
159        ('UPPP', 0, 0, 100, "Up"),
160        ('DOWN', 0, 0, 100, "Down"),
161    ]
162    instances = [
163        dict(location=dict(LEFT=0, RGHT=0, UPPP=0, DOWN=0), stylename="TotallyNormal"),
164        dict(location=dict(LEFT=0, RGHT=100, UPPP=100, DOWN=0), stylename="Right Up"),
165    ]
166    fb.setupFvar(axes, instances)
167    variations = {}
168    # Four (x, y) pairs and four phantom points:
169    leftDeltas = [(-200, 0), (-200, 0), (0, 0), (0, 0), None, None, None, None]
170    rightDeltas = [(0, 0), (0, 0), (200, 0), (200, 0), None, None, None, None]
171    upDeltas = [(0, 0), (0, 200), (0, 200), (0, 0), None, None, None, None]
172    downDeltas = [(0, -200), (0, 0), (0, 0), (0, -200), None, None, None, None]
173    variations['a'] = [
174        TupleVariation(dict(RGHT=(0, 1, 1)), rightDeltas),
175        TupleVariation(dict(LEFT=(0, 1, 1)), leftDeltas),
176        TupleVariation(dict(UPPP=(0, 1, 1)), upDeltas),
177        TupleVariation(dict(DOWN=(0, 1, 1)), downDeltas),
178    ]
179    fb.setupGvar(variations)
180
181    fb.setupOS2()
182    fb.setupPost()
183    fb.setupDummyDSIG()
184
185    fb.save(outPath)
186
187    _verifyOutput(outPath)
188
189
190def test_build_cff2(tmpdir):
191    outPath = os.path.join(str(tmpdir), "test_var.otf")
192
193    fb, advanceWidths, nameStrings = _setupFontBuilder(False, 1000)
194
195    fb.setupNameTable(nameStrings)
196
197    axes = [
198        ('TEST', 0, 0, 100, "Test Axis"),
199    ]
200    instances = [
201        dict(location=dict(TEST=0), stylename="TotallyNormal"),
202        dict(location=dict(TEST=100), stylename="TotallyTested"),
203    ]
204    fb.setupFvar(axes, instances)
205
206    pen = T2CharStringPen(None, None, CFF2=True)
207    drawTestGlyph(pen)
208    charString = pen.getCharString()
209
210    program = [
211        200, 200, -200, -200, 2, "blend", "rmoveto",
212        400, 400, 1, "blend", "hlineto",
213        400, 400, 1, "blend", "vlineto",
214        -400, -400, 1, "blend", "hlineto"
215    ]
216    charStringVariable = T2CharString(program=program)
217
218    charStrings = {".notdef": charString, "A": charString, "a": charStringVariable, ".null": charString}
219    fb.setupCFF2(charStrings, regions=[{"TEST": (0, 1, 1)}])
220
221    metrics = {gn: (advanceWidth, 0) for gn, advanceWidth in advanceWidths.items()}
222    fb.setupHorizontalMetrics(metrics)
223
224    fb.setupHorizontalHeader(ascent=824, descent=200)
225    fb.setupOS2(sTypoAscender=825, sTypoDescender=200, usWinAscent=824, usWinDescent=200)
226    fb.setupPost()
227
228    fb.save(outPath)
229
230    _verifyOutput(outPath)
231
232
233def test_setupNameTable_no_mac():
234    fb, _, nameStrings = _setupFontBuilder(True)
235    fb.setupNameTable(nameStrings, mac=False)
236
237    assert all(n for n in fb.font["name"].names if n.platformID == 3)
238    assert not any(n for n in fb.font["name"].names if n.platformID == 1)
239
240
241def test_setupNameTable_no_windows():
242    fb, _, nameStrings = _setupFontBuilder(True)
243    fb.setupNameTable(nameStrings, windows=False)
244
245    assert all(n for n in fb.font["name"].names if n.platformID == 1)
246    assert not any(n for n in fb.font["name"].names if n.platformID == 3)
247
248
249def test_unicodeVariationSequences(tmpdir):
250    familyName = "UVSTestFont"
251    styleName = "Regular"
252    nameStrings = dict(familyName=familyName, styleName=styleName)
253    nameStrings['psName'] = familyName + "-" + styleName
254    glyphOrder = [".notdef", "space", "zero", "zero.slash"]
255    cmap = {ord(" "): "space", ord("0"): "zero"}
256    uvs = [
257        (0x0030, 0xFE00, "zero.slash"),
258        (0x0030, 0xFE01, None),  # not an official sequence, just testing
259    ]
260    metrics = {gn: (600, 0) for gn in glyphOrder}
261    pen = TTGlyphPen(None)
262    glyph = pen.glyph()  # empty placeholder
263    glyphs = {gn: glyph for gn in glyphOrder}
264
265    fb = FontBuilder(1024, isTTF=True)
266    fb.setupGlyphOrder(glyphOrder)
267    fb.setupCharacterMap(cmap, uvs)
268    fb.setupGlyf(glyphs)
269    fb.setupHorizontalMetrics(metrics)
270    fb.setupHorizontalHeader(ascent=824, descent=200)
271    fb.setupNameTable(nameStrings)
272    fb.setupOS2()
273    fb.setupPost()
274
275    outPath = os.path.join(str(tmpdir), "test_uvs.ttf")
276    fb.save(outPath)
277    _verifyOutput(outPath, tables=["cmap"])
278
279    uvs = [
280        (0x0030, 0xFE00, "zero.slash"),
281        (0x0030, 0xFE01, "zero"),  # should result in the exact same subtable data, due to cmap[0x0030] == "zero"
282    ]
283    fb.setupCharacterMap(cmap, uvs)
284    fb.save(outPath)
285    _verifyOutput(outPath, tables=["cmap"])
286