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