Skip to content

Commit

Permalink
Merge pull request #1747 from justvanrossum/rclt_issue1625
Browse files Browse the repository at this point in the history
[designspaceLib] [varLib] Allow FeatureVariations to be processed *after* other features
  • Loading branch information
justvanrossum committed Oct 21, 2019
2 parents 021820f + 76b8517 commit d96c92f
Show file tree
Hide file tree
Showing 9 changed files with 356 additions and 31 deletions.
17 changes: 15 additions & 2 deletions Lib/fontTools/designspaceLib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ def getRuleDescriptor(cls):
def __init__(self, documentPath, documentObject):
self.path = documentPath
self.documentObject = documentObject
self.documentVersion = "4.0"
self.documentVersion = "4.1"
self.root = ET.Element("designspace")
self.root.attrib['format'] = self.documentVersion
self._axes = [] # for use by the writer only
Expand All @@ -371,7 +371,11 @@ def write(self, pretty=True, encoding="UTF-8", xml_declaration=True):
self._addAxis(axisObject)

if self.documentObject.rules:
self.root.append(ET.Element("rules"))
if getattr(self.documentObject, "rulesProcessingLast", False):
attributes = {"processing": "last"}
else:
attributes = {}
self.root.append(ET.Element("rules", attributes))
for ruleObject in self.documentObject.rules:
self._addRule(ruleObject)

Expand Down Expand Up @@ -675,6 +679,14 @@ def read(self):
def readRules(self):
# we also need to read any conditions that are outside of a condition set.
rules = []
rulesElement = self.root.find(".rules")
if rulesElement is not None:
processingValue = rulesElement.attrib.get("processing", "first")
if processingValue not in {"first", "last"}:
raise DesignSpaceDocumentError(
"<rules> processing attribute value is not valid: %r, "
"expected 'first' or 'last'" % processingValue)
self.documentObject.rulesProcessingLast = processingValue == "last"
for ruleElement in self.root.findall(".rules/rule"):
ruleObject = self.ruleDescriptorClass()
ruleName = ruleObject.name = ruleElement.attrib.get("name")
Expand Down Expand Up @@ -996,6 +1008,7 @@ def __init__(self, readerClass=None, writerClass=None):
self.instances = []
self.axes = []
self.rules = []
self.rulesProcessingLast = False
self.default = None # name of the default master

self.lib = {}
Expand Down
12 changes: 9 additions & 3 deletions Lib/fontTools/varLib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ def _merge_OTL(font, model, master_fonts, axisTags):
font['GPOS'].table.remap_device_varidxes(varidx_map)


def _add_GSUB_feature_variations(font, axes, internal_axis_supports, rules):
def _add_GSUB_feature_variations(font, axes, internal_axis_supports, rules, rulesProcessingLast):

def normalize(name, value):
return models.normalizeLocation(
Expand Down Expand Up @@ -648,7 +648,11 @@ def normalize(name, value):

conditional_subs.append((region, subs))

addFeatureVariations(font, conditional_subs)
if rulesProcessingLast:
featureTag = 'rclt'
else:
featureTag = 'rvrn'
addFeatureVariations(font, conditional_subs, featureTag)


_DesignSpaceData = namedtuple(
Expand All @@ -661,6 +665,7 @@ def normalize(name, value):
"masters",
"instances",
"rules",
"rulesProcessingLast",
],
)

Expand Down Expand Up @@ -761,6 +766,7 @@ def load_designspace(designspace):
masters,
instances,
ds.rules,
ds.rulesProcessingLast,
)


Expand Down Expand Up @@ -865,7 +871,7 @@ def build(designspace, master_finder=lambda s:s, exclude=[], optimize=True):
if 'cvar' not in exclude and 'glyf' in vf:
_merge_TTHinting(vf, model, master_fonts)
if 'GSUB' not in exclude and ds.rules:
_add_GSUB_feature_variations(vf, ds.axes, ds.internal_axis_supports, ds.rules)
_add_GSUB_feature_variations(vf, ds.axes, ds.internal_axis_supports, ds.rules, ds.rulesProcessingLast)
if 'CFF2' not in exclude and 'CFF ' in vf:
_add_CFF2(vf, model, master_fonts)
if "post" in vf:
Expand Down
49 changes: 29 additions & 20 deletions Lib/fontTools/varLib/featureVars.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from collections import OrderedDict


def addFeatureVariations(font, conditionalSubstitutions):
def addFeatureVariations(font, conditionalSubstitutions, featureTag='rvrn'):
"""Add conditional substitutions to a Variable Font.
The `conditionalSubstitutions` argument is a list of (Region, Substitutions)
Expand Down Expand Up @@ -43,7 +43,8 @@ def addFeatureVariations(font, conditionalSubstitutions):
"""

addFeatureVariationsRaw(font,
overlayFeatureVariations(conditionalSubstitutions))
overlayFeatureVariations(conditionalSubstitutions),
featureTag)

