fonttools 4.55.4__cp313-cp313-musllinux_1_2_aarch64.whl → 4.61.1__cp313-cp313-musllinux_1_2_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fontTools/__init__.py +1 -1
- fontTools/annotations.py +30 -0
- fontTools/cffLib/CFF2ToCFF.py +65 -10
- fontTools/cffLib/__init__.py +61 -26
- fontTools/cffLib/specializer.py +4 -1
- fontTools/cffLib/transforms.py +11 -6
- fontTools/config/__init__.py +15 -0
- fontTools/cu2qu/cu2qu.c +6567 -5579
- fontTools/cu2qu/cu2qu.cpython-313-aarch64-linux-musl.so +0 -0
- fontTools/cu2qu/cu2qu.py +36 -4
- fontTools/cu2qu/ufo.py +14 -0
- fontTools/designspaceLib/__init__.py +8 -3
- fontTools/designspaceLib/statNames.py +14 -7
- fontTools/feaLib/ast.py +24 -15
- fontTools/feaLib/builder.py +139 -66
- fontTools/feaLib/error.py +1 -1
- fontTools/feaLib/lexer.c +7038 -7995
- fontTools/feaLib/lexer.cpython-313-aarch64-linux-musl.so +0 -0
- fontTools/feaLib/parser.py +75 -40
- fontTools/feaLib/variableScalar.py +6 -1
- fontTools/fontBuilder.py +50 -44
- fontTools/merge/__init__.py +1 -1
- fontTools/merge/cmap.py +33 -1
- fontTools/merge/tables.py +12 -1
- fontTools/misc/bezierTools.c +14913 -17013
- fontTools/misc/bezierTools.cpython-313-aarch64-linux-musl.so +0 -0
- fontTools/misc/bezierTools.py +4 -1
- fontTools/misc/configTools.py +3 -1
- fontTools/misc/enumTools.py +23 -0
- fontTools/misc/etree.py +4 -27
- fontTools/misc/filesystem/__init__.py +68 -0
- fontTools/misc/filesystem/_base.py +134 -0
- fontTools/misc/filesystem/_copy.py +45 -0
- fontTools/misc/filesystem/_errors.py +54 -0
- fontTools/misc/filesystem/_info.py +75 -0
- fontTools/misc/filesystem/_osfs.py +164 -0
- fontTools/misc/filesystem/_path.py +67 -0
- fontTools/misc/filesystem/_subfs.py +92 -0
- fontTools/misc/filesystem/_tempfs.py +34 -0
- fontTools/misc/filesystem/_tools.py +34 -0
- fontTools/misc/filesystem/_walk.py +55 -0
- fontTools/misc/filesystem/_zipfs.py +204 -0
- fontTools/misc/fixedTools.py +1 -1
- fontTools/misc/loggingTools.py +1 -1
- fontTools/misc/psCharStrings.py +17 -2
- fontTools/misc/sstruct.py +2 -6
- fontTools/misc/symfont.py +6 -8
- fontTools/misc/testTools.py +5 -1
- fontTools/misc/textTools.py +4 -2
- fontTools/misc/visitor.py +32 -16
- fontTools/misc/xmlWriter.py +44 -8
- fontTools/mtiLib/__init__.py +1 -3
- fontTools/otlLib/builder.py +402 -155
- fontTools/otlLib/optimize/gpos.py +49 -63
- fontTools/pens/filterPen.py +218 -26
- fontTools/pens/momentsPen.c +5514 -5584
- fontTools/pens/momentsPen.cpython-313-aarch64-linux-musl.so +0 -0
- fontTools/pens/pointPen.py +61 -18
- fontTools/pens/roundingPen.py +2 -2
- fontTools/pens/t2CharStringPen.py +31 -11
- fontTools/qu2cu/qu2cu.c +6581 -6168
- fontTools/qu2cu/qu2cu.cpython-313-aarch64-linux-musl.so +0 -0
- fontTools/subset/__init__.py +283 -25
- fontTools/subset/svg.py +2 -3
- fontTools/ttLib/__init__.py +4 -0
- fontTools/ttLib/__main__.py +47 -8
- fontTools/ttLib/removeOverlaps.py +7 -5
- fontTools/ttLib/reorderGlyphs.py +8 -7
- fontTools/ttLib/sfnt.py +11 -9
- fontTools/ttLib/tables/D__e_b_g.py +20 -2
- fontTools/ttLib/tables/G_V_A_R_.py +5 -0
- fontTools/ttLib/tables/S__i_l_f.py +2 -2
- fontTools/ttLib/tables/T_S_I__0.py +14 -3
- fontTools/ttLib/tables/T_S_I__1.py +2 -5
- fontTools/ttLib/tables/T_S_I__5.py +18 -7
- fontTools/ttLib/tables/__init__.py +1 -0
- fontTools/ttLib/tables/_a_v_a_r.py +12 -3
- fontTools/ttLib/tables/_c_m_a_p.py +20 -7
- fontTools/ttLib/tables/_c_v_t.py +3 -2
- fontTools/ttLib/tables/_f_p_g_m.py +3 -1
- fontTools/ttLib/tables/_g_l_y_f.py +45 -21
- fontTools/ttLib/tables/_g_v_a_r.py +67 -19
- fontTools/ttLib/tables/_h_d_m_x.py +4 -4
- fontTools/ttLib/tables/_h_m_t_x.py +7 -3
- fontTools/ttLib/tables/_l_o_c_a.py +2 -2
- fontTools/ttLib/tables/_n_a_m_e.py +11 -6
- fontTools/ttLib/tables/_p_o_s_t.py +9 -7
- fontTools/ttLib/tables/otBase.py +5 -12
- fontTools/ttLib/tables/otConverters.py +5 -2
- fontTools/ttLib/tables/otData.py +1 -1
- fontTools/ttLib/tables/otTables.py +33 -30
- fontTools/ttLib/tables/otTraverse.py +2 -1
- fontTools/ttLib/tables/sbixStrike.py +3 -3
- fontTools/ttLib/ttFont.py +666 -120
- fontTools/ttLib/ttGlyphSet.py +0 -10
- fontTools/ttLib/woff2.py +10 -13
- fontTools/ttx.py +13 -1
- fontTools/ufoLib/__init__.py +300 -202
- fontTools/ufoLib/converters.py +103 -30
- fontTools/ufoLib/errors.py +8 -0
- fontTools/ufoLib/etree.py +1 -1
- fontTools/ufoLib/filenames.py +171 -106
- fontTools/ufoLib/glifLib.py +303 -205
- fontTools/ufoLib/kerning.py +98 -48
- fontTools/ufoLib/utils.py +46 -15
- fontTools/ufoLib/validators.py +121 -99
- fontTools/unicodedata/Blocks.py +35 -20
- fontTools/unicodedata/Mirrored.py +446 -0
- fontTools/unicodedata/ScriptExtensions.py +63 -37
- fontTools/unicodedata/Scripts.py +173 -152
- fontTools/unicodedata/__init__.py +10 -2
- fontTools/varLib/__init__.py +198 -109
- 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/featureVars.py +21 -7
- fontTools/varLib/hvar.py +113 -0
- fontTools/varLib/instancer/__init__.py +180 -65
- fontTools/varLib/interpolatableHelpers.py +3 -0
- fontTools/varLib/iup.c +7564 -6903
- fontTools/varLib/iup.cpython-313-aarch64-linux-musl.so +0 -0
- fontTools/varLib/models.py +17 -2
- fontTools/varLib/mutator.py +11 -0
- fontTools/varLib/varStore.py +10 -38
- fontTools/voltLib/__main__.py +206 -0
- fontTools/voltLib/ast.py +4 -0
- fontTools/voltLib/parser.py +16 -8
- fontTools/voltLib/voltToFea.py +347 -166
- {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/METADATA +269 -1410
- {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/RECORD +318 -294
- {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/WHEEL +1 -1
- fonttools-4.61.1.dist-info/licenses/LICENSE.external +388 -0
- {fonttools-4.55.4.data → fonttools-4.61.1.data}/data/share/man/man1/ttx.1 +0 -0
- {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/entry_points.txt +0 -0
- {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info/licenses}/LICENSE +0 -0
- {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/top_level.txt +0 -0
fontTools/ufoLib/__init__.py
CHANGED
|
@@ -32,30 +32,57 @@ Value conversion functions are available for converting
|
|
|
32
32
|
- :func:`.convertFontInfoValueForAttributeFromVersion3ToVersion2`
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
|
-
import
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import enum
|
|
38
38
|
import logging
|
|
39
|
+
import os
|
|
39
40
|
import zipfile
|
|
40
|
-
import enum
|
|
41
41
|
from collections import OrderedDict
|
|
42
|
-
import
|
|
43
|
-
import
|
|
44
|
-
import
|
|
45
|
-
|
|
46
|
-
import fs
|
|
47
|
-
import fs.osfs
|
|
48
|
-
import fs.zipfs
|
|
49
|
-
import fs.tempfs
|
|
50
|
-
import fs.tools
|
|
42
|
+
from copy import deepcopy
|
|
43
|
+
from os import fsdecode
|
|
44
|
+
from typing import IO, TYPE_CHECKING, Any, Optional, Union, cast
|
|
45
|
+
|
|
46
|
+
from fontTools.misc import filesystem as fs
|
|
51
47
|
from fontTools.misc import plistlib
|
|
52
|
-
from fontTools.ufoLib.validators import *
|
|
53
|
-
from fontTools.ufoLib.filenames import userNameToFileName
|
|
54
48
|
from fontTools.ufoLib.converters import convertUFO1OrUFO2KerningToUFO3Kerning
|
|
55
49
|
from fontTools.ufoLib.errors import UFOLibError
|
|
56
|
-
from fontTools.ufoLib.
|
|
50
|
+
from fontTools.ufoLib.filenames import userNameToFileName
|
|
51
|
+
from fontTools.ufoLib.utils import (
|
|
52
|
+
BaseFormatVersion,
|
|
53
|
+
normalizeFormatVersion,
|
|
54
|
+
numberTypes,
|
|
55
|
+
)
|
|
56
|
+
from fontTools.ufoLib.validators import *
|
|
57
|
+
|
|
58
|
+
if TYPE_CHECKING:
|
|
59
|
+
from logging import Logger
|
|
60
|
+
|
|
61
|
+
from fontTools.annotations import (
|
|
62
|
+
GlyphNameToFileNameFunc,
|
|
63
|
+
K,
|
|
64
|
+
KerningDict,
|
|
65
|
+
KerningGroups,
|
|
66
|
+
KerningNested,
|
|
67
|
+
PathOrFS,
|
|
68
|
+
PathStr,
|
|
69
|
+
UFOFormatVersionInput,
|
|
70
|
+
V,
|
|
71
|
+
)
|
|
72
|
+
from fontTools.misc.filesystem._base import FS
|
|
73
|
+
from fontTools.ufoLib.glifLib import GlyphSet
|
|
74
|
+
|
|
75
|
+
KerningGroupRenameMaps = dict[str, dict[str, str]]
|
|
76
|
+
LibDict = dict[str, Any]
|
|
77
|
+
LayerOrderList = Optional[list[Optional[str]]]
|
|
78
|
+
AttributeDataDict = dict[str, Any]
|
|
79
|
+
FontInfoAttributes = dict[str, AttributeDataDict]
|
|
80
|
+
|
|
81
|
+
# client code can check this to see if the upstream `fs` package is being used
|
|
82
|
+
haveFS = fs._haveFS
|
|
57
83
|
|
|
58
|
-
__all__ = [
|
|
84
|
+
__all__: list[str] = [
|
|
85
|
+
"haveFS",
|
|
59
86
|
"makeUFOPath",
|
|
60
87
|
"UFOLibError",
|
|
61
88
|
"UFOReader",
|
|
@@ -72,43 +99,37 @@ __all__ = [
|
|
|
72
99
|
"convertFontInfoValueForAttributeFromVersion2ToVersion1",
|
|
73
100
|
]
|
|
74
101
|
|
|
75
|
-
__version__ = "3.0.0"
|
|
102
|
+
__version__: str = "3.0.0"
|
|
76
103
|
|
|
77
104
|
|
|
78
|
-
logger = logging.getLogger(__name__)
|
|
105
|
+
logger: Logger = logging.getLogger(__name__)
|
|
79
106
|
|
|
80
107
|
|
|
81
108
|
# ---------
|
|
82
109
|
# Constants
|
|
83
110
|
# ---------
|
|
84
111
|
|
|
85
|
-
DEFAULT_GLYPHS_DIRNAME = "glyphs"
|
|
86
|
-
DATA_DIRNAME = "data"
|
|
87
|
-
IMAGES_DIRNAME = "images"
|
|
88
|
-
METAINFO_FILENAME = "metainfo.plist"
|
|
89
|
-
FONTINFO_FILENAME = "fontinfo.plist"
|
|
90
|
-
LIB_FILENAME = "lib.plist"
|
|
91
|
-
GROUPS_FILENAME = "groups.plist"
|
|
92
|
-
KERNING_FILENAME = "kerning.plist"
|
|
93
|
-
FEATURES_FILENAME = "features.fea"
|
|
94
|
-
LAYERCONTENTS_FILENAME = "layercontents.plist"
|
|
95
|
-
LAYERINFO_FILENAME = "layerinfo.plist"
|
|
112
|
+
DEFAULT_GLYPHS_DIRNAME: str = "glyphs"
|
|
113
|
+
DATA_DIRNAME: str = "data"
|
|
114
|
+
IMAGES_DIRNAME: str = "images"
|
|
115
|
+
METAINFO_FILENAME: str = "metainfo.plist"
|
|
116
|
+
FONTINFO_FILENAME: str = "fontinfo.plist"
|
|
117
|
+
LIB_FILENAME: str = "lib.plist"
|
|
118
|
+
GROUPS_FILENAME: str = "groups.plist"
|
|
119
|
+
KERNING_FILENAME: str = "kerning.plist"
|
|
120
|
+
FEATURES_FILENAME: str = "features.fea"
|
|
121
|
+
LAYERCONTENTS_FILENAME: str = "layercontents.plist"
|
|
122
|
+
LAYERINFO_FILENAME: str = "layerinfo.plist"
|
|
96
123
|
|
|
97
|
-
DEFAULT_LAYER_NAME = "public.default"
|
|
124
|
+
DEFAULT_LAYER_NAME: str = "public.default"
|
|
98
125
|
|
|
99
126
|
|
|
100
|
-
class UFOFormatVersion(
|
|
127
|
+
class UFOFormatVersion(BaseFormatVersion):
|
|
101
128
|
FORMAT_1_0 = (1, 0)
|
|
102
129
|
FORMAT_2_0 = (2, 0)
|
|
103
130
|
FORMAT_3_0 = (3, 0)
|
|
104
131
|
|
|
105
132
|
|
|
106
|
-
# python 3.11 doesn't like when a mixin overrides a dunder method like __str__
|
|
107
|
-
# for some reasons it keep using Enum.__str__, see
|
|
108
|
-
# https://github.com/fonttools/fonttools/pull/2655
|
|
109
|
-
UFOFormatVersion.__str__ = _VersionTupleEnumMixin.__str__
|
|
110
|
-
|
|
111
|
-
|
|
112
133
|
class UFOFileStructure(enum.Enum):
|
|
113
134
|
ZIP = "zip"
|
|
114
135
|
PACKAGE = "package"
|
|
@@ -120,7 +141,11 @@ class UFOFileStructure(enum.Enum):
|
|
|
120
141
|
|
|
121
142
|
|
|
122
143
|
class _UFOBaseIO:
|
|
123
|
-
|
|
144
|
+
if TYPE_CHECKING:
|
|
145
|
+
fs: FS
|
|
146
|
+
_havePreviousFile: bool
|
|
147
|
+
|
|
148
|
+
def getFileModificationTime(self, path: PathStr) -> Optional[float]:
|
|
124
149
|
"""
|
|
125
150
|
Returns the modification time for the file at the given path, as a
|
|
126
151
|
floating point number giving the number of seconds since the epoch.
|
|
@@ -132,9 +157,11 @@ class _UFOBaseIO:
|
|
|
132
157
|
except (fs.errors.MissingInfoNamespace, fs.errors.ResourceNotFound):
|
|
133
158
|
return None
|
|
134
159
|
else:
|
|
135
|
-
|
|
160
|
+
if dt is not None:
|
|
161
|
+
return dt.timestamp()
|
|
162
|
+
return None
|
|
136
163
|
|
|
137
|
-
def _getPlist(self, fileName, default=None):
|
|
164
|
+
def _getPlist(self, fileName: str, default: Optional[Any] = None) -> Any:
|
|
138
165
|
"""
|
|
139
166
|
Read a property list relative to the UFO filesystem's root.
|
|
140
167
|
Raises UFOLibError if the file is missing and default is None,
|
|
@@ -158,7 +185,7 @@ class _UFOBaseIO:
|
|
|
158
185
|
# TODO(anthrotype): try to narrow this down a little
|
|
159
186
|
raise UFOLibError(f"'{fileName}' could not be read on {self.fs}: {e}")
|
|
160
187
|
|
|
161
|
-
def _writePlist(self, fileName, obj):
|
|
188
|
+
def _writePlist(self, fileName: str, obj: Any) -> None:
|
|
162
189
|
"""
|
|
163
190
|
Write a property list to a file relative to the UFO filesystem's root.
|
|
164
191
|
|
|
@@ -184,7 +211,7 @@ class _UFOBaseIO:
|
|
|
184
211
|
return
|
|
185
212
|
self.fs.writebytes(fileName, data)
|
|
186
213
|
else:
|
|
187
|
-
with self.fs.
|
|
214
|
+
with self.fs.open(fileName, mode="wb") as fp:
|
|
188
215
|
try:
|
|
189
216
|
plistlib.dump(obj, fp)
|
|
190
217
|
except Exception as e:
|
|
@@ -204,7 +231,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
204
231
|
"""Read the various components of a .ufo.
|
|
205
232
|
|
|
206
233
|
Attributes:
|
|
207
|
-
path: An
|
|
234
|
+
path: An :class:`os.PathLike` object pointing to the .ufo.
|
|
208
235
|
validate: A boolean indicating if the data read should be
|
|
209
236
|
validated. Defaults to `True`.
|
|
210
237
|
|
|
@@ -212,15 +239,17 @@ class UFOReader(_UFOBaseIO):
|
|
|
212
239
|
``False`` to not validate the data.
|
|
213
240
|
"""
|
|
214
241
|
|
|
215
|
-
def __init__(self, path, validate=True):
|
|
216
|
-
if
|
|
242
|
+
def __init__(self, path: PathOrFS, validate: bool = True) -> None:
|
|
243
|
+
# Only call __fspath__ if path is not already a str or FS object
|
|
244
|
+
if not isinstance(path, (str, fs.base.FS)) and hasattr(path, "__fspath__"):
|
|
217
245
|
path = path.__fspath__()
|
|
218
246
|
|
|
219
247
|
if isinstance(path, str):
|
|
220
248
|
structure = _sniffFileStructure(path)
|
|
249
|
+
parentFS: FS
|
|
221
250
|
try:
|
|
222
251
|
if structure is UFOFileStructure.ZIP:
|
|
223
|
-
parentFS = fs.zipfs.ZipFS(path, write=False, encoding="utf-8")
|
|
252
|
+
parentFS = fs.zipfs.ZipFS(path, write=False, encoding="utf-8") # type: ignore[abstract]
|
|
224
253
|
else:
|
|
225
254
|
parentFS = fs.osfs.OSFS(path)
|
|
226
255
|
except fs.errors.CreateFailed as e:
|
|
@@ -238,7 +267,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
238
267
|
if len(rootDirs) == 1:
|
|
239
268
|
# 'ClosingSubFS' ensures that the parent zip file is closed when
|
|
240
269
|
# its root subdirectory is closed
|
|
241
|
-
self.fs = parentFS.opendir(
|
|
270
|
+
self.fs: FS = parentFS.opendir(
|
|
242
271
|
rootDirs[0], factory=fs.subfs.ClosingSubFS
|
|
243
272
|
)
|
|
244
273
|
else:
|
|
@@ -250,10 +279,10 @@ class UFOReader(_UFOBaseIO):
|
|
|
250
279
|
self.fs = parentFS
|
|
251
280
|
# when passed a path string, we make sure we close the newly opened fs
|
|
252
281
|
# upon calling UFOReader.close method or context manager's __exit__
|
|
253
|
-
self._shouldClose = True
|
|
282
|
+
self._shouldClose: bool = True
|
|
254
283
|
self._fileStructure = structure
|
|
255
284
|
elif isinstance(path, fs.base.FS):
|
|
256
|
-
filesystem = path
|
|
285
|
+
filesystem: FS = path
|
|
257
286
|
try:
|
|
258
287
|
filesystem.check()
|
|
259
288
|
except fs.errors.FilesystemClosed:
|
|
@@ -275,9 +304,9 @@ class UFOReader(_UFOBaseIO):
|
|
|
275
304
|
"Expected a path string or fs.base.FS object, found '%s'"
|
|
276
305
|
% type(path).__name__
|
|
277
306
|
)
|
|
278
|
-
self._path = fsdecode(path)
|
|
279
|
-
self._validate = validate
|
|
280
|
-
self._upConvertedKerningData = None
|
|
307
|
+
self._path: str = fsdecode(path)
|
|
308
|
+
self._validate: bool = validate
|
|
309
|
+
self._upConvertedKerningData: Optional[dict[str, Any]] = None
|
|
281
310
|
|
|
282
311
|
try:
|
|
283
312
|
self.readMetaInfo(validate=validate)
|
|
@@ -287,7 +316,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
287
316
|
|
|
288
317
|
# properties
|
|
289
318
|
|
|
290
|
-
def _get_path(self):
|
|
319
|
+
def _get_path(self) -> str:
|
|
291
320
|
import warnings
|
|
292
321
|
|
|
293
322
|
warnings.warn(
|
|
@@ -297,9 +326,9 @@ class UFOReader(_UFOBaseIO):
|
|
|
297
326
|
)
|
|
298
327
|
return self._path
|
|
299
328
|
|
|
300
|
-
path = property(_get_path, doc="The path of the UFO (DEPRECATED).")
|
|
329
|
+
path: property = property(_get_path, doc="The path of the UFO (DEPRECATED).")
|
|
301
330
|
|
|
302
|
-
def _get_formatVersion(self):
|
|
331
|
+
def _get_formatVersion(self) -> int:
|
|
303
332
|
import warnings
|
|
304
333
|
|
|
305
334
|
warnings.warn(
|
|
@@ -315,16 +344,16 @@ class UFOReader(_UFOBaseIO):
|
|
|
315
344
|
)
|
|
316
345
|
|
|
317
346
|
@property
|
|
318
|
-
def formatVersionTuple(self):
|
|
347
|
+
def formatVersionTuple(self) -> tuple[int, int]:
|
|
319
348
|
"""The (major, minor) format version of the UFO.
|
|
320
349
|
This is determined by reading metainfo.plist during __init__.
|
|
321
350
|
"""
|
|
322
351
|
return self._formatVersion
|
|
323
352
|
|
|
324
|
-
def _get_fileStructure(self):
|
|
353
|
+
def _get_fileStructure(self) -> Any:
|
|
325
354
|
return self._fileStructure
|
|
326
355
|
|
|
327
|
-
fileStructure = property(
|
|
356
|
+
fileStructure: property = property(
|
|
328
357
|
_get_fileStructure,
|
|
329
358
|
doc=(
|
|
330
359
|
"The file structure of the UFO: "
|
|
@@ -334,7 +363,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
334
363
|
|
|
335
364
|
# up conversion
|
|
336
365
|
|
|
337
|
-
def _upConvertKerning(self, validate):
|
|
366
|
+
def _upConvertKerning(self, validate: bool) -> None:
|
|
338
367
|
"""
|
|
339
368
|
Up convert kerning and groups in UFO 1 and 2.
|
|
340
369
|
The data will be held internally until each bit of data
|
|
@@ -388,7 +417,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
388
417
|
|
|
389
418
|
# support methods
|
|
390
419
|
|
|
391
|
-
def readBytesFromPath(self, path):
|
|
420
|
+
def readBytesFromPath(self, path: PathStr) -> Optional[bytes]:
|
|
392
421
|
"""
|
|
393
422
|
Returns the bytes in the file at the given path.
|
|
394
423
|
The path must be relative to the UFO's filesystem root.
|
|
@@ -399,7 +428,9 @@ class UFOReader(_UFOBaseIO):
|
|
|
399
428
|
except fs.errors.ResourceNotFound:
|
|
400
429
|
return None
|
|
401
430
|
|
|
402
|
-
def getReadFileForPath(
|
|
431
|
+
def getReadFileForPath(
|
|
432
|
+
self, path: PathStr, encoding: Optional[str] = None
|
|
433
|
+
) -> Optional[Union[IO[bytes], IO[str]]]:
|
|
403
434
|
"""
|
|
404
435
|
Returns a file (or file-like) object for the file at the given path.
|
|
405
436
|
The path must be relative to the UFO path.
|
|
@@ -412,7 +443,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
412
443
|
path = fsdecode(path)
|
|
413
444
|
try:
|
|
414
445
|
if encoding is None:
|
|
415
|
-
return self.fs.
|
|
446
|
+
return self.fs.open(path, mode="rb")
|
|
416
447
|
else:
|
|
417
448
|
return self.fs.open(path, mode="r", encoding=encoding)
|
|
418
449
|
except fs.errors.ResourceNotFound:
|
|
@@ -420,7 +451,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
420
451
|
|
|
421
452
|
# metainfo.plist
|
|
422
453
|
|
|
423
|
-
def _readMetaInfo(self, validate=None):
|
|
454
|
+
def _readMetaInfo(self, validate: Optional[bool] = None) -> dict[str, Any]:
|
|
424
455
|
"""
|
|
425
456
|
Read metainfo.plist and return raw data. Only used for internal operations.
|
|
426
457
|
|
|
@@ -462,7 +493,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
462
493
|
data["formatVersionTuple"] = formatVersion
|
|
463
494
|
return data
|
|
464
495
|
|
|
465
|
-
def readMetaInfo(self, validate=None):
|
|
496
|
+
def readMetaInfo(self, validate: Optional[bool] = None) -> None:
|
|
466
497
|
"""
|
|
467
498
|
Read metainfo.plist and set formatVersion. Only used for internal operations.
|
|
468
499
|
|
|
@@ -474,7 +505,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
474
505
|
|
|
475
506
|
# groups.plist
|
|
476
507
|
|
|
477
|
-
def _readGroups(self):
|
|
508
|
+
def _readGroups(self) -> dict[str, list[str]]:
|
|
478
509
|
groups = self._getPlist(GROUPS_FILENAME, {})
|
|
479
510
|
# remove any duplicate glyphs in a kerning group
|
|
480
511
|
for groupName, glyphList in groups.items():
|
|
@@ -482,7 +513,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
482
513
|
groups[groupName] = list(OrderedDict.fromkeys(glyphList))
|
|
483
514
|
return groups
|
|
484
515
|
|
|
485
|
-
def readGroups(self, validate=None):
|
|
516
|
+
def readGroups(self, validate: Optional[bool] = None) -> dict[str, list[str]]:
|
|
486
517
|
"""
|
|
487
518
|
Read groups.plist. Returns a dict.
|
|
488
519
|
``validate`` will validate the read data, by default it is set to the
|
|
@@ -493,7 +524,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
493
524
|
# handle up conversion
|
|
494
525
|
if self._formatVersion < UFOFormatVersion.FORMAT_3_0:
|
|
495
526
|
self._upConvertKerning(validate)
|
|
496
|
-
groups = self._upConvertedKerningData["groups"]
|
|
527
|
+
groups = cast(dict, self._upConvertedKerningData)["groups"]
|
|
497
528
|
# normal
|
|
498
529
|
else:
|
|
499
530
|
groups = self._readGroups()
|
|
@@ -503,7 +534,9 @@ class UFOReader(_UFOBaseIO):
|
|
|
503
534
|
raise UFOLibError(message)
|
|
504
535
|
return groups
|
|
505
536
|
|
|
506
|
-
def getKerningGroupConversionRenameMaps(
|
|
537
|
+
def getKerningGroupConversionRenameMaps(
|
|
538
|
+
self, validate: Optional[bool] = None
|
|
539
|
+
) -> KerningGroupRenameMaps:
|
|
507
540
|
"""
|
|
508
541
|
Get maps defining the renaming that was done during any
|
|
509
542
|
needed kerning group conversion. This method returns a
|
|
@@ -527,17 +560,17 @@ class UFOReader(_UFOBaseIO):
|
|
|
527
560
|
# use the public group reader to force the load and
|
|
528
561
|
# conversion of the data if it hasn't happened yet.
|
|
529
562
|
self.readGroups(validate=validate)
|
|
530
|
-
return self._upConvertedKerningData["groupRenameMaps"]
|
|
563
|
+
return cast(dict, self._upConvertedKerningData)["groupRenameMaps"]
|
|
531
564
|
|
|
532
565
|
# fontinfo.plist
|
|
533
566
|
|
|
534
|
-
def _readInfo(self, validate):
|
|
567
|
+
def _readInfo(self, validate: bool) -> dict[str, Any]:
|
|
535
568
|
data = self._getPlist(FONTINFO_FILENAME, {})
|
|
536
569
|
if validate and not isinstance(data, dict):
|
|
537
570
|
raise UFOLibError("fontinfo.plist is not properly formatted.")
|
|
538
571
|
return data
|
|
539
572
|
|
|
540
|
-
def readInfo(self, info, validate=None):
|
|
573
|
+
def readInfo(self, info: Any, validate: Optional[bool] = None) -> None:
|
|
541
574
|
"""
|
|
542
575
|
Read fontinfo.plist. It requires an object that allows
|
|
543
576
|
setting attributes with names that follow the fontinfo.plist
|
|
@@ -596,11 +629,11 @@ class UFOReader(_UFOBaseIO):
|
|
|
596
629
|
|
|
597
630
|
# kerning.plist
|
|
598
631
|
|
|
599
|
-
def _readKerning(self):
|
|
632
|
+
def _readKerning(self) -> KerningNested:
|
|
600
633
|
data = self._getPlist(KERNING_FILENAME, {})
|
|
601
634
|
return data
|
|
602
635
|
|
|
603
|
-
def readKerning(self, validate=None):
|
|
636
|
+
def readKerning(self, validate: Optional[bool] = None) -> KerningDict:
|
|
604
637
|
"""
|
|
605
638
|
Read kerning.plist. Returns a dict.
|
|
606
639
|
|
|
@@ -612,7 +645,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
612
645
|
# handle up conversion
|
|
613
646
|
if self._formatVersion < UFOFormatVersion.FORMAT_3_0:
|
|
614
647
|
self._upConvertKerning(validate)
|
|
615
|
-
kerningNested = self._upConvertedKerningData["kerning"]
|
|
648
|
+
kerningNested = cast(dict, self._upConvertedKerningData)["kerning"]
|
|
616
649
|
# normal
|
|
617
650
|
else:
|
|
618
651
|
kerningNested = self._readKerning()
|
|
@@ -630,7 +663,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
630
663
|
|
|
631
664
|
# lib.plist
|
|
632
665
|
|
|
633
|
-
def readLib(self, validate=None):
|
|
666
|
+
def readLib(self, validate: Optional[bool] = None) -> dict[str, Any]:
|
|
634
667
|
"""
|
|
635
668
|
Read lib.plist. Returns a dict.
|
|
636
669
|
|
|
@@ -648,20 +681,20 @@ class UFOReader(_UFOBaseIO):
|
|
|
648
681
|
|
|
649
682
|
# features.fea
|
|
650
683
|
|
|
651
|
-
def readFeatures(self):
|
|
684
|
+
def readFeatures(self) -> str:
|
|
652
685
|
"""
|
|
653
686
|
Read features.fea. Return a string.
|
|
654
687
|
The returned string is empty if the file is missing.
|
|
655
688
|
"""
|
|
656
689
|
try:
|
|
657
|
-
with self.fs.open(FEATURES_FILENAME, "r", encoding="utf-8") as f:
|
|
690
|
+
with self.fs.open(FEATURES_FILENAME, "r", encoding="utf-8-sig") as f:
|
|
658
691
|
return f.read()
|
|
659
692
|
except fs.errors.ResourceNotFound:
|
|
660
693
|
return ""
|
|
661
694
|
|
|
662
695
|
# glyph sets & layers
|
|
663
696
|
|
|
664
|
-
def _readLayerContents(self, validate):
|
|
697
|
+
def _readLayerContents(self, validate: bool) -> list[tuple[str, str]]:
|
|
665
698
|
"""
|
|
666
699
|
Rebuild the layer contents list by checking what glyphsets
|
|
667
700
|
are available on disk.
|
|
@@ -677,7 +710,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
677
710
|
raise UFOLibError(error)
|
|
678
711
|
return contents
|
|
679
712
|
|
|
680
|
-
def getLayerNames(self, validate=None):
|
|
713
|
+
def getLayerNames(self, validate: Optional[bool] = None) -> list[str]:
|
|
681
714
|
"""
|
|
682
715
|
Get the ordered layer names from layercontents.plist.
|
|
683
716
|
|
|
@@ -690,7 +723,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
690
723
|
layerNames = [layerName for layerName, directoryName in layerContents]
|
|
691
724
|
return layerNames
|
|
692
725
|
|
|
693
|
-
def getDefaultLayerName(self, validate=None):
|
|
726
|
+
def getDefaultLayerName(self, validate: Optional[bool] = None) -> str:
|
|
694
727
|
"""
|
|
695
728
|
Get the default layer name from layercontents.plist.
|
|
696
729
|
|
|
@@ -706,7 +739,12 @@ class UFOReader(_UFOBaseIO):
|
|
|
706
739
|
# this will already have been raised during __init__
|
|
707
740
|
raise UFOLibError("The default layer is not defined in layercontents.plist.")
|
|
708
741
|
|
|
709
|
-
def getGlyphSet(
|
|
742
|
+
def getGlyphSet(
|
|
743
|
+
self,
|
|
744
|
+
layerName: Optional[str] = None,
|
|
745
|
+
validateRead: Optional[bool] = None,
|
|
746
|
+
validateWrite: Optional[bool] = None,
|
|
747
|
+
) -> GlyphSet:
|
|
710
748
|
"""
|
|
711
749
|
Return the GlyphSet associated with the
|
|
712
750
|
glyphs directory mapped to layerName
|
|
@@ -747,7 +785,9 @@ class UFOReader(_UFOBaseIO):
|
|
|
747
785
|
expectContentsFile=True,
|
|
748
786
|
)
|
|
749
787
|
|
|
750
|
-
def getCharacterMapping(
|
|
788
|
+
def getCharacterMapping(
|
|
789
|
+
self, layerName: Optional[str] = None, validate: Optional[bool] = None
|
|
790
|
+
) -> dict[int, list[str]]:
|
|
751
791
|
"""
|
|
752
792
|
Return a dictionary that maps unicode values (ints) to
|
|
753
793
|
lists of glyph names.
|
|
@@ -758,7 +798,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
758
798
|
layerName, validateRead=validate, validateWrite=True
|
|
759
799
|
)
|
|
760
800
|
allUnicodes = glyphSet.getUnicodes()
|
|
761
|
-
cmap = {}
|
|
801
|
+
cmap: dict[int, list[str]] = {}
|
|
762
802
|
for glyphName, unicodes in allUnicodes.items():
|
|
763
803
|
for code in unicodes:
|
|
764
804
|
if code in cmap:
|
|
@@ -769,7 +809,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
769
809
|
|
|
770
810
|
# /data
|
|
771
811
|
|
|
772
|
-
def getDataDirectoryListing(self):
|
|
812
|
+
def getDataDirectoryListing(self) -> list[str]:
|
|
773
813
|
"""
|
|
774
814
|
Returns a list of all files in the data directory.
|
|
775
815
|
The returned paths will be relative to the UFO.
|
|
@@ -790,7 +830,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
790
830
|
except fs.errors.ResourceError:
|
|
791
831
|
return []
|
|
792
832
|
|
|
793
|
-
def getImageDirectoryListing(self, validate=None):
|
|
833
|
+
def getImageDirectoryListing(self, validate: Optional[bool] = None) -> list[str]:
|
|
794
834
|
"""
|
|
795
835
|
Returns a list of all image file names in
|
|
796
836
|
the images directory. Each of the images will
|
|
@@ -818,7 +858,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
818
858
|
# systems often have hidden directories
|
|
819
859
|
continue
|
|
820
860
|
if validate:
|
|
821
|
-
with imagesFS.
|
|
861
|
+
with imagesFS.open(path.name, "rb") as fp:
|
|
822
862
|
valid, error = pngValidator(fileObj=fp)
|
|
823
863
|
if valid:
|
|
824
864
|
result.append(path.name)
|
|
@@ -826,7 +866,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
826
866
|
result.append(path.name)
|
|
827
867
|
return result
|
|
828
868
|
|
|
829
|
-
def readData(self, fileName):
|
|
869
|
+
def readData(self, fileName: PathStr) -> bytes:
|
|
830
870
|
"""
|
|
831
871
|
Return bytes for the file named 'fileName' inside the 'data/' directory.
|
|
832
872
|
"""
|
|
@@ -842,7 +882,7 @@ class UFOReader(_UFOBaseIO):
|
|
|
842
882
|
raise UFOLibError(f"No data file named '{fileName}' on {self.fs}")
|
|
843
883
|
return data
|
|
844
884
|
|
|
845
|
-
def readImage(self, fileName, validate=None):
|
|
885
|
+
def readImage(self, fileName: PathStr, validate: Optional[bool] = None) -> bytes:
|
|
846
886
|
"""
|
|
847
887
|
Return image data for the file named fileName.
|
|
848
888
|
|
|
@@ -871,14 +911,14 @@ class UFOReader(_UFOBaseIO):
|
|
|
871
911
|
raise UFOLibError(error)
|
|
872
912
|
return data
|
|
873
913
|
|
|
874
|
-
def close(self):
|
|
914
|
+
def close(self) -> None:
|
|
875
915
|
if self._shouldClose:
|
|
876
916
|
self.fs.close()
|
|
877
917
|
|
|
878
|
-
def __enter__(self):
|
|
918
|
+
def __enter__(self) -> UFOReader:
|
|
879
919
|
return self
|
|
880
920
|
|
|
881
|
-
def __exit__(self, exc_type, exc_value, exc_tb):
|
|
921
|
+
def __exit__(self, exc_type: Any, exc_value: Any, exc_tb: Any) -> None:
|
|
882
922
|
self.close()
|
|
883
923
|
|
|
884
924
|
|
|
@@ -891,7 +931,7 @@ class UFOWriter(UFOReader):
|
|
|
891
931
|
"""Write the various components of a .ufo.
|
|
892
932
|
|
|
893
933
|
Attributes:
|
|
894
|
-
path: An
|
|
934
|
+
path: An :class:`os.PathLike` object pointing to the .ufo.
|
|
895
935
|
formatVersion: the UFO format version as a tuple of integers (major, minor),
|
|
896
936
|
or as a single integer for the major digit only (minor is implied to be 0).
|
|
897
937
|
By default, the latest formatVersion will be used; currently it is 3.0,
|
|
@@ -913,14 +953,14 @@ class UFOWriter(UFOReader):
|
|
|
913
953
|
|
|
914
954
|
def __init__(
|
|
915
955
|
self,
|
|
916
|
-
path,
|
|
917
|
-
formatVersion=None,
|
|
918
|
-
fileCreator="com.github.fonttools.ufoLib",
|
|
919
|
-
structure=None,
|
|
920
|
-
validate=True,
|
|
921
|
-
):
|
|
956
|
+
path: PathOrFS,
|
|
957
|
+
formatVersion: UFOFormatVersionInput = None,
|
|
958
|
+
fileCreator: str = "com.github.fonttools.ufoLib",
|
|
959
|
+
structure: Optional[UFOFileStructure] = None,
|
|
960
|
+
validate: bool = True,
|
|
961
|
+
) -> None:
|
|
922
962
|
try:
|
|
923
|
-
formatVersion =
|
|
963
|
+
formatVersion = normalizeFormatVersion(formatVersion, UFOFormatVersion)
|
|
924
964
|
except ValueError as e:
|
|
925
965
|
from fontTools.ufoLib.errors import UnsupportedUFOFormat
|
|
926
966
|
|
|
@@ -966,8 +1006,8 @@ class UFOWriter(UFOReader):
|
|
|
966
1006
|
# we can't write a zip in-place, so we have to copy its
|
|
967
1007
|
# contents to a temporary location and work from there, then
|
|
968
1008
|
# upon closing UFOWriter we create the final zip file
|
|
969
|
-
parentFS = fs.tempfs.TempFS()
|
|
970
|
-
with fs.zipfs.ZipFS(path, encoding="utf-8") as origFS:
|
|
1009
|
+
parentFS: FS = fs.tempfs.TempFS()
|
|
1010
|
+
with fs.zipfs.ZipFS(path, encoding="utf-8") as origFS: # type: ignore[abstract]
|
|
971
1011
|
fs.copy.copy_fs(origFS, parentFS)
|
|
972
1012
|
# if output path is an existing zip, we require that it contains
|
|
973
1013
|
# one, and only one, root directory (with arbitrary name), in turn
|
|
@@ -984,25 +1024,23 @@ class UFOWriter(UFOReader):
|
|
|
984
1024
|
% len(rootDirs)
|
|
985
1025
|
)
|
|
986
1026
|
else:
|
|
987
|
-
|
|
988
|
-
# when its root subdirectory is closed
|
|
989
|
-
self.fs = parentFS.opendir(
|
|
990
|
-
rootDirs[0], factory=fs.subfs.ClosingSubFS
|
|
991
|
-
)
|
|
1027
|
+
rootDir = rootDirs[0]
|
|
992
1028
|
else:
|
|
993
1029
|
# if the output zip file didn't exist, we create the root folder;
|
|
994
1030
|
# we name it the same as input 'path', but with '.ufo' extension
|
|
995
1031
|
rootDir = os.path.splitext(os.path.basename(path))[0] + ".ufo"
|
|
996
|
-
parentFS = fs.zipfs.ZipFS(path, write=True, encoding="utf-8")
|
|
1032
|
+
parentFS = fs.zipfs.ZipFS(path, write=True, encoding="utf-8") # type: ignore[abstract]
|
|
997
1033
|
parentFS.makedir(rootDir)
|
|
998
|
-
|
|
1034
|
+
# 'ClosingSubFS' ensures that the parent filesystem is closed
|
|
1035
|
+
# when its root subdirectory is closed
|
|
1036
|
+
self.fs = parentFS.opendir(rootDir, factory=fs.subfs.ClosingSubFS)
|
|
999
1037
|
else:
|
|
1000
1038
|
self.fs = fs.osfs.OSFS(path, create=True)
|
|
1001
1039
|
self._fileStructure = structure
|
|
1002
1040
|
self._havePreviousFile = havePreviousFile
|
|
1003
1041
|
self._shouldClose = True
|
|
1004
1042
|
elif isinstance(path, fs.base.FS):
|
|
1005
|
-
filesystem = path
|
|
1043
|
+
filesystem: FS = path
|
|
1006
1044
|
try:
|
|
1007
1045
|
filesystem.check()
|
|
1008
1046
|
except fs.errors.FilesystemClosed:
|
|
@@ -1037,7 +1075,7 @@ class UFOWriter(UFOReader):
|
|
|
1037
1075
|
self._path = fsdecode(path)
|
|
1038
1076
|
self._formatVersion = formatVersion
|
|
1039
1077
|
self._fileCreator = fileCreator
|
|
1040
|
-
self._downConversionKerningData = None
|
|
1078
|
+
self._downConversionKerningData: Optional[KerningGroupRenameMaps] = None
|
|
1041
1079
|
self._validate = validate
|
|
1042
1080
|
# if the file already exists, get the format version.
|
|
1043
1081
|
# this will be needed for up and down conversion.
|
|
@@ -1055,7 +1093,7 @@ class UFOWriter(UFOReader):
|
|
|
1055
1093
|
"that is trying to be written. This is not supported."
|
|
1056
1094
|
)
|
|
1057
1095
|
# handle the layer contents
|
|
1058
|
-
self.layerContents = {}
|
|
1096
|
+
self.layerContents: Union[dict[str, str], OrderedDict[str, str]] = {}
|
|
1059
1097
|
if previousFormatVersion is not None and previousFormatVersion.major >= 3:
|
|
1060
1098
|
# already exists
|
|
1061
1099
|
self.layerContents = OrderedDict(self._readLayerContents(validate))
|
|
@@ -1069,17 +1107,19 @@ class UFOWriter(UFOReader):
|
|
|
1069
1107
|
|
|
1070
1108
|
# properties
|
|
1071
1109
|
|
|
1072
|
-
def _get_fileCreator(self):
|
|
1110
|
+
def _get_fileCreator(self) -> str:
|
|
1073
1111
|
return self._fileCreator
|
|
1074
1112
|
|
|
1075
|
-
fileCreator = property(
|
|
1113
|
+
fileCreator: property = property(
|
|
1076
1114
|
_get_fileCreator,
|
|
1077
1115
|
doc="The file creator of the UFO. This is set into metainfo.plist during __init__.",
|
|
1078
1116
|
)
|
|
1079
1117
|
|
|
1080
1118
|
# support methods for file system interaction
|
|
1081
1119
|
|
|
1082
|
-
def copyFromReader(
|
|
1120
|
+
def copyFromReader(
|
|
1121
|
+
self, reader: UFOReader, sourcePath: PathStr, destPath: PathStr
|
|
1122
|
+
) -> None:
|
|
1083
1123
|
"""
|
|
1084
1124
|
Copy the sourcePath in the provided UFOReader to destPath
|
|
1085
1125
|
in this writer. The paths must be relative. This works with
|
|
@@ -1102,7 +1142,7 @@ class UFOWriter(UFOReader):
|
|
|
1102
1142
|
else:
|
|
1103
1143
|
fs.copy.copy_file(reader.fs, sourcePath, self.fs, destPath)
|
|
1104
1144
|
|
|
1105
|
-
def writeBytesToPath(self, path, data):
|
|
1145
|
+
def writeBytesToPath(self, path: PathStr, data: bytes) -> None:
|
|
1106
1146
|
"""
|
|
1107
1147
|
Write bytes to a path relative to the UFO filesystem's root.
|
|
1108
1148
|
If writing to an existing UFO, check to see if data matches the data
|
|
@@ -1122,7 +1162,12 @@ class UFOWriter(UFOReader):
|
|
|
1122
1162
|
self.fs.makedirs(fs.path.dirname(path), recreate=True)
|
|
1123
1163
|
self.fs.writebytes(path, data)
|
|
1124
1164
|
|
|
1125
|
-
def getFileObjectForPath(
|
|
1165
|
+
def getFileObjectForPath(
|
|
1166
|
+
self,
|
|
1167
|
+
path: PathStr,
|
|
1168
|
+
mode: str = "w",
|
|
1169
|
+
encoding: Optional[str] = None,
|
|
1170
|
+
) -> Optional[IO[Any]]:
|
|
1126
1171
|
"""
|
|
1127
1172
|
Returns a file (or file-like) object for the
|
|
1128
1173
|
file at the given path. The path must be relative
|
|
@@ -1145,9 +1190,12 @@ class UFOWriter(UFOReader):
|
|
|
1145
1190
|
self.fs.makedirs(fs.path.dirname(path), recreate=True)
|
|
1146
1191
|
return self.fs.open(path, mode=mode, encoding=encoding)
|
|
1147
1192
|
except fs.errors.ResourceError as e:
|
|
1148
|
-
|
|
1193
|
+
raise UFOLibError(f"unable to open '{path}' on {self.fs}: {e}")
|
|
1194
|
+
return None
|
|
1149
1195
|
|
|
1150
|
-
def removePath(
|
|
1196
|
+
def removePath(
|
|
1197
|
+
self, path: PathStr, force: bool = False, removeEmptyParents: bool = True
|
|
1198
|
+
) -> None:
|
|
1151
1199
|
"""
|
|
1152
1200
|
Remove the file (or directory) at path. The path
|
|
1153
1201
|
must be relative to the UFO.
|
|
@@ -1174,7 +1222,7 @@ class UFOWriter(UFOReader):
|
|
|
1174
1222
|
|
|
1175
1223
|
# UFO mod time
|
|
1176
1224
|
|
|
1177
|
-
def setModificationTime(self):
|
|
1225
|
+
def setModificationTime(self) -> None:
|
|
1178
1226
|
"""
|
|
1179
1227
|
Set the UFO modification time to the current time.
|
|
1180
1228
|
This is never called automatically. It is up to the
|
|
@@ -1190,7 +1238,7 @@ class UFOWriter(UFOReader):
|
|
|
1190
1238
|
|
|
1191
1239
|
# metainfo.plist
|
|
1192
1240
|
|
|
1193
|
-
def _writeMetaInfo(self):
|
|
1241
|
+
def _writeMetaInfo(self) -> None:
|
|
1194
1242
|
metaInfo = dict(
|
|
1195
1243
|
creator=self._fileCreator,
|
|
1196
1244
|
formatVersion=self._formatVersion.major,
|
|
@@ -1201,7 +1249,7 @@ class UFOWriter(UFOReader):
|
|
|
1201
1249
|
|
|
1202
1250
|
# groups.plist
|
|
1203
1251
|
|
|
1204
|
-
def setKerningGroupConversionRenameMaps(self, maps):
|
|
1252
|
+
def setKerningGroupConversionRenameMaps(self, maps: KerningGroupRenameMaps) -> None:
|
|
1205
1253
|
"""
|
|
1206
1254
|
Set maps defining the renaming that should be done
|
|
1207
1255
|
when writing groups and kerning in UFO 1 and UFO 2.
|
|
@@ -1226,7 +1274,9 @@ class UFOWriter(UFOReader):
|
|
|
1226
1274
|
remap[dataName] = writeName
|
|
1227
1275
|
self._downConversionKerningData = dict(groupRenameMap=remap)
|
|
1228
1276
|
|
|
1229
|
-
def writeGroups(
|
|
1277
|
+
def writeGroups(
|
|
1278
|
+
self, groups: KerningGroups, validate: Optional[bool] = None
|
|
1279
|
+
) -> None:
|
|
1230
1280
|
"""
|
|
1231
1281
|
Write groups.plist. This method requires a
|
|
1232
1282
|
dict of glyph groups as an argument.
|
|
@@ -1281,7 +1331,7 @@ class UFOWriter(UFOReader):
|
|
|
1281
1331
|
|
|
1282
1332
|
# fontinfo.plist
|
|
1283
1333
|
|
|
1284
|
-
def writeInfo(self, info, validate=None):
|
|
1334
|
+
def writeInfo(self, info: Any, validate: Optional[bool] = None) -> None:
|
|
1285
1335
|
"""
|
|
1286
1336
|
Write info.plist. This method requires an object
|
|
1287
1337
|
that supports getting attributes that follow the
|
|
@@ -1327,7 +1377,9 @@ class UFOWriter(UFOReader):
|
|
|
1327
1377
|
|
|
1328
1378
|
# kerning.plist
|
|
1329
1379
|
|
|
1330
|
-
def writeKerning(
|
|
1380
|
+
def writeKerning(
|
|
1381
|
+
self, kerning: KerningDict, validate: Optional[bool] = None
|
|
1382
|
+
) -> None:
|
|
1331
1383
|
"""
|
|
1332
1384
|
Write kerning.plist. This method requires a
|
|
1333
1385
|
dict of kerning pairs as an argument.
|
|
@@ -1371,7 +1423,7 @@ class UFOWriter(UFOReader):
|
|
|
1371
1423
|
remappedKerning[side1, side2] = value
|
|
1372
1424
|
kerning = remappedKerning
|
|
1373
1425
|
# pack and write
|
|
1374
|
-
kerningDict = {}
|
|
1426
|
+
kerningDict: KerningNested = {}
|
|
1375
1427
|
for left, right in kerning.keys():
|
|
1376
1428
|
value = kerning[left, right]
|
|
1377
1429
|
if left not in kerningDict:
|
|
@@ -1384,7 +1436,7 @@ class UFOWriter(UFOReader):
|
|
|
1384
1436
|
|
|
1385
1437
|
# lib.plist
|
|
1386
1438
|
|
|
1387
|
-
def writeLib(self, libDict, validate=None):
|
|
1439
|
+
def writeLib(self, libDict: LibDict, validate: Optional[bool] = None) -> None:
|
|
1388
1440
|
"""
|
|
1389
1441
|
Write lib.plist. This method requires a
|
|
1390
1442
|
lib dict as an argument.
|
|
@@ -1405,7 +1457,7 @@ class UFOWriter(UFOReader):
|
|
|
1405
1457
|
|
|
1406
1458
|
# features.fea
|
|
1407
1459
|
|
|
1408
|
-
def writeFeatures(self, features, validate=None):
|
|
1460
|
+
def writeFeatures(self, features: str, validate: Optional[bool] = None) -> None:
|
|
1409
1461
|
"""
|
|
1410
1462
|
Write features.fea. This method requires a
|
|
1411
1463
|
features string as an argument.
|
|
@@ -1424,7 +1476,9 @@ class UFOWriter(UFOReader):
|
|
|
1424
1476
|
|
|
1425
1477
|
# glyph sets & layers
|
|
1426
1478
|
|
|
1427
|
-
def writeLayerContents(
|
|
1479
|
+
def writeLayerContents(
|
|
1480
|
+
self, layerOrder: LayerOrderList = None, validate: Optional[bool] = None
|
|
1481
|
+
) -> None:
|
|
1428
1482
|
"""
|
|
1429
1483
|
Write the layercontents.plist file. This method *must* be called
|
|
1430
1484
|
after all glyph sets have been written.
|
|
@@ -1434,7 +1488,7 @@ class UFOWriter(UFOReader):
|
|
|
1434
1488
|
if self._formatVersion < UFOFormatVersion.FORMAT_3_0:
|
|
1435
1489
|
return
|
|
1436
1490
|
if layerOrder is not None:
|
|
1437
|
-
newOrder = []
|
|
1491
|
+
newOrder: list[Optional[str]] = []
|
|
1438
1492
|
for layerName in layerOrder:
|
|
1439
1493
|
if layerName is None:
|
|
1440
1494
|
layerName = DEFAULT_LAYER_NAME
|
|
@@ -1447,11 +1501,13 @@ class UFOWriter(UFOReader):
|
|
|
1447
1501
|
"The layer order content does not match the glyph sets that have been created."
|
|
1448
1502
|
)
|
|
1449
1503
|
layerContents = [
|
|
1450
|
-
(layerName, self.layerContents[layerName])
|
|
1504
|
+
(layerName, self.layerContents[layerName])
|
|
1505
|
+
for layerName in layerOrder
|
|
1506
|
+
if layerName is not None
|
|
1451
1507
|
]
|
|
1452
1508
|
self._writePlist(LAYERCONTENTS_FILENAME, layerContents)
|
|
1453
1509
|
|
|
1454
|
-
def _findDirectoryForLayerName(self, layerName):
|
|
1510
|
+
def _findDirectoryForLayerName(self, layerName: Optional[str]) -> str:
|
|
1455
1511
|
foundDirectory = None
|
|
1456
1512
|
for existingLayerName, directoryName in list(self.layerContents.items()):
|
|
1457
1513
|
if layerName is None and directoryName == DEFAULT_GLYPHS_DIRNAME:
|
|
@@ -1467,15 +1523,15 @@ class UFOWriter(UFOReader):
|
|
|
1467
1523
|
)
|
|
1468
1524
|
return foundDirectory
|
|
1469
1525
|
|
|
1470
|
-
def getGlyphSet(
|
|
1526
|
+
def getGlyphSet( # type: ignore[override]
|
|
1471
1527
|
self,
|
|
1472
|
-
layerName=None,
|
|
1473
|
-
defaultLayer=True,
|
|
1474
|
-
glyphNameToFileNameFunc=None,
|
|
1475
|
-
validateRead=None,
|
|
1476
|
-
validateWrite=None,
|
|
1477
|
-
expectContentsFile=False,
|
|
1478
|
-
):
|
|
1528
|
+
layerName: Optional[str] = None,
|
|
1529
|
+
defaultLayer: bool = True,
|
|
1530
|
+
glyphNameToFileNameFunc: GlyphNameToFileNameFunc = None,
|
|
1531
|
+
validateRead: Optional[bool] = None,
|
|
1532
|
+
validateWrite: Optional[bool] = None,
|
|
1533
|
+
expectContentsFile: bool = False,
|
|
1534
|
+
) -> GlyphSet:
|
|
1479
1535
|
"""
|
|
1480
1536
|
Return the GlyphSet object associated with the
|
|
1481
1537
|
appropriate glyph directory in the .ufo.
|
|
@@ -1535,11 +1591,11 @@ class UFOWriter(UFOReader):
|
|
|
1535
1591
|
|
|
1536
1592
|
def _getDefaultGlyphSet(
|
|
1537
1593
|
self,
|
|
1538
|
-
validateRead,
|
|
1539
|
-
validateWrite,
|
|
1540
|
-
glyphNameToFileNameFunc=None,
|
|
1541
|
-
expectContentsFile=False,
|
|
1542
|
-
):
|
|
1594
|
+
validateRead: bool,
|
|
1595
|
+
validateWrite: bool,
|
|
1596
|
+
glyphNameToFileNameFunc: GlyphNameToFileNameFunc = None,
|
|
1597
|
+
expectContentsFile: bool = False,
|
|
1598
|
+
) -> GlyphSet:
|
|
1543
1599
|
from fontTools.ufoLib.glifLib import GlyphSet
|
|
1544
1600
|
|
|
1545
1601
|
glyphSubFS = self.fs.makedir(DEFAULT_GLYPHS_DIRNAME, recreate=True)
|
|
@@ -1554,13 +1610,13 @@ class UFOWriter(UFOReader):
|
|
|
1554
1610
|
|
|
1555
1611
|
def _getGlyphSetFormatVersion3(
|
|
1556
1612
|
self,
|
|
1557
|
-
validateRead,
|
|
1558
|
-
validateWrite,
|
|
1559
|
-
layerName=None,
|
|
1560
|
-
defaultLayer=True,
|
|
1561
|
-
glyphNameToFileNameFunc=None,
|
|
1562
|
-
expectContentsFile=False,
|
|
1563
|
-
):
|
|
1613
|
+
validateRead: bool,
|
|
1614
|
+
validateWrite: bool,
|
|
1615
|
+
layerName: Optional[str] = None,
|
|
1616
|
+
defaultLayer: bool = True,
|
|
1617
|
+
glyphNameToFileNameFunc: GlyphNameToFileNameFunc = None,
|
|
1618
|
+
expectContentsFile: bool = False,
|
|
1619
|
+
) -> GlyphSet:
|
|
1564
1620
|
from fontTools.ufoLib.glifLib import GlyphSet
|
|
1565
1621
|
|
|
1566
1622
|
# if the default flag is on, make sure that the default in the file
|
|
@@ -1578,6 +1634,11 @@ class UFOWriter(UFOReader):
|
|
|
1578
1634
|
raise UFOLibError(
|
|
1579
1635
|
"The layer name is already mapped to a non-default layer."
|
|
1580
1636
|
)
|
|
1637
|
+
|
|
1638
|
+
# handle layerName is None to avoid MyPy errors
|
|
1639
|
+
if layerName is None:
|
|
1640
|
+
raise TypeError("'leyerName' cannot be None.")
|
|
1641
|
+
|
|
1581
1642
|
# get an existing directory name
|
|
1582
1643
|
if layerName in self.layerContents:
|
|
1583
1644
|
directory = self.layerContents[layerName]
|
|
@@ -1606,7 +1667,12 @@ class UFOWriter(UFOReader):
|
|
|
1606
1667
|
expectContentsFile=expectContentsFile,
|
|
1607
1668
|
)
|
|
1608
1669
|
|
|
1609
|
-
def renameGlyphSet(
|
|
1670
|
+
def renameGlyphSet(
|
|
1671
|
+
self,
|
|
1672
|
+
layerName: Optional[str],
|
|
1673
|
+
newLayerName: Optional[str],
|
|
1674
|
+
defaultLayer: bool = False,
|
|
1675
|
+
) -> None:
|
|
1610
1676
|
"""
|
|
1611
1677
|
Rename a glyph set.
|
|
1612
1678
|
|
|
@@ -1620,7 +1686,7 @@ class UFOWriter(UFOReader):
|
|
|
1620
1686
|
return
|
|
1621
1687
|
# the new and old names can be the same
|
|
1622
1688
|
# as long as the default is being switched
|
|
1623
|
-
if layerName == newLayerName:
|
|
1689
|
+
if layerName is not None and layerName == newLayerName:
|
|
1624
1690
|
# if the default is off and the layer is already not the default, skip
|
|
1625
1691
|
if (
|
|
1626
1692
|
self.layerContents[layerName] != DEFAULT_GLYPHS_DIRNAME
|
|
@@ -1649,12 +1715,13 @@ class UFOWriter(UFOReader):
|
|
|
1649
1715
|
newLayerName, existing=existing, prefix="glyphs."
|
|
1650
1716
|
)
|
|
1651
1717
|
# update the internal mapping
|
|
1652
|
-
|
|
1718
|
+
if layerName is not None:
|
|
1719
|
+
del self.layerContents[layerName]
|
|
1653
1720
|
self.layerContents[newLayerName] = newDirectory
|
|
1654
1721
|
# do the file system copy
|
|
1655
1722
|
self.fs.movedir(oldDirectory, newDirectory, create=True)
|
|
1656
1723
|
|
|
1657
|
-
def deleteGlyphSet(self, layerName):
|
|
1724
|
+
def deleteGlyphSet(self, layerName: Optional[str]) -> None:
|
|
1658
1725
|
"""
|
|
1659
1726
|
Remove the glyph set matching layerName.
|
|
1660
1727
|
"""
|
|
@@ -1664,16 +1731,17 @@ class UFOWriter(UFOReader):
|
|
|
1664
1731
|
return
|
|
1665
1732
|
foundDirectory = self._findDirectoryForLayerName(layerName)
|
|
1666
1733
|
self.removePath(foundDirectory, removeEmptyParents=False)
|
|
1667
|
-
|
|
1734
|
+
if layerName is not None:
|
|
1735
|
+
del self.layerContents[layerName]
|
|
1668
1736
|
|
|
1669
|
-
def writeData(self, fileName, data):
|
|
1737
|
+
def writeData(self, fileName: PathStr, data: bytes) -> None:
|
|
1670
1738
|
"""
|
|
1671
1739
|
Write data to fileName in the 'data' directory.
|
|
1672
1740
|
The data must be a bytes string.
|
|
1673
1741
|
"""
|
|
1674
1742
|
self.writeBytesToPath(f"{DATA_DIRNAME}/{fsdecode(fileName)}", data)
|
|
1675
1743
|
|
|
1676
|
-
def removeData(self, fileName):
|
|
1744
|
+
def removeData(self, fileName: PathStr) -> None:
|
|
1677
1745
|
"""
|
|
1678
1746
|
Remove the file named fileName from the data directory.
|
|
1679
1747
|
"""
|
|
@@ -1681,7 +1749,12 @@ class UFOWriter(UFOReader):
|
|
|
1681
1749
|
|
|
1682
1750
|
# /images
|
|
1683
1751
|
|
|
1684
|
-
def writeImage(
|
|
1752
|
+
def writeImage(
|
|
1753
|
+
self,
|
|
1754
|
+
fileName: PathStr,
|
|
1755
|
+
data: bytes,
|
|
1756
|
+
validate: Optional[bool] = None,
|
|
1757
|
+
) -> None:
|
|
1685
1758
|
"""
|
|
1686
1759
|
Write data to fileName in the images directory.
|
|
1687
1760
|
The data must be a valid PNG.
|
|
@@ -1699,7 +1772,11 @@ class UFOWriter(UFOReader):
|
|
|
1699
1772
|
raise UFOLibError(error)
|
|
1700
1773
|
self.writeBytesToPath(f"{IMAGES_DIRNAME}/{fileName}", data)
|
|
1701
1774
|
|
|
1702
|
-
def removeImage(
|
|
1775
|
+
def removeImage(
|
|
1776
|
+
self,
|
|
1777
|
+
fileName: PathStr,
|
|
1778
|
+
validate: Optional[bool] = None,
|
|
1779
|
+
) -> None: # XXX remove unused 'validate'?
|
|
1703
1780
|
"""
|
|
1704
1781
|
Remove the file named fileName from the
|
|
1705
1782
|
images directory.
|
|
@@ -1710,7 +1787,13 @@ class UFOWriter(UFOReader):
|
|
|
1710
1787
|
)
|
|
1711
1788
|
self.removePath(f"{IMAGES_DIRNAME}/{fsdecode(fileName)}")
|
|
1712
1789
|
|
|
1713
|
-
def copyImageFromReader(
|
|
1790
|
+
def copyImageFromReader(
|
|
1791
|
+
self,
|
|
1792
|
+
reader: UFOReader,
|
|
1793
|
+
sourceFileName: PathStr,
|
|
1794
|
+
destFileName: PathStr,
|
|
1795
|
+
validate: Optional[bool] = None,
|
|
1796
|
+
) -> None:
|
|
1714
1797
|
"""
|
|
1715
1798
|
Copy the sourceFileName in the provided UFOReader to destFileName
|
|
1716
1799
|
in this writer. This uses the most memory efficient method possible
|
|
@@ -1726,12 +1809,12 @@ class UFOWriter(UFOReader):
|
|
|
1726
1809
|
destPath = f"{IMAGES_DIRNAME}/{fsdecode(destFileName)}"
|
|
1727
1810
|
self.copyFromReader(reader, sourcePath, destPath)
|
|
1728
1811
|
|
|
1729
|
-
def close(self):
|
|
1812
|
+
def close(self) -> None:
|
|
1730
1813
|
if self._havePreviousFile and self._fileStructure is UFOFileStructure.ZIP:
|
|
1731
1814
|
# if we are updating an existing zip file, we can now compress the
|
|
1732
1815
|
# contents of the temporary filesystem in the destination path
|
|
1733
1816
|
rootDir = os.path.splitext(os.path.basename(self._path))[0] + ".ufo"
|
|
1734
|
-
with fs.zipfs.ZipFS(self._path, write=True, encoding="utf-8") as destFS:
|
|
1817
|
+
with fs.zipfs.ZipFS(self._path, write=True, encoding="utf-8") as destFS: # type: ignore[abstract]
|
|
1735
1818
|
fs.copy.copy_fs(self.fs, destFS.makedir(rootDir))
|
|
1736
1819
|
super().close()
|
|
1737
1820
|
|
|
@@ -1745,7 +1828,7 @@ UFOReaderWriter = UFOWriter
|
|
|
1745
1828
|
# ----------------
|
|
1746
1829
|
|
|
1747
1830
|
|
|
1748
|
-
def _sniffFileStructure(ufo_path):
|
|
1831
|
+
def _sniffFileStructure(ufo_path: PathStr) -> UFOFileStructure:
|
|
1749
1832
|
"""Return UFOFileStructure.ZIP if the UFO at path 'ufo_path' (str)
|
|
1750
1833
|
is a zip file, else return UFOFileStructure.PACKAGE if 'ufo_path' is a
|
|
1751
1834
|
directory.
|
|
@@ -1764,7 +1847,7 @@ def _sniffFileStructure(ufo_path):
|
|
|
1764
1847
|
raise UFOLibError("No such file or directory: '%s'" % ufo_path)
|
|
1765
1848
|
|
|
1766
1849
|
|
|
1767
|
-
def makeUFOPath(path):
|
|
1850
|
+
def makeUFOPath(path: PathStr) -> str:
|
|
1768
1851
|
"""
|
|
1769
1852
|
Return a .ufo pathname.
|
|
1770
1853
|
|
|
@@ -1791,7 +1874,7 @@ def makeUFOPath(path):
|
|
|
1791
1874
|
# cases of invalid values.
|
|
1792
1875
|
|
|
1793
1876
|
|
|
1794
|
-
def validateFontInfoVersion2ValueForAttribute(attr, value):
|
|
1877
|
+
def validateFontInfoVersion2ValueForAttribute(attr: str, value: Any) -> bool:
|
|
1795
1878
|
"""
|
|
1796
1879
|
This performs very basic validation of the value for attribute
|
|
1797
1880
|
following the UFO 2 fontinfo.plist specification. The results
|
|
@@ -1803,7 +1886,7 @@ def validateFontInfoVersion2ValueForAttribute(attr, value):
|
|
|
1803
1886
|
"""
|
|
1804
1887
|
dataValidationDict = fontInfoAttributesVersion2ValueData[attr]
|
|
1805
1888
|
valueType = dataValidationDict.get("type")
|
|
1806
|
-
validator = dataValidationDict.get("valueValidator")
|
|
1889
|
+
validator = dataValidationDict.get("valueValidator", genericTypeValidator)
|
|
1807
1890
|
valueOptions = dataValidationDict.get("valueOptions")
|
|
1808
1891
|
# have specific options for the validator
|
|
1809
1892
|
if valueOptions is not None:
|
|
@@ -1817,7 +1900,7 @@ def validateFontInfoVersion2ValueForAttribute(attr, value):
|
|
|
1817
1900
|
return isValidValue
|
|
1818
1901
|
|
|
1819
1902
|
|
|
1820
|
-
def validateInfoVersion2Data(infoData):
|
|
1903
|
+
def validateInfoVersion2Data(infoData: dict[str, Any]) -> dict[str, Any]:
|
|
1821
1904
|
"""
|
|
1822
1905
|
This performs very basic validation of the value for infoData
|
|
1823
1906
|
following the UFO 2 fontinfo.plist specification. The results
|
|
@@ -1837,7 +1920,7 @@ def validateInfoVersion2Data(infoData):
|
|
|
1837
1920
|
return validInfoData
|
|
1838
1921
|
|
|
1839
1922
|
|
|
1840
|
-
def validateFontInfoVersion3ValueForAttribute(attr, value):
|
|
1923
|
+
def validateFontInfoVersion3ValueForAttribute(attr: str, value: Any) -> bool:
|
|
1841
1924
|
"""
|
|
1842
1925
|
This performs very basic validation of the value for attribute
|
|
1843
1926
|
following the UFO 3 fontinfo.plist specification. The results
|
|
@@ -1849,7 +1932,7 @@ def validateFontInfoVersion3ValueForAttribute(attr, value):
|
|
|
1849
1932
|
"""
|
|
1850
1933
|
dataValidationDict = fontInfoAttributesVersion3ValueData[attr]
|
|
1851
1934
|
valueType = dataValidationDict.get("type")
|
|
1852
|
-
validator = dataValidationDict.get("valueValidator")
|
|
1935
|
+
validator = dataValidationDict.get("valueValidator", genericTypeValidator)
|
|
1853
1936
|
valueOptions = dataValidationDict.get("valueOptions")
|
|
1854
1937
|
# have specific options for the validator
|
|
1855
1938
|
if valueOptions is not None:
|
|
@@ -1863,7 +1946,7 @@ def validateFontInfoVersion3ValueForAttribute(attr, value):
|
|
|
1863
1946
|
return isValidValue
|
|
1864
1947
|
|
|
1865
1948
|
|
|
1866
|
-
def validateInfoVersion3Data(infoData):
|
|
1949
|
+
def validateInfoVersion3Data(infoData: dict[str, Any]) -> dict[str, Any]:
|
|
1867
1950
|
"""
|
|
1868
1951
|
This performs very basic validation of the value for infoData
|
|
1869
1952
|
following the UFO 3 fontinfo.plist specification. The results
|
|
@@ -1885,18 +1968,18 @@ def validateInfoVersion3Data(infoData):
|
|
|
1885
1968
|
|
|
1886
1969
|
# Value Options
|
|
1887
1970
|
|
|
1888
|
-
fontInfoOpenTypeHeadFlagsOptions = list(range(0, 15))
|
|
1889
|
-
fontInfoOpenTypeOS2SelectionOptions = [1, 2, 3, 4, 7, 8, 9]
|
|
1890
|
-
fontInfoOpenTypeOS2UnicodeRangesOptions = list(range(0, 128))
|
|
1891
|
-
fontInfoOpenTypeOS2CodePageRangesOptions = list(range(0, 64))
|
|
1892
|
-
fontInfoOpenTypeOS2TypeOptions = [0, 1, 2, 3, 8, 9]
|
|
1971
|
+
fontInfoOpenTypeHeadFlagsOptions: list[int] = list(range(0, 15))
|
|
1972
|
+
fontInfoOpenTypeOS2SelectionOptions: list[int] = [1, 2, 3, 4, 7, 8, 9]
|
|
1973
|
+
fontInfoOpenTypeOS2UnicodeRangesOptions: list[int] = list(range(0, 128))
|
|
1974
|
+
fontInfoOpenTypeOS2CodePageRangesOptions: list[int] = list(range(0, 64))
|
|
1975
|
+
fontInfoOpenTypeOS2TypeOptions: list[int] = [0, 1, 2, 3, 8, 9]
|
|
1893
1976
|
|
|
1894
1977
|
# Version Attribute Definitions
|
|
1895
1978
|
# This defines the attributes, types and, in some
|
|
1896
1979
|
# cases the possible values, that can exist is
|
|
1897
1980
|
# fontinfo.plist.
|
|
1898
1981
|
|
|
1899
|
-
fontInfoAttributesVersion1 = {
|
|
1982
|
+
fontInfoAttributesVersion1: set[str] = {
|
|
1900
1983
|
"familyName",
|
|
1901
1984
|
"styleName",
|
|
1902
1985
|
"fullName",
|
|
@@ -1939,7 +2022,7 @@ fontInfoAttributesVersion1 = {
|
|
|
1939
2022
|
"ttVersion",
|
|
1940
2023
|
}
|
|
1941
2024
|
|
|
1942
|
-
fontInfoAttributesVersion2ValueData = {
|
|
2025
|
+
fontInfoAttributesVersion2ValueData: FontInfoAttributes = {
|
|
1943
2026
|
"familyName": dict(type=str),
|
|
1944
2027
|
"styleName": dict(type=str),
|
|
1945
2028
|
"styleMapFamilyName": dict(type=str),
|
|
@@ -2081,9 +2164,11 @@ fontInfoAttributesVersion2ValueData = {
|
|
|
2081
2164
|
"macintoshFONDFamilyID": dict(type=int),
|
|
2082
2165
|
"macintoshFONDName": dict(type=str),
|
|
2083
2166
|
}
|
|
2084
|
-
fontInfoAttributesVersion2 = set(fontInfoAttributesVersion2ValueData.keys())
|
|
2167
|
+
fontInfoAttributesVersion2: set[str] = set(fontInfoAttributesVersion2ValueData.keys())
|
|
2085
2168
|
|
|
2086
|
-
fontInfoAttributesVersion3ValueData = deepcopy(
|
|
2169
|
+
fontInfoAttributesVersion3ValueData: FontInfoAttributes = deepcopy(
|
|
2170
|
+
fontInfoAttributesVersion2ValueData
|
|
2171
|
+
)
|
|
2087
2172
|
fontInfoAttributesVersion3ValueData.update(
|
|
2088
2173
|
{
|
|
2089
2174
|
"versionMinor": dict(type=int, valueValidator=genericNonNegativeIntValidator),
|
|
@@ -2166,7 +2251,7 @@ fontInfoAttributesVersion3ValueData.update(
|
|
|
2166
2251
|
"guidelines": dict(type=list, valueValidator=guidelinesValidator),
|
|
2167
2252
|
}
|
|
2168
2253
|
)
|
|
2169
|
-
fontInfoAttributesVersion3 = set(fontInfoAttributesVersion3ValueData.keys())
|
|
2254
|
+
fontInfoAttributesVersion3: set[str] = set(fontInfoAttributesVersion3ValueData.keys())
|
|
2170
2255
|
|
|
2171
2256
|
# insert the type validator for all attrs that
|
|
2172
2257
|
# have no defined validator.
|
|
@@ -2183,14 +2268,14 @@ for attr, dataDict in list(fontInfoAttributesVersion3ValueData.items()):
|
|
|
2183
2268
|
# to version 2 or vice-versa.
|
|
2184
2269
|
|
|
2185
2270
|
|
|
2186
|
-
def _flipDict(d):
|
|
2271
|
+
def _flipDict(d: dict[K, V]) -> dict[V, K]:
|
|
2187
2272
|
flipped = {}
|
|
2188
2273
|
for key, value in list(d.items()):
|
|
2189
2274
|
flipped[value] = key
|
|
2190
2275
|
return flipped
|
|
2191
2276
|
|
|
2192
2277
|
|
|
2193
|
-
fontInfoAttributesVersion1To2 = {
|
|
2278
|
+
fontInfoAttributesVersion1To2: dict[str, str] = {
|
|
2194
2279
|
"menuName": "styleMapFamilyName",
|
|
2195
2280
|
"designer": "openTypeNameDesigner",
|
|
2196
2281
|
"designerURL": "openTypeNameDesignerURL",
|
|
@@ -2222,12 +2307,17 @@ fontInfoAttributesVersion1To2 = {
|
|
|
2222
2307
|
fontInfoAttributesVersion2To1 = _flipDict(fontInfoAttributesVersion1To2)
|
|
2223
2308
|
deprecatedFontInfoAttributesVersion2 = set(fontInfoAttributesVersion1To2.keys())
|
|
2224
2309
|
|
|
2225
|
-
_fontStyle1To2
|
|
2226
|
-
|
|
2310
|
+
_fontStyle1To2: dict[int, str] = {
|
|
2311
|
+
64: "regular",
|
|
2312
|
+
1: "italic",
|
|
2313
|
+
32: "bold",
|
|
2314
|
+
33: "bold italic",
|
|
2315
|
+
}
|
|
2316
|
+
_fontStyle2To1: dict[str, int] = _flipDict(_fontStyle1To2)
|
|
2227
2317
|
# Some UFO 1 files have 0
|
|
2228
2318
|
_fontStyle1To2[0] = "regular"
|
|
2229
2319
|
|
|
2230
|
-
_widthName1To2 = {
|
|
2320
|
+
_widthName1To2: dict[str, int] = {
|
|
2231
2321
|
"Ultra-condensed": 1,
|
|
2232
2322
|
"Extra-condensed": 2,
|
|
2233
2323
|
"Condensed": 3,
|
|
@@ -2238,7 +2328,7 @@ _widthName1To2 = {
|
|
|
2238
2328
|
"Extra-expanded": 8,
|
|
2239
2329
|
"Ultra-expanded": 9,
|
|
2240
2330
|
}
|
|
2241
|
-
_widthName2To1 = _flipDict(_widthName1To2)
|
|
2331
|
+
_widthName2To1: dict[int, str] = _flipDict(_widthName1To2)
|
|
2242
2332
|
# FontLab's default width value is "Normal".
|
|
2243
2333
|
# Many format version 1 UFOs will have this.
|
|
2244
2334
|
_widthName1To2["Normal"] = 5
|
|
@@ -2250,7 +2340,7 @@ _widthName1To2["medium"] = 5
|
|
|
2250
2340
|
# "Medium" appears in a lot of UFO 1 files.
|
|
2251
2341
|
_widthName1To2["Medium"] = 5
|
|
2252
2342
|
|
|
2253
|
-
_msCharSet1To2 = {
|
|
2343
|
+
_msCharSet1To2: dict[int, int] = {
|
|
2254
2344
|
0: 1,
|
|
2255
2345
|
1: 2,
|
|
2256
2346
|
2: 3,
|
|
@@ -2272,12 +2362,14 @@ _msCharSet1To2 = {
|
|
|
2272
2362
|
238: 19,
|
|
2273
2363
|
255: 20,
|
|
2274
2364
|
}
|
|
2275
|
-
_msCharSet2To1 = _flipDict(_msCharSet1To2)
|
|
2365
|
+
_msCharSet2To1: dict[int, int] = _flipDict(_msCharSet1To2)
|
|
2276
2366
|
|
|
2277
2367
|
# 1 <-> 2
|
|
2278
2368
|
|
|
2279
2369
|
|
|
2280
|
-
def convertFontInfoValueForAttributeFromVersion1ToVersion2(
|
|
2370
|
+
def convertFontInfoValueForAttributeFromVersion1ToVersion2(
|
|
2371
|
+
attr: str, value: Any
|
|
2372
|
+
) -> tuple[str, Any]:
|
|
2281
2373
|
"""
|
|
2282
2374
|
Convert value from version 1 to version 2 format.
|
|
2283
2375
|
Returns the new attribute name and the converted value.
|
|
@@ -2289,7 +2381,7 @@ def convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value):
|
|
|
2289
2381
|
value = int(value)
|
|
2290
2382
|
if value is not None:
|
|
2291
2383
|
if attr == "fontStyle":
|
|
2292
|
-
v = _fontStyle1To2.get(value)
|
|
2384
|
+
v: Optional[Union[str, int]] = _fontStyle1To2.get(value)
|
|
2293
2385
|
if v is None:
|
|
2294
2386
|
raise UFOLibError(
|
|
2295
2387
|
f"Cannot convert value ({value!r}) for attribute {attr}."
|
|
@@ -2313,7 +2405,9 @@ def convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value):
|
|
|
2313
2405
|
return attr, value
|
|
2314
2406
|
|
|
2315
2407
|
|
|
2316
|
-
def convertFontInfoValueForAttributeFromVersion2ToVersion1(
|
|
2408
|
+
def convertFontInfoValueForAttributeFromVersion2ToVersion1(
|
|
2409
|
+
attr: str, value: Any
|
|
2410
|
+
) -> tuple[str, Any]:
|
|
2317
2411
|
"""
|
|
2318
2412
|
Convert value from version 2 to version 1 format.
|
|
2319
2413
|
Returns the new attribute name and the converted value.
|
|
@@ -2330,7 +2424,7 @@ def convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value):
|
|
|
2330
2424
|
return attr, value
|
|
2331
2425
|
|
|
2332
2426
|
|
|
2333
|
-
def _convertFontInfoDataVersion1ToVersion2(data):
|
|
2427
|
+
def _convertFontInfoDataVersion1ToVersion2(data: dict[str, Any]) -> dict[str, Any]:
|
|
2334
2428
|
converted = {}
|
|
2335
2429
|
for attr, value in list(data.items()):
|
|
2336
2430
|
# FontLab gives -1 for the weightValue
|
|
@@ -2354,7 +2448,7 @@ def _convertFontInfoDataVersion1ToVersion2(data):
|
|
|
2354
2448
|
return converted
|
|
2355
2449
|
|
|
2356
2450
|
|
|
2357
|
-
def _convertFontInfoDataVersion2ToVersion1(data):
|
|
2451
|
+
def _convertFontInfoDataVersion2ToVersion1(data: dict[str, Any]) -> dict[str, Any]:
|
|
2358
2452
|
converted = {}
|
|
2359
2453
|
for attr, value in list(data.items()):
|
|
2360
2454
|
newAttr, newValue = convertFontInfoValueForAttributeFromVersion2ToVersion1(
|
|
@@ -2375,16 +2469,16 @@ def _convertFontInfoDataVersion2ToVersion1(data):
|
|
|
2375
2469
|
|
|
2376
2470
|
# 2 <-> 3
|
|
2377
2471
|
|
|
2378
|
-
_ufo2To3NonNegativeInt = {
|
|
2472
|
+
_ufo2To3NonNegativeInt: set[str] = {
|
|
2379
2473
|
"versionMinor",
|
|
2380
2474
|
"openTypeHeadLowestRecPPEM",
|
|
2381
2475
|
"openTypeOS2WinAscent",
|
|
2382
2476
|
"openTypeOS2WinDescent",
|
|
2383
2477
|
}
|
|
2384
|
-
_ufo2To3NonNegativeIntOrFloat = {
|
|
2478
|
+
_ufo2To3NonNegativeIntOrFloat: set[str] = {
|
|
2385
2479
|
"unitsPerEm",
|
|
2386
2480
|
}
|
|
2387
|
-
_ufo2To3FloatToInt = {
|
|
2481
|
+
_ufo2To3FloatToInt: set[str] = {
|
|
2388
2482
|
"openTypeHeadLowestRecPPEM",
|
|
2389
2483
|
"openTypeHheaAscender",
|
|
2390
2484
|
"openTypeHheaDescender",
|
|
@@ -2412,7 +2506,9 @@ _ufo2To3FloatToInt = {
|
|
|
2412
2506
|
}
|
|
2413
2507
|
|
|
2414
2508
|
|
|
2415
|
-
def convertFontInfoValueForAttributeFromVersion2ToVersion3(
|
|
2509
|
+
def convertFontInfoValueForAttributeFromVersion2ToVersion3(
|
|
2510
|
+
attr: str, value: Any
|
|
2511
|
+
) -> tuple[str, Any]:
|
|
2416
2512
|
"""
|
|
2417
2513
|
Convert value from version 2 to version 3 format.
|
|
2418
2514
|
Returns the new attribute name and the converted value.
|
|
@@ -2440,7 +2536,9 @@ def convertFontInfoValueForAttributeFromVersion2ToVersion3(attr, value):
|
|
|
2440
2536
|
return attr, value
|
|
2441
2537
|
|
|
2442
2538
|
|
|
2443
|
-
def convertFontInfoValueForAttributeFromVersion3ToVersion2(
|
|
2539
|
+
def convertFontInfoValueForAttributeFromVersion3ToVersion2(
|
|
2540
|
+
attr: str, value: Any
|
|
2541
|
+
) -> tuple[str, Any]:
|
|
2444
2542
|
"""
|
|
2445
2543
|
Convert value from version 3 to version 2 format.
|
|
2446
2544
|
Returns the new attribute name and the converted value.
|
|
@@ -2449,7 +2547,7 @@ def convertFontInfoValueForAttributeFromVersion3ToVersion2(attr, value):
|
|
|
2449
2547
|
return attr, value
|
|
2450
2548
|
|
|
2451
2549
|
|
|
2452
|
-
def _convertFontInfoDataVersion3ToVersion2(data):
|
|
2550
|
+
def _convertFontInfoDataVersion3ToVersion2(data: dict[str, Any]) -> dict[str, Any]:
|
|
2453
2551
|
converted = {}
|
|
2454
2552
|
for attr, value in list(data.items()):
|
|
2455
2553
|
newAttr, newValue = convertFontInfoValueForAttributeFromVersion3ToVersion2(
|
|
@@ -2461,7 +2559,7 @@ def _convertFontInfoDataVersion3ToVersion2(data):
|
|
|
2461
2559
|
return converted
|
|
2462
2560
|
|
|
2463
2561
|
|
|
2464
|
-
def _convertFontInfoDataVersion2ToVersion3(data):
|
|
2562
|
+
def _convertFontInfoDataVersion2ToVersion3(data: dict[str, Any]) -> dict[str, Any]:
|
|
2465
2563
|
converted = {}
|
|
2466
2564
|
for attr, value in list(data.items()):
|
|
2467
2565
|
attr, value = convertFontInfoValueForAttributeFromVersion2ToVersion3(
|