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,6 @@
1
+ import sys
2
+ from fontTools.subset import main
3
+
4
+
5
+ if __name__ == "__main__":
6
+ sys.exit(main())
@@ -0,0 +1,184 @@
1
+ from fontTools.misc import psCharStrings
2
+ from fontTools import ttLib
3
+ from fontTools.pens.basePen import NullPen
4
+ from fontTools.misc.roundTools import otRound
5
+ from fontTools.misc.loggingTools import deprecateFunction
6
+ from fontTools.subset.util import _add_method, _uniq_sort
7
+
8
+
9
+ class _ClosureGlyphsT2Decompiler(psCharStrings.SimpleT2Decompiler):
10
+ def __init__(self, components, localSubrs, globalSubrs):
11
+ psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs)
12
+ self.components = components
13
+
14
+ def op_endchar(self, index):
15
+ args = self.popall()
16
+ if len(args) >= 4:
17
+ from fontTools.encodings.StandardEncoding import StandardEncoding
18
+
19
+ # endchar can do seac accent bulding; The T2 spec says it's deprecated,
20
+ # but recent software that shall remain nameless does output it.
21
+ adx, ady, bchar, achar = args[-4:]
22
+ baseGlyph = StandardEncoding[bchar]
23
+ accentGlyph = StandardEncoding[achar]
24
+ self.components.add(baseGlyph)
25
+ self.components.add(accentGlyph)
26
+
27
+
28
+ @_add_method(ttLib.getTableClass("CFF "))
29
+ def closure_glyphs(self, s):
30
+ cff = self.cff
31
+ assert len(cff) == 1
32
+ font = cff[cff.keys()[0]]
33
+ glyphSet = font.CharStrings
34
+
35
+ decompose = s.glyphs
36
+ while decompose:
37
+ components = set()
38
+ for g in decompose:
39
+ if g not in glyphSet:
40
+ continue
41
+ gl = glyphSet[g]
42
+
43
+ subrs = getattr(gl.private, "Subrs", [])
44
+ decompiler = _ClosureGlyphsT2Decompiler(components, subrs, gl.globalSubrs)
45
+ decompiler.execute(gl)
46
+ components -= s.glyphs
47
+ s.glyphs.update(components)
48
+ decompose = components
49
+
50
+
51
+ def _empty_charstring(font, glyphName, isCFF2, ignoreWidth=False):
52
+ c, fdSelectIndex = font.CharStrings.getItemAndSelector(glyphName)
53
+ if isCFF2 or ignoreWidth:
54
+ # CFF2 charstrings have no widths nor 'endchar' operators
55
+ c.setProgram([] if isCFF2 else ["endchar"])
56
+ else:
57
+ if hasattr(font, "FDArray") and font.FDArray is not None:
58
+ private = font.FDArray[fdSelectIndex].Private
59
+ else:
60
+ private = font.Private
61
+ dfltWdX = private.defaultWidthX
62
+ nmnlWdX = private.nominalWidthX
63
+ pen = NullPen()
64
+ c.draw(pen) # this will set the charstring's width
65
+ if c.width != dfltWdX:
66
+ c.program = [c.width - nmnlWdX, "endchar"]
67
+ else:
68
+ c.program = ["endchar"]
69
+
70
+
71
+ @_add_method(ttLib.getTableClass("CFF "))
72
+ def prune_pre_subset(self, font, options):
73
+ cff = self.cff
74
+ # CFF table must have one font only
75
+ cff.fontNames = cff.fontNames[:1]
76
+
77
+ if options.notdef_glyph and not options.notdef_outline:
78
+ isCFF2 = cff.major > 1
79
+ for fontname in cff.keys():
80
+ font = cff[fontname]
81
+ _empty_charstring(font, ".notdef", isCFF2=isCFF2)
82
+
83
+ # Clear useless Encoding
84
+ for fontname in cff.keys():
85
+ font = cff[fontname]
86
+ # https://github.com/fonttools/fonttools/issues/620
87
+ font.Encoding = "StandardEncoding"
88
+
89
+ return True # bool(cff.fontNames)
90
+
91
+
92
+ @_add_method(ttLib.getTableClass("CFF "))
93
+ def subset_glyphs(self, s):
94
+ cff = self.cff
95
+ for fontname in cff.keys():
96
+ font = cff[fontname]
97
+ cs = font.CharStrings
98
+
99
+ glyphs = s.glyphs.union(s.glyphs_emptied)
100
+
101
+ # Load all glyphs
102
+ for g in font.charset:
103
+ if g not in glyphs:
104
+ continue
105
+ c, _ = cs.getItemAndSelector(g)
106
+
107
+ if cs.charStringsAreIndexed:
108
+ indices = [i for i, g in enumerate(font.charset) if g in glyphs]
109
+ csi = cs.charStringsIndex
110
+ csi.items = [csi.items[i] for i in indices]
111
+ del csi.file, csi.offsets
112
+ if hasattr(font, "FDSelect"):
113
+ sel = font.FDSelect
114
+ sel.format = None
115
+ sel.gidArray = [sel.gidArray[i] for i in indices]
116
+ newCharStrings = {}
117
+ for indicesIdx, charsetIdx in enumerate(indices):
118
+ g = font.charset[charsetIdx]
119
+ if g in cs.charStrings:
120
+ newCharStrings[g] = indicesIdx
121
+ cs.charStrings = newCharStrings
122
+ else:
123
+ cs.charStrings = {g: v for g, v in cs.charStrings.items() if g in glyphs}
124
+ font.charset = [g for g in font.charset if g in glyphs]
125
+ font.numGlyphs = len(font.charset)
126
+
127
+ if s.options.retain_gids:
128
+ isCFF2 = cff.major > 1
129
+ for g in s.glyphs_emptied:
130
+ _empty_charstring(font, g, isCFF2=isCFF2, ignoreWidth=True)
131
+
132
+ return True # any(cff[fontname].numGlyphs for fontname in cff.keys())
133
+
134
+
135
+ @_add_method(ttLib.getTableClass("CFF "))
136
+ def prune_post_subset(self, ttfFont, options):
137
+ cff = self.cff
138
+ for fontname in cff.keys():
139
+ font = cff[fontname]
140
+ cs = font.CharStrings
141
+
142
+ # Drop unused FontDictionaries
143
+ if hasattr(font, "FDSelect"):
144
+ sel = font.FDSelect
145
+ indices = _uniq_sort(sel.gidArray)
146
+ sel.gidArray = [indices.index(ss) for ss in sel.gidArray]
147
+ arr = font.FDArray
148
+ arr.items = [arr[i] for i in indices]
149
+ del arr.file, arr.offsets
150
+
151
+ # Desubroutinize if asked for
152
+ if options.desubroutinize:
153
+ cff.desubroutinize()
154
+
155
+ # Drop hints if not needed
156
+ if not options.hinting:
157
+ self.remove_hints()
158
+ elif not options.desubroutinize:
159
+ self.remove_unused_subroutines()
160
+ return True
161
+
162
+
163
+ @deprecateFunction(
164
+ "use 'CFFFontSet.desubroutinize()' instead", category=DeprecationWarning
165
+ )
166
+ @_add_method(ttLib.getTableClass("CFF "))
167
+ def desubroutinize(self):
168
+ self.cff.desubroutinize()
169
+
170
+
171
+ @deprecateFunction(
172
+ "use 'CFFFontSet.remove_hints()' instead", category=DeprecationWarning
173
+ )
174
+ @_add_method(ttLib.getTableClass("CFF "))
175
+ def remove_hints(self):
176
+ self.cff.remove_hints()
177
+
178
+
179
+ @deprecateFunction(
180
+ "use 'CFFFontSet.remove_unused_subroutines' instead", category=DeprecationWarning
181
+ )
182
+ @_add_method(ttLib.getTableClass("CFF "))
183
+ def remove_unused_subroutines(self):
184
+ self.cff.remove_unused_subroutines()
@@ -0,0 +1,253 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from functools import lru_cache
5
+ from itertools import chain, count
6
+ from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple
7
+
8
+ try:
9
+ from lxml import etree
10
+ except ImportError:
11
+ # lxml is required for subsetting SVG, but we prefer to delay the import error
12
+ # until subset_glyphs() is called (i.e. if font to subset has an 'SVG ' table)
13
+ etree = None
14
+
15
+ from fontTools import ttLib
16
+ from fontTools.subset.util import _add_method
17
+ from fontTools.ttLib.tables.S_V_G_ import SVGDocument
18
+
19
+
20
+ __all__ = ["subset_glyphs"]
21
+
22
+
23
+ GID_RE = re.compile(r"^glyph(\d+)$")
24
+
25
+ NAMESPACES = {
26
+ "svg": "http://www.w3.org/2000/svg",
27
+ "xlink": "http://www.w3.org/1999/xlink",
28
+ }
29
+ XLINK_HREF = f'{{{NAMESPACES["xlink"]}}}href'
30
+
31
+
32
+ # TODO(antrotype): Replace with functools.cache once we are 3.9+
33
+ @lru_cache(maxsize=None)
34
+ def xpath(path):
35
+ # compile XPath upfront, caching result to reuse on multiple elements
36
+ return etree.XPath(path, namespaces=NAMESPACES)
37
+
38
+
39
+ def group_elements_by_id(tree: etree.Element) -> Dict[str, etree.Element]:
40
+ # select all svg elements with 'id' attribute no matter where they are
41
+ # including the root element itself:
42
+ # https://github.com/fonttools/fonttools/issues/2548
43
+ return {el.attrib["id"]: el for el in xpath("//svg:*[@id]")(tree)}
44
+
45
+
46
+ def parse_css_declarations(style_attr: str) -> Dict[str, str]:
47
+ # https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/style
48
+ # https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax#css_declarations
49
+ result = {}
50
+ for declaration in style_attr.split(";"):
51
+ if declaration.count(":") == 1:
52
+ property_name, value = declaration.split(":")
53
+ property_name = property_name.strip()
54
+ result[property_name] = value.strip()
55
+ elif declaration.strip():
56
+ raise ValueError(f"Invalid CSS declaration syntax: {declaration}")
57
+ return result
58
+
59
+
60
+ def iter_referenced_ids(tree: etree.Element) -> Iterator[str]:
61
+ # Yield all the ids that can be reached via references from this element tree.
62
+ # We currently support xlink:href (as used by <use> and gradient templates),
63
+ # and local url(#...) links found in fill or clip-path attributes
64
+ # TODO(anthrotype): Check we aren't missing other supported kinds of reference
65
+ find_svg_elements_with_references = xpath(
66
+ ".//svg:*[ "
67
+ "starts-with(@xlink:href, '#') "
68
+ "or starts-with(@fill, 'url(#') "
69
+ "or starts-with(@clip-path, 'url(#') "
70
+ "or contains(@style, ':url(#') "
71
+ "]",
72
+ )
73
+ for el in chain([tree], find_svg_elements_with_references(tree)):
74
+ ref_id = href_local_target(el)
75
+ if ref_id is not None:
76
+ yield ref_id
77
+
78
+ attrs = el.attrib
79
+ if "style" in attrs:
80
+ attrs = {**dict(attrs), **parse_css_declarations(el.attrib["style"])}
81
+ for attr in ("fill", "clip-path"):
82
+ if attr in attrs:
83
+ value = attrs[attr]
84
+ if value.startswith("url(#") and value.endswith(")"):
85
+ ref_id = value[5:-1]
86
+ assert ref_id
87
+ yield ref_id
88
+
89
+
90
+ def closure_element_ids(
91
+ elements: Dict[str, etree.Element], element_ids: Set[str]
92
+ ) -> None:
93
+ # Expand the initial subset of element ids to include ids that can be reached
94
+ # via references from the initial set.
95
+ unvisited = element_ids
96
+ while unvisited:
97
+ referenced: Set[str] = set()
98
+ for el_id in unvisited:
99
+ if el_id not in elements:
100
+ # ignore dangling reference; not our job to validate svg
101
+ continue
102
+ referenced.update(iter_referenced_ids(elements[el_id]))
103
+ referenced -= element_ids
104
+ element_ids.update(referenced)
105
+ unvisited = referenced
106
+
107
+
108
+ def subset_elements(el: etree.Element, retained_ids: Set[str]) -> bool:
109
+ # Keep elements if their id is in the subset, or any of their children's id is.
110
+ # Drop elements whose id is not in the subset, and either have no children,
111
+ # or all their children are being dropped.
112
+ if el.attrib.get("id") in retained_ids:
113
+ # if id is in the set, don't recurse; keep whole subtree
114
+ return True
115
+ # recursively subset all the children; we use a list comprehension instead
116
+ # of a parentheses-less generator expression because we don't want any() to
117
+ # short-circuit, as our function has a side effect of dropping empty elements.
118
+ if any([subset_elements(e, retained_ids) for e in el]):
119
+ return True
120
+ assert len(el) == 0
121
+ parent = el.getparent()
122
+ if parent is not None:
123
+ parent.remove(el)
124
+ return False
125
+
126
+
127
+ def remap_glyph_ids(
128
+ svg: etree.Element, glyph_index_map: Dict[int, int]
129
+ ) -> Dict[str, str]:
130
+ # Given {old_gid: new_gid} map, rename all elements containing id="glyph{gid}"
131
+ # special attributes
132
+ elements = group_elements_by_id(svg)
133
+ id_map = {}
134
+ for el_id, el in elements.items():
135
+ m = GID_RE.match(el_id)
136
+ if not m:
137
+ continue
138
+ old_index = int(m.group(1))
139
+ new_index = glyph_index_map.get(old_index)
140
+ if new_index is not None:
141
+ if old_index == new_index:
142
+ continue
143
+ new_id = f"glyph{new_index}"
144
+ else:
145
+ # If the old index is missing, the element correspond to a glyph that was
146
+ # excluded from the font's subset.
147
+ # We rename it to avoid clashes with the new GIDs or other element ids.
148
+ new_id = f".{el_id}"
149
+ n = count(1)
150
+ while new_id in elements:
151
+ new_id = f"{new_id}.{next(n)}"
152
+
153
+ id_map[el_id] = new_id
154
+ el.attrib["id"] = new_id
155
+
156
+ return id_map
157
+
158
+
159
+ def href_local_target(el: etree.Element) -> Optional[str]:
160
+ if XLINK_HREF in el.attrib:
161
+ href = el.attrib[XLINK_HREF]
162
+ if href.startswith("#") and len(href) > 1:
163
+ return href[1:] # drop the leading #
164
+ return None
165
+
166
+
167
+ def update_glyph_href_links(svg: etree.Element, id_map: Dict[str, str]) -> None:
168
+ # update all xlink:href="#glyph..." attributes to point to the new glyph ids
169
+ for el in xpath(".//svg:*[starts-with(@xlink:href, '#glyph')]")(svg):
170
+ old_id = href_local_target(el)
171
+ assert old_id is not None
172
+ if old_id in id_map:
173
+ new_id = id_map[old_id]
174
+ el.attrib[XLINK_HREF] = f"#{new_id}"
175
+
176
+
177
+ def ranges(ints: Iterable[int]) -> Iterator[Tuple[int, int]]:
178
+ # Yield sorted, non-overlapping (min, max) ranges of consecutive integers
179
+ sorted_ints = iter(sorted(set(ints)))
180
+ try:
181
+ start = end = next(sorted_ints)
182
+ except StopIteration:
183
+ return
184
+ for v in sorted_ints:
185
+ if v - 1 == end:
186
+ end = v
187
+ else:
188
+ yield (start, end)
189
+ start = end = v
190
+ yield (start, end)
191
+
192
+
193
+ @_add_method(ttLib.getTableClass("SVG "))
194
+ def subset_glyphs(self, s) -> bool:
195
+ if etree is None:
196
+ raise ImportError("No module named 'lxml', required to subset SVG")
197
+
198
+ # glyph names (before subsetting)
199
+ glyph_order: List[str] = s.orig_glyph_order
200
+ # map from glyph names to original glyph indices
201
+ rev_orig_glyph_map: Dict[str, int] = s.reverseOrigGlyphMap
202
+ # map from original to new glyph indices (after subsetting)
203
+ glyph_index_map: Dict[int, int] = s.glyph_index_map
204
+
205
+ new_docs: List[SVGDocument] = []
206
+ for doc in self.docList:
207
+ glyphs = {
208
+ glyph_order[i] for i in range(doc.startGlyphID, doc.endGlyphID + 1)
209
+ }.intersection(s.glyphs)
210
+ if not glyphs:
211
+ # no intersection: we can drop the whole record
212
+ continue
213
+
214
+ svg = etree.fromstring(
215
+ # encode because fromstring dislikes xml encoding decl if input is str.
216
+ # SVG xml encoding must be utf-8 as per OT spec.
217
+ doc.data.encode("utf-8"),
218
+ parser=etree.XMLParser(
219
+ # Disable libxml2 security restrictions to support very deep trees.
220
+ # Without this we would get an error like this:
221
+ # `lxml.etree.XMLSyntaxError: internal error: Huge input lookup`
222
+ # when parsing big fonts e.g. noto-emoji-picosvg.ttf.
223
+ huge_tree=True,
224
+ # ignore blank text as it's not meaningful in OT-SVG; it also prevents
225
+ # dangling tail text after removing an element when pretty_print=True
226
+ remove_blank_text=True,
227
+ # don't replace entities; we don't expect any in OT-SVG and they may
228
+ # be abused for XXE attacks
229
+ resolve_entities=False,
230
+ ),
231
+ )
232
+
233
+ elements = group_elements_by_id(svg)
234
+ gids = {rev_orig_glyph_map[g] for g in glyphs}
235
+ element_ids = {f"glyph{i}" for i in gids}
236
+ closure_element_ids(elements, element_ids)
237
+
238
+ if not subset_elements(svg, element_ids):
239
+ continue
240
+
241
+ if not s.options.retain_gids:
242
+ id_map = remap_glyph_ids(svg, glyph_index_map)
243
+ update_glyph_href_links(svg, id_map)
244
+
245
+ new_doc = etree.tostring(svg, pretty_print=s.options.pretty_svg).decode("utf-8")
246
+
247
+ new_gids = (glyph_index_map[i] for i in gids)
248
+ for start, end in ranges(new_gids):
249
+ new_docs.append(SVGDocument(new_doc, start, end, doc.compressed))
250
+
251
+ self.docList = new_docs
252
+
253
+ return bool(self.docList)
@@ -0,0 +1,25 @@
1
+ """Private utility methods used by the subset modules"""
2
+
3
+
4
+ def _add_method(*clazzes):
5
+ """Returns a decorator function that adds a new method to one or
6
+ more classes."""
7
+
8
+ def wrapper(method):
9
+ done = []
10
+ for clazz in clazzes:
11
+ if clazz in done:
12
+ continue # Support multiple names of a clazz
13
+ done.append(clazz)
14
+ assert clazz.__name__ != "DefaultTable", "Oops, table class not found."
15
+ assert not hasattr(
16
+ clazz, method.__name__
17
+ ), "Oops, class '%s' has method '%s'." % (clazz.__name__, method.__name__)
18
+ setattr(clazz, method.__name__, method)
19
+ return None
20
+
21
+ return wrapper
22
+
23
+
24
+ def _uniq_sort(l):
25
+ return sorted(set(l))
@@ -0,0 +1,3 @@
1
+ from .path import SVGPath, parse_path
2
+
3
+ __all__ = ["SVGPath", "parse_path"]
@@ -0,0 +1,65 @@
1
+ from fontTools.pens.transformPen import TransformPen
2
+ from fontTools.misc import etree
3
+ from fontTools.misc.textTools import tostr
4
+ from .parser import parse_path
5
+ from .shapes import PathBuilder
6
+
7
+
8
+ __all__ = [tostr(s) for s in ("SVGPath", "parse_path")]
9
+
10
+
11
+ class SVGPath(object):
12
+ """Parse SVG ``path`` elements from a file or string, and draw them
13
+ onto a glyph object that supports the FontTools Pen protocol.
14
+
15
+ For example, reading from an SVG file and drawing to a Defcon Glyph:
16
+
17
+ .. code-block::
18
+
19
+ import defcon
20
+ glyph = defcon.Glyph()
21
+ pen = glyph.getPen()
22
+ svg = SVGPath("path/to/a.svg")
23
+ svg.draw(pen)
24
+
25
+ Or reading from a string containing SVG data, using the alternative
26
+ 'fromstring' (a class method):
27
+
28
+ .. code-block::
29
+
30
+ data = '<?xml version="1.0" ...'
31
+ svg = SVGPath.fromstring(data)
32
+ svg.draw(pen)
33
+
34
+ Both constructors can optionally take a 'transform' matrix (6-float
35
+ tuple, or a FontTools Transform object) to modify the draw output.
36
+ """
37
+
38
+ def __init__(self, filename=None, transform=None):
39
+ if filename is None:
40
+ self.root = etree.ElementTree()
41
+ else:
42
+ tree = etree.parse(filename)
43
+ self.root = tree.getroot()
44
+ self.transform = transform
45
+
46
+ @classmethod
47
+ def fromstring(cls, data, transform=None):
48
+ self = cls(transform=transform)
49
+ self.root = etree.fromstring(data)
50
+ return self
51
+
52
+ def draw(self, pen):
53
+ if self.transform:
54
+ pen = TransformPen(pen, self.transform)
55
+ pb = PathBuilder()
56
+ # xpath | doesn't seem to reliable work so just walk it
57
+ for el in self.root.iter():
58
+ pb.add_path_from_element(el)
59
+ original_pen = pen
60
+ for path, transform in zip(pb.paths, pb.transforms):
61
+ if transform:
62
+ pen = TransformPen(original_pen, transform)
63
+ else:
64
+ pen = original_pen
65
+ parse_path(path, pen)
@@ -0,0 +1,154 @@
1
+ """Convert SVG Path's elliptical arcs to Bezier curves.
2
+
3
+ The code is mostly adapted from Blink's SVGPathNormalizer::DecomposeArcToCubic
4
+ https://github.com/chromium/chromium/blob/93831f2/third_party/
5
+ blink/renderer/core/svg/svg_path_parser.cc#L169-L278
6
+ """
7
+
8
+ from fontTools.misc.transform import Identity, Scale
9
+ from math import atan2, ceil, cos, fabs, isfinite, pi, radians, sin, sqrt, tan
10
+
11
+
12
+ TWO_PI = 2 * pi
13
+ PI_OVER_TWO = 0.5 * pi
14
+
15
+
16
+ def _map_point(matrix, pt):
17
+ # apply Transform matrix to a point represented as a complex number
18
+ r = matrix.transformPoint((pt.real, pt.imag))
19
+ return r[0] + r[1] * 1j
20
+
21
+
22
+ class EllipticalArc(object):
23
+ def __init__(self, current_point, rx, ry, rotation, large, sweep, target_point):
24
+ self.current_point = current_point
25
+ self.rx = rx
26
+ self.ry = ry
27
+ self.rotation = rotation
28
+ self.large = large
29
+ self.sweep = sweep
30
+ self.target_point = target_point
31
+
32
+ # SVG arc's rotation angle is expressed in degrees, whereas Transform.rotate
33
+ # uses radians
34
+ self.angle = radians(rotation)
35
+
36
+ # these derived attributes are computed by the _parametrize method
37
+ self.center_point = self.theta1 = self.theta2 = self.theta_arc = None
38
+
39
+ def _parametrize(self):
40
+ # convert from endopoint to center parametrization:
41
+ # https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
42
+
43
+ # If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a
44
+ # "lineto") joining the endpoints.
45
+ # http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
46
+ rx = fabs(self.rx)
47
+ ry = fabs(self.ry)
48
+ if not (rx and ry):
49
+ return False
50
+
51
+ # If the current point and target point for the arc are identical, it should
52
+ # be treated as a zero length path. This ensures continuity in animations.
53
+ if self.target_point == self.current_point:
54
+ return False
55
+
56
+ mid_point_distance = (self.current_point - self.target_point) * 0.5
57
+
58
+ point_transform = Identity.rotate(-self.angle)
59
+
60
+ transformed_mid_point = _map_point(point_transform, mid_point_distance)
61
+ square_rx = rx * rx
62
+ square_ry = ry * ry
63
+ square_x = transformed_mid_point.real * transformed_mid_point.real
64
+ square_y = transformed_mid_point.imag * transformed_mid_point.imag
65
+
66
+ # Check if the radii are big enough to draw the arc, scale radii if not.
67
+ # http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
68
+ radii_scale = square_x / square_rx + square_y / square_ry
69
+ if radii_scale > 1:
70
+ rx *= sqrt(radii_scale)
71
+ ry *= sqrt(radii_scale)
72
+ self.rx, self.ry = rx, ry
73
+
74
+ point_transform = Scale(1 / rx, 1 / ry).rotate(-self.angle)
75
+
76
+ point1 = _map_point(point_transform, self.current_point)
77
+ point2 = _map_point(point_transform, self.target_point)
78
+ delta = point2 - point1
79
+
80
+ d = delta.real * delta.real + delta.imag * delta.imag
81
+ scale_factor_squared = max(1 / d - 0.25, 0.0)
82
+
83
+ scale_factor = sqrt(scale_factor_squared)
84
+ if self.sweep == self.large:
85
+ scale_factor = -scale_factor
86
+
87
+ delta *= scale_factor
88
+ center_point = (point1 + point2) * 0.5
89
+ center_point += complex(-delta.imag, delta.real)
90
+ point1 -= center_point
91
+ point2 -= center_point
92
+
93
+ theta1 = atan2(point1.imag, point1.real)
94
+ theta2 = atan2(point2.imag, point2.real)
95
+
96
+ theta_arc = theta2 - theta1
97
+ if theta_arc < 0 and self.sweep:
98
+ theta_arc += TWO_PI
99
+ elif theta_arc > 0 and not self.sweep:
100
+ theta_arc -= TWO_PI
101
+
102
+ self.theta1 = theta1
103
+ self.theta2 = theta1 + theta_arc
104
+ self.theta_arc = theta_arc
105
+ self.center_point = center_point
106
+
107
+ return True
108
+
109
+ def _decompose_to_cubic_curves(self):
110
+ if self.center_point is None and not self._parametrize():
111
+ return
112
+
113
+ point_transform = Identity.rotate(self.angle).scale(self.rx, self.ry)
114
+
115
+ # Some results of atan2 on some platform implementations are not exact
116
+ # enough. So that we get more cubic curves than expected here. Adding 0.001f
117
+ # reduces the count of sgements to the correct count.
118
+ num_segments = int(ceil(fabs(self.theta_arc / (PI_OVER_TWO + 0.001))))
119
+ for i in range(num_segments):
120
+ start_theta = self.theta1 + i * self.theta_arc / num_segments
121
+ end_theta = self.theta1 + (i + 1) * self.theta_arc / num_segments
122
+
123
+ t = (4 / 3) * tan(0.25 * (end_theta - start_theta))
124
+ if not isfinite(t):
125
+ return
126
+
127
+ sin_start_theta = sin(start_theta)
128
+ cos_start_theta = cos(start_theta)
129
+ sin_end_theta = sin(end_theta)
130
+ cos_end_theta = cos(end_theta)
131
+
132
+ point1 = complex(
133
+ cos_start_theta - t * sin_start_theta,
134
+ sin_start_theta + t * cos_start_theta,
135
+ )
136
+ point1 += self.center_point
137
+ target_point = complex(cos_end_theta, sin_end_theta)
138
+ target_point += self.center_point
139
+ point2 = target_point
140
+ point2 += complex(t * sin_end_theta, -t * cos_end_theta)
141
+
142
+ point1 = _map_point(point_transform, point1)
143
+ point2 = _map_point(point_transform, point2)
144
+ target_point = _map_point(point_transform, target_point)
145
+
146
+ yield point1, point2, target_point
147
+
148
+ def draw(self, pen):
149
+ for point1, point2, target_point in self._decompose_to_cubic_curves():
150
+ pen.curveTo(
151
+ (point1.real, point1.imag),
152
+ (point2.real, point2.imag),
153
+ (target_point.real, target_point.imag),
154
+ )