fonttools 4.55.6__cp39-cp39-manylinux_2_17_x86_64.manylinux2014_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 +3659 -0
- fontTools/cffLib/specializer.py +924 -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 +75 -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 +14829 -0
- fontTools/cu2qu/cu2qu.cpython-39-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 +253 -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 +2134 -0
- fontTools/feaLib/builder.py +1747 -0
- fontTools/feaLib/error.py +22 -0
- fontTools/feaLib/lexer.c +17986 -0
- fontTools/feaLib/lexer.cpython-39-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 +2359 -0
- fontTools/feaLib/variableScalar.py +113 -0
- fontTools/fontBuilder.py +1008 -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 +141 -0
- fontTools/merge/layout.py +526 -0
- fontTools/merge/options.py +85 -0
- fontTools/merge/tables.py +341 -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 +41831 -0
- fontTools/misc/bezierTools.cpython-39-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 +479 -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 +244 -0
- fontTools/misc/testTools.py +229 -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 +1402 -0
- fontTools/mtiLib/__main__.py +5 -0
- fontTools/otlLib/__init__.py +1 -0
- fontTools/otlLib/builder.py +3221 -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 +453 -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 +13448 -0
- fontTools/pens/momentsPen.cpython-39-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 +600 -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 +68 -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 +16269 -0
- fontTools/qu2cu/qu2cu.cpython-39-x86_64-linux-gnu.so +0 -0
- fontTools/qu2cu/qu2cu.py +405 -0
- fontTools/subset/__init__.py +3838 -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 +26 -0
- fontTools/ttLib/__main__.py +109 -0
- fontTools/ttLib/macUtils.py +54 -0
- fontTools/ttLib/removeOverlaps.py +393 -0
- fontTools/ttLib/reorderGlyphs.py +284 -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 +17 -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__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 +59 -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 +49 -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 +97 -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 +1578 -0
- fontTools/ttLib/tables/_c_v_a_r.py +94 -0
- fontTools/ttLib/tables/_c_v_t.py +55 -0
- fontTools/ttLib/tables/_f_e_a_t.py +15 -0
- fontTools/ttLib/tables/_f_p_g_m.py +60 -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 +292 -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 +317 -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 +1465 -0
- fontTools/ttLib/tables/otConverters.py +2065 -0
- fontTools/ttLib/tables/otData.py +6400 -0
- fontTools/ttLib/tables/otTables.py +2700 -0
- fontTools/ttLib/tables/otTraverse.py +162 -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 +1155 -0
- fontTools/ttLib/ttGlyphSet.py +500 -0
- fontTools/ttLib/ttVisitor.py +32 -0
- fontTools/ttLib/woff2.py +1683 -0
- fontTools/ttx.py +467 -0
- fontTools/ufoLib/__init__.py +2477 -0
- fontTools/ufoLib/converters.py +334 -0
- fontTools/ufoLib/errors.py +22 -0
- fontTools/ufoLib/etree.py +6 -0
- fontTools/ufoLib/filenames.py +291 -0
- fontTools/ufoLib/glifLib.py +2022 -0
- fontTools/ufoLib/kerning.py +91 -0
- fontTools/ufoLib/plistlib.py +47 -0
- fontTools/ufoLib/pointPen.py +6 -0
- fontTools/ufoLib/utils.py +76 -0
- fontTools/ufoLib/validators.py +1186 -0
- fontTools/unicode.py +50 -0
- fontTools/unicodedata/Blocks.py +802 -0
- fontTools/unicodedata/OTTags.py +50 -0
- fontTools/unicodedata/ScriptExtensions.py +806 -0
- fontTools/unicodedata/Scripts.py +3618 -0
- fontTools/unicodedata/__init__.py +298 -0
- fontTools/varLib/__init__.py +1511 -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 +689 -0
- fontTools/varLib/instancer/__init__.py +1937 -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 +19154 -0
- fontTools/varLib/iup.cpython-39-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 +767 -0
- fontTools/voltLib/__init__.py +5 -0
- fontTools/voltLib/ast.py +448 -0
- fontTools/voltLib/error.py +12 -0
- fontTools/voltLib/lexer.py +99 -0
- fontTools/voltLib/parser.py +656 -0
- fontTools/voltLib/voltToFea.py +730 -0
- fonttools-4.55.6.data/data/share/man/man1/ttx.1 +225 -0
- fonttools-4.55.6.dist-info/LICENSE +21 -0
- fonttools-4.55.6.dist-info/METADATA +3413 -0
- fonttools-4.55.6.dist-info/RECORD +329 -0
- fonttools-4.55.6.dist-info/WHEEL +6 -0
- fonttools-4.55.6.dist-info/entry_points.txt +5 -0
- fonttools-4.55.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Conversion functions.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# adapted from the UFO spec
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def convertUFO1OrUFO2KerningToUFO3Kerning(kerning, groups, glyphSet=()):
|
|
9
|
+
# gather known kerning groups based on the prefixes
|
|
10
|
+
firstReferencedGroups, secondReferencedGroups = findKnownKerningGroups(groups)
|
|
11
|
+
# Make lists of groups referenced in kerning pairs.
|
|
12
|
+
for first, seconds in list(kerning.items()):
|
|
13
|
+
if first in groups and first not in glyphSet:
|
|
14
|
+
if not first.startswith("public.kern1."):
|
|
15
|
+
firstReferencedGroups.add(first)
|
|
16
|
+
for second in list(seconds.keys()):
|
|
17
|
+
if second in groups and second not in glyphSet:
|
|
18
|
+
if not second.startswith("public.kern2."):
|
|
19
|
+
secondReferencedGroups.add(second)
|
|
20
|
+
# Create new names for these groups.
|
|
21
|
+
firstRenamedGroups = {}
|
|
22
|
+
for first in firstReferencedGroups:
|
|
23
|
+
# Make a list of existing group names.
|
|
24
|
+
existingGroupNames = list(groups.keys()) + list(firstRenamedGroups.keys())
|
|
25
|
+
# Remove the old prefix from the name
|
|
26
|
+
newName = first.replace("@MMK_L_", "")
|
|
27
|
+
# Add the new prefix to the name.
|
|
28
|
+
newName = "public.kern1." + newName
|
|
29
|
+
# Make a unique group name.
|
|
30
|
+
newName = makeUniqueGroupName(newName, existingGroupNames)
|
|
31
|
+
# Store for use later.
|
|
32
|
+
firstRenamedGroups[first] = newName
|
|
33
|
+
secondRenamedGroups = {}
|
|
34
|
+
for second in secondReferencedGroups:
|
|
35
|
+
# Make a list of existing group names.
|
|
36
|
+
existingGroupNames = list(groups.keys()) + list(secondRenamedGroups.keys())
|
|
37
|
+
# Remove the old prefix from the name
|
|
38
|
+
newName = second.replace("@MMK_R_", "")
|
|
39
|
+
# Add the new prefix to the name.
|
|
40
|
+
newName = "public.kern2." + newName
|
|
41
|
+
# Make a unique group name.
|
|
42
|
+
newName = makeUniqueGroupName(newName, existingGroupNames)
|
|
43
|
+
# Store for use later.
|
|
44
|
+
secondRenamedGroups[second] = newName
|
|
45
|
+
# Populate the new group names into the kerning dictionary as needed.
|
|
46
|
+
newKerning = {}
|
|
47
|
+
for first, seconds in list(kerning.items()):
|
|
48
|
+
first = firstRenamedGroups.get(first, first)
|
|
49
|
+
newSeconds = {}
|
|
50
|
+
for second, value in list(seconds.items()):
|
|
51
|
+
second = secondRenamedGroups.get(second, second)
|
|
52
|
+
newSeconds[second] = value
|
|
53
|
+
newKerning[first] = newSeconds
|
|
54
|
+
# Make copies of the referenced groups and store them
|
|
55
|
+
# under the new names in the overall groups dictionary.
|
|
56
|
+
allRenamedGroups = list(firstRenamedGroups.items())
|
|
57
|
+
allRenamedGroups += list(secondRenamedGroups.items())
|
|
58
|
+
for oldName, newName in allRenamedGroups:
|
|
59
|
+
group = list(groups[oldName])
|
|
60
|
+
groups[newName] = group
|
|
61
|
+
# Return the kerning and the groups.
|
|
62
|
+
return newKerning, groups, dict(side1=firstRenamedGroups, side2=secondRenamedGroups)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def findKnownKerningGroups(groups):
|
|
66
|
+
"""
|
|
67
|
+
This will find kerning groups with known prefixes.
|
|
68
|
+
In some cases not all kerning groups will be referenced
|
|
69
|
+
by the kerning pairs. The algorithm for locating groups
|
|
70
|
+
in convertUFO1OrUFO2KerningToUFO3Kerning will miss these
|
|
71
|
+
unreferenced groups. By scanning for known prefixes
|
|
72
|
+
this function will catch all of the prefixed groups.
|
|
73
|
+
|
|
74
|
+
These are the prefixes and sides that are handled:
|
|
75
|
+
@MMK_L_ - side 1
|
|
76
|
+
@MMK_R_ - side 2
|
|
77
|
+
|
|
78
|
+
>>> testGroups = {
|
|
79
|
+
... "@MMK_L_1" : None,
|
|
80
|
+
... "@MMK_L_2" : None,
|
|
81
|
+
... "@MMK_L_3" : None,
|
|
82
|
+
... "@MMK_R_1" : None,
|
|
83
|
+
... "@MMK_R_2" : None,
|
|
84
|
+
... "@MMK_R_3" : None,
|
|
85
|
+
... "@MMK_l_1" : None,
|
|
86
|
+
... "@MMK_r_1" : None,
|
|
87
|
+
... "@MMK_X_1" : None,
|
|
88
|
+
... "foo" : None,
|
|
89
|
+
... }
|
|
90
|
+
>>> first, second = findKnownKerningGroups(testGroups)
|
|
91
|
+
>>> sorted(first) == ['@MMK_L_1', '@MMK_L_2', '@MMK_L_3']
|
|
92
|
+
True
|
|
93
|
+
>>> sorted(second) == ['@MMK_R_1', '@MMK_R_2', '@MMK_R_3']
|
|
94
|
+
True
|
|
95
|
+
"""
|
|
96
|
+
knownFirstGroupPrefixes = ["@MMK_L_"]
|
|
97
|
+
knownSecondGroupPrefixes = ["@MMK_R_"]
|
|
98
|
+
firstGroups = set()
|
|
99
|
+
secondGroups = set()
|
|
100
|
+
for groupName in list(groups.keys()):
|
|
101
|
+
for firstPrefix in knownFirstGroupPrefixes:
|
|
102
|
+
if groupName.startswith(firstPrefix):
|
|
103
|
+
firstGroups.add(groupName)
|
|
104
|
+
break
|
|
105
|
+
for secondPrefix in knownSecondGroupPrefixes:
|
|
106
|
+
if groupName.startswith(secondPrefix):
|
|
107
|
+
secondGroups.add(groupName)
|
|
108
|
+
break
|
|
109
|
+
return firstGroups, secondGroups
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def makeUniqueGroupName(name, groupNames, counter=0):
|
|
113
|
+
# Add a number to the name if the counter is higher than zero.
|
|
114
|
+
newName = name
|
|
115
|
+
if counter > 0:
|
|
116
|
+
newName = "%s%d" % (newName, counter)
|
|
117
|
+
# If the new name is in the existing group names, recurse.
|
|
118
|
+
if newName in groupNames:
|
|
119
|
+
return makeUniqueGroupName(name, groupNames, counter + 1)
|
|
120
|
+
# Otherwise send back the new name.
|
|
121
|
+
return newName
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test():
|
|
125
|
+
"""
|
|
126
|
+
No known prefixes.
|
|
127
|
+
|
|
128
|
+
>>> testKerning = {
|
|
129
|
+
... "A" : {
|
|
130
|
+
... "A" : 1,
|
|
131
|
+
... "B" : 2,
|
|
132
|
+
... "CGroup" : 3,
|
|
133
|
+
... "DGroup" : 4
|
|
134
|
+
... },
|
|
135
|
+
... "BGroup" : {
|
|
136
|
+
... "A" : 5,
|
|
137
|
+
... "B" : 6,
|
|
138
|
+
... "CGroup" : 7,
|
|
139
|
+
... "DGroup" : 8
|
|
140
|
+
... },
|
|
141
|
+
... "CGroup" : {
|
|
142
|
+
... "A" : 9,
|
|
143
|
+
... "B" : 10,
|
|
144
|
+
... "CGroup" : 11,
|
|
145
|
+
... "DGroup" : 12
|
|
146
|
+
... },
|
|
147
|
+
... }
|
|
148
|
+
>>> testGroups = {
|
|
149
|
+
... "BGroup" : ["B"],
|
|
150
|
+
... "CGroup" : ["C"],
|
|
151
|
+
... "DGroup" : ["D"],
|
|
152
|
+
... }
|
|
153
|
+
>>> kerning, groups, maps = convertUFO1OrUFO2KerningToUFO3Kerning(
|
|
154
|
+
... testKerning, testGroups, [])
|
|
155
|
+
>>> expected = {
|
|
156
|
+
... "A" : {
|
|
157
|
+
... "A": 1,
|
|
158
|
+
... "B": 2,
|
|
159
|
+
... "public.kern2.CGroup": 3,
|
|
160
|
+
... "public.kern2.DGroup": 4
|
|
161
|
+
... },
|
|
162
|
+
... "public.kern1.BGroup": {
|
|
163
|
+
... "A": 5,
|
|
164
|
+
... "B": 6,
|
|
165
|
+
... "public.kern2.CGroup": 7,
|
|
166
|
+
... "public.kern2.DGroup": 8
|
|
167
|
+
... },
|
|
168
|
+
... "public.kern1.CGroup": {
|
|
169
|
+
... "A": 9,
|
|
170
|
+
... "B": 10,
|
|
171
|
+
... "public.kern2.CGroup": 11,
|
|
172
|
+
... "public.kern2.DGroup": 12
|
|
173
|
+
... }
|
|
174
|
+
... }
|
|
175
|
+
>>> kerning == expected
|
|
176
|
+
True
|
|
177
|
+
>>> expected = {
|
|
178
|
+
... "BGroup": ["B"],
|
|
179
|
+
... "CGroup": ["C"],
|
|
180
|
+
... "DGroup": ["D"],
|
|
181
|
+
... "public.kern1.BGroup": ["B"],
|
|
182
|
+
... "public.kern1.CGroup": ["C"],
|
|
183
|
+
... "public.kern2.CGroup": ["C"],
|
|
184
|
+
... "public.kern2.DGroup": ["D"],
|
|
185
|
+
... }
|
|
186
|
+
>>> groups == expected
|
|
187
|
+
True
|
|
188
|
+
|
|
189
|
+
Known prefixes.
|
|
190
|
+
|
|
191
|
+
>>> testKerning = {
|
|
192
|
+
... "A" : {
|
|
193
|
+
... "A" : 1,
|
|
194
|
+
... "B" : 2,
|
|
195
|
+
... "@MMK_R_CGroup" : 3,
|
|
196
|
+
... "@MMK_R_DGroup" : 4
|
|
197
|
+
... },
|
|
198
|
+
... "@MMK_L_BGroup" : {
|
|
199
|
+
... "A" : 5,
|
|
200
|
+
... "B" : 6,
|
|
201
|
+
... "@MMK_R_CGroup" : 7,
|
|
202
|
+
... "@MMK_R_DGroup" : 8
|
|
203
|
+
... },
|
|
204
|
+
... "@MMK_L_CGroup" : {
|
|
205
|
+
... "A" : 9,
|
|
206
|
+
... "B" : 10,
|
|
207
|
+
... "@MMK_R_CGroup" : 11,
|
|
208
|
+
... "@MMK_R_DGroup" : 12
|
|
209
|
+
... },
|
|
210
|
+
... }
|
|
211
|
+
>>> testGroups = {
|
|
212
|
+
... "@MMK_L_BGroup" : ["B"],
|
|
213
|
+
... "@MMK_L_CGroup" : ["C"],
|
|
214
|
+
... "@MMK_L_XGroup" : ["X"],
|
|
215
|
+
... "@MMK_R_CGroup" : ["C"],
|
|
216
|
+
... "@MMK_R_DGroup" : ["D"],
|
|
217
|
+
... "@MMK_R_XGroup" : ["X"],
|
|
218
|
+
... }
|
|
219
|
+
>>> kerning, groups, maps = convertUFO1OrUFO2KerningToUFO3Kerning(
|
|
220
|
+
... testKerning, testGroups, [])
|
|
221
|
+
>>> expected = {
|
|
222
|
+
... "A" : {
|
|
223
|
+
... "A": 1,
|
|
224
|
+
... "B": 2,
|
|
225
|
+
... "public.kern2.CGroup": 3,
|
|
226
|
+
... "public.kern2.DGroup": 4
|
|
227
|
+
... },
|
|
228
|
+
... "public.kern1.BGroup": {
|
|
229
|
+
... "A": 5,
|
|
230
|
+
... "B": 6,
|
|
231
|
+
... "public.kern2.CGroup": 7,
|
|
232
|
+
... "public.kern2.DGroup": 8
|
|
233
|
+
... },
|
|
234
|
+
... "public.kern1.CGroup": {
|
|
235
|
+
... "A": 9,
|
|
236
|
+
... "B": 10,
|
|
237
|
+
... "public.kern2.CGroup": 11,
|
|
238
|
+
... "public.kern2.DGroup": 12
|
|
239
|
+
... }
|
|
240
|
+
... }
|
|
241
|
+
>>> kerning == expected
|
|
242
|
+
True
|
|
243
|
+
>>> expected = {
|
|
244
|
+
... "@MMK_L_BGroup": ["B"],
|
|
245
|
+
... "@MMK_L_CGroup": ["C"],
|
|
246
|
+
... "@MMK_L_XGroup": ["X"],
|
|
247
|
+
... "@MMK_R_CGroup": ["C"],
|
|
248
|
+
... "@MMK_R_DGroup": ["D"],
|
|
249
|
+
... "@MMK_R_XGroup": ["X"],
|
|
250
|
+
... "public.kern1.BGroup": ["B"],
|
|
251
|
+
... "public.kern1.CGroup": ["C"],
|
|
252
|
+
... "public.kern1.XGroup": ["X"],
|
|
253
|
+
... "public.kern2.CGroup": ["C"],
|
|
254
|
+
... "public.kern2.DGroup": ["D"],
|
|
255
|
+
... "public.kern2.XGroup": ["X"],
|
|
256
|
+
... }
|
|
257
|
+
>>> groups == expected
|
|
258
|
+
True
|
|
259
|
+
|
|
260
|
+
>>> from .validators import kerningValidator
|
|
261
|
+
>>> kerningValidator(kerning)
|
|
262
|
+
(True, None)
|
|
263
|
+
|
|
264
|
+
Mixture of known prefixes and groups without prefixes.
|
|
265
|
+
|
|
266
|
+
>>> testKerning = {
|
|
267
|
+
... "A" : {
|
|
268
|
+
... "A" : 1,
|
|
269
|
+
... "B" : 2,
|
|
270
|
+
... "@MMK_R_CGroup" : 3,
|
|
271
|
+
... "DGroup" : 4
|
|
272
|
+
... },
|
|
273
|
+
... "BGroup" : {
|
|
274
|
+
... "A" : 5,
|
|
275
|
+
... "B" : 6,
|
|
276
|
+
... "@MMK_R_CGroup" : 7,
|
|
277
|
+
... "DGroup" : 8
|
|
278
|
+
... },
|
|
279
|
+
... "@MMK_L_CGroup" : {
|
|
280
|
+
... "A" : 9,
|
|
281
|
+
... "B" : 10,
|
|
282
|
+
... "@MMK_R_CGroup" : 11,
|
|
283
|
+
... "DGroup" : 12
|
|
284
|
+
... },
|
|
285
|
+
... }
|
|
286
|
+
>>> testGroups = {
|
|
287
|
+
... "BGroup" : ["B"],
|
|
288
|
+
... "@MMK_L_CGroup" : ["C"],
|
|
289
|
+
... "@MMK_R_CGroup" : ["C"],
|
|
290
|
+
... "DGroup" : ["D"],
|
|
291
|
+
... }
|
|
292
|
+
>>> kerning, groups, maps = convertUFO1OrUFO2KerningToUFO3Kerning(
|
|
293
|
+
... testKerning, testGroups, [])
|
|
294
|
+
>>> expected = {
|
|
295
|
+
... "A" : {
|
|
296
|
+
... "A": 1,
|
|
297
|
+
... "B": 2,
|
|
298
|
+
... "public.kern2.CGroup": 3,
|
|
299
|
+
... "public.kern2.DGroup": 4
|
|
300
|
+
... },
|
|
301
|
+
... "public.kern1.BGroup": {
|
|
302
|
+
... "A": 5,
|
|
303
|
+
... "B": 6,
|
|
304
|
+
... "public.kern2.CGroup": 7,
|
|
305
|
+
... "public.kern2.DGroup": 8
|
|
306
|
+
... },
|
|
307
|
+
... "public.kern1.CGroup": {
|
|
308
|
+
... "A": 9,
|
|
309
|
+
... "B": 10,
|
|
310
|
+
... "public.kern2.CGroup": 11,
|
|
311
|
+
... "public.kern2.DGroup": 12
|
|
312
|
+
... }
|
|
313
|
+
... }
|
|
314
|
+
>>> kerning == expected
|
|
315
|
+
True
|
|
316
|
+
>>> expected = {
|
|
317
|
+
... "BGroup": ["B"],
|
|
318
|
+
... "@MMK_L_CGroup": ["C"],
|
|
319
|
+
... "@MMK_R_CGroup": ["C"],
|
|
320
|
+
... "DGroup": ["D"],
|
|
321
|
+
... "public.kern1.BGroup": ["B"],
|
|
322
|
+
... "public.kern1.CGroup": ["C"],
|
|
323
|
+
... "public.kern2.CGroup": ["C"],
|
|
324
|
+
... "public.kern2.DGroup": ["D"],
|
|
325
|
+
... }
|
|
326
|
+
>>> groups == expected
|
|
327
|
+
True
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
if __name__ == "__main__":
|
|
332
|
+
import doctest
|
|
333
|
+
|
|
334
|
+
doctest.testmod()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class UFOLibError(Exception):
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UnsupportedUFOFormat(UFOLibError):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GlifLibError(UFOLibError):
|
|
13
|
+
def _add_note(self, note: str) -> None:
|
|
14
|
+
# Loose backport of PEP 678 until we only support Python 3.11+, used for
|
|
15
|
+
# adding additional context to errors.
|
|
16
|
+
# TODO: Replace with https://docs.python.org/3.11/library/exceptions.html#BaseException.add_note
|
|
17
|
+
(message, *rest) = self.args
|
|
18
|
+
self.args = ((message + "\n" + note), *rest)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class UnsupportedGLIFFormat(GlifLibError):
|
|
22
|
+
pass
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""
|
|
2
|
+
User name to file name conversion.
|
|
3
|
+
This was taken from the UFO 3 spec.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Restrictions are taken mostly from
|
|
7
|
+
# https://docs.microsoft.com/en-gb/windows/win32/fileio/naming-a-file#naming-conventions.
|
|
8
|
+
#
|
|
9
|
+
# 1. Integer value zero, sometimes referred to as the ASCII NUL character.
|
|
10
|
+
# 2. Characters whose integer representations are in the range 1 to 31,
|
|
11
|
+
# inclusive.
|
|
12
|
+
# 3. Various characters that (mostly) Windows and POSIX-y filesystems don't
|
|
13
|
+
# allow, plus "(" and ")", as per the specification.
|
|
14
|
+
illegalCharacters = {
|
|
15
|
+
"\x00",
|
|
16
|
+
"\x01",
|
|
17
|
+
"\x02",
|
|
18
|
+
"\x03",
|
|
19
|
+
"\x04",
|
|
20
|
+
"\x05",
|
|
21
|
+
"\x06",
|
|
22
|
+
"\x07",
|
|
23
|
+
"\x08",
|
|
24
|
+
"\t",
|
|
25
|
+
"\n",
|
|
26
|
+
"\x0b",
|
|
27
|
+
"\x0c",
|
|
28
|
+
"\r",
|
|
29
|
+
"\x0e",
|
|
30
|
+
"\x0f",
|
|
31
|
+
"\x10",
|
|
32
|
+
"\x11",
|
|
33
|
+
"\x12",
|
|
34
|
+
"\x13",
|
|
35
|
+
"\x14",
|
|
36
|
+
"\x15",
|
|
37
|
+
"\x16",
|
|
38
|
+
"\x17",
|
|
39
|
+
"\x18",
|
|
40
|
+
"\x19",
|
|
41
|
+
"\x1a",
|
|
42
|
+
"\x1b",
|
|
43
|
+
"\x1c",
|
|
44
|
+
"\x1d",
|
|
45
|
+
"\x1e",
|
|
46
|
+
"\x1f",
|
|
47
|
+
'"',
|
|
48
|
+
"*",
|
|
49
|
+
"+",
|
|
50
|
+
"/",
|
|
51
|
+
":",
|
|
52
|
+
"<",
|
|
53
|
+
">",
|
|
54
|
+
"?",
|
|
55
|
+
"[",
|
|
56
|
+
"\\",
|
|
57
|
+
"]",
|
|
58
|
+
"(",
|
|
59
|
+
")",
|
|
60
|
+
"|",
|
|
61
|
+
"\x7f",
|
|
62
|
+
}
|
|
63
|
+
reservedFileNames = {
|
|
64
|
+
"aux",
|
|
65
|
+
"clock$",
|
|
66
|
+
"com1",
|
|
67
|
+
"com2",
|
|
68
|
+
"com3",
|
|
69
|
+
"com4",
|
|
70
|
+
"com5",
|
|
71
|
+
"com6",
|
|
72
|
+
"com7",
|
|
73
|
+
"com8",
|
|
74
|
+
"com9",
|
|
75
|
+
"con",
|
|
76
|
+
"lpt1",
|
|
77
|
+
"lpt2",
|
|
78
|
+
"lpt3",
|
|
79
|
+
"lpt4",
|
|
80
|
+
"lpt5",
|
|
81
|
+
"lpt6",
|
|
82
|
+
"lpt7",
|
|
83
|
+
"lpt8",
|
|
84
|
+
"lpt9",
|
|
85
|
+
"nul",
|
|
86
|
+
"prn",
|
|
87
|
+
}
|
|
88
|
+
maxFileNameLength = 255
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class NameTranslationError(Exception):
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def userNameToFileName(userName: str, existing=(), prefix="", suffix=""):
|
|
96
|
+
"""
|
|
97
|
+
`existing` should be a set-like object.
|
|
98
|
+
|
|
99
|
+
>>> userNameToFileName("a") == "a"
|
|
100
|
+
True
|
|
101
|
+
>>> userNameToFileName("A") == "A_"
|
|
102
|
+
True
|
|
103
|
+
>>> userNameToFileName("AE") == "A_E_"
|
|
104
|
+
True
|
|
105
|
+
>>> userNameToFileName("Ae") == "A_e"
|
|
106
|
+
True
|
|
107
|
+
>>> userNameToFileName("ae") == "ae"
|
|
108
|
+
True
|
|
109
|
+
>>> userNameToFileName("aE") == "aE_"
|
|
110
|
+
True
|
|
111
|
+
>>> userNameToFileName("a.alt") == "a.alt"
|
|
112
|
+
True
|
|
113
|
+
>>> userNameToFileName("A.alt") == "A_.alt"
|
|
114
|
+
True
|
|
115
|
+
>>> userNameToFileName("A.Alt") == "A_.A_lt"
|
|
116
|
+
True
|
|
117
|
+
>>> userNameToFileName("A.aLt") == "A_.aL_t"
|
|
118
|
+
True
|
|
119
|
+
>>> userNameToFileName(u"A.alT") == "A_.alT_"
|
|
120
|
+
True
|
|
121
|
+
>>> userNameToFileName("T_H") == "T__H_"
|
|
122
|
+
True
|
|
123
|
+
>>> userNameToFileName("T_h") == "T__h"
|
|
124
|
+
True
|
|
125
|
+
>>> userNameToFileName("t_h") == "t_h"
|
|
126
|
+
True
|
|
127
|
+
>>> userNameToFileName("F_F_I") == "F__F__I_"
|
|
128
|
+
True
|
|
129
|
+
>>> userNameToFileName("f_f_i") == "f_f_i"
|
|
130
|
+
True
|
|
131
|
+
>>> userNameToFileName("Aacute_V.swash") == "A_acute_V_.swash"
|
|
132
|
+
True
|
|
133
|
+
>>> userNameToFileName(".notdef") == "_notdef"
|
|
134
|
+
True
|
|
135
|
+
>>> userNameToFileName("con") == "_con"
|
|
136
|
+
True
|
|
137
|
+
>>> userNameToFileName("CON") == "C_O_N_"
|
|
138
|
+
True
|
|
139
|
+
>>> userNameToFileName("con.alt") == "_con.alt"
|
|
140
|
+
True
|
|
141
|
+
>>> userNameToFileName("alt.con") == "alt._con"
|
|
142
|
+
True
|
|
143
|
+
"""
|
|
144
|
+
# the incoming name must be a string
|
|
145
|
+
if not isinstance(userName, str):
|
|
146
|
+
raise ValueError("The value for userName must be a string.")
|
|
147
|
+
# establish the prefix and suffix lengths
|
|
148
|
+
prefixLength = len(prefix)
|
|
149
|
+
suffixLength = len(suffix)
|
|
150
|
+
# replace an initial period with an _
|
|
151
|
+
# if no prefix is to be added
|
|
152
|
+
if not prefix and userName[0] == ".":
|
|
153
|
+
userName = "_" + userName[1:]
|
|
154
|
+
# filter the user name
|
|
155
|
+
filteredUserName = []
|
|
156
|
+
for character in userName:
|
|
157
|
+
# replace illegal characters with _
|
|
158
|
+
if character in illegalCharacters:
|
|
159
|
+
character = "_"
|
|
160
|
+
# add _ to all non-lower characters
|
|
161
|
+
elif character != character.lower():
|
|
162
|
+
character += "_"
|
|
163
|
+
filteredUserName.append(character)
|
|
164
|
+
userName = "".join(filteredUserName)
|
|
165
|
+
# clip to 255
|
|
166
|
+
sliceLength = maxFileNameLength - prefixLength - suffixLength
|
|
167
|
+
userName = userName[:sliceLength]
|
|
168
|
+
# test for illegal files names
|
|
169
|
+
parts = []
|
|
170
|
+
for part in userName.split("."):
|
|
171
|
+
if part.lower() in reservedFileNames:
|
|
172
|
+
part = "_" + part
|
|
173
|
+
parts.append(part)
|
|
174
|
+
userName = ".".join(parts)
|
|
175
|
+
# test for clash
|
|
176
|
+
fullName = prefix + userName + suffix
|
|
177
|
+
if fullName.lower() in existing:
|
|
178
|
+
fullName = handleClash1(userName, existing, prefix, suffix)
|
|
179
|
+
# finished
|
|
180
|
+
return fullName
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def handleClash1(userName, existing=[], prefix="", suffix=""):
|
|
184
|
+
"""
|
|
185
|
+
existing should be a case-insensitive list
|
|
186
|
+
of all existing file names.
|
|
187
|
+
|
|
188
|
+
>>> prefix = ("0" * 5) + "."
|
|
189
|
+
>>> suffix = "." + ("0" * 10)
|
|
190
|
+
>>> existing = ["a" * 5]
|
|
191
|
+
|
|
192
|
+
>>> e = list(existing)
|
|
193
|
+
>>> handleClash1(userName="A" * 5, existing=e,
|
|
194
|
+
... prefix=prefix, suffix=suffix) == (
|
|
195
|
+
... '00000.AAAAA000000000000001.0000000000')
|
|
196
|
+
True
|
|
197
|
+
|
|
198
|
+
>>> e = list(existing)
|
|
199
|
+
>>> e.append(prefix + "aaaaa" + "1".zfill(15) + suffix)
|
|
200
|
+
>>> handleClash1(userName="A" * 5, existing=e,
|
|
201
|
+
... prefix=prefix, suffix=suffix) == (
|
|
202
|
+
... '00000.AAAAA000000000000002.0000000000')
|
|
203
|
+
True
|
|
204
|
+
|
|
205
|
+
>>> e = list(existing)
|
|
206
|
+
>>> e.append(prefix + "AAAAA" + "2".zfill(15) + suffix)
|
|
207
|
+
>>> handleClash1(userName="A" * 5, existing=e,
|
|
208
|
+
... prefix=prefix, suffix=suffix) == (
|
|
209
|
+
... '00000.AAAAA000000000000001.0000000000')
|
|
210
|
+
True
|
|
211
|
+
"""
|
|
212
|
+
# if the prefix length + user name length + suffix length + 15 is at
|
|
213
|
+
# or past the maximum length, silce 15 characters off of the user name
|
|
214
|
+
prefixLength = len(prefix)
|
|
215
|
+
suffixLength = len(suffix)
|
|
216
|
+
if prefixLength + len(userName) + suffixLength + 15 > maxFileNameLength:
|
|
217
|
+
l = prefixLength + len(userName) + suffixLength + 15
|
|
218
|
+
sliceLength = maxFileNameLength - l
|
|
219
|
+
userName = userName[:sliceLength]
|
|
220
|
+
finalName = None
|
|
221
|
+
# try to add numbers to create a unique name
|
|
222
|
+
counter = 1
|
|
223
|
+
while finalName is None:
|
|
224
|
+
name = userName + str(counter).zfill(15)
|
|
225
|
+
fullName = prefix + name + suffix
|
|
226
|
+
if fullName.lower() not in existing:
|
|
227
|
+
finalName = fullName
|
|
228
|
+
break
|
|
229
|
+
else:
|
|
230
|
+
counter += 1
|
|
231
|
+
if counter >= 999999999999999:
|
|
232
|
+
break
|
|
233
|
+
# if there is a clash, go to the next fallback
|
|
234
|
+
if finalName is None:
|
|
235
|
+
finalName = handleClash2(existing, prefix, suffix)
|
|
236
|
+
# finished
|
|
237
|
+
return finalName
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def handleClash2(existing=[], prefix="", suffix=""):
|
|
241
|
+
"""
|
|
242
|
+
existing should be a case-insensitive list
|
|
243
|
+
of all existing file names.
|
|
244
|
+
|
|
245
|
+
>>> prefix = ("0" * 5) + "."
|
|
246
|
+
>>> suffix = "." + ("0" * 10)
|
|
247
|
+
>>> existing = [prefix + str(i) + suffix for i in range(100)]
|
|
248
|
+
|
|
249
|
+
>>> e = list(existing)
|
|
250
|
+
>>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
|
|
251
|
+
... '00000.100.0000000000')
|
|
252
|
+
True
|
|
253
|
+
|
|
254
|
+
>>> e = list(existing)
|
|
255
|
+
>>> e.remove(prefix + "1" + suffix)
|
|
256
|
+
>>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
|
|
257
|
+
... '00000.1.0000000000')
|
|
258
|
+
True
|
|
259
|
+
|
|
260
|
+
>>> e = list(existing)
|
|
261
|
+
>>> e.remove(prefix + "2" + suffix)
|
|
262
|
+
>>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
|
|
263
|
+
... '00000.2.0000000000')
|
|
264
|
+
True
|
|
265
|
+
"""
|
|
266
|
+
# calculate the longest possible string
|
|
267
|
+
maxLength = maxFileNameLength - len(prefix) - len(suffix)
|
|
268
|
+
maxValue = int("9" * maxLength)
|
|
269
|
+
# try to find a number
|
|
270
|
+
finalName = None
|
|
271
|
+
counter = 1
|
|
272
|
+
while finalName is None:
|
|
273
|
+
fullName = prefix + str(counter) + suffix
|
|
274
|
+
if fullName.lower() not in existing:
|
|
275
|
+
finalName = fullName
|
|
276
|
+
break
|
|
277
|
+
else:
|
|
278
|
+
counter += 1
|
|
279
|
+
if counter >= maxValue:
|
|
280
|
+
break
|
|
281
|
+
# raise an error if nothing has been found
|
|
282
|
+
if finalName is None:
|
|
283
|
+
raise NameTranslationError("No unique name could be found.")
|
|
284
|
+
# finished
|
|
285
|
+
return finalName
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
if __name__ == "__main__":
|
|
289
|
+
import doctest
|
|
290
|
+
|
|
291
|
+
doctest.testmod()
|