Merge pull request #1747 from justvanrossum/rclt_issue1625

[designspaceLib] [varLib] Allow FeatureVariations to be processed *after* other features
This commit is contained in:
Just van Rossum 2019-10-21 16:08:04 +02:00 committed by GitHub
commit d96c92f95e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 356 additions and 31 deletions

View File

@ -358,7 +358,7 @@ class BaseDocWriter(object):
def __init__(self, documentPath, documentObject): def __init__(self, documentPath, documentObject):
self.path = documentPath self.path = documentPath
self.documentObject = documentObject self.documentObject = documentObject
self.documentVersion = "4.0" self.documentVersion = "4.1"
self.root = ET.Element("designspace") self.root = ET.Element("designspace")
self.root.attrib['format'] = self.documentVersion self.root.attrib['format'] = self.documentVersion
self._axes = [] # for use by the writer only self._axes = [] # for use by the writer only
@ -371,7 +371,11 @@ class BaseDocWriter(object):
self._addAxis(axisObject) self._addAxis(axisObject)
if self.documentObject.rules: 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: for ruleObject in self.documentObject.rules:
self._addRule(ruleObject) self._addRule(ruleObject)
@ -675,6 +679,14 @@ class BaseDocReader(LogMixin):
def readRules(self): def readRules(self):
# we also need to read any conditions that are outside of a condition set. # we also need to read any conditions that are outside of a condition set.
rules = [] 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"): for ruleElement in self.root.findall(".rules/rule"):
ruleObject = self.ruleDescriptorClass() ruleObject = self.ruleDescriptorClass()
ruleName = ruleObject.name = ruleElement.attrib.get("name") ruleName = ruleObject.name = ruleElement.attrib.get("name")
@ -996,6 +1008,7 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
self.instances = [] self.instances = []
self.axes = [] self.axes = []
self.rules = [] self.rules = []
self.rulesProcessingLast = False
self.default = None # name of the default master self.default = None # name of the default master
self.lib = {} self.lib = {}

View File

@ -613,7 +613,7 @@ def _merge_OTL(font, model, master_fonts, axisTags):
font['GPOS'].table.remap_device_varidxes(varidx_map) 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): def normalize(name, value):
return models.normalizeLocation( return models.normalizeLocation(
@ -648,7 +648,11 @@ def _add_GSUB_feature_variations(font, axes, internal_axis_supports, rules):
conditional_subs.append((region, subs)) conditional_subs.append((region, subs))
addFeatureVariations(font, conditional_subs) if rulesProcessingLast:
featureTag = 'rclt'
else:
featureTag = 'rvrn'
addFeatureVariations(font, conditional_subs, featureTag)
_DesignSpaceData = namedtuple( _DesignSpaceData = namedtuple(
@ -661,6 +665,7 @@ _DesignSpaceData = namedtuple(
"masters", "masters",
"instances", "instances",
"rules", "rules",
"rulesProcessingLast",
], ],
) )
@ -761,6 +766,7 @@ def load_designspace(designspace):
masters, masters,
instances, instances,
ds.rules, ds.rules,
ds.rulesProcessingLast,
) )
@ -865,7 +871,7 @@ def build(designspace, master_finder=lambda s:s, exclude=[], optimize=True):
if 'cvar' not in exclude and 'glyf' in vf: if 'cvar' not in exclude and 'glyf' in vf:
_merge_TTHinting(vf, model, master_fonts) _merge_TTHinting(vf, model, master_fonts)
if 'GSUB' not in exclude and ds.rules: 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: if 'CFF2' not in exclude and 'CFF ' in vf:
_add_CFF2(vf, model, master_fonts) _add_CFF2(vf, model, master_fonts)
if "post" in vf: if "post" in vf:

View File

