From 6c547864b615afd9c4fcb045122441128203563e Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Thu, 18 Mar 2021 15:49:49 +0000 Subject: [PATCH] Use individual exception classes instead of enum --- Lib/fontTools/varLib/errors.py | 169 +++++++++++++++++++++------------ Lib/fontTools/varLib/merger.py | 80 +++++++--------- 2 files changed, 144 insertions(+), 105 deletions(-) diff --git a/Lib/fontTools/varLib/errors.py b/Lib/fontTools/varLib/errors.py index 390f48218..ca502dacb 100644 --- a/Lib/fontTools/varLib/errors.py +++ b/Lib/fontTools/varLib/errors.py @@ -1,22 +1,6 @@ -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" - - class VarLibError(Exception): """Base exception for the varLib module.""" @@ -32,6 +16,18 @@ class VarLibMergeError(VarLibError): self.merger = merger self.args = args + @property + def cause(self): + return self.args[0] + + @property + def stack(self): + return self.args[1:] + + @property + def reason(self): + return self.__doc__ + def _master_name(self, ix): ttf = self.merger.ttfs[ix] if ( @@ -45,26 +41,55 @@ class VarLibMergeError(VarLibError): else: return "master number %i" % ix - 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 is VarLibMergeFailure.FoundANone: - index = [x is None for x in cause["got"]].index(True) + @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 + ) return index, self._master_name(index) return None, None - def _incompatible_features(self, offender_index): - cause, stack = self.args[0], self.args[1:] + @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']}, instead saw {got}\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 VarLibMergeFailureShouldBeConstant(VarLibMergeError): + """some values were different, but should have been the same""" + + @property + def details(self): + if self.stack[0] != ".FeatureCount": + return super() + offender_index, offender = self.offender 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 + x.FeatureTag + for x in good_ttf[self.stack[-1]].table.FeatureList.FeatureRecord ] bad_features = [ - x.FeatureTag for x in bad_ttf[stack[-1]].table.FeatureList.FeatureRecord + x.FeatureTag + for x in bad_ttf[self.stack[-1]].table.FeatureList.FeatureRecord ] return ( "\nIncompatible features between masters.\n" @@ -72,39 +97,65 @@ class VarLibMergeError(VarLibError): f"Got: {', '.join(bad_features)}.\n" ) - def __str__(self): - cause, stack = self.args[0], self.args[1:] - context = "".join(reversed(stack)) - details = "" - reason = cause["reason"].value - offender_index, offender = self._offender(cause) - if offender: - details = f"\n\nThe problem is likely to be in {offender}:\n" - if cause["reason"] is VarLibMergeFailure.FoundANone: - details = details + f"{stack[0]}=={cause['got']}\n" - elif ( - cause["reason"] is VarLibMergeFailure.ShouldBeConstant - and stack[0] == ".FeatureCount" - ): - # Common case - details = details + self._incompatible_features(offender_index) - elif "expected" in cause and "got" in cause: - got = cause["got"][offender_index] - details = details + ( - f"Expected to see {stack[0]}=={cause['expected']}, instead saw {got}\n" - ) - if ( - cause["reason"] is VarLibMergeFailure.UnsupportedFormat - or cause["reason"] is VarLibMergeFailure.InconsistentFormat - ): - 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 +class VarLibMergeFailureFoundANone(VarLibMergeError): + """one of the values in a list was empty when it shouldn't have been""" + + @property + def offender(self): + cause = self.argv[0] + index = [x is None for x in cause["got"]].index(True) + return index, self._master_name(index) + + @property + def details(self): + cause, stack = self.args[0], self.args[1:] + return f"{stack[0]}=={cause['got']}\n" + + +class VarLibMergeFailureMismatchedTypes(VarLibMergeError): + """data had inconsistent types""" + + pass + + +class VarLibMergeFailureLengthsDiffer(VarLibMergeError): + """a list of objects had inconsistent lengths""" + + pass + + +class VarLibMergeFailureKeysDiffer(VarLibMergeError): + """a list of objects had different keys""" + + pass + + +class VarLibMergeFailureInconsistentGlyphOrder(VarLibMergeError): + """the glyph order was inconsistent between masters""" + + pass + + +class VarLibMergeFailureInconsistentExtensions(VarLibMergeError): + """the masters use extension lookups in inconsistent ways""" + + pass + + +class VarLibMergeFailureUnsupportedFormat(VarLibMergeError): + """an OpenType subtable (%s) had a format I didn't expect""" + + @property + def reason(self): + cause, stack = self.args[0], self.args[1:] + return self.__doc__ % cause["subtable"] + + +class VarLibMergeFailureUnsupportedFormat(VarLibMergeFailureUnsupportedFormat): + """an OpenType subtable (%s) had inconsistent formats between masters""" + + pass class VarLibCFFDictMergeError(VarLibMergeError): diff --git a/Lib/fontTools/varLib/merger.py b/Lib/fontTools/varLib/merger.py index a48759392..14910ad2f 100644 --- a/Lib/fontTools/varLib/merger.py +++ b/Lib/fontTools/varLib/merger.py @@ -14,8 +14,18 @@ from fontTools.varLib.varStore import VarStoreInstancer from functools import reduce from fontTools.otlLib.builder import buildSinglePos -from .errors import VarLibMergeError, VarLibMergeFailure - +from .errors import ( + VarLibMergeFailureShouldBeConstant, + VarLibMergeFailureFoundANone, + VarLibMergeFailureMismatchedTypes, + VarLibMergeFailureLengthsDiffer, + VarLibMergeFailureKeysDiffer, + VarLibMergeFailureInconsistentGlyphOrder, + VarLibMergeFailureInconsistentExtensions, + VarLibMergeFailureUnsupportedFormat, + VarLibMergeFailureUnsupportedFormat, + VarLibMergeError, +) class Merger(object): @@ -69,8 +79,7 @@ class Merger(object): item.ensureDecompiled() keys = sorted(vars(out).keys()) if not all(keys == sorted(vars(v).keys()) for v in lst): - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.KeysDiffer, + raise VarLibMergeFailureKeysDiffer(self, ({ "expected": keys, "got": [sorted(vars(v).keys()) for v in lst]},)) mergers = self.mergersFor(out) @@ -88,8 +97,7 @@ class Merger(object): def mergeLists(self, out, lst): if not allEqualTo(out, lst, len): - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.LengthsDiffer, + raise VarLibMergeFailureLengthsDiffer(self, ({ "expected": len(out), "got": [len(x) for x in lst]},)) for i,(value,values) in enumerate(zip(out, zip(*lst))): @@ -101,8 +109,7 @@ class Merger(object): def mergeThings(self, out, lst): if not allEqualTo(out, lst, type): - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.MismatchedTypes, + raise VarLibMergeFailureMismatchedTypes(self, ({ "expected": type(out), "got": [type(x) for x in lst]},)) mergerFunc = self.mergersFor(out).get(None, None) @@ -114,8 +121,7 @@ class Merger(object): self.mergeLists(out, lst) else: if not allEqualTo(out, lst): - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.ShouldBeConstant, + raise VarLibMergeFailureShouldBeConstant(self, ({ "expected": out, "got": lst},)) @@ -140,8 +146,7 @@ class AligningMerger(Merger): def merge(merger, self, lst): if self is None: if not allNone(lst): - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.NotANone, + raise VarLibMergeFailureNotANone(self, ({ "expected": None, "got": lst },)) @@ -157,8 +162,7 @@ def merge(merger, self, lst): for k in allKeys: allValues = nonNone(l.get(k) for l in lst) if not allEqual(allValues): - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.ShouldBeConstant, + raise VarLibMergeFailureShouldBeConstant(self, ({ "expected": allValues[0], "got": lst, }, "."+k)) @@ -198,8 +202,7 @@ def _merge_GlyphOrders(font, lst, values_lst=None, default=None): order = sorted(combined, key=sortKey) # Make sure all input glyphsets were in proper order if not all(sorted(vs, key=sortKey) == vs for vs in lst): - raise VarLibMergeError(self, ({ - "reason" : VarLibMergeFailure.InconsistentGlyphOrder + raise VarLibMergeFailureInconsistentGlyphOrder(self, ({ },)) del combined @@ -227,8 +230,7 @@ def _Lookup_SinglePos_get_effective_value(subtables, glyph): elif self.Format == 2: return self.Value[self.Coverage.glyphs.index(glyph)] else: - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.UnsupportedFormat, + raise VarLibMergeFailureUnsupportedFormat(self, ({ "subtable": "single positioning lookup" },)) return None @@ -252,8 +254,7 @@ def _Lookup_PairPos_get_effective_value_pair(subtables, firstGlyph, secondGlyph) klass2 = self.ClassDef2.classDefs.get(secondGlyph, 0) return self.Class1Record[klass1].Class2Record[klass2] else: - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.UnsupportedFormat, + raise VarLibMergeFailureUnsupportedFormat(self, ({ "subtable": "pair positioning lookup" },)) return None @@ -262,8 +263,7 @@ def _Lookup_PairPos_get_effective_value_pair(subtables, firstGlyph, secondGlyph) def merge(merger, self, lst): self.ValueFormat = valueFormat = reduce(int.__or__, [l.ValueFormat for l in lst], 0) if not (len(lst) == 1 or (valueFormat & ~0xF == 0)): - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.UnsupportedFormat, + raise VarLibMergeFailureUnsupportedFormat(self, ({ "subtable": "single positioning lookup" },)) @@ -552,8 +552,7 @@ def merge(merger, self, lst): elif self.Format == 2: _PairPosFormat2_merge(self, lst, merger) else: - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.UnsupportedFormat, + raise VarLibMergeFailureUnsupportedFormat(self, ({ "subtable": "pair positioning lookup" },)) @@ -621,8 +620,7 @@ def _MarkBasePosFormat1_merge(self, lst, merger, Mark='Mark', Base='Base'): # input masters. if not allEqual(allClasses): - raise VarLibMergeError(self, allClasses) - if not allClasses: + raise allClasses(self, allClasses) rec = None else: rec = ot.MarkRecord() @@ -671,32 +669,28 @@ def _MarkBasePosFormat1_merge(self, lst, merger, Mark='Mark', Base='Base'): @AligningMerger.merger(ot.MarkBasePos) def merge(merger, self, lst): if not allEqualTo(self.Format, (l.Format for l in lst)): - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.InconsistentFormats, + raise VarLibMergeFailureInconsistentFormats(self, ({ "subtable": "mark-to-base positioning lookup", "expected": self.Format, "got": [l.Format for l in lst]},)) if self.Format == 1: _MarkBasePosFormat1_merge(self, lst, merger) else: - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.UnsupportedFormat, + raise VarLibMergeFailureUnsupportedFormat(self, ({ "subtable": "mark-to-base positioning lookup", })) @AligningMerger.merger(ot.MarkMarkPos) def merge(merger, self, lst): if not allEqualTo(self.Format, (l.Format for l in lst)): - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.InconsistentFormats, + raise VarLibMergeFailureInconsistentFormats(self, ({ "subtable": "mark-to-mark positioning lookup", "expected": self.Format, "got": [l.Format for l in lst]},)) if self.Format == 1: _MarkBasePosFormat1_merge(self, lst, merger, 'Mark1', 'Mark2') else: - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.UnsupportedFormat, + raise VarLibMergeFailureUnsupportedFormat(self, ({ "subtable": "mark-to-mark positioning lookup", })) @@ -828,14 +822,12 @@ def merge(merger, self, lst): continue if sts[0].__class__.__name__.startswith('Extension'): if not allEqual([st.__class__ for st in sts]): - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.InconsistentExtensions, + raise VarLibMergeFailureInconsistentExtensions(self, ({ "expected": "Extension", "got": [st.__class__.__name__ for st in sts] },)) if not allEqual([st.ExtensionLookupType for st in sts]): - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.InconsistentExtensions, + raise VarLibMergeFailureInconsistentExtensions(self, ({ },)) l.LookupType = sts[0].ExtensionLookupType new_sts = [st.ExtSubTable for st in sts] @@ -1065,8 +1057,7 @@ class VariationMerger(AligningMerger): if None in lst: if allNone(lst): if out is not None: - raise VarLibMergeError(self, ({ - "reason": VarLibMergeFailure.FoundANone, + raise VarLibMergeFailureFoundANone(self, ({ "got": lst },)) return @@ -1089,8 +1080,7 @@ def buildVarDevTable(store_builder, master_values): @VariationMerger.merger(ot.BaseCoord) def merge(merger, self, lst): if self.Format != 1: - raise VarLibMergeError(self, ({ - "cause": VarLibMergeFailure.UnsupportedFormat, + raise VarLibMergeFailureUnsupportedFormat(self, ({ "subtable": "a baseline coordinate" },)) self.Coordinate, DeviceTable = buildVarDevTable(merger.store_builder, [a.Coordinate for a in lst]) @@ -1101,8 +1091,7 @@ def merge(merger, self, lst): @VariationMerger.merger(ot.CaretValue) def merge(merger, self, lst): if self.Format != 1: - raise VarLibMergeError(self, ({ - "cause": VarLibMergeFailure.UnsupportedFormat, + raise VarLibMergeFailureUnsupportedFormat(self, ({ "subtable": "a caret" },)) self.Coordinate, DeviceTable = buildVarDevTable(merger.store_builder, [a.Coordinate for a in lst]) @@ -1113,8 +1102,7 @@ def merge(merger, self, lst): @VariationMerger.merger(ot.Anchor) def merge(merger, self, lst): if self.Format != 1: - raise VarLibMergeError(self, ({ - "cause": VarLibMergeFailure.UnsupportedFormat, + raise VarLibMergeFailureUnsupportedFormat(self, ({ "subtable": "an anchor" },)) self.XCoordinate, XDeviceTable = buildVarDevTable(merger.store_builder, [a.XCoordinate for a in lst])