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