fonttools 4.55.3__cp39-cp39-macosx_10_9_universal2.whl → 4.55.7__cp39-cp39-macosx_10_9_universal2.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.

@@ -631,7 +631,14 @@ def splitCubicAtT(pt1, pt2, pt3, pt4, *ts):
631
631
  ((77.3438, 56.25), (85.9375, 43.75), (93.75, 25), (100, 0))
632
632
  """
633
633
  a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
634
- return _splitCubicAtT(a, b, c, d, *ts)
634
+ split = _splitCubicAtT(a, b, c, d, *ts)
635
+
636
+ # the split impl can introduce floating point errors; we know the first
637
+ # segment should always start at pt1 and the last segment should end at pt4,
638
+ # so we set those values directly before returning.
639
+ split[0] = (pt1, *split[0][1:])
640
+ split[-1] = (*split[-1][:-1], pt4)
641
+ return split
635
642
 
636
643
 
637
644
  @cython.locals(
@@ -52,6 +52,8 @@ translate, rotation, scale, skew, and transformation-center components.
52
52
  >>>
53
53
  """
54
54
 
55
+ from __future__ import annotations
56
+
55
57
  import math
56
58
  from typing import NamedTuple
57
59
  from dataclasses import dataclass
@@ -65,7 +67,7 @@ _ONE_EPSILON = 1 - _EPSILON
65
67
  _MINUS_ONE_EPSILON = -1 + _EPSILON
66
68
 
67
69
 
68
- def _normSinCos(v):
70
+ def _normSinCos(v: float) -> float:
69
71
  if abs(v) < _EPSILON:
70
72
  v = 0
71
73
  elif v > _ONE_EPSILON:
@@ -214,7 +216,7 @@ class Transform(NamedTuple):
214
216
  xx, xy, yx, yy = self[:4]
215
217
  return [(xx * dx + yx * dy, xy * dx + yy * dy) for dx, dy in vectors]
216
218
 
217
- def translate(self, x=0, y=0):
219
+ def translate(self, x: float = 0, y: float = 0):
218
220
  """Return a new transformation, translated (offset) by x, y.
219
221
 
220
222
  :Example:
@@ -225,7 +227,7 @@ class Transform(NamedTuple):
225
227
  """
226
228
  return self.transform((1, 0, 0, 1, x, y))
227
229
 
228
- def scale(self, x=1, y=None):
230
+ def scale(self, x: float = 1, y: float | None = None):
229
231
  """Return a new transformation, scaled by x, y. The 'y' argument
230
232
  may be None, which implies to use the x value for y as well.
231
233
 
@@ -241,7 +243,7 @@ class Transform(NamedTuple):
241
243
  y = x
242
244
  return self.transform((x, 0, 0, y, 0, 0))
243
245
 
244
- def rotate(self, angle):
246
+ def rotate(self, angle: float):
245
247
  """Return a new transformation, rotated by 'angle' (radians).
246
248
 
247
249
  :Example:
@@ -251,13 +253,11 @@ class Transform(NamedTuple):
251
253
  <Transform [0 1 -1 0 0 0]>
252
254
  >>>
253
255
  """
254
- import math
255
-
256
256
  c = _normSinCos(math.cos(angle))
257
257
  s = _normSinCos(math.sin(angle))
258
258
  return self.transform((c, s, -s, c, 0, 0))
259
259
 
260
- def skew(self, x=0, y=0):
260
+ def skew(self, x: float = 0, y: float = 0):
261
261
  """Return a new transformation, skewed by x and y.
262
262
 
263
263
  :Example:
@@ -267,8 +267,6 @@ class Transform(NamedTuple):
267
267
  <Transform [1 0 1 1 0 0]>
268
268
  >>>
269
269
  """
270
- import math
271
-
272
270
  return self.transform((1, math.tan(y), math.tan(x), 1, 0, 0))
273
271
 
274
272
  def transform(self, other):
@@ -336,7 +334,7 @@ class Transform(NamedTuple):
336
334
  dx, dy = -xx * dx - yx * dy, -xy * dx - yy * dy
337
335
  return self.__class__(xx, xy, yx, yy, dx, dy)
338
336
 
339
- def toPS(self):
337
+ def toPS(self) -> str:
340
338
  """Return a PostScript representation
341
339
 
342
340
  :Example:
@@ -352,7 +350,7 @@ class Transform(NamedTuple):
352
350
  """Decompose into a DecomposedTransform."""
353
351
  return DecomposedTransform.fromTransform(self)
