• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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