2015-08-01 12:35:22 +02:00
|
|
|
from __future__ import print_function, division, absolute_import
|
|
|
|
from __future__ import unicode_literals
|
2015-08-21 17:09:46 +02:00
|
|
|
from fontTools.feaLib.error import FeatureLibError
|
|
|
|
from fontTools.feaLib.parser import Parser, SymbolTable
|
2015-08-01 12:35:22 +02:00
|
|
|
from fontTools.misc.py23 import *
|
2015-08-05 10:41:04 +02:00
|
|
|
import fontTools.feaLib.ast as ast
|
2015-08-01 12:35:22 +02:00
|
|
|
import codecs
|
|
|
|
import os
|
|
|
|
import shutil
|
|
|
|
import sys
|
|
|
|
import tempfile
|
|
|
|
import unittest
|
|
|
|
|
|
|
|
|
|
|
|
class ParserTest(unittest.TestCase):
|
2015-08-01 14:33:06 +02:00
|
|
|
def __init__(self, methodName):
|
|
|
|
unittest.TestCase.__init__(self, methodName)
|
|
|
|
# Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
|
|
|
|
# and fires deprecation warnings if a program uses the old name.
|
|
|
|
if not hasattr(self, "assertRaisesRegex"):
|
|
|
|
self.assertRaisesRegex = self.assertRaisesRegexp
|
|
|
|
|
2015-08-11 12:53:30 +02:00
|
|
|
def test_anchordef(self):
|
|
|
|
[foo] = self.parse("anchorDef 123 456 foo;").statements
|
|
|
|
self.assertEqual(type(foo), ast.AnchorDefinition)
|
|
|
|
self.assertEqual(foo.name, "foo")
|
|
|
|
self.assertEqual(foo.x, 123)
|
|
|
|
self.assertEqual(foo.y, 456)
|
|
|
|
self.assertEqual(foo.contourpoint, None)
|
|
|
|
|
|
|
|
def test_anchordef_contourpoint(self):
|
|
|
|
[foo] = self.parse("anchorDef 123 456 contourpoint 5 foo;").statements
|
|
|
|
self.assertEqual(type(foo), ast.AnchorDefinition)
|
|
|
|
self.assertEqual(foo.name, "foo")
|
|
|
|
self.assertEqual(foo.x, 123)
|
|
|
|
self.assertEqual(foo.y, 456)
|
|
|
|
self.assertEqual(foo.contourpoint, 5)
|
|
|
|
|
2015-08-11 15:28:59 +02:00
|
|
|
def test_feature_block(self):
|
|
|
|
[liga] = self.parse("feature liga {} liga;").statements
|
|
|
|
self.assertEqual(liga.name, "liga")
|
|
|
|
self.assertFalse(liga.use_extension)
|
|
|
|
|
|
|
|
def test_feature_block_useExtension(self):
|
|
|
|
[liga] = self.parse("feature liga useExtension {} liga;").statements
|
|
|
|
self.assertEqual(liga.name, "liga")
|
|
|
|
self.assertTrue(liga.use_extension)
|
|
|
|
|
2015-08-01 17:34:02 +02:00
|
|
|
def test_glyphclass(self):
|
|
|
|
[gc] = self.parse("@dash = [endash emdash figuredash];").statements
|
|
|
|
self.assertEqual(gc.name, "dash")
|
|
|
|
self.assertEqual(gc.glyphs, {"endash", "emdash", "figuredash"})
|
|
|
|
|
2015-08-01 19:25:48 +02:00
|
|
|
def test_glyphclass_bad(self):
|
|
|
|
self.assertRaisesRegex(
|
2015-08-21 17:09:46 +02:00
|
|
|
FeatureLibError,
|
2015-08-03 09:47:06 +02:00
|
|
|
"Expected glyph name, glyph range, or glyph class reference",
|
2015-08-01 19:25:48 +02:00
|
|
|
self.parse, "@bad = [a 123];")
|
|
|
|
|
2015-08-01 19:01:24 +02:00
|
|
|
def test_glyphclass_duplicate(self):
|
|
|
|
self.assertRaisesRegex(
|
2015-08-21 17:09:46 +02:00
|
|
|
FeatureLibError, "Glyph class @dup already defined",
|
2015-08-01 19:01:24 +02:00
|
|
|
self.parse, "@dup = [a b]; @dup = [x];")
|
|
|
|
|
2015-08-01 19:25:48 +02:00
|
|
|
def test_glyphclass_empty(self):
|
|
|
|
[gc] = self.parse("@empty_set = [];").statements
|
|
|
|
self.assertEqual(gc.name, "empty_set")
|
|
|
|
self.assertEqual(gc.glyphs, set())
|
|
|
|
|
|
|
|
def test_glyphclass_equality(self):
|
|
|
|
[foo, bar] = self.parse("@foo = [a b]; @bar = @foo;").statements
|
|
|
|
self.assertEqual(foo.glyphs, {"a", "b"})
|
|
|
|
self.assertEqual(bar.glyphs, {"a", "b"})
|
|
|
|
|
2015-08-01 17:34:02 +02:00
|
|
|
def test_glyphclass_range_uppercase(self):
|
|
|
|
[gc] = self.parse("@swashes = [X.swash-Z.swash];").statements
|
|
|
|
self.assertEqual(gc.name, "swashes")
|
|
|
|
self.assertEqual(gc.glyphs, {"X.swash", "Y.swash", "Z.swash"})
|
|
|
|
|
|
|
|
def test_glyphclass_range_lowercase(self):
|
|
|
|
[gc] = self.parse("@defg.sc = [d.sc-g.sc];").statements
|
|
|
|
self.assertEqual(gc.name, "defg.sc")
|
|
|
|
self.assertEqual(gc.glyphs, {"d.sc", "e.sc", "f.sc", "g.sc"})
|
|
|
|
|
|
|
|
def test_glyphclass_range_digit1(self):
|
|
|
|
[gc] = self.parse("@range = [foo.2-foo.5];").statements
|
|
|
|
self.assertEqual(gc.glyphs, {"foo.2", "foo.3", "foo.4", "foo.5"})
|
|
|
|
|
|
|
|
def test_glyphclass_range_digit2(self):
|
|
|
|
[gc] = self.parse("@range = [foo.09-foo.11];").statements
|
|
|
|
self.assertEqual(gc.glyphs, {"foo.09", "foo.10", "foo.11"})
|
|
|
|
|
|
|
|
def test_glyphclass_range_digit3(self):
|
|
|
|
[gc] = self.parse("@range = [foo.123-foo.125];").statements
|
|
|
|
self.assertEqual(gc.glyphs, {"foo.123", "foo.124", "foo.125"})
|
|
|
|
|
|
|
|
def test_glyphclass_range_bad(self):
|
|
|
|
self.assertRaisesRegex(
|
2015-08-21 17:09:46 +02:00
|
|
|
FeatureLibError,
|
2015-08-01 17:34:02 +02:00
|
|
|
"Bad range: \"a\" and \"foobar\" should have the same length",
|
|
|
|
self.parse, "@bad = [a-foobar];")
|
|
|
|
self.assertRaisesRegex(
|
2015-08-21 17:09:46 +02:00
|
|
|
FeatureLibError, "Bad range: \"A.swash-z.swash\"",
|
2015-08-01 17:34:02 +02:00
|
|
|
self.parse, "@bad = [A.swash-z.swash];")
|
|
|
|
self.assertRaisesRegex(
|
2015-08-21 17:09:46 +02:00
|
|
|
FeatureLibError, "Start of range must be smaller than its end",
|
2015-08-01 17:34:02 +02:00
|
|
|
self.parse, "@bad = [B.swash-A.swash];")
|
|
|
|
self.assertRaisesRegex(
|
2015-08-21 17:09:46 +02:00
|
|
|
FeatureLibError, "Bad range: \"foo.1234-foo.9876\"",
|
2015-08-01 17:34:02 +02:00
|
|
|
self.parse, "@bad = [foo.1234-foo.9876];")
|
|
|
|
|
|
|
|
def test_glyphclass_range_mixed(self):
|
|
|
|
[gc] = self.parse("@range = [a foo.09-foo.11 X.sc-Z.sc];").statements
|
|
|
|
self.assertEqual(gc.glyphs, {
|
|
|
|
"a", "foo.09", "foo.10", "foo.11", "X.sc", "Y.sc", "Z.sc"
|
|
|
|
})
|
|
|
|
|
2015-08-01 19:25:48 +02:00
|
|
|
def test_glyphclass_reference(self):
|
|
|
|
[vowels_lc, vowels_uc, vowels] = self.parse(
|
2015-08-01 19:58:54 +02:00
|
|
|
"@Vowels.lc = [a e i o u]; @Vowels.uc = [A E I O U];"
|
2015-08-01 19:25:48 +02:00
|
|
|
"@Vowels = [@Vowels.lc @Vowels.uc y Y];").statements
|
|
|
|
self.assertEqual(vowels_lc.glyphs, set(list("aeiou")))
|
|
|
|
self.assertEqual(vowels_uc.glyphs, set(list("AEIOU")))
|
|
|
|
self.assertEqual(vowels.glyphs, set(list("aeiouyAEIOUY")))
|
|
|
|
self.assertRaisesRegex(
|
2015-08-21 17:09:46 +02:00
|
|
|
FeatureLibError, "Unknown glyph class @unknown",
|
2015-08-01 19:25:48 +02:00
|
|
|
self.parse, "@bad = [@unknown];")
|
2015-08-01 17:34:02 +02:00
|
|
|
|
2015-08-01 19:58:54 +02:00
|
|
|
def test_glyphclass_scoping(self):
|
|
|
|
[foo, liga, smcp] = self.parse(
|
|
|
|
"@foo = [a b];"
|
|
|
|
"feature liga { @bar = [@foo l]; } liga;"
|
|
|
|
"feature smcp { @bar = [@foo s]; } smcp;"
|
|
|
|
).statements
|
|
|
|
self.assertEqual(foo.glyphs, {"a", "b"})
|
|
|
|
self.assertEqual(liga.statements[0].glyphs, {"a", "b", "l"})
|
|
|
|
self.assertEqual(smcp.statements[0].glyphs, {"a", "b", "s"})
|
|
|
|
|
2015-08-05 10:41:04 +02:00
|
|
|
def test_ignore_sub(self):
|
|
|
|
doc = self.parse("feature test {ignore sub e t' c;} test;")
|
|
|
|
s = doc.statements[0].statements[0]
|
|
|
|
self.assertEqual(type(s), ast.IgnoreSubstitutionRule)
|
|
|
|
self.assertEqual(s.prefix, [{"e"}])
|
|
|
|
self.assertEqual(s.glyphs, [{"t"}])
|
|
|
|
self.assertEqual(s.suffix, [{"c"}])
|
|
|
|
|
|
|
|
def test_ignore_substitute(self):
|
|
|
|
doc = self.parse(
|
|
|
|
"feature test {"
|
|
|
|
" ignore substitute f [a e] d' [a u]' [e y];"
|
|
|
|
"} test;")
|
|
|
|
s = doc.statements[0].statements[0]
|
|
|
|
self.assertEqual(type(s), ast.IgnoreSubstitutionRule)
|
|
|
|
self.assertEqual(s.prefix, [{"f"}, {"a", "e"}])
|
|
|
|
self.assertEqual(s.glyphs, [{"d"}, {"a", "u"}])
|
|
|
|
self.assertEqual(s.suffix, [{"e", "y"}])
|
|
|
|
|
2015-08-10 16:30:10 +02:00
|
|
|
def test_language(self):
|
|
|
|
doc = self.parse("feature test {language DEU;} test;")
|
|
|
|
s = doc.statements[0].statements[0]
|
|
|
|
self.assertEqual(type(s), ast.LanguageStatement)
|
2015-09-07 22:05:10 +02:00
|
|
|
self.assertEqual(s.language, "DEU ")
|
2015-08-10 16:30:10 +02:00
|
|
|
self.assertTrue(s.include_default)
|
|
|
|
self.assertFalse(s.required)
|
|
|
|
|
|
|
|
def test_language_exclude_dflt(self):
|
|
|
|
doc = self.parse("feature test {language DEU exclude_dflt;} test;")
|
|
|
|
s = doc.statements[0].statements[0]
|
|
|
|
self.assertEqual(type(s), ast.LanguageStatement)
|
2015-09-07 22:05:10 +02:00
|
|
|
self.assertEqual(s.language, "DEU ")
|
2015-08-10 16:30:10 +02:00
|
|
|
self.assertFalse(s.include_default)
|
|
|
|
self.assertFalse(s.required)
|
|
|
|
|
|
|
|
def test_language_exclude_dflt_required(self):
|
|
|
|
doc = self.parse("feature test {"
|
|
|
|
" language DEU exclude_dflt required;"
|
|
|
|
"} test;")
|
|
|
|
s = doc.statements[0].statements[0]
|
|
|
|
self.assertEqual(type(s), ast.LanguageStatement)
|
2015-09-07 22:05:10 +02:00
|
|
|
self.assertEqual(s.language, "DEU ")
|
2015-08-10 16:30:10 +02:00
|
|
|
self.assertFalse(s.include_default)
|
|
|
|
self.assertTrue(s.required)
|
|
|
|
|
|
|
|
def test_language_include_dflt(self):
|
|
|
|
doc = self.parse("feature test {language DEU include_dflt;} test;")
|
|
|
|
s = doc.statements[0].statements[0]
|
|
|
|
self.assertEqual(type(s), ast.LanguageStatement)
|
2015-09-07 22:05:10 +02:00
|
|
|
self.assertEqual(s.language, "DEU ")
|
2015-08-10 16:30:10 +02:00
|
|
|
self.assertTrue(s.include_default)
|
|
|
|
self.assertFalse(s.required)
|
|
|
|
|
|
|
|
def test_language_include_dflt_required(self):
|
|
|
|
doc = self.parse("feature test {"
|
|
|
|
" language DEU include_dflt required;"
|
|
|
|
"} test;")
|
|
|
|
s = doc.statements[0].statements[0]
|
|
|
|
self.assertEqual(type(s), ast.LanguageStatement)
|
2015-09-07 22:05:10 +02:00
|
|
|
self.assertEqual(s.language, "DEU ")
|
2015-08-10 16:30:10 +02:00
|
|
|
self.assertTrue(s.include_default)
|
|
|
|
self.assertTrue(s.required)
|
|
|
|
|
2015-09-08 09:26:24 +02:00
|
|
|
def test_language_DFLT(self):
|
|
|
|
self.assertRaisesRegex(
|
|
|
|
FeatureLibError,
|
|
|
|
'"DFLT" is not a valid language tag; use "dflt" instead',
|
|
|
|
self.parse, "feature test { language DFLT; } test;")
|
|
|
|
|
2015-08-11 10:59:26 +02:00
|
|
|
def test_lookup_block(self):
|
|
|
|
[lookup] = self.parse("lookup Ligatures {} Ligatures;").statements
|
|
|
|
self.assertEqual(lookup.name, "Ligatures")
|
|
|
|
self.assertFalse(lookup.use_extension)
|
|
|
|
|
|
|
|
def test_lookup_block_useExtension(self):
|
|
|
|
[lookup] = self.parse("lookup Foo useExtension {} Foo;").statements
|
|
|
|
self.assertEqual(lookup.name, "Foo")
|
|
|
|
self.assertTrue(lookup.use_extension)
|
|
|
|
|
|
|
|
def test_lookup_block_name_mismatch(self):
|
|
|
|
self.assertRaisesRegex(
|
2015-08-21 17:09:46 +02:00
|
|
|
FeatureLibError, 'Expected "Foo"',
|
2015-08-11 10:59:26 +02:00
|
|
|
self.parse, "lookup Foo {} Bar;")
|
|
|
|
|
|
|
|
def test_lookup_block_with_horizontal_valueRecordDef(self):
|
|
|
|
doc = self.parse("feature liga {"
|
|
|
|
" lookup look {"
|
|
|
|
" valueRecordDef 123 foo;"
|
|
|
|
" } look;"
|
|
|
|
"} liga;")
|
|
|
|
[liga] = doc.statements
|
|
|
|
[look] = liga.statements
|
|
|
|
[foo] = look.statements
|
|
|
|
self.assertEqual(foo.value.xAdvance, 123)
|
|
|
|
self.assertEqual(foo.value.yAdvance, 0)
|
|
|
|
|
|
|
|
def test_lookup_block_with_vertical_valueRecordDef(self):
|
|
|
|
doc = self.parse("feature vkrn {"
|
|
|
|
" lookup look {"
|
|
|
|
" valueRecordDef 123 foo;"
|
|
|
|
" } look;"
|
|
|
|
"} vkrn;")
|
|
|
|
[vkrn] = doc.statements
|
|
|
|
[look] = vkrn.statements
|
|
|
|
[foo] = look.statements
|
|
|
|
self.assertEqual(foo.value.xAdvance, 0)
|
|
|
|
self.assertEqual(foo.value.yAdvance, 123)
|
|
|
|
|
|
|
|
def test_lookup_reference(self):
|
|
|
|
[foo, bar] = self.parse("lookup Foo {} Foo;"
|
|
|
|
"feature Bar {lookup Foo;} Bar;").statements
|
|
|
|
[ref] = bar.statements
|
|
|
|
self.assertEqual(type(ref), ast.LookupReferenceStatement)
|
|
|
|
self.assertEqual(ref.lookup, foo)
|
|
|
|
|
|
|
|
def test_lookup_reference_unknown(self):
|
|
|
|
self.assertRaisesRegex(
|
2015-08-21 17:09:46 +02:00
|
|
|
FeatureLibError, 'Unknown lookup "Huh"',
|
2015-08-11 10:59:26 +02:00
|
|
|
self.parse, "feature liga {lookup Huh;} liga;")
|
|
|
|
|
2015-08-10 11:30:47 +02:00
|
|
|
def test_script(self):
|
|
|
|
doc = self.parse("feature test {script cyrl;} test;")
|
|
|
|
s = doc.statements[0].statements[0]
|
|
|
|
self.assertEqual(type(s), ast.ScriptStatement)
|
|
|
|
self.assertEqual(s.script, "cyrl")
|
|
|
|
|
2015-09-08 09:26:24 +02:00
|
|
|
def test_script_dflt(self):
|
|
|
|
self.assertRaisesRegex(
|
|
|
|
FeatureLibError,
|
|
|
|
'"dflt" is not a valid script tag; use "DFLT" instead',
|
|
|
|
self.parse, "feature test {script dflt;} test;")
|
|
|
|
|
2015-08-04 19:55:55 +02:00
|
|
|
def test_substitute_single_format_a(self): # GSUB LookupType 1
|
|
|
|
doc = self.parse("feature smcp {substitute a by a.sc;} smcp;")
|
|
|
|
sub = doc.statements[0].statements[0]
|
2015-09-08 10:33:07 +02:00
|
|
|
self.assertEqual(type(sub), ast.SingleSubstitution)
|
|
|
|
self.assertEqual(sub.mapping, {"a": "a.sc"})
|
2015-08-04 19:55:55 +02:00
|
|
|
|
|
|
|
def test_substitute_single_format_b(self): # GSUB LookupType 1
|
|
|
|
doc = self.parse(
|
|
|
|
"feature smcp {"
|
|
|
|
" substitute [one.fitted one.oldstyle] by one;"
|
|
|
|
"} smcp;")
|
|
|
|
sub = doc.statements[0].statements[0]
|
2015-09-08 10:33:07 +02:00
|
|
|
self.assertEqual(type(sub), ast.SingleSubstitution)
|
|
|
|
self.assertEqual(sub.mapping, {
|
|
|
|
"one.fitted": "one",
|
|
|
|
"one.oldstyle": "one"
|
|
|
|
})
|
2015-08-04 19:55:55 +02:00
|
|
|
|
|
|
|
def test_substitute_single_format_c(self): # GSUB LookupType 1
|
|
|
|
doc = self.parse(
|
|
|
|
"feature smcp {"
|
|
|
|
" substitute [a-d] by [A.sc-D.sc];"
|
|
|
|
"} smcp;")
|
|
|
|
sub = doc.statements[0].statements[0]
|
2015-09-08 10:33:07 +02:00
|
|
|
self.assertEqual(type(sub), ast.SingleSubstitution)
|
|
|
|
self.assertEqual(sub.mapping, {
|
|
|
|
"a": "A.sc",
|
|
|
|
"b": "B.sc",
|
|
|
|
"c": "C.sc",
|
|
|
|
"d": "D.sc"
|
|
|
|
})
|
|
|
|
|
|
|
|
def test_substitute_single_format_c_different_num_elements(self):
|
|
|
|
self.assertRaisesRegex(
|
|
|
|
FeatureLibError,
|
|
|
|
'Expected a glyph class with 4 elements after "by", '
|
|
|
|
'but found a glyph class with 26 elements',
|
|
|
|
self.parse, "feature smcp {sub [a-d] by [A.sc-Z.sc];} smcp;")
|
2015-08-04 19:55:55 +02:00
|
|
|
|
2015-08-11 16:45:54 +02:00
|
|
|
def test_substitute_multiple(self): # GSUB LookupType 2
|
|
|
|
doc = self.parse("lookup Look {substitute f_f_i by f f i;} Look;")
|
|
|
|
sub = doc.statements[0].statements[0]
|
2015-09-08 12:05:44 +02:00
|
|
|
self.assertEqual(type(sub), ast.MultipleSubstitution)
|
|
|
|
self.assertEqual(sub.glyph, "f_f_i")
|
|
|
|
self.assertEqual(sub.replacement, ("f", "f", "i"))
|
2015-08-11 16:45:54 +02:00
|
|
|
|
2015-08-11 15:54:54 +02:00
|
|
|
def test_substitute_from(self): # GSUB LookupType 3
|
|
|
|
doc = self.parse("feature test {"
|
|
|
|
" substitute a from [a.1 a.2 a.3];"
|
|
|
|
"} test;")
|
|
|
|
sub = doc.statements[0].statements[0]
|
|
|
|
self.assertEqual(type(sub), ast.AlternateSubstitution)
|
|
|
|
self.assertEqual(sub.glyph, "a")
|
|
|
|
self.assertEqual(sub.from_class, {"a.1", "a.2", "a.3"})
|
|
|
|
|
|
|
|
def test_substitute_from_glyphclass(self): # GSUB LookupType 3
|
|
|
|
doc = self.parse("feature test {"
|
|
|
|
" @Ampersands = [ampersand.1 ampersand.2];"
|
|
|
|
" substitute ampersand from @Ampersands;"
|
|
|
|
"} test;")
|
|
|
|
[glyphclass, sub] = doc.statements[0].statements
|
|
|
|
self.assertEqual(type(sub), ast.AlternateSubstitution)
|
|
|
|
self.assertEqual(sub.glyph, "ampersand")
|
|
|
|
self.assertEqual(sub.from_class, {"ampersand.1", "ampersand.2"})
|
|
|
|
|
2015-08-04 19:55:55 +02:00
|
|
|
def test_substitute_ligature(self): # GSUB LookupType 4
|
|
|
|
doc = self.parse("feature liga {substitute f f i by f_f_i;} liga;")
|
|
|
|
sub = doc.statements[0].statements[0]
|
2015-09-07 16:10:13 +02:00
|
|
|
self.assertEqual(type(sub), ast.LigatureSubstitution)
|
|
|
|
self.assertEqual(sub.glyphs, [{"f"}, {"f"}, {"i"}])
|
|
|
|
self.assertEqual(sub.replacement, "f_f_i")
|
2015-08-11 12:22:07 +02:00
|
|
|
|
|
|
|
def test_substitute_lookups(self):
|
2015-10-27 19:59:25 +01:00
|
|
|
doc = Parser(self.getpath("spec5fi1.fea")).parse()
|
2015-11-30 15:02:09 +01:00
|
|
|
[langsys, ligs, sub, feature] = doc.statements
|
2015-08-11 12:22:07 +02:00
|
|
|
self.assertEqual(feature.statements[0].lookups, [ligs, None, sub])
|
|
|
|
self.assertEqual(feature.statements[1].lookups, [ligs, None, sub])
|
2015-08-04 19:55:55 +02:00
|
|
|
|
2015-08-05 10:41:04 +02:00
|
|
|
def test_substitute_missing_by(self):
|
|
|
|
self.assertRaisesRegex(
|
2015-08-21 17:09:46 +02:00
|
|
|
FeatureLibError,
|
|
|
|
'Expected "by", "from" or explicit lookup references',
|
2015-08-05 10:41:04 +02:00
|
|
|
self.parse, "feature liga {substitute f f i;} liga;")
|
|
|
|
|
2015-08-11 15:14:47 +02:00
|
|
|
def test_subtable(self):
|
|
|
|
doc = self.parse("feature test {subtable;} test;")
|
|
|
|
s = doc.statements[0].statements[0]
|
|
|
|
self.assertEqual(type(s), ast.SubtableStatement)
|
|
|
|
|
2015-08-04 11:01:04 +02:00
|
|
|
def test_valuerecord_format_a_horizontal(self):
|
|
|
|
doc = self.parse("feature liga {valueRecordDef 123 foo;} liga;")
|
|
|
|
value = doc.statements[0].statements[0].value
|
|
|
|
self.assertEqual(value.xPlacement, 0)
|
|
|
|
self.assertEqual(value.yPlacement, 0)
|
|
|
|
self.assertEqual(value.xAdvance, 123)
|
|
|
|
self.assertEqual(value.yAdvance, 0)
|
|
|
|
|
|
|
|
def test_valuerecord_format_a_vertical(self):
|
|
|
|
doc = self.parse("feature vkrn {valueRecordDef 123 foo;} vkrn;")
|
|
|
|
value = doc.statements[0].statements[0].value
|
|
|
|
self.assertEqual(value.xPlacement, 0)
|
|
|
|
self.assertEqual(value.yPlacement, 0)
|
|
|
|
self.assertEqual(value.xAdvance, 0)
|
|
|
|
self.assertEqual(value.yAdvance, 123)
|
|
|
|
|
|
|
|
def test_valuerecord_format_b(self):
|
|
|
|
doc = self.parse("feature liga {valueRecordDef <1 2 3 4> foo;} liga;")
|
|
|
|
value = doc.statements[0].statements[0].value
|
|
|
|
self.assertEqual(value.xPlacement, 1)
|
|
|
|
self.assertEqual(value.yPlacement, 2)
|
|
|
|
self.assertEqual(value.xAdvance, 3)
|
|
|
|
self.assertEqual(value.yAdvance, 4)
|
|
|
|
|
|
|
|
def test_valuerecord_named(self):
|
|
|
|
doc = self.parse("valueRecordDef <1 2 3 4> foo;"
|
|
|
|
"feature liga {valueRecordDef <foo> bar;} liga;")
|
|
|
|
value = doc.statements[1].statements[0].value
|
|
|
|
self.assertEqual(value.xPlacement, 1)
|
|
|
|
self.assertEqual(value.yPlacement, 2)
|
|
|
|
self.assertEqual(value.xAdvance, 3)
|
|
|
|
self.assertEqual(value.yAdvance, 4)
|
|
|
|
|
|
|
|
def test_valuerecord_named_unknown(self):
|
|
|
|
self.assertRaisesRegex(
|
2015-08-21 17:09:46 +02:00
|
|
|
FeatureLibError, "Unknown valueRecordDef \"unknown\"",
|
2015-08-04 11:01:04 +02:00
|
|
|
self.parse, "valueRecordDef <unknown> foo;")
|
|
|
|
|
2015-08-04 17:08:48 +02:00
|
|
|
def test_valuerecord_scoping(self):
|
|
|
|
[foo, liga, smcp] = self.parse(
|
|
|
|
"valueRecordDef 789 foo;"
|
|
|
|
"feature liga {valueRecordDef <foo> bar;} liga;"
|
|
|
|
"feature smcp {valueRecordDef <foo> bar;} smcp;"
|
|
|
|
).statements
|
|
|
|
self.assertEqual(foo.value.xAdvance, 789)
|
|
|
|
self.assertEqual(liga.statements[0].value.xAdvance, 789)
|
|
|
|
self.assertEqual(smcp.statements[0].value.xAdvance, 789)
|
|
|
|
|
2015-08-01 12:35:22 +02:00
|
|
|
def test_languagesystem(self):
|
2015-08-01 14:49:19 +02:00
|
|
|
[langsys] = self.parse("languagesystem latn DEU;").statements
|
|
|
|
self.assertEqual(langsys.script, "latn")
|
|
|
|
self.assertEqual(langsys.language, "DEU ")
|
2015-08-21 21:14:06 +02:00
|
|
|
self.assertRaisesRegex(
|
|
|
|
FeatureLibError,
|
|
|
|
'For script "DFLT", the language must be "dflt"',
|
|
|
|
self.parse, "languagesystem DFLT DEU;")
|
2015-09-08 09:26:24 +02:00
|
|
|
self.assertRaisesRegex(
|
|
|
|
FeatureLibError,
|
|
|
|
'"dflt" is not a valid script tag; use "DFLT" instead',
|
|
|
|
self.parse, "languagesystem dflt dflt;")
|
|
|
|
self.assertRaisesRegex(
|
|
|
|
FeatureLibError,
|
|
|
|
'"DFLT" is not a valid language tag; use "dflt" instead',
|
|
|
|
self.parse, "languagesystem latn DFLT;")
|
2015-08-01 14:33:06 +02:00
|
|
|
self.assertRaisesRegex(
|
2015-08-21 17:09:46 +02:00
|
|
|
FeatureLibError, "Expected ';'",
|
2015-08-01 12:35:22 +02:00
|
|
|
self.parse, "languagesystem latn DEU")
|
2015-08-01 14:33:06 +02:00
|
|
|
self.assertRaisesRegex(
|
2015-08-21 17:09:46 +02:00
|
|
|
FeatureLibError, "longer than 4 characters",
|
2015-08-21 21:14:06 +02:00
|
|
|
self.parse, "languagesystem foobar DEU;")
|
2015-08-01 14:33:06 +02:00
|
|
|
self.assertRaisesRegex(
|
2015-08-21 17:09:46 +02:00
|
|
|
FeatureLibError, "longer than 4 characters",
|
2015-08-21 21:14:06 +02:00
|
|
|
self.parse, "languagesystem latn FOOBAR;")
|
2015-08-01 12:35:22 +02:00
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
self.tempdir = None
|
|
|
|
self.num_tempfiles = 0
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
if self.tempdir:
|
|
|
|
shutil.rmtree(self.tempdir)
|
|
|
|
|
|
|
|
def parse(self, text):
|
|
|
|
if not self.tempdir:
|
|
|
|
self.tempdir = tempfile.mkdtemp()
|
|
|
|
self.num_tempfiles += 1
|
|
|
|
path = os.path.join(self.tempdir, "tmp%d.fea" % self.num_tempfiles)
|
|
|
|
with codecs.open(path, "wb", "utf-8") as outfile:
|
|
|
|
outfile.write(text)
|
|
|
|
return Parser(path).parse()
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def getpath(testfile):
|
|
|
|
path, _ = os.path.split(__file__)
|
|
|
|
return os.path.join(path, "testdata", testfile)
|
|
|
|
|
|
|
|
|
2015-08-03 09:37:27 +02:00
|
|
|
class SymbolTableTest(unittest.TestCase):
|
|
|
|
def test_scopes(self):
|
|
|
|
symtab = SymbolTable()
|
|
|
|
symtab.define("foo", 23)
|
|
|
|
self.assertEqual(symtab.resolve("foo"), 23)
|
|
|
|
symtab.enter_scope()
|
|
|
|
self.assertEqual(symtab.resolve("foo"), 23)
|
|
|
|
symtab.define("foo", 42)
|
|
|
|
self.assertEqual(symtab.resolve("foo"), 42)
|
|
|
|
symtab.exit_scope()
|
|
|
|
self.assertEqual(symtab.resolve("foo"), 23)
|
|
|
|
|
|
|
|
def test_resolve_undefined(self):
|
|
|
|
self.assertEqual(SymbolTable().resolve("abc"), None)
|
|
|
|
|
|
|
|
|
2015-08-01 12:35:22 +02:00
|
|
|
if __name__ == "__main__":
|
|
|
|
unittest.main()
|