Merge pull request #3241 from fonttools/better-packer

[otBase/packer] Allow sharing tables reached by different offset sizes
This commit is contained in:
Cosimo Lupo 2023-08-04 16:06:15 +01:00 committed by GitHub
commit a8d5d45b39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 80 additions and 64 deletions

View File

@ -376,28 +376,32 @@ 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."""
def __init__(self, localState=None, tableTag=None, offsetSize=2): def __init__(self, localState=None, tableTag=None):
self.items = [] self.items = []
self.pos = None self.pos = None
self.localState = localState self.localState = localState
self.tableTag = tableTag self.tableTag = tableTag
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,9 @@ 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 +551,33 @@ 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 +612,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 +621,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)
@ -698,10 +709,8 @@ class OTTableWriter(object):
# interface for gathering data, as used by table.compile() # interface for gathering data, as used by table.compile()
def getSubWriter(self, offsetSize=2): def getSubWriter(self):
subwriter = self.__class__( subwriter = self.__class__(self.localState, self.tableTag)
self.localState, self.tableTag, offsetSize=offsetSize
)
subwriter.parent = ( subwriter.parent = (
self # because some subtables have idential values, we discard self # because some subtables have idential values, we discard
) )
@ -773,8 +782,8 @@ class OTTableWriter(object):
assert len(tag) == 4, tag assert len(tag) == 4, tag
self.items.append(tag) self.items.append(tag)
def writeSubTable(self, subWriter): def writeSubTable(self, subWriter, offsetSize):
self.items.append(subWriter) self.items.append(OffsetToWriter(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)
@ -1365,7 +1374,7 @@ class ValueRecordFactory(object):
if isDevice: if isDevice:
if value: if value:
subWriter = writer.getSubWriter() subWriter = writer.getSubWriter()
writer.writeSubTable(subWriter) writer.writeSubTable(subWriter, offsetSize=2)
value.compile(subWriter, font) value.compile(subWriter, font)
else: else:
writer.writeUShort(0) writer.writeUShort(0)
@ -1376,7 +1385,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):

View File

@ -720,7 +720,6 @@ class StructWithLength(Struct):
class Table(Struct): class Table(Struct):
staticSize = 2 staticSize = 2
def readOffset(self, reader): def readOffset(self, reader):
@ -746,16 +745,15 @@ class Table(Struct):
if value is None: if value is None:
self.writeNullOffset(writer) self.writeNullOffset(writer)
else: else:
subWriter = writer.getSubWriter(offsetSize=self.staticSize) subWriter = writer.getSubWriter()
subWriter.name = self.name subWriter.name = self.name
if repeatIndex is not None: if repeatIndex is not None:
subWriter.repeatIndex = repeatIndex subWriter.repeatIndex = repeatIndex
writer.writeSubTable(subWriter) writer.writeSubTable(subWriter, offsetSize=self.staticSize)
value.compile(subWriter, font) value.compile(subWriter, font)
class LTable(Table): class LTable(Table):
staticSize = 4 staticSize = 4
def readOffset(self, reader): def readOffset(self, reader):
@ -767,7 +765,6 @@ class LTable(Table):
# Table pointed to by a 24-bit, 3-byte long offset # Table pointed to by a 24-bit, 3-byte long offset
class Table24(Table): class Table24(Table):
staticSize = 3 staticSize = 3
def readOffset(self, reader): def readOffset(self, reader):
@ -1147,13 +1144,13 @@ class AATLookupWithDataOffset(BaseConverter):
offsetByGlyph[glyph] = offset offsetByGlyph[glyph] = offset
# For calculating the offsets to our AATLookup and data table, # For calculating the offsets to our AATLookup and data table,
# we can use the regular OTTableWriter infrastructure. # we can use the regular OTTableWriter infrastructure.
lookupWriter = writer.getSubWriter(offsetSize=4) lookupWriter = writer.getSubWriter()
lookup = AATLookup("DataOffsets", None, None, UShort) lookup = AATLookup("DataOffsets", None, None, UShort)
lookup.write(lookupWriter, font, tableDict, offsetByGlyph, None) lookup.write(lookupWriter, font, tableDict, offsetByGlyph, None)
dataWriter = writer.getSubWriter(offsetSize=4) dataWriter = writer.getSubWriter()
writer.writeSubTable(lookupWriter) writer.writeSubTable(lookupWriter, offsetSize=4)
writer.writeSubTable(dataWriter) writer.writeSubTable(dataWriter, offsetSize=4)
for d in compiledData: for d in compiledData:
dataWriter.writeData(d) dataWriter.writeData(d)
@ -1483,9 +1480,9 @@ class STXHeader(BaseConverter):
) )
writer = OTTableWriter() writer = OTTableWriter()
for lookup in table.PerGlyphLookups: for lookup in table.PerGlyphLookups:
lookupWriter = writer.getSubWriter(offsetSize=4) lookupWriter = writer.getSubWriter()
self.perGlyphLookup.write(lookupWriter, font, {}, lookup, None) self.perGlyphLookup.write(lookupWriter, font, {}, lookup, None)
writer.writeSubTable(lookupWriter) writer.writeSubTable(lookupWriter, offsetSize=4)
return writer.getAllData() return writer.getAllData()
def _compileLigComponents(self, table, font): def _compileLigComponents(self, table, font):

View File

@ -116,7 +116,7 @@ COLR_V1_SAMPLE = (
(b"\x00\x03", "LayerRecordCount (3)"), (b"\x00\x03", "LayerRecordCount (3)"),
(b"\x00\x00\x00\x34", "Offset to BaseGlyphList from beginning of table (52)"), (b"\x00\x00\x00\x34", "Offset to BaseGlyphList from beginning of table (52)"),
(b"\x00\x00\x00\x9f", "Offset to LayerList from beginning of table (159)"), (b"\x00\x00\x00\x9f", "Offset to LayerList from beginning of table (159)"),
(b"\x00\x00\x01\x62", "Offset to ClipList (354)"), (b"\x00\x00\x01\x66", "Offset to ClipList (358)"),
(b"\x00\x00\x00\x00", "Offset to DeltaSetIndexMap (NULL)"), (b"\x00\x00\x00\x00", "Offset to DeltaSetIndexMap (NULL)"),
(b"\x00\x00\x00\x00", "Offset to VarStore (NULL)"), (b"\x00\x00\x00\x00", "Offset to VarStore (NULL)"),
(b"\x00\x06", "BaseGlyphRecord[0].BaseGlyph (6)"), (b"\x00\x06", "BaseGlyphRecord[0].BaseGlyph (6)"),
@ -187,22 +187,26 @@ COLR_V1_SAMPLE = (
(b"\x00\x05", "ColorLine.ColorStop[1].PaletteIndex (5)"), (b"\x00\x05", "ColorLine.ColorStop[1].PaletteIndex (5)"),
(b"@\x00", "ColorLine.ColorStop[1].Alpha (1.0)"), (b"@\x00", "ColorLine.ColorStop[1].Alpha (1.0)"),
# LayerList # LayerList
(b"\x00\x00\x00\x04", "LayerList.LayerCount (4)"), (b"\x00\x00\x00\x05", "LayerList.LayerCount (5)"),
( (
b"\x00\x00\x00\x14", b"\x00\x00\x00\x18",
"First Offset to Paint table from beginning of LayerList (20)", "First Offset to Paint table from beginning of LayerList (24)",
), ),
( (
b"\x00\x00\x00\x23", b"\x00\x00\x00\x27",
"Second Offset to Paint table from beginning of LayerList (35)", "Second Offset to Paint table from beginning of LayerList (39)",
), ),
( (
b"\x00\x00\x00\x4e", b"\x00\x00\x00\x52",
"Third Offset to Paint table from beginning of LayerList (78)", "Third Offset to Paint table from beginning of LayerList (82)",
), ),
( (
b"\x00\x00\x00\x9e", b"\x00\x00\x00\xa2",
"Fourth Offset to Paint table from beginning of LayerList (158)", "Fourth Offset to Paint table from beginning of LayerList (162)",
),
(
b"\x00\x00\x00\xbc",
"Fifth Offset to Paint table from beginning of LayerList (188)",
), ),
# BaseGlyphPaintRecord[2] # BaseGlyphPaintRecord[2]
(b"\x0a", "BaseGlyphPaintRecord[2].Paint.Format (10)"), (b"\x0a", "BaseGlyphPaintRecord[2].Paint.Format (10)"),
@ -296,7 +300,7 @@ COLR_V1_SAMPLE = (
), ),
(b"\xfc\x17", "xSkewAngle (-0.0611)"), (b"\xfc\x17", "xSkewAngle (-0.0611)"),
(b"\x01\xc7", "ySkewAngle (0.0278)"), (b"\x01\xc7", "ySkewAngle (0.0278)"),
# PaintGlyph # PaintGlyph glyph00011 (pointed to by both PaintSkew above and by LayerList[4] offset)
(b"\x0a", "LayerList.Paint[3].Paint.Paint.Paint.Format (10)"), (b"\x0a", "LayerList.Paint[3].Paint.Paint.Paint.Format (10)"),
(b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"), (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"),
(b"\x00\x0b", "LayerList.Paint[2].Glyph (11)"), (b"\x00\x0b", "LayerList.Paint[2].Glyph (11)"),
@ -413,7 +417,7 @@ COLR_V1_XML = [
" </BaseGlyphPaintRecord>", " </BaseGlyphPaintRecord>",
"</BaseGlyphList>", "</BaseGlyphList>",
"<LayerList>", "<LayerList>",
" <!-- LayerCount=4 -->", " <!-- LayerCount=5 -->",
' <Paint index="0" Format="10"><!-- PaintGlyph -->', ' <Paint index="0" Format="10"><!-- PaintGlyph -->',
' <Paint Format="3"><!-- PaintVarSolid -->', ' <Paint Format="3"><!-- PaintVarSolid -->',
' <PaletteIndex value="2"/>', ' <PaletteIndex value="2"/>',
@ -510,6 +514,13 @@ COLR_V1_XML = [
' <dx value="257"/>', ' <dx value="257"/>',
' <dy value="258"/>', ' <dy value="258"/>',
" </Paint>", " </Paint>",
' <Paint index="4" Format="10"><!-- PaintGlyph -->',
' <Paint Format="2"><!-- PaintSolid -->',
' <PaletteIndex value="2"/>',
' <Alpha value="0.5"/>',
" </Paint>",
' <Glyph value="glyph00011"/>',
" </Paint>",
"</LayerList>", "</LayerList>",
'<ClipList Format="1">', '<ClipList Format="1">',
" <Clip>", " <Clip>",