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