fonttools 4.55.4__cp313-cp313-musllinux_1_2_aarch64.whl → 4.61.1__cp313-cp313-musllinux_1_2_aarch64.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.
Files changed (140) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/annotations.py +30 -0
  3. fontTools/cffLib/CFF2ToCFF.py +65 -10
  4. fontTools/cffLib/__init__.py +61 -26
  5. fontTools/cffLib/specializer.py +4 -1
  6. fontTools/cffLib/transforms.py +11 -6
  7. fontTools/config/__init__.py +15 -0
  8. fontTools/cu2qu/cu2qu.c +6567 -5579
  9. fontTools/cu2qu/cu2qu.cpython-313-aarch64-linux-musl.so +0 -0
  10. fontTools/cu2qu/cu2qu.py +36 -4
  11. fontTools/cu2qu/ufo.py +14 -0
  12. fontTools/designspaceLib/__init__.py +8 -3
  13. fontTools/designspaceLib/statNames.py +14 -7
  14. fontTools/feaLib/ast.py +24 -15
  15. fontTools/feaLib/builder.py +139 -66
  16. fontTools/feaLib/error.py +1 -1
  17. fontTools/feaLib/lexer.c +7038 -7995
  18. fontTools/feaLib/lexer.cpython-313-aarch64-linux-musl.so +0 -0
  19. fontTools/feaLib/parser.py +75 -40
  20. fontTools/feaLib/variableScalar.py +6 -1
  21. fontTools/fontBuilder.py +50 -44
  22. fontTools/merge/__init__.py +1 -1
  23. fontTools/merge/cmap.py +33 -1
  24. fontTools/merge/tables.py +12 -1
  25. fontTools/misc/bezierTools.c +14913 -17013
  26. fontTools/misc/bezierTools.cpython-313-aarch64-linux-musl.so +0 -0
  27. fontTools/misc/bezierTools.py +4 -1
  28. fontTools/misc/configTools.py +3 -1
  29. fontTools/misc/enumTools.py +23 -0
  30. fontTools/misc/etree.py +4 -27
  31. fontTools/misc/filesystem/__init__.py +68 -0
  32. fontTools/misc/filesystem/_base.py +134 -0
  33. fontTools/misc/filesystem/_copy.py +45 -0
  34. fontTools/misc/filesystem/_errors.py +54 -0
  35. fontTools/misc/filesystem/_info.py +75 -0
  36. fontTools/misc/filesystem/_osfs.py +164 -0
  37. fontTools/misc/filesystem/_path.py +67 -0
  38. fontTools/misc/filesystem/_subfs.py +92 -0
  39. fontTools/misc/filesystem/_tempfs.py +34 -0
  40. fontTools/misc/filesystem/_tools.py +34 -0
  41. fontTools/misc/filesystem/_walk.py +55 -0
  42. fontTools/misc/filesystem/_zipfs.py +204 -0
  43. fontTools/misc/fixedTools.py +1 -1
  44. fontTools/misc/loggingTools.py +1 -1
  45. fontTools/misc/psCharStrings.py +17 -2
  46. fontTools/misc/sstruct.py +2 -6
  47. fontTools/misc/symfont.py +6 -8
  48. fontTools/misc/testTools.py +5 -1
  49. fontTools/misc/textTools.py +4 -2
  50. fontTools/misc/visitor.py +32 -16
  51. fontTools/misc/xmlWriter.py +44 -8
  52. fontTools/mtiLib/__init__.py +1 -3
  53. fontTools/otlLib/builder.py +402 -155
  54. fontTools/otlLib/optimize/gpos.py +49 -63
  55. fontTools/pens/filterPen.py +218 -26
  56. fontTools/pens/momentsPen.c +5514 -5584
  57. fontTools/pens/momentsPen.cpython-313-aarch64-linux-musl.so +0 -0
  58. fontTools/pens/pointPen.py +61 -18
  59. fontTools/pens/roundingPen.py +2 -2
  60. fontTools/pens/t2CharStringPen.py +31 -11
  61. fontTools/qu2cu/qu2cu.c +6581 -6168
  62. fontTools/qu2cu/qu2cu.cpython-313-aarch64-linux-musl.so +0 -0
  63. fontTools/subset/__init__.py +283 -25
  64. fontTools/subset/svg.py +2 -3
  65. fontTools/ttLib/__init__.py +4 -0
  66. fontTools/ttLib/__main__.py +47 -8
  67. fontTools/ttLib/removeOverlaps.py +7 -5
  68. fontTools/ttLib/reorderGlyphs.py +8 -7
  69. fontTools/ttLib/sfnt.py +11 -9
  70. fontTools/ttLib/tables/D__e_b_g.py +20 -2
  71. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  72. fontTools/ttLib/tables/S__i_l_f.py +2 -2
  73. fontTools/ttLib/tables/T_S_I__0.py +14 -3
  74. fontTools/ttLib/tables/T_S_I__1.py +2 -5
  75. fontTools/ttLib/tables/T_S_I__5.py +18 -7
  76. fontTools/ttLib/tables/__init__.py +1 -0
  77. fontTools/ttLib/tables/_a_v_a_r.py +12 -3
  78. fontTools/ttLib/tables/_c_m_a_p.py +20 -7
  79. fontTools/ttLib/tables/_c_v_t.py +3 -2
  80. fontTools/ttLib/tables/_f_p_g_m.py +3 -1
  81. fontTools/ttLib/tables/_g_l_y_f.py +45 -21
  82. fontTools/ttLib/tables/_g_v_a_r.py +67 -19
  83. fontTools/ttLib/tables/_h_d_m_x.py +4 -4
  84. fontTools/ttLib/tables/_h_m_t_x.py +7 -3
  85. fontTools/ttLib/tables/_l_o_c_a.py +2 -2
  86. fontTools/ttLib/tables/_n_a_m_e.py +11 -6
  87. fontTools/ttLib/tables/_p_o_s_t.py +9 -7
  88. fontTools/ttLib/tables/otBase.py +5 -12
  89. fontTools/ttLib/tables/otConverters.py +5 -2
  90. fontTools/ttLib/tables/otData.py +1 -1
  91. fontTools/ttLib/tables/otTables.py +33 -30
  92. fontTools/ttLib/tables/otTraverse.py +2 -1
  93. fontTools/ttLib/tables/sbixStrike.py +3 -3
  94. fontTools/ttLib/ttFont.py +666 -120
  95. fontTools/ttLib/ttGlyphSet.py +0 -10
  96. fontTools/ttLib/woff2.py +10 -13
  97. fontTools/ttx.py +13 -1
  98. fontTools/ufoLib/__init__.py +300 -202
  99. fontTools/ufoLib/converters.py +103 -30
  100. fontTools/ufoLib/errors.py +8 -0
  101. fontTools/ufoLib/etree.py +1 -1
  102. fontTools/ufoLib/filenames.py +171 -106
  103. fontTools/ufoLib/glifLib.py +303 -205
  104. fontTools/ufoLib/kerning.py +98 -48
  105. fontTools/ufoLib/utils.py +46 -15
  106. fontTools/ufoLib/validators.py +121 -99
  107. fontTools/unicodedata/Blocks.py +35 -20
  108. fontTools/unicodedata/Mirrored.py +446 -0
  109. fontTools/unicodedata/ScriptExtensions.py +63 -37
  110. fontTools/unicodedata/Scripts.py +173 -152
  111. fontTools/unicodedata/__init__.py +10 -2
  112. fontTools/varLib/__init__.py +198 -109
  113. fontTools/varLib/avar/__init__.py +0 -0
  114. fontTools/varLib/avar/__main__.py +72 -0
  115. fontTools/varLib/avar/build.py +79 -0
  116. fontTools/varLib/avar/map.py +108 -0
  117. fontTools/varLib/avar/plan.py +1004 -0
  118. fontTools/varLib/{avar.py → avar/unbuild.py} +70 -59
  119. fontTools/varLib/avarPlanner.py +3 -999
  120. fontTools/varLib/featureVars.py +21 -7
  121. fontTools/varLib/hvar.py +113 -0
  122. fontTools/varLib/instancer/__init__.py +180 -65
  123. fontTools/varLib/interpolatableHelpers.py +3 -0
  124. fontTools/varLib/iup.c +7564 -6903
  125. fontTools/varLib/iup.cpython-313-aarch64-linux-musl.so +0 -0
  126. fontTools/varLib/models.py +17 -2
  127. fontTools/varLib/mutator.py +11 -0
  128. fontTools/varLib/varStore.py +10 -38
  129. fontTools/voltLib/__main__.py +206 -0
  130. fontTools/voltLib/ast.py +4 -0
  131. fontTools/voltLib/parser.py +16 -8
  132. fontTools/voltLib/voltToFea.py +347 -166
  133. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/METADATA +269 -1410
  134. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/RECORD +318 -294
  135. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/WHEEL +1 -1
  136. fonttools-4.61.1.dist-info/licenses/LICENSE.external +388 -0
  137. {fonttools-4.55.4.data → fonttools-4.61.1.data}/data/share/man/man1/ttx.1 +0 -0
  138. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/entry_points.txt +0 -0
  139. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info/licenses}/LICENSE +0 -0
  140. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  from collections import namedtuple, OrderedDict
