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