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.
- fontTools/__init__.py +1 -1
- fontTools/cffLib/__init__.py +61 -26
- fontTools/designspaceLib/statNames.py +14 -7
- fontTools/feaLib/ast.py +84 -10
- fontTools/feaLib/builder.py +20 -4
- fontTools/feaLib/parser.py +1 -39
- fontTools/fontBuilder.py +6 -0
- fontTools/misc/etree.py +4 -27
- fontTools/mtiLib/__init__.py +0 -2
- fontTools/otlLib/builder.py +195 -145
- fontTools/otlLib/optimize/gpos.py +42 -62
- fontTools/pens/pointPen.py +21 -12
- fontTools/subset/__init__.py +11 -0
- fontTools/ttLib/tables/G_V_A_R_.py +5 -0
- fontTools/ttLib/tables/T_S_I__0.py +14 -3
- fontTools/ttLib/tables/T_S_I__5.py +16 -5
- fontTools/ttLib/tables/__init__.py +1 -0
- fontTools/ttLib/tables/_c_v_t.py +2 -0
- fontTools/ttLib/tables/_f_p_g_m.py +3 -1
- fontTools/ttLib/tables/_g_l_y_f.py +2 -6
- fontTools/ttLib/tables/_g_v_a_r.py +58 -15
- fontTools/ttLib/tables/_p_o_s_t.py +5 -2
- fontTools/ttLib/tables/otBase.py +1 -0
- fontTools/ufoLib/__init__.py +2 -2
- fontTools/ufoLib/converters.py +89 -25
- fontTools/ufoLib/errors.py +8 -0
- fontTools/ufoLib/etree.py +1 -1
- fontTools/ufoLib/filenames.py +155 -100
- fontTools/ufoLib/glifLib.py +9 -2
- fontTools/ufoLib/kerning.py +66 -36
- fontTools/ufoLib/utils.py +5 -2
- fontTools/unicodedata/Mirrored.py +446 -0
- fontTools/unicodedata/__init__.py +6 -2
- fontTools/varLib/__init__.py +2 -0
- fontTools/voltLib/__main__.py +206 -0
- fontTools/voltLib/ast.py +4 -0
- fontTools/voltLib/parser.py +16 -8
- fontTools/voltLib/voltToFea.py +347 -166
- {fonttools-4.57.0.dist-info → fonttools-4.58.0.dist-info}/METADATA +45 -11
- {fonttools-4.57.0.dist-info → fonttools-4.58.0.dist-info}/RECORD +46 -42
- {fonttools-4.57.0.dist-info → fonttools-4.58.0.dist-info}/WHEEL +1 -1
- fonttools-4.58.0.dist-info/licenses/LICENSE.external +359 -0
- {fonttools-4.57.0.data → fonttools-4.58.0.data}/data/share/man/man1/ttx.1 +0 -0
- {fonttools-4.57.0.dist-info → fonttools-4.58.0.dist-info}/entry_points.txt +0 -0
- {fonttools-4.57.0.dist-info → fonttools-4.58.0.dist-info}/licenses/LICENSE +0 -0
- {fonttools-4.57.0.dist-info → fonttools-4.58.0.dist-info}/top_level.txt +0 -0
fontTools/otlLib/builder.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from collections import namedtuple, OrderedDict
|
|
2
4
|
import itertools
|
|
3
|
-
import
|
|
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.
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
953
|
-
|
|
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.
|
|
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 = {
|
|
998
|
-
|
|
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
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
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
|
|
1060
|
+
"Mark class %s not found for mark glyph %s" % (mc, mark)
|
|
1023
1061
|
)
|
|
1024
|
-
|
|
1025
|
-
|
|
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.
|
|
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 = {
|
|
1074
|
-
|
|
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
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
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.
|
|
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 = {
|
|
1138
|
-
|
|
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
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
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
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
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
|
-
|
|
1907
|
-
|
|
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.
|
|
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
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
197
|
-
|
|
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
|
-
@
|
|
201
|
+
@cached_property
|
|
216
202
|
def indices(self):
|
|
217
|
-
|
|
218
|
-
self._indices = bit_indices(self.indices_bitmask)
|
|
219
|
-
return self._indices
|
|
203
|
+
return bit_indices(self.indices_bitmask)
|
|
220
204
|
|
|
221
|
-
@
|
|
205
|
+
@cached_property
|
|
222
206
|
def column_indices(self):
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
@
|
|
217
|
+
@cached_property
|
|
236
218
|
def cost(self):
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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):
|