fonttools 4.59.0__cp312-cp312-win_amd64.whl → 4.59.2__cp312-cp312-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 (38) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/cffLib/CFF2ToCFF.py +40 -10
  3. fontTools/cffLib/transforms.py +11 -6
  4. fontTools/cu2qu/cu2qu.c +760 -653
  5. fontTools/cu2qu/cu2qu.cp312-win_amd64.pyd +0 -0
  6. fontTools/cu2qu/cu2qu.py +17 -2
  7. fontTools/feaLib/builder.py +15 -4
  8. fontTools/feaLib/lexer.c +30 -16
  9. fontTools/feaLib/lexer.cp312-win_amd64.pyd +0 -0
  10. fontTools/feaLib/parser.py +11 -1
  11. fontTools/feaLib/variableScalar.py +6 -1
  12. fontTools/misc/bezierTools.c +33 -19
  13. fontTools/misc/bezierTools.cp312-win_amd64.pyd +0 -0
  14. fontTools/misc/psCharStrings.py +17 -2
  15. fontTools/misc/textTools.py +4 -2
  16. fontTools/pens/momentsPen.c +20 -14
  17. fontTools/pens/momentsPen.cp312-win_amd64.pyd +0 -0
  18. fontTools/qu2cu/qu2cu.c +32 -18
  19. fontTools/qu2cu/qu2cu.cp312-win_amd64.pyd +0 -0
  20. fontTools/subset/__init__.py +1 -0
  21. fontTools/ttLib/tables/_a_v_a_r.py +4 -2
  22. fontTools/ttLib/tables/_g_v_a_r.py +6 -3
  23. fontTools/ttLib/tables/_h_m_t_x.py +7 -3
  24. fontTools/ttLib/tables/_n_a_m_e.py +11 -6
  25. fontTools/varLib/__init__.py +80 -1
  26. fontTools/varLib/featureVars.py +8 -0
  27. fontTools/varLib/instancer/__init__.py +120 -22
  28. fontTools/varLib/iup.c +32 -18
  29. fontTools/varLib/iup.cp312-win_amd64.pyd +0 -0
  30. fontTools/varLib/mutator.py +11 -0
  31. {fonttools-4.59.0.dist-info → fonttools-4.59.2.dist-info}/METADATA +38 -10
  32. {fonttools-4.59.0.dist-info → fonttools-4.59.2.dist-info}/RECORD +38 -38
  33. {fonttools-4.59.0.data → fonttools-4.59.2.data}/data/share/man/man1/ttx.1 +0 -0
  34. {fonttools-4.59.0.dist-info → fonttools-4.59.2.dist-info}/WHEEL +0 -0
  35. {fonttools-4.59.0.dist-info → fonttools-4.59.2.dist-info}/entry_points.txt +0 -0
  36. {fonttools-4.59.0.dist-info → fonttools-4.59.2.dist-info}/licenses/LICENSE +0 -0
  37. {fonttools-4.59.0.dist-info → fonttools-4.59.2.dist-info}/licenses/LICENSE.external +0 -0
  38. {fonttools-4.59.0.dist-info → fonttools-4.59.2.dist-info}/top_level.txt +0 -0
fontTools/qu2cu/qu2cu.c CHANGED
@@ -1,4 +1,4 @@
1
- /* Generated by Cython 3.1.2 */
1
+ /* Generated by Cython 3.1.3 */
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_2"
36
- #define CYTHON_HEX_VERSION 0x030102F0
35
+ #define __PYX_ABI_VERSION "3_1_3"
36
+ #define CYTHON_HEX_VERSION 0x030103F0
37
37
  #define CYTHON_FUTURE_DIVISION 1
38
38
  /* CModulePreamble */
39
39
  #include <stddef.h>
@@ -396,6 +396,9 @@ END: Cython Metadata */
396
396
  enum { __pyx_check_sizeof_voidp = 1 / (int)(SIZEOF_VOID_P == sizeof(void*)) };
397
397
  #endif
398
398
  #endif
