Coverage for /usr/lib/python3/dist-packages/fontTools/varLib/errors.py: 40%

113 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1import textwrap 

2 

3 

4class VarLibError(Exception): 

5 """Base exception for the varLib module.""" 

6 

7 

8class VarLibValidationError(VarLibError): 

9 """Raised when input data is invalid from varLib's point of view.""" 

10 

11 

12class VarLibMergeError(VarLibError): 

13 """Raised when input data cannot be merged into a variable font.""" 

14 

15 def __init__(self, merger=None, **kwargs): 

16 self.merger = merger 

17 if not kwargs: 

18 kwargs = {} 

19 if "stack" in kwargs: 

20 self.stack = kwargs["stack"] 

21 del kwargs["stack"] 

22 else: 

23 self.stack = [] 

24 self.cause = kwargs 

25 

26 @property 

27 def reason(self): 

28 return self.__doc__ 

29 

30 def _master_name(self, ix): 

31 if self.merger is not None: 

32 ttf = self.merger.ttfs[ix] 

33 if "name" in ttf and ttf["name"].getBestFullName(): 

34 return ttf["name"].getBestFullName() 

35 elif hasattr(ttf.reader, "file") and hasattr(ttf.reader.file, "name"): 

36 return ttf.reader.file.name 

37 return f"master number {ix}" 

38 

39 @property 

40 def offender(self): 

41 if "expected" in self.cause and "got" in self.cause: 

42 index = [x == self.cause["expected"] for x in self.cause["got"]].index( 

43 False 

44 ) 

45 master_name = self._master_name(index) 

46 if "location" in self.cause: 

47 master_name = f"{master_name} ({self.cause['location']})" 

48 return index, master_name 

49 return None, None 

50 

51 @property 

52 def details(self): 

53 if "expected" in self.cause and "got" in self.cause: 

54 offender_index, offender = self.offender 

55 got = self.cause["got"][offender_index] 

56 return f"Expected to see {self.stack[0]}=={self.cause['expected']!r}, instead saw {got!r}\n" 

57 return "" 

58 

59 def __str__(self): 

60 offender_index, offender = self.offender 

61 location = "" 

62 if offender: 

63 location = f"\n\nThe problem is likely to be in {offender}:\n" 

64 context = "".join(reversed(self.stack)) 

65 basic = textwrap.fill( 

66 f"Couldn't merge the fonts, because {self.reason}. " 

67 f"This happened while performing the following operation: {context}", 

68 width=78, 

69 ) 

70 return "\n\n" + basic + location + self.details 

71 

72 

73class ShouldBeConstant(VarLibMergeError): 

74 """some values were different, but should have been the same""" 

75 

76 @property 

77 def details(self): 

78 basic_message = super().details 

79 

80 if self.stack[0] != ".FeatureCount" or self.merger is None: 

81 return basic_message 

82 

83 assert self.stack[0] == ".FeatureCount" 

84 offender_index, _ = self.offender 

85 bad_ttf = self.merger.ttfs[offender_index] 

86 good_ttf = next( 

87 ttf 

88 for ttf in self.merger.ttfs 

89 if self.stack[-1] in ttf 

90 and ttf[self.stack[-1]].table.FeatureList.FeatureCount 

91 == self.cause["expected"] 

92 ) 

93 

94 good_features = [ 

95 x.FeatureTag 

96 for x in good_ttf[self.stack[-1]].table.FeatureList.FeatureRecord 

97 ] 

98 bad_features = [ 

99 x.FeatureTag 

100 for x in bad_ttf[self.stack[-1]].table.FeatureList.FeatureRecord 

101 ] 

102 return basic_message + ( 

103 "\nIncompatible features between masters.\n" 

104 f"Expected: {', '.join(good_features)}.\n" 

105 f"Got: {', '.join(bad_features)}.\n" 

106 ) 

107 

108 

109class FoundANone(VarLibMergeError): 

