aboutsummaryrefslogtreecommitdiff
path: root/Tests/ttLib/tables/_f_v_a_r_test.py
blob: 2ac5237f14cc9985ee35cb62aab84a6615124f0f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
from fontTools.misc.testTools import parseXML
from fontTools.misc.textTools import deHexStr
from fontTools.misc.xmlWriter import XMLWriter
from fontTools.ttLib import TTLibError
from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis, NamedInstance
from fontTools.ttLib.tables._n_a_m_e import table__n_a_m_e, NameRecord
from io import BytesIO
import unittest



FVAR_DATA = deHexStr(
    "00 01 00 00 00 10 00 02 00 02 00 14 00 02 00 0C "
    "77 67 68 74 00 64 00 00 01 90 00 00 03 84 00 00 00 00 01 01 "
    "77 64 74 68 00 32 00 00 00 64 00 00 00 c8 00 00 00 00 01 02 "
    "01 03 00 00 01 2c 00 00 00 64 00 00 "
    "01 04 00 00 01 2c 00 00 00 4b 00 00")

FVAR_AXIS_DATA = deHexStr(
    "6F 70 73 7a ff ff 80 00 00 01 4c cd 00 01 80 00 00 00 01 59")

FVAR_INSTANCE_DATA_WITHOUT_PSNAME = deHexStr(
    "01 59 00 00 00 00 b3 33 00 00 80 00")

FVAR_INSTANCE_DATA_WITH_PSNAME = (
    FVAR_INSTANCE_DATA_WITHOUT_PSNAME + deHexStr("02 34"))


def xml_lines(writer):
    content = writer.file.getvalue().decode("utf-8")
    return [line.strip() for line in content.splitlines()][1:]


def AddName(font, name):
    nameTable = font.get("name")
    if nameTable is None:
        nameTable = font["name"] = table__n_a_m_e()
        nameTable.names = []
    namerec = NameRecord()
    namerec.nameID = 1 + max([n.nameID for n in nameTable.names] + [256])
    namerec.string = name.encode('mac_roman')
    namerec.platformID, namerec.platEncID, namerec.langID = (1, 0, 0)
    nameTable.names.append(namerec)
    return namerec


def MakeFont():
    axes = [("wght", "Weight", 100, 400, 900), ("wdth", "Width", 50, 100, 200)]
    instances = [("Light", 300, 100), ("Light Condensed", 300, 75)]
    fvarTable = table__f_v_a_r()
    font = {"fvar": fvarTable}
    for tag, name, minValue, defaultValue, maxValue in axes:
        axis = Axis()
        axis.axisTag = tag
        axis.defaultValue = defaultValue
        axis.minValue, axis.maxValue = minValue, maxValue
        axis.axisNameID = AddName(font, name).nameID
        fvarTable.axes.append(axis)
    for name, weight, width in instances:
        inst = NamedInstance()
        inst.subfamilyNameID = AddName(font, name).nameID
        inst.coordinates = {"wght": weight, "wdth": width}
        fvarTable.instances.append(inst)
    return font


class FontVariationTableTest(unittest.TestCase):
    def test_compile(self):
        font = MakeFont()
        h = font["fvar"].compile(font)
        self.assertEqual(FVAR_DATA, font["fvar"].compile(font))

    def test_decompile(self):
        fvar = table__f_v_a_r()
        fvar.decompile(FVAR_DATA, ttFont={"fvar": fvar})
        self.assertEqual(["wght", "wdth"], [a.axisTag for a in fvar.axes])
        self.assertEqual([259, 260], [i.subfamilyNameID for i in fvar.instances])

    def test_toXML(self):
        font = MakeFont()
        writer = XMLWriter(BytesIO())
        font["fvar"].toXML(writer, font)
        xml = writer.file.getvalue().decode("utf-8")
        self.assertEqual(2, xml.count("<Axis>"))
        self.assertTrue("<AxisTag>wght</AxisTag>" in xml)
        self.assertTrue("<AxisTag>wdth</AxisTag>" in xml)
        self.assertEqual(2, xml.count("<NamedInstance "))
        self.assertTrue("<!-- Light -->" in xml)
        self.assertTrue("<!-- Light Condensed -->" in xml)

    def test_fromXML(self):
        fvar = table__f_v_a_r()
        for name, attrs, content in parseXML(
                '<Axis>'
                '    <AxisTag>opsz</AxisTag>'
                '</Axis>'
                '<Axis>'
                '    <AxisTag>slnt</AxisTag>'
                '    <Flags>0x123</Flags>'
                '</Axis>'
                '<NamedInstance subfamilyNameID="765"/>'
                '<NamedInstance subfamilyNameID="234"/>'):
            fvar.fromXML(name, attrs, content, ttFont=None)
        self.assertEqual(["opsz", "slnt"], [a.axisTag for a in fvar.axes])
        self.assertEqual([0, 0x123], [a.flags for a in fvar.axes])
        self.assertEqual([765, 234], [i.subfamilyNameID for i in fvar.instances])


