fonttools 4.59.1__cp314-cp314t-musllinux_1_2_aarch64.whl → 4.59.2__cp314-cp314t-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.
Potentially problematic release.
This version of fonttools might be problematic. Click here for more details.
- fontTools/__init__.py +1 -1
- fontTools/cu2qu/cu2qu.c +730 -637
- fontTools/cu2qu/cu2qu.cpython-314t-aarch64-linux-musl.so +0 -0
- fontTools/cu2qu/cu2qu.py +17 -2
- fontTools/feaLib/builder.py +9 -3
- fontTools/feaLib/lexer.cpython-314t-aarch64-linux-musl.so +0 -0
- fontTools/feaLib/parser.py +11 -1
- fontTools/feaLib/variableScalar.py +6 -1
- fontTools/misc/bezierTools.cpython-314t-aarch64-linux-musl.so +0 -0
- fontTools/misc/textTools.py +4 -2
- fontTools/pens/momentsPen.cpython-314t-aarch64-linux-musl.so +0 -0
- fontTools/qu2cu/qu2cu.cpython-314t-aarch64-linux-musl.so +0 -0
- fontTools/subset/__init__.py +1 -0
- fontTools/ttLib/tables/_a_v_a_r.py +4 -2
- fontTools/ttLib/tables/_n_a_m_e.py +11 -6
- fontTools/varLib/__init__.py +80 -1
- fontTools/varLib/instancer/__init__.py +56 -18
- fontTools/varLib/iup.cpython-314t-aarch64-linux-musl.so +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.59.2.dist-info}/METADATA +15 -1
- {fonttools-4.59.1.dist-info → fonttools-4.59.2.dist-info}/RECORD +26 -26
- {fonttools-4.59.1.data → fonttools-4.59.2.data}/data/share/man/man1/ttx.1 +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.59.2.dist-info}/WHEEL +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.59.2.dist-info}/entry_points.txt +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.59.2.dist-info}/licenses/LICENSE +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.59.2.dist-info}/licenses/LICENSE.external +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.59.2.dist-info}/top_level.txt +0 -0
|
Binary file
|
fontTools/cu2qu/cu2qu.py
CHANGED
|
@@ -37,7 +37,7 @@ NAN = float("NaN")
|
|
|
37
37
|
@cython.cfunc
|
|
38
38
|
@cython.inline
|
|
39
39
|
@cython.returns(cython.double)
|
|
40
|
-
@cython.locals(v1=cython.complex, v2=cython.complex)
|
|
40
|
+
@cython.locals(v1=cython.complex, v2=cython.complex, result=cython.double)
|
|
41
41
|
def dot(v1, v2):
|
|
42
42
|
"""Return the dot product of two vectors.
|
|
43
43
|
|
|
@@ -48,7 +48,16 @@ def dot(v1, v2):
|
|
|
48
48
|
Returns:
|
|
49
49
|
double: Dot product.
|
|
50
50
|
"""
|
|
51
|
-
|
|
51
|
+
result = (v1 * v2.conjugate()).real
|
|
52
|
+
# When vectors are perpendicular (i.e. dot product is 0), the above expression may
|
|
53
|
+
# yield slightly different results when running in pure Python vs C/Cython,
|
|
54
|
+
# both of which are correct within IEEE-754 floating-point precision.
|
|
55
|
+
# It's probably due to the different order of operations and roundings in each
|
|
56
|
+
# implementation. Because we are using the result in a denominator and catching
|
|
57
|
+
# ZeroDivisionError (see `calc_intersect`), it's best to normalize the result here.
|
|
58
|
+
if abs(result) < 1e-15:
|
|
59
|
+
result = 0.0
|
|
60
|
+
return result
|
|
52
61
|
|
|
53
62
|
|
|
54
63
|
@cython.cfunc
|
|
@@ -273,6 +282,12 @@ def calc_intersect(a, b, c, d):
|
|
|
273
282
|
try:
|
|
274
283
|
h = dot(p, a - c) / dot(p, cd)
|
|
275
284
|
except ZeroDivisionError:
|
|
285
|
+
# if 3 or 4 points are equal, we do have an intersection despite the zero-div:
|
|
286
|
+
# return one of the off-curves so that the algorithm can attempt a one-curve
|
|
287
|
+
# solution if it's within tolerance:
|
|
288
|
+
# https://github.com/linebender/kurbo/pull/484
|
|
289
|
+
if b == c and (a == b or c == d):
|
|
290
|
+
return b
|
|
276
291
|
return complex(NAN, NAN)
|
|
277
292
|
return c + cd * h
|
|
278
293
|
|
fontTools/feaLib/builder.py
CHANGED
|
@@ -32,6 +32,7 @@ from fontTools.otlLib.builder import (
|
|
|
32
32
|
AnySubstBuilder,
|
|
33
33
|
)
|
|
34
34
|
from fontTools.otlLib.error import OpenTypeLibError
|
|
35
|
+
from fontTools.varLib.errors import VarLibError
|
|
35
36
|
from fontTools.varLib.varStore import OnlineVarStoreBuilder
|
|
36
37
|
from fontTools.varLib.builder import buildVarDevTable
|
|
37
38
|
from fontTools.varLib.featureVars import addFeatureVariationsRaw
|
|
@@ -1728,9 +1729,14 @@ class Builder(object):
|
|
|
1728
1729
|
if not varscalar.does_vary:
|
|
1729
1730
|
return varscalar.default, None
|
|
1730
1731
|
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1732
|
+
try:
|
|
1733
|
+
default, index = varscalar.add_to_variation_store(
|
|
1734
|
+
self.varstorebuilder, self.model_cache, self.font.get("avar")
|
|
1735
|
+
)
|
|
1736
|
+
except VarLibError as e:
|
|
1737
|
+
raise FeatureLibError(
|
|
1738
|
+
"Failed to compute deltas for variable scalar", location
|
|
1739
|
+
) from e
|
|
1734
1740
|
|
|
1735
1741
|
device = None
|
|
1736
1742
|
if index is not None and index != 0xFFFFFFFF:
|
|
Binary file
|
fontTools/feaLib/parser.py
CHANGED
|
@@ -2198,7 +2198,7 @@ class Parser(object):
|
|
|
2198
2198
|
raise FeatureLibError(
|
|
2199
2199
|
"Expected an equals sign", self.cur_token_location_
|
|
2200
2200
|
)
|
|
2201
|
-
value = self.
|
|
2201
|
+
value = self.expect_integer_or_float_()
|
|
2202
2202
|
location[axis] = value
|
|
2203
2203
|
if self.next_token_type_ is Lexer.NAME and self.next_token_[0] == ":":
|
|
2204
2204
|
# Lexer has just read the value as a glyph name. We'll correct it later
|
|
@@ -2230,6 +2230,16 @@ class Parser(object):
|
|
|
2230
2230
|
"Expected a floating-point number", self.cur_token_location_
|
|
2231
2231
|
)
|
|
2232
2232
|
|
|
2233
|
+
def expect_integer_or_float_(self):
|
|
2234
|
+
if self.next_token_type_ == Lexer.FLOAT:
|
|
2235
|
+
return self.expect_float_()
|
|
2236
|
+
elif self.next_token_type_ is Lexer.NUMBER:
|
|
2237
|
+
return self.expect_number_()
|
|
2238
|
+
else:
|
|
2239
|
+
raise FeatureLibError(
|
|
2240
|
+
"Expected an integer or floating-point number", self.cur_token_location_
|
|
2241
|
+
)
|
|
2242
|
+
|
|
2233
2243
|
def expect_decipoint_(self):
|
|
2234
2244
|
if self.next_token_type_ == Lexer.FLOAT:
|
|
2235
2245
|
return self.expect_float_()
|
|
@@ -17,7 +17,12 @@ class VariableScalar:
|
|
|
17
17
|
def __repr__(self):
|
|
18
18
|
items = []
|
|
19
19
|
for location, value in self.values.items():
|
|
20
|
-
loc = ",".join(
|
|
20
|
+
loc = ",".join(
|
|
21
|
+
[
|
|
22
|
+
f"{ax}={int(coord) if float(coord).is_integer() else coord}"
|
|
23
|
+
for ax, coord in location
|
|
24
|
+
]
|
|
25
|
+
)
|
|
21
26
|
items.append("%s:%i" % (loc, value))
|
|
22
27
|
return "(" + (" ".join(items)) + ")"
|
|
23
28
|
|
|
Binary file
|
fontTools/misc/textTools.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""fontTools.misc.textTools.py -- miscellaneous routines."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
import ast
|
|
4
6
|
import string
|
|
5
7
|
|
|
@@ -118,14 +120,14 @@ def pad(data, size):
|
|
|
118
120
|
return data
|
|
119
121
|
|
|
120
122
|
|
|
121
|
-
def tostr(s, encoding="ascii", errors="strict"):
|
|
123
|
+
def tostr(s: str | bytes, encoding: str = "ascii", errors: str = "strict") -> str:
|
|
122
124
|
if not isinstance(s, str):
|
|
123
125
|
return s.decode(encoding, errors)
|
|
124
126
|
else:
|
|
125
127
|
return s
|
|
126
128
|
|
|
127
129
|
|
|
128
|
-
def tobytes(s, encoding="ascii", errors="strict"):
|
|
130
|
+
def tobytes(s: str | bytes, encoding: str = "ascii", errors: str = "strict") -> bytes:
|
|
129
131
|
if isinstance(s, str):
|
|
130
132
|
return s.encode(encoding, errors)
|
|
131
133
|
else:
|
|
Binary file
|
|
Binary file
|
fontTools/subset/__init__.py
CHANGED
|
@@ -1530,6 +1530,7 @@ def subset_glyphs(self, s):
|
|
|
1530
1530
|
if self.MarkFilteringSet not in s.used_mark_sets:
|
|
1531
1531
|
self.MarkFilteringSet = None
|
|
1532
1532
|
self.LookupFlag &= ~0x10
|
|
1533
|
+
self.LookupFlag |= 0x8
|
|
1533
1534
|
else:
|
|
1534
1535
|
self.MarkFilteringSet = s.used_mark_sets.index(self.MarkFilteringSet)
|
|
1535
1536
|
return bool(self.SubTableCount)
|
|
@@ -143,7 +143,7 @@ class table__a_v_a_r(BaseTTXConverter):
|
|
|
143
143
|
else:
|
|
144
144
|
super().fromXML(name, attrs, content, ttFont)
|
|
145
145
|
|
|
146
|
-
def renormalizeLocation(self, location, font):
|
|
146
|
+
def renormalizeLocation(self, location, font, dropZeroes=True):
|
|
147
147
|
|
|
148
148
|
majorVersion = getattr(self, "majorVersion", 1)
|
|
149
149
|
|
|
@@ -185,7 +185,9 @@ class table__a_v_a_r(BaseTTXConverter):
|
|
|
185
185
|
out.append(v)
|
|
186
186
|
|
|
187
187
|
mappedLocation = {
|
|
188
|
-
axis.axisTag: fi2fl(v, 14)
|
|
188
|
+
axis.axisTag: fi2fl(v, 14)
|
|
189
|
+
for v, axis in zip(out, axes)
|
|
190
|
+
if v != 0 or not dropZeroes
|
|
189
191
|
}
|
|
190
192
|
|
|
191
193
|
return mappedLocation
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
2
4
|
from fontTools.misc import sstruct
|
|
3
5
|
from fontTools.misc.textTools import (
|
|
4
6
|
bytechr,
|
|
@@ -63,7 +65,7 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
|
|
|
63
65
|
)
|
|
64
66
|
stringData = data[stringOffset:]
|
|
65
67
|
data = data[6:]
|
|
66
|
-
self.names = []
|
|
68
|
+
self.names: list[NameRecord] = []
|
|
67
69
|
for i in range(n):
|
|
68
70
|
if len(data) < 12:
|
|
69
71
|
log.error("skipping malformed name record #%d", i)
|
|
@@ -112,7 +114,9 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
|
|
|
112
114
|
self.names.append(name)
|
|
113
115
|
name.fromXML(name, attrs, content, ttFont)
|
|
114
116
|
|
|
115
|
-
def getName(
|
|
117
|
+
def getName(
|
|
118
|
+
self, nameID: int, platformID: int, platEncID: int, langID: int | None = None
|
|
119
|
+
) -> "NameRecord | None":
|
|
116
120
|
for namerecord in self.names:
|
|
117
121
|
if (
|
|
118
122
|
namerecord.nameID == nameID
|
|
@@ -123,8 +127,9 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
|
|
|
123
127
|
return namerecord
|
|
124
128
|
return None # not found
|
|
125
129
|
|
|
126
|
-
def getDebugName(self, nameID):
|
|
127
|
-
englishName
|
|
130
|
+
def getDebugName(self, nameID: int) -> str | None:
|
|
131
|
+
englishName: str | None = None
|
|
132
|
+
someName: str | None = None
|
|
128
133
|
for name in self.names:
|
|
129
134
|
if name.nameID != nameID:
|
|
130
135
|
continue
|
|
@@ -513,7 +518,7 @@ class NameRecord(object):
|
|
|
513
518
|
self.platformID == 3 and self.platEncID in [0, 1, 10]
|
|
514
519
|
)
|
|
515
520
|
|
|
516
|
-
def toUnicode(self, errors="strict"):
|
|
521
|
+
def toUnicode(self, errors: str = "strict") -> str:
|
|
517
522
|
"""
|
|
518
523
|
If self.string is a Unicode string, return it; otherwise try decoding the
|
|
519
524
|
bytes in self.string to a Unicode string using the encoding of this
|
|
@@ -533,7 +538,7 @@ class NameRecord(object):
|
|
|
533
538
|
and saving it back will not change them.
|
|
534
539
|
"""
|
|
535
540
|
|
|
536
|
-
def isascii(b):
|
|
541
|
+
def isascii(b: int) -> bool:
|
|
537
542
|
return (b >= 0x20 and b <= 0x7E) or b in [0x09, 0x0A, 0x0D]
|
|
538
543
|
|
|
539
544
|
encoding = self.getEncoding()
|
fontTools/varLib/__init__.py
CHANGED
|
@@ -30,7 +30,11 @@ from fontTools.misc.fixedTools import floatToFixed as fl2fi
|
|
|
30
30
|
from fontTools.misc.textTools import Tag, tostr
|
|
31
31
|
from fontTools.ttLib import TTFont, newTable
|
|
32
32
|
from fontTools.ttLib.tables._f_v_a_r import Axis, NamedInstance
|
|
33
|
-
from fontTools.ttLib.tables._g_l_y_f import
|
|
33
|
+
from fontTools.ttLib.tables._g_l_y_f import (
|
|
34
|
+
GlyphCoordinates,
|
|
35
|
+
dropImpliedOnCurvePoints,
|
|
36
|
+
USE_MY_METRICS,
|
|
37
|
+
)
|
|
34
38
|
from fontTools.ttLib.tables.ttProgram import Program
|
|
35
39
|
from fontTools.ttLib.tables.TupleVariation import TupleVariation
|
|
36
40
|
from fontTools.ttLib.tables import otTables as ot
|
|
@@ -489,6 +493,77 @@ def _merge_TTHinting(font, masterModel, master_ttfs):
|
|
|
489
493
|
cvar.variations = variations
|
|
490
494
|
|
|
491
495
|
|
|
496
|
+
def _has_inconsistent_use_my_metrics_flag(
|
|
497
|
+
master_glyf, glyph_name, flagged_components, expected_num_components
|
|
498
|
+
) -> bool:
|
|
499
|
+
master_glyph = master_glyf.get(glyph_name)
|
|
500
|
+
# 'sparse' glyph master doesn't contribute. Besides when components don't match
|
|
501
|
+
# the VF build is going to fail anyway, so be lenient here.
|
|
502
|
+
if (
|
|
503
|
+
master_glyph is not None
|
|
504
|
+
and master_glyph.isComposite()
|
|
505
|
+
and len(master_glyph.components) == expected_num_components
|
|
506
|
+
):
|
|
507
|
+
for i, base_glyph in flagged_components:
|
|
508
|
+
comp = master_glyph.components[i]
|
|
509
|
+
if comp.glyphName != base_glyph:
|
|
510
|
+
break
|
|
511
|
+
if not (comp.flags & USE_MY_METRICS):
|
|
512
|
+
return True
|
|
513
|
+
return False
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def _unset_inconsistent_use_my_metrics_flags(vf, master_fonts):
|
|
517
|
+
"""Clear USE_MY_METRICS on composite components if inconsistent across masters.
|
|
518
|
+
|
|
519
|
+
If a composite glyph's component has USE_MY_METRICS set differently among
|
|
520
|
+
the masters, the flag is removed from the variable font's glyf table so that
|
|
521
|
+
advance widths are not determined by that single component's phantom points.
|
|
522
|
+
"""
|
|
523
|
+
glyf = vf["glyf"]
|
|
524
|
+
master_glyfs = [m["glyf"] for m in master_fonts if "glyf" in m]
|
|
525
|
+
if not master_glyfs:
|
|
526
|
+
# Should not happen: at least the base master (as copied into vf) has glyf
|
|
527
|
+
return
|
|
528
|
+
|
|
529
|
+
for glyph_name in glyf.keys():
|
|
530
|
+
glyph = glyf[glyph_name]
|
|
531
|
+
if not glyph.isComposite():
|
|
532
|
+
continue
|
|
533
|
+
|
|
534
|
+
# collect indices of component(s) that carry the USE_MY_METRICS flag.
|
|
535
|
+
# This is supposed to be 1 component per composite, but you never know.
|
|
536
|
+
flagged_components = [
|
|
537
|
+
(i, comp.glyphName)
|
|
538
|
+
for i, comp in enumerate(glyph.components)
|
|
539
|
+
if (comp.flags & USE_MY_METRICS)
|
|
540
|
+
]
|
|
541
|
+
if not flagged_components:
|
|
542
|
+
# Nothing to fix
|
|
543
|
+
continue
|
|
544
|
+
|
|
545
|
+
# Verify that for all master glyf tables that contribute this glyph, the
|
|
546
|
+
# corresponding component (same glyphName and index) also carries USE_MY_METRICS
|
|
547
|
+
# and unset the flag if not.
|
|
548
|
+
expected_num_components = len(glyph.components)
|
|
549
|
+
if any(
|
|
550
|
+
_has_inconsistent_use_my_metrics_flag(
|
|
551
|
+
master_glyf, glyph_name, flagged_components, expected_num_components
|
|
552
|
+
)
|
|
553
|
+
for master_glyf in master_glyfs
|
|
554
|
+
):
|
|
555
|
+
comp_names = [name for _, name in flagged_components]
|
|
556
|
+
log.info(
|
|
557
|
+
"Composite glyph '%s' has inconsistent USE_MY_METRICS flags across "
|
|
558
|
+
"masters; clearing the flag on component%s %s",
|
|
559
|
+
glyph_name,
|
|
560
|
+
"s" if len(comp_names) > 1 else "",
|
|
561
|
+
comp_names if len(comp_names) > 1 else comp_names[0],
|
|
562
|
+
)
|
|
563
|
+
for i, _ in flagged_components:
|
|
564
|
+
glyph.components[i].flags &= ~USE_MY_METRICS
|
|
565
|
+
|
|
566
|
+
|
|
492
567
|
_MetricsFields = namedtuple(
|
|
493
568
|
"_MetricsFields",
|
|
494
569
|
[
|
|
@@ -1205,6 +1280,10 @@ def build(
|
|
|
1205
1280
|
if "DSIG" in vf:
|
|
1206
1281
|
del vf["DSIG"]
|
|
1207
1282
|
|
|
1283
|
+
# Clear USE_MY_METRICS composite flags if set inconsistently across masters.
|
|
1284
|
+
if "glyf" in vf:
|
|
1285
|
+
_unset_inconsistent_use_my_metrics_flags(vf, master_fonts)
|
|
1286
|
+
|
|
1208
1287
|
# TODO append masters as named-instances as well; needs .designspace change.
|
|
1209
1288
|
fvar = _add_fvar(vf, ds.axes, ds.instances)
|
|
1210
1289
|
if "STAT" not in exclude:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Partially instantiate a variable font.
|
|
2
2
|
|
|
3
3
|
The module exports an `instantiateVariableFont` function and CLI that allow to
|
|
4
4
|
create full instances (i.e. static fonts) from variable fonts, as well as "partial"
|
|
@@ -36,7 +36,7 @@ If the input location specifies all the axes, the resulting instance is no longe
|
|
|
36
36
|
'variable' (same as using fontools varLib.mutator):
|
|
37
37
|
.. code-block:: pycon
|
|
38
38
|
|
|
39
|
-
>>>
|
|
39
|
+
>>>
|
|
40
40
|
>> instance = instancer.instantiateVariableFont(
|
|
41
41
|
... varfont, {"wght": 700, "wdth": 67.5}
|
|
42
42
|
... )
|
|
@@ -56,8 +56,10 @@ From the console script, this is equivalent to passing `wght=drop` as input.
|
|
|
56
56
|
|
|
57
57
|
This module is similar to fontTools.varLib.mutator, which it's intended to supersede.
|
|
58
58
|
Note that, unlike varLib.mutator, when an axis is not mentioned in the input
|
|
59
|
-
location, the varLib.instancer will keep the axis and the corresponding
|
|
60
|
-
whereas mutator implicitly drops the axis at its default coordinate.
|
|
59
|
+
location, by default the varLib.instancer will keep the axis and the corresponding
|
|
60
|
+
deltas, whereas mutator implicitly drops the axis at its default coordinate.
|
|
61
|
+
To obtain the same behavior as mutator, pass the `static=True` parameter or
|
|
62
|
+
the `--static` CLI option.
|
|
61
63
|
|
|
62
64
|
The module supports all the following "levels" of instancing, which can of
|
|
63
65
|
course be combined:
|
|
@@ -72,7 +74,7 @@ L1
|
|
|
72
74
|
L2
|
|
73
75
|
dropping one or more axes while pinning them at non-default locations;
|
|
74
76
|
.. code-block:: pycon
|
|
75
|
-
|
|
77
|
+
|
|
76
78
|
>>>
|
|
77
79
|
>> font = instancer.instantiateVariableFont(varfont, {"wght": 700})
|
|
78
80
|
|
|
@@ -81,22 +83,18 @@ L3
|
|
|
81
83
|
a new minimum or maximum, potentially -- though not necessarily -- dropping
|
|
82
84
|
entire regions of variations that fall completely outside this new range.
|
|
83
85
|
.. code-block:: pycon
|
|
84
|
-
|
|
86
|
+
|
|
85
87
|
>>>
|
|
86
88
|
>> font = instancer.instantiateVariableFont(varfont, {"wght": (100, 300)})
|
|
87
89
|
|
|
88
90
|
L4
|
|
89
91
|
moving the default location of an axis, by specifying (min,defalt,max) values:
|
|
90
92
|
.. code-block:: pycon
|
|
91
|
-
|
|
93
|
+
|
|
92
94
|
>>>
|
|
93
95
|
>> font = instancer.instantiateVariableFont(varfont, {"wght": (100, 300, 700)})
|
|
94
96
|
|
|
95
|
-
|
|
96
|
-
are supported, but support for CFF2 variable fonts will be added soon.
|
|
97
|
-
|
|
98
|
-
The discussion and implementation of these features are tracked at
|
|
99
|
-
https://github.com/fonttools/fonttools/issues/1537
|
|
97
|
+
Both TrueType-flavored (glyf+gvar) variable and CFF2 variable fonts are supported.
|
|
100
98
|
"""
|
|
101
99
|
|
|
102
100
|
from fontTools.misc.fixedTools import (
|
|
@@ -435,7 +433,27 @@ class AxisLimits(_BaseAxisLimits):
|
|
|
435
433
|
|
|
436
434
|
avarSegments = {}
|
|
437
435
|
if usingAvar and "avar" in varfont:
|
|
438
|
-
|
|
436
|
+
avar = varfont["avar"]
|
|
437
|
+
avarSegments = avar.segments
|
|
438
|
+
|
|
439
|
+
if getattr(avar, "majorVersion", 1) >= 2 and avar.table.VarStore:
|
|
440
|
+
pinnedAxes = set(self.pinnedLocation())
|
|
441
|
+
if not pinnedAxes.issuperset(avarSegments):
|
|
442
|
+
raise NotImplementedError(
|
|
443
|
+
"Partial-instancing avar2 table is not supported"
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# TODO: Merge this with the main codepath.
|
|
447
|
+
|
|
448
|
+
# Full instancing of avar2 font. Use avar table to normalize location and return.
|
|
449
|
+
location = self.pinnedLocation()
|
|
450
|
+
location = {
|
|
451
|
+
tag: normalize(value, axes[tag], avarSegments.get(tag, None))
|
|
452
|
+
for tag, value in location.items()
|
|
453
|
+
}
|
|
454
|
+
return NormalizedAxisLimits(
|
|
455
|
+
**avar.renormalizeLocation(location, varfont, dropZeroes=False)
|
|
456
|
+
)
|
|
439
457
|
|
|
440
458
|
normalizedLimits = {}
|
|
441
459
|
|
|
@@ -1122,7 +1140,8 @@ def _instantiateVHVAR(varfont, axisLimits, tableFields, *, round=round):
|
|
|
1122
1140
|
varIdx = advMapping.mapping[glyphName]
|
|
1123
1141
|
else:
|
|
1124
1142
|
varIdx = varfont.getGlyphID(glyphName)
|
|
1125
|
-
|
|
1143
|
+
delta = round(defaultDeltas[varIdx])
|
|
1144
|
+
metrics[glyphName] = (max(0, advanceWidth + delta), sb)
|
|
1126
1145
|
|
|
1127
1146
|
if (
|
|
1128
1147
|
tableTag == "VVAR"
|
|
@@ -1384,7 +1403,6 @@ def _isValidAvarSegmentMap(axisTag, segmentMap):
|
|
|
1384
1403
|
|
|
1385
1404
|
|
|
1386
1405
|
def downgradeCFF2ToCFF(varfont):
|
|
1387
|
-
|
|
1388
1406
|
# Save these properties
|
|
1389
1407
|
recalcTimestamp = varfont.recalcTimestamp
|
|
1390
1408
|
recalcBBoxes = varfont.recalcBBoxes
|
|
@@ -1433,8 +1451,6 @@ def instantiateAvar(varfont, axisLimits):
|
|
|
1433
1451
|
# 'axisLimits' dict must contain user-space (non-normalized) coordinates.
|
|
1434
1452
|
|
|
1435
1453
|
avar = varfont["avar"]
|
|
1436
|
-
if getattr(avar, "majorVersion", 1) >= 2 and avar.table.VarStore:
|
|
1437
|
-
raise NotImplementedError("avar table with VarStore is not supported")
|
|
1438
1454
|
|
|
1439
1455
|
segments = avar.segments
|
|
1440
1456
|
|
|
@@ -1445,6 +1461,9 @@ def instantiateAvar(varfont, axisLimits):
|
|
|
1445
1461
|
del varfont["avar"]
|
|
1446
1462
|
return
|
|
1447
1463
|
|
|
1464
|
+
if getattr(avar, "majorVersion", 1) >= 2 and avar.table.VarStore:
|
|
1465
|
+
raise NotImplementedError("avar table with VarStore is not supported")
|
|
1466
|
+
|
|
1448
1467
|
log.info("Instantiating avar table")
|
|
1449
1468
|
for axis in pinnedAxes:
|
|
1450
1469
|
if axis in segments:
|
|
@@ -1646,6 +1665,7 @@ def instantiateVariableFont(
|
|
|
1646
1665
|
updateFontNames=False,
|
|
1647
1666
|
*,
|
|
1648
1667
|
downgradeCFF2=False,
|
|
1668
|
+
static=False,
|
|
1649
1669
|
):
|
|
1650
1670
|
"""Instantiate variable font, either fully or partially.
|
|
1651
1671
|
|
|
@@ -1689,12 +1709,23 @@ def instantiateVariableFont(
|
|
|
1689
1709
|
software that does not support CFF2. Defaults to False. Note that this
|
|
1690
1710
|
operation also removes overlaps within glyph shapes, as CFF does not support
|
|
1691
1711
|
overlaps but CFF2 does.
|
|
1712
|
+
static (bool): if True, generate a full instance (static font) instead of a partial
|
|
1713
|
+
instance (variable font).
|
|
1692
1714
|
"""
|
|
1693
1715
|
# 'overlap' used to be bool and is now enum; for backward compat keep accepting bool
|
|
1694
1716
|
overlap = OverlapMode(int(overlap))
|
|
1695
1717
|
|
|
1696
1718
|
sanityCheckVariableTables(varfont)
|
|
1697
1719
|
|
|
1720
|
+
if static:
|
|
1721
|
+
unspecified = []
|
|
1722
|
+
for axis in varfont["fvar"].axes:
|
|
1723
|
+
if axis.axisTag not in axisLimits:
|
|
1724
|
+
axisLimits[axis.axisTag] = None
|
|
1725
|
+
unspecified.append(axis.axisTag)
|
|
1726
|
+
if unspecified:
|
|
1727
|
+
log.info("Pinning unspecified axes to default: %s", unspecified)
|
|
1728
|
+
|
|
1698
1729
|
axisLimits = AxisLimits(axisLimits).limitAxesAndPopulateDefaults(varfont)
|
|
1699
1730
|
|
|
1700
1731
|
log.info("Restricted limits: %s", axisLimits)
|
|
@@ -1886,6 +1917,12 @@ def parseArgs(args):
|
|
|
1886
1917
|
default=None,
|
|
1887
1918
|
help="Output instance TTF file (default: INPUT-instance.ttf).",
|
|
1888
1919
|
)
|
|
1920
|
+
parser.add_argument(
|
|
1921
|
+
"--static",
|
|
1922
|
+
dest="static",
|
|
1923
|
+
action="store_true",
|
|
1924
|
+
help="Make a static font: pin unspecified axes to their default location.",
|
|
1925
|
+
)
|
|
1889
1926
|
parser.add_argument(
|
|
1890
1927
|
"--no-optimize",
|
|
1891
1928
|
dest="optimize",
|
|
@@ -1983,7 +2020,7 @@ def main(args=None):
|
|
|
1983
2020
|
recalcBBoxes=options.recalc_bounds,
|
|
1984
2021
|
)
|
|
1985
2022
|
|
|
1986
|
-
isFullInstance = {
|
|
2023
|
+
isFullInstance = options.static or {
|
|
1987
2024
|
axisTag
|
|
1988
2025
|
for axisTag, limit in axisLimits.items()
|
|
1989
2026
|
if limit is None or limit[0] == limit[2]
|
|
@@ -1997,6 +2034,7 @@ def main(args=None):
|
|
|
1997
2034
|
overlap=options.overlap,
|
|
1998
2035
|
updateFontNames=options.update_name_table,
|
|
1999
2036
|
downgradeCFF2=options.downgrade_cff2,
|
|
2037
|
+
static=options.static,
|
|
2000
2038
|
)
|
|
2001
2039
|
|
|
2002
2040
|
suffix = "-instance" if isFullInstance else "-partial"
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fonttools
|
|
3
|
-
Version: 4.59.
|
|
3
|
+
Version: 4.59.2
|
|
4
4
|
Summary: Tools to manipulate font files
|
|
5
5
|
Home-page: http://github.com/fonttools/fonttools
|
|
6
6
|
Author: Just van Rossum
|
|
@@ -388,6 +388,20 @@ Have fun!
|
|
|
388
388
|
Changelog
|
|
389
389
|
~~~~~~~~~
|
|
390
390
|
|
|
391
|
+
4.59.2 (released 2025-08-27)
|
|
392
|
+
----------------------------
|
|
393
|
+
|
|
394
|
+
- [varLib] Clear ``USE_MY_METRICS`` component flags when inconsistent across masters (#3912).
|
|
395
|
+
- [varLib.instancer] Avoid negative advance width/height values when instatiating HVAR/VVAR,
|
|
396
|
+
(unlikely in well-behaved fonts) (#3918).
|
|
397
|
+
- [subset] Fix shaping behaviour when pruning empty mark sets (#3915, harfbuzz/harfbuzz#5499).
|
|
398
|
+
- [cu2qu] Fixed ``dot()`` product of perpendicular vectors not always returning exactly 0.0
|
|
399
|
+
in all Python implementations (#3911)
|
|
400
|
+
- [varLib.instancer] Implemented fully-instantiating ``avar2`` fonts (#3909).
|
|
401
|
+
- [feaLib] Allow float values in ``VariableScalar``'s axis locations (#3906, #3907).
|
|
402
|
+
- [cu2qu] Handle special case in ``calc_intersect`` for degenerate cubic curves where 3 to 4
|
|
403
|
+
control points are equal (#3904).
|
|
404
|
+
|
|
391
405
|
4.59.1 (released 2025-08-14)
|
|
392
406
|
----------------------------
|
|
393
407
|
|