| __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 |
| from collections import OrderedDict |
| |
| |
| _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.otFont = self.font |
| 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 |
| topDict.GlobalSubrs = fontSet.GlobalSubrs |
| 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) |
| topDict = self.font["CFF2"].cff.topDictIndex[0] |
| topDict.VarStore = vstore |
| for fontDict in topDict.FDArray: |
| fontDict.Private.vstore = 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): |
| """Adds an font variations table to the font. |
| |
| Args: |
| axes (list): See below. |
| instances (list): See below. |
| |
| ``axes`` should be a list of axes, with each axis either supplied as |
| a py:class:`.designspaceLib.AxisDescriptor` object, or a tuple in the |
| format ```tupletag, minValue, defaultValue, maxValue, name``. |
| The ``name`` is either a string, or a dict, mapping language codes |
| to strings, to allow localized name table entries. |
| |
| ```instances`` should be a list of instances, with each instance either |
| supplied as a py:class:`.designspaceLib.InstanceDescriptor` object, or a |
| dict with keys ``location`` (mapping of axis tags to float values), |
| ``stylename`` and (optionally) ``postscriptfontname``. |
| The ``stylename`` is either a string, or a dict, mapping language codes |
| to strings, to allow localized name table entries. |
| """ |
| |
| addFvar(self.font, axes, instances) |
| |
| def setupAvar(self, axes): |
| """Adds an axis variations table to the font. |
| |
| Args: |
| axes (list): A list of py:class:`.designspaceLib.AxisDescriptor` objects. |
| """ |
| from .varLib import _add_avar |
| |
| _add_avar(self.font, OrderedDict(enumerate(axes))) # Only values are used |
| |
| 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 addFeatureVariations(self, conditionalSubstitutions, featureTag="rvrn"): |
| """Add conditional substitutions to a Variable Font. |
| |
| See `fontTools.varLib.featureVars.addFeatureVariations`. |
| """ |
| from .varLib import featureVars |
| |
| if "fvar" not in self.font: |
| raise KeyError("'fvar' table is missing; can't add FeatureVariations.") |
| |
| featureVars.addFeatureVariations( |
| self.font, conditionalSubstitutions, featureTag=featureTag |
| ) |
| |
| def setupCOLR(self, colorLayers, version=None, varStore=None): |
| """Build new COLR table using color layers dictionary. |
| |
| Cf. `fontTools.colorLib.builder.buildCOLR`. |
| """ |
| from fontTools.colorLib.builder import buildCOLR |
| |
| glyphMap = self.font.getReverseGlyphMap() |
| self.font["COLR"] = buildCOLR( |
| colorLayers, version=version, glyphMap=glyphMap, varStore=varStore |
| ) |
| |
| def setupCPAL( |
| self, |
| palettes, |
| paletteTypes=None, |
| paletteLabels=None, |
| paletteEntryLabels=None, |
| ): |
| """Build new CPAL table using list of palettes. |
| |
| Optionally build CPAL v1 table using paletteTypes, paletteLabels and |
| paletteEntryLabels. |
| |
| Cf. `fontTools.colorLib.builder.buildCPAL`. |
| """ |
| from fontTools.colorLib.builder import buildCPAL |
| |
| self.font["CPAL"] = buildCPAL( |
| palettes, |
| paletteTypes=paletteTypes, |
| paletteLabels=paletteLabels, |
| paletteEntryLabels=paletteEntryLabels, |
| nameTable=self.font.get("name"), |
| ) |
| |
| def setupStat(self, axes, locations=None, elidedFallbackName=2): |
| """Build a new 'STAT' table. |
| |
| See `fontTools.otlLib.builder.buildStatTable` for details about |
| the arguments. |
| """ |
| from .otlLib.builder import buildStatTable |
| |
| buildStatTable(self.font, axes, locations, elidedFallbackName) |
| |
| |
| 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 .ttLib.tables._f_v_a_r import Axis, NamedInstance |
| from .designspaceLib import AxisDescriptor |
| |
| assert axes |
| |
| fvar = newTable("fvar") |
| nameTable = font["name"] |
| |
| for axis_def in axes: |
| axis = Axis() |
| |
| if isinstance(axis_def, tuple): |
| ( |
| axis.axisTag, |
| axis.minValue, |
| axis.defaultValue, |
| axis.maxValue, |
| name, |
| ) = axis_def |
| else: |
| (axis.axisTag, axis.minValue, axis.defaultValue, axis.maxValue, name) = ( |
| axis_def.tag, |
| axis_def.minimum, |
| axis_def.default, |
| axis_def.maximum, |
| axis_def.name, |
| ) |
| |
| if isinstance(name, str): |
| name = dict(en=name) |
| |
| axis.axisNameID = nameTable.addMultilingualName(name, ttFont=font) |
| fvar.axes.append(axis) |
| |
| for instance in instances: |
| if isinstance(instance, dict): |
| coordinates = instance["location"] |
| name = instance["stylename"] |
| psname = instance.get("postscriptfontname") |
| else: |
| coordinates = instance.location |
| name = instance.localisedStyleName or instance.styleName |
| psname = instance.postScriptFontName |
| |
| if isinstance(name, str): |
| name = dict(en=name) |
| |
| inst = NamedInstance() |
| inst.subfamilyNameID = nameTable.addMultilingualName(name, ttFont=font) |
| if psname is not None: |
| inst.postscriptNameID = nameTable.addName(psname) |
| inst.coordinates = coordinates |
| fvar.instances.append(inst) |
| |
| font["fvar"] = fvar |