354
352
 
355
- def __bool__(self):
353
+ def __bool__(self) -> bool:
356
354
  """Returns True if transform is not identity, False otherwise.
357
355
 
358
356
  :Example:
@@ -374,14 +372,14 @@ class Transform(NamedTuple):
374
372
  """
375
373
  return self != Identity
376
374
 
377
- def __repr__(self):
375
+ def __repr__(self) -> str:
378
376
  return "<%s [%g %g %g %g %g %g]>" % ((self.__class__.__name__,) + self)
379
377
 
380
378
 
381
379
  Identity = Transform()
382
380
 
383
381
 
384
- def Offset(x=0, y=0):
382
+ def Offset(x: float = 0, y: float = 0) -> Transform:
385
383
  """Return the identity transformation offset by x, y.
386
384
 
387
385
  :Example:
@@ -392,7 +390,7 @@ def Offset(x=0, y=0):
392
390
  return Transform(1, 0, 0, 1, x, y)
393
391
 
394
392
 
395
- def Scale(x, y=None):
393
+ def Scale(x: float, y: float | None = None) -> Transform:
396
394
  """Return the identity transformation scaled by x, y. The 'y' argument
397
395
  may be None, which implies to use the x value for y as well.
398
396
 
@@ -492,7 +490,7 @@ class DecomposedTransform:
492
490
  0,
493
491
  )
494
492
 
495
- def toTransform(self):
493
+ def toTransform(self) -> Transform:
496
494
  """Return the Transform() equivalent of this transformation.
497
495
 
498
496
  :Example:
@@ -106,6 +106,7 @@ class StatisticsControlPen(StatisticsBase, BasePen):
106
106
 
107
107
  def _moveTo(self, pt):
108
108
  self._nodes.append(complex(*pt))
109
+ self._startPoint = pt
109
110
 
110
111
  def _lineTo(self, pt):
111
112
  self._nodes.append(complex(*pt))
@@ -119,12 +120,16 @@ class StatisticsControlPen(StatisticsBase, BasePen):
119
120
  self._nodes.append(complex(*pt))
120
121
 
121
122
  def _closePath(self):
123
+ p0 = self._getCurrentPoint()
124
+ if p0 != self._startPoint:
125
+ self._lineTo(self._startPoint)
122
126
  self._update()
123
127
 
124
128
  def _endPath(self):
125
129
  p0 = self._getCurrentPoint()
126
130
  if p0 != self._startPoint:
127
131
  raise OpenContourError("Glyph statistics not defined on open contours.")
132
+ self._update()
128
133
 
129
134
  def _update(self):
130
135
  nodes = self._nodes
Binary file
@@ -1187,7 +1187,7 @@ class Glyph(object):
1187
1187
  ):
1188
1188
  return
1189
1189
  try:
1190
- coords, endPts, flags = self.getCoordinates(glyfTable)
1190
+ coords, endPts, flags = self.getCoordinates(glyfTable, round=otRound)
1191
1191
  self.xMin, self.yMin, self.xMax, self.yMax = coords.calcIntBounds()
1192
1192
  except NotImplementedError:
1193
1193
  pass
@@ -1206,9 +1206,7 @@ class Glyph(object):
1206
1206
  Return True if bounds were calculated, False otherwise.
1207
1207
  """
1208
1208
  for compo in self.components:
1209
- if hasattr(compo, "firstPt") or hasattr(compo, "transform"):
1210
- return False
1211
- if not float(compo.x).is_integer() or not float(compo.y).is_integer():
1209
+ if not compo._hasOnlyIntegerTranslate():
1212
1210
  return False
1213
1211
 
1214
1212
  # All components are untransformed and have an integer x/y translate
@@ -1241,7 +1239,7 @@ class Glyph(object):
1241
1239
  else:
1242
1240
  return self.numberOfContours == -1
1243
1241
 
