diff --git a/Lib/fontTools/varLib/varStore.py b/Lib/fontTools/varLib/varStore.py index dcc36bb1e..f05f41f5d 100644 --- a/Lib/fontTools/varLib/varStore.py +++ b/Lib/fontTools/varLib/varStore.py @@ -470,7 +470,7 @@ class _EncodingDict(dict): return chars -def VarStore_optimize(self, use_NO_VARIATION_INDEX=True): +def VarStore_optimize(self, use_NO_VARIATION_INDEX=True, quantization=1): """Optimize storage. Returns mapping from old VarIdxes to new ones.""" # Overview: @@ -551,8 +551,16 @@ def VarStore_optimize(self, use_NO_VARIATION_INDEX=True): for minor, item in enumerate(data.Item): row = list(zeroes) - for regionIdx, v in zip(regionIndices, item): - row[regionIdx] += v + + if quantization == 1: + for regionIdx, v in zip(regionIndices, item): + row[regionIdx] += v + else: + for regionIdx, v in zip(regionIndices, item): + row[regionIdx] += ( + round(v / quantization) * quantization + ) # TODO https://github.com/fonttools/fonttools/pull/3126#discussion_r1205439785 + row = tuple(row) if use_NO_VARIATION_INDEX and not any(row): @@ -684,6 +692,7 @@ def main(args=None): from fontTools.ttLib.tables.otBase import OTTableWriter parser = ArgumentParser(prog="varLib.varStore", description=main.__doc__) + parser.add_argument("--quantization", type=int, default=1) parser.add_argument("fontfile") parser.add_argument("outfile", nargs="?") options = parser.parse_args(args) @@ -691,6 +700,7 @@ def main(args=None): # TODO: allow user to configure logging via command-line options configLogger(level="INFO") + quantization = options.quantization fontfile = options.fontfile outfile = options.outfile @@ -703,7 +713,7 @@ def main(args=None): size = len(writer.getAllData()) print("Before: %7d bytes" % size) - varidx_map = store.optimize() + varidx_map = store.optimize(quantization=quantization) writer = OTTableWriter() store.compile(writer, font) diff --git a/Tests/varLib/varStore_test.py b/Tests/varLib/varStore_test.py index 37447c051..70d1340f2 100644 --- a/Tests/varLib/varStore_test.py +++ b/Tests/varLib/varStore_test.py @@ -202,3 +202,57 @@ def test_optimize(numRegions, varData, expectedNumVarData, expectedBytes): data = writer.getAllData() assert len(data) == expectedBytes, xml + + +@pytest.mark.parametrize( + "quantization, expectedBytes", + [ + (1, 200), + (2, 180), + (3, 170), + (4, 175), + (8, 170), + (32, 152), + (64, 146), + ], +) +def test_quantize(quantization, expectedBytes): + varData = [ + [0, 11, 12, 0, 20], + [0, 13, 12, 0, 20], + [0, 14, 12, 0, 20], + [0, 15, 12, 0, 20], + [0, 16, 12, 0, 20], + [10, 300, 0, 0, 20], + [10, 301, 0, 0, 20], + [10, 302, 0, 0, 20], + [10, 303, 0, 0, 20], + [10, 304, 0, 0, 20], + ] + + numRegions = 5 + locations = [{i: i / 16384.0} for i in range(numRegions)] + axisTags = sorted({k for loc in locations for k in loc}) + + model = VariationModel(locations) + + builder = OnlineVarStoreBuilder(axisTags) + builder.setModel(model) + + for data in varData: + builder.storeMasters(data) + + varStore = builder.finish() + varStore.optimize(quantization=quantization) + + dummyFont = TTFont() + + writer = XMLWriter(StringIO()) + varStore.toXML(writer, dummyFont) + xml = writer.file.getvalue() + + writer = OTTableWriter() + varStore.compile(writer, dummyFont) + data = writer.getAllData() + + assert len(data) == expectedBytes, xml