• 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=None, **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        if self.merger is not None:
32            ttf = self.merger.ttfs[ix]
33            if (
34                "name" in ttf
35                and ttf["name"].getDebugName(1)
36                and ttf["name"].getDebugName(2)
37            ):
38                return ttf["name"].getDebugName(1) + " " + ttf["name"].getDebugName(2)
39            elif hasattr(ttf.reader, "file") and hasattr(ttf.reader.file, "name"):
40                return ttf.reader.file.name
41        return f"master number {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" or self.merger is None:
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        index = [x is None for x in self.cause["got"]].index(True)
106        return index, self._master_name(index)
107
108    @property
109    def details(self):
110        cause, stack = self.cause, self.stack
111        return f"{stack[0]}=={cause['got']}\n"
112
113
114class MismatchedTypes(VarLibMergeError):
115    """data had inconsistent types"""
116
117
118class LengthsDiffer(VarLibMergeError):
119    """a list of objects had inconsistent lengths"""
120
121
122class KeysDiffer(VarLibMergeError):
123    """a list of objects had different keys"""
124
125
126class InconsistentGlyphOrder(VarLibMergeError):
127    """the glyph order was inconsistent between masters"""
128
129
130class InconsistentExtensions(VarLibMergeError):
131    """the masters use extension lookups in inconsistent ways"""
132
133
134class UnsupportedFormat(VarLibMergeError):
135    """an OpenType subtable (%s) had a format I didn't expect"""
136
137    @property
138    def reason(self):
139        return self.__doc__ % self.cause["subtable"]
140
141
142class UnsupportedFormat(UnsupportedFormat):
143    """an OpenType subtable (%s) had inconsistent formats between masters"""
144
145
146class VarLibCFFMergeError(VarLibError):
147    pass
148
149
150class VarLibCFFDictMergeError(VarLibCFFMergeError):
151    """Raised when a CFF PrivateDict cannot be merged."""
152
153    def __init__(self, key, value, values):
154        error_msg = (
155            f"For the Private Dict key '{key}', the default font value list:"
156            f"\n\t{value}\nhad a different number of values than a region font:"
157        )
158        for region_value in values:
159            error_msg += f"\n\t{region_value}"
160        self.args = (error_msg,)
161
162
163class VarLibCFFPointTypeMergeError(VarLibCFFMergeError):
164    """Raised when a CFF glyph cannot be merged because of point type differences."""
165
166    def __init__(self, point_type, pt_index, m_index, default_type, glyph_name):
167        error_msg = (
168            f"Glyph '{glyph_name}': '{point_type}' at point index {pt_index} in "
169            f"master index {m_index} differs from the default font point type "
170            f"'{default_type}'"
171        )
172        self.args = (error_msg,)
173
174
175class VarLibCFFHintTypeMergeError(VarLibCFFMergeError):
176    """Raised when a CFF glyph cannot be merged because of hint type differences."""
177
178    def __init__(self, hint_type, cmd_index, m_index, default_type, glyph_name):
179        error_msg = (
180            f"Glyph '{glyph_name}': '{hint_type}' at index {cmd_index} in "
181            f"master index {m_index} differs from the default font hint type "
182            f"'{default_type}'"
183        )
184        self.args = (error_msg,)
185
186
187class VariationModelError(VarLibError):
188    """Raised when a variation model is faulty."""
189