fonttools 4.58.5__cp311-cp311-win32.whl → 4.59.1__cp311-cp311-win32.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/cffLib/CFF2ToCFF.py +40 -10
- fontTools/cffLib/transforms.py +11 -6
- fontTools/cu2qu/cu2qu.c +30 -16
- fontTools/cu2qu/cu2qu.cp311-win32.pyd +0 -0
- fontTools/feaLib/builder.py +6 -1
- fontTools/feaLib/lexer.c +30 -16
- fontTools/feaLib/lexer.cp311-win32.pyd +0 -0
- fontTools/merge/__init__.py +1 -1
- fontTools/misc/bezierTools.c +33 -19
- fontTools/misc/bezierTools.cp311-win32.pyd +0 -0
- 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/psCharStrings.py +17 -2
- fontTools/misc/sstruct.py +2 -6
- fontTools/misc/xmlWriter.py +28 -1
- fontTools/pens/momentsPen.c +20 -14
- fontTools/pens/momentsPen.cp311-win32.pyd +0 -0
- fontTools/pens/roundingPen.py +2 -2
- fontTools/qu2cu/qu2cu.c +32 -18
- fontTools/qu2cu/qu2cu.cp311-win32.pyd +0 -0
- fontTools/subset/__init__.py +11 -11
- fontTools/ttLib/sfnt.py +2 -3
- fontTools/ttLib/tables/S__i_l_f.py +2 -2
- fontTools/ttLib/tables/T_S_I__1.py +2 -5
- fontTools/ttLib/tables/T_S_I__5.py +2 -2
- fontTools/ttLib/tables/_c_m_a_p.py +1 -1
- fontTools/ttLib/tables/_c_v_t.py +1 -2
- fontTools/ttLib/tables/_g_l_y_f.py +9 -10
- fontTools/ttLib/tables/_g_v_a_r.py +6 -3
- 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/_p_o_s_t.py +3 -4
- fontTools/ttLib/tables/otBase.py +2 -4
- fontTools/ttLib/tables/otTables.py +7 -12
- fontTools/ttLib/tables/sbixStrike.py +3 -3
- fontTools/ttLib/ttFont.py +7 -16
- fontTools/ttLib/woff2.py +10 -13
- fontTools/ufoLib/__init__.py +20 -25
- fontTools/ufoLib/glifLib.py +12 -17
- fontTools/ufoLib/validators.py +2 -4
- fontTools/unicodedata/__init__.py +2 -0
- fontTools/varLib/featureVars.py +8 -0
- fontTools/varLib/hvar.py +1 -1
- fontTools/varLib/instancer/__init__.py +65 -5
- fontTools/varLib/iup.c +32 -18
- fontTools/varLib/iup.cp311-win32.pyd +0 -0
- fontTools/varLib/mutator.py +11 -0
- {fonttools-4.58.5.dist-info → fonttools-4.59.1.dist-info}/METADATA +42 -12
- {fonttools-4.58.5.dist-info → fonttools-4.59.1.dist-info}/RECORD +67 -55
- {fonttools-4.58.5.dist-info → fonttools-4.59.1.dist-info}/licenses/LICENSE.external +29 -0
- {fonttools-4.58.5.data → fonttools-4.59.1.data}/data/share/man/man1/ttx.1 +0 -0
- {fonttools-4.58.5.dist-info → fonttools-4.59.1.dist-info}/WHEEL +0 -0
- {fonttools-4.58.5.dist-info → fonttools-4.59.1.dist-info}/entry_points.txt +0 -0
- {fonttools-4.58.5.dist-info → fonttools-4.59.1.dist-info}/licenses/LICENSE +0 -0
- {fonttools-4.58.5.dist-info → fonttools-4.59.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
import tempfile
|
|
5
|
+
|
|
6
|
+
from ._errors import OperationFailed
|
|
7
|
+
from ._osfs import OSFS
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TempFS(OSFS):
|
|
11
|
+
def __init__(self, auto_clean: bool = True, ignore_clean_errors: bool = True):
|
|
12
|
+
self.auto_clean = auto_clean
|
|
13
|
+
self.ignore_clean_errors = ignore_clean_errors
|
|
14
|
+
self._temp_dir = tempfile.mkdtemp("__temp_fs__")
|
|
15
|
+
self._cleaned = False
|
|
16
|
+
super().__init__(self._temp_dir)
|
|
17
|
+
|
|
18
|
+
def close(self):
|
|
19
|
+
if self.auto_clean:
|
|
20
|
+
self.clean()
|
|
21
|
+
super().close()
|
|
22
|
+
|
|
23
|
+
def clean(self):
|
|
24
|
+
if self._cleaned:
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
shutil.rmtree(self._temp_dir)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
if not self.ignore_clean_errors:
|
|
31
|
+
raise OperationFailed(
|
|
32
|
+
f"failed to remove temporary directory: {self._temp_dir!r}"
|
|
33
|
+
) from e
|
|
34
|
+
self._cleaned = True
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
|
|
6
|
+
from ._errors import DirectoryNotEmpty
|
|
7
|
+
|
|
8
|
+
if typing.TYPE_CHECKING:
|
|
9
|
+
from typing import IO
|
|
10
|
+
|
|
11
|
+
from ._base import FS
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def remove_empty(fs: FS, path: str):
|
|
15
|
+
"""Remove all empty parents."""
|
|
16
|
+
path = PurePosixPath(path)
|
|
17
|
+
root = PurePosixPath("/")
|
|
18
|
+
try:
|
|
19
|
+
while path != root:
|
|
20
|
+
fs.removedir(path.as_posix())
|
|
21
|
+
path = path.parent
|
|
22
|
+
except DirectoryNotEmpty:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def copy_file_data(src_file: IO, dst_file: IO, chunk_size: int | None = None):
|
|
27
|
+
"""Copy data from one file object to another."""
|
|
28
|
+
_chunk_size = 1024 * 1024 if chunk_size is None else chunk_size
|
|
29
|
+
read = src_file.read
|
|
30
|
+
write = dst_file.write
|
|
31
|
+
# in iter(callable, sentilel), callable is called until it returns the sentinel;
|
|
32
|
+
# this allows to copy `chunk_size` bytes at a time.
|
|
33
|
+
for chunk in iter(lambda: read(_chunk_size) or None, None):
|
|
34
|
+
write(chunk)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
from collections import deque
|
|
5
|
+
from collections.abc import Collection, Iterator
|
|
6
|
+
|
|
7
|
+
from ._path import combine
|
|
8
|
+
|
|
9
|
+
if typing.TYPE_CHECKING:
|
|
10
|
+
from typing import Callable
|
|
11
|
+
|
|
12
|
+
from ._base import FS
|
|
13
|
+
from ._info import Info
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BoundWalker:
|
|
17
|
+
def __init__(self, fs: FS):
|
|
18
|
+
self._fs = fs
|
|
19
|
+
|
|
20
|
+
def _iter_walk(
|
|
21
|
+
self, path: str, namespaces: Collection[str] | None = None
|
|
22
|
+
) -> Iterator[tuple[str, Info | None]]:
|
|
23
|
+
"""Walk files using a *breadth first* search."""
|
|
24
|
+
queue = deque([path])
|
|
25
|
+
push = queue.appendleft
|
|
26
|
+
pop = queue.pop
|
|
27
|
+
_scan = self._fs.scandir
|
|
28
|
+
_combine = combine
|
|
29
|
+
|
|
30
|
+
while queue:
|
|
31
|
+
dir_path = pop()
|
|
32
|
+
for info in _scan(dir_path, namespaces=namespaces):
|
|
33
|
+
if info.is_dir:
|
|
34
|
+
yield dir_path, info
|
|
35
|
+
push(_combine(dir_path, info.name))
|
|
36
|
+
else:
|
|
37
|
+
yield dir_path, info
|
|
38
|
+
yield path, None
|
|
39
|
+
|
|
40
|
+
def _filter(
|
|
41
|
+
self,
|
|
42
|
+
include: Callable[[str, Info], bool] = lambda path, info: True,
|
|
43
|
+
path: str = "/",
|
|
44
|
+
namespaces: Collection[str] | None = None,
|
|
45
|
+
) -> Iterator[str]:
|
|
46
|
+
_combine = combine
|
|
47
|
+
for path, info in self._iter_walk(path, namespaces):
|
|
48
|
+
if info is not None and include(path, info):
|
|
49
|
+
yield _combine(path, info.name)
|
|
50
|
+
|
|
51
|
+
def files(self, path: str = "/") -> Iterator[str]:
|
|
52
|
+
yield from self._filter(lambda _, info: info.is_file, path)
|
|
53
|
+
|
|
54
|
+
def dirs(self, path: str = "/") -> Iterator[str]:
|
|
55
|
+
yield from self._filter(lambda _, info: info.is_dir, path)
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import stat
|
|
7
|
+
import typing
|
|
8
|
+
import zipfile
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
from ._base import FS
|
|
12
|
+
from ._errors import FileExpected, ResourceNotFound, ResourceReadOnly
|
|
13
|
+
from ._info import Info
|
|
14
|
+
from ._path import dirname, forcedir, normpath, relpath
|
|
15
|
+
from ._tempfs import TempFS
|
|
16
|
+
|
|
17
|
+
if typing.TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Collection
|
|
19
|
+
from typing import IO, Any
|
|
20
|
+
|
|
21
|
+
from ._subfs import SubFS
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ZipFS(FS):
|
|
25
|
+
"""Read and write zip files."""
|
|
26
|
+
|
|
27
|
+
def __new__(
|
|
28
|
+
cls, file: str | os.PathLike, write: bool = False, encoding: str = "utf-8"
|
|
29
|
+
):
|
|
30
|
+
if write:
|
|
31
|
+
return WriteZipFS(file, encoding)
|
|
32
|
+
else:
|
|
33
|
+
return ReadZipFS(file, encoding)
|
|
34
|
+
|
|
35
|
+
if typing.TYPE_CHECKING:
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self, file: str | os.PathLike, write: bool = False, encoding: str = "utf-8"
|
|
39
|
+
):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ReadZipFS(FS):
|
|
44
|
+
"""A readable zip file."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, file: str | os.PathLike, encoding: str = "utf-8"):
|
|
47
|
+
super().__init__()
|
|
48
|
+
self._file = os.fspath(file)
|
|
49
|
+
self.encoding = encoding # unused
|
|
50
|
+
self._zip = zipfile.ZipFile(file, "r")
|
|
51
|
+
self._directory_fs = None
|
|
52
|
+
|
|
53
|
+
def __repr__(self) -> str:
|
|
54
|
+
return f"ReadZipFS({self._file!r})"
|
|
55
|
+
|
|
56
|
+
def __str__(self) -> str:
|
|
57
|
+
return f"<zipfs '{self._file}'>"
|
|
58
|
+
|
|
59
|
+
def _path_to_zip_name(self, path: str) -> str:
|
|
60
|
+
"""Convert a path to a zip file name."""
|
|
61
|
+
path = relpath(normpath(path))
|
|
62
|
+
if self._directory.isdir(path):
|
|
63
|
+
path = forcedir(path)
|
|
64
|
+
return path
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def _directory(self) -> TempFS:
|
|
68
|
+
if self._directory_fs is None:
|
|
69
|
+
self._directory_fs = _fs = TempFS()
|
|
70
|
+
for zip_name in self._zip.namelist():
|
|
71
|
+
resource_name = zip_name
|
|
72
|
+
if resource_name.endswith("/"):
|
|
73
|
+
_fs.makedirs(resource_name, recreate=True)
|
|
74
|
+
else:
|
|
75
|
+
_fs.makedirs(dirname(resource_name), recreate=True)
|
|
76
|
+
_fs.create(resource_name)
|
|
77
|
+
return self._directory_fs
|
|
78
|
+
|
|
79
|
+
def close(self):
|
|
80
|
+
super(ReadZipFS, self).close()
|
|
81
|
+
self._zip.close()
|
|
82
|
+
if self._directory_fs is not None:
|
|
83
|
+
self._directory_fs.close()
|
|
84
|
+
|
|
85
|
+
def getinfo(self, path: str, namespaces: Collection[str] | None = None) -> Info:
|
|
86
|
+
namespaces = namespaces or ()
|
|
87
|
+
raw_info = {}
|
|
88
|
+
|
|
89
|
+
if path == "/":
|
|
90
|
+
raw_info["basic"] = {"name": "", "is_dir": True}
|
|
91
|
+
if "details" in namespaces:
|
|
92
|
+
raw_info["details"] = {"type": stat.S_IFDIR}
|
|
93
|
+
else:
|
|
94
|
+
basic_info = self._directory.getinfo(path)
|
|
95
|
+
raw_info["basic"] = {"name": basic_info.name, "is_dir": basic_info.is_dir}
|
|
96
|
+
|
|
97
|
+
if "details" in namespaces:
|
|
98
|
+
zip_name = self._path_to_zip_name(path)
|
|
99
|
+
try:
|
|
100
|
+
zip_info = self._zip.getinfo(zip_name)
|
|
101
|
+
except KeyError:
|
|
102
|
+
pass
|
|
103
|
+
else:
|
|
104
|
+
if "details" in namespaces:
|
|
105
|
+
raw_info["details"] = {
|
|
106
|
+
"size": zip_info.file_size,
|
|
107
|
+
"type": int(
|
|
108
|
+
stat.S_IFDIR if basic_info.is_dir else stat.S_IFREG
|
|
109
|
+
),
|
|
110
|
+
"modified": datetime(*zip_info.date_time).timestamp(),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return Info(raw_info)
|
|
114
|
+
|
|
115
|
+
def exists(self, path: str) -> bool:
|
|
116
|
+
self.check()
|
|
117
|
+
return self._directory.exists(path)
|
|
118
|
+
|
|
119
|
+
def isdir(self, path: str) -> bool:
|
|
120
|
+
self.check()
|
|
121
|
+
return self._directory.isdir(path)
|
|
122
|
+
|
|
123
|
+
def isfile(self, path: str) -> bool:
|
|
124
|
+
self.check()
|
|
125
|
+
return self._directory.isfile(path)
|
|
126
|
+
|
|
127
|
+
def listdir(self, path: str) -> str:
|
|
128
|
+
self.check()
|
|
129
|
+
return self._directory.listdir(path)
|
|
130
|
+
|
|
131
|
+
def makedir(self, path: str, recreate: bool = False) -> SubFS:
|
|
132
|
+
self.check()
|
|
133
|
+
raise ResourceReadOnly(path)
|
|
134
|
+
|
|
135
|
+
def makedirs(self, path: str, recreate: bool = False) -> SubFS:
|
|
136
|
+
self.check()
|
|
137
|
+
raise ResourceReadOnly(path)
|
|
138
|
+
|
|
139
|
+
def remove(self, path: str):
|
|
140
|
+
self.check()
|
|
141
|
+
raise ResourceReadOnly(path)
|
|
142
|
+
|
|
143
|
+
def removedir(self, path: str):
|
|
144
|
+
self.check()
|
|
145
|
+
raise ResourceReadOnly(path)
|
|
146
|
+
|
|
147
|
+
def removetree(self, path: str):
|
|
148
|
+
self.check()
|
|
149
|
+
raise ResourceReadOnly(path)
|
|
150
|
+
|
|
151
|
+
def movedir(self, src: str, dst: str, create: bool = False):
|
|
152
|
+
self.check()
|
|
153
|
+
raise ResourceReadOnly(src)
|
|
154
|
+
|
|
155
|
+
def readbytes(self, path: str) -> bytes:
|
|
156
|
+
self.check()
|
|
157
|
+
if not self._directory.isfile(path):
|
|
158
|
+
raise ResourceNotFound(path)
|
|
159
|
+
zip_name = self._path_to_zip_name(path)
|
|
160
|
+
zip_bytes = self._zip.read(zip_name)
|
|
161
|
+
return zip_bytes
|
|
162
|
+
|
|
163
|
+
def open(self, path: str, mode: str = "rb", **kwargs) -> IO[Any]:
|
|
164
|
+
self.check()
|
|
165
|
+
if self._directory.isdir(path):
|
|
166
|
+
raise FileExpected(f"{path!r} is a directory")
|
|
167
|
+
|
|
168
|
+
zip_mode = mode[0]
|
|
169
|
+
if zip_mode == "r" and not self._directory.exists(path):
|
|
170
|
+
raise ResourceNotFound(f"No such file or directory: {path!r}")
|
|
171
|
+
|
|
172
|
+
if any(m in mode for m in "wax+"):
|
|
173
|
+
raise ResourceReadOnly(path)
|
|
174
|
+
|
|
175
|
+
zip_name = self._path_to_zip_name(path)
|
|
176
|
+
stream = self._zip.open(zip_name, zip_mode)
|
|
177
|
+
if "b" in mode:
|
|
178
|
+
if kwargs:
|
|
179
|
+
raise ValueError("encoding args invalid for binary operation")
|
|
180
|
+
return stream
|
|
181
|
+
# Text mode
|
|
182
|
+
return io.TextIOWrapper(stream, **kwargs)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class WriteZipFS(TempFS):
|
|
186
|
+
"""A writable zip file."""
|
|
187
|
+
|
|
188
|
+
def __init__(self, file: str | os.PathLike, encoding: str = "utf-8"):
|
|
189
|
+
super().__init__()
|
|
190
|
+
self._file = os.fspath(file)
|
|
191
|
+
self.encoding = encoding # unused
|
|
192
|
+
|
|
193
|
+
def __repr__(self) -> str:
|
|
194
|
+
return f"WriteZipFS({self._file!r})"
|
|
195
|
+
|
|
196
|
+
def __str__(self) -> str:
|
|
197
|
+
return f"<zipfs-write '{self._file}'>"
|
|
198
|
+
|
|
199
|
+
def close(self):
|
|
200
|
+
base_name = os.path.splitext(self._file)[0]
|
|
201
|
+
shutil.make_archive(base_name, format="zip", root_dir=self._temp_dir)
|
|
202
|
+
if self._file != base_name + ".zip":
|
|
203
|
+
shutil.move(base_name + ".zip", self._file)
|
|
204
|
+
super().close()
|
fontTools/misc/psCharStrings.py
CHANGED
|
@@ -338,7 +338,7 @@ class SimpleT2Decompiler(object):
|
|
|
338
338
|
self.numRegions = 0
|
|
339
339
|
self.vsIndex = 0
|
|
340
340
|
|
|
341
|
-
def execute(self, charString):
|
|
341
|
+
def execute(self, charString, *, pushToStack=None):
|
|
342
342
|
self.callingStack.append(charString)
|
|
343
343
|
needsDecompilation = charString.needsDecompilation()
|
|
344
344
|
if needsDecompilation:
|
|
@@ -346,7 +346,8 @@ class SimpleT2Decompiler(object):
|
|
|
346
346
|
pushToProgram = program.append
|
|
347
347
|
else:
|
|
348
348
|
pushToProgram = lambda x: None
|
|
349
|
-
pushToStack
|
|
349
|
+
if pushToStack is None:
|
|
350
|
+
pushToStack = self.operandStack.append
|
|
350
351
|
index = 0
|
|
351
352
|
while True:
|
|
352
353
|
token, isOperator, index = charString.getToken(index)
|
|
@@ -551,6 +552,20 @@ t1Operators = [
|
|
|
551
552
|
]
|
|
552
553
|
|
|
553
554
|
|
|
555
|
+
class T2StackUseExtractor(SimpleT2Decompiler):
|
|
556
|
+
|
|
557
|
+
def execute(self, charString):
|
|
558
|
+
maxStackUse = 0
|
|
559
|
+
|
|
560
|
+
def pushToStack(value):
|
|
561
|
+
nonlocal maxStackUse
|
|
562
|
+
self.operandStack.append(value)
|
|
563
|
+
maxStackUse = max(maxStackUse, len(self.operandStack))
|
|
564
|
+
|
|
565
|
+
super().execute(charString, pushToStack=pushToStack)
|
|
566
|
+
return maxStackUse
|
|
567
|
+
|
|
568
|
+
|
|
554
569
|
class T2WidthExtractor(SimpleT2Decompiler):
|
|
555
570
|
def __init__(
|
|
556
571
|
self,
|
fontTools/misc/sstruct.py
CHANGED
|
@@ -64,10 +64,7 @@ def pack(fmt, obj):
|
|
|
64
64
|
elements = []
|
|
65
65
|
if not isinstance(obj, dict):
|
|
66
66
|
obj = obj.__dict__
|
|
67
|
-
|
|
68
|
-
if formatstring.startswith(">"):
|
|
69
|
-
string_index = formatstring[1:]
|
|
70
|
-
for ix, name in enumerate(names.keys()):
|
|
67
|
+
for name in names.keys():
|
|
71
68
|
value = obj[name]
|
|
72
69
|
if name in fixes:
|
|
73
70
|
# fixed point conversion
|
|
@@ -96,8 +93,7 @@ def unpack(fmt, data, obj=None):
|
|
|
96
93
|
else:
|
|
97
94
|
d = obj.__dict__
|
|
98
95
|
elements = struct.unpack(formatstring, data)
|
|
99
|
-
for i in
|
|
100
|
-
name = list(names.keys())[i]
|
|
96
|
+
for i, name in enumerate(names.keys()):
|
|
101
97
|
value = elements[i]
|
|
102
98
|
if name in fixes:
|
|
103
99
|
# fixed point conversion
|
fontTools/misc/xmlWriter.py
CHANGED
|
@@ -4,8 +4,22 @@ from fontTools.misc.textTools import byteord, strjoin, tobytes, tostr
|
|
|
4
4
|
import sys
|
|
5
5
|
import os
|
|
6
6
|
import string
|
|
7
|
+
import logging
|
|
8
|
+
import itertools
|
|
7
9
|
|
|
8
10
|
INDENT = " "
|
|
11
|
+
TTX_LOG = logging.getLogger("fontTools.ttx")
|
|
12
|
+
REPLACEMENT = "?"
|
|
13
|
+
ILLEGAL_XML_CHARS = dict.fromkeys(
|
|
14
|
+
itertools.chain(
|
|
15
|
+
range(0x00, 0x09),
|
|
16
|
+
(0x0B, 0x0C),
|
|
17
|
+
range(0x0E, 0x20),
|
|
18
|
+
range(0xD800, 0xE000),
|
|
19
|
+
(0xFFFE, 0xFFFF),
|
|
20
|
+
),
|
|
21
|
+
REPLACEMENT,
|
|
22
|
+
)
|
|
9
23
|
|
|
10
24
|
|
|
11
25
|
class XMLWriter(object):
|
|
@@ -168,12 +182,25 @@ class XMLWriter(object):
|
|
|
168
182
|
|
|
169
183
|
|
|
170
184
|
def escape(data):
|
|
185
|
+
"""Escape characters not allowed in `XML 1.0 <https://www.w3.org/TR/xml/#NT-Char>`_."""
|
|
171
186
|
data = tostr(data, "utf_8")
|
|
172
187
|
data = data.replace("&", "&")
|
|
173
188
|
data = data.replace("<", "<")
|
|
174
189
|
data = data.replace(">", ">")
|
|
175
190
|
data = data.replace("\r", " ")
|
|
176
|
-
|
|
191
|
+
|
|
192
|
+
newData = data.translate(ILLEGAL_XML_CHARS)
|
|
193
|
+
if newData != data:
|
|
194
|
+
maxLen = 10
|
|
195
|
+
preview = repr(data)
|
|
196
|
+
if len(data) > maxLen:
|
|
197
|
+
preview = repr(data[:maxLen])[1:-1] + "..."
|
|
198
|
+
TTX_LOG.warning(
|
|
199
|
+
"Illegal XML character(s) found; replacing offending " "string %r with %r",
|
|
200
|
+
preview,
|
|
201
|
+
REPLACEMENT,
|
|
202
|
+
)
|
|
203
|
+
return newData
|
|
177
204
|
|
|
178
205
|
|
|
179
206
|
def escapeattr(data):
|
fontTools/pens/momentsPen.c
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Generated by Cython 3.1.
|
|
1
|
+
/* Generated by Cython 3.1.3 */
|
|
2
2
|
|
|
3
3
|
/* BEGIN: Cython Metadata
|
|
4
4
|
{
|
|
@@ -26,8 +26,8 @@ END: Cython Metadata */
|
|
|
26
26
|
#elif PY_VERSION_HEX < 0x03080000
|
|
27
27
|
#error Cython requires Python 3.8+.
|
|
28
28
|
#else
|
|
29
|
-
#define __PYX_ABI_VERSION "
|
|
30
|
-
#define CYTHON_HEX_VERSION
|
|
29
|
+
#define __PYX_ABI_VERSION "3_1_3"
|
|
30
|
+
#define CYTHON_HEX_VERSION 0x030103F0
|
|
31
31
|
#define CYTHON_FUTURE_DIVISION 1
|
|
32
32
|
/* CModulePreamble */
|
|
33
33
|
#include <stddef.h>
|
|
@@ -390,6 +390,9 @@ END: Cython Metadata */
|
|
|
390
390
|
enum { __pyx_check_sizeof_voidp = 1 / (int)(SIZEOF_VOID_P == sizeof(void*)) };
|
|
391
391
|
#endif
|
|
392
392
|
#endif
|
|
393
|
+
#ifndef CYTHON_LOCK_AND_GIL_DEADLOCK_AVOIDANCE_TIME
|
|
394
|
+
#define CYTHON_LOCK_AND_GIL_DEADLOCK_AVOIDANCE_TIME 100
|
|
395
|
+
#endif
|
|
393
396
|
#ifndef __has_attribute
|
|
394
397
|
#define __has_attribute(x) 0
|
|
395
398
|
#endif
|
|
@@ -8294,16 +8297,15 @@ static int __Pyx_InitConstants(__pyx_mstatetype *__pyx_mstate) {
|
|
|
8294
8297
|
return -1;
|
|
8295
8298
|
}
|
|
8296
8299
|
/* #### Code section: init_codeobjects ### */
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
} __Pyx_PyCode_New_function_description;
|
|
8300
|
+
typedef struct {
|
|
8301
|
+
unsigned int argcount : 3;
|
|
8302
|
+
unsigned int num_posonly_args : 1;
|
|
8303
|
+
unsigned int num_kwonly_args : 1;
|
|
8304
|
+
unsigned int nlocals : 8;
|
|
8305
|
+
unsigned int flags : 10;
|
|
8306
|
+
unsigned int first_line : 9;
|
|
8307
|
+
unsigned int line_table_length : 17;
|
|
8308
|
+
} __Pyx_PyCode_New_function_description;
|
|
8307
8309
|
/* NewCodeObj.proto */
|
|
8308
8310
|
static PyObject* __Pyx_PyCode_New(
|
|
8309
8311
|
const __Pyx_PyCode_New_function_description descr,
|
|
@@ -10172,6 +10174,7 @@ static int __Pyx_fix_up_extension_type_from_spec(PyType_Spec *spec, PyTypeObject
|
|
|
10172
10174
|
changed = 1;
|
|
10173
10175
|
}
|
|
10174
10176
|
#endif // CYTHON_METH_FASTCALL
|
|
10177
|
+
#if !CYTHON_COMPILING_IN_PYPY
|
|
10175
10178
|
else if (strcmp(memb->name, "__module__") == 0) {
|
|
10176
10179
|
PyObject *descr;
|
|
10177
10180
|
assert(memb->type == T_OBJECT);
|
|
@@ -10186,11 +10189,13 @@ static int __Pyx_fix_up_extension_type_from_spec(PyType_Spec *spec, PyTypeObject
|
|
|
10186
10189
|
}
|
|
10187
10190
|
changed = 1;
|
|
10188
10191
|
}
|
|
10192
|
+
#endif // !CYTHON_COMPILING_IN_PYPY
|
|
10189
10193
|
}
|
|
10190
10194
|
memb++;
|
|
10191
10195
|
}
|
|
10192
10196
|
}
|
|
10193
10197
|
#endif // !CYTHON_COMPILING_IN_LIMITED_API
|
|
10198
|
+
#if !CYTHON_COMPILING_IN_PYPY
|
|
10194
10199
|
slot = spec->slots;
|
|
10195
10200
|
while (slot && slot->slot && slot->slot != Py_tp_getset)
|
|
10196
10201
|
slot++;
|
|
@@ -10222,6 +10227,7 @@ static int __Pyx_fix_up_extension_type_from_spec(PyType_Spec *spec, PyTypeObject
|
|
|
10222
10227
|
++getset;
|
|
10223
10228
|
}
|
|
10224
10229
|
}
|
|
10230
|
+
#endif // !CYTHON_COMPILING_IN_PYPY
|
|
10225
10231
|
if (changed)
|
|
10226
10232
|
PyType_Modified(type);
|
|
10227
10233
|
#endif // PY_VERSION_HEX > 0x030900B1
|
|
@@ -10354,7 +10360,7 @@ bad:
|
|
|
10354
10360
|
}
|
|
10355
10361
|
|
|
10356
10362
|
/* CommonTypesMetaclass */
|
|
10357
|
-
PyObject* __pyx_CommonTypesMetaclass_get_module(CYTHON_UNUSED PyObject *self, CYTHON_UNUSED void* context) {
|
|
10363
|
+
static PyObject* __pyx_CommonTypesMetaclass_get_module(CYTHON_UNUSED PyObject *self, CYTHON_UNUSED void* context) {
|
|
10358
10364
|
return PyUnicode_FromString(__PYX_ABI_MODULE_NAME);
|
|
10359
10365
|
}
|
|
10360
10366
|
static PyGetSetDef __pyx_CommonTypesMetaclass_getset[] = {
|
|
Binary file
|
fontTools/pens/roundingPen.py
CHANGED
|
@@ -116,8 +116,8 @@ class RoundingPointPen(FilterPointPen):
|
|
|
116
116
|
def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs):
|
|
117
117
|
xx, xy, yx, yy, dx, dy = transformation
|
|
118
118
|
self._outPen.addComponent(
|
|
119
|
-
baseGlyphName
|
|
120
|
-
|
|
119
|
+
baseGlyphName,
|
|
120
|
+
Transform(
|
|
121
121
|
self.transformRoundFunc(xx),
|
|
122
122
|
self.transformRoundFunc(xy),
|
|
123
123
|
self.transformRoundFunc(yx),
|