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,170 @@
1
+ """ fontTools.misc.classifyTools.py -- tools for classifying things.
2
+ """
3
+
4
+
5
+ class Classifier(object):
6
+ """
7
+ Main Classifier object, used to classify things into similar sets.
8
+ """
9
+
10
+ def __init__(self, sort=True):
11
+ self._things = set() # set of all things known so far
12
+ self._sets = [] # list of class sets produced so far
13
+ self._mapping = {} # map from things to their class set
14
+ self._dirty = False
15
+ self._sort = sort
16
+
17
+ def add(self, set_of_things):
18
+ """
19
+ Add a set to the classifier. Any iterable is accepted.
20
+ """
21
+ if not set_of_things:
22
+ return
23
+
24
+ self._dirty = True
25
+
26
+ things, sets, mapping = self._things, self._sets, self._mapping
27
+
28
+ s = set(set_of_things)
29
+ intersection = s.intersection(things) # existing things
30
+ s.difference_update(intersection) # new things
31
+ difference = s
32
+ del s
33
+
34
+ # Add new class for new things
35
+ if difference:
36
+ things.update(difference)
37
+ sets.append(difference)
38
+ for thing in difference:
39
+ mapping[thing] = difference
40
+ del difference
41
+
42
+ while intersection:
43
+ # Take one item and process the old class it belongs to
44
+ old_class = mapping[next(iter(intersection))]
45
+ old_class_intersection = old_class.intersection(intersection)
46
+
47
+ # Update old class to remove items from new set
48
+ old_class.difference_update(old_class_intersection)
49
+
50
+ # Remove processed items from todo list
51
+ intersection.difference_update(old_class_intersection)
52
+
53
+ # Add new class for the intersection with old class
54
+ sets.append(old_class_intersection)
55
+ for thing in old_class_intersection:
56
+ mapping[thing] = old_class_intersection
57
+ del old_class_intersection
58
+
59
+ def update(self, list_of_sets):
60
+ """
61
+ Add a a list of sets to the classifier. Any iterable of iterables is accepted.
62
+ """
63
+ for s in list_of_sets:
64
+ self.add(s)
65
+
66
+ def _process(self):
67
+ if not self._dirty:
68
+ return
69
+
70
+ # Do any deferred processing
71
+ sets = self._sets
72
+ self._sets = [s for s in sets if s]
73
+
74
+ if self._sort:
75
+ self._sets = sorted(self._sets, key=lambda s: (-len(s), sorted(s)))
76
+
77
+ self._dirty = False
78
+
79
+ # Output methods
80
+
81
+ def getThings(self):
82
+ """Returns the set of all things known so far.
83
+
84
+ The return value belongs to the Classifier object and should NOT
85
+ be modified while the classifier is still in use.
86
+ """
87
+ self._process()
88
+ return self._things
89
+
90
+ def getMapping(self):
91
+ """Returns the mapping from things to their class set.
92
+
93
+ The return value belongs to the Classifier object and should NOT
94
+ be modified while the classifier is still in use.
95
+ """
96
+ self._process()
97
+ return self._mapping
98
+
99
+ def getClasses(self):
100
+ """Returns the list of class sets.
101
+
102
+ The return value belongs to the Classifier object and should NOT
103
+ be modified while the classifier is still in use.
104
+ """
105
+ self._process()
106
+ return self._sets
107
+
108
+
109
+ def classify(list_of_sets, sort=True):
110
+ """
111
+ Takes a iterable of iterables (list of sets from here on; but any
112
+ iterable works.), and returns the smallest list of sets such that
113
+ each set, is either a subset, or is disjoint from, each of the input
114
+ sets.
115
+
116
+ In other words, this function classifies all the things present in
117
+ any of the input sets, into similar classes, based on which sets
118
+ things are a member of.
119
+
120
+ If sort=True, return class sets are sorted by decreasing size and
121
+ their natural sort order within each class size. Otherwise, class
122
+ sets are returned in the order that they were identified, which is
123
+ generally not significant.
124
+
125
+ >>> classify([]) == ([], {})
126
+ True
127
+ >>> classify([[]]) == ([], {})
128
+ True
129
+ >>> classify([[], []]) == ([], {})
130
+ True
131
+ >>> classify([[1]]) == ([{1}], {1: {1}})
132
+ True
133
+ >>> classify([[1,2]]) == ([{1, 2}], {1: {1, 2}, 2: {1, 2}})
134
+ True
135
+ >>> classify([[1],[2]]) == ([{1}, {2}], {1: {1}, 2: {2}})
136
+ True
137
+ >>> classify([[1,2],[2]]) == ([{1}, {2}], {1: {1}, 2: {2}})
138
+ True
139
+ >>> classify([[1,2],[2,4]]) == ([{1}, {2}, {4}], {1: {1}, 2: {2}, 4: {4}})
140
+ True
141
+ >>> classify([[1,2],[2,4,5]]) == (
142
+ ... [{4, 5}, {1}, {2}], {1: {1}, 2: {2}, 4: {4, 5}, 5: {4, 5}})
143
+ True
144
+ >>> classify([[1,2],[2,4,5]], sort=False) == (
145
+ ... [{1}, {4, 5}, {2}], {1: {1}, 2: {2}, 4: {4, 5}, 5: {4, 5}})
146
+ True
147
+ >>> classify([[1,2,9],[2,4,5]], sort=False) == (
148
+ ... [{1, 9}, {4, 5}, {2}], {1: {1, 9}, 2: {2}, 4: {4, 5}, 5: {4, 5},
149
+ ... 9: {1, 9}})
150
+ True
151
+ >>> classify([[1,2,9,15],[2,4,5]], sort=False) == (
152
+ ... [{1, 9, 15}, {4, 5}, {2}], {1: {1, 9, 15}, 2: {2}, 4: {4, 5},
153
+ ... 5: {4, 5}, 9: {1, 9, 15}, 15: {1, 9, 15}})
154
+ True
155
+ >>> classes, mapping = classify([[1,2,9,15],[2,4,5],[15,5]], sort=False)
156
+ >>> set([frozenset(c) for c in classes]) == set(
157
+ ... [frozenset(s) for s in ({1, 9}, {4}, {2}, {5}, {15})])
158
+ True
159
+ >>> mapping == {1: {1, 9}, 2: {2}, 4: {4}, 5: {5}, 9: {1, 9}, 15: {15}}
160
+ True
161
+ """
162
+ classifier = Classifier(sort=sort)
163
+ classifier.update(list_of_sets)
164
+ return classifier.getClasses(), classifier.getMapping()
165
+
166
+
167
+ if __name__ == "__main__":
168
+ import sys, doctest
169
+
170
+ sys.exit(doctest.testmod(optionflags=doctest.ELLIPSIS).failed)
@@ -0,0 +1,53 @@
1
+ """Collection of utilities for command-line interfaces and console scripts."""
2
+
3
+ import os
4
+ import re
5
+
6
+
7
+ numberAddedRE = re.compile(r"#\d+$")
8
+
9
+
10
+ def makeOutputFileName(
11
+ input, outputDir=None, extension=None, overWrite=False, suffix=""
12
+ ):
13
+ """Generates a suitable file name for writing output.
14
+
15
+ Often tools will want to take a file, do some kind of transformation to it,
16
+ and write it out again. This function determines an appropriate name for the
17
+ output file, through one or more of the following steps:
18
+
19
+ - changing the output directory
20
+ - appending suffix before file extension
21
+ - replacing the file extension
22
+ - suffixing the filename with a number (``#1``, ``#2``, etc.) to avoid
23
+ overwriting an existing file.
24
+
25
+ Args:
26
+ input: Name of input file.
27
+ outputDir: Optionally, a new directory to write the file into.
28
+ suffix: Optionally, a string suffix is appended to file name before
29
+ the extension.
30
+ extension: Optionally, a replacement for the current file extension.
31
+ overWrite: Overwriting an existing file is permitted if true; if false
32
+ and the proposed filename exists, a new name will be generated by
33
+ adding an appropriate number suffix.
34
+
35
+ Returns:
36
+ str: Suitable output filename
37
+ """
38
+ dirName, fileName = os.path.split(input)
39
+ fileName, ext = os.path.splitext(fileName)
40
+ if outputDir:
41
+ dirName = outputDir
42
+ fileName = numberAddedRE.split(fileName)[0]
43
+ if extension is None:
44
+ extension = os.path.splitext(input)[1]
45
+ output = os.path.join(dirName, fileName + suffix + extension)
46
+ n = 1
47
+ if not overWrite:
48
+ while os.path.exists(output):
49
+ output = os.path.join(
50
+ dirName, fileName + suffix + "#" + repr(n) + extension
51
+ )
52
+ n += 1
53
+ return output
@@ -0,0 +1,349 @@
1
+ """
2
+ Code of the config system; not related to fontTools or fonts in particular.
3
+
4
+ The options that are specific to fontTools are in :mod:`fontTools.config`.
5
+
6
+ To create your own config system, you need to create an instance of
7
+ :class:`Options`, and a subclass of :class:`AbstractConfig` with its
8
+ ``options`` class variable set to your instance of Options.
9
+
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ from dataclasses import dataclass
16
+ from typing import (
17
+ Any,
18
+ Callable,
19
+ ClassVar,
20
+ Dict,
21
+ Iterable,
22
+ Mapping,
23
+ MutableMapping,
24
+ Optional,
25
+ Set,
26
+ Union,
27
+ )
28
+
29
+
30
+ log = logging.getLogger(__name__)
31
+
32
+ __all__ = [
33
+ "AbstractConfig",
34
+ "ConfigAlreadyRegisteredError",
35
+ "ConfigError",
36
+ "ConfigUnknownOptionError",
37
+ "ConfigValueParsingError",
38
+ "ConfigValueValidationError",
39
+ "Option",
40
+ "Options",
41
+ ]
42
+
43
+
44
+ class ConfigError(Exception):
45
+ """Base exception for the config module."""
46
+
47
+
48
+ class ConfigAlreadyRegisteredError(ConfigError):
49
+ """Raised when a module tries to register a configuration option that
50
+ already exists.
51
+
52
+ Should not be raised too much really, only when developing new fontTools
53
+ modules.
54
+ """
55
+
56
+ def __init__(self, name):
57
+ super().__init__(f"Config option {name} is already registered.")
58
+
59
+
60
+ class ConfigValueParsingError(ConfigError):
61
+ """Raised when a configuration value cannot be parsed."""
62
+
63
+ def __init__(self, name, value):
64
+ super().__init__(
65
+ f"Config option {name}: value cannot be parsed (given {repr(value)})"
66
+ )
67
+
68
+
69
+ class ConfigValueValidationError(ConfigError):
70
+ """Raised when a configuration value cannot be validated."""
71
+
72
+ def __init__(self, name, value):
73
+ super().__init__(
74
+ f"Config option {name}: value is invalid (given {repr(value)})"
75
+ )
76
+
77
+
78
+ class ConfigUnknownOptionError(ConfigError):
79
+ """Raised when a configuration option is unknown."""
80
+
81
+ def __init__(self, option_or_name):
82
+ name = (
83
+ f"'{option_or_name.name}' (id={id(option_or_name)})>"
84
+ if isinstance(option_or_name, Option)
85
+ else f"'{option_or_name}'"
86
+ )
87
+ super().__init__(f"Config option {name} is unknown")
88
+
89
+
90
+ # eq=False because Options are unique, not fungible objects
91
+ @dataclass(frozen=True, eq=False)
92
+ class Option:
93
+ name: str
94
+ """Unique name identifying the option (e.g. package.module:MY_OPTION)."""
95
+ help: str
96
+ """Help text for this option."""
97
+ default: Any
98
+ """Default value for this option."""
99
+ parse: Callable[[str], Any]
100
+ """Turn input (e.g. string) into proper type. Only when reading from file."""
101
+ validate: Optional[Callable[[Any], bool]] = None
102
+ """Return true if the given value is an acceptable value."""
103
+
104
+ @staticmethod
105
+ def parse_optional_bool(v: str) -> Optional[bool]:
106
+ s = str(v).lower()
107
+ if s in {"0", "no", "false"}:
108
+ return False
109
+ if s in {"1", "yes", "true"}:
110
+ return True
111
+ if s in {"auto", "none"}:
112
+ return None
113
+ raise ValueError("invalid optional bool: {v!r}")
114
+
115
+ @staticmethod
116
+ def validate_optional_bool(v: Any) -> bool:
117
+ return v is None or isinstance(v, bool)
118
+
119
+
120
+ class Options(Mapping):
121
+ """Registry of available options for a given config system.
122
+
123
+ Define new options using the :meth:`register()` method.
124
+
125
+ Access existing options using the Mapping interface.
126
+ """
127
+
128
+ __options: Dict[str, Option]
129
+
130
+ def __init__(self, other: "Options" = None) -> None:
131
+ self.__options = {}
132
+ if other is not None:
133
+ for option in other.values():
134
+ self.register_option(option)
135
+
136
+ def register(
137
+ self,
138
+ name: str,
139
+ help: str,
140
+ default: Any,
141
+ parse: Callable[[str], Any],
142
+ validate: Optional[Callable[[Any], bool]] = None,
143
+ ) -> Option:
144
+ """Create and register a new option."""
145
+ return self.register_option(Option(name, help, default, parse, validate))
146
+
147
+ def register_option(self, option: Option) -> Option:
148
+ """Register a new option."""
149
+ name = option.name
150
+ if name in self.__options:
151
+ raise ConfigAlreadyRegisteredError(name)
152
+ self.__options[name] = option
153
+ return option
154
+
155
+ def is_registered(self, option: Option) -> bool:
156
+ """Return True if the same option object is already registered."""
157
+ return self.__options.get(option.name) is option
158
+
159
+ def __getitem__(self, key: str) -> Option:
160
+ return self.__options.__getitem__(key)
161
+
162
+ def __iter__(self) -> Iterator[str]:
163
+ return self.__options.__iter__()
164
+
165
+ def __len__(self) -> int:
166
+ return self.__options.__len__()
167
+
168
+ def __repr__(self) -> str:
169
+ return (
170
+ f"{self.__class__.__name__}({{\n"
171
+ + "".join(
172
+ f" {k!r}: Option(default={v.default!r}, ...),\n"
173
+ for k, v in self.__options.items()
174
+ )
175
+ + "})"
176
+ )
177
+
178
+
179
+ _USE_GLOBAL_DEFAULT = object()
180
+
181
+
182
+ class AbstractConfig(MutableMapping):
183
+ """
184
+ Create a set of config values, optionally pre-filled with values from
185
+ the given dictionary or pre-existing config object.
186
+
187
+ The class implements the MutableMapping protocol keyed by option name (`str`).
188
+ For convenience its methods accept either Option or str as the key parameter.
189
+
190
+ .. seealso:: :meth:`set()`
191
+
192
+ This config class is abstract because it needs its ``options`` class
193
+ var to be set to an instance of :class:`Options` before it can be
194
+ instanciated and used.
195
+
196
+ .. code:: python
197
+
198
+ class MyConfig(AbstractConfig):
199
+ options = Options()
200
+
201
+ MyConfig.register_option( "test:option_name", "This is an option", 0, int, lambda v: isinstance(v, int))
202
+
203
+ cfg = MyConfig({"test:option_name": 10})
204
+
205
+ """
206
+
207
+ options: ClassVar[Options]
208
+
209
+ @classmethod
210
+ def register_option(
211
+ cls,
212
+ name: str,
213
+ help: str,
214
+ default: Any,
215
+ parse: Callable[[str], Any],
216
+ validate: Optional[Callable[[Any], bool]] = None,
217
+ ) -> Option:
218
+ """Register an available option in this config system."""
219
+ return cls.options.register(
220
+ name, help=help, default=default, parse=parse, validate=validate
221
+ )
222
+
223
+ _values: Dict[str, Any]
224
+
225
+ def __init__(
226
+ self,
227
+ values: Union[AbstractConfig, Dict[Union[Option, str], Any]] = {},
228
+ parse_values: bool = False,
229
+ skip_unknown: bool = False,
230
+ ):
231
+ self._values = {}
232
+ values_dict = values._values if isinstance(values, AbstractConfig) else values
233
+ for name, value in values_dict.items():
234
+ self.set(name, value, parse_values, skip_unknown)
235
+
236
+ def _resolve_option(self, option_or_name: Union[Option, str]) -> Option:
237
+ if isinstance(option_or_name, Option):
238
+ option = option_or_name
239
+ if not self.options.is_registered(option):
240
+ raise ConfigUnknownOptionError(option)
241
+ return option
242
+ elif isinstance(option_or_name, str):
243
+ name = option_or_name
244
+ try:
245
+ return self.options[name]
246
+ except KeyError:
247
+ raise ConfigUnknownOptionError(name)
248
+ else:
249
+ raise TypeError(
250
+ "expected Option or str, found "
251
+ f"{type(option_or_name).__name__}: {option_or_name!r}"
252
+ )
253
+
254
+ def set(
255
+ self,
256
+ option_or_name: Union[Option, str],
257
+ value: Any,
258
+ parse_values: bool = False,
259
+ skip_unknown: bool = False,
260
+ ):
261
+ """Set the value of an option.
262
+
263
+ Args:
264
+ * `option_or_name`: an `Option` object or its name (`str`).
265
+ * `value`: the value to be assigned to given option.
266
+ * `parse_values`: parse the configuration value from a string into
267
+ its proper type, as per its `Option` object. The default
268
+ behavior is to raise `ConfigValueValidationError` when the value
269
+ is not of the right type. Useful when reading options from a
270
+ file type that doesn't support as many types as Python.
271
+ * `skip_unknown`: skip unknown configuration options. The default
272
+ behaviour is to raise `ConfigUnknownOptionError`. Useful when
273
+ reading options from a configuration file that has extra entries
274
+ (e.g. for a later version of fontTools)
275
+ """
276
+ try:
277
+ option = self._resolve_option(option_or_name)
278
+ except ConfigUnknownOptionError as e:
279
+ if skip_unknown:
280
+ log.debug(str(e))
281
+ return
282
+ raise
283
+
284
+ # Can be useful if the values come from a source that doesn't have
285
+ # strict typing (.ini file? Terminal input?)
286
+ if parse_values:
287
+ try:
288
+ value = option.parse(value)
289
+ except Exception as e:
290
+ raise ConfigValueParsingError(option.name, value) from e
291
+
292
+ if option.validate is not None and not option.validate(value):
293
+ raise ConfigValueValidationError(option.name, value)
294
+
295
+ self._values[option.name] = value
296
+
297
+ def get(
298
+ self, option_or_name: Union[Option, str], default: Any = _USE_GLOBAL_DEFAULT
299
+ ) -> Any:
300
+ """
301
+ Get the value of an option. The value which is returned is the first
302
+ provided among:
303
+
304
+ 1. a user-provided value in the options's ``self._values`` dict
305
+ 2. a caller-provided default value to this method call
306
+ 3. the global default for the option provided in ``fontTools.config``
307
+
308
+ This is to provide the ability to migrate progressively from config
309
+ options passed as arguments to fontTools APIs to config options read
310
+ from the current TTFont, e.g.
311
+
312
+ .. code:: python
313
+
314
+ def fontToolsAPI(font, some_option):
315
+ value = font.cfg.get("someLib.module:SOME_OPTION", some_option)
316
+ # use value
317
+
318
+ That way, the function will work the same for users of the API that
319
+ still pass the option to the function call, but will favour the new
320
+ config mechanism if the given font specifies a value for that option.
321
+ """
322
+ option = self._resolve_option(option_or_name)
323
+ if option.name in self._values:
324
+ return self._values[option.name]
325
+ if default is not _USE_GLOBAL_DEFAULT:
326
+ return default
327
+ return option.default
328
+
329
+ def copy(self):
330
+ return self.__class__(self._values)
331
+
332
+ def __getitem__(self, option_or_name: Union[Option, str]) -> Any:
333
+ return self.get(option_or_name)
334
+
335
+ def __setitem__(self, option_or_name: Union[Option, str], value: Any) -> None:
336
+ return self.set(option_or_name, value)
337
+
338
+ def __delitem__(self, option_or_name: Union[Option, str]) -> None:
339
+ option = self._resolve_option(option_or_name)
340
+ del self._values[option.name]
341
+
342
+ def __iter__(self) -> Iterable[str]:
343
+ return self._values.__iter__()
344
+
345
+ def __len__(self) -> int:
346
+ return len(self._values)
347
+
348
+ def __repr__(self) -> str:
349
+ return f"{self.__class__.__name__}({repr(self._values)})"
@@ -0,0 +1,27 @@
1
+ """ Exports a no-op 'cython' namespace similar to
2
+ https://github.com/cython/cython/blob/master/Cython/Shadow.py
3
+
4
+ This allows to optionally compile @cython decorated functions
5
+ (when cython is available at built time), or run the same code
6
+ as pure-python, without runtime dependency on cython module.
7
+
8
+ We only define the symbols that we use. E.g. see fontTools.cu2qu
9
+ """
10
+
11
+ from types import SimpleNamespace
12
+
13
+
14
+ def _empty_decorator(x):
15
+ return x
16
+
17
+
18
+ compiled = False
19
+
20
+ for name in ("double", "complex", "int"):
21
+ globals()[name] = None
22
+
23
+ for name in ("cfunc", "inline"):
24
+ globals()[name] = _empty_decorator
25
+
26
+ locals = lambda **_: _empty_decorator
27
+ returns = lambda _: _empty_decorator
@@ -0,0 +1,83 @@
1
+ """Misc dict tools."""
2
+
3
+ __all__ = ["hashdict"]
4
+
5
+
6
+ # https://stackoverflow.com/questions/1151658/python-hashable-dicts
7
+ class hashdict(dict):
8
+ """
9
+ hashable dict implementation, suitable for use as a key into
10
+ other dicts.
11
+
12
+ >>> h1 = hashdict({"apples": 1, "bananas":2})
13
+ >>> h2 = hashdict({"bananas": 3, "mangoes": 5})
14
+ >>> h1+h2
15
+ hashdict(apples=1, bananas=3, mangoes=5)
16
+ >>> d1 = {}
17
+ >>> d1[h1] = "salad"
18
+ >>> d1[h1]
19
+ 'salad'
20
+ >>> d1[h2]
21
+ Traceback (most recent call last):
22
+ ...
23
+ KeyError: hashdict(bananas=3, mangoes=5)
24
+
25
+ based on answers from
26
+ http://stackoverflow.com/questions/1151658/python-hashable-dicts
27
+
28
+ """
29
+
30
+ def __key(self):
31
+ return tuple(sorted(self.items()))
32
+
33
+ def __repr__(self):
34
+ return "{0}({1})".format(
35
+ self.__class__.__name__,
36
+ ", ".join("{0}={1}".format(str(i[0]), repr(i[1])) for i in self.__key()),
37
+ )
38
+
39
+ def __hash__(self):
40
+ return hash(self.__key())
41
+
42
+ def __setitem__(self, key, value):
43
+ raise TypeError(
44
+ "{0} does not support item assignment".format(self.__class__.__name__)
45
+ )
46
+
47
+ def __delitem__(self, key):
48
+ raise TypeError(
49
+ "{0} does not support item assignment".format(self.__class__.__name__)
50
+ )
51
+
52
+ def clear(self):
53
+ raise TypeError(
54
+ "{0} does not support item assignment".format(self.__class__.__name__)
55
+ )
56
+
57
+ def pop(self, *args, **kwargs):
58
+ raise TypeError(
59
+ "{0} does not support item assignment".format(self.__class__.__name__)
60
+ )
61
+
62
+ def popitem(self, *args, **kwargs):
63
+ raise TypeError(
64
+ "{0} does not support item assignment".format(self.__class__.__name__)
65
+ )
66
+
67
+ def setdefault(self, *args, **kwargs):
68
+ raise TypeError(
69
+ "{0} does not support item assignment".format(self.__class__.__name__)
70
+ )
71
+
72
+ def update(self, *args, **kwargs):
73
+ raise TypeError(
74
+ "{0} does not support item assignment".format(self.__class__.__name__)
75
+ )
76
+
77
+ # update is not ok because it mutates the object
78
+ # __add__ is ok because it creates a new object
79
+ # while the new object is under construction, it's ok to mutate it
80
+ def __add__(self, right):
81
+ result = hashdict(self)
82
+ dict.update(result, right)
83
+ return result