110 """one of the values in a list was empty when it shouldn't have been""" 

111 

112 @property 

113 def offender(self): 

114 index = [x is None for x in self.cause["got"]].index(True) 

115 return index, self._master_name(index) 

116 

117 @property 

118 def details(self): 

119 cause, stack = self.cause, self.stack 

120 return f"{stack[0]}=={cause['got']}\n" 

121 

122 

123class NotANone(VarLibMergeError): 

124 """one of the values in a list was not empty when it should have been""" 

125 

126 @property 

127 def offender(self): 

128 index = [x is not None for x in self.cause["got"]].index(True) 

129 return index, self._master_name(index) 

130 

131 @property 

132 def details(self): 

133 cause, stack = self.cause, self.stack 

134 return f"{stack[0]}=={cause['got']}\n" 

135 

136 

137class MismatchedTypes(VarLibMergeError): 

138 """data had inconsistent types""" 

139 

140 

141class LengthsDiffer(VarLibMergeError): 

142 """a list of objects had inconsistent lengths""" 

143 

144 

145class KeysDiffer(VarLibMergeError): 

146 """a list of objects had different keys""" 

147 

148 

149class InconsistentGlyphOrder(VarLibMergeError): 

150 """the glyph order was inconsistent between masters""" 

151 

152 

153class InconsistentExtensions(VarLibMergeError): 

154 """the masters use extension lookups in inconsistent ways""" 

155 

156 

157class UnsupportedFormat(VarLibMergeError): 

158 """an OpenType subtable (%s) had a format I didn't expect""" 

159 

160 def __init__(self, merger=None, **kwargs): 

161 super().__init__(merger, **kwargs) 

162 if not self.stack: 

163 self.stack = [".Format"] 

164 

165 @property 

166 def reason(self): 

167 s = self.__doc__ % self.cause["subtable"] 

168 if "value" in self.cause: 

169 s += f" ({self.cause['value']!r})" 

170 return s 

171 

172 

173class InconsistentFormats(UnsupportedFormat): 

174 """an OpenType subtable (%s) had inconsistent formats between masters""" 

175 

176 

177class VarLibCFFMergeError(VarLibError): 

178 pass 

179 

180 

181class VarLibCFFDictMergeError(VarLibCFFMergeError): 

182 """Raised when a CFF PrivateDict cannot be merged.""" 

183 

184 def __init__(self, key, value, values): 

185 error_msg = ( 

186 f"For the Private Dict key '{key}', the default font value list:" 

187 f"\n\t{value}\nhad a different number of values than a region font:" 

188 ) 

189 for region_value in values: 

190 error_msg += f"\n\t{region_value}" 

191 self.args = (error_msg,) 

192 

193 

194class VarLibCFFPointTypeMergeError(VarLibCFFMergeError): 

195 """Raised when a CFF glyph cannot be merged because of point type differences.""" 

196 

197 def __init__(self, point_type, pt_index, m_index, default_type, glyph_name): 

198 error_msg = ( 

199 f"Glyph '{glyph_name}': '{point_type}' at point index {pt_index} in " 

200 f"master index {m_index} differs from the default font point type " 

201 f"'{default_type}'" 

202 ) 

203 self.args = (error_msg,) 

204 

205 

206class VarLibCFFHintTypeMergeError(VarLibCFFMergeError): 

207 """Raised when a CFF glyph cannot be merged because of hint type differences.""" 

208 

209 def __init__(self, hint_type, cmd_index, m_index, default_type, glyph_name): 

210 error_msg = ( 

211 f"Glyph '{glyph_name}': '{hint_type}' at index {cmd_index} in " 

212 f"master index {m_index} differs from the default font hint type " 

213 f"'{default_type}'" 

214 ) 

215 self.args = (error_msg,) 

216 

217 

218class VariationModelError(VarLibError): 

219 """Raised when a variation model is faulty."""