fonttools 4.56.0__cp39-cp39-win_amd64.whl → 4.58.0__cp39-cp39-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/cffLib/__init__.py +61 -26
- fontTools/config/__init__.py +15 -0
- fontTools/cu2qu/cu2qu.cp39-win_amd64.pyd +0 -0
- fontTools/designspaceLib/statNames.py +14 -7
- fontTools/feaLib/ast.py +92 -13
- fontTools/feaLib/builder.py +52 -13
- fontTools/feaLib/lexer.cp39-win_amd64.pyd +0 -0
- fontTools/feaLib/parser.py +59 -39
- fontTools/fontBuilder.py +6 -0
- fontTools/misc/bezierTools.cp39-win_amd64.pyd +0 -0
- fontTools/misc/etree.py +4 -27
- fontTools/misc/testTools.py +2 -1
- fontTools/mtiLib/__init__.py +0 -2
- fontTools/otlLib/builder.py +195 -145
- fontTools/otlLib/optimize/gpos.py +49 -63
- fontTools/pens/momentsPen.cp39-win_amd64.pyd +0 -0
- fontTools/pens/pointPen.py +21 -12
- fontTools/qu2cu/qu2cu.cp39-win_amd64.pyd +0 -0
- fontTools/subset/__init__.py +11 -0
- fontTools/ttLib/__init__.py +4 -0
- fontTools/ttLib/__main__.py +47 -8
- fontTools/ttLib/tables/D__e_b_g.py +20 -2
- fontTools/ttLib/tables/G_V_A_R_.py +5 -0
- fontTools/ttLib/tables/T_S_I__0.py +14 -3
- fontTools/ttLib/tables/T_S_I__5.py +16 -5
- fontTools/ttLib/tables/__init__.py +1 -0
- fontTools/ttLib/tables/_c_m_a_p.py +19 -6
- fontTools/ttLib/tables/_c_v_t.py +2 -0
- fontTools/ttLib/tables/_f_p_g_m.py +3 -1
- fontTools/ttLib/tables/_g_l_y_f.py +11 -10
- fontTools/ttLib/tables/_g_v_a_r.py +62 -17
- fontTools/ttLib/tables/_p_o_s_t.py +5 -2
- fontTools/ttLib/tables/otBase.py +1 -0
- fontTools/ttLib/tables/otConverters.py +5 -2
- fontTools/ttLib/tables/otTables.py +5 -1
- fontTools/ttLib/ttFont.py +3 -5
- fontTools/ttLib/ttGlyphSet.py +0 -10
- fontTools/ttx.py +13 -1
- fontTools/ufoLib/__init__.py +2 -2
- fontTools/ufoLib/converters.py +89 -25
- fontTools/ufoLib/errors.py +8 -0
- fontTools/ufoLib/etree.py +1 -1
- fontTools/ufoLib/filenames.py +155 -100
- fontTools/ufoLib/glifLib.py +9 -2
- fontTools/ufoLib/kerning.py +66 -36
- fontTools/ufoLib/utils.py +5 -2
- fontTools/unicodedata/Mirrored.py +446 -0
- fontTools/unicodedata/__init__.py +6 -2
- fontTools/varLib/__init__.py +94 -89
- fontTools/varLib/hvar.py +113 -0
- fontTools/varLib/iup.cp39-win_amd64.pyd +0 -0
- fontTools/varLib/varStore.py +1 -1
- 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.56.0.dist-info → fonttools-4.58.0.dist-info}/METADATA +60 -12
- {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/RECORD +65 -66
- {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/WHEEL +1 -1
- fonttools-4.58.0.dist-info/licenses/LICENSE.external +359 -0
- fontTools/cu2qu/cu2qu.c +0 -14829
- fontTools/feaLib/lexer.c +0 -17986
- fontTools/misc/bezierTools.c +0 -41831
- fontTools/pens/momentsPen.c +0 -13448
- fontTools/qu2cu/qu2cu.c +0 -16269
- fontTools/varLib/iup.c +0 -19154
- {fonttools-4.56.0.data → fonttools-4.58.0.data}/data/share/man/man1/ttx.1 +0 -0
- {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/entry_points.txt +0 -0
- {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info/licenses}/LICENSE +0 -0
- {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
3
|
from collections import defaultdict, namedtuple
|
|
4
|
-
from
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from functools import cached_property, reduce
|
|
5
6
|
from itertools import chain
|
|
6
7
|
from math import log2
|
|
7
8
|
from typing import DefaultDict, Dict, Iterable, List, Sequence, Tuple
|
|
@@ -53,12 +54,18 @@ def compact(font: TTFont, level: int) -> TTFont:
|
|
|
53
54
|
# are not grouped together first; instead each subtable is treated
|
|
54
55
|
# independently, so currently this step is:
|
|
55
56
|
# Split existing subtables into more smaller subtables
|
|
56
|
-
gpos = font
|
|
57
|
+
gpos = font.get("GPOS")
|
|
58
|
+
|
|
59
|
+
# If the font does not contain a GPOS table, there is nothing to do.
|
|
60
|
+
if gpos is None:
|
|
61
|
+
return font
|
|
62
|
+
|
|
57
63
|
for lookup in gpos.table.LookupList.Lookup:
|
|
58
64
|
if lookup.LookupType == 2:
|
|
59
65
|
compact_lookup(font, level, lookup)
|
|
60
66
|
elif lookup.LookupType == 9 and lookup.SubTable[0].ExtensionLookupType == 2:
|
|
61
67
|
compact_ext_lookup(font, level, lookup)
|
|
68
|
+
|
|
62
69
|
return font
|
|
63
70
|
|
|
64
71
|
|
|
@@ -186,79 +193,58 @@ ClusteringContext = namedtuple(
|
|
|
186
193
|
)
|
|
187
194
|
|
|
188
195
|
|
|
196
|
+
@dataclass
|
|
189
197
|
class Cluster:
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
# indices: int
|
|
193
|
-
# Caches
|
|
194
|
-
# TODO(Python 3.8): use functools.cached_property instead of the
|
|
195
|
-
# manually cached properties, and remove the cache fields listed below.
|
|
196
|
-
# _indices: Optional[List[int]] = None
|
|
197
|
-
# _column_indices: Optional[List[int]] = None
|
|
198
|
-
# _cost: Optional[int] = None
|
|
199
|
-
|
|
200
|
-
__slots__ = "ctx", "indices_bitmask", "_indices", "_column_indices", "_cost"
|
|
201
|
-
|
|
202
|
-
def __init__(self, ctx: ClusteringContext, indices_bitmask: int):
|
|
203
|
-
self.ctx = ctx
|
|
204
|
-
self.indices_bitmask = indices_bitmask
|
|
205
|
-
self._indices = None
|
|
206
|
-
self._column_indices = None
|
|
207
|
-
self._cost = None
|
|
198
|
+
ctx: ClusteringContext
|
|
199
|
+
indices_bitmask: int
|
|
208
200
|
|
|
209
|
-
@
|
|
201
|
+
@cached_property
|
|
210
202
|
def indices(self):
|
|
211
|
-
|
|
212
|
-
self._indices = bit_indices(self.indices_bitmask)
|
|
213
|
-
return self._indices
|
|
203
|
+
return bit_indices(self.indices_bitmask)
|
|
214
204
|
|
|
215
|
-
@
|
|
205
|
+
@cached_property
|
|
216
206
|
def column_indices(self):
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
self._column_indices = bit_indices(bitmask)
|
|
222
|
-
return self._column_indices
|
|
207
|
+
# Indices of columns that have a 1 in at least 1 line
|
|
208
|
+
# => binary OR all the lines
|
|
209
|
+
bitmask = reduce(int.__or__, (self.ctx.lines[i] for i in self.indices))
|
|
210
|
+
return bit_indices(bitmask)
|
|
223
211
|
|
|
224
212
|
@property
|
|
225
213
|
def width(self):
|
|
226
214
|
# Add 1 because Class2=0 cannot be used but needs to be encoded.
|
|
227
215
|
return len(self.column_indices) + 1
|
|
228
216
|
|
|
229
|
-
@
|
|
217
|
+
@cached_property
|
|
230
218
|
def cost(self):
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
)
|
|
261
|
-
return self._cost
|
|
219
|
+
return (
|
|
220
|
+
# 2 bytes to store the offset to this subtable in the Lookup table above
|
|
221
|
+
2
|
|
222
|
+
# Contents of the subtable
|
|
223
|
+
# From: https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#pair-adjustment-positioning-format-2-class-pair-adjustment
|
|
224
|
+
# uint16 posFormat Format identifier: format = 2
|
|
225
|
+
+ 2
|
|
226
|
+
# Offset16 coverageOffset Offset to Coverage table, from beginning of PairPos subtable.
|
|
227
|
+
+ 2
|
|
228
|
+
+ self.coverage_bytes
|
|
229
|
+
# uint16 valueFormat1 ValueRecord definition — for the first glyph of the pair (may be zero).
|
|
230
|
+
+ 2
|
|
231
|
+
# uint16 valueFormat2 ValueRecord definition — for the second glyph of the pair (may be zero).
|
|
232
|
+
+ 2
|
|
233
|
+
# Offset16 classDef1Offset Offset to ClassDef table, from beginning of PairPos subtable — for the first glyph of the pair.
|
|
234
|
+
+ 2
|
|
235
|
+
+ self.classDef1_bytes
|
|
236
|
+
# Offset16 classDef2Offset Offset to ClassDef table, from beginning of PairPos subtable — for the second glyph of the pair.
|
|
237
|
+
+ 2
|
|
238
|
+
+ self.classDef2_bytes
|
|
239
|
+
# uint16 class1Count Number of classes in classDef1 table — includes Class 0.
|
|
240
|
+
+ 2
|
|
241
|
+
# uint16 class2Count Number of classes in classDef2 table — includes Class 0.
|
|
242
|
+
+ 2
|
|
243
|
+
# Class1Record class1Records[class1Count] Array of Class1 records, ordered by classes in classDef1.
|
|
244
|
+
+ (self.ctx.valueFormat1_bytes + self.ctx.valueFormat2_bytes)
|
|
245
|
+
* len(self.indices)
|
|
246
|
+
* self.width
|
|
247
|
+
)
|
|
262
248
|
|
|
263
249
|
@property
|
|
264
250
|
def coverage_bytes(self):
|
|
Binary file
|
fontTools/pens/pointPen.py
CHANGED
|
@@ -12,12 +12,14 @@ This allows the caller to provide more data for each point.
|
|
|
12
12
|
For instance, whether or not a point is smooth, and its name.
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
15
17
|
import math
|
|
16
|
-
from typing import Any, Optional, Tuple
|
|
18
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
17
19
|
|
|
18
20
|
from fontTools.misc.loggingTools import LogMixin
|
|
19
|
-
from fontTools.pens.basePen import AbstractPen, MissingComponentError, PenError
|
|
20
21
|
from fontTools.misc.transform import DecomposedTransform, Identity
|
|
22
|
+
from fontTools.pens.basePen import AbstractPen, MissingComponentError, PenError
|
|
21
23
|
|
|
22
24
|
__all__ = [
|
|
23
25
|
"AbstractPointPen",
|
|
@@ -28,6 +30,14 @@ __all__ = [
|
|
|
28
30
|
"ReverseContourPointPen",
|
|
29
31
|
]
|
|
30
32
|
|
|
33
|
+
# Some type aliases to make it easier below
|
|
34
|
+
Point = Tuple[float, float]
|
|
35
|
+
PointName = Optional[str]
|
|
36
|
+
# [(pt, smooth, name, kwargs)]
|
|
37
|
+
SegmentPointList = List[Tuple[Optional[Point], bool, PointName, Any]]
|
|
38
|
+
SegmentType = Optional[str]
|
|
39
|
+
SegmentList = List[Tuple[SegmentType, SegmentPointList]]
|
|
40
|
+
|
|
31
41
|
|
|
32
42
|
class AbstractPointPen:
|
|
33
43
|
"""Baseclass for all PointPens."""
|
|
@@ -88,7 +98,7 @@ class BasePointToSegmentPen(AbstractPointPen):
|
|
|
88
98
|
care of all the edge cases.
|
|
89
99
|
"""
|
|
90
100
|
|
|
91
|
-
def __init__(self):
|
|
101
|
+
def __init__(self) -> None:
|
|
92
102
|
self.currentPath = None
|
|
93
103
|
|
|
94
104
|
def beginPath(self, identifier=None, **kwargs):
|
|
@@ -96,7 +106,7 @@ class BasePointToSegmentPen(AbstractPointPen):
|
|
|
96
106
|
raise PenError("Path already begun.")
|
|
97
107
|
self.currentPath = []
|
|
98
108
|
|
|
99
|
-
def _flushContour(self, segments):
|
|
109
|
+
def _flushContour(self, segments: SegmentList) -> None:
|
|
100
110
|
"""Override this method.
|
|
101
111
|
|
|
102
112
|
It will be called for each non-empty sub path with a list
|
|
@@ -124,7 +134,7 @@ class BasePointToSegmentPen(AbstractPointPen):
|
|
|
124
134
|
"""
|
|
125
135
|
raise NotImplementedError
|
|
126
136
|
|
|
127
|
-
def endPath(self):
|
|
137
|
+
def endPath(self) -> None:
|
|
128
138
|
if self.currentPath is None:
|
|
129
139
|
raise PenError("Path not begun.")
|
|
130
140
|
points = self.currentPath
|
|
@@ -134,7 +144,7 @@ class BasePointToSegmentPen(AbstractPointPen):
|
|
|
134
144
|
if len(points) == 1:
|
|
135
145
|
# Not much more we can do than output a single move segment.
|
|
136
146
|
pt, segmentType, smooth, name, kwargs = points[0]
|
|
137
|
-
segments = [("move", [(pt, smooth, name, kwargs)])]
|
|
147
|
+
segments: SegmentList = [("move", [(pt, smooth, name, kwargs)])]
|
|
138
148
|
self._flushContour(segments)
|
|
139
149
|
return
|
|
140
150
|
segments = []
|
|
@@ -162,7 +172,7 @@ class BasePointToSegmentPen(AbstractPointPen):
|
|
|
162
172
|
else:
|
|
163
173
|
points = points[firstOnCurve + 1 :] + points[: firstOnCurve + 1]
|
|
164
174
|
|
|
165
|
-
currentSegment = []
|
|
175
|
+
currentSegment: SegmentPointList = []
|
|
166
176
|
for pt, segmentType, smooth, name, kwargs in points:
|
|
167
177
|
currentSegment.append((pt, smooth, name, kwargs))
|
|
168
178
|
if segmentType is None:
|
|
@@ -189,7 +199,7 @@ class PointToSegmentPen(BasePointToSegmentPen):
|
|
|
189
199
|
and kwargs.
|
|
190
200
|
"""
|
|
191
201
|
|
|
192
|
-
def __init__(self, segmentPen, outputImpliedClosingLine=False):
|
|
202
|
+
def __init__(self, segmentPen, outputImpliedClosingLine: bool = False) -> None:
|
|
193
203
|
BasePointToSegmentPen.__init__(self)
|
|
194
204
|
self.pen = segmentPen
|
|
195
205
|
self.outputImpliedClosingLine = outputImpliedClosingLine
|
|
@@ -271,14 +281,14 @@ class SegmentToPointPen(AbstractPen):
|
|
|
271
281
|
PointPen protocol.
|
|
272
282
|
"""
|
|
273
283
|
|
|
274
|
-
def __init__(self, pointPen, guessSmooth=True):
|
|
284
|
+
def __init__(self, pointPen, guessSmooth=True) -> None:
|
|
275
285
|
if guessSmooth:
|
|
276
286
|
self.pen = GuessSmoothPointPen(pointPen)
|
|
277
287
|
else:
|
|
278
288
|
self.pen = pointPen
|
|
279
|
-
self.contour = None
|
|
289
|
+
self.contour: Optional[List[Tuple[Point, SegmentType]]] = None
|
|
280
290
|
|
|
281
|
-
def _flushContour(self):
|
|
291
|
+
def _flushContour(self) -> None:
|
|
282
292
|
pen = self.pen
|
|
283
293
|
pen.beginPath()
|
|
284
294
|
for pt, segmentType in self.contour:
|
|
@@ -594,7 +604,6 @@ class DecomposingPointPen(LogMixin, AbstractPointPen):
|
|
|
594
604
|
# if the transformation has a negative determinant, it will
|
|
595
605
|
# reverse the contour direction of the component
|
|
596
606
|
a, b, c, d = transformation[:4]
|
|
597
|
-
det = a * d - b * c
|
|
598
607
|
if a * d - b * c < 0:
|
|
599
608
|
pen = ReverseContourPointPen(pen)
|
|
600
609
|
glyph.drawPoints(pen)
|
|
Binary file
|
fontTools/subset/__init__.py
CHANGED
|
@@ -16,6 +16,7 @@ from fontTools.subset.cff import *
|
|
|
16
16
|
from fontTools.subset.svg import *
|
|
17
17
|
from fontTools.varLib import varStore, multiVarStore # For monkey-patching
|
|
18
18
|
from fontTools.ttLib.tables._n_a_m_e import NameRecordVisitor
|
|
19
|
+
from fontTools.unicodedata import mirrored
|
|
19
20
|
import sys
|
|
20
21
|
import struct
|
|
21
22
|
import array
|
|
@@ -2870,6 +2871,15 @@ def prune_post_subset(self, font, options):
|
|
|
2870
2871
|
def closure_glyphs(self, s):
|
|
2871
2872
|
tables = [t for t in self.tables if t.isUnicode()]
|
|
2872
2873
|
|
|
2874
|
+
# Closure unicodes, which for now is pulling in bidi mirrored variants
|
|
2875
|
+
if s.options.bidi_closure:
|
|
2876
|
+
additional_unicodes = set()
|
|
2877
|
+
for u in s.unicodes_requested:
|
|
2878
|
+
mirror_u = mirrored(u)
|
|
2879
|
+
if mirror_u is not None:
|
|
2880
|
+
additional_unicodes.add(mirror_u)
|
|
2881
|
+
s.unicodes_requested.update(additional_unicodes)
|
|
2882
|
+
|
|
2873
2883
|
# Close glyphs
|
|
2874
2884
|
for table in tables:
|
|
2875
2885
|
if table.format == 14:
|
|
@@ -3191,6 +3201,7 @@ class Options(object):
|
|
|
3191
3201
|
self.font_number = -1
|
|
3192
3202
|
self.pretty_svg = False
|
|
3193
3203
|
self.lazy = True
|
|
3204
|
+
self.bidi_closure = True
|
|
3194
3205
|
|
|
3195
3206
|
self.set(**kwargs)
|
|
3196
3207
|
|
fontTools/ttLib/__init__.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""fontTools.ttLib -- a package for dealing with TrueType fonts."""
|
|
2
2
|
|
|
3
|
+
from fontTools.config import OPTIONS
|
|
3
4
|
from fontTools.misc.loggingTools import deprecateFunction
|
|
4
5
|
import logging
|
|
5
6
|
|
|
@@ -7,6 +8,9 @@ import logging
|
|
|
7
8
|
log = logging.getLogger(__name__)
|
|
8
9
|
|
|
9
10
|
|
|
11
|
+
OPTIMIZE_FONT_SPEED = OPTIONS["fontTools.ttLib:OPTIMIZE_FONT_SPEED"]
|
|
12
|
+
|
|
13
|
+
|
|
10
14
|
class TTLibError(Exception):
|
|
11
15
|
pass
|
|
12
16
|
|
fontTools/ttLib/__main__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import sys
|
|
2
|
-
from fontTools.ttLib import TTLibError, TTLibFileIsCollectionError
|
|
2
|
+
from fontTools.ttLib import OPTIMIZE_FONT_SPEED, TTLibError, TTLibFileIsCollectionError
|
|
3
3
|
from fontTools.ttLib.ttFont import *
|
|
4
4
|
from fontTools.ttLib.ttCollection import TTCollection
|
|
5
5
|
|
|
@@ -51,7 +51,7 @@ def main(args=None):
|
|
|
51
51
|
)
|
|
52
52
|
parser.add_argument("font", metavar="font", nargs="*", help="Font file.")
|
|
53
53
|
parser.add_argument(
|
|
54
|
-
"-t", "--table", metavar="table",
|
|
54
|
+
"-t", "--table", metavar="table", action="append", help="Tables to decompile."
|
|
55
55
|
)
|
|
56
56
|
parser.add_argument(
|
|
57
57
|
"-o", "--output", metavar="FILE", default=None, help="Output file."
|
|
@@ -71,27 +71,66 @@ def main(args=None):
|
|
|
71
71
|
default=None,
|
|
72
72
|
help="Flavor of output font. 'woff' or 'woff2'.",
|
|
73
73
|
)
|
|
74
|
+
parser.add_argument(
|
|
75
|
+
"--no-recalc-timestamp",
|
|
76
|
+
dest="recalcTimestamp",
|
|
77
|
+
action="store_false",
|
|
78
|
+
help="Keep the original font 'modified' timestamp.",
|
|
79
|
+
)
|
|
80
|
+
parser.add_argument(
|
|
81
|
+
"-b",
|
|
82
|
+
dest="recalcBBoxes",
|
|
83
|
+
action="store_false",
|
|
84
|
+
help="Don't recalc glyph bounding boxes: use the values in the original font.",
|
|
85
|
+
)
|
|
86
|
+
parser.add_argument(
|
|
87
|
+
"--optimize-font-speed",
|
|
88
|
+
action="store_true",
|
|
89
|
+
help=(
|
|
90
|
+
"Enable optimizations that prioritize speed over file size. This "
|
|
91
|
+
"mainly affects how glyf table and gvar / VARC tables are compiled."
|
|
92
|
+
),
|
|
93
|
+
)
|
|
74
94
|
options = parser.parse_args(args)
|
|
75
95
|
|
|
76
96
|
fontNumber = int(options.y) if options.y is not None else None
|
|
77
97
|
outFile = options.output
|
|
78
98
|
lazy = options.lazy
|
|
79
99
|
flavor = options.flavor
|
|
80
|
-
tables = options.table
|
|
100
|
+
tables = options.table
|
|
101
|
+
recalcBBoxes = options.recalcBBoxes
|
|
102
|
+
recalcTimestamp = options.recalcTimestamp
|
|
103
|
+
optimizeFontSpeed = options.optimize_font_speed
|
|
81
104
|
|
|
82
105
|
fonts = []
|
|
83
106
|
for f in options.font:
|
|
84
107
|
try:
|
|
85
|
-
font = TTFont(
|
|
108
|
+
font = TTFont(
|
|
109
|
+
f,
|
|
110
|
+
recalcBBoxes=recalcBBoxes,
|
|
111
|
+
recalcTimestamp=recalcTimestamp,
|
|
112
|
+
fontNumber=fontNumber,
|
|
113
|
+
lazy=lazy,
|
|
114
|
+
)
|
|
115
|
+
if optimizeFontSpeed:
|
|
116
|
+
font.cfg[OPTIMIZE_FONT_SPEED] = optimizeFontSpeed
|
|
86
117
|
fonts.append(font)
|
|
87
118
|
except TTLibFileIsCollectionError:
|
|
88
119
|
collection = TTCollection(f, lazy=lazy)
|
|
89
120
|
fonts.extend(collection.fonts)
|
|
90
121
|
|
|
91
|
-
if
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
122
|
+
if tables is None:
|
|
123
|
+
if lazy is False:
|
|
124
|
+
tables = ["*"]
|
|
125
|
+
elif optimizeFontSpeed:
|
|
126
|
+
tables = {"glyf", "gvar", "VARC"}.intersection(font.keys())
|
|
127
|
+
else:
|
|
128
|
+
tables = []
|
|
129
|
+
for font in fonts:
|
|
130
|
+
if "GlyphOrder" in tables:
|
|
131
|
+
font.getGlyphOrder()
|
|
132
|
+
for table in tables if "*" not in tables else font.keys():
|
|
133
|
+
font[table] # Decompiles
|
|
95
134
|
|
|
96
135
|
if outFile is not None:
|
|
97
136
|
if len(fonts) == 1:
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from textwrap import indent
|
|
2
3
|
|
|
3
4
|
from . import DefaultTable
|
|
5
|
+
from fontTools.misc.textTools import tostr
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
class table_D__e_b_g(DefaultTable.DefaultTable):
|
|
9
|
+
def __init__(self, tag=None):
|
|
10
|
+
DefaultTable.DefaultTable.__init__(self, tag)
|
|
11
|
+
self.data = {}
|
|
12
|
+
|
|
7
13
|
def decompile(self, data, ttFont):
|
|
8
14
|
self.data = json.loads(data)
|
|
9
15
|
|
|
@@ -11,7 +17,19 @@ class table_D__e_b_g(DefaultTable.DefaultTable):
|
|
|
11
17
|
return json.dumps(self.data).encode("utf-8")
|
|
12
18
|
|
|
13
19
|
def toXML(self, writer, ttFont):
|
|
14
|
-
|
|
20
|
+
# make sure json indentation inside CDATA block matches XMLWriter's
|
|
21
|
+
data = json.dumps(self.data, indent=len(writer.indentwhite))
|
|
22
|
+
prefix = tostr(writer.indentwhite) * (writer.indentlevel + 1)
|
|
23
|
+
# but don't indent the first json line so it's adjacent to `<![CDATA[{`
|
|
24
|
+
cdata = indent(data, prefix, lambda ln: ln != "{\n")
|
|
25
|
+
|
|
26
|
+
writer.begintag("json")
|
|
27
|
+
writer.newline()
|
|
28
|
+
writer.writecdata(cdata)
|
|
29
|
+
writer.newline()
|
|
30
|
+
writer.endtag("json")
|
|
31
|
+
writer.newline()
|
|
15
32
|
|
|
16
33
|
def fromXML(self, name, attrs, content, ttFont):
|
|
17
|
-
|
|
34
|
+
if name == "json":
|
|
35
|
+
self.data = json.loads("".join(content))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
|
|
2
2
|
tool to store its hinting source data.
|
|
3
3
|
|
|
4
4
|
TSI0 is the index table containing the lengths and offsets for the glyph
|
|
@@ -8,9 +8,13 @@ in the TSI1 table.
|
|
|
8
8
|
See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
import logging
|
|
12
12
|
import struct
|
|
13
13
|
|
|
14
|
+
from . import DefaultTable
|
|
15
|
+
|
|
16
|
+
log = logging.getLogger(__name__)
|
|
17
|
+
|
|
14
18
|
tsi0Format = ">HHL"
|
|
15
19
|
|
|
16
20
|
|
|
@@ -25,7 +29,14 @@ class table_T_S_I__0(DefaultTable.DefaultTable):
|
|
|
25
29
|
numGlyphs = ttFont["maxp"].numGlyphs
|
|
26
30
|
indices = []
|
|
27
31
|
size = struct.calcsize(tsi0Format)
|
|
28
|
-
|
|
32
|
+
numEntries = len(data) // size
|
|
33
|
+
if numEntries != numGlyphs + 5:
|
|
34
|
+
diff = numEntries - numGlyphs - 5
|
|
35
|
+
log.warning(
|
|
36
|
+
"Number of glyphPrograms differs from the number of glyphs in the font "
|
|
37
|
+
f"by {abs(diff)} ({numEntries - 5} programs vs. {numGlyphs} glyphs)."
|
|
38
|
+
)
|
|
39
|
+
for _ in range(numEntries):
|
|
29
40
|
glyphID, textLength, textOffset = fixlongs(
|
|
30
41
|
*struct.unpack(tsi0Format, data[:size])
|
|
31
42
|
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
|
|
2
2
|
tool to store its hinting source data.
|
|
3
3
|
|
|
4
4
|
TSI5 contains the VTT character groups.
|
|
@@ -6,22 +6,33 @@ TSI5 contains the VTT character groups.
|
|
|
6
6
|
See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
import array
|
|
10
|
+
import logging
|
|
11
|
+
import sys
|
|
12
|
+
|
|
9
13
|
from fontTools.misc.textTools import safeEval
|
|
14
|
+
|
|
10
15
|
from . import DefaultTable
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
|
|
17
|
+
log = logging.getLogger(__name__)
|
|
13
18
|
|
|
14
19
|
|
|
15
20
|
class table_T_S_I__5(DefaultTable.DefaultTable):
|
|
16
21
|
def decompile(self, data, ttFont):
|
|
17
22
|
numGlyphs = ttFont["maxp"].numGlyphs
|
|
18
|
-
assert len(data) == 2 * numGlyphs
|
|
19
23
|
a = array.array("H")
|
|
20
24
|
a.frombytes(data)
|
|
21
25
|
if sys.byteorder != "big":
|
|
22
26
|
a.byteswap()
|
|
23
27
|
self.glyphGrouping = {}
|
|
24
|
-
|
|
28
|
+
numEntries = len(data) // 2
|
|
29
|
+
if numEntries != numGlyphs:
|
|
30
|
+
diff = numEntries - numGlyphs
|
|
31
|
+
log.warning(
|
|
32
|
+
"Number of entries differs from the number of glyphs in the font "
|
|
33
|
+
f"by {abs(diff)} ({numEntries} entries vs. {numGlyphs} glyphs)."
|
|
34
|
+
)
|
|
35
|
+
for i in range(numEntries):
|
|
25
36
|
self.glyphGrouping[ttFont.getGlyphName(i)] = a[i]
|
|
26
37
|
|
|
27
38
|
def compile(self, ttFont):
|
|
@@ -141,6 +141,17 @@ class table__c_m_a_p(DefaultTable.DefaultTable):
|
|
|
141
141
|
result.setdefault(name, set()).add(codepoint)
|
|
142
142
|
return result
|
|
143
143
|
|
|
144
|
+
def buildReversedMin(self):
|
|
145
|
+
result = {}
|
|
146
|
+
for subtable in self.tables:
|
|
147
|
+
if subtable.isUnicode():
|
|
148
|
+
for codepoint, name in subtable.cmap.items():
|
|
149
|
+
if name in result:
|
|
150
|
+
result[name] = min(result[name], codepoint)
|
|
151
|
+
else:
|
|
152
|
+
result[name] = codepoint
|
|
153
|
+
return result
|
|
154
|
+
|
|
144
155
|
def decompile(self, data, ttFont):
|
|
145
156
|
tableVersion, numSubTables = struct.unpack(">HH", data[:4])
|
|
146
157
|
self.tableVersion = int(tableVersion)
|
|
@@ -1162,13 +1173,15 @@ class cmap_format_12_or_13(CmapSubtable):
|
|
|
1162
1173
|
charCodes = []
|
|
1163
1174
|
gids = []
|
|
1164
1175
|
pos = 0
|
|
1176
|
+
groups = array.array("I", data[: self.nGroups * 12])
|
|
1177
|
+
if sys.byteorder != "big":
|
|
1178
|
+
groups.byteswap()
|
|
1165
1179
|
for i in range(self.nGroups):
|
|
1166
|
-
startCharCode
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
pos += 12
|
|
1180
|
+
startCharCode = groups[i * 3]
|
|
1181
|
+
endCharCode = groups[i * 3 + 1]
|
|
1182
|
+
glyphID = groups[i * 3 + 2]
|
|
1170
1183
|
lenGroup = 1 + endCharCode - startCharCode
|
|
1171
|
-
charCodes.extend(
|
|
1184
|
+
charCodes.extend(range(startCharCode, endCharCode + 1))
|
|
1172
1185
|
gids.extend(self._computeGIDs(glyphID, lenGroup))
|
|
1173
1186
|
self.data = data = None
|
|
1174
1187
|
self.cmap = _make_map(self.ttFont, charCodes, gids)
|
|
@@ -1299,7 +1312,7 @@ class cmap_format_12(cmap_format_12_or_13):
|
|
|
1299
1312
|
cmap_format_12_or_13.__init__(self, format)
|
|
1300
1313
|
|
|
1301
1314
|
def _computeGIDs(self, startingGlyph, numberOfGlyphs):
|
|
1302
|
-
return
|
|
1315
|
+
return range(startingGlyph, startingGlyph + numberOfGlyphs)
|
|
1303
1316
|
|
|
1304
1317
|
def _IsInSameRun(self, glyphID, lastGlyphID, charCode, lastCharCode):
|
|
1305
1318
|
return (glyphID == 1 + lastGlyphID) and (charCode == 1 + lastCharCode)
|
fontTools/ttLib/tables/_c_v_t.py
CHANGED
|
@@ -20,7 +20,9 @@ class table__f_p_g_m(DefaultTable.DefaultTable):
|
|
|
20
20
|
self.program = program
|
|
21
21
|
|
|
22
22
|
def compile(self, ttFont):
|
|
23
|
-
|
|
23
|
+
if hasattr(self, "program"):
|
|
24
|
+
return self.program.getBytecode()
|
|
25
|
+
return b""
|
|
24
26
|
|
|
25
27
|
def toXML(self, writer, ttFont):
|
|
26
28
|
self.program.toXML(writer, ttFont)
|
|
@@ -134,6 +134,8 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
|
|
|
134
134
|
glyph.expand(self)
|
|
135
135
|
|
|
136
136
|
def compile(self, ttFont):
|
|
137
|
+
optimizeSpeed = ttFont.cfg[ttLib.OPTIMIZE_FONT_SPEED]
|
|
138
|
+
|
|
137
139
|
self.axisTags = (
|
|
138
140
|
[axis.axisTag for axis in ttFont["fvar"].axes] if "fvar" in ttFont else []
|
|
139
141
|
)
|
|
@@ -148,7 +150,12 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
|
|
|
148
150
|
boundsDone = set()
|
|
149
151
|
for glyphName in self.glyphOrder:
|
|
150
152
|
glyph = self.glyphs[glyphName]
|
|
151
|
-
glyphData = glyph.compile(
|
|
153
|
+
glyphData = glyph.compile(
|
|
154
|
+
self,
|
|
155
|
+
recalcBBoxes,
|
|
156
|
+
boundsDone=boundsDone,
|
|
157
|
+
optimizeSize=not optimizeSpeed,
|
|
158
|
+
)
|
|
152
159
|
if padding > 1:
|
|
153
160
|
glyphData = pad(glyphData, size=padding)
|
|
154
161
|
locations.append(currentLocation)
|
|
@@ -714,7 +721,7 @@ class Glyph(object):
|
|
|
714
721
|
self.decompileCoordinates(data)
|
|
715
722
|
|
|
716
723
|
def compile(
|
|
717
|
-
self, glyfTable, recalcBBoxes=True, *, boundsDone=None, optimizeSize=
|
|
724
|
+
self, glyfTable, recalcBBoxes=True, *, boundsDone=None, optimizeSize=True
|
|
718
725
|
):
|
|
719
726
|
if hasattr(self, "data"):
|
|
720
727
|
if recalcBBoxes:
|
|
@@ -732,8 +739,6 @@ class Glyph(object):
|
|
|
732
739
|
if self.isComposite():
|
|
733
740
|
data = data + self.compileComponents(glyfTable)
|
|
734
741
|
else:
|
|
735
|
-
if optimizeSize is None:
|
|
736
|
-
optimizeSize = getattr(glyfTable, "optimizeSize", True)
|
|
737
742
|
data = data + self.compileCoordinates(optimizeSize=optimizeSize)
|
|
738
743
|
return data
|
|
739
744
|
|
|
@@ -1220,7 +1225,7 @@ class Glyph(object):
|
|
|
1220
1225
|
if boundsDone is not None:
|
|
1221
1226
|
boundsDone.add(glyphName)
|
|
1222
1227
|
# empty components shouldn't update the bounds of the parent glyph
|
|
1223
|
-
if g.
|
|
1228
|
+
if g.yMin == g.yMax and g.xMin == g.xMax:
|
|
1224
1229
|
continue
|
|
1225
1230
|
|
|
1226
1231
|
x, y = compo.x, compo.y
|
|
@@ -1280,11 +1285,7 @@ class Glyph(object):
|
|
|
1280
1285
|
# however, if the referenced component glyph is another composite, we
|
|
1281
1286
|
# must not round here but only at the end, after all the nested
|
|
1282
1287
|
# transforms have been applied, or else rounding errors will compound.
|
|
1283
|
-
if
|
|
1284
|
-
round is not noRound
|
|
1285
|
-
and g.numberOfContours > 0
|
|
1286
|
-
and not compo._hasOnlyIntegerTranslate()
|
|
1287
|
-
):
|
|
1288
|
+
if round is not noRound and g.numberOfContours > 0:
|
|
1288
1289
|
coordinates.toInt(round=round)
|
|
1289
1290
|
if hasattr(compo, "firstPt"):
|
|
1290
1291
|
# component uses two reference points: we apply the transform _before_
|