[feaLib] Implement the LigatureCaretByPos statement
This commit is contained in:
parent
4daf0601b2
commit
a44d8c5364
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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))
|
||||
|
@ -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_("<")
|
||||
|
@ -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
|
||||
|
4
Lib/fontTools/feaLib/testdata/LigatureCaretByPos.fea
vendored
Normal file
4
Lib/fontTools/feaLib/testdata/LigatureCaretByPos.fea
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
table GDEF {
|
||||
LigatureCaretByPos [c_h c_k] 500;
|
||||
LigatureCaretByPos f_f_i 600 300;
|
||||
} GDEF;
|
37
Lib/fontTools/feaLib/testdata/LigatureCaretByPos.ttx
vendored
Normal file
37
Lib/fontTools/feaLib/testdata/LigatureCaretByPos.ttx
vendored
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user