2
4
  import itertools
3
- import os
5
+ from typing import Dict, Union
4
6
  from fontTools.misc.fixedTools import fixedToFloat
5
7
  from fontTools.misc.roundTools import otRound
6
8
  from fontTools import ttLib
@@ -10,15 +12,15 @@ from fontTools.ttLib.tables.otBase import (
10
12
  valueRecordFormatDict,
11
13
  OTLOffsetOverflowError,
12
14
  OTTableWriter,
13
- CountReference,
14
15
  )
15
- from fontTools.ttLib.tables import otBase
16
+ from fontTools.ttLib.ttFont import TTFont
16
17
  from fontTools.feaLib.ast import STATNameStatement
17
18
  from fontTools.otlLib.optimize.gpos import (
18
19
  _compression_level_from_env,
19
20
  compact_lookup,
20
21
  )
21
22
  from fontTools.otlLib.error import OpenTypeLibError
23
+ from fontTools.misc.loggingTools import deprecateFunction
22
24
  from functools import reduce
23
25
  import logging
24
26
  import copy
@@ -51,18 +53,21 @@ def buildCoverage(glyphs, glyphMap):
51
53
  ``font.getReverseGlyphMap()``.
52
54
 
53
55
  Returns:
54
- An ``otTables.Coverage`` object or ``None`` if there are no glyphs
55
- supplied.
56
+ An ``otTables.Coverage`` object (empty if no glyphs supplied).
56
57
  """
57
-
58
- if not glyphs:
59
- return None
58
+ # Per the OpenType spec: "For cases in which subtable offset fields are not
59
+ # documented as permitting NULL values, font compilers must include a subtable
60
+ # of the indicated format, even if it is a header stub without further data
61
+ # (for example, a coverage table with no glyph IDs)."
62
+ # https://github.com/fonttools/fonttools/issues/4003
60
63
  self = ot.Coverage()
61
- try:
62
- self.glyphs = sorted(set(glyphs), key=glyphMap.__getitem__)
63
- except KeyError as e:
64
- raise ValueError(f"Could not find glyph {e} in font") from e
65
-
64
+ if glyphs:
65
+ try:
66
+ self.glyphs = sorted(set(glyphs), key=glyphMap.__getitem__)
67
+ except KeyError as e:
68
+ raise ValueError(f"Could not find glyph {e} in font") from e
69
+ else:
70
+ self.glyphs = []
66
71
  return self
67
72
 
68
73
 
@@ -73,7 +78,7 @@ LOOKUP_FLAG_IGNORE_MARKS = 0x0008
73
78
  LOOKUP_FLAG_USE_MARK_FILTERING_SET = 0x0010
74
79
 
75
80
 
76
- def buildLookup(subtables, flags=0, markFilterSet=None):
81
+ def buildLookup(subtables, flags=0, markFilterSet=None, table=None, extension=False):
77
82
  """Turns a collection of rules into a lookup.
