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