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
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):
"""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.pos = None
self.localState = localState
self.tableTag = tableTag
self.offsetSize = offsetSize
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):
state = self.localState.copy() if self.localState else dict()
state[name] = value
@ -417,7 +421,7 @@ class OTTableWriter(object):
for item in self.items:
if hasattr(item, "getCountData"):
l += item.size
elif hasattr(item, "getData"):
elif hasattr(item, "subWriter"):
l += item.offsetSize
else:
l = l + len(item)
@ -431,19 +435,19 @@ class OTTableWriter(object):
for i in range(numItems):
item = items[i]
if hasattr(item, "getData"):
if hasattr(item, "subWriter"):
if item.offsetSize == 4:
items[i] = packULong(item.pos - pos)
items[i] = packULong(item.subWriter.pos - pos)
elif item.offsetSize == 2:
try:
items[i] = packUShort(item.pos - pos)
items[i] = packUShort(item.subWriter.pos - pos)
except struct.error:
# provide data to fix overflow problem.
overflowErrorRecord = self.getOverflowErrorRecord(item)
raise OTLOffsetOverflowError(overflowErrorRecord)
elif item.offsetSize == 3:
items[i] = packUInt24(item.pos - pos)
items[i] = packUInt24(item.subWriter.pos - pos)
else:
raise ValueError(item.offsetSize)
@ -454,7 +458,7 @@ class OTTableWriter(object):
items = list(self.items)
packFuncs = {2: packUShort, 3: packUInt24, 4: packULong}
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
if item.offsetSize in packFuncs:
items[i] = packFuncs[item.offsetSize](0)
@ -474,7 +478,7 @@ class OTTableWriter(object):
def __eq__(self, other):
if type(self) != type(other):
return NotImplemented
return self.offsetSize == other.offsetSize and self.items == other.items
return self.items == other.items
def _doneWriting(self, internedTables, shareExtension=False):
# Convert CountData references to data string items
@ -500,8 +504,10 @@ class OTTableWriter(object):
item = items[i]
if hasattr(item, "getCountData"):
items[i] = item.getCountData()
elif hasattr(item, "getData"):
item._doneWriting(internedTables, shareExtension=shareExtension)
elif hasattr(item, "subWriter"):
item.subWriter._doneWriting(
internedTables, shareExtension=shareExtension
)
# At this point, all subwriters are hashable based on their items.
# (See hash and comparison magic methods above.) So the ``setdefault``
# 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
# object, which deduplicates the tree.
if not dontShare:
items[i] = item = internedTables.setdefault(item, item)
items[i].subWriter = internedTables.setdefault(
item.subWriter, item.subWriter
)
self.items = tuple(items)
def _gatherTables(self, tables, extTables, done):
@ -543,30 +551,33 @@ class OTTableWriter(object):
# Find coverage table
for i in range(numItems):
item = self.items[i]
if getattr(item, "name", None) == "Coverage":
if (
hasattr(item, "subWriter")
and getattr(item.subWriter, "name", None) == "Coverage"
):
sortCoverageLast = True
break
if id(item) not in done:
item._gatherTables(tables, extTables, done)
if id(item.subWriter) not in done:
item.subWriter._gatherTables(tables, extTables, done)
else:
# We're a new parent of item
pass
for i in iRange:
item = self.items[i]
if not hasattr(item, "getData"):
if not hasattr(item, "subWriter"):
continue
if (
sortCoverageLast
and (i == 1)
and getattr(item, "name", None) == "Coverage"
and getattr(item.subWriter, "name", None) == "Coverage"
):
# we've already 'gathered' it above
continue
if id(item) not in done:
item._gatherTables(tables, extTables, done)
if id(item.subWriter) not in done:
item.subWriter._gatherTables(tables, extTables, done)
else:
# Item is already written out by other parent
pass
@ -601,7 +612,7 @@ class OTTableWriter(object):
child_idx = 0
offset_pos = 0
for i, item in enumerate(self.items):
if hasattr(item, "getData"):
if hasattr(item, "subWriter"):
pos = offset_pos
elif hasattr(item, "getCountData"):
offset_pos += item.size
@ -610,12 +621,12 @@ class OTTableWriter(object):
offset_pos = offset_pos + len(item)
continue
if id(item) not in done:
child_idx = item_idx = item._gatherGraphForHarfbuzz(
if id(item.subWriter) not in done:
child_idx = item_idx = item.subWriter._gatherGraphForHarfbuzz(
tables, obj_list, done, item_idx, virtual_edges
)
else:
child_idx = done[id(item)]
child_idx = done[id(item.subWriter)]
real_edge = (pos, item.offsetSize, child_idx)
real_links.append(real_edge)
@ -698,10 +709,8 @@ class OTTableWriter(object):
# interface for gathering data, as used by table.compile()
def getSubWriter(self, offsetSize=2):
subwriter = self.__class__(
self.localState, self.tableTag, offsetSize=offsetSize
)
def getSubWriter(self):
subwriter = self.__class__(self.localState, self.tableTag)
subwriter.parent = (
self # because some subtables have idential values, we discard
)
@ -773,8 +782,8 @@ class OTTableWriter(object):
assert len(tag) == 4, tag
self.items.append(tag)
def writeSubTable(self, subWriter):
self.items.append(subWriter)
def writeSubTable(self, subWriter, offsetSize):
self.items.append(OffsetToWriter(subWriter, offsetSize))
def writeCountReference(self, table, name, size=2, value=None):
ref = CountReference(table, name, size=size, value=value)
@ -1365,7 +1374,7 @@ class ValueRecordFactory(object):
if isDevice:
if value:
subWriter = writer.getSubWriter()
writer.writeSubTable(subWriter)
writer.writeSubTable(subWriter, offsetSize=2)
value.compile(subWriter, font)
else:
writer.writeUShort(0)
@ -1376,7 +1385,6 @@ class ValueRecordFactory(object):
class ValueRecord(object):
# see ValueRecordFactory
def __init__(self, valueFormat=None, src=None):

View File

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

View File

@ -116,7 +116,7 @@ COLR_V1_SAMPLE = (
(b"\x00\x03", "LayerRecordCount (3)"),
(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\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 VarStore (NULL)"),
(b"\x00\x06", "BaseGlyphRecord[0].BaseGlyph (6)"),
@ -187,22 +187,26 @@ COLR_V1_SAMPLE = (
(b"\x00\x05", "ColorLine.ColorStop[1].PaletteIndex (5)"),
(b"@\x00", "ColorLine.ColorStop[1].Alpha (1.0)"),
# LayerList
(b"\x00\x00\x00\x04", "LayerList.LayerCount (4)"),
(b"\x00\x00\x00\x05", "LayerList.LayerCount (5)"),
(
b"\x00\x00\x00\x14",
"First Offset to Paint table from beginning of LayerList (20)",
b"\x00\x00\x00\x18",
"First Offset to Paint table from beginning of LayerList (24)",
),
(
b"\x00\x00\x00\x23",
"Second Offset to Paint table from beginning of LayerList (35)",
b"\x00\x00\x00\x27",
"Second Offset to Paint table from beginning of LayerList (39)",
),
(
b"\x00\x00\x00\x4e",
"Third Offset to Paint table from beginning of LayerList (78)",
b"\x00\x00\x00\x52",
"Third Offset to Paint table from beginning of LayerList (82)",
),
(
b"\x00\x00\x00\x9e",
"Fourth Offset to Paint table from beginning of LayerList (158)",
b"\x00\x00\x00\xa2",
"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]
(b"\x0a", "BaseGlyphPaintRecord[2].Paint.Format (10)"),
@ -296,7 +300,7 @@ COLR_V1_SAMPLE = (
),
(b"\xfc\x17", "xSkewAngle (-0.0611)"),
(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"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"),
(b"\x00\x0b", "LayerList.Paint[2].Glyph (11)"),
@ -413,7 +417,7 @@ COLR_V1_XML = [
" </BaseGlyphPaintRecord>",
"</BaseGlyphList>",
"<LayerList>",
" <!-- LayerCount=4 -->",
" <!-- LayerCount=5 -->",
' <Paint index="0" Format="10"><!-- PaintGlyph -->',
' <Paint Format="3"><!-- PaintVarSolid -->',
' <PaletteIndex value="2"/>',
@ -510,6 +514,13 @@ COLR_V1_XML = [
' <dx value="257"/>',
' <dy value="258"/>',
" </Paint>",
' <Paint index="4" Format="10"><!-- PaintGlyph -->',
' <Paint Format="2"><!-- PaintSolid -->',
' <PaletteIndex value="2"/>',
' <Alpha value="0.5"/>',
" </Paint>",
' <Glyph value="glyph00011"/>',
" </Paint>",
"</LayerList>",
'<ClipList Format="1">',
" <Clip>",