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,322 @@
1
+ # SVG Path specification parser.
2
+ # This is an adaptation from 'svg.path' by Lennart Regebro (@regebro),
3
+ # modified so that the parser takes a FontTools Pen object instead of
4
+ # returning a list of svg.path Path objects.
5
+ # The original code can be found at:
6
+ # https://github.com/regebro/svg.path/blob/4f9b6e3/src/svg/path/parser.py
7
+ # Copyright (c) 2013-2014 Lennart Regebro
8
+ # License: MIT
9
+
10
+ from .arc import EllipticalArc
11
+ import re
12
+
13
+
14
+ COMMANDS = set("MmZzLlHhVvCcSsQqTtAa")
15
+ ARC_COMMANDS = set("Aa")
16
+ UPPERCASE = set("MZLHVCSQTA")
17
+
18
+ COMMAND_RE = re.compile("([MmZzLlHhVvCcSsQqTtAa])")
19
+
20
+ # https://www.w3.org/TR/css-syntax-3/#number-token-diagram
21
+ # but -6.e-5 will be tokenized as "-6" then "-5" and confuse parsing
22
+ FLOAT_RE = re.compile(
23
+ r"[-+]?" # optional sign
24
+ r"(?:"
25
+ r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][-+]?[0-9]+)?" # int/float
26
+ r"|"
27
+ r"(?:\.[0-9]+(?:[eE][-+]?[0-9]+)?)" # float with leading dot (e.g. '.42')
28
+ r")"
29
+ )
30
+ BOOL_RE = re.compile("^[01]")
31
+ SEPARATOR_RE = re.compile(f"[, \t]")
32
+
33
+
34
+ def _tokenize_path(pathdef):
35
+ arc_cmd = None
36
+ for x in COMMAND_RE.split(pathdef):
37
+ if x in COMMANDS:
38
+ arc_cmd = x if x in ARC_COMMANDS else None
39
+ yield x
40
+ continue
41
+
42
+ if arc_cmd:
43
+ try:
44
+ yield from _tokenize_arc_arguments(x)
45
+ except ValueError as e:
46
+ raise ValueError(f"Invalid arc command: '{arc_cmd}{x}'") from e
47
+ else:
48
+ for token in FLOAT_RE.findall(x):
49
+ yield token
50
+
51
+
52
+ ARC_ARGUMENT_TYPES = (
53
+ ("rx", FLOAT_RE),
54
+ ("ry", FLOAT_RE),
55
+ ("x-axis-rotation", FLOAT_RE),
56
+ ("large-arc-flag", BOOL_RE),
57
+ ("sweep-flag", BOOL_RE),
58
+ ("x", FLOAT_RE),
59
+ ("y", FLOAT_RE),
60
+ )
61
+
62
+
63
+ def _tokenize_arc_arguments(arcdef):
64
+ raw_args = [s for s in SEPARATOR_RE.split(arcdef) if s]
65
+ if not raw_args:
66
+ raise ValueError(f"Not enough arguments: '{arcdef}'")
67
+ raw_args.reverse()
68
+
69
+ i = 0
70
+ while raw_args:
71
+ arg = raw_args.pop()
72
+
73
+ name, pattern = ARC_ARGUMENT_TYPES[i]
74
+ match = pattern.search(arg)
75
+ if not match:
76
+ raise ValueError(f"Invalid argument for '{name}' parameter: {arg!r}")
77
+
78
+ j, k = match.span()
79
+ yield arg[j:k]
80
+ arg = arg[k:]
81
+
82
+ if arg:
83
+ raw_args.append(arg)
84
+
85
+ # wrap around every 7 consecutive arguments
86
+ if i == 6:
87
+ i = 0
88
+ else:
89
+ i += 1
90
+
91
+ if i != 0:
92
+ raise ValueError(f"Not enough arguments: '{arcdef}'")
93
+
94
+
95
+ def parse_path(pathdef, pen, current_pos=(0, 0), arc_class=EllipticalArc):
96
+ """Parse SVG path definition (i.e. "d" attribute of <path> elements)
97
+ and call a 'pen' object's moveTo, lineTo, curveTo, qCurveTo and closePath
98
+ methods.
99
+
100
+ If 'current_pos' (2-float tuple) is provided, the initial moveTo will
101
+ be relative to that instead being absolute.
102
+
103
+ If the pen has an "arcTo" method, it is called with the original values
104
+ of the elliptical arc curve commands:
105
+
106
+ .. code-block::
107
+
108
+ pen.arcTo(rx, ry, rotation, arc_large, arc_sweep, (x, y))
109
+
110
+ Otherwise, the arcs are approximated by series of cubic Bezier segments
111
+ ("curveTo"), one every 90 degrees.
112
+ """
113
+ # In the SVG specs, initial movetos are absolute, even if
114
+ # specified as 'm'. This is the default behavior here as well.
115
+ # But if you pass in a current_pos variable, the initial moveto
116
+ # will be relative to that current_pos. This is useful.
117
+ current_pos = complex(*current_pos)
118
+
119
+ elements = list(_tokenize_path(pathdef))
120
+ # Reverse for easy use of .pop()
121
+ elements.reverse()
122
+
123
+ start_pos = None
124
+ command = None
125
+ last_control = None
126
+
127
+ have_arcTo = hasattr(pen, "arcTo")
128
+
129
+ while elements:
130
+ if elements[-1] in COMMANDS:
131
+ # New command.
132
+ last_command = command # Used by S and T
133
+ command = elements.pop()
134
+ absolute = command in UPPERCASE
135
+ command = command.upper()
136
+ else:
137
+ # If this element starts with numbers, it is an implicit command
138
+ # and we don't change the command. Check that it's allowed:
139
+ if command is None:
140
+ raise ValueError(
141
+ "Unallowed implicit command in %s, position %s"
142
+ % (pathdef, len(pathdef.split()) - len(elements))
143
+ )
144
+ last_command = command # Used by S and T
145
+
146
+ if command == "M":
147
+ # Moveto command.
148
+ x = elements.pop()
149
+ y = elements.pop()
150
+ pos = float(x) + float(y) * 1j
151
+ if absolute:
152
+ current_pos = pos
153
+ else:
154
+ current_pos += pos
155
+
156
+ # M is not preceded by Z; it's an open subpath
157
+ if start_pos is not None:
158
+ pen.endPath()
159
+
160
+ pen.moveTo((current_pos.real, current_pos.imag))
161
+
162
+ # when M is called, reset start_pos
163
+ # This behavior of Z is defined in svg spec:
164
+ # http://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand
165
+ start_pos = current_pos
166
+
167
+ # Implicit moveto commands are treated as lineto commands.
168
+ # So we set command to lineto here, in case there are
169
+ # further implicit commands after this moveto.
170
+ command = "L"
171
+
172
+ elif command == "Z":
173
+ # Close path
174
+ if current_pos != start_pos:
175
+ pen.lineTo((start_pos.real, start_pos.imag))
176
+ pen.closePath()
177
+ current_pos = start_pos
178
+ start_pos = None
179
+ command = None # You can't have implicit commands after closing.
180
+
181
+ elif command == "L":
182
+ x = elements.pop()
183
+ y = elements.pop()
184
+ pos = float(x) + float(y) * 1j
185
+ if not absolute:
186
+ pos += current_pos
187
+ pen.lineTo((pos.real, pos.imag))
188
+ current_pos = pos
189
+
190
+ elif command == "H":
191
+ x = elements.pop()
192
+ pos = float(x) + current_pos.imag * 1j
193
+ if not absolute:
194
+ pos += current_pos.real
195
+ pen.lineTo((pos.real, pos.imag))
196
+ current_pos = pos
197
+
198
+ elif command == "V":
199
+ y = elements.pop()
200
+ pos = current_pos.real + float(y) * 1j
201
+ if not absolute:
202
+ pos += current_pos.imag * 1j
203
+ pen.lineTo((pos.real, pos.imag))
204
+ current_pos = pos
205
+
206
+ elif command == "C":
207
+ control1 = float(elements.pop()) + float(elements.pop()) * 1j
208
+ control2 = float(elements.pop()) + float(elements.pop()) * 1j
209
+ end = float(elements.pop()) + float(elements.pop()) * 1j
210
+
211
+ if not absolute:
212
+ control1 += current_pos
213
+ control2 += current_pos
214
+ end += current_pos
215
+
216
+ pen.curveTo(
217
+ (control1.real, control1.imag),
218
+ (control2.real, control2.imag),
219
+ (end.real, end.imag),
220
+ )
221
+ current_pos = end
222
+ last_control = control2
223
+
224
+ elif command == "S":
225
+ # Smooth curve. First control point is the "reflection" of
226
+ # the second control point in the previous path.
227
+
228
+ if last_command not in "CS":
229
+ # If there is no previous command or if the previous command
230
+ # was not an C, c, S or s, assume the first control point is
231
+ # coincident with the current point.
232
+ control1 = current_pos
233
+ else:
234
+ # The first control point is assumed to be the reflection of
235
+ # the second control point on the previous command relative
236
+ # to the current point.
237
+ control1 = current_pos + current_pos - last_control
238
+
239
+ control2 = float(elements.pop()) + float(elements.pop()) * 1j
240
+ end = float(elements.pop()) + float(elements.pop()) * 1j
241
+
242
+ if not absolute:
243
+ control2 += current_pos
244
+ end += current_pos
245
+
246
+ pen.curveTo(
247
+ (control1.real, control1.imag),
248
+ (control2.real, control2.imag),
249
+ (end.real, end.imag),
250
+ )
251
+ current_pos = end
252
+ last_control = control2
253
+
254
+ elif command == "Q":
255
+ control = float(elements.pop()) + float(elements.pop()) * 1j
256
+ end = float(elements.pop()) + float(elements.pop()) * 1j
257
+
258
+ if not absolute:
259
+ control += current_pos
260
+ end += current_pos
261
+
262
+ pen.qCurveTo((control.real, control.imag), (end.real, end.imag))
263
+ current_pos = end
264
+ last_control = control
265
+
266
+ elif command == "T":
267
+ # Smooth curve. Control point is the "reflection" of
268
+ # the second control point in the previous path.
269
+
270
+ if last_command not in "QT":
271
+ # If there is no previous command or if the previous command
272
+ # was not an Q, q, T or t, assume the first control point is
273
+ # coincident with the current point.
274
+ control = current_pos
275
+ else:
276
+ # The control point is assumed to be the reflection of
277
+ # the control point on the previous command relative
278
+ # to the current point.
279
+ control = current_pos + current_pos - last_control
280
+
281
+ end = float(elements.pop()) + float(elements.pop()) * 1j
282
+
283
+ if not absolute:
284
+ end += current_pos
285
+
286
+ pen.qCurveTo((control.real, control.imag), (end.real, end.imag))
287
+ current_pos = end
288
+ last_control = control
289
+
290
+ elif command == "A":
291
+ rx = abs(float(elements.pop()))
292
+ ry = abs(float(elements.pop()))
293
+ rotation = float(elements.pop())
294
+ arc_large = bool(int(elements.pop()))
295
+ arc_sweep = bool(int(elements.pop()))
296
+ end = float(elements.pop()) + float(elements.pop()) * 1j
297
+
298
+ if not absolute:
299
+ end += current_pos
300
+
301
+ # if the pen supports arcs, pass the values unchanged, otherwise
302
+ # approximate the arc with a series of cubic bezier curves
303
+ if have_arcTo:
304
+ pen.arcTo(
305
+ rx,
306
+ ry,
307
+ rotation,
308
+ arc_large,
309
+ arc_sweep,
310
+ (end.real, end.imag),
311
+ )
312
+ else:
313
+ arc = arc_class(
314
+ current_pos, rx, ry, rotation, arc_large, arc_sweep, end
315
+ )
316
+ arc.draw(pen)
317
+
318
+ current_pos = end
319
+
320
+ # no final Z command, it's an open path
321
+ if start_pos is not None:
322
+ pen.endPath()
@@ -0,0 +1,183 @@
1
+ import re
2
+
3
+
4
+ def _prefer_non_zero(*args):
5
+ for arg in args:
6
+ if arg != 0:
7
+ return arg
8
+ return 0.0
9
+
10
+
11
+ def _ntos(n):
12
+ # %f likes to add unnecessary 0's, %g isn't consistent about # decimals
13
+ return ("%.3f" % n).rstrip("0").rstrip(".")
14
+
15
+
16
+ def _strip_xml_ns(tag):
17
+ # ElementTree API doesn't provide a way to ignore XML namespaces in tags
18
+ # so we here strip them ourselves: cf. https://bugs.python.org/issue18304
19
+ return tag.split("}", 1)[1] if "}" in tag else tag
20
+
21
+
22
+ def _transform(raw_value):
23
+ # TODO assumes a 'matrix' transform.
24
+ # No other transform functions are supported at the moment.
25
+ # https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform
26
+ # start simple: if you aren't exactly matrix(...) then no love
27
+ match = re.match(r"matrix\((.*)\)", raw_value)
28
+ if not match:
29
+ raise NotImplementedError
30
+ matrix = tuple(float(p) for p in re.split(r"\s+|,", match.group(1)))
31
+ if len(matrix) != 6:
32
+ raise ValueError("wrong # of terms in %s" % raw_value)
33
+ return matrix
34
+
35
+
36
+ class PathBuilder(object):
37
+ def __init__(self):
38
+ self.paths = []
39
+ self.transforms = []
40
+
41
+ def _start_path(self, initial_path=""):
42
+ self.paths.append(initial_path)
43
+ self.transforms.append(None)
44
+
45
+ def _end_path(self):
46
+ self._add("z")
47
+
48
+ def _add(self, path_snippet):
49
+ path = self.paths[-1]
50
+ if path:
51
+ path += " " + path_snippet
52
+ else:
53
+ path = path_snippet
54
+ self.paths[-1] = path
55
+
56
+ def _move(self, c, x, y):
57
+ self._add("%s%s,%s" % (c, _ntos(x), _ntos(y)))
58
+
59
+ def M(self, x, y):
60
+ self._move("M", x, y)
61
+
62
+ def m(self, x, y):
63
+ self._move("m", x, y)
64
+
65
+ def _arc(self, c, rx, ry, x, y, large_arc):
66
+ self._add(
67
+ "%s%s,%s 0 %d 1 %s,%s"
68
+ % (c, _ntos(rx), _ntos(ry), large_arc, _ntos(x), _ntos(y))
69
+ )
70
+
71
+ def A(self, rx, ry, x, y, large_arc=0):
72
+ self._arc("A", rx, ry, x, y, large_arc)
73
+
74
+ def a(self, rx, ry, x, y, large_arc=0):
75
+ self._arc("a", rx, ry, x, y, large_arc)
76
+
77
+ def _vhline(self, c, x):
78
+ self._add("%s%s" % (c, _ntos(x)))
79
+
80
+ def H(self, x):
81
+ self._vhline("H", x)
82
+
83
+ def h(self, x):
84
+ self._vhline("h", x)
85
+
86
+ def V(self, y):
87
+ self._vhline("V", y)
88
+
89
+ def v(self, y):
90
+ self._vhline("v", y)
91
+
92
+ def _line(self, c, x, y):
93
+ self._add("%s%s,%s" % (c, _ntos(x), _ntos(y)))
94
+
95
+ def L(self, x, y):
96
+ self._line("L", x, y)
97
+
98
+ def l(self, x, y):
99
+ self._line("l", x, y)
100
+
101
+ def _parse_line(self, line):
102
+ x1 = float(line.attrib.get("x1", 0))
103
+ y1 = float(line.attrib.get("y1", 0))
104
+ x2 = float(line.attrib.get("x2", 0))
105
+ y2 = float(line.attrib.get("y2", 0))
106
+
107
+ self._start_path()
108
+ self.M(x1, y1)
109
+ self.L(x2, y2)
110
+
111
+ def _parse_rect(self, rect):
112
+ x = float(rect.attrib.get("x", 0))
113
+ y = float(rect.attrib.get("y", 0))
114
+ w = float(rect.attrib.get("width"))
115
+ h = float(rect.attrib.get("height"))
116
+ rx = float(rect.attrib.get("rx", 0))
117
+ ry = float(rect.attrib.get("ry", 0))
118
+
119
+ rx = _prefer_non_zero(rx, ry)
120
+ ry = _prefer_non_zero(ry, rx)
121
+ # TODO there are more rules for adjusting rx, ry
122
+
123
+ self._start_path()
124
+ self.M(x + rx, y)
125
+ self.H(x + w - rx)
126
+ if rx > 0:
127
+ self.A(rx, ry, x + w, y + ry)
128
+ self.V(y + h - ry)
129
+ if rx > 0:
130
+ self.A(rx, ry, x + w - rx, y + h)
131
+ self.H(x + rx)
132
+ if rx > 0:
133
+ self.A(rx, ry, x, y + h - ry)
134
+ self.V(y + ry)
135
+ if rx > 0:
136
+ self.A(rx, ry, x + rx, y)
137
+ self._end_path()
138
+
139
+ def _parse_path(self, path):
140
+ if "d" in path.attrib:
141
+ self._start_path(initial_path=path.attrib["d"])
142
+
143
+ def _parse_polygon(self, poly):
144
+ if "points" in poly.attrib:
145
+ self._start_path("M" + poly.attrib["points"])
146
+ self._end_path()
147
+
148
+ def _parse_polyline(self, poly):
149
+ if "points" in poly.attrib:
150
+ self._start_path("M" + poly.attrib["points"])
151
+
152
+ def _parse_circle(self, circle):
153
+ cx = float(circle.attrib.get("cx", 0))
154
+ cy = float(circle.attrib.get("cy", 0))
155
+ r = float(circle.attrib.get("r"))
156
+
157
+ # arc doesn't seem to like being a complete shape, draw two halves
158
+ self._start_path()
159
+ self.M(cx - r, cy)
160
+ self.A(r, r, cx + r, cy, large_arc=1)
161
+ self.A(r, r, cx - r, cy, large_arc=1)
162
+
163
+ def _parse_ellipse(self, ellipse):
164
+ cx = float(ellipse.attrib.get("cx", 0))
165
+ cy = float(ellipse.attrib.get("cy", 0))
166
+ rx = float(ellipse.attrib.get("rx"))
167
+ ry = float(ellipse.attrib.get("ry"))
168
+
169
+ # arc doesn't seem to like being a complete shape, draw two halves
170
+ self._start_path()
171
+ self.M(cx - rx, cy)
172
+ self.A(rx, ry, cx + rx, cy, large_arc=1)
173
+ self.A(rx, ry, cx - rx, cy, large_arc=1)
174
+
175
+ def add_path_from_element(self, el):
176
+ tag = _strip_xml_ns(el.tag)
177
+ parse_fn = getattr(self, "_parse_%s" % tag.lower(), None)
178
+ if not callable(parse_fn):
179
+ return False
180
+ parse_fn(el)
181
+ if "transform" in el.attrib:
182
+ self.transforms[-1] = _transform(el.attrib["transform"])
183
+ return True