1from __future__ import ( 2 print_function, 3 division, 4 absolute_import, 5 unicode_literals, 6) 7from textwrap import dedent 8import logging 9import re 10from fontTools import ttLib 11from fontTools.feaLib.error import IncludedFeaNotFound, FeatureLibError 12from ufo2ft.featureWriters import ( 13 BaseFeatureWriter, 14 KernFeatureWriter, 15 FEATURE_WRITERS_KEY, 16 ast, 17) 18from ufo2ft.featureCompiler import FeatureCompiler, parseLayoutFeatures, logger 19import py 20import pytest 21from .testSupport import pushd 22 23 24class ParseLayoutFeaturesTest(object): 25 26 def test_include(self, FontClass, tmpdir): 27 tmpdir.join("test.fea").write_text( 28 dedent( 29 """\ 30 # hello world 31 """ 32 ), 33 encoding="utf-8", 34 ) 35 ufo = FontClass() 36 ufo.features.text = dedent( 37 """\ 38 include(test.fea) 39 """ 40 ) 41 ufo.save(str(tmpdir.join("Test.ufo"))) 42 43 fea = parseLayoutFeatures(ufo) 44 45 assert "# hello world" in str(fea) 46 47 def test_include_no_ufo_path(self, FontClass, tmpdir): 48 ufo = FontClass() 49 ufo.features.text = dedent( 50 """\ 51 include(test.fea) 52 """ 53 ) 54 with pushd(str(tmpdir)): 55 with pytest.raises(IncludedFeaNotFound): 56 parseLayoutFeatures(ufo) 57 58 def test_include_not_found(self, FontClass, tmpdir, caplog): 59 caplog.set_level(logging.ERROR) 60 61 tmpdir.join("test.fea").write_text( 62 dedent( 63 """\ 64 # hello world 65 """ 66 ), 67 encoding="utf-8", 68 ) 69 ufo = FontClass() 70 ufo.features.text = dedent( 71 """\ 72 include(../test.fea) 73 """ 74 ) 75 ufo.save(str(tmpdir.join("Test.ufo"))) 76 77 with caplog.at_level(logging.WARNING, logger=logger.name): 78 with pytest.raises(IncludedFeaNotFound): 79 parseLayoutFeatures(ufo) 80 81 assert len(caplog.records) == 1 82 assert "change the file name in the include" in caplog.text 83 84 85class FeatureCompilerTest(object): 86 87 def test_ttFont(self, FontClass): 88 ufo = FontClass() 89 ufo.newGlyph("f") 90 ufo.newGlyph("f_f") 91 ufo.features.text = dedent( 92 """\ 93 feature liga { 94 sub f f by f_f; 95 } liga; 96 """ 97 ) 98 ttFont = ttLib.TTFont() 99 ttFont.setGlyphOrder(["f", "f_f"]) 100 101 compiler = FeatureCompiler(ufo, ttFont) 102 compiler.compile() 103 104 assert "GSUB" in ttFont 105 106 gsub = ttFont["GSUB"].table 107 assert gsub.FeatureList.FeatureCount == 1 108 assert gsub.FeatureList.FeatureRecord[0].FeatureTag == "liga" 109 110 def test_ttFont_None(self, FontClass): 111 ufo = FontClass() 112 ufo.newGlyph("f") 113 ufo.newGlyph("f_f") 114 ufo.features.text = dedent( 115 """\ 116 feature liga { 117 sub f f by f_f; 118 } liga; 119 """ 120 ) 121 122 compiler = FeatureCompiler(ufo) 123 ttFont = compiler.compile() 124 125 assert "GSUB" in ttFont 126 127 gsub = ttFont["GSUB"].table 128 assert gsub.FeatureList.FeatureCount == 1 129 assert gsub.FeatureList.FeatureRecord[0].FeatureTag == "liga" 130 131 def test_deprecated_methods(self, FontClass): 132 compiler = FeatureCompiler(FontClass()) 133 with pytest.warns(UserWarning, match="method is deprecated"): 134 compiler.setupFile_features() 135 136 compiler.features = "" 137 with pytest.warns(UserWarning, match="method is deprecated"): 138 compiler.setupFile_featureTables() 139 140 class UserCompiler(FeatureCompiler): 141 142 def setupFile_features(self): 143 self.features = "# hello world" 144 145 def setupFile_featureTables(self): 146 self.ttFont = ttLib.TTFont() 147 148 compiler = UserCompiler(FontClass()) 149 with pytest.warns(UserWarning, match="method is deprecated"): 150 compiler.compile() 151 152 def test_deprecated_mtiFeatures_argument(self, FontClass): 153 with pytest.warns(UserWarning, match="argument is ignored"): 154 FeatureCompiler(FontClass(), mtiFeatures="whatever") 155 156 def test_featureWriters_empty(self, FontClass): 157 kernWriter = KernFeatureWriter(ignoreMarks=False) 158 ufo = FontClass() 159 ufo.newGlyph("a") 160 ufo.newGlyph("v") 161 ufo.kerning.update({("a", "v"): -40}) 162 compiler = FeatureCompiler(ufo, featureWriters=[kernWriter]) 163 ttFont1 = compiler.compile() 164 assert "GPOS" in ttFont1 165 166 compiler = FeatureCompiler(ufo, featureWriters=[]) 167 ttFont2 = compiler.compile() 168 assert "GPOS" not in ttFont2 169 170 def test_loadFeatureWriters_from_UFO_lib(self, FontClass): 171 ufo = FontClass() 172 ufo.newGlyph("a") 173 ufo.newGlyph("v") 174 ufo.kerning.update({("a", "v"): -40}) 175 ufo.lib[FEATURE_WRITERS_KEY] = [{"class": "KernFeatureWriter"}] 176 compiler = FeatureCompiler(ufo) 177 ttFont = compiler.compile() 178 179 assert len(compiler.featureWriters) == 1 180 assert isinstance(compiler.featureWriters[0], KernFeatureWriter) 181 assert "GPOS" in ttFont 182 183 def test_GSUB_writers_run_first(self, FontClass): 184 185 class FooFeatureWriter(BaseFeatureWriter): 186 187 tableTag = "GSUB" 188 189 def write(self, font, feaFile, compiler=None): 190 foo = ast.FeatureBlock("FOO ") 191 foo.statements.append( 192 ast.SingleSubstStatement( 193 "a", "v", prefix="", suffix="", forceChain=None 194 ) 195 ) 196 feaFile.statements.append(foo) 197 198 featureWriters = [KernFeatureWriter, FooFeatureWriter] 199 200 ufo = FontClass() 201 ufo.newGlyph("a") 202 ufo.newGlyph("v") 203 ufo.kerning.update({("a", "v"): -40}) 204 205 compiler = FeatureCompiler(ufo, featureWriters=featureWriters) 206 207 assert len(compiler.featureWriters) == 2 208 assert compiler.featureWriters[0].tableTag == "GSUB" 209 assert compiler.featureWriters[1].tableTag == "GPOS" 210 211 ttFont = compiler.compile() 212 213 assert "GSUB" in ttFont 214 215 gsub = ttFont["GSUB"].table 216 assert gsub.FeatureList.FeatureCount == 1 217 assert gsub.FeatureList.FeatureRecord[0].FeatureTag == "FOO " 218 219 def test_buildTables_FeatureLibError(self, FontClass, caplog): 220 caplog.set_level(logging.CRITICAL) 221 222 ufo = FontClass() 223 ufo.newGlyph("f") 224 ufo.newGlyph("f.alt01") 225 features = dedent( 226 """\ 227 feature BUGS { 228 # invalid 229 lookup MIXED_TYPE { 230 sub f by f.alt01; 231 sub f f by f_f; 232 } MIXED_TYPE; 233 234 } BUGS; 235 """ 236 ) 237 ufo.features.text = features 238 239 compiler = FeatureCompiler(ufo) 240 241 tmpfile = None 242 try: 243 with caplog.at_level(logging.ERROR, logger=logger.name): 244 with pytest.raises(FeatureLibError): 245 compiler.compile() 246 247 assert len(caplog.records) == 1 248 assert "Compilation failed! Inspect temporary file" in caplog.text 249 250 tmpfile = py.path.local(re.findall(".*: '(.*)'$", caplog.text)[0]) 251 252 assert tmpfile.exists() 253 assert tmpfile.read_text("utf-8") == features 254 finally: 255 if tmpfile is not None: 256 tmpfile.remove(ignore_errors=True) 257