1244
- def getCoordinates(self, glyfTable):
1242
+ def getCoordinates(self, glyfTable, *, round=noRound):
1245
1243
  """Return the coordinates, end points and flags
1246
1244
 
1247
1245
  This method returns three values: A :py:class:`GlyphCoordinates` object,
@@ -1267,13 +1265,27 @@ class Glyph(object):
1267
1265
  for compo in self.components:
1268
1266
  g = glyfTable[compo.glyphName]
1269
1267
  try:
1270
- coordinates, endPts, flags = g.getCoordinates(glyfTable)
1268
+ coordinates, endPts, flags = g.getCoordinates(
1269
+ glyfTable, round=round
1270
+ )
1271
1271
  except RecursionError:
1272
1272
  raise ttLib.TTLibError(
1273
1273
  "glyph '%s' contains a recursive component reference"
1274
1274
  % compo.glyphName
1275
1275
  )
1276
1276
  coordinates = GlyphCoordinates(coordinates)
1277
+ # if asked to round e.g. while computing bboxes, it's important we
1278
+ # do it immediately before a component transform is applied to a
1279
+ # simple glyph's coordinates in case these might still contain floats;
1280
+ # however, if the referenced component glyph is another composite, we
1281
+ # must not round here but only at the end, after all the nested
1282
+ # 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
+ coordinates.toInt(round=round)
1277
1289
  if hasattr(compo, "firstPt"):
1278
1290
  # component uses two reference points: we apply the transform _before_
1279
1291
  # computing the offset between the points
@@ -1930,6 +1942,18 @@ class GlyphComponent(object):
1930
1942
  result = self.__eq__(other)
1931
1943
  return result if result is NotImplemented else not result
1932
1944
 
1945
+ def _hasOnlyIntegerTranslate(self):
1946
+ """Return True if it's a 'simple' component.
1947
+
1948
+ That is, it has no anchor points and no transform other than integer translate.
1949
+ """
1950
+ return (
1951
+ not hasattr(self, "firstPt")
1952
+ and not hasattr(self, "transform")
1953
+ and float(self.x).is_integer()
1954
+ and float(self.y).is_integer()
1955
+ )
1956
+
1933
1957
 
1934
1958
  class GlyphCoordinates(object):
1935
1959
  """A list of glyph coordinates.
@@ -48,6 +48,10 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
48
48
 
49
49
  dependencies = ["ltag"]
50
50
 
51
+ def __init__(self, tag=None):
52
+ super().__init__(tag)
53
+ self.names = []
54
+
51
55
  def decompile(self, data, ttFont):
52
56
  format, n, stringOffset = struct.unpack(b">HHH", data[:6])
53
57
  expectedStringOffset = 6 + n * nameRecordSize
@@ -78,10 +82,6 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
78
82
  self.names.append(name)
79
83
 
80
84
  def compile(self, ttFont):
81
- if not hasattr(self, "names"):
82
- # only happens when there are NO name table entries read
83
- # from the TTX file
84
- self.names = []
85
85
  names = self.names
86
86
  names.sort() # sort according to the spec; see NameRecord.__lt__()
87
87
  stringData = b""
@@ -108,8 +108,6 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
108
108
  def fromXML(self, name, attrs, content, ttFont):
109
109
  if name != "namerecord":
110
110
  return # ignore unknown tags
111
- if not hasattr(self, "names"):
112
- self.names = []
113
111
  name = NameRecord()
114
112
  self.names.append(name)
115
113
  name.fromXML(name, attrs, content, ttFont)
@@ -194,8 +192,6 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
194
192
  identified by the (platformID, platEncID, langID) triplet. A warning is issued
195
193
  to prevent unexpected results.
196
194
  """
197
- if not hasattr(self, "names"):
198
- self.names = []
199
195
  if not isinstance(string, str):
200
196
  if isinstance(string, bytes):
201
197
  log.warning(
@@ -262,7 +258,7 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
262
258
  The nameID is assigned in the range between 'minNameID' and 32767 (inclusive),
263
259
  following the last nameID in the name table.
264
260
  """
265
- names = getattr(self, "names", [])
261
+ names = self.names
266
262
  nameID = 1 + max([n.nameID for n in names] + [minNameID - 1])
267
263
  if nameID > 32767:
268
264
  raise ValueError("nameID must be less than 32768")
@@ -359,8 +355,6 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
359
355
  If the 'nameID' argument is None, the created nameID will not
360
356
  be less than the 'minNameID' argument.
361
357
  """
362
- if not hasattr(self, "names"):
363
- self.names = []
364
358
  if nameID is None:
365
359
  # Reuse nameID if possible
366
360
  nameID = self.findMultilingualName(
@@ -404,8 +398,6 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
404
398
  assert (
405
399
  len(platforms) > 0
406
400
  ), "'platforms' must contain at least one (platformID, platEncID, langID) tuple"
407
- if not hasattr(self, "names"):
408
- self.names = []
409
401
  if not isinstance(string, str):
410
402
  raise TypeError(
411
403
  "expected str, found %s: %r" % (type(string).__name__, string)
Binary file