fonttools 4.57.0__py3-none-any.whl → 4.58.0__py3-none-any.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 (46) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/cffLib/__init__.py +61 -26
  3. fontTools/designspaceLib/statNames.py +14 -7
  4. fontTools/feaLib/ast.py +84 -10
  5. fontTools/feaLib/builder.py +20 -4
  6. fontTools/feaLib/parser.py +1 -39
  7. fontTools/fontBuilder.py +6 -0
  8. fontTools/misc/etree.py +4 -27
  9. fontTools/mtiLib/__init__.py +0 -2
  10. fontTools/otlLib/builder.py +195 -145
  11. fontTools/otlLib/optimize/gpos.py +42 -62
  12. fontTools/pens/pointPen.py +21 -12
  13. fontTools/subset/__init__.py +11 -0
  14. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  15. fontTools/ttLib/tables/T_S_I__0.py +14 -3
  16. fontTools/ttLib/tables/T_S_I__5.py +16 -5
  17. fontTools/ttLib/tables/__init__.py +1 -0
  18. fontTools/ttLib/tables/_c_v_t.py +2 -0
  19. fontTools/ttLib/tables/_f_p_g_m.py +3 -1
  20. fontTools/ttLib/tables/_g_l_y_f.py +2 -6
  21. fontTools/ttLib/tables/_g_v_a_r.py +58 -15
  22. fontTools/ttLib/tables/_p_o_s_t.py +5 -2
  23. fontTools/ttLib/tables/otBase.py +1 -0
  24. fontTools/ufoLib/__init__.py +2 -2
  25. fontTools/ufoLib/converters.py +89 -25
  26. fontTools/ufoLib/errors.py +8 -0
  27. fontTools/ufoLib/etree.py +1 -1
  28. fontTools/ufoLib/filenames.py +155 -100
  29. fontTools/ufoLib/glifLib.py +9 -2
  30. fontTools/ufoLib/kerning.py +66 -36
  31. fontTools/ufoLib/utils.py +5 -2
  32. fontTools/unicodedata/Mirrored.py +446 -0
  33. fontTools/unicodedata/__init__.py +6 -2
  34. fontTools/varLib/__init__.py +2 -0
  35. fontTools/voltLib/__main__.py +206 -0
  36. fontTools/voltLib/ast.py +4 -0
  37. fontTools/voltLib/parser.py +16 -8
  38. fontTools/voltLib/voltToFea.py +347 -166
  39. {fonttools-4.57.0.dist-info → fonttools-4.58.0.dist-info}/METADATA +45 -11
  40. {fonttools-4.57.0.dist-info → fonttools-4.58.0.dist-info}/RECORD +46 -42
  41. {fonttools-4.57.0.dist-info → fonttools-4.58.0.dist-info}/WHEEL +1 -1
  42. fonttools-4.58.0.dist-info/licenses/LICENSE.external +359 -0
  43. {fonttools-4.57.0.data → fonttools-4.58.0.data}/data/share/man/man1/ttx.1 +0 -0
  44. {fonttools-4.57.0.dist-info → fonttools-4.58.0.dist-info}/entry_points.txt +0 -0
  45. {fonttools-4.57.0.dist-info → fonttools-4.58.0.dist-info}/licenses/LICENSE +0 -0
  46. {fonttools-4.57.0.dist-info → fonttools-4.58.0.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,6 +167,7 @@ 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
 
154
173
  def inferGlyphClasses(self):
@@ -160,7 +179,13 @@ class LookupBuilder(object):
160
179
  return {}
161
180
 
162
181
  def buildLookup_(self, subtables):
163
- return buildLookup(subtables, self.lookupflag, self.markFilterSet)
182
+ return buildLookup(
183
+ subtables,
184
+ self.lookupflag,
185
+ self.markFilterSet,
186
+ self.table,
187
+ self.extension,
188
+ )
164
189
 
165
190
  def buildMarkClasses_(self, marks):
166
191
  """{"cedilla": ("BOTTOM", ast.Anchor), ...} --> {"BOTTOM":0, "TOP":1}
@@ -949,8 +974,20 @@ class CursivePosBuilder(LookupBuilder):
949
974
  An ``otTables.Lookup`` object representing the cursive
950
975
  positioning lookup.
951
976
  """
952
- st = buildCursivePosSubtable(self.attachments, self.glyphMap)
953
- return self.buildLookup_([st])
977
+ attachments = [{}]
978
+ for key in self.attachments:
979
+ if key[0] == self.SUBTABLE_BREAK_:
980
+ attachments.append({})
981
+ else:
982
+ attachments[-1][key] = self.attachments[key]
983
+ subtables = [buildCursivePosSubtable(s, self.glyphMap) for s in attachments]
984
+ return self.buildLookup_(subtables)
985
+
986
+ def add_subtable_break(self, location):
987
+ self.attachments[(self.SUBTABLE_BREAK_, location)] = (
988
+ self.SUBTABLE_BREAK_,
989
+ self.SUBTABLE_BREAK_,
990
+ )
954
991
 
955
992
 
956
993
  class MarkBasePosBuilder(LookupBuilder):
@@ -985,17 +1022,25 @@ class MarkBasePosBuilder(LookupBuilder):
985
1022
  LookupBuilder.__init__(self, font, location, "GPOS", 4)
986
1023
  self.marks = {} # glyphName -> (markClassName, anchor)
987
1024
  self.bases = {} # glyphName -> {markClassName: anchor}
1025
+ self.subtables_ = []
1026
+
1027
+ def get_subtables_(self):
1028
+ subtables_ = self.subtables_
1029
+ if self.bases or self.marks:
1030
+ subtables_.append((self.marks, self.bases))
1031
+ return subtables_
988
1032
 
989
1033
  def equals(self, other):
990
1034
  return (
991
1035
  LookupBuilder.equals(self, other)
992
- and self.marks == other.marks
993
- and self.bases == other.bases
1036
+ and self.get_subtables_() == other.get_subtables_()
994
1037
  )
995
1038
 
996
1039
  def inferGlyphClasses(self):
997
- result = {glyph: 1 for glyph in self.bases}
998
- result.update({glyph: 3 for glyph in self.marks})
1040
+ result = {}
1041
+ for marks, bases in self.get_subtables_():
1042
+ result.update({glyph: 1 for glyph in bases})
1043
+ result.update({glyph: 3 for glyph in marks})
999
1044
  return result
1000
1045
 
1001
1046
  def build(self):
@@ -1005,26 +1050,33 @@ class MarkBasePosBuilder(LookupBuilder):
1005
1050
  An ``otTables.Lookup`` object representing the mark-to-base
1006
1051
  positioning lookup.
1007
1052
  """
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():
1053
+ subtables = []
1054
+ for subtable in self.get_subtables_():
1055
+ markClasses = self.buildMarkClasses_(subtable[0])
1056
+ marks = {}
1057
+ for mark, (mc, anchor) in subtable[0].items():
1020
1058
  if mc not in markClasses:
1021
1059
  raise ValueError(
1022
- "Mark class %s not found for base glyph %s" % (mc, glyph)
1060
+ "Mark class %s not found for mark glyph %s" % (mc, mark)
1023
1061
  )
1024
- bases[glyph][markClasses[mc]] = anchor
1025
- subtables = buildMarkBasePos(marks, bases, self.glyphMap)
1062
+ marks[mark] = (markClasses[mc], anchor)
1063
+ bases = {}
1064
+ for glyph, anchors in subtable[1].items():
1065
+ bases[glyph] = {}
1066
+ for mc, anchor in anchors.items():
1067
+ if mc not in markClasses:
1068
+ raise ValueError(
1069
+ "Mark class %s not found for base glyph %s" % (mc, glyph)
1070
+ )
1071
+ bases[glyph][markClasses[mc]] = anchor
1072
+ subtables.append(buildMarkBasePosSubtable(marks, bases, self.glyphMap))
1026
1073
  return self.buildLookup_(subtables)
1027
1074
 
1075
+ def add_subtable_break(self, location):
1076
+ self.subtables_.append((self.marks, self.bases))
1077
+ self.marks = {}
1078
+ self.bases = {}
1079
+
1028
1080
 
1029
1081
  class MarkLigPosBuilder(LookupBuilder):
1030
1082
  """Builds a Mark-To-Ligature Positioning (GPOS5) lookup.
@@ -1061,17 +1113,25 @@ class MarkLigPosBuilder(LookupBuilder):
1061
1113
  LookupBuilder.__init__(self, font, location, "GPOS", 5)
1062
1114
  self.marks = {} # glyphName -> (markClassName, anchor)
1063
1115
  self.ligatures = {} # glyphName -> [{markClassName: anchor}, ...]
1116
+ self.subtables_ = []
1117
+
1118
+ def get_subtables_(self):
1119
+ subtables_ = self.subtables_
1120
+ if self.ligatures or self.marks:
1121
+ subtables_.append((self.marks, self.ligatures))
1122
+ return subtables_
1064
1123
 
1065
1124
  def equals(self, other):
1066
1125
  return (
1067
1126
  LookupBuilder.equals(self, other)
1068
- and self.marks == other.marks
1069
- and self.ligatures == other.ligatures
1127
+ and self.get_subtables_() == other.get_subtables_()
1070
1128
  )
1071
1129
 
1072
1130
  def inferGlyphClasses(self):
1073
- result = {glyph: 2 for glyph in self.ligatures}
1074
- result.update({glyph: 3 for glyph in self.marks})
1131
+ result = {}
1132
+ for marks, ligatures in self.get_subtables_():
1133
+ result.update({glyph: 2 for glyph in ligatures})
1134
+ result.update({glyph: 3 for glyph in marks})
1075
1135
  return result
1076
1136
 
1077
1137
  def build(self):
@@ -1081,18 +1141,26 @@ class MarkLigPosBuilder(LookupBuilder):
1081
1141
  An ``otTables.Lookup`` object representing the mark-to-ligature
1082
1142
  positioning lookup.
1083
1143
  """
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)
1144
+ subtables = []
1145
+ for subtable in self.get_subtables_():
1146
+ markClasses = self.buildMarkClasses_(subtable[0])
1147
+ marks = {
1148
+ mark: (markClasses[mc], anchor)
1149
+ for mark, (mc, anchor) in subtable[0].items()
1150
+ }
1151
+ ligs = {}
1152
+ for lig, components in subtable[1].items():
1153
+ ligs[lig] = []
1154
+ for c in components:
1155
+ ligs[lig].append({markClasses[mc]: a for mc, a in c.items()})
1156
+ subtables.append(buildMarkLigPosSubtable(marks, ligs, self.glyphMap))
1094
1157
  return self.buildLookup_(subtables)
1095
1158
 
1159
+ def add_subtable_break(self, location):
1160
+ self.subtables_.append((self.marks, self.ligatures))
1161
+ self.marks = {}
1162
+ self.ligatures = {}
1163
+
1096
1164
 
1097
1165
  class MarkMarkPosBuilder(LookupBuilder):
1098
1166
  """Builds a Mark-To-Mark Positioning (GPOS6) lookup.
@@ -1125,17 +1193,25 @@ class MarkMarkPosBuilder(LookupBuilder):
1125
1193
  LookupBuilder.__init__(self, font, location, "GPOS", 6)
1126
1194
  self.marks = {} # glyphName -> (markClassName, anchor)
1127
1195
  self.baseMarks = {} # glyphName -> {markClassName: anchor}
1196
+ self.subtables_ = []
1197
+
1198
+ def get_subtables_(self):
1199
+ subtables_ = self.subtables_
1200
+ if self.baseMarks or self.marks:
1201
+ subtables_.append((self.marks, self.baseMarks))
1202
+ return subtables_
1128
1203
 
1129
1204
  def equals(self, other):
1130
1205
  return (
1131
1206
  LookupBuilder.equals(self, other)
1132
- and self.marks == other.marks
1133
- and self.baseMarks == other.baseMarks
1207
+ and self.get_subtables_() == other.get_subtables_()
1134
1208
  )
1135
1209
 
1136
1210
  def inferGlyphClasses(self):
1137
- result = {glyph: 3 for glyph in self.baseMarks}
1138
- result.update({glyph: 3 for glyph in self.marks})
1211
+ result = {}
1212
+ for marks, baseMarks in self.get_subtables_():
1213
+ result.update({glyph: 3 for glyph in baseMarks})
1214
+ result.update({glyph: 3 for glyph in marks})
1139
1215
  return result
1140
1216
 
1141
1217
  def build(self):
@@ -1145,25 +1221,34 @@ class MarkMarkPosBuilder(LookupBuilder):
1145
1221
  An ``otTables.Lookup`` object representing the mark-to-mark
1146
1222
  positioning lookup.
1147
1223
  """
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
- }
1224
+ subtables = []
1225
+ for subtable in self.get_subtables_():
1226
+ markClasses = self.buildMarkClasses_(subtable[0])
1227
+ markClassList = sorted(markClasses.keys(), key=markClasses.get)
1228
+ marks = {
1229
+ mark: (markClasses[mc], anchor)
1230
+ for mark, (mc, anchor) in subtable[0].items()
1231
+ }
1232
+
1233
+ st = ot.MarkMarkPos()
1234
+ st.Format = 1
1235
+ st.ClassCount = len(markClasses)
1236
+ st.Mark1Coverage = buildCoverage(marks, self.glyphMap)
1237
+ st.Mark2Coverage = buildCoverage(subtable[1], self.glyphMap)
1238
+ st.Mark1Array = buildMarkArray(marks, self.glyphMap)
1239
+ st.Mark2Array = ot.Mark2Array()
1240
+ st.Mark2Array.Mark2Count = len(st.Mark2Coverage.glyphs)
1241
+ st.Mark2Array.Mark2Record = []
1242
+ for base in st.Mark2Coverage.glyphs:
1243
+ anchors = [subtable[1][base].get(mc) for mc in markClassList]
1244
+ st.Mark2Array.Mark2Record.append(buildMark2Record(anchors))
1245
+ subtables.append(st)
1246
+ return self.buildLookup_(subtables)
1153
1247
 
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])
1248
+ def add_subtable_break(self, location):
1249
+ self.subtables_.append((self.marks, self.baseMarks))
1250
+ self.marks = {}
1251
+ self.baseMarks = {}
1167
1252
 
1168
1253
 
1169
1254
  class ReverseChainSingleSubstBuilder(LookupBuilder):
@@ -1484,6 +1569,8 @@ class SinglePosBuilder(LookupBuilder):
1484
1569
  otValueRection: A ``otTables.ValueRecord`` used to position the
1485
1570
  glyph.
1486
1571
  """
1572
+ if otValueRecord is None:
1573
+ otValueRecord = ValueRecord()
1487
1574
  if not self.can_add(glyph, otValueRecord):
1488
1575
  otherLoc = self.locations[glyph]
1489
1576
  raise OpenTypeLibError(
@@ -1900,53 +1987,15 @@ def buildMarkArray(marks, glyphMap):
1900
1987
  return self
1901
1988
 
1902
1989
 
1990
+ @deprecateFunction(
1991
+ "use buildMarkBasePosSubtable() instead", category=DeprecationWarning
1992
+ )
1903
1993
  def buildMarkBasePos(marks, bases, glyphMap):
1904
1994
  """Build a list of MarkBasePos (GPOS4) subtables.
1905
1995
 
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.
1996
+ .. deprecated:: 4.58.0
1997
+ Use :func:`buildMarkBasePosSubtable` instead.
1938
1998
  """
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
1999
  return [buildMarkBasePosSubtable(marks, bases, glyphMap)]
1951
2000
 
1952
2001
 
@@ -1954,7 +2003,15 @@ def buildMarkBasePosSubtable(marks, bases, glyphMap):
1954
2003
  """Build a single MarkBasePos (GPOS4) subtable.
1955
2004
 
1956
2005
  This builds a mark-to-base lookup subtable containing all of the referenced
1957
- marks and bases. See :func:`buildMarkBasePos`.
2006
+ marks and bases.
2007
+
2008
+ Example::
2009
+
2010
+ # a1, a2, a3, a4, a5 = buildAnchor(500, 100), ...
2011
+
2012
+ marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)}
2013
+ bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}}
2014
+ markbaseposes = [buildMarkBasePosSubtable(marks, bases, font.getReverseGlyphMap())]
1958
2015
 
1959
2016
  Args:
1960
2017
  marks (dict): A dictionary mapping anchors to glyphs; the keys being
@@ -1981,14 +2038,21 @@ def buildMarkBasePosSubtable(marks, bases, glyphMap):
1981
2038
  return self
1982
2039
 
1983
2040
 
2041
+ @deprecateFunction("use buildMarkLigPosSubtable() instead", category=DeprecationWarning)
1984
2042
  def buildMarkLigPos(marks, ligs, glyphMap):
1985
2043
  """Build a list of MarkLigPos (GPOS5) subtables.
1986
2044
 
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.
2045
+ .. deprecated:: 4.58.0
2046
+ Use :func:`buildMarkLigPosSubtable` instead.
2047
+ """
2048
+ return [buildMarkLigPosSubtable(marks, ligs, glyphMap)]
2049
+
2050
+
2051
+ def buildMarkLigPosSubtable(marks, ligs, glyphMap):
2052
+ """Build a single MarkLigPos (GPOS5) subtable.
2053
+
2054
+ This builds a mark-to-base lookup subtable containing all of the referenced
2055
+ marks and bases.
1992
2056
 
1993
2057
  Note that if you are implementing a layout compiler, you may find it more
1994
2058
  flexible to use
@@ -2009,37 +2073,9 @@ def buildMarkLigPos(marks, ligs, glyphMap):
2009
2073
  ],
2010
2074
  # "c_t": [{...}, {...}]
2011
2075
  }
