fonttools 4.60.2__cp311-cp311-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. fontTools/__init__.py +8 -0
  2. fontTools/__main__.py +35 -0
  3. fontTools/afmLib.py +439 -0
  4. fontTools/agl.py +5233 -0
  5. fontTools/annotations.py +30 -0
  6. fontTools/cffLib/CFF2ToCFF.py +258 -0
  7. fontTools/cffLib/CFFToCFF2.py +305 -0
  8. fontTools/cffLib/__init__.py +3694 -0
  9. fontTools/cffLib/specializer.py +927 -0
  10. fontTools/cffLib/transforms.py +495 -0
  11. fontTools/cffLib/width.py +210 -0
  12. fontTools/colorLib/__init__.py +0 -0
  13. fontTools/colorLib/builder.py +664 -0
  14. fontTools/colorLib/errors.py +2 -0
  15. fontTools/colorLib/geometry.py +143 -0
  16. fontTools/colorLib/table_builder.py +223 -0
  17. fontTools/colorLib/unbuilder.py +81 -0
  18. fontTools/config/__init__.py +90 -0
  19. fontTools/cu2qu/__init__.py +15 -0
  20. fontTools/cu2qu/__main__.py +6 -0
  21. fontTools/cu2qu/benchmark.py +54 -0
  22. fontTools/cu2qu/cli.py +198 -0
  23. fontTools/cu2qu/cu2qu.c +15817 -0
  24. fontTools/cu2qu/cu2qu.cp311-win32.pyd +0 -0
  25. fontTools/cu2qu/cu2qu.py +563 -0
  26. fontTools/cu2qu/errors.py +77 -0
  27. fontTools/cu2qu/ufo.py +363 -0
  28. fontTools/designspaceLib/__init__.py +3343 -0
  29. fontTools/designspaceLib/__main__.py +6 -0
  30. fontTools/designspaceLib/split.py +475 -0
  31. fontTools/designspaceLib/statNames.py +260 -0
  32. fontTools/designspaceLib/types.py +147 -0
  33. fontTools/encodings/MacRoman.py +258 -0
  34. fontTools/encodings/StandardEncoding.py +258 -0
  35. fontTools/encodings/__init__.py +1 -0
  36. fontTools/encodings/codecs.py +135 -0
  37. fontTools/feaLib/__init__.py +4 -0
  38. fontTools/feaLib/__main__.py +78 -0
  39. fontTools/feaLib/ast.py +2143 -0
  40. fontTools/feaLib/builder.py +1814 -0
  41. fontTools/feaLib/error.py +22 -0
  42. fontTools/feaLib/lexer.c +17029 -0
  43. fontTools/feaLib/lexer.cp311-win32.pyd +0 -0
  44. fontTools/feaLib/lexer.py +287 -0
  45. fontTools/feaLib/location.py +12 -0
  46. fontTools/feaLib/lookupDebugInfo.py +12 -0
  47. fontTools/feaLib/parser.py +2394 -0
  48. fontTools/feaLib/variableScalar.py +118 -0
  49. fontTools/fontBuilder.py +1014 -0
  50. fontTools/help.py +36 -0
  51. fontTools/merge/__init__.py +248 -0
  52. fontTools/merge/__main__.py +6 -0
  53. fontTools/merge/base.py +81 -0
  54. fontTools/merge/cmap.py +173 -0
  55. fontTools/merge/layout.py +526 -0
  56. fontTools/merge/options.py +85 -0
  57. fontTools/merge/tables.py +352 -0
  58. fontTools/merge/unicode.py +78 -0
  59. fontTools/merge/util.py +143 -0
  60. fontTools/misc/__init__.py +1 -0
  61. fontTools/misc/arrayTools.py +424 -0
  62. fontTools/misc/bezierTools.c +39731 -0
  63. fontTools/misc/bezierTools.cp311-win32.pyd +0 -0
  64. fontTools/misc/bezierTools.py +1500 -0
  65. fontTools/misc/classifyTools.py +170 -0
  66. fontTools/misc/cliTools.py +53 -0
  67. fontTools/misc/configTools.py +349 -0
  68. fontTools/misc/cython.py +27 -0
  69. fontTools/misc/dictTools.py +83 -0
  70. fontTools/misc/eexec.py +119 -0
  71. fontTools/misc/encodingTools.py +72 -0
  72. fontTools/misc/enumTools.py +23 -0
  73. fontTools/misc/etree.py +456 -0
  74. fontTools/misc/filenames.py +245 -0
  75. fontTools/misc/filesystem/__init__.py +68 -0
  76. fontTools/misc/filesystem/_base.py +134 -0
  77. fontTools/misc/filesystem/_copy.py +45 -0
  78. fontTools/misc/filesystem/_errors.py +54 -0
  79. fontTools/misc/filesystem/_info.py +75 -0
  80. fontTools/misc/filesystem/_osfs.py +164 -0
  81. fontTools/misc/filesystem/_path.py +67 -0
  82. fontTools/misc/filesystem/_subfs.py +92 -0
  83. fontTools/misc/filesystem/_tempfs.py +34 -0
  84. fontTools/misc/filesystem/_tools.py +34 -0
  85. fontTools/misc/filesystem/_walk.py +55 -0
  86. fontTools/misc/filesystem/_zipfs.py +204 -0
  87. fontTools/misc/fixedTools.py +253 -0
  88. fontTools/misc/intTools.py +25 -0
  89. fontTools/misc/iterTools.py +12 -0
  90. fontTools/misc/lazyTools.py +42 -0
  91. fontTools/misc/loggingTools.py +543 -0
  92. fontTools/misc/macCreatorType.py +56 -0
  93. fontTools/misc/macRes.py +261 -0
  94. fontTools/misc/plistlib/__init__.py +681 -0
  95. fontTools/misc/plistlib/py.typed +0 -0
  96. fontTools/misc/psCharStrings.py +1511 -0
  97. fontTools/misc/psLib.py +398 -0
  98. fontTools/misc/psOperators.py +572 -0
  99. fontTools/misc/py23.py +96 -0
  100. fontTools/misc/roundTools.py +110 -0
  101. fontTools/misc/sstruct.py +227 -0
  102. fontTools/misc/symfont.py +242 -0
  103. fontTools/misc/testTools.py +233 -0
  104. fontTools/misc/textTools.py +156 -0
  105. fontTools/misc/timeTools.py +88 -0
  106. fontTools/misc/transform.py +516 -0
  107. fontTools/misc/treeTools.py +45 -0
  108. fontTools/misc/vector.py +147 -0
  109. fontTools/misc/visitor.py +158 -0
  110. fontTools/misc/xmlReader.py +188 -0
  111. fontTools/misc/xmlWriter.py +231 -0
  112. fontTools/mtiLib/__init__.py +1400 -0
  113. fontTools/mtiLib/__main__.py +5 -0
  114. fontTools/otlLib/__init__.py +1 -0
  115. fontTools/otlLib/builder.py +3465 -0
  116. fontTools/otlLib/error.py +11 -0
  117. fontTools/otlLib/maxContextCalc.py +96 -0
  118. fontTools/otlLib/optimize/__init__.py +53 -0
  119. fontTools/otlLib/optimize/__main__.py +6 -0
  120. fontTools/otlLib/optimize/gpos.py +439 -0
  121. fontTools/pens/__init__.py +1 -0
  122. fontTools/pens/areaPen.py +52 -0
  123. fontTools/pens/basePen.py +475 -0
  124. fontTools/pens/boundsPen.py +98 -0
  125. fontTools/pens/cairoPen.py +26 -0
  126. fontTools/pens/cocoaPen.py +26 -0
  127. fontTools/pens/cu2quPen.py +325 -0
  128. fontTools/pens/explicitClosingLinePen.py +101 -0
  129. fontTools/pens/filterPen.py +433 -0
  130. fontTools/pens/freetypePen.py +462 -0
  131. fontTools/pens/hashPointPen.py +89 -0
  132. fontTools/pens/momentsPen.c +13378 -0
  133. fontTools/pens/momentsPen.cp311-win32.pyd +0 -0
  134. fontTools/pens/momentsPen.py +879 -0
  135. fontTools/pens/perimeterPen.py +69 -0
  136. fontTools/pens/pointInsidePen.py +192 -0
  137. fontTools/pens/pointPen.py +643 -0
  138. fontTools/pens/qtPen.py +29 -0
  139. fontTools/pens/qu2cuPen.py +105 -0
  140. fontTools/pens/quartzPen.py +43 -0
  141. fontTools/pens/recordingPen.py +335 -0
  142. fontTools/pens/reportLabPen.py +79 -0
  143. fontTools/pens/reverseContourPen.py +96 -0
  144. fontTools/pens/roundingPen.py +130 -0
  145. fontTools/pens/statisticsPen.py +312 -0
  146. fontTools/pens/svgPathPen.py +310 -0
  147. fontTools/pens/t2CharStringPen.py +88 -0
  148. fontTools/pens/teePen.py +55 -0
  149. fontTools/pens/transformPen.py +115 -0
  150. fontTools/pens/ttGlyphPen.py +335 -0
  151. fontTools/pens/wxPen.py +29 -0
  152. fontTools/qu2cu/__init__.py +15 -0
  153. fontTools/qu2cu/__main__.py +7 -0
  154. fontTools/qu2cu/benchmark.py +56 -0
  155. fontTools/qu2cu/cli.py +125 -0
  156. fontTools/qu2cu/qu2cu.c +16682 -0
  157. fontTools/qu2cu/qu2cu.cp311-win32.pyd +0 -0
  158. fontTools/qu2cu/qu2cu.py +405 -0
  159. fontTools/subset/__init__.py +4096 -0
  160. fontTools/subset/__main__.py +6 -0
  161. fontTools/subset/cff.py +184 -0
  162. fontTools/subset/svg.py +253 -0
  163. fontTools/subset/util.py +25 -0
  164. fontTools/svgLib/__init__.py +3 -0
  165. fontTools/svgLib/path/__init__.py +65 -0
  166. fontTools/svgLib/path/arc.py +154 -0
  167. fontTools/svgLib/path/parser.py +322 -0
  168. fontTools/svgLib/path/shapes.py +183 -0
  169. fontTools/t1Lib/__init__.py +648 -0
  170. fontTools/tfmLib.py +460 -0
  171. fontTools/ttLib/__init__.py +30 -0
  172. fontTools/ttLib/__main__.py +148 -0
  173. fontTools/ttLib/macUtils.py +54 -0
  174. fontTools/ttLib/removeOverlaps.py +395 -0
  175. fontTools/ttLib/reorderGlyphs.py +285 -0
  176. fontTools/ttLib/scaleUpem.py +436 -0
  177. fontTools/ttLib/sfnt.py +661 -0
  178. fontTools/ttLib/standardGlyphOrder.py +271 -0
  179. fontTools/ttLib/tables/B_A_S_E_.py +14 -0
  180. fontTools/ttLib/tables/BitmapGlyphMetrics.py +64 -0
  181. fontTools/ttLib/tables/C_B_D_T_.py +113 -0
  182. fontTools/ttLib/tables/C_B_L_C_.py +19 -0
  183. fontTools/ttLib/tables/C_F_F_.py +61 -0
  184. fontTools/ttLib/tables/C_F_F__2.py +26 -0
  185. fontTools/ttLib/tables/C_O_L_R_.py +165 -0
  186. fontTools/ttLib/tables/C_P_A_L_.py +305 -0
  187. fontTools/ttLib/tables/D_S_I_G_.py +158 -0
  188. fontTools/ttLib/tables/D__e_b_g.py +35 -0
  189. fontTools/ttLib/tables/DefaultTable.py +49 -0
  190. fontTools/ttLib/tables/E_B_D_T_.py +835 -0
  191. fontTools/ttLib/tables/E_B_L_C_.py +718 -0
  192. fontTools/ttLib/tables/F_F_T_M_.py +52 -0
  193. fontTools/ttLib/tables/F__e_a_t.py +149 -0
  194. fontTools/ttLib/tables/G_D_E_F_.py +13 -0
  195. fontTools/ttLib/tables/G_M_A_P_.py +148 -0
  196. fontTools/ttLib/tables/G_P_K_G_.py +133 -0
  197. fontTools/ttLib/tables/G_P_O_S_.py +14 -0
  198. fontTools/ttLib/tables/G_S_U_B_.py +13 -0
  199. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  200. fontTools/ttLib/tables/G__l_a_t.py +235 -0
  201. fontTools/ttLib/tables/G__l_o_c.py +85 -0
  202. fontTools/ttLib/tables/H_V_A_R_.py +13 -0
  203. fontTools/ttLib/tables/J_S_T_F_.py +13 -0
  204. fontTools/ttLib/tables/L_T_S_H_.py +58 -0
  205. fontTools/ttLib/tables/M_A_T_H_.py +13 -0
  206. fontTools/ttLib/tables/M_E_T_A_.py +352 -0
  207. fontTools/ttLib/tables/M_V_A_R_.py +13 -0
  208. fontTools/ttLib/tables/O_S_2f_2.py +752 -0
  209. fontTools/ttLib/tables/S_I_N_G_.py +99 -0
  210. fontTools/ttLib/tables/S_T_A_T_.py +15 -0
  211. fontTools/ttLib/tables/S_V_G_.py +223 -0
  212. fontTools/ttLib/tables/S__i_l_f.py +1040 -0
  213. fontTools/ttLib/tables/S__i_l_l.py +92 -0
  214. fontTools/ttLib/tables/T_S_I_B_.py +13 -0
  215. fontTools/ttLib/tables/T_S_I_C_.py +14 -0
  216. fontTools/ttLib/tables/T_S_I_D_.py +13 -0
  217. fontTools/ttLib/tables/T_S_I_J_.py +13 -0
  218. fontTools/ttLib/tables/T_S_I_P_.py +13 -0
  219. fontTools/ttLib/tables/T_S_I_S_.py +13 -0
  220. fontTools/ttLib/tables/T_S_I_V_.py +26 -0
  221. fontTools/ttLib/tables/T_S_I__0.py +70 -0
  222. fontTools/ttLib/tables/T_S_I__1.py +163 -0
  223. fontTools/ttLib/tables/T_S_I__2.py +17 -0
  224. fontTools/ttLib/tables/T_S_I__3.py +22 -0
  225. fontTools/ttLib/tables/T_S_I__5.py +60 -0
  226. fontTools/ttLib/tables/T_T_F_A_.py +14 -0
  227. fontTools/ttLib/tables/TupleVariation.py +884 -0
  228. fontTools/ttLib/tables/V_A_R_C_.py +12 -0
  229. fontTools/ttLib/tables/V_D_M_X_.py +249 -0
  230. fontTools/ttLib/tables/V_O_R_G_.py +165 -0
  231. fontTools/ttLib/tables/V_V_A_R_.py +13 -0
  232. fontTools/ttLib/tables/__init__.py +98 -0
  233. fontTools/ttLib/tables/_a_n_k_r.py +15 -0
  234. fontTools/ttLib/tables/_a_v_a_r.py +193 -0
  235. fontTools/ttLib/tables/_b_s_l_n.py +15 -0
  236. fontTools/ttLib/tables/_c_i_d_g.py +24 -0
  237. fontTools/ttLib/tables/_c_m_a_p.py +1591 -0
  238. fontTools/ttLib/tables/_c_v_a_r.py +94 -0
  239. fontTools/ttLib/tables/_c_v_t.py +56 -0
  240. fontTools/ttLib/tables/_f_e_a_t.py +15 -0
  241. fontTools/ttLib/tables/_f_p_g_m.py +62 -0
  242. fontTools/ttLib/tables/_f_v_a_r.py +261 -0
  243. fontTools/ttLib/tables/_g_a_s_p.py +63 -0
  244. fontTools/ttLib/tables/_g_c_i_d.py +13 -0
  245. fontTools/ttLib/tables/_g_l_y_f.py +2311 -0
  246. fontTools/ttLib/tables/_g_v_a_r.py +340 -0
  247. fontTools/ttLib/tables/_h_d_m_x.py +127 -0
  248. fontTools/ttLib/tables/_h_e_a_d.py +130 -0
  249. fontTools/ttLib/tables/_h_h_e_a.py +147 -0
  250. fontTools/ttLib/tables/_h_m_t_x.py +164 -0
  251. fontTools/ttLib/tables/_k_e_r_n.py +289 -0
  252. fontTools/ttLib/tables/_l_c_a_r.py +13 -0
  253. fontTools/ttLib/tables/_l_o_c_a.py +70 -0
  254. fontTools/ttLib/tables/_l_t_a_g.py +72 -0
  255. fontTools/ttLib/tables/_m_a_x_p.py +147 -0
  256. fontTools/ttLib/tables/_m_e_t_a.py +112 -0
  257. fontTools/ttLib/tables/_m_o_r_t.py +14 -0
  258. fontTools/ttLib/tables/_m_o_r_x.py +15 -0
  259. fontTools/ttLib/tables/_n_a_m_e.py +1242 -0
  260. fontTools/ttLib/tables/_o_p_b_d.py +14 -0
  261. fontTools/ttLib/tables/_p_o_s_t.py +319 -0
  262. fontTools/ttLib/tables/_p_r_e_p.py +16 -0
  263. fontTools/ttLib/tables/_p_r_o_p.py +12 -0
  264. fontTools/ttLib/tables/_s_b_i_x.py +129 -0
  265. fontTools/ttLib/tables/_t_r_a_k.py +332 -0
  266. fontTools/ttLib/tables/_v_h_e_a.py +139 -0
  267. fontTools/ttLib/tables/_v_m_t_x.py +19 -0
  268. fontTools/ttLib/tables/asciiTable.py +20 -0
  269. fontTools/ttLib/tables/grUtils.py +92 -0
  270. fontTools/ttLib/tables/otBase.py +1458 -0
  271. fontTools/ttLib/tables/otConverters.py +2068 -0
  272. fontTools/ttLib/tables/otData.py +6400 -0
  273. fontTools/ttLib/tables/otTables.py +2703 -0
  274. fontTools/ttLib/tables/otTraverse.py +163 -0
  275. fontTools/ttLib/tables/sbixGlyph.py +149 -0
  276. fontTools/ttLib/tables/sbixStrike.py +177 -0
  277. fontTools/ttLib/tables/table_API_readme.txt +91 -0
  278. fontTools/ttLib/tables/ttProgram.py +594 -0
  279. fontTools/ttLib/ttCollection.py +125 -0
  280. fontTools/ttLib/ttFont.py +1148 -0
  281. fontTools/ttLib/ttGlyphSet.py +490 -0
  282. fontTools/ttLib/ttVisitor.py +32 -0
  283. fontTools/ttLib/woff2.py +1680 -0
  284. fontTools/ttx.py +479 -0
  285. fontTools/ufoLib/__init__.py +2575 -0
  286. fontTools/ufoLib/converters.py +407 -0
  287. fontTools/ufoLib/errors.py +30 -0
  288. fontTools/ufoLib/etree.py +6 -0
  289. fontTools/ufoLib/filenames.py +356 -0
  290. fontTools/ufoLib/glifLib.py +2120 -0
  291. fontTools/ufoLib/kerning.py +141 -0
  292. fontTools/ufoLib/plistlib.py +47 -0
  293. fontTools/ufoLib/pointPen.py +6 -0
  294. fontTools/ufoLib/utils.py +107 -0
  295. fontTools/ufoLib/validators.py +1208 -0
  296. fontTools/unicode.py +50 -0
  297. fontTools/unicodedata/Blocks.py +817 -0
  298. fontTools/unicodedata/Mirrored.py +446 -0
  299. fontTools/unicodedata/OTTags.py +50 -0
  300. fontTools/unicodedata/ScriptExtensions.py +832 -0
  301. fontTools/unicodedata/Scripts.py +3639 -0
  302. fontTools/unicodedata/__init__.py +306 -0
  303. fontTools/varLib/__init__.py +1600 -0
  304. fontTools/varLib/__main__.py +6 -0
  305. fontTools/varLib/avar/__init__.py +0 -0
  306. fontTools/varLib/avar/__main__.py +72 -0
  307. fontTools/varLib/avar/build.py +79 -0
  308. fontTools/varLib/avar/map.py +108 -0
  309. fontTools/varLib/avar/plan.py +1004 -0
  310. fontTools/varLib/avar/unbuild.py +271 -0
  311. fontTools/varLib/avarPlanner.py +8 -0
  312. fontTools/varLib/builder.py +215 -0
  313. fontTools/varLib/cff.py +631 -0
  314. fontTools/varLib/errors.py +219 -0
  315. fontTools/varLib/featureVars.py +703 -0
  316. fontTools/varLib/hvar.py +113 -0
  317. fontTools/varLib/instancer/__init__.py +2052 -0
  318. fontTools/varLib/instancer/__main__.py +5 -0
  319. fontTools/varLib/instancer/featureVars.py +190 -0
  320. fontTools/varLib/instancer/names.py +388 -0
  321. fontTools/varLib/instancer/solver.py +309 -0
  322. fontTools/varLib/interpolatable.py +1209 -0
  323. fontTools/varLib/interpolatableHelpers.py +399 -0
  324. fontTools/varLib/interpolatablePlot.py +1269 -0
  325. fontTools/varLib/interpolatableTestContourOrder.py +82 -0
  326. fontTools/varLib/interpolatableTestStartingPoint.py +107 -0
  327. fontTools/varLib/interpolate_layout.py +124 -0
  328. fontTools/varLib/iup.c +19815 -0
  329. fontTools/varLib/iup.cp311-win32.pyd +0 -0
  330. fontTools/varLib/iup.py +490 -0
  331. fontTools/varLib/merger.py +1717 -0
  332. fontTools/varLib/models.py +642 -0
  333. fontTools/varLib/multiVarStore.py +253 -0
  334. fontTools/varLib/mutator.py +529 -0
  335. fontTools/varLib/mvar.py +40 -0
  336. fontTools/varLib/plot.py +238 -0
  337. fontTools/varLib/stat.py +149 -0
  338. fontTools/varLib/varStore.py +739 -0
  339. fontTools/voltLib/__init__.py +5 -0
  340. fontTools/voltLib/__main__.py +206 -0
  341. fontTools/voltLib/ast.py +452 -0
  342. fontTools/voltLib/error.py +12 -0
  343. fontTools/voltLib/lexer.py +99 -0
  344. fontTools/voltLib/parser.py +664 -0
  345. fontTools/voltLib/voltToFea.py +911 -0
  346. fonttools-4.60.2.data/data/share/man/man1/ttx.1 +225 -0
  347. fonttools-4.60.2.dist-info/METADATA +2250 -0
  348. fonttools-4.60.2.dist-info/RECORD +353 -0
  349. fonttools-4.60.2.dist-info/WHEEL +5 -0
  350. fonttools-4.60.2.dist-info/entry_points.txt +5 -0
  351. fonttools-4.60.2.dist-info/licenses/LICENSE +21 -0
  352. fonttools-4.60.2.dist-info/licenses/LICENSE.external +388 -0
  353. fonttools-4.60.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,395 @@
