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