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.
- fontTools/__init__.py +8 -0
- fontTools/__main__.py +35 -0
- fontTools/afmLib.py +439 -0
- fontTools/agl.py +5233 -0
- fontTools/annotations.py +30 -0
- fontTools/cffLib/CFF2ToCFF.py +258 -0
- fontTools/cffLib/CFFToCFF2.py +305 -0
- fontTools/cffLib/__init__.py +3694 -0
- fontTools/cffLib/specializer.py +927 -0
- fontTools/cffLib/transforms.py +495 -0
- fontTools/cffLib/width.py +210 -0
- fontTools/colorLib/__init__.py +0 -0
- fontTools/colorLib/builder.py +664 -0
- fontTools/colorLib/errors.py +2 -0
- fontTools/colorLib/geometry.py +143 -0
- fontTools/colorLib/table_builder.py +223 -0
- fontTools/colorLib/unbuilder.py +81 -0
- fontTools/config/__init__.py +90 -0
- fontTools/cu2qu/__init__.py +15 -0
- fontTools/cu2qu/__main__.py +6 -0
- fontTools/cu2qu/benchmark.py +54 -0
- fontTools/cu2qu/cli.py +198 -0
- fontTools/cu2qu/cu2qu.c +15817 -0
- fontTools/cu2qu/cu2qu.cp311-win32.pyd +0 -0
- fontTools/cu2qu/cu2qu.py +563 -0
- fontTools/cu2qu/errors.py +77 -0
- fontTools/cu2qu/ufo.py +363 -0
- fontTools/designspaceLib/__init__.py +3343 -0
- fontTools/designspaceLib/__main__.py +6 -0
- fontTools/designspaceLib/split.py +475 -0
- fontTools/designspaceLib/statNames.py +260 -0
- fontTools/designspaceLib/types.py +147 -0
- fontTools/encodings/MacRoman.py +258 -0
- fontTools/encodings/StandardEncoding.py +258 -0
- fontTools/encodings/__init__.py +1 -0
- fontTools/encodings/codecs.py +135 -0
- fontTools/feaLib/__init__.py +4 -0
- fontTools/feaLib/__main__.py +78 -0
- fontTools/feaLib/ast.py +2143 -0
- fontTools/feaLib/builder.py +1814 -0
- fontTools/feaLib/error.py +22 -0
- fontTools/feaLib/lexer.c +17029 -0
- fontTools/feaLib/lexer.cp311-win32.pyd +0 -0
- fontTools/feaLib/lexer.py +287 -0
- fontTools/feaLib/location.py +12 -0
- fontTools/feaLib/lookupDebugInfo.py +12 -0
- fontTools/feaLib/parser.py +2394 -0
- fontTools/feaLib/variableScalar.py +118 -0
- fontTools/fontBuilder.py +1014 -0
- fontTools/help.py +36 -0
- fontTools/merge/__init__.py +248 -0
- fontTools/merge/__main__.py +6 -0
- fontTools/merge/base.py +81 -0
- fontTools/merge/cmap.py +173 -0
- fontTools/merge/layout.py +526 -0
- fontTools/merge/options.py +85 -0
- fontTools/merge/tables.py +352 -0
- fontTools/merge/unicode.py +78 -0
- fontTools/merge/util.py +143 -0
- fontTools/misc/__init__.py +1 -0
- fontTools/misc/arrayTools.py +424 -0
- fontTools/misc/bezierTools.c +39731 -0
- fontTools/misc/bezierTools.cp311-win32.pyd +0 -0
- fontTools/misc/bezierTools.py +1500 -0
- fontTools/misc/classifyTools.py +170 -0
- fontTools/misc/cliTools.py +53 -0
- fontTools/misc/configTools.py +349 -0
- fontTools/misc/cython.py +27 -0
- fontTools/misc/dictTools.py +83 -0
- fontTools/misc/eexec.py +119 -0
- fontTools/misc/encodingTools.py +72 -0
- fontTools/misc/enumTools.py +23 -0
- fontTools/misc/etree.py +456 -0
- fontTools/misc/filenames.py +245 -0
- fontTools/misc/filesystem/__init__.py +68 -0
- fontTools/misc/filesystem/_base.py +134 -0
- fontTools/misc/filesystem/_copy.py +45 -0
- fontTools/misc/filesystem/_errors.py +54 -0
- fontTools/misc/filesystem/_info.py +75 -0
- fontTools/misc/filesystem/_osfs.py +164 -0
- fontTools/misc/filesystem/_path.py +67 -0
- fontTools/misc/filesystem/_subfs.py +92 -0
- fontTools/misc/filesystem/_tempfs.py +34 -0
- fontTools/misc/filesystem/_tools.py +34 -0
- fontTools/misc/filesystem/_walk.py +55 -0
- fontTools/misc/filesystem/_zipfs.py +204 -0
- fontTools/misc/fixedTools.py +253 -0
- fontTools/misc/intTools.py +25 -0
- fontTools/misc/iterTools.py +12 -0
- fontTools/misc/lazyTools.py +42 -0
- fontTools/misc/loggingTools.py +543 -0
- fontTools/misc/macCreatorType.py +56 -0
- fontTools/misc/macRes.py +261 -0
- fontTools/misc/plistlib/__init__.py +681 -0
- fontTools/misc/plistlib/py.typed +0 -0
- fontTools/misc/psCharStrings.py +1511 -0
- fontTools/misc/psLib.py +398 -0
- fontTools/misc/psOperators.py +572 -0
- fontTools/misc/py23.py +96 -0
- fontTools/misc/roundTools.py +110 -0
- fontTools/misc/sstruct.py +227 -0
- fontTools/misc/symfont.py +242 -0
- fontTools/misc/testTools.py +233 -0
- fontTools/misc/textTools.py +156 -0
- fontTools/misc/timeTools.py +88 -0
- fontTools/misc/transform.py +516 -0
- fontTools/misc/treeTools.py +45 -0
- fontTools/misc/vector.py +147 -0
- fontTools/misc/visitor.py +158 -0
- fontTools/misc/xmlReader.py +188 -0
- fontTools/misc/xmlWriter.py +231 -0
- fontTools/mtiLib/__init__.py +1400 -0
- fontTools/mtiLib/__main__.py +5 -0
- fontTools/otlLib/__init__.py +1 -0
- fontTools/otlLib/builder.py +3465 -0
- fontTools/otlLib/error.py +11 -0
- fontTools/otlLib/maxContextCalc.py +96 -0
- fontTools/otlLib/optimize/__init__.py +53 -0
- fontTools/otlLib/optimize/__main__.py +6 -0
- fontTools/otlLib/optimize/gpos.py +439 -0
- fontTools/pens/__init__.py +1 -0
- fontTools/pens/areaPen.py +52 -0
- fontTools/pens/basePen.py +475 -0
- fontTools/pens/boundsPen.py +98 -0
- fontTools/pens/cairoPen.py +26 -0
- fontTools/pens/cocoaPen.py +26 -0
- fontTools/pens/cu2quPen.py +325 -0
- fontTools/pens/explicitClosingLinePen.py +101 -0
- fontTools/pens/filterPen.py +433 -0
- fontTools/pens/freetypePen.py +462 -0
- fontTools/pens/hashPointPen.py +89 -0
- fontTools/pens/momentsPen.c +13378 -0
- fontTools/pens/momentsPen.cp311-win32.pyd +0 -0
- fontTools/pens/momentsPen.py +879 -0
- fontTools/pens/perimeterPen.py +69 -0
- fontTools/pens/pointInsidePen.py +192 -0
- fontTools/pens/pointPen.py +643 -0
- fontTools/pens/qtPen.py +29 -0
- fontTools/pens/qu2cuPen.py +105 -0
- fontTools/pens/quartzPen.py +43 -0
- fontTools/pens/recordingPen.py +335 -0
- fontTools/pens/reportLabPen.py +79 -0
- fontTools/pens/reverseContourPen.py +96 -0
- fontTools/pens/roundingPen.py +130 -0
- fontTools/pens/statisticsPen.py +312 -0
- fontTools/pens/svgPathPen.py +310 -0
- fontTools/pens/t2CharStringPen.py +88 -0
- fontTools/pens/teePen.py +55 -0
- fontTools/pens/transformPen.py +115 -0
- fontTools/pens/ttGlyphPen.py +335 -0
- fontTools/pens/wxPen.py +29 -0
- fontTools/qu2cu/__init__.py +15 -0
- fontTools/qu2cu/__main__.py +7 -0
- fontTools/qu2cu/benchmark.py +56 -0
- fontTools/qu2cu/cli.py +125 -0
- fontTools/qu2cu/qu2cu.c +16682 -0
- fontTools/qu2cu/qu2cu.cp311-win32.pyd +0 -0
- fontTools/qu2cu/qu2cu.py +405 -0
- fontTools/subset/__init__.py +4096 -0
- fontTools/subset/__main__.py +6 -0
- fontTools/subset/cff.py +184 -0
- fontTools/subset/svg.py +253 -0
- fontTools/subset/util.py +25 -0
- fontTools/svgLib/__init__.py +3 -0
- fontTools/svgLib/path/__init__.py +65 -0
- fontTools/svgLib/path/arc.py +154 -0
- fontTools/svgLib/path/parser.py +322 -0
- fontTools/svgLib/path/shapes.py +183 -0
- fontTools/t1Lib/__init__.py +648 -0
- fontTools/tfmLib.py +460 -0
- fontTools/ttLib/__init__.py +30 -0
- fontTools/ttLib/__main__.py +148 -0
- fontTools/ttLib/macUtils.py +54 -0
- fontTools/ttLib/removeOverlaps.py +395 -0
- fontTools/ttLib/reorderGlyphs.py +285 -0
- fontTools/ttLib/scaleUpem.py +436 -0
- fontTools/ttLib/sfnt.py +661 -0
- fontTools/ttLib/standardGlyphOrder.py +271 -0
- fontTools/ttLib/tables/B_A_S_E_.py +14 -0
- fontTools/ttLib/tables/BitmapGlyphMetrics.py +64 -0
- fontTools/ttLib/tables/C_B_D_T_.py +113 -0
- fontTools/ttLib/tables/C_B_L_C_.py +19 -0
- fontTools/ttLib/tables/C_F_F_.py +61 -0
- fontTools/ttLib/tables/C_F_F__2.py +26 -0
- fontTools/ttLib/tables/C_O_L_R_.py +165 -0
- fontTools/ttLib/tables/C_P_A_L_.py +305 -0
- fontTools/ttLib/tables/D_S_I_G_.py +158 -0
- fontTools/ttLib/tables/D__e_b_g.py +35 -0
- fontTools/ttLib/tables/DefaultTable.py +49 -0
- fontTools/ttLib/tables/E_B_D_T_.py +835 -0
- fontTools/ttLib/tables/E_B_L_C_.py +718 -0
- fontTools/ttLib/tables/F_F_T_M_.py +52 -0
- fontTools/ttLib/tables/F__e_a_t.py +149 -0
- fontTools/ttLib/tables/G_D_E_F_.py +13 -0
- fontTools/ttLib/tables/G_M_A_P_.py +148 -0
- fontTools/ttLib/tables/G_P_K_G_.py +133 -0
- fontTools/ttLib/tables/G_P_O_S_.py +14 -0
- fontTools/ttLib/tables/G_S_U_B_.py +13 -0
- fontTools/ttLib/tables/G_V_A_R_.py +5 -0
- fontTools/ttLib/tables/G__l_a_t.py +235 -0
- fontTools/ttLib/tables/G__l_o_c.py +85 -0
- fontTools/ttLib/tables/H_V_A_R_.py +13 -0
- fontTools/ttLib/tables/J_S_T_F_.py +13 -0
- fontTools/ttLib/tables/L_T_S_H_.py +58 -0
- fontTools/ttLib/tables/M_A_T_H_.py +13 -0
- fontTools/ttLib/tables/M_E_T_A_.py +352 -0
- fontTools/ttLib/tables/M_V_A_R_.py +13 -0
- fontTools/ttLib/tables/O_S_2f_2.py +752 -0
- fontTools/ttLib/tables/S_I_N_G_.py +99 -0
- fontTools/ttLib/tables/S_T_A_T_.py +15 -0
- fontTools/ttLib/tables/S_V_G_.py +223 -0
- fontTools/ttLib/tables/S__i_l_f.py +1040 -0
- fontTools/ttLib/tables/S__i_l_l.py +92 -0
- fontTools/ttLib/tables/T_S_I_B_.py +13 -0
- fontTools/ttLib/tables/T_S_I_C_.py +14 -0
- fontTools/ttLib/tables/T_S_I_D_.py +13 -0
- fontTools/ttLib/tables/T_S_I_J_.py +13 -0
- fontTools/ttLib/tables/T_S_I_P_.py +13 -0
- fontTools/ttLib/tables/T_S_I_S_.py +13 -0
- fontTools/ttLib/tables/T_S_I_V_.py +26 -0
- fontTools/ttLib/tables/T_S_I__0.py +70 -0
- fontTools/ttLib/tables/T_S_I__1.py +163 -0
- fontTools/ttLib/tables/T_S_I__2.py +17 -0
- fontTools/ttLib/tables/T_S_I__3.py +22 -0
- fontTools/ttLib/tables/T_S_I__5.py +60 -0
- fontTools/ttLib/tables/T_T_F_A_.py +14 -0
- fontTools/ttLib/tables/TupleVariation.py +884 -0
- fontTools/ttLib/tables/V_A_R_C_.py +12 -0
- fontTools/ttLib/tables/V_D_M_X_.py +249 -0
- fontTools/ttLib/tables/V_O_R_G_.py +165 -0
- fontTools/ttLib/tables/V_V_A_R_.py +13 -0
- fontTools/ttLib/tables/__init__.py +98 -0
- fontTools/ttLib/tables/_a_n_k_r.py +15 -0
- fontTools/ttLib/tables/_a_v_a_r.py +193 -0
- fontTools/ttLib/tables/_b_s_l_n.py +15 -0
- fontTools/ttLib/tables/_c_i_d_g.py +24 -0
- fontTools/ttLib/tables/_c_m_a_p.py +1591 -0
- fontTools/ttLib/tables/_c_v_a_r.py +94 -0
- fontTools/ttLib/tables/_c_v_t.py +56 -0
- fontTools/ttLib/tables/_f_e_a_t.py +15 -0
- fontTools/ttLib/tables/_f_p_g_m.py +62 -0
- fontTools/ttLib/tables/_f_v_a_r.py +261 -0
- fontTools/ttLib/tables/_g_a_s_p.py +63 -0
- fontTools/ttLib/tables/_g_c_i_d.py +13 -0
- fontTools/ttLib/tables/_g_l_y_f.py +2311 -0
- fontTools/ttLib/tables/_g_v_a_r.py +340 -0
- fontTools/ttLib/tables/_h_d_m_x.py +127 -0
- fontTools/ttLib/tables/_h_e_a_d.py +130 -0
- fontTools/ttLib/tables/_h_h_e_a.py +147 -0
- fontTools/ttLib/tables/_h_m_t_x.py +164 -0
- fontTools/ttLib/tables/_k_e_r_n.py +289 -0
- fontTools/ttLib/tables/_l_c_a_r.py +13 -0
- fontTools/ttLib/tables/_l_o_c_a.py +70 -0
- fontTools/ttLib/tables/_l_t_a_g.py +72 -0
- fontTools/ttLib/tables/_m_a_x_p.py +147 -0
- fontTools/ttLib/tables/_m_e_t_a.py +112 -0
- fontTools/ttLib/tables/_m_o_r_t.py +14 -0
- fontTools/ttLib/tables/_m_o_r_x.py +15 -0
- fontTools/ttLib/tables/_n_a_m_e.py +1242 -0
- fontTools/ttLib/tables/_o_p_b_d.py +14 -0
- fontTools/ttLib/tables/_p_o_s_t.py +319 -0
- fontTools/ttLib/tables/_p_r_e_p.py +16 -0
- fontTools/ttLib/tables/_p_r_o_p.py +12 -0
- fontTools/ttLib/tables/_s_b_i_x.py +129 -0
- fontTools/ttLib/tables/_t_r_a_k.py +332 -0
- fontTools/ttLib/tables/_v_h_e_a.py +139 -0
- fontTools/ttLib/tables/_v_m_t_x.py +19 -0
- fontTools/ttLib/tables/asciiTable.py +20 -0
- fontTools/ttLib/tables/grUtils.py +92 -0
- fontTools/ttLib/tables/otBase.py +1458 -0
- fontTools/ttLib/tables/otConverters.py +2068 -0
- fontTools/ttLib/tables/otData.py +6400 -0
- fontTools/ttLib/tables/otTables.py +2703 -0
- fontTools/ttLib/tables/otTraverse.py +163 -0
- fontTools/ttLib/tables/sbixGlyph.py +149 -0
- fontTools/ttLib/tables/sbixStrike.py +177 -0
- fontTools/ttLib/tables/table_API_readme.txt +91 -0
- fontTools/ttLib/tables/ttProgram.py +594 -0
- fontTools/ttLib/ttCollection.py +125 -0
- fontTools/ttLib/ttFont.py +1148 -0
- fontTools/ttLib/ttGlyphSet.py +490 -0
- fontTools/ttLib/ttVisitor.py +32 -0
- fontTools/ttLib/woff2.py +1680 -0
- fontTools/ttx.py +479 -0
- fontTools/ufoLib/__init__.py +2575 -0
- fontTools/ufoLib/converters.py +407 -0
- fontTools/ufoLib/errors.py +30 -0
- fontTools/ufoLib/etree.py +6 -0
- fontTools/ufoLib/filenames.py +356 -0
- fontTools/ufoLib/glifLib.py +2120 -0
- fontTools/ufoLib/kerning.py +141 -0
- fontTools/ufoLib/plistlib.py +47 -0
- fontTools/ufoLib/pointPen.py +6 -0
- fontTools/ufoLib/utils.py +107 -0
- fontTools/ufoLib/validators.py +1208 -0
- fontTools/unicode.py +50 -0
- fontTools/unicodedata/Blocks.py +817 -0
- fontTools/unicodedata/Mirrored.py +446 -0
- fontTools/unicodedata/OTTags.py +50 -0
- fontTools/unicodedata/ScriptExtensions.py +832 -0
- fontTools/unicodedata/Scripts.py +3639 -0
- fontTools/unicodedata/__init__.py +306 -0
- fontTools/varLib/__init__.py +1600 -0
- fontTools/varLib/__main__.py +6 -0
- fontTools/varLib/avar/__init__.py +0 -0
- fontTools/varLib/avar/__main__.py +72 -0
- fontTools/varLib/avar/build.py +79 -0
- fontTools/varLib/avar/map.py +108 -0
- fontTools/varLib/avar/plan.py +1004 -0
- fontTools/varLib/avar/unbuild.py +271 -0
- fontTools/varLib/avarPlanner.py +8 -0
- fontTools/varLib/builder.py +215 -0
- fontTools/varLib/cff.py +631 -0
- fontTools/varLib/errors.py +219 -0
- fontTools/varLib/featureVars.py +703 -0
- fontTools/varLib/hvar.py +113 -0
- fontTools/varLib/instancer/__init__.py +2052 -0
- fontTools/varLib/instancer/__main__.py +5 -0
- fontTools/varLib/instancer/featureVars.py +190 -0
- fontTools/varLib/instancer/names.py +388 -0
- fontTools/varLib/instancer/solver.py +309 -0
- fontTools/varLib/interpolatable.py +1209 -0
- fontTools/varLib/interpolatableHelpers.py +399 -0
- fontTools/varLib/interpolatablePlot.py +1269 -0
- fontTools/varLib/interpolatableTestContourOrder.py +82 -0
- fontTools/varLib/interpolatableTestStartingPoint.py +107 -0
- fontTools/varLib/interpolate_layout.py +124 -0
- fontTools/varLib/iup.c +19815 -0
- fontTools/varLib/iup.cp311-win32.pyd +0 -0
- fontTools/varLib/iup.py +490 -0
- fontTools/varLib/merger.py +1717 -0
- fontTools/varLib/models.py +642 -0
- fontTools/varLib/multiVarStore.py +253 -0
- fontTools/varLib/mutator.py +529 -0
- fontTools/varLib/mvar.py +40 -0
- fontTools/varLib/plot.py +238 -0
- fontTools/varLib/stat.py +149 -0
- fontTools/varLib/varStore.py +739 -0
- fontTools/voltLib/__init__.py +5 -0
- fontTools/voltLib/__main__.py +206 -0
- fontTools/voltLib/ast.py +452 -0
- fontTools/voltLib/error.py +12 -0
- fontTools/voltLib/lexer.py +99 -0
- fontTools/voltLib/parser.py +664 -0
- fontTools/voltLib/voltToFea.py +911 -0
- fonttools-4.60.2.data/data/share/man/man1/ttx.1 +225 -0
- fonttools-4.60.2.dist-info/METADATA +2250 -0
- fonttools-4.60.2.dist-info/RECORD +353 -0
- fonttools-4.60.2.dist-info/WHEEL +5 -0
- fonttools-4.60.2.dist-info/entry_points.txt +5 -0
- fonttools-4.60.2.dist-info/licenses/LICENSE +21 -0
- fonttools-4.60.2.dist-info/licenses/LICENSE.external +388 -0
- 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
|
fontTools/misc/vector.py
ADDED
|
@@ -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)
|