[feaLib] Implement markClass
statement
This commit is contained in:
parent
66386ab488
commit
dfa1551ece
@ -39,6 +39,7 @@ class Block(Statement):
|
||||
class FeatureFile(Block):
|
||||
def __init__(self):
|
||||
Block.__init__(self, location=None)
|
||||
self.markClasses = {} # name --> ast.MarkClassDefinition
|
||||
|
||||
|
||||
class FeatureBlock(Block):
|
||||
@ -72,6 +73,13 @@ class GlyphClassDefinition(Statement):
|
||||
self.glyphs = glyphs
|
||||
|
||||
|
||||
class MarkClassDefinition(object):
|
||||
def __init__(self, location, name):
|
||||
self.location, self.name = location, name
|
||||
self.anchors = {} # glyph --> ast.Anchor
|
||||
self.glyphLocations = {} # glyph --> (filepath, line, column)
|
||||
|
||||
|
||||
class AlternateSubstitution(Statement):
|
||||
def __init__(self, location, glyph, from_class):
|
||||
Statement.__init__(self, location)
|
||||
|
@ -26,11 +26,12 @@ class Builder(object):
|
||||
self.cur_feature_name_ = None
|
||||
self.lookups_ = []
|
||||
self.features_ = {} # ('latn', 'DEU ', 'smcp') --> [LookupBuilder*]
|
||||
self.parseTree = None
|
||||
self.required_features_ = {} # ('latn', 'DEU ') --> 'scmp'
|
||||
|
||||
def build(self):
|
||||
parsetree = Parser(self.featurefile_path).parse()
|
||||
parsetree.build(self)
|
||||
self.parseTree = Parser(self.featurefile_path).parse()
|
||||
self.parseTree.build(self)
|
||||
for tag in ('GPOS', 'GSUB'):
|
||||
table = self.makeTable(tag)
|
||||
if (table.ScriptList.ScriptCount > 0 or
|
||||
@ -40,6 +41,11 @@ class Builder(object):
|
||||
fontTable.table = table
|
||||
elif tag in self.font:
|
||||
del self.font[tag]
|
||||
gdef = self.makeGDEF()
|
||||
if gdef:
|
||||
self.font["GDEF"] = gdef
|
||||
elif "GDEF" in self.font:
|
||||
del self.font["GDEF"]
|
||||
|
||||
def get_lookup_(self, location, builder_class):
|
||||
if (self.cur_lookup_ and
|
||||
@ -64,6 +70,32 @@ class Builder(object):
|
||||
self.features_.setdefault(key, []).append(self.cur_lookup_)
|
||||
return self.cur_lookup_
|
||||
|
||||
def makeGDEF(self):
|
||||
gdef = otTables.GDEF()
|
||||
gdef.Version = 1.0
|
||||
gdef.GlyphClassDef = otTables.GlyphClassDef()
|
||||
gdef.GlyphClassDef.classDefs = {}
|
||||
|
||||
glyphMarkClass = {} # glyph --> markClass
|
||||
for markClass in self.parseTree.markClasses.values():
|
||||
for glyph in markClass.anchors.keys():
|
||||
if glyph in glyphMarkClass:
|
||||
other = glyphMarkClass[glyph]
|
||||
name1, name2 = sorted([markClass.name, other.name])
|
||||
raise FeatureLibError(
|
||||
'glyph %s cannot be both in markClass @%s and @%s' %
|
||||
(glyph, name1, name2), markClass.location)
|
||||
glyphMarkClass[glyph] = markClass
|
||||
gdef.GlyphClassDef.classDefs[glyph] = 3
|
||||
gdef.AttachList = None
|
||||
gdef.LigCaretList = None
|
||||
gdef.MarkAttachClassDef = None
|
||||
if len(gdef.GlyphClassDef.classDefs) == 0:
|
||||
return None
|
||||
result = getTableClass("GDEF")()
|
||||
result.table = gdef
|
||||
return result
|
||||
|
||||
def makeTable(self, tag):
|
||||
table = getattr(otTables, tag, None)()
|
||||
table.Version = 1.0
|
||||
|
@ -75,7 +75,7 @@ class BuilderTest(unittest.TestCase):
|
||||
|
||||
def expect_ttx(self, font, expected_ttx):
|
||||
path = self.temp_path(suffix=".ttx")
|
||||
font.saveXML(path, quiet=True, tables=['GSUB', 'GPOS'])
|
||||
font.saveXML(path, quiet=True, tables=['GDEF', 'GSUB', 'GPOS'])
|
||||
actual = self.read_ttx(path)
|
||||
expected = self.read_ttx(expected_ttx)
|
||||
if actual != expected:
|
||||
@ -218,6 +218,19 @@ class BuilderTest(unittest.TestCase):
|
||||
"it must be the first of the languagesystem statements",
|
||||
self.build, "languagesystem latn TRK; languagesystem DFLT dflt;")
|
||||
|
||||
def test_markClass(self):
|
||||
font = makeTTFont()
|
||||
addOpenTypeFeatures(self.getpath("markClass.fea"), font)
|
||||
self.expect_ttx(font, self.getpath("markClass.ttx"))
|
||||
|
||||
def test_markClass_redefine(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
"glyph C cannot be both in markClass @MARK1 and @MARK2",
|
||||
self.build,
|
||||
"markClass [A B C] <anchor 100 50> @MARK1;"
|
||||
"markClass [C D E] <anchor 200 80> @MARK2;")
|
||||
|
||||
def test_script(self):
|
||||
builder = Builder(None, makeTTFont())
|
||||
builder.start_feature(location=None, name='test')
|
||||
|
@ -35,14 +35,17 @@ class Parser(object):
|
||||
statements.append(self.parse_languagesystem_())
|
||||
elif self.is_cur_keyword_("lookup"):
|
||||
statements.append(self.parse_lookup_(vertical=False))
|
||||
elif self.is_cur_keyword_("markClass"):
|
||||
self.parse_markClass_()
|
||||
elif self.is_cur_keyword_("feature"):
|
||||
statements.append(self.parse_feature_block_())
|
||||
elif self.is_cur_keyword_("valueRecordDef"):
|
||||
statements.append(
|
||||
self.parse_valuerecord_definition_(vertical=False))
|
||||
else:
|
||||
raise FeatureLibError("Expected feature, languagesystem, "
|
||||
"lookup, or glyph class definition",
|
||||
raise FeatureLibError(
|
||||
"Expected feature, languagesystem, lookup, markClass, "
|
||||
"or glyph class definition",
|
||||
self.cur_token_location_)
|
||||
return self.doc_
|
||||
|
||||
@ -244,6 +247,21 @@ class Parser(object):
|
||||
self.lookups_.define(name, block)
|
||||
return block
|
||||
|
||||
def parse_markClass_(self):
|
||||
assert self.is_cur_keyword_("markClass")
|
||||
location = self.cur_token_location_
|
||||
glyphs = self.parse_glyphclass_(accept_glyphname=True)
|
||||
anchor = self.parse_anchor_()
|
||||
name = self.expect_glyphclass_()
|
||||
self.expect_symbol_(";")
|
||||
markClass = self.doc_.markClasses.get(name)
|
||||
if markClass is None:
|
||||
markClass = ast.MarkClassDefinition(location, name)
|
||||
self.doc_.markClasses[name] = markClass
|
||||
for glyph in glyphs:
|
||||
markClass.anchors[glyph] = anchor
|
||||
markClass.glyphLocations[glyph] = location
|
||||
|
||||
def is_next_glyphclass_(self):
|
||||
return (self.next_token_ == "[" or
|
||||
self.next_token_type_ in (Lexer.GLYPHCLASS, Lexer.NAME))
|
||||
@ -531,6 +549,8 @@ class Parser(object):
|
||||
statements.append(self.parse_language_())
|
||||
elif self.is_cur_keyword_("lookup"):
|
||||
statements.append(self.parse_lookup_(vertical))
|
||||
elif self.is_cur_keyword_("markClass"):
|
||||
self.parse_markClass_()
|
||||
elif self.is_cur_keyword_({"pos", "position"}):
|
||||
statements.append(
|
||||
self.parse_position_(enumerated=False, vertical=vertical))
|
||||
@ -566,6 +586,12 @@ class Parser(object):
|
||||
return self.cur_token_ in k
|
||||
return False
|
||||
|
||||
def expect_glyphclass_(self):
|
||||
self.advance_lexer_()
|
||||
if self.cur_token_type_ is not Lexer.GLYPHCLASS:
|
||||
raise FeatureLibError("Expected @NAME", self.cur_token_location_)
|
||||
return self.cur_token_
|
||||
|
||||
def expect_tag_(self):
|
||||
self.advance_lexer_()
|
||||
if self.cur_token_type_ is not Lexer.NAME:
|
||||
|
@ -442,6 +442,21 @@ class ParserTest(unittest.TestCase):
|
||||
" enumerate position cursive A <anchor 12 -2> <anchor 2 3>;"
|
||||
"} kern;")
|
||||
|
||||
def test_markClass(self):
|
||||
doc = self.parse("markClass [acute grave] <anchor 350 3> @MARKS;"
|
||||
"feature test {"
|
||||
" markClass cedilla <anchor 400 -4> @MARKS;"
|
||||
"} test;")
|
||||
markClass = doc.markClasses["MARKS"]
|
||||
self.assertEqual(set(markClass.anchors.keys()),
|
||||
{"acute", "cedilla", "grave"})
|
||||
acuteAnchor = markClass.anchors["acute"]
|
||||
cedillaAnchor = markClass.anchors["cedilla"]
|
||||
graveAnchor = markClass.anchors["grave"]
|
||||
self.assertEqual((acuteAnchor.x, acuteAnchor.y), (350, 3))
|
||||
self.assertEqual((cedillaAnchor.x, cedillaAnchor.y), (400, -4))
|
||||
self.assertEqual((graveAnchor.x, graveAnchor.y), (350, 3))
|
||||
|
||||
def test_rsub_format_a(self):
|
||||
doc = self.parse("feature test {rsub a [b B] c' d [e E] by C;} test;")
|
||||
rsub = doc.statements[0].statements[0]
|
||||
|
12
Lib/fontTools/feaLib/testdata/markClass.fea
vendored
Normal file
12
Lib/fontTools/feaLib/testdata/markClass.fea
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
languagesystem DFLT dflt;
|
||||
|
||||
markClass [acute] <anchor 350 0> @TOP_MARKS;
|
||||
|
||||
feature foo {
|
||||
markClass [acute grave] <anchor 350 0> @TOP_MARKS;
|
||||
markClass cedilla <anchor 300 0> @BOTTOM_MARKS;
|
||||
} foo;
|
||||
|
||||
feature bar {
|
||||
markClass [dieresis breve] <anchor 400 0> @TOP_MARKS;
|
||||
} bar;
|
15
Lib/fontTools/feaLib/testdata/markClass.ttx
vendored
Normal file
15
Lib/fontTools/feaLib/testdata/markClass.ttx
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont>
|
||||
|
||||
<GDEF>
|
||||
<Version value="1.0"/>
|
||||
<GlyphClassDef>
|
||||
<ClassDef glyph="acute" class="3"/>
|
||||
<ClassDef glyph="breve" class="3"/>
|
||||
<ClassDef glyph="cedilla" class="3"/>
|
||||
<ClassDef glyph="dieresis" class="3"/>
|
||||
<ClassDef glyph="grave" class="3"/>
|
||||
</GlyphClassDef>
|
||||
</GDEF>
|
||||
|
||||
</ttFont>
|
Loading…
x
Reference in New Issue
Block a user