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