diff options
Diffstat (limited to 'Lib/fontTools/feaLib/builder.py')
-rw-r--r-- | Lib/fontTools/feaLib/builder.py | 493 |
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 |