fonttools 4.56.0__cp313-cp313-win_amd64.whl → 4.58.0__cp313-cp313-win_amd64.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.

Files changed (71) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/cffLib/__init__.py +61 -26
  3. fontTools/config/__init__.py +15 -0
  4. fontTools/cu2qu/cu2qu.c +15345 -14829
  5. fontTools/cu2qu/cu2qu.cp313-win_amd64.pyd +0 -0
  6. fontTools/designspaceLib/statNames.py +14 -7
  7. fontTools/feaLib/ast.py +92 -13
  8. fontTools/feaLib/builder.py +52 -13
  9. fontTools/feaLib/lexer.c +17143 -17986
  10. fontTools/feaLib/lexer.cp313-win_amd64.pyd +0 -0
  11. fontTools/feaLib/parser.py +59 -39
  12. fontTools/fontBuilder.py +6 -0
  13. fontTools/misc/bezierTools.c +39936 -41831
  14. fontTools/misc/bezierTools.cp313-win_amd64.pyd +0 -0
  15. fontTools/misc/etree.py +4 -27
  16. fontTools/misc/testTools.py +2 -1
  17. fontTools/mtiLib/__init__.py +0 -2
  18. fontTools/otlLib/builder.py +195 -145
  19. fontTools/otlLib/optimize/gpos.py +49 -63
  20. fontTools/pens/momentsPen.c +13266 -13448
  21. fontTools/pens/momentsPen.cp313-win_amd64.pyd +0 -0
  22. fontTools/pens/pointPen.py +21 -12
  23. fontTools/qu2cu/qu2cu.c +16538 -16269
  24. fontTools/qu2cu/qu2cu.cp313-win_amd64.pyd +0 -0
  25. fontTools/subset/__init__.py +11 -0
  26. fontTools/ttLib/__init__.py +4 -0
  27. fontTools/ttLib/__main__.py +47 -8
  28. fontTools/ttLib/tables/D__e_b_g.py +20 -2
  29. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  30. fontTools/ttLib/tables/T_S_I__0.py +14 -3
  31. fontTools/ttLib/tables/T_S_I__5.py +16 -5
  32. fontTools/ttLib/tables/__init__.py +1 -0
  33. fontTools/ttLib/tables/_c_m_a_p.py +19 -6
  34. fontTools/ttLib/tables/_c_v_t.py +2 -0
  35. fontTools/ttLib/tables/_f_p_g_m.py +3 -1
  36. fontTools/ttLib/tables/_g_l_y_f.py +11 -10
  37. fontTools/ttLib/tables/_g_v_a_r.py +62 -17
  38. fontTools/ttLib/tables/_p_o_s_t.py +5 -2
  39. fontTools/ttLib/tables/otBase.py +1 -0
  40. fontTools/ttLib/tables/otConverters.py +5 -2
  41. fontTools/ttLib/tables/otTables.py +5 -1
  42. fontTools/ttLib/ttFont.py +3 -5
  43. fontTools/ttLib/ttGlyphSet.py +0 -10
  44. fontTools/ttx.py +13 -1
  45. fontTools/ufoLib/__init__.py +2 -2
  46. fontTools/ufoLib/converters.py +89 -25
  47. fontTools/ufoLib/errors.py +8 -0
  48. fontTools/ufoLib/etree.py +1 -1
  49. fontTools/ufoLib/filenames.py +155 -100
  50. fontTools/ufoLib/glifLib.py +9 -2
  51. fontTools/ufoLib/kerning.py +66 -36
  52. fontTools/ufoLib/utils.py +5 -2
  53. fontTools/unicodedata/Mirrored.py +446 -0
  54. fontTools/unicodedata/__init__.py +6 -2
  55. fontTools/varLib/__init__.py +94 -89
  56. fontTools/varLib/hvar.py +113 -0
  57. fontTools/varLib/iup.c +19630 -19154
  58. fontTools/varLib/iup.cp313-win_amd64.pyd +0 -0
  59. fontTools/varLib/varStore.py +1 -1
  60. fontTools/voltLib/__main__.py +206 -0
  61. fontTools/voltLib/ast.py +4 -0
  62. fontTools/voltLib/parser.py +16 -8
  63. fontTools/voltLib/voltToFea.py +347 -166
  64. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/METADATA +60 -12
  65. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/RECORD +71 -66
  66. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/WHEEL +1 -1
  67. fonttools-4.58.0.dist-info/licenses/LICENSE.external +359 -0
  68. {fonttools-4.56.0.data → fonttools-4.58.0.data}/data/share/man/man1/ttx.1 +0 -0
  69. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/entry_points.txt +0 -0
  70. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info/licenses}/LICENSE +0 -0
  71. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/top_level.txt +0 -0
