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,245 @@
1
+ """
2
+ This module implements the algorithm for converting between a "user name" -
3
+ something that a user can choose arbitrarily inside a font editor - and a file
4
+ name suitable for use in a wide range of operating systems and filesystems.
5
+
6
+ The `UFO 3 specification <http://unifiedfontobject.org/versions/ufo3/conventions/>`_
7
+ provides an example of an algorithm for such conversion, which avoids illegal
8
+ characters, reserved file names, ambiguity between upper- and lower-case
9
+ characters, and clashes with existing files.
10
+
11
+ This code was originally copied from
12
+ `ufoLib <https://github.com/unified-font-object/ufoLib/blob/8747da7/Lib/ufoLib/filenames.py>`_
13
+ by Tal Leming and is copyright (c) 2005-2016, The RoboFab Developers:
14
+
15
+ - Erik van Blokland
16
+ - Tal Leming
17
+ - Just van Rossum
18
+ """
19
+
20
+ illegalCharacters = r"\" * + / : < > ? [ \ ] | \0".split(" ")
21
+ illegalCharacters += [chr(i) for i in range(1, 32)]
22
+ illegalCharacters += [chr(0x7F)]
23
+ reservedFileNames = "CON PRN AUX CLOCK$ NUL A:-Z: COM1".lower().split(" ")
24
+ reservedFileNames += "LPT1 LPT2 LPT3 COM2 COM3 COM4".lower().split(" ")
25
+ maxFileNameLength = 255
26
+
27
+
28
+ class NameTranslationError(Exception):
29
+ pass
30
+
31
+
32
+ def userNameToFileName(userName, existing=[], prefix="", suffix=""):
33
+ """Converts from a user name to a file name.
34
+
35
+ Takes care to avoid illegal characters, reserved file names, ambiguity between
36
+ upper- and lower-case characters, and clashes with existing files.
37
+
38
+ Args:
39
+ userName (str): The input file name.
40
+ existing: A case-insensitive list of all existing file names.
41
+ prefix: Prefix to be prepended to the file name.
42
+ suffix: Suffix to be appended to the file name.
43
+
44
+ Returns:
45
+ A suitable filename.
46
+
47
+ Raises:
48
+ NameTranslationError: If no suitable name could be generated.
49
+
50
+ Examples::
51
+
52
+ >>> userNameToFileName("a") == "a"
53
+ True
54
+ >>> userNameToFileName("A") == "A_"
55
+ True
56
+ >>> userNameToFileName("AE") == "A_E_"
57
+ True
58
+ >>> userNameToFileName("Ae") == "A_e"
59
+ True
60
+ >>> userNameToFileName("ae") == "ae"
61
+ True
62
+ >>> userNameToFileName("aE") == "aE_"
63
+ True
64
+ >>> userNameToFileName("a.alt") == "a.alt"
65
+ True
66
+ >>> userNameToFileName("A.alt") == "A_.alt"
67
+ True
68
+ >>> userNameToFileName("A.Alt") == "A_.A_lt"
69
+ True
70
+ >>> userNameToFileName("A.aLt") == "A_.aL_t"
71
+ True
72
+ >>> userNameToFileName(u"A.alT") == "A_.alT_"
73
+ True
74
+ >>> userNameToFileName("T_H") == "T__H_"
75
+ True
76
+ >>> userNameToFileName("T_h") == "T__h"
77
+ True
78
+ >>> userNameToFileName("t_h") == "t_h"
79
+ True
80
+ >>> userNameToFileName("F_F_I") == "F__F__I_"
81
+ True
82
+ >>> userNameToFileName("f_f_i") == "f_f_i"
83
+ True
84
+ >>> userNameToFileName("Aacute_V.swash") == "A_acute_V_.swash"
85
+ True
86
+ >>> userNameToFileName(".notdef") == "_notdef"
87
+ True
88
+ >>> userNameToFileName("con") == "_con"
89
+ True
90
+ >>> userNameToFileName("CON") == "C_O_N_"
91
+ True
92
+ >>> userNameToFileName("con.alt") == "_con.alt"
93
+ True
94
+ >>> userNameToFileName("alt.con") == "alt._con"
95
+ True
96
+ """
97
+ # the incoming name must be a str
98
+ if not isinstance(userName, str):
99
+ raise ValueError("The value for userName must be a string.")
100
+ # establish the prefix and suffix lengths
101
+ prefixLength = len(prefix)
102
+ suffixLength = len(suffix)
103
+ # replace an initial period with an _
104
+ # if no prefix is to be added
105
+ if not prefix and userName[0] == ".":
106
+ userName = "_" + userName[1:]
107
+ # filter the user name
108
+ filteredUserName = []
109
+ for character in userName:
110
+ # replace illegal characters with _
111
+ if character in illegalCharacters:
112
+ character = "_"
113
+ # add _ to all non-lower characters
114
+ elif character != character.lower():
115
+ character += "_"
116
+ filteredUserName.append(character)
117
+ userName = "".join(filteredUserName)
118
+ # clip to 255
119
+ sliceLength = maxFileNameLength - prefixLength - suffixLength
120
+ userName = userName[:sliceLength]
121
+ # test for illegal files names
122
+ parts = []
123
+ for part in userName.split("."):
124
+ if part.lower() in reservedFileNames:
125
+ part = "_" + part
126
+ parts.append(part)
127
+ userName = ".".join(parts)
128
+ # test for clash
129
+ fullName = prefix + userName + suffix
130
+ if fullName.lower() in existing:
131
+ fullName = handleClash1(userName, existing, prefix, suffix)
132
+ # finished
133
+ return fullName
134
+
135
+
136
+ def handleClash1(userName, existing=[], prefix="", suffix=""):
137
+ """
138
+ existing should be a case-insensitive list
139
+ of all existing file names.
140
+
141
+ >>> prefix = ("0" * 5) + "."
142
+ >>> suffix = "." + ("0" * 10)
143
+ >>> existing = ["a" * 5]
144
+
145
+ >>> e = list(existing)
146
+ >>> handleClash1(userName="A" * 5, existing=e,
147
+ ... prefix=prefix, suffix=suffix) == (
148
+ ... '00000.AAAAA000000000000001.0000000000')
149
+ True
150
+
151
+ >>> e = list(existing)
152
+ >>> e.append(prefix + "aaaaa" + "1".zfill(15) + suffix)
153
+ >>> handleClash1(userName="A" * 5, existing=e,
154
+ ... prefix=prefix, suffix=suffix) == (
155
+ ... '00000.AAAAA000000000000002.0000000000')
156
+ True
157
+
158
+ >>> e = list(existing)
159
+ >>> e.append(prefix + "AAAAA" + "2".zfill(15) + suffix)
160
+ >>> handleClash1(userName="A" * 5, existing=e,
161
+ ... prefix=prefix, suffix=suffix) == (
162
+ ... '00000.AAAAA000000000000001.0000000000')
163
+ True
164
+ """
165
+ # if the prefix length + user name length + suffix length + 15 is at
166
+ # or past the maximum length, silce 15 characters off of the user name
167
+ prefixLength = len(prefix)
168
+ suffixLength = len(suffix)
169
+ if prefixLength + len(userName) + suffixLength + 15 > maxFileNameLength:
170
+ l = prefixLength + len(userName) + suffixLength + 15
171
+ sliceLength = maxFileNameLength - l
172
+ userName = userName[:sliceLength]
173
+ finalName = None
174
+ # try to add numbers to create a unique name
175
+ counter = 1
176
+ while finalName is None:
177
+ name = userName + str(counter).zfill(15)
178
+ fullName = prefix + name + suffix
179
+ if fullName.lower() not in existing:
180
+ finalName = fullName
181
+ break
182
+ else:
183
+ counter += 1
184
+ if counter >= 999999999999999:
185
+ break
186
+ # if there is a clash, go to the next fallback
187
+ if finalName is None:
188
+ finalName = handleClash2(existing, prefix, suffix)
189
+ # finished
190
+ return finalName
191
+
192
+
193
+ def handleClash2(existing=[], prefix="", suffix=""):
194
+ """
195
+ existing should be a case-insensitive list
196
+ of all existing file names.
197
+
198
+ >>> prefix = ("0" * 5) + "."
199
+ >>> suffix = "." + ("0" * 10)
200
+ >>> existing = [prefix + str(i) + suffix for i in range(100)]
201
+
202
+ >>> e = list(existing)
203
+ >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
204
+ ... '00000.100.0000000000')
205
+ True
206
+
207
+ >>> e = list(existing)
208
+ >>> e.remove(prefix + "1" + suffix)
209
+ >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
210
+ ... '00000.1.0000000000')
211
+ True
212
+
213
+ >>> e = list(existing)
214
+ >>> e.remove(prefix + "2" + suffix)
215
+ >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
216
+ ... '00000.2.0000000000')
217
+ True
218
+ """
219
+ # calculate the longest possible string
220
+ maxLength = maxFileNameLength - len(prefix) - len(suffix)
221
+ maxValue = int("9" * maxLength)
222
+ # try to find a number
223
+ finalName = None
224
+ counter = 1
225
+ while finalName is None:
226
+ fullName = prefix + str(counter) + suffix
227
+ if fullName.lower() not in existing:
228
+ finalName = fullName
229
+ break
230
+ else:
231
+ counter += 1
232
+ if counter >= maxValue:
233
+ break
234
+ # raise an error if nothing has been found
235
+ if finalName is None:
236
+ raise NameTranslationError("No unique name could be found.")
237
+ # finished
238
+ return finalName
239
+
240
+
241
+ if __name__ == "__main__":
242
+ import doctest
243
+ import sys
244
+
245
+ sys.exit(doctest.testmod().failed)
@@ -0,0 +1,68 @@
1
+ """Minimal, stdlib-only replacement for [`pyfilesystem2`][1] API for use by `fontTools.ufoLib`.
2
+
3
+ This package is a partial reimplementation of the `fs` package by Will McGugan, used under the
4
+ MIT license. See LICENSE.external for details.
5
+
6
+ Note this only exports a **subset** of the `pyfilesystem2` API, in particular the modules,
7
+ classes and functions that are currently used directly by `fontTools.ufoLib`.
8
+
9
+ It opportunistically tries to import the relevant modules from the upstream `fs` package
10
+ when this is available. Otherwise it falls back to the replacement modules within this package.
11
+
12
+ As of version 4.59.0, the `fonttools[ufo]` extra no longer requires the `fs` package, thus
13
+ this `fontTools.misc.filesystem` package is used by default.
14
+
15
+ Client code can either replace `import fs` with `from fontTools.misc import filesystem as fs`
16
+ if that happens to work (no guarantee), or they can continue to use `fs` but they will have
17
+ to specify it as an explicit dependency of their project.
18
+
19
+ [1]: https://github.com/PyFilesystem/pyfilesystem2
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ try:
25
+ __import__("fs")
26
+ except ImportError:
27
+ from . import _base as base
28
+ from . import _copy as copy
29
+ from . import _errors as errors
30
+ from . import _info as info
31
+ from . import _osfs as osfs
32
+ from . import _path as path
33
+ from . import _subfs as subfs
34
+ from . import _tempfs as tempfs
35
+ from . import _tools as tools
36
+ from . import _walk as walk
37
+ from . import _zipfs as zipfs
38
+
39
+ _haveFS = False
40
+ else:
41
+ import fs.base as base
42
+ import fs.copy as copy
43
+ import fs.errors as errors
44
+ import fs.info as info
45
+ import fs.osfs as osfs
46
+ import fs.path as path
47
+ import fs.subfs as subfs
48
+ import fs.tempfs as tempfs
49
+ import fs.tools as tools
50
+ import fs.walk as walk
51
+ import fs.zipfs as zipfs
52
+
53
+ _haveFS = True
54
+
55
+
56
+ __all__ = [
57
+ "base",
58
+ "copy",
59
+ "errors",
60
+ "info",
61
+ "osfs",
62
+ "path",
63
+ "subfs",
64
+ "tempfs",
65
+ "tools",
66
+ "walk",
67
+ "zipfs",
68
+ ]
@@ -0,0 +1,134 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from abc import ABC, abstractmethod
5
+
6
+ from ._copy import copy_dir, copy_file
7
+ from ._errors import (
8
+ DestinationExists,
9
+ DirectoryExpected,
10
+ FileExpected,
11
+ FilesystemClosed,
12
+ NoSysPath,
13
+ ResourceNotFound,
14
+ )
15
+ from ._path import dirname
16
+ from ._walk import BoundWalker
17
+
18
+ if typing.TYPE_CHECKING:
19
+ from typing import IO, Any, Collection, Iterator, Self, Type
20
+
21
+ from ._info import Info
22
+ from ._subfs import SubFS
23
+
24
+
25
+ class FS(ABC):
26
+ """Abstract base class for custom filesystems."""
27
+
28
+ _closed: bool = False
29
+
30
+ @abstractmethod
31
+ def open(self, path: str, mode: str = "rb", **kwargs) -> IO[Any]: ...
32
+
33
+ @abstractmethod
34
+ def exists(self, path: str) -> bool: ...
35
+
36
+ @abstractmethod
37
+ def isdir(self, path: str) -> bool: ...
38
+
39
+ @abstractmethod
40
+ def isfile(self, path: str) -> bool: ...
41
+
42
+ @abstractmethod
43
+ def listdir(self, path: str) -> list[str]: ...
44
+
45
+ @abstractmethod
46
+ def makedir(self, path: str, recreate: bool = False) -> SubFS: ...
47
+
48
+ @abstractmethod
49
+ def makedirs(self, path: str, recreate: bool = False) -> SubFS: ...
50
+
51
+ @abstractmethod
52
+ def getinfo(self, path: str, namespaces: Collection[str] | None = None) -> Info: ...
53
+
54
+ @abstractmethod
55
+ def remove(self, path: str) -> None: ...
56
+
57
+ @abstractmethod
58
+ def removedir(self, path: str) -> None: ...
59
+
60
+ @abstractmethod
61
+ def removetree(self, path: str) -> None: ...
62
+
63
+ @abstractmethod
64
+ def movedir(self, src: str, dst: str, create: bool = False) -> None: ...
65
+
66
+ def getsyspath(self, path: str) -> str:
67
+ raise NoSysPath(f"the filesystem {self!r} has no system path")
68
+
69
+ def close(self):
70
+ self._closed = True
71
+
72
+ def isclosed(self) -> bool:
73
+ return self._closed
74
+
75
+ def __enter__(self) -> Self:
76
+ return self
77
+
78
+ def __exit__(self, exc_type, exc, tb):
79
+ self.close()
80
+ return False # never swallow exceptions
81
+
82
+ def check(self):
83
+ if self._closed:
84
+ raise FilesystemClosed(f"the filesystem {self!r} is closed")
85
+
86
+ def opendir(self, path: str, *, factory: Type[SubFS] | None = None) -> SubFS:
87
+ """Return a sub‑filesystem rooted at `path`."""
88
+ if factory is None:
89
+ from ._subfs import SubFS
90
+
91
+ factory = SubFS
92
+ return factory(self, path)
93
+
94
+ def scandir(
95
+ self, path: str, namespaces: Collection[str] | None = None
96
+ ) -> Iterator[Info]:
97
+ return (self.getinfo(f"{path}/{p}", namespaces) for p in self.listdir(path))
98
+
99
+ @property
100
+ def walk(self) -> BoundWalker:
101
+ return BoundWalker(self)
102
+
103
+ def readbytes(self, path: str) -> bytes:
104
+ with self.open(path, "rb") as f:
105
+ return f.read()
106
+
107
+ def writebytes(self, path: str, data: bytes):
108
+ with self.open(path, "wb") as f:
109
+ f.write(data)
110
+
111
+ def create(self, path: str, wipe: bool = False):
112
+ if not wipe and self.exists(path):
113
+ return False
114
+ with self.open(path, "wb"):
115
+ pass # 'touch' empty file
116
+ return True
117
+
118
+ def copy(self, src_path: str, dst_path: str, overwrite=False):
119
+ if not self.exists(src_path):
120
+ raise ResourceNotFound(f"{src_path!r} does not exist")
121
+ elif not self.isfile(src_path):
122
+ raise FileExpected(f"path {src_path!r} should be a file")
123
+ if not overwrite and self.exists(dst_path):
124
+ raise DestinationExists(f"destination {dst_path!r} already exists")
125
+ if not self.isdir(dirname(dst_path)):
126
+ raise DirectoryExpected(f"path {dirname(dst_path)!r} should be a directory")
127
+ copy_file(self, src_path, self, dst_path)
128
+
129
+ def copydir(self, src_path: str, dst_path: str, create=False):
130
+ if not create and not self.exists(dst_path):
131
+ raise ResourceNotFound(f"{dst_path!r} does not exist")
132
+ if not self.isdir(src_path):
133
+ raise DirectoryExpected(f"path {src_path!r} should be a directory")
134
+ copy_dir(self, src_path, self, dst_path)
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ from ._errors import IllegalDestination
6
+ from ._path import combine, frombase, isbase
7
+ from ._tools import copy_file_data
8
+
9
+ if typing.TYPE_CHECKING:
10
+ from ._base import FS
11
+
12
+
13
+ def copy_file(src_fs: FS, src_path: str, dst_fs: FS, dst_path: str):
14
+ if src_fs is dst_fs and src_path == dst_path:
15
+ raise IllegalDestination(f"cannot copy {src_path!r} to itself")
16
+
17
+ with src_fs.open(src_path, "rb") as src_file:
18
+ with dst_fs.open(dst_path, "wb") as dst_file:
19
+ copy_file_data(src_file, dst_file)
20
+
21
+
22
+ def copy_structure(
23
+ src_fs: FS,
24
+ dst_fs: FS,
25
+ src_root: str = "/",
26
+ dst_root: str = "/",
27
+ ):
28
+ if src_fs is dst_fs and isbase(src_root, dst_root):
29
+ raise IllegalDestination(f"cannot copy {src_fs!r} to itself")
30
+
31
+ dst_fs.makedirs(dst_root, recreate=True)
32
+ for dir_path in src_fs.walk.dirs(src_root):
33
+ dst_fs.makedir(combine(dst_root, frombase(src_root, dir_path)), recreate=True)
34
+
35
+
36
+ def copy_dir(src_fs: FS, src_path: str, dst_fs: FS, dst_path: str):
37
+ copy_structure(src_fs, dst_fs, src_path, dst_path)
38
+
39
+ for file_path in src_fs.walk.files(src_path):
40
+ copy_path = combine(dst_path, frombase(src_path, file_path))
41
+ copy_file(src_fs, file_path, dst_fs, copy_path)
42
+
43
+
44
+ def copy_fs(src_fs: FS, dst_fs: FS):
45
+ copy_dir(src_fs, "/", dst_fs, "/")
@@ -0,0 +1,54 @@
1
+ class FSError(Exception):
2
+ pass
3
+
4
+
5
+ class CreateFailed(FSError):
6
+ pass
7
+
8
+
9
+ class FilesystemClosed(FSError):
10
+ pass
11
+
12
+
13
+ class MissingInfoNamespace(FSError):
14
+ pass
15
+
16
+
17
+ class NoSysPath(FSError):
18
+ pass
19
+
20
+
21
+ class OperationFailed(FSError):
22
+ pass
23
+
24
+
25
+ class IllegalDestination(OperationFailed):
26
+ pass
27
+
28
+
29
+ class ResourceError(FSError):
30
+ pass
31
+
32
+
33
+ class ResourceNotFound(ResourceError):
34
+ pass
35
+
36
+
37
+ class DirectoryExpected(ResourceError):
38
+ pass
39
+
40
+
41
+ class DirectoryNotEmpty(ResourceError):
42
+ pass
43
+
44
+
45
+ class FileExpected(ResourceError):
46
+ pass
47
+
48
+
49
+ class DestinationExists(ResourceError):
50
+ pass
51
+
52
+
53
+ class ResourceReadOnly(ResourceError):
54
+ pass
@@ -0,0 +1,75 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from datetime import datetime, timezone
5
+
6
+ from ._errors import MissingInfoNamespace
7
+
8
+ if typing.TYPE_CHECKING:
9
+ from collections.abc import Mapping
10
+ from typing import Any
11
+
12
+
13
+ def epoch_to_datetime(t: int | None) -> datetime | None:
14
+ """Convert epoch time to a UTC datetime."""
15
+ if t is None:
16
+ return None
17
+ return datetime.fromtimestamp(t, tz=timezone.utc)
18
+
19
+
20
+ class Info:
21
+ __slots__ = ["raw", "namespaces"]
22
+
23
+ def __init__(self, raw_info: Mapping[str, Any]):
24
+ self.raw = raw_info
25
+ self.namespaces = frozenset(raw_info.keys())
26
+
27
+ def get(self, namespace: str, key: str, default: Any | None = None) -> Any | None:
28
+ try:
29
+ return self.raw[namespace].get(key, default)
30
+ except KeyError:
31
+ raise MissingInfoNamespace(f"Namespace {namespace!r} does not exist")
32
+
33
+ @property
34
+ def name(self) -> str:
35
+ return self.get("basic", "name")
36
+
37
+ @property
38
+ def is_dir(self) -> bool:
39
+ return self.get("basic", "is_dir")
40
+
41
+ @property
42
+ def is_file(self) -> bool:
43
+ return not self.is_dir
44
+
45
+ @property
46
+ def accessed(self) -> datetime | None:
47
+ return epoch_to_datetime(self.get("details", "accessed"))
48
+
49
+ @property
50
+ def modified(self) -> datetime | None:
51
+ return epoch_to_datetime(self.get("details", "modified"))
52
+
53
+ @property
54
+ def size(self) -> int | None:
55
+ return self.get("details", "size")
56
+
57
+ @property
58
+ def type(self) -> int | None:
59
+ return self.get("details", "type")
60
+
61
+ @property
62
+ def created(self) -> datetime | None:
63
+ return epoch_to_datetime(self.get("details", "created"))
64
+
65
+ @property
66
+ def metadata_changed(self) -> datetime | None:
67
+ return epoch_to_datetime(self.get("details", "metadata_changed"))
68
+
69
+ def __str__(self) -> str:
70
+ if self.is_dir:
71
+ return "<dir '{}'>".format(self.name)
72
+ else:
73
+ return "<file '{}'>".format(self.name)
74
+
75
+ __repr__ = __str__