[TupleVariation] Option to optimize for loading speed, not size
This commit is contained in:
parent
18ca57cad4
commit
0213bea88e
@ -129,7 +129,9 @@ class TupleVariation(object):
|
||||
else:
|
||||
log.warning("bad delta format: %s" % ", ".join(sorted(attrs.keys())))
|
||||
|
||||
def compile(self, axisTags, sharedCoordIndices={}, pointData=None):
|
||||
def compile(
|
||||
self, axisTags, sharedCoordIndices={}, pointData=None, *, optimizeSize=True
|
||||
):
|
||||
assert set(self.axes.keys()) <= set(axisTags), (
|
||||
"Unknown axis tag found.",
|
||||
self.axes.keys(),
|
||||
@ -161,7 +163,7 @@ class TupleVariation(object):
|
||||
flags |= PRIVATE_POINT_NUMBERS
|
||||
auxData.append(pointData)
|
||||
|
||||
auxData.append(self.compileDeltas())
|
||||
auxData.append(self.compileDeltas(optimizeSize=optimizeSize))
|
||||
auxData = b"".join(auxData)
|
||||
|
||||
tupleData.insert(0, struct.pack(">HH", len(auxData), flags))
|
||||
@ -322,7 +324,7 @@ class TupleVariation(object):
|
||||
)
|
||||
return (result, pos)
|
||||
|
||||
def compileDeltas(self):
|
||||
def compileDeltas(self, optimizeSize=True):
|
||||
deltaX = []
|
||||
deltaY = []
|
||||
if self.getCoordWidth() == 2:
|
||||
@ -337,12 +339,12 @@ class TupleVariation(object):
|
||||
continue
|
||||
deltaX.append(c)
|
||||
bytearr = bytearray()
|
||||
self.compileDeltaValues_(deltaX, bytearr)
|
||||
self.compileDeltaValues_(deltaY, bytearr)
|
||||
self.compileDeltaValues_(deltaX, bytearr, optimizeSize=optimizeSize)
|
||||
self.compileDeltaValues_(deltaY, bytearr, optimizeSize=optimizeSize)
|
||||
return bytearr
|
||||
|
||||
@staticmethod
|
||||
def compileDeltaValues_(deltas, bytearr=None):
|
||||
def compileDeltaValues_(deltas, bytearr=None, *, optimizeSize=True):
|
||||
"""[value1, value2, value3, ...] --> bytearray
|
||||
|
||||
Emits a sequence of runs. Each run starts with a
|
||||
@ -360,8 +362,11 @@ class TupleVariation(object):
|
||||
""" # Explaining the format because the 'gvar' spec is hard to understand.
|
||||
if bytearr is None:
|
||||
bytearr = bytearray()
|
||||
|
||||
pos = 0
|
||||
numDeltas = len(deltas)
|
||||
|
||||
if optimizeSize:
|
||||
while pos < numDeltas:
|
||||
value = deltas[pos]
|
||||
if value == 0:
|
||||
@ -372,6 +377,25 @@ class TupleVariation(object):
|
||||
pos = TupleVariation.encodeDeltaRunAsWords_(deltas, pos, bytearr)
|
||||
else:
|
||||
pos = TupleVariation.encodeDeltaRunAsLongs_(deltas, pos, bytearr)
|
||||
else:
|
||||
minVal, maxVal = min(deltas), max(deltas)
|
||||
if minVal == 0 == maxVal:
|
||||
pos = TupleVariation.encodeDeltaRunAsZeroes_(deltas, pos, bytearr)
|
||||
elif -128 <= minVal <= maxVal <= 127:
|
||||
pos = TupleVariation.encodeDeltaRunAsBytes_(
|
||||
deltas, pos, bytearr, optimizeSize=False
|
||||
)
|
||||
elif -32768 <= minVal <= maxVal <= 32767:
|
||||
pos = TupleVariation.encodeDeltaRunAsWords_(
|
||||
deltas, pos, bytearr, optimizeSize=False
|
||||
)
|
||||
else:
|
||||
pos = TupleVariation.encodeDeltaRunAsLongs_(
|
||||
deltas, pos, bytearr, optimizeSize=False
|
||||
)
|
||||
|
||||
assert pos == numDeltas, (pos, numDeltas)
|
||||
|
||||
return bytearr
|
||||
|
||||
@staticmethod
|
||||
@ -389,7 +413,7 @@ class TupleVariation(object):
|
||||
return pos
|
||||
|
||||
@staticmethod
|
||||
def encodeDeltaRunAsBytes_(deltas, offset, bytearr):
|
||||
def encodeDeltaRunAsBytes_(deltas, offset, bytearr, optimizeSize=True):
|
||||
pos = offset
|
||||
numDeltas = len(deltas)
|
||||
while pos < numDeltas:
|
||||
@ -404,7 +428,12 @@ class TupleVariation(object):
|
||||
# (04 0F 0F 00 0F 0F) when storing the zero value
|
||||
# literally, but 7 bytes (01 0F 0F 80 01 0F 0F)
|
||||
# when starting a new run.
|
||||
if value == 0 and pos + 1 < numDeltas and deltas[pos + 1] == 0:
|
||||
if (
|
||||
optimizeSize
|
||||
and value == 0
|
||||
and pos + 1 < numDeltas
|
||||
and deltas[pos + 1] == 0
|
||||
):
|
||||
break
|
||||
pos += 1
|
||||
runLength = pos - offset
|
||||
@ -419,7 +448,7 @@ class TupleVariation(object):
|
||||
return pos
|
||||
|
||||
@staticmethod
|
||||
def encodeDeltaRunAsWords_(deltas, offset, bytearr):
|
||||
def encodeDeltaRunAsWords_(deltas, offset, bytearr, optimizeSize=True):
|
||||
pos = offset
|
||||
numDeltas = len(deltas)
|
||||
while pos < numDeltas:
|
||||
@ -432,7 +461,7 @@ class TupleVariation(object):
|
||||
# storing the zero literally (42 66 66 00 00 77 77),
|
||||
# and equally 7 bytes when starting a new run
|
||||
# (40 66 66 80 40 77 77).
|
||||
if value == 0:
|
||||
if optimizeSize and value == 0:
|
||||
break
|
||||
|
||||
# Within a word-encoded run of deltas, a single value
|
||||
@ -442,7 +471,8 @@ class TupleVariation(object):
|
||||
# the value literally (42 66 66 00 02 77 77), but 8 bytes
|
||||
# when starting a new run (40 66 66 00 02 40 77 77).
|
||||
if (
|
||||
(-128 <= value <= 127)
|
||||
optimizeSize
|
||||
and (-128 <= value <= 127)
|
||||
and pos + 1 < numDeltas
|
||||
and (-128 <= deltas[pos + 1] <= 127)
|
||||
):
|
||||
@ -470,12 +500,12 @@ class TupleVariation(object):
|
||||
return pos
|
||||
|
||||
@staticmethod
|
||||
def encodeDeltaRunAsLongs_(deltas, offset, bytearr):
|
||||
def encodeDeltaRunAsLongs_(deltas, offset, bytearr, optimizeSize=True):
|
||||
pos = offset
|
||||
numDeltas = len(deltas)
|
||||
while pos < numDeltas:
|
||||
value = deltas[pos]
|
||||
if -32768 <= value <= 32767:
|
||||
if optimizeSize and -32768 <= value <= 32767:
|
||||
break
|
||||
pos += 1
|
||||
runLength = pos - offset
|
||||
@ -677,7 +707,13 @@ def compileSharedTuples(
|
||||
|
||||
|
||||
def compileTupleVariationStore(
|
||||
variations, pointCount, axisTags, sharedTupleIndices, useSharedPoints=True
|
||||
variations,
|
||||
pointCount,
|
||||
axisTags,
|
||||
sharedTupleIndices,
|
||||
useSharedPoints=True,
|
||||
*,
|
||||
optimizeSize=True,
|
||||
):
|
||||
# pointCount is actually unused. Keeping for API compat.
|
||||
del pointCount
|
||||
@ -733,7 +769,9 @@ def compileTupleVariationStore(
|
||||
]
|
||||
|
||||
for v, p in zip(variations, pointDatas):
|
||||
thisTuple, thisData = v.compile(axisTags, sharedTupleIndices, pointData=p)
|
||||
thisTuple, thisData = v.compile(
|
||||
axisTags, sharedTupleIndices, pointData=p, optimizeSize=optimizeSize
|
||||
)
|
||||
|
||||
tuples.append(thisTuple)
|
||||
data.append(thisData)
|
||||
|
@ -85,6 +85,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
|
||||
def compileGlyphs_(self, ttFont, axisTags, sharedCoordIndices):
|
||||
result = []
|
||||
glyf = ttFont["glyf"]
|
||||
optimizeSize = getattr(self, "optimizeSize", True)
|
||||
for glyphName in ttFont.getGlyphOrder():
|
||||
variations = self.variations.get(glyphName, [])
|
||||
if not variations:
|
||||
@ -93,7 +94,11 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
|
||||
pointCountUnused = 0 # pointCount is actually unused by compileGlyph
|
||||
result.append(
|
||||
compileGlyph_(
|
||||
variations, pointCountUnused, axisTags, sharedCoordIndices
|
||||
variations,
|
||||
pointCountUnused,
|
||||
axisTags,
|
||||
sharedCoordIndices,
|
||||
optimizeSize=optimizeSize,
|
||||
)
|
||||
)
|
||||
return result
|
||||
@ -248,9 +253,11 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
|
||||
return len(getattr(glyph, "coordinates", [])) + NUM_PHANTOM_POINTS
|
||||
|
||||
|
||||
def compileGlyph_(variations, pointCount, axisTags, sharedCoordIndices):
|
||||
def compileGlyph_(
|
||||
variations, pointCount, axisTags, sharedCoordIndices, *, optimizeSize=True
|
||||
):
|
||||
tupleVariationCount, tuples, data = tv.compileTupleVariationStore(
|
||||
variations, pointCount, axisTags, sharedCoordIndices
|
||||
variations, pointCount, axisTags, sharedCoordIndices, optimizeSize=optimizeSize
|
||||
)
|
||||
if tupleVariationCount == 0:
|
||||
return b""
|
||||
|
@ -594,8 +594,8 @@ class TupleVariationTest(unittest.TestCase):
|
||||
self.assertEqual("02 01 02 04", hexencode(var.compileDeltas()))
|
||||
|
||||
def test_compileDeltaValues(self):
|
||||
compileDeltaValues = lambda values: hexencode(
|
||||
TupleVariation.compileDeltaValues_(values)
|
||||
compileDeltaValues = lambda values, optimizeSize=True: hexencode(
|
||||
TupleVariation.compileDeltaValues_(values, optimizeSize=optimizeSize)
|
||||
)
|
||||
# zeroes
|
||||
self.assertEqual("80", compileDeltaValues([0]))
|
||||
@ -635,18 +635,34 @@ class TupleVariationTest(unittest.TestCase):
|
||||
)
|
||||
# bytes, zeroes
|
||||
self.assertEqual("01 01 00", compileDeltaValues([1, 0]))
|
||||
self.assertEqual("01 01 00", compileDeltaValues([1, 0], optimizeSize=False))
|
||||
self.assertEqual("00 01 81", compileDeltaValues([1, 0, 0]))
|
||||
self.assertEqual(
|
||||
"02 01 00 00", compileDeltaValues([1, 0, 0], optimizeSize=False)
|
||||
)
|
||||
# words, bytes, words: a single byte is more compact when encoded as part of the words run
|
||||
self.assertEqual(
|
||||
"42 66 66 00 02 77 77", compileDeltaValues([0x6666, 2, 0x7777])
|
||||
)
|
||||
self.assertEqual(
|
||||
"42 66 66 00 02 77 77",
|
||||
compileDeltaValues([0x6666, 2, 0x7777], optimizeSize=False),
|
||||
)
|
||||
self.assertEqual(
|
||||
"40 66 66 01 02 02 40 77 77", compileDeltaValues([0x6666, 2, 2, 0x7777])
|
||||
)
|
||||
self.assertEqual(
|
||||
"43 66 66 00 02 00 02 77 77",
|
||||
compileDeltaValues([0x6666, 2, 2, 0x7777], optimizeSize=False),
|
||||
)
|
||||
# words, zeroes, words
|
||||
self.assertEqual(
|
||||
"40 66 66 80 40 77 77", compileDeltaValues([0x6666, 0, 0x7777])
|
||||
)
|
||||
self.assertEqual(
|
||||
"42 66 66 00 00 77 77",
|
||||
compileDeltaValues([0x6666, 0, 0x7777], optimizeSize=False),
|
||||
)
|
||||
self.assertEqual(
|
||||
"40 66 66 81 40 77 77", compileDeltaValues([0x6666, 0, 0, 0x7777])
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user