fonttools 4.55.4__cp313-cp313-musllinux_1_2_aarch64.whl → 4.61.1__cp313-cp313-musllinux_1_2_aarch64.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.
Files changed (140) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/annotations.py +30 -0
  3. fontTools/cffLib/CFF2ToCFF.py +65 -10
  4. fontTools/cffLib/__init__.py +61 -26
  5. fontTools/cffLib/specializer.py +4 -1
  6. fontTools/cffLib/transforms.py +11 -6
  7. fontTools/config/__init__.py +15 -0
  8. fontTools/cu2qu/cu2qu.c +6567 -5579
  9. fontTools/cu2qu/cu2qu.cpython-313-aarch64-linux-musl.so +0 -0
  10. fontTools/cu2qu/cu2qu.py +36 -4
  11. fontTools/cu2qu/ufo.py +14 -0
  12. fontTools/designspaceLib/__init__.py +8 -3
  13. fontTools/designspaceLib/statNames.py +14 -7
  14. fontTools/feaLib/ast.py +24 -15
  15. fontTools/feaLib/builder.py +139 -66
  16. fontTools/feaLib/error.py +1 -1
  17. fontTools/feaLib/lexer.c +7038 -7995
  18. fontTools/feaLib/lexer.cpython-313-aarch64-linux-musl.so +0 -0
  19. fontTools/feaLib/parser.py +75 -40
  20. fontTools/feaLib/variableScalar.py +6 -1
  21. fontTools/fontBuilder.py +50 -44
  22. fontTools/merge/__init__.py +1 -1
  23. fontTools/merge/cmap.py +33 -1
  24. fontTools/merge/tables.py +12 -1
  25. fontTools/misc/bezierTools.c +14913 -17013
  26. fontTools/misc/bezierTools.cpython-313-aarch64-linux-musl.so +0 -0
  27. fontTools/misc/bezierTools.py +4 -1
  28. fontTools/misc/configTools.py +3 -1
  29. fontTools/misc/enumTools.py +23 -0
  30. fontTools/misc/etree.py +4 -27
  31. fontTools/misc/filesystem/__init__.py +68 -0
  32. fontTools/misc/filesystem/_base.py +134 -0
  33. fontTools/misc/filesystem/_copy.py +45 -0
  34. fontTools/misc/filesystem/_errors.py +54 -0
  35. fontTools/misc/filesystem/_info.py +75 -0
  36. fontTools/misc/filesystem/_osfs.py +164 -0
  37. fontTools/misc/filesystem/_path.py +67 -0
  38. fontTools/misc/filesystem/_subfs.py +92 -0
  39. fontTools/misc/filesystem/_tempfs.py +34 -0
  40. fontTools/misc/filesystem/_tools.py +34 -0
  41. fontTools/misc/filesystem/_walk.py +55 -0
  42. fontTools/misc/filesystem/_zipfs.py +204 -0
  43. fontTools/misc/fixedTools.py +1 -1
  44. fontTools/misc/loggingTools.py +1 -1
  45. fontTools/misc/psCharStrings.py +17 -2
  46. fontTools/misc/sstruct.py +2 -6
  47. fontTools/misc/symfont.py +6 -8
  48. fontTools/misc/testTools.py +5 -1
  49. fontTools/misc/textTools.py +4 -2
  50. fontTools/misc/visitor.py +32 -16
  51. fontTools/misc/xmlWriter.py +44 -8
  52. fontTools/mtiLib/__init__.py +1 -3
  53. fontTools/otlLib/builder.py +402 -155
  54. fontTools/otlLib/optimize/gpos.py +49 -63
  55. fontTools/pens/filterPen.py +218 -26
  56. fontTools/pens/momentsPen.c +5514 -5584
  57. fontTools/pens/momentsPen.cpython-313-aarch64-linux-musl.so +0 -0
  58. fontTools/pens/pointPen.py +61 -18
  59. fontTools/pens/roundingPen.py +2 -2
  60. fontTools/pens/t2CharStringPen.py +31 -11
  61. fontTools/qu2cu/qu2cu.c +6581 -6168
  62. fontTools/qu2cu/qu2cu.cpython-313-aarch64-linux-musl.so +0 -0
  63. fontTools/subset/__init__.py +283 -25
  64. fontTools/subset/svg.py +2 -3
  65. fontTools/ttLib/__init__.py +4 -0
  66. fontTools/ttLib/__main__.py +47 -8
  67. fontTools/ttLib/removeOverlaps.py +7 -5
  68. fontTools/ttLib/reorderGlyphs.py +8 -7
  69. fontTools/ttLib/sfnt.py +11 -9
  70. fontTools/ttLib/tables/D__e_b_g.py +20 -2
  71. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  72. fontTools/ttLib/tables/S__i_l_f.py +2 -2
  73. fontTools/ttLib/tables/T_S_I__0.py +14 -3
  74. fontTools/ttLib/tables/T_S_I__1.py +2 -5
  75. fontTools/ttLib/tables/T_S_I__5.py +18 -7
  76. fontTools/ttLib/tables/__init__.py +1 -0
  77. fontTools/ttLib/tables/_a_v_a_r.py +12 -3
  78. fontTools/ttLib/tables/_c_m_a_p.py +20 -7
  79. fontTools/ttLib/tables/_c_v_t.py +3 -2
  80. fontTools/ttLib/tables/_f_p_g_m.py +3 -1
  81. fontTools/ttLib/tables/_g_l_y_f.py +45 -21
  82. fontTools/ttLib/tables/_g_v_a_r.py +67 -19
  83. fontTools/ttLib/tables/_h_d_m_x.py +4 -4
  84. fontTools/ttLib/tables/_h_m_t_x.py +7 -3
  85. fontTools/ttLib/tables/_l_o_c_a.py +2 -2
  86. fontTools/ttLib/tables/_n_a_m_e.py +11 -6
  87. fontTools/ttLib/tables/_p_o_s_t.py +9 -7
  88. fontTools/ttLib/tables/otBase.py +5 -12
  89. fontTools/ttLib/tables/otConverters.py +5 -2
  90. fontTools/ttLib/tables/otData.py +1 -1
  91. fontTools/ttLib/tables/otTables.py +33 -30
  92. fontTools/ttLib/tables/otTraverse.py +2 -1
  93. fontTools/ttLib/tables/sbixStrike.py +3 -3
  94. fontTools/ttLib/ttFont.py +666 -120
  95. fontTools/ttLib/ttGlyphSet.py +0 -10
  96. fontTools/ttLib/woff2.py +10 -13
  97. fontTools/ttx.py +13 -1
  98. fontTools/ufoLib/__init__.py +300 -202
  99. fontTools/ufoLib/converters.py +103 -30
  100. fontTools/ufoLib/errors.py +8 -0
  101. fontTools/ufoLib/etree.py +1 -1
  102. fontTools/ufoLib/filenames.py +171 -106
  103. fontTools/ufoLib/glifLib.py +303 -205
  104. fontTools/ufoLib/kerning.py +98 -48
  105. fontTools/ufoLib/utils.py +46 -15
  106. fontTools/ufoLib/validators.py +121 -99
  107. fontTools/unicodedata/Blocks.py +35 -20
  108. fontTools/unicodedata/Mirrored.py +446 -0
  109. fontTools/unicodedata/ScriptExtensions.py +63 -37
  110. fontTools/unicodedata/Scripts.py +173 -152
  111. fontTools/unicodedata/__init__.py +10 -2
  112. fontTools/varLib/__init__.py +198 -109
  113. fontTools/varLib/avar/__init__.py +0 -0
  114. fontTools/varLib/avar/__main__.py +72 -0
  115. fontTools/varLib/avar/build.py +79 -0
  116. fontTools/varLib/avar/map.py +108 -0
  117. fontTools/varLib/avar/plan.py +1004 -0
  118. fontTools/varLib/{avar.py → avar/unbuild.py} +70 -59
  119. fontTools/varLib/avarPlanner.py +3 -999
  120. fontTools/varLib/featureVars.py +21 -7
  121. fontTools/varLib/hvar.py +113 -0
  122. fontTools/varLib/instancer/__init__.py +180 -65
  123. fontTools/varLib/interpolatableHelpers.py +3 -0
  124. fontTools/varLib/iup.c +7564 -6903
  125. fontTools/varLib/iup.cpython-313-aarch64-linux-musl.so +0 -0
  126. fontTools/varLib/models.py +17 -2
  127. fontTools/varLib/mutator.py +11 -0
  128. fontTools/varLib/varStore.py +10 -38
  129. fontTools/voltLib/__main__.py +206 -0
  130. fontTools/voltLib/ast.py +4 -0
  131. fontTools/voltLib/parser.py +16 -8
  132. fontTools/voltLib/voltToFea.py +347 -166
  133. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/METADATA +269 -1410
  134. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/RECORD +318 -294
  135. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/WHEEL +1 -1
  136. fonttools-4.61.1.dist-info/licenses/LICENSE.external +388 -0
  137. {fonttools-4.55.4.data → fonttools-4.61.1.data}/data/share/man/man1/ttx.1 +0 -0
  138. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/entry_points.txt +0 -0
  139. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info/licenses}/LICENSE +0 -0
  140. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/top_level.txt +0 -0
