fonttools 4.58.0__cp311-cp311-macosx_10_9_x86_64.whl → 4.58.2__cp311-cp311-macosx_10_9_x86_64.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/specializer.py +4 -1
  3. fontTools/cu2qu/cu2qu.c +36 -24
  4. fontTools/cu2qu/cu2qu.cpython-311-darwin.so +0 -0
  5. fontTools/feaLib/ast.py +1 -72
  6. fontTools/feaLib/builder.py +55 -45
  7. fontTools/feaLib/lexer.c +60 -43
  8. fontTools/feaLib/lexer.cpython-311-darwin.so +0 -0
  9. fontTools/merge/cmap.py +33 -1
  10. fontTools/merge/tables.py +12 -1
  11. fontTools/misc/bezierTools.c +137 -125
  12. fontTools/misc/bezierTools.cpython-311-darwin.so +0 -0
  13. fontTools/misc/loggingTools.py +1 -1
  14. fontTools/misc/symfont.py +6 -8
  15. fontTools/mtiLib/__init__.py +1 -1
  16. fontTools/otlLib/builder.py +164 -0
  17. fontTools/pens/momentsPen.c +42 -25
  18. fontTools/pens/momentsPen.cpython-311-darwin.so +0 -0
  19. fontTools/pens/t2CharStringPen.py +31 -11
  20. fontTools/qu2cu/qu2cu.c +47 -35
  21. fontTools/qu2cu/qu2cu.cpython-311-darwin.so +0 -0
  22. fontTools/subset/__init__.py +82 -2
  23. fontTools/ttLib/reorderGlyphs.py +8 -7
  24. fontTools/ufoLib/__init__.py +1 -1
  25. fontTools/varLib/__init__.py +18 -6
  26. fontTools/varLib/featureVars.py +13 -7
  27. fontTools/varLib/hvar.py +1 -1
  28. fontTools/varLib/instancer/__init__.py +14 -5
  29. fontTools/varLib/iup.c +52 -40
  30. fontTools/varLib/iup.cpython-311-darwin.so +0 -0
  31. {fonttools-4.58.0.dist-info → fonttools-4.58.2.dist-info}/METADATA +26 -1
  32. {fonttools-4.58.0.dist-info → fonttools-4.58.2.dist-info}/RECORD +38 -38
  33. {fonttools-4.58.0.dist-info → fonttools-4.58.2.dist-info}/WHEEL +1 -1
  34. {fonttools-4.58.0.data → fonttools-4.58.2.data}/data/share/man/man1/ttx.1 +0 -0
  35. {fonttools-4.58.0.dist-info → fonttools-4.58.2.dist-info}/entry_points.txt +0 -0
  36. {fonttools-4.58.0.dist-info → fonttools-4.58.2.dist-info}/licenses/LICENSE +0 -0
  37. {fonttools-4.58.0.dist-info → fonttools-4.58.2.dist-info}/licenses/LICENSE.external +0 -0
  38. {fonttools-4.58.0.dist-info → fonttools-4.58.2.dist-info}/top_level.txt +0 -0
fontTools/__init__.py CHANGED
@@ -3,6 +3,6 @@ from fontTools.misc.loggingTools import configLogger
3
3
 
4
4
  log = logging.getLogger(__name__)
5
5
 
6
- version = __version__ = "4.58.0"
6
+ version = __version__ = "4.58.2"
7
7
 
8
8
  __all__ = ["version", "log", "configLogger"]
