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