fontTools/ttLib/ttFont.py CHANGED
@@ -593,10 +593,8 @@ class TTFont(object):
593
593
  # temporary cmap and by the real cmap in case we don't find a unicode
594
594
  # cmap.
595
595
  numGlyphs = int(self["maxp"].numGlyphs)
596
- glyphOrder = [None] * numGlyphs
596
+ glyphOrder = ["glyph%.5d" % i for i in range(numGlyphs)]
597
597
  glyphOrder[0] = ".notdef"
598
- for i in range(1, numGlyphs):
599
- glyphOrder[i] = "glyph%.5d" % i
600
598
  # Set the glyph order, so the cmap parser has something
601
599
  # to work with (so we don't get called recursively).
602
600
  self.glyphOrder = glyphOrder
@@ -606,7 +604,7 @@ class TTFont(object):
606
604
  # this naming table will usually not cover all glyphs in the font.
607
605
  # If the font has no Unicode cmap table, reversecmap will be empty.
608
606
  if "cmap" in self:
609
- reversecmap = self["cmap"].buildReversed()
607
+ reversecmap = self["cmap"].buildReversedMin()
610
608
  else:
611
609
  reversecmap = {}
612
610
  useCount = {}
@@ -616,7 +614,7 @@ class TTFont(object):
616
614
  # If a font maps both U+0041 LATIN CAPITAL LETTER A and
617
615
  # U+0391 GREEK CAPITAL LETTER ALPHA to the same glyph,
618
616
  # we prefer naming the glyph as "A".
619
- glyphName = self._makeGlyphName(min(reversecmap[tempName]))
617
+ glyphName = self._makeGlyphName(reversecmap[tempName])
620
618
  numUses = useCount[glyphName] = useCount.get(glyphName, 0) + 1
621
619
  if numUses > 1:
622
620
  glyphName = "%s.alt%d" % (glyphName, numUses - 1)
@@ -104,16 +104,6 @@ class _TTGlyphSetGlyf(_TTGlyphSet):
104
104
  return _TTGlyphGlyf(self, glyphName, recalcBounds=self.recalcBounds)
105
105
 
106
106
 
107
- class _TTGlyphSetGlyf(_TTGlyphSet):
108
- def __init__(self, font, location, recalcBounds=True):
109
- self.glyfTable = font["glyf"]
110
- super().__init__(font, location, self.glyfTable, recalcBounds=recalcBounds)
111
- self.gvarTable = font.get("gvar")
112
-
113
- def __getitem__(self, glyphName):
114
- return _TTGlyphGlyf(self, glyphName, recalcBounds=self.recalcBounds)
115
-
116
-
117
107
  class _TTGlyphSetCFF(_TTGlyphSet):
118
108
  def __init__(self, font, location):
119
109
  tableTag = "CFF2" if "CFF2" in font else "CFF "
fontTools/ttx.py CHANGED
@@ -101,9 +101,15 @@ Compile options
101
101
  --with-zopfli
102
102
  Use Zopfli instead of Zlib to compress WOFF. The Python
103
103
  extension is available at https://pypi.python.org/pypi/zopfli
