import textwrap class VarLibError(Exception): """Base exception for the varLib module.""" class VarLibValidationError(VarLibError): """Raised when input data is invalid from varLib's point of view.""" class VarLibMergeError(VarLibError): """Raised when input data cannot be merged into a variable font.""" def __init__(self, merger=None, **kwargs): self.merger = merger if not kwargs: kwargs = {} if "stack" in kwargs: self.stack = kwargs["stack"] del kwargs["stack"] else: self.stack = [] self.cause = kwargs @property def reason(self): return self.__doc__ def _master_name(self, ix): if self.merger is not None: ttf = self.merger.ttfs[ix] if ( "name" in ttf and ttf["name"].getDebugName(1) and ttf["name"].getDebugName(2) ): return ttf["name"].getDebugName(1) + " " + ttf["name"].getDebugName(2) elif hasattr(ttf.reader, "file") and hasattr(ttf.reader.file, "name"): return ttf.reader.file.name return f"master number {ix}" @property def offender(self): if "expected" in self.cause and "got" in self.cause: index = [x == self.cause["expected"] for x in self.cause["got"]].index( False ) return index, self._master_name(index) return None, None @property def details(self): if "expected" in self.cause and "got" in self.cause: offender_index, offender = self.offender got = self.cause["got"][offender_index] return f"Expected to see {self.stack[0]}=={self.cause['expected']}, instead saw {got}\n" return "" def __str__(self): offender_index, offender = self.offender location = "" if offender: location = f"\n\nThe problem is likely to be in {offender}:\n" context = "".join(reversed(self.stack)) basic = textwrap.fill( f"Couldn't merge the fonts, because {self.reason}. " f"This happened while performing the following operation: {context}", width=78, ) return "\n\n" + basic + location + self.details class ShouldBeConstant(VarLibMergeError): """some values were different, but should have been the same""" @property def details(self): if self.stack[0] != ".FeatureCount" or self.merger is None: return super().details offender_index, offender = self.offender bad_ttf = self.merger.ttfs[offender_index] good_ttf = self.merger.ttfs[offender_index - 1] good_features = [ x.FeatureTag for x in good_ttf[self.stack[-1]].table.FeatureList.FeatureRecord ] bad_features = [ x.FeatureTag for x in bad_ttf[self.stack[-1]].table.FeatureList.FeatureRecord ] return ( "\nIncompatible features between masters.\n" f"Expected: {', '.join(good_features)}.\n" f"Got: {', '.join(bad_features)}.\n" ) class FoundANone(VarLibMergeError): """one of the values in a list was empty when it shouldn't have been""" @property def offender(self): index = [x is None for x in self.cause["got"]].index(True) return index, self._master_name(index) @property def details(self): cause, stack = self.cause, self.stack return f"{stack[0]}=={cause['got']}\n" class MismatchedTypes(VarLibMergeError): """data had inconsistent types""" class LengthsDiffer(VarLibMergeError): """a list of objects had inconsistent lengths""" class KeysDiffer(VarLibMergeError): """a list of objects had different keys""" class InconsistentGlyphOrder(VarLibMergeError): """the glyph order was inconsistent between masters""" class InconsistentExtensions(VarLibMergeError): """the masters use extension lookups in inconsistent ways""" class UnsupportedFormat(VarLibMergeError): """an OpenType subtable (%s) had a format I didn't expect""" @property def reason(self): return self.__doc__ % self.cause["subtable"] class UnsupportedFormat(UnsupportedFormat): """an OpenType subtable (%s) had inconsistent formats between masters""" class VarLibCFFMergeError(VarLibError): pass class VarLibCFFDictMergeError(VarLibCFFMergeError): """Raised when a CFF PrivateDict cannot be merged.""" def __init__(self, key, value, values): error_msg = ( f"For the Private Dict key '{key}', the default font value list:" f"\n\t{value}\nhad a different number of values than a region font:" ) for region_value in values: error_msg += f"\n\t{region_value}" self.args = (error_msg,) class VarLibCFFPointTypeMergeError(VarLibCFFMergeError): """Raised when a CFF glyph cannot be merged because of point type differences.""" def __init__(self, point_type, pt_index, m_index, default_type, glyph_name): error_msg = ( f"Glyph '{glyph_name}': '{point_type}' at point index {pt_index} in " f"master index {m_index} differs from the default font point type " f"'{default_type}'" ) self.args = (error_msg,) class VarLibCFFHintTypeMergeError(VarLibCFFMergeError): """Raised when a CFF glyph cannot be merged because of hint type differences.""" def __init__(self, hint_type, cmd_index, m_index, default_type, glyph_name): error_msg = ( f"Glyph '{glyph_name}': '{hint_type}' at index {cmd_index} in " f"master index {m_index} differs from the default font hint type " f"'{default_type}'" ) self.args = (error_msg,) class VariationModelError(VarLibError): """Raised when a variation model is faulty."""