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