[otBase/packer] Allow sharing tables reached by different offset sizes

This makes us match hb-subset now.

Fixes https://github.com/fonttools/fonttools/issues/3236
This commit is contained in:
Behdad Esfahbod 2023-08-03 11:10:16 -06:00
parent 91731f7d2a
commit 2b629e51c2

View File

@ -376,6 +376,20 @@ class OTTableReader(object):
return self.localState and name in self.localState return self.localState and name in self.localState
class OffsetToWriter(object):
def __init__(self, subWriter, offsetSize):
self.subWriter = subWriter
self.offsetSize = offsetSize
def __eq__(self, other):
if type(self) != type(other):
return NotImplemented
return self.subWriter == other.subWriter and self.offsetSize == other.offsetSize
def __hash__(self):
# only works after self._doneWriting() has been called
return hash((self.subWriter, self.offsetSize))
class OTTableWriter(object): class OTTableWriter(object):
"""Helper class to gather and assemble data for OpenType tables.""" """Helper class to gather and assemble data for OpenType tables."""
@ -388,16 +402,6 @@ class OTTableWriter(object):
self.offsetSize = offsetSize self.offsetSize = offsetSize
self.parent = None self.parent = None
# DEPRECATED: 'longOffset' is kept as a property for backward compat with old code.
# You should use 'offsetSize' instead (2, 3 or 4 bytes).
@property
def longOffset(self):
return self.offsetSize == 4
@longOffset.setter
def longOffset(self, value):
self.offsetSize = 4 if value else 2
def __setitem__(self, name, value): def __setitem__(self, name, value):
state = self.localState.copy() if self.localState else dict() state = self.localState.copy() if self.localState else dict()
state[name] = value state[name] = value
@ -417,7 +421,7 @@ class OTTableWriter(object):
for item in self.items: for item in self.items:
if hasattr(item, "getCountData"): if hasattr(item, "getCountData"):
l += item.size l += item.size
elif hasattr(item, "getData"): elif hasattr(item, "subWriter"):
l += item.offsetSize l += item.offsetSize
else: else:
l = l + len(item) l = l + len(item)
@ -431,19 +435,19 @@ class OTTableWriter(object):
for i in range(numItems): for i in range(numItems):
item = items[i] item = items[i]
if hasattr(item, "getData"): if hasattr(item, "subWriter"):
if item.offsetSize == 4: if item.offsetSize == 4:
items[i] = packULong(item.pos - pos) items[i] = packULong(item.subWriter.pos - pos)
elif item.offsetSize == 2: elif item.offsetSize == 2:
try: try:
items[i] = packUShort(item.pos - pos) items[i] = packUShort(item.subWriter.pos - pos)
except struct.error: except struct.error:
# provide data to fix overflow problem. # provide data to fix overflow problem.
overflowErrorRecord = self.getOverflowErrorRecord(item) overflowErrorRecord = self.getOverflowErrorRecord(item)
raise OTLOffsetOverflowError(overflowErrorRecord) raise OTLOffsetOverflowError(overflowErrorRecord)
elif item.offsetSize == 3: elif item.offsetSize == 3:
items[i] = packUInt24(item.pos - pos) items[i] = packUInt24(item.subWriter.pos - pos)
else: else:
raise ValueError(item.offsetSize) raise ValueError(item.offsetSize)
@ -454,7 +458,7 @@ class OTTableWriter(object):
items = list(self.items) items = list(self.items)
packFuncs = {2: packUShort, 3: packUInt24, 4: packULong} packFuncs = {2: packUShort, 3: packUInt24, 4: packULong}
for i, item in enumerate(items): for i, item in enumerate(items):
if hasattr(item, "getData"): if hasattr(item, "subWriter"):
# Offset value is not needed in harfbuzz repacker, so setting offset to 0 to avoid overflow here # Offset value is not needed in harfbuzz repacker, so setting offset to 0 to avoid overflow here
if item.offsetSize in packFuncs: if item.offsetSize in packFuncs:
items[i] = packFuncs[item.offsetSize](0) items[i] = packFuncs[item.offsetSize](0)
@ -474,7 +478,7 @@ class OTTableWriter(object):
def __eq__(self, other): def __eq__(self, other):
if type(self) != type(other): if type(self) != type(other):
return NotImplemented return NotImplemented
return self.offsetSize == other.offsetSize and self.items == other.items return self.items == other.items
def _doneWriting(self, internedTables, shareExtension=False): def _doneWriting(self, internedTables, shareExtension=False):
# Convert CountData references to data string items # Convert CountData references to data string items
@ -500,8 +504,10 @@ class OTTableWriter(object):
item = items[i] item = items[i]
if hasattr(item, "getCountData"): if hasattr(item, "getCountData"):
items[i] = item.getCountData() items[i] = item.getCountData()
elif hasattr(item, "getData"): elif hasattr(item, "subWriter"):
item._doneWriting(internedTables, shareExtension=shareExtension) item.subWriter._doneWriting(
internedTables, shareExtension=shareExtension
)
# At this point, all subwriters are hashable based on their items. # At this point, all subwriters are hashable based on their items.
# (See hash and comparison magic methods above.) So the ``setdefault`` # (See hash and comparison magic methods above.) So the ``setdefault``
# call here will return the first writer object we've seen with # call here will return the first writer object we've seen with
@ -509,7 +515,7 @@ class OTTableWriter(object):
# seen yet. We therefore replace the subwriter object with an equivalent # seen yet. We therefore replace the subwriter object with an equivalent
# object, which deduplicates the tree. # object, which deduplicates the tree.
if not dontShare: if not dontShare:
items[i] = item = internedTables.setdefault(item, item) items[i].subWriter = internedTables.setdefault(item.subWriter, item.subWriter)
self.items = tuple(items) self.items = tuple(items)
def _gatherTables(self, tables, extTables, done): def _gatherTables(self, tables, extTables, done):
@ -543,30 +549,30 @@ class OTTableWriter(object):
# Find coverage table # Find coverage table
for i in range(numItems): for i in range(numItems):
item = self.items[i] item = self.items[i]
if getattr(item, "name", None) == "Coverage": if hasattr(item, "subWriter") and getattr(item.subWriter, "name", None) == "Coverage":
sortCoverageLast = True sortCoverageLast = True
break break
if id(item) not in done: if id(item.subWriter) not in done:
item._gatherTables(tables, extTables, done) item.subWriter._gatherTables(tables, extTables, done)
else: else:
# We're a new parent of item # We're a new parent of item
pass pass
for i in iRange: for i in iRange:
item = self.items[i] item = self.items[i]
if not hasattr(item, "getData"): if not hasattr(item, "subWriter"):
continue continue
if ( if (
sortCoverageLast sortCoverageLast
and (i == 1) and (i == 1)
and getattr(item, "name", None) == "Coverage" and getattr(item.subWriter, "name", None) == "Coverage"
): ):
# we've already 'gathered' it above # we've already 'gathered' it above
continue continue
if id(item) not in done: if id(item.subWriter) not in done:
item._gatherTables(tables, extTables, done) item.subWriter._gatherTables(tables, extTables, done)
else: else:
# Item is already written out by other parent # Item is already written out by other parent
pass pass
@ -601,7 +607,7 @@ class OTTableWriter(object):
child_idx = 0 child_idx = 0
offset_pos = 0 offset_pos = 0
for i, item in enumerate(self.items): for i, item in enumerate(self.items):
if hasattr(item, "getData"): if hasattr(item, "subWriter"):
pos = offset_pos pos = offset_pos
elif hasattr(item, "getCountData"): elif hasattr(item, "getCountData"):
offset_pos += item.size offset_pos += item.size
@ -610,12 +616,12 @@ class OTTableWriter(object):
offset_pos = offset_pos + len(item) offset_pos = offset_pos + len(item)
continue continue
if id(item) not in done: if id(item.subWriter) not in done:
child_idx = item_idx = item._gatherGraphForHarfbuzz( child_idx = item_idx = item.subWriter._gatherGraphForHarfbuzz(
tables, obj_list, done, item_idx, virtual_edges tables, obj_list, done, item_idx, virtual_edges
) )
else: else:
child_idx = done[id(item)] child_idx = done[id(item.subWriter)]
real_edge = (pos, item.offsetSize, child_idx) real_edge = (pos, item.offsetSize, child_idx)
real_links.append(real_edge) real_links.append(real_edge)
@ -774,7 +780,8 @@ class OTTableWriter(object):
self.items.append(tag) self.items.append(tag)
def writeSubTable(self, subWriter): def writeSubTable(self, subWriter):
self.items.append(subWriter) self.items.append(OffsetToWriter(subWriter, subWriter.offsetSize))
del subWriter.offsetSize
def writeCountReference(self, table, name, size=2, value=None): def writeCountReference(self, table, name, size=2, value=None):
ref = CountReference(table, name, size=size, value=value) ref = CountReference(table, name, size=size, value=value)
@ -1376,7 +1383,6 @@ class ValueRecordFactory(object):
class ValueRecord(object): class ValueRecord(object):
# see ValueRecordFactory # see ValueRecordFactory
def __init__(self, valueFormat=None, src=None): def __init__(self, valueFormat=None, src=None):