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