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