399
+ #ifndef CYTHON_LOCK_AND_GIL_DEADLOCK_AVOIDANCE_TIME
400
+ #define CYTHON_LOCK_AND_GIL_DEADLOCK_AVOIDANCE_TIME 100
401
+ #endif
399
402
  #ifndef __has_attribute
400
403
  #define __has_attribute(x) 0
401
404
  #endif
@@ -1963,7 +1966,7 @@ static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb, PyObject
1963
1966
  static PyObject *__Pyx_PyLong_AbsNeg(PyObject *num);
1964
1967
  #define __Pyx_PyNumber_Absolute(x)\
1965
1968
  ((likely(PyLong_CheckExact(x))) ?\
1966
- (likely(__Pyx_PyLong_IsNonNeg(x)) ? (Py_INCREF(x), (x)) : __Pyx_PyLong_AbsNeg(x)) :\
1969
+ (likely(__Pyx_PyLong_IsNonNeg(x)) ? __Pyx_NewRef(x) : __Pyx_PyLong_AbsNeg(x)) :\
1967
1970
  PyNumber_Absolute(x))
1968
1971
  #else
1969
1972
  #define __Pyx_PyNumber_Absolute(x) PyNumber_Absolute(x)
@@ -9028,16 +9031,15 @@ static int __Pyx_InitConstants(__pyx_mstatetype *__pyx_mstate) {
9028
9031
  return -1;
9029
9032
  }
9030
9033
  /* #### Code section: init_codeobjects ### */
9031
- \
9032
- typedef struct {
9033
- unsigned int argcount : 3;
9034
- unsigned int num_posonly_args : 1;
9035
- unsigned int num_kwonly_args : 1;
9036
- unsigned int nlocals : 6;
9037
- unsigned int flags : 10;
9038
- unsigned int first_line : 9;
9039
- unsigned int line_table_length : 15;
9040
- } __Pyx_PyCode_New_function_description;
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;
9041
9043
  /* NewCodeObj.proto */
