| from __future__ import print_function, division, absolute_import |
| from __future__ import unicode_literals |
| |
| __all__ = ["FontBuilder"] |
| |
| """ |
| This module is *experimental*, meaning it still may evolve and change. |
| |
| The `FontBuilder` class is a convenient helper to construct working TTF or |
| OTF fonts from scratch. |
| |
| Note that the various setup methods cannot be called in arbitrary order, |
| due to various interdependencies between OpenType tables. Here is an order |
| that works: |
| |
| fb = FontBuilder(...) |
| fb.setupGlyphOrder(...) |
| fb.setupCharacterMap(...) |
| fb.setupGlyf(...) --or-- fb.setupCFF(...) |
| fb.setupHorizontalMetrics(...) |
| fb.setupHorizontalHeader() |
| fb.setupNameTable(...) |
| fb.setupOS2() |
| fb.addOpenTypeFeatures(...) |
| fb.setupPost() |
| fb.save(...) |
| |
| Here is how to build a minimal TTF: |
| |
| ```python |
| from fontTools.fontBuilder import FontBuilder |
| from fontTools.pens.ttGlyphPen import TTGlyphPen |
| |
| |
| def drawTestGlyph(pen): |
| pen.moveTo((100, 100)) |
| pen.lineTo((100, 1000)) |
| pen.qCurveTo((200, 900), (400, 900), (500, 1000)) |
| pen.lineTo((500, 100)) |
| pen.closePath() |
| |
| |
| fb = FontBuilder(1024, isTTF=True) |
| fb.setupGlyphOrder([".notdef", ".null", "space", "A", "a"]) |
| fb.setupCharacterMap({32: "space", 65: "A", 97: "a"}) |
| advanceWidths = {".notdef": 600, "space": 500, "A": 600, "a": 600, ".null": 0} |
| |
| familyName = "HelloTestFont" |
| styleName = "TotallyNormal" |
| version = "0.1" |
| |
| nameStrings = dict( |
| familyName=dict(en=familyName, nl="HalloTestFont"), |
| styleName=dict(en=styleName, nl="TotaalNormaal"), |
| uniqueFontIdentifier="fontBuilder: " + familyName + "." + styleName, |
| fullName=familyName + "-" + styleName, |
| psName=familyName + "-" + styleName, |
| version="Version " + version, |
| ) |
| |
| pen = TTGlyphPen(None) |
| drawTestGlyph(pen) |
| glyph = pen.glyph() |
| glyphs = {".notdef": glyph, "space": glyph, "A": glyph, "a": glyph, ".null": glyph} |
| fb.setupGlyf(glyphs) |
| metrics = {} |
| glyphTable = fb.font["glyf"] |
| for gn, advanceWidth in advanceWidths.items(): |
| metrics[gn] = (advanceWidth, glyphTable[gn].xMin) |
| fb.setupHorizontalMetrics(metrics) |
| fb.setupHorizontalHeader(ascent=824, descent=-200) |
| fb.setupNameTable(nameStrings) |
| fb.setupOS2(sTypoAscender=824, usWinAscent=824, usWinDescent=200) |
| fb.setupPost() |
| fb.save("test.ttf") |
| ``` |
| |
| And here's how to build a minimal OTF: |
| |
| ```python |
| from fontTools.fontBuilder import FontBuilder |
| from fontTools.pens.t2CharStringPen import T2CharStringPen |
| |
| |
| def drawTestGlyph(pen): |
| pen.moveTo((100, 100)) |
| pen.lineTo((100, 1000)) |
| pen.curveTo((200, 900), (400, 900), (500, 1000)) |
| pen.lineTo((500, 100)) |
| pen.closePath() |
| |
| |
| fb = FontBuilder(1024, isTTF=False) |
| fb.setupGlyphOrder([".notdef", ".null", "space", "A", "a"]) |
| fb.setupCharacterMap({32: "space", 65: "A", 97: "a"}) |
| advanceWidths = {".notdef": 600, "space": 500, "A": 600, "a": 600, ".null": 0} |
| |
| familyName = "HelloTestFont" |
| styleName = "TotallyNormal" |
| version = "0.1" |
| |
| nameStrings = dict( |
| familyName=dict(en=familyName, nl="HalloTestFont"), |
| styleName=dict(en=styleName, nl="TotaalNormaal"), |
| uniqueFontIdentifier="fontBuilder: " + familyName + "." + styleName, |
| fullName=familyName + "-" + styleName, |
| psName=familyName + "-" + styleName, |
| version="Version " + version, |
| ) |
| |
| pen = T2CharStringPen(600, None) |
| drawTestGlyph(pen) |
| charString = pen.getCharString() |
| charStrings = { |
| ".notdef": charString, |
| "space": charString, |
| "A": charString, |
| "a": charString, |
| ".null": charString, |
| } |
| fb.setupCFF(nameStrings["psName"], {"FullName": nameStrings["psName"]}, charStrings, {}) |
| lsb = {gn: cs.calcBounds(None)[0] for gn, cs in charStrings.items()} |
| metrics = {} |
| for gn, advanceWidth in advanceWidths.items(): |
| metrics[gn] = (advanceWidth, lsb[gn]) |
| fb.setupHorizontalMetrics(metrics) |
| fb.setupHorizontalHeader(ascent=824, descent=200) |
| fb.setupNameTable(nameStrings) |
| fb.setupOS2(sTypoAscender=824, usWinAscent=824, usWinDescent=200) |
| fb.setupPost() |
| fb.save("test.otf") |
| ``` |
| """ |
| |
| from .misc.py23 import * |
| from .ttLib import TTFont, newTable |
| from .ttLib.tables._c_m_a_p import cmap_classes |
| from .ttLib.tables._n_a_m_e import NameRecord, makeName |
| from .misc.timeTools import timestampNow |
| import struct |
| |
| |
| _headDefaults = dict( |
| tableVersion = 1.0, |
| fontRevision = 1.0, |
| checkSumAdjustment = 0, |
| magicNumber = 0x5F0F3CF5, |
| flags = 0x0003, |
| unitsPerEm = 1000, |
| created = 0, |
| modified = 0, |
| xMin = 0, |
| yMin = 0, |
| xMax = 0, |
| yMax = 0, |
| macStyle = 0, |
| lowestRecPPEM = 3, |
| fontDirectionHint = 2, |
| indexToLocFormat = 0, |
| glyphDataFormat = 0, |
| ) |
| |
| _maxpDefaultsTTF = dict( |
| tableVersion = 0x00010000, |
| numGlyphs = 0, |
| maxPoints = 0, |
| maxContours = 0, |
| maxCompositePoints = 0, |
| maxCompositeContours = 0, |
| maxZones = 2, |
| maxTwilightPoints = 0, |
| maxStorage = 0, |
| maxFunctionDefs = 0, |
| maxInstructionDefs = 0, |
| maxStackElements = 0, |
| maxSizeOfInstructions = 0, |
| maxComponentElements = 0, |
| maxComponentDepth = 0, |
| ) |
| _maxpDefaultsOTF = dict( |
| tableVersion = 0x00005000, |
| numGlyphs = 0, |
| ) |
| |
| _postDefaults = dict( |
| formatType = 3.0, |
| italicAngle = 0, |
| underlinePosition = 0, |
| underlineThickness = 0, |
| isFixedPitch = 0, |
| minMemType42 = 0, |
| maxMemType42 = 0, |
| minMemType1 = 0, |
| maxMemType1 = 0, |
| ) |
| |
| _hheaDefaults = dict( |
| tableVersion = 0x00010000, |
| ascent = 0, |
| descent = 0, |
| lineGap = 0, |
| advanceWidthMax = 0, |
| minLeftSideBearing = 0, |
| minRightSideBearing = 0, |
| xMaxExtent = 0, |
| caretSlopeRise = 1, |
| caretSlopeRun = 0, |
| caretOffset = 0, |
| reserved0 = 0, |
| reserved1 = 0, |
| reserved2 = 0, |
| reserved3 = 0, |
| metricDataFormat = 0, |
| numberOfHMetrics = 0, |
| ) |
| |
| _vheaDefaults = dict( |
| tableVersion = 0x00010000, |
| ascent = 0, |
| descent = 0, |
| lineGap = 0, |
| advanceHeightMax = 0, |
| minTopSideBearing = 0, |
| minBottomSideBearing = 0, |
| yMaxExtent = 0, |
| caretSlopeRise = 0, |
| caretSlopeRun = 0, |
| reserved0 = 0, |
| reserved1 = 0, |
| reserved2 = 0, |
| reserved3 = 0, |
| reserved4 = 0, |
| metricDataFormat = 0, |
| numberOfVMetrics = 0, |
| ) |
| |
| _nameIDs = dict( |
| copyright = 0, |
| familyName = 1, |
| styleName = 2, |
| uniqueFontIdentifier = 3, |
| fullName = 4, |
| version = 5, |
| psName = 6, |
| trademark = 7, |
| manufacturer = 8, |
| designer = 9, |
| description = 10, |
| vendorURL = 11, |
| designerURL = 12, |
| licenseDescription = 13, |
| licenseInfoURL = 14, |
| # reserved = 15, |
| typographicFamily = 16, |
| typographicSubfamily = 17, |
| compatibleFullName = 18, |
| sampleText = 19, |
| postScriptCIDFindfontName = 20, |
| wwsFamilyName = 21, |
| wwsSubfamilyName = 22, |
| lightBackgroundPalette = 23, |
| darkBackgroundPalette = 24, |
| variationsPostScriptNamePrefix = 25, |
| ) |
| |
| # to insert in setupNameTable doc string: |
| # print("\n".join(("%s (nameID %s)" % (k, v)) for k, v in sorted(_nameIDs.items(), key=lambda x: x[1]))) |
| |
| _panoseDefaults = dict( |
| bFamilyType = 0, |
| bSerifStyle = 0, |
| bWeight = 0, |
| bProportion = 0, |
| bContrast = 0, |
| bStrokeVariation = 0, |
| bArmStyle = 0, |
| bLetterForm = 0, |
| bMidline = 0, |
| bXHeight = 0, |
| ) |
| |
| _OS2Defaults = dict( |
| version = 3, |
| xAvgCharWidth = 0, |
| usWeightClass = 400, |
| usWidthClass = 5, |
| fsType = 0x0004, # default: Preview & Print embedding |
| ySubscriptXSize = 0, |
| ySubscriptYSize = 0, |
| ySubscriptXOffset = 0, |
| ySubscriptYOffset = 0, |
| ySuperscriptXSize = 0, |
| ySuperscriptYSize = 0, |
| ySuperscriptXOffset = 0, |
| ySuperscriptYOffset = 0, |
| yStrikeoutSize = 0, |
| yStrikeoutPosition = 0, |
| sFamilyClass = 0, |
| panose = _panoseDefaults, |
| ulUnicodeRange1 = 0, |
| ulUnicodeRange2 = 0, |
| ulUnicodeRange3 = 0, |
| ulUnicodeRange4 = 0, |
| achVendID = "????", |
| fsSelection = 0, |
| usFirstCharIndex = 0, |
| usLastCharIndex = 0, |
| sTypoAscender = 0, |
| sTypoDescender = 0, |
| sTypoLineGap = 0, |
| usWinAscent = 0, |
| usWinDescent = 0, |
| ulCodePageRange1 = 0, |
| ulCodePageRange2 = 0, |
| sxHeight = 0, |
| sCapHeight = 0, |
| usDefaultChar = 0, # .notdef |
| usBreakChar = 32, # space |
| usMaxContext = 0, |
| usLowerOpticalPointSize = 0, |
| usUpperOpticalPointSize = 0, |
| ) |
| |
| |
| class FontBuilder(object): |
| |
| def __init__(self, unitsPerEm=None, font=None, isTTF=True): |
| """Initialize a FontBuilder instance. |
| |
| If the `font` argument is not given, a new `TTFont` will be |
| constructed, and `unitsPerEm` must be given. If `isTTF` is True, |
| the font will be a glyf-based TTF; if `isTTF` is False it will be |
| a CFF-based OTF. |
| |
| If `font` is given, it must be a `TTFont` instance and `unitsPerEm` |
| must _not_ be given. The `isTTF` argument will be ignored. |
| """ |
| if font is None: |
| self.font = TTFont(recalcTimestamp=False) |
| self.isTTF = isTTF |
| now = timestampNow() |
| assert unitsPerEm is not None |
| self.setupHead(unitsPerEm=unitsPerEm, created=now, modified=now) |
| self.setupMaxp() |
| else: |
| assert unitsPerEm is None |
| self.font = font |
| self.isTTF = "glyf" in font |
| |
| def save(self, file): |
| """Save the font. The 'file' argument can be either a pathname or a |
| writable file object. |
| """ |
| self.font.save(file) |
| |
| def _initTableWithValues(self, tableTag, defaults, values): |
| table = self.font[tableTag] = newTable(tableTag) |
| for k, v in defaults.items(): |
| setattr(table, k, v) |
| for k, v in values.items(): |
| setattr(table, k, v) |
| return table |
| |
| def _updateTableWithValues(self, tableTag, values): |
| table = self.font[tableTag] |
| for k, v in values.items(): |
| setattr(table, k, v) |
| |
| def setupHead(self, **values): |
| """Create a new `head` table and initialize it with default values, |
| which can be overridden by keyword arguments. |
| """ |
| self._initTableWithValues("head", _headDefaults, values) |
| |
| def updateHead(self, **values): |
| """Update the head table with the fields and values passed as |
| keyword arguments. |
| """ |
| self._updateTableWithValues("head", values) |
| |
| def setupGlyphOrder(self, glyphOrder): |
| """Set the glyph order for the font.""" |
| self.font.setGlyphOrder(glyphOrder) |
| |
| def setupCharacterMap(self, cmapping, uvs=None, allowFallback=False): |
| """Build the `cmap` table for the font. The `cmapping` argument should |
| be a dict mapping unicode code points as integers to glyph names. |
| |
| The `uvs` argument, when passed, must be a list of tuples, describing |
| Unicode Variation Sequences. These tuples have three elements: |
| (unicodeValue, variationSelector, glyphName) |
| `unicodeValue` and `variationSelector` are integer code points. |
| `glyphName` may be None, to indicate this is the default variation. |
| Text processors will then use the cmap to find the glyph name. |
| Each Unicode Variation Sequence should be an officially supported |
| sequence, but this is not policed. |
| """ |
| subTables = [] |
| highestUnicode = max(cmapping) |
| if highestUnicode > 0xffff: |
| cmapping_3_1 = dict((k, v) for k, v in cmapping.items() if k < 0x10000) |
| subTable_3_10 = buildCmapSubTable(cmapping, 12, 3, 10) |
| subTables.append(subTable_3_10) |
| else: |
| cmapping_3_1 = cmapping |
| format = 4 |
| subTable_3_1 = buildCmapSubTable(cmapping_3_1, format, 3, 1) |
| try: |
| subTable_3_1.compile(self.font) |
| except struct.error: |
| # format 4 overflowed, fall back to format 12 |
| if not allowFallback: |
| raise ValueError("cmap format 4 subtable overflowed; sort glyph order by unicode to fix.") |
| format = 12 |
| subTable_3_1 = buildCmapSubTable(cmapping_3_1, format, 3, 1) |
| subTables.append(subTable_3_1) |
| subTable_0_3 = buildCmapSubTable(cmapping_3_1, format, 0, 3) |
| subTables.append(subTable_0_3) |
| |
| if uvs is not None: |
| uvsDict = {} |
| for unicodeValue, variationSelector, glyphName in uvs: |
| if cmapping.get(unicodeValue) == glyphName: |
| # this is a default variation |
| glyphName = None |
| if variationSelector not in uvsDict: |
| uvsDict[variationSelector] = [] |
| uvsDict[variationSelector].append((unicodeValue, glyphName)) |
| uvsSubTable = buildCmapSubTable({}, 14, 0, 5) |
| uvsSubTable.uvsDict = uvsDict |
| subTables.append(uvsSubTable) |
| |
| self.font["cmap"] = newTable("cmap") |
| self.font["cmap"].tableVersion = 0 |
| self.font["cmap"].tables = subTables |
| |
| def setupNameTable(self, nameStrings, windows=True, mac=True): |
| """Create the `name` table for the font. The `nameStrings` argument must |
| be a dict, mapping nameIDs or descriptive names for the nameIDs to name |
| record values. A value is either a string, or a dict, mapping language codes |
| to strings, to allow localized name table entries. |
| |
| By default, both Windows (platformID=3) and Macintosh (platformID=1) name |
| records are added, unless any of `windows` or `mac` arguments is False. |
| |
| The following descriptive names are available for nameIDs: |
| |
| copyright (nameID 0) |
| familyName (nameID 1) |
| styleName (nameID 2) |
| uniqueFontIdentifier (nameID 3) |
| fullName (nameID 4) |
| version (nameID 5) |
| psName (nameID 6) |
| trademark (nameID 7) |
| manufacturer (nameID 8) |
| designer (nameID 9) |
| description (nameID 10) |
| vendorURL (nameID 11) |
| designerURL (nameID 12) |
| licenseDescription (nameID 13) |
| licenseInfoURL (nameID 14) |
| typographicFamily (nameID 16) |
| typographicSubfamily (nameID 17) |
| compatibleFullName (nameID 18) |
| sampleText (nameID 19) |
| postScriptCIDFindfontName (nameID 20) |
| wwsFamilyName (nameID 21) |
| wwsSubfamilyName (nameID 22) |
| lightBackgroundPalette (nameID 23) |
| darkBackgroundPalette (nameID 24) |
| variationsPostScriptNamePrefix (nameID 25) |
| """ |
| nameTable = self.font["name"] = newTable("name") |
| nameTable.names = [] |
| |
| for nameName, nameValue in nameStrings.items(): |
| if isinstance(nameName, int): |
| nameID = nameName |
| else: |
| nameID = _nameIDs[nameName] |
| if isinstance(nameValue, basestring): |
| nameValue = dict(en=nameValue) |
| nameTable.addMultilingualName( |
| nameValue, ttFont=self.font, nameID=nameID, windows=windows, mac=mac |
| ) |
| |
| def setupOS2(self, **values): |
| """Create a new `OS/2` table and initialize it with default values, |
| which can be overridden by keyword arguments. |
| """ |
| if "xAvgCharWidth" not in values: |
| gs = self.font.getGlyphSet() |
| widths = [gs[glyphName].width for glyphName in gs.keys() if gs[glyphName].width > 0] |
| values["xAvgCharWidth"] = int(round(sum(widths) / float(len(widths)))) |
| self._initTableWithValues("OS/2", _OS2Defaults, values) |
| if not ("ulUnicodeRange1" in values or "ulUnicodeRange2" in values or |
| "ulUnicodeRange3" in values or "ulUnicodeRange3" in values): |
| assert "cmap" in self.font, "the 'cmap' table must be setup before the 'OS/2' table" |
| self.font["OS/2"].recalcUnicodeRanges(self.font) |
| |
| def setupCFF(self, psName, fontInfo, charStringsDict, privateDict): |
| from .cffLib import CFFFontSet, TopDictIndex, TopDict, CharStrings, \ |
| GlobalSubrsIndex, PrivateDict |
| |
| assert not self.isTTF |
| self.font.sfntVersion = "OTTO" |
| fontSet = CFFFontSet() |
| fontSet.major = 1 |
| fontSet.minor = 0 |
| fontSet.fontNames = [psName] |
| fontSet.topDictIndex = TopDictIndex() |
| |
| globalSubrs = GlobalSubrsIndex() |
| fontSet.GlobalSubrs = globalSubrs |
| private = PrivateDict() |
| for key, value in privateDict.items(): |
| setattr(private, key, value) |
| fdSelect = None |
| fdArray = None |
| |
| topDict = TopDict() |
| topDict.charset = self.font.getGlyphOrder() |
| topDict.Private = private |
| for key, value in fontInfo.items(): |
| setattr(topDict, key, value) |
| if "FontMatrix" not in fontInfo: |
| scale = 1 / self.font["head"].unitsPerEm |
| topDict.FontMatrix = [scale, 0, 0, scale, 0, 0] |
| |
| charStrings = CharStrings(None, topDict.charset, globalSubrs, private, fdSelect, fdArray) |
| for glyphName, charString in charStringsDict.items(): |
| charString.private = private |
| charString.globalSubrs = globalSubrs |
| charStrings[glyphName] = charString |
| topDict.CharStrings = charStrings |
| |
| fontSet.topDictIndex.append(topDict) |
| |
| self.font["CFF "] = newTable("CFF ") |
| self.font["CFF "].cff = fontSet |
| |
| def setupCFF2(self, charStringsDict, fdArrayList=None, regions=None): |
| from .cffLib import CFFFontSet, TopDictIndex, TopDict, CharStrings, \ |
| GlobalSubrsIndex, PrivateDict, FDArrayIndex, FontDict |
| |
| assert not self.isTTF |
| self.font.sfntVersion = "OTTO" |
| fontSet = CFFFontSet() |
| fontSet.major = 2 |
| fontSet.minor = 0 |
| |
| cff2GetGlyphOrder = self.font.getGlyphOrder |
| fontSet.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder, None) |
| |
| globalSubrs = GlobalSubrsIndex() |
| fontSet.GlobalSubrs = globalSubrs |
| |
| if fdArrayList is None: |
| fdArrayList = [{}] |
| fdSelect = None |
| fdArray = FDArrayIndex() |
| fdArray.strings = None |
| fdArray.GlobalSubrs = globalSubrs |
| for privateDict in fdArrayList: |
| fontDict = FontDict() |
| fontDict.setCFF2(True) |
| private = PrivateDict() |
| for key, value in privateDict.items(): |
| setattr(private, key, value) |
| fontDict.Private = private |
| fdArray.append(fontDict) |
| |
| topDict = TopDict() |
| topDict.cff2GetGlyphOrder = cff2GetGlyphOrder |
| topDict.FDArray = fdArray |
| scale = 1 / self.font["head"].unitsPerEm |
| topDict.FontMatrix = [scale, 0, 0, scale, 0, 0] |
| |
| private = fdArray[0].Private |
| charStrings = CharStrings(None, None, globalSubrs, private, fdSelect, fdArray) |
| for glyphName, charString in charStringsDict.items(): |
| charString.private = private |
| charString.globalSubrs = globalSubrs |
| charStrings[glyphName] = charString |
| topDict.CharStrings = charStrings |
| |
| fontSet.topDictIndex.append(topDict) |
| |
| self.font["CFF2"] = newTable("CFF2") |
| self.font["CFF2"].cff = fontSet |
| |
| if regions: |
| self.setupCFF2Regions(regions) |
| |
| def setupCFF2Regions(self, regions): |
| from .varLib.builder import buildVarRegionList, buildVarData, buildVarStore |
| from .cffLib import VarStoreData |
| |
| assert "fvar" in self.font, "fvar must to be set up first" |
| assert "CFF2" in self.font, "CFF2 must to be set up first" |
| axisTags = [a.axisTag for a in self.font["fvar"].axes] |
| varRegionList = buildVarRegionList(regions, axisTags) |
| varData = buildVarData(list(range(len(regions))), None, optimize=False) |
| varStore = buildVarStore(varRegionList, [varData]) |
| vstore = VarStoreData(otVarStore=varStore) |
| self.font["CFF2"].cff.topDictIndex[0].VarStore = vstore |
| |
| def setupGlyf(self, glyphs, calcGlyphBounds=True): |
| """Create the `glyf` table from a dict, that maps glyph names |
| to `fontTools.ttLib.tables._g_l_y_f.Glyph` objects, for example |
| as made by `fontTools.pens.ttGlyphPen.TTGlyphPen`. |
| |
| If `calcGlyphBounds` is True, the bounds of all glyphs will be |
| calculated. Only pass False if your glyph objects already have |
| their bounding box values set. |
| """ |
| assert self.isTTF |
| self.font["loca"] = newTable("loca") |
| self.font["glyf"] = newTable("glyf") |
| self.font["glyf"].glyphs = glyphs |
| if hasattr(self.font, "glyphOrder"): |
| self.font["glyf"].glyphOrder = self.font.glyphOrder |
| if calcGlyphBounds: |
| self.calcGlyphBounds() |
| |
| def setupFvar(self, axes, instances): |
| addFvar(self.font, axes, instances) |
| |
| def setupGvar(self, variations): |
| gvar = self.font["gvar"] = newTable('gvar') |
| gvar.version = 1 |
| gvar.reserved = 0 |
| gvar.variations = variations |
| |
| def calcGlyphBounds(self): |
| """Calculate the bounding boxes of all glyphs in the `glyf` table. |
| This is usually not called explicitly by client code. |
| """ |
| glyphTable = self.font["glyf"] |
| for glyph in glyphTable.glyphs.values(): |
| glyph.recalcBounds(glyphTable) |
| |
| def setupHorizontalMetrics(self, metrics): |
| """Create a new `hmtx` table, for horizontal metrics. |
| |
| The `metrics` argument must be a dict, mapping glyph names to |
| `(width, leftSidebearing)` tuples. |
| """ |
| self.setupMetrics('hmtx', metrics) |
| |
| def setupVerticalMetrics(self, metrics): |
| """Create a new `vmtx` table, for horizontal metrics. |
| |
| The `metrics` argument must be a dict, mapping glyph names to |
| `(height, topSidebearing)` tuples. |
| """ |
| self.setupMetrics('vmtx', metrics) |
| |
| def setupMetrics(self, tableTag, metrics): |
| """See `setupHorizontalMetrics()` and `setupVerticalMetrics()`.""" |
| assert tableTag in ("hmtx", "vmtx") |
| mtxTable = self.font[tableTag] = newTable(tableTag) |
| roundedMetrics = {} |
| for gn in metrics: |
| w, lsb = metrics[gn] |
| roundedMetrics[gn] = int(round(w)), int(round(lsb)) |
| mtxTable.metrics = roundedMetrics |
| |
| def setupHorizontalHeader(self, **values): |
| """Create a new `hhea` table initialize it with default values, |
| which can be overridden by keyword arguments. |
| """ |
| self._initTableWithValues("hhea", _hheaDefaults, values) |
| |
| def setupVerticalHeader(self, **values): |
| """Create a new `vhea` table initialize it with default values, |
| which can be overridden by keyword arguments. |
| """ |
| self._initTableWithValues("vhea", _vheaDefaults, values) |
| |
| def setupVerticalOrigins(self, verticalOrigins, defaultVerticalOrigin=None): |
| """Create a new `VORG` table. The `verticalOrigins` argument must be |
| a dict, mapping glyph names to vertical origin values. |
| |
| The `defaultVerticalOrigin` argument should be the most common vertical |
| origin value. If omitted, this value will be derived from the actual |
| values in the `verticalOrigins` argument. |
| """ |
| if defaultVerticalOrigin is None: |
| # find the most frequent vorg value |
| bag = {} |
| for gn in verticalOrigins: |
| vorg = verticalOrigins[gn] |
| if vorg not in bag: |
| bag[vorg] = 1 |
| else: |
| bag[vorg] += 1 |
| defaultVerticalOrigin = sorted(bag, key=lambda vorg: bag[vorg], reverse=True)[0] |
| self._initTableWithValues("VORG", {}, dict(VOriginRecords={}, defaultVertOriginY=defaultVerticalOrigin)) |
| vorgTable = self.font["VORG"] |
| vorgTable.majorVersion = 1 |
| vorgTable.minorVersion = 0 |
| for gn in verticalOrigins: |
| vorgTable[gn] = verticalOrigins[gn] |
| |
| def setupPost(self, keepGlyphNames=True, **values): |
| """Create a new `post` table and initialize it with default values, |
| which can be overridden by keyword arguments. |
| """ |
| isCFF2 = 'CFF2' in self.font |
| postTable = self._initTableWithValues("post", _postDefaults, values) |
| if (self.isTTF or isCFF2) and keepGlyphNames: |
| postTable.formatType = 2.0 |
| postTable.extraNames = [] |
| postTable.mapping = {} |
| else: |
| postTable.formatType = 3.0 |
| |
| def setupMaxp(self): |
| """Create a new `maxp` table. This is called implicitly by FontBuilder |
| itself and is usually not called by client code. |
| """ |
| if self.isTTF: |
| defaults = _maxpDefaultsTTF |
| else: |
| defaults = _maxpDefaultsOTF |
| self._initTableWithValues("maxp", defaults, {}) |
| |
| def setupDummyDSIG(self): |
| """This adds an empty DSIG table to the font to make some MS applications |
| happy. This does not properly sign the font. |
| """ |
| values = dict( |
| ulVersion = 1, |
| usFlag = 0, |
| usNumSigs = 0, |
| signatureRecords = [], |
| ) |
| self._initTableWithValues("DSIG", {}, values) |
| |
| def addOpenTypeFeatures(self, features, filename=None, tables=None): |
| """Add OpenType features to the font from a string containing |
| Feature File syntax. |
| |
| The `filename` argument is used in error messages and to determine |
| where to look for "include" files. |
| |
| The optional `tables` argument can be a list of OTL tables tags to |
| build, allowing the caller to only build selected OTL tables. See |
| `fontTools.feaLib` for details. |
| """ |
| from .feaLib.builder import addOpenTypeFeaturesFromString |
| addOpenTypeFeaturesFromString(self.font, features, filename=filename, tables=tables) |
| |
| |
| def buildCmapSubTable(cmapping, format, platformID, platEncID): |
| subTable = cmap_classes[format](format) |
| subTable.cmap = cmapping |
| subTable.platformID = platformID |
| subTable.platEncID = platEncID |
| subTable.language = 0 |
| return subTable |
| |
| |
| def addFvar(font, axes, instances): |
| from .misc.py23 import Tag, tounicode |
| from .ttLib.tables._f_v_a_r import Axis, NamedInstance |
| |
| assert axes |
| |
| fvar = newTable('fvar') |
| nameTable = font['name'] |
| |
| for tag, minValue, defaultValue, maxValue, name in axes: |
| axis = Axis() |
| axis.axisTag = Tag(tag) |
| axis.minValue, axis.defaultValue, axis.maxValue = minValue, defaultValue, maxValue |
| axis.axisNameID = nameTable.addName(tounicode(name)) |
| fvar.axes.append(axis) |
| |
| for instance in instances: |
| coordinates = instance['location'] |
| name = tounicode(instance['stylename']) |
| psname = instance.get('postscriptfontname') |
| |
| inst = NamedInstance() |
| inst.subfamilyNameID = nameTable.addName(name) |
| if psname is not None: |
| psname = tounicode(psname) |
| inst.postscriptNameID = nameTable.addName(psname) |
| inst.coordinates = coordinates |
| fvar.instances.append(inst) |
| |
| font['fvar'] = fvar |