1*e1fe3e4aSElliott Hughesimport pytest 2*e1fe3e4aSElliott Hughesfrom io import StringIO 3*e1fe3e4aSElliott Hughesfrom fontTools.misc.xmlWriter import XMLWriter 4*e1fe3e4aSElliott Hughesfrom fontTools.misc.roundTools import noRound 5*e1fe3e4aSElliott Hughesfrom fontTools.varLib.models import VariationModel 6*e1fe3e4aSElliott Hughesfrom fontTools.varLib.varStore import OnlineVarStoreBuilder, VarStoreInstancer 7*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import TTFont, newTable 8*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables._f_v_a_r import Axis 9*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables.otBase import OTTableReader, OTTableWriter 10*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables.otTables import VarStore 11*e1fe3e4aSElliott Hughes 12*e1fe3e4aSElliott Hughes 13*e1fe3e4aSElliott Hughes@pytest.mark.parametrize( 14*e1fe3e4aSElliott Hughes "locations, masterValues", 15*e1fe3e4aSElliott Hughes [ 16*e1fe3e4aSElliott Hughes ( 17*e1fe3e4aSElliott Hughes [{}, {"a": 1}], 18*e1fe3e4aSElliott Hughes [ 19*e1fe3e4aSElliott Hughes [10, 10], # Test NO_VARIATION_INDEX 20*e1fe3e4aSElliott Hughes [100, 2000], 21*e1fe3e4aSElliott Hughes [100, 22000], 22*e1fe3e4aSElliott Hughes ], 23*e1fe3e4aSElliott Hughes ), 24*e1fe3e4aSElliott Hughes ( 25*e1fe3e4aSElliott Hughes [{}, {"a": 1}, {"b": 1}, {"a": 1, "b": 1}], 26*e1fe3e4aSElliott Hughes [ 27*e1fe3e4aSElliott Hughes [10, 20, 40, 60], 28*e1fe3e4aSElliott Hughes [100, 2000, 400, 6000], 29*e1fe3e4aSElliott Hughes [7100, 22000, 4000, 30000], 30*e1fe3e4aSElliott Hughes ], 31*e1fe3e4aSElliott Hughes ), 32*e1fe3e4aSElliott Hughes ( 33*e1fe3e4aSElliott Hughes [{}, {"a": 1}], 34*e1fe3e4aSElliott Hughes [ 35*e1fe3e4aSElliott Hughes [10, 20], 36*e1fe3e4aSElliott Hughes [42000, 100], 37*e1fe3e4aSElliott Hughes [100, 52000], 38*e1fe3e4aSElliott Hughes ], 39*e1fe3e4aSElliott Hughes ), 40*e1fe3e4aSElliott Hughes ( 41*e1fe3e4aSElliott Hughes [{}, {"a": 1}, {"b": 1}, {"a": 1, "b": 1}], 42*e1fe3e4aSElliott Hughes [ 43*e1fe3e4aSElliott Hughes [10, 20, 40, 60], 44*e1fe3e4aSElliott Hughes [40000, 42000, 400, 6000], 45*e1fe3e4aSElliott Hughes [100, 22000, 4000, 173000], 46*e1fe3e4aSElliott Hughes ], 47*e1fe3e4aSElliott Hughes ), 48*e1fe3e4aSElliott Hughes ], 49*e1fe3e4aSElliott Hughes) 50*e1fe3e4aSElliott Hughesdef test_onlineVarStoreBuilder(locations, masterValues): 51*e1fe3e4aSElliott Hughes axisTags = sorted({k for loc in locations for k in loc}) 52*e1fe3e4aSElliott Hughes model = VariationModel(locations) 53*e1fe3e4aSElliott Hughes builder = OnlineVarStoreBuilder(axisTags) 54*e1fe3e4aSElliott Hughes builder.setModel(model) 55*e1fe3e4aSElliott Hughes varIdxs = [] 56*e1fe3e4aSElliott Hughes for masters in masterValues: 57*e1fe3e4aSElliott Hughes _, varIdx = builder.storeMasters(masters) 58*e1fe3e4aSElliott Hughes varIdxs.append(varIdx) 59*e1fe3e4aSElliott Hughes 60*e1fe3e4aSElliott Hughes varStore = builder.finish() 61*e1fe3e4aSElliott Hughes mapping = varStore.optimize() 62*e1fe3e4aSElliott Hughes varIdxs = [mapping[varIdx] for varIdx in varIdxs] 63*e1fe3e4aSElliott Hughes 64*e1fe3e4aSElliott Hughes dummyFont = TTFont() 65*e1fe3e4aSElliott Hughes writer = OTTableWriter() 66*e1fe3e4aSElliott Hughes varStore.compile(writer, dummyFont) 67*e1fe3e4aSElliott Hughes data = writer.getAllData() 68*e1fe3e4aSElliott Hughes reader = OTTableReader(data) 69*e1fe3e4aSElliott Hughes varStore = VarStore() 70*e1fe3e4aSElliott Hughes varStore.decompile(reader, dummyFont) 71*e1fe3e4aSElliott Hughes 72*e1fe3e4aSElliott Hughes fvarAxes = [buildAxis(axisTag) for axisTag in axisTags] 73*e1fe3e4aSElliott Hughes instancer = VarStoreInstancer(varStore, fvarAxes) 74*e1fe3e4aSElliott Hughes for masters, varIdx in zip(masterValues, varIdxs): 75*e1fe3e4aSElliott Hughes base, *rest = masters 76*e1fe3e4aSElliott Hughes for expectedValue, loc in zip(masters, locations): 77*e1fe3e4aSElliott Hughes instancer.setLocation(loc) 78*e1fe3e4aSElliott Hughes value = base + instancer[varIdx] 79*e1fe3e4aSElliott Hughes assert expectedValue == value 80*e1fe3e4aSElliott Hughes 81*e1fe3e4aSElliott Hughes 82*e1fe3e4aSElliott Hughesdef buildAxis(axisTag): 83*e1fe3e4aSElliott Hughes axis = Axis() 84*e1fe3e4aSElliott Hughes axis.axisTag = axisTag 85*e1fe3e4aSElliott Hughes return axis 86*e1fe3e4aSElliott Hughes 87*e1fe3e4aSElliott Hughes 88*e1fe3e4aSElliott Hughes@pytest.mark.parametrize( 89*e1fe3e4aSElliott Hughes "numRegions, varData, expectedNumVarData, expectedBytes", 90*e1fe3e4aSElliott Hughes [ 91*e1fe3e4aSElliott Hughes ( 92*e1fe3e4aSElliott Hughes 5, 93*e1fe3e4aSElliott Hughes [ 94*e1fe3e4aSElliott Hughes [10, 10, 0, 0, 20], 95*e1fe3e4aSElliott Hughes {3: 300}, 96*e1fe3e4aSElliott Hughes ], 97*e1fe3e4aSElliott Hughes 1, 98*e1fe3e4aSElliott Hughes 126, 99*e1fe3e4aSElliott Hughes ), 100*e1fe3e4aSElliott Hughes ( 101*e1fe3e4aSElliott Hughes 5, 102*e1fe3e4aSElliott Hughes [ 103*e1fe3e4aSElliott Hughes [10, 10, 0, 0, 20], 104*e1fe3e4aSElliott Hughes [10, 11, 0, 0, 20], 105*e1fe3e4aSElliott Hughes [10, 12, 0, 0, 20], 106*e1fe3e4aSElliott Hughes [10, 13, 0, 0, 20], 107*e1fe3e4aSElliott Hughes {3: 300}, 108*e1fe3e4aSElliott Hughes ], 109*e1fe3e4aSElliott Hughes 1, 110*e1fe3e4aSElliott Hughes 175, 111*e1fe3e4aSElliott Hughes ), 112*e1fe3e4aSElliott Hughes ( 113*e1fe3e4aSElliott Hughes 5, 114*e1fe3e4aSElliott Hughes [ 115*e1fe3e4aSElliott Hughes [10, 11, 0, 0, 20], 116*e1fe3e4aSElliott Hughes [10, 300, 0, 0, 20], 117*e1fe3e4aSElliott Hughes [10, 301, 0, 0, 20], 118*e1fe3e4aSElliott Hughes [10, 302, 0, 0, 20], 119*e1fe3e4aSElliott Hughes [10, 303, 0, 0, 20], 120*e1fe3e4aSElliott Hughes [10, 304, 0, 0, 20], 121*e1fe3e4aSElliott Hughes ], 122*e1fe3e4aSElliott Hughes 1, 123*e1fe3e4aSElliott Hughes 180, 124*e1fe3e4aSElliott Hughes ), 125*e1fe3e4aSElliott Hughes ( 126*e1fe3e4aSElliott Hughes 5, 127*e1fe3e4aSElliott Hughes [ 128*e1fe3e4aSElliott Hughes [0, 11, 12, 0, 20], 129*e1fe3e4aSElliott Hughes [0, 13, 12, 0, 20], 130*e1fe3e4aSElliott Hughes [0, 14, 12, 0, 20], 131*e1fe3e4aSElliott Hughes [0, 15, 12, 0, 20], 132*e1fe3e4aSElliott Hughes [0, 16, 12, 0, 20], 133*e1fe3e4aSElliott Hughes [10, 300, 0, 0, 20], 134*e1fe3e4aSElliott Hughes [10, 301, 0, 0, 20], 135*e1fe3e4aSElliott Hughes [10, 302, 0, 0, 20], 136*e1fe3e4aSElliott Hughes [10, 303, 0, 0, 20], 137*e1fe3e4aSElliott Hughes [10, 304, 0, 0, 20], 138*e1fe3e4aSElliott Hughes ], 139*e1fe3e4aSElliott Hughes 1, 140*e1fe3e4aSElliott Hughes 200, 141*e1fe3e4aSElliott Hughes ), 142*e1fe3e4aSElliott Hughes ( 143*e1fe3e4aSElliott Hughes 5, 144*e1fe3e4aSElliott Hughes [ 145*e1fe3e4aSElliott Hughes [0, 11, 12, 0, 20], 146*e1fe3e4aSElliott Hughes [0, 13, 12, 0, 20], 147*e1fe3e4aSElliott Hughes [0, 14, 12, 0, 20], 148*e1fe3e4aSElliott Hughes [0, 15, 12, 0, 20], 149*e1fe3e4aSElliott Hughes [0, 16, 12, 0, 20], 150*e1fe3e4aSElliott Hughes [0, 17, 12, 0, 20], 151*e1fe3e4aSElliott Hughes [0, 18, 12, 0, 20], 152*e1fe3e4aSElliott Hughes [0, 19, 12, 0, 20], 153*e1fe3e4aSElliott Hughes [0, 20, 12, 0, 20], 154*e1fe3e4aSElliott Hughes [10, 300, 0, 0, 20], 155*e1fe3e4aSElliott Hughes [10, 301, 0, 0, 20], 156*e1fe3e4aSElliott Hughes [10, 302, 0, 0, 20], 157*e1fe3e4aSElliott Hughes [10, 303, 0, 0, 20], 158*e1fe3e4aSElliott Hughes [10, 304, 0, 0, 20], 159*e1fe3e4aSElliott Hughes ], 160*e1fe3e4aSElliott Hughes 2, 161*e1fe3e4aSElliott Hughes 218, 162*e1fe3e4aSElliott Hughes ), 163*e1fe3e4aSElliott Hughes ( 164*e1fe3e4aSElliott Hughes 3, 165*e1fe3e4aSElliott Hughes [ 166*e1fe3e4aSElliott Hughes [10, 10, 10], 167*e1fe3e4aSElliott Hughes ], 168*e1fe3e4aSElliott Hughes 0, 169*e1fe3e4aSElliott Hughes 12, 170*e1fe3e4aSElliott Hughes ), 171*e1fe3e4aSElliott Hughes ], 172*e1fe3e4aSElliott Hughes) 173*e1fe3e4aSElliott Hughesdef test_optimize(numRegions, varData, expectedNumVarData, expectedBytes): 174*e1fe3e4aSElliott Hughes locations = [{i: i / 16384.0} for i in range(numRegions)] 175*e1fe3e4aSElliott Hughes axisTags = sorted({k for loc in locations for k in loc}) 176*e1fe3e4aSElliott Hughes 177*e1fe3e4aSElliott Hughes model = VariationModel(locations) 178*e1fe3e4aSElliott Hughes builder = OnlineVarStoreBuilder(axisTags) 179*e1fe3e4aSElliott Hughes builder.setModel(model) 180*e1fe3e4aSElliott Hughes 181*e1fe3e4aSElliott Hughes for data in varData: 182*e1fe3e4aSElliott Hughes if type(data) is dict: 183*e1fe3e4aSElliott Hughes newData = [0] * numRegions 184*e1fe3e4aSElliott Hughes for k, v in data.items(): 185*e1fe3e4aSElliott Hughes newData[k] = v 186*e1fe3e4aSElliott Hughes data = newData 187*e1fe3e4aSElliott Hughes 188*e1fe3e4aSElliott Hughes builder.storeMasters(data) 189*e1fe3e4aSElliott Hughes 190*e1fe3e4aSElliott Hughes varStore = builder.finish() 191*e1fe3e4aSElliott Hughes varStore.optimize() 192*e1fe3e4aSElliott Hughes 193*e1fe3e4aSElliott Hughes dummyFont = TTFont() 194*e1fe3e4aSElliott Hughes 195*e1fe3e4aSElliott Hughes writer = XMLWriter(StringIO()) 196*e1fe3e4aSElliott Hughes varStore.toXML(writer, dummyFont) 197*e1fe3e4aSElliott Hughes xml = writer.file.getvalue() 198*e1fe3e4aSElliott Hughes 199*e1fe3e4aSElliott Hughes assert len(varStore.VarData) == expectedNumVarData, xml 200*e1fe3e4aSElliott Hughes 201*e1fe3e4aSElliott Hughes writer = OTTableWriter() 202*e1fe3e4aSElliott Hughes varStore.compile(writer, dummyFont) 203*e1fe3e4aSElliott Hughes data = writer.getAllData() 204*e1fe3e4aSElliott Hughes 205*e1fe3e4aSElliott Hughes assert len(data) == expectedBytes, xml 206*e1fe3e4aSElliott Hughes 207*e1fe3e4aSElliott Hughes 208*e1fe3e4aSElliott Hughes@pytest.mark.parametrize( 209*e1fe3e4aSElliott Hughes "quantization, expectedBytes", 210*e1fe3e4aSElliott Hughes [ 211*e1fe3e4aSElliott Hughes (1, 200), 212*e1fe3e4aSElliott Hughes (2, 180), 213*e1fe3e4aSElliott Hughes (3, 170), 214*e1fe3e4aSElliott Hughes (4, 175), 215*e1fe3e4aSElliott Hughes (8, 170), 216*e1fe3e4aSElliott Hughes (32, 92), 217*e1fe3e4aSElliott Hughes (64, 56), 218*e1fe3e4aSElliott Hughes ], 219*e1fe3e4aSElliott Hughes) 220*e1fe3e4aSElliott Hughesdef test_quantize(quantization, expectedBytes): 221*e1fe3e4aSElliott Hughes varData = [ 222*e1fe3e4aSElliott Hughes [0, 11, 12, 0, 20], 223*e1fe3e4aSElliott Hughes [0, 13, 12, 0, 20], 224*e1fe3e4aSElliott Hughes [0, 14, 12, 0, 20], 225*e1fe3e4aSElliott Hughes [0, 15, 12, 0, 20], 226*e1fe3e4aSElliott Hughes [0, 16, 12, 0, 20], 227*e1fe3e4aSElliott Hughes [10, 300, 0, 0, 20], 228*e1fe3e4aSElliott Hughes [10, 301, 0, 0, 20], 229*e1fe3e4aSElliott Hughes [10, 302, 0, 0, 20], 230*e1fe3e4aSElliott Hughes [10, 303, 0, 0, 20], 231*e1fe3e4aSElliott Hughes [10, 304, 0, 0, 20], 232*e1fe3e4aSElliott Hughes ] 233*e1fe3e4aSElliott Hughes 234*e1fe3e4aSElliott Hughes numRegions = 5 235*e1fe3e4aSElliott Hughes locations = [{i: i / 16384.0} for i in range(numRegions)] 236*e1fe3e4aSElliott Hughes axisTags = sorted({k for loc in locations for k in loc}) 237*e1fe3e4aSElliott Hughes 238*e1fe3e4aSElliott Hughes model = VariationModel(locations) 239*e1fe3e4aSElliott Hughes 240*e1fe3e4aSElliott Hughes builder = OnlineVarStoreBuilder(axisTags) 241*e1fe3e4aSElliott Hughes builder.setModel(model) 242*e1fe3e4aSElliott Hughes 243*e1fe3e4aSElliott Hughes for data in varData: 244*e1fe3e4aSElliott Hughes builder.storeMasters(data) 245*e1fe3e4aSElliott Hughes 246*e1fe3e4aSElliott Hughes varStore = builder.finish() 247*e1fe3e4aSElliott Hughes varStore.optimize(quantization=quantization) 248*e1fe3e4aSElliott Hughes 249*e1fe3e4aSElliott Hughes dummyFont = TTFont() 250*e1fe3e4aSElliott Hughes 251*e1fe3e4aSElliott Hughes writer = XMLWriter(StringIO()) 252*e1fe3e4aSElliott Hughes varStore.toXML(writer, dummyFont) 253*e1fe3e4aSElliott Hughes xml = writer.file.getvalue() 254*e1fe3e4aSElliott Hughes 255*e1fe3e4aSElliott Hughes writer = OTTableWriter() 256*e1fe3e4aSElliott Hughes varStore.compile(writer, dummyFont) 257*e1fe3e4aSElliott Hughes data = writer.getAllData() 258*e1fe3e4aSElliott Hughes 259*e1fe3e4aSElliott Hughes assert len(data) == expectedBytes, xml 260*e1fe3e4aSElliott Hughes 261*e1fe3e4aSElliott Hughes 262*e1fe3e4aSElliott Hughesdef test_optimize_overflow(): 263*e1fe3e4aSElliott Hughes numRegions = 1 264*e1fe3e4aSElliott Hughes locations = [{"wght": 0}, {"wght": 0.5}] 265*e1fe3e4aSElliott Hughes axisTags = ["wght"] 266*e1fe3e4aSElliott Hughes 267*e1fe3e4aSElliott Hughes model = VariationModel(locations) 268*e1fe3e4aSElliott Hughes builder = OnlineVarStoreBuilder(axisTags) 269*e1fe3e4aSElliott Hughes builder.setModel(model) 270*e1fe3e4aSElliott Hughes 271*e1fe3e4aSElliott Hughes for data in range(0, 0xFFFF * 2): 272*e1fe3e4aSElliott Hughes data = [0, data] 273*e1fe3e4aSElliott Hughes builder.storeMasters(data, round=noRound) 274*e1fe3e4aSElliott Hughes 275*e1fe3e4aSElliott Hughes varStore = builder.finish() 276*e1fe3e4aSElliott Hughes varStore.optimize() 277*e1fe3e4aSElliott Hughes 278*e1fe3e4aSElliott Hughes for s in varStore.VarData: 279*e1fe3e4aSElliott Hughes print(len(s.Item)) 280*e1fe3e4aSElliott Hughes 281*e1fe3e4aSElliott Hughes # 5 data-sets: 282*e1fe3e4aSElliott Hughes # - 0..127: 1-byte dataset 283*e1fe3e4aSElliott Hughes # - 128..32767: 2-byte dataset 284*e1fe3e4aSElliott Hughes # - 32768..32768+65535-1: 4-byte dataset 285*e1fe3e4aSElliott Hughes # - 32768+65535..65535+65535-1: 4-byte dataset 286*e1fe3e4aSElliott Hughes assert len(varStore.VarData) == 4 287