fonttools 4.57.0__cp39-cp39-win_amd64.whl → 4.58.1__cp39-cp39-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 (67) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/cffLib/__init__.py +61 -26
  3. fontTools/cffLib/specializer.py +4 -1
  4. fontTools/cu2qu/cu2qu.cp39-win_amd64.pyd +0 -0
  5. fontTools/designspaceLib/statNames.py +14 -7
  6. fontTools/feaLib/ast.py +12 -9
  7. fontTools/feaLib/builder.py +75 -49
  8. fontTools/feaLib/lexer.cp39-win_amd64.pyd +0 -0
  9. fontTools/feaLib/parser.py +1 -39
  10. fontTools/fontBuilder.py +6 -0
  11. fontTools/merge/cmap.py +33 -1
  12. fontTools/merge/tables.py +12 -1
  13. fontTools/misc/bezierTools.cp39-win_amd64.pyd +0 -0
  14. fontTools/misc/etree.py +4 -27
  15. fontTools/misc/loggingTools.py +1 -1
  16. fontTools/misc/symfont.py +6 -8
  17. fontTools/mtiLib/__init__.py +1 -3
  18. fontTools/otlLib/builder.py +359 -145
  19. fontTools/otlLib/optimize/gpos.py +42 -62
  20. fontTools/pens/momentsPen.cp39-win_amd64.pyd +0 -0
  21. fontTools/pens/pointPen.py +21 -12
  22. fontTools/pens/t2CharStringPen.py +31 -11
  23. fontTools/qu2cu/qu2cu.cp39-win_amd64.pyd +0 -0
  24. fontTools/subset/__init__.py +12 -1
  25. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  26. fontTools/ttLib/tables/T_S_I__0.py +14 -3
  27. fontTools/ttLib/tables/T_S_I__5.py +16 -5
  28. fontTools/ttLib/tables/__init__.py +1 -0
  29. fontTools/ttLib/tables/_c_v_t.py +2 -0
  30. fontTools/ttLib/tables/_f_p_g_m.py +3 -1
  31. fontTools/ttLib/tables/_g_l_y_f.py +2 -6
  32. fontTools/ttLib/tables/_g_v_a_r.py +58 -15
  33. fontTools/ttLib/tables/_p_o_s_t.py +5 -2
  34. fontTools/ttLib/tables/otBase.py +1 -0
  35. fontTools/ufoLib/__init__.py +3 -3
  36. fontTools/ufoLib/converters.py +89 -25
  37. fontTools/ufoLib/errors.py +8 -0
  38. fontTools/ufoLib/etree.py +1 -1
  39. fontTools/ufoLib/filenames.py +155 -100
  40. fontTools/ufoLib/glifLib.py +9 -2
  41. fontTools/ufoLib/kerning.py +66 -36
  42. fontTools/ufoLib/utils.py +5 -2
  43. fontTools/unicodedata/Mirrored.py +446 -0
  44. fontTools/unicodedata/__init__.py +6 -2
  45. fontTools/varLib/__init__.py +20 -6
  46. fontTools/varLib/featureVars.py +13 -7
  47. fontTools/varLib/hvar.py +1 -1
  48. fontTools/varLib/instancer/__init__.py +14 -5
  49. fontTools/varLib/iup.cp39-win_amd64.pyd +0 -0
  50. fontTools/voltLib/__main__.py +206 -0
  51. fontTools/voltLib/ast.py +4 -0
  52. fontTools/voltLib/parser.py +16 -8
  53. fontTools/voltLib/voltToFea.py +347 -166
  54. {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/METADATA +64 -11
  55. {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/RECORD +61 -63
  56. {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/WHEEL +1 -1
  57. fonttools-4.58.1.dist-info/licenses/LICENSE.external +359 -0
  58. fontTools/cu2qu/cu2qu.c +0 -14829
  59. fontTools/feaLib/lexer.c +0 -17986
  60. fontTools/misc/bezierTools.c +0 -41831
  61. fontTools/pens/momentsPen.c +0 -13448
  62. fontTools/qu2cu/qu2cu.c +0 -16269
  63. fontTools/varLib/iup.c +0 -19154
  64. {fonttools-4.57.0.data → fonttools-4.58.1.data}/data/share/man/man1/ttx.1 +0 -0
  65. {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/entry_points.txt +0 -0
  66. {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/licenses/LICENSE +0 -0
  67. {fonttools-4.57.0.dist-info → fonttools-4.58.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
@@ -73,7 +75,7 @@ LOOKUP_FLAG_IGNORE_MARKS = 0x0008
73
75
  LOOKUP_FLAG_USE_MARK_FILTERING_SET = 0x0010
74
76
 
75
77
 
76
- def buildLookup(subtables, flags=0, markFilterSet=None):
78
+ def buildLookup(subtables, flags=0, markFilterSet=None, table=None, extension=False):
77
79
  """Turns a collection of rules into a lookup.
78
80
 
79
81
  A Lookup (as defined in the `OpenType Spec <https://docs.microsoft.com/en-gb/typography/opentype/spec/chapter2#lookupTbl>`__)
@@ -98,6 +100,8 @@ def buildLookup(subtables, flags=0, markFilterSet=None):
98
100
  lookup. If a mark filtering set is provided,
99
101
  `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
100
102
  flags.
103
+ table (str): The name of the table this lookup belongs to, e.g. "GPOS" or "GSUB".
104
+ extension (bool): ``True`` if this is an extension lookup, ``False`` otherwise.
101
105
 
102
106
  Returns:
103
107
  An ``otTables.Lookup`` object or ``None`` if there are no subtables
@@ -113,8 +117,21 @@ def buildLookup(subtables, flags=0, markFilterSet=None):
113
117
  ), "all subtables must have the same LookupType; got %s" % repr(
114
118
  [t.LookupType for t in subtables]
115
119
  )
120
+
121
+ if extension:
122
+ assert table in ("GPOS", "GSUB")
123
+ lookupType = 7 if table == "GSUB" else 9
124
+ extSubTableClass = ot.lookupTypes[table][lookupType]
125
+ for i, st in enumerate(subtables):
126
+ subtables[i] = extSubTableClass()
127
+ subtables[i].Format = 1
128
+ subtables[i].ExtSubTable = st
129
+ subtables[i].ExtensionLookupType = st.LookupType
130
+ else:
131
+ lookupType = subtables[0].LookupType
132
+
116
133
  self = ot.Lookup()
117
- self.LookupType = subtables[0].LookupType
134
+ self.LookupType = lookupType
118
135
  self.LookupFlag = flags
119
136
  self.SubTable = subtables
120
137
  self.SubTableCount = len(self.SubTable)
@@ -133,7 +150,7 @@ def buildLookup(subtables, flags=0, markFilterSet=None):
133
150
  class LookupBuilder(object):
134
151
  SUBTABLE_BREAK_ = "SUBTABLE_BREAK"
135
152
 
136
- def __init__(self, font, location, table, lookup_type):
153
+ def __init__(self, font, location, table, lookup_type, extension=False):
137
154
  self.font = font
138
155
  self.glyphMap = font.getReverseGlyphMap()
139
156
  self.location = location
@@ -141,6 +158,7 @@ class LookupBuilder(object):
141
158
  self.lookupflag = 0
142
159
  self.markFilterSet = None
143
160
  self.lookup_index = None # assigned when making final tables
161
+ self.extension = extension
144
162
  assert table in ("GPOS", "GSUB")
145
163
 
146
164
  def equals(self, other):
@@ -149,8 +167,12 @@ class LookupBuilder(object):
149
167
  and self.table == other.table
150
168
  and self.lookupflag == other.lookupflag
151
169
  and self.markFilterSet == other.markFilterSet
170
+ and self.extension == other.extension
152
171
  )
153
172
 
173
+ def promote_lookup_type(self, is_named_lookup):
174
+ return [self]
175
+
154
176
  def inferGlyphClasses(self):
155
177
  """Infers glyph glasses for the GDEF table, such as {"cedilla":3}."""
156
178
  return {}
@@ -160,7 +182,13 @@ class LookupBuilder(object):
160
182
  return {}
161
183
 
162
184
  def buildLookup_(self, subtables):
163
- return buildLookup(subtables, self.lookupflag, self.markFilterSet)
185
+ return buildLookup(
186
+ subtables,
187
+ self.lookupflag,
188
+ self.markFilterSet,
189
+ self.table,
190
+ self.extension,
191
+ )
164
192
 
165
193
  def buildMarkClasses_(self, marks):
166
194
  """{"cedilla": ("BOTTOM", ast.Anchor), ...} --> {"BOTTOM":0, "TOP":1}
@@ -858,6 +886,14 @@ class LigatureSubstBuilder(LookupBuilder):
858
886
  )
859
887
  return self.buildLookup_(subtables)
860
888
 
889
+ def getAlternateGlyphs(self):
890
+ # https://github.com/fonttools/fonttools/issues/3845
891
+ return {
892
+ components[0]: [ligature]
893
+ for components, ligature in self.ligatures.items()
894
+ if len(components) == 1
895
+ }
896
+
861
897
  def add_subtable_break(self, location):
862
898
  self.ligatures[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
863
899
 
@@ -896,6 +932,14 @@ class MultipleSubstBuilder(LookupBuilder):
896
932
  subtables = self.build_subst_subtables(self.mapping, buildMultipleSubstSubtable)
897
933
  return self.buildLookup_(subtables)
898
934
 
935
+ def getAlternateGlyphs(self):
936
+ # https://github.com/fonttools/fonttools/issues/3845
937
+ return {
938
+ glyph: replacements
939
+ for glyph, replacements in self.mapping.items()
940
+ if len(replacements) == 1
941
+ }
942
+
899
943
  def add_subtable_break(self, location):
900
944
  self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
901
945
 
@@ -949,8 +993,20 @@ class CursivePosBuilder(LookupBuilder):
949
993
  An ``otTables.Lookup`` object representing the cursive
950
994
  positioning lookup.
951
995
  """
952
- st = buildCursivePosSubtable(self.attachments, self.glyphMap)
953
- return self.buildLookup_([st])
996
+ attachments = [{}]
997
+ for key in self.attachments:
998
+ if key[0] == self.SUBTABLE_BREAK_:
999
+ attachments.append({})
1000
+ else:
1001
+ attachments[-1][key] = self.attachments[key]
1002
+ subtables = [buildCursivePosSubtable(s, self.glyphMap) for s in attachments]
1003
+ return self.buildLookup_(subtables)
1004
+
1005
+ def add_subtable_break(self, location):
1006
+ self.attachments[(self.SUBTABLE_BREAK_, location)] = (
1007
+ self.SUBTABLE_BREAK_,
1008
+ self.SUBTABLE_BREAK_,
1009
+ )
954
1010
 
955
1011
 
956
1012
  class MarkBasePosBuilder(LookupBuilder):
@@ -985,17 +1041,25 @@ class MarkBasePosBuilder(LookupBuilder):
985
1041
  LookupBuilder.__init__(self, font, location, "GPOS", 4)
986
1042
  self.marks = {} # glyphName -> (markClassName, anchor)
987
1043
  self.bases = {} # glyphName -> {markClassName: anchor}
1044
+ self.subtables_ = []
1045
+
1046
+ def get_subtables_(self):
1047
+ subtables_ = self.subtables_
1048
+ if self.bases or self.marks:
1049
+ subtables_.append((self.marks, self.bases))
1050
+ return subtables_
988
1051
 
989
1052
  def equals(self, other):
990
1053
  return (
991
1054
  LookupBuilder.equals(self, other)
992
- and self.marks == other.marks
993
- and self.bases == other.bases
1055
+ and self.get_subtables_() == other.get_subtables_()
994
1056
  )
995
1057
 
996
1058
  def inferGlyphClasses(self):
997
- result = {glyph: 1 for glyph in self.bases}
998
- result.update({glyph: 3 for glyph in self.marks})
1059
+ result = {}
1060
+ for marks, bases in self.get_subtables_():
1061
+ result.update({glyph: 1 for glyph in bases})
1062
+ result.update({glyph: 3 for glyph in marks})
999
1063
  return result
1000
1064
 
1001
1065
  def build(self):
@@ -1005,26 +1069,33 @@ class MarkBasePosBuilder(LookupBuilder):
1005
1069
  An ``otTables.Lookup`` object representing the mark-to-base
1006
1070
  positioning lookup.
1007
1071
  """
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():
1072
+ subtables = []
1073
+ for subtable in self.get_subtables_():
1074
+ markClasses = self.buildMarkClasses_(subtable[0])
1075
+ marks = {}
1076
+ for mark, (mc, anchor) in subtable[0].items():
1020
1077
  if mc not in markClasses:
1021
1078
  raise ValueError(
1022
- "Mark class %s not found for base glyph %s" % (mc, glyph)
1079
+ "Mark class %s not found for mark glyph %s" % (mc, mark)
1023
1080
  )
1024
- bases[glyph][markClasses[mc]] = anchor
1025
- subtables = buildMarkBasePos(marks, bases, self.glyphMap)
1081
+ marks[mark] = (markClasses[mc], anchor)
1082
+ bases = {}
1083
+ for glyph, anchors in subtable[1].items():
1084
+ bases[glyph] = {}
1085
+ for mc, anchor in anchors.items():
1086
+ if mc not in markClasses:
1087
+ raise ValueError(
1088
+ "Mark class %s not found for base glyph %s" % (mc, glyph)
1089
+ )
1090
+ bases[glyph][markClasses[mc]] = anchor
1091
+ subtables.append(buildMarkBasePosSubtable(marks, bases, self.glyphMap))
1026
1092
  return self.buildLookup_(subtables)
1027
1093
 
1094
+ def add_subtable_break(self, location):
1095
+ self.subtables_.append((self.marks, self.bases))
1096
+ self.marks = {}
1097
+ self.bases = {}
1098
+
1028
1099
 
1029
1100
  class MarkLigPosBuilder(LookupBuilder):
1030
1101
  """Builds a Mark-To-Ligature Positioning (GPOS5) lookup.
@@ -1061,17 +1132,25 @@ class MarkLigPosBuilder(LookupBuilder):
1061
1132
  LookupBuilder.__init__(self, font, location, "GPOS", 5)
1062
1133
  self.marks = {} # glyphName -> (markClassName, anchor)
1063
1134
  self.ligatures = {} # glyphName -> [{markClassName: anchor}, ...]
1135
+ self.subtables_ = []
1136
+
1137
+ def get_subtables_(self):
1138
+ subtables_ = self.subtables_
1139
+ if self.ligatures or self.marks:
1140
+ subtables_.append((self.marks, self.ligatures))
1141
+ return subtables_
1064
1142
 
1065
1143
  def equals(self, other):
1066
1144
  return (
1067
1145
  LookupBuilder.equals(self, other)
1068
- and self.marks == other.marks
1069
- and self.ligatures == other.ligatures
1146
+ and self.get_subtables_() == other.get_subtables_()
1070
1147
  )
1071
1148
 
1072
1149
  def inferGlyphClasses(self):
1073
- result = {glyph: 2 for glyph in self.ligatures}
1074
- result.update({glyph: 3 for glyph in self.marks})
1150
+ result = {}
1151
+ for marks, ligatures in self.get_subtables_():
1152
+ result.update({glyph: 2 for glyph in ligatures})
1153
+ result.update({glyph: 3 for glyph in marks})
1075
1154
  return result
1076
1155
 
1077
1156
  def build(self):
@@ -1081,18 +1160,26 @@ class MarkLigPosBuilder(LookupBuilder):
1081
1160
  An ``otTables.Lookup`` object representing the mark-to-ligature
1082
1161
  positioning lookup.
1083
1162
  """
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)
1163
+ subtables = []
1164
+ for subtable in self.get_subtables_():
1165
+ markClasses = self.buildMarkClasses_(subtable[0])
1166
+ marks = {
1167
+ mark: (markClasses[mc], anchor)
1168
+ for mark, (mc, anchor) in subtable[0].items()
1169
+ }
1170
+ ligs = {}
1171
+ for lig, components in subtable[1].items():
1172
+ ligs[lig] = []
1173
+ for c in components:
1174
+ ligs[lig].append({markClasses[mc]: a for mc, a in c.items()})
1175
+ subtables.append(buildMarkLigPosSubtable(marks, ligs, self.glyphMap))
1094
1176
  return self.buildLookup_(subtables)
1095
1177
 
1178
+ def add_subtable_break(self, location):
1179
+ self.subtables_.append((self.marks, self.ligatures))
1180
+ self.marks = {}
1181
+ self.ligatures = {}
1182
+
1096
1183
 
1097
1184
  class MarkMarkPosBuilder(LookupBuilder):
1098
1185
  """Builds a Mark-To-Mark Positioning (GPOS6) lookup.
@@ -1125,17 +1212,25 @@ class MarkMarkPosBuilder(LookupBuilder):
1125
1212
  LookupBuilder.__init__(self, font, location, "GPOS", 6)
1126
1213
  self.marks = {} # glyphName -> (markClassName, anchor)
1127
1214
  self.baseMarks = {} # glyphName -> {markClassName: anchor}
1215
+ self.subtables_ = []
1216
+
1217
+ def get_subtables_(self):
1218
+ subtables_ = self.subtables_
1219
+ if self.baseMarks or self.marks:
1220
+ subtables_.append((self.marks, self.baseMarks))
1221
+ return subtables_
1128
1222
 
1129
1223
  def equals(self, other):
1130
1224
  return (
1131
1225
  LookupBuilder.equals(self, other)
1132
- and self.marks == other.marks
1133
- and self.baseMarks == other.baseMarks
1226
+ and self.get_subtables_() == other.get_subtables_()
1134
1227
  )
1135
1228
 
1136
1229
  def inferGlyphClasses(self):
1137
- result = {glyph: 3 for glyph in self.baseMarks}
1138
- result.update({glyph: 3 for glyph in self.marks})
1230
+ result = {}
1231
+ for marks, baseMarks in self.get_subtables_():
1232
+ result.update({glyph: 3 for glyph in baseMarks})
1233
+ result.update({glyph: 3 for glyph in marks})
1139
1234
  return result
1140
1235
 
1141
1236
  def build(self):
@@ -1145,25 +1240,34 @@ class MarkMarkPosBuilder(LookupBuilder):
1145
1240
  An ``otTables.Lookup`` object representing the mark-to-mark
1146
1241
  positioning lookup.
1147
1242
  """
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
- }
1243
+ subtables = []
1244
+ for subtable in self.get_subtables_():
1245
+ markClasses = self.buildMarkClasses_(subtable[0])
1246
+ markClassList = sorted(markClasses.keys(), key=markClasses.get)
1247
+ marks = {
1248
+ mark: (markClasses[mc], anchor)
1249
+ for mark, (mc, anchor) in subtable[0].items()
1250
+ }
1251
+
1252
+ st = ot.MarkMarkPos()
1253
+ st.Format = 1
1254
+ st.ClassCount = len(markClasses)
1255
+ st.Mark1Coverage = buildCoverage(marks, self.glyphMap)
1256
+ st.Mark2Coverage = buildCoverage(subtable[1], self.glyphMap)
1257
+ st.Mark1Array = buildMarkArray(marks, self.glyphMap)
1258
+ st.Mark2Array = ot.Mark2Array()
1259
+ st.Mark2Array.Mark2Count = len(st.Mark2Coverage.glyphs)
1260
+ st.Mark2Array.Mark2Record = []
1261
+ for base in st.Mark2Coverage.glyphs:
1262
+ anchors = [subtable[1][base].get(mc) for mc in markClassList]
1263
+ st.Mark2Array.Mark2Record.append(buildMark2Record(anchors))
1264
+ subtables.append(st)
1265
+ return self.buildLookup_(subtables)
1153
1266
 
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])
1267
+ def add_subtable_break(self, location):
1268
+ self.subtables_.append((self.marks, self.baseMarks))
1269
+ self.marks = {}
1270
+ self.baseMarks = {}
1167
1271
 
1168
1272
 
1169
1273
  class ReverseChainSingleSubstBuilder(LookupBuilder):
@@ -1223,6 +1327,151 @@ class ReverseChainSingleSubstBuilder(LookupBuilder):
1223
1327
  pass
1224
1328
 
1225
1329
 
1330
+ class AnySubstBuilder(LookupBuilder):
1331
+ """A temporary builder for Single, Multiple, or Ligature substitution lookup.
1332
+
1333
+ Users are expected to manually add substitutions to the ``mapping``
1334
+ attribute after the object has been initialized, e.g.::
1335
+
1336
+ # sub x by y;
1337
+ builder.mapping[("x",)] = ("y",)
1338
+ # sub a by b c;
1339
+ builder.mapping[("a",)] = ("b", "c")
1340
+ # sub f i by f_i;
1341
+ builder.mapping[("f", "i")] = ("f_i",)
1342
+
1343
+ Then call `promote_lookup_type()` to convert this builder into the
1344
+ appropriate type of substitution lookup builder. This would promote single
1345
+ substitutions to either multiple or ligature substitutions, depending on the
1346
+ rest of the rules in the mapping.
1347
+
1348
+ Attributes:
1349
+ font (``fontTools.TTLib.TTFont``): A font object.
1350
+ location: A string or tuple representing the location in the original
1351
+ source which produced this lookup.
1352
+ mapping: An ordered dictionary mapping a tuple of glyph names to another
1353
+ tuple of glyph names.
1354
+ lookupflag (int): The lookup's flag
1355
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
1356
+ an integer representing the filtering set to be used for this
1357
+ lookup. If a mark filtering set is provided,
1358
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
1359
+ flags.
1360
+ """
1361
+
1362
+ def __init__(self, font, location):
1363
+ LookupBuilder.__init__(self, font, location, "GSUB", 0)
1364
+ self.mapping = OrderedDict()
1365
+
1366
+ def _add_to_single_subst(self, builder, key, value):
1367
+ if key[0] != self.SUBTABLE_BREAK_:
1368
+ key = key[0]
1369
+ builder.mapping[key] = value[0]
1370
+
1371
+ def _add_to_multiple_subst(self, builder, key, value):
1372
+ if key[0] != self.SUBTABLE_BREAK_:
1373
+ key = key[0]
1374
+ builder.mapping[key] = value
1375
+
1376
+ def _add_to_ligature_subst(self, builder, key, value):
1377
+ builder.ligatures[key] = value[0]
1378
+
1379
+ def promote_lookup_type(self, is_named_lookup):
1380
+ # https://github.com/fonttools/fonttools/issues/612
1381
+ # A multiple substitution may have a single destination, in which case
1382
+ # it will look just like a single substitution. So if there are both
1383
+ # multiple and single substitutions, upgrade all the single ones to
1384
+ # multiple substitutions. Similarly, a ligature substitution may have a
1385
+ # single source glyph, so if there are both ligature and single
1386
+ # substitutions, upgrade all the single ones to ligature substitutions.
1387
+ builder_classes = []
1388
+ for key, value in self.mapping.items():
1389
+ if key[0] == self.SUBTABLE_BREAK_:
1390
+ builder_classes.append(None)
1391
+ elif len(key) == 1 and len(value) == 1:
1392
+ builder_classes.append(SingleSubstBuilder)
1393
+ elif len(key) == 1 and len(value) != 1:
1394
+ builder_classes.append(MultipleSubstBuilder)
1395
+ elif len(key) > 1 and len(value) == 1:
1396
+ builder_classes.append(LigatureSubstBuilder)
1397
+ else:
1398
+ assert False, "Should not happen"
1399
+
1400
+ has_multiple = any(b is MultipleSubstBuilder for b in builder_classes)
1401
+ has_ligature = any(b is LigatureSubstBuilder for b in builder_classes)
1402
+
1403
+ # If we have mixed single and multiple substitutions,
1404
+ # upgrade all single substitutions to multiple substitutions.
1405
+ to_multiple = has_multiple and not has_ligature
1406
+
1407
+ # If we have mixed single and ligature substitutions,
1408
+ # upgrade all single substitutions to ligature substitutions.
1409
+ to_ligature = has_ligature and not has_multiple
1410
+
1411
+ # If we have only single substitutions, we can keep them as is.
1412
+ to_single = not has_ligature and not has_multiple
1413
+
1414
+ ret = []
1415
+ if to_single:
1416
+ builder = SingleSubstBuilder(self.font, self.location)
1417
+ for key, value in self.mapping.items():
1418
+ self._add_to_single_subst(builder, key, value)
1419
+ ret = [builder]
1420
+ elif to_multiple:
1421
+ builder = MultipleSubstBuilder(self.font, self.location)
1422
+ for key, value in self.mapping.items():
1423
+ self._add_to_multiple_subst(builder, key, value)
1424
+ ret = [builder]
1425
+ elif to_ligature:
1426
+ builder = LigatureSubstBuilder(self.font, self.location)
1427
+ for key, value in self.mapping.items():
1428
+ self._add_to_ligature_subst(builder, key, value)
1429
+ ret = [builder]
1430
+ elif is_named_lookup:
1431
+ # This is a named lookup with mixed substitutions that can’t be promoted,
1432
+ # since we can’t split it into multiple lookups, we return None here to
1433
+ # signal that to the caller
1434
+ return None
1435
+ else:
1436
+ curr_builder = None
1437
+ for builder_class, (key, value) in zip(
1438
+ builder_classes, self.mapping.items()
1439
+ ):
1440
+ if curr_builder is None or type(curr_builder) is not builder_class:
1441
+ curr_builder = builder_class(self.font, self.location)
1442
+ ret.append(curr_builder)
1443
+ if builder_class is SingleSubstBuilder:
1444
+ self._add_to_single_subst(curr_builder, key, value)
1445
+ elif builder_class is MultipleSubstBuilder:
1446
+ self._add_to_multiple_subst(curr_builder, key, value)
1447
+ elif builder_class is LigatureSubstBuilder:
1448
+ self._add_to_ligature_subst(curr_builder, key, value)
1449
+ else:
1450
+ assert False, "Should not happen"
1451
+
1452
+ for builder in ret:
1453
+ builder.extension = self.extension
1454
+ builder.lookupflag = self.lookupflag
1455
+ builder.markFilterSet = self.markFilterSet
1456
+ return ret
1457
+
1458
+ def equals(self, other):
1459
+ return LookupBuilder.equals(self, other) and self.mapping == other.mapping
1460
+
1461
+ def build(self):
1462
+ assert False
1463
+
1464
+ def getAlternateGlyphs(self):
1465
+ return {
1466
+ key[0]: value
1467
+ for key, value in self.mapping.items()
1468
+ if len(key) == 1 and len(value) == 1
1469
+ }
1470
+
1471
+ def add_subtable_break(self, location):
1472
+ self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
1473
+
1474
+
1226
1475
  class SingleSubstBuilder(LookupBuilder):
1227
1476
  """Builds a Single Substitution (GSUB1) lookup.
1228
1477
 
@@ -1484,6 +1733,8 @@ class SinglePosBuilder(LookupBuilder):
1484
1733
  otValueRection: A ``otTables.ValueRecord`` used to position the
1485
1734
  glyph.
1486
1735
  """
1736
+ if otValueRecord is None:
1737
+ otValueRecord = ValueRecord()
1487
1738
  if not self.can_add(glyph, otValueRecord):
1488
1739
  otherLoc = self.locations[glyph]
1489
1740
  raise OpenTypeLibError(
@@ -1900,53 +2151,15 @@ def buildMarkArray(marks, glyphMap):
1900
2151
  return self
1901
2152
 
1902
2153
 
2154
+ @deprecateFunction(
2155
+ "use buildMarkBasePosSubtable() instead", category=DeprecationWarning
2156
+ )
1903
2157
  def buildMarkBasePos(marks, bases, glyphMap):
1904
2158
  """Build a list of MarkBasePos (GPOS4) subtables.
1905
2159
 
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.
2160
+ .. deprecated:: 4.58.0
2161
+ Use :func:`buildMarkBasePosSubtable` instead.
1938
2162
  """
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
2163
  return [buildMarkBasePosSubtable(marks, bases, glyphMap)]
1951
2164
 
1952
2165
 
@@ -1954,7 +2167,15 @@ def buildMarkBasePosSubtable(marks, bases, glyphMap):
1954
2167
  """Build a single MarkBasePos (GPOS4) subtable.
1955
2168
 
1956
2169
  This builds a mark-to-base lookup subtable containing all of the referenced
1957
- marks and bases. See :func:`buildMarkBasePos`.
2170
+ marks and bases.
2171
+
2172
+ Example::
2173
+
2174
+ # a1, a2, a3, a4, a5 = buildAnchor(500, 100), ...
2175
+
2176
+ marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)}
2177
+ bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}}
2178
+ markbaseposes = [buildMarkBasePosSubtable(marks, bases, font.getReverseGlyphMap())]
1958
2179
 
1959
2180
  Args:
1960
2181
  marks (dict): A dictionary mapping anchors to glyphs; the keys being
@@ -1981,14 +2202,21 @@ def buildMarkBasePosSubtable(marks, bases, glyphMap):
1981
2202
  return self
1982
2203
 
1983
2204
 
2205
+ @deprecateFunction("use buildMarkLigPosSubtable() instead", category=DeprecationWarning)
1984
2206
  def buildMarkLigPos(marks, ligs, glyphMap):
1985
2207
  """Build a list of MarkLigPos (GPOS5) subtables.
1986
2208
 
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.
2209
+ .. deprecated:: 4.58.0
2210
+ Use :func:`buildMarkLigPosSubtable` instead.
2211
+ """
2212
+ return [buildMarkLigPosSubtable(marks, ligs, glyphMap)]
2213
+
2214
+
2215
+ def buildMarkLigPosSubtable(marks, ligs, glyphMap):
2216
+ """Build a single MarkLigPos (GPOS5) subtable.
2217
+
2218
+ This builds a mark-to-base lookup subtable containing all of the referenced
2219
+ marks and bases.
1992
2220
 
1993
2221
  Note that if you are implementing a layout compiler, you may find it more
1994
2222
  flexible to use
@@ -2009,37 +2237,9 @@ def buildMarkLigPos(marks, ligs, glyphMap):
2009
2237
  ],
2010
2238
  # "c_t": [{...}, {...}]
2011
2239
  }
2012
- markligposes = buildMarkLigPos(marks, ligs,
2240
+ markligpose = buildMarkLigPosSubtable(marks, ligs,
2013
2241
  font.getReverseGlyphMap())
2014
2242
 
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
2243
  Args:
2044
2244
  marks (dict): A dictionary mapping anchors to glyphs; the keys being
2045
2245
  glyph names, and the values being a tuple of mark class number and
@@ -2706,10 +2906,18 @@ class ClassDefBuilder(object):
2706
2906
  AXIS_VALUE_NEGATIVE_INFINITY = fixedToFloat(-0x80000000, 16)
2707
2907
  AXIS_VALUE_POSITIVE_INFINITY = fixedToFloat(0x7FFFFFFF, 16)
2708
2908
 
2909
+ STATName = Union[int, str, Dict[str, str]]
2910
+ """A raw name ID, English name, or multilingual name."""
2911
+
2709
2912
 
2710
2913
  def buildStatTable(
2711
- ttFont, axes, locations=None, elidedFallbackName=2, windowsNames=True, macNames=True
2712
- ):
2914
+ ttFont: TTFont,
2915
+ axes,
2916
+ locations=None,
2917
+ elidedFallbackName: Union[STATName, STATNameStatement] = 2,
2918
+ windowsNames: bool = True,
2919
+ macNames: bool = True,
2920
+ ) -> None:
2713
2921
  """Add a 'STAT' table to 'ttFont'.
2714
2922
 
2715
2923
  'axes' is a list of dictionaries describing axes and their
@@ -2900,7 +3108,13 @@ def _buildAxisValuesFormat4(locations, axes, ttFont, windowsNames=True, macNames
2900
3108
  return axisValues
2901
3109
 
2902
3110
 
2903
- def _addName(ttFont, value, minNameID=0, windows=True, mac=True):
3111
+ def _addName(
3112
+ ttFont: TTFont,
3113
+ value: Union[STATName, STATNameStatement],
3114
+ minNameID: int = 0,
3115
+ windows: bool = True,
3116
+ mac: bool = True,
3117
+ ) -> int:
2904
3118
  nameTable = ttFont["name"]
2905
3119
  if isinstance(value, int):
2906
3120
  # Already a nameID