104
+ --optimize-font-speed
105
+ Enable optimizations that prioritize speed over file size.
106
+ This mainly affects how glyf t able and gvar / VARC tables are
107
+ compiled. The produced fonts will be larger, but rendering
108
+ performance will be improved with HarfBuzz and other text
109
+ layout engines.
104
110
  """
105
111
 
106
- from fontTools.ttLib import TTFont, TTLibError
112
+ from fontTools.ttLib import OPTIMIZE_FONT_SPEED, TTFont, TTLibError
107
113
  from fontTools.misc.macCreatorType import getMacCreatorAndType
108
114
  from fontTools.unicode import setUnicodeData
109
115
  from fontTools.misc.textTools import Tag, tostr
@@ -141,6 +147,7 @@ class Options(object):
141
147
  recalcTimestamp = None
142
148
  flavor = None
143
149
  useZopfli = False
150
+ optimizeFontSpeed = False
144
151
 
145
152
  def __init__(self, rawOptions, numFiles):
146
153
  self.onlyTables = []
@@ -229,6 +236,8 @@ class Options(object):
229
236
  self.flavor = value
230
237
  elif option == "--with-zopfli":
231
238
  self.useZopfli = True
239
+ elif option == "--optimize-font-speed":
240
+ self.optimizeFontSpeed = True
232
241
  if self.verbose and self.quiet:
233
242
  raise getopt.GetoptError("-q and -v options are mutually exclusive")
234
243
  if self.verbose:
@@ -324,6 +333,8 @@ def ttCompile(input, output, options):
324
333
  recalcBBoxes=options.recalcBBoxes,
325
334
  recalcTimestamp=options.recalcTimestamp,
326
335
  )
336
+ if options.optimizeFontSpeed:
337
+ ttf.cfg[OPTIMIZE_FONT_SPEED] = options.optimizeFontSpeed
327
338
  ttf.importXML(input)
328
339
 
329
340
  if options.recalcTimestamp is None and "head" in ttf and input is not sys.stdin:
@@ -386,6 +397,7 @@ def parseOptions(args):
386
397
  "version",
387
398
  "with-zopfli",
388
399
  "newline=",
400
+ "optimize-font-speed",
389
401
  ],
390
402
  )
391
403
 
@@ -204,7 +204,7 @@ class UFOReader(_UFOBaseIO):
204
204
  """Read the various components of a .ufo.
205
205
 
206
206
  Attributes:
207
- path: An `os.PathLike` object pointing to the .ufo.
207
+ path: An :class:`os.PathLike` object pointing to the .ufo.
208
208
  validate: A boolean indicating if the data read should be
209
209
  validated. Defaults to `True`.
210
210
 
@@ -891,7 +891,7 @@ class UFOWriter(UFOReader):
891
891
  """Write the various components of a .ufo.
892
892
 
893
893
  Attributes:
894
- path: An `os.PathLike` object pointing to the .ufo.
894
+ path: An :class:`os.PathLike` object pointing to the .ufo.
895
895
  formatVersion: the UFO format version as a tuple of integers (major, minor),
896
896
  or as a single integer for the major digit only (minor is implied to be 0).
897
897
  By default, the latest formatVersion will be used; currently it is 3.0,
@@ -1,11 +1,33 @@
1
1
  """
2
- Conversion functions.
2
+ Functions for converting UFO1 or UFO2 files into UFO3 format.
3
+
4
+ Currently provides functionality for converting kerning rules
5
+ and kerning groups. Conversion is only supported _from_ UFO1
6
+ or UFO2, and _to_ UFO3.
3
7
  """
4
8
 
5
9
  # adapted from the UFO spec
6
10
 
7
11
 
8
12
  def convertUFO1OrUFO2KerningToUFO3Kerning(kerning, groups, glyphSet=()):
13
+ """Convert kerning data in UFO1 or UFO2 syntax into UFO3 syntax.
14
+
15
+ Args:
16
+ kerning:
17
+ A dictionary containing the kerning rules defined in
18
+ the UFO font, as used in :class:`.UFOReader` objects.
19
+ groups:
20
+ A dictionary containing the groups defined in the UFO
21
+ font, as used in :class:`.UFOReader` objects.
22
+ glyphSet:
23
+ Optional; a set of glyph objects to skip (default: None).
24
+
25
+ Returns:
26
+ 1. A dictionary representing the converted kerning data.
27
+ 2. A copy of the groups dictionary, with all groups renamed to UFO3 syntax.
28
+ 3. A dictionary containing the mapping of old group names to new group names.
29
+
30
+ """
9
31
  # gather known kerning groups based on the prefixes
10
32
  firstReferencedGroups, secondReferencedGroups = findKnownKerningGroups(groups)
11
33
  # Make lists of groups referenced in kerning pairs.
@@ -63,35 +85,54 @@ def convertUFO1OrUFO2KerningToUFO3Kerning(kerning, groups, glyphSet=()):
63
85
 
64
86
 
65
87
  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
88
+ """Find all kerning groups in a UFO1 or UFO2 font that use known prefixes.
89
+
90
+ In some cases, not all kerning groups will be referenced
91
+ by the kerning pairs in a UFO. The algorithm for locating
92
+ groups in :func:`convertUFO1OrUFO2KerningToUFO3Kerning` will
93
+ miss these unreferenced groups. By scanning for known prefixes,
72
94
  this function will catch all of the prefixed groups.
