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