1*e1fe3e4aSElliott Hughes# -*- coding: utf-8 -*- 2*e1fe3e4aSElliott Hughesfrom fontTools.misc.loggingTools import CapturingLogHandler 3*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.error import FeatureLibError 4*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.parser import Parser, SymbolTable 5*e1fe3e4aSElliott Hughesfrom io import StringIO 6*e1fe3e4aSElliott Hughesimport warnings 7*e1fe3e4aSElliott Hughesimport fontTools.feaLib.ast as ast 8*e1fe3e4aSElliott Hughesimport os 9*e1fe3e4aSElliott Hughesimport unittest 10*e1fe3e4aSElliott Hughes 11*e1fe3e4aSElliott Hughes 12*e1fe3e4aSElliott Hughesdef glyphstr(glyphs): 13*e1fe3e4aSElliott Hughes def f(x): 14*e1fe3e4aSElliott Hughes if len(x) == 1: 15*e1fe3e4aSElliott Hughes return list(x)[0] 16*e1fe3e4aSElliott Hughes else: 17*e1fe3e4aSElliott Hughes return "[%s]" % " ".join(sorted(list(x))) 18*e1fe3e4aSElliott Hughes 19*e1fe3e4aSElliott Hughes return " ".join(f(g.glyphSet()) for g in glyphs) 20*e1fe3e4aSElliott Hughes 21*e1fe3e4aSElliott Hughes 22*e1fe3e4aSElliott Hughesdef mapping(s): 23*e1fe3e4aSElliott Hughes b = [] 24*e1fe3e4aSElliott Hughes for a in s.glyphs: 25*e1fe3e4aSElliott Hughes b.extend(a.glyphSet()) 26*e1fe3e4aSElliott Hughes c = [] 27*e1fe3e4aSElliott Hughes for a in s.replacements: 28*e1fe3e4aSElliott Hughes c.extend(a.glyphSet()) 29*e1fe3e4aSElliott Hughes if len(c) == 1: 30*e1fe3e4aSElliott Hughes c = c * len(b) 31*e1fe3e4aSElliott Hughes return dict(zip(b, c)) 32*e1fe3e4aSElliott Hughes 33*e1fe3e4aSElliott Hughes 34*e1fe3e4aSElliott HughesGLYPHNAMES = ( 35*e1fe3e4aSElliott Hughes ( 36*e1fe3e4aSElliott Hughes """ 37*e1fe3e4aSElliott Hughes .notdef space A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 38*e1fe3e4aSElliott Hughes A.sc B.sc C.sc D.sc E.sc F.sc G.sc H.sc I.sc J.sc K.sc L.sc M.sc 39*e1fe3e4aSElliott Hughes N.sc O.sc P.sc Q.sc R.sc S.sc T.sc U.sc V.sc W.sc X.sc Y.sc Z.sc 40*e1fe3e4aSElliott Hughes A.swash B.swash X.swash Y.swash Z.swash 41*e1fe3e4aSElliott Hughes a b c d e f g h i j k l m n o p q r s t u v w x y z 42*e1fe3e4aSElliott Hughes a.sc b.sc c.sc d.sc e.sc f.sc g.sc h.sc i.sc j.sc k.sc l.sc m.sc 43*e1fe3e4aSElliott Hughes n.sc o.sc p.sc q.sc r.sc s.sc t.sc u.sc v.sc w.sc x.sc y.sc z.sc 44*e1fe3e4aSElliott Hughes a.swash b.swash x.swash y.swash z.swash 45*e1fe3e4aSElliott Hughes foobar foo.09 foo.1234 foo.9876 46*e1fe3e4aSElliott Hughes one two five six acute grave dieresis umlaut cedilla ogonek macron 47*e1fe3e4aSElliott Hughes a_f_f_i o_f_f_i f_i f_l f_f_i one.fitted one.oldstyle a.1 a.2 a.3 c_t 48*e1fe3e4aSElliott Hughes PRE SUF FIX BACK TRACK LOOK AHEAD ampersand ampersand.1 ampersand.2 49*e1fe3e4aSElliott Hughes cid00001 cid00002 cid00003 cid00004 cid00005 cid00006 cid00007 50*e1fe3e4aSElliott Hughes cid12345 cid78987 cid00999 cid01000 cid01001 cid00998 cid00995 51*e1fe3e4aSElliott Hughes cid00111 cid00222 52*e1fe3e4aSElliott Hughes comma endash emdash figuredash damma hamza 53*e1fe3e4aSElliott Hughes c_d d.alt n.end s.end f_f 54*e1fe3e4aSElliott Hughes""" 55*e1fe3e4aSElliott Hughes ).split() 56*e1fe3e4aSElliott Hughes + ["foo.%d" % i for i in range(1, 200)] 57*e1fe3e4aSElliott Hughes + ["G" * 600] 58*e1fe3e4aSElliott Hughes) 59*e1fe3e4aSElliott Hughes 60*e1fe3e4aSElliott Hughes 61*e1fe3e4aSElliott Hughesclass ParserTest(unittest.TestCase): 62*e1fe3e4aSElliott Hughes def __init__(self, methodName): 63*e1fe3e4aSElliott Hughes unittest.TestCase.__init__(self, methodName) 64*e1fe3e4aSElliott Hughes # Python 3 renamed assertRaisesRegexp to assertRaisesRegex, 65*e1fe3e4aSElliott Hughes # and fires deprecation warnings if a program uses the old name. 66*e1fe3e4aSElliott Hughes if not hasattr(self, "assertRaisesRegex"): 67*e1fe3e4aSElliott Hughes self.assertRaisesRegex = self.assertRaisesRegexp 68*e1fe3e4aSElliott Hughes 69*e1fe3e4aSElliott Hughes def test_glyphMap_deprecated(self): 70*e1fe3e4aSElliott Hughes glyphMap = {"a": 0, "b": 1, "c": 2} 71*e1fe3e4aSElliott Hughes with warnings.catch_warnings(record=True) as w: 72*e1fe3e4aSElliott Hughes warnings.simplefilter("always") 73*e1fe3e4aSElliott Hughes parser = Parser(StringIO(), glyphMap=glyphMap) 74*e1fe3e4aSElliott Hughes 75*e1fe3e4aSElliott Hughes self.assertEqual(len(w), 1) 76*e1fe3e4aSElliott Hughes self.assertEqual(w[-1].category, UserWarning) 77*e1fe3e4aSElliott Hughes self.assertIn("deprecated", str(w[-1].message)) 78*e1fe3e4aSElliott Hughes self.assertEqual(parser.glyphNames_, {"a", "b", "c"}) 79*e1fe3e4aSElliott Hughes 80*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 81*e1fe3e4aSElliott Hughes TypeError, 82*e1fe3e4aSElliott Hughes "mutually exclusive", 83*e1fe3e4aSElliott Hughes Parser, 84*e1fe3e4aSElliott Hughes StringIO(), 85*e1fe3e4aSElliott Hughes ("a",), 86*e1fe3e4aSElliott Hughes glyphMap={"a": 0}, 87*e1fe3e4aSElliott Hughes ) 88*e1fe3e4aSElliott Hughes 89*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 90*e1fe3e4aSElliott Hughes TypeError, "unsupported keyword argument", Parser, StringIO(), foo="bar" 91*e1fe3e4aSElliott Hughes ) 92*e1fe3e4aSElliott Hughes 93*e1fe3e4aSElliott Hughes def test_comments(self): 94*e1fe3e4aSElliott Hughes doc = self.parse( 95*e1fe3e4aSElliott Hughes """ # Initial 96*e1fe3e4aSElliott Hughes feature test { 97*e1fe3e4aSElliott Hughes sub A by B; # simple 98*e1fe3e4aSElliott Hughes } test;""" 99*e1fe3e4aSElliott Hughes ) 100*e1fe3e4aSElliott Hughes c1 = doc.statements[0] 101*e1fe3e4aSElliott Hughes c2 = doc.statements[1].statements[1] 102*e1fe3e4aSElliott Hughes self.assertEqual(type(c1), ast.Comment) 103*e1fe3e4aSElliott Hughes self.assertEqual(c1.text, "# Initial") 104*e1fe3e4aSElliott Hughes self.assertEqual(str(c1), "# Initial") 105*e1fe3e4aSElliott Hughes self.assertEqual(type(c2), ast.Comment) 106*e1fe3e4aSElliott Hughes self.assertEqual(c2.text, "# simple") 107*e1fe3e4aSElliott Hughes self.assertEqual(doc.statements[1].name, "test") 108*e1fe3e4aSElliott Hughes 109*e1fe3e4aSElliott Hughes def test_only_comments(self): 110*e1fe3e4aSElliott Hughes doc = self.parse( 111*e1fe3e4aSElliott Hughes """\ 112*e1fe3e4aSElliott Hughes # Initial 113*e1fe3e4aSElliott Hughes """ 114*e1fe3e4aSElliott Hughes ) 115*e1fe3e4aSElliott Hughes c1 = doc.statements[0] 116*e1fe3e4aSElliott Hughes self.assertEqual(type(c1), ast.Comment) 117*e1fe3e4aSElliott Hughes self.assertEqual(c1.text, "# Initial") 118*e1fe3e4aSElliott Hughes self.assertEqual(str(c1), "# Initial") 119*e1fe3e4aSElliott Hughes 120*e1fe3e4aSElliott Hughes def test_anchor_format_a(self): 121*e1fe3e4aSElliott Hughes doc = self.parse( 122*e1fe3e4aSElliott Hughes "feature test {" 123*e1fe3e4aSElliott Hughes " pos cursive A <anchor 120 -20> <anchor NULL>;" 124*e1fe3e4aSElliott Hughes "} test;" 125*e1fe3e4aSElliott Hughes ) 126*e1fe3e4aSElliott Hughes anchor = doc.statements[0].statements[0].entryAnchor 127*e1fe3e4aSElliott Hughes self.assertEqual(type(anchor), ast.Anchor) 128*e1fe3e4aSElliott Hughes self.assertEqual(anchor.x, 120) 129*e1fe3e4aSElliott Hughes self.assertEqual(anchor.y, -20) 130*e1fe3e4aSElliott Hughes self.assertIsNone(anchor.contourpoint) 131*e1fe3e4aSElliott Hughes self.assertIsNone(anchor.xDeviceTable) 132*e1fe3e4aSElliott Hughes self.assertIsNone(anchor.yDeviceTable) 133*e1fe3e4aSElliott Hughes 134*e1fe3e4aSElliott Hughes def test_anchor_format_b(self): 135*e1fe3e4aSElliott Hughes doc = self.parse( 136*e1fe3e4aSElliott Hughes "feature test {" 137*e1fe3e4aSElliott Hughes " pos cursive A <anchor 120 -20 contourpoint 5> <anchor NULL>;" 138*e1fe3e4aSElliott Hughes "} test;" 139*e1fe3e4aSElliott Hughes ) 140*e1fe3e4aSElliott Hughes anchor = doc.statements[0].statements[0].entryAnchor 141*e1fe3e4aSElliott Hughes self.assertEqual(type(anchor), ast.Anchor) 142*e1fe3e4aSElliott Hughes self.assertEqual(anchor.x, 120) 143*e1fe3e4aSElliott Hughes self.assertEqual(anchor.y, -20) 144*e1fe3e4aSElliott Hughes self.assertEqual(anchor.contourpoint, 5) 145*e1fe3e4aSElliott Hughes self.assertIsNone(anchor.xDeviceTable) 146*e1fe3e4aSElliott Hughes self.assertIsNone(anchor.yDeviceTable) 147*e1fe3e4aSElliott Hughes 148*e1fe3e4aSElliott Hughes def test_anchor_format_c(self): 149*e1fe3e4aSElliott Hughes doc = self.parse( 150*e1fe3e4aSElliott Hughes "feature test {" 151*e1fe3e4aSElliott Hughes " pos cursive A " 152*e1fe3e4aSElliott Hughes " <anchor 120 -20 <device 11 111, 12 112> <device NULL>>" 153*e1fe3e4aSElliott Hughes " <anchor NULL>;" 154*e1fe3e4aSElliott Hughes "} test;" 155*e1fe3e4aSElliott Hughes ) 156*e1fe3e4aSElliott Hughes anchor = doc.statements[0].statements[0].entryAnchor 157*e1fe3e4aSElliott Hughes self.assertEqual(type(anchor), ast.Anchor) 158*e1fe3e4aSElliott Hughes self.assertEqual(anchor.x, 120) 159*e1fe3e4aSElliott Hughes self.assertEqual(anchor.y, -20) 160*e1fe3e4aSElliott Hughes self.assertIsNone(anchor.contourpoint) 161*e1fe3e4aSElliott Hughes self.assertEqual(anchor.xDeviceTable, ((11, 111), (12, 112))) 162*e1fe3e4aSElliott Hughes self.assertIsNone(anchor.yDeviceTable) 163*e1fe3e4aSElliott Hughes 164*e1fe3e4aSElliott Hughes def test_anchor_format_d(self): 165*e1fe3e4aSElliott Hughes doc = self.parse( 166*e1fe3e4aSElliott Hughes "feature test {" 167*e1fe3e4aSElliott Hughes " pos cursive A <anchor 120 -20> <anchor NULL>;" 168*e1fe3e4aSElliott Hughes "} test;" 169*e1fe3e4aSElliott Hughes ) 170*e1fe3e4aSElliott Hughes anchor = doc.statements[0].statements[0].exitAnchor 171*e1fe3e4aSElliott Hughes self.assertIsNone(anchor) 172*e1fe3e4aSElliott Hughes 173*e1fe3e4aSElliott Hughes def test_anchor_format_e(self): 174*e1fe3e4aSElliott Hughes doc = self.parse( 175*e1fe3e4aSElliott Hughes "feature test {" 176*e1fe3e4aSElliott Hughes " anchorDef 120 -20 contourpoint 7 Foo;" 177*e1fe3e4aSElliott Hughes " pos cursive A <anchor Foo> <anchor NULL>;" 178*e1fe3e4aSElliott Hughes "} test;" 179*e1fe3e4aSElliott Hughes ) 180*e1fe3e4aSElliott Hughes anchor = doc.statements[0].statements[1].entryAnchor 181*e1fe3e4aSElliott Hughes self.assertEqual(type(anchor), ast.Anchor) 182*e1fe3e4aSElliott Hughes self.assertEqual(anchor.x, 120) 183*e1fe3e4aSElliott Hughes self.assertEqual(anchor.y, -20) 184*e1fe3e4aSElliott Hughes self.assertEqual(anchor.contourpoint, 7) 185*e1fe3e4aSElliott Hughes self.assertIsNone(anchor.xDeviceTable) 186*e1fe3e4aSElliott Hughes self.assertIsNone(anchor.yDeviceTable) 187*e1fe3e4aSElliott Hughes 188*e1fe3e4aSElliott Hughes def test_anchor_format_e_undefined(self): 189*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 190*e1fe3e4aSElliott Hughes FeatureLibError, 191*e1fe3e4aSElliott Hughes 'Unknown anchor "UnknownName"', 192*e1fe3e4aSElliott Hughes self.parse, 193*e1fe3e4aSElliott Hughes "feature test {" 194*e1fe3e4aSElliott Hughes " position cursive A <anchor UnknownName> <anchor NULL>;" 195*e1fe3e4aSElliott Hughes "} test;", 196*e1fe3e4aSElliott Hughes ) 197*e1fe3e4aSElliott Hughes 198*e1fe3e4aSElliott Hughes def test_anchor_variable_scalar(self): 199*e1fe3e4aSElliott Hughes doc = self.parse( 200*e1fe3e4aSElliott Hughes "feature test {" 201*e1fe3e4aSElliott Hughes " pos cursive A <anchor (wght=200:-100 wght=900:-150 wdth=150,wght=900:-120) -20> <anchor NULL>;" 202*e1fe3e4aSElliott Hughes "} test;" 203*e1fe3e4aSElliott Hughes ) 204*e1fe3e4aSElliott Hughes anchor = doc.statements[0].statements[0].entryAnchor 205*e1fe3e4aSElliott Hughes self.assertEqual( 206*e1fe3e4aSElliott Hughes anchor.asFea(), 207*e1fe3e4aSElliott Hughes "<anchor (wght=200:-100 wght=900:-150 wdth=150,wght=900:-120) -20>", 208*e1fe3e4aSElliott Hughes ) 209*e1fe3e4aSElliott Hughes 210*e1fe3e4aSElliott Hughes def test_anchordef(self): 211*e1fe3e4aSElliott Hughes [foo] = self.parse("anchorDef 123 456 foo;").statements 212*e1fe3e4aSElliott Hughes self.assertEqual(type(foo), ast.AnchorDefinition) 213*e1fe3e4aSElliott Hughes self.assertEqual(foo.name, "foo") 214*e1fe3e4aSElliott Hughes self.assertEqual(foo.x, 123) 215*e1fe3e4aSElliott Hughes self.assertEqual(foo.y, 456) 216*e1fe3e4aSElliott Hughes self.assertEqual(foo.contourpoint, None) 217*e1fe3e4aSElliott Hughes 218*e1fe3e4aSElliott Hughes def test_anchordef_contourpoint(self): 219*e1fe3e4aSElliott Hughes [foo] = self.parse("anchorDef 123 456 contourpoint 5 foo;").statements 220*e1fe3e4aSElliott Hughes self.assertEqual(type(foo), ast.AnchorDefinition) 221*e1fe3e4aSElliott Hughes self.assertEqual(foo.name, "foo") 222*e1fe3e4aSElliott Hughes self.assertEqual(foo.x, 123) 223*e1fe3e4aSElliott Hughes self.assertEqual(foo.y, 456) 224*e1fe3e4aSElliott Hughes self.assertEqual(foo.contourpoint, 5) 225*e1fe3e4aSElliott Hughes 226*e1fe3e4aSElliott Hughes def test_anon(self): 227*e1fe3e4aSElliott Hughes anon = self.parse("anon TEST { # a\nfoo\n } TEST; # qux").statements[0] 228*e1fe3e4aSElliott Hughes self.assertIsInstance(anon, ast.AnonymousBlock) 229*e1fe3e4aSElliott Hughes self.assertEqual(anon.tag, "TEST") 230*e1fe3e4aSElliott Hughes self.assertEqual(anon.content, "foo\n ") 231*e1fe3e4aSElliott Hughes 232*e1fe3e4aSElliott Hughes def test_anonymous(self): 233*e1fe3e4aSElliott Hughes anon = self.parse("anonymous TEST {\nbar\n} TEST;").statements[0] 234*e1fe3e4aSElliott Hughes self.assertIsInstance(anon, ast.AnonymousBlock) 235*e1fe3e4aSElliott Hughes self.assertEqual(anon.tag, "TEST") 236*e1fe3e4aSElliott Hughes # feature file spec requires passing the final end-of-line 237*e1fe3e4aSElliott Hughes self.assertEqual(anon.content, "bar\n") 238*e1fe3e4aSElliott Hughes 239*e1fe3e4aSElliott Hughes def test_anon_missingBrace(self): 240*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 241*e1fe3e4aSElliott Hughes FeatureLibError, 242*e1fe3e4aSElliott Hughes "Expected '} TEST;' to terminate anonymous block", 243*e1fe3e4aSElliott Hughes self.parse, 244*e1fe3e4aSElliott Hughes "anon TEST { \n no end in sight", 245*e1fe3e4aSElliott Hughes ) 246*e1fe3e4aSElliott Hughes 247*e1fe3e4aSElliott Hughes def test_attach(self): 248*e1fe3e4aSElliott Hughes doc = self.parse("table GDEF {Attach [a e] 2;} GDEF;") 249*e1fe3e4aSElliott Hughes s = doc.statements[0].statements[0] 250*e1fe3e4aSElliott Hughes self.assertIsInstance(s, ast.AttachStatement) 251*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([s.glyphs]), "[a e]") 252*e1fe3e4aSElliott Hughes self.assertEqual(s.contourPoints, {2}) 253*e1fe3e4aSElliott Hughes 254*e1fe3e4aSElliott Hughes def test_feature_block(self): 255*e1fe3e4aSElliott Hughes [liga] = self.parse("feature liga {} liga;").statements 256*e1fe3e4aSElliott Hughes self.assertEqual(liga.name, "liga") 257*e1fe3e4aSElliott Hughes self.assertFalse(liga.use_extension) 258*e1fe3e4aSElliott Hughes 259*e1fe3e4aSElliott Hughes def test_feature_block_useExtension(self): 260*e1fe3e4aSElliott Hughes [liga] = self.parse("feature liga useExtension {} liga;").statements 261*e1fe3e4aSElliott Hughes self.assertEqual(liga.name, "liga") 262*e1fe3e4aSElliott Hughes self.assertTrue(liga.use_extension) 263*e1fe3e4aSElliott Hughes self.assertEqual(liga.asFea(), "feature liga useExtension {\n \n} liga;\n") 264*e1fe3e4aSElliott Hughes 265*e1fe3e4aSElliott Hughes def test_feature_comment(self): 266*e1fe3e4aSElliott Hughes [liga] = self.parse("feature liga { # Comment\n } liga;").statements 267*e1fe3e4aSElliott Hughes [comment] = liga.statements 268*e1fe3e4aSElliott Hughes self.assertIsInstance(comment, ast.Comment) 269*e1fe3e4aSElliott Hughes self.assertEqual(comment.text, "# Comment") 270*e1fe3e4aSElliott Hughes 271*e1fe3e4aSElliott Hughes def test_feature_reference(self): 272*e1fe3e4aSElliott Hughes doc = self.parse("feature aalt { feature salt; } aalt;") 273*e1fe3e4aSElliott Hughes ref = doc.statements[0].statements[0] 274*e1fe3e4aSElliott Hughes self.assertIsInstance(ref, ast.FeatureReferenceStatement) 275*e1fe3e4aSElliott Hughes self.assertEqual(ref.featureName, "salt") 276*e1fe3e4aSElliott Hughes 277*e1fe3e4aSElliott Hughes def test_FeatureNames_bad(self): 278*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 279*e1fe3e4aSElliott Hughes FeatureLibError, 280*e1fe3e4aSElliott Hughes 'Expected "name"', 281*e1fe3e4aSElliott Hughes self.parse, 282*e1fe3e4aSElliott Hughes "feature ss01 { featureNames { feature test; } ss01;", 283*e1fe3e4aSElliott Hughes ) 284*e1fe3e4aSElliott Hughes 285*e1fe3e4aSElliott Hughes def test_FeatureNames_comment(self): 286*e1fe3e4aSElliott Hughes [feature] = self.parse( 287*e1fe3e4aSElliott Hughes "feature ss01 { featureNames { # Comment\n }; } ss01;" 288*e1fe3e4aSElliott Hughes ).statements 289*e1fe3e4aSElliott Hughes [featureNames] = feature.statements 290*e1fe3e4aSElliott Hughes self.assertIsInstance(featureNames, ast.NestedBlock) 291*e1fe3e4aSElliott Hughes [comment] = featureNames.statements 292*e1fe3e4aSElliott Hughes self.assertIsInstance(comment, ast.Comment) 293*e1fe3e4aSElliott Hughes self.assertEqual(comment.text, "# Comment") 294*e1fe3e4aSElliott Hughes 295*e1fe3e4aSElliott Hughes def test_FeatureNames_emptyStatements(self): 296*e1fe3e4aSElliott Hughes [feature] = self.parse( 297*e1fe3e4aSElliott Hughes "feature ss01 { featureNames { ;;; }; } ss01;" 298*e1fe3e4aSElliott Hughes ).statements 299*e1fe3e4aSElliott Hughes [featureNames] = feature.statements 300*e1fe3e4aSElliott Hughes self.assertIsInstance(featureNames, ast.NestedBlock) 301*e1fe3e4aSElliott Hughes self.assertEqual(featureNames.statements, []) 302*e1fe3e4aSElliott Hughes 303*e1fe3e4aSElliott Hughes def test_FontRevision(self): 304*e1fe3e4aSElliott Hughes doc = self.parse("table head {FontRevision 2.5;} head;") 305*e1fe3e4aSElliott Hughes s = doc.statements[0].statements[0] 306*e1fe3e4aSElliott Hughes self.assertIsInstance(s, ast.FontRevisionStatement) 307*e1fe3e4aSElliott Hughes self.assertEqual(s.revision, 2.5) 308*e1fe3e4aSElliott Hughes 309*e1fe3e4aSElliott Hughes def test_FontRevision_negative(self): 310*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 311*e1fe3e4aSElliott Hughes FeatureLibError, 312*e1fe3e4aSElliott Hughes "Font revision numbers must be positive", 313*e1fe3e4aSElliott Hughes self.parse, 314*e1fe3e4aSElliott Hughes "table head {FontRevision -17.2;} head;", 315*e1fe3e4aSElliott Hughes ) 316*e1fe3e4aSElliott Hughes 317*e1fe3e4aSElliott Hughes def test_strict_glyph_name_check(self): 318*e1fe3e4aSElliott Hughes self.parse("@bad = [a b ccc];", glyphNames=("a", "b", "ccc")) 319*e1fe3e4aSElliott Hughes 320*e1fe3e4aSElliott Hughes with self.assertRaisesRegex( 321*e1fe3e4aSElliott Hughes FeatureLibError, "(?s)missing from the glyph set:.*ccc" 322*e1fe3e4aSElliott Hughes ): 323*e1fe3e4aSElliott Hughes self.parse("@bad = [a b ccc];", glyphNames=("a", "b")) 324*e1fe3e4aSElliott Hughes 325*e1fe3e4aSElliott Hughes def test_glyphclass(self): 326*e1fe3e4aSElliott Hughes [gc] = self.parse("@dash = [endash emdash figuredash];").statements 327*e1fe3e4aSElliott Hughes self.assertEqual(gc.name, "dash") 328*e1fe3e4aSElliott Hughes self.assertEqual(gc.glyphSet(), ("endash", "emdash", "figuredash")) 329*e1fe3e4aSElliott Hughes 330*e1fe3e4aSElliott Hughes def test_glyphclass_glyphNameTooLong(self): 331*e1fe3e4aSElliott Hughes gname = "G" * 600 332*e1fe3e4aSElliott Hughes [gc] = self.parse(f"@GlyphClass = [{gname}];").statements 333*e1fe3e4aSElliott Hughes self.assertEqual(gc.name, "GlyphClass") 334*e1fe3e4aSElliott Hughes self.assertEqual(gc.glyphSet(), (gname,)) 335*e1fe3e4aSElliott Hughes 336*e1fe3e4aSElliott Hughes def test_glyphclass_bad(self): 337*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 338*e1fe3e4aSElliott Hughes FeatureLibError, 339*e1fe3e4aSElliott Hughes "Expected glyph name, glyph range, or glyph class reference", 340*e1fe3e4aSElliott Hughes self.parse, 341*e1fe3e4aSElliott Hughes "@bad = [a 123];", 342*e1fe3e4aSElliott Hughes ) 343*e1fe3e4aSElliott Hughes 344*e1fe3e4aSElliott Hughes def test_glyphclass_duplicate(self): 345*e1fe3e4aSElliott Hughes # makeotf accepts this, so we should too 346*e1fe3e4aSElliott Hughes ab, xy = self.parse("@dup = [a b]; @dup = [x y];").statements 347*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([ab]), "[a b]") 348*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([xy]), "[x y]") 349*e1fe3e4aSElliott Hughes 350*e1fe3e4aSElliott Hughes def test_glyphclass_empty(self): 351*e1fe3e4aSElliott Hughes [gc] = self.parse("@empty_set = [];").statements 352*e1fe3e4aSElliott Hughes self.assertEqual(gc.name, "empty_set") 353*e1fe3e4aSElliott Hughes self.assertEqual(gc.glyphSet(), tuple()) 354*e1fe3e4aSElliott Hughes 355*e1fe3e4aSElliott Hughes def test_glyphclass_equality(self): 356*e1fe3e4aSElliott Hughes [foo, bar] = self.parse("@foo = [a b]; @bar = @foo;").statements 357*e1fe3e4aSElliott Hughes self.assertEqual(foo.glyphSet(), ("a", "b")) 358*e1fe3e4aSElliott Hughes self.assertEqual(bar.glyphSet(), ("a", "b")) 359*e1fe3e4aSElliott Hughes 360*e1fe3e4aSElliott Hughes def test_glyphclass_from_markClass(self): 361*e1fe3e4aSElliott Hughes doc = self.parse( 362*e1fe3e4aSElliott Hughes "markClass [acute grave] <anchor 500 800> @TOP_MARKS;" 363*e1fe3e4aSElliott Hughes "markClass cedilla <anchor 500 -100> @BOTTOM_MARKS;" 364*e1fe3e4aSElliott Hughes "@MARKS = [@TOP_MARKS @BOTTOM_MARKS ogonek];" 365*e1fe3e4aSElliott Hughes "@ALL = @MARKS;" 366*e1fe3e4aSElliott Hughes ) 367*e1fe3e4aSElliott Hughes self.assertEqual( 368*e1fe3e4aSElliott Hughes doc.statements[-1].glyphSet(), ("acute", "grave", "cedilla", "ogonek") 369*e1fe3e4aSElliott Hughes ) 370*e1fe3e4aSElliott Hughes 371*e1fe3e4aSElliott Hughes def test_glyphclass_range_cid(self): 372*e1fe3e4aSElliott Hughes [gc] = self.parse(r"@GlyphClass = [\999-\1001];").statements 373*e1fe3e4aSElliott Hughes self.assertEqual(gc.name, "GlyphClass") 374*e1fe3e4aSElliott Hughes self.assertEqual(gc.glyphSet(), ("cid00999", "cid01000", "cid01001")) 375*e1fe3e4aSElliott Hughes 376*e1fe3e4aSElliott Hughes def test_glyphclass_range_cid_bad(self): 377*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 378*e1fe3e4aSElliott Hughes FeatureLibError, 379*e1fe3e4aSElliott Hughes "Bad range: start should be less than limit", 380*e1fe3e4aSElliott Hughes self.parse, 381*e1fe3e4aSElliott Hughes r"@bad = [\998-\995];", 382*e1fe3e4aSElliott Hughes ) 383*e1fe3e4aSElliott Hughes 384*e1fe3e4aSElliott Hughes def test_glyphclass_range_uppercase(self): 385*e1fe3e4aSElliott Hughes [gc] = self.parse("@swashes = [X.swash-Z.swash];").statements 386*e1fe3e4aSElliott Hughes self.assertEqual(gc.name, "swashes") 387*e1fe3e4aSElliott Hughes self.assertEqual(gc.glyphSet(), ("X.swash", "Y.swash", "Z.swash")) 388*e1fe3e4aSElliott Hughes 389*e1fe3e4aSElliott Hughes def test_glyphclass_range_lowercase(self): 390*e1fe3e4aSElliott Hughes [gc] = self.parse("@defg.sc = [d.sc-g.sc];").statements 391*e1fe3e4aSElliott Hughes self.assertEqual(gc.name, "defg.sc") 392*e1fe3e4aSElliott Hughes self.assertEqual(gc.glyphSet(), ("d.sc", "e.sc", "f.sc", "g.sc")) 393*e1fe3e4aSElliott Hughes 394*e1fe3e4aSElliott Hughes def test_glyphclass_range_dash(self): 395*e1fe3e4aSElliott Hughes glyphNames = "A-foo.sc B-foo.sc C-foo.sc".split() 396*e1fe3e4aSElliott Hughes [gc] = self.parse("@range = [A-foo.sc-C-foo.sc];", glyphNames).statements 397*e1fe3e4aSElliott Hughes self.assertEqual(gc.glyphSet(), ("A-foo.sc", "B-foo.sc", "C-foo.sc")) 398*e1fe3e4aSElliott Hughes 399*e1fe3e4aSElliott Hughes def test_glyphclass_range_dash_with_space(self): 400*e1fe3e4aSElliott Hughes gn = "A-foo.sc B-foo.sc C-foo.sc".split() 401*e1fe3e4aSElliott Hughes [gc] = self.parse("@range = [A-foo.sc - C-foo.sc];", gn).statements 402*e1fe3e4aSElliott Hughes self.assertEqual(gc.glyphSet(), ("A-foo.sc", "B-foo.sc", "C-foo.sc")) 403*e1fe3e4aSElliott Hughes 404*e1fe3e4aSElliott Hughes def test_glyphclass_ambiguous_dash_no_glyph_names(self): 405*e1fe3e4aSElliott Hughes # If Parser is initialized without a glyphNames parameter (or with empty one) 406*e1fe3e4aSElliott Hughes # it cannot distinguish between a glyph name containing an hyphen, or a 407*e1fe3e4aSElliott Hughes # range of glyph names; thus it will interpret them as literal glyph names 408*e1fe3e4aSElliott Hughes # while also outputting a logging warning to alert user about the ambiguity. 409*e1fe3e4aSElliott Hughes # https://github.com/fonttools/fonttools/issues/1768 410*e1fe3e4aSElliott Hughes glyphNames = () 411*e1fe3e4aSElliott Hughes with CapturingLogHandler("fontTools.feaLib.parser", level="WARNING") as caplog: 412*e1fe3e4aSElliott Hughes [gc] = self.parse( 413*e1fe3e4aSElliott Hughes "@class = [A-foo.sc B-foo.sc C D];", glyphNames 414*e1fe3e4aSElliott Hughes ).statements 415*e1fe3e4aSElliott Hughes self.assertEqual(gc.glyphSet(), ("A-foo.sc", "B-foo.sc", "C", "D")) 416*e1fe3e4aSElliott Hughes self.assertEqual(len(caplog.records), 2) 417*e1fe3e4aSElliott Hughes caplog.assertRegex("Ambiguous glyph name that looks like a range:") 418*e1fe3e4aSElliott Hughes 419*e1fe3e4aSElliott Hughes def test_glyphclass_glyph_name_should_win_over_range(self): 420*e1fe3e4aSElliott Hughes # The OpenType Feature File Specification v1.20 makes it clear 421*e1fe3e4aSElliott Hughes # that if a dashed name could be interpreted either as a glyph name 422*e1fe3e4aSElliott Hughes # or as a range, then the semantics should be the single dashed name. 423*e1fe3e4aSElliott Hughes glyphNames = "A-foo.sc-C-foo.sc A-foo.sc B-foo.sc C-foo.sc".split() 424*e1fe3e4aSElliott Hughes [gc] = self.parse("@range = [A-foo.sc-C-foo.sc];", glyphNames).statements 425*e1fe3e4aSElliott Hughes self.assertEqual(gc.glyphSet(), ("A-foo.sc-C-foo.sc",)) 426*e1fe3e4aSElliott Hughes 427*e1fe3e4aSElliott Hughes def test_glyphclass_range_dash_ambiguous(self): 428*e1fe3e4aSElliott Hughes glyphNames = "A B C A-B B-C".split() 429*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 430*e1fe3e4aSElliott Hughes FeatureLibError, 431*e1fe3e4aSElliott Hughes 'Ambiguous glyph range "A-B-C"; ' 432*e1fe3e4aSElliott Hughes 'please use "A - B-C" or "A-B - C" to clarify what you mean', 433*e1fe3e4aSElliott Hughes self.parse, 434*e1fe3e4aSElliott Hughes r"@bad = [A-B-C];", 435*e1fe3e4aSElliott Hughes glyphNames, 436*e1fe3e4aSElliott Hughes ) 437*e1fe3e4aSElliott Hughes 438*e1fe3e4aSElliott Hughes def test_glyphclass_range_digit1(self): 439*e1fe3e4aSElliott Hughes [gc] = self.parse("@range = [foo.2-foo.5];").statements 440*e1fe3e4aSElliott Hughes self.assertEqual(gc.glyphSet(), ("foo.2", "foo.3", "foo.4", "foo.5")) 441*e1fe3e4aSElliott Hughes 442*e1fe3e4aSElliott Hughes def test_glyphclass_range_digit2(self): 443*e1fe3e4aSElliott Hughes [gc] = self.parse("@range = [foo.09-foo.11];").statements 444*e1fe3e4aSElliott Hughes self.assertEqual(gc.glyphSet(), ("foo.09", "foo.10", "foo.11")) 445*e1fe3e4aSElliott Hughes 446*e1fe3e4aSElliott Hughes def test_glyphclass_range_digit3(self): 447*e1fe3e4aSElliott Hughes [gc] = self.parse("@range = [foo.123-foo.125];").statements 448*e1fe3e4aSElliott Hughes self.assertEqual(gc.glyphSet(), ("foo.123", "foo.124", "foo.125")) 449*e1fe3e4aSElliott Hughes 450*e1fe3e4aSElliott Hughes def test_glyphclass_range_bad(self): 451*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 452*e1fe3e4aSElliott Hughes FeatureLibError, 453*e1fe3e4aSElliott Hughes 'Bad range: "a" and "foobar" should have the same length', 454*e1fe3e4aSElliott Hughes self.parse, 455*e1fe3e4aSElliott Hughes "@bad = [a-foobar];", 456*e1fe3e4aSElliott Hughes ) 457*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 458*e1fe3e4aSElliott Hughes FeatureLibError, 459*e1fe3e4aSElliott Hughes 'Bad range: "A.swash-z.swash"', 460*e1fe3e4aSElliott Hughes self.parse, 461*e1fe3e4aSElliott Hughes "@bad = [A.swash-z.swash];", 462*e1fe3e4aSElliott Hughes ) 463*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 464*e1fe3e4aSElliott Hughes FeatureLibError, 465*e1fe3e4aSElliott Hughes "Start of range must be smaller than its end", 466*e1fe3e4aSElliott Hughes self.parse, 467*e1fe3e4aSElliott Hughes "@bad = [B.swash-A.swash];", 468*e1fe3e4aSElliott Hughes ) 469*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 470*e1fe3e4aSElliott Hughes FeatureLibError, 471*e1fe3e4aSElliott Hughes 'Bad range: "foo.1234-foo.9876"', 472*e1fe3e4aSElliott Hughes self.parse, 473*e1fe3e4aSElliott Hughes "@bad = [foo.1234-foo.9876];", 474*e1fe3e4aSElliott Hughes ) 475*e1fe3e4aSElliott Hughes 476*e1fe3e4aSElliott Hughes def test_glyphclass_range_mixed(self): 477*e1fe3e4aSElliott Hughes [gc] = self.parse("@range = [a foo.09-foo.11 X.sc-Z.sc];").statements 478*e1fe3e4aSElliott Hughes self.assertEqual( 479*e1fe3e4aSElliott Hughes gc.glyphSet(), ("a", "foo.09", "foo.10", "foo.11", "X.sc", "Y.sc", "Z.sc") 480*e1fe3e4aSElliott Hughes ) 481*e1fe3e4aSElliott Hughes 482*e1fe3e4aSElliott Hughes def test_glyphclass_reference(self): 483*e1fe3e4aSElliott Hughes [vowels_lc, vowels_uc, vowels] = self.parse( 484*e1fe3e4aSElliott Hughes "@Vowels.lc = [a e i o u]; @Vowels.uc = [A E I O U];" 485*e1fe3e4aSElliott Hughes "@Vowels = [@Vowels.lc @Vowels.uc y Y];" 486*e1fe3e4aSElliott Hughes ).statements 487*e1fe3e4aSElliott Hughes self.assertEqual(vowels_lc.glyphSet(), tuple("aeiou")) 488*e1fe3e4aSElliott Hughes self.assertEqual(vowels_uc.glyphSet(), tuple("AEIOU")) 489*e1fe3e4aSElliott Hughes self.assertEqual(vowels.glyphSet(), tuple("aeiouAEIOUyY")) 490*e1fe3e4aSElliott Hughes self.assertEqual(vowels.asFea(), "@Vowels = [@Vowels.lc @Vowels.uc y Y];") 491*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 492*e1fe3e4aSElliott Hughes FeatureLibError, 493*e1fe3e4aSElliott Hughes "Unknown glyph class @unknown", 494*e1fe3e4aSElliott Hughes self.parse, 495*e1fe3e4aSElliott Hughes "@bad = [@unknown];", 496*e1fe3e4aSElliott Hughes ) 497*e1fe3e4aSElliott Hughes 498*e1fe3e4aSElliott Hughes def test_glyphclass_scoping(self): 499*e1fe3e4aSElliott Hughes [foo, liga, smcp] = self.parse( 500*e1fe3e4aSElliott Hughes "@foo = [a b];" 501*e1fe3e4aSElliott Hughes "feature liga { @bar = [@foo l]; } liga;" 502*e1fe3e4aSElliott Hughes "feature smcp { @bar = [@foo s]; } smcp;" 503*e1fe3e4aSElliott Hughes ).statements 504*e1fe3e4aSElliott Hughes self.assertEqual(foo.glyphSet(), ("a", "b")) 505*e1fe3e4aSElliott Hughes self.assertEqual(liga.statements[0].glyphSet(), ("a", "b", "l")) 506*e1fe3e4aSElliott Hughes self.assertEqual(smcp.statements[0].glyphSet(), ("a", "b", "s")) 507*e1fe3e4aSElliott Hughes 508*e1fe3e4aSElliott Hughes def test_glyphclass_scoping_bug496(self): 509*e1fe3e4aSElliott Hughes # https://github.com/fonttools/fonttools/issues/496 510*e1fe3e4aSElliott Hughes f1, f2 = self.parse( 511*e1fe3e4aSElliott Hughes "feature F1 { lookup L { @GLYPHCLASS = [A B C];} L; } F1;" 512*e1fe3e4aSElliott Hughes "feature F2 { sub @GLYPHCLASS by D; } F2;" 513*e1fe3e4aSElliott Hughes ).statements 514*e1fe3e4aSElliott Hughes self.assertEqual(list(f2.statements[0].glyphs[0].glyphSet()), ["A", "B", "C"]) 515*e1fe3e4aSElliott Hughes 516*e1fe3e4aSElliott Hughes def test_GlyphClassDef(self): 517*e1fe3e4aSElliott Hughes doc = self.parse("table GDEF {GlyphClassDef [b],[l],[m],[C c];} GDEF;") 518*e1fe3e4aSElliott Hughes s = doc.statements[0].statements[0] 519*e1fe3e4aSElliott Hughes self.assertIsInstance(s, ast.GlyphClassDefStatement) 520*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([s.baseGlyphs]), "b") 521*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([s.ligatureGlyphs]), "l") 522*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([s.markGlyphs]), "m") 523*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([s.componentGlyphs]), "[C c]") 524*e1fe3e4aSElliott Hughes 525*e1fe3e4aSElliott Hughes def test_GlyphClassDef_noCLassesSpecified(self): 526*e1fe3e4aSElliott Hughes doc = self.parse("table GDEF {GlyphClassDef ,,,;} GDEF;") 527*e1fe3e4aSElliott Hughes s = doc.statements[0].statements[0] 528*e1fe3e4aSElliott Hughes self.assertIsNone(s.baseGlyphs) 529*e1fe3e4aSElliott Hughes self.assertIsNone(s.ligatureGlyphs) 530*e1fe3e4aSElliott Hughes self.assertIsNone(s.markGlyphs) 531*e1fe3e4aSElliott Hughes self.assertIsNone(s.componentGlyphs) 532*e1fe3e4aSElliott Hughes 533*e1fe3e4aSElliott Hughes def test_ignore_pos(self): 534*e1fe3e4aSElliott Hughes doc = self.parse("feature test {ignore pos e t' c, q u' u' x;} test;") 535*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 536*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.IgnorePosStatement) 537*e1fe3e4aSElliott Hughes [(pref1, glyphs1, suff1), (pref2, glyphs2, suff2)] = sub.chainContexts 538*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(pref1), "e") 539*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(glyphs1), "t") 540*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(suff1), "c") 541*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(pref2), "q") 542*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(glyphs2), "u u") 543*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(suff2), "x") 544*e1fe3e4aSElliott Hughes 545*e1fe3e4aSElliott Hughes def test_ignore_position(self): 546*e1fe3e4aSElliott Hughes doc = self.parse( 547*e1fe3e4aSElliott Hughes "feature test {" " ignore position f [a e] d' [a u]' [e y];" "} test;" 548*e1fe3e4aSElliott Hughes ) 549*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 550*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.IgnorePosStatement) 551*e1fe3e4aSElliott Hughes [(prefix, glyphs, suffix)] = sub.chainContexts 552*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(prefix), "f [a e]") 553*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(glyphs), "d [a u]") 554*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(suffix), "[e y]") 555*e1fe3e4aSElliott Hughes 556*e1fe3e4aSElliott Hughes def test_ignore_position_with_lookup(self): 557*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 558*e1fe3e4aSElliott Hughes FeatureLibError, 559*e1fe3e4aSElliott Hughes 'No lookups can be specified for "ignore pos"', 560*e1fe3e4aSElliott Hughes self.parse, 561*e1fe3e4aSElliott Hughes "lookup L { pos [A A.sc] -100; } L;" 562*e1fe3e4aSElliott Hughes "feature test { ignore pos f' i', A' lookup L; } test;", 563*e1fe3e4aSElliott Hughes ) 564*e1fe3e4aSElliott Hughes 565*e1fe3e4aSElliott Hughes def test_ignore_sub(self): 566*e1fe3e4aSElliott Hughes doc = self.parse("feature test {ignore sub e t' c, q u' u' x;} test;") 567*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 568*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.IgnoreSubstStatement) 569*e1fe3e4aSElliott Hughes [(pref1, glyphs1, suff1), (pref2, glyphs2, suff2)] = sub.chainContexts 570*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(pref1), "e") 571*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(glyphs1), "t") 572*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(suff1), "c") 573*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(pref2), "q") 574*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(glyphs2), "u u") 575*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(suff2), "x") 576*e1fe3e4aSElliott Hughes 577*e1fe3e4aSElliott Hughes def test_ignore_substitute(self): 578*e1fe3e4aSElliott Hughes doc = self.parse( 579*e1fe3e4aSElliott Hughes "feature test {" " ignore substitute f [a e] d' [a u]' [e y];" "} test;" 580*e1fe3e4aSElliott Hughes ) 581*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 582*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.IgnoreSubstStatement) 583*e1fe3e4aSElliott Hughes [(prefix, glyphs, suffix)] = sub.chainContexts 584*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(prefix), "f [a e]") 585*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(glyphs), "d [a u]") 586*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(suffix), "[e y]") 587*e1fe3e4aSElliott Hughes 588*e1fe3e4aSElliott Hughes def test_ignore_substitute_with_lookup(self): 589*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 590*e1fe3e4aSElliott Hughes FeatureLibError, 591*e1fe3e4aSElliott Hughes 'No lookups can be specified for "ignore sub"', 592*e1fe3e4aSElliott Hughes self.parse, 593*e1fe3e4aSElliott Hughes "lookup L { sub [A A.sc] by a; } L;" 594*e1fe3e4aSElliott Hughes "feature test { ignore sub f' i', A' lookup L; } test;", 595*e1fe3e4aSElliott Hughes ) 596*e1fe3e4aSElliott Hughes 597*e1fe3e4aSElliott Hughes def test_include_statement(self): 598*e1fe3e4aSElliott Hughes doc = self.parse( 599*e1fe3e4aSElliott Hughes """\ 600*e1fe3e4aSElliott Hughes include(../family.fea); 601*e1fe3e4aSElliott Hughes include # Comment 602*e1fe3e4aSElliott Hughes (foo) 603*e1fe3e4aSElliott Hughes ; 604*e1fe3e4aSElliott Hughes """, 605*e1fe3e4aSElliott Hughes followIncludes=False, 606*e1fe3e4aSElliott Hughes ) 607*e1fe3e4aSElliott Hughes s1, s2, s3 = doc.statements 608*e1fe3e4aSElliott Hughes self.assertEqual(type(s1), ast.IncludeStatement) 609*e1fe3e4aSElliott Hughes self.assertEqual(s1.filename, "../family.fea") 610*e1fe3e4aSElliott Hughes self.assertEqual(s1.asFea(), "include(../family.fea);") 611*e1fe3e4aSElliott Hughes self.assertEqual(type(s2), ast.IncludeStatement) 612*e1fe3e4aSElliott Hughes self.assertEqual(s2.filename, "foo") 613*e1fe3e4aSElliott Hughes self.assertEqual(s2.asFea(), "include(foo);") 614*e1fe3e4aSElliott Hughes self.assertEqual(type(s3), ast.Comment) 615*e1fe3e4aSElliott Hughes self.assertEqual(s3.text, "# Comment") 616*e1fe3e4aSElliott Hughes 617*e1fe3e4aSElliott Hughes def test_include_statement_no_semicolon(self): 618*e1fe3e4aSElliott Hughes doc = self.parse( 619*e1fe3e4aSElliott Hughes """\ 620*e1fe3e4aSElliott Hughes include(../family.fea) 621*e1fe3e4aSElliott Hughes """, 622*e1fe3e4aSElliott Hughes followIncludes=False, 623*e1fe3e4aSElliott Hughes ) 624*e1fe3e4aSElliott Hughes s1 = doc.statements[0] 625*e1fe3e4aSElliott Hughes self.assertEqual(type(s1), ast.IncludeStatement) 626*e1fe3e4aSElliott Hughes self.assertEqual(s1.filename, "../family.fea") 627*e1fe3e4aSElliott Hughes self.assertEqual(s1.asFea(), "include(../family.fea);") 628*e1fe3e4aSElliott Hughes 629*e1fe3e4aSElliott Hughes def test_language(self): 630*e1fe3e4aSElliott Hughes doc = self.parse("feature test {language DEU;} test;") 631*e1fe3e4aSElliott Hughes s = doc.statements[0].statements[0] 632*e1fe3e4aSElliott Hughes self.assertEqual(type(s), ast.LanguageStatement) 633*e1fe3e4aSElliott Hughes self.assertEqual(s.language, "DEU ") 634*e1fe3e4aSElliott Hughes self.assertTrue(s.include_default) 635*e1fe3e4aSElliott Hughes self.assertFalse(s.required) 636*e1fe3e4aSElliott Hughes 637*e1fe3e4aSElliott Hughes def test_language_exclude_dflt(self): 638*e1fe3e4aSElliott Hughes doc = self.parse("feature test {language DEU exclude_dflt;} test;") 639*e1fe3e4aSElliott Hughes s = doc.statements[0].statements[0] 640*e1fe3e4aSElliott Hughes self.assertEqual(type(s), ast.LanguageStatement) 641*e1fe3e4aSElliott Hughes self.assertEqual(s.language, "DEU ") 642*e1fe3e4aSElliott Hughes self.assertFalse(s.include_default) 643*e1fe3e4aSElliott Hughes self.assertFalse(s.required) 644*e1fe3e4aSElliott Hughes 645*e1fe3e4aSElliott Hughes def test_language_exclude_dflt_required(self): 646*e1fe3e4aSElliott Hughes doc = self.parse( 647*e1fe3e4aSElliott Hughes "feature test {" " language DEU exclude_dflt required;" "} test;" 648*e1fe3e4aSElliott Hughes ) 649*e1fe3e4aSElliott Hughes s = doc.statements[0].statements[0] 650*e1fe3e4aSElliott Hughes self.assertEqual(type(s), ast.LanguageStatement) 651*e1fe3e4aSElliott Hughes self.assertEqual(s.language, "DEU ") 652*e1fe3e4aSElliott Hughes self.assertFalse(s.include_default) 653*e1fe3e4aSElliott Hughes self.assertTrue(s.required) 654*e1fe3e4aSElliott Hughes 655*e1fe3e4aSElliott Hughes def test_language_include_dflt(self): 656*e1fe3e4aSElliott Hughes doc = self.parse("feature test {language DEU include_dflt;} test;") 657*e1fe3e4aSElliott Hughes s = doc.statements[0].statements[0] 658*e1fe3e4aSElliott Hughes self.assertEqual(type(s), ast.LanguageStatement) 659*e1fe3e4aSElliott Hughes self.assertEqual(s.language, "DEU ") 660*e1fe3e4aSElliott Hughes self.assertTrue(s.include_default) 661*e1fe3e4aSElliott Hughes self.assertFalse(s.required) 662*e1fe3e4aSElliott Hughes 663*e1fe3e4aSElliott Hughes def test_language_include_dflt_required(self): 664*e1fe3e4aSElliott Hughes doc = self.parse( 665*e1fe3e4aSElliott Hughes "feature test {" " language DEU include_dflt required;" "} test;" 666*e1fe3e4aSElliott Hughes ) 667*e1fe3e4aSElliott Hughes s = doc.statements[0].statements[0] 668*e1fe3e4aSElliott Hughes self.assertEqual(type(s), ast.LanguageStatement) 669*e1fe3e4aSElliott Hughes self.assertEqual(s.language, "DEU ") 670*e1fe3e4aSElliott Hughes self.assertTrue(s.include_default) 671*e1fe3e4aSElliott Hughes self.assertTrue(s.required) 672*e1fe3e4aSElliott Hughes 673*e1fe3e4aSElliott Hughes def test_language_DFLT(self): 674*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 675*e1fe3e4aSElliott Hughes FeatureLibError, 676*e1fe3e4aSElliott Hughes '"DFLT" is not a valid language tag; use "dflt" instead', 677*e1fe3e4aSElliott Hughes self.parse, 678*e1fe3e4aSElliott Hughes "feature test { language DFLT; } test;", 679*e1fe3e4aSElliott Hughes ) 680*e1fe3e4aSElliott Hughes 681*e1fe3e4aSElliott Hughes def test_ligatureCaretByIndex_glyphClass(self): 682*e1fe3e4aSElliott Hughes doc = self.parse("table GDEF{LigatureCaretByIndex [c_t f_i] 2;}GDEF;") 683*e1fe3e4aSElliott Hughes s = doc.statements[0].statements[0] 684*e1fe3e4aSElliott Hughes self.assertIsInstance(s, ast.LigatureCaretByIndexStatement) 685*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([s.glyphs]), "[c_t f_i]") 686*e1fe3e4aSElliott Hughes self.assertEqual(s.carets, [2]) 687*e1fe3e4aSElliott Hughes 688*e1fe3e4aSElliott Hughes def test_ligatureCaretByIndex_singleGlyph(self): 689*e1fe3e4aSElliott Hughes doc = self.parse("table GDEF{LigatureCaretByIndex f_f_i 3 7;}GDEF;") 690*e1fe3e4aSElliott Hughes s = doc.statements[0].statements[0] 691*e1fe3e4aSElliott Hughes self.assertIsInstance(s, ast.LigatureCaretByIndexStatement) 692*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([s.glyphs]), "f_f_i") 693*e1fe3e4aSElliott Hughes self.assertEqual(s.carets, [3, 7]) 694*e1fe3e4aSElliott Hughes 695*e1fe3e4aSElliott Hughes def test_ligatureCaretByPos_glyphClass(self): 696*e1fe3e4aSElliott Hughes doc = self.parse("table GDEF {LigatureCaretByPos [c_t f_i] 7;} GDEF;") 697*e1fe3e4aSElliott Hughes s = doc.statements[0].statements[0] 698*e1fe3e4aSElliott Hughes self.assertIsInstance(s, ast.LigatureCaretByPosStatement) 699*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([s.glyphs]), "[c_t f_i]") 700*e1fe3e4aSElliott Hughes self.assertEqual(s.carets, [7]) 701*e1fe3e4aSElliott Hughes 702*e1fe3e4aSElliott Hughes def test_ligatureCaretByPos_singleGlyph(self): 703*e1fe3e4aSElliott Hughes doc = self.parse("table GDEF {LigatureCaretByPos f_i 400 380;} GDEF;") 704*e1fe3e4aSElliott Hughes s = doc.statements[0].statements[0] 705*e1fe3e4aSElliott Hughes self.assertIsInstance(s, ast.LigatureCaretByPosStatement) 706*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([s.glyphs]), "f_i") 707*e1fe3e4aSElliott Hughes self.assertEqual(s.carets, [400, 380]) 708*e1fe3e4aSElliott Hughes 709*e1fe3e4aSElliott Hughes def test_ligatureCaretByPos_variable_scalar(self): 710*e1fe3e4aSElliott Hughes doc = self.parse( 711*e1fe3e4aSElliott Hughes "table GDEF {LigatureCaretByPos f_i (wght=200:400 wght=900:1000) 380;} GDEF;" 712*e1fe3e4aSElliott Hughes ) 713*e1fe3e4aSElliott Hughes s = doc.statements[0].statements[0] 714*e1fe3e4aSElliott Hughes self.assertIsInstance(s, ast.LigatureCaretByPosStatement) 715*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([s.glyphs]), "f_i") 716*e1fe3e4aSElliott Hughes self.assertEqual(len(s.carets), 2) 717*e1fe3e4aSElliott Hughes self.assertEqual(str(s.carets[0]), "(wght=200:400 wght=900:1000)") 718*e1fe3e4aSElliott Hughes self.assertEqual(s.carets[1], 380) 719*e1fe3e4aSElliott Hughes 720*e1fe3e4aSElliott Hughes def test_lookup_block(self): 721*e1fe3e4aSElliott Hughes [lookup] = self.parse("lookup Ligatures {} Ligatures;").statements 722*e1fe3e4aSElliott Hughes self.assertEqual(lookup.name, "Ligatures") 723*e1fe3e4aSElliott Hughes self.assertFalse(lookup.use_extension) 724*e1fe3e4aSElliott Hughes 725*e1fe3e4aSElliott Hughes def test_lookup_block_useExtension(self): 726*e1fe3e4aSElliott Hughes [lookup] = self.parse("lookup Foo useExtension {} Foo;").statements 727*e1fe3e4aSElliott Hughes self.assertEqual(lookup.name, "Foo") 728*e1fe3e4aSElliott Hughes self.assertTrue(lookup.use_extension) 729*e1fe3e4aSElliott Hughes self.assertEqual(lookup.asFea(), "lookup Foo useExtension {\n \n} Foo;\n") 730*e1fe3e4aSElliott Hughes 731*e1fe3e4aSElliott Hughes def test_lookup_block_name_mismatch(self): 732*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 733*e1fe3e4aSElliott Hughes FeatureLibError, 'Expected "Foo"', self.parse, "lookup Foo {} Bar;" 734*e1fe3e4aSElliott Hughes ) 735*e1fe3e4aSElliott Hughes 736*e1fe3e4aSElliott Hughes def test_lookup_block_with_horizontal_valueRecordDef(self): 737*e1fe3e4aSElliott Hughes doc = self.parse( 738*e1fe3e4aSElliott Hughes "feature liga {" 739*e1fe3e4aSElliott Hughes " lookup look {" 740*e1fe3e4aSElliott Hughes " valueRecordDef 123 foo;" 741*e1fe3e4aSElliott Hughes " } look;" 742*e1fe3e4aSElliott Hughes "} liga;" 743*e1fe3e4aSElliott Hughes ) 744*e1fe3e4aSElliott Hughes [liga] = doc.statements 745*e1fe3e4aSElliott Hughes [look] = liga.statements 746*e1fe3e4aSElliott Hughes [foo] = look.statements 747*e1fe3e4aSElliott Hughes self.assertEqual(foo.value.xAdvance, 123) 748*e1fe3e4aSElliott Hughes self.assertIsNone(foo.value.yAdvance) 749*e1fe3e4aSElliott Hughes 750*e1fe3e4aSElliott Hughes def test_lookup_block_with_vertical_valueRecordDef(self): 751*e1fe3e4aSElliott Hughes doc = self.parse( 752*e1fe3e4aSElliott Hughes "feature vkrn {" 753*e1fe3e4aSElliott Hughes " lookup look {" 754*e1fe3e4aSElliott Hughes " valueRecordDef 123 foo;" 755*e1fe3e4aSElliott Hughes " } look;" 756*e1fe3e4aSElliott Hughes "} vkrn;" 757*e1fe3e4aSElliott Hughes ) 758*e1fe3e4aSElliott Hughes [vkrn] = doc.statements 759*e1fe3e4aSElliott Hughes [look] = vkrn.statements 760*e1fe3e4aSElliott Hughes [foo] = look.statements 761*e1fe3e4aSElliott Hughes self.assertIsNone(foo.value.xAdvance) 762*e1fe3e4aSElliott Hughes self.assertEqual(foo.value.yAdvance, 123) 763*e1fe3e4aSElliott Hughes 764*e1fe3e4aSElliott Hughes def test_lookup_comment(self): 765*e1fe3e4aSElliott Hughes [lookup] = self.parse("lookup L { # Comment\n } L;").statements 766*e1fe3e4aSElliott Hughes [comment] = lookup.statements 767*e1fe3e4aSElliott Hughes self.assertIsInstance(comment, ast.Comment) 768*e1fe3e4aSElliott Hughes self.assertEqual(comment.text, "# Comment") 769*e1fe3e4aSElliott Hughes 770*e1fe3e4aSElliott Hughes def test_lookup_reference(self): 771*e1fe3e4aSElliott Hughes [foo, bar] = self.parse( 772*e1fe3e4aSElliott Hughes "lookup Foo {} Foo;" "feature Bar {lookup Foo;} Bar;" 773*e1fe3e4aSElliott Hughes ).statements 774*e1fe3e4aSElliott Hughes [ref] = bar.statements 775*e1fe3e4aSElliott Hughes self.assertEqual(type(ref), ast.LookupReferenceStatement) 776*e1fe3e4aSElliott Hughes self.assertEqual(ref.lookup, foo) 777*e1fe3e4aSElliott Hughes 778*e1fe3e4aSElliott Hughes def test_lookup_reference_to_lookup_inside_feature(self): 779*e1fe3e4aSElliott Hughes [qux, bar] = self.parse( 780*e1fe3e4aSElliott Hughes "feature Qux {lookup Foo {} Foo;} Qux;" "feature Bar {lookup Foo;} Bar;" 781*e1fe3e4aSElliott Hughes ).statements 782*e1fe3e4aSElliott Hughes [foo] = qux.statements 783*e1fe3e4aSElliott Hughes [ref] = bar.statements 784*e1fe3e4aSElliott Hughes self.assertIsInstance(ref, ast.LookupReferenceStatement) 785*e1fe3e4aSElliott Hughes self.assertEqual(ref.lookup, foo) 786*e1fe3e4aSElliott Hughes 787*e1fe3e4aSElliott Hughes def test_lookup_reference_unknown(self): 788*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 789*e1fe3e4aSElliott Hughes FeatureLibError, 790*e1fe3e4aSElliott Hughes 'Unknown lookup "Huh"', 791*e1fe3e4aSElliott Hughes self.parse, 792*e1fe3e4aSElliott Hughes "feature liga {lookup Huh;} liga;", 793*e1fe3e4aSElliott Hughes ) 794*e1fe3e4aSElliott Hughes 795*e1fe3e4aSElliott Hughes def parse_lookupflag_(self, s): 796*e1fe3e4aSElliott Hughes return self.parse("lookup L {%s} L;" % s).statements[0].statements[-1] 797*e1fe3e4aSElliott Hughes 798*e1fe3e4aSElliott Hughes def test_lookupflag_format_A(self): 799*e1fe3e4aSElliott Hughes flag = self.parse_lookupflag_("lookupflag RightToLeft IgnoreMarks;") 800*e1fe3e4aSElliott Hughes self.assertIsInstance(flag, ast.LookupFlagStatement) 801*e1fe3e4aSElliott Hughes self.assertEqual(flag.value, 9) 802*e1fe3e4aSElliott Hughes self.assertIsNone(flag.markAttachment) 803*e1fe3e4aSElliott Hughes self.assertIsNone(flag.markFilteringSet) 804*e1fe3e4aSElliott Hughes self.assertEqual(flag.asFea(), "lookupflag RightToLeft IgnoreMarks;") 805*e1fe3e4aSElliott Hughes 806*e1fe3e4aSElliott Hughes def test_lookupflag_format_A_MarkAttachmentType(self): 807*e1fe3e4aSElliott Hughes flag = self.parse_lookupflag_( 808*e1fe3e4aSElliott Hughes "@TOP_MARKS = [acute grave macron];" 809*e1fe3e4aSElliott Hughes "lookupflag RightToLeft MarkAttachmentType @TOP_MARKS;" 810*e1fe3e4aSElliott Hughes ) 811*e1fe3e4aSElliott Hughes self.assertIsInstance(flag, ast.LookupFlagStatement) 812*e1fe3e4aSElliott Hughes self.assertEqual(flag.value, 1) 813*e1fe3e4aSElliott Hughes self.assertIsInstance(flag.markAttachment, ast.GlyphClassName) 814*e1fe3e4aSElliott Hughes self.assertEqual(flag.markAttachment.glyphSet(), ("acute", "grave", "macron")) 815*e1fe3e4aSElliott Hughes self.assertIsNone(flag.markFilteringSet) 816*e1fe3e4aSElliott Hughes self.assertEqual( 817*e1fe3e4aSElliott Hughes flag.asFea(), "lookupflag RightToLeft MarkAttachmentType @TOP_MARKS;" 818*e1fe3e4aSElliott Hughes ) 819*e1fe3e4aSElliott Hughes 820*e1fe3e4aSElliott Hughes def test_lookupflag_format_A_MarkAttachmentType_glyphClass(self): 821*e1fe3e4aSElliott Hughes flag = self.parse_lookupflag_( 822*e1fe3e4aSElliott Hughes "lookupflag RightToLeft MarkAttachmentType [acute grave macron];" 823*e1fe3e4aSElliott Hughes ) 824*e1fe3e4aSElliott Hughes self.assertIsInstance(flag, ast.LookupFlagStatement) 825*e1fe3e4aSElliott Hughes self.assertEqual(flag.value, 1) 826*e1fe3e4aSElliott Hughes self.assertIsInstance(flag.markAttachment, ast.GlyphClass) 827*e1fe3e4aSElliott Hughes self.assertEqual(flag.markAttachment.glyphSet(), ("acute", "grave", "macron")) 828*e1fe3e4aSElliott Hughes self.assertIsNone(flag.markFilteringSet) 829*e1fe3e4aSElliott Hughes self.assertEqual( 830*e1fe3e4aSElliott Hughes flag.asFea(), 831*e1fe3e4aSElliott Hughes "lookupflag RightToLeft MarkAttachmentType [acute grave macron];", 832*e1fe3e4aSElliott Hughes ) 833*e1fe3e4aSElliott Hughes 834*e1fe3e4aSElliott Hughes def test_lookupflag_format_A_UseMarkFilteringSet(self): 835*e1fe3e4aSElliott Hughes flag = self.parse_lookupflag_( 836*e1fe3e4aSElliott Hughes "@BOTTOM_MARKS = [cedilla ogonek];" 837*e1fe3e4aSElliott Hughes "lookupflag UseMarkFilteringSet @BOTTOM_MARKS IgnoreLigatures;" 838*e1fe3e4aSElliott Hughes ) 839*e1fe3e4aSElliott Hughes self.assertIsInstance(flag, ast.LookupFlagStatement) 840*e1fe3e4aSElliott Hughes self.assertEqual(flag.value, 4) 841*e1fe3e4aSElliott Hughes self.assertIsNone(flag.markAttachment) 842*e1fe3e4aSElliott Hughes self.assertIsInstance(flag.markFilteringSet, ast.GlyphClassName) 843*e1fe3e4aSElliott Hughes self.assertEqual(flag.markFilteringSet.glyphSet(), ("cedilla", "ogonek")) 844*e1fe3e4aSElliott Hughes self.assertEqual( 845*e1fe3e4aSElliott Hughes flag.asFea(), 846*e1fe3e4aSElliott Hughes "lookupflag IgnoreLigatures UseMarkFilteringSet @BOTTOM_MARKS;", 847*e1fe3e4aSElliott Hughes ) 848*e1fe3e4aSElliott Hughes 849*e1fe3e4aSElliott Hughes def test_lookupflag_format_A_UseMarkFilteringSet_glyphClass(self): 850*e1fe3e4aSElliott Hughes flag = self.parse_lookupflag_( 851*e1fe3e4aSElliott Hughes "lookupflag UseMarkFilteringSet [cedilla ogonek] IgnoreLigatures;" 852*e1fe3e4aSElliott Hughes ) 853*e1fe3e4aSElliott Hughes self.assertIsInstance(flag, ast.LookupFlagStatement) 854*e1fe3e4aSElliott Hughes self.assertEqual(flag.value, 4) 855*e1fe3e4aSElliott Hughes self.assertIsNone(flag.markAttachment) 856*e1fe3e4aSElliott Hughes self.assertIsInstance(flag.markFilteringSet, ast.GlyphClass) 857*e1fe3e4aSElliott Hughes self.assertEqual(flag.markFilteringSet.glyphSet(), ("cedilla", "ogonek")) 858*e1fe3e4aSElliott Hughes self.assertEqual( 859*e1fe3e4aSElliott Hughes flag.asFea(), 860*e1fe3e4aSElliott Hughes "lookupflag IgnoreLigatures UseMarkFilteringSet [cedilla ogonek];", 861*e1fe3e4aSElliott Hughes ) 862*e1fe3e4aSElliott Hughes 863*e1fe3e4aSElliott Hughes def test_lookupflag_format_B(self): 864*e1fe3e4aSElliott Hughes flag = self.parse_lookupflag_("lookupflag 7;") 865*e1fe3e4aSElliott Hughes self.assertIsInstance(flag, ast.LookupFlagStatement) 866*e1fe3e4aSElliott Hughes self.assertEqual(flag.value, 7) 867*e1fe3e4aSElliott Hughes self.assertIsNone(flag.markAttachment) 868*e1fe3e4aSElliott Hughes self.assertIsNone(flag.markFilteringSet) 869*e1fe3e4aSElliott Hughes self.assertEqual( 870*e1fe3e4aSElliott Hughes flag.asFea(), "lookupflag RightToLeft IgnoreBaseGlyphs IgnoreLigatures;" 871*e1fe3e4aSElliott Hughes ) 872*e1fe3e4aSElliott Hughes 873*e1fe3e4aSElliott Hughes def test_lookupflag_format_B_zero(self): 874*e1fe3e4aSElliott Hughes flag = self.parse_lookupflag_("lookupflag 0;") 875*e1fe3e4aSElliott Hughes self.assertIsInstance(flag, ast.LookupFlagStatement) 876*e1fe3e4aSElliott Hughes self.assertEqual(flag.value, 0) 877*e1fe3e4aSElliott Hughes self.assertIsNone(flag.markAttachment) 878*e1fe3e4aSElliott Hughes self.assertIsNone(flag.markFilteringSet) 879*e1fe3e4aSElliott Hughes self.assertEqual(flag.asFea(), "lookupflag 0;") 880*e1fe3e4aSElliott Hughes 881*e1fe3e4aSElliott Hughes def test_lookupflag_no_value(self): 882*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 883*e1fe3e4aSElliott Hughes FeatureLibError, 884*e1fe3e4aSElliott Hughes "lookupflag must have a value", 885*e1fe3e4aSElliott Hughes self.parse, 886*e1fe3e4aSElliott Hughes "feature test {lookupflag;} test;", 887*e1fe3e4aSElliott Hughes ) 888*e1fe3e4aSElliott Hughes 889*e1fe3e4aSElliott Hughes def test_lookupflag_repeated(self): 890*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 891*e1fe3e4aSElliott Hughes FeatureLibError, 892*e1fe3e4aSElliott Hughes "RightToLeft can be specified only once", 893*e1fe3e4aSElliott Hughes self.parse, 894*e1fe3e4aSElliott Hughes "feature test {lookupflag RightToLeft RightToLeft;} test;", 895*e1fe3e4aSElliott Hughes ) 896*e1fe3e4aSElliott Hughes 897*e1fe3e4aSElliott Hughes def test_lookupflag_unrecognized(self): 898*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 899*e1fe3e4aSElliott Hughes FeatureLibError, 900*e1fe3e4aSElliott Hughes '"IgnoreCookies" is not a recognized lookupflag', 901*e1fe3e4aSElliott Hughes self.parse, 902*e1fe3e4aSElliott Hughes "feature test {lookupflag IgnoreCookies;} test;", 903*e1fe3e4aSElliott Hughes ) 904*e1fe3e4aSElliott Hughes 905*e1fe3e4aSElliott Hughes def test_gpos_type_1_glyph(self): 906*e1fe3e4aSElliott Hughes doc = self.parse("feature kern {pos one <1 2 3 4>;} kern;") 907*e1fe3e4aSElliott Hughes pos = doc.statements[0].statements[0] 908*e1fe3e4aSElliott Hughes self.assertIsInstance(pos, ast.SinglePosStatement) 909*e1fe3e4aSElliott Hughes [(glyphs, value)] = pos.pos 910*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([glyphs]), "one") 911*e1fe3e4aSElliott Hughes self.assertEqual(value.asFea(), "<1 2 3 4>") 912*e1fe3e4aSElliott Hughes 913*e1fe3e4aSElliott Hughes def test_gpos_type_1_glyphclass_horizontal(self): 914*e1fe3e4aSElliott Hughes doc = self.parse("feature kern {pos [one two] -300;} kern;") 915*e1fe3e4aSElliott Hughes pos = doc.statements[0].statements[0] 916*e1fe3e4aSElliott Hughes self.assertIsInstance(pos, ast.SinglePosStatement) 917*e1fe3e4aSElliott Hughes [(glyphs, value)] = pos.pos 918*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([glyphs]), "[one two]") 919*e1fe3e4aSElliott Hughes self.assertEqual(value.asFea(), "-300") 920*e1fe3e4aSElliott Hughes 921*e1fe3e4aSElliott Hughes def test_gpos_type_1_glyphclass_vertical(self): 922*e1fe3e4aSElliott Hughes doc = self.parse("feature vkrn {pos [one two] -300;} vkrn;") 923*e1fe3e4aSElliott Hughes pos = doc.statements[0].statements[0] 924*e1fe3e4aSElliott Hughes self.assertIsInstance(pos, ast.SinglePosStatement) 925*e1fe3e4aSElliott Hughes [(glyphs, value)] = pos.pos 926*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([glyphs]), "[one two]") 927*e1fe3e4aSElliott Hughes self.assertEqual(value.asFea(), "-300") 928*e1fe3e4aSElliott Hughes 929*e1fe3e4aSElliott Hughes def test_gpos_type_1_multiple(self): 930*e1fe3e4aSElliott Hughes doc = self.parse("feature f {pos one'1 two'2 [five six]'56;} f;") 931*e1fe3e4aSElliott Hughes pos = doc.statements[0].statements[0] 932*e1fe3e4aSElliott Hughes self.assertIsInstance(pos, ast.SinglePosStatement) 933*e1fe3e4aSElliott Hughes [(glyphs1, val1), (glyphs2, val2), (glyphs3, val3)] = pos.pos 934*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([glyphs1]), "one") 935*e1fe3e4aSElliott Hughes self.assertEqual(val1.asFea(), "1") 936*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([glyphs2]), "two") 937*e1fe3e4aSElliott Hughes self.assertEqual(val2.asFea(), "2") 938*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([glyphs3]), "[five six]") 939*e1fe3e4aSElliott Hughes self.assertEqual(val3.asFea(), "56") 940*e1fe3e4aSElliott Hughes self.assertEqual(pos.prefix, []) 941*e1fe3e4aSElliott Hughes self.assertEqual(pos.suffix, []) 942*e1fe3e4aSElliott Hughes 943*e1fe3e4aSElliott Hughes def test_gpos_type_1_enumerated(self): 944*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 945*e1fe3e4aSElliott Hughes FeatureLibError, 946*e1fe3e4aSElliott Hughes '"enumerate" is only allowed with pair positionings', 947*e1fe3e4aSElliott Hughes self.parse, 948*e1fe3e4aSElliott Hughes "feature test {enum pos T 100;} test;", 949*e1fe3e4aSElliott Hughes ) 950*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 951*e1fe3e4aSElliott Hughes FeatureLibError, 952*e1fe3e4aSElliott Hughes '"enumerate" is only allowed with pair positionings', 953*e1fe3e4aSElliott Hughes self.parse, 954*e1fe3e4aSElliott Hughes "feature test {enumerate pos T 100;} test;", 955*e1fe3e4aSElliott Hughes ) 956*e1fe3e4aSElliott Hughes 957*e1fe3e4aSElliott Hughes def test_gpos_type_1_chained(self): 958*e1fe3e4aSElliott Hughes doc = self.parse("feature kern {pos [A B] [T Y]' 20 comma;} kern;") 959*e1fe3e4aSElliott Hughes pos = doc.statements[0].statements[0] 960*e1fe3e4aSElliott Hughes self.assertIsInstance(pos, ast.SinglePosStatement) 961*e1fe3e4aSElliott Hughes [(glyphs, value)] = pos.pos 962*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([glyphs]), "[T Y]") 963*e1fe3e4aSElliott Hughes self.assertEqual(value.asFea(), "20") 964*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(pos.prefix), "[A B]") 965*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(pos.suffix), "comma") 966*e1fe3e4aSElliott Hughes 967*e1fe3e4aSElliott Hughes def test_gpos_type_1_chained_special_kern_format_valuerecord_format_a(self): 968*e1fe3e4aSElliott Hughes doc = self.parse("feature kern {pos [A B] [T Y]' comma 20;} kern;") 969*e1fe3e4aSElliott Hughes pos = doc.statements[0].statements[0] 970*e1fe3e4aSElliott Hughes self.assertIsInstance(pos, ast.SinglePosStatement) 971*e1fe3e4aSElliott Hughes [(glyphs, value)] = pos.pos 972*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([glyphs]), "[T Y]") 973*e1fe3e4aSElliott Hughes self.assertEqual(value.asFea(), "20") 974*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(pos.prefix), "[A B]") 975*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(pos.suffix), "comma") 976*e1fe3e4aSElliott Hughes 977*e1fe3e4aSElliott Hughes def test_gpos_type_1_chained_special_kern_format_valuerecord_format_b(self): 978*e1fe3e4aSElliott Hughes doc = self.parse("feature kern {pos [A B] [T Y]' comma <0 0 0 0>;} kern;") 979*e1fe3e4aSElliott Hughes pos = doc.statements[0].statements[0] 980*e1fe3e4aSElliott Hughes self.assertIsInstance(pos, ast.SinglePosStatement) 981*e1fe3e4aSElliott Hughes [(glyphs, value)] = pos.pos 982*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([glyphs]), "[T Y]") 983*e1fe3e4aSElliott Hughes self.assertEqual(value.asFea(), "<0 0 0 0>") 984*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(pos.prefix), "[A B]") 985*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(pos.suffix), "comma") 986*e1fe3e4aSElliott Hughes 987*e1fe3e4aSElliott Hughes def test_gpos_type_1_chained_special_kern_format_valuerecord_format_b_bug2293(self): 988*e1fe3e4aSElliott Hughes # https://github.com/fonttools/fonttools/issues/2293 989*e1fe3e4aSElliott Hughes doc = self.parse("feature kern {pos [A B] [T Y]' comma a <0 0 0 0>;} kern;") 990*e1fe3e4aSElliott Hughes pos = doc.statements[0].statements[0] 991*e1fe3e4aSElliott Hughes self.assertIsInstance(pos, ast.SinglePosStatement) 992*e1fe3e4aSElliott Hughes [(glyphs, value)] = pos.pos 993*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([glyphs]), "[T Y]") 994*e1fe3e4aSElliott Hughes self.assertEqual(value.asFea(), "<0 0 0 0>") 995*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(pos.prefix), "[A B]") 996*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(pos.suffix), "comma a") 997*e1fe3e4aSElliott Hughes 998*e1fe3e4aSElliott Hughes def test_gpos_type_1_chained_exception1(self): 999*e1fe3e4aSElliott Hughes with self.assertRaisesRegex(FeatureLibError, "Positioning values are allowed"): 1000*e1fe3e4aSElliott Hughes doc = self.parse( 1001*e1fe3e4aSElliott Hughes "feature kern {" " pos [A B]' [T Y]' comma a <0 0 0 0>;" "} kern;" 1002*e1fe3e4aSElliott Hughes ) 1003*e1fe3e4aSElliott Hughes 1004*e1fe3e4aSElliott Hughes def test_gpos_type_1_chained_exception2(self): 1005*e1fe3e4aSElliott Hughes with self.assertRaisesRegex(FeatureLibError, "Positioning values are allowed"): 1006*e1fe3e4aSElliott Hughes doc = self.parse( 1007*e1fe3e4aSElliott Hughes "feature kern {" 1008*e1fe3e4aSElliott Hughes " pos [A B]' <0 0 0 0> [T Y]' comma a <0 0 0 0>;" 1009*e1fe3e4aSElliott Hughes "} kern;" 1010*e1fe3e4aSElliott Hughes ) 1011*e1fe3e4aSElliott Hughes 1012*e1fe3e4aSElliott Hughes def test_gpos_type_1_chained_exception3(self): 1013*e1fe3e4aSElliott Hughes with self.assertRaisesRegex(FeatureLibError, "Positioning cannot be applied"): 1014*e1fe3e4aSElliott Hughes doc = self.parse( 1015*e1fe3e4aSElliott Hughes "feature kern {" 1016*e1fe3e4aSElliott Hughes " pos [A B] <0 0 0 0> [T Y]' comma a <0 0 0 0>;" 1017*e1fe3e4aSElliott Hughes "} kern;" 1018*e1fe3e4aSElliott Hughes ) 1019*e1fe3e4aSElliott Hughes 1020*e1fe3e4aSElliott Hughes def test_gpos_type_1_chained_exception4(self): 1021*e1fe3e4aSElliott Hughes with self.assertRaisesRegex(FeatureLibError, "Positioning values are allowed"): 1022*e1fe3e4aSElliott Hughes doc = self.parse("feature kern {" " pos a' b c 123 d;" "} kern;") 1023*e1fe3e4aSElliott Hughes 1024*e1fe3e4aSElliott Hughes def test_gpos_type_2_format_a(self): 1025*e1fe3e4aSElliott Hughes doc = self.parse( 1026*e1fe3e4aSElliott Hughes "feature kern {" " pos [T V] -60 [a b c] <1 2 3 4>;" "} kern;" 1027*e1fe3e4aSElliott Hughes ) 1028*e1fe3e4aSElliott Hughes pos = doc.statements[0].statements[0] 1029*e1fe3e4aSElliott Hughes self.assertEqual(type(pos), ast.PairPosStatement) 1030*e1fe3e4aSElliott Hughes self.assertFalse(pos.enumerated) 1031*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([pos.glyphs1]), "[T V]") 1032*e1fe3e4aSElliott Hughes self.assertEqual(pos.valuerecord1.asFea(), "-60") 1033*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([pos.glyphs2]), "[a b c]") 1034*e1fe3e4aSElliott Hughes self.assertEqual(pos.valuerecord2.asFea(), "<1 2 3 4>") 1035*e1fe3e4aSElliott Hughes 1036*e1fe3e4aSElliott Hughes def test_gpos_type_2_format_a_enumerated(self): 1037*e1fe3e4aSElliott Hughes doc = self.parse( 1038*e1fe3e4aSElliott Hughes "feature kern {" " enum pos [T V] -60 [a b c] <1 2 3 4>;" "} kern;" 1039*e1fe3e4aSElliott Hughes ) 1040*e1fe3e4aSElliott Hughes pos = doc.statements[0].statements[0] 1041*e1fe3e4aSElliott Hughes self.assertEqual(type(pos), ast.PairPosStatement) 1042*e1fe3e4aSElliott Hughes self.assertTrue(pos.enumerated) 1043*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([pos.glyphs1]), "[T V]") 1044*e1fe3e4aSElliott Hughes self.assertEqual(pos.valuerecord1.asFea(), "-60") 1045*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([pos.glyphs2]), "[a b c]") 1046*e1fe3e4aSElliott Hughes self.assertEqual(pos.valuerecord2.asFea(), "<1 2 3 4>") 1047*e1fe3e4aSElliott Hughes 1048*e1fe3e4aSElliott Hughes def test_gpos_type_2_format_a_with_null_first(self): 1049*e1fe3e4aSElliott Hughes doc = self.parse( 1050*e1fe3e4aSElliott Hughes "feature kern {" " pos [T V] <NULL> [a b c] <1 2 3 4>;" "} kern;" 1051*e1fe3e4aSElliott Hughes ) 1052*e1fe3e4aSElliott Hughes pos = doc.statements[0].statements[0] 1053*e1fe3e4aSElliott Hughes self.assertEqual(type(pos), ast.PairPosStatement) 1054*e1fe3e4aSElliott Hughes self.assertFalse(pos.enumerated) 1055*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([pos.glyphs1]), "[T V]") 1056*e1fe3e4aSElliott Hughes self.assertFalse(pos.valuerecord1) 1057*e1fe3e4aSElliott Hughes self.assertEqual(pos.valuerecord1.asFea(), "<NULL>") 1058*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([pos.glyphs2]), "[a b c]") 1059*e1fe3e4aSElliott Hughes self.assertEqual(pos.valuerecord2.asFea(), "<1 2 3 4>") 1060*e1fe3e4aSElliott Hughes self.assertEqual(pos.asFea(), "pos [T V] <NULL> [a b c] <1 2 3 4>;") 1061*e1fe3e4aSElliott Hughes 1062*e1fe3e4aSElliott Hughes def test_gpos_type_2_format_a_with_null_second(self): 1063*e1fe3e4aSElliott Hughes doc = self.parse( 1064*e1fe3e4aSElliott Hughes "feature kern {" " pos [T V] <1 2 3 4> [a b c] <NULL>;" "} kern;" 1065*e1fe3e4aSElliott Hughes ) 1066*e1fe3e4aSElliott Hughes pos = doc.statements[0].statements[0] 1067*e1fe3e4aSElliott Hughes self.assertEqual(type(pos), ast.PairPosStatement) 1068*e1fe3e4aSElliott Hughes self.assertFalse(pos.enumerated) 1069*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([pos.glyphs1]), "[T V]") 1070*e1fe3e4aSElliott Hughes self.assertEqual(pos.valuerecord1.asFea(), "<1 2 3 4>") 1071*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([pos.glyphs2]), "[a b c]") 1072*e1fe3e4aSElliott Hughes self.assertFalse(pos.valuerecord2) 1073*e1fe3e4aSElliott Hughes self.assertEqual(pos.asFea(), "pos [T V] [a b c] <1 2 3 4>;") 1074*e1fe3e4aSElliott Hughes 1075*e1fe3e4aSElliott Hughes def test_gpos_type_2_format_b(self): 1076*e1fe3e4aSElliott Hughes doc = self.parse("feature kern {" " pos [T V] [a b c] <1 2 3 4>;" "} kern;") 1077*e1fe3e4aSElliott Hughes pos = doc.statements[0].statements[0] 1078*e1fe3e4aSElliott Hughes self.assertEqual(type(pos), ast.PairPosStatement) 1079*e1fe3e4aSElliott Hughes self.assertFalse(pos.enumerated) 1080*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([pos.glyphs1]), "[T V]") 1081*e1fe3e4aSElliott Hughes self.assertEqual(pos.valuerecord1.asFea(), "<1 2 3 4>") 1082*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([pos.glyphs2]), "[a b c]") 1083*e1fe3e4aSElliott Hughes self.assertIsNone(pos.valuerecord2) 1084*e1fe3e4aSElliott Hughes 1085*e1fe3e4aSElliott Hughes def test_gpos_type_2_format_b_enumerated(self): 1086*e1fe3e4aSElliott Hughes doc = self.parse( 1087*e1fe3e4aSElliott Hughes "feature kern {" " enumerate position [T V] [a b c] <1 2 3 4>;" "} kern;" 1088*e1fe3e4aSElliott Hughes ) 1089*e1fe3e4aSElliott Hughes pos = doc.statements[0].statements[0] 1090*e1fe3e4aSElliott Hughes self.assertEqual(type(pos), ast.PairPosStatement) 1091*e1fe3e4aSElliott Hughes self.assertTrue(pos.enumerated) 1092*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([pos.glyphs1]), "[T V]") 1093*e1fe3e4aSElliott Hughes self.assertEqual(pos.valuerecord1.asFea(), "<1 2 3 4>") 1094*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([pos.glyphs2]), "[a b c]") 1095*e1fe3e4aSElliott Hughes self.assertIsNone(pos.valuerecord2) 1096*e1fe3e4aSElliott Hughes 1097*e1fe3e4aSElliott Hughes def test_gpos_type_3(self): 1098*e1fe3e4aSElliott Hughes doc = self.parse( 1099*e1fe3e4aSElliott Hughes "feature kern {" 1100*e1fe3e4aSElliott Hughes " position cursive A <anchor 12 -2> <anchor 2 3>;" 1101*e1fe3e4aSElliott Hughes "} kern;" 1102*e1fe3e4aSElliott Hughes ) 1103*e1fe3e4aSElliott Hughes pos = doc.statements[0].statements[0] 1104*e1fe3e4aSElliott Hughes self.assertEqual(type(pos), ast.CursivePosStatement) 1105*e1fe3e4aSElliott Hughes self.assertEqual(pos.glyphclass.glyphSet(), ("A",)) 1106*e1fe3e4aSElliott Hughes self.assertEqual((pos.entryAnchor.x, pos.entryAnchor.y), (12, -2)) 1107*e1fe3e4aSElliott Hughes self.assertEqual((pos.exitAnchor.x, pos.exitAnchor.y), (2, 3)) 1108*e1fe3e4aSElliott Hughes 1109*e1fe3e4aSElliott Hughes def test_gpos_type_3_enumerated(self): 1110*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1111*e1fe3e4aSElliott Hughes FeatureLibError, 1112*e1fe3e4aSElliott Hughes '"enumerate" is not allowed with cursive attachment positioning', 1113*e1fe3e4aSElliott Hughes self.parse, 1114*e1fe3e4aSElliott Hughes "feature kern {" 1115*e1fe3e4aSElliott Hughes " enumerate position cursive A <anchor 12 -2> <anchor 2 3>;" 1116*e1fe3e4aSElliott Hughes "} kern;", 1117*e1fe3e4aSElliott Hughes ) 1118*e1fe3e4aSElliott Hughes 1119*e1fe3e4aSElliott Hughes def test_gpos_type_4(self): 1120*e1fe3e4aSElliott Hughes doc = self.parse( 1121*e1fe3e4aSElliott Hughes "markClass [acute grave] <anchor 150 -10> @TOP_MARKS;" 1122*e1fe3e4aSElliott Hughes "markClass [dieresis umlaut] <anchor 300 -10> @TOP_MARKS;" 1123*e1fe3e4aSElliott Hughes "markClass [cedilla] <anchor 300 600> @BOTTOM_MARKS;" 1124*e1fe3e4aSElliott Hughes "feature test {" 1125*e1fe3e4aSElliott Hughes " position base [a e o u] " 1126*e1fe3e4aSElliott Hughes " <anchor 250 450> mark @TOP_MARKS " 1127*e1fe3e4aSElliott Hughes " <anchor 210 -10> mark @BOTTOM_MARKS;" 1128*e1fe3e4aSElliott Hughes "} test;" 1129*e1fe3e4aSElliott Hughes ) 1130*e1fe3e4aSElliott Hughes pos = doc.statements[-1].statements[0] 1131*e1fe3e4aSElliott Hughes self.assertEqual(type(pos), ast.MarkBasePosStatement) 1132*e1fe3e4aSElliott Hughes self.assertEqual(pos.base.glyphSet(), ("a", "e", "o", "u")) 1133*e1fe3e4aSElliott Hughes (a1, m1), (a2, m2) = pos.marks 1134*e1fe3e4aSElliott Hughes self.assertEqual((a1.x, a1.y, m1.name), (250, 450, "TOP_MARKS")) 1135*e1fe3e4aSElliott Hughes self.assertEqual((a2.x, a2.y, m2.name), (210, -10, "BOTTOM_MARKS")) 1136*e1fe3e4aSElliott Hughes 1137*e1fe3e4aSElliott Hughes def test_gpos_type_4_enumerated(self): 1138*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1139*e1fe3e4aSElliott Hughes FeatureLibError, 1140*e1fe3e4aSElliott Hughes '"enumerate" is not allowed with ' "mark-to-base attachment positioning", 1141*e1fe3e4aSElliott Hughes self.parse, 1142*e1fe3e4aSElliott Hughes "feature kern {" 1143*e1fe3e4aSElliott Hughes " markClass cedilla <anchor 300 600> @BOTTOM_MARKS;" 1144*e1fe3e4aSElliott Hughes " enumerate position base A <anchor 12 -2> mark @BOTTOM_MARKS;" 1145*e1fe3e4aSElliott Hughes "} kern;", 1146*e1fe3e4aSElliott Hughes ) 1147*e1fe3e4aSElliott Hughes 1148*e1fe3e4aSElliott Hughes def test_gpos_type_4_not_markClass(self): 1149*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1150*e1fe3e4aSElliott Hughes FeatureLibError, 1151*e1fe3e4aSElliott Hughes "@MARKS is not a markClass", 1152*e1fe3e4aSElliott Hughes self.parse, 1153*e1fe3e4aSElliott Hughes "@MARKS = [acute grave];" 1154*e1fe3e4aSElliott Hughes "feature test {" 1155*e1fe3e4aSElliott Hughes " position base [a e o u] <anchor 250 450> mark @MARKS;" 1156*e1fe3e4aSElliott Hughes "} test;", 1157*e1fe3e4aSElliott Hughes ) 1158*e1fe3e4aSElliott Hughes 1159*e1fe3e4aSElliott Hughes def test_gpos_type_5(self): 1160*e1fe3e4aSElliott Hughes doc = self.parse( 1161*e1fe3e4aSElliott Hughes "markClass [grave acute] <anchor 150 500> @TOP_MARKS;" 1162*e1fe3e4aSElliott Hughes "markClass [cedilla] <anchor 300 -100> @BOTTOM_MARKS;" 1163*e1fe3e4aSElliott Hughes "feature test {" 1164*e1fe3e4aSElliott Hughes " position " 1165*e1fe3e4aSElliott Hughes " ligature [a_f_f_i o_f_f_i] " 1166*e1fe3e4aSElliott Hughes " <anchor 50 600> mark @TOP_MARKS " 1167*e1fe3e4aSElliott Hughes " <anchor 50 -10> mark @BOTTOM_MARKS " 1168*e1fe3e4aSElliott Hughes " ligComponent " 1169*e1fe3e4aSElliott Hughes " <anchor 30 800> mark @TOP_MARKS " 1170*e1fe3e4aSElliott Hughes " ligComponent " 1171*e1fe3e4aSElliott Hughes " <anchor NULL> " 1172*e1fe3e4aSElliott Hughes " ligComponent " 1173*e1fe3e4aSElliott Hughes " <anchor 30 -10> mark @BOTTOM_MARKS;" 1174*e1fe3e4aSElliott Hughes "} test;" 1175*e1fe3e4aSElliott Hughes ) 1176*e1fe3e4aSElliott Hughes pos = doc.statements[-1].statements[0] 1177*e1fe3e4aSElliott Hughes self.assertEqual(type(pos), ast.MarkLigPosStatement) 1178*e1fe3e4aSElliott Hughes self.assertEqual(pos.ligatures.glyphSet(), ("a_f_f_i", "o_f_f_i")) 1179*e1fe3e4aSElliott Hughes [(a11, m11), (a12, m12)], [(a2, m2)], [], [(a4, m4)] = pos.marks 1180*e1fe3e4aSElliott Hughes self.assertEqual((a11.x, a11.y, m11.name), (50, 600, "TOP_MARKS")) 1181*e1fe3e4aSElliott Hughes self.assertEqual((a12.x, a12.y, m12.name), (50, -10, "BOTTOM_MARKS")) 1182*e1fe3e4aSElliott Hughes self.assertEqual((a2.x, a2.y, m2.name), (30, 800, "TOP_MARKS")) 1183*e1fe3e4aSElliott Hughes self.assertEqual((a4.x, a4.y, m4.name), (30, -10, "BOTTOM_MARKS")) 1184*e1fe3e4aSElliott Hughes 1185*e1fe3e4aSElliott Hughes def test_gpos_type_5_enumerated(self): 1186*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1187*e1fe3e4aSElliott Hughes FeatureLibError, 1188*e1fe3e4aSElliott Hughes '"enumerate" is not allowed with ' 1189*e1fe3e4aSElliott Hughes "mark-to-ligature attachment positioning", 1190*e1fe3e4aSElliott Hughes self.parse, 1191*e1fe3e4aSElliott Hughes "feature test {" 1192*e1fe3e4aSElliott Hughes " markClass cedilla <anchor 300 600> @MARKS;" 1193*e1fe3e4aSElliott Hughes " enumerate position " 1194*e1fe3e4aSElliott Hughes " ligature f_i <anchor 100 0> mark @MARKS" 1195*e1fe3e4aSElliott Hughes " ligComponent <anchor NULL>;" 1196*e1fe3e4aSElliott Hughes "} test;", 1197*e1fe3e4aSElliott Hughes ) 1198*e1fe3e4aSElliott Hughes 1199*e1fe3e4aSElliott Hughes def test_gpos_type_5_not_markClass(self): 1200*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1201*e1fe3e4aSElliott Hughes FeatureLibError, 1202*e1fe3e4aSElliott Hughes "@MARKS is not a markClass", 1203*e1fe3e4aSElliott Hughes self.parse, 1204*e1fe3e4aSElliott Hughes "@MARKS = [acute grave];" 1205*e1fe3e4aSElliott Hughes "feature test {" 1206*e1fe3e4aSElliott Hughes " position ligature f_i <anchor 250 450> mark @MARKS;" 1207*e1fe3e4aSElliott Hughes "} test;", 1208*e1fe3e4aSElliott Hughes ) 1209*e1fe3e4aSElliott Hughes 1210*e1fe3e4aSElliott Hughes def test_gpos_type_6(self): 1211*e1fe3e4aSElliott Hughes doc = self.parse( 1212*e1fe3e4aSElliott Hughes "markClass damma <anchor 189 -103> @MARK_CLASS_1;" 1213*e1fe3e4aSElliott Hughes "feature test {" 1214*e1fe3e4aSElliott Hughes " position mark hamza <anchor 221 301> mark @MARK_CLASS_1;" 1215*e1fe3e4aSElliott Hughes "} test;" 1216*e1fe3e4aSElliott Hughes ) 1217*e1fe3e4aSElliott Hughes pos = doc.statements[-1].statements[0] 1218*e1fe3e4aSElliott Hughes self.assertEqual(type(pos), ast.MarkMarkPosStatement) 1219*e1fe3e4aSElliott Hughes self.assertEqual(pos.baseMarks.glyphSet(), ("hamza",)) 1220*e1fe3e4aSElliott Hughes [(a1, m1)] = pos.marks 1221*e1fe3e4aSElliott Hughes self.assertEqual((a1.x, a1.y, m1.name), (221, 301, "MARK_CLASS_1")) 1222*e1fe3e4aSElliott Hughes 1223*e1fe3e4aSElliott Hughes def test_gpos_type_6_enumerated(self): 1224*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1225*e1fe3e4aSElliott Hughes FeatureLibError, 1226*e1fe3e4aSElliott Hughes '"enumerate" is not allowed with ' "mark-to-mark attachment positioning", 1227*e1fe3e4aSElliott Hughes self.parse, 1228*e1fe3e4aSElliott Hughes "markClass damma <anchor 189 -103> @MARK_CLASS_1;" 1229*e1fe3e4aSElliott Hughes "feature test {" 1230*e1fe3e4aSElliott Hughes " enum pos mark hamza <anchor 221 301> mark @MARK_CLASS_1;" 1231*e1fe3e4aSElliott Hughes "} test;", 1232*e1fe3e4aSElliott Hughes ) 1233*e1fe3e4aSElliott Hughes 1234*e1fe3e4aSElliott Hughes def test_gpos_type_6_not_markClass(self): 1235*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1236*e1fe3e4aSElliott Hughes FeatureLibError, 1237*e1fe3e4aSElliott Hughes "@MARKS is not a markClass", 1238*e1fe3e4aSElliott Hughes self.parse, 1239*e1fe3e4aSElliott Hughes "@MARKS = [acute grave];" 1240*e1fe3e4aSElliott Hughes "feature test {" 1241*e1fe3e4aSElliott Hughes " position mark cedilla <anchor 250 450> mark @MARKS;" 1242*e1fe3e4aSElliott Hughes "} test;", 1243*e1fe3e4aSElliott Hughes ) 1244*e1fe3e4aSElliott Hughes 1245*e1fe3e4aSElliott Hughes def test_gpos_type_8(self): 1246*e1fe3e4aSElliott Hughes doc = self.parse( 1247*e1fe3e4aSElliott Hughes "lookup L1 {pos one 100;} L1; lookup L2 {pos two 200;} L2;" 1248*e1fe3e4aSElliott Hughes "feature test {" 1249*e1fe3e4aSElliott Hughes " pos [A a] [B b] I' lookup L1 [N n]' lookup L2 P' [Y y] [Z z];" 1250*e1fe3e4aSElliott Hughes "} test;" 1251*e1fe3e4aSElliott Hughes ) 1252*e1fe3e4aSElliott Hughes lookup1, lookup2 = doc.statements[0:2] 1253*e1fe3e4aSElliott Hughes pos = doc.statements[-1].statements[0] 1254*e1fe3e4aSElliott Hughes self.assertEqual(type(pos), ast.ChainContextPosStatement) 1255*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(pos.prefix), "[A a] [B b]") 1256*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(pos.glyphs), "I [N n] P") 1257*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(pos.suffix), "[Y y] [Z z]") 1258*e1fe3e4aSElliott Hughes self.assertEqual(pos.lookups, [[lookup1], [lookup2], None]) 1259*e1fe3e4aSElliott Hughes 1260*e1fe3e4aSElliott Hughes def test_gpos_type_8_lookup_with_values(self): 1261*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1262*e1fe3e4aSElliott Hughes FeatureLibError, 1263*e1fe3e4aSElliott Hughes 'If "lookup" is present, no values must be specified', 1264*e1fe3e4aSElliott Hughes self.parse, 1265*e1fe3e4aSElliott Hughes "lookup L1 {pos one 100;} L1;" 1266*e1fe3e4aSElliott Hughes "feature test {" 1267*e1fe3e4aSElliott Hughes " pos A' lookup L1 B' 20;" 1268*e1fe3e4aSElliott Hughes "} test;", 1269*e1fe3e4aSElliott Hughes ) 1270*e1fe3e4aSElliott Hughes 1271*e1fe3e4aSElliott Hughes def test_markClass(self): 1272*e1fe3e4aSElliott Hughes doc = self.parse("markClass [acute grave] <anchor 350 3> @MARKS;") 1273*e1fe3e4aSElliott Hughes mc = doc.statements[0] 1274*e1fe3e4aSElliott Hughes self.assertIsInstance(mc, ast.MarkClassDefinition) 1275*e1fe3e4aSElliott Hughes self.assertEqual(mc.markClass.name, "MARKS") 1276*e1fe3e4aSElliott Hughes self.assertEqual(mc.glyphSet(), ("acute", "grave")) 1277*e1fe3e4aSElliott Hughes self.assertEqual((mc.anchor.x, mc.anchor.y), (350, 3)) 1278*e1fe3e4aSElliott Hughes 1279*e1fe3e4aSElliott Hughes def test_nameid_windows_utf16(self): 1280*e1fe3e4aSElliott Hughes doc = self.parse(r'table name { nameid 9 "M\00fcller-Lanc\00e9"; } name;') 1281*e1fe3e4aSElliott Hughes name = doc.statements[0].statements[0] 1282*e1fe3e4aSElliott Hughes self.assertIsInstance(name, ast.NameRecord) 1283*e1fe3e4aSElliott Hughes self.assertEqual(name.nameID, 9) 1284*e1fe3e4aSElliott Hughes self.assertEqual(name.platformID, 3) 1285*e1fe3e4aSElliott Hughes self.assertEqual(name.platEncID, 1) 1286*e1fe3e4aSElliott Hughes self.assertEqual(name.langID, 0x0409) 1287*e1fe3e4aSElliott Hughes self.assertEqual(name.string, "Müller-Lancé") 1288*e1fe3e4aSElliott Hughes self.assertEqual(name.asFea(), r'nameid 9 "M\00fcller-Lanc\00e9";') 1289*e1fe3e4aSElliott Hughes 1290*e1fe3e4aSElliott Hughes def test_nameid_windows_utf16_backslash(self): 1291*e1fe3e4aSElliott Hughes doc = self.parse(r'table name { nameid 9 "Back\005cslash"; } name;') 1292*e1fe3e4aSElliott Hughes name = doc.statements[0].statements[0] 1293*e1fe3e4aSElliott Hughes self.assertEqual(name.string, r"Back\slash") 1294*e1fe3e4aSElliott Hughes self.assertEqual(name.asFea(), r'nameid 9 "Back\005cslash";') 1295*e1fe3e4aSElliott Hughes 1296*e1fe3e4aSElliott Hughes def test_nameid_windows_utf16_quotation_mark(self): 1297*e1fe3e4aSElliott Hughes doc = self.parse(r'table name { nameid 9 "Quotation \0022Mark\0022"; } name;') 1298*e1fe3e4aSElliott Hughes name = doc.statements[0].statements[0] 1299*e1fe3e4aSElliott Hughes self.assertEqual(name.string, 'Quotation "Mark"') 1300*e1fe3e4aSElliott Hughes self.assertEqual(name.asFea(), r'nameid 9 "Quotation \0022Mark\0022";') 1301*e1fe3e4aSElliott Hughes 1302*e1fe3e4aSElliott Hughes def test_nameid_windows_utf16_surroates(self): 1303*e1fe3e4aSElliott Hughes doc = self.parse(r'table name { nameid 9 "Carrot \D83E\DD55"; } name;') 1304*e1fe3e4aSElliott Hughes name = doc.statements[0].statements[0] 1305*e1fe3e4aSElliott Hughes self.assertEqual(name.string, r"Carrot ") 1306*e1fe3e4aSElliott Hughes self.assertEqual(name.asFea(), r'nameid 9 "Carrot \d83e\dd55";') 1307*e1fe3e4aSElliott Hughes 1308*e1fe3e4aSElliott Hughes def test_nameid_mac_roman(self): 1309*e1fe3e4aSElliott Hughes doc = self.parse(r'table name { nameid 9 1 "Joachim M\9fller-Lanc\8e"; } name;') 1310*e1fe3e4aSElliott Hughes name = doc.statements[0].statements[0] 1311*e1fe3e4aSElliott Hughes self.assertIsInstance(name, ast.NameRecord) 1312*e1fe3e4aSElliott Hughes self.assertEqual(name.nameID, 9) 1313*e1fe3e4aSElliott Hughes self.assertEqual(name.platformID, 1) 1314*e1fe3e4aSElliott Hughes self.assertEqual(name.platEncID, 0) 1315*e1fe3e4aSElliott Hughes self.assertEqual(name.langID, 0) 1316*e1fe3e4aSElliott Hughes self.assertEqual(name.string, "Joachim Müller-Lancé") 1317*e1fe3e4aSElliott Hughes self.assertEqual(name.asFea(), r'nameid 9 1 "Joachim M\9fller-Lanc\8e";') 1318*e1fe3e4aSElliott Hughes 1319*e1fe3e4aSElliott Hughes def test_nameid_mac_croatian(self): 1320*e1fe3e4aSElliott Hughes doc = self.parse(r'table name { nameid 9 1 0 18 "Jovica Veljovi\e6"; } name;') 1321*e1fe3e4aSElliott Hughes name = doc.statements[0].statements[0] 1322*e1fe3e4aSElliott Hughes self.assertEqual(name.nameID, 9) 1323*e1fe3e4aSElliott Hughes self.assertEqual(name.platformID, 1) 1324*e1fe3e4aSElliott Hughes self.assertEqual(name.platEncID, 0) 1325*e1fe3e4aSElliott Hughes self.assertEqual(name.langID, 18) 1326*e1fe3e4aSElliott Hughes self.assertEqual(name.string, "Jovica Veljović") 1327*e1fe3e4aSElliott Hughes self.assertEqual(name.asFea(), r'nameid 9 1 0 18 "Jovica Veljovi\e6";') 1328*e1fe3e4aSElliott Hughes 1329*e1fe3e4aSElliott Hughes def test_nameid_unsupported_platform(self): 1330*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1331*e1fe3e4aSElliott Hughes FeatureLibError, 1332*e1fe3e4aSElliott Hughes "Expected platform id 1 or 3", 1333*e1fe3e4aSElliott Hughes self.parse, 1334*e1fe3e4aSElliott Hughes 'table name { nameid 9 666 "Foo"; } name;', 1335*e1fe3e4aSElliott Hughes ) 1336*e1fe3e4aSElliott Hughes 1337*e1fe3e4aSElliott Hughes def test_nameid_hexadecimal(self): 1338*e1fe3e4aSElliott Hughes doc = self.parse(r'table name { nameid 0x9 0x3 0x1 0x0409 "Test"; } name;') 1339*e1fe3e4aSElliott Hughes name = doc.statements[0].statements[0] 1340*e1fe3e4aSElliott Hughes self.assertEqual(name.nameID, 9) 1341*e1fe3e4aSElliott Hughes self.assertEqual(name.platformID, 3) 1342*e1fe3e4aSElliott Hughes self.assertEqual(name.platEncID, 1) 1343*e1fe3e4aSElliott Hughes self.assertEqual(name.langID, 0x0409) 1344*e1fe3e4aSElliott Hughes 1345*e1fe3e4aSElliott Hughes def test_nameid_octal(self): 1346*e1fe3e4aSElliott Hughes doc = self.parse(r'table name { nameid 011 03 012 02011 "Test"; } name;') 1347*e1fe3e4aSElliott Hughes name = doc.statements[0].statements[0] 1348*e1fe3e4aSElliott Hughes self.assertEqual(name.nameID, 9) 1349*e1fe3e4aSElliott Hughes self.assertEqual(name.platformID, 3) 1350*e1fe3e4aSElliott Hughes self.assertEqual(name.platEncID, 10) 1351*e1fe3e4aSElliott Hughes self.assertEqual(name.langID, 0o2011) 1352*e1fe3e4aSElliott Hughes 1353*e1fe3e4aSElliott Hughes def test_cv_hexadecimal(self): 1354*e1fe3e4aSElliott Hughes doc = self.parse(r"feature cv01 { cvParameters { Character 0x5DDE; }; } cv01;") 1355*e1fe3e4aSElliott Hughes cv = doc.statements[0].statements[0].statements[0] 1356*e1fe3e4aSElliott Hughes self.assertEqual(cv.character, 0x5DDE) 1357*e1fe3e4aSElliott Hughes 1358*e1fe3e4aSElliott Hughes def test_cv_octal(self): 1359*e1fe3e4aSElliott Hughes doc = self.parse(r"feature cv01 { cvParameters { Character 056736; }; } cv01;") 1360*e1fe3e4aSElliott Hughes cv = doc.statements[0].statements[0].statements[0] 1361*e1fe3e4aSElliott Hughes self.assertEqual(cv.character, 0o56736) 1362*e1fe3e4aSElliott Hughes 1363*e1fe3e4aSElliott Hughes def test_rsub_format_a(self): 1364*e1fe3e4aSElliott Hughes doc = self.parse("feature test {rsub a [b B] c' d [e E] by C;} test;") 1365*e1fe3e4aSElliott Hughes rsub = doc.statements[0].statements[0] 1366*e1fe3e4aSElliott Hughes self.assertEqual(type(rsub), ast.ReverseChainSingleSubstStatement) 1367*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(rsub.old_prefix), "a [B b]") 1368*e1fe3e4aSElliott Hughes self.assertEqual(rsub.glyphs[0].glyphSet(), ("c",)) 1369*e1fe3e4aSElliott Hughes self.assertEqual(rsub.replacements[0].glyphSet(), ("C",)) 1370*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(rsub.old_suffix), "d [E e]") 1371*e1fe3e4aSElliott Hughes 1372*e1fe3e4aSElliott Hughes def test_rsub_format_a_cid(self): 1373*e1fe3e4aSElliott Hughes doc = self.parse(r"feature test {rsub \1 [\2 \3] \4' \5 by \6;} test;") 1374*e1fe3e4aSElliott Hughes rsub = doc.statements[0].statements[0] 1375*e1fe3e4aSElliott Hughes self.assertEqual(type(rsub), ast.ReverseChainSingleSubstStatement) 1376*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(rsub.old_prefix), "cid00001 [cid00002 cid00003]") 1377*e1fe3e4aSElliott Hughes self.assertEqual(rsub.glyphs[0].glyphSet(), ("cid00004",)) 1378*e1fe3e4aSElliott Hughes self.assertEqual(rsub.replacements[0].glyphSet(), ("cid00006",)) 1379*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(rsub.old_suffix), "cid00005") 1380*e1fe3e4aSElliott Hughes 1381*e1fe3e4aSElliott Hughes def test_rsub_format_b(self): 1382*e1fe3e4aSElliott Hughes doc = self.parse( 1383*e1fe3e4aSElliott Hughes "feature smcp {" 1384*e1fe3e4aSElliott Hughes " reversesub A B [one.fitted one.oldstyle]' C [d D] by one;" 1385*e1fe3e4aSElliott Hughes "} smcp;" 1386*e1fe3e4aSElliott Hughes ) 1387*e1fe3e4aSElliott Hughes rsub = doc.statements[0].statements[0] 1388*e1fe3e4aSElliott Hughes self.assertEqual(type(rsub), ast.ReverseChainSingleSubstStatement) 1389*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(rsub.old_prefix), "A B") 1390*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(rsub.old_suffix), "C [D d]") 1391*e1fe3e4aSElliott Hughes self.assertEqual(mapping(rsub), {"one.fitted": "one", "one.oldstyle": "one"}) 1392*e1fe3e4aSElliott Hughes 1393*e1fe3e4aSElliott Hughes def test_rsub_format_c(self): 1394*e1fe3e4aSElliott Hughes doc = self.parse( 1395*e1fe3e4aSElliott Hughes "feature test {" 1396*e1fe3e4aSElliott Hughes " reversesub BACK TRACK [a-d]' LOOK AHEAD by [A.sc-D.sc];" 1397*e1fe3e4aSElliott Hughes "} test;" 1398*e1fe3e4aSElliott Hughes ) 1399*e1fe3e4aSElliott Hughes rsub = doc.statements[0].statements[0] 1400*e1fe3e4aSElliott Hughes self.assertEqual(type(rsub), ast.ReverseChainSingleSubstStatement) 1401*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(rsub.old_prefix), "BACK TRACK") 1402*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(rsub.old_suffix), "LOOK AHEAD") 1403*e1fe3e4aSElliott Hughes self.assertEqual( 1404*e1fe3e4aSElliott Hughes mapping(rsub), {"a": "A.sc", "b": "B.sc", "c": "C.sc", "d": "D.sc"} 1405*e1fe3e4aSElliott Hughes ) 1406*e1fe3e4aSElliott Hughes 1407*e1fe3e4aSElliott Hughes def test_rsub_from(self): 1408*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1409*e1fe3e4aSElliott Hughes FeatureLibError, 1410*e1fe3e4aSElliott Hughes 'Reverse chaining substitutions do not support "from"', 1411*e1fe3e4aSElliott Hughes self.parse, 1412*e1fe3e4aSElliott Hughes "feature test {rsub a from [a.1 a.2 a.3];} test;", 1413*e1fe3e4aSElliott Hughes ) 1414*e1fe3e4aSElliott Hughes 1415*e1fe3e4aSElliott Hughes def test_rsub_nonsingle(self): 1416*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1417*e1fe3e4aSElliott Hughes FeatureLibError, 1418*e1fe3e4aSElliott Hughes "In reverse chaining single substitutions, only a single glyph " 1419*e1fe3e4aSElliott Hughes "or glyph class can be replaced", 1420*e1fe3e4aSElliott Hughes self.parse, 1421*e1fe3e4aSElliott Hughes "feature test {rsub c d by c_d;} test;", 1422*e1fe3e4aSElliott Hughes ) 1423*e1fe3e4aSElliott Hughes 1424*e1fe3e4aSElliott Hughes def test_rsub_multiple_replacement_glyphs(self): 1425*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1426*e1fe3e4aSElliott Hughes FeatureLibError, 1427*e1fe3e4aSElliott Hughes "In reverse chaining single substitutions, the replacement " 1428*e1fe3e4aSElliott Hughes r'\(after "by"\) must be a single glyph or glyph class', 1429*e1fe3e4aSElliott Hughes self.parse, 1430*e1fe3e4aSElliott Hughes "feature test {rsub f_i by f i;} test;", 1431*e1fe3e4aSElliott Hughes ) 1432*e1fe3e4aSElliott Hughes 1433*e1fe3e4aSElliott Hughes def test_script(self): 1434*e1fe3e4aSElliott Hughes doc = self.parse("feature test {script cyrl;} test;") 1435*e1fe3e4aSElliott Hughes s = doc.statements[0].statements[0] 1436*e1fe3e4aSElliott Hughes self.assertEqual(type(s), ast.ScriptStatement) 1437*e1fe3e4aSElliott Hughes self.assertEqual(s.script, "cyrl") 1438*e1fe3e4aSElliott Hughes 1439*e1fe3e4aSElliott Hughes def test_script_dflt(self): 1440*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1441*e1fe3e4aSElliott Hughes FeatureLibError, 1442*e1fe3e4aSElliott Hughes '"dflt" is not a valid script tag; use "DFLT" instead', 1443*e1fe3e4aSElliott Hughes self.parse, 1444*e1fe3e4aSElliott Hughes "feature test {script dflt;} test;", 1445*e1fe3e4aSElliott Hughes ) 1446*e1fe3e4aSElliott Hughes 1447*e1fe3e4aSElliott Hughes def test_stat_design_axis(self): # STAT DesignAxis 1448*e1fe3e4aSElliott Hughes doc = self.parse( 1449*e1fe3e4aSElliott Hughes "table STAT { DesignAxis opsz 0 " '{name "Optical Size";}; } STAT;' 1450*e1fe3e4aSElliott Hughes ) 1451*e1fe3e4aSElliott Hughes da = doc.statements[0].statements[0] 1452*e1fe3e4aSElliott Hughes self.assertIsInstance(da, ast.STATDesignAxisStatement) 1453*e1fe3e4aSElliott Hughes self.assertEqual(da.tag, "opsz") 1454*e1fe3e4aSElliott Hughes self.assertEqual(da.axisOrder, 0) 1455*e1fe3e4aSElliott Hughes self.assertEqual(da.names[0].string, "Optical Size") 1456*e1fe3e4aSElliott Hughes 1457*e1fe3e4aSElliott Hughes def test_stat_axis_value_format1(self): # STAT AxisValue 1458*e1fe3e4aSElliott Hughes doc = self.parse( 1459*e1fe3e4aSElliott Hughes "table STAT { DesignAxis opsz 0 " 1460*e1fe3e4aSElliott Hughes '{name "Optical Size";}; ' 1461*e1fe3e4aSElliott Hughes 'AxisValue {location opsz 8; name "Caption";}; } ' 1462*e1fe3e4aSElliott Hughes "STAT;" 1463*e1fe3e4aSElliott Hughes ) 1464*e1fe3e4aSElliott Hughes avr = doc.statements[0].statements[1] 1465*e1fe3e4aSElliott Hughes self.assertIsInstance(avr, ast.STATAxisValueStatement) 1466*e1fe3e4aSElliott Hughes self.assertEqual(avr.locations[0].tag, "opsz") 1467*e1fe3e4aSElliott Hughes self.assertEqual(avr.locations[0].values[0], 8) 1468*e1fe3e4aSElliott Hughes self.assertEqual(avr.names[0].string, "Caption") 1469*e1fe3e4aSElliott Hughes 1470*e1fe3e4aSElliott Hughes def test_stat_axis_value_format2(self): # STAT AxisValue 1471*e1fe3e4aSElliott Hughes doc = self.parse( 1472*e1fe3e4aSElliott Hughes "table STAT { DesignAxis opsz 0 " 1473*e1fe3e4aSElliott Hughes '{name "Optical Size";}; ' 1474*e1fe3e4aSElliott Hughes 'AxisValue {location opsz 8 6 10; name "Caption";}; } ' 1475*e1fe3e4aSElliott Hughes "STAT;" 1476*e1fe3e4aSElliott Hughes ) 1477*e1fe3e4aSElliott Hughes avr = doc.statements[0].statements[1] 1478*e1fe3e4aSElliott Hughes self.assertIsInstance(avr, ast.STATAxisValueStatement) 1479*e1fe3e4aSElliott Hughes self.assertEqual(avr.locations[0].tag, "opsz") 1480*e1fe3e4aSElliott Hughes self.assertEqual(avr.locations[0].values, [8, 6, 10]) 1481*e1fe3e4aSElliott Hughes self.assertEqual(avr.names[0].string, "Caption") 1482*e1fe3e4aSElliott Hughes 1483*e1fe3e4aSElliott Hughes def test_stat_axis_value_format2_bad_range(self): # STAT AxisValue 1484*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1485*e1fe3e4aSElliott Hughes FeatureLibError, 1486*e1fe3e4aSElliott Hughes "Default value 5 is outside of specified range 6-10.", 1487*e1fe3e4aSElliott Hughes self.parse, 1488*e1fe3e4aSElliott Hughes "table STAT { DesignAxis opsz 0 " 1489*e1fe3e4aSElliott Hughes '{name "Optical Size";}; ' 1490*e1fe3e4aSElliott Hughes 'AxisValue {location opsz 5 6 10; name "Caption";}; } ' 1491*e1fe3e4aSElliott Hughes "STAT;", 1492*e1fe3e4aSElliott Hughes ) 1493*e1fe3e4aSElliott Hughes 1494*e1fe3e4aSElliott Hughes def test_stat_axis_value_format4(self): # STAT AxisValue 1495*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1496*e1fe3e4aSElliott Hughes FeatureLibError, 1497*e1fe3e4aSElliott Hughes "Only one value is allowed in a Format 4 Axis Value Record, but 3 were found.", 1498*e1fe3e4aSElliott Hughes self.parse, 1499*e1fe3e4aSElliott Hughes "table STAT { " 1500*e1fe3e4aSElliott Hughes 'DesignAxis opsz 0 {name "Optical Size";}; ' 1501*e1fe3e4aSElliott Hughes 'DesignAxis wdth 0 {name "Width";}; ' 1502*e1fe3e4aSElliott Hughes "AxisValue {" 1503*e1fe3e4aSElliott Hughes "location opsz 8 6 10; " 1504*e1fe3e4aSElliott Hughes "location wdth 400; " 1505*e1fe3e4aSElliott Hughes 'name "Caption";}; } ' 1506*e1fe3e4aSElliott Hughes "STAT;", 1507*e1fe3e4aSElliott Hughes ) 1508*e1fe3e4aSElliott Hughes 1509*e1fe3e4aSElliott Hughes def test_stat_elidedfallbackname(self): # STAT ElidedFallbackName 1510*e1fe3e4aSElliott Hughes doc = self.parse( 1511*e1fe3e4aSElliott Hughes 'table STAT { ElidedFallbackName {name "Roman"; ' 1512*e1fe3e4aSElliott Hughes 'name 3 1 0x0411 "ローマン"; }; ' 1513*e1fe3e4aSElliott Hughes "} STAT;" 1514*e1fe3e4aSElliott Hughes ) 1515*e1fe3e4aSElliott Hughes nameRecord = doc.statements[0].statements[0] 1516*e1fe3e4aSElliott Hughes self.assertIsInstance(nameRecord, ast.ElidedFallbackName) 1517*e1fe3e4aSElliott Hughes self.assertEqual(nameRecord.names[0].string, "Roman") 1518*e1fe3e4aSElliott Hughes self.assertEqual(nameRecord.names[1].string, "ローマン") 1519*e1fe3e4aSElliott Hughes 1520*e1fe3e4aSElliott Hughes def test_stat_elidedfallbacknameid(self): # STAT ElidedFallbackNameID 1521*e1fe3e4aSElliott Hughes doc = self.parse( 1522*e1fe3e4aSElliott Hughes 'table name { nameid 278 "Roman"; } name; ' 1523*e1fe3e4aSElliott Hughes "table STAT { ElidedFallbackNameID 278; " 1524*e1fe3e4aSElliott Hughes "} STAT;" 1525*e1fe3e4aSElliott Hughes ) 1526*e1fe3e4aSElliott Hughes nameRecord = doc.statements[0].statements[0] 1527*e1fe3e4aSElliott Hughes self.assertIsInstance(nameRecord, ast.NameRecord) 1528*e1fe3e4aSElliott Hughes self.assertEqual(nameRecord.string, "Roman") 1529*e1fe3e4aSElliott Hughes 1530*e1fe3e4aSElliott Hughes def test_sub_single_format_a(self): # GSUB LookupType 1 1531*e1fe3e4aSElliott Hughes doc = self.parse("feature smcp {substitute a by a.sc;} smcp;") 1532*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1533*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.SingleSubstStatement) 1534*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.prefix), "") 1535*e1fe3e4aSElliott Hughes self.assertEqual(mapping(sub), {"a": "a.sc"}) 1536*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.suffix), "") 1537*e1fe3e4aSElliott Hughes 1538*e1fe3e4aSElliott Hughes def test_sub_single_format_a_chained(self): # chain to GSUB LookupType 1 1539*e1fe3e4aSElliott Hughes doc = self.parse("feature test {sub [A a] d' [C] by d.alt;} test;") 1540*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1541*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.SingleSubstStatement) 1542*e1fe3e4aSElliott Hughes self.assertEqual(mapping(sub), {"d": "d.alt"}) 1543*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.prefix), "[A a]") 1544*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.suffix), "C") 1545*e1fe3e4aSElliott Hughes 1546*e1fe3e4aSElliott Hughes def test_sub_single_format_a_cid(self): # GSUB LookupType 1 1547*e1fe3e4aSElliott Hughes doc = self.parse(r"feature smcp {substitute \12345 by \78987;} smcp;") 1548*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1549*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.SingleSubstStatement) 1550*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.prefix), "") 1551*e1fe3e4aSElliott Hughes self.assertEqual(mapping(sub), {"cid12345": "cid78987"}) 1552*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.suffix), "") 1553*e1fe3e4aSElliott Hughes 1554*e1fe3e4aSElliott Hughes def test_sub_single_format_b(self): # GSUB LookupType 1 1555*e1fe3e4aSElliott Hughes doc = self.parse( 1556*e1fe3e4aSElliott Hughes "feature smcp {" 1557*e1fe3e4aSElliott Hughes " substitute [one.fitted one.oldstyle] by one;" 1558*e1fe3e4aSElliott Hughes "} smcp;" 1559*e1fe3e4aSElliott Hughes ) 1560*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1561*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.SingleSubstStatement) 1562*e1fe3e4aSElliott Hughes self.assertEqual(mapping(sub), {"one.fitted": "one", "one.oldstyle": "one"}) 1563*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.prefix), "") 1564*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.suffix), "") 1565*e1fe3e4aSElliott Hughes 1566*e1fe3e4aSElliott Hughes def test_sub_single_format_b_chained(self): # chain to GSUB LookupType 1 1567*e1fe3e4aSElliott Hughes doc = self.parse( 1568*e1fe3e4aSElliott Hughes "feature smcp {" 1569*e1fe3e4aSElliott Hughes " substitute PRE FIX [one.fitted one.oldstyle]' SUF FIX by one;" 1570*e1fe3e4aSElliott Hughes "} smcp;" 1571*e1fe3e4aSElliott Hughes ) 1572*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1573*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.SingleSubstStatement) 1574*e1fe3e4aSElliott Hughes self.assertEqual(mapping(sub), {"one.fitted": "one", "one.oldstyle": "one"}) 1575*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.prefix), "PRE FIX") 1576*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.suffix), "SUF FIX") 1577*e1fe3e4aSElliott Hughes 1578*e1fe3e4aSElliott Hughes def test_sub_single_format_c(self): # GSUB LookupType 1 1579*e1fe3e4aSElliott Hughes doc = self.parse( 1580*e1fe3e4aSElliott Hughes "feature smcp {" " substitute [a-d] by [A.sc-D.sc];" "} smcp;" 1581*e1fe3e4aSElliott Hughes ) 1582*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1583*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.SingleSubstStatement) 1584*e1fe3e4aSElliott Hughes self.assertEqual( 1585*e1fe3e4aSElliott Hughes mapping(sub), {"a": "A.sc", "b": "B.sc", "c": "C.sc", "d": "D.sc"} 1586*e1fe3e4aSElliott Hughes ) 1587*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.prefix), "") 1588*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.suffix), "") 1589*e1fe3e4aSElliott Hughes 1590*e1fe3e4aSElliott Hughes def test_sub_single_format_c_chained(self): # chain to GSUB LookupType 1 1591*e1fe3e4aSElliott Hughes doc = self.parse( 1592*e1fe3e4aSElliott Hughes "feature smcp {" " substitute [a-d]' X Y [Z z] by [A.sc-D.sc];" "} smcp;" 1593*e1fe3e4aSElliott Hughes ) 1594*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1595*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.SingleSubstStatement) 1596*e1fe3e4aSElliott Hughes self.assertEqual( 1597*e1fe3e4aSElliott Hughes mapping(sub), {"a": "A.sc", "b": "B.sc", "c": "C.sc", "d": "D.sc"} 1598*e1fe3e4aSElliott Hughes ) 1599*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.prefix), "") 1600*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.suffix), "X Y [Z z]") 1601*e1fe3e4aSElliott Hughes 1602*e1fe3e4aSElliott Hughes def test_sub_single_format_c_different_num_elements(self): 1603*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1604*e1fe3e4aSElliott Hughes FeatureLibError, 1605*e1fe3e4aSElliott Hughes 'Expected a glyph class with 4 elements after "by", ' 1606*e1fe3e4aSElliott Hughes "but found a glyph class with 26 elements", 1607*e1fe3e4aSElliott Hughes self.parse, 1608*e1fe3e4aSElliott Hughes "feature smcp {sub [a-d] by [A.sc-Z.sc];} smcp;", 1609*e1fe3e4aSElliott Hughes ) 1610*e1fe3e4aSElliott Hughes 1611*e1fe3e4aSElliott Hughes def test_sub_with_values(self): 1612*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1613*e1fe3e4aSElliott Hughes FeatureLibError, 1614*e1fe3e4aSElliott Hughes "Substitution statements cannot contain values", 1615*e1fe3e4aSElliott Hughes self.parse, 1616*e1fe3e4aSElliott Hughes "feature smcp {sub A' 20 by A.sc;} smcp;", 1617*e1fe3e4aSElliott Hughes ) 1618*e1fe3e4aSElliott Hughes 1619*e1fe3e4aSElliott Hughes def test_substitute_multiple(self): # GSUB LookupType 2 1620*e1fe3e4aSElliott Hughes doc = self.parse("lookup Look {substitute f_f_i by f f i;} Look;") 1621*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1622*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.MultipleSubstStatement) 1623*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([sub.glyph]), "f_f_i") 1624*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.replacement), "f f i") 1625*e1fe3e4aSElliott Hughes 1626*e1fe3e4aSElliott Hughes def test_substitute_multiple_chained(self): # chain to GSUB LookupType 2 1627*e1fe3e4aSElliott Hughes doc = self.parse("lookup L {sub [A-C] f_f_i' [X-Z] by f f i;} L;") 1628*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1629*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.MultipleSubstStatement) 1630*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([sub.glyph]), "f_f_i") 1631*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.replacement), "f f i") 1632*e1fe3e4aSElliott Hughes 1633*e1fe3e4aSElliott Hughes def test_substitute_multiple_force_chained(self): 1634*e1fe3e4aSElliott Hughes doc = self.parse("lookup L {sub f_f_i' by f f i;} L;") 1635*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1636*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.MultipleSubstStatement) 1637*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([sub.glyph]), "f_f_i") 1638*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.replacement), "f f i") 1639*e1fe3e4aSElliott Hughes self.assertEqual(sub.asFea(), "sub f_f_i' by f f i;") 1640*e1fe3e4aSElliott Hughes 1641*e1fe3e4aSElliott Hughes def test_substitute_multiple_classes(self): 1642*e1fe3e4aSElliott Hughes doc = self.parse("lookup Look {substitute [f_i f_l] by [f f] [i l];} Look;") 1643*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1644*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.MultipleSubstStatement) 1645*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([sub.glyph]), "[f_i f_l]") 1646*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.replacement), "[f f] [i l]") 1647*e1fe3e4aSElliott Hughes 1648*e1fe3e4aSElliott Hughes def test_substitute_multiple_classes_mixed(self): 1649*e1fe3e4aSElliott Hughes doc = self.parse("lookup Look {substitute [f_i f_l] by f [i l];} Look;") 1650*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1651*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.MultipleSubstStatement) 1652*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([sub.glyph]), "[f_i f_l]") 1653*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.replacement), "f [i l]") 1654*e1fe3e4aSElliott Hughes 1655*e1fe3e4aSElliott Hughes def test_substitute_multiple_classes_mixed_singleton(self): 1656*e1fe3e4aSElliott Hughes doc = self.parse("lookup Look {substitute [f_i f_l] by [f] [i l];} Look;") 1657*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1658*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.MultipleSubstStatement) 1659*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([sub.glyph]), "[f_i f_l]") 1660*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.replacement), "f [i l]") 1661*e1fe3e4aSElliott Hughes 1662*e1fe3e4aSElliott Hughes def test_substitute_multiple_classes_mismatch(self): 1663*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1664*e1fe3e4aSElliott Hughes FeatureLibError, 1665*e1fe3e4aSElliott Hughes 'Expected a glyph class with 1 or 3 elements after "by", ' 1666*e1fe3e4aSElliott Hughes "but found a glyph class with 2 elements", 1667*e1fe3e4aSElliott Hughes self.parse, 1668*e1fe3e4aSElliott Hughes "lookup Look {substitute [f_i f_l f_f_i] by [f f_f] [i l i];} Look;", 1669*e1fe3e4aSElliott Hughes ) 1670*e1fe3e4aSElliott Hughes 1671*e1fe3e4aSElliott Hughes def test_substitute_multiple_by_mutliple(self): 1672*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1673*e1fe3e4aSElliott Hughes FeatureLibError, 1674*e1fe3e4aSElliott Hughes "Direct substitution of multiple glyphs by multiple glyphs " 1675*e1fe3e4aSElliott Hughes "is not supported", 1676*e1fe3e4aSElliott Hughes self.parse, 1677*e1fe3e4aSElliott Hughes "lookup MxM {sub a b c by d e f;} MxM;", 1678*e1fe3e4aSElliott Hughes ) 1679*e1fe3e4aSElliott Hughes 1680*e1fe3e4aSElliott Hughes def test_split_marked_glyphs_runs(self): 1681*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1682*e1fe3e4aSElliott Hughes FeatureLibError, 1683*e1fe3e4aSElliott Hughes "Unsupported contextual target sequence", 1684*e1fe3e4aSElliott Hughes self.parse, 1685*e1fe3e4aSElliott Hughes "feature test{" " ignore pos a' x x A';" "} test;", 1686*e1fe3e4aSElliott Hughes ) 1687*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1688*e1fe3e4aSElliott Hughes FeatureLibError, 1689*e1fe3e4aSElliott Hughes "Unsupported contextual target sequence", 1690*e1fe3e4aSElliott Hughes self.parse, 1691*e1fe3e4aSElliott Hughes "lookup shift {" 1692*e1fe3e4aSElliott Hughes " pos a <0 -10 0 0>;" 1693*e1fe3e4aSElliott Hughes " pos A <0 10 0 0>;" 1694*e1fe3e4aSElliott Hughes "} shift;" 1695*e1fe3e4aSElliott Hughes "feature test {" 1696*e1fe3e4aSElliott Hughes " sub a' lookup shift x x A' lookup shift;" 1697*e1fe3e4aSElliott Hughes "} test;", 1698*e1fe3e4aSElliott Hughes ) 1699*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1700*e1fe3e4aSElliott Hughes FeatureLibError, 1701*e1fe3e4aSElliott Hughes "Unsupported contextual target sequence", 1702*e1fe3e4aSElliott Hughes self.parse, 1703*e1fe3e4aSElliott Hughes "feature test {" " ignore sub a' x x A';" "} test;", 1704*e1fe3e4aSElliott Hughes ) 1705*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1706*e1fe3e4aSElliott Hughes FeatureLibError, 1707*e1fe3e4aSElliott Hughes "Unsupported contextual target sequence", 1708*e1fe3e4aSElliott Hughes self.parse, 1709*e1fe3e4aSElliott Hughes "lookup upper {" 1710*e1fe3e4aSElliott Hughes " sub a by A;" 1711*e1fe3e4aSElliott Hughes "} upper;" 1712*e1fe3e4aSElliott Hughes "lookup lower {" 1713*e1fe3e4aSElliott Hughes " sub A by a;" 1714*e1fe3e4aSElliott Hughes "} lower;" 1715*e1fe3e4aSElliott Hughes "feature test {" 1716*e1fe3e4aSElliott Hughes " sub a' lookup upper x x A' lookup lower;" 1717*e1fe3e4aSElliott Hughes "} test;", 1718*e1fe3e4aSElliott Hughes ) 1719*e1fe3e4aSElliott Hughes 1720*e1fe3e4aSElliott Hughes def test_substitute_mix_single_multiple(self): 1721*e1fe3e4aSElliott Hughes doc = self.parse( 1722*e1fe3e4aSElliott Hughes "lookup Look {" 1723*e1fe3e4aSElliott Hughes " sub f_f by f f;" 1724*e1fe3e4aSElliott Hughes " sub f by f;" 1725*e1fe3e4aSElliott Hughes " sub f_f_i by f f i;" 1726*e1fe3e4aSElliott Hughes " sub [a a.sc] by a;" 1727*e1fe3e4aSElliott Hughes " sub [a a.sc] by [b b.sc];" 1728*e1fe3e4aSElliott Hughes "} Look;" 1729*e1fe3e4aSElliott Hughes ) 1730*e1fe3e4aSElliott Hughes statements = doc.statements[0].statements 1731*e1fe3e4aSElliott Hughes for sub in statements: 1732*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.MultipleSubstStatement) 1733*e1fe3e4aSElliott Hughes self.assertEqual(statements[1].glyph, "f") 1734*e1fe3e4aSElliott Hughes self.assertEqual(statements[1].replacement, ["f"]) 1735*e1fe3e4aSElliott Hughes self.assertEqual(statements[3].glyph, "a") 1736*e1fe3e4aSElliott Hughes self.assertEqual(statements[3].replacement, ["a"]) 1737*e1fe3e4aSElliott Hughes self.assertEqual(statements[4].glyph, "a.sc") 1738*e1fe3e4aSElliott Hughes self.assertEqual(statements[4].replacement, ["a"]) 1739*e1fe3e4aSElliott Hughes self.assertEqual(statements[5].glyph, "a") 1740*e1fe3e4aSElliott Hughes self.assertEqual(statements[5].replacement, ["b"]) 1741*e1fe3e4aSElliott Hughes self.assertEqual(statements[6].glyph, "a.sc") 1742*e1fe3e4aSElliott Hughes self.assertEqual(statements[6].replacement, ["b.sc"]) 1743*e1fe3e4aSElliott Hughes 1744*e1fe3e4aSElliott Hughes def test_substitute_from(self): # GSUB LookupType 3 1745*e1fe3e4aSElliott Hughes doc = self.parse( 1746*e1fe3e4aSElliott Hughes "feature test {" " substitute a from [a.1 a.2 a.3];" "} test;" 1747*e1fe3e4aSElliott Hughes ) 1748*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1749*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.AlternateSubstStatement) 1750*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.prefix), "") 1751*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([sub.glyph]), "a") 1752*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.suffix), "") 1753*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([sub.replacement]), "[a.1 a.2 a.3]") 1754*e1fe3e4aSElliott Hughes 1755*e1fe3e4aSElliott Hughes def test_substitute_from_chained(self): # chain to GSUB LookupType 3 1756*e1fe3e4aSElliott Hughes doc = self.parse( 1757*e1fe3e4aSElliott Hughes "feature test {" " substitute A B a' [Y y] Z from [a.1 a.2 a.3];" "} test;" 1758*e1fe3e4aSElliott Hughes ) 1759*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1760*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.AlternateSubstStatement) 1761*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.prefix), "A B") 1762*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([sub.glyph]), "a") 1763*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.suffix), "[Y y] Z") 1764*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([sub.replacement]), "[a.1 a.2 a.3]") 1765*e1fe3e4aSElliott Hughes 1766*e1fe3e4aSElliott Hughes def test_substitute_from_cid(self): # GSUB LookupType 3 1767*e1fe3e4aSElliott Hughes doc = self.parse( 1768*e1fe3e4aSElliott Hughes r"feature test {" r" substitute \7 from [\111 \222];" r"} test;" 1769*e1fe3e4aSElliott Hughes ) 1770*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1771*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.AlternateSubstStatement) 1772*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.prefix), "") 1773*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([sub.glyph]), "cid00007") 1774*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.suffix), "") 1775*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([sub.replacement]), "[cid00111 cid00222]") 1776*e1fe3e4aSElliott Hughes 1777*e1fe3e4aSElliott Hughes def test_substitute_from_glyphclass(self): # GSUB LookupType 3 1778*e1fe3e4aSElliott Hughes doc = self.parse( 1779*e1fe3e4aSElliott Hughes "feature test {" 1780*e1fe3e4aSElliott Hughes " @Ampersands = [ampersand.1 ampersand.2];" 1781*e1fe3e4aSElliott Hughes " substitute ampersand from @Ampersands;" 1782*e1fe3e4aSElliott Hughes "} test;" 1783*e1fe3e4aSElliott Hughes ) 1784*e1fe3e4aSElliott Hughes [glyphclass, sub] = doc.statements[0].statements 1785*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.AlternateSubstStatement) 1786*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.prefix), "") 1787*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([sub.glyph]), "ampersand") 1788*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.suffix), "") 1789*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr([sub.replacement]), "[ampersand.1 ampersand.2]") 1790*e1fe3e4aSElliott Hughes 1791*e1fe3e4aSElliott Hughes def test_substitute_ligature(self): # GSUB LookupType 4 1792*e1fe3e4aSElliott Hughes doc = self.parse("feature liga {substitute f f i by f_f_i;} liga;") 1793*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1794*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.LigatureSubstStatement) 1795*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.glyphs), "f f i") 1796*e1fe3e4aSElliott Hughes self.assertEqual(sub.replacement, "f_f_i") 1797*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.prefix), "") 1798*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.suffix), "") 1799*e1fe3e4aSElliott Hughes 1800*e1fe3e4aSElliott Hughes def test_substitute_ligature_chained(self): # chain to GSUB LookupType 4 1801*e1fe3e4aSElliott Hughes doc = self.parse("feature F {substitute A B f' i' Z by f_i;} F;") 1802*e1fe3e4aSElliott Hughes sub = doc.statements[0].statements[0] 1803*e1fe3e4aSElliott Hughes self.assertIsInstance(sub, ast.LigatureSubstStatement) 1804*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.glyphs), "f i") 1805*e1fe3e4aSElliott Hughes self.assertEqual(sub.replacement, "f_i") 1806*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.prefix), "A B") 1807*e1fe3e4aSElliott Hughes self.assertEqual(glyphstr(sub.suffix), "Z") 1808*e1fe3e4aSElliott Hughes 1809*e1fe3e4aSElliott Hughes def test_substitute_lookups(self): # GSUB LookupType 6 1810*e1fe3e4aSElliott Hughes doc = Parser(self.getpath("spec5fi1.fea"), GLYPHNAMES).parse() 1811*e1fe3e4aSElliott Hughes [_, _, _, langsys, ligs, sub, feature] = doc.statements 1812*e1fe3e4aSElliott Hughes self.assertEqual(feature.statements[0].lookups, [[ligs], None, [sub]]) 1813*e1fe3e4aSElliott Hughes self.assertEqual(feature.statements[1].lookups, [[ligs], None, [sub]]) 1814*e1fe3e4aSElliott Hughes 1815*e1fe3e4aSElliott Hughes def test_substitute_missing_by(self): 1816*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1817*e1fe3e4aSElliott Hughes FeatureLibError, 1818*e1fe3e4aSElliott Hughes 'Expected "by", "from" or explicit lookup references', 1819*e1fe3e4aSElliott Hughes self.parse, 1820*e1fe3e4aSElliott Hughes "feature liga {substitute f f i;} liga;", 1821*e1fe3e4aSElliott Hughes ) 1822*e1fe3e4aSElliott Hughes 1823*e1fe3e4aSElliott Hughes def test_substitute_invalid_statement(self): 1824*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1825*e1fe3e4aSElliott Hughes FeatureLibError, 1826*e1fe3e4aSElliott Hughes "Invalid substitution statement", 1827*e1fe3e4aSElliott Hughes Parser(self.getpath("GSUB_error.fea"), GLYPHNAMES).parse, 1828*e1fe3e4aSElliott Hughes ) 1829*e1fe3e4aSElliott Hughes 1830*e1fe3e4aSElliott Hughes def test_subtable(self): 1831*e1fe3e4aSElliott Hughes doc = self.parse("feature test {subtable;} test;") 1832*e1fe3e4aSElliott Hughes s = doc.statements[0].statements[0] 1833*e1fe3e4aSElliott Hughes self.assertIsInstance(s, ast.SubtableStatement) 1834*e1fe3e4aSElliott Hughes 1835*e1fe3e4aSElliott Hughes def test_table_badEnd(self): 1836*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1837*e1fe3e4aSElliott Hughes FeatureLibError, 1838*e1fe3e4aSElliott Hughes 'Expected "GDEF"', 1839*e1fe3e4aSElliott Hughes self.parse, 1840*e1fe3e4aSElliott Hughes "table GDEF {LigatureCaretByPos f_i 400;} ABCD;", 1841*e1fe3e4aSElliott Hughes ) 1842*e1fe3e4aSElliott Hughes 1843*e1fe3e4aSElliott Hughes def test_table_comment(self): 1844*e1fe3e4aSElliott Hughes for table in "BASE GDEF OS/2 head hhea name vhea".split(): 1845*e1fe3e4aSElliott Hughes doc = self.parse("table %s { # Comment\n } %s;" % (table, table)) 1846*e1fe3e4aSElliott Hughes comment = doc.statements[0].statements[0] 1847*e1fe3e4aSElliott Hughes self.assertIsInstance(comment, ast.Comment) 1848*e1fe3e4aSElliott Hughes self.assertEqual(comment.text, "# Comment") 1849*e1fe3e4aSElliott Hughes 1850*e1fe3e4aSElliott Hughes def test_table_unsupported(self): 1851*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 1852*e1fe3e4aSElliott Hughes FeatureLibError, 1853*e1fe3e4aSElliott Hughes '"table Foo" is not supported', 1854*e1fe3e4aSElliott Hughes self.parse, 1855*e1fe3e4aSElliott Hughes "table Foo {LigatureCaretByPos f_i 400;} Foo;", 1856*e1fe3e4aSElliott Hughes ) 1857*e1fe3e4aSElliott Hughes 1858*e1fe3e4aSElliott Hughes def test_valuerecord_format_a_horizontal(self): 1859*e1fe3e4aSElliott Hughes doc = self.parse("feature liga {valueRecordDef 123 foo;} liga;") 1860*e1fe3e4aSElliott Hughes valuedef = doc.statements[0].statements[0] 1861*e1fe3e4aSElliott Hughes value = valuedef.value 1862*e1fe3e4aSElliott Hughes self.assertIsNone(value.xPlacement) 1863*e1fe3e4aSElliott Hughes self.assertIsNone(value.yPlacement) 1864*e1fe3e4aSElliott Hughes self.assertEqual(value.xAdvance, 123) 1865*e1fe3e4aSElliott Hughes self.assertIsNone(value.yAdvance) 1866*e1fe3e4aSElliott Hughes self.assertIsNone(value.xPlaDevice) 1867*e1fe3e4aSElliott Hughes self.assertIsNone(value.yPlaDevice) 1868*e1fe3e4aSElliott Hughes self.assertIsNone(value.xAdvDevice) 1869*e1fe3e4aSElliott Hughes self.assertIsNone(value.yAdvDevice) 1870*e1fe3e4aSElliott Hughes self.assertEqual(valuedef.asFea(), "valueRecordDef 123 foo;") 1871*e1fe3e4aSElliott Hughes self.assertEqual(value.asFea(), "123") 1872*e1fe3e4aSElliott Hughes 1873*e1fe3e4aSElliott Hughes def test_valuerecord_format_a_vertical(self): 1874*e1fe3e4aSElliott Hughes doc = self.parse("feature vkrn {valueRecordDef 123 foo;} vkrn;") 1875*e1fe3e4aSElliott Hughes valuedef = doc.statements[0].statements[0] 1876*e1fe3e4aSElliott Hughes value = valuedef.value 1877*e1fe3e4aSElliott Hughes self.assertIsNone(value.xPlacement) 1878*e1fe3e4aSElliott Hughes self.assertIsNone(value.yPlacement) 1879*e1fe3e4aSElliott Hughes self.assertIsNone(value.xAdvance) 1880*e1fe3e4aSElliott Hughes self.assertEqual(value.yAdvance, 123) 1881*e1fe3e4aSElliott Hughes self.assertIsNone(value.xPlaDevice) 1882*e1fe3e4aSElliott Hughes self.assertIsNone(value.yPlaDevice) 1883*e1fe3e4aSElliott Hughes self.assertIsNone(value.xAdvDevice) 1884*e1fe3e4aSElliott Hughes self.assertIsNone(value.yAdvDevice) 1885*e1fe3e4aSElliott Hughes self.assertEqual(valuedef.asFea(), "valueRecordDef 123 foo;") 1886*e1fe3e4aSElliott Hughes self.assertEqual(value.asFea(), "123") 1887*e1fe3e4aSElliott Hughes 1888*e1fe3e4aSElliott Hughes def test_valuerecord_format_a_zero_horizontal(self): 1889*e1fe3e4aSElliott Hughes doc = self.parse("feature liga {valueRecordDef 0 foo;} liga;") 1890*e1fe3e4aSElliott Hughes valuedef = doc.statements[0].statements[0] 1891*e1fe3e4aSElliott Hughes value = valuedef.value 1892*e1fe3e4aSElliott Hughes self.assertIsNone(value.xPlacement) 1893*e1fe3e4aSElliott Hughes self.assertIsNone(value.yPlacement) 1894*e1fe3e4aSElliott Hughes self.assertEqual(value.xAdvance, 0) 1895*e1fe3e4aSElliott Hughes self.assertIsNone(value.yAdvance) 1896*e1fe3e4aSElliott Hughes self.assertIsNone(value.xPlaDevice) 1897*e1fe3e4aSElliott Hughes self.assertIsNone(value.yPlaDevice) 1898*e1fe3e4aSElliott Hughes self.assertIsNone(value.xAdvDevice) 1899*e1fe3e4aSElliott Hughes self.assertIsNone(value.yAdvDevice) 1900*e1fe3e4aSElliott Hughes self.assertEqual(valuedef.asFea(), "valueRecordDef 0 foo;") 1901*e1fe3e4aSElliott Hughes self.assertEqual(value.asFea(), "0") 1902*e1fe3e4aSElliott Hughes 1903*e1fe3e4aSElliott Hughes def test_valuerecord_format_a_zero_vertical(self): 1904*e1fe3e4aSElliott Hughes doc = self.parse("feature vkrn {valueRecordDef 0 foo;} vkrn;") 1905*e1fe3e4aSElliott Hughes valuedef = doc.statements[0].statements[0] 1906*e1fe3e4aSElliott Hughes value = valuedef.value 1907*e1fe3e4aSElliott Hughes self.assertIsNone(value.xPlacement) 1908*e1fe3e4aSElliott Hughes self.assertIsNone(value.yPlacement) 1909*e1fe3e4aSElliott Hughes self.assertIsNone(value.xAdvance) 1910*e1fe3e4aSElliott Hughes self.assertEqual(value.yAdvance, 0) 1911*e1fe3e4aSElliott Hughes self.assertIsNone(value.xPlaDevice) 1912*e1fe3e4aSElliott Hughes self.assertIsNone(value.yPlaDevice) 1913*e1fe3e4aSElliott Hughes self.assertIsNone(value.xAdvDevice) 1914*e1fe3e4aSElliott Hughes self.assertIsNone(value.yAdvDevice) 1915*e1fe3e4aSElliott Hughes self.assertEqual(valuedef.asFea(), "valueRecordDef 0 foo;") 1916*e1fe3e4aSElliott Hughes self.assertEqual(value.asFea(), "0") 1917*e1fe3e4aSElliott Hughes 1918*e1fe3e4aSElliott Hughes def test_valuerecord_format_a_vertical_contexts_(self): 1919*e1fe3e4aSElliott Hughes for tag in "vkrn vpal vhal valt".split(): 1920*e1fe3e4aSElliott Hughes doc = self.parse("feature %s {valueRecordDef 77 foo;} %s;" % (tag, tag)) 1921*e1fe3e4aSElliott Hughes value = doc.statements[0].statements[0].value 1922*e1fe3e4aSElliott Hughes if value.yAdvance != 77: 1923*e1fe3e4aSElliott Hughes self.fail( 1924*e1fe3e4aSElliott Hughes msg="feature %s should be a vertical context " 1925*e1fe3e4aSElliott Hughes "for ValueRecord format A" % tag 1926*e1fe3e4aSElliott Hughes ) 1927*e1fe3e4aSElliott Hughes 1928*e1fe3e4aSElliott Hughes def test_valuerecord_format_b(self): 1929*e1fe3e4aSElliott Hughes doc = self.parse("feature liga {valueRecordDef <1 2 3 4> foo;} liga;") 1930*e1fe3e4aSElliott Hughes valuedef = doc.statements[0].statements[0] 1931*e1fe3e4aSElliott Hughes value = valuedef.value 1932*e1fe3e4aSElliott Hughes self.assertEqual(value.xPlacement, 1) 1933*e1fe3e4aSElliott Hughes self.assertEqual(value.yPlacement, 2) 1934*e1fe3e4aSElliott Hughes self.assertEqual(value.xAdvance, 3) 1935*e1fe3e4aSElliott Hughes self.assertEqual(value.yAdvance, 4) 1936*e1fe3e4aSElliott Hughes self.assertIsNone(value.xPlaDevice) 1937*e1fe3e4aSElliott Hughes self.assertIsNone(value.yPlaDevice) 1938*e1fe3e4aSElliott Hughes self.assertIsNone(value.xAdvDevice) 1939*e1fe3e4aSElliott Hughes self.assertIsNone(value.yAdvDevice) 1940*e1fe3e4aSElliott Hughes self.assertEqual(valuedef.asFea(), "valueRecordDef <1 2 3 4> foo;") 1941*e1fe3e4aSElliott Hughes self.assertEqual(value.asFea(), "<1 2 3 4>") 1942*e1fe3e4aSElliott Hughes 1943*e1fe3e4aSElliott Hughes def test_valuerecord_format_b_zero(self): 1944*e1fe3e4aSElliott Hughes doc = self.parse("feature liga {valueRecordDef <0 0 0 0> foo;} liga;") 1945*e1fe3e4aSElliott Hughes valuedef = doc.statements[0].statements[0] 1946*e1fe3e4aSElliott Hughes value = valuedef.value 1947*e1fe3e4aSElliott Hughes self.assertEqual(value.xPlacement, 0) 1948*e1fe3e4aSElliott Hughes self.assertEqual(value.yPlacement, 0) 1949*e1fe3e4aSElliott Hughes self.assertEqual(value.xAdvance, 0) 1950*e1fe3e4aSElliott Hughes self.assertEqual(value.yAdvance, 0) 1951*e1fe3e4aSElliott Hughes self.assertIsNone(value.xPlaDevice) 1952*e1fe3e4aSElliott Hughes self.assertIsNone(value.yPlaDevice) 1953*e1fe3e4aSElliott Hughes self.assertIsNone(value.xAdvDevice) 1954*e1fe3e4aSElliott Hughes self.assertIsNone(value.yAdvDevice) 1955*e1fe3e4aSElliott Hughes self.assertEqual(valuedef.asFea(), "valueRecordDef <0 0 0 0> foo;") 1956*e1fe3e4aSElliott Hughes self.assertEqual(value.asFea(), "<0 0 0 0>") 1957*e1fe3e4aSElliott Hughes 1958*e1fe3e4aSElliott Hughes def test_valuerecord_format_c(self): 1959*e1fe3e4aSElliott Hughes doc = self.parse( 1960*e1fe3e4aSElliott Hughes "feature liga {" 1961*e1fe3e4aSElliott Hughes " valueRecordDef <" 1962*e1fe3e4aSElliott Hughes " 1 2 3 4" 1963*e1fe3e4aSElliott Hughes " <device 8 88>" 1964*e1fe3e4aSElliott Hughes " <device 11 111, 12 112>" 1965*e1fe3e4aSElliott Hughes " <device NULL>" 1966*e1fe3e4aSElliott Hughes " <device 33 -113, 44 -114, 55 115>" 1967*e1fe3e4aSElliott Hughes " > foo;" 1968*e1fe3e4aSElliott Hughes "} liga;" 1969*e1fe3e4aSElliott Hughes ) 1970*e1fe3e4aSElliott Hughes value = doc.statements[0].statements[0].value 1971*e1fe3e4aSElliott Hughes self.assertEqual(value.xPlacement, 1) 1972*e1fe3e4aSElliott Hughes self.assertEqual(value.yPlacement, 2) 1973*e1fe3e4aSElliott Hughes self.assertEqual(value.xAdvance, 3) 1974*e1fe3e4aSElliott Hughes self.assertEqual(value.yAdvance, 4) 1975*e1fe3e4aSElliott Hughes self.assertEqual(value.xPlaDevice, ((8, 88),)) 1976*e1fe3e4aSElliott Hughes self.assertEqual(value.yPlaDevice, ((11, 111), (12, 112))) 1977*e1fe3e4aSElliott Hughes self.assertIsNone(value.xAdvDevice) 1978*e1fe3e4aSElliott Hughes self.assertEqual(value.yAdvDevice, ((33, -113), (44, -114), (55, 115))) 1979*e1fe3e4aSElliott Hughes self.assertEqual( 1980*e1fe3e4aSElliott Hughes value.asFea(), 1981*e1fe3e4aSElliott Hughes "<1 2 3 4 <device 8 88> <device 11 111, 12 112>" 1982*e1fe3e4aSElliott Hughes " <device NULL> <device 33 -113, 44 -114, 55 115>>", 1983*e1fe3e4aSElliott Hughes ) 1984*e1fe3e4aSElliott Hughes 1985*e1fe3e4aSElliott Hughes def test_valuerecord_format_d(self): 1986*e1fe3e4aSElliott Hughes doc = self.parse("feature test {valueRecordDef <NULL> foo;} test;") 1987*e1fe3e4aSElliott Hughes value = doc.statements[0].statements[0].value 1988*e1fe3e4aSElliott Hughes self.assertFalse(value) 1989*e1fe3e4aSElliott Hughes self.assertEqual(value.asFea(), "<NULL>") 1990*e1fe3e4aSElliott Hughes 1991*e1fe3e4aSElliott Hughes def test_valuerecord_variable_scalar(self): 1992*e1fe3e4aSElliott Hughes doc = self.parse( 1993*e1fe3e4aSElliott Hughes "feature test {valueRecordDef <0 (wght=200:-100 wght=900:-150 wdth=150,wght=900:-120) 0 0> foo;} test;" 1994*e1fe3e4aSElliott Hughes ) 1995*e1fe3e4aSElliott Hughes value = doc.statements[0].statements[0].value 1996*e1fe3e4aSElliott Hughes self.assertEqual( 1997*e1fe3e4aSElliott Hughes value.asFea(), 1998*e1fe3e4aSElliott Hughes "<0 (wght=200:-100 wght=900:-150 wdth=150,wght=900:-120) 0 0>", 1999*e1fe3e4aSElliott Hughes ) 2000*e1fe3e4aSElliott Hughes 2001*e1fe3e4aSElliott Hughes def test_valuerecord_named(self): 2002*e1fe3e4aSElliott Hughes doc = self.parse( 2003*e1fe3e4aSElliott Hughes "valueRecordDef <1 2 3 4> foo;" 2004*e1fe3e4aSElliott Hughes "feature liga {valueRecordDef <foo> bar;} liga;" 2005*e1fe3e4aSElliott Hughes ) 2006*e1fe3e4aSElliott Hughes value = doc.statements[1].statements[0].value 2007*e1fe3e4aSElliott Hughes self.assertEqual(value.xPlacement, 1) 2008*e1fe3e4aSElliott Hughes self.assertEqual(value.yPlacement, 2) 2009*e1fe3e4aSElliott Hughes self.assertEqual(value.xAdvance, 3) 2010*e1fe3e4aSElliott Hughes self.assertEqual(value.yAdvance, 4) 2011*e1fe3e4aSElliott Hughes 2012*e1fe3e4aSElliott Hughes def test_valuerecord_named_unknown(self): 2013*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 2014*e1fe3e4aSElliott Hughes FeatureLibError, 2015*e1fe3e4aSElliott Hughes 'Unknown valueRecordDef "unknown"', 2016*e1fe3e4aSElliott Hughes self.parse, 2017*e1fe3e4aSElliott Hughes "valueRecordDef <unknown> foo;", 2018*e1fe3e4aSElliott Hughes ) 2019*e1fe3e4aSElliott Hughes 2020*e1fe3e4aSElliott Hughes def test_valuerecord_scoping(self): 2021*e1fe3e4aSElliott Hughes [foo, liga, smcp] = self.parse( 2022*e1fe3e4aSElliott Hughes "valueRecordDef 789 foo;" 2023*e1fe3e4aSElliott Hughes "feature liga {valueRecordDef <foo> bar;} liga;" 2024*e1fe3e4aSElliott Hughes "feature smcp {valueRecordDef <foo> bar;} smcp;" 2025*e1fe3e4aSElliott Hughes ).statements 2026*e1fe3e4aSElliott Hughes self.assertEqual(foo.value.xAdvance, 789) 2027*e1fe3e4aSElliott Hughes self.assertEqual(liga.statements[0].value.xAdvance, 789) 2028*e1fe3e4aSElliott Hughes self.assertEqual(smcp.statements[0].value.xAdvance, 789) 2029*e1fe3e4aSElliott Hughes 2030*e1fe3e4aSElliott Hughes def test_valuerecord_device_value_out_of_range(self): 2031*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 2032*e1fe3e4aSElliott Hughes FeatureLibError, 2033*e1fe3e4aSElliott Hughes r"Device value out of valid range \(-128..127\)", 2034*e1fe3e4aSElliott Hughes self.parse, 2035*e1fe3e4aSElliott Hughes "valueRecordDef <1 2 3 4 <device NULL> <device NULL> " 2036*e1fe3e4aSElliott Hughes "<device NULL> <device 11 128>> foo;", 2037*e1fe3e4aSElliott Hughes ) 2038*e1fe3e4aSElliott Hughes 2039*e1fe3e4aSElliott Hughes def test_conditionset(self): 2040*e1fe3e4aSElliott Hughes doc = self.parse("conditionset heavy { wght 700 900; } heavy;") 2041*e1fe3e4aSElliott Hughes value = doc.statements[0] 2042*e1fe3e4aSElliott Hughes self.assertEqual(value.conditions["wght"], (700, 900)) 2043*e1fe3e4aSElliott Hughes self.assertEqual( 2044*e1fe3e4aSElliott Hughes value.asFea(), "conditionset heavy {\n wght 700 900;\n} heavy;\n" 2045*e1fe3e4aSElliott Hughes ) 2046*e1fe3e4aSElliott Hughes 2047*e1fe3e4aSElliott Hughes doc = self.parse("conditionset heavy { wght 700 900; opsz 17 18;} heavy;") 2048*e1fe3e4aSElliott Hughes value = doc.statements[0] 2049*e1fe3e4aSElliott Hughes self.assertEqual(value.conditions["wght"], (700, 900)) 2050*e1fe3e4aSElliott Hughes self.assertEqual(value.conditions["opsz"], (17, 18)) 2051*e1fe3e4aSElliott Hughes self.assertEqual( 2052*e1fe3e4aSElliott Hughes value.asFea(), 2053*e1fe3e4aSElliott Hughes "conditionset heavy {\n wght 700 900;\n opsz 17 18;\n} heavy;\n", 2054*e1fe3e4aSElliott Hughes ) 2055*e1fe3e4aSElliott Hughes 2056*e1fe3e4aSElliott Hughes def test_conditionset_same_axis(self): 2057*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 2058*e1fe3e4aSElliott Hughes FeatureLibError, 2059*e1fe3e4aSElliott Hughes r"Repeated condition for axis wght", 2060*e1fe3e4aSElliott Hughes self.parse, 2061*e1fe3e4aSElliott Hughes "conditionset heavy { wght 700 900; wght 100 200; } heavy;", 2062*e1fe3e4aSElliott Hughes ) 2063*e1fe3e4aSElliott Hughes 2064*e1fe3e4aSElliott Hughes def test_conditionset_float(self): 2065*e1fe3e4aSElliott Hughes doc = self.parse("conditionset heavy { wght 700.0 900.0; } heavy;") 2066*e1fe3e4aSElliott Hughes value = doc.statements[0] 2067*e1fe3e4aSElliott Hughes self.assertEqual(value.conditions["wght"], (700.0, 900.0)) 2068*e1fe3e4aSElliott Hughes self.assertEqual( 2069*e1fe3e4aSElliott Hughes value.asFea(), "conditionset heavy {\n wght 700.0 900.0;\n} heavy;\n" 2070*e1fe3e4aSElliott Hughes ) 2071*e1fe3e4aSElliott Hughes 2072*e1fe3e4aSElliott Hughes def test_variation(self): 2073*e1fe3e4aSElliott Hughes doc = self.parse("variation rvrn heavy { sub a by b; } rvrn;") 2074*e1fe3e4aSElliott Hughes value = doc.statements[0] 2075*e1fe3e4aSElliott Hughes 2076*e1fe3e4aSElliott Hughes def test_languagesystem(self): 2077*e1fe3e4aSElliott Hughes [langsys] = self.parse("languagesystem latn DEU;").statements 2078*e1fe3e4aSElliott Hughes self.assertEqual(langsys.script, "latn") 2079*e1fe3e4aSElliott Hughes self.assertEqual(langsys.language, "DEU ") 2080*e1fe3e4aSElliott Hughes [langsys] = self.parse("languagesystem DFLT DEU;").statements 2081*e1fe3e4aSElliott Hughes self.assertEqual(langsys.script, "DFLT") 2082*e1fe3e4aSElliott Hughes self.assertEqual(langsys.language, "DEU ") 2083*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 2084*e1fe3e4aSElliott Hughes FeatureLibError, 2085*e1fe3e4aSElliott Hughes '"dflt" is not a valid script tag; use "DFLT" instead', 2086*e1fe3e4aSElliott Hughes self.parse, 2087*e1fe3e4aSElliott Hughes "languagesystem dflt dflt;", 2088*e1fe3e4aSElliott Hughes ) 2089*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 2090*e1fe3e4aSElliott Hughes FeatureLibError, 2091*e1fe3e4aSElliott Hughes '"DFLT" is not a valid language tag; use "dflt" instead', 2092*e1fe3e4aSElliott Hughes self.parse, 2093*e1fe3e4aSElliott Hughes "languagesystem latn DFLT;", 2094*e1fe3e4aSElliott Hughes ) 2095*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 2096*e1fe3e4aSElliott Hughes FeatureLibError, "Expected ';'", self.parse, "languagesystem latn DEU" 2097*e1fe3e4aSElliott Hughes ) 2098*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 2099*e1fe3e4aSElliott Hughes FeatureLibError, 2100*e1fe3e4aSElliott Hughes "longer than 4 characters", 2101*e1fe3e4aSElliott Hughes self.parse, 2102*e1fe3e4aSElliott Hughes "languagesystem foobar DEU;", 2103*e1fe3e4aSElliott Hughes ) 2104*e1fe3e4aSElliott Hughes self.assertRaisesRegex( 2105*e1fe3e4aSElliott Hughes FeatureLibError, 2106*e1fe3e4aSElliott Hughes "longer than 4 characters", 2107*e1fe3e4aSElliott Hughes self.parse, 2108*e1fe3e4aSElliott Hughes "languagesystem latn FOOBAR;", 2109*e1fe3e4aSElliott Hughes ) 2110*e1fe3e4aSElliott Hughes 2111*e1fe3e4aSElliott Hughes def test_empty_statement_ignored(self): 2112*e1fe3e4aSElliott Hughes doc = self.parse("feature test {;} test;") 2113*e1fe3e4aSElliott Hughes self.assertFalse(doc.statements[0].statements) 2114*e1fe3e4aSElliott Hughes doc = self.parse(";;;") 2115*e1fe3e4aSElliott Hughes self.assertFalse(doc.statements) 2116*e1fe3e4aSElliott Hughes for table in "BASE GDEF OS/2 head hhea name vhea".split(): 2117*e1fe3e4aSElliott Hughes doc = self.parse("table %s { ;;; } %s;" % (table, table)) 2118*e1fe3e4aSElliott Hughes self.assertEqual(doc.statements[0].statements, []) 2119*e1fe3e4aSElliott Hughes 2120*e1fe3e4aSElliott Hughes def test_ufo_features_parse_include_dir(self): 2121*e1fe3e4aSElliott Hughes fea_path = self.getpath("include/test.ufo/features.fea") 2122*e1fe3e4aSElliott Hughes include_dir = os.path.dirname(os.path.dirname(fea_path)) 2123*e1fe3e4aSElliott Hughes doc = Parser(fea_path, includeDir=include_dir).parse() 2124*e1fe3e4aSElliott Hughes assert len(doc.statements) == 1 and doc.statements[0].text == "# Nothing" 2125*e1fe3e4aSElliott Hughes 2126*e1fe3e4aSElliott Hughes def test_unmarked_ignore_statement(self): 2127*e1fe3e4aSElliott Hughes with CapturingLogHandler("fontTools.feaLib.parser", level="WARNING") as caplog: 2128*e1fe3e4aSElliott Hughes doc = self.parse("lookup foo { ignore sub A; } foo;") 2129*e1fe3e4aSElliott Hughes self.assertEqual(doc.statements[0].statements[0].asFea(), "ignore sub A';") 2130*e1fe3e4aSElliott Hughes self.assertEqual(len(caplog.records), 1) 2131*e1fe3e4aSElliott Hughes caplog.assertRegex( 2132*e1fe3e4aSElliott Hughes 'Ambiguous "ignore sub", there should be least one marked glyph' 2133*e1fe3e4aSElliott Hughes ) 2134*e1fe3e4aSElliott Hughes 2135*e1fe3e4aSElliott Hughes def parse(self, text, glyphNames=GLYPHNAMES, followIncludes=True): 2136*e1fe3e4aSElliott Hughes featurefile = StringIO(text) 2137*e1fe3e4aSElliott Hughes p = Parser(featurefile, glyphNames, followIncludes=followIncludes) 2138*e1fe3e4aSElliott Hughes return p.parse() 2139*e1fe3e4aSElliott Hughes 2140*e1fe3e4aSElliott Hughes @staticmethod 2141*e1fe3e4aSElliott Hughes def getpath(testfile): 2142*e1fe3e4aSElliott Hughes path, _ = os.path.split(__file__) 2143*e1fe3e4aSElliott Hughes return os.path.join(path, "data", testfile) 2144*e1fe3e4aSElliott Hughes 2145*e1fe3e4aSElliott Hughes 2146*e1fe3e4aSElliott Hughesclass SymbolTableTest(unittest.TestCase): 2147*e1fe3e4aSElliott Hughes def test_scopes(self): 2148*e1fe3e4aSElliott Hughes symtab = SymbolTable() 2149*e1fe3e4aSElliott Hughes symtab.define("foo", 23) 2150*e1fe3e4aSElliott Hughes self.assertEqual(symtab.resolve("foo"), 23) 2151*e1fe3e4aSElliott Hughes symtab.enter_scope() 2152*e1fe3e4aSElliott Hughes self.assertEqual(symtab.resolve("foo"), 23) 2153*e1fe3e4aSElliott Hughes symtab.define("foo", 42) 2154*e1fe3e4aSElliott Hughes self.assertEqual(symtab.resolve("foo"), 42) 2155*e1fe3e4aSElliott Hughes symtab.exit_scope() 2156*e1fe3e4aSElliott Hughes self.assertEqual(symtab.resolve("foo"), 23) 2157*e1fe3e4aSElliott Hughes 2158*e1fe3e4aSElliott Hughes def test_resolve_undefined(self): 2159*e1fe3e4aSElliott Hughes self.assertEqual(SymbolTable().resolve("abc"), None) 2160*e1fe3e4aSElliott Hughes 2161*e1fe3e4aSElliott Hughes 2162*e1fe3e4aSElliott Hughesif __name__ == "__main__": 2163*e1fe3e4aSElliott Hughes import sys 2164*e1fe3e4aSElliott Hughes 2165*e1fe3e4aSElliott Hughes sys.exit(unittest.main()) 2166