fonttools 4.58.3__cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.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.
Potentially problematic release.
This version of fonttools might be problematic. Click here for more details.
- fontTools/__init__.py +8 -0
- fontTools/__main__.py +35 -0
- fontTools/afmLib.py +439 -0
- fontTools/agl.py +5233 -0
- fontTools/cffLib/CFF2ToCFF.py +203 -0
- fontTools/cffLib/CFFToCFF2.py +305 -0
- fontTools/cffLib/__init__.py +3694 -0
- fontTools/cffLib/specializer.py +927 -0
- fontTools/cffLib/transforms.py +490 -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 +15545 -0
- fontTools/cu2qu/cu2qu.cpython-310-x86_64-linux-gnu.so +0 -0
- fontTools/cu2qu/cu2qu.py +531 -0
- fontTools/cu2qu/errors.py +77 -0
- fontTools/cu2qu/ufo.py +349 -0
- fontTools/designspaceLib/__init__.py +3338 -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 +2142 -0
- fontTools/feaLib/builder.py +1796 -0
- fontTools/feaLib/error.py +22 -0
- fontTools/feaLib/lexer.c +17336 -0
- fontTools/feaLib/lexer.cpython-310-x86_64-linux-gnu.so +0 -0
- fontTools/feaLib/lexer.py +287 -0
- fontTools/feaLib/location.py +12 -0
- fontTools/feaLib/lookupDebugInfo.py +12 -0
- fontTools/feaLib/parser.py +2379 -0
- fontTools/feaLib/variableScalar.py +113 -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 +40136 -0
- fontTools/misc/bezierTools.cpython-310-x86_64-linux-gnu.so +0 -0
- fontTools/misc/bezierTools.py +1497 -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/etree.py +456 -0
- fontTools/misc/filenames.py +245 -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 +1496 -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 +231 -0
- fontTools/misc/symfont.py +242 -0
- fontTools/misc/testTools.py +233 -0
- fontTools/misc/textTools.py +154 -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 +142 -0
- fontTools/misc/xmlReader.py +188 -0
- fontTools/misc/xmlWriter.py +204 -0
- fontTools/mtiLib/__init__.py +1400 -0
- fontTools/mtiLib/__main__.py +5 -0
- fontTools/otlLib/__init__.py +1 -0
- fontTools/otlLib/builder.py +3435 -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 +241 -0
- fontTools/pens/freetypePen.py +462 -0
- fontTools/pens/hashPointPen.py +89 -0
- fontTools/pens/momentsPen.c +13459 -0
- fontTools/pens/momentsPen.cpython-310-x86_64-linux-gnu.so +0 -0
- fontTools/pens/momentsPen.py +879 -0
- fontTools/pens/perimeterPen.py +69 -0
- fontTools/pens/pointInsidePen.py +192 -0
- fontTools/pens/pointPen.py +609 -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 +16738 -0
- fontTools/qu2cu/qu2cu.cpython-310-x86_64-linux-gnu.so +0 -0
- fontTools/qu2cu/qu2cu.py +405 -0
- fontTools/subset/__init__.py +3929 -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 +393 -0
- fontTools/ttLib/reorderGlyphs.py +285 -0
- fontTools/ttLib/scaleUpem.py +436 -0
- fontTools/ttLib/sfnt.py +662 -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 +166 -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 +191 -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 +57 -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 +2312 -0
- fontTools/ttLib/tables/_g_v_a_r.py +337 -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 +160 -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 +1237 -0
- fontTools/ttLib/tables/_o_p_b_d.py +14 -0
- fontTools/ttLib/tables/_p_o_s_t.py +320 -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 +1466 -0
- fontTools/ttLib/tables/otConverters.py +2068 -0
- fontTools/ttLib/tables/otData.py +6400 -0
- fontTools/ttLib/tables/otTables.py +2708 -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 +1157 -0
- fontTools/ttLib/ttGlyphSet.py +490 -0
- fontTools/ttLib/ttVisitor.py +32 -0
- fontTools/ttLib/woff2.py +1683 -0
- fontTools/ttx.py +479 -0
- fontTools/ufoLib/__init__.py +2477 -0
- fontTools/ufoLib/converters.py +398 -0
- fontTools/ufoLib/errors.py +30 -0
- fontTools/ufoLib/etree.py +6 -0
- fontTools/ufoLib/filenames.py +346 -0
- fontTools/ufoLib/glifLib.py +2029 -0
- fontTools/ufoLib/kerning.py +121 -0
- fontTools/ufoLib/plistlib.py +47 -0
- fontTools/ufoLib/pointPen.py +6 -0
- fontTools/ufoLib/utils.py +79 -0
- fontTools/ufoLib/validators.py +1186 -0
- fontTools/unicode.py +50 -0
- fontTools/unicodedata/Blocks.py +801 -0
- fontTools/unicodedata/Mirrored.py +446 -0
- fontTools/unicodedata/OTTags.py +50 -0
- fontTools/unicodedata/ScriptExtensions.py +826 -0
- fontTools/unicodedata/Scripts.py +3617 -0
- fontTools/unicodedata/__init__.py +302 -0
- fontTools/varLib/__init__.py +1517 -0
- fontTools/varLib/__main__.py +6 -0
- fontTools/varLib/avar.py +260 -0
- fontTools/varLib/avarPlanner.py +1004 -0
- fontTools/varLib/builder.py +215 -0
- fontTools/varLib/cff.py +631 -0
- fontTools/varLib/errors.py +219 -0
- fontTools/varLib/featureVars.py +695 -0
- fontTools/varLib/hvar.py +113 -0
- fontTools/varLib/instancer/__init__.py +1946 -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 +396 -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 +19830 -0
- fontTools/varLib/iup.cpython-310-x86_64-linux-gnu.so +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 +518 -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.58.3.data/data/share/man/man1/ttx.1 +225 -0
- fonttools-4.58.3.dist-info/METADATA +2133 -0
- fonttools-4.58.3.dist-info/RECORD +334 -0
- fonttools-4.58.3.dist-info/WHEEL +6 -0
- fonttools-4.58.3.dist-info/entry_points.txt +5 -0
- fonttools-4.58.3.dist-info/licenses/LICENSE +21 -0
- fonttools-4.58.3.dist-info/licenses/LICENSE.external +359 -0
- fonttools-4.58.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module implements the algorithm for converting between a "user name" -
|
|
3
|
+
something that a user can choose arbitrarily inside a font editor - and a file
|
|
4
|
+
name suitable for use in a wide range of operating systems and filesystems.
|
|
5
|
+
|
|
6
|
+
The `UFO 3 specification <http://unifiedfontobject.org/versions/ufo3/conventions/>`_
|
|
7
|
+
provides an example of an algorithm for such conversion, which avoids illegal
|
|
8
|
+
characters, reserved file names, ambiguity between upper- and lower-case
|
|
9
|
+
characters, and clashes with existing files.
|
|
10
|
+
|
|
11
|
+
This code was originally copied from
|
|
12
|
+
`ufoLib <https://github.com/unified-font-object/ufoLib/blob/8747da7/Lib/ufoLib/filenames.py>`_
|
|
13
|
+
by Tal Leming and is copyright (c) 2005-2016, The RoboFab Developers:
|
|
14
|
+
|
|
15
|
+
- Erik van Blokland
|
|
16
|
+
- Tal Leming
|
|
17
|
+
- Just van Rossum
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
illegalCharacters = r"\" * + / : < > ? [ \ ] | \0".split(" ")
|
|
21
|
+
illegalCharacters += [chr(i) for i in range(1, 32)]
|
|
22
|
+
illegalCharacters += [chr(0x7F)]
|
|
23
|
+
reservedFileNames = "CON PRN AUX CLOCK$ NUL A:-Z: COM1".lower().split(" ")
|
|
24
|
+
reservedFileNames += "LPT1 LPT2 LPT3 COM2 COM3 COM4".lower().split(" ")
|
|
25
|
+
maxFileNameLength = 255
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class NameTranslationError(Exception):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def userNameToFileName(userName, existing=[], prefix="", suffix=""):
|
|
33
|
+
"""Converts from a user name to a file name.
|
|
34
|
+
|
|
35
|
+
Takes care to avoid illegal characters, reserved file names, ambiguity between
|
|
36
|
+
upper- and lower-case characters, and clashes with existing files.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
userName (str): The input file name.
|
|
40
|
+
existing: A case-insensitive list of all existing file names.
|
|
41
|
+
prefix: Prefix to be prepended to the file name.
|
|
42
|
+
suffix: Suffix to be appended to the file name.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
A suitable filename.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
NameTranslationError: If no suitable name could be generated.
|
|
49
|
+
|
|
50
|
+
Examples::
|
|
51
|
+
|
|
52
|
+
>>> userNameToFileName("a") == "a"
|
|
53
|
+
True
|
|
54
|
+
>>> userNameToFileName("A") == "A_"
|
|
55
|
+
True
|
|
56
|
+
>>> userNameToFileName("AE") == "A_E_"
|
|
57
|
+
True
|
|
58
|
+
>>> userNameToFileName("Ae") == "A_e"
|
|
59
|
+
True
|
|
60
|
+
>>> userNameToFileName("ae") == "ae"
|
|
61
|
+
True
|
|
62
|
+
>>> userNameToFileName("aE") == "aE_"
|
|
63
|
+
True
|
|
64
|
+
>>> userNameToFileName("a.alt") == "a.alt"
|
|
65
|
+
True
|
|
66
|
+
>>> userNameToFileName("A.alt") == "A_.alt"
|
|
67
|
+
True
|
|
68
|
+
>>> userNameToFileName("A.Alt") == "A_.A_lt"
|
|
69
|
+
True
|
|
70
|
+
>>> userNameToFileName("A.aLt") == "A_.aL_t"
|
|
71
|
+
True
|
|
72
|
+
>>> userNameToFileName(u"A.alT") == "A_.alT_"
|
|
73
|
+
True
|
|
74
|
+
>>> userNameToFileName("T_H") == "T__H_"
|
|
75
|
+
True
|
|
76
|
+
>>> userNameToFileName("T_h") == "T__h"
|
|
77
|
+
True
|
|
78
|
+
>>> userNameToFileName("t_h") == "t_h"
|
|
79
|
+
True
|
|
80
|
+
>>> userNameToFileName("F_F_I") == "F__F__I_"
|
|
81
|
+
True
|
|
82
|
+
>>> userNameToFileName("f_f_i") == "f_f_i"
|
|
83
|
+
True
|
|
84
|
+
>>> userNameToFileName("Aacute_V.swash") == "A_acute_V_.swash"
|
|
85
|
+
True
|
|
86
|
+
>>> userNameToFileName(".notdef") == "_notdef"
|
|
87
|
+
True
|
|
88
|
+
>>> userNameToFileName("con") == "_con"
|
|
89
|
+
True
|
|
90
|
+
>>> userNameToFileName("CON") == "C_O_N_"
|
|
91
|
+
True
|
|
92
|
+
>>> userNameToFileName("con.alt") == "_con.alt"
|
|
93
|
+
True
|
|
94
|
+
>>> userNameToFileName("alt.con") == "alt._con"
|
|
95
|
+
True
|
|
96
|
+
"""
|
|
97
|
+
# the incoming name must be a str
|
|
98
|
+
if not isinstance(userName, str):
|
|
99
|
+
raise ValueError("The value for userName must be a string.")
|
|
100
|
+
# establish the prefix and suffix lengths
|
|
101
|
+
prefixLength = len(prefix)
|
|
102
|
+
suffixLength = len(suffix)
|
|
103
|
+
# replace an initial period with an _
|
|
104
|
+
# if no prefix is to be added
|
|
105
|
+
if not prefix and userName[0] == ".":
|
|
106
|
+
userName = "_" + userName[1:]
|
|
107
|
+
# filter the user name
|
|
108
|
+
filteredUserName = []
|
|
109
|
+
for character in userName:
|
|
110
|
+
# replace illegal characters with _
|
|
111
|
+
if character in illegalCharacters:
|
|
112
|
+
character = "_"
|
|
113
|
+
# add _ to all non-lower characters
|
|
114
|
+
elif character != character.lower():
|
|
115
|
+
character += "_"
|
|
116
|
+
filteredUserName.append(character)
|
|
117
|
+
userName = "".join(filteredUserName)
|
|
118
|
+
# clip to 255
|
|
119
|
+
sliceLength = maxFileNameLength - prefixLength - suffixLength
|
|
120
|
+
userName = userName[:sliceLength]
|
|
121
|
+
# test for illegal files names
|
|
122
|
+
parts = []
|
|
123
|
+
for part in userName.split("."):
|
|
124
|
+
if part.lower() in reservedFileNames:
|
|
125
|
+
part = "_" + part
|
|
126
|
+
parts.append(part)
|
|
127
|
+
userName = ".".join(parts)
|
|
128
|
+
# test for clash
|
|
129
|
+
fullName = prefix + userName + suffix
|
|
130
|
+
if fullName.lower() in existing:
|
|
131
|
+
fullName = handleClash1(userName, existing, prefix, suffix)
|
|
132
|
+
# finished
|
|
133
|
+
return fullName
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def handleClash1(userName, existing=[], prefix="", suffix=""):
|
|
137
|
+
"""
|
|
138
|
+
existing should be a case-insensitive list
|
|
139
|
+
of all existing file names.
|
|
140
|
+
|
|
141
|
+
>>> prefix = ("0" * 5) + "."
|
|
142
|
+
>>> suffix = "." + ("0" * 10)
|
|
143
|
+
>>> existing = ["a" * 5]
|
|
144
|
+
|
|
145
|
+
>>> e = list(existing)
|
|
146
|
+
>>> handleClash1(userName="A" * 5, existing=e,
|
|
147
|
+
... prefix=prefix, suffix=suffix) == (
|
|
148
|
+
... '00000.AAAAA000000000000001.0000000000')
|
|
149
|
+
True
|
|
150
|
+
|
|
151
|
+
>>> e = list(existing)
|
|
152
|
+
>>> e.append(prefix + "aaaaa" + "1".zfill(15) + suffix)
|
|
153
|
+
>>> handleClash1(userName="A" * 5, existing=e,
|
|
154
|
+
... prefix=prefix, suffix=suffix) == (
|
|
155
|
+
... '00000.AAAAA000000000000002.0000000000')
|
|
156
|
+
True
|
|
157
|
+
|
|
158
|
+
>>> e = list(existing)
|
|
159
|
+
>>> e.append(prefix + "AAAAA" + "2".zfill(15) + suffix)
|
|
160
|
+
>>> handleClash1(userName="A" * 5, existing=e,
|
|
161
|
+
... prefix=prefix, suffix=suffix) == (
|
|
162
|
+
... '00000.AAAAA000000000000001.0000000000')
|
|
163
|
+
True
|
|
164
|
+
"""
|
|
165
|
+
# if the prefix length + user name length + suffix length + 15 is at
|
|
166
|
+
# or past the maximum length, silce 15 characters off of the user name
|
|
167
|
+
prefixLength = len(prefix)
|
|
168
|
+
suffixLength = len(suffix)
|
|
169
|
+
if prefixLength + len(userName) + suffixLength + 15 > maxFileNameLength:
|
|
170
|
+
l = prefixLength + len(userName) + suffixLength + 15
|
|
171
|
+
sliceLength = maxFileNameLength - l
|
|
172
|
+
userName = userName[:sliceLength]
|
|
173
|
+
finalName = None
|
|
174
|
+
# try to add numbers to create a unique name
|
|
175
|
+
counter = 1
|
|
176
|
+
while finalName is None:
|
|
177
|
+
name = userName + str(counter).zfill(15)
|
|
178
|
+
fullName = prefix + name + suffix
|
|
179
|
+
if fullName.lower() not in existing:
|
|
180
|
+
finalName = fullName
|
|
181
|
+
break
|
|
182
|
+
else:
|
|
183
|
+
counter += 1
|
|
184
|
+
if counter >= 999999999999999:
|
|
185
|
+
break
|
|
186
|
+
# if there is a clash, go to the next fallback
|
|
187
|
+
if finalName is None:
|
|
188
|
+
finalName = handleClash2(existing, prefix, suffix)
|
|
189
|
+
# finished
|
|
190
|
+
return finalName
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def handleClash2(existing=[], prefix="", suffix=""):
|
|
194
|
+
"""
|
|
195
|
+
existing should be a case-insensitive list
|
|
196
|
+
of all existing file names.
|
|
197
|
+
|
|
198
|
+
>>> prefix = ("0" * 5) + "."
|
|
199
|
+
>>> suffix = "." + ("0" * 10)
|
|
200
|
+
>>> existing = [prefix + str(i) + suffix for i in range(100)]
|
|
201
|
+
|
|
202
|
+
>>> e = list(existing)
|
|
203
|
+
>>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
|
|
204
|
+
... '00000.100.0000000000')
|
|
205
|
+
True
|
|
206
|
+
|
|
207
|
+
>>> e = list(existing)
|
|
208
|
+
>>> e.remove(prefix + "1" + suffix)
|
|
209
|
+
>>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
|
|
210
|
+
... '00000.1.0000000000')
|
|
211
|
+
True
|
|
212
|
+
|
|
213
|
+
>>> e = list(existing)
|
|
214
|
+
>>> e.remove(prefix + "2" + suffix)
|
|
215
|
+
>>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
|
|
216
|
+
... '00000.2.0000000000')
|
|
217
|
+
True
|
|
218
|
+
"""
|
|
219
|
+
# calculate the longest possible string
|
|
220
|
+
maxLength = maxFileNameLength - len(prefix) - len(suffix)
|
|
221
|
+
maxValue = int("9" * maxLength)
|
|
222
|
+
# try to find a number
|
|
223
|
+
finalName = None
|
|
224
|
+
counter = 1
|
|
225
|
+
while finalName is None:
|
|
226
|
+
fullName = prefix + str(counter) + suffix
|
|
227
|
+
if fullName.lower() not in existing:
|
|
228
|
+
finalName = fullName
|
|
229
|
+
break
|
|
230
|
+
else:
|
|
231
|
+
counter += 1
|
|
232
|
+
if counter >= maxValue:
|
|
233
|
+
break
|
|
234
|
+
# raise an error if nothing has been found
|
|
235
|
+
if finalName is None:
|
|
236
|
+
raise NameTranslationError("No unique name could be found.")
|
|
237
|
+
# finished
|
|
238
|
+
return finalName
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
if __name__ == "__main__":
|
|
242
|
+
import doctest
|
|
243
|
+
import sys
|
|
244
|
+
|
|
245
|
+
sys.exit(doctest.testmod().failed)
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The `OpenType specification <https://docs.microsoft.com/en-us/typography/opentype/spec/otff#data-types>`_
|
|
3
|
+
defines two fixed-point data types:
|
|
4
|
+
|
|
5
|
+
``Fixed``
|
|
6
|
+
A 32-bit signed fixed-point number with a 16 bit twos-complement
|
|
7
|
+
magnitude component and 16 fractional bits.
|
|
8
|
+
``F2DOT14``
|
|
9
|
+
A 16-bit signed fixed-point number with a 2 bit twos-complement
|
|
10
|
+
magnitude component and 14 fractional bits.
|
|
11
|
+
|
|
12
|
+
To support reading and writing data with these data types, this module provides
|
|
13
|
+
functions for converting between fixed-point, float and string representations.
|
|
14
|
+
|
|
15
|
+
.. data:: MAX_F2DOT14
|
|
16
|
+
|
|
17
|
+
The maximum value that can still fit in an F2Dot14. (1.99993896484375)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .roundTools import otRound, nearestMultipleShortestRepr
|
|
21
|
+
import logging
|
|
22
|
+
|
|
23
|
+
log = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"MAX_F2DOT14",
|
|
27
|
+
"fixedToFloat",
|
|
28
|
+
"floatToFixed",
|
|
29
|
+
"floatToFixedToFloat",
|
|
30
|
+
"floatToFixedToStr",
|
|
31
|
+
"fixedToStr",
|
|
32
|
+
"strToFixed",
|
|
33
|
+
"strToFixedToFloat",
|
|
34
|
+
"ensureVersionIsLong",
|
|
35
|
+
"versionToFixed",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
MAX_F2DOT14 = 0x7FFF / (1 << 14)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def fixedToFloat(value, precisionBits):
|
|
43
|
+
"""Converts a fixed-point number to a float given the number of
|
|
44
|
+
precision bits.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
value (int): Number in fixed-point format.
|
|
48
|
+
precisionBits (int): Number of precision bits.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Floating point value.
|
|
52
|
+
|
|
53
|
+
Examples::
|
|
54
|
+
|
|
55
|
+
>>> import math
|
|
56
|
+
>>> f = fixedToFloat(-10139, precisionBits=14)
|
|
57
|
+
>>> math.isclose(f, -0.61883544921875)
|
|
58
|
+
True
|
|
59
|
+
"""
|
|
60
|
+
return value / (1 << precisionBits)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def floatToFixed(value, precisionBits):
|
|
64
|
+
"""Converts a float to a fixed-point number given the number of
|
|
65
|
+
precision bits.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
value (float): Floating point value.
|
|
69
|
+
precisionBits (int): Number of precision bits.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
int: Fixed-point representation.
|
|
73
|
+
|
|
74
|
+
Examples::
|
|
75
|
+
|
|
76
|
+
>>> floatToFixed(-0.61883544921875, precisionBits=14)
|
|
77
|
+
-10139
|
|
78
|
+
>>> floatToFixed(-0.61884, precisionBits=14)
|
|
79
|
+
-10139
|
|
80
|
+
"""
|
|
81
|
+
return otRound(value * (1 << precisionBits))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def floatToFixedToFloat(value, precisionBits):
|
|
85
|
+
"""Converts a float to a fixed-point number and back again.
|
|
86
|
+
|
|
87
|
+
By converting the float to fixed, rounding it, and converting it back
|
|
88
|
+
to float again, this returns a floating point values which is exactly
|
|
89
|
+
representable in fixed-point format.
|
|
90
|
+
|
|
91
|
+
Note: this **is** equivalent to ``fixedToFloat(floatToFixed(value))``.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
value (float): The input floating point value.
|
|
95
|
+
precisionBits (int): Number of precision bits.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
float: The transformed and rounded value.
|
|
99
|
+
|
|
100
|
+
Examples::
|
|
101
|
+
>>> import math
|
|
102
|
+
>>> f1 = -0.61884
|
|
103
|
+
>>> f2 = floatToFixedToFloat(-0.61884, precisionBits=14)
|
|
104
|
+
>>> f1 != f2
|
|
105
|
+
True
|
|
106
|
+
>>> math.isclose(f2, -0.61883544921875)
|
|
107
|
+
True
|
|
108
|
+
"""
|
|
109
|
+
scale = 1 << precisionBits
|
|
110
|
+
return otRound(value * scale) / scale
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def fixedToStr(value, precisionBits):
|
|
114
|
+
"""Converts a fixed-point number to a string representing a decimal float.
|
|
115
|
+
|
|
116
|
+
This chooses the float that has the shortest decimal representation (the least
|
|
117
|
+
number of fractional decimal digits).
|
|
118
|
+
|
|
119
|
+
For example, to convert a fixed-point number in a 2.14 format, use
|
|
120
|
+
``precisionBits=14``::
|
|
121
|
+
|
|
122
|
+
>>> fixedToStr(-10139, precisionBits=14)
|
|
123
|
+
'-0.61884'
|
|
124
|
+
|
|
125
|
+
This is pretty slow compared to the simple division used in ``fixedToFloat``.
|
|
126
|
+
Use sporadically when you need to serialize or print the fixed-point number in
|
|
127
|
+
a human-readable form.
|
|
128
|
+
It uses nearestMultipleShortestRepr under the hood.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
value (int): The fixed-point value to convert.
|
|
132
|
+
precisionBits (int): Number of precision bits, *up to a maximum of 16*.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
str: A string representation of the value.
|
|
136
|
+
"""
|
|
137
|
+
scale = 1 << precisionBits
|
|
138
|
+
return nearestMultipleShortestRepr(value / scale, factor=1.0 / scale)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def strToFixed(string, precisionBits):
|
|
142
|
+
"""Converts a string representing a decimal float to a fixed-point number.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
string (str): A string representing a decimal float.
|
|
146
|
+
precisionBits (int): Number of precision bits, *up to a maximum of 16*.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
int: Fixed-point representation.
|
|
150
|
+
|
|
151
|
+
Examples::
|
|
152
|
+
|
|
153
|
+
>>> ## to convert a float string to a 2.14 fixed-point number:
|
|
154
|
+
>>> strToFixed('-0.61884', precisionBits=14)
|
|
155
|
+
-10139
|
|
156
|
+
"""
|
|
157
|
+
value = float(string)
|
|
158
|
+
return otRound(value * (1 << precisionBits))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def strToFixedToFloat(string, precisionBits):
|
|
162
|
+
"""Convert a string to a decimal float with fixed-point rounding.
|
|
163
|
+
|
|
164
|
+
This first converts string to a float, then turns it into a fixed-point
|
|
165
|
+
number with ``precisionBits`` fractional binary digits, then back to a
|
|
166
|
+
float again.
|
|
167
|
+
|
|
168
|
+
This is simply a shorthand for fixedToFloat(floatToFixed(float(s))).
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
string (str): A string representing a decimal float.
|
|
172
|
+
precisionBits (int): Number of precision bits.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
float: The transformed and rounded value.
|
|
176
|
+
|
|
177
|
+
Examples::
|
|
178
|
+
|
|
179
|
+
>>> import math
|
|
180
|
+
>>> s = '-0.61884'
|
|
181
|
+
>>> bits = 14
|
|
182
|
+
>>> f = strToFixedToFloat(s, precisionBits=bits)
|
|
183
|
+
>>> math.isclose(f, -0.61883544921875)
|
|
184
|
+
True
|
|
185
|
+
>>> f == fixedToFloat(floatToFixed(float(s), precisionBits=bits), precisionBits=bits)
|
|
186
|
+
True
|
|
187
|
+
"""
|
|
188
|
+
value = float(string)
|
|
189
|
+
scale = 1 << precisionBits
|
|
190
|
+
return otRound(value * scale) / scale
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def floatToFixedToStr(value, precisionBits):
|
|
194
|
+
"""Convert float to string with fixed-point rounding.
|
|
195
|
+
|
|
196
|
+
This uses the shortest decimal representation (ie. the least
|
|
197
|
+
number of fractional decimal digits) to represent the equivalent
|
|
198
|
+
fixed-point number with ``precisionBits`` fractional binary digits.
|
|
199
|
+
It uses nearestMultipleShortestRepr under the hood.
|
|
200
|
+
|
|
201
|
+
>>> floatToFixedToStr(-0.61883544921875, precisionBits=14)
|
|
202
|
+
'-0.61884'
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
value (float): The float value to convert.
|
|
206
|
+
precisionBits (int): Number of precision bits, *up to a maximum of 16*.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
str: A string representation of the value.
|
|
210
|
+
|
|
211
|
+
"""
|
|
212
|
+
scale = 1 << precisionBits
|
|
213
|
+
return nearestMultipleShortestRepr(value, factor=1.0 / scale)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def ensureVersionIsLong(value):
|
|
217
|
+
"""Ensure a table version is an unsigned long.
|
|
218
|
+
|
|
219
|
+
OpenType table version numbers are expressed as a single unsigned long
|
|
220
|
+
comprising of an unsigned short major version and unsigned short minor
|
|
221
|
+
version. This function detects if the value to be used as a version number
|
|
222
|
+
looks too small (i.e. is less than ``0x10000``), and converts it to
|
|
223
|
+
fixed-point using :func:`floatToFixed` if so.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
value (Number): a candidate table version number.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
int: A table version number, possibly corrected to fixed-point.
|
|
230
|
+
"""
|
|
231
|
+
if value < 0x10000:
|
|
232
|
+
newValue = floatToFixed(value, 16)
|
|
233
|
+
log.warning(
|
|
234
|
+
"Table version value is a float: %.4f; " "fix to use hex instead: 0x%08x",
|
|
235
|
+
value,
|
|
236
|
+
newValue,
|
|
237
|
+
)
|
|
238
|
+
value = newValue
|
|
239
|
+
return value
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def versionToFixed(value):
|
|
243
|
+
"""Ensure a table version number is fixed-point.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
value (str): a candidate table version number.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
int: A table version number, possibly corrected to fixed-point.
|
|
250
|
+
"""
|
|
251
|
+
value = int(value, 0) if value.startswith("0") else float(value)
|
|
252
|
+
value = ensureVersionIsLong(value)
|
|
253
|
+
return value
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
__all__ = ["popCount", "bit_count", "bit_indices"]
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
bit_count = int.bit_count
|
|
6
|
+
except AttributeError:
|
|
7
|
+
|
|
8
|
+
def bit_count(v):
|
|
9
|
+
return bin(v).count("1")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
"""Return number of 1 bits (population count) of the absolute value of an integer.
|
|
13
|
+
|
|
14
|
+
See https://docs.python.org/3.10/library/stdtypes.html#int.bit_count
|
|
15
|
+
"""
|
|
16
|
+
popCount = bit_count # alias
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def bit_indices(v):
|
|
20
|
+
"""Return list of indices where bits are set, 0 being the index of the least significant bit.
|
|
21
|
+
|
|
22
|
+
>>> bit_indices(0b101)
|
|
23
|
+
[0, 2]
|
|
24
|
+
"""
|
|
25
|
+
return [i for i, b in enumerate(bin(v)[::-1]) if b == "1"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from itertools import *
|
|
2
|
+
|
|
3
|
+
# Python 3.12:
|
|
4
|
+
if "batched" not in globals():
|
|
5
|
+
# https://docs.python.org/3/library/itertools.html#itertools.batched
|
|
6
|
+
def batched(iterable, n):
|
|
7
|
+
# batched('ABCDEFG', 3) --> ABC DEF G
|
|
8
|
+
if n < 1:
|
|
9
|
+
raise ValueError("n must be at least one")
|
|
10
|
+
it = iter(iterable)
|
|
11
|
+
while batch := tuple(islice(it, n)):
|
|
12
|
+
yield batch
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from collections import UserDict, UserList
|
|
2
|
+
|
|
3
|
+
__all__ = ["LazyDict", "LazyList"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LazyDict(UserDict):
|
|
7
|
+
def __init__(self, data):
|
|
8
|
+
super().__init__()
|
|
9
|
+
self.data = data
|
|
10
|
+
|
|
11
|
+
def __getitem__(self, k):
|
|
12
|
+
v = self.data[k]
|
|
13
|
+
if callable(v):
|
|
14
|
+
v = v(k)
|
|
15
|
+
self.data[k] = v
|
|
16
|
+
return v
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LazyList(UserList):
|
|
20
|
+
def __getitem__(self, k):
|
|
21
|
+
if isinstance(k, slice):
|
|
22
|
+
indices = range(*k.indices(len(self)))
|
|
23
|
+
return [self[i] for i in indices]
|
|
24
|
+
v = self.data[k]
|
|
25
|
+
if callable(v):
|
|
26
|
+
v = v(k)
|
|
27
|
+
self.data[k] = v
|
|
28
|
+
return v
|
|
29
|
+
|
|
30
|
+
def __add__(self, other):
|
|
31
|
+
if isinstance(other, LazyList):
|
|
32
|
+
other = list(other)
|
|
33
|
+
elif isinstance(other, list):
|
|
34
|
+
pass
|
|
35
|
+
else:
|
|
36
|
+
return NotImplemented
|
|
37
|
+
return list(self) + other
|
|
38
|
+
|
|
39
|
+
def __radd__(self, other):
|
|
40
|
+
if not isinstance(other, list):
|
|
41
|
+
return NotImplemented
|
|
42
|
+
return other + list(self)
|