1""" 2Conversion functions. 3""" 4 5# adapted from the UFO spec 6 7 8def convertUFO1OrUFO2KerningToUFO3Kerning(kerning, groups, glyphSet=()): 9 # gather known kerning groups based on the prefixes 10 firstReferencedGroups, secondReferencedGroups = findKnownKerningGroups(groups) 11 # Make lists of groups referenced in kerning pairs. 12 for first, seconds in list(kerning.items()): 13 if first in groups and first not in glyphSet: 14 if not first.startswith("public.kern1."): 15 firstReferencedGroups.add(first) 16 for second in list(seconds.keys()): 17 if second in groups and second not in glyphSet: 18 if not second.startswith("public.kern2."): 19 secondReferencedGroups.add(second) 20 # Create new names for these groups. 21 firstRenamedGroups = {} 22 for first in firstReferencedGroups: 23 # Make a list of existing group names. 24 existingGroupNames = list(groups.keys()) + list(firstRenamedGroups.keys()) 25 # Remove the old prefix from the name 26 newName = first.replace("@MMK_L_", "") 27 # Add the new prefix to the name. 28 newName = "public.kern1." + newName 29 # Make a unique group name. 30 newName = makeUniqueGroupName(newName, existingGroupNames) 31 # Store for use later. 32 firstRenamedGroups[first] = newName 33 secondRenamedGroups = {} 34 for second in secondReferencedGroups: 35 # Make a list of existing group names. 36 existingGroupNames = list(groups.keys()) + list(secondRenamedGroups.keys()) 37 # Remove the old prefix from the name 38 newName = second.replace("@MMK_R_", "") 39 # Add the new prefix to the name. 40 newName = "public.kern2." + newName 41 # Make a unique group name. 42 newName = makeUniqueGroupName(newName, existingGroupNames) 43 # Store for use later. 44 secondRenamedGroups[second] = newName 45 # Populate the new group names into the kerning dictionary as needed. 46 newKerning = {} 47 for first, seconds in list(kerning.items()): 48 first = firstRenamedGroups.get(first, first) 49 newSeconds = {} 50 for second, value in list(seconds.items()): 51 second = secondRenamedGroups.get(second, second) 52 newSeconds[second] = value 53 newKerning[first] = newSeconds 54 # Make copies of the referenced groups and store them 55 # under the new names in the overall groups dictionary. 56 allRenamedGroups = list(firstRenamedGroups.items()) 57 allRenamedGroups += list(secondRenamedGroups.items()) 58 for oldName, newName in allRenamedGroups: 59 group = list(groups[oldName]) 60 groups[newName] = group 61 # Return the kerning and the groups. 62 return newKerning, groups, dict(side1=firstRenamedGroups, side2=secondRenamedGroups) 63 64 65def findKnownKerningGroups(groups): 66 """ 67 This will find kerning groups with known prefixes. 68 In some cases not all kerning groups will be referenced 69 by the kerning pairs. The algorithm for locating groups 70 in convertUFO1OrUFO2KerningToUFO3Kerning will miss these 71 unreferenced groups. By scanning for known prefixes 72 this function will catch all of the prefixed groups. 73 74 These are the prefixes and sides that are handled: 75 @MMK_L_ - side 1 76 @MMK_R_ - side 2 77 78 >>> testGroups = { 79 ... "@MMK_L_1" : None, 80 ... "@MMK_L_2" : None, 81 ... "@MMK_L_3" : None, 82 ... "@MMK_R_1" : None, 83 ... "@MMK_R_2" : None, 84 ... "@MMK_R_3" : None, 85 ... "@MMK_l_1" : None, 86 ... "@MMK_r_1" : None, 87 ... "@MMK_X_1" : None, 88 ... "foo" : None, 89 ... } 90 >>> first, second = findKnownKerningGroups(testGroups) 91 >>> sorted(first) == ['@MMK_L_1', '@MMK_L_2', '@MMK_L_3'] 92 True 93 >>> sorted(second) == ['@MMK_R_1', '@MMK_R_2', '@MMK_R_3'] 94 True 95 """ 96 knownFirstGroupPrefixes = ["@MMK_L_"] 97 knownSecondGroupPrefixes = ["@MMK_R_"] 98 firstGroups = set() 99 secondGroups = set() 100 for groupName in list(groups.keys()): 101 for firstPrefix in knownFirstGroupPrefixes: 102 if groupName.startswith(firstPrefix): 103 firstGroups.add(groupName) 104 break 105 for secondPrefix in knownSecondGroupPrefixes: 106 if groupName.startswith(secondPrefix): 107 secondGroups.add(groupName) 108 break 109 return firstGroups, secondGroups 110 111 112def makeUniqueGroupName(name, groupNames, counter=0): 113 # Add a number to the name if the counter is higher than zero. 114 newName = name 115 if counter > 0: 116 newName = "%s%d" % (newName, counter) 117 # If the new name is in the existing group names, recurse. 118 if newName in groupNames: 119 return makeUniqueGroupName(name, groupNames, counter + 1) 120 # Otherwise send back the new name. 121 return newName 122 123 124def test(): 125 """ 126 No known prefixes. 127 128 >>> testKerning = { 129 ... "A" : { 130 ... "A" : 1, 131 ... "B" : 2, 132 ... "CGroup" : 3, 133 ... "DGroup" : 4 134 ... }, 135 ... "BGroup" : { 136 ... "A" : 5, 137 ... "B" : 6, 138 ... "CGroup" : 7, 139 ... "DGroup" : 8 140 ... }, 141 ... "CGroup" : { 142 ... "A" : 9, 143 ... "B" : 10, 144 ... "CGroup" : 11, 145 ... "DGroup" : 12 146 ... }, 147 ... } 148 >>> testGroups = { 149 ... "BGroup" : ["B"], 150 ... "CGroup" : ["C"], 151 ... "DGroup" : ["D"], 152 ... } 153 >>> kerning, groups, maps = convertUFO1OrUFO2KerningToUFO3Kerning( 154 ... testKerning, testGroups, []) 155 >>> expected = { 156 ... "A" : { 157 ... "A": 1, 158 ... "B": 2, 159 ... "public.kern2.CGroup": 3, 160 ... "public.kern2.DGroup": 4 161 ... }, 162 ... "public.kern1.BGroup": { 163 ... "A": 5, 164 ... "B": 6, 165 ... "public.kern2.CGroup": 7, 166 ... "public.kern2.DGroup": 8 167 ... }, 168 ... "public.kern1.CGroup": { 169 ... "A": 9, 170 ... "B": 10, 171 ... "public.kern2.CGroup": 11, 172 ... "public.kern2.DGroup": 12 173 ... } 174 ... } 175 >>> kerning == expected 176 True 177 >>> expected = { 178 ... "BGroup": ["B"], 179 ... "CGroup": ["C"], 180 ... "DGroup": ["D"], 181 ... "public.kern1.BGroup": ["B"], 182 ... "public.kern1.CGroup": ["C"], 183 ... "public.kern2.CGroup": ["C"], 184 ... "public.kern2.DGroup": ["D"], 185 ... } 186 >>> groups == expected 187 True 188 189 Known prefixes. 190 191 >>> testKerning = { 192 ... "A" : { 193 ... "A" : 1, 194 ... "B" : 2, 195 ... "@MMK_R_CGroup" : 3, 196 ... "@MMK_R_DGroup" : 4 197 ... }, 198 ... "@MMK_L_BGroup" : { 199 ... "A" : 5, 200 ... "B" : 6, 201 ... "@MMK_R_CGroup" : 7, 202 ... "@MMK_R_DGroup" : 8 203 ... }, 204 ... "@MMK_L_CGroup" : { 205 ... "A" : 9, 206 ... "B" : 10, 207 ... "@MMK_R_CGroup" : 11, 208 ... "@MMK_R_DGroup" : 12 209 ... }, 210 ... } 211 >>> testGroups = { 212 ... "@MMK_L_BGroup" : ["B"], 213 ... "@MMK_L_CGroup" : ["C"], 214 ... "@MMK_L_XGroup" : ["X"], 215 ... "@MMK_R_CGroup" : ["C"], 216 ... "@MMK_R_DGroup" : ["D"], 217 ... "@MMK_R_XGroup" : ["X"], 218 ... } 219 >>> kerning, groups, maps = convertUFO1OrUFO2KerningToUFO3Kerning( 220 ... testKerning, testGroups, []) 221 >>> expected = { 222 ... "A" : { 223 ... "A": 1, 224 ... "B": 2, 225 ... "public.kern2.CGroup": 3, 226 ... "public.kern2.DGroup": 4 227 ... }, 228 ... "public.kern1.BGroup": { 229 ... "A": 5, 230 ... "B": 6, 231 ... "public.kern2.CGroup": 7, 232 ... "public.kern2.DGroup": 8 233 ... }, 234 ... "public.kern1.CGroup": { 235 ... "A": 9, 236 ... "B": 10, 237 ... "public.kern2.CGroup": 11, 238 ... "public.kern2.DGroup": 12 239 ... } 240 ... } 241 >>> kerning == expected 242 True 243 >>> expected = { 244 ... "@MMK_L_BGroup": ["B"], 245 ... "@MMK_L_CGroup": ["C"], 246 ... "@MMK_L_XGroup": ["X"], 247 ... "@MMK_R_CGroup": ["C"], 248 ... "@MMK_R_DGroup": ["D"], 249 ... "@MMK_R_XGroup": ["X"], 250 ... "public.kern1.BGroup": ["B"], 251 ... "public.kern1.CGroup": ["C"], 252 ... "public.kern1.XGroup": ["X"], 253 ... "public.kern2.CGroup": ["C"], 254 ... "public.kern2.DGroup": ["D"], 255 ... "public.kern2.XGroup": ["X"], 256 ... } 257 >>> groups == expected 258 True 259 260 >>> from .validators import kerningValidator 261 >>> kerningValidator(kerning) 262 (True, None) 263 264 Mixture of known prefixes and groups without prefixes. 265 266 >>> testKerning = { 267 ... "A" : { 268 ... "A" : 1, 269 ... "B" : 2, 270 ... "@MMK_R_CGroup" : 3, 271 ... "DGroup" : 4 272 ... }, 273 ... "BGroup" : { 274 ... "A" : 5, 275 ... "B" : 6, 276 ... "@MMK_R_CGroup" : 7, 277 ... "DGroup" : 8 278 ... }, 279 ... "@MMK_L_CGroup" : { 280 ... "A" : 9, 281 ... "B" : 10, 282 ... "@MMK_R_CGroup" : 11, 283 ... "DGroup" : 12 284 ... }, 285 ... } 286 >>> testGroups = { 287 ... "BGroup" : ["B"], 288 ... "@MMK_L_CGroup" : ["C"], 289 ... "@MMK_R_CGroup" : ["C"], 290 ... "DGroup" : ["D"], 291 ... } 292 >>> kerning, groups, maps = convertUFO1OrUFO2KerningToUFO3Kerning( 293 ... testKerning, testGroups, []) 294 >>> expected = { 295 ... "A" : { 296 ... "A": 1, 297 ... "B": 2, 298 ... "public.kern2.CGroup": 3, 299 ... "public.kern2.DGroup": 4 300 ... }, 301 ... "public.kern1.BGroup": { 302 ... "A": 5, 303 ... "B": 6, 304 ... "public.kern2.CGroup": 7, 305 ... "public.kern2.DGroup": 8 306 ... }, 307 ... "public.kern1.CGroup": { 308 ... "A": 9, 309 ... "B": 10, 310 ... "public.kern2.CGroup": 11, 311 ... "public.kern2.DGroup": 12 312 ... } 313 ... } 314 >>> kerning == expected 315 True 316 >>> expected = { 317 ... "BGroup": ["B"], 318 ... "@MMK_L_CGroup": ["C"], 319 ... "@MMK_R_CGroup": ["C"], 320 ... "DGroup": ["D"], 321 ... "public.kern1.BGroup": ["B"], 322 ... "public.kern1.CGroup": ["C"], 323 ... "public.kern2.CGroup": ["C"], 324 ... "public.kern2.DGroup": ["D"], 325 ... } 326 >>> groups == expected 327 True 328 """ 329 330 331if __name__ == "__main__": 332 import doctest 333 334 doctest.testmod() 335