Allow 'sub X by NULL;' sequence to delete a glyph

This commit is contained in:
Simon Cozens 2021-02-03 14:12:46 +00:00
parent 728258d66f
commit d1e85cb888
5 changed files with 81 additions and 8 deletions

View File

@ -188,6 +188,21 @@ class Comment(Element):
return self.text
class NullGlyph(Expression):
"""The NULL glyph, used in glyph deletion substitutions."""
def __init__(self, location=None):
Expression.__init__(self, location)
#: The name itself as a string
def glyphSet(self):
"""The glyphs in this class as a tuple of :class:`GlyphName` objects."""
return ()
def asFea(self, indent=""):
return "NULL"
class GlyphName(Expression):
"""A single glyph name, such as ``cedilla``."""
@ -1246,8 +1261,9 @@ class MultipleSubstStatement(Statement):
res += " " + " ".join(map(asFea, self.suffix))
else:
res += asFea(self.glyph)
replacement = self.replacement or [ NullGlyph() ]
res += " by "
res += " ".join(map(asFea, self.replacement))
res += " ".join(map(asFea, replacement))
res += ";"
return res

View File

@ -314,10 +314,15 @@ class Parser(object):
location,
)
def parse_glyphclass_(self, accept_glyphname):
def parse_glyphclass_(self, accept_glyphname, accept_null=False):
# Parses a glyph class, either named or anonymous, or (if
# ``bool(accept_glyphname)``) a glyph name.
# ``bool(accept_glyphname)``) a glyph name. If ``bool(accept_null)`` then
# also accept the special NULL glyph.
if accept_glyphname and self.next_token_type_ in (Lexer.NAME, Lexer.CID):
if accept_null and self.next_token_ == "NULL":
# If you want a glyph called NULL, you should escape it.
self.advance_lexer_()
return self.ast.NullGlyph(location=self.cur_token_location_)
glyph = self.expect_glyph_()
self.check_glyph_name_in_glyph_set(glyph)
return self.ast.GlyphName(glyph, location=self.cur_token_location_)
@ -375,7 +380,8 @@ class Parser(object):
self.expect_symbol_("-")
range_end = self.expect_cid_()
self.check_glyph_name_in_glyph_set(
f"cid{range_start:05d}", f"cid{range_end:05d}",
f"cid{range_start:05d}",
f"cid{range_end:05d}",
)
glyphs.add_cid_range(
range_start,
@ -804,7 +810,7 @@ class Parser(object):
if self.next_token_ == "by":
keyword = self.expect_keyword_("by")
while self.next_token_ != ";":
gc = self.parse_glyphclass_(accept_glyphname=True)
gc = self.parse_glyphclass_(accept_glyphname=True, accept_null=True)
new.append(gc)
elif self.next_token_ == "from":
keyword = self.expect_keyword_("from")
@ -837,6 +843,9 @@ class Parser(object):
num_lookups = len([l for l in lookups if l is not None])
if len(new) == 1 and len(new[0].glyphSet()) == 0:
new = [] # Deletion
# GSUB lookup type 1: Single substitution.
# Format A: "substitute a by a.sc;"
# Format B: "substitute [one.fitted one.oldstyle] by one;"
@ -863,8 +872,10 @@ class Parser(object):
not reverse
and len(old) == 1
and len(old[0].glyphSet()) == 1
and len(new) > 1
and max([len(n.glyphSet()) for n in new]) == 1
and (
(len(new) > 1 and max([len(n.glyphSet()) for n in new]) == 1)
or len(new) == 0
)
and num_lookups == 0
):
return self.ast.MultipleSubstStatement(

View File

@ -73,7 +73,7 @@ class BuilderTest(unittest.TestCase):
LigatureSubtable AlternateSubtable MultipleSubstSubtable
SingleSubstSubtable aalt_chain_contextual_subst AlternateChained
MultipleLookupsPerGlyph MultipleLookupsPerGlyph2 GSUB_6_formats
GSUB_5_formats
GSUB_5_formats delete_glyph
""".split()
def __init__(self, methodName):

View File

@ -0,0 +1,3 @@
feature test {
sub a by NULL;
} test;

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont>
<GSUB>
<Version value="0x00010000"/>
<ScriptList>
<!-- ScriptCount=1 -->
<ScriptRecord index="0">
<ScriptTag value="DFLT"/>
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=1 -->
<FeatureIndex index="0" value="0"/>
</DefaultLangSys>
<!-- LangSysCount=0 -->
</Script>
</ScriptRecord>
</ScriptList>
<FeatureList>
<!-- FeatureCount=1 -->
<FeatureRecord index="0">
<FeatureTag value="test"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="0"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MultipleSubst index="0">
<Substitution in="a" out=""/>
</MultipleSubst>
</Lookup>
</LookupList>
</GSUB>
</ttFont>