converters.py 10 KB

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