xref: /aosp_15_r20/external/fonttools/Tests/varLib/varStore_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
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