[feaLib] Implement the LigatureCaretByPos statement

This commit is contained in:
Sascha Brawer 2016-01-07 16:39:35 +01:00
parent 4daf0601b2
commit a44d8c5364
7 changed files with 161 additions and 7 deletions

View File

@ -109,6 +109,12 @@ class LookupBlock(Block):
builder.end_lookup_block()
class TableBlock(Block):
def __init__(self, location, name):
Block.__init__(self, location)
self.name = name
class GlyphClassDefinition(Statement):
def __init__(self, location, name, glyphs):
Statement.__init__(self, location)
@ -257,6 +263,16 @@ class IgnoreSubstitutionRule(Statement):
self.prefix, self.glyphs, self.suffix = (prefix, glyphs, suffix)
class LigatureCaretByPosStatement(Statement):
def __init__(self, location, glyphs, carets):
Statement.__init__(self, location)
self.glyphs, self.carets = (glyphs, carets)
def build(self, builder):
glyphs = self.glyphs.glyphSet()
builder.add_ligatureCaretByPos_(self.location, glyphs, self.carets)
class LigatureSubstStatement(Statement):
def __init__(self, location, prefix, glyphs, suffix, replacement):
Statement.__init__(self, location)

View File

@ -28,6 +28,7 @@ class Builder(object):
self.cur_feature_name_ = None
self.lookups_ = []
self.features_ = {} # ('latn', 'DEU ', 'smcp') --> [LookupBuilder*]
self.ligatureCaretByPos_ = {} # "f_f_i" --> {300, 600}
self.parseTree = None
self.required_features_ = {} # ('latn', 'DEU ') --> 'scmp'
self.markAttach_ = {} # "acute" --> (4, (file, line, column))
@ -92,7 +93,9 @@ class Builder(object):
def makeGDEF(self):
gdef = otTables.GDEF()
gdef.Version = 1.0
gdef.GlyphClassDef = otTables.GlyphClassDef()
gdef.GlyphClassDef = None
gdef.AttachList = None
gdef.LigCaretList = self.makeLigCaretList_()
inferredGlyphClass = {}
for lookup in self.lookups_:
@ -112,9 +115,9 @@ class Builder(object):
marks[glyph] = markClass
inferredGlyphClass[glyph] = 3
gdef.GlyphClassDef.classDefs = inferredGlyphClass
gdef.AttachList = None
gdef.LigCaretList = None
if inferredGlyphClass:
gdef.GlyphClassDef = otTables.GlyphClassDef()
gdef.GlyphClassDef.classDefs = inferredGlyphClass
markAttachClass = {g: c for g, (c, _) in self.markAttach_.items()}
if markAttachClass:
@ -136,13 +139,35 @@ class Builder(object):
coverage.glyphs = sorted(glyphs, key=self.font.getGlyphID)
m.Coverage.append(coverage)
if (len(gdef.GlyphClassDef.classDefs) == 0 and
if (gdef.GlyphClassDef is None and
gdef.LigCaretList is None and
gdef.MarkAttachClassDef is None):
return None
result = getTableClass("GDEF")()
result.table = gdef
return result
def makeLigCaretList_(self):
if not self.ligatureCaretByPos_:
return None
result = otTables.LigCaretList()
result.Coverage = otTables.Coverage()
result.Coverage.glyphs = sorted(self.ligatureCaretByPos_.keys(),
key=self.font.getGlyphID)
result.LigGlyphCount = len(result.Coverage.glyphs)
result.LigGlyph = []
for glyph in result.Coverage.glyphs:
ligGlyph = otTables.LigGlyph()
result.LigGlyph.append(ligGlyph)
ligGlyph.CaretValue = []
for caretPos in sorted(self.ligatureCaretByPos_[glyph]):
val = otTables.CaretValue()
val.Format = 1
val.Coordinate = caretPos
ligGlyph.CaretValue.append(val)
ligGlyph.CaretCount = len(ligGlyph.CaretValue)
return result
def makeTable(self, tag):
table = getattr(otTables, tag, None)()
table.Version = 1.0
@ -540,6 +565,10 @@ class Builder(object):
location)
lookup.mapping[glyph] = valuerecord
def add_ligatureCaretByPos_(self, location, glyphs, carets):
for glyph in glyphs:
self.ligatureCaretByPos_[glyph] = carets
def _makeOpenTypeDeviceTable(deviceTable, device):
device = tuple(sorted(device))

View File

@ -147,7 +147,7 @@ class BuilderTest(unittest.TestCase):
def test_constructs(self):
for name in ("enum markClass language_required "
"lookup lookupflag").split():
"LigatureCaretByPos lookup lookupflag").split():
font = makeTTFont()
addOpenTypeFeatures(self.getpath("%s.fea" % name), font)
self.expect_ttx(font, self.getpath("%s.ttx" % name))

View File

