errors.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import textwrap
  2. class VarLibError(Exception):
  3. """Base exception for the varLib module."""
  4. class VarLibValidationError(VarLibError):
  5. """Raised when input data is invalid from varLib's point of view."""
  6. class VarLibMergeError(VarLibError):
  7. """Raised when input data cannot be merged into a variable font."""
  8. def __init__(self, merger=None, **kwargs):
  9. self.merger = merger
  10. if not kwargs:
  11. kwargs = {}
  12. if "stack" in kwargs:
  13. self.stack = kwargs["stack"]
  14. del kwargs["stack"]
  15. else:
  16. self.stack = []
  17. self.cause = kwargs
  18. @property
  19. def reason(self):
  20. return self.__doc__
  21. def _master_name(self, ix):
  22. if self.merger is not None:
  23. ttf = self.merger.ttfs[ix]
  24. if "name" in ttf and ttf["name"].getBestFullName():
  25. return ttf["name"].getBestFullName()
  26. elif hasattr(ttf.reader, "file") and hasattr(ttf.reader.file, "name"):
  27. return ttf.reader.file.name
  28. return f"master number {ix}"
  29. @property
  30. def offender(self):
  31. if "expected" in self.cause and "got" in self.cause:
  32. index = [x == self.cause["expected"] for x in self.cause["got"]].index(
  33. False
  34. )
  35. master_name = self._master_name(index)
  36. if "location" in self.cause:
  37. master_name = f"{master_name} ({self.cause['location']})"
  38. return index, master_name
  39. return None, None
  40. @property
  41. def details(self):
  42. if "expected" in self.cause and "got" in self.cause:
  43. offender_index, offender = self.offender
  44. got = self.cause["got"][offender_index]
  45. return f"Expected to see {self.stack[0]}=={self.cause['expected']!r}, instead saw {got!r}\n"
  46. return ""
  47. def __str__(self):
  48. offender_index, offender = self.offender
  49. location = ""
  50. if offender:
  51. location = f"\n\nThe problem is likely to be in {offender}:\n"
  52. context = "".join(reversed(self.stack))
  53. basic = textwrap.fill(
  54. f"Couldn't merge the fonts, because {self.reason}. "
  55. f"This happened while performing the following operation: {context}",
  56. width=78,
  57. )
  58. return "\n\n" + basic + location + self.details
  59. class ShouldBeConstant(VarLibMergeError):
  60. """some values were different, but should have been the same"""
  61. @property
  62. def details(self):
  63. basic_message = super().details
  64. if self.stack[0] != ".FeatureCount" or self.merger is None:
  65. return basic_message
  66. assert self.stack[0] == ".FeatureCount"
  67. offender_index, _ = self.offender
  68. bad_ttf = self.merger.ttfs[offender_index]
  69. good_ttf = next(
  70. ttf
  71. for ttf in self.merger.ttfs
  72. if self.stack[-1] in ttf
  73. and ttf[self.stack[-1]].table.FeatureList.FeatureCount
  74. == self.cause["expected"]
  75. )
  76. good_features = [
  77. x.FeatureTag
  78. for x in good_ttf[self.stack[-1]].table.FeatureList.FeatureRecord
  79. ]
  80. bad_features = [
  81. x.FeatureTag
  82. for x in bad_ttf[self.stack[-1]].table.FeatureList.FeatureRecord
  83. ]
  84. return basic_message + (
  85. "\nIncompatible features between masters.\n"
  86. f"Expected: {', '.join(good_features)}.\n"
  87. f"Got: {', '.join(bad_features)}.\n"
  88. )
  89. class FoundANone(VarLibMergeError):
  90. """one of the values in a list was empty when it shouldn't have been"""
  91. @property
  92. def offender(self):
  93. index = [x is None for x in self.cause["got"]].index(True)
  94. return index, self._master_name(index)
  95. @property
  96. def details(self):
  97. cause, stack = self.cause, self.stack
  98. return f"{stack[0]}=={cause['got']}\n"
  99. class NotANone(VarLibMergeError):
  100. """one of the values in a list was not empty when it should have been"""
  101. @property
  102. def offender(self):
  103. index = [x is not None for x in self.cause["got"]].index(True)
  104. return index, self._master_name(index)
  105. @property
  106. def details(self):
  107. cause, stack = self.cause, self.stack
  108. return f"{stack[0]}=={cause['got']}\n"
  109. class MismatchedTypes(VarLibMergeError):
  110. """data had inconsistent types"""
  111. class LengthsDiffer(VarLibMergeError):
  112. """a list of objects had inconsistent lengths"""
  113. class KeysDiffer(VarLibMergeError):
  114. """a list of objects had different keys"""
  115. class InconsistentGlyphOrder(VarLibMergeError):
  116. """the glyph order was inconsistent between masters"""
  117. class InconsistentExtensions(VarLibMergeError):
  118. """the masters use extension lookups in inconsistent ways"""
  119. class UnsupportedFormat(VarLibMergeError):
  120. """an OpenType subtable (%s) had a format I didn't expect"""
  121. def __init__(self, merger=None, **kwargs):
  122. super().__init__(merger, **kwargs)
  123. if not self.stack:
  124. self.stack = [".Format"]
  125. @property
  126. def reason(self):
  127. s = self.__doc__ % self.cause["subtable"]
  128. if "value" in self.cause:
  129. s += f" ({self.cause['value']!r})"
  130. return s
  131. class InconsistentFormats(UnsupportedFormat):
  132. """an OpenType subtable (%s) had inconsistent formats between masters"""
  133. class VarLibCFFMergeError(VarLibError):
  134. pass
  135. class VarLibCFFDictMergeError(VarLibCFFMergeError):
  136. """Raised when a CFF PrivateDict cannot be merged."""
  137. def __init__(self, key, value, values):
  138. error_msg = (
  139. f"For the Private Dict key '{key}', the default font value list:"
  140. f"\n\t{value}\nhad a different number of values than a region font:"
  141. )
  142. for region_value in values:
  143. error_msg += f"\n\t{region_value}"
  144. self.args = (error_msg,)
  145. class VarLibCFFPointTypeMergeError(VarLibCFFMergeError):
  146. """Raised when a CFF glyph cannot be merged because of point type differences."""
  147. def __init__(self, point_type, pt_index, m_index, default_type, glyph_name):
  148. error_msg = (
  149. f"Glyph '{glyph_name}': '{point_type}' at point index {pt_index} in "
  150. f"master index {m_index} differs from the default font point type "
  151. f"'{default_type}'"
  152. )
  153. self.args = (error_msg,)
  154. class VarLibCFFHintTypeMergeError(VarLibCFFMergeError):
  155. """Raised when a CFF glyph cannot be merged because of hint type differences."""
  156. def __init__(self, hint_type, cmd_index, m_index, default_type, glyph_name):
  157. error_msg = (
  158. f"Glyph '{glyph_name}': '{hint_type}' at index {cmd_index} in "
  159. f"master index {m_index} differs from the default font hint type "
  160. f"'{default_type}'"
  161. )
  162. self.args = (error_msg,)
  163. class VariationModelError(VarLibError):
  164. """Raised when a variation model is faulty."""