import pytest import random from io import StringIO from fontTools.misc.xmlWriter import XMLWriter from fontTools.misc.roundTools import noRound 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, 10], # Test NO_VARIATION_INDEX [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], ], ), ( [{}, {"a": 1}, {"b": 1}, {"a": 1, "b": 1}], [ [random.randint(-128, 127) for _ in range(4)], [random.randint(-128, 127) for _ in range(4)], [random.randint(-128, 127) for _ in range(4)], [random.randint(-32768, 32767) for _ in range(4)], [random.randint(-32768, 32767) for _ in range(4)], [random.randint(-32768, 32767) for _ in range(4)], ], ), ], ) 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 = [] # shuffle input order to ensure optimizer produces stable results random.shuffle(masterValues) 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 @pytest.mark.parametrize( "numRegions, varData, expectedNumVarData, expectedBytes", [ ( 5, [ [10, 10, 0, 0, 20], {3: 300}, ], 1, 126, ), ( 5, [ [10, 10, 0, 0, 20], [10, 11, 0, 0, 20], [10, 12, 0, 0, 20], [10, 13, 0, 0, 20], {3: 300}, ], 1, 175, ), ( 5, [ [10, 11, 0, 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], ], 1, 180, ), ( 5, [ [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], ], 1, 200, ), ( 5, [ [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], [0, 17, 12, 0, 20], [0, 18, 12, 0, 20], [0, 19, 12, 0, 20], [0, 20, 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], ], 2, 218, ), ( 3, [ [10, 10, 10], ], 0, 12, ), ], ) def test_optimize(numRegions, varData, expectedNumVarData, expectedBytes): 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) random.shuffle(varData) for data in varData: if type(data) is dict: newData = [0] * numRegions for k, v in data.items(): newData[k] = v data = newData builder.storeMasters(data) varStore = builder.finish() varStore.optimize() dummyFont = TTFont() writer = XMLWriter(StringIO()) varStore.toXML(writer, dummyFont) xml = writer.file.getvalue() assert len(varStore.VarData) == expectedNumVarData, xml writer = OTTableWriter() varStore.compile(writer, dummyFont) data = writer.getAllData() assert len(data) == expectedBytes, xml @pytest.mark.parametrize( "quantization, expectedBytes", [ (1, 200), (2, 180), (3, 170), (4, 175), (8, 170), (32, 92), (64, 56), ], ) 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) random.shuffle(varData) 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 def test_optimize_overflow(): numRegions = 1 locations = [{"wght": 0}, {"wght": 0.5}] axisTags = ["wght"] model = VariationModel(locations) builder = OnlineVarStoreBuilder(axisTags) builder.setModel(model) varData = list(range(0, 0xFFFF * 2)) random.shuffle(varData) for data in varData: data = [0, data] builder.storeMasters(data, round=noRound) varStore = builder.finish() varStore.optimize() for s in varStore.VarData: print(len(s.Item)) # 5 data-sets: # - 0..127: 1-byte dataset # - 128..32767: 2-byte dataset # - 32768..32768+65535-1: 4-byte dataset # - 32768+65535..65535+65535-1: 4-byte dataset assert len(varStore.VarData) == 4