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,516 @@
1
+ """Affine 2D transformation matrix class.
2
+
3
+ The Transform class implements various transformation matrix operations,
4
+ both on the matrix itself, as well as on 2D coordinates.
5
+
6
+ Transform instances are effectively immutable: all methods that operate on the
7
+ transformation itself always return a new instance. This has as the
8
+ interesting side effect that Transform instances are hashable, ie. they can be
9
+ used as dictionary keys.
10
+
11
+ This module exports the following symbols:
12
+
13
+ Transform
14
+ this is the main class
15
+ Identity
16
+ Transform instance set to the identity transformation
17
+ Offset
18
+ Convenience function that returns a translating transformation
19
+ Scale
20
+ Convenience function that returns a scaling transformation
21
+
22
+ The DecomposedTransform class implements a transformation with separate
23
+ translate, rotation, scale, skew, and transformation-center components.
24
+
25
+ :Example:
26
+
27
+ >>> t = Transform(2, 0, 0, 3, 0, 0)
28
+ >>> t.transformPoint((100, 100))
29
+ (200, 300)
30
+ >>> t = Scale(2, 3)
31
+ >>> t.transformPoint((100, 100))
32
+ (200, 300)
33
+ >>> t.transformPoint((0, 0))
34
+ (0, 0)
35
+ >>> t = Offset(2, 3)
36
+ >>> t.transformPoint((100, 100))
37
+ (102, 103)
38
+ >>> t.transformPoint((0, 0))
39
+ (2, 3)
40
+ >>> t2 = t.scale(0.5)
41
+ >>> t2.transformPoint((100, 100))
42
+ (52.0, 53.0)
43
+ >>> import math
44
+ >>> t3 = t2.rotate(math.pi / 2)
45
+ >>> t3.transformPoint((0, 0))
46
+ (2.0, 3.0)
47
+ >>> t3.transformPoint((100, 100))
48
+ (-48.0, 53.0)
49
+ >>> t = Identity.scale(0.5).translate(100, 200).skew(0.1, 0.2)
50
+ >>> t.transformPoints([(0, 0), (1, 1), (100, 100)])
51
+ [(50.0, 100.0), (50.550167336042726, 100.60135501775433), (105.01673360427253, 160.13550177543362)]
52
+ >>>
53
+ """
54
+
55
+ from __future__ import annotations
56
+
57
+ import math
58
+ from typing import NamedTuple
59
+ from dataclasses import dataclass
60
+
61
+
62
+ __all__ = ["Transform", "Identity", "Offset", "Scale", "DecomposedTransform"]
63
+
64
+
65
+ _EPSILON = 1e-15
66
+ _ONE_EPSILON = 1 - _EPSILON
67
+ _MINUS_ONE_EPSILON = -1 + _EPSILON
68
+
69
+
70
+ def _normSinCos(v: float) -> float:
71
+ if abs(v) < _EPSILON:
72
+ v = 0
73
+ elif v > _ONE_EPSILON:
74
+ v = 1
75
+ elif v < _MINUS_ONE_EPSILON:
76
+ v = -1
77
+ return v
78
+
79
+
80
+ class Transform(NamedTuple):
81
+ """2x2 transformation matrix plus offset, a.k.a. Affine transform.
82
+ Transform instances are immutable: all transforming methods, eg.
83
+ rotate(), return a new Transform instance.
84
+
85
+ :Example:
86
+
87
+ >>> t = Transform()
88
+ >>> t
89
+ <Transform [1 0 0 1 0 0]>
90
+ >>> t.scale(2)
91
+ <Transform [2 0 0 2 0 0]>
92
+ >>> t.scale(2.5, 5.5)
93
+ <Transform [2.5 0 0 5.5 0 0]>
94
+ >>>
95
+ >>> t.scale(2, 3).transformPoint((100, 100))
96
+ (200, 300)
97
+
98
+ Transform's constructor takes six arguments, all of which are
99
+ optional, and can be used as keyword arguments::
100
+
101
+ >>> Transform(12)
102
+ <Transform [12 0 0 1 0 0]>
103
+ >>> Transform(dx=12)
104
+ <Transform [1 0 0 1 12 0]>
105
+ >>> Transform(yx=12)
106
+ <Transform [1 0 12 1 0 0]>
107
+
108
+ Transform instances also behave like sequences of length 6::
109
+
110
+ >>> len(Identity)
111
+ 6
112
+ >>> list(Identity)
113
+ [1, 0, 0, 1, 0, 0]
114
+ >>> tuple(Identity)
115
+ (1, 0, 0, 1, 0, 0)
116
+
117
+ Transform instances are comparable::
118
+
119
+ >>> t1 = Identity.scale(2, 3).translate(4, 6)
120
+ >>> t2 = Identity.translate(8, 18).scale(2, 3)
121
+ >>> t1 == t2
122
+ 1
123
+
124
+ But beware of floating point rounding errors::
125
+
126
+ >>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6)
127
+ >>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3)
128
+ >>> t1
129
+ <Transform [0.2 0 0 0.3 0.08 0.18]>
130
+ >>> t2
131
+ <Transform [0.2 0 0 0.3 0.08 0.18]>
132
+ >>> t1 == t2
133
+ 0
134
+
135
+ Transform instances are hashable, meaning you can use them as
136
+ keys in dictionaries::
137
+
138
+ >>> d = {Scale(12, 13): None}
139
+ >>> d
140
+ {<Transform [12 0 0 13 0 0]>: None}
141
+
142
+ But again, beware of floating point rounding errors::
143
+
144
+ >>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6)
145
+ >>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3)
146
+ >>> t1
147
+ <Transform [0.2 0 0 0.3 0.08 0.18]>
148
+ >>> t2
149
+ <Transform [0.2 0 0 0.3 0.08 0.18]>
150
+ >>> d = {t1: None}
151
+ >>> d
152
+ {<Transform [0.2 0 0 0.3 0.08 0.18]>: None}
153
+ >>> d[t2]
154
+ Traceback (most recent call last):
155
+ File "<stdin>", line 1, in ?
156
+ KeyError: <Transform [0.2 0 0 0.3 0.08 0.18]>
157
+ """
158
+
159
+ xx: float = 1
160
+ xy: float = 0
161
+ yx: float = 0
162
+ yy: float = 1
163
+ dx: float = 0
164
+ dy: float = 0
165
+
166
+ def transformPoint(self, p):
167
+ """Transform a point.
168
+
169
+ :Example:
170
+
171
+ >>> t = Transform()
172
+ >>> t = t.scale(2.5, 5.5)
173
+ >>> t.transformPoint((100, 100))
174
+ (250.0, 550.0)
175
+ """
176
+ (x, y) = p
177
+ xx, xy, yx, yy, dx, dy = self
178
+ return (xx * x + yx * y + dx, xy * x + yy * y + dy)
179
+
180
+ def transformPoints(self, points):
181
+ """Transform a list of points.
182
+
183
+ :Example:
184
+
185
+ >>> t = Scale(2, 3)
186
+ >>> t.transformPoints([(0, 0), (0, 100), (100, 100), (100, 0)])
187
+ [(0, 0), (0, 300), (200, 300), (200, 0)]
188
+ >>>
189
+ """
190
+ xx, xy, yx, yy, dx, dy = self
191
+ return [(xx * x + yx * y + dx, xy * x + yy * y + dy) for x, y in points]
192
+
193
+ def transformVector(self, v):
194
+ """Transform an (dx, dy) vector, treating translation as zero.
195
+
196
+ :Example:
197
+
198
+ >>> t = Transform(2, 0, 0, 2, 10, 20)
199
+ >>> t.transformVector((3, -4))
200
+ (6, -8)
201
+ >>>
202
+ """
203
+ (dx, dy) = v
204
+ xx, xy, yx, yy = self[:4]
205
+ return (xx * dx + yx * dy, xy * dx + yy * dy)
206
+
207
+ def transformVectors(self, vectors):
208
+ """Transform a list of (dx, dy) vector, treating translation as zero.
209
+
210
+ :Example:
211
+ >>> t = Transform(2, 0, 0, 2, 10, 20)
212
+ >>> t.transformVectors([(3, -4), (5, -6)])
213
+ [(6, -8), (10, -12)]
214
+ >>>
215
+ """
216
+ xx, xy, yx, yy = self[:4]
217
+ return [(xx * dx + yx * dy, xy * dx + yy * dy) for dx, dy in vectors]
218
+
219
+ def translate(self, x: float = 0, y: float = 0):
220
+ """Return a new transformation, translated (offset) by x, y.
221
+
222
+ :Example:
223
+ >>> t = Transform()
224
+ >>> t.translate(20, 30)
225
+ <Transform [1 0 0 1 20 30]>
226
+ >>>
227
+ """
228
+ return self.transform((1, 0, 0, 1, x, y))
229
+
230
+ def scale(self, x: float = 1, y: float | None = None):
231
+ """Return a new transformation, scaled by x, y. The 'y' argument
232
+ may be None, which implies to use the x value for y as well.
233
+
234
+ :Example:
235
+ >>> t = Transform()
236
+ >>> t.scale(5)
237
+ <Transform [5 0 0 5 0 0]>
238
+ >>> t.scale(5, 6)
239
+ <Transform [5 0 0 6 0 0]>
240
+ >>>
241
+ """
242
+ if y is None:
243
+ y = x
244
+ return self.transform((x, 0, 0, y, 0, 0))
245
+
246
+ def rotate(self, angle: float):
247
+ """Return a new transformation, rotated by 'angle' (radians).
248
+
249
+ :Example:
250
+ >>> import math
251
+ >>> t = Transform()
252
+ >>> t.rotate(math.pi / 2)
253
+ <Transform [0 1 -1 0 0 0]>
254
+ >>>
255
+ """
256
+ c = _normSinCos(math.cos(angle))
257
+ s = _normSinCos(math.sin(angle))
258
+ return self.transform((c, s, -s, c, 0, 0))
259
+
260
+ def skew(self, x: float = 0, y: float = 0):
261
+ """Return a new transformation, skewed by x and y.
262
+
263
+ :Example:
264
+ >>> import math
265
+ >>> t = Transform()
266
+ >>> t.skew(math.pi / 4)
267
+ <Transform [1 0 1 1 0 0]>
268
+ >>>
269
+ """
270
+ return self.transform((1, math.tan(y), math.tan(x), 1, 0, 0))
271
+
272
+ def transform(self, other):
273
+ """Return a new transformation, transformed by another
274
+ transformation.
275
+
276
+ :Example:
277
+ >>> t = Transform(2, 0, 0, 3, 1, 6)
278
+ >>> t.transform((4, 3, 2, 1, 5, 6))
279
+ <Transform [8 9 4 3 11 24]>
280
+ >>>
281
+ """
282
+ xx1, xy1, yx1, yy1, dx1, dy1 = other
283
+ xx2, xy2, yx2, yy2, dx2, dy2 = self
284
+ return self.__class__(
285
+ xx1 * xx2 + xy1 * yx2,
286
+ xx1 * xy2 + xy1 * yy2,
287
+ yx1 * xx2 + yy1 * yx2,
288
+ yx1 * xy2 + yy1 * yy2,
289
+ xx2 * dx1 + yx2 * dy1 + dx2,
290
+ xy2 * dx1 + yy2 * dy1 + dy2,
291
+ )
292
+
293
+ def reverseTransform(self, other):
294
+ """Return a new transformation, which is the other transformation
295
+ transformed by self. self.reverseTransform(other) is equivalent to
296
+ other.transform(self).
297
+
298
+ :Example:
299
+ >>> t = Transform(2, 0, 0, 3, 1, 6)
300
+ >>> t.reverseTransform((4, 3, 2, 1, 5, 6))
301
+ <Transform [8 6 6 3 21 15]>
302
+ >>> Transform(4, 3, 2, 1, 5, 6).transform((2, 0, 0, 3, 1, 6))
303
+ <Transform [8 6 6 3 21 15]>
304
+ >>>
305
+ """
306
+ xx1, xy1, yx1, yy1, dx1, dy1 = self
307
+ xx2, xy2, yx2, yy2, dx2, dy2 = other
308
+ return self.__class__(
309
+ xx1 * xx2 + xy1 * yx2,
310
+ xx1 * xy2 + xy1 * yy2,
311
+ yx1 * xx2 + yy1 * yx2,
312
+ yx1 * xy2 + yy1 * yy2,
313
+ xx2 * dx1 + yx2 * dy1 + dx2,
314
+ xy2 * dx1 + yy2 * dy1 + dy2,
315
+ )
316
+
317
+ def inverse(self):
318
+ """Return the inverse transformation.
319
+
320
+ :Example:
321
+ >>> t = Identity.translate(2, 3).scale(4, 5)
322
+ >>> t.transformPoint((10, 20))
323
+ (42, 103)
324
+ >>> it = t.inverse()
325
+ >>> it.transformPoint((42, 103))
326
+ (10.0, 20.0)
327
+ >>>
328
+ """
329
+ if self == Identity:
330
+ return self
331
+ xx, xy, yx, yy, dx, dy = self
332
+ det = xx * yy - yx * xy
333
+ xx, xy, yx, yy = yy / det, -xy / det, -yx / det, xx / det
334
+ dx, dy = -xx * dx - yx * dy, -xy * dx - yy * dy
335
+ return self.__class__(xx, xy, yx, yy, dx, dy)
336
+
337
+ def toPS(self) -> str:
338
+ """Return a PostScript representation
339
+
340
+ :Example:
341
+
342
+ >>> t = Identity.scale(2, 3).translate(4, 5)
343
+ >>> t.toPS()
344
+ '[2 0 0 3 8 15]'
345
+ >>>
346
+ """
347
+ return "[%s %s %s %s %s %s]" % self
348
+
349
+ def toDecomposed(self) -> "DecomposedTransform":
350
+ """Decompose into a DecomposedTransform."""
351
+ return DecomposedTransform.fromTransform(self)
352
+
353
+ def __bool__(self) -> bool:
354
+ """Returns True if transform is not identity, False otherwise.
355
+
356
+ :Example:
357
+
358
+ >>> bool(Identity)
359
+ False
360
+ >>> bool(Transform())
361
+ False
362
+ >>> bool(Scale(1.))
363
+ False
364
+ >>> bool(Scale(2))
365
+ True
366
+ >>> bool(Offset())
367
+ False
368
+ >>> bool(Offset(0))
369
+ False
370
+ >>> bool(Offset(2))
371
+ True
372
+ """
373
+ return self != Identity
374
+
375
+ def __repr__(self) -> str:
376
+ return "<%s [%g %g %g %g %g %g]>" % ((self.__class__.__name__,) + self)
377
+
378
+
379
+ Identity = Transform()
380
+
381
+
382
+ def Offset(x: float = 0, y: float = 0) -> Transform:
383
+ """Return the identity transformation offset by x, y.
384
+
385
+ :Example:
386
+ >>> Offset(2, 3)
387
+ <Transform [1 0 0 1 2 3]>
388
+ >>>
389
+ """
390
+ return Transform(1, 0, 0, 1, x, y)
391
+
392
+
393
+ def Scale(x: float, y: float | None = None) -> Transform:
394
+ """Return the identity transformation scaled by x, y. The 'y' argument
395
+ may be None, which implies to use the x value for y as well.
396
+
397
+ :Example:
398
+ >>> Scale(2, 3)
399
+ <Transform [2 0 0 3 0 0]>
400
+ >>>
401
+ """
402
+ if y is None:
403
+ y = x
404
+ return Transform(x, 0, 0, y, 0, 0)
405
+
406
+
407
+ @dataclass
408
+ class DecomposedTransform:
409
+ """The DecomposedTransform class implements a transformation with separate
410
+ translate, rotation, scale, skew, and transformation-center components.
411
+ """
412
+
413
+ translateX: float = 0
414
+ translateY: float = 0
415
+ rotation: float = 0 # in degrees, counter-clockwise
416
+ scaleX: float = 1
417
+ scaleY: float = 1
418
+ skewX: float = 0 # in degrees, clockwise
419
+ skewY: float = 0 # in degrees, counter-clockwise
420
+ tCenterX: float = 0
421
+ tCenterY: float = 0
422
+
423
+ def __bool__(self):
424
+ return (
425
+ self.translateX != 0
426
+ or self.translateY != 0
427
+ or self.rotation != 0
428
+ or self.scaleX != 1
429
+ or self.scaleY != 1
430
+ or self.skewX != 0
431
+ or self.skewY != 0
432
+ or self.tCenterX != 0
433
+ or self.tCenterY != 0
434
+ )
435
+
436
+ @classmethod
437
+ def fromTransform(self, transform):
438
+ """Return a DecomposedTransform() equivalent of this transformation.
439
+ The returned solution always has skewY = 0, and angle in the (-180, 180].
440
+
441
+ :Example:
442
+ >>> DecomposedTransform.fromTransform(Transform(3, 0, 0, 2, 0, 0))
443
+ DecomposedTransform(translateX=0, translateY=0, rotation=0.0, scaleX=3.0, scaleY=2.0, skewX=0.0, skewY=0.0, tCenterX=0, tCenterY=0)
444
+ >>> DecomposedTransform.fromTransform(Transform(0, 0, 0, 1, 0, 0))
445
+ DecomposedTransform(translateX=0, translateY=0, rotation=0.0, scaleX=0.0, scaleY=1.0, skewX=0.0, skewY=0.0, tCenterX=0, tCenterY=0)
446
+ >>> DecomposedTransform.fromTransform(Transform(0, 0, 1, 1, 0, 0))
447
+ DecomposedTransform(translateX=0, translateY=0, rotation=-45.0, scaleX=0.0, scaleY=1.4142135623730951, skewX=0.0, skewY=0.0, tCenterX=0, tCenterY=0)
448
+ """
449
+ # Adapted from an answer on
450
+ # https://math.stackexchange.com/questions/13150/extracting-rotation-scale-values-from-2d-transformation-matrix
451
+
452
+ a, b, c, d, x, y = transform
453
+
454
+ sx = math.copysign(1, a)
455
+ if sx < 0:
456
+ a *= sx
457
+ b *= sx
458
+
459
+ delta = a * d - b * c
460
+
461
+ rotation = 0
462
+ scaleX = scaleY = 0
463
+ skewX = 0
464
+
465
+ # Apply the QR-like decomposition.
466
+ if a != 0 or b != 0:
467
+ r = math.sqrt(a * a + b * b)
468
+ rotation = math.acos(a / r) if b >= 0 else -math.acos(a / r)
469
+ scaleX, scaleY = (r, delta / r)
470
+ skewX = math.atan((a * c + b * d) / (r * r))
471
+ elif c != 0 or d != 0:
472
+ s = math.sqrt(c * c + d * d)
473
+ rotation = math.pi / 2 - (
474
+ math.acos(-c / s) if d >= 0 else -math.acos(c / s)
475
+ )
476
+ scaleX, scaleY = (delta / s, s)
477
+ else:
478
+ # a = b = c = d = 0
479
+ pass
480
+
481
+ return DecomposedTransform(
482
+ x,
483
+ y,
484
+ math.degrees(rotation),
485
+ scaleX * sx,
486
+ scaleY,
487
+ math.degrees(skewX) * sx,
488
+ 0.0,
489
+ 0,
490
+ 0,
491
+ )
492
+
493
+ def toTransform(self) -> Transform:
494
+ """Return the Transform() equivalent of this transformation.
495
+
496
+ :Example:
497
+ >>> DecomposedTransform(scaleX=2, scaleY=2).toTransform()
498
+ <Transform [2 0 0 2 0 0]>
499
+ >>>
500
+ """
501
+ t = Transform()
502
+ t = t.translate(
503
+ self.translateX + self.tCenterX, self.translateY + self.tCenterY
504
+ )
505
+ t = t.rotate(math.radians(self.rotation))
506
+ t = t.scale(self.scaleX, self.scaleY)
507
+ t = t.skew(math.radians(self.skewX), math.radians(self.skewY))
508
+ t = t.translate(-self.tCenterX, -self.tCenterY)
509
+ return t
510
+
511
+
512
+ if __name__ == "__main__":
513
+ import sys
514
+ import doctest
515
+
516
+ sys.exit(doctest.testmod().failed)
@@ -0,0 +1,45 @@
1
+ """Generic tools for working with trees."""
2
+
3
+ from math import ceil, log
4
+
5
+
6
+ def build_n_ary_tree(leaves, n):
7
+ """Build N-ary tree from sequence of leaf nodes.
8
+
9
+ Return a list of lists where each non-leaf node is a list containing
10
+ max n nodes.
11
+ """
12
+ if not leaves:
13
+ return []
14
+
15
+ assert n > 1
16
+
17
+ depth = ceil(log(len(leaves), n))
18
+
19
+ if depth <= 1:
20
+ return list(leaves)
21
+
22
+ # Fully populate complete subtrees of root until we have enough leaves left
23
+ root = []
24
+ unassigned = None
25
+ full_step = n ** (depth - 1)
26
+ for i in range(0, len(leaves), full_step):
27
+ subtree = leaves[i : i + full_step]
28
+ if len(subtree) < full_step:
29
+ unassigned = subtree
30
+ break
31
+ while len(subtree) > n:
32
+ subtree = [subtree[k : k + n] for k in range(0, len(subtree), n)]
33
+ root.append(subtree)
34
+
35
+ if unassigned:
36
+ # Recurse to fill the last subtree, which is the only partially populated one
37
+ subtree = build_n_ary_tree(unassigned, n)
38
+ if len(subtree) <= n - len(root):
39
+ # replace last subtree with its children if they can still fit
40
+ root.extend(subtree)
41
+ else:
42
+ root.append(subtree)
43
+ assert len(root) <= n
44
+
45
+ return root
@@ -0,0 +1,147 @@
1
+ from numbers import Number
2
+ import math
3
+ import operator
4
+ import warnings
5
+
6
+
7
+ __all__ = ["Vector"]
8
+
9
+
10
+ class Vector(tuple):
11
+ """A math-like vector.
12
+
13
+ Represents an n-dimensional numeric vector. ``Vector`` objects support
14
+ vector addition and subtraction, scalar multiplication and division,
15
+ negation, rounding, and comparison tests.
16
+ """
17
+
18
+ __slots__ = ()
19
+
20
+ def __new__(cls, values, keep=False):
21
+ if keep is not False:
22
+ warnings.warn(
23
+ "the 'keep' argument has been deprecated",
24
+ DeprecationWarning,
25
+ )
26
+ if type(values) == Vector:
27
+ # No need to create a new object
28
+ return values
29
+ return super().__new__(cls, values)
30
+
31
+ def __repr__(self):
32
+ return f"{self.__class__.__name__}({super().__repr__()})"
33
+
34
+ def _vectorOp(self, other, op):
35
+ if isinstance(other, Vector):
36
+ assert len(self) == len(other)
37
+ return self.__class__(op(a, b) for a, b in zip(self, other))
38
+ if isinstance(other, Number):
39
+ return self.__class__(op(v, other) for v in self)
40
+ raise NotImplementedError()
41
+
42
+ def _scalarOp(self, other, op):
43
+ if isinstance(other, Number):
44
+ return self.__class__(op(v, other) for v in self)
45
+ raise NotImplementedError()
46
+
47
+ def _unaryOp(self, op):
48
+ return self.__class__(op(v) for v in self)
49
+
50
+ def __add__(self, other):
51
+ return self._vectorOp(other, operator.add)
52
+
53
+ __radd__ = __add__
54
+
55
+ def __sub__(self, other):
56
+ return self._vectorOp(other, operator.sub)
57
+
58
+ def __rsub__(self, other):
59
+ return self._vectorOp(other, _operator_rsub)
60
+
61
+ def __mul__(self, other):
62
+ return self._scalarOp(other, operator.mul)
63
+
64
+ __rmul__ = __mul__
65
+
66
+ def __truediv__(self, other):
67
+ return self._scalarOp(other, operator.truediv)
68
+
69
+ def __rtruediv__(self, other):
70
+ return self._scalarOp(other, _operator_rtruediv)
71
+
72
+ def __pos__(self):
73
+ return self._unaryOp(operator.pos)
74
+
75
+ def __neg__(self):
76
+ return self._unaryOp(operator.neg)
77
+
78
+ def __round__(self, *, round=round):
79
+ return self._unaryOp(round)
80
+
81
+ def __eq__(self, other):
82
+ if isinstance(other, list):
83
+ # bw compat Vector([1, 2, 3]) == [1, 2, 3]
84
+ other = tuple(other)
85
+ return super().__eq__(other)
86
+
87
+ def __ne__(self, other):
88
+ return not self.__eq__(other)
89
+
90
+ def __bool__(self):
91
+ return any(self)
92
+
93
+ __nonzero__ = __bool__
94
+
95
+ def __abs__(self):
96
+ return math.sqrt(sum(x * x for x in self))
97
+
98
+ def length(self):
99
+ """Return the length of the vector. Equivalent to abs(vector)."""
100
+ return abs(self)
101
+
102
+ def normalized(self):
103
+ """Return the normalized vector of the vector."""
104
+ return self / abs(self)
105
+
106
+ def dot(self, other):
107
+ """Performs vector dot product, returning the sum of
108
+ ``a[0] * b[0], a[1] * b[1], ...``"""
109
+ assert len(self) == len(other)
110
+ return sum(a * b for a, b in zip(self, other))
111
+
112
+ # Deprecated methods/properties
113
+
114
+ def toInt(self):
115
+ warnings.warn(
116
+ "the 'toInt' method has been deprecated, use round(vector) instead",
117
+ DeprecationWarning,
118
+ )
119
+ return self.__round__()
120
+
121
+ @property
122
+ def values(self):
123
+ warnings.warn(
124
+ "the 'values' attribute has been deprecated, use "
125
+ "the vector object itself instead",
126
+ DeprecationWarning,
127
+ )
128
+ return list(self)
129
+
130
+ @values.setter
131
+ def values(self, values):
132
+ raise AttributeError(
133
+ "can't set attribute, the 'values' attribute has been deprecated",
134
+ )
135
+
136
+ def isclose(self, other: "Vector", **kwargs) -> bool:
137
+ """Return True if the vector is close to another Vector."""
138
+ assert len(self) == len(other)
139
+ return all(math.isclose(a, b, **kwargs) for a, b in zip(self, other))
140
+
141
+
142
+ def _operator_rsub(a, b):
143
+ return operator.sub(b, a)
144
+
145
+
146
+ def _operator_rtruediv(a, b):
147
+ return operator.truediv(b, a)