def __init__(self, featurefile, font=None, glyphNames=None, includeDir=None): self.ff = fontFeatures.FontFeatures() self.markclasses = {} self.currentFeature = None self.currentRoutine = None self.gensym = 1 self.glyphmap = () self.currentLanguage = None if font and not glyphNames: glyphNames = font.getGlyphOrder() if isinstance(featurefile, str): featurefile = io.StringIO(featurefile) self.featurefile = featurefile if glyphNames: self.parser = Parser(self.featurefile, glyphNames=glyphNames, includeDir=includeDir) else: self.parser = Parser(self.featurefile, includeDir=includeDir) self.parser.ast.ValueRecord = fontFeatures.ValueRecord
def parseLayoutFeatures(font): """Parse OpenType layout features in the UFO and return a feaLib.ast.FeatureFile instance. """ featxt = font.features.text or "" if not featxt: return ast.FeatureFile() buf = StringIO(featxt) ufoPath = font.path includeDir = None if ufoPath is not None: # The UFO v3 specification says "Any include() statements must be relative to # the UFO path, not to the features.fea file itself". We set the `name` # attribute on the buffer to the actual feature file path, which feaLib will # pick up and use to attribute errors to the correct file, and explicitly set # the include directory to the parent of the UFO. ufoPath = os.path.normpath(ufoPath) buf.name = os.path.join(ufoPath, "features.fea") includeDir = os.path.dirname(ufoPath) glyphNames = set(font.keys()) try: parser = Parser(buf, glyphNames, includeDir=includeDir) doc = parser.parse() except IncludedFeaNotFound as e: if ufoPath and os.path.exists(os.path.join(ufoPath, e.args[0])): logger.warning("Please change the file name in the include(...); " "statement to be relative to the UFO itself, " "instead of relative to the 'features.fea' file " "contained in it.") raise return doc
def parseLayoutFeatures(font): """ Parse OpenType layout features in the UFO and return a feaLib.ast.FeatureFile instance. """ featxt = tounicode(font.features.text or "", "utf-8") if not featxt: return ast.FeatureFile() buf = UnicodeIO(featxt) # the path is used by the lexer to resolve 'include' statements # and print filename in error messages. For the UFO spec, this # should be the path of the UFO, not the inner features.fea: # https://github.com/unified-font-object/ufo-spec/issues/55 ufoPath = font.path if ufoPath is not None: buf.name = ufoPath glyphNames = set(font.keys()) try: parser = Parser(buf, glyphNames) doc = parser.parse() except IncludedFeaNotFound as e: if ufoPath and os.path.exists(os.path.join(ufoPath, e.args[0])): logger.warning("Please change the file name in the include(...); " "statement to be relative to the UFO itself, " "instead of relative to the 'features.fea' file " "contained in it.") raise return doc
def parse(self, text): if not self.tempdir: self.tempdir = tempfile.mkdtemp() self.num_tempfiles += 1 path = os.path.join(self.tempdir, "tmp%d.fea" % self.num_tempfiles) with codecs.open(path, "wb", "utf-8") as outfile: outfile.write(text) return Parser(path).parse()
def parseFea(text): from fontTools.feaLib.parser import Parser if isinstance(text, ast.FeatureFile): return text f = StringIO(text) fea = Parser(f).parse() return fea
def check_fea2fea_file(self, name): f = self.getpath("{}.fea".format(name)) p = Parser(f) doc = p.parse() tlines = self.normal_fea(doc.asFea().split("\n")) with open(f, "r", encoding="utf-8") as ofile: olines = self.normal_fea(ofile.readlines()) if olines != tlines: for line in difflib.unified_diff(olines, tlines): sys.stdout.write(line) self.fail("Fea2Fea output is different from expected")
def parse(features): names = set() featurefile = UnicodeIO(tounicode(features)) fea = Parser(featurefile, []).parse() for statement in fea.statements: if getattr(statement, "name", None) in ("isol", "ccmp"): for substatement in statement.statements: if hasattr(substatement, "glyphs"): # Single names.update(substatement.glyphs[0].glyphSet()) elif hasattr(substatement, "glyph"): # Multiple names.add(substatement.glyph) return names
def __init__(self, featurefile, font=None): self.ff = fontFeatures.FontFeatures() self.markclasses = {} self.currentFeature = None self.currentRoutine = None self.gensym = 1 self.glyphmap = () self.currentLanguage = None if font: self.glyphmap = font.getReverseGlyphMap() if isinstance(featurefile, str): featurefile = io.StringIO(featurefile) self.featurefile = featurefile self.parser = Parser(self.featurefile, self.glyphmap) self.parser.ast.ValueRecord = fontFeatures.ValueRecord
def build(self): self.parseTree = Parser(self.featurefile_path).parse() self.parseTree.build(self) for tag in ('GPOS', 'GSUB'): table = self.makeTable(tag) if (table.ScriptList.ScriptCount > 0 or table.FeatureList.FeatureCount > 0 or table.LookupList.LookupCount > 0): fontTable = self.font[tag] = getTableClass(tag)() fontTable.table = table elif tag in self.font: del self.font[tag] gdef = self.makeGDEF() if gdef: self.font["GDEF"] = gdef elif "GDEF" in self.font: del self.font["GDEF"]
def __init__(self, featurefile, font=None): from fontTools.feaLib.parser import Parser self.ff = fontFeatures.FontFeatures() self.markclasses = {} self.currentFeature = None self.currentRoutine = None self.gensym = 1 self.language_systems = [] glyphmap = () if font: glyphmap = font.getReverseGlyphMap() if isinstance(featurefile, str): featurefile = io.StringIO(featurefile) parsetree = Parser(featurefile, glyphmap).parse() self.features_ = {} parsetree.build(self)
def test_build_pre_parsed_ast_featurefile(self): f = StringIO("feature liga {sub f i by f_i;} liga;") tree = Parser(f).parse() font = makeTTFont() addOpenTypeFeatures(font, tree) assert "GSUB" in font
def test_substitute_lookups(self): doc = Parser(self.getpath("spec5fi1.fea")).parse() [langsys, ligs, sub, feature] = doc.statements self.assertEqual(feature.statements[0].lookups, [ligs, None, sub]) self.assertEqual(feature.statements[1].lookups, [ligs, None, sub])
reMarkClass = re.compile(r"markClass.*;") markClassDefinitions = reMarkClass.findall( markFeatures) # Save all the markClass definitions # Read the input feature file with open(in_path, "r") as input_fea: # Replace $markClasses with the generated markClassDefinitions if markClassDefinitions != []: features = input_fea.read().replace( "$markClasses", "\n".join(markClassDefinitions)) else: features = input_fea.read().replace("$markClasses", "") if stylename: # Replace the matra I style-based include path for each style print("Replacing $stylename with '%s' if found in feature file" % stylename) features = features.replace("$stylename", stylename) # Write to a temporary file, which we can parse and expand all includes with open("production/features/tmp.fea", "w") as tmp: tmp.write(features) parser = Parser("production/features/tmp.fea") parsed = parser.parse() os.remove("production/features/tmp.fea") # Write the parsed and substituted features to the UFO features with open(out_path + "/features.fea", "w") as fea: fea.write(str(parsed))
def build(self): parsetree = Parser(self.featurefile_path).parse() parsetree.build(self) for tag in ('GPOS', 'GSUB'): fontTable = self.font[tag] = getTableClass(tag)() fontTable.table = self.makeTable(tag)