2012
- markligposes = buildMarkLigPos(marks, ligs,
2076
+ markligpose = buildMarkLigPosSubtable(marks, ligs,
2013
2077
  font.getReverseGlyphMap())
2014
2078
 
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
2079
  Args:
2044
2080
  marks (dict): A dictionary mapping anchors to glyphs; the keys being
2045
2081
  glyph names, and the values being a tuple of mark class number and
@@ -2706,10 +2742,18 @@ class ClassDefBuilder(object):
2706
2742
  AXIS_VALUE_NEGATIVE_INFINITY = fixedToFloat(-0x80000000, 16)
2707
2743
  AXIS_VALUE_POSITIVE_INFINITY = fixedToFloat(0x7FFFFFFF, 16)
2708
2744
 
2745
+ STATName = Union[int, str, Dict[str, str]]
2746
+ """A raw name ID, English name, or multilingual name."""
2747
+
2709
2748
 
2710
2749
  def buildStatTable(
2711
- ttFont, axes, locations=None, elidedFallbackName=2, windowsNames=True, macNames=True
2712
- ):
2750
+ ttFont: TTFont,
2751
+ axes,
2752
+ locations=None,
2753
+ elidedFallbackName: Union[STATName, STATNameStatement] = 2,
2754
+ windowsNames: bool = True,
2755
+ macNames: bool = True,
2756
+ ) -> None:
2713
2757
  """Add a 'STAT' table to 'ttFont'.
2714
2758
 
2715
2759
  'axes' is a list of dictionaries describing axes and their
@@ -2900,7 +2944,13 @@ def _buildAxisValuesFormat4(locations, axes, ttFont, windowsNames=True, macNames
2900
2944
  return axisValues
2901
2945
 
2902
2946
 
2903
- def _addName(ttFont, value, minNameID=0, windows=True, mac=True):
2947
+ def _addName(
2948
+ ttFont: TTFont,
2949
+ value: Union[STATName, STATNameStatement],
2950
+ minNameID: int = 0,
2951
+ windows: bool = True,
2952
+ mac: bool = True,
2953
+ ) -> int:
2904
2954
  nameTable = ttFont["name"]
2905
2955
  if isinstance(value, int):
2906
2956
  # Already a nameID
@@ -1,7 +1,8 @@
1
1
  import logging
2
2
  import os
3
3
  from collections import defaultdict, namedtuple
4
- from functools import reduce
4
+ from dataclasses import dataclass
5
+ from functools import cached_property, reduce
5
6
  from itertools import chain
6
7
  from math import log2
7
8
  from typing import DefaultDict, Dict, Iterable, List, Sequence, Tuple
@@ -192,79 +193,58 @@ ClusteringContext = namedtuple(
192
193
  )
193
194
 
194
195
 
196
+ @dataclass
195
197
  class Cluster:
196
- # TODO(Python 3.7): Turn this into a dataclass
197
- # ctx: ClusteringContext
198
- # indices: int
199
- # Caches
200
- # TODO(Python 3.8): use functools.cached_property instead of the
201
- # manually cached properties, and remove the cache fields listed below.
202
- # _indices: Optional[List[int]] = None
203
- # _column_indices: Optional[List[int]] = None
204
- # _cost: Optional[int] = None
205
-
206
- __slots__ = "ctx", "indices_bitmask", "_indices", "_column_indices", "_cost"
207
-
208
- def __init__(self, ctx: ClusteringContext, indices_bitmask: int):
209
- self.ctx = ctx
210
- self.indices_bitmask = indices_bitmask
211
- self._indices = None
212
- self._column_indices = None
213
- self._cost = None
198
+ ctx: ClusteringContext
199
+ indices_bitmask: int
214
200
 
215
- @property
201
+ @cached_property
216
202
  def indices(self):
217
- if self._indices is None:
218
- self._indices = bit_indices(self.indices_bitmask)
219
- return self._indices
203
+ return bit_indices(self.indices_bitmask)
220
204
 
221
- @property
205
+ @cached_property
222
206
  def column_indices(self):
223
- if self._column_indices is None:
224
- # Indices of columns that have a 1 in at least 1 line
225
- # => binary OR all the lines
226
- bitmask = reduce(int.__or__, (self.ctx.lines[i] for i in self.indices))
227
- self._column_indices = bit_indices(bitmask)
228
- return self._column_indices
207
+ # Indices of columns that have a 1 in at least 1 line
208
+ # => binary OR all the lines
209
+ bitmask = reduce(int.__or__, (self.ctx.lines[i] for i in self.indices))
210
+ return bit_indices(bitmask)
229
211
 
230
212
  @property
231
213
  def width(self):
232
214
  # Add 1 because Class2=0 cannot be used but needs to be encoded.
233
215
  return len(self.column_indices) + 1
234
216
 
235
- @property
217
+ @cached_property
236
218
  def cost(self):
237
- if self._cost is None:
238
- self._cost = (
239
- # 2 bytes to store the offset to this subtable in the Lookup table above
240
- 2
241
- # Contents of the subtable
242
- # From: https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#pair-adjustment-positioning-format-2-class-pair-adjustment
243
- # uint16 posFormat Format identifier: format = 2
244
- + 2
245
- # Offset16 coverageOffset Offset to Coverage table, from beginning of PairPos subtable.
246
- + 2
247
- + self.coverage_bytes
248
- # uint16 valueFormat1 ValueRecord definition — for the first glyph of the pair (may be zero).
249
- + 2
250
- # uint16 valueFormat2 ValueRecord definition — for the second glyph of the pair (may be zero).
251
- + 2
252
- # Offset16 classDef1Offset Offset to ClassDef table, from beginning of PairPos subtable — for the first glyph of the pair.
253
- + 2
254
- + self.classDef1_bytes
255
- # Offset16 classDef2Offset Offset to ClassDef table, from beginning of PairPos subtable — for the second glyph of the pair.
256
- + 2
257
- + self.classDef2_bytes
258
- # uint16 class1Count Number of classes in classDef1 table — includes Class 0.
259
- + 2
260
- # uint16 class2Count Number of classes in classDef2 table — includes Class 0.
261
- + 2
262
- # Class1Record class1Records[class1Count] Array of Class1 records, ordered by classes in classDef1.
263
- + (self.ctx.valueFormat1_bytes + self.ctx.valueFormat2_bytes)
264
- * len(self.indices)
265
- * self.width
266
- )
267
- return self._cost
219
+ return (
220
+ # 2 bytes to store the offset to this subtable in the Lookup table above
221
+ 2
222
+ # Contents of the subtable
223
+ # From: https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#pair-adjustment-positioning-format-2-class-pair-adjustment
224
+ # uint16 posFormat Format identifier: format = 2
225
+ + 2
226
+ # Offset16 coverageOffset Offset to Coverage table, from beginning of PairPos subtable.
227
+ + 2
228
+ + self.coverage_bytes
229
+ # uint16 valueFormat1 ValueRecord definition — for the first glyph of the pair (may be zero).
230
+ + 2
231
+ # uint16 valueFormat2 ValueRecord definition — for the second glyph of the pair (may be zero).
232
+ + 2
233
+ # Offset16 classDef1Offset Offset to ClassDef table, from beginning of PairPos subtable — for the first glyph of the pair.
234
+ + 2
235
+ + self.classDef1_bytes
236
+ # Offset16 classDef2Offset Offset to ClassDef table, from beginning of PairPos subtable — for the second glyph of the pair.
237
+ + 2
238
+ + self.classDef2_bytes
239
+ # uint16 class1Count Number of classes in classDef1 table — includes Class 0.
240
+ + 2
241
+ # uint16 class2Count Number of classes in classDef2 table — includes Class 0.
242
+ + 2
243
+ # Class1Record class1Records[class1Count] Array of Class1 records, ordered by classes in classDef1.
244
+ + (self.ctx.valueFormat1_bytes + self.ctx.valueFormat2_bytes)
245
+ * len(self.indices)
246
+ * self.width
247
+ )
268
248
 
269
249
  @property
270
250
  def coverage_bytes(self):