@ -11,7 +11,7 @@ from fontTools.otlLib.builder import buildLookup, buildSingleSubstSubtable
from collections import OrderedDict from collections import OrderedDict
def addFeatureVariations(font, conditionalSubstitutions): def addFeatureVariations(font, conditionalSubstitutions, featureTag='rvrn'):
"""Add conditional substitutions to a Variable Font. """Add conditional substitutions to a Variable Font.
The `conditionalSubstitutions` argument is a list of (Region, Substitutions) The `conditionalSubstitutions` argument is a list of (Region, Substitutions)
@ -43,7 +43,8 @@ def addFeatureVariations(font, conditionalSubstitutions):
""" """
addFeatureVariationsRaw(font, addFeatureVariationsRaw(font,
overlayFeatureVariations(conditionalSubstitutions)) overlayFeatureVariations(conditionalSubstitutions),
featureTag)
def overlayFeatureVariations(conditionalSubstitutions): def overlayFeatureVariations(conditionalSubstitutions):
"""Compute overlaps between all conditional substitutions. """Compute overlaps between all conditional substitutions.
@ -255,15 +256,15 @@ def cleanupBox(box):
# Low level implementation # Low level implementation
# #
def addFeatureVariationsRaw(font, conditionalSubstitutions): def addFeatureVariationsRaw(font, conditionalSubstitutions, featureTag='rvrn'):
"""Low level implementation of addFeatureVariations that directly """Low level implementation of addFeatureVariations that directly
models the possibilities of the FeatureVariations table.""" models the possibilities of the FeatureVariations table."""
# #
# assert there is no 'rvrn' feature # if there is no <featureTag> feature:
# make dummy 'rvrn' feature with no lookups # make empty <featureTag> feature
# sort features, get 'rvrn' feature index # sort features, get <featureTag> feature index
# add 'rvrn' feature to all scripts # add <featureTag> feature to all scripts
# make lookups # make lookups
# add feature variations # add feature variations
# #
@ -278,20 +279,25 @@ def addFeatureVariationsRaw(font, conditionalSubstitutions):
gsub.FeatureVariations = None # delete any existing FeatureVariations gsub.FeatureVariations = None # delete any existing FeatureVariations
for feature in gsub.FeatureList.FeatureRecord: varFeatureIndices = []
assert feature.FeatureTag != 'rvrn' for index, feature in enumerate(gsub.FeatureList.FeatureRecord):
if feature.FeatureTag == featureTag:
varFeatureIndices.append(index)
rvrnFeature = buildFeatureRecord('rvrn', []) if not varFeatureIndices:
gsub.FeatureList.FeatureRecord.append(rvrnFeature) varFeature = buildFeatureRecord(featureTag, [])
gsub.FeatureList.FeatureCount = len(gsub.FeatureList.FeatureRecord) gsub.FeatureList.FeatureRecord.append(varFeature)
gsub.FeatureList.FeatureCount = len(gsub.FeatureList.FeatureRecord)
sortFeatureList(gsub) sortFeatureList(gsub)
rvrnFeatureIndex = gsub.FeatureList.FeatureRecord.index(rvrnFeature) varFeatureIndex = gsub.FeatureList.FeatureRecord.index(varFeature)
for scriptRecord in gsub.ScriptList.ScriptRecord: for scriptRecord in gsub.ScriptList.ScriptRecord:
langSystems = [lsr.LangSys for lsr in scriptRecord.Script.LangSysRecord] langSystems = [lsr.LangSys for lsr in scriptRecord.Script.LangSysRecord]
for langSys in [scriptRecord.Script.DefaultLangSys] + langSystems: for langSys in [scriptRecord.Script.DefaultLangSys] + langSystems:
langSys.FeatureIndex.append(rvrnFeatureIndex) langSys.FeatureIndex.append(varFeatureIndex)
varFeatureIndices = [varFeatureIndex]
# setup lookups # setup lookups
@ -311,8 +317,11 @@ def addFeatureVariationsRaw(font, conditionalSubstitutions):
conditionTable.append(ct) conditionTable.append(ct)
lookupIndices = [lookupMap[subst] for subst in substitutions] lookupIndices = [lookupMap[subst] for subst in substitutions]
record = buildFeatureTableSubstitutionRecord(rvrnFeatureIndex, lookupIndices) records = []
featureVariationRecords.append(buildFeatureVariationRecord(conditionTable, [record])) 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) gsub.FeatureVariations = buildFeatureVariations(featureVariationRecords)

View File

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<designspace format="4.0"> <designspace format="4.1">
<axes> <axes>
<axis tag="wght" name="weight" minimum="0" maximum="1000" default="0"> <axis tag="wght" name="weight" minimum="0" maximum="1000" default="0">
<labelname xml:lang="en">Wéíght</labelname> <labelname xml:lang="en">Wéíght</labelname>
@ -13,7 +13,7 @@
<map input="1000" output="990"/> <map input="1000" output="990"/>
</axis> </axis>
</axes> </axes>
<rules> <rules processing="last">
<rule name="named.rule.1"> <rule name="named.rule.1">
<conditionset> <conditionset>
<condition name="axisName_a" minimum="0" maximum="1"/> <condition name="axisName_a" minimum="0" maximum="1"/>

