You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			220 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			220 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Python
		
	
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"].getBestFullName():
 | 
						|
                return ttf["name"].getBestFullName()
 | 
						|
            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
 | 
						|
            )
 | 
						|
            master_name = self._master_name(index)
 | 
						|
            if "location" in self.cause:
 | 
						|
                master_name = f"{master_name} ({self.cause['location']})"
 | 
						|
            return index, master_name
 | 
						|
        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']!r}, instead saw {got!r}\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):
 | 
						|
        basic_message = super().details
 | 
						|
 | 
						|
        if self.stack[0] != ".FeatureCount" or self.merger is None:
 | 
						|
            return basic_message
 | 
						|
 | 
						|
        assert self.stack[0] == ".FeatureCount"
 | 
						|
        offender_index, _ = self.offender
 | 
						|
        bad_ttf = self.merger.ttfs[offender_index]
 | 
						|
        good_ttf = next(
 | 
						|
            ttf
 | 
						|
            for ttf in self.merger.ttfs
 | 
						|
            if self.stack[-1] in ttf
 | 
						|
            and ttf[self.stack[-1]].table.FeatureList.FeatureCount
 | 
						|
            == self.cause["expected"]
 | 
						|
        )
 | 
						|
 | 
						|
        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 basic_message + (
 | 
						|
            "\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 NotANone(VarLibMergeError):
 | 
						|
    """one of the values in a list was not empty when it should have been"""
 | 
						|
 | 
						|
    @property
 | 
						|
    def offender(self):
 | 
						|
        index = [x is not 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"""
 | 
						|
 | 
						|
    def __init__(self, merger=None, **kwargs):
 | 
						|
        super().__init__(merger, **kwargs)
 | 
						|
        if not self.stack:
 | 
						|
            self.stack = [".Format"]
 | 
						|
 | 
						|
    @property
 | 
						|
    def reason(self):
 | 
						|
        s = self.__doc__ % self.cause["subtable"]
 | 
						|
        if "value" in self.cause:
 | 
						|
            s += f" ({self.cause['value']!r})"
 | 
						|
        return s
 | 
						|
 | 
						|
 | 
						|
class InconsistentFormats(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."""
 |