Skip to content

Commit 527896f

Browse files
committed
Intermediate changes
commit_hash:136dddc32a2e4f4b8352e2ebc58c87e8f4b6ff1d
1 parent 0866124 commit 527896f

File tree

13 files changed

+216
-39
lines changed

13 files changed

+216
-39
lines changed

contrib/python/fonttools/.dist-info/METADATA

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Metadata-Version: 2.4
22
Name: fonttools
3-
Version: 4.59.1
3+
Version: 4.59.2
44
Summary: Tools to manipulate font files
55
Home-page: http://github.com/fonttools/fonttools
66
Author: Just van Rossum
@@ -388,6 +388,20 @@ Have fun!
388388
Changelog
389389
~~~~~~~~~
390390

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+
391405
4.59.1 (released 2025-08-14)
392406
----------------------------
393407

contrib/python/fonttools/fontTools/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33

44
log = logging.getLogger(__name__)
55

6-
version = __version__ = "4.59.1"
6+
version = __version__ = "4.59.2"
77

88
__all__ = ["version", "log", "configLogger"]

contrib/python/fonttools/fontTools/cu2qu/cu2qu.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
@cython.cfunc
3838
@cython.inline
3939
@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)
4141
def dot(v1, v2):
4242
"""Return the dot product of two vectors.
4343
@@ -48,7 +48,16 @@ def dot(v1, v2):
4848
Returns:
4949
double: Dot product.
5050
"""
51-
return (v1 * v2.conjugate()).real
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
5261

5362

5463
@cython.cfunc
@@ -273,6 +282,12 @@ def calc_intersect(a, b, c, d):
273282
try:
274283
h = dot(p, a - c) / dot(p, cd)
275284
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
276291
return complex(NAN, NAN)
277292
return c + cd * h
278293

contrib/python/fonttools/fontTools/feaLib/builder.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
AnySubstBuilder,
3333
)
3434
from fontTools.otlLib.error import OpenTypeLibError
35+
from fontTools.varLib.errors import VarLibError
3536
from fontTools.varLib.varStore import OnlineVarStoreBuilder
3637
from fontTools.varLib.builder import buildVarDevTable
3738
from fontTools.varLib.featureVars import addFeatureVariationsRaw
@@ -1728,9 +1729,14 @@ def makeVariablePos(self, location, varscalar):
17281729
if not varscalar.does_vary:
17291730
return varscalar.default, None
17301731

1731-
default, index = varscalar.add_to_variation_store(
1732-
self.varstorebuilder, self.model_cache, self.font.get("avar")
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
17341740

17351741
device = None
17361742
if index is not None and index != 0xFFFFFFFF:

contrib/python/fonttools/fontTools/feaLib/parser.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2198,7 +2198,7 @@ def expect_master_(self):
21982198
raise FeatureLibError(
21992199
"Expected an equals sign", self.cur_token_location_
22002200
)
2201-
value = self.expect_number_()
2201+
value = self.expect_integer_or_float_()
22022202
location[axis] = value
22032203
if self.next_token_type_ is Lexer.NAME and self.next_token_[0] == ":":
22042204
# Lexer has just read the value as a glyph name. We'll correct it later
@@ -2230,6 +2230,16 @@ def expect_float_(self):
22302230
"Expected a floating-point number", self.cur_token_location_
22312231
)
22322232

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+
22332243
def expect_decipoint_(self):
22342244
if self.next_token_type_ == Lexer.FLOAT:
22352245
return self.expect_float_()

contrib/python/fonttools/fontTools/feaLib/variableScalar.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ def __init__(self, location_value={}):
1717
def __repr__(self):
1818
items = []
1919
for location, value in self.values.items():
20-
loc = ",".join(["%s=%i" % (ax, loc) for ax, loc in location])
20+
loc = ",".join(
21+
[
22+
f"{ax}={int(coord) if float(coord).is_integer() else coord}"
23+
for ax, coord in location
24+
]
25+
)
2126
items.append("%s:%i" % (loc, value))
2227
return "(" + (" ".join(items)) + ")"
2328

