2021-03-13 22:30:27 +00:00
|
|
|
from enum import Enum, auto
|
|
|
|
import textwrap
|
|
|
|
|
|
|
|
|
|
|
|
class VarLibMergeFailure(Enum):
|
|
|
|
ShouldBeConstant = "some values were different, but should have been the same"
|
|
|
|
MismatchedTypes = "data had inconsistent types"
|
|
|
|
LengthsDiffer = "a list of objects had inconsistent lengths"
|
|
|
|
KeysDiffer = "a list of objects had different keys"
|
|
|
|
InconsistentGlyphOrder = "the glyph order was inconsistent between masters"
|
|
|
|
FoundANone = "one of the values in a list was empty when it shouldn't have been"
|
|
|
|
NotANone = "one of the values in a list was not empty when it should have been"
|
|
|
|
UnsupportedFormat = "an OpenType subtable (%s) had a format I didn't expect"
|
|
|
|
InconsistentFormat = (
|
|
|
|
"an OpenType subtable (%s) had inconsistent formats between masters"
|
|
|
|
)
|
|
|
|
InconsistentExtensions = "the masters use extension lookups in inconsistent ways"
|
|
|
|
|
|
|
|
|
2020-02-11 13:41:51 +00:00
|
|
|
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."""
|
|
|
|
|
2021-03-13 22:30:27 +00:00
|
|
|
def __init__(self, merger, args):
|
|
|
|
self.merger = merger
|
|
|
|
self.args = args
|
|
|
|
|
2021-03-15 12:50:47 +00:00
|
|
|
def _master_name(self, ix):
|
|
|
|
ttf = self.merger.ttfs[ix]
|
2021-03-17 11:52:11 +00:00
|
|
|
if (
|
|
|
|
"name" in ttf
|
|
|
|
and ttf["name"].getDebugName(1)
|
|
|
|
and ttf["name"].getDebugName(2)
|
|
|
|
):
|
2021-03-15 12:00:31 +00:00
|
|
|
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
|
|
|
|
else:
|
|
|
|
return "master number %i" % ix
|
|
|
|
|
2021-03-15 12:50:47 +00:00
|
|
|
def _offender(self, cause):
|
|
|
|
reason = cause["reason"].value
|
|
|
|
if "expected" in cause and "got" in cause:
|
|
|
|
index = [x == cause["expected"] for x in cause["got"]].index(False)
|
|
|
|
return index, self._master_name(index)
|
|
|
|
if reason == VarLibMergeFailure.FoundANone:
|
|
|
|
index = [x is None for x in cause["got"]].index(True)
|
|
|
|
return index, self._master_name(index)
|
|
|
|
return None, None
|
|
|
|
|
2021-03-15 12:51:02 +00:00
|
|
|
def _incompatible_features(self, offender_index):
|
|
|
|
cause, stack = self.args[0], self.args[1:]
|
|
|
|
bad_ttf = self.merger.ttfs[offender_index]
|
|
|
|
good_ttf = self.merger.ttfs[offender_index - 1]
|
|
|
|
|
|
|
|
good_features = [
|
|
|
|
x.FeatureTag for x in good_ttf[stack[-1]].table.FeatureList.FeatureRecord
|
|
|
|
]
|
|
|
|
bad_features = [
|
|
|
|
x.FeatureTag for x in bad_ttf[stack[-1]].table.FeatureList.FeatureRecord
|
|
|
|
]
|
|
|
|
return (
|
|
|
|
"\nIncompatible features between masters.\n"
|
|
|
|
f"Expected: {', '.join(good_features)}.\n"
|
|
|
|
f"Got: {', '.join(bad_features)}.\n"
|
|
|
|
)
|
|
|
|
|
2021-03-13 22:30:27 +00:00
|
|
|
def __str__(self):
|
|
|
|
cause, stack = self.args[0], self.args[1:]
|
|
|
|
context = "".join(reversed(stack))
|
|
|
|
details = ""
|
|
|
|
reason = cause["reason"].value
|
2021-03-15 12:50:47 +00:00
|
|
|
offender_index, offender = self._offender(cause)
|
|
|
|
if offender:
|
|
|
|
details = f"\n\nThe problem is likely to be in {offender}:\n"
|
|
|
|
if cause["reason"] == VarLibMergeFailure.FoundANone:
|
|
|
|
details = details + f"{stack[0]}=={cause['got']}\n"
|
2021-03-17 11:52:50 +00:00
|
|
|
elif (
|
|
|
|
cause["reason"] == VarLibMergeFailure.ShouldBeConstant
|
|
|
|
and stack[0] == ".FeatureCount"
|
|
|
|
):
|
2021-03-15 12:51:02 +00:00
|
|
|
# Common case
|
|
|
|
details = details + self._incompatible_features(offender_index)
|
2021-03-13 22:30:27 +00:00
|
|
|
elif "expected" in cause and "got" in cause:
|
|
|
|
offender = [x == cause["expected"] for x in cause["got"]].index(False)
|
|
|
|
got = cause["got"][offender]
|
2021-03-15 12:50:47 +00:00
|
|
|
details = details + (
|
2021-03-13 22:30:27 +00:00
|
|
|
f"Expected to see {stack[0]}=={cause['expected']}, instead saw {got}\n"
|
|
|
|
)
|
|
|
|
|
|
|
|
if (
|
2021-03-15 12:50:47 +00:00
|
|
|
cause["reason"] == VarLibMergeFailure.UnsupportedFormat
|
|
|
|
or cause["reason"] == VarLibMergeFailure.InconsistentFormat
|
2021-03-13 22:30:27 +00:00
|
|
|
):
|
|
|
|
reason = reason % cause["subtable"]
|
|
|
|
basic = textwrap.fill(
|
|
|
|
f"Couldn't merge the fonts, because {reason}. "
|
|
|
|
f"This happened while performing the following operation: {context}",
|
|
|
|
width=78,
|
|
|
|
)
|
|
|
|
return "\n\n" + basic + details
|
|
|
|
|
2020-02-11 13:41:51 +00:00
|
|
|
|
|
|
|
class VarLibCFFDictMergeError(VarLibMergeError):
|
|
|
|
"""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(VarLibMergeError):
|
2021-03-05 08:47:33 -08:00
|
|
|
"""Raised when a CFF glyph cannot be merged because of point type differences."""
|
2020-02-11 13:41:51 +00:00
|
|
|
|
|
|
|
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,)
|
|
|
|
|
|
|
|
|
2021-03-05 08:47:33 -08:00
|
|
|
class VarLibCFFHintTypeMergeError(VarLibMergeError):
|
|
|
|
"""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,)
|
|
|
|
|
|
|
|
|
2020-02-11 13:41:51 +00:00
|
|
|
class VariationModelError(VarLibError):
|
|
|
|
"""Raised when a variation model is faulty."""
|