73
95
 
74
- These are the prefixes and sides that are handled:
96
+ The prefixes and sides by this function are:
97
+
75
98
  @MMK_L_ - side 1
76
99
  @MMK_R_ - side 2
77
100
 
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
101
+ as defined in the UFO1 specification.
102
+
103
+ Args:
104
+ groups:
105
+ A dictionary containing the groups defined in the UFO
106
+ font, as read by :class:`.UFOReader`.
107
+
108
+ Returns:
109
+ Two sets; the first containing the names of all
110
+ first-side kerning groups identified in the ``groups``
111
+ dictionary, and the second containing the names of all
112
+ second-side kerning groups identified.
113
+
114
+ "First-side" and "second-side" are with respect to the
115
+ writing direction of the script.
116
+
117
+ Example::
118
+
119
+ >>> testGroups = {
120
+ ... "@MMK_L_1" : None,
121
+ ... "@MMK_L_2" : None,
122
+ ... "@MMK_L_3" : None,
123
+ ... "@MMK_R_1" : None,
124
+ ... "@MMK_R_2" : None,
125
+ ... "@MMK_R_3" : None,
126
+ ... "@MMK_l_1" : None,
127
+ ... "@MMK_r_1" : None,
128
+ ... "@MMK_X_1" : None,
129
+ ... "foo" : None,
130
+ ... }
131
+ >>> first, second = findKnownKerningGroups(testGroups)
132
+ >>> sorted(first) == ['@MMK_L_1', '@MMK_L_2', '@MMK_L_3']
133
+ True
134
+ >>> sorted(second) == ['@MMK_R_1', '@MMK_R_2', '@MMK_R_3']
135
+ True
95
136
  """
96
137
  knownFirstGroupPrefixes = ["@MMK_L_"]
97
138
  knownSecondGroupPrefixes = ["@MMK_R_"]
@@ -110,6 +151,27 @@ def findKnownKerningGroups(groups):
110
151
 
111
152
 
112
153
  def makeUniqueGroupName(name, groupNames, counter=0):
154
+ """Make a kerning group name that will be unique within the set of group names.
155
+
156
+ If the requested kerning group name already exists within the set, this
157
+ will return a new name by adding an incremented counter to the end
158
+ of the requested name.
159
+
160
+ Args:
161
+ name:
162
+ The requested kerning group name.
163
+ groupNames:
164
+ A list of the existing kerning group names.
165
+ counter:
166
+ Optional; a counter of group names already seen (default: 0). If
167
+ :attr:`.counter` is not provided, the function will recurse,
168
+ incrementing the value of :attr:`.counter` until it finds the
169
+ first unused ``name+counter`` combination, and return that result.
170
+
171
+ Returns:
172
+ A unique kerning group name composed of the requested name suffixed
173
+ by the smallest available integer counter.
174
+ """
113
175
  # Add a number to the name if the counter is higher than zero.
114
176
  newName = name
115
177
  if counter > 0:
@@ -123,6 +185,8 @@ def makeUniqueGroupName(name, groupNames, counter=0):
123
185
 
124
186
  def test():
125
187
  """
188
+ Tests for :func:`.convertUFO1OrUFO2KerningToUFO3Kerning`.
189
+
126
190
  No known prefixes.
127
191
 