View File

@ -49,6 +49,7 @@ def test_fill_document(tmpdir):
instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo") instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo")
instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo") instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")
doc = DesignSpaceDocument() doc = DesignSpaceDocument()
doc.rulesProcessingLast = True
# write some axes # write some axes
a1 = AxisDescriptor() a1 = AxisDescriptor()
@ -698,6 +699,7 @@ def test_rulesDocument(tmpdir):
testDocPath = os.path.join(tmpdir, "testRules.designspace") testDocPath = os.path.join(tmpdir, "testRules.designspace")
testDocPath2 = os.path.join(tmpdir, "testRules_roundtrip.designspace") testDocPath2 = os.path.join(tmpdir, "testRules_roundtrip.designspace")
doc = DesignSpaceDocument() doc = DesignSpaceDocument()
doc.rulesProcessingLast = True
a1 = AxisDescriptor() a1 = AxisDescriptor()
a1.minimum = 0 a1.minimum = 0
a1.maximum = 1000 a1.maximum = 1000
@ -741,6 +743,7 @@ def test_rulesDocument(tmpdir):
_addUnwrappedCondition(testDocPath) _addUnwrappedCondition(testDocPath)
doc2 = DesignSpaceDocument() doc2 = DesignSpaceDocument()
doc2.read(testDocPath) doc2.read(testDocPath)
assert doc2.rulesProcessingLast
assert len(doc2.axes) == 2 assert len(doc2.axes) == 2
assert len(doc2.rules) == 1 assert len(doc2.rules) == 1
assert len(doc2.rules[0].conditionSets) == 2 assert len(doc2.rules[0].conditionSets) == 2

View File

@ -6,7 +6,7 @@
<labelname xml:lang="en">Contrast</labelname> <labelname xml:lang="en">Contrast</labelname>
</axis> </axis>
</axes> </axes>
<rules> <rules processing="last">
<rule name="dollar-stroke"> <rule name="dollar-stroke">
<conditionset> <conditionset>
<condition name="weight" minimum="500" /> <!-- intentionally omitted maximum --> <condition name="weight" minimum="500" /> <!-- intentionally omitted maximum -->

View File

@ -43,7 +43,7 @@
<FeatureList> <FeatureList>
<!-- FeatureCount=1 --> <!-- FeatureCount=1 -->
<FeatureRecord index="0"> <FeatureRecord index="0">
<FeatureTag value="rvrn"/> <FeatureTag value="rclt"/>
<Feature> <Feature>
<!-- LookupCount=0 --> <!-- LookupCount=0 -->
</Feature> </Feature>

View File

