aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/feaLib/builder.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/fontTools/feaLib/builder.py')
-rw-r--r--Lib/fontTools/feaLib/builder.py493
1 files changed, 333 insertions, 160 deletions
diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py
index 4a7d9575..a1644875 100644
--- a/Lib/fontTools/feaLib/builder.py
+++ b/Lib/fontTools/feaLib/builder.py
@@ -1,6 +1,5 @@
-from fontTools.misc.py23 import Tag, tostr
from fontTools.misc import sstruct
-from fontTools.misc.textTools import binary2num, safeEval
+from fontTools.misc.textTools import Tag, tostr, binary2num, safeEval
from fontTools.feaLib.error import FeatureLibError
from fontTools.feaLib.lookupDebugInfo import (
LookupDebugInfo,
@@ -9,6 +8,7 @@ from fontTools.feaLib.lookupDebugInfo import (
)
from fontTools.feaLib.parser import Parser
from fontTools.feaLib.ast import FeatureFile
+from fontTools.feaLib.variableScalar import VariableScalar
from fontTools.otlLib import builder as otl
from fontTools.otlLib.maxContextCalc import maxCtxFont
from fontTools.ttLib import newTable, getTableModule
@@ -31,6 +31,10 @@ from fontTools.otlLib.builder import (
ChainContextualRule,
)
from fontTools.otlLib.error import OpenTypeLibError
+from fontTools.varLib.varStore import OnlineVarStoreBuilder
+from fontTools.varLib.builder import buildVarDevTable
+from fontTools.varLib.featureVars import addFeatureVariationsRaw
+from fontTools.varLib.models import normalizeValue
from collections import defaultdict
import itertools
from io import StringIO
@@ -112,6 +116,12 @@ class Builder(object):
else:
self.parseTree, self.file = None, featurefile
self.glyphMap = font.getReverseGlyphMap()
+ self.varstorebuilder = None
+ if "fvar" in font:
+ self.axes = font["fvar"].axes
+ self.varstorebuilder = OnlineVarStoreBuilder(
+ [ax.axisTag for ax in self.axes]
+ )
self.default_language_systems_ = set()
self.script_ = None
self.lookupflag_ = 0
@@ -126,6 +136,7 @@ class Builder(object):
self.lookup_locations = {"GSUB": {}, "GPOS": {}}
self.features_ = {} # ('latn', 'DEU ', 'smcp') --> [LookupBuilder*]
self.required_features_ = {} # ('latn', 'DEU ') --> 'scmp'
+ self.feature_variations_ = {}
# for feature 'aalt'
self.aalt_features_ = [] # [(location, featureName)*], for 'aalt'
self.aalt_location_ = None
@@ -163,6 +174,8 @@ class Builder(object):
self.vhea_ = {}
# for table 'STAT'
self.stat_ = {}
+ # for conditionsets
+ self.conditionsets_ = {}
def build(self, tables=None, debug=False):
if self.parseTree is None:
@@ -198,6 +211,8 @@ class Builder(object):
if tag not in tables:
continue
table = self.makeTable(tag)
+ if self.feature_variations_:
+ self.makeFeatureVariations(table, tag)
if (
table.ScriptList.ScriptCount > 0
or table.FeatureList.FeatureCount > 0
@@ -215,6 +230,8 @@ class Builder(object):
self.font["GDEF"] = gdef
elif "GDEF" in self.font:
del self.font["GDEF"]
+ elif self.varstorebuilder:
+ raise FeatureLibError("Must save GDEF when compiling a variable font")
if "BASE" in tables:
base = self.buildBASE()
if base:
@@ -745,6 +762,16 @@ class Builder(object):
gdef.MarkAttachClassDef = self.buildGDEFMarkAttachClassDef_()
gdef.MarkGlyphSetsDef = self.buildGDEFMarkGlyphSetsDef_()
gdef.Version = 0x00010002 if gdef.MarkGlyphSetsDef else 0x00010000
+ if self.varstorebuilder:
+ store = self.varstorebuilder.finish()
+ if store.VarData:
+ gdef.Version = 0x00010003
+ gdef.VarStore = store
+ varidx_map = store.optimize()
+
+ gdef.remap_device_varidxes(varidx_map)
+ if 'GPOS' in self.font:
+ self.font['GPOS'].table.remap_device_varidxes(varidx_map)
if any(
(
gdef.GlyphClassDef,
@@ -753,7 +780,7 @@ class Builder(object):
gdef.MarkAttachClassDef,
gdef.MarkGlyphSetsDef,
)
- ):
+ ) or hasattr(gdef, "VarStore"):
result = newTable("GDEF")
result.table = gdef
return result
@@ -849,7 +876,8 @@ class Builder(object):
)
size_feature = tag == "GPOS" and feature_tag == "size"
- if len(lookup_indices) == 0 and not size_feature:
+ force_feature = self.any_feature_variations(feature_tag, tag)
+ if len(lookup_indices) == 0 and not size_feature and not force_feature:
continue
for ix in lookup_indices:
@@ -915,6 +943,42 @@ class Builder(object):
table.LookupList.LookupCount = len(table.LookupList.Lookup)
return table
+ def makeFeatureVariations(self, table, table_tag):
+ feature_vars = {}
+ has_any_variations = False
+ # Sort out which lookups to build, gather their indices
+ for (
+ script_,
+ language,
+ feature_tag,
+ ), variations in self.feature_variations_.items():
+ feature_vars[feature_tag] = []
+ for conditionset, builders in variations.items():
+ raw_conditionset = self.conditionsets_[conditionset]
+ indices = []
+ for b in builders:
+ if b.table != table_tag:
+ continue
+ assert b.lookup_index is not None
+ indices.append(b.lookup_index)
+ has_any_variations = True
+ feature_vars[feature_tag].append((raw_conditionset, indices))
+
+ if has_any_variations:
+ for feature_tag, conditions_and_lookups in feature_vars.items():
+ addFeatureVariationsRaw(
+ self.font, table, conditions_and_lookups, feature_tag
+ )
+
+ def any_feature_variations(self, feature_tag, table_tag):
+ for (_, _, feature), variations in self.feature_variations_.items():
+ if feature != feature_tag:
+ continue
+ for conditionset, builders in variations.items():
+ if any(b.table == table_tag for b in builders):
+ return True
+ return False
+
def get_lookup_name_(self, lookup):
rev = {v: k for k, v in self.named_lookups_.items()}
if lookup in rev:
@@ -1005,7 +1069,8 @@ class Builder(object):
assert lookup_name in self.named_lookups_, lookup_name
self.cur_lookup_ = None
lookup = self.named_lookups_[lookup_name]
- self.add_lookup_to_feature_(lookup, self.cur_feature_name_)
+ if lookup is not None: # skip empty named lookup
+ self.add_lookup_to_feature_(lookup, self.cur_feature_name_)
def set_font_revision(self, location, revision):
self.fontRevision_ = revision
@@ -1130,39 +1195,6 @@ class Builder(object):
for glyph in glyphs:
self.attachPoints_.setdefault(glyph, set()).update(contourPoints)
- def add_chain_context_pos(self, location, prefix, glyphs, suffix, lookups):
- lookup = self.get_lookup_(location, ChainContextPosBuilder)
- lookup.rules.append(
- ChainContextualRule(
- prefix, glyphs, suffix, self.find_lookup_builders_(lookups)
- )
- )
-
- def add_chain_context_subst(self, location, prefix, glyphs, suffix, lookups):
- lookup = self.get_lookup_(location, ChainContextSubstBuilder)
- lookup.rules.append(
- ChainContextualRule(
- prefix, glyphs, suffix, self.find_lookup_builders_(lookups)
- )
- )
-
- def add_alternate_subst(self, location, prefix, glyph, suffix, replacement):
- if self.cur_feature_name_ == "aalt":
- alts = self.aalt_alternates_.setdefault(glyph, set())
- alts.update(replacement)
- return
- if prefix or suffix:
- chain = self.get_lookup_(location, ChainContextSubstBuilder)
- lookup = self.get_chained_lookup_(location, AlternateSubstBuilder)
- chain.rules.append(ChainContextualRule(prefix, [{glyph}], suffix, [lookup]))
- else:
- lookup = self.get_lookup_(location, AlternateSubstBuilder)
- if glyph in lookup.alternates:
- raise FeatureLibError(
- 'Already defined alternates for glyph "%s"' % glyph, location
- )
- lookup.alternates[glyph] = replacement
-
def add_feature_reference(self, location, featureName):
if self.cur_feature_name_ != "aalt":
raise FeatureLibError(
@@ -1207,24 +1239,38 @@ class Builder(object):
key = (script, lang, self.cur_feature_name_)
self.features_.setdefault(key, [])
- def add_ligature_subst(
- self, location, prefix, glyphs, suffix, replacement, forceChain
- ):
- if prefix or suffix or forceChain:
- chain = self.get_lookup_(location, ChainContextSubstBuilder)
- lookup = self.get_chained_lookup_(location, LigatureSubstBuilder)
- chain.rules.append(ChainContextualRule(prefix, glyphs, suffix, [lookup]))
- else:
- lookup = self.get_lookup_(location, LigatureSubstBuilder)
+ # GSUB rules
- # OpenType feature file syntax, section 5.d, "Ligature substitution":
- # "Since the OpenType specification does not allow ligature
- # substitutions to be specified on target sequences that contain
- # glyph classes, the implementation software will enumerate
- # all specific glyph sequences if glyph classes are detected"
- for g in sorted(itertools.product(*glyphs)):
- lookup.ligatures[g] = replacement
+ # GSUB 1
+ def add_single_subst(self, location, prefix, suffix, mapping, forceChain):
+ if self.cur_feature_name_ == "aalt":
+ for (from_glyph, to_glyph) in mapping.items():
+ alts = self.aalt_alternates_.setdefault(from_glyph, set())
+ alts.add(to_glyph)
+ return
+ if prefix or suffix or forceChain:
+ self.add_single_subst_chained_(location, prefix, suffix, mapping)
+ return
+ lookup = self.get_lookup_(location, SingleSubstBuilder)
+ for (from_glyph, to_glyph) in mapping.items():
+ if from_glyph in lookup.mapping:
+ if to_glyph == lookup.mapping[from_glyph]:
+ log.info(
+ "Removing duplicate single substitution from glyph"
+ ' "%s" to "%s" at %s',
+ from_glyph,
+ to_glyph,
+ location,
+ )
+ else:
+ raise FeatureLibError(
+ 'Already defined rule for replacing glyph "%s" by "%s"'
+ % (from_glyph, lookup.mapping[from_glyph]),
+ location,
+ )
+ lookup.mapping[from_glyph] = to_glyph
+ # GSUB 2
def add_multiple_subst(
self, location, prefix, glyph, suffix, replacements, forceChain=False
):
@@ -1250,39 +1296,61 @@ class Builder(object):
)
lookup.mapping[glyph] = replacements
- def add_reverse_chain_single_subst(self, location, old_prefix, old_suffix, mapping):
- lookup = self.get_lookup_(location, ReverseChainSingleSubstBuilder)
- lookup.rules.append((old_prefix, old_suffix, mapping))
-
- def add_single_subst(self, location, prefix, suffix, mapping, forceChain):
+ # GSUB 3
+ def add_alternate_subst(self, location, prefix, glyph, suffix, replacement):
if self.cur_feature_name_ == "aalt":
- for (from_glyph, to_glyph) in mapping.items():
- alts = self.aalt_alternates_.setdefault(from_glyph, set())
- alts.add(to_glyph)
+ alts = self.aalt_alternates_.setdefault(glyph, set())
+ alts.update(replacement)
return
+ if prefix or suffix:
+ chain = self.get_lookup_(location, ChainContextSubstBuilder)
+ lookup = self.get_chained_lookup_(location, AlternateSubstBuilder)
+ chain.rules.append(ChainContextualRule(prefix, [{glyph}], suffix, [lookup]))
+ else:
+ lookup = self.get_lookup_(location, AlternateSubstBuilder)
+ if glyph in lookup.alternates:
+ raise FeatureLibError(
+ 'Already defined alternates for glyph "%s"' % glyph, location
+ )
+ # We allow empty replacement glyphs here.
+ lookup.alternates[glyph] = replacement
+
+ # GSUB 4
+ def add_ligature_subst(
+ self, location, prefix, glyphs, suffix, replacement, forceChain
+ ):
if prefix or suffix or forceChain:
- self.add_single_subst_chained_(location, prefix, suffix, mapping)
- return
- lookup = self.get_lookup_(location, SingleSubstBuilder)
- for (from_glyph, to_glyph) in mapping.items():
- if from_glyph in lookup.mapping:
- if to_glyph == lookup.mapping[from_glyph]:
- log.info(
- "Removing duplicate single substitution from glyph"
- ' "%s" to "%s" at %s',
- from_glyph,
- to_glyph,
- location,
- )
- else:
- raise FeatureLibError(
- 'Already defined rule for replacing glyph "%s" by "%s"'
- % (from_glyph, lookup.mapping[from_glyph]),
- location,
- )
- lookup.mapping[from_glyph] = to_glyph
+ chain = self.get_lookup_(location, ChainContextSubstBuilder)
+ lookup = self.get_chained_lookup_(location, LigatureSubstBuilder)
+ chain.rules.append(ChainContextualRule(prefix, glyphs, suffix, [lookup]))
+ else:
+ lookup = self.get_lookup_(location, LigatureSubstBuilder)
+
+ if not all(glyphs):
+ raise FeatureLibError("Empty glyph class in substitution", location)
+
+ # OpenType feature file syntax, section 5.d, "Ligature substitution":
+ # "Since the OpenType specification does not allow ligature
+ # substitutions to be specified on target sequences that contain
+ # glyph classes, the implementation software will enumerate
+ # all specific glyph sequences if glyph classes are detected"
+ for g in sorted(itertools.product(*glyphs)):
+ lookup.ligatures[g] = replacement
+
+ # GSUB 5/6
+ def add_chain_context_subst(self, location, prefix, glyphs, suffix, lookups):
+ if not all(glyphs) or not all(prefix) or not all(suffix):
+ raise FeatureLibError("Empty glyph class in contextual substitution", location)
+ lookup = self.get_lookup_(location, ChainContextSubstBuilder)
+ lookup.rules.append(
+ ChainContextualRule(
+ prefix, glyphs, suffix, self.find_lookup_builders_(lookups)
+ )
+ )
def add_single_subst_chained_(self, location, prefix, suffix, mapping):
+ if not mapping or not all(prefix) or not all(suffix):
+ raise FeatureLibError("Empty glyph class in contextual substitution", location)
# https://github.com/fonttools/fonttools/issues/512
chain = self.get_lookup_(location, ChainContextSubstBuilder)
sub = chain.find_chainable_single_subst(set(mapping.keys()))
@@ -1293,91 +1361,115 @@ class Builder(object):
ChainContextualRule(prefix, [list(mapping.keys())], suffix, [sub])
)
+ # GSUB 8
+ def add_reverse_chain_single_subst(self, location, old_prefix, old_suffix, mapping):
+ if not mapping:
+ raise FeatureLibError("Empty glyph class in substitution", location)
+ lookup = self.get_lookup_(location, ReverseChainSingleSubstBuilder)
+ lookup.rules.append((old_prefix, old_suffix, mapping))
+
+ # GPOS rules
+
+ # GPOS 1
+ def add_single_pos(self, location, prefix, suffix, pos, forceChain):
+ if prefix or suffix or forceChain:
+ self.add_single_pos_chained_(location, prefix, suffix, pos)
+ else:
+ lookup = self.get_lookup_(location, SinglePosBuilder)
+ for glyphs, value in pos:
+ if not glyphs:
+ raise FeatureLibError("Empty glyph class in positioning rule", location)
+ otValueRecord = self.makeOpenTypeValueRecord(location, value, pairPosContext=False)
+ for glyph in glyphs:
+ try:
+ lookup.add_pos(location, glyph, otValueRecord)
+ except OpenTypeLibError as e:
+ raise FeatureLibError(str(e), e.location) from e
+
+ # GPOS 2
+ def add_class_pair_pos(self, location, glyphclass1, value1, glyphclass2, value2):
+ if not glyphclass1 or not glyphclass2:
+ raise FeatureLibError(
+ "Empty glyph class in positioning rule", location
+ )
+ lookup = self.get_lookup_(location, PairPosBuilder)
+ v1 = self.makeOpenTypeValueRecord(location, value1, pairPosContext=True)
+ v2 = self.makeOpenTypeValueRecord(location, value2, pairPosContext=True)
+ lookup.addClassPair(location, glyphclass1, v1, glyphclass2, v2)
+
+ def add_specific_pair_pos(self, location, glyph1, value1, glyph2, value2):
+ if not glyph1 or not glyph2:
+ raise FeatureLibError("Empty glyph class in positioning rule", location)
+ lookup = self.get_lookup_(location, PairPosBuilder)
+ v1 = self.makeOpenTypeValueRecord(location, value1, pairPosContext=True)
+ v2 = self.makeOpenTypeValueRecord(location, value2, pairPosContext=True)
+ lookup.addGlyphPair(location, glyph1, v1, glyph2, v2)
+
+ # GPOS 3
def add_cursive_pos(self, location, glyphclass, entryAnchor, exitAnchor):
+ if not glyphclass:
+ raise FeatureLibError("Empty glyph class in positioning rule", location)
lookup = self.get_lookup_(location, CursivePosBuilder)
lookup.add_attachment(
location,
glyphclass,
- makeOpenTypeAnchor(entryAnchor),
- makeOpenTypeAnchor(exitAnchor),
+ self.makeOpenTypeAnchor(location, entryAnchor),
+ self.makeOpenTypeAnchor(location, exitAnchor),
)
- def add_marks_(self, location, lookupBuilder, marks):
- """Helper for add_mark_{base,liga,mark}_pos."""
- for _, markClass in marks:
- for markClassDef in markClass.definitions:
- for mark in markClassDef.glyphs.glyphSet():
- if mark not in lookupBuilder.marks:
- otMarkAnchor = makeOpenTypeAnchor(markClassDef.anchor)
- lookupBuilder.marks[mark] = (markClass.name, otMarkAnchor)
- else:
- existingMarkClass = lookupBuilder.marks[mark][0]
- if markClass.name != existingMarkClass:
- raise FeatureLibError(
- "Glyph %s cannot be in both @%s and @%s"
- % (mark, existingMarkClass, markClass.name),
- location,
- )
-
+ # GPOS 4
def add_mark_base_pos(self, location, bases, marks):
builder = self.get_lookup_(location, MarkBasePosBuilder)
self.add_marks_(location, builder, marks)
+ if not bases:
+ raise FeatureLibError("Empty glyph class in positioning rule", location)
for baseAnchor, markClass in marks:
- otBaseAnchor = makeOpenTypeAnchor(baseAnchor)
+ otBaseAnchor = self.makeOpenTypeAnchor(location, baseAnchor)
for base in bases:
builder.bases.setdefault(base, {})[markClass.name] = otBaseAnchor
+ # GPOS 5
def add_mark_lig_pos(self, location, ligatures, components):
builder = self.get_lookup_(location, MarkLigPosBuilder)
componentAnchors = []
+ if not ligatures:
+ raise FeatureLibError("Empty glyph class in positioning rule", location)
for marks in components:
anchors = {}
self.add_marks_(location, builder, marks)
for ligAnchor, markClass in marks:
- anchors[markClass.name] = makeOpenTypeAnchor(ligAnchor)
+ anchors[markClass.name] = self.makeOpenTypeAnchor(location, ligAnchor)
componentAnchors.append(anchors)
for glyph in ligatures:
builder.ligatures[glyph] = componentAnchors
+ # GPOS 6
def add_mark_mark_pos(self, location, baseMarks, marks):
builder = self.get_lookup_(location, MarkMarkPosBuilder)
self.add_marks_(location, builder, marks)
+ if not baseMarks:
+ raise FeatureLibError("Empty glyph class in positioning rule", location)
for baseAnchor, markClass in marks:
- otBaseAnchor = makeOpenTypeAnchor(baseAnchor)
+ otBaseAnchor = self.makeOpenTypeAnchor(location, baseAnchor)
for baseMark in baseMarks:
builder.baseMarks.setdefault(baseMark, {})[
markClass.name
] = otBaseAnchor
- def add_class_pair_pos(self, location, glyphclass1, value1, glyphclass2, value2):
- lookup = self.get_lookup_(location, PairPosBuilder)
- v1 = makeOpenTypeValueRecord(value1, pairPosContext=True)
- v2 = makeOpenTypeValueRecord(value2, pairPosContext=True)
- lookup.addClassPair(location, glyphclass1, v1, glyphclass2, v2)
-
- def add_subtable_break(self, location):
- self.cur_lookup_.add_subtable_break(location)
-
- def add_specific_pair_pos(self, location, glyph1, value1, glyph2, value2):
- lookup = self.get_lookup_(location, PairPosBuilder)
- v1 = makeOpenTypeValueRecord(value1, pairPosContext=True)
- v2 = makeOpenTypeValueRecord(value2, pairPosContext=True)
- lookup.addGlyphPair(location, glyph1, v1, glyph2, v2)
-
- def add_single_pos(self, location, prefix, suffix, pos, forceChain):
- if prefix or suffix or forceChain:
- self.add_single_pos_chained_(location, prefix, suffix, pos)
- else:
- lookup = self.get_lookup_(location, SinglePosBuilder)
- for glyphs, value in pos:
- otValueRecord = makeOpenTypeValueRecord(value, pairPosContext=False)
- for glyph in glyphs:
- try:
- lookup.add_pos(location, glyph, otValueRecord)
- except OpenTypeLibError as e:
- raise FeatureLibError(str(e), e.location) from e
+ # GPOS 7/8
+ def add_chain_context_pos(self, location, prefix, glyphs, suffix, lookups):
+ if not all(glyphs) or not all(prefix) or not all(suffix):
+ raise FeatureLibError("Empty glyph class in contextual positioning rule", location)
+ lookup = self.get_lookup_(location, ChainContextPosBuilder)
+ lookup.rules.append(
+ ChainContextualRule(
+ prefix, glyphs, suffix, self.find_lookup_builders_(lookups)
+ )
+ )
def add_single_pos_chained_(self, location, prefix, suffix, pos):
+ if not pos or not all(prefix) or not all(suffix):
+ raise FeatureLibError("Empty glyph class in contextual positioning rule", location)
# https://github.com/fonttools/fonttools/issues/514
chain = self.get_lookup_(location, ChainContextPosBuilder)
targets = []
@@ -1388,7 +1480,7 @@ class Builder(object):
if value is None:
subs.append(None)
continue
- otValue = makeOpenTypeValueRecord(value, pairPosContext=False)
+ otValue = self.makeOpenTypeValueRecord(location, value, pairPosContext=False)
sub = chain.find_chainable_single_pos(targets, glyphs, otValue)
if sub is None:
sub = self.get_chained_lookup_(location, SinglePosBuilder)
@@ -1401,6 +1493,26 @@ class Builder(object):
ChainContextualRule(prefix, [g for g, v in pos], suffix, subs)
)
+ def add_marks_(self, location, lookupBuilder, marks):
+ """Helper for add_mark_{base,liga,mark}_pos."""
+ for _, markClass in marks:
+ for markClassDef in markClass.definitions:
+ for mark in markClassDef.glyphs.glyphSet():
+ if mark not in lookupBuilder.marks:
+ otMarkAnchor = self.makeOpenTypeAnchor(location, markClassDef.anchor)
+ lookupBuilder.marks[mark] = (markClass.name, otMarkAnchor)
+ else:
+ existingMarkClass = lookupBuilder.marks[mark][0]
+ if markClass.name != existingMarkClass:
+ raise FeatureLibError(
+ "Glyph %s cannot be in both @%s and @%s"
+ % (mark, existingMarkClass, markClass.name),
+ location,
+ )
+
+ def add_subtable_break(self, location):
+ self.cur_lookup_.add_subtable_break(location)
+
def setGlyphClass_(self, location, glyph, glyphClass):
oldClass, oldLocation = self.glyphClassDefs_.get(glyph, (None, None))
if oldClass and oldClass != glyphClass:
@@ -1445,37 +1557,98 @@ class Builder(object):
def add_vhea_field(self, key, value):
self.vhea_[key] = value
+ def add_conditionset(self, key, value):
+ if not "fvar" in self.font:
+ raise FeatureLibError(
+ "Cannot add feature variations to a font without an 'fvar' table"
+ )
+
+ # Normalize
+ axisMap = {
+ axis.axisTag: (axis.minValue, axis.defaultValue, axis.maxValue)
+ for axis in self.axes
+ }
-def makeOpenTypeAnchor(anchor):
- """ast.Anchor --> otTables.Anchor"""
- if anchor is None:
- return None
- deviceX, deviceY = None, None
- if anchor.xDeviceTable is not None:
- deviceX = otl.buildDevice(dict(anchor.xDeviceTable))
- if anchor.yDeviceTable is not None:
- deviceY = otl.buildDevice(dict(anchor.yDeviceTable))
- return otl.buildAnchor(anchor.x, anchor.y, anchor.contourpoint, deviceX, deviceY)
+ value = {
+ tag: (
+ normalizeValue(bottom, axisMap[tag]),
+ normalizeValue(top, axisMap[tag]),
+ )
+ for tag, (bottom, top) in value.items()
+ }
+
+ self.conditionsets_[key] = value
+
+ def makeOpenTypeAnchor(self, location, anchor):
+ """ast.Anchor --> otTables.Anchor"""
+ if anchor is None:
+ return None
+ variable = False
+ deviceX, deviceY = None, None
+ if anchor.xDeviceTable is not None:
+ deviceX = otl.buildDevice(dict(anchor.xDeviceTable))
+ if anchor.yDeviceTable is not None:
+ deviceY = otl.buildDevice(dict(anchor.yDeviceTable))
+ for dim in ("x", "y"):
+ if not isinstance(getattr(anchor, dim), VariableScalar):
+ continue
+ if getattr(anchor, dim+"DeviceTable") is not None:
+ raise FeatureLibError("Can't define a device coordinate and variable scalar", location)
+ if not self.varstorebuilder:
+ raise FeatureLibError("Can't define a variable scalar in a non-variable font", location)
+ varscalar = getattr(anchor,dim)
+ varscalar.axes = self.axes
+ default, index = varscalar.add_to_variation_store(self.varstorebuilder)
+ setattr(anchor, dim, default)
+ if index is not None and index != 0xFFFFFFFF:
+ if dim == "x":
+ deviceX = buildVarDevTable(index)
+ else:
+ deviceY = buildVarDevTable(index)
+ variable = True
+ otlanchor = otl.buildAnchor(anchor.x, anchor.y, anchor.contourpoint, deviceX, deviceY)
+ if variable:
+ otlanchor.Format = 3
+ return otlanchor
-_VALUEREC_ATTRS = {
- name[0].lower() + name[1:]: (name, isDevice)
- for _, name, isDevice, _ in otBase.valueRecordFormat
- if not name.startswith("Reserved")
-}
+ _VALUEREC_ATTRS = {
+ name[0].lower() + name[1:]: (name, isDevice)
+ for _, name, isDevice, _ in otBase.valueRecordFormat
+ if not name.startswith("Reserved")
+ }
-def makeOpenTypeValueRecord(v, pairPosContext):
- """ast.ValueRecord --> otBase.ValueRecord"""
- if not v:
- return None
+ def makeOpenTypeValueRecord(self, location, v, pairPosContext):
+ """ast.ValueRecord --> otBase.ValueRecord"""
+ if not v:
+ return None
+
+ vr = {}
+ variable = False
+ for astName, (otName, isDevice) in self._VALUEREC_ATTRS.items():
+ val = getattr(v, astName, None)
+ if not val:
+ continue
+ if isDevice:
+ vr[otName] = otl.buildDevice(dict(val))
+ elif isinstance(val, VariableScalar):
+ otDeviceName = otName[0:4] + "Device"
+ feaDeviceName = otDeviceName[0].lower() + otDeviceName[1:]
+ if getattr(v, feaDeviceName):
+ raise FeatureLibError("Can't define a device coordinate and variable scalar", location)
+ if not self.varstorebuilder:
+ raise FeatureLibError("Can't define a variable scalar in a non-variable font", location)
+ val.axes = self.axes
+ default, index = val.add_to_variation_store(self.varstorebuilder)
+ vr[otName] = default
+ if index is not None and index != 0xFFFFFFFF:
+ vr[otDeviceName] = buildVarDevTable(index)
+ variable = True
+ else:
+ vr[otName] = val
- vr = {}
- for astName, (otName, isDevice) in _VALUEREC_ATTRS.items():
- val = getattr(v, astName, None)
- if val:
- vr[otName] = otl.buildDevice(dict(val)) if isDevice else val
- if pairPosContext and not vr:
- vr = {"YAdvance": 0} if v.vertical else {"XAdvance": 0}
- valRec = otl.buildValue(vr)
- return valRec
+ if pairPosContext and not vr:
+ vr = {"YAdvance": 0} if v.vertical else {"XAdvance": 0}
+ valRec = otl.buildValue(vr)
+ return valRec