@@ -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
- http://unifiedfontobject.org
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
  """
@@ -13,34 +13,50 @@ glyph data. See the class doc string for details.
13
13
  from __future__ import annotations
14
14
 
15
15
  import logging
16
- import enum
17
- from warnings import warn
18
16
  from collections import OrderedDict
19
- import fs
20
- import fs.base
21
- import fs.errors
22
- import fs.osfs
23
- import fs.path
17
+ from typing import TYPE_CHECKING, Any, Optional, Union, cast
18
+ from warnings import warn
19
+
20
+ import fontTools.misc.filesystem as fs
21
+ from fontTools.misc import etree, plistlib
24
22
  from fontTools.misc.textTools import tobytes
25
- from fontTools.misc import plistlib
26
23
  from fontTools.pens.pointPen import AbstractPointPen, PointToSegmentPen
24
+ from fontTools.ufoLib import UFOFormatVersion, _UFOBaseIO
27
25
  from fontTools.ufoLib.errors import GlifLibError
28
26
  from fontTools.ufoLib.filenames import userNameToFileName
27
+ from fontTools.ufoLib.utils import (
28
+ BaseFormatVersion,
29
+ normalizeFormatVersion,
30
+ numberTypes,
31
+ )
29
32
  from fontTools.ufoLib.validators import (
30
- genericTypeValidator,
33
+ anchorsValidator,
31
34
  colorValidator,
35
+ genericTypeValidator,
36
+ glyphLibValidator,
32
37
  guidelinesValidator,
33
- anchorsValidator,
34
38
  identifierValidator,
35
39
  imageValidator,
36
- glyphLibValidator,
37
40
  )
38
- from fontTools.misc import etree
39
- from fontTools.ufoLib import _UFOBaseIO, UFOFormatVersion
40
- from fontTools.ufoLib.utils import numberTypes, _VersionTupleEnumMixin
41
+
42
+ if TYPE_CHECKING:
43
+ from collections.abc import Callable, Iterable, Set
44
+ from logging import Logger
45
+
46
+ from fontTools.annotations import (
47
+ ElementType,
48
+ FormatVersion,
49
+ FormatVersions,
50
+ GLIFFormatVersionInput,
51
+ GlyphNameToFileNameFunc,
52
+ IntFloat,
53
+ PathOrFS,
54
+ UFOFormatVersionInput,
55
+ )
56
+ from fontTools.misc.filesystem._base import FS
41
57
 
42
58
 
43
- __all__ = [
59
+ __all__: list[str] = [
44
60
  "GlyphSet",
45
61
  "GlifLibError",
46
62
  "readGlyphFromString",
@@ -48,7 +64,7 @@ __all__ = [
48
64
  "glyphNameToFileName",
49
65
  ]
50
66
 
51
- logger = logging.getLogger(__name__)
67
+ logger: Logger = logging.getLogger(__name__)
52
68
 
53
69
 
54
70
  # ---------
@@ -59,18 +75,29 @@ CONTENTS_FILENAME = "contents.plist"
59
75
  LAYERINFO_FILENAME = "layerinfo.plist"
60
76
 
61
77
 
62
- class GLIFFormatVersion(tuple, _VersionTupleEnumMixin, enum.Enum):
78
+ class GLIFFormatVersion(BaseFormatVersion):
79
+ """Class representing the versions of the .glif format supported by the UFO version in use.
80
+
81
+ For a given :mod:`fontTools.ufoLib.UFOFormatVersion`, the :func:`supported_versions` method will
82
+ return the supported versions of the GLIF file format. If the UFO version is unspecified, the
83
+ :func:`supported_versions` method will return all available GLIF format versions.
84
+ """
85
+
63
86
  FORMAT_1_0 = (1, 0)
64
87
  FORMAT_2_0 = (2, 0)
65
88
 
66
89
  @classmethod
67
- def default(cls, ufoFormatVersion=None):
90
+ def default(
91
+ cls, ufoFormatVersion: Optional[UFOFormatVersion] = None
92
+ ) -> GLIFFormatVersion:
68
93
  if ufoFormatVersion is not None:
69
94
  return max(cls.supported_versions(ufoFormatVersion))
70
95
  return super().default()
71
96
 
72
97
  @classmethod
73
- def supported_versions(cls, ufoFormatVersion=None):
98
+ def supported_versions(
99
+ cls, ufoFormatVersion: Optional[UFOFormatVersion] = None
100
+ ) -> frozenset[GLIFFormatVersion]:
74
101
  if ufoFormatVersion is None:
75
102
  # if ufo format unspecified, return all the supported GLIF formats
76
103
  return super().supported_versions()
@@ -81,10 +108,6 @@ class GLIFFormatVersion(tuple, _VersionTupleEnumMixin, enum.Enum):
81
108
  return frozenset(versions)
82
109
 
83
110
 
84
- # workaround for py3.11, see https://github.com/fonttools/fonttools/pull/2655
85
- GLIFFormatVersion.__str__ = _VersionTupleEnumMixin.__str__
86
-
87
-
88
111
  # ------------
89
112
  # Simple Glyph
90
113
  # ------------
@@ -96,11 +119,11 @@ class Glyph:
96
119
  the draw() or the drawPoints() method has been called.
97
120
  """
98
121
 
99
- def __init__(self, glyphName, glyphSet):
100
- self.glyphName = glyphName
101
- self.glyphSet = glyphSet
122
+ def __init__(self, glyphName: str, glyphSet: GlyphSet) -> None:
123
+ self.glyphName: str = glyphName
124
+ self.glyphSet: GlyphSet = glyphSet
102
125
 
103
- def draw(self, pen, outputImpliedClosingLine=False):
126
+ def draw(self, pen: Any, outputImpliedClosingLine: bool = False) -> None:
104
127
  """
105
128
  Draw this glyph onto a *FontTools* Pen.
106
129
  """
@@ -109,7 +132,7 @@ class Glyph:
109
132
  )
110
133
  self.drawPoints(pointPen)
111
134
 
112
- def drawPoints(self, pointPen):
135
+ def drawPoints(self, pointPen: AbstractPointPen) -> None:
113
136
  """
114
137
  Draw this glyph onto a PointPen.
115
138
  """
@@ -139,13 +162,13 @@ class GlyphSet(_UFOBaseIO):
139
162
 
140
163
  def __init__(
141
164
  self,
142
- path,
143
- glyphNameToFileNameFunc=None,
144
- ufoFormatVersion=None,
145
- validateRead=True,
146
- validateWrite=True,
147
- expectContentsFile=False,
148
- ):
165
+ path: PathOrFS,
166
+ glyphNameToFileNameFunc: GlyphNameToFileNameFunc = None,
167
+ ufoFormatVersion: UFOFormatVersionInput = None,
168
+ validateRead: bool = True,
169
+ validateWrite: bool = True,
170
+ expectContentsFile: bool = False,
171
+ ) -> None:
149
172
  """
150
173
  'path' should be a path (string) to an existing local directory, or
151
174
  an instance of fs.base.FS class.
@@ -163,7 +186,9 @@ class GlyphSet(_UFOBaseIO):
163
186
  are reading an existing UFO and ``False`` if you create a fresh glyph set.
164
187
  """
