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