fonttools/Lib/fontTools/fontBuilder.py

442 lines
13 KiB
Python
Raw Normal View History

2018-10-31 20:54:34 +01:00
from __future__ import print_function, division, absolute_import
from __future__ import unicode_literals
2018-11-01 09:44:56 +01:00
from .misc.py23 import *
2018-10-31 20:54:34 +01:00
from .ttLib import TTFont, newTable
from .ttLib.tables._c_m_a_p import cmap_classes
2018-11-01 09:36:37 +01:00
from .ttLib.tables._n_a_m_e import NameRecord, makeName
2018-10-31 20:54:34 +01:00
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 = []
for nameName, nameValue in nameStrings.items():
if isinstance(nameName, int):
nameID = nameName
else:
nameID = _nameIDs[nameName]
2018-11-01 09:44:56 +01:00
if not isinstance(nameValue, basestring):
nameValue, language = nameValue
else:
language = "en"
nameTable.addMultilingualName({language: nameValue}, ttFont=self.font, nameID=nameID)
2018-10-31 20:54:34 +01:00
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, filename=None, tables=None):
2018-10-31 20:54:34 +01:00
from .feaLib.builder import addOpenTypeFeaturesFromString
addOpenTypeFeaturesFromString(self.font, features, filename=filename, tables=tables)
2018-10-31 20:54:34 +01:00
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