def test_mark_mkmk_features(self, testufo): writer = MarkFeatureWriter() # by default both mark + mkmk are built feaFile = ast.FeatureFile() assert writer.write(testufo, feaFile) assert str(feaFile) == dedent("""\ markClass acutecomb <anchor 100 200> @MC_top; markClass tildecomb <anchor 100 200> @MC_top; feature mark { lookup mark2base { pos base a <anchor 100 200> mark @MC_top; } mark2base; lookup mark2liga { pos ligature f_i <anchor 100 500> mark @MC_top ligComponent <anchor 600 500> mark @MC_top; } mark2liga; } mark; feature mkmk { lookup mark2mark_top { @MFS_mark2mark_top = [acutecomb tildecomb]; lookupflag UseMarkFilteringSet @MFS_mark2mark_top; pos mark tildecomb <anchor 100 300> mark @MC_top; } mark2mark_top; } mkmk; """)
def test__makeMarkClassDefinitions_non_empty(self, FontClass): ufo = FontClass() ufo.newGlyph("a").appendAnchor({"name": "top", "x": 250, "y": 500}) ufo.newGlyph("c").appendAnchor({"name": "bottom", "x": 250, "y": -100}) ufo.newGlyph("grave").appendAnchor({ "name": "_top", "x": 100, "y": 200 }) ufo.newGlyph("cedilla").appendAnchor({ "name": "_bottom", "x": 100, "y": 0 }) ufo.features.text = dedent("""\ markClass cedilla <anchor 200 0> @MC_bottom; markClass grave <anchor 100 200> @MC_top; """) writer = MarkFeatureWriter() feaFile = parseLayoutFeatures(ufo) writer.setContext(ufo, feaFile) markClassDefs = writer._makeMarkClassDefinitions() assert len(markClassDefs) == 1 assert len(feaFile.markClasses) == 3 assert "MC_bottom" in feaFile.markClasses assert "MC_top" in feaFile.markClasses assert [str(mcd) for mcd in markClassDefs ] == ["markClass cedilla <anchor 100 0> @MC_bottom_1;"]
def test_warn_duplicate_anchor_names(self, FontClass, caplog): caplog.set_level(logging.ERROR) ufo = FontClass() ufo.newGlyph("a").anchors = [ { "name": "top", "x": 100, "y": 200 }, { "name": "top", "x": 200, "y": 300 }, ] writer = MarkFeatureWriter() feaFile = ast.FeatureFile() logger = "ufo2ft.featureWriters.markFeatureWriter.MarkFeatureWriter" with caplog.at_level(logging.WARNING, logger=logger): writer.setContext(ufo, feaFile) assert len(caplog.records) == 1 assert "duplicate anchor 'top' in glyph 'a'" in caplog.text
def test_insert_comment_middle(self, testufo): writer = MarkFeatureWriter() testufo.features.text = dedent("""\ markClass acutecomb <anchor 100 200> @MC_top; feature mark { lookup mark1 { pos base a <anchor 100 200> mark @MC_top; } mark1; # # Automatic Code # lookup mark2 { pos base a <anchor 150 250> mark @MC_top; } mark2; } mark; """) feaFile = parseLayoutFeatures(testufo) with pytest.raises( InvalidFeaturesData, match="Insert marker has rules before and after, feature mark " "cannot be inserted.", ): writer.write(testufo, feaFile) # test append mode ignores insert marker generated = self.writeFeatures(testufo, mode="append") assert str(generated) == dedent("""\ markClass tildecomb <anchor 100 200> @MC_top; feature mark { lookup mark2base { pos base a <anchor 100 200> mark @MC_top; } mark2base; lookup mark2liga { pos ligature f_i <anchor 100 500> mark @MC_top ligComponent <anchor 600 500> mark @MC_top; } mark2liga; } mark; feature mkmk { lookup mark2mark_top { @MFS_mark2mark_top = [acutecomb tildecomb]; lookupflag UseMarkFilteringSet @MFS_mark2mark_top; pos mark tildecomb <anchor 100 300> mark @MC_top; } mark2mark_top; } mkmk; """)
def test_defs_and_lookups_first(self, testufo): testufo.newGlyph("circumflexcomb") writer = MarkFeatureWriter() testufo.features.text = dedent("""\ feature mkmk { # Automatic Code # Move acutecomb down and right if preceded by circumflexcomb lookup move_acutecomb { lookupflag UseMarkFilteringSet [acutecomb circumflexcomb]; pos circumflexcomb acutecomb' <0 20 0 20>; } move_acutecomb; } mkmk; """) feaFile = parseLayoutFeatures(testufo) assert writer.write(testufo, feaFile) assert str(feaFile) == dedent("""\ markClass acutecomb <anchor 100 200> @MC_top; markClass tildecomb <anchor 100 200> @MC_top; feature mark { lookup mark2base { pos base a <anchor 100 200> mark @MC_top; } mark2base; lookup mark2liga { pos ligature f_i <anchor 100 500> mark @MC_top ligComponent <anchor 600 500> mark @MC_top; } mark2liga; } mark; feature mkmk { lookup mark2mark_top { @MFS_mark2mark_top = [acutecomb tildecomb]; lookupflag UseMarkFilteringSet @MFS_mark2mark_top; pos mark tildecomb <anchor 100 300> mark @MC_top; } mark2mark_top; } mkmk; feature mkmk { # Move acutecomb down and right if preceded by circumflexcomb lookup move_acutecomb { lookupflag UseMarkFilteringSet [acutecomb circumflexcomb]; pos circumflexcomb acutecomb' <0 20 0 20>; } move_acutecomb; } mkmk; """)
def test_skip_unnamed_anchors(self, FontClass, caplog): caplog.set_level(logging.ERROR) ufo = FontClass() ufo.newGlyph("a").appendAnchor({"x": 100, "y": 200}) writer = MarkFeatureWriter() feaFile = ast.FeatureFile() logger = "ufo2ft.featureWriters.markFeatureWriter.MarkFeatureWriter" with caplog.at_level(logging.WARNING, logger=logger): writer.setContext(ufo, feaFile) assert len(caplog.records) == 1 assert "unnamed anchor discarded in glyph 'a'" in caplog.text
def test_insert_comment_outside_block(self, testufo): writer = MarkFeatureWriter() testufo.features.text = dedent("""\ # # Automatic Code # """) feaFile = parseLayoutFeatures(testufo) assert writer.write(testufo, feaFile) testufo.features.text = dedent("""\ # # Automatic Code # markClass acutecomb <anchor 100 200> @MC_top; feature mark { lookup mark1 { pos base a <anchor 100 200> mark @MC_top; } mark1; } mark; """) feaFile = parseLayoutFeatures(testufo) assert writer.write(testufo, feaFile) # test append mode writer = MarkFeatureWriter(mode="append") assert writer.write(testufo, feaFile)
def test__makeMarkClassDefinitions_empty(self, FontClass): ufo = FontClass() ufo.newGlyph("a").appendAnchor({"name": "top", "x": 250, "y": 500}) ufo.newGlyph("c").appendAnchor({"name": "bottom", "x": 250, "y": -100}) ufo.newGlyph("grave").appendAnchor({ "name": "_top", "x": 100, "y": 200 }) ufo.newGlyph("cedilla").appendAnchor({ "name": "_bottom", "x": 100, "y": 0 }) writer = MarkFeatureWriter() feaFile = ast.FeatureFile() writer.setContext(ufo, feaFile) markClassDefs = writer._makeMarkClassDefinitions() assert len(feaFile.markClasses) == 2 assert [str(mcd) for mcd in markClassDefs] == [ "markClass cedilla <anchor 100 0> @MC_bottom;", "markClass grave <anchor 100 200> @MC_top;", ]
def test_write_only_one(self, testufo): writer = MarkFeatureWriter(features=["mkmk"]) # only builds "mkmk" feaFile = ast.FeatureFile() assert writer.write(testufo, feaFile) fea = str(feaFile) assert "feature mark" not in fea assert "feature mkmk" in fea writer = MarkFeatureWriter(features=["mark"]) # only builds "mark" feaFile = ast.FeatureFile() assert writer.write(testufo, feaFile) fea = str(feaFile) assert "feature mark" in fea assert "feature mkmk" not in fea
in_path = sys.argv[1] out_path = sys.argv[2] if len(sys.argv) == 4: stylename = sys.argv[3] else: # e.g. "Bold" in "sources/masters/Rasa-Bold.ufo" stylename = re.search(r"Rasa\-([A-z]+)", out_path).group(1) features = "" # Generate markClass statements and prepend them font = Font(out_path) markClassDefinitions = [] feaFile = FeatureFile() # A holder for the generated mark features markWriter = MarkFeatureWriter() markWriter.write(font, feaFile) markFeatures = feaFile.asFea() # Get the generated mark features 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", "")
def test_insert_comment_after(self, testufo): writer = MarkFeatureWriter() testufo.features.text = dedent("""\ markClass acutecomb <anchor 100 200> @MC_top; feature mark { lookup mark1 { pos base a <anchor 100 200> mark @MC_top; } mark1; # # Automatic Code # } mark; """) feaFile = parseLayoutFeatures(testufo) assert writer.write(testufo, feaFile) assert str(feaFile) == dedent("""\ markClass acutecomb <anchor 100 200> @MC_top; feature mark { lookup mark1 { pos base a <anchor 100 200> mark @MC_top; } mark1; # # } mark; markClass tildecomb <anchor 100 200> @MC_top; feature mark { lookup mark2base { pos base a <anchor 100 200> mark @MC_top; } mark2base; lookup mark2liga { pos ligature f_i <anchor 100 500> mark @MC_top ligComponent <anchor 600 500> mark @MC_top; } mark2liga; } mark; feature mkmk { lookup mark2mark_top { @MFS_mark2mark_top = [acutecomb tildecomb]; lookupflag UseMarkFilteringSet @MFS_mark2mark_top; pos mark tildecomb <anchor 100 300> mark @MC_top; } mark2mark_top; } mkmk; """) # test append mode ignores insert marker generated = self.writeFeatures(testufo, mode="append") assert str(generated) == dedent("""\ markClass tildecomb <anchor 100 200> @MC_top; feature mark { lookup mark2base { pos base a <anchor 100 200> mark @MC_top; } mark2base; lookup mark2liga { pos ligature f_i <anchor 100 500> mark @MC_top ligComponent <anchor 600 500> mark @MC_top; } mark2liga; } mark; feature mkmk { lookup mark2mark_top { @MFS_mark2mark_top = [acutecomb tildecomb]; lookupflag UseMarkFilteringSet @MFS_mark2mark_top; pos mark tildecomb <anchor 100 300> mark @MC_top; } mark2mark_top; } mkmk; """)