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,170 @@
|
|
|
1
|
+
""" fontTools.misc.classifyTools.py -- tools for classifying things.
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Classifier(object):
|
|
6
|
+
"""
|
|
7
|
+
Main Classifier object, used to classify things into similar sets.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, sort=True):
|
|
11
|
+
self._things = set() # set of all things known so far
|
|
12
|
+
self._sets = [] # list of class sets produced so far
|
|
13
|
+
self._mapping = {} # map from things to their class set
|
|
14
|
+
self._dirty = False
|
|
15
|
+
self._sort = sort
|
|
16
|
+
|
|
17
|
+
def add(self, set_of_things):
|
|
18
|
+
"""
|
|
19
|
+
Add a set to the classifier. Any iterable is accepted.
|
|
20
|
+
"""
|
|
21
|
+
if not set_of_things:
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
self._dirty = True
|
|
25
|
+
|
|
26
|
+
things, sets, mapping = self._things, self._sets, self._mapping
|
|
27
|
+
|
|
28
|
+
s = set(set_of_things)
|
|
29
|
+
intersection = s.intersection(things) # existing things
|
|
30
|
+
s.difference_update(intersection) # new things
|
|
31
|
+
difference = s
|
|
32
|
+
del s
|
|
33
|
+
|
|
34
|
+
# Add new class for new things
|
|
35
|
+
if difference:
|
|
36
|
+
things.update(difference)
|
|
37
|
+
sets.append(difference)
|
|
38
|
+
for thing in difference:
|
|
39
|
+
mapping[thing] = difference
|
|
40
|
+
del difference
|
|
41
|
+
|
|
42
|
+
while intersection:
|
|
43
|
+
# Take one item and process the old class it belongs to
|
|
44
|
+
old_class = mapping[next(iter(intersection))]
|
|
45
|
+
old_class_intersection = old_class.intersection(intersection)
|
|
46
|
+
|
|
47
|
+
# Update old class to remove items from new set
|
|
48
|
+
old_class.difference_update(old_class_intersection)
|
|
49
|
+
|
|
50
|
+
# Remove processed items from todo list
|
|
51
|
+
intersection.difference_update(old_class_intersection)
|
|
52
|
+
|
|
53
|
+
# Add new class for the intersection with old class
|
|
54
|
+
sets.append(old_class_intersection)
|
|
55
|
+
for thing in old_class_intersection:
|
|
56
|
+
mapping[thing] = old_class_intersection
|
|
57
|
+
del old_class_intersection
|
|
58
|
+
|
|
59
|
+
def update(self, list_of_sets):
|
|
60
|
+
"""
|
|
61
|
+
Add a a list of sets to the classifier. Any iterable of iterables is accepted.
|
|
62
|
+
"""
|
|
63
|
+
for s in list_of_sets:
|
|
64
|
+
self.add(s)
|
|
65
|
+
|
|
66
|
+
def _process(self):
|
|
67
|
+
if not self._dirty:
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
# Do any deferred processing
|
|
71
|
+
sets = self._sets
|
|
72
|
+
self._sets = [s for s in sets if s]
|
|
73
|
+
|
|
74
|
+
if self._sort:
|
|
75
|
+
self._sets = sorted(self._sets, key=lambda s: (-len(s), sorted(s)))
|
|
76
|
+
|
|
77
|
+
self._dirty = False
|
|
78
|
+
|
|
79
|
+
# Output methods
|
|
80
|
+
|
|
81
|
+
def getThings(self):
|
|
82
|
+
"""Returns the set of all things known so far.
|
|
83
|
+
|
|
84
|
+
The return value belongs to the Classifier object and should NOT
|
|
85
|
+
be modified while the classifier is still in use.
|
|
86
|
+
"""
|
|
87
|
+
self._process()
|
|
88
|
+
return self._things
|
|
89
|
+
|
|
90
|
+
def getMapping(self):
|
|
91
|
+
"""Returns the mapping from things to their class set.
|
|
92
|
+
|
|
93
|
+
The return value belongs to the Classifier object and should NOT
|
|
94
|
+
be modified while the classifier is still in use.
|
|
95
|
+
"""
|
|
96
|
+
self._process()
|
|
97
|
+
return self._mapping
|
|
98
|
+
|
|
99
|
+
def getClasses(self):
|
|
100
|
+
"""Returns the list of class sets.
|
|
101
|
+
|
|
102
|
+
The return value belongs to the Classifier object and should NOT
|
|
103
|
+
be modified while the classifier is still in use.
|
|
104
|
+
"""
|
|
105
|
+
self._process()
|
|
106
|
+
return self._sets
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def classify(list_of_sets, sort=True):
|
|
110
|
+
"""
|
|
111
|
+
Takes a iterable of iterables (list of sets from here on; but any
|
|
112
|
+
iterable works.), and returns the smallest list of sets such that
|
|
113
|
+
each set, is either a subset, or is disjoint from, each of the input
|
|
114
|
+
sets.
|
|
115
|
+
|
|
116
|
+
In other words, this function classifies all the things present in
|
|
117
|
+
any of the input sets, into similar classes, based on which sets
|
|
118
|
+
things are a member of.
|
|
119
|
+
|
|
120
|
+
If sort=True, return class sets are sorted by decreasing size and
|
|
121
|
+
their natural sort order within each class size. Otherwise, class
|
|
122
|
+
sets are returned in the order that they were identified, which is
|
|
123
|
+
generally not significant.
|
|
124
|
+
|
|
125
|
+
>>> classify([]) == ([], {})
|
|
126
|
+
True
|
|
127
|
+
>>> classify([[]]) == ([], {})
|
|
128
|
+
True
|
|
129
|
+
>>> classify([[], []]) == ([], {})
|
|
130
|
+
True
|
|
131
|
+
>>> classify([[1]]) == ([{1}], {1: {1}})
|
|
132
|
+
True
|
|
133
|
+
>>> classify([[1,2]]) == ([{1, 2}], {1: {1, 2}, 2: {1, 2}})
|
|
134
|
+
True
|
|
135
|
+
>>> classify([[1],[2]]) == ([{1}, {2}], {1: {1}, 2: {2}})
|
|
136
|
+
True
|
|
137
|
+
>>> classify([[1,2],[2]]) == ([{1}, {2}], {1: {1}, 2: {2}})
|
|
138
|
+
True
|
|
139
|
+
>>> classify([[1,2],[2,4]]) == ([{1}, {2}, {4}], {1: {1}, 2: {2}, 4: {4}})
|
|
140
|
+
True
|
|
141
|
+
>>> classify([[1,2],[2,4,5]]) == (
|
|
142
|
+
... [{4, 5}, {1}, {2}], {1: {1}, 2: {2}, 4: {4, 5}, 5: {4, 5}})
|
|
143
|
+
True
|
|
144
|
+
>>> classify([[1,2],[2,4,5]], sort=False) == (
|
|
145
|
+
... [{1}, {4, 5}, {2}], {1: {1}, 2: {2}, 4: {4, 5}, 5: {4, 5}})
|
|
146
|
+
True
|
|
147
|
+
>>> classify([[1,2,9],[2,4,5]], sort=False) == (
|
|
148
|
+
... [{1, 9}, {4, 5}, {2}], {1: {1, 9}, 2: {2}, 4: {4, 5}, 5: {4, 5},
|
|
149
|
+
... 9: {1, 9}})
|
|
150
|
+
True
|
|
151
|
+
>>> classify([[1,2,9,15],[2,4,5]], sort=False) == (
|
|
152
|
+
... [{1, 9, 15}, {4, 5}, {2}], {1: {1, 9, 15}, 2: {2}, 4: {4, 5},
|
|
153
|
+
... 5: {4, 5}, 9: {1, 9, 15}, 15: {1, 9, 15}})
|
|
154
|
+
True
|
|
155
|
+
>>> classes, mapping = classify([[1,2,9,15],[2,4,5],[15,5]], sort=False)
|
|
156
|
+
>>> set([frozenset(c) for c in classes]) == set(
|
|
157
|
+
... [frozenset(s) for s in ({1, 9}, {4}, {2}, {5}, {15})])
|
|
158
|
+
True
|
|
159
|
+
>>> mapping == {1: {1, 9}, 2: {2}, 4: {4}, 5: {5}, 9: {1, 9}, 15: {15}}
|
|
160
|
+
True
|
|
161
|
+
"""
|
|
162
|
+
classifier = Classifier(sort=sort)
|
|
163
|
+
classifier.update(list_of_sets)
|
|
164
|
+
return classifier.getClasses(), classifier.getMapping()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
import sys, doctest
|
|
169
|
+
|
|
170
|
+
sys.exit(doctest.testmod(optionflags=doctest.ELLIPSIS).failed)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Collection of utilities for command-line interfaces and console scripts."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
numberAddedRE = re.compile(r"#\d+$")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def makeOutputFileName(
|
|
11
|
+
input, outputDir=None, extension=None, overWrite=False, suffix=""
|
|
12
|
+
):
|
|
13
|
+
"""Generates a suitable file name for writing output.
|
|
14
|
+
|
|
15
|
+
Often tools will want to take a file, do some kind of transformation to it,
|
|
16
|
+
and write it out again. This function determines an appropriate name for the
|
|
17
|
+
output file, through one or more of the following steps:
|
|
18
|
+
|
|
19
|
+
- changing the output directory
|
|
20
|
+
- appending suffix before file extension
|
|
21
|
+
- replacing the file extension
|
|
22
|
+
- suffixing the filename with a number (``#1``, ``#2``, etc.) to avoid
|
|
23
|
+
overwriting an existing file.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
input: Name of input file.
|
|
27
|
+
outputDir: Optionally, a new directory to write the file into.
|
|
28
|
+
suffix: Optionally, a string suffix is appended to file name before
|
|
29
|
+
the extension.
|
|
30
|
+
extension: Optionally, a replacement for the current file extension.
|
|
31
|
+
overWrite: Overwriting an existing file is permitted if true; if false
|
|
32
|
+
and the proposed filename exists, a new name will be generated by
|
|
33
|
+
adding an appropriate number suffix.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
str: Suitable output filename
|
|
37
|
+
"""
|
|
38
|
+
dirName, fileName = os.path.split(input)
|
|
39
|
+
fileName, ext = os.path.splitext(fileName)
|
|
40
|
+
if outputDir:
|
|
41
|
+
dirName = outputDir
|
|
42
|
+
fileName = numberAddedRE.split(fileName)[0]
|
|
43
|
+
if extension is None:
|
|
44
|
+
extension = os.path.splitext(input)[1]
|
|
45
|
+
output = os.path.join(dirName, fileName + suffix + extension)
|
|
46
|
+
n = 1
|
|
47
|
+
if not overWrite:
|
|
48
|
+
while os.path.exists(output):
|
|
49
|
+
output = os.path.join(
|
|
50
|
+
dirName, fileName + suffix + "#" + repr(n) + extension
|
|
51
|
+
)
|
|
52
|
+
n += 1
|
|
53
|
+
return output
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Code of the config system; not related to fontTools or fonts in particular.
|
|
3
|
+
|
|
4
|
+
The options that are specific to fontTools are in :mod:`fontTools.config`.
|
|
5
|
+
|
|
6
|
+
To create your own config system, you need to create an instance of
|
|
7
|
+
:class:`Options`, and a subclass of :class:`AbstractConfig` with its
|
|
8
|
+
``options`` class variable set to your instance of Options.
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import (
|
|
17
|
+
Any,
|
|
18
|
+
Callable,
|
|
19
|
+
ClassVar,
|
|
20
|
+
Dict,
|
|
21
|
+
Iterable,
|
|
22
|
+
Mapping,
|
|
23
|
+
MutableMapping,
|
|
24
|
+
Optional,
|
|
25
|
+
Set,
|
|
26
|
+
Union,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
log = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"AbstractConfig",
|
|
34
|
+
"ConfigAlreadyRegisteredError",
|
|
35
|
+
"ConfigError",
|
|
36
|
+
"ConfigUnknownOptionError",
|
|
37
|
+
"ConfigValueParsingError",
|
|
38
|
+
"ConfigValueValidationError",
|
|
39
|
+
"Option",
|
|
40
|
+
"Options",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ConfigError(Exception):
|
|
45
|
+
"""Base exception for the config module."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ConfigAlreadyRegisteredError(ConfigError):
|
|
49
|
+
"""Raised when a module tries to register a configuration option that
|
|
50
|
+
already exists.
|
|
51
|
+
|
|
52
|
+
Should not be raised too much really, only when developing new fontTools
|
|
53
|
+
modules.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, name):
|
|
57
|
+
super().__init__(f"Config option {name} is already registered.")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ConfigValueParsingError(ConfigError):
|
|
61
|
+
"""Raised when a configuration value cannot be parsed."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, name, value):
|
|
64
|
+
super().__init__(
|
|
65
|
+
f"Config option {name}: value cannot be parsed (given {repr(value)})"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ConfigValueValidationError(ConfigError):
|
|
70
|
+
"""Raised when a configuration value cannot be validated."""
|
|
71
|
+
|
|
72
|
+
def __init__(self, name, value):
|
|
73
|
+
super().__init__(
|
|
74
|
+
f"Config option {name}: value is invalid (given {repr(value)})"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ConfigUnknownOptionError(ConfigError):
|
|
79
|
+
"""Raised when a configuration option is unknown."""
|
|
80
|
+
|
|
81
|
+
def __init__(self, option_or_name):
|
|
82
|
+
name = (
|
|
83
|
+
f"'{option_or_name.name}' (id={id(option_or_name)})>"
|
|
84
|
+
if isinstance(option_or_name, Option)
|
|
85
|
+
else f"'{option_or_name}'"
|
|
86
|
+
)
|
|
87
|
+
super().__init__(f"Config option {name} is unknown")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# eq=False because Options are unique, not fungible objects
|
|
91
|
+
@dataclass(frozen=True, eq=False)
|
|
92
|
+
class Option:
|
|
93
|
+
name: str
|
|
94
|
+
"""Unique name identifying the option (e.g. package.module:MY_OPTION)."""
|
|
95
|
+
help: str
|
|
96
|
+
"""Help text for this option."""
|
|
97
|
+
default: Any
|
|
98
|
+
"""Default value for this option."""
|
|
99
|
+
parse: Callable[[str], Any]
|
|
100
|
+
"""Turn input (e.g. string) into proper type. Only when reading from file."""
|
|
101
|
+
validate: Optional[Callable[[Any], bool]] = None
|
|
102
|
+
"""Return true if the given value is an acceptable value."""
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def parse_optional_bool(v: str) -> Optional[bool]:
|
|
106
|
+
s = str(v).lower()
|
|
107
|
+
if s in {"0", "no", "false"}:
|
|
108
|
+
return False
|
|
109
|
+
if s in {"1", "yes", "true"}:
|
|
110
|
+
return True
|
|
111
|
+
if s in {"auto", "none"}:
|
|
112
|
+
return None
|
|
113
|
+
raise ValueError("invalid optional bool: {v!r}")
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def validate_optional_bool(v: Any) -> bool:
|
|
117
|
+
return v is None or isinstance(v, bool)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class Options(Mapping):
|
|
121
|
+
"""Registry of available options for a given config system.
|
|
122
|
+
|
|
123
|
+
Define new options using the :meth:`register()` method.
|
|
124
|
+
|
|
125
|
+
Access existing options using the Mapping interface.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
__options: Dict[str, Option]
|
|
129
|
+
|
|
130
|
+
def __init__(self, other: "Options" = None) -> None:
|
|
131
|
+
self.__options = {}
|
|
132
|
+
if other is not None:
|
|
133
|
+
for option in other.values():
|
|
134
|
+
self.register_option(option)
|
|
135
|
+
|
|
136
|
+
def register(
|
|
137
|
+
self,
|
|
138
|
+
name: str,
|
|
139
|
+
help: str,
|
|
140
|
+
default: Any,
|
|
141
|
+
parse: Callable[[str], Any],
|
|
142
|
+
validate: Optional[Callable[[Any], bool]] = None,
|
|
143
|
+
) -> Option:
|
|
144
|
+
"""Create and register a new option."""
|
|
145
|
+
return self.register_option(Option(name, help, default, parse, validate))
|
|
146
|
+
|
|
147
|
+
def register_option(self, option: Option) -> Option:
|
|
148
|
+
"""Register a new option."""
|
|
149
|
+
name = option.name
|
|
150
|
+
if name in self.__options:
|
|
151
|
+
raise ConfigAlreadyRegisteredError(name)
|
|
152
|
+
self.__options[name] = option
|
|
153
|
+
return option
|
|
154
|
+
|
|
155
|
+
def is_registered(self, option: Option) -> bool:
|
|
156
|
+
"""Return True if the same option object is already registered."""
|
|
157
|
+
return self.__options.get(option.name) is option
|
|
158
|
+
|
|
159
|
+
def __getitem__(self, key: str) -> Option:
|
|
160
|
+
return self.__options.__getitem__(key)
|
|
161
|
+
|
|
162
|
+
def __iter__(self) -> Iterator[str]:
|
|
163
|
+
return self.__options.__iter__()
|
|
164
|
+
|
|
165
|
+
def __len__(self) -> int:
|
|
166
|
+
return self.__options.__len__()
|
|
167
|
+
|
|
168
|
+
def __repr__(self) -> str:
|
|
169
|
+
return (
|
|
170
|
+
f"{self.__class__.__name__}({{\n"
|
|
171
|
+
+ "".join(
|
|
172
|
+
f" {k!r}: Option(default={v.default!r}, ...),\n"
|
|
173
|
+
for k, v in self.__options.items()
|
|
174
|
+
)
|
|
175
|
+
+ "})"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
_USE_GLOBAL_DEFAULT = object()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class AbstractConfig(MutableMapping):
|
|
183
|
+
"""
|
|
184
|
+
Create a set of config values, optionally pre-filled with values from
|
|
185
|
+
the given dictionary or pre-existing config object.
|
|
186
|
+
|
|
187
|
+
The class implements the MutableMapping protocol keyed by option name (`str`).
|
|
188
|
+
For convenience its methods accept either Option or str as the key parameter.
|
|
189
|
+
|
|
190
|
+
.. seealso:: :meth:`set()`
|
|
191
|
+
|
|
192
|
+
This config class is abstract because it needs its ``options`` class
|
|
193
|
+
var to be set to an instance of :class:`Options` before it can be
|
|
194
|
+
instanciated and used.
|
|
195
|
+
|
|
196
|
+
.. code:: python
|
|
197
|
+
|
|
198
|
+
class MyConfig(AbstractConfig):
|
|
199
|
+
options = Options()
|
|
200
|
+
|
|
201
|
+
MyConfig.register_option( "test:option_name", "This is an option", 0, int, lambda v: isinstance(v, int))
|
|
202
|
+
|
|
203
|
+
cfg = MyConfig({"test:option_name": 10})
|
|
204
|
+
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
options: ClassVar[Options]
|
|
208
|
+
|
|
209
|
+
@classmethod
|
|
210
|
+
def register_option(
|
|
211
|
+
cls,
|
|
212
|
+
name: str,
|
|
213
|
+
help: str,
|
|
214
|
+
default: Any,
|
|
215
|
+
parse: Callable[[str], Any],
|
|
216
|
+
validate: Optional[Callable[[Any], bool]] = None,
|
|
217
|
+
) -> Option:
|
|
218
|
+
"""Register an available option in this config system."""
|
|
219
|
+
return cls.options.register(
|
|
220
|
+
name, help=help, default=default, parse=parse, validate=validate
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
_values: Dict[str, Any]
|
|
224
|
+
|
|
225
|
+
def __init__(
|
|
226
|
+
self,
|
|
227
|
+
values: Union[AbstractConfig, Dict[Union[Option, str], Any]] = {},
|
|
228
|
+
parse_values: bool = False,
|
|
229
|
+
skip_unknown: bool = False,
|
|
230
|
+
):
|
|
231
|
+
self._values = {}
|
|
232
|
+
values_dict = values._values if isinstance(values, AbstractConfig) else values
|
|
233
|
+
for name, value in values_dict.items():
|
|
234
|
+
self.set(name, value, parse_values, skip_unknown)
|
|
235
|
+
|
|
236
|
+
def _resolve_option(self, option_or_name: Union[Option, str]) -> Option:
|
|
237
|
+
if isinstance(option_or_name, Option):
|
|
238
|
+
option = option_or_name
|
|
239
|
+
if not self.options.is_registered(option):
|
|
240
|
+
raise ConfigUnknownOptionError(option)
|
|
241
|
+
return option
|
|
242
|
+
elif isinstance(option_or_name, str):
|
|
243
|
+
name = option_or_name
|
|
244
|
+
try:
|
|
245
|
+
return self.options[name]
|
|
246
|
+
except KeyError:
|
|
247
|
+
raise ConfigUnknownOptionError(name)
|
|
248
|
+
else:
|
|
249
|
+
raise TypeError(
|
|
250
|
+
"expected Option or str, found "
|
|
251
|
+
f"{type(option_or_name).__name__}: {option_or_name!r}"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
def set(
|
|
255
|
+
self,
|
|
256
|
+
option_or_name: Union[Option, str],
|
|
257
|
+
value: Any,
|
|
258
|
+
parse_values: bool = False,
|
|
259
|
+
skip_unknown: bool = False,
|
|
260
|
+
):
|
|
261
|
+
"""Set the value of an option.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
* `option_or_name`: an `Option` object or its name (`str`).
|
|
265
|
+
* `value`: the value to be assigned to given option.
|
|
266
|
+
* `parse_values`: parse the configuration value from a string into
|
|
267
|
+
its proper type, as per its `Option` object. The default
|
|
268
|
+
behavior is to raise `ConfigValueValidationError` when the value
|
|
269
|
+
is not of the right type. Useful when reading options from a
|
|
270
|
+
file type that doesn't support as many types as Python.
|
|
271
|
+
* `skip_unknown`: skip unknown configuration options. The default
|
|
272
|
+
behaviour is to raise `ConfigUnknownOptionError`. Useful when
|
|
273
|
+
reading options from a configuration file that has extra entries
|
|
274
|
+
(e.g. for a later version of fontTools)
|
|
275
|
+
"""
|
|
276
|
+
try:
|
|
277
|
+
option = self._resolve_option(option_or_name)
|
|
278
|
+
except ConfigUnknownOptionError as e:
|
|
279
|
+
if skip_unknown:
|
|
280
|
+
log.debug(str(e))
|
|
281
|
+
return
|
|
282
|
+
raise
|
|
283
|
+
|
|
284
|
+
# Can be useful if the values come from a source that doesn't have
|
|
285
|
+
# strict typing (.ini file? Terminal input?)
|
|
286
|
+
if parse_values:
|
|
287
|
+
try:
|
|
288
|
+
value = option.parse(value)
|
|
289
|
+
except Exception as e:
|
|
290
|
+
raise ConfigValueParsingError(option.name, value) from e
|
|
291
|
+
|
|
292
|
+
if option.validate is not None and not option.validate(value):
|
|
293
|
+
raise ConfigValueValidationError(option.name, value)
|
|
294
|
+
|
|
295
|
+
self._values[option.name] = value
|
|
296
|
+
|
|
297
|
+
def get(
|
|
298
|
+
self, option_or_name: Union[Option, str], default: Any = _USE_GLOBAL_DEFAULT
|
|
299
|
+
) -> Any:
|
|
300
|
+
"""
|
|
301
|
+
Get the value of an option. The value which is returned is the first
|
|
302
|
+
provided among:
|
|
303
|
+
|
|
304
|
+
1. a user-provided value in the options's ``self._values`` dict
|
|
305
|
+
2. a caller-provided default value to this method call
|
|
306
|
+
3. the global default for the option provided in ``fontTools.config``
|
|
307
|
+
|
|
308
|
+
This is to provide the ability to migrate progressively from config
|
|
309
|
+
options passed as arguments to fontTools APIs to config options read
|
|
310
|
+
from the current TTFont, e.g.
|
|
311
|
+
|
|
312
|
+
.. code:: python
|
|
313
|
+
|
|
314
|
+
def fontToolsAPI(font, some_option):
|
|
315
|
+
value = font.cfg.get("someLib.module:SOME_OPTION", some_option)
|
|
316
|
+
# use value
|
|
317
|
+
|
|
318
|
+
That way, the function will work the same for users of the API that
|
|
319
|
+
still pass the option to the function call, but will favour the new
|
|
320
|
+
config mechanism if the given font specifies a value for that option.
|
|
321
|
+
"""
|
|
322
|
+
option = self._resolve_option(option_or_name)
|
|
323
|
+
if option.name in self._values:
|
|
324
|
+
return self._values[option.name]
|
|
325
|
+
if default is not _USE_GLOBAL_DEFAULT:
|
|
326
|
+
return default
|
|
327
|
+
return option.default
|
|
328
|
+
|
|
329
|
+
def copy(self):
|
|
330
|
+
return self.__class__(self._values)
|
|
331
|
+
|
|
332
|
+
def __getitem__(self, option_or_name: Union[Option, str]) -> Any:
|
|
333
|
+
return self.get(option_or_name)
|
|
334
|
+
|
|
335
|
+
def __setitem__(self, option_or_name: Union[Option, str], value: Any) -> None:
|
|
336
|
+
return self.set(option_or_name, value)
|
|
337
|
+
|
|
338
|
+
def __delitem__(self, option_or_name: Union[Option, str]) -> None:
|
|
339
|
+
option = self._resolve_option(option_or_name)
|
|
340
|
+
del self._values[option.name]
|
|
341
|
+
|
|
342
|
+
def __iter__(self) -> Iterable[str]:
|
|
343
|
+
return self._values.__iter__()
|
|
344
|
+
|
|
345
|
+
def __len__(self) -> int:
|
|
346
|
+
return len(self._values)
|
|
347
|
+
|
|
348
|
+
def __repr__(self) -> str:
|
|
349
|
+
return f"{self.__class__.__name__}({repr(self._values)})"
|
fontTools/misc/cython.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
""" Exports a no-op 'cython' namespace similar to
|
|
2
|
+
https://github.com/cython/cython/blob/master/Cython/Shadow.py
|
|
3
|
+
|
|
4
|
+
This allows to optionally compile @cython decorated functions
|
|
5
|
+
(when cython is available at built time), or run the same code
|
|
6
|
+
as pure-python, without runtime dependency on cython module.
|
|
7
|
+
|
|
8
|
+
We only define the symbols that we use. E.g. see fontTools.cu2qu
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from types import SimpleNamespace
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _empty_decorator(x):
|
|
15
|
+
return x
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
compiled = False
|
|
19
|
+
|
|
20
|
+
for name in ("double", "complex", "int"):
|
|
21
|
+
globals()[name] = None
|
|
22
|
+
|
|
23
|
+
for name in ("cfunc", "inline"):
|
|
24
|
+
globals()[name] = _empty_decorator
|
|
25
|
+
|
|
26
|
+
locals = lambda **_: _empty_decorator
|
|
27
|
+
returns = lambda _: _empty_decorator
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Misc dict tools."""
|
|
2
|
+
|
|
3
|
+
__all__ = ["hashdict"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# https://stackoverflow.com/questions/1151658/python-hashable-dicts
|
|
7
|
+
class hashdict(dict):
|
|
8
|
+
"""
|
|
9
|
+
hashable dict implementation, suitable for use as a key into
|
|
10
|
+
other dicts.
|
|
11
|
+
|
|
12
|
+
>>> h1 = hashdict({"apples": 1, "bananas":2})
|
|
13
|
+
>>> h2 = hashdict({"bananas": 3, "mangoes": 5})
|
|
14
|
+
>>> h1+h2
|
|
15
|
+
hashdict(apples=1, bananas=3, mangoes=5)
|
|
16
|
+
>>> d1 = {}
|
|
17
|
+
>>> d1[h1] = "salad"
|
|
18
|
+
>>> d1[h1]
|
|
19
|
+
'salad'
|
|
20
|
+
>>> d1[h2]
|
|
21
|
+
Traceback (most recent call last):
|
|
22
|
+
...
|
|
23
|
+
KeyError: hashdict(bananas=3, mangoes=5)
|
|
24
|
+
|
|
25
|
+
based on answers from
|
|
26
|
+
http://stackoverflow.com/questions/1151658/python-hashable-dicts
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __key(self):
|
|
31
|
+
return tuple(sorted(self.items()))
|
|
32
|
+
|
|
33
|
+
def __repr__(self):
|
|
34
|
+
return "{0}({1})".format(
|
|
35
|
+
self.__class__.__name__,
|
|
36
|
+
", ".join("{0}={1}".format(str(i[0]), repr(i[1])) for i in self.__key()),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def __hash__(self):
|
|
40
|
+
return hash(self.__key())
|
|
41
|
+
|
|
42
|
+
def __setitem__(self, key, value):
|
|
43
|
+
raise TypeError(
|
|
44
|
+
"{0} does not support item assignment".format(self.__class__.__name__)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def __delitem__(self, key):
|
|
48
|
+
raise TypeError(
|
|
49
|
+
"{0} does not support item assignment".format(self.__class__.__name__)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def clear(self):
|
|
53
|
+
raise TypeError(
|
|
54
|
+
"{0} does not support item assignment".format(self.__class__.__name__)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def pop(self, *args, **kwargs):
|
|
58
|
+
raise TypeError(
|
|
59
|
+
"{0} does not support item assignment".format(self.__class__.__name__)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def popitem(self, *args, **kwargs):
|
|
63
|
+
raise TypeError(
|
|
64
|
+
"{0} does not support item assignment".format(self.__class__.__name__)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def setdefault(self, *args, **kwargs):
|
|
68
|
+
raise TypeError(
|
|
69
|
+
"{0} does not support item assignment".format(self.__class__.__name__)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def update(self, *args, **kwargs):
|
|
73
|
+
raise TypeError(
|
|
74
|
+
"{0} does not support item assignment".format(self.__class__.__name__)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# update is not ok because it mutates the object
|
|
78
|
+
# __add__ is ok because it creates a new object
|
|
79
|
+
# while the new object is under construction, it's ok to mutate it
|
|
80
|
+
def __add__(self, right):
|
|
81
|
+
result = hashdict(self)
|
|
82
|
+
dict.update(result, right)
|
|
83
|
+
return result
|