@ -39,13 +39,15 @@ class Parser(object):
statements.append(self.parse_markClass_())
elif self.is_cur_keyword_("feature"):
statements.append(self.parse_feature_block_())
elif self.is_cur_keyword_("table"):
statements.append(self.parse_table_())
elif self.is_cur_keyword_("valueRecordDef"):
statements.append(
self.parse_valuerecord_definition_(vertical=False))
else:
raise FeatureLibError(
"Expected feature, languagesystem, lookup, markClass, "
"or glyph class definition",
"table, or glyph class definition",
self.cur_token_location_)
return self.doc_
@ -252,6 +254,16 @@ class Parser(object):
return ast.LanguageStatement(location, language,
include_default, required)
def parse_ligatureCaretByPos_(self):
assert self.is_cur_keyword_("LigatureCaretByPos")
location = self.cur_token_location_
glyphs = self.parse_glyphclass_(accept_glyphname=True)
carets = {self.expect_number_()}
while self.next_token_ != ";":
carets.add(self.expect_number_())
self.expect_symbol_(";")
return ast.LigatureCaretByPosStatement(location, glyphs, carets)
def parse_lookup_(self, vertical):
assert self.is_cur_keyword_("lookup")
location, name = self.cur_token_location_, self.expect_name_()
@ -561,6 +573,38 @@ class Parser(object):
self.expect_symbol_(";")
return ast.SubtableStatement(location)
def parse_table_(self):
assert self.is_cur_keyword_("table")
location, name = self.cur_token_location_, self.expect_tag_()
table = ast.TableBlock(location, name)
self.expect_symbol_("{")
handler = {
"GDEF": self.parse_table_GDEF_,
}.get(name)
if handler:
handler(table)
else:
raise FeatureLibError('"table %s" is not supported' % name.strip(),
location)
self.expect_symbol_("}")
end_tag = self.expect_tag_()
if end_tag != name:
raise FeatureLibError('Expected "%s"' % name.strip(),
self.cur_token_location_)
self.expect_symbol_(";")
return table
def parse_table_GDEF_(self, table):
statements = table.statements
while self.next_token_ != "}":
self.advance_lexer_()
if self.is_cur_keyword_("LigatureCaretByPos"):
statements.append(self.parse_ligatureCaretByPos_())
else:
raise FeatureLibError(
"Expected LigatureCaretByPos",
self.cur_token_location_)
def parse_device_(self):
result = None
self.expect_symbol_("<")

View File

@ -293,6 +293,20 @@ class ParserTest(unittest.TestCase):
'"DFLT" is not a valid language tag; use "dflt" instead',
self.parse, "feature test { language DFLT; } test;")
def test_ligatureCaretByPos_glyphClass(self):
doc = self.parse("table GDEF {LigatureCaretByPos [c_t f_i] 7;} GDEF;")
s = doc.statements[0].statements[0]
self.assertIsInstance(s, ast.LigatureCaretByPosStatement)
self.assertEqual(glyphstr([s.glyphs]), "[c_t f_i]")
self.assertEqual(s.carets, {7})
def test_ligatureCaretByPos_singleGlyph(self):
doc = self.parse("table GDEF {LigatureCaretByPos f_i 400 380;} GDEF;")
s = doc.statements[0].statements[0]
self.assertIsInstance(s, ast.LigatureCaretByPosStatement)
self.assertEqual(glyphstr([s.glyphs]), "f_i")
self.assertEqual(s.carets, {380, 400})
def test_lookup_block(self):
[lookup] = self.parse("lookup Ligatures {} Ligatures;").statements
self.assertEqual(lookup.name, "Ligatures")
@ -884,6 +898,16 @@ class ParserTest(unittest.TestCase):
s = doc.statements[0].statements[0]
self.assertEqual(type(s), ast.SubtableStatement)
def test_table_badEnd(self):
self.assertRaisesRegex(
FeatureLibError, 'Expected "GDEF"', self.parse,
"table GDEF {LigatureCaretByPos f_i 400;} ABCD;")
def test_table_unsupported(self):
self.assertRaisesRegex(
FeatureLibError, '"table Foo" is not supported', self.parse,
"table Foo {LigatureCaretByPos f_i 400;} Foo;")
def test_valuerecord_format_a_horizontal(self):
doc = self.parse("feature liga {valueRecordDef 123 foo;} liga;")
value = doc.statements[0].statements[0].value

View File

@ -0,0 +1,4 @@
table GDEF {
LigatureCaretByPos [c_h c_k] 500;
LigatureCaretByPos f_f_i 600 300;
} GDEF;

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont>
<GDEF>
<Version value="1.0"/>
<LigCaretList>
<Coverage>
<Glyph value="c_h"/>
<Glyph value="c_k"/>
<Glyph value="f_f_i"/>
</Coverage>
<!-- LigGlyphCount=3 -->
<LigGlyph index="0">
<!-- CaretCount=1 -->
<CaretValue index="0" Format="1">
<Coordinate value="500"/>
</CaretValue>
</LigGlyph>
<LigGlyph index="1">
<!-- CaretCount=1 -->
<CaretValue index="0" Format="1">
<Coordinate value="500"/>
</CaretValue>
</LigGlyph>
<LigGlyph index="2">
<!-- CaretCount=2 -->
<CaretValue index="0" Format="1">
<Coordinate value="300"/>
</CaretValue>
<CaretValue index="1" Format="1">
<Coordinate value="600"/>
</CaretValue>
</LigGlyph>
</LigCaretList>
</GDEF>
</ttFont>