1
+ """Simplify TrueType glyphs by merging overlapping contours/components.
2
+
3
+ Requires https://github.com/fonttools/skia-pathops
4
+ """
5
+
6
+ import itertools
7
+ import logging
8
+ from typing import Callable, Iterable, Optional, Mapping
9
+
10
+ from fontTools.cffLib import CFFFontSet
11
+ from fontTools.ttLib import ttFont
12
+ from fontTools.ttLib.tables import _g_l_y_f
13
+ from fontTools.ttLib.tables import _h_m_t_x
14
+ from fontTools.misc.psCharStrings import T2CharString
15
+ from fontTools.misc.roundTools import otRound, noRound
16
+ from fontTools.pens.ttGlyphPen import TTGlyphPen
17
+ from fontTools.pens.t2CharStringPen import T2CharStringPen
18
+
19
+ import pathops
20
+
21
+
22
+ __all__ = ["removeOverlaps"]
23
+
24
+
25
+ class RemoveOverlapsError(Exception):
26
+ pass
27
+
28
+
29
+ log = logging.getLogger("fontTools.ttLib.removeOverlaps")
30
+
31
+ _TTGlyphMapping = Mapping[str, ttFont._TTGlyph]
32
+
33
+
34
+ def skPathFromGlyph(glyphName: str, glyphSet: _TTGlyphMapping) -> pathops.Path:
35
+ path = pathops.Path()
36
+ pathPen = path.getPen(glyphSet=glyphSet)
37
+ glyphSet[glyphName].draw(pathPen)
38
+ return path
39
+
40
+
41
+ def skPathFromGlyphComponent(
42
+ component: _g_l_y_f.GlyphComponent, glyphSet: _TTGlyphMapping
43
+ ):
44
+ baseGlyphName, transformation = component.getComponentInfo()
45
+ path = skPathFromGlyph(baseGlyphName, glyphSet)
46
+ return path.transform(*transformation)
47
+
48
+
49
+ def componentsOverlap(glyph: _g_l_y_f.Glyph, glyphSet: _TTGlyphMapping) -> bool:
50
+ if not glyph.isComposite():
51
+ raise ValueError("This method only works with TrueType composite glyphs")
52
+ if len(glyph.components) < 2:
53
+ return False # single component, no overlaps
54
+
55
+ component_paths = {}
56
+
57
+ def _get_nth_component_path(index: int) -> pathops.Path:
58
+ if index not in component_paths:
59
+ component_paths[index] = skPathFromGlyphComponent(
60
+ glyph.components[index], glyphSet
61
+ )
62
+ return component_paths[index]
63
+
64
+ return any(
65
+ pathops.op(
66
+ _get_nth_component_path(i),
67
+ _get_nth_component_path(j),
68
+ pathops.PathOp.INTERSECTION,
69
+ fix_winding=False,
70
+ keep_starting_points=False,
71
+ )
72
+ for i, j in itertools.combinations(range(len(glyph.components)), 2)
73
+ )
74
+
75
+
76
+ def ttfGlyphFromSkPath(path: pathops.Path) -> _g_l_y_f.Glyph:
77
+ # Skia paths have no 'components', no need for glyphSet
78
+ ttPen = TTGlyphPen(glyphSet=None)
79
+ path.draw(ttPen)
80
+ glyph = ttPen.glyph()
81
+ assert not glyph.isComposite()
82
+ # compute glyph.xMin (glyfTable parameter unused for non composites)
83
+ glyph.recalcBounds(glyfTable=None)
84
+ return glyph
85
+
86
+
87
+ def _charString_from_SkPath(
88
+ path: pathops.Path, charString: T2CharString
89
+ ) -> T2CharString:
90
+ if charString.width == charString.private.defaultWidthX:
91
+ width = None
92
+ else:
93
+ width = charString.width - charString.private.nominalWidthX
94
+ t2Pen = T2CharStringPen(width=width, glyphSet=None)
95
+ path.draw(t2Pen)
96
+ return t2Pen.getCharString(charString.private, charString.globalSubrs)
97
+
98
+
99
+ def _round_path(
100
+ path: pathops.Path, round: Callable[[float], float] = otRound
101
+ ) -> pathops.Path:
102
+ rounded_path = pathops.Path()
103
+ for verb, points in path:
104
+ rounded_path.add(verb, *((round(p[0]), round(p[1])) for p in points))
105
+ return rounded_path
106
+
107
+
108
+ def _simplify(
109
+ path: pathops.Path,
110
+ debugGlyphName: str,
111
+ *,
112
+ round: Callable[[float], float] = otRound,
113
+ ) -> pathops.Path:
114
+ # skia-pathops has a bug where it sometimes fails to simplify paths when there
115
+ # are float coordinates and control points are very close to one another.
116
+ # Rounding coordinates to integers works around the bug.
117
+ # Since we are going to round glyf coordinates later on anyway, here it is
118
+ # ok(-ish) to also round before simplify. Better than failing the whole process
119
+ # for the entire font.
120
+ # https://bugs.chromium.org/p/skia/issues/detail?id=11958
121
+ # https://github.com/google/fonts/issues/3365
122
+ # TODO(anthrotype): remove once this Skia bug is fixed
123
+ try:
124
+ return pathops.simplify(path, clockwise=path.clockwise)
125
+ except pathops.PathOpsError:
126
+ pass
127
+
128
+ path = _round_path(path, round=round)
129
+ try:
130
+ path = pathops.simplify(path, clockwise=path.clockwise)
131
+ log.debug(
132
+ "skia-pathops failed to simplify '%s' with float coordinates, "
133
+ "but succeded using rounded integer coordinates",
134
+ debugGlyphName,
135
+ )
136
+ return path
137
+ except pathops.PathOpsError as e:
138
+ if log.isEnabledFor(logging.DEBUG):
139
+ path.dump()
140
+ raise RemoveOverlapsError(
141
+ f"Failed to remove overlaps from glyph {debugGlyphName!r}"
142
+ ) from e
143
+
144
+ raise AssertionError("Unreachable")
145
+
146
+
147
+ def _same_path(path1: pathops.Path, path2: pathops.Path) -> bool:
148
+ return {tuple(c) for c in path1.contours} == {tuple(c) for c in path2.contours}
149
+
150
+
151
+ def removeTTGlyphOverlaps(
152
+ glyphName: str,
153
+ glyphSet: _TTGlyphMapping,
154
+ glyfTable: _g_l_y_f.table__g_l_y_f,
155
+ hmtxTable: _h_m_t_x.table__h_m_t_x,
156
+ removeHinting: bool = True,
157
+ ) -> bool:
158
+ glyph = glyfTable[glyphName]
159
+ # decompose composite glyphs only if components overlap each other
160
+ if (
161
+ glyph.numberOfContours > 0
162
+ or glyph.isComposite()
163
+ and componentsOverlap(glyph, glyphSet)
164
+ ):
165
+ path = skPathFromGlyph(glyphName, glyphSet)
166
+
167
+ # remove overlaps
168
+ path2 = _simplify(path, glyphName)
169
+
170
+ # replace TTGlyph if simplified path is different (ignoring contour order)
171
+ if not _same_path(path, path2):
172
+ glyfTable[glyphName] = glyph = ttfGlyphFromSkPath(path2)
173
+ # simplified glyph is always unhinted
174
+ assert not glyph.program
175
+ # also ensure hmtx LSB == glyph.xMin so glyph origin is at x=0
176
+ width, lsb = hmtxTable[glyphName]
177
+ if lsb != glyph.xMin:
178
+ hmtxTable[glyphName] = (width, glyph.xMin)
179
+ return True
180
+
181
+ if removeHinting:
182
+ glyph.removeHinting()
183
+ return False
184
+
185
+
186
+ def _remove_glyf_overlaps(
187
+ *,
188
+ font: ttFont.TTFont,
189
+ glyphNames: Iterable[str],
190
+ glyphSet: _TTGlyphMapping,
191
+ removeHinting: bool,
192
+ ignoreErrors: bool,
193
+ ) -> None:
194
+ glyfTable = font["glyf"]
195
+ hmtxTable = font["hmtx"]
196
+
197
+ # process all simple glyphs first, then composites with increasing component depth,
198
+ # so that by the time we test for component intersections the respective base glyphs
199
+ # have already been simplified
200
+ glyphNames = sorted(
201
+ glyphNames,
202
+ key=lambda name: (
203
+ (
204
+ glyfTable[name].getCompositeMaxpValues(glyfTable).maxComponentDepth
205
+ if glyfTable[name].isComposite()
206
+ else 0
207
+ ),
208
+ name,
209
+ ),
210
+ )
211
+ modified = set()
212
+ for glyphName in glyphNames:
213
+ try:
214
+ if removeTTGlyphOverlaps(
215
+ glyphName, glyphSet, glyfTable, hmtxTable, removeHinting
216
+ ):
217
+ modified.add(glyphName)
218
+ except RemoveOverlapsError:
219
+ if not ignoreErrors:
220
+ raise
221
+ log.error("Failed to remove overlaps for '%s'", glyphName)
222
+
223
+ log.debug("Removed overlaps for %s glyphs:\n%s", len(modified), " ".join(modified))
224
+
225
+
226
+ def _remove_charstring_overlaps(
227
+ *,
228
+ glyphName: str,
229
+ glyphSet: _TTGlyphMapping,
230
+ cffFontSet: CFFFontSet,
231
+ ) -> bool:
232
+ path = skPathFromGlyph(glyphName, glyphSet)
233
+
234
+ # remove overlaps
235
+ path2 = _simplify(path, glyphName, round=noRound)
236
+
237
+ # replace TTGlyph if simplified path is different (ignoring contour order)
238
+ if not _same_path(path, path2):
239
+ charStrings = cffFontSet[0].CharStrings
240
+ charStrings[glyphName] = _charString_from_SkPath(path2, charStrings[glyphName])
241
+ return True
242
+
243
+ return False
244
+
245
+
246
+ def _remove_cff_overlaps(
247
+ *,
248
+ font: ttFont.TTFont,
249
+ glyphNames: Iterable[str],
250
+ glyphSet: _TTGlyphMapping,
251
+ removeHinting: bool,
252
+ ignoreErrors: bool,
253
+ table_tag: str,
254
+ removeUnusedSubroutines: bool = True,
255
+ ) -> None:
256
+ cffFontSet = font[table_tag].cff
257
+ modified = set()
258
+ for glyphName in glyphNames:
259
+ try:
260
+ if _remove_charstring_overlaps(
261
+ glyphName=glyphName,
262
+ glyphSet=glyphSet,
263
+ cffFontSet=cffFontSet,
264
+ ):
265
+ modified.add(glyphName)
266
+ except RemoveOverlapsError:
267
+ if not ignoreErrors:
268
+ raise
269
+ log.error("Failed to remove overlaps for '%s'", glyphName)
270
+
271
+ if not modified:
272
+ log.debug("No overlaps found in the specified CFF glyphs")
273
+ return
274
+
275
+ if removeHinting:
276
+ cffFontSet.remove_hints()
277
+
278
+ if removeUnusedSubroutines:
279
+ cffFontSet.remove_unused_subroutines()
280
+
281
+ log.debug("Removed overlaps for %s glyphs:\n%s", len(modified), " ".join(modified))
282
+
283
+
284
+ def removeOverlaps(
285
+ font: ttFont.TTFont,
286
+ glyphNames: Optional[Iterable[str]] = None,
287
+ removeHinting: bool = True,
288
+ ignoreErrors: bool = False,
289
+ *,
290
+ removeUnusedSubroutines: bool = True,
291
+ ) -> None:
292
+ """Simplify glyphs in TTFont by merging overlapping contours.
293
+
294
+ Overlapping components are first decomposed to simple contours, then merged.
295
+
296
+ Currently this only works for fonts with 'glyf' or 'CFF ' tables.
297
+ Raises NotImplementedError if 'glyf' or 'CFF ' tables are absent.
298
+
299
+ Note that removing overlaps invalidates the hinting. By default we drop hinting
300
+ from all glyphs whether or not overlaps are removed from a given one, as it would
301
+ look weird if only some glyphs are left (un)hinted.
302
+
303
+ Args:
304
+ font: input TTFont object, modified in place.
305
+ glyphNames: optional iterable of glyph names (str) to remove overlaps from.
306
+ By default, all glyphs in the font are processed.
307
+ removeHinting (bool): set to False to keep hinting for unmodified glyphs.
308
+ ignoreErrors (bool): set to True to ignore errors while removing overlaps,
309
+ thus keeping the tricky glyphs unchanged (fonttools/fonttools#2363).
310
+ removeUnusedSubroutines (bool): set to False to keep unused subroutines
311
+ in CFF table after removing overlaps. Default is to remove them if
312
+ any glyphs are modified.
313
+ """
314
+
315
+ if "glyf" not in font and "CFF " not in font and "CFF2" not in font:
316
+ raise NotImplementedError(
317
+ "No outline data found in the font: missing 'glyf', 'CFF ', or 'CFF2' table"
318
+ )
319
+
320
+ if glyphNames is None:
321
+ glyphNames = font.getGlyphOrder()
322
+
323
+ # Wraps the underlying glyphs, takes care of interfacing with drawing pens
324
+ glyphSet = font.getGlyphSet()
325
+
326
+ if "glyf" in font:
327
+ _remove_glyf_overlaps(
328
+ font=font,
329
+ glyphNames=glyphNames,
330
+ glyphSet=glyphSet,
331
+ removeHinting=removeHinting,
332
+ ignoreErrors=ignoreErrors,
333
+ )
334
+
335
+ if "CFF " in font or "CFF2" in font:
336
+ _remove_cff_overlaps(
337
+ font=font,
338
+ glyphNames=glyphNames,
339
+ glyphSet=glyphSet,
340
+ removeHinting=removeHinting,
341
+ ignoreErrors=ignoreErrors,
342
+ table_tag="CFF " if "CFF " in font else "CFF2",
343
+ removeUnusedSubroutines=removeUnusedSubroutines,
344
+ )
345
+
346
+
347
+ def main(args=None):
348
+ """Simplify glyphs in TTFont by merging overlapping contours."""
349
+
350
+ import argparse
351
+
352
+ parser = argparse.ArgumentParser(
353
+ "fonttools ttLib.removeOverlaps", description=__doc__
354
+ )
355
+
356
+ parser.add_argument("input", metavar="INPUT.ttf", help="Input font file")
357
+ parser.add_argument("output", metavar="OUTPUT.ttf", help="Output font file")
358
+ parser.add_argument(
359
+ "glyphs",
360
+ metavar="GLYPHS",
361
+ nargs="*",
362
+ help="Optional list of glyph names to remove overlaps from",
363
+ )
364
+ parser.add_argument(
365
+ "--keep-hinting",
366
+ action="store_true",
367
+ help="Keep hinting for unmodified glyphs, default is to drop hinting",
368
+ )
369
+ parser.add_argument(
370
+ "--ignore-errors",
371
+ action="store_true",
372
+ help="ignore errors while removing overlaps, "
373
+ "thus keeping the tricky glyphs unchanged",
374
+ )
375
+ parser.add_argument(
376
+ "--keep-unused-subroutines",
377
+ action="store_true",
378
+ help="Keep unused subroutines in CFF table after removing overlaps, "
379
+ "default is to remove them if any glyphs are modified",
380
+ )
381
+ args = parser.parse_args(args)
382
+
383
+ with ttFont.TTFont(args.input) as font:
384
+ removeOverlaps(
385
+ font=font,
386
+ glyphNames=args.glyphs or None,
387
+ removeHinting=not args.keep_hinting,
388
+ ignoreErrors=args.ignore_errors,
389
+ removeUnusedSubroutines=not args.keep_unused_subroutines,
390
+ )
391
+ font.save(args.output)
392
+
393
+
394
+ if __name__ == "__main__":
395
+ main()
@@ -0,0 +1,285 @@
1
+ """Reorder glyphs in a font."""
2
+
3
+ __author__ = "Rod Sheeter"
4
+
5
+ # See https://docs.google.com/document/d/1h9O-C_ndods87uY0QeIIcgAMiX2gDTpvO_IhMJsKAqs/
6
+ # for details.
7
+
8
+
9
+ from fontTools import ttLib
10
+ from fontTools.ttLib.tables import otBase
11
+ from fontTools.ttLib.tables import otTables as ot
12
+ from abc import ABC, abstractmethod
13
+ from dataclasses import dataclass
14
+ from collections import deque
15
+ from typing import (
16
+ Optional,
17
+ Any,
18
+ Callable,
19
+ Deque,
20
+ Iterable,
21
+ List,
22
+ Tuple,
23
+ )
24
+
25
+
26
+ _COVERAGE_ATTR = "Coverage" # tables that have one coverage use this name
27
+
28
+
29
+ def _sort_by_gid(
30
+ get_glyph_id: Callable[[str], int],
31
+ glyphs: List[str],
32
+ parallel_list: Optional[List[Any]],
33
+ ):
34
+ if parallel_list:
35
+ reordered = sorted(
36
+ ((g, e) for g, e in zip(glyphs, parallel_list)),
37
+ key=lambda t: get_glyph_id(t[0]),
38
+ )
39
+ sorted_glyphs, sorted_parallel_list = map(list, zip(*reordered))
40
+ parallel_list[:] = sorted_parallel_list
41
+ else:
42
+ sorted_glyphs = sorted(glyphs, key=get_glyph_id)
43
+
44
+ glyphs[:] = sorted_glyphs
45
+
46
+
47
+ def _get_dotted_attr(value: Any, dotted_attr: str) -> Any:
48
+ attr_names = dotted_attr.split(".")
49
+ assert attr_names
50
+
51
+ while attr_names:
52
+ attr_name = attr_names.pop(0)
53
+ value = getattr(value, attr_name)
54
+ return value
55
+
56
+
57
+ class ReorderRule(ABC):
58
+ """A rule to reorder something in a font to match the fonts glyph order."""
59
+
60
+ @abstractmethod
61
+ def apply(self, font: ttLib.TTFont, value: otBase.BaseTable) -> None: ...
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class ReorderCoverage(ReorderRule):
66
+ """Reorder a Coverage table, and optionally a list that is sorted parallel to it."""
67
+
68
+ # A list that is parallel to Coverage
69
+ parallel_list_attr: Optional[str] = None
70
+ coverage_attr: str = _COVERAGE_ATTR
71
+
72
+ def apply(self, font: ttLib.TTFont, value: otBase.BaseTable) -> None:
73
+ coverage = _get_dotted_attr(value, self.coverage_attr)
74
+
75
+ if type(coverage) is not list:
76
+ # Normal path, process one coverage that might have a parallel list
77
+ parallel_list = None
78
+ if self.parallel_list_attr:
79
+ parallel_list = _get_dotted_attr(value, self.parallel_list_attr)
80
+ assert (
81
+ type(parallel_list) is list
82
+ ), f"{self.parallel_list_attr} should be a list"
83
+ assert len(parallel_list) == len(coverage.glyphs), "Nothing makes sense"
84
+
85
+ _sort_by_gid(font.getGlyphID, coverage.glyphs, parallel_list)
86
+
87
+ else:
88
+ # A few tables have a list of coverage. No parallel list can exist.
89
+ assert (
90
+ not self.parallel_list_attr
91
+ ), f"Can't have multiple coverage AND a parallel list; {self}"
92
+ for coverage_entry in coverage:
93
+ _sort_by_gid(font.getGlyphID, coverage_entry.glyphs, None)
94
+
95
+
96
+ @dataclass(frozen=True)
97
+ class ReorderList(ReorderRule):
98
+ """Reorder the items within a list to match the updated glyph order.
99
+
100
+ Useful when a list ordered by coverage itself contains something ordered by a gid.
101
+ For example, the PairSet table of https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable.
102
+ """
103
+
104
+ list_attr: str
105
+ key: str
106
+
107
+ def apply(self, font: ttLib.TTFont, value: otBase.BaseTable) -> None:
108
+ lst = _get_dotted_attr(value, self.list_attr)
109
+ assert isinstance(lst, list), f"{self.list_attr} should be a list"
110
+ lst.sort(key=lambda v: font.getGlyphID(getattr(v, self.key)))
111
+
112
+
113
+ # (Type, Optional Format) => List[ReorderRule]
114
+ # Encodes the relationships Cosimo identified
115
+ _REORDER_RULES = {
116
+ # GPOS
117
+ (ot.SinglePos, 1): [ReorderCoverage()],
118
+ (ot.SinglePos, 2): [ReorderCoverage(parallel_list_attr="Value")],
119
+ (ot.PairPos, 1): [ReorderCoverage(parallel_list_attr="PairSet")],
120
+ (ot.PairSet, None): [ReorderList("PairValueRecord", key="SecondGlyph")],
121
+ (ot.PairPos, 2): [ReorderCoverage()],
122
+ (ot.CursivePos, 1): [ReorderCoverage(parallel_list_attr="EntryExitRecord")],
123
+ (ot.MarkBasePos, 1): [
124
+ ReorderCoverage(
125
+ coverage_attr="MarkCoverage", parallel_list_attr="MarkArray.MarkRecord"
126
+ ),
127
+ ReorderCoverage(
128
+ coverage_attr="BaseCoverage", parallel_list_attr="BaseArray.BaseRecord"
129
+ ),
130
+ ],
131
+ (ot.MarkLigPos, 1): [
132
+ ReorderCoverage(
133
+ coverage_attr="MarkCoverage", parallel_list_attr="MarkArray.MarkRecord"
134
+ ),
135
+ ReorderCoverage(
136
+ coverage_attr="LigatureCoverage",
137
+ parallel_list_attr="LigatureArray.LigatureAttach",
138
+ ),
139
+ ],
140
+ (ot.MarkMarkPos, 1): [
141
+ ReorderCoverage(
142
+ coverage_attr="Mark1Coverage", parallel_list_attr="Mark1Array.MarkRecord"
143
+ ),
144
+ ReorderCoverage(
145
+ coverage_attr="Mark2Coverage", parallel_list_attr="Mark2Array.Mark2Record"
146
+ ),
147
+ ],
148
+ (ot.ContextPos, 1): [ReorderCoverage(parallel_list_attr="PosRuleSet")],
149
+ (ot.ContextPos, 2): [ReorderCoverage()],
150
+ (ot.ContextPos, 3): [ReorderCoverage()],
151
+ (ot.ChainContextPos, 1): [ReorderCoverage(parallel_list_attr="ChainPosRuleSet")],
152
+ (ot.ChainContextPos, 2): [ReorderCoverage()],
153
+ (ot.ChainContextPos, 3): [
154
+ ReorderCoverage(coverage_attr="BacktrackCoverage"),
155
+ ReorderCoverage(coverage_attr="InputCoverage"),
156
+ ReorderCoverage(coverage_attr="LookAheadCoverage"),
157
+ ],
158
+ # GSUB
159
+ (ot.ContextSubst, 1): [ReorderCoverage(parallel_list_attr="SubRuleSet")],
160
+ (ot.ContextSubst, 2): [ReorderCoverage()],
161
+ (ot.ContextSubst, 3): [ReorderCoverage()],
162
+ (ot.ChainContextSubst, 1): [ReorderCoverage(parallel_list_attr="ChainSubRuleSet")],
163
+ (ot.ChainContextSubst, 2): [ReorderCoverage()],
164
+ (ot.ChainContextSubst, 3): [
165
+ ReorderCoverage(coverage_attr="BacktrackCoverage"),
166
+ ReorderCoverage(coverage_attr="InputCoverage"),
167
+ ReorderCoverage(coverage_attr="LookAheadCoverage"),
168
+ ],
169
+ (ot.ReverseChainSingleSubst, 1): [
170
+ ReorderCoverage(parallel_list_attr="Substitute"),
171
+ ReorderCoverage(coverage_attr="BacktrackCoverage"),
172
+ ReorderCoverage(coverage_attr="LookAheadCoverage"),
173
+ ],
174
+ # GDEF
175
+ (ot.AttachList, None): [ReorderCoverage(parallel_list_attr="AttachPoint")],
176
+ (ot.LigCaretList, None): [ReorderCoverage(parallel_list_attr="LigGlyph")],
177
+ (ot.MarkGlyphSetsDef, None): [ReorderCoverage()],
178
+ # MATH
179
+ (ot.MathGlyphInfo, None): [ReorderCoverage(coverage_attr="ExtendedShapeCoverage")],
180
+ (ot.MathItalicsCorrectionInfo, None): [
181
+ ReorderCoverage(parallel_list_attr="ItalicsCorrection")
182
+ ],
183
+ (ot.MathTopAccentAttachment, None): [
184
+ ReorderCoverage(
185
+ coverage_attr="TopAccentCoverage", parallel_list_attr="TopAccentAttachment"
186
+ )
187
+ ],
188
+ (ot.MathKernInfo, None): [
189
+ ReorderCoverage(
190
+ coverage_attr="MathKernCoverage", parallel_list_attr="MathKernInfoRecords"
191
+ )
192
+ ],
193
+ (ot.MathVariants, None): [
194
+ ReorderCoverage(
195
+ coverage_attr="VertGlyphCoverage",
196
+ parallel_list_attr="VertGlyphConstruction",
197
+ ),
198
+ ReorderCoverage(
199
+ coverage_attr="HorizGlyphCoverage",
200
+ parallel_list_attr="HorizGlyphConstruction",
201
+ ),
202
+ ],
203
+ }
204
+
205
+
206
+ # TODO Port to otTraverse
207
+
208
+ SubTablePath = Tuple[otBase.BaseTable.SubTableEntry, ...]
209
+
210
+
211
+ def _bfs_base_table(
212
+ root: otBase.BaseTable, root_accessor: str
213
+ ) -> Iterable[SubTablePath]:
214
+ yield from _traverse_ot_data(
215
+ root, root_accessor, lambda frontier, new: frontier.extend(new)
216
+ )
217
+
218
+
219
+ # Given f(current frontier, new entries) add new entries to frontier
220
+ AddToFrontierFn = Callable[[Deque[SubTablePath], List[SubTablePath]], None]
221
+
222
+
223
+ def _traverse_ot_data(
224
+ root: otBase.BaseTable, root_accessor: str, add_to_frontier_fn: AddToFrontierFn
225
+ ) -> Iterable[SubTablePath]:
226
+ # no visited because general otData is forward-offset only and thus cannot cycle
227
+
228
+ frontier: Deque[SubTablePath] = deque()
229
+ frontier.append((otBase.BaseTable.SubTableEntry(root_accessor, root),))
230
+ while frontier:
231
+ # path is (value, attr_name) tuples. attr_name is attr of parent to get value
232
+ path = frontier.popleft()
233
+ current = path[-1].value
234
+
235
+ yield path
236
+
237
+ new_entries = []
238
+ for subtable_entry in current.iterSubTables():
239
+ new_entries.append(path + (subtable_entry,))
240
+
241
+ add_to_frontier_fn(frontier, new_entries)
242
+
243
+
244
+ def reorderGlyphs(font: ttLib.TTFont, new_glyph_order: List[str]):
245
+ old_glyph_order = font.getGlyphOrder()
246
+ if len(new_glyph_order) != len(old_glyph_order):
247
+ raise ValueError(
248
+ f"New glyph order contains {len(new_glyph_order)} glyphs, "
249
+ f"but font has {len(old_glyph_order)} glyphs"
250
+ )
251
+
252
+ if set(old_glyph_order) != set(new_glyph_order):
253
+ raise ValueError(
254
+ "New glyph order does not contain the same set of glyphs as the font:\n"
255
+ f"* only in new: {set(new_glyph_order) - set(old_glyph_order)}\n"
256
+ f"* only in old: {set(old_glyph_order) - set(new_glyph_order)}"
257
+ )
258
+
259
+ # Changing the order of glyphs in a TTFont requires that all tables that use
260
+ # glyph indexes have been fully.
261
+ # Cf. https://github.com/fonttools/fonttools/issues/2060
262
+ font.ensureDecompiled()
263
+ not_loaded = sorted(t for t in font.keys() if not font.isLoaded(t))
264
+ if not_loaded:
265
+ raise ValueError(f"Everything should be loaded, following aren't: {not_loaded}")
266
+
267
+ font.setGlyphOrder(new_glyph_order)
268
+
269
+ coverage_containers = {"GDEF", "GPOS", "GSUB", "MATH"}
270
+ for tag in coverage_containers:
271
+ if tag in font.keys():
272
+ for path in _bfs_base_table(font[tag].table, f'font["{tag}"]'):
273
+ value = path[-1].value
274
+ reorder_key = (type(value), getattr(value, "Format", None))
275
+ for reorder in _REORDER_RULES.get(reorder_key, []):
276
+ reorder.apply(font, value)
277
+
278
+ for tag in ["CFF ", "CFF2"]:
279
+ if tag in font:
280
+ cff_table = font[tag]
281
+ charstrings = cff_table.cff.topDictIndex[0].CharStrings.charStrings
282
+ cff_table.cff.topDictIndex[0].charset = new_glyph_order
283
+ cff_table.cff.topDictIndex[0].CharStrings.charStrings = {
284
+ k: charstrings.get(k) for k in new_glyph_order
285
+ }