xref: /aosp_15_r20/external/fonttools/Lib/fontTools/ufoLib/converters.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
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