class AxisTest(unittest.TestCase):
    def test_compile(self):
        axis = Axis()
        axis.axisTag, axis.axisNameID = ('opsz', 345)
        axis.minValue, axis.defaultValue, axis.maxValue = (-0.5, 1.3, 1.5)
        self.assertEqual(FVAR_AXIS_DATA, axis.compile())

    def test_decompile(self):
        axis = Axis()
        axis.decompile(FVAR_AXIS_DATA)
        self.assertEqual("opsz", axis.axisTag)
        self.assertEqual(345, axis.axisNameID)
        self.assertEqual(-0.5, axis.minValue)
        self.assertAlmostEqual(1.3000031, axis.defaultValue)
        self.assertEqual(1.5, axis.maxValue)

    def test_toXML(self):
        font = MakeFont()
        axis = Axis()
        axis.decompile(FVAR_AXIS_DATA)
        AddName(font, "Optical Size").nameID = 256
        axis.axisNameID = 256
        axis.flags = 0xABC
        writer = XMLWriter(BytesIO())
        axis.toXML(writer, font)
        self.assertEqual([
            '',
            '<!-- Optical Size -->',
            '<Axis>',
                '<AxisTag>opsz</AxisTag>',
                '<Flags>0xABC</Flags>',
                '<MinValue>-0.5</MinValue>',
                '<DefaultValue>1.3</DefaultValue>',
                '<MaxValue>1.5</MaxValue>',
                '<AxisNameID>256</AxisNameID>',
            '</Axis>'
        ], xml_lines(writer))

    def test_fromXML(self):
        axis = Axis()
        for name, attrs, content in parseXML(
                '<Axis>'
                '    <AxisTag>wght</AxisTag>'
                '    <Flags>0x123ABC</Flags>'
                '    <MinValue>100</MinValue>'
                '    <DefaultValue>400</DefaultValue>'
                '    <MaxValue>900</MaxValue>'
                '    <AxisNameID>256</AxisNameID>'
                '</Axis>'):
            axis.fromXML(name, attrs, content, ttFont=None)
        self.assertEqual("wght", axis.axisTag)
        self.assertEqual(0x123ABC, axis.flags)
        self.assertEqual(100, axis.minValue)
        self.assertEqual(400, axis.defaultValue)
        self.assertEqual(900, axis.maxValue)
        self.assertEqual(256, axis.axisNameID)


