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