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