from __future__ import print_function, division, absolute_import from __future__ import unicode_literals from .ttLib import TTFont, newTable from .ttLib.tables._c_m_a_p import cmap_classes from .ttLib.tables._n_a_m_e import NameRecord 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, identifier = 3, fullName = 4, version = 5, psName = 6, trademark = 7, manufacturer = 8, typographicFamily = 16, typographicSubfamily = 17, # XXX this needs to be extended with legal things, etc. ) _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 = 2, # just kerning usLowerOpticalPointSize = 0, usUpperOpticalPointSize = 0, ) class FontBuilder(object): def __init__(self, unitsPerEm=None, font=None, isTTF=True): 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, path): self.font.save(path) 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): self._initTableWithValues("head", _headDefaults, values) def updateHead(self, **values): self._updateTableWithValues("head", values) def setupGlyphOrder(self, glyphOrder): self.font.setGlyphOrder(glyphOrder) def setupCharacterMap(self, cmapping, allowFallback=False): 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) self.font["cmap"] = newTable("cmap") self.font["cmap"].tableVersion = 0 self.font["cmap"].tables = subTables def setupNameTable(self, nameStrings): nameTable = self.font["name"] = newTable("name") nameTable.names = [] windows = (3, 1, 0x409, "utf-16-be") macintosh = (1, 0, 0, "mac-roman") for nameName, nameValue in nameStrings.items(): if isinstance(nameName, int): nameID = nameName else: nameID = _nameIDs[nameName] for platformID, platEncID, langID, encoding in [windows, macintosh]: nameRecord = NameRecord() nameRecord.nameID = nameID nameRecord.platformID = platformID nameRecord.platEncID = platEncID nameRecord.langID = langID nameRecord.string = nameValue.encode(encoding, "replace") nameTable.names.append(nameRecord) def setupOS2(self, **values): 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 self.font["OS/2"].recalcUnicodeRanges(self.font) def setupCFF(self, psName, fontInfo, charStringsDict, privateDict): assert not self.isTTF from .cffLib import CFFFontSet, TopDictIndex, TopDict, CharStrings, \ GlobalSubrsIndex, PrivateDict 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) charStrings = CharStrings(None, topDict.charset, globalSubrs, private, fdSelect, fdArray) for glypnName, charString in charStringsDict.items(): charString.private = private charString.globalSubrs = globalSubrs charStrings[glypnName] = charString topDict.CharStrings = charStrings fontSet.topDictIndex.append(topDict) self.font["CFF "] = newTable("CFF ") self.font["CFF "].cff = fontSet def setupGlyf(self, glyphs): 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 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): glyphTable = self.font["glyf"] for glyph in glyphTable.glyphs.values(): glyph.recalcBounds(glyphTable) def setupMetrics(self, tableTag, metrics): 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): self._initTableWithValues("hhea", _hheaDefaults, values) def setupVerticalHeader(self, **values): self._initTableWithValues("vhea", _vheaDefaults, values) def setupVerticalOrigins(self, verticalOrigins, defaultVerticalOrigin=None): 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): postTable = self._initTableWithValues("post", _postDefaults, values) if self.isTTF and keepGlyphNames: postTable.formatType = 2.0 postTable.extraNames = [] postTable.mapping = {} else: postTable.formatType = 3.0 def setupMaxp(self): if self.isTTF: defaults = _maxpDefaultsTTF else: defaults = _maxpDefaultsOTF self._initTableWithValues("maxp", defaults, {}) def setupDSIG(self): from .ttLib.tables.D_S_I_G_ import SignatureRecord sig = SignatureRecord() sig.ulLength = 20 sig.cbSignature = 12 sig.usReserved2 = 0 sig.usReserved1 = 0 sig.pkcs7 = '\xd3M4\xd3M5\xd3M4\xd3M4' sig.ulFormat = 1 sig.ulOffset = 20 values = dict( ulVersion = 1, usFlag = 1, usNumSigs = 1, signatureRecords = [sig], ) self._initTableWithValues("DSIG", {}, values) def addOpenTypeFeatures(self, features): from .feaLib.builder import addOpenTypeFeaturesFromString addOpenTypeFeaturesFromString(self.font, features) 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