@@ -580,7 +580,10 @@ def specializeCommands(
580
580
  for i in range(len(commands) - 1, 0, -1):
581
581
  if "rmoveto" == commands[i][0] == commands[i - 1][0]:
582
582
  v1, v2 = commands[i - 1][1], commands[i][1]
583
- commands[i - 1] = ("rmoveto", [v1[0] + v2[0], v1[1] + v2[1]])
583
+ commands[i - 1] = (
584
+ "rmoveto",
585
+ [_addArgs(v1[0], v2[0]), _addArgs(v1[1], v2[1])],
586
+ )
584
587
  del commands[i]
585
588
 
586
589
  # 2. Specialize rmoveto/rlineto/rrcurveto operators into horizontal/vertical variants.
fontTools/cu2qu/cu2qu.c CHANGED
@@ -1,4 +1,4 @@
1
- /* Generated by Cython 3.1.0 */
1
+ /* Generated by Cython 3.1.1 */
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_0"
36
- #define CYTHON_HEX_VERSION 0x030100F0
35
+ #define __PYX_ABI_VERSION "3_1_1"
36
+ #define CYTHON_HEX_VERSION 0x030101F0
37
37
  #define CYTHON_FUTURE_DIVISION 1
38
38
  /* CModulePreamble */
39
39
  #include <stddef.h>
@@ -8031,8 +8031,8 @@ static int __Pyx_InitConstants(__pyx_mstatetype *__pyx_mstate) {
8031
8031
  } __Pyx_PyCode_New_function_description;
8032
8032
  /* NewCodeObj.proto */
8033
8033
  static PyObject* __Pyx_PyCode_New(
8034
- __Pyx_PyCode_New_function_description descr,
8035
- PyObject **varnames,
8034
+ const __Pyx_PyCode_New_function_description descr,
8035
+ PyObject * const *varnames,
8036
8036
  PyObject *filename,
8037
8037
  PyObject *funcname,
8038
8038
  const char *line_table,
@@ -8044,18 +8044,18 @@ static int __Pyx_CreateCodeObjects(__pyx_mstatetype *__pyx_mstate) {
8044
8044
  PyObject* tuple_dedup_map = PyDict_New();
8045
8045
  if (unlikely(!tuple_dedup_map)) return -1;
8046
8046
  {
8047
- __Pyx_PyCode_New_function_description descr = {5, 0, 0, 19, (unsigned int)(CO_OPTIMIZED|CO_NEWLOCALS|CO_GENERATOR), 124, 2};
8048
- PyObject* varnames[] = {__pyx_mstate->__pyx_n_u_p0, __pyx_mstate->__pyx_n_u_p1, __pyx_mstate->__pyx_n_u_p2, __pyx_mstate->__pyx_n_u_p3, __pyx_mstate->__pyx_n_u_n, __pyx_mstate->__pyx_n_u_a1, __pyx_mstate->__pyx_n_u_b1, __pyx_mstate->__pyx_n_u_c1, __pyx_mstate->__pyx_n_u_d1, __pyx_mstate->__pyx_n_u_dt, __pyx_mstate->__pyx_n_u_delta_2, __pyx_mstate->__pyx_n_u_delta_3, __pyx_mstate->__pyx_n_u_i, __pyx_mstate->__pyx_n_u_a, __pyx_mstate->__pyx_n_u_b, __pyx_mstate->__pyx_n_u_c, __pyx_mstate->__pyx_n_u_d, __pyx_mstate->__pyx_n_u_t1, __pyx_mstate->__pyx_n_u_t1_2};
8047
+ const __Pyx_PyCode_New_function_description descr = {5, 0, 0, 19, (unsigned int)(CO_OPTIMIZED|CO_NEWLOCALS|CO_GENERATOR), 124, 2};
8048
+ PyObject* const varnames[] = {__pyx_mstate->__pyx_n_u_p0, __pyx_mstate->__pyx_n_u_p1, __pyx_mstate->__pyx_n_u_p2, __pyx_mstate->__pyx_n_u_p3, __pyx_mstate->__pyx_n_u_n, __pyx_mstate->__pyx_n_u_a1, __pyx_mstate->__pyx_n_u_b1, __pyx_mstate->__pyx_n_u_c1, __pyx_mstate->__pyx_n_u_d1, __pyx_mstate->__pyx_n_u_dt, __pyx_mstate->__pyx_n_u_delta_2, __pyx_mstate->__pyx_n_u_delta_3, __pyx_mstate->__pyx_n_u_i, __pyx_mstate->__pyx_n_u_a, __pyx_mstate->__pyx_n_u_b, __pyx_mstate->__pyx_n_u_c, __pyx_mstate->__pyx_n_u_d, __pyx_mstate->__pyx_n_u_t1, __pyx_mstate->__pyx_n_u_t1_2};
8049
8049
  __pyx_mstate_global->__pyx_codeobj_tab[0] = __Pyx_PyCode_New(descr, varnames, __pyx_mstate->__pyx_kp_u_Lib_fontTools_cu2qu_cu2qu_py, __pyx_mstate->__pyx_n_u_split_cubic_into_n_gen, __pyx_k__3, tuple_dedup_map); if (unlikely(!__pyx_mstate_global->__pyx_codeobj_tab[0])) goto bad;
8050
8050
  }
8051
8051
  {
8052
- __Pyx_PyCode_New_function_description descr = {3, 0, 0, 7, (unsigned int)(CO_OPTIMIZED|CO_NEWLOCALS), 436, 97};
8053
- PyObject* varnames[] = {__pyx_mstate->__pyx_n_u_curve, __pyx_mstate->__pyx_n_u_max_err, __pyx_mstate->__pyx_n_u_all_quadratic, __pyx_mstate->__pyx_n_u_n, __pyx_mstate->__pyx_n_u_spline, __pyx_mstate->__pyx_n_u_p, __pyx_mstate->__pyx_n_u_s};
8052
+ const __Pyx_PyCode_New_function_description descr = {3, 0, 0, 7, (unsigned int)(CO_OPTIMIZED|CO_NEWLOCALS), 436, 97};
8053
+ PyObject* const varnames[] = {__pyx_mstate->__pyx_n_u_curve, __pyx_mstate->__pyx_n_u_max_err, __pyx_mstate->__pyx_n_u_all_quadratic, __pyx_mstate->__pyx_n_u_n, __pyx_mstate->__pyx_n_u_spline, __pyx_mstate->__pyx_n_u_p, __pyx_mstate->__pyx_n_u_s};
8054
8054
  __pyx_mstate_global->__pyx_codeobj_tab[1] = __Pyx_PyCode_New(descr, varnames, __pyx_mstate->__pyx_kp_u_Lib_fontTools_cu2qu_cu2qu_py, __pyx_mstate->__pyx_n_u_curve_to_quadratic, __pyx_k_AWBc_U_U_3fBa_AWCy_7_2QgQgT_a_Q, tuple_dedup_map); if (unlikely(!__pyx_mstate_global->__pyx_codeobj_tab[1])) goto bad;
8055
8055
  }
8056
8056
  {
8057
- __Pyx_PyCode_New_function_description descr = {3, 0, 0, 13, (unsigned int)(CO_OPTIMIZED|CO_NEWLOCALS), 471, 211};
8058
- PyObject* varnames[] = {__pyx_mstate->__pyx_n_u_curves, __pyx_mstate->__pyx_n_u_max_errors, __pyx_mstate->__pyx_n_u_all_quadratic, __pyx_mstate->__pyx_n_u_l, __pyx_mstate->__pyx_n_u_last_i, __pyx_mstate->__pyx_n_u_i, __pyx_mstate->__pyx_n_u_splines, __pyx_mstate->__pyx_n_u_n, __pyx_mstate->__pyx_n_u_spline, __pyx_mstate->__pyx_n_u_curve, __pyx_mstate->__pyx_n_u_p, __pyx_mstate->__pyx_n_u_spline, __pyx_mstate->__pyx_n_u_s};
8057
+ const __Pyx_PyCode_New_function_description descr = {3, 0, 0, 13, (unsigned int)(CO_OPTIMIZED|CO_NEWLOCALS), 471, 211};
8058
+ PyObject* const varnames[] = {__pyx_mstate->__pyx_n_u_curves, __pyx_mstate->__pyx_n_u_max_errors, __pyx_mstate->__pyx_n_u_all_quadratic, __pyx_mstate->__pyx_n_u_l, __pyx_mstate->__pyx_n_u_last_i, __pyx_mstate->__pyx_n_u_i, __pyx_mstate->__pyx_n_u_splines, __pyx_mstate->__pyx_n_u_n, __pyx_mstate->__pyx_n_u_spline, __pyx_mstate->__pyx_n_u_curve, __pyx_mstate->__pyx_n_u_p, __pyx_mstate->__pyx_n_u_spline, __pyx_mstate->__pyx_n_u_s};
8059
8059
  __pyx_mstate_global->__pyx_codeobj_tab[2] = __Pyx_PyCode_New(descr, varnames, __pyx_mstate->__pyx_kp_u_Lib_fontTools_cu2qu_cu2qu_py, __pyx_mstate->__pyx_n_u_curves_to_quadratic, __pyx_k_J_Qawb_4uG4y_3a_3c_1A_avRq_T_AV, tuple_dedup_map); if (unlikely(!__pyx_mstate_global->__pyx_codeobj_tab[2])) goto bad;
8060
8060
  }
8061
8061
  Py_DECREF(tuple_dedup_map);
@@ -9034,6 +9034,11 @@ static PyObject *__Pyx_SelflessCall(PyObject *method, PyObject *args, PyObject *
9034
9034
  Py_DECREF(selfless_args);
9035
9035
  return result;
9036
9036
  }
9037
+ #elif CYTHON_COMPILING_IN_PYPY && PY_VERSION_HEX < 0x03090000
9038
+ static PyObject *__Pyx_SelflessCall(PyObject *method, PyObject **args, Py_ssize_t nargs, PyObject *kwnames) {
9039
+ return _PyObject_Vectorcall
9040
+ (method, args ? args+1 : NULL, nargs ? nargs-1 : 0, kwnames);
9041
+ }
9037
9042
  #else
9038
9043
  static PyObject *__Pyx_SelflessCall(PyObject *method, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) {
9039
9044
  return
@@ -9357,7 +9362,9 @@ static int __Pyx_ParseKeywordDict(
9357
9362
  PyObject** const *name;
9358
9363
  PyObject** const *first_kw_arg = argnames + num_pos_args;
9359
9364
  Py_ssize_t extracted = 0;
9365
+ #if !CYTHON_COMPILING_IN_PYPY || defined(PyArg_ValidateKeywordArguments)
9360
9366
  if (unlikely(!PyArg_ValidateKeywordArguments(kwds))) return -1;
9367
+ #endif
9361
9368
  name = first_kw_arg;
9362
9369
  while (*name && num_kwargs > extracted) {
9363
9370
  PyObject * key = **name;
@@ -9405,7 +9412,9 @@ static int __Pyx_ParseKeywordDictToDict(
9405
9412
  PyObject** const *name;
9406
9413
  PyObject** const *first_kw_arg = argnames + num_pos_args;
9407
9414
  Py_ssize_t len;
9415
+ #if !CYTHON_COMPILING_IN_PYPY || defined(PyArg_ValidateKeywordArguments)
9408
9416
  if (unlikely(!PyArg_ValidateKeywordArguments(kwds))) return -1;
9417
+ #endif
9409
9418
  if (PyDict_Update(kwds2, kwds) < 0) goto bad;
9410
9419
  name = first_kw_arg;
9411
9420
  while (*name) {
@@ -10530,7 +10539,8 @@ static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list, int level) {
10530
10539
  if (unlikely(!empty_dict))
10531
10540
  goto bad;
10532
10541
  if (level == -1) {
10533
- if (strchr(__Pyx_MODULE_NAME, '.') != (0)) {
10542
+ const char* package_sep = strchr(__Pyx_MODULE_NAME, '.');
10543
+ if (package_sep != (0)) {
10534
10544
  module = PyImport_ImportModuleLevelObject(
10535
10545
  name, __pyx_mstate_global->__pyx_d, empty_dict, from_list, 1);
10536
10546
  if (unlikely(!module)) {
@@ -12900,7 +12910,7 @@ static CYTHON_INLINE PyObject* __Pyx_PyLong_From_long(long value) {
12900
12910
  return PyLong_FromLong((long) value);
12901
12911
  } else if (sizeof(long) <= sizeof(unsigned long)) {
12902
12912
  return PyLong_FromUnsignedLong((unsigned long) value);
12903
- #ifdef HAVE_LONG_LONG
12913
+ #if defined(HAVE_LONG_LONG) && !CYTHON_COMPILING_IN_PYPY
12904
12914
  } else if (sizeof(long) <= sizeof(unsigned PY_LONG_LONG)) {
12905
12915
  return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG) value);
12906
12916
  #endif
@@ -12971,7 +12981,7 @@ static CYTHON_INLINE PyObject* __Pyx_PyLong_From_int(int value) {
12971
12981
  return PyLong_FromLong((long) value);
12972
12982
  } else if (sizeof(int) <= sizeof(unsigned long)) {
12973
12983
  return PyLong_FromUnsignedLong((unsigned long) value);
12974
- #ifdef HAVE_LONG_LONG
12984
+ #if defined(HAVE_LONG_LONG) && !CYTHON_COMPILING_IN_PYPY
12975
12985
  } else if (sizeof(int) <= sizeof(unsigned PY_LONG_LONG)) {
12976
12986
  return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG) value);
12977
12987
  #endif
@@ -13511,8 +13521,8 @@ static void __Pyx__ReturnWithStopIteration(PyObject* value, int async) {
13511
13521
  PyObject *exc;
13512
13522
  PyObject *exc_type = async ? PyExc_StopAsyncIteration : PyExc_StopIteration;
13513
13523
  #if CYTHON_COMPILING_IN_CPYTHON
13514
- if ((PY_VERSION_HEX >= 0x030C00A6) || unlikely(PyTuple_Check(value) || PyExceptionInstance_Check(value))) {
13515
- if ((PY_VERSION_HEX >= 0x030e00A1)) {
13524
+ if ((PY_VERSION_HEX >= (0x030C00A6)) || unlikely(PyTuple_Check(value) || PyExceptionInstance_Check(value))) {
13525
+ if (PY_VERSION_HEX >= (0x030e00A1)) {
13516
13526
  exc = __Pyx_PyObject_CallOneArg(exc_type, value);
13517
13527
  } else {
13518
13528
  PyObject *args_tuple = PyTuple_New(1);
@@ -13877,17 +13887,19 @@ __Pyx_Coroutine_FinishDelegation(__pyx_CoroutineObject *gen, PyObject** retval)
13877
13887
  static __Pyx_PySendResult
13878
13888
  __Pyx_Coroutine_SendToDelegate(__pyx_CoroutineObject *gen, __Pyx_pyiter_sendfunc gen_am_send, PyObject *value, PyObject **retval) {
13879
13889
  PyObject *ret = NULL;
13880
- __Pyx_PySendResult result;
13890
+ __Pyx_PySendResult delegate_result, result;
13881
13891
  assert(__Pyx_Coroutine_get_is_running(gen));
13882
- result = gen_am_send(gen->yieldfrom, value, &ret);
13883
- if (result == PYGEN_NEXT) {
13892
+ delegate_result = gen_am_send(gen->yieldfrom, value, &ret);
13893
+ if (delegate_result == PYGEN_NEXT) {
13884
13894
  assert (ret != NULL);
13885
13895
  *retval = ret;
13886
13896
  return PYGEN_NEXT;
13887
13897
  }
13888
- assert (result != PYGEN_ERROR || ret == NULL);
13898
+ assert (delegate_result != PYGEN_ERROR || ret == NULL);
13889
13899
  __Pyx_Coroutine_Undelegate(gen);
13890
- return __Pyx_Coroutine_SendEx(gen, ret, retval, 0);
13900
+ result = __Pyx_Coroutine_SendEx(gen, ret, retval, 0);
13901
+ Py_XDECREF(ret);
13902
+ return result;
13891
13903
  }
13892
13904
  #endif
13893
13905
  static PyObject *__Pyx_Coroutine_Send(PyObject *self, PyObject *value) {
@@ -14700,9 +14712,9 @@ static int __Pyx_check_binary_version(unsigned long ct_version, unsigned long rt
14700
14712
  PyCode_New(a, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)
14701
14713
  #endif
14702
14714
  static PyObject* __Pyx_PyCode_New(
14703
- __Pyx_PyCode_New_function_description descr,
14704
- PyObject **varnames,
14705
- PyObject* filename,
14715
+ const __Pyx_PyCode_New_function_description descr,
14716
+ PyObject * const *varnames,
14717
+ PyObject *filename,
14706
14718
  PyObject *funcname,
14707
14719
  const char *line_table,
14708
14720
  PyObject *tuple_dedup_map
Binary file
fontTools/feaLib/ast.py CHANGED
@@ -337,76 +337,6 @@ class AnonymousBlock(Statement):
337
337
  return res
338
338
 
339
339
 
340
- def _upgrade_mixed_subst_statements(statements):
341
- # https://github.com/fonttools/fonttools/issues/612
342
- # A multiple substitution may have a single destination, in which case
343
- # it will look just like a single substitution. So if there are both
344
- # multiple and single substitutions, upgrade all the single ones to
345
- # multiple substitutions. Similarly, a ligature substitution may have a
346
- # single source glyph, so if there are both ligature and single
347
- # substitutions, upgrade all the single ones to ligature substitutions.
348
-
349
- has_single = False
350
- has_multiple = False
351
- has_ligature = False
352
- for s in statements:
353
- if isinstance(s, SingleSubstStatement):
354
- has_single = not any([s.prefix, s.suffix, s.forceChain])
355
- elif isinstance(s, MultipleSubstStatement):
356
- has_multiple = not any([s.prefix, s.suffix, s.forceChain])
357
- elif isinstance(s, LigatureSubstStatement):
358
- has_ligature = not any([s.prefix, s.suffix, s.forceChain])
359
-
360
- to_multiple = False
361
- to_ligature = False
362
-
363
- # If we have mixed single and multiple substitutions,
364
- # upgrade all single substitutions to multiple substitutions.
365
- if has_single and has_multiple and not has_ligature:
366
- to_multiple = True
367
-
368
- # If we have mixed single and ligature substitutions,
369
- # upgrade all single substitutions to ligature substitutions.
370
- elif has_single and has_ligature and not has_multiple:
371
- to_ligature = True
372
-
373
- if to_multiple or to_ligature:
374
- ret = []
375
- for s in statements:
376
- if isinstance(s, SingleSubstStatement):
377
- glyphs = s.glyphs[0].glyphSet()
378
- replacements = s.replacements[0].glyphSet()
379
- if len(replacements) == 1:
380
- replacements *= len(glyphs)
381
- for glyph, replacement in zip(glyphs, replacements):
382
- if to_multiple:
383
- ret.append(
384
- MultipleSubstStatement(
385
- s.prefix,
386
- glyph,
387
- s.suffix,
388
- [replacement],
389
- s.forceChain,
390
- location=s.location,
391
- )
392
- )
393
- elif to_ligature:
394
- ret.append(
395
- LigatureSubstStatement(
396
- s.prefix,
397
- [GlyphName(glyph)],
398
- s.suffix,
399
- replacement,
400
- s.forceChain,
401
- location=s.location,
402
- )
403
- )
404
- else:
405
- ret.append(s)
406
- return ret
407
- return statements
408
-
409
-
410
340
  class Block(Statement):
411
341
  """A block of statements: feature, lookup, etc."""
412
342
 
@@ -418,8 +348,7 @@ class Block(Statement):
418
348
  """When handed a 'builder' object of comparable interface to
419
349
  :class:`fontTools.feaLib.builder`, walks the statements in this
420
350
  block, calling the builder callbacks."""
421
- statements = _upgrade_mixed_subst_statements(self.statements)
422
- for s in statements:
351
+ for s in self.statements:
423
352
  s.build(builder)
424
353
 
425
354
  def asFea(self, indent=""):
@@ -29,6 +29,7 @@ from fontTools.otlLib.builder import (
29
29
  PairPosBuilder,
30
30
  SinglePosBuilder,
31
31
  ChainContextualRule,
32
+ AnySubstBuilder,
32
33
  )
33
34
  from fontTools.otlLib.error import OpenTypeLibError
34
35
  from fontTools.varLib.varStore import OnlineVarStoreBuilder
@@ -866,13 +867,22 @@ class Builder(object):
866
867
  for lookup in self.lookups_:
867
868
  if lookup.table != tag:
868
869
  continue
869
- lookup.lookup_index = len(lookups)
870
- self.lookup_locations[tag][str(lookup.lookup_index)] = LookupDebugInfo(
871
- location=str(lookup.location),
872
- name=self.get_lookup_name_(lookup),
873
- feature=None,
874
- )
875
- lookups.append(lookup)
870
+ name = self.get_lookup_name_(lookup)
871
+ resolved = lookup.promote_lookup_type(is_named_lookup=name is not None)
872
+ if resolved is None:
873
+ raise FeatureLibError(
874
+ "Within a named lookup block, all rules must be of "
875
+ "the same lookup type and flag",
876
+ lookup.location,
877
+ )
878
+ for l in resolved:
879
+ lookup.lookup_index = len(lookups)
880
+ self.lookup_locations[tag][str(lookup.lookup_index)] = LookupDebugInfo(
881
+ location=str(lookup.location),
882
+ name=name,
883
+ feature=None,
884
+ )
885
+ lookups.append(l)
876
886
  otLookups = []
877
887
  for l in lookups:
878
888
  try:
@@ -1294,6 +1304,24 @@ class Builder(object):
1294
1304
 
1295
1305
  # GSUB rules
1296
1306
 
1307
+ def add_any_subst_(self, location, mapping):
1308
+ lookup = self.get_lookup_(location, AnySubstBuilder)
1309
+ for key, value in mapping.items():
1310
+ if key in lookup.mapping:
1311
+ if value == lookup.mapping[key]:
1312
+ log.info(
1313
+ 'Removing duplicate substitution from "%s" to "%s" at %s',
1314
+ ", ".join(key),
1315
+ ", ".join(value),
1316
+ location,
1317
+ )
1318
+ else:
1319
+ raise FeatureLibError(
1320
+ 'Already defined substitution for "%s"' % ", ".join(key),
1321
+ location,
1322
+ )
1323
+ lookup.mapping[key] = value
1324
+
1297
1325
  # GSUB 1
1298
1326
  def add_single_subst(self, location, prefix, suffix, mapping, forceChain):
1299
1327
  if self.cur_feature_name_ == "aalt":
@@ -1305,24 +1333,11 @@ class Builder(object):
1305
1333
  if prefix or suffix or forceChain:
1306
1334
  self.add_single_subst_chained_(location, prefix, suffix, mapping)
1307
1335
  return
1308
- lookup = self.get_lookup_(location, SingleSubstBuilder)
1309
- for from_glyph, to_glyph in mapping.items():
1310
- if from_glyph in lookup.mapping:
1311
- if to_glyph == lookup.mapping[from_glyph]:
1312
- log.info(
1313
- "Removing duplicate single substitution from glyph"
1314
- ' "%s" to "%s" at %s',
1315
- from_glyph,
1316
- to_glyph,
1317
- location,
1318
- )
1319
- else:
1320
- raise FeatureLibError(
1321
- 'Already defined rule for replacing glyph "%s" by "%s"'
1322
- % (from_glyph, lookup.mapping[from_glyph]),
1323
- location,
1324
- )
1325
- lookup.mapping[from_glyph] = to_glyph
1336
+
1337
+ self.add_any_subst_(
1338
+ location,
1339
+ {(key,): (value,) for key, value in mapping.items()},
1340
+ )
1326
1341
 
1327
1342
  # GSUB 2
1328
1343
  def add_multiple_subst(
@@ -1331,21 +1346,10 @@ class Builder(object):
1331
1346
  if prefix or suffix or forceChain:
1332
1347
  self.add_multi_subst_chained_(location, prefix, glyph, suffix, replacements)
1333
1348
  return
1334
- lookup = self.get_lookup_(location, MultipleSubstBuilder)
1335
- if glyph in lookup.mapping:
1336
- if replacements == lookup.mapping[glyph]:
1337
- log.info(
1338
- "Removing duplicate multiple substitution from glyph"
1339
- ' "%s" to %s%s',
1340
- glyph,
1341
- replacements,
1342
- f" at {location}" if location else "",
1343
- )
1344
- else:
1345
- raise FeatureLibError(
1346
- 'Already defined substitution for glyph "%s"' % glyph, location
1347
- )
1348
- lookup.mapping[glyph] = replacements
1349
+ self.add_any_subst_(
1350
+ location,
1351
+ {(glyph,): tuple(replacements)},
1352
+ )
1349
1353
 
1350
1354
  # GSUB 3
1351
1355
  def add_alternate_subst(self, location, prefix, glyph, suffix, replacement):
@@ -1375,9 +1379,6 @@ class Builder(object):
1375
1379
  location, prefix, glyphs, suffix, replacement
1376
1380
  )
1377
1381
  return
1378
- else:
1379
- lookup = self.get_lookup_(location, LigatureSubstBuilder)
1380
-
1381
1382
  if not all(glyphs):
1382
1383
  raise FeatureLibError("Empty glyph class in substitution", location)
1383
1384
 
@@ -1386,8 +1387,10 @@ class Builder(object):
1386
1387
  # substitutions to be specified on target sequences that contain
1387
1388
  # glyph classes, the implementation software will enumerate
1388
1389
  # all specific glyph sequences if glyph classes are detected"
1389
- for g in itertools.product(*glyphs):
1390
- lookup.ligatures[g] = replacement
1390
+ self.add_any_subst_(
1391
+ location,
1392
+ {g: (replacement,) for g in itertools.product(*glyphs)},
1393
+ )
1391
1394
 
1392
1395
  # GSUB 5/6
1393
1396
  def add_chain_context_subst(self, location, prefix, glyphs, suffix, lookups):
@@ -1445,6 +1448,13 @@ class Builder(object):
1445
1448
  sub = self.get_chained_lookup_(location, LigatureSubstBuilder)
1446
1449
 
1447
1450
  for g in itertools.product(*glyphs):
1451
+ existing = sub.ligatures.get(g, replacement)
1452
+ if existing != replacement:
1453
+ raise FeatureLibError(
1454
+ f"Conflicting ligature sub rules: '{g}' maps to '{existing}' and '{replacement}'",
1455
+ location,
1456
+ )
1457
+
1448
1458
  sub.ligatures[g] = replacement
1449
1459
 
1450
1460
  chain.rules.append(ChainContextualRule(prefix, glyphs, suffix, [sub]))