165
188
  try:
166
- ufoFormatVersion = UFOFormatVersion(ufoFormatVersion)
189
+ ufoFormatVersion = normalizeFormatVersion(
190
+ ufoFormatVersion, UFOFormatVersion
191
+ )
167
192
  except ValueError as e:
168
193
  from fontTools.ufoLib.errors import UnsupportedUFOFormat
169
194
 
@@ -176,10 +201,10 @@ class GlyphSet(_UFOBaseIO):
176
201
 
177
202
  if isinstance(path, str):
178
203
  try:
179
- filesystem = fs.osfs.OSFS(path)
204
+ filesystem: FS = fs.osfs.OSFS(path)
180
205
  except fs.errors.CreateFailed:
181
206
  raise GlifLibError("No glyphs directory '%s'" % path)
182
- self._shouldClose = True
207
+ self._shouldClose: bool = True
183
208
  elif isinstance(path, fs.base.FS):
184
209
  filesystem = path
185
210
  try:
@@ -199,26 +224,28 @@ class GlyphSet(_UFOBaseIO):
199
224
  # 'dirName' is kept for backward compatibility only, but it's DEPRECATED
200
225
  # as it's not guaranteed that it maps to an existing OSFS directory.
201
226
  # Client could use the FS api via the `self.fs` attribute instead.
202
- self.dirName = fs.path.parts(path)[-1]
203
- self.fs = filesystem
227
+ self.dirName: str = fs.path.basename(path)
228
+ self.fs: FS = filesystem
204
229
  # if glyphSet contains no 'contents.plist', we consider it empty
205
- self._havePreviousFile = filesystem.exists(CONTENTS_FILENAME)
230
+ self._havePreviousFile: bool = filesystem.exists(CONTENTS_FILENAME)
206
231
  if expectContentsFile and not self._havePreviousFile:
207
232
  raise GlifLibError(f"{CONTENTS_FILENAME} is missing.")
208
233
  # attribute kept for backward compatibility
209
- self.ufoFormatVersion = ufoFormatVersion.major
210
- self.ufoFormatVersionTuple = ufoFormatVersion
234
+ self.ufoFormatVersion: int = ufoFormatVersion.major
235
+ self.ufoFormatVersionTuple: UFOFormatVersion = ufoFormatVersion
211
236
  if glyphNameToFileNameFunc is None:
212
237
  glyphNameToFileNameFunc = glyphNameToFileName
213
- self.glyphNameToFileName = glyphNameToFileNameFunc
214
- self._validateRead = validateRead
215
- self._validateWrite = validateWrite
238
+ self.glyphNameToFileName: Callable[[str, set[str]], str] = (
239
+ glyphNameToFileNameFunc
240
+ )
241
+ self._validateRead: bool = validateRead
242
+ self._validateWrite: bool = validateWrite
216
243
  self._existingFileNames: set[str] | None = None
217
- self._reverseContents = None
244
+ self._reverseContents: Optional[dict[str, str]] = None
218
245
 
219
246
  self.rebuildContents()
220
247
 
221
- def rebuildContents(self, validateRead=None):
248
+ def rebuildContents(self, validateRead: bool = False) -> None:
222
249
  """
223
250
  Rebuild the contents dict by loading contents.plist.
224
251
 
@@ -246,11 +273,11 @@ class GlyphSet(_UFOBaseIO):
246
273
  )
247
274
  if invalidFormat:
248
275
  raise GlifLibError("%s is not properly formatted" % CONTENTS_FILENAME)
249
- self.contents = contents
276
+ self.contents: dict[str, str] = contents
250
277
  self._existingFileNames = None
251
278
  self._reverseContents = None
252
279
 
253
- def getReverseContents(self):
280
+ def getReverseContents(self) -> dict[str, str]:
254
281
  """
255
282
  Return a reversed dict of self.contents, mapping file names to
256
283
  glyph names. This is primarily an aid for custom glyph name to file
@@ -266,7 +293,7 @@ class GlyphSet(_UFOBaseIO):
266
293
  self._reverseContents = d
267
294
  return self._reverseContents
268
295
 
269
- def writeContents(self):
296
+ def writeContents(self) -> None:
270
297
  """
271
298
  Write the contents.plist file out to disk. Call this method when
272
299
  you're done writing glyphs.
@@ -275,7 +302,7 @@ class GlyphSet(_UFOBaseIO):
275
302
 
276
303
  # layer info
277
304
 
278
- def readLayerInfo(self, info, validateRead=None):
305
+ def readLayerInfo(self, info: Any, validateRead: Optional[bool] = None) -> None:
279
306
  """
280
307
  ``validateRead`` will validate the data, by default it is set to the
281
308
  class's ``validateRead`` value, can be overridden.
@@ -297,7 +324,7 @@ class GlyphSet(_UFOBaseIO):
297
324
  % attr
298
325
  )
299
326
 
300
- def writeLayerInfo(self, info, validateWrite=None):
327
+ def writeLayerInfo(self, info: Any, validateWrite: Optional[bool] = None) -> None:
301
328
  """
302
329
  ``validateWrite`` will validate the data, by default it is set to the
303
330
  class's ``validateWrite`` value, can be overridden.
@@ -333,7 +360,7 @@ class GlyphSet(_UFOBaseIO):
333
360
  # data empty, remove existing file
334
361
  self.fs.remove(LAYERINFO_FILENAME)
335
362
 
336
- def getGLIF(self, glyphName):
363
+ def getGLIF(self, glyphName: str) -> bytes:
337
364
  """
338
365
  Get the raw GLIF text for a given glyph name. This only works
339
366
  for GLIF files that are already on disk.
@@ -354,7 +381,7 @@ class GlyphSet(_UFOBaseIO):
354
381
  "does not exist on %s" % (fileName, glyphName, self.fs)
355
382
  )
356
383
 
357
- def getGLIFModificationTime(self, glyphName):
384
+ def getGLIFModificationTime(self, glyphName: str) -> Optional[float]:
358
385
  """
359
386
  Returns the modification time for the GLIF file with 'glyphName', as
360
387
  a floating point number giving the number of seconds since the epoch.
@@ -367,7 +394,13 @@ class GlyphSet(_UFOBaseIO):
367
394
 
368
395
  # reading/writing API
369
396
 
370
- def readGlyph(self, glyphName, glyphObject=None, pointPen=None, validate=None):
397
+ def readGlyph(
398
+ self,
399
+ glyphName: str,
400
+ glyphObject: Optional[Any] = None,
401
+ pointPen: Optional[AbstractPointPen] = None,
402
+ validate: Optional[bool] = None,
403
+ ) -> None:
371
404
  """
372
405
  Read a .glif file for 'glyphName' from the glyph set. The
373
406
  'glyphObject' argument can be any kind of object (even None);
@@ -444,12 +477,12 @@ class GlyphSet(_UFOBaseIO):
444
477
 
445
478
  def writeGlyph(
446
479
  self,
447
- glyphName,
448
- glyphObject=None,
449
- drawPointsFunc=None,
450
- formatVersion=None,
451
- validate=None,
452
- ):
480
+ glyphName: str,
481
+ glyphObject: Optional[Any] = None,
482
+ drawPointsFunc: Optional[Callable[[AbstractPointPen], None]] = None,
483
+ formatVersion: GLIFFormatVersionInput = None,
484
+ validate: Optional[bool] = None,
485
+ ) -> None:
453
486
  """
454
487
  Write a .glif file for 'glyphName' to the glyph set. The
455
488
  'glyphObject' argument can be any kind of object (even None);
@@ -499,7 +532,7 @@ class GlyphSet(_UFOBaseIO):
499
532
  formatVersion = GLIFFormatVersion.default(self.ufoFormatVersionTuple)
500
533
  else:
501
534
  try:
502
- formatVersion = GLIFFormatVersion(formatVersion)
535
+ formatVersion = normalizeFormatVersion(formatVersion, GLIFFormatVersion)
503
536
  except ValueError as e:
504
537
  from fontTools.ufoLib.errors import UnsupportedGLIFFormat
505
538
 
@@ -543,7 +576,7 @@ class GlyphSet(_UFOBaseIO):
543
576
  return
544
577
  self.fs.writebytes(fileName, data)
545
578
 
546
- def deleteGlyph(self, glyphName):
579
+ def deleteGlyph(self, glyphName: str) -> None:
547
580
  """Permanently delete the glyph from the glyph set on disk. Will