@ -0,0 +1,249 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.29">
<fvar>
<!-- Weight -->
<Axis>
<AxisTag>wght</AxisTag>
<Flags>0x0</Flags>
<MinValue>0.0</MinValue>
<DefaultValue>368.0</DefaultValue>
<MaxValue>1000.0</MaxValue>
<AxisNameID>256</AxisNameID>
</Axis>
<!-- Contrast -->
<Axis>
<AxisTag>cntr</AxisTag>
<Flags>0x0</Flags>
<MinValue>0.0</MinValue>
<DefaultValue>0.0</DefaultValue>
<MaxValue>100.0</MaxValue>
<AxisNameID>257</AxisNameID>
</Axis>
</fvar>
<GSUB>
<Version value="0x00010001"/>
<ScriptList>
<!-- ScriptCount=1 -->
<ScriptRecord index="0">
<ScriptTag value="latn"/>
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=1 -->
<FeatureIndex index="0" value="1"/>
</DefaultLangSys>
<!-- LangSysCount=1 -->
<LangSysRecord index="0">
<LangSysTag value="NLD "/>
<LangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=1 -->
<FeatureIndex index="0" value="0"/>
</LangSys>
</LangSysRecord>
</Script>
</ScriptRecord>
</ScriptList>
<FeatureList>
<!-- FeatureCount=2 -->
<FeatureRecord index="0">
<FeatureTag value="rclt"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="0"/>
</Feature>
</FeatureRecord>
<FeatureRecord index="1">
<FeatureTag value="rclt"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="1"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
<!-- LookupCount=5 -->
<Lookup index="0">
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SingleSubst index="0" Format="1">
<Substitution in="uni0041" out="uni0061"/>
</SingleSubst>
</Lookup>
<Lookup index="1">
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SingleSubst index="0" Format="1">
<Substitution in="uni0041" out="uni0061"/>
</SingleSubst>
</Lookup>
<Lookup index="2">
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SingleSubst index="0" Format="1">
<Substitution in="uni0024" out="uni0024.nostroke"/>
</SingleSubst>
</Lookup>
<Lookup index="3">
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SingleSubst index="0" Format="1">
<Substitution in="uni0041" out="uni0061"/>
</SingleSubst>
</Lookup>
<Lookup index="4">
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SingleSubst index="0" Format="1">
<Substitution in="uni0061" out="uni0041"/>
</SingleSubst>
</Lookup>
</LookupList>
<FeatureVariations>
<Version value="0x00010000"/>
<!-- FeatureVariationCount=4 -->
<FeatureVariationRecord index="0">
<ConditionSet>
<!-- ConditionCount=2 -->
<ConditionTable index="0" Format="1">
<AxisIndex value="1"/>
<FilterRangeMinValue value="0.75"/>
<FilterRangeMaxValue value="1.0"/>
</ConditionTable>
<ConditionTable index="1" Format="1">
<AxisIndex value="0"/>
<FilterRangeMinValue value="0.20886"/>
<FilterRangeMaxValue value="1.0"/>
</ConditionTable>
</ConditionSet>
<FeatureTableSubstitution>
<Version value="0x00010000"/>
<!-- SubstitutionCount=2 -->
<SubstitutionRecord index="0">
<FeatureIndex value="0"/>
<Feature>
<!-- LookupCount=3 -->
<LookupListIndex index="0" value="0"/>
<LookupListIndex index="1" value="2"/>
<LookupListIndex index="2" value="3"/>
</Feature>
</SubstitutionRecord>
<SubstitutionRecord index="1">
<FeatureIndex value="1"/>
<Feature>
<!-- LookupCount=3 -->
<LookupListIndex index="0" value="1"/>
<LookupListIndex index="1" value="2"/>
<LookupListIndex index="2" value="3"/>
</Feature>
</SubstitutionRecord>
</FeatureTableSubstitution>
</FeatureVariationRecord>
<FeatureVariationRecord index="1">
<ConditionSet>
<!-- ConditionCount=2 -->
<ConditionTable index="0" Format="1">
<AxisIndex value="1"/>
<FilterRangeMinValue value="0.0"/>
<FilterRangeMaxValue value="0.25"/>
</ConditionTable>
<ConditionTable index="1" Format="1">
<AxisIndex value="0"/>
<FilterRangeMinValue value="-1.0"/>
<FilterRangeMaxValue value="-0.45654"/>
</ConditionTable>
</ConditionSet>
<FeatureTableSubstitution>
<Version value="0x00010000"/>
<!-- SubstitutionCount=2 -->
<SubstitutionRecord index="0">
<FeatureIndex value="0"/>
<Feature>
<!-- LookupCount=2 -->
<LookupListIndex index="0" value="0"/>
<LookupListIndex index="1" value="4"/>
</Feature>
</SubstitutionRecord>
<SubstitutionRecord index="1">
<FeatureIndex value="1"/>
<Feature>
<!-- LookupCount=2 -->
<LookupListIndex index="0" value="1"/>
<LookupListIndex index="1" value="4"/>
</Feature>
</SubstitutionRecord>
</FeatureTableSubstitution>
</FeatureVariationRecord>
<FeatureVariationRecord index="2">
<ConditionSet>
<!-- ConditionCount=1 -->
<ConditionTable index="0" Format="1">
<AxisIndex value="1"/>
<FilterRangeMinValue value="0.75"/>
<FilterRangeMaxValue value="1.0"/>
</ConditionTable>
</ConditionSet>
<FeatureTableSubstitution>
<Version value="0x00010000"/>
<!-- SubstitutionCount=2 -->
<SubstitutionRecord index="0">
<FeatureIndex value="0"/>
<Feature>
<!-- LookupCount=2 -->
<LookupListIndex index="0" value="0"/>
<LookupListIndex index="1" value="3"/>
</Feature>
</SubstitutionRecord>
<SubstitutionRecord index="1">
<FeatureIndex value="1"/>
<Feature>
<!-- LookupCount=2 -->
<LookupListIndex index="0" value="1"/>
<LookupListIndex index="1" value="3"/>
</Feature>
</SubstitutionRecord>
</FeatureTableSubstitution>
</FeatureVariationRecord>
<FeatureVariationRecord index="3">
<ConditionSet>
<!-- ConditionCount=1 -->
<ConditionTable index="0" Format="1">
<AxisIndex value="0"/>
<FilterRangeMinValue value="0.20886"/>
<FilterRangeMaxValue value="1.0"/>
</ConditionTable>
</ConditionSet>
<FeatureTableSubstitution>
<Version value="0x00010000"/>
<!-- SubstitutionCount=2 -->
<SubstitutionRecord index="0">
<FeatureIndex value="0"/>
<Feature>
<!-- LookupCount=2 -->
<LookupListIndex index="0" value="0"/>
<LookupListIndex index="1" value="2"/>
</Feature>
</SubstitutionRecord>
<SubstitutionRecord index="1">
<FeatureIndex value="1"/>
<Feature>
<!-- LookupCount=2 -->
<LookupListIndex index="0" value="1"/>
<LookupListIndex index="1" value="2"/>
</Feature>
</SubstitutionRecord>
</FeatureTableSubstitution>
</FeatureVariationRecord>
</FeatureVariations>
</GSUB>
</ttFont>