128
192
  >>> testKerning = {
@@ -10,6 +10,14 @@ class UnsupportedUFOFormat(UFOLibError):
10
10
 
11
11
 
12
12
  class GlifLibError(UFOLibError):
13
+ """An error raised by glifLib.
14
+
15
+ This class is a loose backport of PEP 678, adding a :attr:`.note`
16
+ attribute that can hold additional context for errors encountered.
17
+
18
+ It will be maintained until only Python 3.11-and-later are supported.
19
+ """
20
+
13
21
  def _add_note(self, note: str) -> None:
14
22
  # Loose backport of PEP 678 until we only support Python 3.11+, used for
15
23
  # adding additional context to errors.
fontTools/ufoLib/etree.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """DEPRECATED - This module is kept here only as a backward compatibility shim
2
- for the old ufoLib.etree module, which was moved to fontTools.misc.etree.
2
+ for the old ufoLib.etree module, which was moved to :mod:`fontTools.misc.etree`.
3
3
  Please use the latter instead.
4
4
  """
5
5
 
@@ -1,6 +1,22 @@
1
1
  """
2
- User name to file name conversion.
3
- This was taken from the UFO 3 spec.
2
+ Convert user-provided internal UFO names to spec-compliant filenames.
3
+
4
+ This module implements the algorithm for converting between a "user name" -
5
+ something that a user can choose arbitrarily inside a font editor - and a file
6
+ name suitable for use in a wide range of operating systems and filesystems.
7
+
8
+ The `UFO 3 specification <http://unifiedfontobject.org/versions/ufo3/conventions/>`_
9
+ provides an example of an algorithm for such conversion, which avoids illegal
10
+ characters, reserved file names, ambiguity between upper- and lower-case
11
+ characters, and clashes with existing files.
12
+
13
+ This code was originally copied from
14
+ `ufoLib <https://github.com/unified-font-object/ufoLib/blob/8747da7/Lib/ufoLib/filenames.py>`_
15
+ by Tal Leming and is copyright (c) 2005-2016, The RoboFab Developers:
16
+
17
+ - Erik van Blokland
18
+ - Tal Leming
19
+ - Just van Rossum
4
20
  """
5
21
 
6
22
  # Restrictions are taken mostly from
@@ -93,53 +109,69 @@ class NameTranslationError(Exception):
93
109
 
94
110
 
95
111
  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
112
+ """Converts from a user name to a file name.
113
+
114
+ Takes care to avoid illegal characters, reserved file names, ambiguity between
115
+ upper- and lower-case characters, and clashes with existing files.
116
+
117
+ Args:
118
+ userName (str): The input file name.
119
+ existing: A case-insensitive list of all existing file names.
120
+ prefix: Prefix to be prepended to the file name.
121
+ suffix: Suffix to be appended to the file name.
122
+
123
+ Returns:
124
+ A suitable filename.
125
+
126
+ Raises:
127
+ NameTranslationError: If no suitable name could be generated.
128
+
129
+ Examples::
130
+
131
+ >>> userNameToFileName("a") == "a"
132
+ True
133
+ >>> userNameToFileName("A") == "A_"
134
+ True
135
+ >>> userNameToFileName("AE") == "A_E_"
136
+ True
137
+ >>> userNameToFileName("Ae") == "A_e"
138
+ True
139
+ >>> userNameToFileName("ae") == "ae"
140
+ True
141
+ >>> userNameToFileName("aE") == "aE_"
142
+ True
143
+ >>> userNameToFileName("a.alt") == "a.alt"
144
+ True
145
+ >>> userNameToFileName("A.alt") == "A_.alt"
146
+ True
147
+ >>> userNameToFileName("A.Alt") == "A_.A_lt"
148
+ True
149
+ >>> userNameToFileName("A.aLt") == "A_.aL_t"
150
+ True
151
+ >>> userNameToFileName(u"A.alT") == "A_.alT_"
152
+ True
153
+ >>> userNameToFileName("T_H") == "T__H_"
154
+ True
155
+ >>> userNameToFileName("T_h") == "T__h"
156
+ True
157
+ >>> userNameToFileName("t_h") == "t_h"
158
+ True
159
+ >>> userNameToFileName("F_F_I") == "F__F__I_"
160
+ True
161
+ >>> userNameToFileName("f_f_i") == "f_f_i"
162
+ True
163
+ >>> userNameToFileName("Aacute_V.swash") == "A_acute_V_.swash"
164
+ True
165
+ >>> userNameToFileName(".notdef") == "_notdef"
166
+ True
167
+ >>> userNameToFileName("con") == "_con"
168
+ True
169
+ >>> userNameToFileName("CON") == "C_O_N_"
170
+ True
171
+ >>> userNameToFileName("con.alt") == "_con.alt"
172
+ True
173
+ >>> userNameToFileName("alt.con") == "alt._con"
174
+ True
143
175
  """
144
176
  # the incoming name must be a string
145
177
  if not isinstance(userName, str):
@@ -181,33 +213,42 @@ def userNameToFileName(userName: str, existing=(), prefix="", suffix=""):
181
213
 
182
214
 
183
215
  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
216
+ """A helper function that resolves collisions with existing names when choosing a filename.
217
+
218
+ This function attempts to append an unused integer counter to the filename.
219
+
220
+ Args:
221
+ userName (str): The input file name.
222
+ existing: A case-insensitive list of all existing file names.
223
+ prefix: Prefix to be prepended to the file name.
224
+ suffix: Suffix to be appended to the file name.
225
+
226
+ Returns:
227
+ A suitable filename.
228
+
229
+ >>> prefix = ("0" * 5) + "."
230
+ >>> suffix = "." + ("0" * 10)
231
+ >>> existing = ["a" * 5]
232
+
233
+ >>> e = list(existing)
234
+ >>> handleClash1(userName="A" * 5, existing=e,
235
+ ... prefix=prefix, suffix=suffix) == (
236
+ ... '00000.AAAAA000000000000001.0000000000')
237
+ True
238
+
239
+ >>> e = list(existing)
240
+ >>> e.append(prefix + "aaaaa" + "1".zfill(15) + suffix)
241
+ >>> handleClash1(userName="A" * 5, existing=e,
242
+ ... prefix=prefix, suffix=suffix) == (
243
+ ... '00000.AAAAA000000000000002.0000000000')
244
+ True
245
+
246
+ >>> e = list(existing)
247
+ >>> e.append(prefix + "AAAAA" + "2".zfill(15) + suffix)
248
+ >>> handleClash1(userName="A" * 5, existing=e,
249
+ ... prefix=prefix, suffix=suffix) == (
250
+ ... '00000.AAAAA000000000000001.0000000000')
251
+ True
211
252
  """
212
253
  # if the prefix length + user name length + suffix length + 15 is at
213
254
  # or past the maximum length, silce 15 characters off of the user name
@@ -238,30 +279,44 @@ def handleClash1(userName, existing=[], prefix="", suffix=""):
238
279
 
239
280
 
240
281
  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
282
+ """A helper function that resolves collisions with existing names when choosing a filename.
283
+
284
+ This function is a fallback to :func:`handleClash1`. It attempts to append an unused integer counter to the filename.
285
+
286
+ Args:
287
+ userName (str): The input file name.
288
+ existing: A case-insensitive list of all existing file names.
289
+ prefix: Prefix to be prepended to the file name.
290
+ suffix: Suffix to be appended to the file name.
291
+
292
+ Returns:
293
+ A suitable filename.
294
+
295
+ Raises:
296
+ NameTranslationError: If no suitable name could be generated.
297
+
298
+ Examples::
299
+
300
+ >>> prefix = ("0" * 5) + "."
301
+ >>> suffix = "." + ("0" * 10)
302
+ >>> existing = [prefix + str(i) + suffix for i in range(100)]
303
+
304
+ >>> e = list(existing)
305
+ >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
306
+ ... '00000.100.0000000000')
307
+ True
308
+
309
+ >>> e = list(existing)
310
+ >>> e.remove(prefix + "1" + suffix)
311
+ >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
312
+ ... '00000.1.0000000000')
313
+ True
314
+
315
+ >>> e = list(existing)
316
+ >>> e.remove(prefix + "2" + suffix)
317
+ >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
318
+ ... '00000.2.0000000000')
319
+ True
265
320
  """
266
321
  # calculate the longest possible string
267
322
  maxLength = maxFileNameLength - len(prefix) - len(suffix)
@@ -1,11 +1,11 @@
1
1
  """
2
- glifLib.py -- Generic module for reading and writing the .glif format.
2
+ Generic module for reading and writing the .glif format.
3
3
 
4
4
  More info about the .glif format (GLyphInterchangeFormat) can be found here:
5
5
 
6
6
  http://unifiedfontobject.org
7
7
 
8
- The main class in this module is GlyphSet. It manages a set of .glif files
8
+ The main class in this module is :class:`GlyphSet`. It manages a set of .glif files
9
9
  in a folder. It offers two ways to read glyph data, and one way to write
10
10
  glyph data. See the class doc string for details.
11
11
  """
@@ -60,6 +60,13 @@ LAYERINFO_FILENAME = "layerinfo.plist"
60
60
 
61
61
 
62
62
  class GLIFFormatVersion(tuple, _VersionTupleEnumMixin, enum.Enum):
63
+ """Class representing the versions of the .glif format supported by the UFO version in use.
64
+
65
+ For a given :mod:`fontTools.ufoLib.UFOFormatVersion`, the :func:`supported_versions` method will
66
+ return the supported versions of the GLIF file format. If the UFO version is unspecified, the
67
+ :func:`supported_versions` method will return all available GLIF format versions.
68
+ """
69
+
63
70
  FORMAT_1_0 = (1, 0)
64
71
  FORMAT_2_0 = (2, 0)
65
72