9042
9044
  static PyObject* __Pyx_PyCode_New(
9043
9045
  const __Pyx_PyCode_New_function_description descr,
@@ -10557,7 +10559,7 @@ static PyObject *__Pyx_PyLong_AbsNeg(PyObject *n) {
10557
10559
  PyObject *copy = _PyLong_Copy((PyLongObject*)n);
10558
10560
  if (likely(copy)) {
10559
10561
  #if PY_VERSION_HEX >= 0x030C00A7
10560
- ((PyLongObject*)copy)->long_value.lv_tag = ((PyLongObject*)copy)->long_value.lv_tag & ~_PyLong_SIGN_MASK;
10562
+ ((PyLongObject*)copy)->long_value.lv_tag ^= ((PyLongObject*)copy)->long_value.lv_tag & _PyLong_SIGN_MASK;
10561
10563
  #else
10562
10564
  __Pyx_SET_SIZE(copy, -Py_SIZE(copy));
10563
10565
  #endif
@@ -11193,6 +11195,13 @@ try_unpack:
11193
11195
 
11194
11196
  /* PyObjectCallMethod0 */
11195
11197
  static PyObject* __Pyx_PyObject_CallMethod0(PyObject* obj, PyObject* method_name) {
11198
+ #if CYTHON_VECTORCALL && (__PYX_LIMITED_VERSION_HEX >= 0x030C0000 || (!CYTHON_COMPILING_IN_LIMITED_API && PY_VERSION_HEX >= 0x03090000))
11199
+ PyObject *args[1] = {obj};
11200
+ (void) __Pyx_PyObject_GetMethod;
11201
+ (void) __Pyx_PyObject_CallOneArg;
11202
+ (void) __Pyx_PyObject_CallNoArg;
11203
+ return PyObject_VectorcallMethod(method_name, args, 1 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL);
11204
+ #else
11196
11205
  PyObject *method = NULL, *result = NULL;
11197
11206
  int is_method = __Pyx_PyObject_GetMethod(obj, method_name, &method);
11198
11207
  if (likely(is_method)) {
@@ -11205,6 +11214,7 @@ static PyObject* __Pyx_PyObject_CallMethod0(PyObject* obj, PyObject* method_name
11205
11214
  Py_DECREF(method);
11206
11215
  bad:
11207
11216
  return result;
11217
+ #endif
11208
11218
  }
11209
11219
 
11210
11220
  /* CallUnboundCMethod0 */
@@ -11742,6 +11752,7 @@ static int __Pyx_fix_up_extension_type_from_spec(PyType_Spec *spec, PyTypeObject
11742
11752
  changed = 1;
11743
11753
  }
11744
11754
  #endif // CYTHON_METH_FASTCALL
11755
+ #if !CYTHON_COMPILING_IN_PYPY
11745
11756
  else if (strcmp(memb->name, "__module__") == 0) {
11746
11757
  PyObject *descr;
11747
11758
  assert(memb->type == T_OBJECT);
@@ -11756,11 +11767,13 @@ static int __Pyx_fix_up_extension_type_from_spec(PyType_Spec *spec, PyTypeObject
11756
11767
  }
11757
11768
  changed = 1;
11758
11769
  }
11770
+ #endif // !CYTHON_COMPILING_IN_PYPY
11759
11771
  }
11760
11772
  memb++;
11761
11773
  }
11762
11774
  }
11763
11775
  #endif // !CYTHON_COMPILING_IN_LIMITED_API
11776
+ #if !CYTHON_COMPILING_IN_PYPY
11764
11777
  slot = spec->slots;
11765
11778
  while (slot && slot->slot && slot->slot != Py_tp_getset)
11766
11779
  slot++;
@@ -11792,6 +11805,7 @@ static int __Pyx_fix_up_extension_type_from_spec(PyType_Spec *spec, PyTypeObject
11792
11805
  ++getset;
11793
11806
  }
11794
11807
  }
11808
+ #endif // !CYTHON_COMPILING_IN_PYPY
11795
11809
  if (changed)
11796
11810
  PyType_Modified(type);
11797
11811
  #endif // PY_VERSION_HEX > 0x030900B1
@@ -12227,7 +12241,7 @@ bad:
12227
12241
  }
12228
12242
 
12229
12243
  /* CommonTypesMetaclass */
