[ttc] Implement table sharing in save()

This commit is contained in:
Behdad Esfahbod 2018-01-25 17:08:30 -08:00
parent fb77bd0b0c
commit 370368d8c2
3 changed files with 32 additions and 14 deletions

View File

@ -215,6 +215,12 @@ class SFNTWriter(object):
self.file.write(b'\0' * (self.nextTableOffset - self.file.tell())) self.file.write(b'\0' * (self.nextTableOffset - self.file.tell()))
self.tables = OrderedDict() self.tables = OrderedDict()
def setEntry(self, tag, entry):
if tag in self.tables:
raise TTLibError("cannot rewrite '%s' table" % tag)
self.tables[tag] = entry
def __setitem__(self, tag, data): def __setitem__(self, tag, data):
"""Write raw table data to disk.""" """Write raw table data to disk."""
if tag in self.tables: if tag in self.tables:
@ -243,7 +249,10 @@ class SFNTWriter(object):
self.file.write(b'\0' * (self.nextTableOffset - self.file.tell())) self.file.write(b'\0' * (self.nextTableOffset - self.file.tell()))
assert self.nextTableOffset == self.file.tell() assert self.nextTableOffset == self.file.tell()
self.tables[tag] = entry self.setEntry(tag, entry)
def __getitem__(self, tag):
return self.tables[tag]
def close(self): def close(self):
"""All tables must have been written to disk. Now write the """All tables must have been written to disk. Now write the

View File

@ -37,7 +37,7 @@ class TTCollection(object):
font = TTFont(file, fontNumber=i, _tableCache=tableCache, **kwargs) font = TTFont(file, fontNumber=i, _tableCache=tableCache, **kwargs)
fonts.append(font) fonts.append(font)
def save(self, file): def save(self, file, shareTables=True):
"""Save the font to disk. Similarly to the constructor, """Save the font to disk. Similarly to the constructor,
the 'file' argument can be either a pathname or a writable the 'file' argument can be either a pathname or a writable
file object. file object.
@ -51,12 +51,13 @@ class TTCollection(object):
final = file final = file
file = BytesIO() file = BytesIO()
offsets_offset = writeTTCHeader(file, len(self.fonts)) tableCache = {} if shareTables else None
offsets_offset = writeTTCHeader(file, len(self.fonts))
offsets = [] offsets = []
for font in self.fonts: for font in self.fonts:
offsets.append(file.tell()) offsets.append(file.tell())
font._save(file) font._save(file, tableCache=tableCache)
file.seek(0,2) file.seek(0,2)
file.seek(offsets_offset) file.seek(offsets_offset)

View File

@ -134,7 +134,7 @@ class TTFont(object):
if closeStream: if closeStream:
file.close() file.close()
file = tmp file = tmp
self.tableCache = _tableCache self._tableCache = _tableCache
self.reader = SFNTReader(file, checkChecksums, fontNumber=fontNumber) self.reader = SFNTReader(file, checkChecksums, fontNumber=fontNumber)
self.sfntVersion = self.reader.sfntVersion self.sfntVersion = self.reader.sfntVersion
self.flavor = self.reader.flavor self.flavor = self.reader.flavor
@ -186,7 +186,7 @@ class TTFont(object):
if closeStream: if closeStream:
file.close() file.close()
def _save(self, file): def _save(self, file, tableCache=None):
"""Internal function, to be shared by save() and TTCollection.save()""" """Internal function, to be shared by save() and TTCollection.save()"""
if self.recalcTimestamp and 'head' in self: if self.recalcTimestamp and 'head' in self:
@ -201,7 +201,7 @@ class TTFont(object):
done = [] done = []
for tag in tags: for tag in tags:
self._writeTable(tag, writer, done) self._writeTable(tag, writer, done, tableCache)
writer.close() writer.close()
@ -380,8 +380,8 @@ class TTFont(object):
import traceback import traceback
log.debug("Reading '%s' table from disk", tag) log.debug("Reading '%s' table from disk", tag)
data = self.reader[tag] data = self.reader[tag]
if self.tableCache is not None: if self._tableCache is not None:
table = self.tableCache.get((Tag(tag), data)) table = self._tableCache.get((Tag(tag), data))
if table is not None: if table is not None:
return table return table
tableClass = getTableClass(tag) tableClass = getTableClass(tag)
@ -403,8 +403,8 @@ class TTFont(object):
table.ERROR = file.getvalue() table.ERROR = file.getvalue()
self.tables[tag] = table self.tables[tag] = table
table.decompile(data, self) table.decompile(data, self)
if self.tableCache is not None: if self._tableCache is not None:
self.tableCache[(Tag(tag), data)] = table self._tableCache[(Tag(tag), data)] = table
return table return table
else: else:
raise KeyError("'%s' table not found" % tag) raise KeyError("'%s' table not found" % tag)
@ -612,7 +612,7 @@ class TTFont(object):
for glyphID in range(len(glyphOrder)): for glyphID in range(len(glyphOrder)):
d[glyphOrder[glyphID]] = glyphID d[glyphOrder[glyphID]] = glyphID
def _writeTable(self, tag, writer, done): def _writeTable(self, tag, writer, done, tableCache=None):
"""Internal helper function for self.save(). Keeps track of """Internal helper function for self.save(). Keeps track of
inter-table dependencies. inter-table dependencies.
""" """
@ -622,13 +622,21 @@ class TTFont(object):
for masterTable in tableClass.dependencies: for masterTable in tableClass.dependencies:
if masterTable not in done: if masterTable not in done:
if masterTable in self: if masterTable in self:
self._writeTable(masterTable, writer, done) self._writeTable(masterTable, writer, done, tableCache)
else: else:
done.append(masterTable) done.append(masterTable)
done.append(tag)
tabledata = self.getTableData(tag) tabledata = self.getTableData(tag)
if tableCache is not None:
entry = tableCache.get((Tag(tag), tabledata))
if entry is not None:
log.debug("reusing '%s' table", tag)
writer.setEntry(tag, entry)
return
log.debug("writing '%s' table to disk", tag) log.debug("writing '%s' table to disk", tag)
writer[tag] = tabledata writer[tag] = tabledata
done.append(tag) if tableCache is not None:
tableCache[(Tag(tag), tabledata)] = writer[tag]
def getTableData(self, tag): def getTableData(self, tag):
"""Returns raw table data, whether compiled or directly read from disk. """Returns raw table data, whether compiled or directly read from disk.