class NamedInstanceTest(unittest.TestCase):
    def assertDictAlmostEqual(self, dict1, dict2):
        self.assertEqual(set(dict1.keys()), set(dict2.keys()))
        for key in dict1:
            self.assertAlmostEqual(dict1[key], dict2[key])

    def test_compile_withPostScriptName(self):
        inst = NamedInstance()
        inst.subfamilyNameID = 345
        inst.postscriptNameID = 564
        inst.coordinates = {"wght": 0.7, "wdth": 0.5}
        self.assertEqual(FVAR_INSTANCE_DATA_WITH_PSNAME,
                         inst.compile(["wght", "wdth"], True))

    def test_compile_withoutPostScriptName(self):
        inst = NamedInstance()
        inst.subfamilyNameID = 345
        inst.postscriptNameID = 564
        inst.coordinates = {"wght": 0.7, "wdth": 0.5}
        self.assertEqual(FVAR_INSTANCE_DATA_WITHOUT_PSNAME,
                         inst.compile(["wght", "wdth"], False))

    def test_decompile_withPostScriptName(self):
        inst = NamedInstance()
        inst.decompile(FVAR_INSTANCE_DATA_WITH_PSNAME, ["wght", "wdth"])
        self.assertEqual(564, inst.postscriptNameID)
        self.assertEqual(345, inst.subfamilyNameID)
        self.assertDictAlmostEqual({"wght": 0.6999969, "wdth": 0.5}, inst.coordinates)

    def test_decompile_withoutPostScriptName(self):
        inst = NamedInstance()
        inst.decompile(FVAR_INSTANCE_DATA_WITHOUT_PSNAME, ["wght", "wdth"])
        self.assertEqual(0xFFFF, inst.postscriptNameID)
        self.assertEqual(345, inst.subfamilyNameID)
        self.assertDictAlmostEqual({"wght": 0.6999969, "wdth": 0.5}, inst.coordinates)

    def test_toXML_withPostScriptName(self):
        font = MakeFont()
        inst = NamedInstance()
        inst.flags = 0xE9
        inst.subfamilyNameID = AddName(font, "Light Condensed").nameID
        inst.postscriptNameID = AddName(font, "Test-LightCondensed").nameID
        inst.coordinates = {"wght": 0.7, "wdth": 0.5}
        writer = XMLWriter(BytesIO())
        inst.toXML(writer, font)
        self.assertEqual([
            '',
            '<!-- Light Condensed -->',
            '<!-- PostScript: Test-LightCondensed -->',
            '<NamedInstance flags="0xE9" postscriptNameID="%s" subfamilyNameID="%s">' % (
                inst.postscriptNameID, inst.subfamilyNameID),
              '<coord axis="wght" value="0.7"/>',
              '<coord axis="wdth" value="0.5"/>',
            '</NamedInstance>'
        ], xml_lines(writer))

    def test_toXML_withoutPostScriptName(self):
        font = MakeFont()
        inst = NamedInstance()
        inst.flags = 0xABC
        inst.subfamilyNameID = AddName(font, "Light Condensed").nameID
        inst.coordinates = {"wght": 0.7, "wdth": 0.5}
        writer = XMLWriter(BytesIO())
        inst.toXML(writer, font)
        self.assertEqual([
            '',
            '<!-- Light Condensed -->',
            '<NamedInstance flags="0xABC" subfamilyNameID="%s">' %
                inst.subfamilyNameID,
              '<coord axis="wght" value="0.7"/>',
              '<coord axis="wdth" value="0.5"/>',
            '</NamedInstance>'
        ], xml_lines(writer))

    def test_fromXML_withPostScriptName(self):
        inst = NamedInstance()
        for name, attrs, content in parseXML(
                '<NamedInstance flags="0x0" postscriptNameID="257" subfamilyNameID="345">'
                '    <coord axis="wght" value="0.7"/>'
                '    <coord axis="wdth" value="0.5"/>'
                '</NamedInstance>'):
            inst.fromXML(name, attrs, content, ttFont=MakeFont())
        self.assertEqual(257, inst.postscriptNameID)
        self.assertEqual(345, inst.subfamilyNameID)
        self.assertDictAlmostEqual({"wght": 0.6999969, "wdth": 0.5}, inst.coordinates)

    def test_fromXML_withoutPostScriptName(self):
        inst = NamedInstance()
        for name, attrs, content in parseXML(
                '<NamedInstance flags="0x123ABC" subfamilyNameID="345">'
                '    <coord axis="wght" value="0.7"/>'
                '    <coord axis="wdth" value="0.5"/>'
                '</NamedInstance>'):
            inst.fromXML(name, attrs, content, ttFont=MakeFont())
        self.assertEqual(0x123ABC, inst.flags)
        self.assertEqual(345, inst.subfamilyNameID)
        self.assertDictAlmostEqual({"wght": 0.6999969, "wdth": 0.5}, inst.coordinates)


if __name__ == "__main__":
    import sys
    sys.exit(unittest.main())