fonttools 4.59.1__cp310-cp310-win_amd64.whl → 4.60.0__cp310-cp310-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.

Files changed (51) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/annotations.py +30 -0
  3. fontTools/cu2qu/cu2qu.c +1185 -971
  4. fontTools/cu2qu/cu2qu.cp310-win_amd64.pyd +0 -0
  5. fontTools/cu2qu/cu2qu.py +36 -4
  6. fontTools/feaLib/builder.py +9 -3
  7. fontTools/feaLib/lexer.c +18 -12
  8. fontTools/feaLib/lexer.cp310-win_amd64.pyd +0 -0
  9. fontTools/feaLib/parser.py +11 -1
  10. fontTools/feaLib/variableScalar.py +6 -1
  11. fontTools/misc/bezierTools.c +18 -12
  12. fontTools/misc/bezierTools.cp310-win_amd64.pyd +0 -0
  13. fontTools/misc/enumTools.py +23 -0
  14. fontTools/misc/textTools.py +4 -2
  15. fontTools/pens/filterPen.py +218 -26
  16. fontTools/pens/momentsPen.c +18 -12
  17. fontTools/pens/momentsPen.cp310-win_amd64.pyd +0 -0
  18. fontTools/pens/pointPen.py +40 -6
  19. fontTools/qu2cu/qu2cu.c +30 -16
  20. fontTools/qu2cu/qu2cu.cp310-win_amd64.pyd +0 -0
  21. fontTools/subset/__init__.py +1 -0
  22. fontTools/ttLib/tables/_a_v_a_r.py +4 -2
  23. fontTools/ttLib/tables/_n_a_m_e.py +11 -6
  24. fontTools/ttLib/tables/_p_o_s_t.py +5 -5
  25. fontTools/ufoLib/__init__.py +279 -176
  26. fontTools/ufoLib/converters.py +14 -5
  27. fontTools/ufoLib/filenames.py +16 -6
  28. fontTools/ufoLib/glifLib.py +286 -190
  29. fontTools/ufoLib/kerning.py +32 -12
  30. fontTools/ufoLib/utils.py +41 -13
  31. fontTools/ufoLib/validators.py +121 -97
  32. fontTools/varLib/__init__.py +80 -1
  33. fontTools/varLib/avar/__init__.py +0 -0
  34. fontTools/varLib/avar/__main__.py +72 -0
  35. fontTools/varLib/avar/build.py +79 -0
  36. fontTools/varLib/avar/map.py +108 -0
  37. fontTools/varLib/avar/plan.py +1004 -0
  38. fontTools/varLib/{avar.py → avar/unbuild.py} +70 -59
  39. fontTools/varLib/avarPlanner.py +3 -999
  40. fontTools/varLib/instancer/__init__.py +56 -18
  41. fontTools/varLib/interpolatableHelpers.py +3 -0
  42. fontTools/varLib/iup.c +24 -14
  43. fontTools/varLib/iup.cp310-win_amd64.pyd +0 -0
  44. {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/METADATA +43 -2
  45. {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/RECORD +51 -44
  46. {fonttools-4.59.1.data → fonttools-4.60.0.data}/data/share/man/man1/ttx.1 +0 -0
  47. {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/WHEEL +0 -0
  48. {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/entry_points.txt +0 -0
  49. {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/licenses/LICENSE +0 -0
  50. {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/licenses/LICENSE.external +0 -0
  51. {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/top_level.txt +0 -0
@@ -17,6 +17,7 @@ from __future__ import annotations
17
17
  import math
18
18
  from typing import Any, Dict, List, Optional, Tuple
19
19
 
20
+ from fontTools.misc.enumTools import StrEnum
20
21
  from fontTools.misc.loggingTools import LogMixin
21
22
  from fontTools.misc.transform import DecomposedTransform, Identity
22
23
  from fontTools.pens.basePen import AbstractPen, MissingComponentError, PenError
@@ -28,6 +29,7 @@ __all__ = [
28
29
  "SegmentToPointPen",
29
30
  "GuessSmoothPointPen",
30
31
  "ReverseContourPointPen",
32
+ "ReverseFlipped",
31
33
  ]
32
34
 
33
35
  # Some type aliases to make it easier below
@@ -39,6 +41,19 @@ SegmentType = Optional[str]
39
41
  SegmentList = List[Tuple[SegmentType, SegmentPointList]]
40
42
 
41
43
 
44
+ class ReverseFlipped(StrEnum):
45
+ """How to handle flipped components during decomposition.
46
+
47
+ NO: Don't reverse flipped components
48
+ KEEP_START: Reverse flipped components, keeping original starting point
49
+ ON_CURVE_FIRST: Reverse flipped components, ensuring first point is on-curve
50
+ """
51
+
52
+ NO = "no"
53
+ KEEP_START = "keep_start"
54
+ ON_CURVE_FIRST = "on_curve_first"
55
+
56
+
42
57
  class AbstractPointPen:
43
58
  """Baseclass for all PointPens."""
44
59
 
@@ -559,15 +574,20 @@ class DecomposingPointPen(LogMixin, AbstractPointPen):
559
574
  glyphSet,
560
575
  *args,
561
576
  skipMissingComponents=None,
562
- reverseFlipped=False,
577
+ reverseFlipped: bool | ReverseFlipped = False,
563
578
  **kwargs,
564
579
  ):
565
580
  """Takes a 'glyphSet' argument (dict), in which the glyphs that are referenced
566
581
  as components are looked up by their name.
567
582
 
568
- If the optional 'reverseFlipped' argument is True, components whose transformation
569
- matrix has a negative determinant will be decomposed with a reversed path direction
570
- to compensate for the flip.
583
+ If the optional 'reverseFlipped' argument is True or a ReverseFlipped enum value,
584
+ components whose transformation matrix has a negative determinant will be decomposed
585
+ with a reversed path direction to compensate for the flip.
586
+
587
+ The reverseFlipped parameter can be:
588
+ - False or ReverseFlipped.NO: Don't reverse flipped components
589
+ - True or ReverseFlipped.KEEP_START: Reverse, keeping original starting point
590
+ - ReverseFlipped.ON_CURVE_FIRST: Reverse, ensuring first point is on-curve
571
591
 
572
592
  The optional 'skipMissingComponents' argument can be set to True/False to
573
593
  override the homonymous class attribute for a given pen instance.
@@ -579,7 +599,13 @@ class DecomposingPointPen(LogMixin, AbstractPointPen):
579
599
  if skipMissingComponents is None
580
600
  else skipMissingComponents
581
601
  )
582
- self.reverseFlipped = reverseFlipped
602
+ # Handle backward compatibility and validate string inputs
603
+ if reverseFlipped is False:
604
+ self.reverseFlipped = ReverseFlipped.NO
605
+ elif reverseFlipped is True:
606
+ self.reverseFlipped = ReverseFlipped.KEEP_START
607
+ else:
608
+ self.reverseFlipped = ReverseFlipped(reverseFlipped)
583
609
 
584
610
  def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs):
585
611
  """Transform the points of the base glyph and draw it onto self.
@@ -600,10 +626,18 @@ class DecomposingPointPen(LogMixin, AbstractPointPen):
600
626
  pen = self
601
627
  if transformation != Identity:
602
628
  pen = TransformPointPen(pen, transformation)
603
- if self.reverseFlipped:
629
+ if self.reverseFlipped != ReverseFlipped.NO:
604
630
  # if the transformation has a negative determinant, it will
605
631
  # reverse the contour direction of the component
606
632
  a, b, c, d = transformation[:4]
607
633
  if a * d - b * c < 0:
608
634
  pen = ReverseContourPointPen(pen)
635
+
636
+ if self.reverseFlipped == ReverseFlipped.ON_CURVE_FIRST:
637
+ from fontTools.pens.filterPen import OnCurveFirstPointPen
638
+
639
+ # Ensure the starting point is an on-curve.
640
+ # Wrap last so this filter runs first during drawPoints
641
+ pen = OnCurveFirstPointPen(pen)
642
+
609
643
  glyph.drawPoints(pen)
fontTools/qu2cu/qu2cu.c CHANGED
@@ -1,4 +1,4 @@
1
- /* Generated by Cython 3.1.3 */
1
+ /* Generated by Cython 3.1.4 */
2
2
 
3
3
  /* BEGIN: Cython Metadata
4
4
  {
@@ -32,8 +32,8 @@ END: Cython Metadata */
32
32
  #elif PY_VERSION_HEX < 0x03080000
33
33
  #error Cython requires Python 3.8+.
34
34
  #else
35
- #define __PYX_ABI_VERSION "3_1_3"
36
- #define CYTHON_HEX_VERSION 0x030103F0
35
+ #define __PYX_ABI_VERSION "3_1_4"
36
+ #define CYTHON_HEX_VERSION 0x030104F0
37
37
  #define CYTHON_FUTURE_DIVISION 1
38
38
  /* CModulePreamble */
39
39
  #include <stddef.h>
@@ -3887,8 +3887,12 @@ static PyObject *__pyx_f_9fontTools_5qu2cu_5qu2cu_merge_curves(PyObject *__pyx_v
3887
3887
  __pyx_t_14 = __Pyx_c_diff_double(__pyx_v_p1, __pyx_v_p0);
3888
3888
  __pyx_t_7 = __pyx_PyComplex_FromComplex(__pyx_t_14); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 146, __pyx_L1_error)
3889
3889
  __Pyx_GOTREF(__pyx_t_7);
3890
- __pyx_t_9 = (__Pyx_PyList_GET_SIZE(__pyx_v_ts) != 0);
3891
- if (unlikely(((!CYTHON_ASSUME_SAFE_MACROS) && __pyx_t_9 < 0))) __PYX_ERR(0, 146, __pyx_L1_error)
3890
+ {
3891
+ Py_ssize_t __pyx_temp = __Pyx_PyList_GET_SIZE(__pyx_v_ts);
3892
+ if (unlikely(((!CYTHON_ASSUME_SAFE_SIZE) && __pyx_temp < 0))) __PYX_ERR(0, 146, __pyx_L1_error)
3893
+ __pyx_t_9 = (__pyx_temp != 0);
3894
+ }
3895
+
3892
3896
  if (__pyx_t_9) {
3893
3897
  __pyx_t_1 = __Pyx_GetItemInt_List(__pyx_v_ts, 0, long, 1, __Pyx_PyLong_From_long, 1, 0, 1, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 146, __pyx_L1_error)
3894
3898
  __Pyx_GOTREF(__pyx_t_1);
@@ -3922,8 +3926,12 @@ static PyObject *__pyx_f_9fontTools_5qu2cu_5qu2cu_merge_curves(PyObject *__pyx_v
3922
3926
  __pyx_t_14 = __Pyx_c_diff_double(__pyx_v_p2, __pyx_v_p3);
3923
3927
  __pyx_t_1 = __pyx_PyComplex_FromComplex(__pyx_t_14); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 147, __pyx_L1_error)
3924
3928
  __Pyx_GOTREF(__pyx_t_1);
3925
- __pyx_t_9 = (__Pyx_PyList_GET_SIZE(__pyx_v_ts) != 0);
3926
- if (unlikely(((!CYTHON_ASSUME_SAFE_MACROS) && __pyx_t_9 < 0))) __PYX_ERR(0, 147, __pyx_L1_error)
3929
+ {
3930
+ Py_ssize_t __pyx_temp = __Pyx_PyList_GET_SIZE(__pyx_v_ts);
3931
+ if (unlikely(((!CYTHON_ASSUME_SAFE_SIZE) && __pyx_temp < 0))) __PYX_ERR(0, 147, __pyx_L1_error)
3932
+ __pyx_t_9 = (__pyx_temp != 0);
3933
+ }
3934
+
3927
3935
  if (__pyx_t_9) {
3928
3936
  __pyx_t_7 = __Pyx_GetItemInt_List(__pyx_v_ts, -1L, long, 1, __Pyx_PyLong_From_long, 1, 1, 1, 1); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 147, __pyx_L1_error)
3929
3937
  __Pyx_GOTREF(__pyx_t_7);
@@ -9031,15 +9039,16 @@ static int __Pyx_InitConstants(__pyx_mstatetype *__pyx_mstate) {
9031
9039
  return -1;
9032
9040
  }
9033
9041
  /* #### Code section: init_codeobjects ### */
9034
- typedef struct {
9035
- unsigned int argcount : 3;
9036
- unsigned int num_posonly_args : 1;
9037
- unsigned int num_kwonly_args : 1;
9038
- unsigned int nlocals : 6;
9039
- unsigned int flags : 10;
9040
- unsigned int first_line : 9;
9041
- unsigned int line_table_length : 15;
9042
- } __Pyx_PyCode_New_function_description;
9042
+ \
9043
+ typedef struct {
9044
+ unsigned int argcount : 3;
9045
+ unsigned int num_posonly_args : 1;
9046
+ unsigned int num_kwonly_args : 1;
9047
+ unsigned int nlocals : 6;
9048
+ unsigned int flags : 10;
9049
+ unsigned int first_line : 9;
9050
+ unsigned int line_table_length : 15;
9051
+ } __Pyx_PyCode_New_function_description;
9043
9052
  /* NewCodeObj.proto */
9044
9053
  static PyObject* __Pyx_PyCode_New(
9045
9054
  const __Pyx_PyCode_New_function_description descr,
@@ -12270,6 +12279,7 @@ static int __pyx_CommonTypesMetaclass_init(PyObject *module) {
12270
12279
  return -1;
12271
12280
  }
12272
12281
  mstate->__pyx_CommonTypesMetaclassType = __Pyx_FetchCommonTypeFromSpec(NULL, module, &__pyx_CommonTypesMetaclass_spec, bases);
12282
+ Py_DECREF(bases);
12273
12283
  if (unlikely(mstate->__pyx_CommonTypesMetaclassType == NULL)) {
12274
12284
  return -1;
12275
12285
  }
@@ -16097,6 +16107,10 @@ static int __Pyx_check_binary_version(unsigned long ct_version, unsigned long rt
16097
16107
  PyCode_NewWithPosOnlyArgs
16098
16108
  #endif
16099
16109
  (a, p, k, l, s, f, code, c, n, v, fv, cell, fn, name, name, fline, lnos, __pyx_mstate_global->__pyx_empty_bytes);
16110
+ #if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030c00A1
16111
+ if (likely(result))
16112
+ result->_co_firsttraceable = 0;
16113
+ #endif
16100
16114
  return result;
16101
16115
  }
16102
16116
  #elif PY_VERSION_HEX >= 0x030800B2 && !CYTHON_COMPILING_IN_PYPY
Binary file
@@ -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) 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
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(self, nameID, platformID, platEncID, langID=None):
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 = someName = None
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()
@@ -118,6 +118,7 @@ class table__p_o_s_t(DefaultTable.DefaultTable):
118
118
  def build_psNameMapping(self, ttFont):
119
119
  mapping = {}
120
120
  allNames = {}
121
+ glyphOrderNames = set(self.glyphOrder)
121
122
  for i in range(ttFont["maxp"].numGlyphs):
122
123
  glyphName = psName = self.glyphOrder[i]
123
124
  if glyphName == "":
@@ -126,16 +127,15 @@ class table__p_o_s_t(DefaultTable.DefaultTable):
126
127
  if glyphName in allNames:
127
128
  # make up a new glyphName that's unique
128
129
  n = allNames[glyphName]
129
- # check if the exists in any of the seen names or later ones
130
- names = set(allNames.keys()) | set(self.glyphOrder)
131
- while (glyphName + "." + str(n)) in names:
130
+ # check if the glyph name exists in the glyph order
131
+ while f"{glyphName}.{n}" in glyphOrderNames:
132
132
  n += 1
133
133
  allNames[glyphName] = n + 1
134
- glyphName = glyphName + "." + str(n)
134
+ glyphName = f"{glyphName}.{n}"
135
135
 
136
- self.glyphOrder[i] = glyphName
137
136
  allNames[glyphName] = 1
138
137
  if glyphName != psName:
138
+ self.glyphOrder[i] = glyphName
139
139
  mapping[glyphName] = psName
140
140
 
141
141
  self.mapping = mapping