fonttools 4.57.0__py3-none-any.whl → 4.58.1__py3-none-any.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/cffLib/specializer.py +4 -1
- fontTools/designspaceLib/statNames.py +14 -7
- fontTools/feaLib/ast.py +12 -9
- fontTools/feaLib/builder.py +75 -49
- fontTools/feaLib/parser.py +1 -39
- fontTools/fontBuilder.py +6 -0
- fontTools/merge/cmap.py +33 -1
- fontTools/merge/tables.py +12 -1
- fontTools/misc/etree.py +4 -27
- fontTools/misc/loggingTools.py +1 -1
- fontTools/misc/symfont.py +6 -8
- fontTools/mtiLib/__init__.py +1 -3
- fontTools/otlLib/builder.py +359 -145
- fontTools/otlLib/optimize/gpos.py +42 -62
- fontTools/pens/pointPen.py +21 -12
- fontTools/pens/t2CharStringPen.py +31 -11
- fontTools/subset/__init__.py +12 -1
- 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_v_t.py +2 -0
- fontTools/ttLib/tables/_f_p_g_m.py +3 -1
- fontTools/ttLib/tables/_g_l_y_f.py +2 -6
- fontTools/ttLib/tables/_g_v_a_r.py +58 -15
- fontTools/ttLib/tables/_p_o_s_t.py +5 -2
- fontTools/ttLib/tables/otBase.py +1 -0
- fontTools/ufoLib/__init__.py +3 -3
- 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 +20 -6
- fontTools/varLib/featureVars.py +13 -7
- fontTools/varLib/hvar.py +1 -1
- fontTools/varLib/instancer/__init__.py +14 -5
- 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.57.0.dist-info → fonttools-4.58.1.dist-info}/METADATA +64 -11
- {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/RECORD +55 -51
- {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/WHEEL +1 -1
- fonttools-4.58.1.dist-info/licenses/LICENSE.external +359 -0
- {fonttools-4.57.0.data → fonttools-4.58.1.data}/data/share/man/man1/ttx.1 +0 -0
- {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/entry_points.txt +0 -0
- {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/licenses/LICENSE +0 -0
- {fonttools-4.57.0.dist-info → fonttools-4.58.1.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
|
|
@@ -192,79 +193,58 @@ ClusteringContext = namedtuple(
|
|
|
192
193
|
)
|
|
193
194
|
|
|
194
195
|
|
|
196
|
+
@dataclass
|
|
195
197
|
class Cluster:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
# indices: int
|
|
199
|
-
# Caches
|
|
200
|
-
# TODO(Python 3.8): use functools.cached_property instead of the
|
|
201
|
-
# manually cached properties, and remove the cache fields listed below.
|
|
202
|
-
# _indices: Optional[List[int]] = None
|
|
203
|
-
# _column_indices: Optional[List[int]] = None
|
|
204
|
-
# _cost: Optional[int] = None
|
|
205
|
-
|
|
206
|
-
__slots__ = "ctx", "indices_bitmask", "_indices", "_column_indices", "_cost"
|
|
207
|
-
|
|
208
|
-
def __init__(self, ctx: ClusteringContext, indices_bitmask: int):
|
|
209
|
-
self.ctx = ctx
|
|
210
|
-
self.indices_bitmask = indices_bitmask
|
|
211
|
-
self._indices = None
|
|
212
|
-
self._column_indices = None
|
|
213
|
-
self._cost = None
|
|
198
|
+
ctx: ClusteringContext
|
|
199
|
+
indices_bitmask: int
|
|
214
200
|
|
|
215
|
-
@
|
|
201
|
+
@cached_property
|
|
216
202
|
def indices(self):
|
|
217
|
-
|
|
218
|
-
self._indices = bit_indices(self.indices_bitmask)
|
|
219
|
-
return self._indices
|
|
203
|
+
return bit_indices(self.indices_bitmask)
|
|
220
204
|
|
|
221
|
-
@
|
|
205
|
+
@cached_property
|
|
222
206
|
def column_indices(self):
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
self._column_indices = bit_indices(bitmask)
|
|
228
|
-
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)
|
|
229
211
|
|
|
230
212
|
@property
|
|
231
213
|
def width(self):
|
|
232
214
|
# Add 1 because Class2=0 cannot be used but needs to be encoded.
|
|
233
215
|
return len(self.column_indices) + 1
|
|
234
216
|
|
|
235
|
-
@
|
|
217
|
+
@cached_property
|
|
236
218
|
def cost(self):
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
)
|
|
267
|
-
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
|
+
)
|
|
268
248
|
|
|
269
249
|
@property
|
|
270
250
|
def coverage_bytes(self):
|
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)
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# Copyright (c) 2009 Type Supply LLC
|
|
2
2
|
# Author: Tal Leming
|
|
3
3
|
|
|
4
|
-
from
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any, Dict, List, Tuple
|
|
7
|
+
|
|
8
|
+
from fontTools.cffLib.specializer import commandsToProgram, specializeCommands
|
|
5
9
|
from fontTools.misc.psCharStrings import T2CharString
|
|
10
|
+
from fontTools.misc.roundTools import otRound, roundFunc
|
|
6
11
|
from fontTools.pens.basePen import BasePen
|
|
7
|
-
from fontTools.cffLib.specializer import specializeCommands, commandsToProgram
|
|
8
12
|
|
|
9
13
|
|
|
10
14
|
class T2CharStringPen(BasePen):
|
|
@@ -18,36 +22,52 @@ class T2CharStringPen(BasePen):
|
|
|
18
22
|
which are close to their integral part within the tolerated range.
|
|
19
23
|
"""
|
|
20
24
|
|
|
21
|
-
def __init__(
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
width: float | None,
|
|
28
|
+
glyphSet: Dict[str, Any] | None,
|
|
29
|
+
roundTolerance: float = 0.5,
|
|
30
|
+
CFF2: bool = False,
|
|
31
|
+
) -> None:
|
|
22
32
|
super(T2CharStringPen, self).__init__(glyphSet)
|
|
23
33
|
self.round = roundFunc(roundTolerance)
|
|
24
34
|
self._CFF2 = CFF2
|
|
25
35
|
self._width = width
|
|
26
|
-
self._commands = []
|
|
36
|
+
self._commands: List[Tuple[str | bytes, List[float]]] = []
|
|
27
37
|
self._p0 = (0, 0)
|
|
28
38
|
|
|
29
|
-
def _p(self, pt):
|
|
39
|
+
def _p(self, pt: Tuple[float, float]) -> List[float]:
|
|
30
40
|
p0 = self._p0
|
|
31
41
|
pt = self._p0 = (self.round(pt[0]), self.round(pt[1]))
|
|
32
42
|
return [pt[0] - p0[0], pt[1] - p0[1]]
|
|
33
43
|
|
|
34
|
-
def _moveTo(self, pt):
|
|
44
|
+
def _moveTo(self, pt: Tuple[float, float]) -> None:
|
|
35
45
|
self._commands.append(("rmoveto", self._p(pt)))
|
|
36
46
|
|
|
37
|
-
def _lineTo(self, pt):
|
|
47
|
+
def _lineTo(self, pt: Tuple[float, float]) -> None:
|
|
38
48
|
self._commands.append(("rlineto", self._p(pt)))
|
|
39
49
|
|
|
40
|
-
def _curveToOne(
|
|
50
|
+
def _curveToOne(
|
|
51
|
+
self,
|
|
52
|
+
pt1: Tuple[float, float],
|
|
53
|
+
pt2: Tuple[float, float],
|
|
54
|
+
pt3: Tuple[float, float],
|
|
55
|
+
) -> None:
|
|
41
56
|
_p = self._p
|
|
42
57
|
self._commands.append(("rrcurveto", _p(pt1) + _p(pt2) + _p(pt3)))
|
|
43
58
|
|
|
44
|
-
def _closePath(self):
|
|
59
|
+
def _closePath(self) -> None:
|
|
45
60
|
pass
|
|
46
61
|
|
|
47
|
-
def _endPath(self):
|
|
62
|
+
def _endPath(self) -> None:
|
|
48
63
|
pass
|
|
49
64
|
|
|
50
|
-
def getCharString(
|
|
65
|
+
def getCharString(
|
|
66
|
+
self,
|
|
67
|
+
private: Dict | None = None,
|
|
68
|
+
globalSubrs: List | None = None,
|
|
69
|
+
optimize: bool = True,
|
|
70
|
+
) -> T2CharString:
|
|
51
71
|
commands = self._commands
|
|
52
72
|
if optimize:
|
|
53
73
|
maxstack = 48 if not self._CFF2 else 513
|
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
|
|
|
@@ -3746,7 +3757,7 @@ def main(args=None):
|
|
|
3746
3757
|
text += g[7:]
|
|
3747
3758
|
continue
|
|
3748
3759
|
if g.startswith("--text-file="):
|
|
3749
|
-
with open(g[12:], encoding="utf-8") as f:
|
|
3760
|
+
with open(g[12:], encoding="utf-8-sig") as f:
|
|
3750
3761
|
text += f.read().replace("\n", "")
|
|
3751
3762
|
continue
|
|
3752
3763
|
if g.startswith("--unicodes="):
|
|
@@ -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):
|
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)
|
|
@@ -1225,7 +1225,7 @@ class Glyph(object):
|
|
|
1225
1225
|
if boundsDone is not None:
|
|
1226
1226
|
boundsDone.add(glyphName)
|
|
1227
1227
|
# empty components shouldn't update the bounds of the parent glyph
|
|
1228
|
-
if g.
|
|
1228
|
+
if g.yMin == g.yMax and g.xMin == g.xMax:
|
|
1229
1229
|
continue
|
|
1230
1230
|
|
|
1231
1231
|
x, y = compo.x, compo.y
|
|
@@ -1285,11 +1285,7 @@ class Glyph(object):
|
|
|
1285
1285
|
# however, if the referenced component glyph is another composite, we
|
|
1286
1286
|
# must not round here but only at the end, after all the nested
|
|
1287
1287
|
# transforms have been applied, or else rounding errors will compound.
|
|
1288
|
-
if
|
|
1289
|
-
round is not noRound
|
|
1290
|
-
and g.numberOfContours > 0
|
|
1291
|
-
and not compo._hasOnlyIntegerTranslate()
|
|
1292
|
-
):
|
|
1288
|
+
if round is not noRound and g.numberOfContours > 0:
|
|
1293
1289
|
coordinates.toInt(round=round)
|
|
1294
1290
|
if hasattr(compo, "firstPt"):
|
|
1295
1291
|
# component uses two reference points: we apply the transform _before_
|
|
@@ -24,19 +24,24 @@ log = logging.getLogger(__name__)
|
|
|
24
24
|
# FreeType2 source code for parsing 'gvar':
|
|
25
25
|
# http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/src/truetype/ttgxvar.c
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
GVAR_HEADER_FORMAT_HEAD = """
|
|
28
28
|
> # big endian
|
|
29
29
|
version: H
|
|
30
30
|
reserved: H
|
|
31
31
|
axisCount: H
|
|
32
32
|
sharedTupleCount: H
|
|
33
33
|
offsetToSharedTuples: I
|
|
34
|
-
|
|
34
|
+
"""
|
|
35
|
+
# In between the HEAD and TAIL lies the glyphCount, which is
|
|
36
|
+
# of different size: 2 bytes for gvar, and 3 bytes for GVAR.
|
|
37
|
+
GVAR_HEADER_FORMAT_TAIL = """
|
|
38
|
+
> # big endian
|
|
35
39
|
flags: H
|
|
36
40
|
offsetToGlyphVariationData: I
|
|
37
41
|
"""
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
GVAR_HEADER_SIZE_HEAD = sstruct.calcsize(GVAR_HEADER_FORMAT_HEAD)
|
|
44
|
+
GVAR_HEADER_SIZE_TAIL = sstruct.calcsize(GVAR_HEADER_FORMAT_TAIL)
|
|
40
45
|
|
|
41
46
|
|
|
42
47
|
class table__g_v_a_r(DefaultTable.DefaultTable):
|
|
@@ -51,6 +56,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
|
|
|
51
56
|
"""
|
|
52
57
|
|
|
53
58
|
dependencies = ["fvar", "glyf"]
|
|
59
|
+
gid_size = 2
|
|
54
60
|
|
|
55
61
|
def __init__(self, tag=None):
|
|
56
62
|
DefaultTable.DefaultTable.__init__(self, tag)
|
|
@@ -74,20 +80,25 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
|
|
|
74
80
|
offsets.append(offset)
|
|
75
81
|
compiledOffsets, tableFormat = self.compileOffsets_(offsets)
|
|
76
82
|
|
|
83
|
+
GVAR_HEADER_SIZE = GVAR_HEADER_SIZE_HEAD + self.gid_size + GVAR_HEADER_SIZE_TAIL
|
|
77
84
|
header = {}
|
|
78
85
|
header["version"] = self.version
|
|
79
86
|
header["reserved"] = self.reserved
|
|
80
87
|
header["axisCount"] = len(axisTags)
|
|
81
88
|
header["sharedTupleCount"] = len(sharedTuples)
|
|
82
89
|
header["offsetToSharedTuples"] = GVAR_HEADER_SIZE + len(compiledOffsets)
|
|
83
|
-
header["glyphCount"] = len(compiledGlyphs)
|
|
84
90
|
header["flags"] = tableFormat
|
|
85
91
|
header["offsetToGlyphVariationData"] = (
|
|
86
92
|
header["offsetToSharedTuples"] + sharedTupleSize
|
|
87
93
|
)
|
|
88
|
-
compiledHeader = sstruct.pack(GVAR_HEADER_FORMAT, header)
|
|
89
94
|
|
|
90
|
-
result = [
|
|
95
|
+
result = [
|
|
96
|
+
sstruct.pack(GVAR_HEADER_FORMAT_HEAD, header),
|
|
97
|
+
len(compiledGlyphs).to_bytes(self.gid_size, "big"),
|
|
98
|
+
sstruct.pack(GVAR_HEADER_FORMAT_TAIL, header),
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
result.append(compiledOffsets)
|
|
91
102
|
result.extend(sharedTuples)
|
|
92
103
|
result.extend(compiledGlyphs)
|
|
93
104
|
return b"".join(result)
|
|
@@ -104,6 +115,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
|
|
|
104
115
|
pointCountUnused = 0 # pointCount is actually unused by compileGlyph
|
|
105
116
|
result.append(
|
|
106
117
|
compileGlyph_(
|
|
118
|
+
self.gid_size,
|
|
107
119
|
variations,
|
|
108
120
|
pointCountUnused,
|
|
109
121
|
axisTags,
|
|
@@ -116,7 +128,19 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
|
|
|
116
128
|
def decompile(self, data, ttFont):
|
|
117
129
|
axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
|
|
118
130
|
glyphs = ttFont.getGlyphOrder()
|
|
119
|
-
|
|
131
|
+
|
|
132
|
+
# Parse the header
|
|
133
|
+
GVAR_HEADER_SIZE = GVAR_HEADER_SIZE_HEAD + self.gid_size + GVAR_HEADER_SIZE_TAIL
|
|
134
|
+
sstruct.unpack(GVAR_HEADER_FORMAT_HEAD, data[:GVAR_HEADER_SIZE_HEAD], self)
|
|
135
|
+
self.glyphCount = int.from_bytes(
|
|
136
|
+
data[GVAR_HEADER_SIZE_HEAD : GVAR_HEADER_SIZE_HEAD + self.gid_size], "big"
|
|
137
|
+
)
|
|
138
|
+
sstruct.unpack(
|
|
139
|
+
GVAR_HEADER_FORMAT_TAIL,
|
|
140
|
+
data[GVAR_HEADER_SIZE_HEAD + self.gid_size : GVAR_HEADER_SIZE],
|
|
141
|
+
self,
|
|
142
|
+
)
|
|
143
|
+
|
|
120
144
|
assert len(glyphs) == self.glyphCount
|
|
121
145
|
assert len(axisTags) == self.axisCount
|
|
122
146
|
sharedCoords = tv.decompileSharedTuples(
|
|
@@ -146,7 +170,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
|
|
|
146
170
|
glyph = glyf[glyphName]
|
|
147
171
|
numPointsInGlyph = self.getNumPoints_(glyph)
|
|
148
172
|
return decompileGlyph_(
|
|
149
|
-
numPointsInGlyph, sharedCoords, axisTags, gvarData
|
|
173
|
+
self.gid_size, numPointsInGlyph, sharedCoords, axisTags, gvarData
|
|
150
174
|
)
|
|
151
175
|
|
|
152
176
|
return read_item
|
|
@@ -264,23 +288,42 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
|
|
|
264
288
|
|
|
265
289
|
|
|
266
290
|
def compileGlyph_(
|
|
267
|
-
|
|
291
|
+
dataOffsetSize,
|
|
292
|
+
variations,
|
|
293
|
+
pointCount,
|
|
294
|
+
axisTags,
|
|
295
|
+
sharedCoordIndices,
|
|
296
|
+
*,
|
|
297
|
+
optimizeSize=True,
|
|
268
298
|
):
|
|
299
|
+
assert dataOffsetSize in (2, 3)
|
|
269
300
|
tupleVariationCount, tuples, data = tv.compileTupleVariationStore(
|
|
270
301
|
variations, pointCount, axisTags, sharedCoordIndices, optimizeSize=optimizeSize
|
|
271
302
|
)
|
|
272
303
|
if tupleVariationCount == 0:
|
|
273
304
|
return b""
|
|
274
|
-
|
|
275
|
-
|
|
305
|
+
|
|
306
|
+
offsetToData = 2 + dataOffsetSize + len(tuples)
|
|
307
|
+
|
|
308
|
+
result = [
|
|
309
|
+
tupleVariationCount.to_bytes(2, "big"),
|
|
310
|
+
offsetToData.to_bytes(dataOffsetSize, "big"),
|
|
311
|
+
tuples,
|
|
312
|
+
data,
|
|
313
|
+
]
|
|
314
|
+
if (offsetToData + len(data)) % 2 != 0:
|
|
276
315
|
result.append(b"\0") # padding
|
|
277
316
|
return b"".join(result)
|
|
278
317
|
|
|
279
318
|
|
|
280
|
-
def decompileGlyph_(pointCount, sharedTuples, axisTags, data):
|
|
281
|
-
|
|
319
|
+
def decompileGlyph_(dataOffsetSize, pointCount, sharedTuples, axisTags, data):
|
|
320
|
+
assert dataOffsetSize in (2, 3)
|
|
321
|
+
if len(data) < 2 + dataOffsetSize:
|
|
282
322
|
return []
|
|
283
|
-
|
|
323
|
+
|
|
324
|
+
tupleVariationCount = int.from_bytes(data[:2], "big")
|
|
325
|
+
offsetToData = int.from_bytes(data[2 : 2 + dataOffsetSize], "big")
|
|
326
|
+
|
|
284
327
|
dataPos = offsetToData
|
|
285
328
|
return tv.decompileTupleVariationStore(
|
|
286
329
|
"gvar",
|
|
@@ -289,6 +332,6 @@ def decompileGlyph_(pointCount, sharedTuples, axisTags, data):
|
|
|
289
332
|
pointCount,
|
|
290
333
|
sharedTuples,
|
|
291
334
|
data,
|
|
292
|
-
|
|
335
|
+
2 + dataOffsetSize,
|
|
293
336
|
offsetToData,
|
|
294
337
|
)
|