def overlayFeatureVariations(conditionalSubstitutions):
"""Compute overlaps between all conditional substitutions.
Expand Down Expand Up @@ -255,15 +256,15 @@ def cleanupBox(box):
# Low level implementation
#

def addFeatureVariationsRaw(font, conditionalSubstitutions):
def addFeatureVariationsRaw(font, conditionalSubstitutions, featureTag='rvrn'):
"""Low level implementation of addFeatureVariations that directly
models the possibilities of the FeatureVariations table."""

#
# assert there is no 'rvrn' feature
# make dummy 'rvrn' feature with no lookups
# sort features, get 'rvrn' feature index
# add 'rvrn' feature to all scripts
# if there is no <featureTag> feature:
# make empty <featureTag> feature
# sort features, get <featureTag> feature index
# add <featureTag> feature to all scripts
# make lookups
# add feature variations
#
Expand All @@ -278,20 +279,25 @@ def addFeatureVariationsRaw(font, conditionalSubstitutions):

gsub.FeatureVariations = None # delete any existing FeatureVariations

for feature in gsub.FeatureList.FeatureRecord:
assert feature.FeatureTag != 'rvrn'
varFeatureIndices = []
for index, feature in enumerate(gsub.FeatureList.FeatureRecord):
if feature.FeatureTag == featureTag:
varFeatureIndices.append(index)

rvrnFeature = buildFeatureRecord('rvrn', [])
gsub.FeatureList.FeatureRecord.append(rvrnFeature)
gsub.FeatureList.FeatureCount = len(gsub.FeatureList.FeatureRecord)
if not varFeatureIndices:
varFeature = buildFeatureRecord(featureTag, [])
gsub.FeatureList.FeatureRecord.append(varFeature)
gsub.FeatureList.FeatureCount = len(gsub.FeatureList.FeatureRecord)

sortFeatureList(gsub)
rvrnFeatureIndex = gsub.FeatureList.FeatureRecord.index(rvrnFeature)
sortFeatureList(gsub)
varFeatureIndex = gsub.FeatureList.FeatureRecord.index(varFeature)

for scriptRecord in gsub.ScriptList.ScriptRecord:
langSystems = [lsr.LangSys for lsr in scriptRecord.Script.LangSysRecord]
for langSys in [scriptRecord.Script.DefaultLangSys] + langSystems:
langSys.FeatureIndex.append(rvrnFeatureIndex)
for scriptRecord in gsub.ScriptList.ScriptRecord:
langSystems = [lsr.LangSys for lsr in scriptRecord.Script.LangSysRecord]
for langSys in [scriptRecord.Script.DefaultLangSys] + langSystems:
langSys.FeatureIndex.append(varFeatureIndex)

varFeatureIndices = [varFeatureIndex]

# setup lookups

Expand All @@ -311,8 +317,11 @@ def addFeatureVariationsRaw(font, conditionalSubstitutions):
conditionTable.append(ct)

lookupIndices = [lookupMap[subst] for subst in substitutions]
record = buildFeatureTableSubstitutionRecord(rvrnFeatureIndex, lookupIndices)
featureVariationRecords.append(buildFeatureVariationRecord(conditionTable, [record]))
records = []
for varFeatureIndex in varFeatureIndices:
existingLookupIndices = gsub.FeatureList.FeatureRecord[varFeatureIndex].Feature.LookupListIndex
records.append(buildFeatureTableSubstitutionRecord(varFeatureIndex, existingLookupIndices + lookupIndices))
featureVariationRecords.append(buildFeatureVariationRecord(conditionTable, records))

gsub.FeatureVariations = buildFeatureVariations(featureVariationRecords)

Expand Down
4 changes: 2 additions & 2 deletions Tests/designspaceLib/data/test.designspace
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<designspace format="4.0">
<designspace format="4.1">
<axes>
<axis tag="wght" name="weight" minimum="0" maximum="1000" default="0">
<labelname xml:lang="en">Wéíght</labelname>
Expand All @@ -13,7 +13,7 @@
<map input="1000" output="990"/>
</axis>
</axes>
<rules>
<rules processing="last">
<rule name="named.rule.1">
<conditionset>
<condition name="axisName_a" minimum="0" maximum="1"/>
Expand Down
3 changes: 3 additions & 0 deletions Tests/designspaceLib/designspace_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def test_fill_document(tmpdir):
instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo")
instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")
doc = DesignSpaceDocument()
doc.rulesProcessingLast = True

# write some axes
a1 = AxisDescriptor()
Expand Down Expand Up @@ -698,6 +699,7 @@ def test_rulesDocument(tmpdir):
testDocPath = os.path.join(tmpdir, "testRules.designspace")
testDocPath2 = os.path.join(tmpdir, "testRules_roundtrip.designspace")
doc = DesignSpaceDocument()
doc.rulesProcessingLast = True
a1 = AxisDescriptor()
a1.minimum = 0
a1.maximum = 1000
Expand Down Expand Up @@ -741,6 +743,7 @@ def test_rulesDocument(tmpdir):
_addUnwrappedCondition(testDocPath)
doc2 = DesignSpaceDocument()
doc2.read(testDocPath)
assert doc2.rulesProcessingLast
assert len(doc2.axes) == 2
assert len(doc2.rules) == 1
assert len(doc2.rules[0].conditionSets) == 2
Expand Down
2 changes: 1 addition & 1 deletion Tests/varLib/data/FeatureVars.designspace
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<labelname xml:lang="en">Contrast</labelname>
</axis>
</axes>
<rules>
<rules processing="last">
<rule name="dollar-stroke">
<conditionset>
<condition name="weight" minimum="500" /> <!-- intentionally omitted maximum -->
Expand Down
2 changes: 1 addition & 1 deletion Tests/varLib/data/test_results/FeatureVars.ttx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
<FeatureList>
<!-- FeatureCount=1 -->
<FeatureRecord index="0">
<FeatureTag value="rvrn"/>
<FeatureTag value="rclt"/>
<Feature>
<!-- LookupCount=0 -->
</Feature>
Expand Down
Loading

0 comments on commit d96c92f

Please sign in to comment.