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