12230
- PyObject* __pyx_CommonTypesMetaclass_get_module(CYTHON_UNUSED PyObject *self, CYTHON_UNUSED void* context) {
12244
+ static PyObject* __pyx_CommonTypesMetaclass_get_module(CYTHON_UNUSED PyObject *self, CYTHON_UNUSED void* context) {
12231
12245
  return PyUnicode_FromString(__PYX_ABI_MODULE_NAME);
12232
12246
  }
12233
12247
  static PyGetSetDef __pyx_CommonTypesMetaclass_getset[] = {
@@ -14847,7 +14861,7 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_Call2Args(PyObject* function, PyOb
14847
14861
  }
14848
14862
 
14849
14863
  /* PyObjectCallMethod1 */
14850
- #if !(CYTHON_VECTORCALL && __PYX_LIMITED_VERSION_HEX >= 0x030C0000)
14864
+ #if !(CYTHON_VECTORCALL && (__PYX_LIMITED_VERSION_HEX >= 0x030C0000 || (!CYTHON_COMPILING_IN_LIMITED_API && PY_VERSION_HEX >= 0x03090000)))
14851
14865
  static PyObject* __Pyx__PyObject_CallMethod1(PyObject* method, PyObject* arg) {
14852
14866
  PyObject *result = __Pyx_PyObject_CallOneArg(method, arg);
14853
14867
  Py_DECREF(method);
@@ -14855,7 +14869,7 @@ static PyObject* __Pyx__PyObject_CallMethod1(PyObject* method, PyObject* arg) {
14855
14869
  }
14856
14870
  #endif
14857
14871
  static PyObject* __Pyx_PyObject_CallMethod1(PyObject* obj, PyObject* method_name, PyObject* arg) {
14858
- #if CYTHON_VECTORCALL && __PYX_LIMITED_VERSION_HEX >= 0x030C0000
14872
+ #if CYTHON_VECTORCALL && (__PYX_LIMITED_VERSION_HEX >= 0x030C0000 || (!CYTHON_COMPILING_IN_LIMITED_API && PY_VERSION_HEX >= 0x03090000))
14859
14873
  PyObject *args[2] = {obj, arg};
14860
14874
  (void) __Pyx_PyObject_GetMethod;
14861
14875
  (void) __Pyx_PyObject_CallOneArg;
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
@@ -64,7 +64,6 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
64
64
  self.variations = {}
65
65
 
66
66
  def compile(self, ttFont):
67
-
68
67
  axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
69
68
  sharedTuples = tv.compileSharedTuples(
70
69
  axisTags, itertools.chain(*self.variations.values())
@@ -141,8 +140,12 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
141
140
  self,
142
141
  )
143
142
 
144
- assert len(glyphs) == self.glyphCount
145
- assert len(axisTags) == self.axisCount
143
+ assert len(glyphs) == self.glyphCount, (len(glyphs), self.glyphCount)
144
+ assert len(axisTags) == self.axisCount, (
145
+ len(axisTags),
146
+ self.axisCount,
147
+ axisTags,
148
+ )
146
149
  sharedCoords = tv.decompileSharedTuples(
147
150
  axisTags, self.sharedTupleCount, data, self.offsetToSharedTuples
148
151
  )
@@ -40,15 +40,19 @@ class table__h_m_t_x(DefaultTable.DefaultTable):
40
40
  % (self.headerTag, self.numberOfMetricsName)
41
41
  )
42
42
  numberOfMetrics = numGlyphs
43
- if len(data) < 4 * numberOfMetrics:
44
- raise ttLib.TTLibError("not enough '%s' table data" % self.tableTag)
43
+ numberOfSideBearings = numGlyphs - numberOfMetrics
44
+ tableSize = 4 * numberOfMetrics + 2 * numberOfSideBearings
45
+ if len(data) < tableSize:
46
+ raise ttLib.TTLibError(
47
+ f"not enough '{self.tableTag}' table data: "
48
+ f"expected {tableSize} bytes, got {len(data)}"
49
+ )
45
50
  # Note: advanceWidth is unsigned, but some font editors might
46
51
  # read/write as signed. We can't be sure whether it was a mistake
47
52
  # or not, so we read as unsigned but also issue a warning...
48
53
  metricsFmt = ">" + self.longMetricFormat * numberOfMetrics
49
54
  metrics = struct.unpack(metricsFmt, data[: 4 * numberOfMetrics])
50
55
  data = data[4 * numberOfMetrics :]
51
- numberOfSideBearings = numGlyphs - numberOfMetrics
52
56
  sideBearings = array.array("h", data[: 2 * numberOfSideBearings])
53
57
  data = data[2 * numberOfSideBearings :]
54
58
 
@@ -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()
@@ -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 GlyphCoordinates, dropImpliedOnCurvePoints
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:
@@ -95,6 +95,14 @@ def addFeatureVariations(font, conditionalSubstitutions, featureTag="rvrn"):
95
95
 
96
96
  addFeatureVariationsRaw(font, font["GSUB"].table, conditionsAndLookups, featureTags)
97
97
 
98
+ # Update OS/2.usMaxContext in case the font didn't have features before, but
99
+ # does now, if the OS/2 table exists. The table may be required, but
100
+ # fontTools needs to be able to deal with non-standard fonts. Since feature
101
+ # variations are always 1:1 mappings, we can set the value to at least 1
102
+ # instead of recomputing it with `otlLib.maxContextCalc.maxCtxFont()`.
103
+ if (os2 := font.get("OS/2")) is not None:
104
+ os2.usMaxContext = max(1, os2.usMaxContext)
105
+
98
106
 
99
107
  def _existingVariableFeatures(table):
100
108
  existingFeatureVarsTags = set()