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