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."""
|