You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1236 lines
47 KiB

from fontTools.voltLib import ast
from fontTools.voltLib.error import VoltLibError
from fontTools.voltLib.parser import Parser
from io import StringIO
import unittest
class ParserTest(unittest.TestCase):
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
def assertSubEqual(self, sub, glyph_ref, replacement_ref):
glyphs = [[g.glyph for g in v] for v in sub.mapping.keys()]
replacement = [[g.glyph for g in v] for v in sub.mapping.values()]
self.assertEqual(glyphs, glyph_ref)
self.assertEqual(replacement, replacement_ref)
def test_def_glyph_base(self):
[def_glyph] = self.parse(
'DEF_GLYPH ".notdef" ID 0 TYPE BASE END_GLYPH'
).statements
self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
def_glyph.type, def_glyph.components),
(".notdef", 0, None, "BASE", None))
def test_def_glyph_base_with_unicode(self):
[def_glyph] = self.parse(
'DEF_GLYPH "space" ID 3 UNICODE 32 TYPE BASE END_GLYPH'
).statements
self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
def_glyph.type, def_glyph.components),
("space", 3, [0x0020], "BASE", None))
def test_def_glyph_base_with_unicodevalues(self):
[def_glyph] = self.parse_(
'DEF_GLYPH "CR" ID 2 UNICODEVALUES "U+0009" '
'TYPE BASE END_GLYPH'
).statements
self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
def_glyph.type, def_glyph.components),
("CR", 2, [0x0009], "BASE", None))
def test_def_glyph_base_with_mult_unicodevalues(self):
[def_glyph] = self.parse(
'DEF_GLYPH "CR" ID 2 UNICODEVALUES "U+0009,U+000D" '
'TYPE BASE END_GLYPH'
).statements
self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
def_glyph.type, def_glyph.components),
("CR", 2, [0x0009, 0x000D], "BASE", None))
def test_def_glyph_base_with_empty_unicodevalues(self):
[def_glyph] = self.parse_(
'DEF_GLYPH "i.locl" ID 269 UNICODEVALUES "" '
'TYPE BASE END_GLYPH'
).statements
self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
def_glyph.type, def_glyph.components),
("i.locl", 269, None, "BASE", None))
def test_def_glyph_base_2_components(self):
[def_glyph] = self.parse(
'DEF_GLYPH "glyphBase" ID 320 TYPE BASE COMPONENTS 2 END_GLYPH'
).statements
self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
def_glyph.type, def_glyph.components),
("glyphBase", 320, None, "BASE", 2))
def test_def_glyph_ligature_2_components(self):
[def_glyph] = self.parse(
'DEF_GLYPH "f_f" ID 320 TYPE LIGATURE COMPONENTS 2 END_GLYPH'
).statements
self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
def_glyph.type, def_glyph.components),
("f_f", 320, None, "LIGATURE", 2))
def test_def_glyph_mark(self):
[def_glyph] = self.parse(
'DEF_GLYPH "brevecomb" ID 320 TYPE MARK END_GLYPH'
).statements
self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
def_glyph.type, def_glyph.components),
("brevecomb", 320, None, "MARK", None))
def test_def_glyph_component(self):
[def_glyph] = self.parse(
'DEF_GLYPH "f.f_f" ID 320 TYPE COMPONENT END_GLYPH'
).statements
self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
def_glyph.type, def_glyph.components),
("f.f_f", 320, None, "COMPONENT", None))
def test_def_glyph_no_type(self):
[def_glyph] = self.parse(
'DEF_GLYPH "glyph20" ID 20 END_GLYPH'
).statements
self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
def_glyph.type, def_glyph.components),
("glyph20", 20, None, None, None))
def test_def_glyph_case_sensitive(self):
def_glyphs = self.parse(
'DEF_GLYPH "A" ID 3 UNICODE 65 TYPE BASE END_GLYPH\n'
'DEF_GLYPH "a" ID 4 UNICODE 97 TYPE BASE END_GLYPH'
).statements
self.assertEqual((def_glyphs[0].name, def_glyphs[0].id,
def_glyphs[0].unicode, def_glyphs[0].type,
def_glyphs[0].components),
("A", 3, [0x41], "BASE", None))
self.assertEqual((def_glyphs[1].name, def_glyphs[1].id,
def_glyphs[1].unicode, def_glyphs[1].type,
def_glyphs[1].components),
("a", 4, [0x61], "BASE", None))
def test_def_group_glyphs(self):
[def_group] = self.parse(
'DEF_GROUP "aaccented"\n'
' ENUM GLYPH "aacute" GLYPH "abreve" GLYPH "acircumflex" '
'GLYPH "adieresis" GLYPH "ae" GLYPH "agrave" GLYPH "amacron" '
'GLYPH "aogonek" GLYPH "aring" GLYPH "atilde" END_ENUM\n'
'END_GROUP'
).statements
self.assertEqual((def_group.name, def_group.enum.glyphSet()),
("aaccented",
("aacute", "abreve", "acircumflex", "adieresis",
"ae", "agrave", "amacron", "aogonek", "aring",
"atilde")))
def test_def_group_groups(self):
[group1, group2, test_group] = self.parse(
'DEF_GROUP "Group1"\n'
' ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "Group2"\n'
' ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "TestGroup"\n'
' ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n'
'END_GROUP'
).statements
groups = [g.group for g in test_group.enum.enum]
self.assertEqual((test_group.name, groups),
("TestGroup", ["Group1", "Group2"]))
def test_def_group_groups_not_yet_defined(self):
[group1, test_group1, test_group2, test_group3, group2] = \
self.parse(
'DEF_GROUP "Group1"\n'
' ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "TestGroup1"\n'
' ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "TestGroup2"\n'
' ENUM GROUP "Group2" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "TestGroup3"\n'
' ENUM GROUP "Group2" GROUP "Group1" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "Group2"\n'
' ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n'
'END_GROUP'
).statements
groups = [g.group for g in test_group1.enum.enum]
self.assertEqual(
(test_group1.name, groups),
("TestGroup1", ["Group1", "Group2"]))
groups = [g.group for g in test_group2.enum.enum]
self.assertEqual(
(test_group2.name, groups),
("TestGroup2", ["Group2"]))
groups = [g.group for g in test_group3.enum.enum]
self.assertEqual(
(test_group3.name, groups),
("TestGroup3", ["Group2", "Group1"]))
# def test_def_group_groups_undefined(self):
# with self.assertRaisesRegex(
# VoltLibError,
# r'Group "Group2" is used but undefined.'):
# [group1, test_group, group2] = self.parse(
# 'DEF_GROUP "Group1"\n'
# 'ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n'
# 'END_GROUP\n'
# 'DEF_GROUP "TestGroup"\n'
# 'ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n'
# 'END_GROUP\n'
# ).statements
def test_def_group_glyphs_and_group(self):
[def_group1, def_group2] = self.parse(
'DEF_GROUP "aaccented"\n'
' ENUM GLYPH "aacute" GLYPH "abreve" GLYPH "acircumflex" '
'GLYPH "adieresis" GLYPH "ae" GLYPH "agrave" GLYPH "amacron" '
'GLYPH "aogonek" GLYPH "aring" GLYPH "atilde" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "KERN_lc_a_2ND"\n'
' ENUM GLYPH "a" GROUP "aaccented" END_ENUM\n'
'END_GROUP'
).statements
items = def_group2.enum.enum
self.assertEqual((def_group2.name, items[0].glyphSet(), items[1].group),
("KERN_lc_a_2ND", ("a",), "aaccented"))
def test_def_group_range(self):
def_group = self.parse(
'DEF_GLYPH "a" ID 163 UNICODE 97 TYPE BASE END_GLYPH\n'
'DEF_GLYPH "agrave" ID 194 UNICODE 224 TYPE BASE END_GLYPH\n'
'DEF_GLYPH "aacute" ID 195 UNICODE 225 TYPE BASE END_GLYPH\n'
'DEF_GLYPH "acircumflex" ID 196 UNICODE 226 TYPE BASE END_GLYPH\n'
'DEF_GLYPH "atilde" ID 197 UNICODE 227 TYPE BASE END_GLYPH\n'
'DEF_GLYPH "c" ID 165 UNICODE 99 TYPE BASE END_GLYPH\n'
'DEF_GLYPH "ccaron" ID 209 UNICODE 269 TYPE BASE END_GLYPH\n'
'DEF_GLYPH "ccedilla" ID 210 UNICODE 231 TYPE BASE END_GLYPH\n'
'DEF_GLYPH "cdotaccent" ID 210 UNICODE 267 TYPE BASE END_GLYPH\n'
'DEF_GROUP "KERN_lc_a_2ND"\n'
' ENUM RANGE "a" TO "atilde" GLYPH "b" RANGE "c" TO "cdotaccent" '
'END_ENUM\n'
'END_GROUP'
).statements[-1]
self.assertEqual((def_group.name, def_group.enum.glyphSet()),
("KERN_lc_a_2ND",
("a", "agrave", "aacute", "acircumflex", "atilde",
"b", "c", "ccaron", "ccedilla", "cdotaccent")))
def test_group_duplicate(self):
self.assertRaisesRegex(
VoltLibError,
'Glyph group "dupe" already defined, '
'group names are case insensitive',
self.parse, 'DEF_GROUP "dupe"\n'
'ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "dupe"\n'
'ENUM GLYPH "x" END_ENUM\n'
'END_GROUP'
)
def test_group_duplicate_case_insensitive(self):
self.assertRaisesRegex(
VoltLibError,
'Glyph group "Dupe" already defined, '
'group names are case insensitive',
self.parse, 'DEF_GROUP "dupe"\n'
'ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "Dupe"\n'
'ENUM GLYPH "x" END_ENUM\n'
'END_GROUP'
)
def test_script_without_langsys(self):
[script] = self.parse(
'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
'END_SCRIPT'
).statements
self.assertEqual((script.name, script.tag, script.langs),
("Latin", "latn", []))
def test_langsys_normal(self):
[def_script] = self.parse(
'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n\n'
'END_LANGSYS\n'
'DEF_LANGSYS NAME "Moldavian" TAG "MOL "\n\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
self.assertEqual((def_script.name, def_script.tag),
("Latin",
"latn"))
def_lang = def_script.langs[0]
self.assertEqual((def_lang.name, def_lang.tag),
("Romanian",
"ROM "))
def_lang = def_script.langs[1]
self.assertEqual((def_lang.name, def_lang.tag),
("Moldavian",
"MOL "))
def test_langsys_no_script_name(self):
[langsys] = self.parse(
'DEF_SCRIPT TAG "latn"\n\n'
'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
self.assertEqual((langsys.name, langsys.tag),
(None,
"latn"))
lang = langsys.langs[0]
self.assertEqual((lang.name, lang.tag),
("Default",
"dflt"))
def test_langsys_no_script_tag_fails(self):
with self.assertRaisesRegex(
VoltLibError,
r'.*Expected "TAG"'):
[langsys] = self.parse(
'DEF_SCRIPT NAME "Latin"\n\n'
'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
def test_langsys_duplicate_script(self):
with self.assertRaisesRegex(
VoltLibError,
'Script "DFLT" already defined, '
'script tags are case insensitive'):
[langsys1, langsys2] = self.parse(
'DEF_SCRIPT NAME "Default" TAG "DFLT"\n\n'
'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
'END_LANGSYS\n'
'END_SCRIPT\n'
'DEF_SCRIPT TAG "DFLT"\n\n'
'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
def test_langsys_duplicate_lang(self):
with self.assertRaisesRegex(
VoltLibError,
'Language "dflt" already defined in script "DFLT", '
'language tags are case insensitive'):
[langsys] = self.parse(
'DEF_SCRIPT NAME "Default" TAG "DFLT"\n'
'DEF_LANGSYS NAME "Default" TAG "dflt"\n'
'END_LANGSYS\n'
'DEF_LANGSYS NAME "Default" TAG "dflt"\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
def test_langsys_lang_in_separate_scripts(self):
[langsys1, langsys2] = self.parse(
'DEF_SCRIPT NAME "Default" TAG "DFLT"\n\n'
'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
'END_LANGSYS\n'
'DEF_LANGSYS NAME "Default" TAG "ROM "\n\n'
'END_LANGSYS\n'
'END_SCRIPT\n'
'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
'END_LANGSYS\n'
'DEF_LANGSYS NAME "Default" TAG "ROM "\n\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
self.assertEqual((langsys1.langs[0].tag, langsys1.langs[1].tag),
("dflt", "ROM "))
self.assertEqual((langsys2.langs[0].tag, langsys2.langs[1].tag),
("dflt", "ROM "))
def test_langsys_no_lang_name(self):
[langsys] = self.parse(
'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
'DEF_LANGSYS TAG "dflt"\n\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
self.assertEqual((langsys.name, langsys.tag),
("Latin",
"latn"))
lang = langsys.langs[0]
self.assertEqual((lang.name, lang.tag),
(None,
"dflt"))
def test_langsys_no_langsys_tag_fails(self):
with self.assertRaisesRegex(
VoltLibError,
r'.*Expected "TAG"'):
[langsys] = self.parse(
'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
'DEF_LANGSYS NAME "Default"\n\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
def test_feature(self):
[def_script] = self.parse(
'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n\n'
'DEF_FEATURE NAME "Fractions" TAG "frac"\n'
' LOOKUP "fraclookup"\n'
'END_FEATURE\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
def_feature = def_script.langs[0].features[0]
self.assertEqual((def_feature.name, def_feature.tag,
def_feature.lookups),
("Fractions",
"frac",
["fraclookup"]))
[def_script] = self.parse(
'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n\n'
'DEF_FEATURE NAME "Kerning" TAG "kern"\n'
' LOOKUP "kern1" LOOKUP "kern2"\n'
'END_FEATURE\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
def_feature = def_script.langs[0].features[0]
self.assertEqual((def_feature.name, def_feature.tag,
def_feature.lookups),
("Kerning",
"kern",
["kern1", "kern2"]))
def test_lookup_duplicate(self):
with self.assertRaisesRegex(
VoltLibError,
'Lookup "dupe" already defined, '
'lookup names are case insensitive',
):
[lookup1, lookup2] = self.parse(
'DEF_LOOKUP "dupe"\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "a"\n'
'WITH GLYPH "a.alt"\n'
'END_SUB\n'
'END_SUBSTITUTION\n'
'DEF_LOOKUP "dupe"\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "b"\n'
'WITH GLYPH "b.alt"\n'
'END_SUB\n'
'END_SUBSTITUTION\n'
).statements
def test_lookup_duplicate_insensitive_case(self):
with self.assertRaisesRegex(
VoltLibError,
'Lookup "Dupe" already defined, '
'lookup names are case insensitive',
):
[lookup1, lookup2] = self.parse(
'DEF_LOOKUP "dupe"\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "a"\n'
'WITH GLYPH "a.alt"\n'
'END_SUB\n'
'END_SUBSTITUTION\n'
'DEF_LOOKUP "Dupe"\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "b"\n'
'WITH GLYPH "b.alt"\n'
'END_SUB\n'
'END_SUBSTITUTION\n'
).statements
def test_lookup_name_starts_with_letter(self):
with self.assertRaisesRegex(
VoltLibError,
r'Lookup name "\\lookupname" must start with a letter'
):
[lookup] = self.parse(
'DEF_LOOKUP "\\lookupname"\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "a"\n'
'WITH GLYPH "a.alt"\n'
'END_SUB\n'
'END_SUBSTITUTION\n'
).statements
def test_lookup_comments(self):
[lookup] = self.parse(
'DEF_LOOKUP "test" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR\n'
'COMMENTS "Hello\\nWorld"\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "a"\n'
'WITH GLYPH "b"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
self.assertEqual(lookup.name, "test")
self.assertEqual(lookup.comments, "Hello\nWorld")
def test_substitution_empty(self):
with self.assertRaisesRegex(
VoltLibError,
r'Expected SUB'):
[lookup] = self.parse(
'DEF_LOOKUP "empty_substitution" PROCESS_BASE PROCESS_MARKS '
'ALL DIRECTION LTR\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'END_SUBSTITUTION'
).statements
def test_substitution_invalid_many_to_many(self):
with self.assertRaisesRegex(
VoltLibError,
r'Invalid substitution type'):
[lookup] = self.parse(
'DEF_LOOKUP "invalid_substitution" PROCESS_BASE PROCESS_MARKS '
'ALL DIRECTION LTR\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "f" GLYPH "i"\n'
'WITH GLYPH "f.alt" GLYPH "i.alt"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
def test_substitution_invalid_reverse_chaining_single(self):
with self.assertRaisesRegex(
VoltLibError,
r'Invalid substitution type'):
[lookup] = self.parse(
'DEF_LOOKUP "invalid_substitution" PROCESS_BASE PROCESS_MARKS '
'ALL DIRECTION LTR REVERSAL\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "f" GLYPH "i"\n'
'WITH GLYPH "f_i"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
def test_substitution_invalid_mixed(self):
with self.assertRaisesRegex(
VoltLibError,
r'Invalid substitution type'):
[lookup] = self.parse(
'DEF_LOOKUP "invalid_substitution" PROCESS_BASE PROCESS_MARKS '
'ALL DIRECTION LTR\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "fi"\n'
'WITH GLYPH "f" GLYPH "i"\n'
'END_SUB\n'
'SUB GLYPH "f" GLYPH "l"\n'
'WITH GLYPH "f_l"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
def test_substitution_single(self):
[lookup] = self.parse(
'DEF_LOOKUP "smcp" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "a"\n'
'WITH GLYPH "a.sc"\n'
'END_SUB\n'
'SUB GLYPH "b"\n'
'WITH GLYPH "b.sc"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
self.assertEqual(lookup.name, "smcp")
self.assertSubEqual(lookup.sub, [["a"], ["b"]], [["a.sc"], ["b.sc"]])
def test_substitution_single_in_context(self):
[group, lookup] = self.parse(
'DEF_GROUP "Denominators"\n'
' ENUM GLYPH "one.dnom" GLYPH "two.dnom" END_ENUM\n'
'END_GROUP\n'
'DEF_LOOKUP "fracdnom" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
'IN_CONTEXT\n'
' LEFT ENUM GROUP "Denominators" GLYPH "fraction" END_ENUM\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "one"\n'
'WITH GLYPH "one.dnom"\n'
'END_SUB\n'
'SUB GLYPH "two"\n'
'WITH GLYPH "two.dnom"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
context = lookup.context[0]
self.assertEqual(lookup.name, "fracdnom")
self.assertEqual(context.ex_or_in, "IN_CONTEXT")
self.assertEqual(len(context.left), 1)
self.assertEqual(len(context.left[0]), 1)
self.assertEqual(len(context.left[0][0].enum), 2)
self.assertEqual(context.left[0][0].enum[0].group, "Denominators")
self.assertEqual(context.left[0][0].enum[1].glyph, "fraction")
self.assertEqual(context.right, [])
self.assertSubEqual(lookup.sub, [["one"], ["two"]],
[["one.dnom"], ["two.dnom"]])
def test_substitution_single_in_contexts(self):
[group, lookup] = self.parse(
'DEF_GROUP "Hebrew"\n'
' ENUM GLYPH "uni05D0" GLYPH "uni05D1" END_ENUM\n'
'END_GROUP\n'
'DEF_LOOKUP "HebrewCurrency" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
'IN_CONTEXT\n'
' RIGHT GROUP "Hebrew"\n'
' RIGHT GLYPH "one.Hebr"\n'
'END_CONTEXT\n'
'IN_CONTEXT\n'
' LEFT GROUP "Hebrew"\n'
' LEFT GLYPH "one.Hebr"\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "dollar"\n'
'WITH GLYPH "dollar.Hebr"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
context1 = lookup.context[0]
context2 = lookup.context[1]
self.assertEqual(lookup.name, "HebrewCurrency")
self.assertEqual(context1.ex_or_in, "IN_CONTEXT")
self.assertEqual(context1.left, [])
self.assertEqual(len(context1.right), 2)
self.assertEqual(len(context1.right[0]), 1)
self.assertEqual(len(context1.right[1]), 1)
self.assertEqual(context1.right[0][0].group, "Hebrew")
self.assertEqual(context1.right[1][0].glyph, "one.Hebr")
self.assertEqual(context2.ex_or_in, "IN_CONTEXT")
self.assertEqual(len(context2.left), 2)
self.assertEqual(len(context2.left[0]), 1)
self.assertEqual(len(context2.left[1]), 1)
self.assertEqual(context2.left[0][0].group, "Hebrew")
self.assertEqual(context2.left[1][0].glyph, "one.Hebr")
self.assertEqual(context2.right, [])
def test_substitution_skip_base(self):
[group, lookup] = self.parse(
'DEF_GROUP "SomeMarks"\n'
' ENUM GLYPH "marka" GLYPH "markb" END_ENUM\n'
'END_GROUP\n'
'DEF_LOOKUP "SomeSub" SKIP_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
self.assertEqual(
(lookup.name, lookup.process_base),
("SomeSub", False))
def test_substitution_process_base(self):
[group, lookup] = self.parse(
'DEF_GROUP "SomeMarks"\n'
' ENUM GLYPH "marka" GLYPH "markb" END_ENUM\n'
'END_GROUP\n'
'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
self.assertEqual(
(lookup.name, lookup.process_base),
("SomeSub", True))
def test_substitution_process_marks(self):
[group, lookup] = self.parse(
'DEF_GROUP "SomeMarks"\n'
' ENUM GLYPH "marka" GLYPH "markb" END_ENUM\n'
'END_GROUP\n'
'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS "SomeMarks"\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
self.assertEqual(
(lookup.name, lookup.process_marks),
("SomeSub", 'SomeMarks'))
def test_substitution_process_marks_all(self):
[lookup] = self.parse(
'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS ALL\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
self.assertEqual(
(lookup.name, lookup.process_marks),
("SomeSub", True))
def test_substitution_process_marks_none(self):
[lookup] = self.parse_(
'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS "NONE"\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
self.assertEqual(
(lookup.name, lookup.process_marks),
("SomeSub", False))
def test_substitution_process_marks_bad(self):
with self.assertRaisesRegex(
VoltLibError,
'Expected ALL, NONE, MARK_GLYPH_SET or an ID'):
self.parse(
'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" '
'END_ENUM END_GROUP\n'
'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS SomeMarks '
'AS_SUBSTITUTION\n'
'SUB GLYPH "A" WITH GLYPH "A.c2sc"\n'
'END_SUB\n'
'END_SUBSTITUTION'
)
def test_substitution_skip_marks(self):
[group, lookup] = self.parse(
'DEF_GROUP "SomeMarks"\n'
' ENUM GLYPH "marka" GLYPH "markb" END_ENUM\n'
'END_GROUP\n'
'DEF_LOOKUP "SomeSub" PROCESS_BASE SKIP_MARKS DIRECTION LTR\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
self.assertEqual(
(lookup.name, lookup.process_marks),
("SomeSub", False))
def test_substitution_mark_attachment(self):
[group, lookup] = self.parse(
'DEF_GROUP "SomeMarks"\n'
' ENUM GLYPH "acutecmb" GLYPH "gravecmb" END_ENUM\n'
'END_GROUP\n'
'DEF_LOOKUP "SomeSub" PROCESS_BASE '
'PROCESS_MARKS "SomeMarks" DIRECTION RTL\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
self.assertEqual(
(lookup.name, lookup.process_marks),
("SomeSub", "SomeMarks"))
def test_substitution_mark_glyph_set(self):
[group, lookup] = self.parse(
'DEF_GROUP "SomeMarks"\n'
' ENUM GLYPH "acutecmb" GLYPH "gravecmb" END_ENUM\n'
'END_GROUP\n'
'DEF_LOOKUP "SomeSub" PROCESS_BASE '
'PROCESS_MARKS MARK_GLYPH_SET "SomeMarks" DIRECTION RTL\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
self.assertEqual(
(lookup.name, lookup.mark_glyph_set),
("SomeSub", "SomeMarks"))
def test_substitution_process_all_marks(self):
[group, lookup] = self.parse(
'DEF_GROUP "SomeMarks"\n'
' ENUM GLYPH "acutecmb" GLYPH "gravecmb" END_ENUM\n'
'END_GROUP\n'
'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION RTL\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
self.assertEqual(
(lookup.name, lookup.process_marks),
("SomeSub", True))
def test_substitution_no_reversal(self):
# TODO: check right context with no reversal
[lookup] = self.parse(
'DEF_LOOKUP "Lookup" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
'IN_CONTEXT\n'
' RIGHT ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "a"\n'
'WITH GLYPH "a.alt"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
self.assertEqual(
(lookup.name, lookup.reversal),
("Lookup", None)
)
def test_substitution_reversal(self):
lookup = self.parse(
'DEF_GROUP "DFLT_Num_standardFigures"\n'
' ENUM GLYPH "zero" GLYPH "one" GLYPH "two" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "DFLT_Num_numerators"\n'
' ENUM GLYPH "zero.numr" GLYPH "one.numr" GLYPH "two.numr" END_ENUM\n'
'END_GROUP\n'
'DEF_LOOKUP "RevLookup" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR REVERSAL\n'
'IN_CONTEXT\n'
' RIGHT ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GROUP "DFLT_Num_standardFigures"\n'
'WITH GROUP "DFLT_Num_numerators"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements[-1]
self.assertEqual(
(lookup.name, lookup.reversal),
("RevLookup", True)
)
def test_substitution_single_to_multiple(self):
[lookup] = self.parse(
'DEF_LOOKUP "ccmp" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "aacute"\n'
'WITH GLYPH "a" GLYPH "acutecomb"\n'
'END_SUB\n'
'SUB GLYPH "agrave"\n'
'WITH GLYPH "a" GLYPH "gravecomb"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
self.assertEqual(lookup.name, "ccmp")
self.assertSubEqual(lookup.sub, [["aacute"], ["agrave"]],
[["a", "acutecomb"], ["a", "gravecomb"]])
def test_substitution_multiple_to_single(self):
[lookup] = self.parse(
'DEF_LOOKUP "liga" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "f" GLYPH "i"\n'
'WITH GLYPH "f_i"\n'
'END_SUB\n'
'SUB GLYPH "f" GLYPH "t"\n'
'WITH GLYPH "f_t"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
self.assertEqual(lookup.name, "liga")
self.assertSubEqual(lookup.sub, [["f", "i"], ["f", "t"]],
[["f_i"], ["f_t"]])
def test_substitution_reverse_chaining_single(self):
[lookup] = self.parse(
'DEF_LOOKUP "numr" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR REVERSAL\n'
'IN_CONTEXT\n'
' RIGHT ENUM '
'GLYPH "fraction" '
'RANGE "zero.numr" TO "nine.numr" '
'END_ENUM\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB RANGE "zero" TO "nine"\n'
'WITH RANGE "zero.numr" TO "nine.numr"\n'
'END_SUB\n'
'END_SUBSTITUTION'
).statements
mapping = lookup.sub.mapping
glyphs = [[(r.start, r.end) for r in v] for v in mapping.keys()]
replacement = [[(r.start, r.end) for r in v] for v in mapping.values()]
self.assertEqual(lookup.name, "numr")
self.assertEqual(glyphs, [[('zero', 'nine')]])
self.assertEqual(replacement, [[('zero.numr', 'nine.numr')]])
self.assertEqual(len(lookup.context[0].right), 1)
self.assertEqual(len(lookup.context[0].right[0]), 1)
enum = lookup.context[0].right[0][0]
self.assertEqual(len(enum.enum), 2)
self.assertEqual(enum.enum[0].glyph, "fraction")
self.assertEqual((enum.enum[1].start, enum.enum[1].end),
('zero.numr', 'nine.numr'))
# GPOS
# ATTACH_CURSIVE
# ATTACH
# ADJUST_PAIR
# ADJUST_SINGLE
def test_position_empty(self):
with self.assertRaisesRegex(
VoltLibError,
'Expected ATTACH, ATTACH_CURSIVE, ADJUST_PAIR, ADJUST_SINGLE'):
[lookup] = self.parse(
'DEF_LOOKUP "empty_position" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
'EXCEPT_CONTEXT\n'
' LEFT GLYPH "glyph"\n'
'END_CONTEXT\n'
'AS_POSITION\n'
'END_POSITION'
).statements
def test_position_attach(self):
[lookup, anchor1, anchor2, anchor3, anchor4] = self.parse(
'DEF_LOOKUP "anchor_top" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION RTL\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_POSITION\n'
'ATTACH GLYPH "a" GLYPH "e"\n'
'TO GLYPH "acutecomb" AT ANCHOR "top" '
'GLYPH "gravecomb" AT ANCHOR "top"\n'
'END_ATTACH\n'
'END_POSITION\n'
'DEF_ANCHOR "MARK_top" ON 120 GLYPH acutecomb COMPONENT 1 '
'AT POS DX 0 DY 450 END_POS END_ANCHOR\n'
'DEF_ANCHOR "MARK_top" ON 121 GLYPH gravecomb COMPONENT 1 '
'AT POS DX 0 DY 450 END_POS END_ANCHOR\n'
'DEF_ANCHOR "top" ON 31 GLYPH a COMPONENT 1 '
'AT POS DX 210 DY 450 END_POS END_ANCHOR\n'
'DEF_ANCHOR "top" ON 35 GLYPH e COMPONENT 1 '
'AT POS DX 215 DY 450 END_POS END_ANCHOR'
).statements
pos = lookup.pos
coverage = [g.glyph for g in pos.coverage]
coverage_to = [[[g.glyph for g in e], a] for (e, a) in pos.coverage_to]
self.assertEqual(
(lookup.name, coverage, coverage_to),
("anchor_top", ["a", "e"],
[[["acutecomb"], "top"], [["gravecomb"], "top"]])
)
self.assertEqual(
(anchor1.name, anchor1.gid, anchor1.glyph_name, anchor1.component,
anchor1.locked, anchor1.pos),
("MARK_top", 120, "acutecomb", 1, False, (None, 0, 450, {}, {},
{}))
)
self.assertEqual(
(anchor2.name, anchor2.gid, anchor2.glyph_name, anchor2.component,
anchor2.locked, anchor2.pos),
("MARK_top", 121, "gravecomb", 1, False, (None, 0, 450, {}, {},
{}))
)
self.assertEqual(
(anchor3.name, anchor3.gid, anchor3.glyph_name, anchor3.component,
anchor3.locked, anchor3.pos),
("top", 31, "a", 1, False, (None, 210, 450, {}, {}, {}))
)
self.assertEqual(
(anchor4.name, anchor4.gid, anchor4.glyph_name, anchor4.component,
anchor4.locked, anchor4.pos),
("top", 35, "e", 1, False, (None, 215, 450, {}, {}, {}))
)
def test_position_attach_cursive(self):
[lookup] = self.parse(
'DEF_LOOKUP "SomeLookup" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION RTL\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_POSITION\n'
'ATTACH_CURSIVE\nEXIT GLYPH "a" GLYPH "b"\nENTER GLYPH "c"\n'
'END_ATTACH\n'
'END_POSITION'
).statements
exit = [[g.glyph for g in v] for v in lookup.pos.coverages_exit]
enter = [[g.glyph for g in v] for v in lookup.pos.coverages_enter]
self.assertEqual(
(lookup.name, exit, enter),
("SomeLookup", [["a", "b"]], [["c"]])
)
def test_position_adjust_pair(self):
[lookup] = self.parse(
'DEF_LOOKUP "kern1" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION RTL\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_POSITION\n'
'ADJUST_PAIR\n'
' FIRST GLYPH "A"\n'
' SECOND GLYPH "V"\n'
' 1 2 BY POS ADV -30 END_POS POS END_POS\n'
' 2 1 BY POS ADV -30 END_POS POS END_POS\n\n'
'END_ADJUST\n'
'END_POSITION'
).statements
coverages_1 = [[g.glyph for g in v] for v in lookup.pos.coverages_1]
coverages_2 = [[g.glyph for g in v] for v in lookup.pos.coverages_2]
self.assertEqual(
(lookup.name, coverages_1, coverages_2,
lookup.pos.adjust_pair),
("kern1", [["A"]], [["V"]],
{(1, 2): ((-30, None, None, {}, {}, {}),
(None, None, None, {}, {}, {})),
(2, 1): ((-30, None, None, {}, {}, {}),
(None, None, None, {}, {}, {}))})
)
def test_position_adjust_single(self):
[lookup] = self.parse(
'DEF_LOOKUP "TestLookup" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
'IN_CONTEXT\n'
# ' LEFT GLYPH "leftGlyph"\n'
# ' RIGHT GLYPH "rightGlyph"\n'
'END_CONTEXT\n'
'AS_POSITION\n'
'ADJUST_SINGLE'
' GLYPH "glyph1" BY POS ADV 0 DX 123 END_POS'
' GLYPH "glyph2" BY POS ADV 0 DX 456 END_POS\n'
'END_ADJUST\n'
'END_POSITION'
).statements
pos = lookup.pos
adjust = [[[g.glyph for g in a], b] for (a, b) in pos.adjust_single]
self.assertEqual(
(lookup.name, adjust),
("TestLookup",
[[["glyph1"], (0, 123, None, {}, {}, {})],
[["glyph2"], (0, 456, None, {}, {}, {})]])
)
def test_def_anchor(self):
[anchor1, anchor2, anchor3] = self.parse(
'DEF_ANCHOR "top" ON 120 GLYPH a '
'COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n'
'DEF_ANCHOR "MARK_top" ON 120 GLYPH acutecomb '
'COMPONENT 1 AT POS DX 0 DY 450 END_POS END_ANCHOR\n'
'DEF_ANCHOR "bottom" ON 120 GLYPH a '
'COMPONENT 1 AT POS DX 250 DY 0 END_POS END_ANCHOR'
).statements
self.assertEqual(
(anchor1.name, anchor1.gid, anchor1.glyph_name, anchor1.component,
anchor1.locked, anchor1.pos),
("top", 120, "a", 1,
False, (None, 250, 450, {}, {}, {}))
)
self.assertEqual(
(anchor2.name, anchor2.gid, anchor2.glyph_name, anchor2.component,
anchor2.locked, anchor2.pos),
("MARK_top", 120, "acutecomb", 1,
False, (None, 0, 450, {}, {}, {}))
)
self.assertEqual(
(anchor3.name, anchor3.gid, anchor3.glyph_name, anchor3.component,
anchor3.locked, anchor3.pos),
("bottom", 120, "a", 1,
False, (None, 250, 0, {}, {}, {}))
)
def test_def_anchor_multi_component(self):
[anchor1, anchor2] = self.parse(
'DEF_ANCHOR "top" ON 120 GLYPH a '
'COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n'
'DEF_ANCHOR "top" ON 120 GLYPH a '
'COMPONENT 2 AT POS DX 250 DY 450 END_POS END_ANCHOR'
).statements
self.assertEqual(
(anchor1.name, anchor1.gid, anchor1.glyph_name, anchor1.component),
("top", 120, "a", 1)
)
self.assertEqual(
(anchor2.name, anchor2.gid, anchor2.glyph_name, anchor2.component),
("top", 120, "a", 2)
)
def test_def_anchor_duplicate(self):
self.assertRaisesRegex(
VoltLibError,
'Anchor "dupe" already defined, '
'anchor names are case insensitive',
self.parse,
'DEF_ANCHOR "dupe" ON 120 GLYPH a '
'COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n'
'DEF_ANCHOR "dupe" ON 120 GLYPH a '
'COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR'
)
def test_def_anchor_locked(self):
[anchor] = self.parse(
'DEF_ANCHOR "top" ON 120 GLYPH a '
'COMPONENT 1 LOCKED AT POS DX 250 DY 450 END_POS END_ANCHOR'
).statements
self.assertEqual(
(anchor.name, anchor.gid, anchor.glyph_name, anchor.component,
anchor.locked, anchor.pos),
("top", 120, "a", 1,
True, (None, 250, 450, {}, {}, {}))
)
def test_anchor_adjust_device(self):
[anchor] = self.parse(
'DEF_ANCHOR "MARK_top" ON 123 GLYPH diacglyph '
'COMPONENT 1 AT POS DX 0 DY 456 ADJUST_BY 12 AT 34 '
'ADJUST_BY 56 AT 78 END_POS END_ANCHOR'
).statements
self.assertEqual(
(anchor.name, anchor.pos),
("MARK_top", (None, 0, 456, {}, {}, {34: 12, 78: 56}))
)
def test_ppem(self):
[grid_ppem, pres_ppem, ppos_ppem] = self.parse(
'GRID_PPEM 20\n'
'PRESENTATION_PPEM 72\n'
'PPOSITIONING_PPEM 144'
).statements
self.assertEqual(
((grid_ppem.name, grid_ppem.value),
(pres_ppem.name, pres_ppem.value),
(ppos_ppem.name, ppos_ppem.value)),
(("GRID_PPEM", 20), ("PRESENTATION_PPEM", 72),
("PPOSITIONING_PPEM", 144))
)
def test_compiler_flags(self):
[setting1, setting2] = self.parse(
'COMPILER_USEEXTENSIONLOOKUPS\n'
'COMPILER_USEPAIRPOSFORMAT2'
).statements
self.assertEqual(
((setting1.name, setting1.value),
(setting2.name, setting2.value)),
(("COMPILER_USEEXTENSIONLOOKUPS", True),
("COMPILER_USEPAIRPOSFORMAT2", True))
)
def test_cmap(self):
[cmap_format1, cmap_format2, cmap_format3] = self.parse(
'CMAP_FORMAT 0 3 4\n'
'CMAP_FORMAT 1 0 6\n'
'CMAP_FORMAT 3 1 4'
).statements
self.assertEqual(
((cmap_format1.name, cmap_format1.value),
(cmap_format2.name, cmap_format2.value),
(cmap_format3.name, cmap_format3.value)),
(("CMAP_FORMAT", (0, 3, 4)),
("CMAP_FORMAT", (1, 0, 6)),
("CMAP_FORMAT", (3, 1, 4)))
)
def test_do_not_touch_cmap(self):
[option1, option2, option3, option4] = self.parse(
'DO_NOT_TOUCH_CMAP\n'
'CMAP_FORMAT 0 3 4\n'
'CMAP_FORMAT 1 0 6\n'
'CMAP_FORMAT 3 1 4'
).statements
self.assertEqual(
((option1.name, option1.value),
(option2.name, option2.value),
(option3.name, option3.value),
(option4.name, option4.value)),
(("DO_NOT_TOUCH_CMAP", True),
("CMAP_FORMAT", (0, 3, 4)),
("CMAP_FORMAT", (1, 0, 6)),
("CMAP_FORMAT", (3, 1, 4)))
)
def test_stop_at_end(self):
doc = self.parse_(
'DEF_GLYPH ".notdef" ID 0 TYPE BASE END_GLYPH END\0\0\0\0'
)
[def_glyph] = doc.statements
self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
def_glyph.type, def_glyph.components),
(".notdef", 0, None, "BASE", None))
self.assertEqual(str(doc),
'\nDEF_GLYPH ".notdef" ID 0 TYPE BASE END_GLYPH END\n')
def parse_(self, text):
return Parser(StringIO(text)).parse()
def parse(self, text):
doc = self.parse_(text)
self.assertEqual('\n'.join(str(s) for s in doc.statements), text)
return Parser(StringIO(text)).parse()
if __name__ == "__main__":
import sys
sys.exit(unittest.main())