View File

@ -7,6 +7,7 @@ from fontTools.varLib import set_default_weight_width_slant
from fontTools.designspaceLib import ( from fontTools.designspaceLib import (
DesignSpaceDocumentError, DesignSpaceDocument, SourceDescriptor, DesignSpaceDocumentError, DesignSpaceDocument, SourceDescriptor,
) )
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
import difflib import difflib
import os import os
import shutil import shutil
@ -107,7 +108,8 @@ class BuildTest(unittest.TestCase):
return font, savepath return font, savepath
def _run_varlib_build_test(self, designspace_name, font_name, tables, def _run_varlib_build_test(self, designspace_name, font_name, tables,
expected_ttx_name, save_before_dump=False): expected_ttx_name, save_before_dump=False,
post_process_master=None):
suffix = '.ttf' suffix = '.ttf'
ds_path = self.get_test_input(designspace_name + '.designspace') ds_path = self.get_test_input(designspace_name + '.designspace')
ufo_dir = self.get_test_input('master_ufo') ufo_dir = self.get_test_input('master_ufo')
@ -116,7 +118,9 @@ class BuildTest(unittest.TestCase):
self.temp_dir() self.temp_dir()
ttx_paths = self.get_file_list(ttx_dir, '.ttx', font_name + '-') ttx_paths = self.get_file_list(ttx_dir, '.ttx', font_name + '-')
for path in ttx_paths: for path in ttx_paths:
self.compile_font(path, suffix, self.tempdir) font, savepath = self.compile_font(path, suffix, self.tempdir)
if post_process_master is not None:
post_process_master(font, savepath)
finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix) finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
varfont, model, _ = build(ds_path, finder) varfont, model, _ = build(ds_path, finder)
@ -213,6 +217,47 @@ class BuildTest(unittest.TestCase):
save_before_dump=True, save_before_dump=True,
) )
def test_varlib_build_feature_variations_with_existing_rclt(self):
"""Designspace file contains <rules> element, used to build GSUB
FeatureVariations table. <rules> is specified to do its OT processing
"last", so a 'rclt' feature will be used or created. This test covers
the case when a 'rclt' already exists in the masters.
We dynamically add a 'rclt' feature to an existing set of test
masters, to avoid adding more test data.
The multiple languages are done to verify whether multiple existing
'rclt' features are updated correctly.
"""
def add_rclt(font, savepath):
features = """
languagesystem DFLT dflt;
languagesystem latn dflt;
languagesystem latn NLD;
feature rclt {
script latn;
language NLD;
lookup A {
sub uni0041 by uni0061;
} A;
language dflt;
lookup B {
sub uni0041 by uni0061;
} B;
} rclt;
"""
addOpenTypeFeaturesFromString(font, features)
font.save(savepath)
self._run_varlib_build_test(
designspace_name="FeatureVars",
font_name="TestFamily",
tables=["fvar", "GSUB"],
expected_ttx_name="FeatureVars_rclt",
save_before_dump=True,
post_process_master=add_rclt,
)
def test_varlib_gvar_explicit_delta(self): def test_varlib_gvar_explicit_delta(self):
"""The variable font contains a composite glyph odieresis which does not """The variable font contains a composite glyph odieresis which does not
need a gvar entry, because all its deltas are 0, but it must be added need a gvar entry, because all its deltas are 0, but it must be added