diff --git a/Lib/fontTools/feaLib/__main__.py b/Lib/fontTools/feaLib/__main__.py index e7db157f0..9c682fc18 100644 --- a/Lib/fontTools/feaLib/__main__.py +++ b/Lib/fontTools/feaLib/__main__.py @@ -15,23 +15,39 @@ log = logging.getLogger("fontTools.feaLib") def main(args=None): """Add features from a feature file (.fea) into a OTF font""" parser = argparse.ArgumentParser( - description="Use fontTools to compile OpenType feature files (*.fea).") + description="Use fontTools to compile OpenType feature files (*.fea)." + ) parser.add_argument( - "input_fea", metavar="FEATURES", help="Path to the feature file") + "input_fea", metavar="FEATURES", help="Path to the feature file" + ) parser.add_argument( - "input_font", metavar="INPUT_FONT", help="Path to the input font") + "input_font", metavar="INPUT_FONT", help="Path to the input font" + ) parser.add_argument( - "-o", "--output", dest="output_font", metavar="OUTPUT_FONT", - help="Path to the output font.") + "-o", + "--output", + dest="output_font", + metavar="OUTPUT_FONT", + help="Path to the output font.", + ) parser.add_argument( - "-t", "--tables", metavar="TABLE_TAG", choices=Builder.supportedTables, - nargs='+', help="Specify the table(s) to be built.") + "-t", + "--tables", + metavar="TABLE_TAG", + choices=Builder.supportedTables, + nargs="+", + help="Specify the table(s) to be built.", + ) parser.add_argument( - "-v", "--verbose", help="increase the logger verbosity. Multiple -v " - "options are allowed.", action="count", default=0) + "-v", + "--verbose", + help="increase the logger verbosity. Multiple -v " "options are allowed.", + action="count", + default=0, + ) parser.add_argument( - "--traceback", help="show traceback for exceptions.", - action="store_true") + "--traceback", help="show traceback for exceptions.", action="store_true" + ) options = parser.parse_args(args) levels = ["WARNING", "INFO", "DEBUG"] @@ -50,5 +66,5 @@ def main(args=None): font.save(output_font) -if __name__ == '__main__': +if __name__ == "__main__": sys.exit(main()) diff --git a/Lib/fontTools/feaLib/error.py b/Lib/fontTools/feaLib/error.py index 50322c487..a2c5f9dbb 100644 --- a/Lib/fontTools/feaLib/error.py +++ b/Lib/fontTools/feaLib/error.py @@ -1,5 +1,3 @@ - - class FeatureLibError(Exception): def __init__(self, message, location): Exception.__init__(self, message) diff --git a/Lib/fontTools/feaLib/lexer.py b/Lib/fontTools/feaLib/lexer.py index be7ac615c..3caf3dc5a 100644 --- a/Lib/fontTools/feaLib/lexer.py +++ b/Lib/fontTools/feaLib/lexer.py @@ -77,75 +77,75 @@ class Lexer(object): self.line_start_ = self.pos_ return (Lexer.NEWLINE, None, location) if cur_char == "\r": - self.pos_ += (2 if next_char == "\n" else 1) + self.pos_ += 2 if next_char == "\n" else 1 self.line_ += 1 self.line_start_ = self.pos_ return (Lexer.NEWLINE, None, location) if cur_char == "#": self.scan_until_(Lexer.CHAR_NEWLINE_) - return (Lexer.COMMENT, text[start:self.pos_], location) + return (Lexer.COMMENT, text[start : self.pos_], location) if self.mode_ is Lexer.MODE_FILENAME_: if cur_char != "(": - raise FeatureLibError("Expected '(' before file name", - location) + raise FeatureLibError("Expected '(' before file name", location) self.scan_until_(")") cur_char = text[self.pos_] if self.pos_ < limit else None if cur_char != ")": - raise FeatureLibError("Expected ')' after file name", - location) + raise FeatureLibError("Expected ')' after file name", location) self.pos_ += 1 self.mode_ = Lexer.MODE_NORMAL_ - return (Lexer.FILENAME, text[start + 1:self.pos_ - 1], location) + return (Lexer.FILENAME, text[start + 1 : self.pos_ - 1], location) if cur_char == "\\" and next_char in Lexer.CHAR_DIGIT_: self.pos_ += 1 self.scan_over_(Lexer.CHAR_DIGIT_) - return (Lexer.CID, int(text[start + 1:self.pos_], 10), location) + return (Lexer.CID, int(text[start + 1 : self.pos_], 10), location) if cur_char == "@": self.pos_ += 1 self.scan_over_(Lexer.CHAR_NAME_CONTINUATION_) - glyphclass = text[start + 1:self.pos_] + glyphclass = text[start + 1 : self.pos_] if len(glyphclass) < 1: raise FeatureLibError("Expected glyph class name", location) if len(glyphclass) > 63: raise FeatureLibError( - "Glyph class names must not be longer than 63 characters", - location) + "Glyph class names must not be longer than 63 characters", location + ) if not Lexer.RE_GLYPHCLASS.match(glyphclass): raise FeatureLibError( "Glyph class names must consist of letters, digits, " - "underscore, period or hyphen", location) + "underscore, period or hyphen", + location, + ) return (Lexer.GLYPHCLASS, glyphclass, location) if cur_char in Lexer.CHAR_NAME_START_: self.pos_ += 1 self.scan_over_(Lexer.CHAR_NAME_CONTINUATION_) - token = text[start:self.pos_] + token = text[start : self.pos_] if token == "include": self.mode_ = Lexer.MODE_FILENAME_ return (Lexer.NAME, token, location) if cur_char == "0" and next_char in "xX": self.pos_ += 2 self.scan_over_(Lexer.CHAR_HEXDIGIT_) - return (Lexer.HEXADECIMAL, int(text[start:self.pos_], 16), location) + return (Lexer.HEXADECIMAL, int(text[start : self.pos_], 16), location) if cur_char == "0" and next_char in Lexer.CHAR_DIGIT_: self.scan_over_(Lexer.CHAR_DIGIT_) - return (Lexer.OCTAL, int(text[start:self.pos_], 8), location) + return (Lexer.OCTAL, int(text[start : self.pos_], 8), location) if cur_char in Lexer.CHAR_DIGIT_: self.scan_over_(Lexer.CHAR_DIGIT_) if self.pos_ >= limit or text[self.pos_] != ".": - return (Lexer.NUMBER, int(text[start:self.pos_], 10), location) + return (Lexer.NUMBER, int(text[start : self.pos_], 10), location) self.scan_over_(".") self.scan_over_(Lexer.CHAR_DIGIT_) - return (Lexer.FLOAT, float(text[start:self.pos_]), location) + return (Lexer.FLOAT, float(text[start : self.pos_]), location) if cur_char == "-" and next_char in Lexer.CHAR_DIGIT_: self.pos_ += 1 self.scan_over_(Lexer.CHAR_DIGIT_) if self.pos_ >= limit or text[self.pos_] != ".": - return (Lexer.NUMBER, int(text[start:self.pos_], 10), location) + return (Lexer.NUMBER, int(text[start : self.pos_], 10), location) self.scan_over_(".") self.scan_over_(Lexer.CHAR_DIGIT_) - return (Lexer.FLOAT, float(text[start:self.pos_]), location) + return (Lexer.FLOAT, float(text[start : self.pos_]), location) if cur_char in Lexer.CHAR_SYMBOL_: self.pos_ += 1 return (Lexer.SYMBOL, cur_char, location) @@ -155,13 +155,11 @@ class Lexer(object): if self.pos_ < self.text_length_ and self.text_[self.pos_] == '"': self.pos_ += 1 # strip newlines embedded within a string - string = re.sub("[\r\n]", "", text[start + 1:self.pos_ - 1]) + string = re.sub("[\r\n]", "", text[start + 1 : self.pos_ - 1]) return (Lexer.STRING, string, location) else: - raise FeatureLibError("Expected '\"' to terminate string", - location) - raise FeatureLibError("Unexpected character: %r" % cur_char, - location) + raise FeatureLibError("Expected '\"' to terminate string", location) + raise FeatureLibError("Unexpected character: %r" % cur_char, location) def scan_over_(self, valid): p = self.pos_ @@ -180,12 +178,12 @@ class Lexer(object): tag = tag.strip() self.scan_until_(Lexer.CHAR_NEWLINE_) self.scan_over_(Lexer.CHAR_NEWLINE_) - regexp = r'}\s*' + tag + r'\s*;' - split = re.split(regexp, self.text_[self.pos_:], maxsplit=1) + regexp = r"}\s*" + tag + r"\s*;" + split = re.split(regexp, self.text_[self.pos_ :], maxsplit=1) if len(split) != 2: raise FeatureLibError( - "Expected '} %s;' to terminate anonymous block" % tag, - location) + "Expected '} %s;' to terminate anonymous block" % tag, location + ) self.pos_ += len(split[0]) return (Lexer.ANONYMOUS_BLOCK, split[0], location) @@ -237,8 +235,8 @@ class IncludingLexer(object): fname_type, fname_token, fname_location = lexer.next() if fname_type is not Lexer.FILENAME: raise FeatureLibError("Expected file name", fname_location) - #semi_type, semi_token, semi_location = lexer.next() - #if semi_type is not Lexer.SYMBOL or semi_token != ";": + # semi_type, semi_token, semi_location = lexer.next() + # if semi_type is not Lexer.SYMBOL or semi_token != ";": # raise FeatureLibError("Expected ';'", semi_location) if os.path.isabs(fname_token): path = fname_token @@ -255,8 +253,7 @@ class IncludingLexer(object): curpath = os.getcwd() path = os.path.join(curpath, fname_token) if len(self.lexers_) >= 5: - raise FeatureLibError("Too many recursive includes", - fname_location) + raise FeatureLibError("Too many recursive includes", fname_location) try: self.lexers_.append(self.make_lexer_(path)) except FileNotFoundError as err: @@ -284,5 +281,6 @@ class IncludingLexer(object): class NonIncludingLexer(IncludingLexer): """Lexer that does not follow `include` statements, emits them as-is.""" + def __next__(self): # Python 3 return next(self.lexers_[0]) diff --git a/Lib/fontTools/feaLib/location.py b/Lib/fontTools/feaLib/location.py index a11062bc7..50f761d2d 100644 --- a/Lib/fontTools/feaLib/location.py +++ b/Lib/fontTools/feaLib/location.py @@ -1,10 +1,12 @@ from typing import NamedTuple + class FeatureLibLocation(NamedTuple): """A location in a feature file""" + file: str line: int column: int def __str__(self): - return f"{self.file}:{self.line}:{self.column}" + return f"{self.file}:{self.line}:{self.column}" diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index 40700d1d5..ab6928ff7 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -35,25 +35,30 @@ class Parser(object): ``includeDir`` to explicitly declare a directory to search included feature files in. """ + extensions = {} ast = ast - SS_FEATURE_TAGS = {"ss%02d" % i for i in range(1, 20+1)} - CV_FEATURE_TAGS = {"cv%02d" % i for i in range(1, 99+1)} + SS_FEATURE_TAGS = {"ss%02d" % i for i in range(1, 20 + 1)} + CV_FEATURE_TAGS = {"cv%02d" % i for i in range(1, 99 + 1)} - def __init__(self, featurefile, glyphNames=(), followIncludes=True, - includeDir=None, **kwargs): + def __init__( + self, featurefile, glyphNames=(), followIncludes=True, includeDir=None, **kwargs + ): if "glyphMap" in kwargs: from fontTools.misc.loggingTools import deprecateArgument + deprecateArgument("glyphMap", "use 'glyphNames' (iterable) instead") if glyphNames: - raise TypeError("'glyphNames' and (deprecated) 'glyphMap' are " - "mutually exclusive") + raise TypeError( + "'glyphNames' and (deprecated) 'glyphMap' are " "mutually exclusive" + ) glyphNames = kwargs.pop("glyphMap") if kwargs: - raise TypeError("unsupported keyword argument%s: %s" - % ("" if len(kwargs) == 1 else "s", - ", ".join(repr(k) for k in kwargs))) + raise TypeError( + "unsupported keyword argument%s: %s" + % ("" if len(kwargs) == 1 else "s", ", ".join(repr(k) for k in kwargs)) + ) self.glyphNames_ = set(glyphNames) self.doc_ = self.ast.FeatureFile() @@ -61,9 +66,7 @@ class Parser(object): self.glyphclasses_ = SymbolTable() self.lookups_ = SymbolTable() self.valuerecords_ = SymbolTable() - self.symbol_tables_ = { - self.anchors_, self.valuerecords_ - } + self.symbol_tables_ = {self.anchors_, self.valuerecords_} self.next_token_type_, self.next_token_ = (None, None) self.cur_comments_ = [] self.next_token_location_ = None @@ -80,8 +83,8 @@ class Parser(object): self.advance_lexer_(comments=True) if self.cur_token_type_ is Lexer.COMMENT: statements.append( - self.ast.Comment(self.cur_token_, - location=self.cur_token_location_)) + self.ast.Comment(self.cur_token_, location=self.cur_token_location_) + ) elif self.is_cur_keyword_("include"): statements.append(self.parse_include_()) elif self.cur_token_type_ is Lexer.GLYPHCLASS: @@ -101,17 +104,22 @@ class Parser(object): elif self.is_cur_keyword_("table"): statements.append(self.parse_table_()) elif self.is_cur_keyword_("valueRecordDef"): - statements.append( - self.parse_valuerecord_definition_(vertical=False)) - elif self.cur_token_type_ is Lexer.NAME and self.cur_token_ in self.extensions: + statements.append(self.parse_valuerecord_definition_(vertical=False)) + elif ( + self.cur_token_type_ is Lexer.NAME + and self.cur_token_ in self.extensions + ): statements.append(self.extensions[self.cur_token_](self)) elif self.cur_token_type_ is Lexer.SYMBOL and self.cur_token_ == ";": continue else: raise FeatureLibError( "Expected feature, languagesystem, lookup, markClass, " - "table, or glyph class definition, got {} \"{}\"".format(self.cur_token_type_, self.cur_token_), - self.cur_token_location_) + 'table, or glyph class definition, got {} "{}"'.format( + self.cur_token_type_, self.cur_token_ + ), + self.cur_token_location_, + ) return self.doc_ def parse_anchor_(self): @@ -121,44 +129,52 @@ class Parser(object): self.expect_keyword_("anchor") location = self.cur_token_location_ - if self.next_token_ == "NULL": # Format D + if self.next_token_ == "NULL": # Format D self.expect_keyword_("NULL") self.expect_symbol_(">") return None - if self.next_token_type_ == Lexer.NAME: # Format E + if self.next_token_type_ == Lexer.NAME: # Format E name = self.expect_name_() anchordef = self.anchors_.resolve(name) if anchordef is None: raise FeatureLibError( - 'Unknown anchor "%s"' % name, - self.cur_token_location_) + 'Unknown anchor "%s"' % name, self.cur_token_location_ + ) self.expect_symbol_(">") - return self.ast.Anchor(anchordef.x, anchordef.y, - name=name, - contourpoint=anchordef.contourpoint, - xDeviceTable=None, yDeviceTable=None, - location=location) + return self.ast.Anchor( + anchordef.x, + anchordef.y, + name=name, + contourpoint=anchordef.contourpoint, + xDeviceTable=None, + yDeviceTable=None, + location=location, + ) x, y = self.expect_number_(), self.expect_number_() contourpoint = None - if self.next_token_ == "contourpoint": # Format B + if self.next_token_ == "contourpoint": # Format B self.expect_keyword_("contourpoint") contourpoint = self.expect_number_() - if self.next_token_ == "<": # Format C + if self.next_token_ == "<": # Format C xDeviceTable = self.parse_device_() yDeviceTable = self.parse_device_() else: xDeviceTable, yDeviceTable = None, None self.expect_symbol_(">") - return self.ast.Anchor(x, y, name=None, - contourpoint=contourpoint, - xDeviceTable=xDeviceTable, - yDeviceTable=yDeviceTable, - location=location) + return self.ast.Anchor( + x, + y, + name=None, + contourpoint=contourpoint, + xDeviceTable=xDeviceTable, + yDeviceTable=yDeviceTable, + location=location, + ) def parse_anchor_marks_(self): # Parses a sequence of ``[ mark @MARKCLASS]*.`` @@ -183,9 +199,9 @@ class Parser(object): contourpoint = self.expect_number_() name = self.expect_name_() self.expect_symbol_(";") - anchordef = self.ast.AnchorDefinition(name, x, y, - contourpoint=contourpoint, - location=location) + anchordef = self.ast.AnchorDefinition( + name, x, y, contourpoint=contourpoint, location=location + ) self.anchors_.define(name, anchordef) return anchordef @@ -195,10 +211,10 @@ class Parser(object): tag = self.expect_tag_() _, content, location = self.lexer_.scan_anonymous_block(tag) self.advance_lexer_() - self.expect_symbol_('}') + self.expect_symbol_("}") end_tag = self.expect_tag_() assert tag == end_tag, "bad splitting in Lexer.scan_anonymous_block()" - self.expect_symbol_(';') + self.expect_symbol_(";") return self.ast.AnonymousBlock(tag, content, location=location) def parse_attach_(self): @@ -210,8 +226,7 @@ class Parser(object): while self.next_token_ != ";": contourPoints.add(self.expect_number_()) self.expect_symbol_(";") - return self.ast.AttachStatement(glyphs, contourPoints, - location=location) + return self.ast.AttachStatement(glyphs, contourPoints, location=location) def parse_enumerate_(self, vertical): # Parse an enumerated pair positioning rule (`section 6.b.ii `_). @@ -243,9 +258,9 @@ class Parser(object): else: componentGlyphs = None self.expect_symbol_(";") - return self.ast.GlyphClassDefStatement(baseGlyphs, markGlyphs, - ligatureGlyphs, componentGlyphs, - location=location) + return self.ast.GlyphClassDefStatement( + baseGlyphs, markGlyphs, ligatureGlyphs, componentGlyphs, location=location + ) def parse_glyphclass_definition_(self): # Parses glyph class definitions such as '@UPPERCASE = [A-Z];' @@ -253,8 +268,7 @@ class Parser(object): self.expect_symbol_("=") glyphs = self.parse_glyphclass_(accept_glyphname=False) self.expect_symbol_(";") - glyphclass = self.ast.GlyphClassDefinition(name, glyphs, - location=location) + glyphclass = self.ast.GlyphClassDefinition(name, glyphs, location=location) self.glyphclasses_.define(name, glyphclass) return glyphclass @@ -288,20 +302,22 @@ class Parser(object): return start, limit elif len(solutions) == 0: raise FeatureLibError( - "\"%s\" is not a glyph in the font, and it can not be split " - "into a range of known glyphs" % name, location) + '"%s" is not a glyph in the font, and it can not be split ' + "into a range of known glyphs" % name, + location, + ) else: - ranges = " or ".join(["\"%s - %s\"" % (s, l) for s, l in solutions]) + ranges = " or ".join(['"%s - %s"' % (s, l) for s, l in solutions]) raise FeatureLibError( - "Ambiguous glyph range \"%s\"; " + 'Ambiguous glyph range "%s"; ' "please use %s to clarify what you mean" % (name, ranges), - location) + location, + ) def parse_glyphclass_(self, accept_glyphname): # Parses a glyph class, either named or anonymous, or (if # ``bool(accept_glyphname)``) a glyph name. - if (accept_glyphname and - self.next_token_type_ in (Lexer.NAME, Lexer.CID)): + if accept_glyphname and self.next_token_type_ in (Lexer.NAME, Lexer.CID): glyph = self.expect_glyph_() self.check_glyph_name_in_glyph_set(glyph) return self.ast.GlyphName(glyph, location=self.cur_token_location_) @@ -311,13 +327,12 @@ class Parser(object): if gc is None: raise FeatureLibError( "Unknown glyph class @%s" % self.cur_token_, - self.cur_token_location_) + self.cur_token_location_, + ) if isinstance(gc, self.ast.MarkClass): - return self.ast.MarkClassName( - gc, location=self.cur_token_location_) + return self.ast.MarkClassName(gc, location=self.cur_token_location_) else: - return self.ast.GlyphClassName( - gc, location=self.cur_token_location_) + return self.ast.GlyphClassName(gc, location=self.cur_token_location_) self.expect_symbol_("[") location = self.cur_token_location_ @@ -326,26 +341,30 @@ class Parser(object): if self.next_token_type_ is Lexer.NAME: glyph = self.expect_glyph_() location = self.cur_token_location_ - if '-' in glyph and self.glyphNames_ and glyph not in self.glyphNames_: + if "-" in glyph and self.glyphNames_ and glyph not in self.glyphNames_: start, limit = self.split_glyph_range_(glyph, location) self.check_glyph_name_in_glyph_set(start, limit) glyphs.add_range( - start, limit, - self.make_glyph_range_(location, start, limit)) + start, limit, self.make_glyph_range_(location, start, limit) + ) elif self.next_token_ == "-": start = glyph self.expect_symbol_("-") limit = self.expect_glyph_() self.check_glyph_name_in_glyph_set(start, limit) glyphs.add_range( - start, limit, - self.make_glyph_range_(location, start, limit)) + start, limit, self.make_glyph_range_(location, start, limit) + ) else: - if '-' in glyph and not self.glyphNames_: - log.warning(str(FeatureLibError( - f"Ambiguous glyph name that looks like a range: {glyph!r}", - location - ))) + if "-" in glyph and not self.glyphNames_: + log.warning( + str( + FeatureLibError( + f"Ambiguous glyph name that looks like a range: {glyph!r}", + location, + ) + ) + ) self.check_glyph_name_in_glyph_set(glyph) glyphs.append(glyph) elif self.next_token_type_ is Lexer.CID: @@ -356,12 +375,13 @@ class Parser(object): self.expect_symbol_("-") range_end = self.expect_cid_() self.check_glyph_name_in_glyph_set( - f"cid{range_start:05d}", - f"cid{range_end:05d}", + f"cid{range_start:05d}", f"cid{range_end:05d}", + ) + glyphs.add_cid_range( + range_start, + range_end, + self.make_cid_range_(range_location, range_start, range_end), ) - glyphs.add_cid_range(range_start, range_end, - self.make_cid_range_(range_location, - range_start, range_end)) else: glyph_name = f"cid{self.cur_token_:05d}" self.check_glyph_name_in_glyph_set(glyph_name) @@ -372,19 +392,19 @@ class Parser(object): if gc is None: raise FeatureLibError( "Unknown glyph class @%s" % self.cur_token_, - self.cur_token_location_) + self.cur_token_location_, + ) if isinstance(gc, self.ast.MarkClass): - gc = self.ast.MarkClassName( - gc, location=self.cur_token_location_) + gc = self.ast.MarkClassName(gc, location=self.cur_token_location_) else: - gc = self.ast.GlyphClassName( - gc, location=self.cur_token_location_) + gc = self.ast.GlyphClassName(gc, location=self.cur_token_location_) glyphs.add_class(gc) else: raise FeatureLibError( "Expected glyph name, glyph range, " f"or glyph class reference, found {self.next_token_!r}", - self.next_token_location_) + self.next_token_location_, + ) self.expect_symbol_("]") return glyphs @@ -394,14 +414,12 @@ class Parser(object): gc = self.glyphclasses_.resolve(name) if gc is None: raise FeatureLibError( - "Unknown glyph class @%s" % name, - self.cur_token_location_) + "Unknown glyph class @%s" % name, self.cur_token_location_ + ) if isinstance(gc, self.ast.MarkClass): - return self.ast.MarkClassName( - gc, location=self.cur_token_location_) + return self.ast.MarkClassName(gc, location=self.cur_token_location_) else: - return self.ast.GlyphClassName( - gc, location=self.cur_token_location_) + return self.ast.GlyphClassName(gc, location=self.cur_token_location_) def parse_glyph_pattern_(self, vertical): # Parses a glyph pattern, including lookups and context, e.g.:: @@ -425,7 +443,8 @@ class Parser(object): raise FeatureLibError( "Unsupported contextual target sequence: at most " "one run of marked (') glyph/class names allowed", - self.cur_token_location_) + self.cur_token_location_, + ) glyphs.append(gc) elif glyphs: suffix.append(gc) @@ -445,13 +464,14 @@ class Parser(object): if not marked: raise FeatureLibError( "Lookups can only follow marked glyphs", - self.cur_token_location_) + self.cur_token_location_, + ) lookup_name = self.expect_name_() lookup = self.lookups_.resolve(lookup_name) if lookup is None: raise FeatureLibError( - 'Unknown lookup "%s"' % lookup_name, - self.cur_token_location_) + 'Unknown lookup "%s"' % lookup_name, self.cur_token_location_ + ) lookuplist.append(lookup) if marked: lookups.append(lookuplist) @@ -460,22 +480,33 @@ class Parser(object): assert lookups == [] return ([], prefix, [None] * len(prefix), values, [], hasMarks) else: - assert not any(values[:len(prefix)]), values - format1 = values[len(prefix):][:len(glyphs)] - format2 = values[(len(prefix) + len(glyphs)):][:len(suffix)] - values = format2 if format2 and isinstance(format2[0], self.ast.ValueRecord) else format1 + assert not any(values[: len(prefix)]), values + format1 = values[len(prefix) :][: len(glyphs)] + format2 = values[(len(prefix) + len(glyphs)) :][: len(suffix)] + values = ( + format2 + if format2 and isinstance(format2[0], self.ast.ValueRecord) + else format1 + ) return (prefix, glyphs, lookups, values, suffix, hasMarks) def parse_chain_context_(self): location = self.cur_token_location_ - prefix, glyphs, lookups, values, suffix, hasMarks = \ - self.parse_glyph_pattern_(vertical=False) + prefix, glyphs, lookups, values, suffix, hasMarks = self.parse_glyph_pattern_( + vertical=False + ) chainContext = [(prefix, glyphs, suffix)] hasLookups = any(lookups) while self.next_token_ == ",": self.expect_symbol_(",") - prefix, glyphs, lookups, values, suffix, hasMarks = \ - self.parse_glyph_pattern_(vertical=False) + ( + prefix, + glyphs, + lookups, + values, + suffix, + hasMarks, + ) = self.parse_glyph_pattern_(vertical=False) chainContext.append((prefix, glyphs, suffix)) hasLookups = hasLookups or any(lookups) self.expect_symbol_(";") @@ -490,21 +521,19 @@ class Parser(object): chainContext, hasLookups = self.parse_chain_context_() if hasLookups: raise FeatureLibError( - "No lookups can be specified for \"ignore sub\"", - location) - return self.ast.IgnoreSubstStatement(chainContext, - location=location) + 'No lookups can be specified for "ignore sub"', location + ) + return self.ast.IgnoreSubstStatement(chainContext, location=location) if self.cur_token_ in ["position", "pos"]: chainContext, hasLookups = self.parse_chain_context_() if hasLookups: raise FeatureLibError( - "No lookups can be specified for \"ignore pos\"", - location) - return self.ast.IgnorePosStatement(chainContext, - location=location) + 'No lookups can be specified for "ignore pos"', location + ) + return self.ast.IgnorePosStatement(chainContext, location=location) raise FeatureLibError( - "Expected \"substitute\" or \"position\"", - self.cur_token_location_) + 'Expected "substitute" or "position"', self.cur_token_location_ + ) def parse_include_(self): assert self.cur_token_ == "include" @@ -519,14 +548,14 @@ class Parser(object): language = self.expect_language_tag_() include_default, required = (True, False) if self.next_token_ in {"exclude_dflt", "include_dflt"}: - include_default = (self.expect_name_() == "include_dflt") + include_default = self.expect_name_() == "include_dflt" if self.next_token_ == "required": self.expect_keyword_("required") required = True self.expect_symbol_(";") - return self.ast.LanguageStatement(language, - include_default, required, - location=location) + return self.ast.LanguageStatement( + language, include_default, required, location=location + ) def parse_ligatureCaretByIndex_(self): assert self.is_cur_keyword_("LigatureCaretByIndex") @@ -536,8 +565,7 @@ class Parser(object): while self.next_token_ != ";": carets.append(self.expect_number_()) self.expect_symbol_(";") - return self.ast.LigatureCaretByIndexStatement(glyphs, carets, - location=location) + return self.ast.LigatureCaretByIndexStatement(glyphs, carets, location=location) def parse_ligatureCaretByPos_(self): assert self.is_cur_keyword_("LigatureCaretByPos") @@ -547,8 +575,7 @@ class Parser(object): while self.next_token_ != ";": carets.append(self.expect_number_()) self.expect_symbol_(";") - return self.ast.LigatureCaretByPosStatement(glyphs, carets, - location=location) + return self.ast.LigatureCaretByPosStatement(glyphs, carets, location=location) def parse_lookup_(self, vertical): # Parses a ``lookup`` - either a lookup block, or a lookup reference @@ -559,11 +586,11 @@ class Parser(object): if self.next_token_ == ";": lookup = self.lookups_.resolve(name) if lookup is None: - raise FeatureLibError("Unknown lookup \"%s\"" % name, - self.cur_token_location_) + raise FeatureLibError( + 'Unknown lookup "%s"' % name, self.cur_token_location_ + ) self.expect_symbol_(";") - return self.ast.LookupReferenceStatement(lookup, - location=location) + return self.ast.LookupReferenceStatement(lookup, location=location) use_extension = False if self.next_token_ == "useExtension": @@ -591,15 +618,18 @@ class Parser(object): value_seen = False value, markAttachment, markFilteringSet = 0, None, None flags = { - "RightToLeft": 1, "IgnoreBaseGlyphs": 2, - "IgnoreLigatures": 4, "IgnoreMarks": 8 + "RightToLeft": 1, + "IgnoreBaseGlyphs": 2, + "IgnoreLigatures": 4, + "IgnoreMarks": 8, } seen = set() while self.next_token_ != ";": if self.next_token_ in seen: raise FeatureLibError( "%s can be specified only once" % self.next_token_, - self.next_token_location_) + self.next_token_location_, + ) seen.add(self.next_token_) if self.next_token_ == "MarkAttachmentType": self.expect_keyword_("MarkAttachmentType") @@ -613,17 +643,21 @@ class Parser(object): else: raise FeatureLibError( '"%s" is not a recognized lookupflag' % self.next_token_, - self.next_token_location_) + self.next_token_location_, + ) self.expect_symbol_(";") if not any([value_seen, markAttachment, markFilteringSet]): raise FeatureLibError( - 'lookupflag must have a value', self.next_token_location_) + "lookupflag must have a value", self.next_token_location_ + ) - return self.ast.LookupFlagStatement(value, - markAttachment=markAttachment, - markFilteringSet=markFilteringSet, - location=location) + return self.ast.LookupFlagStatement( + value, + markAttachment=markAttachment, + markFilteringSet=markFilteringSet, + location=location, + ) def parse_markClass_(self): assert self.is_cur_keyword_("markClass") @@ -637,8 +671,9 @@ class Parser(object): markClass = self.ast.MarkClass(name) self.doc_.markClasses[name] = markClass self.glyphclasses_.define(name, markClass) - mcdef = self.ast.MarkClassDefinition(markClass, anchor, glyphs, - location=location) + mcdef = self.ast.MarkClassDefinition( + markClass, anchor, glyphs, location=location + ) markClass.addDefinition(mcdef) return mcdef @@ -646,26 +681,28 @@ class Parser(object): assert self.cur_token_ in {"position", "pos"} if self.next_token_ == "cursive": # GPOS type 3 return self.parse_position_cursive_(enumerated, vertical) - elif self.next_token_ == "base": # GPOS type 4 + elif self.next_token_ == "base": # GPOS type 4 return self.parse_position_base_(enumerated, vertical) - elif self.next_token_ == "ligature": # GPOS type 5 + elif self.next_token_ == "ligature": # GPOS type 5 return self.parse_position_ligature_(enumerated, vertical) - elif self.next_token_ == "mark": # GPOS type 6 + elif self.next_token_ == "mark": # GPOS type 6 return self.parse_position_mark_(enumerated, vertical) location = self.cur_token_location_ - prefix, glyphs, lookups, values, suffix, hasMarks = \ - self.parse_glyph_pattern_(vertical) + prefix, glyphs, lookups, values, suffix, hasMarks = self.parse_glyph_pattern_( + vertical + ) self.expect_symbol_(";") if any(lookups): # GPOS type 8: Chaining contextual positioning; explicit lookups if any(values): raise FeatureLibError( - "If \"lookup\" is present, no values must be specified", - location) + 'If "lookup" is present, no values must be specified', location + ) return self.ast.ChainContextPosStatement( - prefix, glyphs, suffix, lookups, location=location) + prefix, glyphs, suffix, lookups, location=location + ) # Pair positioning, format A: "pos V 10 A -10;" # Pair positioning, format B: "pos V A -20;" @@ -673,31 +710,41 @@ class Parser(object): if values[0] is None: # Format B: "pos V A -20;" values.reverse() return self.ast.PairPosStatement( - glyphs[0], values[0], glyphs[1], values[1], + glyphs[0], + values[0], + glyphs[1], + values[1], enumerated=enumerated, - location=location) + location=location, + ) if enumerated: raise FeatureLibError( - '"enumerate" is only allowed with pair positionings', location) - return self.ast.SinglePosStatement(list(zip(glyphs, values)), - prefix, suffix, forceChain=hasMarks, - location=location) + '"enumerate" is only allowed with pair positionings', location + ) + return self.ast.SinglePosStatement( + list(zip(glyphs, values)), + prefix, + suffix, + forceChain=hasMarks, + location=location, + ) def parse_position_cursive_(self, enumerated, vertical): location = self.cur_token_location_ self.expect_keyword_("cursive") if enumerated: raise FeatureLibError( - '"enumerate" is not allowed with ' - 'cursive attachment positioning', - location) + '"enumerate" is not allowed with ' "cursive attachment positioning", + location, + ) glyphclass = self.parse_glyphclass_(accept_glyphname=True) entryAnchor = self.parse_anchor_() exitAnchor = self.parse_anchor_() self.expect_symbol_(";") return self.ast.CursivePosStatement( - glyphclass, entryAnchor, exitAnchor, location=location) + glyphclass, entryAnchor, exitAnchor, location=location + ) def parse_position_base_(self, enumerated, vertical): location = self.cur_token_location_ @@ -705,8 +752,9 @@ class Parser(object): if enumerated: raise FeatureLibError( '"enumerate" is not allowed with ' - 'mark-to-base attachment positioning', - location) + "mark-to-base attachment positioning", + location, + ) base = self.parse_glyphclass_(accept_glyphname=True) marks = self.parse_anchor_marks_() self.expect_symbol_(";") @@ -718,8 +766,9 @@ class Parser(object): if enumerated: raise FeatureLibError( '"enumerate" is not allowed with ' - 'mark-to-ligature attachment positioning', - location) + "mark-to-ligature attachment positioning", + location, + ) ligatures = self.parse_glyphclass_(accept_glyphname=True) marks = [self.parse_anchor_marks_()] while self.next_token_ == "ligComponent": @@ -734,13 +783,13 @@ class Parser(object): if enumerated: raise FeatureLibError( '"enumerate" is not allowed with ' - 'mark-to-mark attachment positioning', - location) + "mark-to-mark attachment positioning", + location, + ) baseMarks = self.parse_glyphclass_(accept_glyphname=True) marks = self.parse_anchor_marks_() self.expect_symbol_(";") - return self.ast.MarkMarkPosStatement(baseMarks, marks, - location=location) + return self.ast.MarkMarkPosStatement(baseMarks, marks, location=location) def parse_script_(self): assert self.is_cur_keyword_("script") @@ -752,11 +801,18 @@ class Parser(object): assert self.cur_token_ in {"substitute", "sub", "reversesub", "rsub"} location = self.cur_token_location_ reverse = self.cur_token_ in {"reversesub", "rsub"} - old_prefix, old, lookups, values, old_suffix, hasMarks = \ - self.parse_glyph_pattern_(vertical=False) + ( + old_prefix, + old, + lookups, + values, + old_suffix, + hasMarks, + ) = self.parse_glyph_pattern_(vertical=False) if any(values): raise FeatureLibError( - "Substitution statements cannot contain values", location) + "Substitution statements cannot contain values", location + ) new = [] if self.next_token_ == "by": keyword = self.expect_keyword_("by") @@ -772,25 +828,25 @@ class Parser(object): if len(new) == 0 and not any(lookups): raise FeatureLibError( 'Expected "by", "from" or explicit lookup references', - self.cur_token_location_) + self.cur_token_location_, + ) # GSUB lookup type 3: Alternate substitution. # Format: "substitute a from [a.1 a.2 a.3];" if keyword == "from": if reverse: raise FeatureLibError( - 'Reverse chaining substitutions do not support "from"', - location) + 'Reverse chaining substitutions do not support "from"', location + ) if len(old) != 1 or len(old[0].glyphSet()) != 1: - raise FeatureLibError( - 'Expected a single glyph before "from"', - location) + raise FeatureLibError('Expected a single glyph before "from"', location) if len(new) != 1: raise FeatureLibError( - 'Expected a single glyphclass after "from"', - location) + 'Expected a single glyphclass after "from"', location + ) return self.ast.AlternateSubstStatement( - old_prefix, old[0], old_suffix, new[0], location=location) + old_prefix, old[0], old_suffix, new[0], location=location + ) num_lookups = len([l for l in lookups if l is not None]) @@ -798,8 +854,7 @@ class Parser(object): # Format A: "substitute a by a.sc;" # Format B: "substitute [one.fitted one.oldstyle] by one;" # Format C: "substitute [a-d] by [A.sc-D.sc];" - if (not reverse and len(old) == 1 and len(new) == 1 and - num_lookups == 0): + if not reverse and len(old) == 1 and len(new) == 1 and num_lookups == 0: glyphs = list(old[0].glyphSet()) replacements = list(new[0].glyphSet()) if len(replacements) == 1: @@ -807,36 +862,50 @@ class Parser(object): if len(glyphs) != len(replacements): raise FeatureLibError( 'Expected a glyph class with %d elements after "by", ' - 'but found a glyph class with %d elements' % - (len(glyphs), len(replacements)), location) + "but found a glyph class with %d elements" + % (len(glyphs), len(replacements)), + location, + ) return self.ast.SingleSubstStatement( - old, new, - old_prefix, old_suffix, - forceChain=hasMarks, - location=location + old, new, old_prefix, old_suffix, forceChain=hasMarks, location=location ) # GSUB lookup type 2: Multiple substitution. # Format: "substitute f_f_i by f f i;" - if (not reverse and - len(old) == 1 and len(old[0].glyphSet()) == 1 and - len(new) > 1 and max([len(n.glyphSet()) for n in new]) == 1 and - num_lookups == 0): + if ( + not reverse + and len(old) == 1 + and len(old[0].glyphSet()) == 1 + and len(new) > 1 + and max([len(n.glyphSet()) for n in new]) == 1 + and num_lookups == 0 + ): return self.ast.MultipleSubstStatement( - old_prefix, tuple(old[0].glyphSet())[0], old_suffix, + old_prefix, + tuple(old[0].glyphSet())[0], + old_suffix, tuple([list(n.glyphSet())[0] for n in new]), - forceChain=hasMarks, location=location) + forceChain=hasMarks, + location=location, + ) # GSUB lookup type 4: Ligature substitution. # Format: "substitute f f i by f_f_i;" - if (not reverse and - len(old) > 1 and len(new) == 1 and - len(new[0].glyphSet()) == 1 and - num_lookups == 0): + if ( + not reverse + and len(old) > 1 + and len(new) == 1 + and len(new[0].glyphSet()) == 1 + and num_lookups == 0 + ): return self.ast.LigatureSubstStatement( - old_prefix, old, old_suffix, - list(new[0].glyphSet())[0], forceChain=hasMarks, - location=location) + old_prefix, + old, + old_suffix, + list(new[0].glyphSet())[0], + forceChain=hasMarks, + location=location, + ) # GSUB lookup type 8: Reverse chaining substitution. if reverse: @@ -844,16 +913,19 @@ class Parser(object): raise FeatureLibError( "In reverse chaining single substitutions, " "only a single glyph or glyph class can be replaced", - location) + location, + ) if len(new) != 1: raise FeatureLibError( - 'In reverse chaining single substitutions, ' + "In reverse chaining single substitutions, " 'the replacement (after "by") must be a single glyph ' - 'or glyph class', location) + "or glyph class", + location, + ) if num_lookups != 0: raise FeatureLibError( - "Reverse chaining substitutions cannot call named lookups", - location) + "Reverse chaining substitutions cannot call named lookups", location + ) glyphs = sorted(list(old[0].glyphSet())) replacements = sorted(list(new[0].glyphSet())) if len(replacements) == 1: @@ -861,27 +933,29 @@ class Parser(object): if len(glyphs) != len(replacements): raise FeatureLibError( 'Expected a glyph class with %d elements after "by", ' - 'but found a glyph class with %d elements' % - (len(glyphs), len(replacements)), location) + "but found a glyph class with %d elements" + % (len(glyphs), len(replacements)), + location, + ) return self.ast.ReverseChainSingleSubstStatement( - old_prefix, old_suffix, old, new, location=location) + old_prefix, old_suffix, old, new, location=location + ) if len(old) > 1 and len(new) > 1: raise FeatureLibError( - 'Direct substitution of multiple glyphs by multiple glyphs ' - 'is not supported', - location) + "Direct substitution of multiple glyphs by multiple glyphs " + "is not supported", + location, + ) # If there are remaining glyphs to parse, this is an invalid GSUB statement if len(new) != 0: - raise FeatureLibError( - 'Invalid substitution statement', - location - ) + raise FeatureLibError("Invalid substitution statement", location) # GSUB lookup type 6: Chaining contextual substitution. rule = self.ast.ChainContextSubstStatement( - old_prefix, old, old_suffix, lookups, location=location) + old_prefix, old, old_suffix, lookups, location=location + ) return rule def parse_subtable_(self): @@ -899,23 +973,22 @@ class Parser(object): SubfamilyID = self.expect_number_() RangeStart = 0 RangeEnd = 0 - if self.next_token_type_ in (Lexer.NUMBER, Lexer.FLOAT) or \ - SubfamilyID != 0: + if self.next_token_type_ in (Lexer.NUMBER, Lexer.FLOAT) or SubfamilyID != 0: RangeStart = self.expect_decipoint_() RangeEnd = self.expect_decipoint_() self.expect_symbol_(";") - return self.ast.SizeParameters(DesignSize, SubfamilyID, - RangeStart, RangeEnd, - location=location) + return self.ast.SizeParameters( + DesignSize, SubfamilyID, RangeStart, RangeEnd, location=location + ) def parse_size_menuname_(self): assert self.is_cur_keyword_("sizemenuname") location = self.cur_token_location_ platformID, platEncID, langID, string = self.parse_name_() - return self.ast.FeatureNameStatement("size", platformID, - platEncID, langID, string, - location=location) + return self.ast.FeatureNameStatement( + "size", platformID, platEncID, langID, string, location=location + ) def parse_table_(self): assert self.is_cur_keyword_("table") @@ -934,13 +1007,15 @@ class Parser(object): if handler: handler(table) else: - raise FeatureLibError('"table %s" is not supported' % name.strip(), - location) + raise FeatureLibError( + '"table %s" is not supported' % name.strip(), location + ) self.expect_symbol_("}") end_tag = self.expect_tag_() if end_tag != name: - raise FeatureLibError('Expected "%s"' % name.strip(), - self.cur_token_location_) + raise FeatureLibError( + 'Expected "%s"' % name.strip(), self.cur_token_location_ + ) self.expect_symbol_(";") return table @@ -949,8 +1024,9 @@ class Parser(object): while self.next_token_ != "}" or self.cur_comments_: self.advance_lexer_(comments=True) if self.cur_token_type_ is Lexer.COMMENT: - statements.append(self.ast.Comment( - self.cur_token_, location=self.cur_token_location_)) + statements.append( + self.ast.Comment(self.cur_token_, location=self.cur_token_location_) + ) elif self.is_cur_keyword_("Attach"): statements.append(self.parse_attach_()) elif self.is_cur_keyword_("GlyphClassDef"): @@ -963,24 +1039,24 @@ class Parser(object): continue else: raise FeatureLibError( - "Expected Attach, LigatureCaretByIndex, " - "or LigatureCaretByPos", - self.cur_token_location_) + "Expected Attach, LigatureCaretByIndex, " "or LigatureCaretByPos", + self.cur_token_location_, + ) def parse_table_head_(self, table): statements = table.statements while self.next_token_ != "}" or self.cur_comments_: self.advance_lexer_(comments=True) if self.cur_token_type_ is Lexer.COMMENT: - statements.append(self.ast.Comment( - self.cur_token_, location=self.cur_token_location_)) + statements.append( + self.ast.Comment(self.cur_token_, location=self.cur_token_location_) + ) elif self.is_cur_keyword_("FontRevision"): statements.append(self.parse_FontRevision_()) elif self.cur_token_ == ";": continue else: - raise FeatureLibError("Expected FontRevision", - self.cur_token_location_) + raise FeatureLibError("Expected FontRevision", self.cur_token_location_) def parse_table_hhea_(self, table): statements = table.statements @@ -988,22 +1064,26 @@ class Parser(object): while self.next_token_ != "}" or self.cur_comments_: self.advance_lexer_(comments=True) if self.cur_token_type_ is Lexer.COMMENT: - statements.append(self.ast.Comment( - self.cur_token_, location=self.cur_token_location_)) + statements.append( + self.ast.Comment(self.cur_token_, location=self.cur_token_location_) + ) elif self.cur_token_type_ is Lexer.NAME and self.cur_token_ in fields: key = self.cur_token_.lower() value = self.expect_number_() statements.append( - self.ast.HheaField(key, value, - location=self.cur_token_location_)) + self.ast.HheaField(key, value, location=self.cur_token_location_) + ) if self.next_token_ != ";": - raise FeatureLibError("Incomplete statement", self.next_token_location_) + raise FeatureLibError( + "Incomplete statement", self.next_token_location_ + ) elif self.cur_token_ == ";": continue else: - raise FeatureLibError("Expected CaretOffset, Ascender, " - "Descender or LineGap", - self.cur_token_location_) + raise FeatureLibError( + "Expected CaretOffset, Ascender, " "Descender or LineGap", + self.cur_token_location_, + ) def parse_table_vhea_(self, table): statements = table.statements @@ -1011,30 +1091,36 @@ class Parser(object): while self.next_token_ != "}" or self.cur_comments_: self.advance_lexer_(comments=True) if self.cur_token_type_ is Lexer.COMMENT: - statements.append(self.ast.Comment( - self.cur_token_, location=self.cur_token_location_)) + statements.append( + self.ast.Comment(self.cur_token_, location=self.cur_token_location_) + ) elif self.cur_token_type_ is Lexer.NAME and self.cur_token_ in fields: key = self.cur_token_.lower() value = self.expect_number_() statements.append( - self.ast.VheaField(key, value, - location=self.cur_token_location_)) + self.ast.VheaField(key, value, location=self.cur_token_location_) + ) if self.next_token_ != ";": - raise FeatureLibError("Incomplete statement", self.next_token_location_) + raise FeatureLibError( + "Incomplete statement", self.next_token_location_ + ) elif self.cur_token_ == ";": continue else: - raise FeatureLibError("Expected VertTypoAscender, " - "VertTypoDescender or VertTypoLineGap", - self.cur_token_location_) + raise FeatureLibError( + "Expected VertTypoAscender, " + "VertTypoDescender or VertTypoLineGap", + self.cur_token_location_, + ) def parse_table_name_(self, table): statements = table.statements while self.next_token_ != "}" or self.cur_comments_: self.advance_lexer_(comments=True) if self.cur_token_type_ is Lexer.COMMENT: - statements.append(self.ast.Comment( - self.cur_token_, location=self.cur_token_location_)) + statements.append( + self.ast.Comment(self.cur_token_, location=self.cur_token_location_) + ) elif self.is_cur_keyword_("nameid"): statement = self.parse_nameid_() if statement: @@ -1042,8 +1128,7 @@ class Parser(object): elif self.cur_token_ == ";": continue else: - raise FeatureLibError("Expected nameid", - self.cur_token_location_) + raise FeatureLibError("Expected nameid", self.cur_token_location_) def parse_name_(self): """Parses a name record. See `section 9.e `_.""" @@ -1061,12 +1146,12 @@ class Parser(object): platformID = 3 location = self.cur_token_location_ - if platformID == 1: # Macintosh - platEncID = platEncID or 0 # Roman - langID = langID or 0 # English - else: # 3, Windows - platEncID = platEncID or 1 # Unicode - langID = langID or 0x0409 # English + if platformID == 1: # Macintosh + platEncID = platEncID or 0 # Roman + langID = langID or 0 # English + else: # 3, Windows + platEncID = platEncID or 1 # Unicode + langID = langID or 0x0409 # English string = self.expect_string_() self.expect_symbol_(";") @@ -1081,17 +1166,21 @@ class Parser(object): assert self.cur_token_ == "nameid", self.cur_token_ location, nameID = self.cur_token_location_, self.expect_any_number_() if nameID > 32767: - raise FeatureLibError("Name id value cannot be greater than 32767", - self.cur_token_location_) + raise FeatureLibError( + "Name id value cannot be greater than 32767", self.cur_token_location_ + ) if 1 <= nameID <= 6: - log.warning("Name id %d cannot be set from the feature file. " - "Ignoring record" % nameID) + log.warning( + "Name id %d cannot be set from the feature file. " + "Ignoring record" % nameID + ) self.parse_name_() # skip to the next record return None platformID, platEncID, langID, string = self.parse_name_() - return self.ast.NameRecord(nameID, platformID, platEncID, - langID, string, location=location) + return self.ast.NameRecord( + nameID, platformID, platEncID, langID, string, location=location + ) def unescape_string_(self, string, encoding): if encoding == "utf_16_be": @@ -1120,38 +1209,59 @@ class Parser(object): while self.next_token_ != "}" or self.cur_comments_: self.advance_lexer_(comments=True) if self.cur_token_type_ is Lexer.COMMENT: - statements.append(self.ast.Comment( - self.cur_token_, location=self.cur_token_location_)) + statements.append( + self.ast.Comment(self.cur_token_, location=self.cur_token_location_) + ) elif self.is_cur_keyword_("HorizAxis.BaseTagList"): horiz_bases = self.parse_base_tag_list_() elif self.is_cur_keyword_("HorizAxis.BaseScriptList"): horiz_scripts = self.parse_base_script_list_(len(horiz_bases)) statements.append( - self.ast.BaseAxis(horiz_bases, - horiz_scripts, False, - location=self.cur_token_location_)) + self.ast.BaseAxis( + horiz_bases, + horiz_scripts, + False, + location=self.cur_token_location_, + ) + ) elif self.is_cur_keyword_("VertAxis.BaseTagList"): vert_bases = self.parse_base_tag_list_() elif self.is_cur_keyword_("VertAxis.BaseScriptList"): vert_scripts = self.parse_base_script_list_(len(vert_bases)) statements.append( - self.ast.BaseAxis(vert_bases, - vert_scripts, True, - location=self.cur_token_location_)) + self.ast.BaseAxis( + vert_bases, + vert_scripts, + True, + location=self.cur_token_location_, + ) + ) elif self.cur_token_ == ";": continue def parse_table_OS_2_(self, table): statements = table.statements - numbers = ("FSType", "TypoAscender", "TypoDescender", "TypoLineGap", - "winAscent", "winDescent", "XHeight", "CapHeight", - "WeightClass", "WidthClass", "LowerOpSize", "UpperOpSize") + numbers = ( + "FSType", + "TypoAscender", + "TypoDescender", + "TypoLineGap", + "winAscent", + "winDescent", + "XHeight", + "CapHeight", + "WeightClass", + "WidthClass", + "LowerOpSize", + "UpperOpSize", + ) ranges = ("UnicodeRange", "CodePageRange") while self.next_token_ != "}" or self.cur_comments_: self.advance_lexer_(comments=True) if self.cur_token_type_ is Lexer.COMMENT: - statements.append(self.ast.Comment( - self.cur_token_, location=self.cur_token_location_)) + statements.append( + self.ast.Comment(self.cur_token_, location=self.cur_token_location_) + ) elif self.cur_token_type_ is Lexer.NAME: key = self.cur_token_.lower() value = None @@ -1164,19 +1274,21 @@ class Parser(object): elif self.cur_token_ in ranges: value = [] while self.next_token_ != ";": - value.append(self.expect_number_()) + value.append(self.expect_number_()) elif self.is_cur_keyword_("Vendor"): value = self.expect_string_() statements.append( - self.ast.OS2Field(key, value, - location=self.cur_token_location_)) + self.ast.OS2Field(key, value, location=self.cur_token_location_) + ) elif self.cur_token_ == ";": continue def parse_base_tag_list_(self): # Parses BASE table entries. (See `section 9.a `_) - assert self.cur_token_ in ("HorizAxis.BaseTagList", - "VertAxis.BaseTagList"), self.cur_token_ + assert self.cur_token_ in ( + "HorizAxis.BaseTagList", + "VertAxis.BaseTagList", + ), self.cur_token_ bases = [] while self.next_token_ != ";": bases.append(self.expect_script_tag_()) @@ -1184,8 +1296,10 @@ class Parser(object): return bases def parse_base_script_list_(self, count): - assert self.cur_token_ in ("HorizAxis.BaseScriptList", - "VertAxis.BaseScriptList"), self.cur_token_ + assert self.cur_token_ in ( + "HorizAxis.BaseScriptList", + "VertAxis.BaseScriptList", + ), self.cur_token_ scripts = [(self.parse_base_script_record_(count))] while self.next_token_ == ",": self.expect_symbol_(",") @@ -1221,13 +1335,13 @@ class Parser(object): if self.next_token_type_ is Lexer.NUMBER: number, location = self.expect_number_(), self.cur_token_location_ if vertical: - val = self.ast.ValueRecord(yAdvance=number, - vertical=vertical, - location=location) + val = self.ast.ValueRecord( + yAdvance=number, vertical=vertical, location=location + ) else: - val = self.ast.ValueRecord(xAdvance=number, - vertical=vertical, - location=location) + val = self.ast.ValueRecord( + xAdvance=number, vertical=vertical, location=location + ) return val self.expect_symbol_("<") location = self.cur_token_location_ @@ -1238,40 +1352,57 @@ class Parser(object): return self.ast.ValueRecord() vrd = self.valuerecords_.resolve(name) if vrd is None: - raise FeatureLibError("Unknown valueRecordDef \"%s\"" % name, - self.cur_token_location_) + raise FeatureLibError( + 'Unknown valueRecordDef "%s"' % name, self.cur_token_location_ + ) value = vrd.value xPlacement, yPlacement = (value.xPlacement, value.yPlacement) xAdvance, yAdvance = (value.xAdvance, value.yAdvance) else: xPlacement, yPlacement, xAdvance, yAdvance = ( - self.expect_number_(), self.expect_number_(), - self.expect_number_(), self.expect_number_()) + self.expect_number_(), + self.expect_number_(), + self.expect_number_(), + self.expect_number_(), + ) if self.next_token_ == "<": xPlaDevice, yPlaDevice, xAdvDevice, yAdvDevice = ( - self.parse_device_(), self.parse_device_(), - self.parse_device_(), self.parse_device_()) - allDeltas = sorted([ - delta - for size, delta - in (xPlaDevice if xPlaDevice else ()) + - (yPlaDevice if yPlaDevice else ()) + - (xAdvDevice if xAdvDevice else ()) + - (yAdvDevice if yAdvDevice else ())]) + self.parse_device_(), + self.parse_device_(), + self.parse_device_(), + self.parse_device_(), + ) + allDeltas = sorted( + [ + delta + for size, delta in (xPlaDevice if xPlaDevice else ()) + + (yPlaDevice if yPlaDevice else ()) + + (xAdvDevice if xAdvDevice else ()) + + (yAdvDevice if yAdvDevice else ()) + ] + ) if allDeltas[0] < -128 or allDeltas[-1] > 127: raise FeatureLibError( "Device value out of valid range (-128..127)", - self.cur_token_location_) + self.cur_token_location_, + ) else: - xPlaDevice, yPlaDevice, xAdvDevice, yAdvDevice = ( - None, None, None, None) + xPlaDevice, yPlaDevice, xAdvDevice, yAdvDevice = (None, None, None, None) self.expect_symbol_(">") return self.ast.ValueRecord( - xPlacement, yPlacement, xAdvance, yAdvance, - xPlaDevice, yPlaDevice, xAdvDevice, yAdvDevice, - vertical=vertical, location=location) + xPlacement, + yPlacement, + xAdvance, + yAdvance, + xPlaDevice, + yPlaDevice, + xAdvDevice, + yAdvDevice, + vertical=vertical, + location=location, + ) def parse_valuerecord_definition_(self, vertical): # Parses a named value record definition. (See section `2.e.v `_) @@ -1290,14 +1421,13 @@ class Parser(object): script = self.expect_script_tag_() language = self.expect_language_tag_() self.expect_symbol_(";") - return self.ast.LanguageSystemStatement(script, language, - location=location) + return self.ast.LanguageSystemStatement(script, language, location=location) def parse_feature_block_(self): assert self.cur_token_ == "feature" location = self.cur_token_location_ tag = self.expect_tag_() - vertical = (tag in {"vkrn", "vpal", "vhal", "valt"}) + vertical = tag in {"vkrn", "vpal", "vhal", "valt"} stylisticset = None cv_feature = None @@ -1314,10 +1444,10 @@ class Parser(object): self.expect_keyword_("useExtension") use_extension = True - block = self.ast.FeatureBlock(tag, use_extension=use_extension, - location=location) - self.parse_block_(block, vertical, stylisticset, size_feature, - cv_feature) + block = self.ast.FeatureBlock( + tag, use_extension=use_extension, location=location + ) + self.parse_block_(block, vertical, stylisticset, size_feature, cv_feature) return block def parse_feature_reference_(self): @@ -1325,35 +1455,36 @@ class Parser(object): location = self.cur_token_location_ featureName = self.expect_tag_() self.expect_symbol_(";") - return self.ast.FeatureReferenceStatement(featureName, - location=location) + return self.ast.FeatureReferenceStatement(featureName, location=location) def parse_featureNames_(self, tag): """Parses a ``featureNames`` statement found in stylistic set features. See section `8.c `_.""" assert self.cur_token_ == "featureNames", self.cur_token_ - block = self.ast.NestedBlock(tag, self.cur_token_, - location=self.cur_token_location_) + block = self.ast.NestedBlock( + tag, self.cur_token_, location=self.cur_token_location_ + ) self.expect_symbol_("{") for symtab in self.symbol_tables_: symtab.enter_scope() while self.next_token_ != "}" or self.cur_comments_: self.advance_lexer_(comments=True) if self.cur_token_type_ is Lexer.COMMENT: - block.statements.append(self.ast.Comment( - self.cur_token_, location=self.cur_token_location_)) + block.statements.append( + self.ast.Comment(self.cur_token_, location=self.cur_token_location_) + ) elif self.is_cur_keyword_("name"): location = self.cur_token_location_ platformID, platEncID, langID, string = self.parse_name_() block.statements.append( - self.ast.FeatureNameStatement(tag, platformID, - platEncID, langID, string, - location=location)) + self.ast.FeatureNameStatement( + tag, platformID, platEncID, langID, string, location=location + ) + ) elif self.cur_token_ == ";": continue else: - raise FeatureLibError('Expected "name"', - self.cur_token_location_) + raise FeatureLibError('Expected "name"', self.cur_token_location_) self.expect_symbol_("}") for symtab in self.symbol_tables_: symtab.exit_scope() @@ -1364,8 +1495,9 @@ class Parser(object): # Parses a ``cvParameters`` block found in Character Variant features. # See section `8.d `_. assert self.cur_token_ == "cvParameters", self.cur_token_ - block = self.ast.NestedBlock(tag, self.cur_token_, - location=self.cur_token_location_) + block = self.ast.NestedBlock( + tag, self.cur_token_, location=self.cur_token_location_ + ) self.expect_symbol_("{") for symtab in self.symbol_tables_: symtab.enter_scope() @@ -1374,12 +1506,17 @@ class Parser(object): while self.next_token_ != "}" or self.cur_comments_: self.advance_lexer_(comments=True) if self.cur_token_type_ is Lexer.COMMENT: - statements.append(self.ast.Comment( - self.cur_token_, location=self.cur_token_location_)) - elif self.is_cur_keyword_({"FeatUILabelNameID", - "FeatUITooltipTextNameID", - "SampleTextNameID", - "ParamUILabelNameID"}): + statements.append( + self.ast.Comment(self.cur_token_, location=self.cur_token_location_) + ) + elif self.is_cur_keyword_( + { + "FeatUILabelNameID", + "FeatUITooltipTextNameID", + "SampleTextNameID", + "ParamUILabelNameID", + } + ): statements.append(self.parse_cvNameIDs_(tag, self.cur_token_)) elif self.is_cur_keyword_("Character"): statements.append(self.parse_cvCharacter_(tag)) @@ -1388,8 +1525,10 @@ class Parser(object): else: raise FeatureLibError( "Expected statement: got {} {}".format( - self.cur_token_type_, self.cur_token_), - self.cur_token_location_) + self.cur_token_type_, self.cur_token_ + ), + self.cur_token_location_, + ) self.expect_symbol_("}") for symtab in self.symbol_tables_: @@ -1399,28 +1538,34 @@ class Parser(object): def parse_cvNameIDs_(self, tag, block_name): assert self.cur_token_ == block_name, self.cur_token_ - block = self.ast.NestedBlock(tag, block_name, - location=self.cur_token_location_) + block = self.ast.NestedBlock(tag, block_name, location=self.cur_token_location_) self.expect_symbol_("{") for symtab in self.symbol_tables_: symtab.enter_scope() while self.next_token_ != "}" or self.cur_comments_: self.advance_lexer_(comments=True) if self.cur_token_type_ is Lexer.COMMENT: - block.statements.append(self.ast.Comment( - self.cur_token_, location=self.cur_token_location_)) + block.statements.append( + self.ast.Comment(self.cur_token_, location=self.cur_token_location_) + ) elif self.is_cur_keyword_("name"): location = self.cur_token_location_ platformID, platEncID, langID, string = self.parse_name_() block.statements.append( self.ast.CVParametersNameStatement( - tag, platformID, platEncID, langID, string, - block_name, location=location)) + tag, + platformID, + platEncID, + langID, + string, + block_name, + location=location, + ) + ) elif self.cur_token_ == ";": continue else: - raise FeatureLibError('Expected "name"', - self.cur_token_location_) + raise FeatureLibError('Expected "name"', self.cur_token_location_) self.expect_symbol_("}") for symtab in self.symbol_tables_: symtab.exit_scope() @@ -1432,9 +1577,11 @@ class Parser(object): location, character = self.cur_token_location_, self.expect_any_number_() self.expect_symbol_(";") if not (0xFFFFFF >= character >= 0): - raise FeatureLibError("Character value must be between " - "{:#x} and {:#x}".format(0, 0xFFFFFF), - location) + raise FeatureLibError( + "Character value must be between " + "{:#x} and {:#x}".format(0, 0xFFFFFF), + location, + ) return self.ast.CharacterStatement(character, tag, location=location) def parse_FontRevision_(self): @@ -1444,12 +1591,12 @@ class Parser(object): location, version = self.cur_token_location_, self.expect_float_() self.expect_symbol_(";") if version <= 0: - raise FeatureLibError("Font revision numbers must be positive", - location) + raise FeatureLibError("Font revision numbers must be positive", location) return self.ast.FontRevisionStatement(version, location=location) - def parse_block_(self, block, vertical, stylisticset=None, - size_feature=False, cv_feature=None): + def parse_block_( + self, block, vertical, stylisticset=None, size_feature=False, cv_feature=None + ): self.expect_symbol_("{") for symtab in self.symbol_tables_: symtab.enter_scope() @@ -1458,8 +1605,9 @@ class Parser(object): while self.next_token_ != "}" or self.cur_comments_: self.advance_lexer_(comments=True) if self.cur_token_type_ is Lexer.COMMENT: - statements.append(self.ast.Comment( - self.cur_token_, location=self.cur_token_location_)) + statements.append( + self.ast.Comment(self.cur_token_, location=self.cur_token_location_) + ) elif self.cur_token_type_ is Lexer.GLYPHCLASS: statements.append(self.parse_glyphclass_definition_()) elif self.is_cur_keyword_("anchorDef"): @@ -1480,11 +1628,11 @@ class Parser(object): statements.append(self.parse_markClass_()) elif self.is_cur_keyword_({"pos", "position"}): statements.append( - self.parse_position_(enumerated=False, vertical=vertical)) + self.parse_position_(enumerated=False, vertical=vertical) + ) elif self.is_cur_keyword_("script"): statements.append(self.parse_script_()) - elif (self.is_cur_keyword_({"sub", "substitute", - "rsub", "reversesub"})): + elif self.is_cur_keyword_({"sub", "substitute", "rsub", "reversesub"}): statements.append(self.parse_substitute_()) elif self.is_cur_keyword_("subtable"): statements.append(self.parse_subtable_()) @@ -1498,14 +1646,20 @@ class Parser(object): statements.append(self.parse_size_parameters_()) elif size_feature and self.is_cur_keyword_("sizemenuname"): statements.append(self.parse_size_menuname_()) - elif self.cur_token_type_ is Lexer.NAME and self.cur_token_ in self.extensions: + elif ( + self.cur_token_type_ is Lexer.NAME + and self.cur_token_ in self.extensions + ): statements.append(self.extensions[self.cur_token_](self)) elif self.cur_token_ == ";": continue else: raise FeatureLibError( - "Expected glyph class definition or statement: got {} {}".format(self.cur_token_type_, self.cur_token_), - self.cur_token_location_) + "Expected glyph class definition or statement: got {} {}".format( + self.cur_token_type_, self.cur_token_ + ), + self.cur_token_location_, + ) self.expect_symbol_("}") for symtab in self.symbol_tables_: @@ -1513,8 +1667,9 @@ class Parser(object): name = self.expect_name_() if name != block.name.strip(): - raise FeatureLibError("Expected \"%s\"" % block.name.strip(), - self.cur_token_location_) + raise FeatureLibError( + 'Expected "%s"' % block.name.strip(), self.cur_token_location_ + ) self.expect_symbol_(";") # A multiple substitution may have a single destination, in which case @@ -1543,8 +1698,14 @@ class Parser(object): for i, glyph in enumerate(glyphs): statements.append( self.ast.MultipleSubstStatement( - s.prefix, glyph, s.suffix, [replacements[i]], - s.forceChain, location=s.location)) + s.prefix, + glyph, + s.suffix, + [replacements[i]], + s.forceChain, + location=s.location, + ) + ) else: statements.append(s) block.statements = statements @@ -1572,8 +1733,7 @@ class Parser(object): def expect_filename_(self): self.advance_lexer_() if self.cur_token_type_ is not Lexer.FILENAME: - raise FeatureLibError("Expected file name", - self.cur_token_location_) + raise FeatureLibError("Expected file name", self.cur_token_location_) return self.cur_token_ def expect_glyph_(self): @@ -1583,12 +1743,12 @@ class Parser(object): if len(self.cur_token_) > 63: raise FeatureLibError( "Glyph names must not be longer than 63 characters", - self.cur_token_location_) + self.cur_token_location_, + ) return self.cur_token_ elif self.cur_token_type_ is Lexer.CID: return "cid%05d" % self.cur_token_ - raise FeatureLibError("Expected a glyph name or CID", - self.cur_token_location_) + raise FeatureLibError("Expected a glyph name or CID", self.cur_token_location_) def check_glyph_name_in_glyph_set(self, *names): """Raises if glyph name (just `start`) or glyph names of a @@ -1602,18 +1762,20 @@ class Parser(object): raise FeatureLibError( "The following glyph names are referenced but are missing from the " f"glyph set: {', '.join(missing)}", - self.cur_token_location_ + self.cur_token_location_, ) def expect_markClass_reference_(self): name = self.expect_class_name_() mc = self.glyphclasses_.resolve(name) if mc is None: - raise FeatureLibError("Unknown markClass @%s" % name, - self.cur_token_location_) + raise FeatureLibError( + "Unknown markClass @%s" % name, self.cur_token_location_ + ) if not isinstance(mc, self.ast.MarkClass): - raise FeatureLibError("@%s is not a markClass" % name, - self.cur_token_location_) + raise FeatureLibError( + "@%s is not a markClass" % name, self.cur_token_location_ + ) return mc def expect_tag_(self): @@ -1621,8 +1783,9 @@ class Parser(object): if self.cur_token_type_ is not Lexer.NAME: raise FeatureLibError("Expected a tag", self.cur_token_location_) if len(self.cur_token_) > 4: - raise FeatureLibError("Tags can not be longer than 4 characters", - self.cur_token_location_) + raise FeatureLibError( + "Tags can not be longer than 4 characters", self.cur_token_location_ + ) return (self.cur_token_ + " ")[:4] def expect_script_tag_(self): @@ -1630,7 +1793,8 @@ class Parser(object): if tag == "dflt": raise FeatureLibError( '"dflt" is not a valid script tag; use "DFLT" instead', - self.cur_token_location_) + self.cur_token_location_, + ) return tag def expect_language_tag_(self): @@ -1638,22 +1802,21 @@ class Parser(object): if tag == "DFLT": raise FeatureLibError( '"DFLT" is not a valid language tag; use "dflt" instead', - self.cur_token_location_) + self.cur_token_location_, + ) return tag def expect_symbol_(self, symbol): self.advance_lexer_() if self.cur_token_type_ is Lexer.SYMBOL and self.cur_token_ == symbol: return symbol - raise FeatureLibError("Expected '%s'" % symbol, - self.cur_token_location_) + raise FeatureLibError("Expected '%s'" % symbol, self.cur_token_location_) def expect_keyword_(self, keyword): self.advance_lexer_() if self.cur_token_type_ is Lexer.NAME and self.cur_token_ == keyword: return self.cur_token_ - raise FeatureLibError("Expected \"%s\"" % keyword, - self.cur_token_location_) + raise FeatureLibError('Expected "%s"' % keyword, self.cur_token_location_) def expect_name_(self): self.advance_lexer_() @@ -1671,15 +1834,17 @@ class Parser(object): self.advance_lexer_() if self.cur_token_type_ in Lexer.NUMBERS: return self.cur_token_ - raise FeatureLibError("Expected a decimal, hexadecimal or octal number", - self.cur_token_location_) + raise FeatureLibError( + "Expected a decimal, hexadecimal or octal number", self.cur_token_location_ + ) def expect_float_(self): self.advance_lexer_() if self.cur_token_type_ is Lexer.FLOAT: return self.cur_token_ - raise FeatureLibError("Expected a floating-point number", - self.cur_token_location_) + raise FeatureLibError( + "Expected a floating-point number", self.cur_token_location_ + ) def expect_decipoint_(self): if self.next_token_type_ == Lexer.FLOAT: @@ -1687,8 +1852,9 @@ class Parser(object): elif self.next_token_type_ is Lexer.NUMBER: return self.expect_number_() / 10 else: - raise FeatureLibError("Expected an integer or floating-point number", - self.cur_token_location_) + raise FeatureLibError( + "Expected an integer or floating-point number", self.cur_token_location_ + ) def expect_string_(self): self.advance_lexer_() @@ -1703,11 +1869,17 @@ class Parser(object): return else: self.cur_token_type_, self.cur_token_, self.cur_token_location_ = ( - self.next_token_type_, self.next_token_, self.next_token_location_) + self.next_token_type_, + self.next_token_, + self.next_token_location_, + ) while True: try: - (self.next_token_type_, self.next_token_, - self.next_token_location_) = next(self.lexer_) + ( + self.next_token_type_, + self.next_token_, + self.next_token_location_, + ) = next(self.lexer_) except StopIteration: self.next_token_type_, self.next_token_ = (None, None) if self.next_token_type_ != Lexer.COMMENT: @@ -1717,14 +1889,15 @@ class Parser(object): @staticmethod def reverse_string_(s): """'abc' --> 'cba'""" - return ''.join(reversed(list(s))) + return "".join(reversed(list(s))) def make_cid_range_(self, location, start, limit): """(location, 999, 1001) --> ["cid00999", "cid01000", "cid01001"]""" result = list() if start > limit: raise FeatureLibError( - "Bad range: start should be less than limit", location) + "Bad range: start should be less than limit", location + ) for cid in range(start, limit + 1): result.append("cid%05d" % cid) return result @@ -1734,45 +1907,45 @@ class Parser(object): result = list() if len(start) != len(limit): raise FeatureLibError( - "Bad range: \"%s\" and \"%s\" should have the same length" % - (start, limit), location) + 'Bad range: "%s" and "%s" should have the same length' % (start, limit), + location, + ) rev = self.reverse_string_ prefix = os.path.commonprefix([start, limit]) suffix = rev(os.path.commonprefix([rev(start), rev(limit)])) if len(suffix) > 0: - start_range = start[len(prefix):-len(suffix)] - limit_range = limit[len(prefix):-len(suffix)] + start_range = start[len(prefix) : -len(suffix)] + limit_range = limit[len(prefix) : -len(suffix)] else: - start_range = start[len(prefix):] - limit_range = limit[len(prefix):] + start_range = start[len(prefix) :] + limit_range = limit[len(prefix) :] if start_range >= limit_range: raise FeatureLibError( - "Start of range must be smaller than its end", - location) + "Start of range must be smaller than its end", location + ) - uppercase = re.compile(r'^[A-Z]$') + uppercase = re.compile(r"^[A-Z]$") if uppercase.match(start_range) and uppercase.match(limit_range): for c in range(ord(start_range), ord(limit_range) + 1): result.append("%s%c%s" % (prefix, c, suffix)) return result - lowercase = re.compile(r'^[a-z]$') + lowercase = re.compile(r"^[a-z]$") if lowercase.match(start_range) and lowercase.match(limit_range): for c in range(ord(start_range), ord(limit_range) + 1): result.append("%s%c%s" % (prefix, c, suffix)) return result - digits = re.compile(r'^[0-9]{1,3}$') + digits = re.compile(r"^[0-9]{1,3}$") if digits.match(start_range) and digits.match(limit_range): for i in range(int(start_range, 10), int(limit_range, 10) + 1): - number = ("000" + str(i))[-len(start_range):] + number = ("000" + str(i))[-len(start_range) :] result.append("%s%s%s" % (prefix, number, suffix)) return result - raise FeatureLibError("Bad range: \"%s-%s\"" % (start, limit), - location) + raise FeatureLibError('Bad range: "%s-%s"' % (start, limit), location) class SymbolTable(object): diff --git a/Lib/fontTools/otlLib/error.py b/Lib/fontTools/otlLib/error.py index 177f2ea80..1cbef5783 100644 --- a/Lib/fontTools/otlLib/error.py +++ b/Lib/fontTools/otlLib/error.py @@ -1,5 +1,3 @@ - - class OpenTypeLibError(Exception): def __init__(self, message, location): Exception.__init__(self, message) diff --git a/Lib/fontTools/otlLib/maxContextCalc.py b/Lib/fontTools/otlLib/maxContextCalc.py index 40c3d6dbd..03e7561b6 100644 --- a/Lib/fontTools/otlLib/maxContextCalc.py +++ b/Lib/fontTools/otlLib/maxContextCalc.py @@ -1,12 +1,11 @@ - -__all__ = ['maxCtxFont'] +__all__ = ["maxCtxFont"] def maxCtxFont(font): """Calculate the usMaxContext value for an entire font.""" maxCtx = 0 - for tag in ('GSUB', 'GPOS'): + for tag in ("GSUB", "GPOS"): if tag not in font: continue table = font[tag].table @@ -24,62 +23,59 @@ def maxCtxSubtable(maxCtx, tag, lookupType, st): """ # single positioning, single / multiple substitution - if (tag == 'GPOS' and lookupType == 1) or ( - tag == 'GSUB' and lookupType in (1, 2, 3)): + if (tag == "GPOS" and lookupType == 1) or ( + tag == "GSUB" and lookupType in (1, 2, 3) + ): maxCtx = max(maxCtx, 1) # pair positioning - elif tag == 'GPOS' and lookupType == 2: + elif tag == "GPOS" and lookupType == 2: maxCtx = max(maxCtx, 2) # ligatures - elif tag == 'GSUB' and lookupType == 4: + elif tag == "GSUB" and lookupType == 4: for ligatures in st.ligatures.values(): for ligature in ligatures: maxCtx = max(maxCtx, ligature.CompCount) # context - elif (tag == 'GPOS' and lookupType == 7) or ( - tag == 'GSUB' and lookupType == 5): - maxCtx = maxCtxContextualSubtable( - maxCtx, st, 'Pos' if tag == 'GPOS' else 'Sub') + elif (tag == "GPOS" and lookupType == 7) or (tag == "GSUB" and lookupType == 5): + maxCtx = maxCtxContextualSubtable(maxCtx, st, "Pos" if tag == "GPOS" else "Sub") # chained context - elif (tag == 'GPOS' and lookupType == 8) or ( - tag == 'GSUB' and lookupType == 6): + elif (tag == "GPOS" and lookupType == 8) or (tag == "GSUB" and lookupType == 6): maxCtx = maxCtxContextualSubtable( - maxCtx, st, 'Pos' if tag == 'GPOS' else 'Sub', 'Chain') + maxCtx, st, "Pos" if tag == "GPOS" else "Sub", "Chain" + ) # extensions - elif (tag == 'GPOS' and lookupType == 9) or ( - tag == 'GSUB' and lookupType == 7): - maxCtx = maxCtxSubtable( - maxCtx, tag, st.ExtensionLookupType, st.ExtSubTable) + elif (tag == "GPOS" and lookupType == 9) or (tag == "GSUB" and lookupType == 7): + maxCtx = maxCtxSubtable(maxCtx, tag, st.ExtensionLookupType, st.ExtSubTable) # reverse-chained context - elif tag == 'GSUB' and lookupType == 8: - maxCtx = maxCtxContextualRule(maxCtx, st, 'Reverse') + elif tag == "GSUB" and lookupType == 8: + maxCtx = maxCtxContextualRule(maxCtx, st, "Reverse") return maxCtx -def maxCtxContextualSubtable(maxCtx, st, ruleType, chain=''): +def maxCtxContextualSubtable(maxCtx, st, ruleType, chain=""): """Calculate usMaxContext based on a contextual feature subtable.""" if st.Format == 1: - for ruleset in getattr(st, '%s%sRuleSet' % (chain, ruleType)): + for ruleset in getattr(st, "%s%sRuleSet" % (chain, ruleType)): if ruleset is None: continue - for rule in getattr(ruleset, '%s%sRule' % (chain, ruleType)): + for rule in getattr(ruleset, "%s%sRule" % (chain, ruleType)): if rule is None: continue maxCtx = maxCtxContextualRule(maxCtx, rule, chain) elif st.Format == 2: - for ruleset in getattr(st, '%s%sClassSet' % (chain, ruleType)): + for ruleset in getattr(st, "%s%sClassSet" % (chain, ruleType)): if ruleset is None: continue - for rule in getattr(ruleset, '%s%sClassRule' % (chain, ruleType)): + for rule in getattr(ruleset, "%s%sClassRule" % (chain, ruleType)): if rule is None: continue maxCtx = maxCtxContextualRule(maxCtx, rule, chain) @@ -95,6 +91,6 @@ def maxCtxContextualRule(maxCtx, st, chain): if not chain: return max(maxCtx, st.GlyphCount) - elif chain == 'Reverse': + elif chain == "Reverse": return max(maxCtx, st.GlyphCount + st.LookAheadGlyphCount) return max(maxCtx, st.InputGlyphCount + st.LookAheadGlyphCount)