Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support variable feature syntax #2432

Merged
merged 22 commits into from Oct 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6810cf5
Parse variable scalars in feature files.
simoncozens Mar 18, 2021
3fd6358
Build variable scalars for anchors/value records
simoncozens Mar 18, 2021
c73de1a
Variable scalar tests
simoncozens Mar 18, 2021
0c13412
Support variable scalars in pos A V (...);
simoncozens Mar 20, 2021
a32d7ae
Format bug
simoncozens May 18, 2021
be66887
Useful property
simoncozens May 20, 2021
803dc38
Add support for the conditionset statement
simoncozens May 20, 2021
98215d3
Parse variation blocks
simoncozens May 20, 2021
46527aa
Round-trip FEA correctly
simoncozens May 20, 2021
32ee444
Store the conditional features in a dictionary until we work out what…
simoncozens May 20, 2021
0cb89cc
Rearrange featureVars so we can do *really* raw feature builds (by lo…
simoncozens May 24, 2021
699ffe0
Oops, leftover when testing something else
simoncozens May 24, 2021
087848d
A more generic interface, taking either GSUB or GPOS table
simoncozens May 24, 2021
0de9ce6
Normalize condition sets when storing them
simoncozens May 24, 2021
0031326
feature_variations_ is a better name than conditionalFeatures_
simoncozens May 24, 2021
c87399b
Support building feature variations using the "condition" statement.
simoncozens May 24, 2021
389b0ea
Put count fields in ttx
simoncozens May 24, 2021
6937cc3
Set count fields when building feature variations
simoncozens May 24, 2021
bd8ba64
Allow float values in condition sets.
simoncozens May 25, 2021
ddc7658
Correct indentation
simoncozens Oct 28, 2021
8f06c0e
Address feedback
simoncozens Oct 28, 2021
eab87ed
Run black on all touched files
simoncozens Oct 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
91 changes: 89 additions & 2 deletions Lib/fontTools/feaLib/ast.py
Expand Up @@ -34,6 +34,7 @@
"ChainContextPosStatement",
"ChainContextSubstStatement",
"CharacterStatement",
"ConditionsetStatement",
"CursivePosStatement",
"ElidedFallbackName",
"ElidedFallbackNameID",
Expand Down Expand Up @@ -1261,11 +1262,21 @@ def build(self, builder):
if not self.replacement and hasattr(self.glyph, "glyphSet"):
for glyph in self.glyph.glyphSet():
builder.add_multiple_subst(
self.location, prefix, glyph, suffix, self.replacement, self.forceChain
self.location,
prefix,
glyph,
suffix,
self.replacement,
self.forceChain,
)
else:
builder.add_multiple_subst(
self.location, prefix, self.glyph, suffix, self.replacement, self.forceChain
self.location,
prefix,
self.glyph,
suffix,
self.replacement,
self.forceChain,
)

def asFea(self, indent=""):
Expand Down Expand Up @@ -2033,3 +2044,79 @@ def asFea(self, res=""):
res += f"location {self.tag} "
res += f"{' '.join(str(i) for i in self.values)};\n"
return res


class ConditionsetStatement(Statement):
"""
A variable layout conditionset

Args:
name (str): the name of this conditionset
conditions (dict): a dictionary mapping axis tags to a
tuple of (min,max) userspace coordinates.
"""

def __init__(self, name, conditions, location=None):
Statement.__init__(self, location)
self.name = name
self.conditions = conditions

def build(self, builder):
builder.add_conditionset(self.name, self.conditions)

def asFea(self, res="", indent=""):
res += indent + f"conditionset {self.name} " + "{\n"
for tag, (minvalue, maxvalue) in self.conditions.items():
res += indent + SHIFT + f"{tag} {minvalue} {maxvalue};\n"
res += indent + "}" + f" {self.name};\n"
return res


class VariationBlock(Block):
"""A variation feature block, applicable in a given set of conditions."""

def __init__(self, name, conditionset, use_extension=False, location=None):
Block.__init__(self, location)
self.name, self.conditionset, self.use_extension = (
name,
conditionset,
use_extension,
)

def build(self, builder):
"""Call the ``start_feature`` callback on the builder object, visit
all the statements in this feature, and then call ``end_feature``."""
builder.start_feature(self.location, self.name)
if (
self.conditionset != "NULL"
and self.conditionset not in builder.conditionsets_
):
raise FeatureLibError(
f"variation block used undefined conditionset {self.conditionset}",
self.location,
)

# language exclude_dflt statements modify builder.features_
# limit them to this block with temporary builder.features_
features = builder.features_
builder.features_ = {}
Block.build(self, builder)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not immediately clear what's going on here, why is the features_ attributes temporarily swapped with an empty dict. Maybe add a comment

for key, value in builder.features_.items():
items = builder.feature_variations_.setdefault(key, {}).setdefault(
self.conditionset, []
)
items.extend(value)
if key not in features:
features[key] = [] # Ensure we make a feature record
builder.features_ = features
builder.end_feature()

def asFea(self, indent=""):
res = indent + "variation %s " % self.name.strip()
res += self.conditionset + " "
if self.use_extension:
res += "useExtension "
res += "{\n"
res += Block.asFea(self, indent=indent)
res += indent + "} %s;\n" % self.name.strip()
return res