Merge pull request #103 from olivierberten/post4
'post' format 4.0 support
diff --git a/Lib/fontTools/merge.py b/Lib/fontTools/merge.py
index c3ae1cc..54e91e3 100644
--- a/Lib/fontTools/merge.py
+++ b/Lib/fontTools/merge.py
@@ -33,9 +33,10 @@
# General utility functions for merging values from different fonts
def equal(lst):
+ lst = list(lst)
t = iter(lst)
first = next(t)
- assert all(item == first for item in t)
+ assert all(item == first for item in t), "Expected all items to be equal: %s" % lst
return first
def first(lst):
@@ -57,7 +58,7 @@
lst = list(lst)
return sum(lst) // len(lst)
-def implementedFilter(func):
+def onlyExisting(func):
"""Returns a filter func that when called with a list,
only calls func on the non-NotImplemented items of the list,
and only so if there's at least one item remaining.
@@ -82,14 +83,19 @@
return d
def mergeObjects(lst):
- lst = [item for item in lst if item is not None and item is not NotImplemented]
+ lst = [item for item in lst if item is not NotImplemented]
if not lst:
- return None # Not all can be NotImplemented
+ return NotImplemented
+ lst = [item for item in lst if item is not None]
+ if not lst:
+ return None
clazz = lst[0].__class__
assert all(type(item) == clazz for item in lst), lst
+
logic = clazz.mergeMap
returnTable = clazz()
+ returnDict = {}
allKeys = set.union(set(), *(vars(table).keys() for table in lst))
for key in allKeys:
@@ -105,25 +111,31 @@
continue
value = mergeLogic(getattr(table, key, NotImplemented) for table in lst)
if value is not NotImplemented:
- setattr(returnTable, key, value)
+ returnDict[key] = value
+
+ returnTable.__dict__ = returnDict
return returnTable
-def mergeBits(logic, lst):
- lst = list(lst)
- returnValue = 0
- for bitNumber in range(logic['size']):
- try:
- mergeLogic = logic[bitNumber]
- except KeyError:
+def mergeBits(bitmap):
+
+ def wrapper(lst):
+ lst = list(lst)
+ returnValue = 0
+ for bitNumber in range(bitmap['size']):
try:
- mergeLogic = logic['*']
+ mergeLogic = bitmap[bitNumber]
except KeyError:
- raise Exception("Don't know how to merge bit %s" % bitNumber)
- shiftedBit = 1 << bitNumber
- mergedValue = mergeLogic(bool(item & shiftedBit) for item in lst)
- returnValue |= mergedValue << bitNumber
- return returnValue
+ try:
+ mergeLogic = bitmap['*']
+ except KeyError:
+ raise Exception("Don't know how to merge bit %s" % bitNumber)
+ shiftedBit = 1 << bitNumber
+ mergedValue = mergeLogic(bool(item & shiftedBit) for item in lst)
+ returnValue |= mergedValue << bitNumber
+ return returnValue
+
+ return wrapper
@_add_method(DefaultTable, allowDefaultTable=True)
@@ -152,7 +164,7 @@
# maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions
}
-headFlagsMergeMap = {
+headFlagsMergeBitMap = {
'size': 16,
'*': bitwise_or,
1: bitwise_and, # Baseline at y = 0
@@ -172,7 +184,7 @@
'fontRevision': max,
'checkSumAdjustment': lambda lst: 0, # We need *something* here
'magicNumber': equal,
- 'flags': lambda lst: mergeBits(headFlagsMergeMap, lst),
+ 'flags': mergeBits(headFlagsMergeBitMap),
'unitsPerEm': equal,
'created': current_time,
'modified': current_time,
@@ -204,7 +216,7 @@
'numberOfHMetrics': recalculate,
}
-os2FsTypeMergeMap = {
+os2FsTypeMergeBitMap = {
'size': 16,
'*': lambda bit: 0,
1: bitwise_or, # no embedding permitted
@@ -231,7 +243,7 @@
elif lst[i] == 0:
lst[i] = 0x000C
- fsType = mergeBits(os2FsTypeMergeMap, lst)
+ fsType = mergeBits(os2FsTypeMergeBitMap)(lst)
# unset bits 2 and 3 if bit 1 is set (some font is "no embedding")
if fsType & 0x0002:
fsType &= ~0x000C
@@ -256,9 +268,10 @@
'sTypoLineGap': max,
'usWinAscent': max,
'usWinDescent': max,
- 'ulCodePageRange1': bitwise_or,
- 'ulCodePageRange2': bitwise_or,
- 'usMaxContex': max,
+ # Version 2,3,4
+ 'ulCodePageRange1': onlyExisting(bitwise_or),
+ 'ulCodePageRange2': onlyExisting(bitwise_or),
+ 'usMaxContex': onlyExisting(max),
# TODO version 5
}
@@ -285,7 +298,7 @@
'maxMemType42': lambda lst: 0,
'minMemType1': max,
'maxMemType1': lambda lst: 0,
- 'mapping': implementedFilter(sumDicts),
+ 'mapping': onlyExisting(sumDicts),
'extraNames': lambda lst: [],
}
@@ -337,21 +350,30 @@
@_add_method(ttLib.getTableClass('cmap'))
def merge(self, m, tables):
# TODO Handle format=14.
- cmapTables = [t for table in tables for t in table.tables
- if t.platformID == 3 and t.platEncID in [1, 10]]
+ cmapTables = [(t,fontIdx) for fontIdx,table in enumerate(tables) for t in table.tables
+ if t.isUnicode()]
# TODO Better handle format-4 and format-12 coexisting in same font.
# TODO Insert both a format-4 and format-12 if needed.
module = ttLib.getTableModule('cmap')
- assert all(t.format in [4, 12] for t in cmapTables)
- format = max(t.format for t in cmapTables)
+ assert all(t.format in [4, 12] for t,_ in cmapTables)
+ format = max(t.format for t,_ in cmapTables)
cmapTable = module.cmap_classes[format](format)
cmapTable.cmap = {}
cmapTable.platformID = 3
- cmapTable.platEncID = max(t.platEncID for t in cmapTables)
+ cmapTable.platEncID = max(t.platEncID for t,_ in cmapTables)
cmapTable.language = 0
- for table in cmapTables:
+ cmap = cmapTable.cmap
+ for table,fontIdx in cmapTables:
# TODO handle duplicates.
- cmapTable.cmap.update(table.cmap)
+ for uni,gid in table.cmap.items():
+ oldgid = cmap.get(uni, None)
+ if oldgid is None:
+ cmap[uni] = gid
+ elif oldgid != gid:
+ # Char previously mapped to oldgid, now to gid.
+ # Record, to fix up in GSUB 'locl' later.
+ assert m.duplicateGlyphsPerFont[fontIdx].get(oldgid, gid) == gid
+ m.duplicateGlyphsPerFont[fontIdx][oldgid] = gid
self.tableVersion = 0
self.tables = [cmapTable]
self.numSubTables = len(self.tables)
@@ -417,10 +439,155 @@
ttLib.getTableClass('JSTF').mergeMap = \
ttLib.getTableClass('MATH').mergeMap = \
{
- 'tableTag': equal,
+ 'tableTag': onlyExisting(equal), # XXX clean me up
'table': mergeObjects,
}
+@_add_method(ttLib.getTableClass('GSUB'))
+def merge(self, m, tables):
+
+ assert len(tables) == len(m.duplicateGlyphsPerFont)
+ for i,(table,dups) in enumerate(zip(tables, m.duplicateGlyphsPerFont)):
+ if not dups: continue
+ assert (table is not None and table is not NotImplemented), "Have duplicates to resolve for font %d but no GSUB" % (i + 1)
+ lookupMap = dict((id(v),v) for v in table.table.LookupList.Lookup)
+ featureMap = dict((id(v),v) for v in table.table.FeatureList.FeatureRecord)
+ synthFeature = None
+ synthLookup = None
+ for script in table.table.ScriptList.ScriptRecord:
+ if script.ScriptTag == 'DFLT': continue # XXX
+ for langsys in [script.Script.DefaultLangSys] + [l.LangSys for l in script.Script.LangSysRecord]:
+ feature = [featureMap[v] for v in langsys.FeatureIndex if featureMap[v].FeatureTag == 'locl']
+ assert len(feature) <= 1
+ if feature:
+ feature = feature[0]
+ else:
+ if not synthFeature:
+ synthFeature = otTables.FeatureRecord()
+ synthFeature.FeatureTag = 'locl'
+ f = synthFeature.Feature = otTables.Feature()
+ f.FeatureParams = None
+ f.LookupCount = 0
+ f.LookupListIndex = []
+ langsys.FeatureIndex.append(id(synthFeature))
+ featureMap[id(synthFeature)] = synthFeature
+ langsys.FeatureIndex.sort(key=lambda v: featureMap[v].FeatureTag)
+ table.table.FeatureList.FeatureRecord.append(synthFeature)
+ table.table.FeatureList.FeatureCount += 1
+ feature = synthFeature
+
+ if not synthLookup:
+ subtable = otTables.SingleSubst()
+ subtable.mapping = dups
+ synthLookup = otTables.Lookup()
+ synthLookup.LookupFlag = 0
+ synthLookup.LookupType = 1
+ synthLookup.SubTableCount = 1
+ synthLookup.SubTable = [subtable]
+ table.table.LookupList.Lookup.append(synthLookup)
+ table.table.LookupList.LookupCount += 1
+
+ feature.Feature.LookupListIndex[:0] = [id(synthLookup)]
+ feature.Feature.LookupCount += 1
+
+
+ DefaultTable.merge(self, m, tables)
+ return self
+
+
+
+@_add_method(otTables.SingleSubst,
+ otTables.MultipleSubst,
+ otTables.AlternateSubst,
+ otTables.LigatureSubst,
+ otTables.ReverseChainSingleSubst,
+ otTables.SinglePos,
+ otTables.PairPos,
+ otTables.CursivePos,
+ otTables.MarkBasePos,
+ otTables.MarkLigPos,
+ otTables.MarkMarkPos)
+def mapLookups(self, lookupMap):
+ pass
+
+# Copied and trimmed down from subset.py
+@_add_method(otTables.ContextSubst,
+ otTables.ChainContextSubst,
+ otTables.ContextPos,
+ otTables.ChainContextPos)
+def __classify_context(self):
+
+ class ContextHelper(object):
+ def __init__(self, klass, Format):
+ if klass.__name__.endswith('Subst'):
+ Typ = 'Sub'
+ Type = 'Subst'
+ else:
+ Typ = 'Pos'
+ Type = 'Pos'
+ if klass.__name__.startswith('Chain'):
+ Chain = 'Chain'
+ else:
+ Chain = ''
+ ChainTyp = Chain+Typ
+
+ self.Typ = Typ
+ self.Type = Type
+ self.Chain = Chain
+ self.ChainTyp = ChainTyp
+
+ self.LookupRecord = Type+'LookupRecord'
+
+ if Format == 1:
+ self.Rule = ChainTyp+'Rule'
+ self.RuleSet = ChainTyp+'RuleSet'
+ elif Format == 2:
+ self.Rule = ChainTyp+'ClassRule'
+ self.RuleSet = ChainTyp+'ClassSet'
+
+ if self.Format not in [1, 2, 3]:
+ return None # Don't shoot the messenger; let it go
+ if not hasattr(self.__class__, "__ContextHelpers"):
+ self.__class__.__ContextHelpers = {}
+ if self.Format not in self.__class__.__ContextHelpers:
+ helper = ContextHelper(self.__class__, self.Format)
+ self.__class__.__ContextHelpers[self.Format] = helper
+ return self.__class__.__ContextHelpers[self.Format]
+
+
+@_add_method(otTables.ContextSubst,
+ otTables.ChainContextSubst,
+ otTables.ContextPos,
+ otTables.ChainContextPos)
+def mapLookups(self, lookupMap):
+ c = self.__classify_context()
+
+ if self.Format in [1, 2]:
+ for rs in getattr(self, c.RuleSet):
+ if not rs: continue
+ for r in getattr(rs, c.Rule):
+ if not r: continue
+ for ll in getattr(r, c.LookupRecord):
+ if not ll: continue
+ ll.LookupListIndex = lookupMap[ll.LookupListIndex]
+ elif self.Format == 3:
+ for ll in getattr(self, c.LookupRecord):
+ if not ll: continue
+ ll.LookupListIndex = lookupMap[ll.LookupListIndex]
+ else:
+ assert 0, "unknown format: %s" % self.Format
+
+@_add_method(otTables.Lookup)
+def mapLookups(self, lookupMap):
+ for st in self.SubTable:
+ if not st: continue
+ st.mapLookups(lookupMap)
+
+@_add_method(otTables.LookupList)
+def mapLookups(self, lookupMap):
+ for l in self.Lookup:
+ if not l: continue
+ l.mapLookups(lookupMap)
@_add_method(otTables.Feature)
def mapLookups(self, lookupMap):
@@ -563,14 +730,21 @@
for font in fonts:
self._preMerge(font)
+ self.duplicateGlyphsPerFont = [{} for f in fonts]
+
allTags = reduce(set.union, (list(font.keys()) for font in fonts), set())
allTags.remove('GlyphOrder')
+ allTags.remove('cmap')
+ allTags.remove('GSUB')
+ allTags = ['cmap', 'GSUB'] + list(allTags)
for tag in allTags:
- clazz = ttLib.getTableClass(tag)
-
tables = [font.get(tag, NotImplemented) for font in fonts]
+
+ clazz = ttLib.getTableClass(tag)
table = clazz(tag).merge(self, tables)
+ # XXX Clean this up and use: table = mergeObjects(tables)
+
if table is not NotImplemented and table is not False:
mega[tag] = table
self.log("Merged '%s'." % tag)
@@ -578,6 +752,8 @@
self.log("Dropped '%s'." % tag)
self.log.lapse("merge '%s'" % tag)
+ del self.duplicateGlyphsPerFont
+
self._postMerge(mega)
return mega
@@ -600,9 +776,6 @@
# Right now we don't use self at all. Will use in the future
# for options and logging.
- if logic is NotImplemented:
- return NotImplemented
-
allKeys = set.union(set(), *(vars(table).keys() for table in tables if table is not NotImplemented))
for key in allKeys:
try:
@@ -623,6 +796,8 @@
def _preMerge(self, font):
+ # Map indices to references
+
GDEF = font.get('GDEF')
GSUB = font.get('GSUB')
GPOS = font.get('GPOS')
@@ -630,12 +805,15 @@
for t in [GSUB, GPOS]:
if not t: continue
- if t.table.LookupList and t.table.FeatureList:
- lookupMap = {i:id(v) for i,v in enumerate(t.table.LookupList.Lookup)}
- t.table.FeatureList.mapLookups(lookupMap)
+ if t.table.LookupList:
+ lookupMap = dict((i,id(v)) for i,v in enumerate(t.table.LookupList.Lookup))
+ t.table.LookupList.mapLookups(lookupMap)
+ if t.table.FeatureList:
+ # XXX Handle present FeatureList but absent LookupList
+ t.table.FeatureList.mapLookups(lookupMap)
if t.table.FeatureList and t.table.ScriptList:
- featureMap = {i:id(v) for i,v in enumerate(t.table.FeatureList.FeatureRecord)}
+ featureMap = dict((i,id(v)) for i,v in enumerate(t.table.FeatureList.FeatureRecord))
t.table.ScriptList.mapFeatures(featureMap)
# TODO GDEF/Lookup MarkFilteringSets
@@ -643,6 +821,8 @@
def _postMerge(self, font):
+ # Map references back to indices
+
GDEF = font.get('GDEF')
GSUB = font.get('GSUB')
GPOS = font.get('GPOS')
@@ -650,12 +830,16 @@
for t in [GSUB, GPOS]:
if not t: continue
- if t.table.LookupList and t.table.FeatureList:
- lookupMap = {id(v):i for i,v in enumerate(t.table.LookupList.Lookup)}
- t.table.FeatureList.mapLookups(lookupMap)
+ if t.table.LookupList:
+ lookupMap = dict((id(v),i) for i,v in enumerate(t.table.LookupList.Lookup))
+ t.table.LookupList.mapLookups(lookupMap)
+ if t.table.FeatureList:
+ # XXX Handle present FeatureList but absent LookupList
+ t.table.FeatureList.mapLookups(lookupMap)
if t.table.FeatureList and t.table.ScriptList:
- featureMap = {id(v):i for i,v in enumerate(t.table.FeatureList.FeatureRecord)}
+ # XXX Handle present ScriptList but absent FeatureList
+ featureMap = dict((id(v),i) for i,v in enumerate(t.table.FeatureList.FeatureRecord))
t.table.ScriptList.mapFeatures(featureMap)
# TODO GDEF/Lookup MarkFilteringSets
diff --git a/Lib/fontTools/misc/xmlReader.py b/Lib/fontTools/misc/xmlReader.py
index 581039d..85dd441 100644
--- a/Lib/fontTools/misc/xmlReader.py
+++ b/Lib/fontTools/misc/xmlReader.py
@@ -79,7 +79,7 @@
print(msg)
if tag == "GlyphOrder":
tableClass = ttLib.GlyphOrder
- elif "ERROR" in attrs:
+ elif "ERROR" in attrs or ('raw' in attrs and safeEval(attrs['raw'])):
tableClass = DefaultTable
else:
tableClass = ttLib.getTableClass(tag)
diff --git a/Lib/fontTools/subset.py b/Lib/fontTools/subset.py
index f50d206..45bd457 100644
--- a/Lib/fontTools/subset.py
+++ b/Lib/fontTools/subset.py
@@ -218,7 +218,10 @@
p.PairValueRecord = [r for r in p.PairValueRecord
if r.SecondGlyph in s.glyphs]
p.PairValueCount = len(p.PairValueRecord)
- self.PairSet = [p for p in self.PairSet if p.PairValueCount]
+ # Remove empty pairsets
+ indices = [i for i,p in enumerate(self.PairSet) if p.PairValueCount]
+ self.Coverage.remap(indices)
+ self.PairSet = [self.PairSet[i] for i in indices]
self.PairSetCount = len(self.PairSet)
return bool(self.PairSetCount)
elif self.Format == 2:
@@ -1696,27 +1699,30 @@
@_add_method(ttLib.getTableClass('cmap'))
def closure_glyphs(self, s):
- tables = [t for t in self.tables
- if t.platformID == 3 and t.platEncID in [1, 10]]
+ tables = [t for t in self.tables if t.isUnicode()]
for u in s.unicodes_requested:
found = False
for table in tables:
- if u in table.cmap:
- s.glyphs.add(table.cmap[u])
- found = True
- break
+ if table.format == 14:
+ for l in table.uvsDict.values():
+ # TODO(behdad) Speed this up!
+ gids = [g for uc,g in l if u == uc and g is not None]
+ s.glyphs.update(gids)
+ # Intentionally not setting found=True here.
+ else:
+ if u in table.cmap:
+ s.glyphs.add(table.cmap[u])
+ found = True
if not found:
- s.log("No glyph for Unicode value %s; skipping." % u)
+ s.log("No default glyph for Unicode %04X found." % u)
@_add_method(ttLib.getTableClass('cmap'))
def prune_pre_subset(self, options):
if not options.legacy_cmap:
# Drop non-Unicode / non-Symbol cmaps
- self.tables = [t for t in self.tables
- if t.platformID == 3 and t.platEncID in [0, 1, 10]]
+ self.tables = [t for t in self.tables if t.isUnicode() or t.isSymbol()]
if not options.symbol_cmap:
- self.tables = [t for t in self.tables
- if t.platformID == 3 and t.platEncID in [1, 10]]
+ self.tables = [t for t in self.tables if not t.isSymbol()]
# TODO(behdad) Only keep one subtable?
# For now, drop format=0 which can't be subset_glyphs easily?
self.tables = [t for t in self.tables if t.format != 0]
@@ -1734,13 +1740,18 @@
except AttributeError:
pass
if t.format == 14:
- # TODO(behdad) XXX We drop all the default-UVS mappings(g==None).
- t.uvsDict = dict((v,[(u,g) for u,g in l if g in s.glyphs])
+ # TODO(behdad) We drop all the default-UVS mappings for glyphs_requested.
+ # I don't think we care about that...
+ t.uvsDict = dict((v,[(u,g) for u,g in l
+ if g in s.glyphs or u in s.unicodes_requested])
for v,l in t.uvsDict.items())
t.uvsDict = dict((v,l) for v,l in t.uvsDict.items() if l)
- else:
+ elif t.isUnicode():
t.cmap = dict((u,g) for u,g in t.cmap.items()
if g in s.glyphs_requested or u in s.unicodes_requested)
+ else:
+ t.cmap = dict((u,g) for u,g in t.cmap.items()
+ if g in s.glyphs_requested)
self.tables = [t for t in self.tables
if (t.cmap if t.format != 14 else t.uvsDict)]
self.numSubTables = len(self.tables)
@@ -1755,9 +1766,10 @@
if '*' not in options.name_IDs:
self.names = [n for n in self.names if n.nameID in options.name_IDs]
if not options.name_legacy:
- self.names = [n for n in self.names
- if n.platformID == 3 and n.platEncID == 1]
+ self.names = [n for n in self.names if n.isUnicode()]
+ # TODO(behdad) Option to keep only one platform's
if '*' not in options.name_languages:
+ # TODO(behdad) This is Windows-platform specific!
self.names = [n for n in self.names if n.langID in options.name_languages]
return True # Required table
@@ -1823,6 +1835,7 @@
notdef_outline = False # No need for notdef to have an outline really
recommended_glyphs = False # gid1, gid2, gid3 for TrueType
recalc_bounds = False # Recalculate font bounding boxes
+ recalc_timestamp = False # Recalculate font modified timestamp
canonical_order = False # Order tables as recommended
flavor = None # May be 'woff'
@@ -2112,6 +2125,7 @@
allowVID=allowVID,
checkChecksums=checkChecksums,
recalcBBoxes=options.recalc_bounds,
+ recalcTimestamp=options.recalc_timestamp,
lazy=lazy)
# Hack:
diff --git a/Lib/fontTools/ttLib/__init__.py b/Lib/fontTools/ttLib/__init__.py
index 3866451..9879a4e 100644
--- a/Lib/fontTools/ttLib/__init__.py
+++ b/Lib/fontTools/ttLib/__init__.py
@@ -72,7 +72,7 @@
def __init__(self, file=None, res_name_or_index=None,
sfntVersion="\000\001\000\000", flavor=None, checkChecksums=False,
verbose=False, recalcBBoxes=True, allowVID=False, ignoreDecompileErrors=False,
- fontNumber=-1, lazy=False, quiet=False):
+ recalcTimestamp=True, fontNumber=-1, lazy=False, quiet=False):
"""The constructor can be called with a few different arguments.
When reading a font from disk, 'file' should be either a pathname
@@ -106,6 +106,9 @@
greatly, and therefore should have some impact on the time needed
to parse/compile large fonts.
+ If the recalcTimestamp argument is false, the modified timestamp in the
+ 'head' table will *not* be recalculated upon save/compile.
+
If the allowVID argument is set to true, then virtual GID's are
supported. Asking for a glyph ID with a glyph name or GID that is not in
the font will return a virtual GID. This is valid for GSUB and cmap
@@ -130,6 +133,7 @@
self.quiet = quiet
self.lazy = lazy
self.recalcBBoxes = recalcBBoxes
+ self.recalcTimestamp = recalcTimestamp
self.tables = {}
self.reader = None
@@ -761,7 +765,7 @@
table, but it's nice to present it as such in the TTX format.
"""
- def __init__(self, tag):
+ def __init__(self, tag=None):
pass
def toXML(self, writer, ttFont):
@@ -816,6 +820,15 @@
return tableClass
+def getClassTag(klass):
+ """Fetch the table tag for a class object."""
+ name = klass.__name__
+ assert name[:6] == 'table_'
+ name = name[6:] # Chop 'table_'
+ return identifierToTag(name)
+
+
+
def newTable(tag):
"""Return a new instance of a table."""
tableClass = getTableClass(tag)
@@ -876,7 +889,7 @@
tag = tag + ident[i]
else:
# assume hex
- tag = tag + bytechr(int(ident[i:i+2], 16))
+ tag = tag + chr(int(ident[i:i+2], 16))
# append trailing spaces
tag = tag + (4 - len(tag)) * ' '
return Tag(tag)
diff --git a/Lib/fontTools/ttLib/sfnt.py b/Lib/fontTools/ttLib/sfnt.py
index 0fd12c1..c6bc93a 100644
--- a/Lib/fontTools/ttLib/sfnt.py
+++ b/Lib/fontTools/ttLib/sfnt.py
@@ -55,14 +55,7 @@
for i in range(self.numTables):
entry = self.DirectoryEntry()
entry.fromFile(self.file)
- if entry.length > 0:
- self.tables[Tag(entry.tag)] = entry
- else:
- # Ignore zero-length tables. This doesn't seem to be documented,
- # yet it's apparently how the Windows TT rasterizer behaves.
- # Besides, at least one font has been sighted which actually
- # *has* a zero-length table.
- pass
+ self.tables[Tag(entry.tag)] = entry
# Load flavor data if any
if self.flavor == "woff":
diff --git a/Lib/fontTools/ttLib/tables/C_O_L_R_.py b/Lib/fontTools/ttLib/tables/C_O_L_R_.py
index d0c8b21..139de3c 100644
--- a/Lib/fontTools/ttLib/tables/C_O_L_R_.py
+++ b/Lib/fontTools/ttLib/tables/C_O_L_R_.py
@@ -138,6 +138,9 @@
elif glyphSelector in self.ColorLayers:
del self.ColorLayers[glyphSelector]
+ def __delitem__(self, glyphSelector):
+ del self.ColorLayers[glyphSelector]
+
class LayerRecord(object):
def __init__(self, name = None, colorID = None):
diff --git a/Lib/fontTools/ttLib/tables/DefaultTable.py b/Lib/fontTools/ttLib/tables/DefaultTable.py
index e2e7685..3a6886c 100644
--- a/Lib/fontTools/ttLib/tables/DefaultTable.py
+++ b/Lib/fontTools/ttLib/tables/DefaultTable.py
@@ -1,11 +1,14 @@
from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
+from fontTools.ttLib import getClassTag
class DefaultTable(object):
dependencies = []
- def __init__(self, tag):
+ def __init__(self, tag=None):
+ if tag is None:
+ tag = getClassTag(self.__class__)
self.tableTag = Tag(tag)
def decompile(self, data, ttFont):
diff --git a/Lib/fontTools/ttLib/tables/V_O_R_G_.py b/Lib/fontTools/ttLib/tables/V_O_R_G_.py
index ef80795..19f25b5 100644
--- a/Lib/fontTools/ttLib/tables/V_O_R_G_.py
+++ b/Lib/fontTools/ttLib/tables/V_O_R_G_.py
@@ -115,6 +115,9 @@
elif glyphSelector in self.VOriginRecords:
del self.VOriginRecords[glyphSelector]
+ def __delitem__(self, glyphSelector):
+ del self.VOriginRecords[glyphSelector]
+
class VOriginRecord(object):
def __init__(self, name = None, vOrigin = None):
diff --git a/Lib/fontTools/ttLib/tables/_c_m_a_p.py b/Lib/fontTools/ttLib/tables/_c_m_a_p.py
index 3ffe026..fbfd2ee 100644
--- a/Lib/fontTools/ttLib/tables/_c_m_a_p.py
+++ b/Lib/fontTools/ttLib/tables/_c_m_a_p.py
@@ -1,6 +1,7 @@
from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
from fontTools.misc.textTools import safeEval, readHex
+from fontTools.unicode import Unicode
from . import DefaultTable
import sys
import struct
@@ -137,12 +138,15 @@
writer.endtag(self.__class__.__name__)
writer.newline()
+ def isUnicode(self):
+ return (self.platformID == 0 or
+ (self.platformID == 3 and self.platEncID in [1, 10]))
+
+ def isSymbol(self):
+ return self.platformID == 3 and self.platEncID == 0
+
def _writeCodes(self, codes, writer):
- if (self.platformID, self.platEncID) == (3, 1) or (self.platformID, self.platEncID) == (3, 10) or self.platformID == 0:
- from fontTools.unicode import Unicode
- isUnicode = 1
- else:
- isUnicode = 0
+ isUnicode = self.isUnicode()
for code, name in codes:
writer.simpletag("map", code=hex(code), name=name)
if isUnicode:
@@ -661,21 +665,25 @@
charCodes = []
gids = []
for i in range(len(startCode) - 1): # don't do 0xffff!
+ start = startCode[i]
+ delta = idDelta[i]
+ rangeOffset = idRangeOffset[i]
+ # *someone* needs to get killed.
+ partial = rangeOffset // 2 - start + i - len(idRangeOffset)
+
rangeCharCodes = list(range(startCode[i], endCode[i] + 1))
- charCodes = charCodes + rangeCharCodes
- for charCode in rangeCharCodes:
- rangeOffset = idRangeOffset[i]
- if rangeOffset == 0:
- glyphID = charCode + idDelta[i]
- else:
- # *someone* needs to get killed.
- index = idRangeOffset[i] // 2 + (charCode - startCode[i]) + i - len(idRangeOffset)
+ charCodes.extend(rangeCharCodes)
+ if rangeOffset == 0:
+ gids.extend([(charCode + delta) & 0xFFFF for charCode in rangeCharCodes])
+ else:
+ for charCode in rangeCharCodes:
+ index = charCode + partial
assert (index < lenGIArray), "In format 4 cmap, range (%d), the calculated index (%d) into the glyph index array is not less than the length of the array (%d) !" % (i, index, lenGIArray)
if glyphIndexArray[index] != 0: # if not missing glyph
- glyphID = glyphIndexArray[index] + idDelta[i]
+ glyphID = glyphIndexArray[index] + delta
else:
glyphID = 0 # missing glyph
- gids.append(glyphID % 0x10000)
+ gids.append(glyphID & 0xFFFF)
self.cmap = cmap = {}
lenCmap = len(gids)
@@ -931,8 +939,8 @@
startCharCode, endCharCode, glyphID = struct.unpack(">LLL",data[pos:pos+12] )
pos += 12
lenGroup = 1 + endCharCode - startCharCode
- charCodes += list(range(startCharCode, endCharCode +1))
- gids += self._computeGIDs(glyphID, lenGroup)
+ charCodes.extend(list(range(startCharCode, endCharCode +1)))
+ gids.extend(self._computeGIDs(glyphID, lenGroup))
self.data = data = None
self.cmap = cmap = {}
lenCmap = len(gids)
diff --git a/Lib/fontTools/ttLib/tables/_g_l_y_f.py b/Lib/fontTools/ttLib/tables/_g_l_y_f.py
index 3434d35..970980b 100644
--- a/Lib/fontTools/ttLib/tables/_g_l_y_f.py
+++ b/Lib/fontTools/ttLib/tables/_g_l_y_f.py
@@ -991,6 +991,8 @@
def __setitem__(self, k, v):
if isinstance(k, slice):
indices = range(*k.indices(len(self)))
+ # XXX This only works if len(v) == len(indices)
+ # TODO Implement __delitem__
for j,i in enumerate(indices):
self[i] = v[j]
return
diff --git a/Lib/fontTools/ttLib/tables/_h_e_a_d.py b/Lib/fontTools/ttLib/tables/_h_e_a_d.py
index 97dd1a7..bf4116d 100644
--- a/Lib/fontTools/ttLib/tables/_h_e_a_d.py
+++ b/Lib/fontTools/ttLib/tables/_h_e_a_d.py
@@ -39,7 +39,8 @@
assert rest == "\0\0"
def compile(self, ttFont):
- self.modified = int(time.time() - mac_epoch_diff)
+ if ttFont.recalcTimestamp:
+ self.modified = int(time.time() - mac_epoch_diff)
data = sstruct.pack(headFormat, self)
return data
diff --git a/Lib/fontTools/ttLib/tables/_h_m_t_x.py b/Lib/fontTools/ttLib/tables/_h_m_t_x.py
index acb686b..c7b5ee9 100644
--- a/Lib/fontTools/ttLib/tables/_h_m_t_x.py
+++ b/Lib/fontTools/ttLib/tables/_h_m_t_x.py
@@ -33,12 +33,13 @@
if data:
warnings.warn("too much 'hmtx'/'vmtx' table data")
self.metrics = {}
+ glyphOrder = ttFont.getGlyphOrder()
for i in range(numberOfMetrics):
- glyphName = ttFont.getGlyphName(i)
+ glyphName = glyphOrder[i]
self.metrics[glyphName] = list(metrics[i*2:i*2+2])
lastAdvance = metrics[-2]
for i in range(numberOfSideBearings):
- glyphName = ttFont.getGlyphName(i + numberOfMetrics)
+ glyphName = glyphOrder[i + numberOfMetrics]
self.metrics[glyphName] = [lastAdvance, sideBearings[i]]
def compile(self, ttFont):
@@ -89,6 +90,9 @@
if name == "mtx":
self.metrics[attrs["name"]] = [safeEval(attrs[self.advanceName]),
safeEval(attrs[self.sideBearingName])]
+
+ def __delitem__(self, glyphName):
+ del self.metrics[glyphName]
def __getitem__(self, glyphName):
return self.metrics[glyphName]
diff --git a/Lib/fontTools/ttLib/tables/_n_a_m_e.py b/Lib/fontTools/ttLib/tables/_n_a_m_e.py
index d78b838..53fde4d 100644
--- a/Lib/fontTools/ttLib/tables/_n_a_m_e.py
+++ b/Lib/fontTools/ttLib/tables/_n_a_m_e.py
@@ -90,6 +90,10 @@
class NameRecord(object):
+ def isUnicode(self):
+ return (self.platformID == 0 or
+ (self.platformID == 3 and self.platEncID in [0, 1, 10]))
+
def toXML(self, writer, ttFont):
writer.begintag("namerecord", [
("nameID", self.nameID),
@@ -98,7 +102,7 @@
("langID", hex(self.langID)),
])
writer.newline()
- if self.platformID == 0 or (self.platformID == 3 and self.platEncID in (0, 1)):
+ if self.isUnicode():
if len(self.string) % 2:
# no, shouldn't happen, but some of the Apple
# tools cause this anyway :-(
@@ -117,7 +121,7 @@
self.platEncID = safeEval(attrs["platEncID"])
self.langID = safeEval(attrs["langID"])
s = strjoin(content).strip()
- if self.platformID == 0 or (self.platformID == 3 and self.platEncID in (0, 1)):
+ if self.isUnicode():
self.string = s.encode("utf_16_be")
else:
# This is the inverse of write8bit...
diff --git a/Lib/fontTools/ttLib/tables/otTables.py b/Lib/fontTools/ttLib/tables/otTables.py
index 83b8031..2afc2cc 100644
--- a/Lib/fontTools/ttLib/tables/otTables.py
+++ b/Lib/fontTools/ttLib/tables/otTables.py
@@ -548,7 +548,7 @@
lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup
lookup = lookups[lookupIndex]
# If the previous lookup is an extType, look further back. Very unlikely, but possible.
- while lookup.LookupType == extType:
+ while lookup.SubTable[0].__class__.LookupType == extType:
lookupIndex = lookupIndex -1
if lookupIndex < 0:
return ok
@@ -559,10 +559,8 @@
extSubTableClass = lookupTypes[overflowRecord.tableType][extType]
extSubTable = extSubTableClass()
extSubTable.Format = 1
- extSubTable.ExtensionLookupType = lookup.LookupType
extSubTable.ExtSubTable = subTable
lookup.SubTable[si] = extSubTable
- lookup.LookupType = extType
ok = 1
return ok
@@ -662,20 +660,19 @@
# We split the subtable of the Extension table, and add a new Extension table
# to contain the new subtable.
- subTableType = subtable.ExtensionLookupType
+ subTableType = subtable.ExtSubTable.__class__.LookupType
extSubTable = subtable
subtable = extSubTable.ExtSubTable
- newExtSubTableClass = lookupTypes[overflowRecord.tableType][lookup.LookupType]
+ newExtSubTableClass = lookupTypes[overflowRecord.tableType][subtable.__class__.LookupType]
newExtSubTable = newExtSubTableClass()
newExtSubTable.Format = extSubTable.Format
- newExtSubTable.ExtensionLookupType = extSubTable.ExtensionLookupType
lookup.SubTable.insert(subIndex + 1, newExtSubTable)
newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
newSubTable = newSubTableClass()
newExtSubTable.ExtSubTable = newSubTable
else:
- subTableType = lookup.LookupType
+ subTableType = subtable.__class__.LookupType
newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
newSubTable = newSubTableClass()
lookup.SubTable.insert(subIndex + 1, newSubTable)
diff --git a/Lib/fontTools/unicode.py b/Lib/fontTools/unicode.py
index c72fb9e..b599051 100644
--- a/Lib/fontTools/unicode.py
+++ b/Lib/fontTools/unicode.py
@@ -1,3 +1,5 @@
+from __future__ import print_function, division, absolute_import
+from fontTools.misc.py23 import *
def _makeunicodes(f):
import re