548
581
  raise KeyError if the glyph is not present in the glyph set.
549
582
  """
@@ -557,25 +590,27 @@ class GlyphSet(_UFOBaseIO):
557
590
 
558
591
  # dict-like support
559
592
 
560
- def keys(self):
593
+ def keys(self) -> list[str]:
561
594
  return list(self.contents.keys())
562
595
 
563
- def has_key(self, glyphName):
596
+ def has_key(self, glyphName: str) -> bool:
564
597
  return glyphName in self.contents
565
598
 
566
599
  __contains__ = has_key
567
600
 
568
- def __len__(self):
601
+ def __len__(self) -> int:
569
602
  return len(self.contents)
570
603
 
571
- def __getitem__(self, glyphName):
604
+ def __getitem__(self, glyphName: str) -> Any:
572
605
  if glyphName not in self.contents:
573
606
  raise KeyError(glyphName)
574
607
  return self.glyphClass(glyphName, self)
575
608
 
576
609
  # quickly fetch unicode values
577
610
 
578
- def getUnicodes(self, glyphNames=None):
611
+ def getUnicodes(
612
+ self, glyphNames: Optional[Iterable[str]] = None
613
+ ) -> dict[str, list[int]]:
579
614
  """
580
615
  Return a dictionary that maps glyph names to lists containing
581
616
  the unicode value[s] for that glyph, if any. This parses the .glif
@@ -590,7 +625,9 @@ class GlyphSet(_UFOBaseIO):
590
625
  unicodes[glyphName] = _fetchUnicodes(text)
591
626
  return unicodes
592
627
 
593
- def getComponentReferences(self, glyphNames=None):
628
+ def getComponentReferences(
629
+ self, glyphNames: Optional[Iterable[str]] = None
630
+ ) -> dict[str, list[str]]:
594
631
  """
595
632
  Return a dictionary that maps glyph names to lists containing the
596
633
  base glyph name of components in the glyph. This parses the .glif
@@ -605,7 +642,9 @@ class GlyphSet(_UFOBaseIO):
605
642
  components[glyphName] = _fetchComponentBases(text)
606
643
  return components
607
644
 
608
- def getImageReferences(self, glyphNames=None):
645
+ def getImageReferences(
646
+ self, glyphNames: Optional[Iterable[str]] = None
647
+ ) -> dict[str, Optional[str]]:
609
648
  """
610
649
  Return a dictionary that maps glyph names to the file name of the image
611
650
  referenced by the glyph. This parses the .glif files partially, so it is a
@@ -620,14 +659,14 @@ class GlyphSet(_UFOBaseIO):
620
659
  images[glyphName] = _fetchImageFileName(text)
621
660
  return images
622
661
 
623
- def close(self):
662
+ def close(self) -> None:
624
663
  if self._shouldClose:
625
664
  self.fs.close()
626
665
 
627
- def __enter__(self):
666
+ def __enter__(self) -> GlyphSet:
628
667
  return self
629
668
 
630
- def __exit__(self, exc_type, exc_value, exc_tb):
669
+ def __exit__(self, exc_type: Any, exc_value: Any, exc_tb: Any) -> None:
631
670
  self.close()
632
671
 
633
672
 
@@ -636,7 +675,7 @@ class GlyphSet(_UFOBaseIO):
636
675
  # -----------------------
637
676
 
638
677
 
639
- def glyphNameToFileName(glyphName, existingFileNames):
678
+ def glyphNameToFileName(glyphName: str, existingFileNames: Optional[set[str]]) -> str:
640
679
  """
641
680
  Wrapper around the userNameToFileName function in filenames.py
642
681
 
@@ -654,12 +693,12 @@ def glyphNameToFileName(glyphName, existingFileNames):
654
693
 
655
694
 
656
695
  def readGlyphFromString(
657
- aString,
658
- glyphObject=None,
659
- pointPen=None,
660
- formatVersions=None,
661
- validate=True,
662
- ):
696
+ aString: Union[str, bytes],
697
+ glyphObject: Optional[Any] = None,
698
+ pointPen: Optional[Any] = None,
699
+ formatVersions: FormatVersions = None,
700
+ validate: bool = True,
701
+ ) -> None:
663
702
  """
664
703
  Read .glif data from a string into a glyph object.
665
704
 