contrib/python/fonttools/fontTools/misc/textTools.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""fontTools.misc.textTools.py -- miscellaneous routines."""
22

3+
from __future__ import annotations
4+
35
import ast
46
import string
57

@@ -118,14 +120,14 @@ def pad(data, size):
118120
return data
119121

120122

121-
def tostr(s, encoding="ascii", errors="strict"):
123+
def tostr(s: str | bytes, encoding: str = "ascii", errors: str = "strict") -> str:
122124
if not isinstance(s, str):
123125
return s.decode(encoding, errors)
124126
else:
125127
return s
126128

127129

128-
def tobytes(s, encoding="ascii", errors="strict"):
130+
def tobytes(s: str | bytes, encoding: str = "ascii", errors: str = "strict") -> bytes:
129131
if isinstance(s, str):
130132
return s.encode(encoding, errors)
131133
else:

contrib/python/fonttools/fontTools/subset/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1530,6 +1530,7 @@ def subset_glyphs(self, s):
15301530
if self.MarkFilteringSet not in s.used_mark_sets:
15311531
self.MarkFilteringSet = None
15321532
self.LookupFlag &= ~0x10
1533+
self.LookupFlag |= 0x8
15331534
else:
15341535
self.MarkFilteringSet = s.used_mark_sets.index(self.MarkFilteringSet)
15351536
return bool(self.SubTableCount)

contrib/python/fonttools/fontTools/ttLib/tables/_a_v_a_r.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def fromXML(self, name, attrs, content, ttFont):
143143
else:
144144
super().fromXML(name, attrs, content, ttFont)
145145

146-
def renormalizeLocation(self, location, font):
146+
def renormalizeLocation(self, location, font, dropZeroes=True):
147147

148148
majorVersion = getattr(self, "majorVersion", 1)
149149

@@ -185,7 +185,9 @@ def renormalizeLocation(self, location, font):
185185
out.append(v)
186186

187187
mappedLocation = {
188-
axis.axisTag: fi2fl(v, 14) for v, axis in zip(out, axes) if v != 0
188+
axis.axisTag: fi2fl(v, 14)
189+
for v, axis in zip(out, axes)
190+
if v != 0 or not dropZeroes
189191
}
190192

191193
return mappedLocation

contrib/python/fonttools/fontTools/ttLib/tables/_n_a_m_e.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# -*- coding: utf-8 -*-
2+
from __future__ import annotations
3+
24
from fontTools.misc import sstruct
35
from fontTools.misc.textTools import (
46
bytechr,
@@ -63,7 +65,7 @@ def decompile(self, data, ttFont):
6365
)
6466
stringData = data[stringOffset:]
6567
data = data[6:]
66-
self.names = []
68+
self.names: list[NameRecord] = []
6769
for i in range(n):
6870
if len(data) < 12:
6971
log.error("skipping malformed name record #%d", i)
@@ -112,7 +114,9 @@ def fromXML(self, name, attrs, content, ttFont):
112114
self.names.append(name)
113115
name.fromXML(name, attrs, content, ttFont)
114116

115-
def getName(self, nameID, platformID, platEncID, langID=None):
117+
def getName(
118+
self, nameID: int, platformID: int, platEncID: int, langID: int | None = None
119+
) -> "NameRecord | None":
116120
for namerecord in self.names:
117121
if (
118122
namerecord.nameID == nameID
@@ -123,8 +127,9 @@ def getName(self, nameID, platformID, platEncID, langID=None):
123127
return namerecord
124128
return None # not found
125129

126-
def getDebugName(self, nameID):
127-
englishName = someName = None
130+
def getDebugName(self, nameID: int) -> str | None:
131+
englishName: str | None = None
132+
someName: str | None = None
128133
for name in self.names:
129134
if name.nameID != nameID:
130135
continue
@@ -513,7 +518,7 @@ def isUnicode(self):
513518
self.platformID == 3 and self.platEncID in [0, 1, 10]
514519
)
515520

516-
def toUnicode(self, errors="strict"):
521+
def toUnicode(self, errors: str = "strict") -> str:
517522
"""
518523
If self.string is a Unicode string, return it; otherwise try decoding the
519524
bytes in self.string to a Unicode string using the encoding of this
@@ -533,7 +538,7 @@ def toUnicode(self, errors="strict"):
533538
and saving it back will not change them.
534539
"""
535540

536-
def isascii(b):
541+
def isascii(b: int) -> bool:
537542
return (b >= 0x20 and b <= 0x7E) or b in [0x09, 0x0A, 0x0D]
538543

539544
encoding = self.getEncoding()

0 commit comments

Comments
 (0)