fonttools 4.59.1__cp310-cp310-win_amd64.whl → 4.60.0__cp310-cp310-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fonttools might be problematic. Click here for more details.
- fontTools/__init__.py +1 -1
- fontTools/annotations.py +30 -0
- fontTools/cu2qu/cu2qu.c +1185 -971
- fontTools/cu2qu/cu2qu.cp310-win_amd64.pyd +0 -0
- fontTools/cu2qu/cu2qu.py +36 -4
- fontTools/feaLib/builder.py +9 -3
- fontTools/feaLib/lexer.c +18 -12
- fontTools/feaLib/lexer.cp310-win_amd64.pyd +0 -0
- fontTools/feaLib/parser.py +11 -1
- fontTools/feaLib/variableScalar.py +6 -1
- fontTools/misc/bezierTools.c +18 -12
- fontTools/misc/bezierTools.cp310-win_amd64.pyd +0 -0
- fontTools/misc/enumTools.py +23 -0
- fontTools/misc/textTools.py +4 -2
- fontTools/pens/filterPen.py +218 -26
- fontTools/pens/momentsPen.c +18 -12
- fontTools/pens/momentsPen.cp310-win_amd64.pyd +0 -0
- fontTools/pens/pointPen.py +40 -6
- fontTools/qu2cu/qu2cu.c +30 -16
- fontTools/qu2cu/qu2cu.cp310-win_amd64.pyd +0 -0
- fontTools/subset/__init__.py +1 -0
- fontTools/ttLib/tables/_a_v_a_r.py +4 -2
- fontTools/ttLib/tables/_n_a_m_e.py +11 -6
- fontTools/ttLib/tables/_p_o_s_t.py +5 -5
- fontTools/ufoLib/__init__.py +279 -176
- fontTools/ufoLib/converters.py +14 -5
- fontTools/ufoLib/filenames.py +16 -6
- fontTools/ufoLib/glifLib.py +286 -190
- fontTools/ufoLib/kerning.py +32 -12
- fontTools/ufoLib/utils.py +41 -13
- fontTools/ufoLib/validators.py +121 -97
- fontTools/varLib/__init__.py +80 -1
- fontTools/varLib/avar/__init__.py +0 -0
- fontTools/varLib/avar/__main__.py +72 -0
- fontTools/varLib/avar/build.py +79 -0
- fontTools/varLib/avar/map.py +108 -0
- fontTools/varLib/avar/plan.py +1004 -0
- fontTools/varLib/{avar.py → avar/unbuild.py} +70 -59
- fontTools/varLib/avarPlanner.py +3 -999
- fontTools/varLib/instancer/__init__.py +56 -18
- fontTools/varLib/interpolatableHelpers.py +3 -0
- fontTools/varLib/iup.c +24 -14
- fontTools/varLib/iup.cp310-win_amd64.pyd +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/METADATA +43 -2
- {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/RECORD +51 -44
- {fonttools-4.59.1.data → fonttools-4.60.0.data}/data/share/man/man1/ttx.1 +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/WHEEL +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/entry_points.txt +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/licenses/LICENSE +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/licenses/LICENSE.external +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/top_level.txt +0 -0
fontTools/ufoLib/glifLib.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
216
|
-
|
|
217
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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[
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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",
|
|
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
|
|
915
|
-
|
|
916
|
-
|
|
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(
|
|
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(
|
|
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
|
|
1126
|
-
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
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,
|
|
1684
|
+
pen.addComponent(baseGlyphName, transformation, identifier=identifier)
|
|
1595
1685
|
except TypeError:
|
|
1596
|
-
pen.addComponent(baseGlyphName,
|
|
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,
|
|
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
|
-
|
|
1807
|
-
if
|
|
1808
|
-
self.unicodes.append(
|
|
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__(
|
|
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 =
|
|
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):
|