xref: /aosp_15_r20/external/fonttools/Tests/ufoLib/UFOConversion_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1import os
2import shutil
3import unittest
4import tempfile
5from io import open
6from fontTools.ufoLib import UFOReader, UFOWriter
7from fontTools.ufoLib import plistlib
8from .testSupport import expectedFontInfo1To2Conversion, expectedFontInfo2To1Conversion
9
10
11# the format version 1 lib.plist contains some data
12# that these tests shouldn't be concerned about.
13removeFromFormatVersion1Lib = [
14    "org.robofab.opentype.classes",
15    "org.robofab.opentype.features",
16    "org.robofab.opentype.featureorder",
17    "org.robofab.postScriptHintData",
18]
19
20
21class ConversionFunctionsTestCase(unittest.TestCase):
22    def tearDown(self):
23        path = self.getFontPath("TestFont1 (UFO1) converted.ufo")
24        if os.path.exists(path):
25            shutil.rmtree(path)
26        path = self.getFontPath("TestFont1 (UFO2) converted.ufo")
27        if os.path.exists(path):
28            shutil.rmtree(path)
29
30    def getFontPath(self, fileName):
31        testdata = os.path.join(os.path.dirname(__file__), "testdata")
32        return os.path.join(testdata, fileName)
33
34    def compareFileStructures(self, path1, path2, expectedInfoData, testFeatures):
35        # result
36        metainfoPath1 = os.path.join(path1, "metainfo.plist")
37        fontinfoPath1 = os.path.join(path1, "fontinfo.plist")
38        kerningPath1 = os.path.join(path1, "kerning.plist")
39        groupsPath1 = os.path.join(path1, "groups.plist")
40        libPath1 = os.path.join(path1, "lib.plist")
41        featuresPath1 = os.path.join(path1, "features.plist")
42        glyphsPath1 = os.path.join(path1, "glyphs")
43        glyphsPath1_contents = os.path.join(glyphsPath1, "contents.plist")
44        glyphsPath1_A = os.path.join(glyphsPath1, "A_.glif")
45        glyphsPath1_B = os.path.join(glyphsPath1, "B_.glif")
46        # expected result
47        metainfoPath2 = os.path.join(path2, "metainfo.plist")
48        fontinfoPath2 = os.path.join(path2, "fontinfo.plist")
49        kerningPath2 = os.path.join(path2, "kerning.plist")
50        groupsPath2 = os.path.join(path2, "groups.plist")
51        libPath2 = os.path.join(path2, "lib.plist")
52        featuresPath2 = os.path.join(path2, "features.plist")
53        glyphsPath2 = os.path.join(path2, "glyphs")
54        glyphsPath2_contents = os.path.join(glyphsPath2, "contents.plist")
55        glyphsPath2_A = os.path.join(glyphsPath2, "A_.glif")
56        glyphsPath2_B = os.path.join(glyphsPath2, "B_.glif")
57        # look for existence
58        self.assertEqual(os.path.exists(metainfoPath1), True)
59        self.assertEqual(os.path.exists(fontinfoPath1), True)
60        self.assertEqual(os.path.exists(kerningPath1), True)
61        self.assertEqual(os.path.exists(groupsPath1), True)
62        self.assertEqual(os.path.exists(libPath1), True)
63        self.assertEqual(os.path.exists(glyphsPath1), True)
64        self.assertEqual(os.path.exists(glyphsPath1_contents), True)
65        self.assertEqual(os.path.exists(glyphsPath1_A), True)
66        self.assertEqual(os.path.exists(glyphsPath1_B), True)
67        if testFeatures:
68            self.assertEqual(os.path.exists(featuresPath1), True)
69        # look for aggrement
70        with open(metainfoPath1, "rb") as f:
71            data1 = plistlib.load(f)
72        with open(metainfoPath2, "rb") as f:
73            data2 = plistlib.load(f)
74        self.assertEqual(data1, data2)
75        with open(fontinfoPath1, "rb") as f:
76            data1 = plistlib.load(f)
77        self.assertEqual(sorted(data1.items()), sorted(expectedInfoData.items()))
78        with open(kerningPath1, "rb") as f:
79            data1 = plistlib.load(f)
80        with open(kerningPath2, "rb") as f:
81            data2 = plistlib.load(f)
82        self.assertEqual(data1, data2)
83        with open(groupsPath1, "rb") as f:
84            data1 = plistlib.load(f)
85        with open(groupsPath2, "rb") as f:
86            data2 = plistlib.load(f)
87        self.assertEqual(data1, data2)
88        with open(libPath1, "rb") as f:
89            data1 = plistlib.load(f)
90        with open(libPath2, "rb") as f:
91            data2 = plistlib.load(f)
92        if "UFO1" in libPath1:
93            for key in removeFromFormatVersion1Lib:
94                if key in data1:
95                    del data1[key]
96        if "UFO1" in libPath2:
97            for key in removeFromFormatVersion1Lib:
98                if key in data2:
99                    del data2[key]
100        self.assertEqual(data1, data2)
101        with open(glyphsPath1_contents, "rb") as f:
102            data1 = plistlib.load(f)
103        with open(glyphsPath2_contents, "rb") as f:
104            data2 = plistlib.load(f)
105        self.assertEqual(data1, data2)
106        with open(glyphsPath1_A, "rb") as f:
107            data1 = plistlib.load(f)
108        with open(glyphsPath2_A, "rb") as f:
109            data2 = plistlib.load(f)
110        self.assertEqual(data1, data2)
111        with open(glyphsPath1_B, "rb") as f:
112            data1 = plistlib.load(f)
113        with open(glyphsPath2_B, "rb") as f:
114            data2 = plistlib.load(f)
115        self.assertEqual(data1, data2)
116
117
118# ---------------------
119# kerning up conversion
120# ---------------------
121
122
123class TestInfoObject:
124    pass
125
126
127class KerningUpConversionTestCase(unittest.TestCase):
128    expectedKerning = {
129        ("public.kern1.BGroup", "public.kern2.CGroup"): 7,
130        ("public.kern1.BGroup", "public.kern2.DGroup"): 8,
131        ("public.kern1.BGroup", "A"): 5,
132        ("public.kern1.BGroup", "B"): 6,
133        ("public.kern1.CGroup", "public.kern2.CGroup"): 11,
134        ("public.kern1.CGroup", "public.kern2.DGroup"): 12,
135        ("public.kern1.CGroup", "A"): 9,
136        ("public.kern1.CGroup", "B"): 10,
137        ("A", "public.kern2.CGroup"): 3,
138        ("A", "public.kern2.DGroup"): 4,
139        ("A", "A"): 1,
140        ("A", "B"): 2,
141        ("X", "A"): 13,
142        ("X", "public.kern2.CGroup"): 14,
143    }
144
145    expectedGroups = {
146        "BGroup": ["B"],
147        "CGroup": ["C", "Ccedilla"],
148        "DGroup": ["D"],
149        "public.kern1.BGroup": ["B"],
150        "public.kern1.CGroup": ["C", "Ccedilla"],
151        "public.kern2.CGroup": ["C", "Ccedilla"],
152        "public.kern2.DGroup": ["D"],
153        "Not A Kerning Group": ["A"],
154        "X": ["X", "X.sc"],
155    }
156
157    def setUp(self):
158        self.tempDir = tempfile.mktemp()
159        os.mkdir(self.tempDir)
160        self.ufoPath = os.path.join(self.tempDir, "test.ufo")
161
162    def tearDown(self):
163        shutil.rmtree(self.tempDir)
164
165    def makeUFO(self, formatVersion):
166        self.clearUFO()
167        if not os.path.exists(self.ufoPath):
168            os.mkdir(self.ufoPath)
169
170        # glyphs
171        glyphsPath = os.path.join(self.ufoPath, "glyphs")
172        if not os.path.exists(glyphsPath):
173            os.mkdir(glyphsPath)
174        glyphFile = "X_.glif"
175        glyphsContents = dict(X=glyphFile)
176        path = os.path.join(glyphsPath, "contents.plist")
177        with open(path, "wb") as f:
178            plistlib.dump(glyphsContents, f)
179        path = os.path.join(glyphsPath, glyphFile)
180        with open(path, "w") as f:
181            f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
182
183        # metainfo.plist
184        metaInfo = dict(creator="test", formatVersion=formatVersion)
185        path = os.path.join(self.ufoPath, "metainfo.plist")
186        with open(path, "wb") as f:
187            plistlib.dump(metaInfo, f)
188        # kerning
189        kerning = {
190            "A": {"A": 1, "B": 2, "CGroup": 3, "DGroup": 4},
191            "BGroup": {"A": 5, "B": 6, "CGroup": 7, "DGroup": 8},
192            "CGroup": {"A": 9, "B": 10, "CGroup": 11, "DGroup": 12},
193            "X": {"A": 13, "CGroup": 14},
194        }
195        path = os.path.join(self.ufoPath, "kerning.plist")
196        with open(path, "wb") as f:
197            plistlib.dump(kerning, f)
198        # groups
199        groups = {
200            "BGroup": ["B"],
201            "CGroup": ["C", "Ccedilla"],
202            "DGroup": ["D"],
203            "Not A Kerning Group": ["A"],
204            "X": ["X", "X.sc"],  # a group with a name that is also a glyph name
205        }
206        path = os.path.join(self.ufoPath, "groups.plist")
207        with open(path, "wb") as f:
208            plistlib.dump(groups, f)
209        # font info
210        fontInfo = {"familyName": "Test"}
211        path = os.path.join(self.ufoPath, "fontinfo.plist")
212        with open(path, "wb") as f:
213            plistlib.dump(fontInfo, f)
214
215    def clearUFO(self):
216        if os.path.exists(self.ufoPath):
217            shutil.rmtree(self.ufoPath)
218
219    def testUFO1(self):
220        self.makeUFO(formatVersion=2)
221        reader = UFOReader(self.ufoPath, validate=True)
222        kerning = reader.readKerning()
223        self.assertEqual(self.expectedKerning, kerning)
224        groups = reader.readGroups()
225        self.assertEqual(self.expectedGroups, groups)
226        info = TestInfoObject()
227        reader.readInfo(info)
228
229    def testUFO2(self):
230        self.makeUFO(formatVersion=2)
231        reader = UFOReader(self.ufoPath, validate=True)
232        kerning = reader.readKerning()
233        self.assertEqual(self.expectedKerning, kerning)
234        groups = reader.readGroups()
235        self.assertEqual(self.expectedGroups, groups)
236        info = TestInfoObject()
237        reader.readInfo(info)
238
239
240class KerningDownConversionTestCase(unittest.TestCase):
241    expectedKerning = {
242        ("public.kern1.BGroup", "public.kern2.CGroup"): 7,
243        ("public.kern1.BGroup", "public.kern2.DGroup"): 8,
244        ("public.kern1.BGroup", "A"): 5,
245        ("public.kern1.BGroup", "B"): 6,
246        ("public.kern1.CGroup", "public.kern2.CGroup"): 11,
247        ("public.kern1.CGroup", "public.kern2.DGroup"): 12,
248        ("public.kern1.CGroup", "A"): 9,
249        ("public.kern1.CGroup", "B"): 10,
250        ("A", "public.kern2.CGroup"): 3,
251        ("A", "public.kern2.DGroup"): 4,
252        ("A", "A"): 1,
253        ("A", "B"): 2,
254    }
255
256    groups = {
257        "BGroup": ["B"],
258        "CGroup": ["C"],
259        "DGroup": ["D"],
260        "public.kern1.BGroup": ["B"],
261        "public.kern1.CGroup": ["C", "Ccedilla"],
262        "public.kern2.CGroup": ["C", "Ccedilla"],
263        "public.kern2.DGroup": ["D"],
264        "Not A Kerning Group": ["A"],
265    }
266    expectedWrittenGroups = {
267        "BGroup": ["B"],
268        "CGroup": ["C", "Ccedilla"],
269        "DGroup": ["D"],
270        "Not A Kerning Group": ["A"],
271    }
272
273    kerning = {
274        ("public.kern1.BGroup", "public.kern2.CGroup"): 7,
275        ("public.kern1.BGroup", "public.kern2.DGroup"): 8,
276        ("public.kern1.BGroup", "A"): 5,
277        ("public.kern1.BGroup", "B"): 6,
278        ("public.kern1.CGroup", "public.kern2.CGroup"): 11,
279        ("public.kern1.CGroup", "public.kern2.DGroup"): 12,
280        ("public.kern1.CGroup", "A"): 9,
281        ("public.kern1.CGroup", "B"): 10,
282        ("A", "public.kern2.CGroup"): 3,
283        ("A", "public.kern2.DGroup"): 4,
284        ("A", "A"): 1,
285        ("A", "B"): 2,
286    }
287    expectedWrittenKerning = {
288        "BGroup": {"CGroup": 7, "DGroup": 8, "A": 5, "B": 6},
289        "CGroup": {"CGroup": 11, "DGroup": 12, "A": 9, "B": 10},
290        "A": {"CGroup": 3, "DGroup": 4, "A": 1, "B": 2},
291    }
292
293    downConversionMapping = {
294        "side1": {"BGroup": "public.kern1.BGroup", "CGroup": "public.kern1.CGroup"},
295        "side2": {"CGroup": "public.kern2.CGroup", "DGroup": "public.kern2.DGroup"},
296    }
297
298    def setUp(self):
299        self.tempDir = tempfile.mktemp()
300        os.mkdir(self.tempDir)
301        self.dstDir = os.path.join(self.tempDir, "test.ufo")
302
303    def tearDown(self):
304        shutil.rmtree(self.tempDir)
305
306    def tearDownUFO(self):
307        shutil.rmtree(self.dstDir)
308
309    def testWrite(self):
310        writer = UFOWriter(self.dstDir, formatVersion=2)
311        writer.setKerningGroupConversionRenameMaps(self.downConversionMapping)
312        writer.writeKerning(self.kerning)
313        writer.writeGroups(self.groups)
314        # test groups
315        path = os.path.join(self.dstDir, "groups.plist")
316        with open(path, "rb") as f:
317            writtenGroups = plistlib.load(f)
318        self.assertEqual(writtenGroups, self.expectedWrittenGroups)
319        # test kerning
320        path = os.path.join(self.dstDir, "kerning.plist")
321        with open(path, "rb") as f:
322            writtenKerning = plistlib.load(f)
323        self.assertEqual(writtenKerning, self.expectedWrittenKerning)
324        self.tearDownUFO()
325