1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
|
import textwrap
class VarLibError(Exception):
"""Base exception for the varLib module."""
class VarLibValidationError(VarLibError):
"""Raised when input data is invalid from varLib's point of view."""
class VarLibMergeError(VarLibError):
"""Raised when input data cannot be merged into a variable font."""
def __init__(self, merger=None, **kwargs):
self.merger = merger
if not kwargs:
kwargs = {}
if "stack" in kwargs:
self.stack = kwargs["stack"]
del kwargs["stack"]
else:
self.stack = []
self.cause = kwargs
@property
def reason(self):
return self.__doc__
def _master_name(self, ix):
if self.merger is not None:
ttf = self.merger.ttfs[ix]
if "name" in ttf and ttf["name"].getBestFullName():
return ttf["name"].getBestFullName()
elif hasattr(ttf.reader, "file") and hasattr(ttf.reader.file, "name"):
return ttf.reader.file.name
return f"master number {ix}"
@property
def offender(self):
if "expected" in self.cause and "got" in self.cause:
index = [x == self.cause["expected"] for x in self.cause["got"]].index(
False
)
master_name = self._master_name(index)
if "location" in self.cause:
master_name = f"{master_name} ({self.cause['location']})"
return index, master_name
return None, None
@property
def details(self):
if "expected" in self.cause and "got" in self.cause:
offender_index, offender = self.offender
got = self.cause["got"][offender_index]
return f"Expected to see {self.stack[0]}=={self.cause['expected']!r}, instead saw {got!r}\n"
return ""
def __str__(self):
offender_index, offender = self.offender
location = ""
if offender:
location = f"\n\nThe problem is likely to be in {offender}:\n"
context = "".join(reversed(self.stack))
basic = textwrap.fill(
f"Couldn't merge the fonts, because {self.reason}. "
f"This happened while performing the following operation: {context}",
width=78,
)
return "\n\n" + basic + location + self.details
class ShouldBeConstant(VarLibMergeError):
"""some values were different, but should have been the same"""
@property
def details(self):
basic_message = super().details
if self.stack[0] != ".FeatureCount" or self.merger is None:
return basic_message
assert self.stack[0] == ".FeatureCount"
offender_index, _ = self.offender
bad_ttf = self.merger.ttfs[offender_index]
good_ttf = next(
ttf
for ttf in self.merger.ttfs
if self.stack[-1] in ttf
and ttf[self.stack[-1]].table.FeatureList.FeatureCount
== self.cause["expected"]
)
good_features = [
x.FeatureTag
for x in good_ttf[self.stack[-1]].table.FeatureList.FeatureRecord
]
bad_features = [
x.FeatureTag
for x in bad_ttf[self.stack[-1]].table.FeatureList.FeatureRecord
]
return basic_message + (
"\nIncompatible features between masters.\n"
f"Expected: {', '.join(good_features)}.\n"
f"Got: {', '.join(bad_features)}.\n"
)
class FoundANone(VarLibMergeError):
"""one of the values in a list was empty when it shouldn't have been"""
@property
def offender(self):
index = [x is None for x in self.cause["got"]].index(True)
return index, self._master_name(index)
@property
def details(self):
cause, stack = self.cause, self.stack
return f"{stack[0]}=={cause['got']}\n"
class NotANone(VarLibMergeError):
"""one of the values in a list was not empty when it should have been"""
@property
def offender(self):
index = [x is not None for x in self.cause["got"]].index(True)
return index, self._master_name(index)
@property
def details(self):
cause, stack = self.cause, self.stack
return f"{stack[0]}=={cause['got']}\n"
class MismatchedTypes(VarLibMergeError):
"""data had inconsistent types"""
class LengthsDiffer(VarLibMergeError):
"""a list of objects had inconsistent lengths"""
class KeysDiffer(VarLibMergeError):
"""a list of objects had different keys"""
class InconsistentGlyphOrder(VarLibMergeError):
"""the glyph order was inconsistent between masters"""
class InconsistentExtensions(VarLibMergeError):
"""the masters use extension lookups in inconsistent ways"""
class UnsupportedFormat(VarLibMergeError):
"""an OpenType subtable (%s) had a format I didn't expect"""
def __init__(self, merger=None, **kwargs):
super().__init__(merger, **kwargs)
if not self.stack:
self.stack = [".Format"]
@property
def reason(self):
s = self.__doc__ % self.cause["subtable"]
if "value" in self.cause:
s += f" ({self.cause['value']!r})"
return s
class InconsistentFormats(UnsupportedFormat):
"""an OpenType subtable (%s) had inconsistent formats between masters"""
class VarLibCFFMergeError(VarLibError):
pass
class VarLibCFFDictMergeError(VarLibCFFMergeError):
"""Raised when a CFF PrivateDict cannot be merged."""
def __init__(self, key, value, values):
error_msg = (
f"For the Private Dict key '{key}', the default font value list:"
f"\n\t{value}\nhad a different number of values than a region font:"
)
for region_value in values:
error_msg += f"\n\t{region_value}"
self.args = (error_msg,)
class VarLibCFFPointTypeMergeError(VarLibCFFMergeError):
"""Raised when a CFF glyph cannot be merged because of point type differences."""
def __init__(self, point_type, pt_index, m_index, default_type, glyph_name):
error_msg = (
f"Glyph '{glyph_name}': '{point_type}' at point index {pt_index} in "
f"master index {m_index} differs from the default font point type "
f"'{default_type}'"
)
self.args = (error_msg,)
class VarLibCFFHintTypeMergeError(VarLibCFFMergeError):
"""Raised when a CFF glyph cannot be merged because of hint type differences."""
def __init__(self, hint_type, cmd_index, m_index, default_type, glyph_name):
error_msg = (
f"Glyph '{glyph_name}': '{hint_type}' at index {cmd_index} in "
f"master index {m_index} differs from the default font hint type "
f"'{default_type}'"
)
self.args = (error_msg,)
class VariationModelError(VarLibError):
"""Raised when a variation model is faulty."""
|