1from __future__ import print_function, division, absolute_import 2from __future__ import unicode_literals 3 4__all__ = ["FontBuilder"] 5 6""" 7This module is *experimental*, meaning it still may evolve and change. 8 9The `FontBuilder` class is a convenient helper to construct working TTF or 10OTF fonts from scratch. 11 12Note that the various setup methods cannot be called in arbitrary order, 13due to various interdependencies between OpenType tables. Here is an order 14that works: 15 16 fb = FontBuilder(...) 17 fb.setupGlyphOrder(...) 18 fb.setupCharacterMap(...) 19 fb.setupGlyf(...) --or-- fb.setupCFF(...) 20 fb.setupHorizontalMetrics(...) 21 fb.setupHorizontalHeader() 22 fb.setupNameTable(...) 23 fb.setupOS2() 24 fb.addOpenTypeFeatures(...) 25 fb.setupPost() 26 fb.save(...) 27 28Here is how to build a minimal TTF: 29 30```python 31from fontTools.fontBuilder import FontBuilder 32from fontTools.pens.ttGlyphPen import TTGlyphPen 33 34 35def drawTestGlyph(pen): 36 pen.moveTo((100, 100)) 37 pen.lineTo((100, 1000)) 38 pen.qCurveTo((200, 900), (400, 900), (500, 1000)) 39 pen.lineTo((500, 100)) 40 pen.closePath() 41 42 43fb = FontBuilder(1024, isTTF=True) 44fb.setupGlyphOrder([".notdef", ".null", "space", "A", "a"]) 45fb.setupCharacterMap({32: "space", 65: "A", 97: "a"}) 46advanceWidths = {".notdef": 600, "space": 500, "A": 600, "a": 600, ".null": 0} 47 48familyName = "HelloTestFont" 49styleName = "TotallyNormal" 50version = "0.1" 51 52nameStrings = dict( 53 familyName=dict(en=familyName, nl="HalloTestFont"), 54 styleName=dict(en=styleName, nl="TotaalNormaal"), 55 uniqueFontIdentifier="fontBuilder: " + familyName + "." + styleName, 56 fullName=familyName + "-" + styleName, 57 psName=familyName + "-" + styleName, 58 version="Version " + version, 59) 60 61pen = TTGlyphPen(None) 62drawTestGlyph(pen) 63glyph = pen.glyph() 64glyphs = {".notdef": glyph, "space": glyph, "A": glyph, "a": glyph, ".null": glyph} 65fb.setupGlyf(glyphs) 66metrics = {} 67glyphTable = fb.font["glyf"] 68for gn, advanceWidth in advanceWidths.items(): 69 metrics[gn] = (advanceWidth, glyphTable[gn].xMin) 70fb.setupHorizontalMetrics(metrics) 71fb.setupHorizontalHeader(ascent=824, descent=-200) 72fb.setupNameTable(nameStrings) 73fb.setupOS2(sTypoAscender=824, usWinAscent=824, usWinDescent=200) 74fb.setupPost() 75fb.save("test.ttf") 76``` 77 78And here's how to build a minimal OTF: 79 80```python 81from fontTools.fontBuilder import FontBuilder 82from fontTools.pens.t2CharStringPen import T2CharStringPen 83 84 85def drawTestGlyph(pen): 86 pen.moveTo((100, 100)) 87 pen.lineTo((100, 1000)) 88 pen.curveTo((200, 900), (400, 900), (500, 1000)) 89 pen.lineTo((500, 100)) 90 pen.closePath() 91 92 93fb = FontBuilder(1024, isTTF=False) 94fb.setupGlyphOrder([".notdef", ".null", "space", "A", "a"]) 95fb.setupCharacterMap({32: "space", 65: "A", 97: "a"}) 96advanceWidths = {".notdef": 600, "space": 500, "A": 600, "a": 600, ".null": 0} 97 98familyName = "HelloTestFont" 99styleName = "TotallyNormal" 100version = "0.1" 101 102nameStrings = dict( 103 familyName=dict(en=familyName, nl="HalloTestFont"), 104 styleName=dict(en=styleName, nl="TotaalNormaal"), 105 uniqueFontIdentifier="fontBuilder: " + familyName + "." + styleName, 106 fullName=familyName + "-" + styleName, 107 psName=familyName + "-" + styleName, 108 version="Version " + version, 109) 110 111pen = T2CharStringPen(600, None) 112drawTestGlyph(pen) 113charString = pen.getCharString() 114charStrings = { 115 ".notdef": charString, 116 "space": charString, 117 "A": charString, 118 "a": charString, 119 ".null": charString, 120} 121fb.setupCFF(nameStrings["psName"], {"FullName": nameStrings["psName"]}, charStrings, {}) 122lsb = {gn: cs.calcBounds(None)[0] for gn, cs in charStrings.items()} 123metrics = {} 124for gn, advanceWidth in advanceWidths.items(): 125 metrics[gn] = (advanceWidth, lsb[gn]) 126fb.setupHorizontalMetrics(metrics) 127fb.setupHorizontalHeader(ascent=824, descent=200) 128fb.setupNameTable(nameStrings) 129fb.setupOS2(sTypoAscender=824, usWinAscent=824, usWinDescent=200) 130fb.setupPost() 131fb.save("test.otf") 132``` 133""" 134 135from .misc.py23 import * 136from .ttLib import TTFont, newTable 137from .ttLib.tables._c_m_a_p import cmap_classes 138from .ttLib.tables._n_a_m_e import NameRecord, makeName 139from .misc.timeTools import timestampNow 140import struct 141 142 143_headDefaults = dict( 144 tableVersion = 1.0, 145 fontRevision = 1.0, 146 checkSumAdjustment = 0, 147 magicNumber = 0x5F0F3CF5, 148 flags = 0x0003, 149 unitsPerEm = 1000, 150 created = 0, 151 modified = 0, 152 xMin = 0, 153 yMin = 0, 154 xMax = 0, 155 yMax = 0, 156 macStyle = 0, 157 lowestRecPPEM = 3, 158 fontDirectionHint = 2, 159 indexToLocFormat = 0, 160 glyphDataFormat = 0, 161) 162 163_maxpDefaultsTTF = dict( 164 tableVersion = 0x00010000, 165 numGlyphs = 0, 166 maxPoints = 0, 167 maxContours = 0, 168 maxCompositePoints = 0, 169 maxCompositeContours = 0, 170 maxZones = 2, 171 maxTwilightPoints = 0, 172 maxStorage = 0, 173 maxFunctionDefs = 0, 174 maxInstructionDefs = 0, 175 maxStackElements = 0, 176 maxSizeOfInstructions = 0, 177 maxComponentElements = 0, 178 maxComponentDepth = 0, 179) 180_maxpDefaultsOTF = dict( 181 tableVersion = 0x00005000, 182 numGlyphs = 0, 183) 184 185_postDefaults = dict( 186 formatType = 3.0, 187 italicAngle = 0, 188 underlinePosition = 0, 189 underlineThickness = 0, 190 isFixedPitch = 0, 191 minMemType42 = 0, 192 maxMemType42 = 0, 193 minMemType1 = 0, 194 maxMemType1 = 0, 195) 196 197_hheaDefaults = dict( 198 tableVersion = 0x00010000, 199 ascent = 0, 200 descent = 0, 201 lineGap = 0, 202 advanceWidthMax = 0, 203 minLeftSideBearing = 0, 204 minRightSideBearing = 0, 205 xMaxExtent = 0, 206 caretSlopeRise = 1, 207 caretSlopeRun = 0, 208 caretOffset = 0, 209 reserved0 = 0, 210 reserved1 = 0, 211 reserved2 = 0, 212 reserved3 = 0, 213 metricDataFormat = 0, 214 numberOfHMetrics = 0, 215) 216 217_vheaDefaults = dict( 218 tableVersion = 0x00010000, 219 ascent = 0, 220 descent = 0, 221 lineGap = 0, 222 advanceHeightMax = 0, 223 minTopSideBearing = 0, 224 minBottomSideBearing = 0, 225 yMaxExtent = 0, 226 caretSlopeRise = 0, 227 caretSlopeRun = 0, 228 reserved0 = 0, 229 reserved1 = 0, 230 reserved2 = 0, 231 reserved3 = 0, 232 reserved4 = 0, 233 metricDataFormat = 0, 234 numberOfVMetrics = 0, 235) 236 237_nameIDs = dict( 238 copyright = 0, 239 familyName = 1, 240 styleName = 2, 241 uniqueFontIdentifier = 3, 242 fullName = 4, 243 version = 5, 244 psName = 6, 245 trademark = 7, 246 manufacturer = 8, 247 designer = 9, 248 description = 10, 249 vendorURL = 11, 250 designerURL = 12, 251 licenseDescription = 13, 252 licenseInfoURL = 14, 253 # reserved = 15, 254 typographicFamily = 16, 255 typographicSubfamily = 17, 256 compatibleFullName = 18, 257 sampleText = 19, 258 postScriptCIDFindfontName = 20, 259 wwsFamilyName = 21, 260 wwsSubfamilyName = 22, 261 lightBackgroundPalette = 23, 262 darkBackgroundPalette = 24, 263 variationsPostScriptNamePrefix = 25, 264) 265 266# to insert in setupNameTable doc string: 267# print("\n".join(("%s (nameID %s)" % (k, v)) for k, v in sorted(_nameIDs.items(), key=lambda x: x[1]))) 268 269_panoseDefaults = dict( 270 bFamilyType = 0, 271 bSerifStyle = 0, 272 bWeight = 0, 273 bProportion = 0, 274 bContrast = 0, 275 bStrokeVariation = 0, 276 bArmStyle = 0, 277 bLetterForm = 0, 278 bMidline = 0, 279 bXHeight = 0, 280) 281 282_OS2Defaults = dict( 283 version = 3, 284 xAvgCharWidth = 0, 285 usWeightClass = 400, 286 usWidthClass = 5, 287 fsType = 0x0004, # default: Preview & Print embedding 288 ySubscriptXSize = 0, 289 ySubscriptYSize = 0, 290 ySubscriptXOffset = 0, 291 ySubscriptYOffset = 0, 292 ySuperscriptXSize = 0, 293 ySuperscriptYSize = 0, 294 ySuperscriptXOffset = 0, 295 ySuperscriptYOffset = 0, 296 yStrikeoutSize = 0, 297 yStrikeoutPosition = 0, 298 sFamilyClass = 0, 299 panose = _panoseDefaults, 300 ulUnicodeRange1 = 0, 301 ulUnicodeRange2 = 0, 302 ulUnicodeRange3 = 0, 303 ulUnicodeRange4 = 0, 304 achVendID = "????", 305 fsSelection = 0, 306 usFirstCharIndex = 0, 307 usLastCharIndex = 0, 308 sTypoAscender = 0, 309 sTypoDescender = 0, 310 sTypoLineGap = 0, 311 usWinAscent = 0, 312 usWinDescent = 0, 313 ulCodePageRange1 = 0, 314 ulCodePageRange2 = 0, 315 sxHeight = 0, 316 sCapHeight = 0, 317 usDefaultChar = 0, # .notdef 318 usBreakChar = 32, # space 319 usMaxContext = 0, 320 usLowerOpticalPointSize = 0, 321 usUpperOpticalPointSize = 0, 322) 323 324 325class FontBuilder(object): 326 327 def __init__(self, unitsPerEm=None, font=None, isTTF=True): 328 """Initialize a FontBuilder instance. 329 330 If the `font` argument is not given, a new `TTFont` will be 331 constructed, and `unitsPerEm` must be given. If `isTTF` is True, 332 the font will be a glyf-based TTF; if `isTTF` is False it will be 333 a CFF-based OTF. 334 335 If `font` is given, it must be a `TTFont` instance and `unitsPerEm` 336 must _not_ be given. The `isTTF` argument will be ignored. 337 """ 338 if font is None: 339 self.font = TTFont(recalcTimestamp=False) 340 self.isTTF = isTTF 341 now = timestampNow() 342 assert unitsPerEm is not None 343 self.setupHead(unitsPerEm=unitsPerEm, created=now, modified=now) 344 self.setupMaxp() 345 else: 346 assert unitsPerEm is None 347 self.font = font 348 self.isTTF = "glyf" in font 349 350 def save(self, file): 351 """Save the font. The 'file' argument can be either a pathname or a 352 writable file object. 353 """ 354 self.font.save(file) 355 356 def _initTableWithValues(self, tableTag, defaults, values): 357 table = self.font[tableTag] = newTable(tableTag) 358 for k, v in defaults.items(): 359 setattr(table, k, v) 360 for k, v in values.items(): 361 setattr(table, k, v) 362 return table 363 364 def _updateTableWithValues(self, tableTag, values): 365 table = self.font[tableTag] 366 for k, v in values.items(): 367 setattr(table, k, v) 368 369 def setupHead(self, **values): 370 """Create a new `head` table and initialize it with default values, 371 which can be overridden by keyword arguments. 372 """ 373 self._initTableWithValues("head", _headDefaults, values) 374 375 def updateHead(self, **values): 376 """Update the head table with the fields and values passed as 377 keyword arguments. 378 """ 379 self._updateTableWithValues("head", values) 380 381 def setupGlyphOrder(self, glyphOrder): 382 """Set the glyph order for the font.""" 383 self.font.setGlyphOrder(glyphOrder) 384 385 def setupCharacterMap(self, cmapping, uvs=None, allowFallback=False): 386 """Build the `cmap` table for the font. The `cmapping` argument should 387 be a dict mapping unicode code points as integers to glyph names. 388 389 The `uvs` argument, when passed, must be a list of tuples, describing 390 Unicode Variation Sequences. These tuples have three elements: 391 (unicodeValue, variationSelector, glyphName) 392 `unicodeValue` and `variationSelector` are integer code points. 393 `glyphName` may be None, to indicate this is the default variation. 394 Text processors will then use the cmap to find the glyph name. 395 Each Unicode Variation Sequence should be an officially supported 396 sequence, but this is not policed. 397 """ 398 subTables = [] 399 highestUnicode = max(cmapping) 400 if highestUnicode > 0xffff: 401 cmapping_3_1 = dict((k, v) for k, v in cmapping.items() if k < 0x10000) 402 subTable_3_10 = buildCmapSubTable(cmapping, 12, 3, 10) 403 subTables.append(subTable_3_10) 404 else: 405 cmapping_3_1 = cmapping 406 format = 4 407 subTable_3_1 = buildCmapSubTable(cmapping_3_1, format, 3, 1) 408 try: 409 subTable_3_1.compile(self.font) 410 except struct.error: 411 # format 4 overflowed, fall back to format 12 412 if not allowFallback: 413 raise ValueError("cmap format 4 subtable overflowed; sort glyph order by unicode to fix.") 414 format = 12 415 subTable_3_1 = buildCmapSubTable(cmapping_3_1, format, 3, 1) 416 subTables.append(subTable_3_1) 417 subTable_0_3 = buildCmapSubTable(cmapping_3_1, format, 0, 3) 418 subTables.append(subTable_0_3) 419 420 if uvs is not None: 421 uvsDict = {} 422 for unicodeValue, variationSelector, glyphName in uvs: 423 if cmapping.get(unicodeValue) == glyphName: 424 # this is a default variation 425 glyphName = None 426 if variationSelector not in uvsDict: 427 uvsDict[variationSelector] = [] 428 uvsDict[variationSelector].append((unicodeValue, glyphName)) 429 uvsSubTable = buildCmapSubTable({}, 14, 0, 5) 430 uvsSubTable.uvsDict = uvsDict 431 subTables.append(uvsSubTable) 432 433 self.font["cmap"] = newTable("cmap") 434 self.font["cmap"].tableVersion = 0 435 self.font["cmap"].tables = subTables 436 437 def setupNameTable(self, nameStrings, windows=True, mac=True): 438 """Create the `name` table for the font. The `nameStrings` argument must 439 be a dict, mapping nameIDs or descriptive names for the nameIDs to name 440 record values. A value is either a string, or a dict, mapping language codes 441 to strings, to allow localized name table entries. 442 443 By default, both Windows (platformID=3) and Macintosh (platformID=1) name 444 records are added, unless any of `windows` or `mac` arguments is False. 445 446 The following descriptive names are available for nameIDs: 447 448 copyright (nameID 0) 449 familyName (nameID 1) 450 styleName (nameID 2) 451 uniqueFontIdentifier (nameID 3) 452 fullName (nameID 4) 453 version (nameID 5) 454 psName (nameID 6) 455 trademark (nameID 7) 456 manufacturer (nameID 8) 457 designer (nameID 9) 458 description (nameID 10) 459 vendorURL (nameID 11) 460 designerURL (nameID 12) 461 licenseDescription (nameID 13) 462 licenseInfoURL (nameID 14) 463 typographicFamily (nameID 16) 464 typographicSubfamily (nameID 17) 465 compatibleFullName (nameID 18) 466 sampleText (nameID 19) 467 postScriptCIDFindfontName (nameID 20) 468 wwsFamilyName (nameID 21) 469 wwsSubfamilyName (nameID 22) 470 lightBackgroundPalette (nameID 23) 471 darkBackgroundPalette (nameID 24) 472 variationsPostScriptNamePrefix (nameID 25) 473 """ 474 nameTable = self.font["name"] = newTable("name") 475 nameTable.names = [] 476 477 for nameName, nameValue in nameStrings.items(): 478 if isinstance(nameName, int): 479 nameID = nameName 480 else: 481 nameID = _nameIDs[nameName] 482 if isinstance(nameValue, basestring): 483 nameValue = dict(en=nameValue) 484 nameTable.addMultilingualName( 485 nameValue, ttFont=self.font, nameID=nameID, windows=windows, mac=mac 486 ) 487 488 def setupOS2(self, **values): 489 """Create a new `OS/2` table and initialize it with default values, 490 which can be overridden by keyword arguments. 491 """ 492 if "xAvgCharWidth" not in values: 493 gs = self.font.getGlyphSet() 494 widths = [gs[glyphName].width for glyphName in gs.keys() if gs[glyphName].width > 0] 495 values["xAvgCharWidth"] = int(round(sum(widths) / float(len(widths)))) 496 self._initTableWithValues("OS/2", _OS2Defaults, values) 497 if not ("ulUnicodeRange1" in values or "ulUnicodeRange2" in values or 498 "ulUnicodeRange3" in values or "ulUnicodeRange3" in values): 499 assert "cmap" in self.font, "the 'cmap' table must be setup before the 'OS/2' table" 500 self.font["OS/2"].recalcUnicodeRanges(self.font) 501 502 def setupCFF(self, psName, fontInfo, charStringsDict, privateDict): 503 from .cffLib import CFFFontSet, TopDictIndex, TopDict, CharStrings, \ 504 GlobalSubrsIndex, PrivateDict 505 506 assert not self.isTTF 507 self.font.sfntVersion = "OTTO" 508 fontSet = CFFFontSet() 509 fontSet.major = 1 510 fontSet.minor = 0 511 fontSet.fontNames = [psName] 512 fontSet.topDictIndex = TopDictIndex() 513 514 globalSubrs = GlobalSubrsIndex() 515 fontSet.GlobalSubrs = globalSubrs 516 private = PrivateDict() 517 for key, value in privateDict.items(): 518 setattr(private, key, value) 519 fdSelect = None 520 fdArray = None 521 522 topDict = TopDict() 523 topDict.charset = self.font.getGlyphOrder() 524 topDict.Private = private 525 for key, value in fontInfo.items(): 526 setattr(topDict, key, value) 527 if "FontMatrix" not in fontInfo: 528 scale = 1 / self.font["head"].unitsPerEm 529 topDict.FontMatrix = [scale, 0, 0, scale, 0, 0] 530 531 charStrings = CharStrings(None, topDict.charset, globalSubrs, private, fdSelect, fdArray) 532 for glyphName, charString in charStringsDict.items(): 533 charString.private = private 534 charString.globalSubrs = globalSubrs 535 charStrings[glyphName] = charString 536 topDict.CharStrings = charStrings 537 538 fontSet.topDictIndex.append(topDict) 539 540 self.font["CFF "] = newTable("CFF ") 541 self.font["CFF "].cff = fontSet 542 543 def setupCFF2(self, charStringsDict, fdArrayList=None, regions=None): 544 from .cffLib import CFFFontSet, TopDictIndex, TopDict, CharStrings, \ 545 GlobalSubrsIndex, PrivateDict, FDArrayIndex, FontDict 546 547 assert not self.isTTF 548 self.font.sfntVersion = "OTTO" 549 fontSet = CFFFontSet() 550 fontSet.major = 2 551 fontSet.minor = 0 552 553 cff2GetGlyphOrder = self.font.getGlyphOrder 554 fontSet.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder, None) 555 556 globalSubrs = GlobalSubrsIndex() 557 fontSet.GlobalSubrs = globalSubrs 558 559 if fdArrayList is None: 560 fdArrayList = [{}] 561 fdSelect = None 562 fdArray = FDArrayIndex() 563 fdArray.strings = None 564 fdArray.GlobalSubrs = globalSubrs 565 for privateDict in fdArrayList: 566 fontDict = FontDict() 567 fontDict.setCFF2(True) 568 private = PrivateDict() 569 for key, value in privateDict.items(): 570 setattr(private, key, value) 571 fontDict.Private = private 572 fdArray.append(fontDict) 573 574 topDict = TopDict() 575 topDict.cff2GetGlyphOrder = cff2GetGlyphOrder 576 topDict.FDArray = fdArray 577 scale = 1 / self.font["head"].unitsPerEm 578 topDict.FontMatrix = [scale, 0, 0, scale, 0, 0] 579 580 private = fdArray[0].Private 581 charStrings = CharStrings(None, None, globalSubrs, private, fdSelect, fdArray) 582 for glyphName, charString in charStringsDict.items(): 583 charString.private = private 584 charString.globalSubrs = globalSubrs 585 charStrings[glyphName] = charString 586 topDict.CharStrings = charStrings 587 588 fontSet.topDictIndex.append(topDict) 589 590 self.font["CFF2"] = newTable("CFF2") 591 self.font["CFF2"].cff = fontSet 592 593 if regions: 594 self.setupCFF2Regions(regions) 595 596 def setupCFF2Regions(self, regions): 597 from .varLib.builder import buildVarRegionList, buildVarData, buildVarStore 598 from .cffLib import VarStoreData 599 600 assert "fvar" in self.font, "fvar must to be set up first" 601 assert "CFF2" in self.font, "CFF2 must to be set up first" 602 axisTags = [a.axisTag for a in self.font["fvar"].axes] 603 varRegionList = buildVarRegionList(regions, axisTags) 604 varData = buildVarData(list(range(len(regions))), None, optimize=False) 605 varStore = buildVarStore(varRegionList, [varData]) 606 vstore = VarStoreData(otVarStore=varStore) 607 self.font["CFF2"].cff.topDictIndex[0].VarStore = vstore 608 609 def setupGlyf(self, glyphs, calcGlyphBounds=True): 610 """Create the `glyf` table from a dict, that maps glyph names 611 to `fontTools.ttLib.tables._g_l_y_f.Glyph` objects, for example 612 as made by `fontTools.pens.ttGlyphPen.TTGlyphPen`. 613 614 If `calcGlyphBounds` is True, the bounds of all glyphs will be 615 calculated. Only pass False if your glyph objects already have 616 their bounding box values set. 617 """ 618 assert self.isTTF 619 self.font["loca"] = newTable("loca") 620 self.font["glyf"] = newTable("glyf") 621 self.font["glyf"].glyphs = glyphs 622 if hasattr(self.font, "glyphOrder"): 623 self.font["glyf"].glyphOrder = self.font.glyphOrder 624 if calcGlyphBounds: 625 self.calcGlyphBounds() 626 627 def setupFvar(self, axes, instances): 628 addFvar(self.font, axes, instances) 629 630 def setupGvar(self, variations): 631 gvar = self.font["gvar"] = newTable('gvar') 632 gvar.version = 1 633 gvar.reserved = 0 634 gvar.variations = variations 635 636 def calcGlyphBounds(self): 637 """Calculate the bounding boxes of all glyphs in the `glyf` table. 638 This is usually not called explicitly by client code. 639 """ 640 glyphTable = self.font["glyf"] 641 for glyph in glyphTable.glyphs.values(): 642 glyph.recalcBounds(glyphTable) 643 644 def setupHorizontalMetrics(self, metrics): 645 """Create a new `hmtx` table, for horizontal metrics. 646 647 The `metrics` argument must be a dict, mapping glyph names to 648 `(width, leftSidebearing)` tuples. 649 """ 650 self.setupMetrics('hmtx', metrics) 651 652 def setupVerticalMetrics(self, metrics): 653 """Create a new `vmtx` table, for horizontal metrics. 654 655 The `metrics` argument must be a dict, mapping glyph names to 656 `(height, topSidebearing)` tuples. 657 """ 658 self.setupMetrics('vmtx', metrics) 659 660 def setupMetrics(self, tableTag, metrics): 661 """See `setupHorizontalMetrics()` and `setupVerticalMetrics()`.""" 662 assert tableTag in ("hmtx", "vmtx") 663 mtxTable = self.font[tableTag] = newTable(tableTag) 664 roundedMetrics = {} 665 for gn in metrics: 666 w, lsb = metrics[gn] 667 roundedMetrics[gn] = int(round(w)), int(round(lsb)) 668 mtxTable.metrics = roundedMetrics 669 670 def setupHorizontalHeader(self, **values): 671 """Create a new `hhea` table initialize it with default values, 672 which can be overridden by keyword arguments. 673 """ 674 self._initTableWithValues("hhea", _hheaDefaults, values) 675 676 def setupVerticalHeader(self, **values): 677 """Create a new `vhea` table initialize it with default values, 678 which can be overridden by keyword arguments. 679 """ 680 self._initTableWithValues("vhea", _vheaDefaults, values) 681 682 def setupVerticalOrigins(self, verticalOrigins, defaultVerticalOrigin=None): 683 """Create a new `VORG` table. The `verticalOrigins` argument must be 684 a dict, mapping glyph names to vertical origin values. 685 686 The `defaultVerticalOrigin` argument should be the most common vertical 687 origin value. If omitted, this value will be derived from the actual 688 values in the `verticalOrigins` argument. 689 """ 690 if defaultVerticalOrigin is None: 691 # find the most frequent vorg value 692 bag = {} 693 for gn in verticalOrigins: 694 vorg = verticalOrigins[gn] 695 if vorg not in bag: 696 bag[vorg] = 1 697 else: 698 bag[vorg] += 1 699 defaultVerticalOrigin = sorted(bag, key=lambda vorg: bag[vorg], reverse=True)[0] 700 self._initTableWithValues("VORG", {}, dict(VOriginRecords={}, defaultVertOriginY=defaultVerticalOrigin)) 701 vorgTable = self.font["VORG"] 702 vorgTable.majorVersion = 1 703 vorgTable.minorVersion = 0 704 for gn in verticalOrigins: 705 vorgTable[gn] = verticalOrigins[gn] 706 707 def setupPost(self, keepGlyphNames=True, **values): 708 """Create a new `post` table and initialize it with default values, 709 which can be overridden by keyword arguments. 710 """ 711 isCFF2 = 'CFF2' in self.font 712 postTable = self._initTableWithValues("post", _postDefaults, values) 713 if (self.isTTF or isCFF2) and keepGlyphNames: 714 postTable.formatType = 2.0 715 postTable.extraNames = [] 716 postTable.mapping = {} 717 else: 718 postTable.formatType = 3.0 719 720 def setupMaxp(self): 721 """Create a new `maxp` table. This is called implicitly by FontBuilder 722 itself and is usually not called by client code. 723 """ 724 if self.isTTF: 725 defaults = _maxpDefaultsTTF 726 else: 727 defaults = _maxpDefaultsOTF 728 self._initTableWithValues("maxp", defaults, {}) 729 730 def setupDummyDSIG(self): 731 """This adds an empty DSIG table to the font to make some MS applications 732 happy. This does not properly sign the font. 733 """ 734 values = dict( 735 ulVersion = 1, 736 usFlag = 0, 737 usNumSigs = 0, 738 signatureRecords = [], 739 ) 740 self._initTableWithValues("DSIG", {}, values) 741 742 def addOpenTypeFeatures(self, features, filename=None, tables=None): 743 """Add OpenType features to the font from a string containing 744 Feature File syntax. 745 746 The `filename` argument is used in error messages and to determine 747 where to look for "include" files. 748 749 The optional `tables` argument can be a list of OTL tables tags to 750 build, allowing the caller to only build selected OTL tables. See 751 `fontTools.feaLib` for details. 752 """ 753 from .feaLib.builder import addOpenTypeFeaturesFromString 754 addOpenTypeFeaturesFromString(self.font, features, filename=filename, tables=tables) 755 756 757def buildCmapSubTable(cmapping, format, platformID, platEncID): 758 subTable = cmap_classes[format](format) 759 subTable.cmap = cmapping 760 subTable.platformID = platformID 761 subTable.platEncID = platEncID 762 subTable.language = 0 763 return subTable 764 765 766def addFvar(font, axes, instances): 767 from .misc.py23 import Tag, tounicode 768 from .ttLib.tables._f_v_a_r import Axis, NamedInstance 769 770 assert axes 771 772 fvar = newTable('fvar') 773 nameTable = font['name'] 774 775 for tag, minValue, defaultValue, maxValue, name in axes: 776 axis = Axis() 777 axis.axisTag = Tag(tag) 778 axis.minValue, axis.defaultValue, axis.maxValue = minValue, defaultValue, maxValue 779 axis.axisNameID = nameTable.addName(tounicode(name)) 780 fvar.axes.append(axis) 781 782 for instance in instances: 783 coordinates = instance['location'] 784 name = tounicode(instance['stylename']) 785 psname = instance.get('postscriptfontname') 786 787 inst = NamedInstance() 788 inst.subfamilyNameID = nameTable.addName(name) 789 if psname is not None: 790 psname = tounicode(psname) 791 inst.postscriptNameID = nameTable.addName(psname) 792 inst.coordinates = coordinates 793 fvar.instances.append(inst) 794 795 font['fvar'] = fvar 796