@@ -700,7 +739,7 @@ def readGlyphFromString(
700
739
 
701
740
  The formatVersions optional argument define the GLIF format versions
702
741
  that are allowed to be read.
703
- The type is Optional[Iterable[Tuple[int, int], int]]. It can contain
742
+ The type is Optional[Iterable[tuple[int, int], int]]. It can contain
704
743
  either integers (for the major versions to be allowed, with minor
705
744
  digits defaulting to 0), or tuples of integers to specify both
706
745
  (major, minor) versions.
@@ -712,12 +751,14 @@ def readGlyphFromString(
712
751
  tree = _glifTreeFromString(aString)
713
752
 
714
753
  if formatVersions is None:
715
- validFormatVersions = GLIFFormatVersion.supported_versions()
754
+ validFormatVersions: Set[GLIFFormatVersion] = (
755
+ GLIFFormatVersion.supported_versions()
756
+ )
716
757
  else:
717
758
  validFormatVersions, invalidFormatVersions = set(), set()
718
759
  for v in formatVersions:
719
760
  try:
720
- formatVersion = GLIFFormatVersion(v)
761
+ formatVersion = normalizeFormatVersion(v, GLIFFormatVersion)
721
762
  except ValueError:
722
763
  invalidFormatVersions.add(v)
723
764
  else:
@@ -738,16 +779,16 @@ def readGlyphFromString(
738
779
 
739
780
 
740
781
  def _writeGlyphToBytes(
741
- glyphName,
742
- glyphObject=None,
743
- drawPointsFunc=None,
744
- writer=None,
745
- formatVersion=None,
746
- validate=True,
747
- ):
782
+ glyphName: str,
783
+ glyphObject: Optional[Any] = None,
784
+ drawPointsFunc: Optional[Callable[[Any], None]] = None,
785
+ writer: Optional[Any] = None,
786
+ formatVersion: Optional[FormatVersion] = None,
787
+ validate: bool = True,
788
+ ) -> bytes:
748
789
  """Return .glif data for a glyph as a UTF-8 encoded bytes string."""
749
790
  try:
750
- formatVersion = GLIFFormatVersion(formatVersion)
791
+ formatVersion = normalizeFormatVersion(formatVersion, GLIFFormatVersion)
751
792
  except ValueError:
752
793
  from fontTools.ufoLib.errors import UnsupportedGLIFFormat
753
794
 
@@ -765,7 +806,7 @@ def _writeGlyphToBytes(
765
806
  if formatVersion.minor != 0:
766
807
  glyphAttrs["formatMinor"] = repr(formatVersion.minor)
767
808
  root = etree.Element("glyph", glyphAttrs)
768
- identifiers = set()
809
+ identifiers: set[str] = set()
769
810
  # advance
770
811
  _writeAdvance(glyphObject, root, validate)
771
812
  # unicodes
@@ -805,12 +846,12 @@ def _writeGlyphToBytes(
805
846
 
806
847
 
807
848
  def writeGlyphToString(
808
- glyphName,
809
- glyphObject=None,
810
- drawPointsFunc=None,
811
- formatVersion=None,
812
- validate=True,
813
- ):
849
+ glyphName: str,
850
+ glyphObject: Optional[Any] = None,
851
+ drawPointsFunc: Optional[Callable[[Any], None]] = None,
852
+ formatVersion: Optional[FormatVersion] = None,
853
+ validate: bool = True,
854
+ ) -> str:
814
855
  """
815
856
  Return .glif data for a glyph as a string. The XML declaration's
816
857
  encoding is always set to "UTF-8".
@@ -865,7 +906,7 @@ def writeGlyphToString(
865
906
  return data.decode("utf-8")
866
907
 
867
908
 
868
- def _writeAdvance(glyphObject, element, validate):
909
+ def _writeAdvance(glyphObject: Any, element: ElementType, validate: bool) -> None:
869
910
  width = getattr(glyphObject, "width", None)
870
911
  if width is not None:
871
912
  if validate and not isinstance(width, numberTypes):
@@ -890,8 +931,8 @@ def _writeAdvance(glyphObject, element, validate):
890
931
  etree.SubElement(element, "advance", dict(height=repr(height)))
891
932
 
892
933
 
893
- def _writeUnicodes(glyphObject, element, validate):
894
- unicodes = getattr(glyphObject, "unicodes", None)
934
+ def _writeUnicodes(glyphObject: Any, element: ElementType, validate: bool) -> None:
935
+ unicodes = getattr(glyphObject, "unicodes", [])
895
936
  if validate and isinstance(unicodes, int):
896
937
  unicodes = [unicodes]
897
938
  seen = set()
@@ -905,17 +946,21 @@ def _writeUnicodes(glyphObject, element, validate):
905
946
  etree.SubElement(element, "unicode", dict(hex=hexCode))
906
947
 
907
948
 
908
- def _writeNote(glyphObject, element, validate):
949
+ def _writeNote(glyphObject: Any, element: ElementType, validate: bool) -> None:
909
950
  note = getattr(glyphObject, "note", None)
910
951
  if validate and not isinstance(note, str):
911
952
  raise GlifLibError("note attribute must be str")
912
- note = note.strip()
913
- note = "\n" + note + "\n"
914
- etree.SubElement(element, "note").text = note
953
+ if isinstance(note, str):
954
+ note = note.strip()
955
+ note = "\n" + note + "\n"
956
+ etree.SubElement(element, "note").text = note
915
957
 
916
958
 
917
- def _writeImage(glyphObject, element, validate):
959
+ def _writeImage(glyphObject: Any, element: ElementType, validate: bool) -> None:
918
960
  image = getattr(glyphObject, "image", None)
961
+ if image is None:
962
+ return
963
+
919
964
  if validate and not imageValidator(image):
920
965
  raise GlifLibError(
921
966
  "image attribute must be a dict or dict-like object with the proper structure."
@@ -931,7 +976,9 @@ def _writeImage(glyphObject, element, validate):
931
976
  etree.SubElement(element, "image", attrs)
932
977
 
933
978
 
934
- def _writeGuidelines(glyphObject, element, identifiers, validate):
979
+ def _writeGuidelines(
980
+ glyphObject: Any, element: ElementType, identifiers: set[str], validate: bool
981
+ ) -> None:
935
982
  guidelines = getattr(glyphObject, "guidelines", [])
936
983
  if validate and not guidelinesValidator(guidelines):
937
984
  raise GlifLibError("guidelines attribute does not have the proper structure.")
@@ -961,7 +1008,7 @@ def _writeGuidelines(glyphObject, element, identifiers, validate):
961
1008
  etree.SubElement(element, "guideline", attrs)
962
1009
 
963
1010
 
964
- def _writeAnchorsFormat1(pen, anchors, validate):
1011
+ def _writeAnchorsFormat1(pen: Any, anchors: Any, validate: bool) -> None:
965
1012
  if validate and not anchorsValidator(anchors):
966
1013
  raise GlifLibError("anchors attribute does not have the proper structure.")
967
1014
  for anchor in anchors:
@@ -978,7 +1025,12 @@ def _writeAnchorsFormat1(pen, anchors, validate):
978
1025
  pen.endPath()
979
1026
 
980
1027
 
981
- def _writeAnchors(glyphObject, element, identifiers, validate):
1028
+ def _writeAnchors(
1029
+ glyphObject: Any,
1030
+ element: ElementType,
1031
+ identifiers: set[str],
1032
+ validate: bool,
1033
+ ) -> None:
982
1034
  anchors = getattr(glyphObject, "anchors", [])
983
1035
  if validate and not anchorsValidator(anchors):
984
1036
  raise GlifLibError("anchors attribute does not have the proper structure.")
@@ -1003,7 +1055,7 @@ def _writeAnchors(glyphObject, element, identifiers, validate):
1003
1055
  etree.SubElement(element, "anchor", attrs)
1004
1056
 
1005
1057
 
1006
- def _writeLib(glyphObject, element, validate):
1058
+ def _writeLib(glyphObject: Any, element: ElementType, validate: bool) -> None:
1007
1059
  lib = getattr(glyphObject, "lib", None)
1008
1060
  if not lib:
1009
1061
  # don't write empty lib
@@ -1029,7 +1081,7 @@ layerInfoVersion3ValueData = {
1029
1081
  }
1030
1082
 
1031
1083
 
1032
- def validateLayerInfoVersion3ValueForAttribute(attr, value):
1084
+ def validateLayerInfoVersion3ValueForAttribute(attr: str, value: Any) -> bool:
1033
1085
  """
1034
1086
  This performs very basic validation of the value for attribute
1035
1087
  following the UFO 3 fontinfo.plist specification. The results
@@ -1046,6 +1098,7 @@ def validateLayerInfoVersion3ValueForAttribute(attr, value):
1046
1098
  validator = dataValidationDict.get("valueValidator")
1047
1099
  valueOptions = dataValidationDict.get("valueOptions")
1048
1100
  # have specific options for the validator
1101
+ assert callable(validator)
1049
1102
  if valueOptions is not None:
1050
1103
  isValidValue = validator(value, valueOptions)
1051
1104
  # no specific options
@@ -1057,7 +1110,7 @@ def validateLayerInfoVersion3ValueForAttribute(attr, value):
1057
1110
  return isValidValue
1058
1111
 
1059
1112
 
1060
- def validateLayerInfoVersion3Data(infoData):
1113
+ def validateLayerInfoVersion3Data(infoData: dict[str, Any]) -> dict[str, Any]:
1061
1114
  """
1062
1115
  This performs very basic validation of the value for infoData
1063
1116
  following the UFO 3 layerinfo.plist specification. The results
@@ -1081,7 +1134,7 @@ def validateLayerInfoVersion3Data(infoData):
1081
1134
  # -----------------
1082
1135
 
1083
1136
 
1084
- def _glifTreeFromFile(aFile):
1137
+ def _glifTreeFromFile(aFile: Union[str, bytes, int]) -> ElementType:
1085
1138
  if etree._have_lxml:
1086
1139
  tree = etree.parse(aFile, parser=etree.XMLParser(remove_comments=True))
1087
1140
  else:
@@ -1094,7 +1147,7 @@ def _glifTreeFromFile(aFile):
1094
1147
  return root
1095
1148
 
1096
1149
 
1097
- def _glifTreeFromString(aString):
1150
+ def _glifTreeFromString(aString: Union[str, bytes]) -> ElementType:
1098
1151
  data = tobytes(aString, encoding="utf-8")
1099
1152
  try:
1100
1153
  if etree._have_lxml:
@@ -1112,16 +1165,18 @@ def _glifTreeFromString(aString):
1112
1165
 
1113
1166
 
1114
1167
  def _readGlyphFromTree(
1115
- tree,
1116
- glyphObject=None,
1117
- pointPen=None,
1118
- formatVersions=GLIFFormatVersion.supported_versions(),
1119
- validate=True,
1120
- ):
1168
+ tree: ElementType,
1169
+ glyphObject: Optional[Any] = None,
1170
+ pointPen: Optional[AbstractPointPen] = None,
1171
+ formatVersions: Set[GLIFFormatVersion] = GLIFFormatVersion.supported_versions(),
1172
+ validate: bool = True,
1173
+ ) -> None:
1121
1174
  # check the format version
1122
1175
  formatVersionMajor = tree.get("format")
1123
- if validate and formatVersionMajor is None:
1124
- raise GlifLibError("Unspecified format version in GLIF.")
1176
+ if formatVersionMajor is None:
1177
+ if validate:
1178
+ raise GlifLibError("Unspecified format version in GLIF.")
1179
+ formatVersionMajor = 0
1125
1180
  formatVersionMinor = tree.get("formatMinor", 0)
1126
1181
  try:
1127
1182
  formatVersion = GLIFFormatVersion(
@@ -1163,14 +1218,21 @@ def _readGlyphFromTree(
1163
1218
 
1164
1219
 
1165
1220
  def _readGlyphFromTreeFormat1(
1166
- tree, glyphObject=None, pointPen=None, validate=None, **kwargs
1167
- ):
1221
+ tree: ElementType,
1222
+ glyphObject: Optional[Any] = None,
1223
+ pointPen: Optional[AbstractPointPen] = None,
1224
+ validate: bool = False,
1225
+ **kwargs: Any,
1226
+ ) -> None:
1168
1227
  # get the name
1169
1228
  _readName(glyphObject, tree, validate)
1170
1229
  # populate the sub elements
1171
1230
  unicodes = []
1172
1231
  haveSeenAdvance = haveSeenOutline = haveSeenLib = haveSeenNote = False
1173
1232
  for element in tree:
1233
+ if glyphObject is None:
1234
+ continue
1235
+
1174
1236
  if element.tag == "outline":
1175
1237
  if validate:
1176
1238
  if haveSeenOutline:
@@ -1183,8 +1245,6 @@ def _readGlyphFromTreeFormat1(
1183
1245
  raise GlifLibError("Invalid outline structure.")
1184
1246
  haveSeenOutline = True
1185
1247
  buildOutlineFormat1(glyphObject, pointPen, element, validate)
1186
- elif glyphObject is None:
1187
- continue
1188
1248
  elif element.tag == "advance":
1189
1249
  if validate and haveSeenAdvance:
1190
1250
  raise GlifLibError("The advance element occurs more than once.")
@@ -1222,8 +1282,12 @@ def _readGlyphFromTreeFormat1(
1222
1282
 
1223
1283
 
1224
1284
  def _readGlyphFromTreeFormat2(
1225
- tree, glyphObject=None, pointPen=None, validate=None, formatMinor=0
1226
- ):
1285
+ tree: ElementType,
1286
+ glyphObject: Optional[Any] = None,
1287
+ pointPen: Optional[AbstractPointPen] = None,
1288
+ validate: bool = False,
1289
+ formatMinor: int = 0,
1290
+ ) -> None:
1227
1291
  # get the name
1228
1292
  _readName(glyphObject, tree, validate)
1229
1293
  # populate the sub elements
@@ -1233,8 +1297,10 @@ def _readGlyphFromTreeFormat2(
1233
1297
  haveSeenAdvance = haveSeenImage = haveSeenOutline = haveSeenLib = haveSeenNote = (
1234
1298
  False
1235
1299
  )
1236
- identifiers = set()
1300
+ identifiers: set[str] = set()
1237
1301
  for element in tree:
1302
+ if glyphObject is None:
1303
+ continue
1238
1304
  if element.tag == "outline":
1239
1305
  if validate:
1240
1306
  if haveSeenOutline:
@@ -1250,8 +1316,6 @@ def _readGlyphFromTreeFormat2(
1250
1316
  buildOutlineFormat2(
1251
1317
  glyphObject, pointPen, element, identifiers, validate
1252
1318
  )
1253
- elif glyphObject is None:
1254
- continue
1255
1319
  elif element.tag == "advance":
1256
1320
  if validate and haveSeenAdvance:
1257
1321
  raise GlifLibError("The advance element occurs more than once.")
@@ -1322,13 +1386,13 @@ def _readGlyphFromTreeFormat2(
1322
1386
  _relaxedSetattr(glyphObject, "anchors", anchors)
1323
1387
 
1324
1388
 
1325
- _READ_GLYPH_FROM_TREE_FUNCS = {
1389
+ _READ_GLYPH_FROM_TREE_FUNCS: dict[GLIFFormatVersion, Callable[..., Any]] = {
1326
1390
  GLIFFormatVersion.FORMAT_1_0: _readGlyphFromTreeFormat1,
1327
1391
  GLIFFormatVersion.FORMAT_2_0: _readGlyphFromTreeFormat2,
1328
1392
  }
1329
1393
 
1330
1394
 
1331
- def _readName(glyphObject, root, validate):
1395
+ def _readName(glyphObject: Optional[Any], root: ElementType, validate: bool) -> None:
1332
1396
  glyphName = root.get("name")
1333
1397
  if validate and not glyphName:
1334
1398
  raise GlifLibError("Empty glyph name in GLIF.")
@@ -1336,20 +1400,22 @@ def _readName(glyphObject, root, validate):
1336
1400
  _relaxedSetattr(glyphObject, "name", glyphName)
1337
1401
 
1338
1402
 
1339
- def _readAdvance(glyphObject, advance):
1403
+ def _readAdvance(glyphObject: Optional[Any], advance: ElementType) -> None:
1340
1404
  width = _number(advance.get("width", 0))
1341
1405
  _relaxedSetattr(glyphObject, "width", width)
1342
1406
  height = _number(advance.get("height", 0))
1343
1407
  _relaxedSetattr(glyphObject, "height", height)
1344
1408
 
1345
1409
 
1346
- def _readNote(glyphObject, note):
1410
+ def _readNote(glyphObject: Optional[Any], note: ElementType) -> None:
1411
+ if note.text is None:
1412
+ return
1347
1413
  lines = note.text.split("\n")
1348
1414
  note = "\n".join(line.strip() for line in lines if line.strip())
1349
1415
  _relaxedSetattr(glyphObject, "note", note)
1350
1416
 
1351
1417
 
1352
- def _readLib(glyphObject, lib, validate):
1418
+ def _readLib(glyphObject: Optional[Any], lib: ElementType, validate: bool) -> None:
1353
1419
  assert len(lib) == 1
1354
1420
  child = lib[0]
1355
1421
  plist = plistlib.fromtree(child)
@@ -1360,7 +1426,7 @@ def _readLib(glyphObject, lib, validate):
1360
1426
  _relaxedSetattr(glyphObject, "lib", plist)
1361
1427
 
1362
1428
 
1363
- def _readImage(glyphObject, image, validate):
1429
+ def _readImage(glyphObject: Optional[Any], image: ElementType, validate: bool) -> None:
1364
1430
  imageData = dict(image.attrib)
1365
1431
  for attr, default in _transformationInfo:
1366
1432
  value = imageData.get(attr, default)
@@ -1374,8 +1440,8 @@ def _readImage(glyphObject, image, validate):
1374
1440
  # GLIF to PointPen
1375
1441
  # ----------------
1376
1442
 
1377
- contourAttributesFormat2 = {"identifier"}
1378
- componentAttributesFormat1 = {
1443
+ contourAttributesFormat2: set[str] = {"identifier"}
1444
+ componentAttributesFormat1: set[str] = {
1379
1445
  "base",
1380
1446
  "xScale",
1381
1447
  "xyScale",
@@ -1384,16 +1450,21 @@ componentAttributesFormat1 = {
1384
1450
  "xOffset",
1385
1451
  "yOffset",
1386
1452
  }
1387
- componentAttributesFormat2 = componentAttributesFormat1 | {"identifier"}
1388
- pointAttributesFormat1 = {"x", "y", "type", "smooth", "name"}
1389
- pointAttributesFormat2 = pointAttributesFormat1 | {"identifier"}
1390
- pointSmoothOptions = {"no", "yes"}
1391
- pointTypeOptions = {"move", "line", "offcurve", "curve", "qcurve"}
1453
+ componentAttributesFormat2: set[str] = componentAttributesFormat1 | {"identifier"}
1454
+ pointAttributesFormat1: set[str] = {"x", "y", "type", "smooth", "name"}
1455
+ pointAttributesFormat2: set[str] = pointAttributesFormat1 | {"identifier"}
1456
+ pointSmoothOptions: set[str] = {"no", "yes"}
1457
+ pointTypeOptions: set[str] = {"move", "line", "offcurve", "curve", "qcurve"}
1392
1458
 
1393
1459
  # format 1
1394
1460
 
1395
1461
 
1396
- def buildOutlineFormat1(glyphObject, pen, outline, validate):
1462
+ def buildOutlineFormat1(
1463
+ glyphObject: Any,
1464
+ pen: Optional[AbstractPointPen],
1465
+ outline: Iterable[ElementType],
1466
+ validate: bool,
1467
+ ) -> None:
1397
1468
  anchors = []
1398
1469
  for element in outline:
1399
1470
  if element.tag == "contour":
@@ -1417,7 +1488,7 @@ def buildOutlineFormat1(glyphObject, pen, outline, validate):
1417
1488
  _relaxedSetattr(glyphObject, "anchors", anchors)
1418
1489
 
1419
1490
 
1420
- def _buildAnchorFormat1(point, validate):
1491
+ def _buildAnchorFormat1(point: ElementType, validate: bool) -> Optional[dict[str, Any]]:
1421
1492
  if point.get("type") != "move":
1422
1493
  return None
1423
1494
  name = point.get("name")
@@ -1427,15 +1498,19 @@ def _buildAnchorFormat1(point, validate):
1427
1498
  y = point.get("y")
1428
1499
  if validate and x is None:
1429
1500
  raise GlifLibError("Required x attribute is missing in point element.")
1501
+ assert x is not None
1430
1502
  if validate and y is None:
1431
1503
  raise GlifLibError("Required y attribute is missing in point element.")
1504
+ assert y is not None
1432
1505
  x = _number(x)
1433
1506
  y = _number(y)
1434
1507
  anchor = dict(x=x, y=y, name=name)
1435
1508
  return anchor
1436
1509
 
1437
1510
 
1438
- def _buildOutlineContourFormat1(pen, contour, validate):
1511
+ def _buildOutlineContourFormat1(
1512
+ pen: AbstractPointPen, contour: ElementType, validate: bool
1513
+ ) -> None:
1439
1514
  if validate and contour.attrib:
1440
1515
  raise GlifLibError("Unknown attributes in contour element.")
1441
1516
  pen.beginPath()
@@ -1450,7 +1525,9 @@ def _buildOutlineContourFormat1(pen, contour, validate):
1450
1525
  pen.endPath()
1451
1526
 
1452
1527
 
1453
- def _buildOutlinePointsFormat1(pen, contour):
1528
+ def _buildOutlinePointsFormat1(
1529
+ pen: AbstractPointPen, contour: list[dict[str, Any]]
1530
+ ) -> None:
1454
1531
  for point in contour:
1455
1532
  x = point["x"]
1456
1533
  y = point["y"]
@@ -1460,7 +1537,9 @@ def _buildOutlinePointsFormat1(pen, contour):
1460
1537
  pen.addPoint((x, y), segmentType=segmentType, smooth=smooth, name=name)
1461
1538
 
1462
1539
 
1463
- def _buildOutlineComponentFormat1(pen, component, validate):
1540
+ def _buildOutlineComponentFormat1(
1541
+ pen: AbstractPointPen, component: ElementType, validate: bool
1542
+ ) -> None:
1464
1543
  if validate:
1465
1544
  if len(component):
1466
1545
  raise GlifLibError("Unknown child elements of component element.")
@@ -1470,21 +1549,26 @@ def _buildOutlineComponentFormat1(pen, component, validate):
1470
1549
  baseGlyphName = component.get("base")
1471
1550
  if validate and baseGlyphName is None:
1472
1551
  raise GlifLibError("The base attribute is not defined in the component.")
1473
- transformation = []
1474
- for attr, default in _transformationInfo:
1475
- value = component.get(attr)
1476
- if value is None:
1477
- value = default
1478
- else:
1479
- value = _number(value)
1480
- transformation.append(value)
1481
- pen.addComponent(baseGlyphName, tuple(transformation))
1552
+ assert baseGlyphName is not None
1553
+ transformation = tuple(
1554
+ _number(component.get(attr) or default) for attr, default in _transformationInfo
1555
+ )
1556
+ transformation = cast(
1557
+ tuple[float, float, float, float, float, float], transformation
1558
+ )
1559
+ pen.addComponent(baseGlyphName, transformation)
1482
1560
 
1483
1561
 
1484
1562
  # format 2
1485
1563
 
1486
1564
 
1487
- def buildOutlineFormat2(glyphObject, pen, outline, identifiers, validate):
1565
+ def buildOutlineFormat2(
1566
+ glyphObject: Any,
1567
+ pen: AbstractPointPen,
1568
+ outline: Iterable[ElementType],
1569
+ identifiers: set[str],
1570
+ validate: bool,
1571
+ ) -> None:
1488
1572
  for element in outline:
1489
1573
  if element.tag == "contour":
1490
1574
  _buildOutlineContourFormat2(pen, element, identifiers, validate)
@@ -1494,7 +1578,9 @@ def buildOutlineFormat2(glyphObject, pen, outline, identifiers, validate):
1494
1578
  raise GlifLibError("Unknown element in outline element: %s" % element.tag)
1495
1579
 
1496
1580
 
1497
- def _buildOutlineContourFormat2(pen, contour, identifiers, validate):
1581
+ def _buildOutlineContourFormat2(
1582
+ pen: AbstractPointPen, contour: ElementType, identifiers: set[str], validate: bool
1583
+ ) -> None:
1498
1584
  if validate:
1499
1585
  for attr in contour.attrib.keys():
1500
1586
  if attr not in contourAttributesFormat2:
@@ -1527,7 +1613,12 @@ def _buildOutlineContourFormat2(pen, contour, identifiers, validate):
1527
1613
  pen.endPath()
1528
1614
 
1529
1615
 
1530
- def _buildOutlinePointsFormat2(pen, contour, identifiers, validate):
1616
+ def _buildOutlinePointsFormat2(
1617
+ pen: AbstractPointPen,
1618
+ contour: list[dict[str, Any]],
1619
+ identifiers: set[str],
1620
+ validate: bool,
1621
+ ) -> None:
1531
1622
  for point in contour:
1532
1623
  x = point["x"]
1533
1624
  y = point["y"]
@@ -1560,7 +1651,9 @@ def _buildOutlinePointsFormat2(pen, contour, identifiers, validate):
1560
1651
  )
1561
1652
 
1562
1653
 
1563
- def _buildOutlineComponentFormat2(pen, component, identifiers, validate):
1654
+ def _buildOutlineComponentFormat2(
1655
+ pen: AbstractPointPen, component: ElementType, identifiers: set[str], validate: bool
1656
+ ) -> None:
1564
1657
  if validate:
1565
1658
  if len(component):
1566
1659
  raise GlifLibError("Unknown child elements of component element.")
@@ -1570,14 +1663,13 @@ def _buildOutlineComponentFormat2(pen, component, identifiers, validate):
1570
1663
  baseGlyphName = component.get("base")
1571
1664
  if validate and baseGlyphName is None:
1572
1665
  raise GlifLibError("The base attribute is not defined in the component.")
1573
- transformation = []
1574
- for attr, default in _transformationInfo:
1575
- value = component.get(attr)
1576
- if value is None:
1577
- value = default
1578
- else:
1579
- value = _number(value)
1580
- transformation.append(value)
1666
+ assert baseGlyphName is not None
1667
+ transformation = tuple(
1668
+ _number(component.get(attr) or default) for attr, default in _transformationInfo
1669
+ )
1670
+ transformation = cast(
1671
+ tuple[float, float, float, float, float, float], transformation
1672
+ )
1581
1673
  identifier = component.get("identifier")
1582
1674
  if identifier is not None:
1583
1675
  if validate:
@@ -1589,9 +1681,9 @@ def _buildOutlineComponentFormat2(pen, component, identifiers, validate):
1589
1681
  raise GlifLibError("The identifier %s is not valid." % identifier)
1590
1682
  identifiers.add(identifier)
1591
1683
  try:
1592
- pen.addComponent(baseGlyphName, tuple(transformation), identifier=identifier)
1684
+ pen.addComponent(baseGlyphName, transformation, identifier=identifier)
1593
1685
  except TypeError:
1594
- pen.addComponent(baseGlyphName, tuple(transformation))
1686
+ pen.addComponent(baseGlyphName, transformation)
1595
1687
  warn(
1596
1688
  "The addComponent method needs an identifier kwarg. The component's identifier value has been discarded.",
1597
1689
  DeprecationWarning,
@@ -1710,14 +1802,14 @@ def _validateAndMassagePointStructures(
1710
1802
  # ---------------------
1711
1803
 
1712
1804
 
1713
- def _relaxedSetattr(object, attr, value):
1805
+ def _relaxedSetattr(object: Any, attr: str, value: Any) -> None:
1714
1806
  try:
1715
1807
  setattr(object, attr, value)
1716
1808
  except AttributeError:
1717
1809
  pass
1718
1810
 
1719
1811
 
1720
- def _number(s):
1812
+ def _number(s: Union[str, int, float]) -> IntFloat:
1721
1813
  """
1722
1814
  Given a numeric string, return an integer or a float, whichever
1723
1815
  the string indicates. _number("1") will return the integer 1,
@@ -1733,7 +1825,7 @@ def _number(s):
1733
1825
  GlifLibError: Could not convert a to an int or float.
1734
1826
  """
1735
1827
  try:
1736
- n = int(s)
1828
+ n: IntFloat = int(s)
1737
1829
  return n
1738
1830
  except ValueError:
1739
1831
  pass
@@ -1756,21 +1848,21 @@ class _DoneParsing(Exception):
1756
1848
 
1757
1849
 
1758
1850
  class _BaseParser:
1759
- def __init__(self):
1760
- self._elementStack = []
1851
+ def __init__(self) -> None:
1852
+ self._elementStack: list[str] = []
1761
1853
 
1762
- def parse(self, text):
1854
+ def parse(self, text: bytes):
1763
1855
  from xml.parsers.expat import ParserCreate
1764
1856
 
1765
1857
  parser = ParserCreate()
1766
1858
  parser.StartElementHandler = self.startElementHandler
1767
1859
  parser.EndElementHandler = self.endElementHandler
1768
- parser.Parse(text, 1)
1860
+ parser.Parse(text, True)
1769
1861
 
1770
- def startElementHandler(self, name, attrs):
1862
+ def startElementHandler(self, name: str, attrs: Any) -> None:
1771
1863
  self._elementStack.append(name)
1772
1864
 
1773
- def endElementHandler(self, name):
1865
+ def endElementHandler(self, name: str) -> None:
1774
1866
  other = self._elementStack.pop(-1)
1775
1867
  assert other == name
1776
1868
 
@@ -1778,7 +1870,7 @@ class _BaseParser:
1778
1870
  # unicodes
1779
1871
 
1780
1872
 
1781
- def _fetchUnicodes(glif):
1873
+ def _fetchUnicodes(glif: bytes) -> list[int]:
1782
1874
  """
1783
1875
  Get a list of unicodes listed in glif.
1784
1876
  """
@@ -1788,11 +1880,11 @@ def _fetchUnicodes(glif):
1788
1880
 
1789
1881
 
1790
1882
  class _FetchUnicodesParser(_BaseParser):
1791
- def __init__(self):
1792
- self.unicodes = []
1883
+ def __init__(self) -> None:
1884
+ self.unicodes: list[int] = []
1793
1885
  super().__init__()
1794
1886
 
1795
- def startElementHandler(self, name, attrs):
1887
+ def startElementHandler(self, name: str, attrs: dict[str, str]) -> None:
1796
1888
  if (
1797
1889
  name == "unicode"
1798
1890
  and self._elementStack
@@ -1801,9 +1893,9 @@ class _FetchUnicodesParser(_BaseParser):
1801
1893
  value = attrs.get("hex")
1802
1894
  if value is not None:
1803
1895
  try:
1804
- value = int(value, 16)
1805
- if value not in self.unicodes:
1806
- self.unicodes.append(value)
1896
+ intValue = int(value, 16)
1897
+ if intValue not in self.unicodes:
1898
+ self.unicodes.append(intValue)
1807
1899
  except ValueError:
1808
1900
  pass
1809
1901
  super().startElementHandler(name, attrs)
@@ -1812,7 +1904,7 @@ class _FetchUnicodesParser(_BaseParser):
1812
1904
  # image
1813
1905
 
1814
1906
 
1815
- def _fetchImageFileName(glif):
1907
+ def _fetchImageFileName(glif: bytes) -> Optional[str]:
1816
1908
  """
1817
1909
  The image file name (if any) from glif.
1818
1910
  """
@@ -1825,11 +1917,11 @@ def _fetchImageFileName(glif):
1825
1917
 
1826
1918
 
1827
1919
  class _FetchImageFileNameParser(_BaseParser):
1828
- def __init__(self):
1829
- self.fileName = None
1920
+ def __init__(self) -> None:
1921
+ self.fileName: Optional[str] = None
1830
1922
  super().__init__()
1831
1923
 
1832
- def startElementHandler(self, name, attrs):
1924
+ def startElementHandler(self, name: str, attrs: dict[str, str]) -> None:
1833
1925
  if name == "image" and self._elementStack and self._elementStack[-1] == "glyph":
1834
1926
  self.fileName = attrs.get("fileName")
1835
1927
  raise _DoneParsing
@@ -1839,7 +1931,7 @@ class _FetchImageFileNameParser(_BaseParser):
1839
1931
  # component references
1840
1932
 
1841
1933
 
1842
- def _fetchComponentBases(glif):
1934
+ def _fetchComponentBases(glif: bytes) -> list[str]:
1843
1935
  """
1844
1936
  Get a list of component base glyphs listed in glif.
1845
1937
  """
@@ -1852,11 +1944,11 @@ def _fetchComponentBases(glif):
1852
1944
 
1853
1945
 
1854
1946
  class _FetchComponentBasesParser(_BaseParser):
1855
- def __init__(self):
1856
- self.bases = []
1947
+ def __init__(self) -> None:
1948
+ self.bases: list[str] = []
1857
1949
  super().__init__()
1858
1950
 
1859
- def startElementHandler(self, name, attrs):
1951
+ def startElementHandler(self, name: str, attrs: dict[str, str]) -> None:
1860
1952
  if (
1861
1953
  name == "component"
1862
1954
  and self._elementStack
@@ -1867,7 +1959,7 @@ class _FetchComponentBasesParser(_BaseParser):
1867
1959
  self.bases.append(base)
1868
1960
  super().startElementHandler(name, attrs)
1869
1961
 
1870
- def endElementHandler(self, name):
1962
+ def endElementHandler(self, name: str) -> None:
1871
1963
  if name == "outline":
1872
1964
  raise _DoneParsing
1873
1965
  super().endElementHandler(name)
@@ -1877,7 +1969,7 @@ class _FetchComponentBasesParser(_BaseParser):
1877
1969
  # GLIF Point Pen
1878
1970
  # --------------
1879
1971
 
1880
- _transformationInfo = [
1972
+ _transformationInfo: list[tuple[str, int]] = [
1881
1973
  # field name, default value
1882
1974
  ("xScale", 1),
1883
1975
  ("xyScale", 0),
@@ -1894,15 +1986,21 @@ class GLIFPointPen(AbstractPointPen):
1894
1986
  part of .glif files.
1895
1987
  """
1896
1988
 
1897
- def __init__(self, element, formatVersion=None, identifiers=None, validate=True):
1989
+ def __init__(
1990
+ self,
1991
+ element: ElementType,
1992
+ formatVersion: Optional[FormatVersion] = None,
1993
+ identifiers: Optional[set[str]] = None,
1994
+ validate: bool = True,
1995
+ ) -> None:
1898
1996
  if identifiers is None:
1899
1997
  identifiers = set()
1900
- self.formatVersion = GLIFFormatVersion(formatVersion)
1998
+ self.formatVersion = normalizeFormatVersion(formatVersion, GLIFFormatVersion)
1901
1999
  self.identifiers = identifiers
1902
2000
  self.outline = element
1903
2001
  self.contour = None
1904
2002
  self.prevOffCurveCount = 0
1905
- self.prevPointTypes = []
2003
+ self.prevPointTypes: list[str] = []
1906
2004
  self.validate = validate
1907
2005
 
1908
2006
  def beginPath(self, identifier=None, **kwargs):