Merge pull request #2285 from fonttools/varStore-32bit
Improve otBase facilities / towards 32bit VariationStore
This commit is contained in:
commit
31ab3aae0b
@ -106,6 +106,10 @@ class BaseTTXConverter(DefaultTable):
|
||||
self.table.populateDefaults()
|
||||
|
||||
|
||||
# https://github.com/fonttools/fonttools/pull/2285#issuecomment-834652928
|
||||
assert len(struct.pack('i', 0)) == 4
|
||||
assert array.array('i').itemsize == 4, "Oops, file a bug against fonttools."
|
||||
|
||||
class OTTableReader(object):
|
||||
|
||||
"""Helper class to retrieve data from an OpenType table."""
|
||||
@ -140,32 +144,43 @@ class OTTableReader(object):
|
||||
value, = struct.unpack(f">{typecode}", self.data[pos:newpos])
|
||||
self.pos = newpos
|
||||
return value
|
||||
|
||||
def readUShort(self):
|
||||
return self.readValue("H", staticSize=2)
|
||||
|
||||
def readArray(self, typecode, staticSize, count):
|
||||
pos = self.pos
|
||||
newpos = pos + count * staticSize
|
||||
value = array.array(typecode, self.data[pos:newpos])
|
||||
if sys.byteorder != "big": value.byteswap()
|
||||
self.pos = newpos
|
||||
return value
|
||||
|
||||
def readUShortArray(self, count):
|
||||
return self.readArray("H", staticSize=2, count=count)
|
||||
return value.tolist()
|
||||
|
||||
def readInt8(self):
|
||||
return self.readValue("b", staticSize=1)
|
||||
def readInt8Array(self, count):
|
||||
return self.readArray("b", staticSize=1, count=count)
|
||||
|
||||
def readShort(self):
|
||||
return self.readValue("h", staticSize=2)
|
||||
def readShortArray(self, count):
|
||||
return self.readArray("h", staticSize=2, count=count)
|
||||
|
||||
def readLong(self):
|
||||
return self.readValue("l", staticSize=4)
|
||||
return self.readValue("i", staticSize=4)
|
||||
def readLongArray(self, count):
|
||||
return self.readArray("i", staticSize=4, count=count)
|
||||
|
||||
def readUInt8(self):
|
||||
return self.readValue("B", staticSize=1)
|
||||
def readUInt8Array(self, count):
|
||||
return self.readArray("B", staticSize=1, count=count)
|
||||
|
||||
def readUShort(self):
|
||||
return self.readValue("H", staticSize=2)
|
||||
def readUShortArray(self, count):
|
||||
return self.readArray("H", staticSize=2, count=count)
|
||||
|
||||
def readULong(self):
|
||||
return self.readValue("I", staticSize=4)
|
||||
def readULongArray(self, count):
|
||||
return self.readArray("I", staticSize=4, count=count)
|
||||
|
||||
def readUInt24(self):
|
||||
pos = self.pos
|
||||
@ -173,9 +188,8 @@ class OTTableReader(object):
|
||||
value, = struct.unpack(">l", b'\0'+self.data[pos:newpos])
|
||||
self.pos = newpos
|
||||
return value
|
||||
|
||||
def readULong(self):
|
||||
return self.readValue("L", staticSize=4)
|
||||
def readUInt24Array(self, count):
|
||||
return [self.readUInt24() for _ in range(count)]
|
||||
|
||||
def readTag(self):
|
||||
pos = self.pos
|
||||
@ -419,33 +433,52 @@ class OTTableWriter(object):
|
||||
|
||||
def writeValue(self, typecode, value):
|
||||
self.items.append(struct.pack(f">{typecode}", value))
|
||||
|
||||
def writeUShort(self, value):
|
||||
assert 0 <= value < 0x10000, value
|
||||
self.items.append(struct.pack(">H", value))
|
||||
|
||||
def writeShort(self, value):
|
||||
assert -32768 <= value < 32768, value
|
||||
self.items.append(struct.pack(">h", value))
|
||||
|
||||
def writeUInt8(self, value):
|
||||
assert 0 <= value < 256, value
|
||||
self.items.append(struct.pack(">B", value))
|
||||
def writeArray(self, typecode, values):
|
||||
a = array.array(typecode, values)
|
||||
if sys.byteorder != "big": a.byteswap()
|
||||
self.items.append(a.tobytes())
|
||||
|
||||
def writeInt8(self, value):
|
||||
assert -128 <= value < 128, value
|
||||
self.items.append(struct.pack(">b", value))
|
||||
def writeInt8Array(self, values):
|
||||
self.writeArray('b', values)
|
||||
|
||||
def writeShort(self, value):
|
||||
assert -32768 <= value < 32768, value
|
||||
self.items.append(struct.pack(">h", value))
|
||||
def writeShortArray(self, values):
|
||||
self.writeArray('h', values)
|
||||
|
||||
def writeLong(self, value):
|
||||
self.items.append(struct.pack(">i", value))
|
||||
def writeLongArray(self, values):
|
||||
self.writeArray('i', values)
|
||||
|
||||
def writeUInt8(self, value):
|
||||
assert 0 <= value < 256, value
|
||||
self.items.append(struct.pack(">B", value))
|
||||
def writeUInt8Array(self, values):
|
||||
self.writeArray('B', values)
|
||||
|
||||
def writeUShort(self, value):
|
||||
assert 0 <= value < 0x10000, value
|
||||
self.items.append(struct.pack(">H", value))
|
||||
def writeUShortArray(self, values):
|
||||
self.writeArray('H', values)
|
||||
|
||||
def writeULong(self, value):
|
||||
self.items.append(struct.pack(">I", value))
|
||||
def writeULongArray(self, values):
|
||||
self.writeArray('I', values)
|
||||
|
||||
def writeUInt24(self, value):
|
||||
assert 0 <= value < 0x1000000, value
|
||||
b = struct.pack(">L", value)
|
||||
self.items.append(b[1:])
|
||||
|
||||
def writeLong(self, value):
|
||||
self.items.append(struct.pack(">l", value))
|
||||
|
||||
def writeULong(self, value):
|
||||
self.items.append(struct.pack(">L", value))
|
||||
def writeUInt24Array(self, values):
|
||||
for value in values:
|
||||
self.writeUInt24(value)
|
||||
|
||||
def writeTag(self, tag):
|
||||
tag = Tag(tag).tobytes()
|
||||
@ -532,11 +565,11 @@ def packUShort(value):
|
||||
|
||||
def packULong(value):
|
||||
assert 0 <= value < 0x100000000, value
|
||||
return struct.pack(">L", value)
|
||||
return struct.pack(">I", value)
|
||||
|
||||
def packUInt24(value):
|
||||
assert 0 <= value < 0x1000000, value
|
||||
return struct.pack(">L", value)[1:]
|
||||
return struct.pack(">I", value)[1:]
|
||||
|
||||
|
||||
class BaseTable(object):
|
||||
@ -698,14 +731,11 @@ class BaseTable(object):
|
||||
else:
|
||||
# conv.repeat is a propagated count
|
||||
writer[conv.repeat].setValue(countValue)
|
||||
values = value
|
||||
for i, value in enumerate(values):
|
||||
try:
|
||||
conv.write(writer, font, table, value, i)
|
||||
except Exception as e:
|
||||
name = value.__class__.__name__ if value is not None else conv.name
|
||||
e.args = e.args + (name+'['+str(i)+']',)
|
||||
raise
|
||||
try:
|
||||
conv.writeArray(writer, font, table, value)
|
||||
except Exception as e:
|
||||
e.args = e.args + (conv.name+'[]',)
|
||||
raise
|
||||
elif conv.isCount:
|
||||
# Special-case Count values.
|
||||
# Assumption: a Count field will *always* precede
|
||||
|
@ -192,8 +192,12 @@ class BaseConverter(object):
|
||||
raise NotImplementedError(self)
|
||||
|
||||
def writeArray(self, writer, font, tableDict, values):
|
||||
for i, value in enumerate(values):
|
||||
self.write(writer, font, tableDict, value, i)
|
||||
try:
|
||||
for i, value in enumerate(values):
|
||||
self.write(writer, font, tableDict, value, i)
|
||||
except Exception as e:
|
||||
e.args = e.args + (i,)
|
||||
raise
|
||||
|
||||
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
||||
"""Write a value to the writer."""
|
||||
@ -230,15 +234,23 @@ class Long(IntValue):
|
||||
staticSize = 4
|
||||
def read(self, reader, font, tableDict):
|
||||
return reader.readLong()
|
||||
def readArray(self, reader, font, tableDict, count):
|
||||
return reader.readLongArray(count)
|
||||
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
||||
writer.writeLong(value)
|
||||
def writeArray(self, writer, font, tableDict, values):
|
||||
writer.writeLongArray(values)
|
||||
|
||||
class ULong(IntValue):
|
||||
staticSize = 4
|
||||
def read(self, reader, font, tableDict):
|
||||
return reader.readULong()
|
||||
def readArray(self, reader, font, tableDict, count):
|
||||
return reader.readULongArray(count)
|
||||
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
||||
writer.writeULong(value)
|
||||
def writeArray(self, writer, font, tableDict, values):
|
||||
writer.writeULongArray(values)
|
||||
|
||||
class Flags32(ULong):
|
||||
@staticmethod
|
||||
@ -249,29 +261,45 @@ class Short(IntValue):
|
||||
staticSize = 2
|
||||
def read(self, reader, font, tableDict):
|
||||
return reader.readShort()
|
||||
def readArray(self, reader, font, tableDict, count):
|
||||
return reader.readShortArray(count)
|
||||
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
||||
writer.writeShort(value)
|
||||
def writeArray(self, writer, font, tableDict, values):
|
||||
writer.writeShortArray(values)
|
||||
|
||||
class UShort(IntValue):
|
||||
staticSize = 2
|
||||
def read(self, reader, font, tableDict):
|
||||
return reader.readUShort()
|
||||
def readArray(self, reader, font, tableDict, count):
|
||||
return reader.readUShortArray(count)
|
||||
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
||||
writer.writeUShort(value)
|
||||
def writeArray(self, writer, font, tableDict, values):
|
||||
writer.writeUShortArray(values)
|
||||
|
||||
class Int8(IntValue):
|
||||
staticSize = 1
|
||||
def read(self, reader, font, tableDict):
|
||||
return reader.readInt8()
|
||||
def readArray(self, reader, font, tableDict, count):
|
||||
return reader.readInt8Array(count)
|
||||
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
||||
writer.writeInt8(value)
|
||||
def writeArray(self, writer, font, tableDict, values):
|
||||
writer.writeInt8Array(values)
|
||||
|
||||
class UInt8(IntValue):
|
||||
staticSize = 1
|
||||
def read(self, reader, font, tableDict):
|
||||
return reader.readUInt8()
|
||||
def readArray(self, reader, font, tableDict, count):
|
||||
return reader.readUInt8Array(count)
|
||||
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
||||
writer.writeUInt8(value)
|
||||
def writeArray(self, writer, font, tableDict, values):
|
||||
writer.writeUInt8Array(values)
|
||||
|
||||
class UInt24(IntValue):
|
||||
staticSize = 3
|
||||
@ -314,6 +342,14 @@ class GlyphID(SimpleValue):
|
||||
return l
|
||||
def read(self, reader, font, tableDict):
|
||||
return font.getGlyphName(reader.readValue(self.typecode, self.staticSize))
|
||||
def writeArray(self, writer, font, tableDict, values):
|
||||
glyphMap = font.getReverseGlyphMap()
|
||||
try:
|
||||
values = [glyphMap[glyphname] for glyphname in values]
|
||||
except KeyError:
|
||||
# Slower, but will not throw a KeyError on an out-of-range glyph name.
|
||||
values = [font.getGlyphID(glyphname) for glyphname in values]
|
||||
writer.writeArray(self.typecode, values)
|
||||
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
||||
writer.writeValue(self.typecode, font.getGlyphID(value))
|
||||
|
||||
@ -1551,20 +1587,15 @@ class VarIdxMapValue(BaseConverter):
|
||||
outerShift = 16 - innerBits
|
||||
|
||||
entrySize = 1 + ((fmt & 0x0030) >> 4)
|
||||
read = {
|
||||
1: reader.readUInt8,
|
||||
2: reader.readUShort,
|
||||
3: reader.readUInt24,
|
||||
4: reader.readULong,
|
||||
readArray = {
|
||||
1: reader.readUInt8Array,
|
||||
2: reader.readUShortArray,
|
||||
3: reader.readUInt24Array,
|
||||
4: reader.readULongArray,
|
||||
}[entrySize]
|
||||
|
||||
mapping = []
|
||||
for i in range(nItems):
|
||||
raw = read()
|
||||
idx = ((raw & outerMask) << outerShift) | (raw & innerMask)
|
||||
mapping.append(idx)
|
||||
|
||||
return mapping
|
||||
return [(((raw & outerMask) << outerShift) | (raw & innerMask))
|
||||
for raw in readArray(nItems)]
|
||||
|
||||
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
||||
fmt = tableDict['EntryFormat']
|
||||
@ -1576,16 +1607,15 @@ class VarIdxMapValue(BaseConverter):
|
||||
outerShift = 16 - innerBits
|
||||
|
||||
entrySize = 1 + ((fmt & 0x0030) >> 4)
|
||||
write = {
|
||||
1: writer.writeUInt8,
|
||||
2: writer.writeUShort,
|
||||
3: writer.writeUInt24,
|
||||
4: writer.writeULong,
|
||||
writeArray = {
|
||||
1: writer.writeUInt8Array,
|
||||
2: writer.writeUShortArray,
|
||||
3: writer.writeUInt24Array,
|
||||
4: writer.writeULongArray,
|
||||
}[entrySize]
|
||||
|
||||
for idx in mapping:
|
||||
raw = ((idx & 0xFFFF0000) >> outerShift) | (idx & innerMask)
|
||||
write(raw)
|
||||
writeArray([(((idx & 0xFFFF0000) >> outerShift) | (idx & innerMask))
|
||||
for idx in mapping])
|
||||
|
||||
|
||||
class VarDataValue(BaseConverter):
|
||||
@ -1594,27 +1624,43 @@ class VarDataValue(BaseConverter):
|
||||
values = []
|
||||
|
||||
regionCount = tableDict["VarRegionCount"]
|
||||
shortCount = tableDict["NumShorts"]
|
||||
wordCount = tableDict["NumShorts"]
|
||||
|
||||
for i in range(min(regionCount, shortCount)):
|
||||
values.append(reader.readShort())
|
||||
for i in range(min(regionCount, shortCount), regionCount):
|
||||
values.append(reader.readInt8())
|
||||
for i in range(regionCount, shortCount):
|
||||
reader.readInt8()
|
||||
# https://github.com/fonttools/fonttools/issues/2279
|
||||
longWords = bool(wordCount & 0x8000)
|
||||
wordCount = wordCount & 0x7FFF
|
||||
|
||||
if longWords:
|
||||
readBigArray, readSmallArray = reader.readLongArray, reader.readShortArray
|
||||
else:
|
||||
readBigArray, readSmallArray = reader.readShortArray, reader.readInt8Array
|
||||
|
||||
n1, n2 = min(regionCount, wordCount), max(regionCount, wordCount)
|
||||
values.extend(readBigArray(n1))
|
||||
values.extend(readSmallArray(n2 - n1))
|
||||
if n2 > regionCount: # Padding
|
||||
del values[regionCount:]
|
||||
|
||||
return values
|
||||
|
||||
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
||||
def write(self, writer, font, tableDict, values, repeatIndex=None):
|
||||
regionCount = tableDict["VarRegionCount"]
|
||||
shortCount = tableDict["NumShorts"]
|
||||
wordCount = tableDict["NumShorts"]
|
||||
|
||||
for i in range(min(regionCount, shortCount)):
|
||||
writer.writeShort(value[i])
|
||||
for i in range(min(regionCount, shortCount), regionCount):
|
||||
writer.writeInt8(value[i])
|
||||
for i in range(regionCount, shortCount):
|
||||
writer.writeInt8(0)
|
||||
# https://github.com/fonttools/fonttools/issues/2279
|
||||
longWords = bool(wordCount & 0x8000)
|
||||
wordCount = wordCount & 0x7FFF
|
||||
|
||||
(writeBigArray, writeSmallArray) = {
|
||||
False: (writer.writeShortArray, writer.writeInt8Array),
|
||||
True: (writer.writeLongArray, writer.writeShortArray),
|
||||
}[longWords]
|
||||
|
||||
n1, n2 = min(regionCount, wordCount), max(regionCount, wordCount)
|
||||
writeBigArray(values[:n1])
|
||||
writeSmallArray(values[n1:regionCount])
|
||||
if n2 > regionCount: # Padding
|
||||
writer.writeSmallArray([0] * (n2 - regionCount))
|
||||
|
||||
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
||||
xmlWriter.simpletag(name, attrs + [("value", value)])
|
||||
|
@ -26,39 +26,54 @@ def buildVarRegionList(supports, axisTags):
|
||||
return self
|
||||
|
||||
|
||||
def _reorderItem(lst, narrows, zeroes):
|
||||
out = []
|
||||
count = len(lst)
|
||||
for i in range(count):
|
||||
if i not in narrows:
|
||||
out.append(lst[i])
|
||||
for i in range(count):
|
||||
if i in narrows and i not in zeroes:
|
||||
out.append(lst[i])
|
||||
return out
|
||||
def _reorderItem(lst, mapping):
|
||||
return [lst[i] for i in mapping]
|
||||
|
||||
def VarData_calculateNumShorts(self, optimize=False):
|
||||
count = self.VarRegionCount
|
||||
items = self.Item
|
||||
narrows = set(range(count))
|
||||
zeroes = set(range(count))
|
||||
bit_lengths = [0] * count
|
||||
for item in items:
|
||||
wides = [i for i in narrows if not (-128 <= item[i] <= 127)]
|
||||
narrows.difference_update(wides)
|
||||
nonzeroes = [i for i in zeroes if item[i]]
|
||||
zeroes.difference_update(nonzeroes)
|
||||
if not narrows and not zeroes:
|
||||
break
|
||||
# The "+ (i < -1)" magic is to handle two's-compliment.
|
||||
# That is, we want to get back 7 for -128, whereas
|
||||
# bit_length() returns 8. Similarly for -65536.
|
||||
# The reason "i < -1" is used instead of "i < 0" is that
|
||||
# the latter would make it return 0 for "-1" instead of 1.
|
||||
bl = [(i + (i < -1)).bit_length() for i in item]
|
||||
bit_lengths = [max(*pair) for pair in zip(bl, bit_lengths)]
|
||||
# The addition of 8, instead of seven, is to account for the sign bit.
|
||||
# This "((b + 8) >> 3) if b else 0" when combined with the above
|
||||
# "(i + (i < -1)).bit_length()" is a faster way to compute byte-lengths
|
||||
# conforming to:
|
||||
#
|
||||
# byte_length = (0 if i == 0 else
|
||||
# 1 if -128 <= i < 128 else
|
||||
# 2 if -65536 <= i < 65536 else
|
||||
# ...)
|
||||
byte_lengths = [((b + 8) >> 3) if b else 0 for b in bit_lengths]
|
||||
|
||||
# https://github.com/fonttools/fonttools/issues/2279
|
||||
longWords = any(b > 2 for b in byte_lengths)
|
||||
|
||||
if optimize:
|
||||
# Reorder columns such that all SHORT columns come before UINT8
|
||||
self.VarRegionIndex = _reorderItem(self.VarRegionIndex, narrows, zeroes)
|
||||
# Reorder columns such that wider columns come before narrower columns
|
||||
mapping = []
|
||||
mapping.extend(i for i,b in enumerate(byte_lengths) if b > 2)
|
||||
mapping.extend(i for i,b in enumerate(byte_lengths) if b == 2)
|
||||
mapping.extend(i for i,b in enumerate(byte_lengths) if b == 1)
|
||||
|
||||
byte_lengths = _reorderItem(byte_lengths, mapping)
|
||||
self.VarRegionIndex = _reorderItem(self.VarRegionIndex, mapping)
|
||||
self.VarRegionCount = len(self.VarRegionIndex)
|
||||
for i in range(len(items)):
|
||||
items[i] = _reorderItem(items[i], narrows, zeroes)
|
||||
self.NumShorts = count - len(narrows)
|
||||
items[i] = _reorderItem(items[i], mapping)
|
||||
|
||||
if longWords:
|
||||
self.NumShorts = max((i for i,b in enumerate(byte_lengths) if b > 2), default=-1) + 1
|
||||
self.NumShorts |= 0x8000
|
||||
else:
|
||||
wides = set(range(count)) - narrows
|
||||
self.NumShorts = 1+max(wides) if wides else 0
|
||||
self.NumShorts = max((i for i,b in enumerate(byte_lengths) if b > 1), default=-1) + 1
|
||||
|
||||
self.VarRegionCount = len(self.VarRegionIndex)
|
||||
return self
|
||||
|
||||
|
@ -5,7 +5,6 @@ from fontTools.varLib.builder import (buildVarRegionList, buildVarStore,
|
||||
buildVarRegion, buildVarData)
|
||||
from functools import partial
|
||||
from collections import defaultdict
|
||||
from array import array
|
||||
|
||||
|
||||
def _getLocationKey(loc):
|
||||
@ -375,12 +374,11 @@ class _Encoding(object):
|
||||
as a VarData."""
|
||||
c = 6
|
||||
while chars:
|
||||
if chars & 3:
|
||||
if chars & 0b1111:
|
||||
c += 2
|
||||
chars >>= 2
|
||||
chars >>= 4
|
||||
return c
|
||||
|
||||
|
||||
def _find_yourself_best_new_encoding(self, done_by_width):
|
||||
self.best_new_encoding = None
|
||||
for new_width in range(self.width+1, self.width+self.room+1):
|
||||
@ -405,14 +403,31 @@ class _EncodingDict(dict):
|
||||
@staticmethod
|
||||
def _row_characteristics(row):
|
||||
"""Returns encoding characteristics for a row."""
|
||||
longWords = False
|
||||
|
||||
chars = 0
|
||||
i = 1
|
||||
for v in row:
|
||||
if v:
|
||||
chars += i
|
||||
if not (-128 <= v <= 127):
|
||||
chars += i * 2
|
||||
i <<= 2
|
||||
chars += i * 0b0010
|
||||
if not (-32768 <= v <= 32767):
|
||||
longWords = True
|
||||
break
|
||||
i <<= 4
|
||||
|
||||
if longWords:
|
||||
# Redo; only allow 2byte/4byte encoding
|
||||
chars = 0
|
||||
i = 1
|
||||
for v in row:
|
||||
if v:
|
||||
chars += i * 0b0011
|
||||
if not (-32768 <= v <= 32767):
|
||||
chars += i * 0b1100
|
||||
i <<= 4
|
||||
|
||||
return chars
|
||||
|
||||
|
||||
@ -423,7 +438,7 @@ def VarStore_optimize(self):
|
||||
# Check that no two VarRegions are the same; if they are, fold them.
|
||||
|
||||
n = len(self.VarRegionList.Region) # Number of columns
|
||||
zeroes = array('h', [0]*n)
|
||||
zeroes = [0] * n
|
||||
|
||||
front_mapping = {} # Map from old VarIdxes to full row tuples
|
||||
|
||||
@ -435,7 +450,7 @@ def VarStore_optimize(self):
|
||||
|
||||
for minor,item in enumerate(data.Item):
|
||||
|
||||
row = array('h', zeroes)
|
||||
row = list(zeroes)
|
||||
for regionIdx,v in zip(regionIndices, item):
|
||||
row[regionIdx] += v
|
||||
row = tuple(row)
|
||||
|
@ -8,12 +8,20 @@ import pytest
|
||||
([0], [[128]], 1),
|
||||
([0, 1, 2], [[128, 1, 2], [3, -129, 5], [6, 7, 8]], 2),
|
||||
([0, 1, 2], [[0, 128, 2], [3, 4, 5], [6, 7, -129]], 3),
|
||||
([0], [[32768]], 0x8001),
|
||||
([0, 1, 2], [[32768, 1, 2], [3, -129, 5], [6, 7, 8]], 0x8001),
|
||||
([0, 1, 2], [[32768, 1, 2], [3, -32769, 5], [6, 7, 8]], 0x8002),
|
||||
([0, 1, 2], [[0, 32768, 2], [3, 4, 5], [6, 7, -32769]], 0x8003),
|
||||
], ids=[
|
||||
"0_regions_0_deltas",
|
||||
"1_region_1_uint8",
|
||||
"1_region_1_short",
|
||||
"3_regions_2_shorts_ordered",
|
||||
"3_regions_2_shorts_unordered",
|
||||
"1_region_1_long",
|
||||
"3_regions_1_long_ordered",
|
||||
"3_regions_2_longs_ordered",
|
||||
"3_regions_2_longs_unordered",
|
||||
])
|
||||
def test_buildVarData_no_optimize(region_indices, items, expected_num_shorts):
|
||||
data = buildVarData(region_indices, items, optimize=False)
|
||||
@ -41,6 +49,16 @@ def test_buildVarData_no_optimize(region_indices, items, expected_num_shorts):
|
||||
[0, 1, 2], [[0, 1, 128], [3, -129, 5], [256, 7, 8]]),
|
||||
([0, 1, 2], [[0, 128, 2], [0, 4, 5], [0, 7, 8]], 1,
|
||||
[1, 2], [[128, 2], [4, 5], [7, 8]]),
|
||||
([0, 1, 2], [[0, 32768, 2], [3, 4, 5], [6, 7, 8]], 0x8001,
|
||||
[1, 0, 2], [[32768, 0, 2], [4, 3, 5], [7, 6, 8]]),
|
||||
([0, 1, 2], [[0, 1, 32768], [3, 4, 5], [6, -32769, 8]], 0x8002,
|
||||
[1, 2, 0], [[1, 32768, 0], [4, 5, 3], [-32769, 8, 6]]),
|
||||
([0, 1, 2], [[32768, 1, -32769], [3, 4, 5], [6, 7, 8]], 0x8002,
|
||||
[0, 2, 1], [[32768, -32769, 1], [3, 5, 4], [6, 8, 7]]),
|
||||
([0, 1, 2], [[0, 1, 32768], [3, -32769, 5], [65536, 7, 8]], 0x8003,
|
||||
[0, 1, 2], [[0, 1, 32768], [3, -32769, 5], [65536, 7, 8]]),
|
||||
([0, 1, 2], [[0, 32768, 2], [0, 4, 5], [0, 7, 8]], 0x8001,
|
||||
[1, 2], [[32768, 2], [4, 5], [7, 8]]),
|
||||
], ids=[
|
||||
"0/3_shorts_no_reorder",
|
||||
"1/3_shorts_reorder",
|
||||
@ -48,6 +66,11 @@ def test_buildVarData_no_optimize(region_indices, items, expected_num_shorts):
|
||||
"2/3_shorts_same_row_reorder",
|
||||
"3/3_shorts_no_reorder",
|
||||
"1/3_shorts_1/3_zeroes",
|
||||
"1/3_longs_reorder",
|
||||
"2/3_longs_reorder",
|
||||
"2/3_longs_same_row_reorder",
|
||||
"3/3_longs_no_reorder",
|
||||
"1/3_longs_1/3_zeroes",
|
||||
])
|
||||
def test_buildVarData_optimize(
|
||||
region_indices, items, expected_num_shorts, expected_regions,
|
||||
|
82
Tests/varLib/varStore_test.py
Normal file
82
Tests/varLib/varStore_test.py
Normal file
@ -0,0 +1,82 @@
|
||||
import pytest
|
||||
from fontTools.varLib.models import VariationModel
|
||||
from fontTools.varLib.varStore import OnlineVarStoreBuilder, VarStoreInstancer
|
||||
from fontTools.ttLib import TTFont, newTable
|
||||
from fontTools.ttLib.tables._f_v_a_r import Axis
|
||||
from fontTools.ttLib.tables.otBase import OTTableReader, OTTableWriter
|
||||
from fontTools.ttLib.tables.otTables import VarStore
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"locations, masterValues",
|
||||
[
|
||||
(
|
||||
[{}, {"a": 1}],
|
||||
[
|
||||
[10, 20],
|
||||
[100, 2000],
|
||||
[100, 22000],
|
||||
],
|
||||
),
|
||||
(
|
||||
[{}, {"a": 1}, {"b": 1}, {"a": 1, "b": 1}],
|
||||
[
|
||||
[10, 20, 40, 60],
|
||||
[100, 2000, 400, 6000],
|
||||
[7100, 22000, 4000, 30000],
|
||||
],
|
||||
),
|
||||
(
|
||||
[{}, {"a": 1}],
|
||||
[
|
||||
[10, 20],
|
||||
[42000, 100],
|
||||
[100, 52000],
|
||||
],
|
||||
),
|
||||
(
|
||||
[{}, {"a": 1}, {"b": 1}, {"a": 1, "b": 1}],
|
||||
[
|
||||
[10, 20, 40, 60],
|
||||
[40000, 42000, 400, 6000],
|
||||
[100, 22000, 4000, 173000],
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_onlineVarStoreBuilder(locations, masterValues):
|
||||
axisTags = sorted({k for loc in locations for k in loc})
|
||||
model = VariationModel(locations)
|
||||
builder = OnlineVarStoreBuilder(axisTags)
|
||||
builder.setModel(model)
|
||||
varIdxs = []
|
||||
for masters in masterValues:
|
||||
_, varIdx = builder.storeMasters(masters)
|
||||
varIdxs.append(varIdx)
|
||||
|
||||
varStore = builder.finish()
|
||||
mapping = varStore.optimize()
|
||||
varIdxs = [mapping[varIdx] for varIdx in varIdxs]
|
||||
|
||||
dummyFont = TTFont()
|
||||
writer = OTTableWriter()
|
||||
varStore.compile(writer, dummyFont)
|
||||
data = writer.getAllData()
|
||||
reader = OTTableReader(data)
|
||||
varStore = VarStore()
|
||||
varStore.decompile(reader, dummyFont)
|
||||
|
||||
fvarAxes = [buildAxis(axisTag) for axisTag in axisTags]
|
||||
instancer = VarStoreInstancer(varStore, fvarAxes)
|
||||
for masters, varIdx in zip(masterValues, varIdxs):
|
||||
base, *rest = masters
|
||||
for expectedValue, loc in zip(masters, locations):
|
||||
instancer.setLocation(loc)
|
||||
value = base + instancer[varIdx]
|
||||
assert expectedValue == value
|
||||
|
||||
|
||||
def buildAxis(axisTag):
|
||||
axis = Axis()
|
||||
axis.axisTag = axisTag
|
||||
return axis
|
Loading…
x
Reference in New Issue
Block a user