78
83
 
79
84
  A Lookup (as defined in the `OpenType Spec <https://docs.microsoft.com/en-gb/typography/opentype/spec/chapter2#lookupTbl>`__)
@@ -98,6 +103,8 @@ def buildLookup(subtables, flags=0, markFilterSet=None):
98
103
  lookup. If a mark filtering set is provided,
99
104
  `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
100
105
  flags.
106
+ table (str): The name of the table this lookup belongs to, e.g. "GPOS" or "GSUB".
107
+ extension (bool): ``True`` if this is an extension lookup, ``False`` otherwise.
101
108
 
102
109
  Returns:
103
110
  An ``otTables.Lookup`` object or ``None`` if there are no subtables
@@ -113,8 +120,21 @@ def buildLookup(subtables, flags=0, markFilterSet=None):
113
120
  ), "all subtables must have the same LookupType; got %s" % repr(
114
121
  [t.LookupType for t in subtables]
115
122
  )
123
+
124
+ if extension:
125
+ assert table in ("GPOS", "GSUB")
126
+ lookupType = 7 if table == "GSUB" else 9
127
+ extSubTableClass = ot.lookupTypes[table][lookupType]
128
+ for i, st in enumerate(subtables):
129
+ subtables[i] = extSubTableClass()
130
+ subtables[i].Format = 1
131
+ subtables[i].ExtSubTable = st
132
+ subtables[i].ExtensionLookupType = st.LookupType
133
+ else:
134
+ lookupType = subtables[0].LookupType
135
+
116
136
  self = ot.Lookup()
117
- self.LookupType = subtables[0].LookupType
137
+ self.LookupType = lookupType
118
138
  self.LookupFlag = flags
119
139
  self.SubTable = subtables
120
140
  self.SubTableCount = len(self.SubTable)
@@ -133,7 +153,7 @@ def buildLookup(subtables, flags=0, markFilterSet=None):
133
153
  class LookupBuilder(object):
134
154
  SUBTABLE_BREAK_ = "SUBTABLE_BREAK"
135
155
 
136
- def __init__(self, font, location, table, lookup_type):
156
+ def __init__(self, font, location, table, lookup_type, extension=False):
137
157
  self.font = font
138
158
  self.glyphMap = font.getReverseGlyphMap()
139
159
  self.location = location
@@ -141,6 +161,7 @@ class LookupBuilder(object):
141
161
  self.lookupflag = 0
142
162
  self.markFilterSet = None
143
163
  self.lookup_index = None # assigned when making final tables
164
+ self.extension = extension
144
165
  assert table in ("GPOS", "GSUB")
145
166
 
146
167
  def equals(self, other):
@@ -149,8 +170,12 @@ class LookupBuilder(object):
149
170
  and self.table == other.table
150
171
  and self.lookupflag == other.lookupflag
151
172
  and self.markFilterSet == other.markFilterSet
173
+ and self.extension == other.extension
152
174
  )
153
175
 
176
+ def promote_lookup_type(self, is_named_lookup):
177
+ return [self]
178
+
154
179
  def inferGlyphClasses(self):
155
180
  """Infers glyph glasses for the GDEF table, such as {"cedilla":3}."""
156
181
  return {}
@@ -160,7 +185,13 @@ class LookupBuilder(object):
160
185
  return {}
161
186
 
162
187
  def buildLookup_(self, subtables):
163
- return buildLookup(subtables, self.lookupflag, self.markFilterSet)
188
+ return buildLookup(
189
+ subtables,
190
+ self.lookupflag,
191
+ self.markFilterSet,
192
+ self.table,
193
+ self.extension,
194
+ )
164
195
 
165
196
  def buildMarkClasses_(self, marks):
166
197
  """{"cedilla": ("BOTTOM", ast.Anchor), ...} --> {"BOTTOM":0, "TOP":1}
@@ -228,6 +259,10 @@ class LookupBuilder(object):
228
259
  )
229
260
  )
230
261
 
262
+ def can_add_mapping(self, _mapping) -> bool:
263
+ # used by AnySubstBuilder, below
264
+ return True
265
+
231
266
 
232
267
  class AlternateSubstBuilder(LookupBuilder):
233
268
  """Builds an Alternate Substitution (GSUB3) lookup.
@@ -858,6 +893,14 @@ class LigatureSubstBuilder(LookupBuilder):
858
893
  )
859
894
  return self.buildLookup_(subtables)
860
895
 
896
+ def getAlternateGlyphs(self):
897
+ # https://github.com/fonttools/fonttools/issues/3845
898
+ return {
899
+ components[0]: [ligature]
900
+ for components, ligature in self.ligatures.items()
901
+ if len(components) == 1
902
+ }
903
+
861
904
  def add_subtable_break(self, location):
862
905
  self.ligatures[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
863
906
 
@@ -896,6 +939,14 @@ class MultipleSubstBuilder(LookupBuilder):
896
939
  subtables = self.build_subst_subtables(self.mapping, buildMultipleSubstSubtable)
897
940
  return self.buildLookup_(subtables)
898
941
 
942
+ def getAlternateGlyphs(self):
943
+ # https://github.com/fonttools/fonttools/issues/3845
944
+ return {
945
+ glyph: replacements
946
+ for glyph, replacements in self.mapping.items()
947
+ if len(replacements) == 1
948
+ }
949
+
899
950
  def add_subtable_break(self, location):
900
951
  self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
901
952
 
@@ -949,8 +1000,20 @@ class CursivePosBuilder(LookupBuilder):
949
1000
  An ``otTables.Lookup`` object representing the cursive
950
1001
  positioning lookup.
951
1002
  """
952
- st = buildCursivePosSubtable(self.attachments, self.glyphMap)
953
- return self.buildLookup_([st])
1003
+ attachments = [{}]
1004
+ for key in self.attachments:
1005
+ if key[0] == self.SUBTABLE_BREAK_:
1006
+ attachments.append({})
1007
+ else:
1008
+ attachments[-1][key] = self.attachments[key]
1009
+ subtables = [buildCursivePosSubtable(s, self.glyphMap) for s in attachments]
1010
+ return self.buildLookup_(subtables)
1011
+
1012
+ def add_subtable_break(self, location):
1013
+ self.attachments[(self.SUBTABLE_BREAK_, location)] = (
1014
+ self.SUBTABLE_BREAK_,
1015
+ self.SUBTABLE_BREAK_,
1016
+ )
954
1017
 
955
1018
 
956
1019
  class MarkBasePosBuilder(LookupBuilder):
@@ -985,17 +1048,25 @@ class MarkBasePosBuilder(LookupBuilder):
985
1048
  LookupBuilder.__init__(self, font, location, "GPOS", 4)
986
1049
  self.marks = {} # glyphName -> (markClassName, anchor)
987
1050
  self.bases = {} # glyphName -> {markClassName: anchor}
1051
+ self.subtables_ = []
1052
+
1053
+ def get_subtables_(self):
1054
+ subtables_ = self.subtables_
1055
+ if self.bases or self.marks:
1056
+ subtables_.append((self.marks, self.bases))
1057
+ return subtables_
988
1058
 
989
1059
  def equals(self, other):
990
1060
  return (
991
1061
  LookupBuilder.equals(self, other)
992
- and self.marks == other.marks
993
- and self.bases == other.bases
1062
+ and self.get_subtables_() == other.get_subtables_()
994
1063
  )
995
1064
 
996
1065
  def inferGlyphClasses(self):
997
- result = {glyph: 1 for glyph in self.bases}
998
- result.update({glyph: 3 for glyph in self.marks})
1066
+ result = {}
1067
+ for marks, bases in self.get_subtables_():
1068
+ result.update({glyph: 1 for glyph in bases})
1069
+ result.update({glyph: 3 for glyph in marks})
999
1070
  return result
1000
1071
 
1001
1072
  def build(self):
@@ -1005,26 +1076,33 @@ class MarkBasePosBuilder(LookupBuilder):
1005
1076
  An ``otTables.Lookup`` object representing the mark-to-base
1006
1077
  positioning lookup.
1007
1078
  """
1008
- markClasses = self.buildMarkClasses_(self.marks)
1009
- marks = {}
1010
- for mark, (mc, anchor) in self.marks.items():
1011
- if mc not in markClasses:
1012
- raise ValueError(
1013
- "Mark class %s not found for mark glyph %s" % (mc, mark)
1014
- )
1015
- marks[mark] = (markClasses[mc], anchor)
1016
- bases = {}
1017
- for glyph, anchors in self.bases.items():
1018
- bases[glyph] = {}
1019
- for mc, anchor in anchors.items():
1079
+ subtables = []
1080
+ for subtable in self.get_subtables_():
1081
+ markClasses = self.buildMarkClasses_(subtable[0])
1082
+ marks = {}
1083
+ for mark, (mc, anchor) in subtable[0].items():
1020
1084
  if mc not in markClasses:
1021
1085
  raise ValueError(
1022
- "Mark class %s not found for base glyph %s" % (mc, glyph)
1086
+ "Mark class %s not found for mark glyph %s" % (mc, mark)
1023
1087
  )
1024
- bases[glyph][markClasses[mc]] = anchor
1025
- subtables = buildMarkBasePos(marks, bases, self.glyphMap)
1088
+ marks[mark] = (markClasses[mc], anchor)
1089
+ bases = {}
1090
+ for glyph, anchors in subtable[1].items():
1091
+ bases[glyph] = {}
1092
+ for mc, anchor in anchors.items():
1093
+ if mc not in markClasses:
1094
+ raise ValueError(
1095
+ "Mark class %s not found for base glyph %s" % (mc, glyph)
1096
+ )
1097
+ bases[glyph][markClasses[mc]] = anchor
1098
+ subtables.append(buildMarkBasePosSubtable(marks, bases, self.glyphMap))
1026
1099
  return self.buildLookup_(subtables)
1027
1100
 
1101
+ def add_subtable_break(self, location):
1102
+ self.subtables_.append((self.marks, self.bases))
1103
+ self.marks = {}
1104
+ self.bases = {}
1105
+
1028
1106
 
1029
1107
  class MarkLigPosBuilder(LookupBuilder):
1030
1108
  """Builds a Mark-To-Ligature Positioning (GPOS5) lookup.
@@ -1061,17 +1139,25 @@ class MarkLigPosBuilder(LookupBuilder):
1061
1139
  LookupBuilder.__init__(self, font, location, "GPOS", 5)
1062
1140
  self.marks = {} # glyphName -> (markClassName, anchor)
1063
1141
  self.ligatures = {} # glyphName -> [{markClassName: anchor}, ...]
1142
+ self.subtables_ = []
1143
+
1144
+ def get_subtables_(self):
1145
+ subtables_ = self.subtables_
1146
+ if self.ligatures or self.marks:
1147
+ subtables_.append((self.marks, self.ligatures))
1148
+ return subtables_
1064
1149
 
1065
1150
  def equals(self, other):
1066
1151
  return (
1067
1152
  LookupBuilder.equals(self, other)
1068
- and self.marks == other.marks
1069
- and self.ligatures == other.ligatures
1153
+ and self.get_subtables_() == other.get_subtables_()
1070
1154
  )
1071
1155
 
1072
1156
  def inferGlyphClasses(self):
1073
- result = {glyph: 2 for glyph in self.ligatures}
1074
- result.update({glyph: 3 for glyph in self.marks})
1157
+ result = {}
1158
+ for marks, ligatures in self.get_subtables_():
1159
+ result.update({glyph: 2 for glyph in ligatures})
1160
+ result.update({glyph: 3 for glyph in marks})
1075
1161
  return result
1076
1162
 
1077
1163
  def build(self):
@@ -1081,18 +1167,26 @@ class MarkLigPosBuilder(LookupBuilder):
1081
1167
  An ``otTables.Lookup`` object representing the mark-to-ligature
1082
1168
  positioning lookup.
1083
1169
  """
1084
- markClasses = self.buildMarkClasses_(self.marks)
1085
- marks = {
1086
- mark: (markClasses[mc], anchor) for mark, (mc, anchor) in self.marks.items()
1087
- }
1088
- ligs = {}
1089
- for lig, components in self.ligatures.items():
1090
- ligs[lig] = []
1091
- for c in components:
1092
- ligs[lig].append({markClasses[mc]: a for mc, a in c.items()})
1093
- subtables = buildMarkLigPos(marks, ligs, self.glyphMap)
1170
+ subtables = []
1171
+ for subtable in self.get_subtables_():
1172
+ markClasses = self.buildMarkClasses_(subtable[0])
1173
+ marks = {
1174
+ mark: (markClasses[mc], anchor)
1175
+ for mark, (mc, anchor) in subtable[0].items()
1176
+ }
1177
+ ligs = {}
1178
+ for lig, components in subtable[1].items():
1179
+ ligs[lig] = []
1180
+ for c in components:
1181
+ ligs[lig].append({markClasses[mc]: a for mc, a in c.items()})
1182
+ subtables.append(buildMarkLigPosSubtable(marks, ligs, self.glyphMap))
1094
1183
  return self.buildLookup_(subtables)
1095
1184
 
1185
+ def add_subtable_break(self, location):
1186
+ self.subtables_.append((self.marks, self.ligatures))
1187
+ self.marks = {}
1188
+ self.ligatures = {}
1189
+
1096
1190
 
1097
1191
  class MarkMarkPosBuilder(LookupBuilder):
1098
1192
  """Builds a Mark-To-Mark Positioning (GPOS6) lookup.
@@ -1125,17 +1219,25 @@ class MarkMarkPosBuilder(LookupBuilder):
1125
1219
  LookupBuilder.__init__(self, font, location, "GPOS", 6)
1126
1220
  self.marks = {} # glyphName -> (markClassName, anchor)
1127
1221
  self.baseMarks = {} # glyphName -> {markClassName: anchor}
1222
+ self.subtables_ = []
1223
+
1224
+ def get_subtables_(self):
1225
+ subtables_ = self.subtables_
1226
+ if self.baseMarks or self.marks:
1227
+ subtables_.append((self.marks, self.baseMarks))
1228
+ return subtables_
1128
1229
 
1129
1230
  def equals(self, other):
1130
1231
  return (
1131
1232
  LookupBuilder.equals(self, other)
1132
- and self.marks == other.marks
1133
- and self.baseMarks == other.baseMarks
1233
+ and self.get_subtables_() == other.get_subtables_()
1134
1234
  )
1135
1235
 
1136
1236
  def inferGlyphClasses(self):
1137
- result = {glyph: 3 for glyph in self.baseMarks}
1138
- result.update({glyph: 3 for glyph in self.marks})
1237
+ result = {}
1238
+ for marks, baseMarks in self.get_subtables_():
1239
+ result.update({glyph: 3 for glyph in baseMarks})
1240
+ result.update({glyph: 3 for glyph in marks})
1139
1241
  return result
1140
1242
 
1141
1243
  def build(self):
@@ -1145,25 +1247,34 @@ class MarkMarkPosBuilder(LookupBuilder):
1145
1247
  An ``otTables.Lookup`` object representing the mark-to-mark
1146
1248
  positioning lookup.
1147
1249
  """
1148
- markClasses = self.buildMarkClasses_(self.marks)
1149
- markClassList = sorted(markClasses.keys(), key=markClasses.get)
1150
- marks = {
1151
- mark: (markClasses[mc], anchor) for mark, (mc, anchor) in self.marks.items()
1152
- }
1250
+ subtables = []
1251
+ for subtable in self.get_subtables_():
1252
+ markClasses = self.buildMarkClasses_(subtable[0])
1253
+ markClassList = sorted(markClasses.keys(), key=markClasses.get)
1254
+ marks = {
1255
+ mark: (markClasses[mc], anchor)
1256
+ for mark, (mc, anchor) in subtable[0].items()
1257
+ }
1258
+
1259
+ st = ot.MarkMarkPos()
1260
+ st.Format = 1
1261
+ st.ClassCount = len(markClasses)
1262
+ st.Mark1Coverage = buildCoverage(marks, self.glyphMap)
1263
+ st.Mark2Coverage = buildCoverage(subtable[1], self.glyphMap)
1264
+ st.Mark1Array = buildMarkArray(marks, self.glyphMap)
1265
+ st.Mark2Array = ot.Mark2Array()
1266
+ st.Mark2Array.Mark2Count = len(st.Mark2Coverage.glyphs)
1267
+ st.Mark2Array.Mark2Record = []
1268
+ for base in st.Mark2Coverage.glyphs:
1269
+ anchors = [subtable[1][base].get(mc) for mc in markClassList]
1270
+ st.Mark2Array.Mark2Record.append(buildMark2Record(anchors))
1271
+ subtables.append(st)
1272
+ return self.buildLookup_(subtables)
1153
1273
 
1154
- st = ot.MarkMarkPos()
1155
- st.Format = 1
1156
- st.ClassCount = len(markClasses)
1157
- st.Mark1Coverage = buildCoverage(marks, self.glyphMap)
1158
- st.Mark2Coverage = buildCoverage(self.baseMarks, self.glyphMap)
1159
- st.Mark1Array = buildMarkArray(marks, self.glyphMap)
1160
- st.Mark2Array = ot.Mark2Array()
1161
- st.Mark2Array.Mark2Count = len(st.Mark2Coverage.glyphs)
1162
- st.Mark2Array.Mark2Record = []
1163
- for base in st.Mark2Coverage.glyphs:
1164
- anchors = [self.baseMarks[base].get(mc) for mc in markClassList]
1165
- st.Mark2Array.Mark2Record.append(buildMark2Record(anchors))
1166
- return self.buildLookup_([st])
1274
+ def add_subtable_break(self, location):
1275
+ self.subtables_.append((self.marks, self.baseMarks))
1276
+ self.marks = {}
1277
+ self.baseMarks = {}
1167
1278
 
1168
1279
 
1169
1280
  class ReverseChainSingleSubstBuilder(LookupBuilder):
@@ -1223,6 +1334,177 @@ class ReverseChainSingleSubstBuilder(LookupBuilder):
1223
1334
  pass
1224
1335
 
1225
1336
 
1337
+ class AnySubstBuilder(LookupBuilder):
1338
+ """A temporary builder for Single, Multiple, or Ligature substitution lookup.
1339
+
1340
+ Users are expected to manually add substitutions to the ``mapping``
1341
+ attribute after the object has been initialized, e.g.::
1342
+
1343
+ # sub x by y;
1344
+ builder.mapping[("x",)] = ("y",)
1345
+ # sub a by b c;
1346
+ builder.mapping[("a",)] = ("b", "c")
1347
+ # sub f i by f_i;
1348
+ builder.mapping[("f", "i")] = ("f_i",)
1349
+
1350
+ Then call `promote_lookup_type()` to convert this builder into the
1351
+ appropriate type of substitution lookup builder. This would promote single
1352
+ substitutions to either multiple or ligature substitutions, depending on the
1353
+ rest of the rules in the mapping.
1354
+
1355
+ Attributes:
1356
+ font (``fontTools.TTLib.TTFont``): A font object.
1357
+ location: A string or tuple representing the location in the original
1358
+ source which produced this lookup.
1359
+ mapping: An ordered dictionary mapping a tuple of glyph names to another
1360
+ tuple of glyph names.
1361
+ lookupflag (int): The lookup's flag
1362
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
1363
+ an integer representing the filtering set to be used for this
1364
+ lookup. If a mark filtering set is provided,
1365
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
1366
+ flags.
1367
+ """
1368
+
1369
+ def __init__(self, font, location):
1370
+ LookupBuilder.__init__(self, font, location, "GSUB", 0)
1371
+ self.mapping = OrderedDict()
1372
+
1373
+ def _add_to_single_subst(self, builder, key, value):
1374
+ if key[0] != self.SUBTABLE_BREAK_:
1375
+ key = key[0]
1376
+ builder.mapping[key] = value[0]
1377
+
1378
+ def _add_to_multiple_subst(self, builder, key, value):
1379
+ if key[0] != self.SUBTABLE_BREAK_:
1380
+ key = key[0]
1381
+ builder.mapping[key] = value
1382
+
1383
+ def _add_to_ligature_subst(self, builder, key, value):
1384
+ builder.ligatures[key] = value[0]
1385
+
1386
+ def can_add_mapping(self, mapping) -> bool:
1387
+ if mapping is None:
1388
+ return True
1389
+ # single sub rules can be treated as (degenerate) liga-or-multi sub
1390
+ # rules, but multi and liga sub rules themselves have incompatible
1391
+ # representations. It is uncommon that these are in the same set of
1392
+ # rules, but it happens.
1393
+ is_multi = any(len(v) > 1 for v in mapping.values())
1394
+ is_liga = any(len(k) > 1 for k in mapping.keys())
1395
+
1396
+ has_existing_multi = False
1397
+ has_existing_liga = False
1398
+
1399
+ for k, v in self.mapping.items():
1400
+ if k[0] == self.SUBTABLE_BREAK_:
1401
+ continue
1402
+ if len(k) > 1:
1403
+ has_existing_liga = True
1404
+ if len(v) > 1:
1405
+ has_existing_multi = True
1406
+
1407
+ can_reuse = not (has_existing_multi and is_liga) and not (
1408
+ has_existing_liga and is_multi
1409
+ )
1410
+ return can_reuse
1411
+
1412
+ def promote_lookup_type(self, is_named_lookup):
1413
+ # https://github.com/fonttools/fonttools/issues/612
1414
+ # A multiple substitution may have a single destination, in which case
1415
+ # it will look just like a single substitution. So if there are both
1416
+ # multiple and single substitutions, upgrade all the single ones to
1417
+ # multiple substitutions. Similarly, a ligature substitution may have a
1418
+ # single source glyph, so if there are both ligature and single
1419
+ # substitutions, upgrade all the single ones to ligature substitutions.
1420
+ builder_classes = []
1421
+ for key, value in self.mapping.items():
1422
+ if key[0] == self.SUBTABLE_BREAK_:
1423
+ builder_classes.append(None)
1424
+ elif len(key) == 1 and len(value) == 1:
1425
+ builder_classes.append(SingleSubstBuilder)
1426
+ elif len(key) == 1 and len(value) != 1:
1427
+ builder_classes.append(MultipleSubstBuilder)
1428
+ elif len(key) > 1 and len(value) == 1:
1429
+ builder_classes.append(LigatureSubstBuilder)
1430
+ else:
1431
+ assert False, "Should not happen"
1432
+
1433
+ has_multiple = any(b is MultipleSubstBuilder for b in builder_classes)
1434
+ has_ligature = any(b is LigatureSubstBuilder for b in builder_classes)
1435
+
1436
+ # If we have mixed single and multiple substitutions,
1437
+ # upgrade all single substitutions to multiple substitutions.
1438
+ to_multiple = has_multiple and not has_ligature
1439
+
1440
+ # If we have mixed single and ligature substitutions,
1441
+ # upgrade all single substitutions to ligature substitutions.
1442
+ to_ligature = has_ligature and not has_multiple
1443
+
1444
+ # If we have only single substitutions, we can keep them as is.
1445
+ to_single = not has_ligature and not has_multiple
1446
+
1447
+ ret = []
1448
+ if to_single:
1449
+ builder = SingleSubstBuilder(self.font, self.location)
1450
+ for key, value in self.mapping.items():
1451
+ self._add_to_single_subst(builder, key, value)
1452
+ ret = [builder]
1453
+ elif to_multiple:
1454
+ builder = MultipleSubstBuilder(self.font, self.location)
1455
+ for key, value in self.mapping.items():
1456
+ self._add_to_multiple_subst(builder, key, value)
1457
+ ret = [builder]
1458
+ elif to_ligature:
1459
+ builder = LigatureSubstBuilder(self.font, self.location)
1460
+ for key, value in self.mapping.items():
1461
+ self._add_to_ligature_subst(builder, key, value)
1462
+ ret = [builder]
1463
+ elif is_named_lookup:
1464
+ # This is a named lookup with mixed substitutions that can’t be promoted,
1465
+ # since we can’t split it into multiple lookups, we return None here to
1466
+ # signal that to the caller
1467
+ return None
1468
+ else:
1469
+ curr_builder = None
1470
+ for builder_class, (key, value) in zip(
1471
+ builder_classes, self.mapping.items()
1472
+ ):
1473
+ if curr_builder is None or type(curr_builder) is not builder_class:
1474
+ curr_builder = builder_class(self.font, self.location)
1475
+ ret.append(curr_builder)
1476
+ if builder_class is SingleSubstBuilder:
1477
+ self._add_to_single_subst(curr_builder, key, value)
1478
+ elif builder_class is MultipleSubstBuilder:
1479
+ self._add_to_multiple_subst(curr_builder, key, value)
1480
+ elif builder_class is LigatureSubstBuilder:
1481
+ self._add_to_ligature_subst(curr_builder, key, value)
1482
+ else:
1483
+ assert False, "Should not happen"
1484
+
1485
+ for builder in ret:
1486
+ builder.extension = self.extension
1487
+ builder.lookupflag = self.lookupflag
1488
+ builder.markFilterSet = self.markFilterSet
1489
+ return ret
1490
+
1491
+ def equals(self, other):
1492
+ return LookupBuilder.equals(self, other) and self.mapping == other.mapping
1493
+
1494
+ def build(self):
1495
+ assert False
1496
+
1497
+ def getAlternateGlyphs(self):
1498
+ return {
1499
+ key[0]: value
1500
+ for key, value in self.mapping.items()
1501
+ if len(key) == 1 and len(value) == 1
1502
+ }
1503
+
1504
+ def add_subtable_break(self, location):
1505
+ self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
1506
+
1507
+
1226
1508
  class SingleSubstBuilder(LookupBuilder):
1227
1509
  """Builds a Single Substitution (GSUB1) lookup.
1228
1510
 
@@ -1484,6 +1766,8 @@ class SinglePosBuilder(LookupBuilder):
1484
1766
  otValueRection: A ``otTables.ValueRecord`` used to position the
1485
1767
  glyph.
1486
1768
  """
1769
+ if otValueRecord is None:
1770
+ otValueRecord = ValueRecord()
1487
1771
  if not self.can_add(glyph, otValueRecord):
1488
1772
  otherLoc = self.locations[glyph]
1489
1773
  raise OpenTypeLibError(
@@ -1900,53 +2184,15 @@ def buildMarkArray(marks, glyphMap):
1900
2184
  return self
1901
2185
 
1902
2186
 
2187
+ @deprecateFunction(
2188
+ "use buildMarkBasePosSubtable() instead", category=DeprecationWarning
2189
+ )
1903
2190
  def buildMarkBasePos(marks, bases, glyphMap):
1904
2191
  """Build a list of MarkBasePos (GPOS4) subtables.
1905
2192
 
1906
- This routine turns a set of marks and bases into a list of mark-to-base
1907
- positioning subtables. Currently the list will contain a single subtable
1908
- containing all marks and bases, although at a later date it may return the
1909
- optimal list of subtables subsetting the marks and bases into groups which
1910
- save space. See :func:`buildMarkBasePosSubtable` below.
1911
-
1912
- Note that if you are implementing a layout compiler, you may find it more
1913
- flexible to use
1914
- :py:class:`fontTools.otlLib.lookupBuilders.MarkBasePosBuilder` instead.
1915
-
1916
- Example::
1917
-
1918
- # a1, a2, a3, a4, a5 = buildAnchor(500, 100), ...
1919
-
1920
- marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)}
1921
- bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}}
1922
- markbaseposes = buildMarkBasePos(marks, bases, font.getReverseGlyphMap())
1923
-
1924
- Args:
1925
- marks (dict): A dictionary mapping anchors to glyphs; the keys being
1926
- glyph names, and the values being a tuple of mark class number and
1927
- an ``otTables.Anchor`` object representing the mark's attachment
1928
- point. (See :func:`buildMarkArray`.)
1929
- bases (dict): A dictionary mapping anchors to glyphs; the keys being
1930
- glyph names, and the values being dictionaries mapping mark class ID
1931
- to the appropriate ``otTables.Anchor`` object used for attaching marks
1932
- of that class. (See :func:`buildBaseArray`.)
1933
- glyphMap: a glyph name to ID map, typically returned from
1934
- ``font.getReverseGlyphMap()``.
1935
-
1936
- Returns:
1937
- A list of ``otTables.MarkBasePos`` objects.
2193
+ .. deprecated:: 4.58.0
2194
+ Use :func:`buildMarkBasePosSubtable` instead.
1938
2195
  """
1939
- # TODO: Consider emitting multiple subtables to save space.
1940
- # Partition the marks and bases into disjoint subsets, so that
1941
- # MarkBasePos rules would only access glyphs from a single
1942
- # subset. This would likely lead to smaller mark/base
1943
- # matrices, so we might be able to omit many of the empty
1944
- # anchor tables that we currently produce. Of course, this
1945
- # would only work if the MarkBasePos rules of real-world fonts
1946
- # allow partitioning into multiple subsets. We should find out
1947
- # whether this is the case; if so, implement the optimization.
1948
- # On the other hand, a very large number of subtables could
1949
- # slow down layout engines; so this would need profiling.
1950
2196
  return [buildMarkBasePosSubtable(marks, bases, glyphMap)]
1951
2197
 
1952
2198
 
@@ -1954,7 +2200,15 @@ def buildMarkBasePosSubtable(marks, bases, glyphMap):
1954
2200
  """Build a single MarkBasePos (GPOS4) subtable.
1955
2201
 
1956
2202
  This builds a mark-to-base lookup subtable containing all of the referenced
1957
- marks and bases. See :func:`buildMarkBasePos`.
2203
+ marks and bases.
2204
+
2205
+ Example::
2206
+
2207
+ # a1, a2, a3, a4, a5 = buildAnchor(500, 100), ...
2208
+
2209
+ marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)}
2210
+ bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}}
2211
+ markbaseposes = [buildMarkBasePosSubtable(marks, bases, font.getReverseGlyphMap())]
1958
2212
 
1959
2213
  Args:
1960
2214
  marks (dict): A dictionary mapping anchors to glyphs; the keys being
@@ -1981,14 +2235,21 @@ def buildMarkBasePosSubtable(marks, bases, glyphMap):
1981
2235
  return self
1982
2236
 
1983
2237
 
2238
+ @deprecateFunction("use buildMarkLigPosSubtable() instead", category=DeprecationWarning)
1984
2239
  def buildMarkLigPos(marks, ligs, glyphMap):
1985
2240
  """Build a list of MarkLigPos (GPOS5) subtables.
1986
2241
 
1987
- This routine turns a set of marks and ligatures into a list of mark-to-ligature
1988
- positioning subtables. Currently the list will contain a single subtable
1989
- containing all marks and ligatures, although at a later date it may return
1990
- the optimal list of subtables subsetting the marks and ligatures into groups
1991
- which save space. See :func:`buildMarkLigPosSubtable` below.
2242
+ .. deprecated:: 4.58.0
2243
+ Use :func:`buildMarkLigPosSubtable` instead.
2244
+ """
2245
+ return [buildMarkLigPosSubtable(marks, ligs, glyphMap)]
2246
+
2247
+
2248
+ def buildMarkLigPosSubtable(marks, ligs, glyphMap):
2249
+ """Build a single MarkLigPos (GPOS5) subtable.
2250
+
2251
+ This builds a mark-to-base lookup subtable containing all of the referenced
2252
+ marks and bases.
1992
2253
 
1993
2254
  Note that if you are implementing a layout compiler, you may find it more
1994
2255
  flexible to use
@@ -2009,37 +2270,9 @@ def buildMarkLigPos(marks, ligs, glyphMap):
2009
2270
  ],
2010
2271
  # "c_t": [{...}, {...}]
2011
2272
  }
2012
- markligposes = buildMarkLigPos(marks, ligs,
2273
+ markligpose = buildMarkLigPosSubtable(marks, ligs,
2013
2274
  font.getReverseGlyphMap())
2014
2275
 
2015
- Args:
2016
- marks (dict): A dictionary mapping anchors to glyphs; the keys being
2017
- glyph names, and the values being a tuple of mark class number and
2018
- an ``otTables.Anchor`` object representing the mark's attachment
2019
- point. (See :func:`buildMarkArray`.)
2020
- ligs (dict): A mapping of ligature names to an array of dictionaries:
2021
- for each component glyph in the ligature, an dictionary mapping
2022
- mark class IDs to anchors. (See :func:`buildLigatureArray`.)
2023
- glyphMap: a glyph name to ID map, typically returned from
2024
- ``font.getReverseGlyphMap()``.
2025
-
2026
- Returns:
2027
- A list of ``otTables.MarkLigPos`` objects.
2028
-
2029
- """
2030
- # TODO: Consider splitting into multiple subtables to save space,
2031
- # as with MarkBasePos, this would be a trade-off that would need
2032
- # profiling. And, depending on how typical fonts are structured,
2033
- # it might not be worth doing at all.
2034
- return [buildMarkLigPosSubtable(marks, ligs, glyphMap)]
2035
-
2036
-
2037
- def buildMarkLigPosSubtable(marks, ligs, glyphMap):
2038
- """Build a single MarkLigPos (GPOS5) subtable.
2039
-
2040
- This builds a mark-to-base lookup subtable containing all of the referenced
2041
- marks and bases. See :func:`buildMarkLigPos`.
2042
-
2043
2276
  Args:
2044
2277
  marks (dict): A dictionary mapping anchors to glyphs; the keys being
2045
2278
  glyph names, and the values being a tuple of mark class number and
@@ -2706,10 +2939,18 @@ class ClassDefBuilder(object):
2706
2939
  AXIS_VALUE_NEGATIVE_INFINITY = fixedToFloat(-0x80000000, 16)
2707
2940
  AXIS_VALUE_POSITIVE_INFINITY = fixedToFloat(0x7FFFFFFF, 16)
2708
2941
 
2942
+ STATName = Union[int, str, Dict[str, str]]
2943
+ """A raw name ID, English name, or multilingual name."""
2944
+
2709
2945
 
2710
2946
  def buildStatTable(
2711
- ttFont, axes, locations=None, elidedFallbackName=2, windowsNames=True, macNames=True
2712
- ):
2947
+ ttFont: TTFont,
2948
+ axes,
2949
+ locations=None,
2950
+ elidedFallbackName: Union[STATName, STATNameStatement] = 2,
2951
+ windowsNames: bool = True,
2952
+ macNames: bool = True,
2953
+ ) -> None:
2713
2954
  """Add a 'STAT' table to 'ttFont'.
2714
2955
 
2715
2956
  'axes' is a list of dictionaries describing axes and their
@@ -2900,7 +3141,13 @@ def _buildAxisValuesFormat4(locations, axes, ttFont, windowsNames=True, macNames
2900
3141
  return axisValues
2901
3142
 
2902
3143
 
2903
- def _addName(ttFont, value, minNameID=0, windows=True, mac=True):
3144
+ def _addName(
3145
+ ttFont: TTFont,
3146
+ value: Union[STATName, STATNameStatement],
3147
+ minNameID: int = 0,
3148
+ windows: bool = True,
3149
+ mac: bool = True,
3150
+ ) -> int:
2904
3151
  nameTable = ttFont["name"]
2905
3152
  if isinstance(value, int):
2906
3153
  # Already a nameID