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.
- fontTools/__init__.py +1 -1
- fontTools/cffLib/__init__.py +61 -26
- fontTools/cffLib/specializer.py +4 -1
- fontTools/cu2qu/cu2qu.cp39-win_amd64.pyd +0 -0
- fontTools/designspaceLib/statNames.py +14 -7
- fontTools/feaLib/ast.py +12 -9
- fontTools/feaLib/builder.py +75 -49
- fontTools/feaLib/lexer.cp39-win_amd64.pyd +0 -0
- fontTools/feaLib/parser.py +1 -39
- fontTools/fontBuilder.py +6 -0
- fontTools/merge/cmap.py +33 -1
- fontTools/merge/tables.py +12 -1
- fontTools/misc/bezierTools.cp39-win_amd64.pyd +0 -0
- fontTools/misc/etree.py +4 -27
- fontTools/misc/loggingTools.py +1 -1
- fontTools/misc/symfont.py +6 -8
- fontTools/mtiLib/__init__.py +1 -3
- fontTools/otlLib/builder.py +359 -145
- fontTools/otlLib/optimize/gpos.py +42 -62
- fontTools/pens/momentsPen.cp39-win_amd64.pyd +0 -0
- fontTools/pens/pointPen.py +21 -12
- fontTools/pens/t2CharStringPen.py +31 -11
- fontTools/qu2cu/qu2cu.cp39-win_amd64.pyd +0 -0
- fontTools/subset/__init__.py +12 -1
- 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 +3 -3
- 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 +20 -6
- fontTools/varLib/featureVars.py +13 -7
- fontTools/varLib/hvar.py +1 -1
- fontTools/varLib/instancer/__init__.py +14 -5
- fontTools/varLib/iup.cp39-win_amd64.pyd +0 -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.1.dist-info}/METADATA +64 -11
- {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/RECORD +61 -63
- {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/WHEEL +1 -1
- fonttools-4.58.1.dist-info/licenses/LICENSE.external +359 -0
- fontTools/cu2qu/cu2qu.c +0 -14829
- fontTools/feaLib/lexer.c +0 -17986
- fontTools/misc/bezierTools.c +0 -41831
- fontTools/pens/momentsPen.c +0 -13448
- fontTools/qu2cu/qu2cu.c +0 -16269
- fontTools/varLib/iup.c +0 -19154
- {fonttools-4.57.0.data → fonttools-4.58.1.data}/data/share/man/man1/ttx.1 +0 -0
- {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/entry_points.txt +0 -0
- {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/licenses/LICENSE +0 -0
- {fonttools-4.57.0.dist-info → fonttools-4.58.1.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,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(
|
|
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
|
-
|
|
953
|
-
|
|
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.
|
|
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 = {
|
|
998
|
-
|
|
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
|
-
|
|
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():
|
|
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
|
|
1079
|
+
"Mark class %s not found for mark glyph %s" % (mc, mark)
|
|
1023
1080
|
)
|
|
1024
|
-
|
|
1025
|
-
|
|
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.
|
|
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 = {
|
|
1074
|
-
|
|
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
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
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.
|
|
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 = {
|
|
1138
|
-
|
|
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
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
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
|
-
|
|
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])
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|