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