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