diff options
Diffstat (limited to 'Lib/fontTools/feaLib')
-rw-r--r-- | Lib/fontTools/feaLib/ast.py | 50 | ||||
-rw-r--r-- | Lib/fontTools/feaLib/builder.py | 171 | ||||
-rw-r--r-- | Lib/fontTools/feaLib/lexer.py | 8 | ||||
-rw-r--r-- | Lib/fontTools/feaLib/lookupDebugInfo.py | 3 | ||||
-rw-r--r-- | Lib/fontTools/feaLib/parser.py | 89 | ||||
-rw-r--r-- | Lib/fontTools/feaLib/variableScalar.py | 37 |
6 files changed, 226 insertions, 132 deletions
diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py index 1273343d..17c6cc3f 100644 --- a/Lib/fontTools/feaLib/ast.py +++ b/Lib/fontTools/feaLib/ast.py @@ -912,14 +912,11 @@ class IgnoreSubstStatement(Statement): contexts = [] for prefix, glyphs, suffix in self.chainContexts: res = "" - if len(prefix) or len(suffix): - if len(prefix): - res += " ".join(map(asFea, prefix)) + " " - res += " ".join(g.asFea() + "'" for g in glyphs) - if len(suffix): - res += " " + " ".join(map(asFea, suffix)) - else: - res += " ".join(map(asFea, glyphs)) + if len(prefix): + res += " ".join(map(asFea, prefix)) + " " + res += " ".join(g.asFea() + "'" for g in glyphs) + if len(suffix): + res += " " + " ".join(map(asFea, suffix)) contexts.append(res) return "ignore sub " + ", ".join(contexts) + ";" @@ -1259,25 +1256,34 @@ class MultipleSubstStatement(Statement): """Calls the builder object's ``add_multiple_subst`` callback.""" prefix = [p.glyphSet() for p in self.prefix] suffix = [s.glyphSet() for s in self.suffix] - if not self.replacement and hasattr(self.glyph, "glyphSet"): - for glyph in self.glyph.glyphSet(): + if hasattr(self.glyph, "glyphSet"): + originals = self.glyph.glyphSet() + else: + originals = [self.glyph] + count = len(originals) + replaces = [] + for r in self.replacement: + if hasattr(r, "glyphSet"): + replace = r.glyphSet() + else: + replace = [r] + if len(replace) == 1 and len(replace) != count: + replace = replace * count + replaces.append(replace) + replaces = list(zip(*replaces)) + + seen_originals = set() + for i, original in enumerate(originals): + if original not in seen_originals: + seen_originals.add(original) builder.add_multiple_subst( self.location, prefix, - glyph, + original, suffix, - self.replacement, + replaces and replaces[i] or (), self.forceChain, ) - else: - builder.add_multiple_subst( - self.location, - prefix, - self.glyph, - suffix, - self.replacement, - self.forceChain, - ) def asFea(self, indent=""): res = "sub " @@ -2068,7 +2074,7 @@ class ConditionsetStatement(Statement): self.conditions = conditions def build(self, builder): - builder.add_conditionset(self.name, self.conditions) + builder.add_conditionset(self.location, self.name, self.conditions) def asFea(self, res="", indent=""): res += indent + f"conditionset {self.name} " + "{\n" diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py index 0a991761..cfaf54d4 100644 --- a/Lib/fontTools/feaLib/builder.py +++ b/Lib/fontTools/feaLib/builder.py @@ -34,7 +34,7 @@ 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 fontTools.varLib.models import normalizeValue, piecewiseLinearMap from collections import defaultdict import itertools from io import StringIO @@ -90,7 +90,6 @@ def addOpenTypeFeaturesFromString( class Builder(object): - supportedTables = frozenset( Tag(tag) for tag in [ @@ -176,6 +175,10 @@ class Builder(object): self.stat_ = {} # for conditionsets self.conditionsets_ = {} + # We will often use exactly the same locations (i.e. the font's masters) + # for a large number of variable scalars. Instead of creating a model + # for each, let's share the models. + self.model_cache = {} def build(self, tables=None, debug=False): if self.parseTree is None: @@ -290,9 +293,8 @@ class Builder(object): ] # "aalt" does not have to specify its own lookups, but it might. if not feature and name != "aalt": - raise FeatureLibError( - "Feature %s has not been defined" % name, location - ) + warnings.warn("%s: Feature %s has not been defined" % (location, name)) + continue for script, lang, feature, lookups in feature: for lookuplist in lookups: if not isinstance(lookuplist, list): @@ -446,6 +448,7 @@ class Builder(object): assert self.cv_parameters_ids_[tag] is not None nameID = self.cv_parameters_ids_[tag] table.setName(string, nameID, platformID, platEncID, langID) + table.names.sort() def build_OS_2(self): if not self.os2_: @@ -768,8 +771,9 @@ class Builder(object): 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 "GPOS" in self.font: + self.font["GPOS"].table.remap_device_varidxes(varidx_map) + self.model_cache.clear() if any( ( gdef.GlyphClassDef, @@ -840,10 +844,15 @@ class Builder(object): feature=None, ) lookups.append(lookup) - try: - otLookups = [l.build() for l in lookups] - except OpenTypeLibError as e: - raise FeatureLibError(str(e), e.location) from e + otLookups = [] + for l in lookups: + try: + otLookups.append(l.build()) + except OpenTypeLibError as e: + raise FeatureLibError(str(e), e.location) from e + except Exception as e: + location = self.lookup_locations[tag][str(l.lookup_index)].location + raise FeatureLibError(str(e), location) from e return otLookups def makeTable(self, tag): @@ -945,11 +954,7 @@ class Builder(object): 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(): + for (_, _, feature_tag), variations in self.feature_variations_.items(): feature_vars[feature_tag] = [] for conditionset, builders in variations.items(): raw_conditionset = self.conditionsets_[conditionset] @@ -1242,7 +1247,7 @@ class Builder(object): # 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(): + for from_glyph, to_glyph in mapping.items(): alts = self.aalt_alternates_.setdefault(from_glyph, set()) alts.add(to_glyph) return @@ -1250,7 +1255,7 @@ class Builder(object): self.add_single_subst_chained_(location, prefix, suffix, mapping) return lookup = self.get_lookup_(location, SingleSubstBuilder) - for (from_glyph, to_glyph) in mapping.items(): + for from_glyph, to_glyph in mapping.items(): if from_glyph in lookup.mapping: if to_glyph == lookup.mapping[from_glyph]: log.info( @@ -1338,7 +1343,9 @@ class Builder(object): # 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) + raise FeatureLibError( + "Empty glyph class in contextual substitution", location + ) lookup = self.get_lookup_(location, ChainContextSubstBuilder) lookup.rules.append( ChainContextualRule( @@ -1348,10 +1355,13 @@ class Builder(object): 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) + raise FeatureLibError( + "Empty glyph class in contextual substitution", location + ) # https://github.com/fonttools/fonttools/issues/512 + # https://github.com/fonttools/fonttools/issues/2150 chain = self.get_lookup_(location, ChainContextSubstBuilder) - sub = chain.find_chainable_single_subst(set(mapping.keys())) + sub = chain.find_chainable_single_subst(mapping) if sub is None: sub = self.get_chained_lookup_(location, SingleSubstBuilder) sub.mapping.update(mapping) @@ -1376,8 +1386,12 @@ class Builder(object): 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) + 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) @@ -1387,9 +1401,7 @@ class Builder(object): # 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 - ) + 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) @@ -1457,7 +1469,9 @@ class Builder(object): # 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) + raise FeatureLibError( + "Empty glyph class in contextual positioning rule", location + ) lookup = self.get_lookup_(location, ChainContextPosBuilder) lookup.rules.append( ChainContextualRule( @@ -1467,7 +1481,9 @@ class Builder(object): 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) + raise FeatureLibError( + "Empty glyph class in contextual positioning rule", location + ) # https://github.com/fonttools/fonttools/issues/514 chain = self.get_lookup_(location, ChainContextPosBuilder) targets = [] @@ -1478,7 +1494,9 @@ class Builder(object): if value is None: subs.append(None) continue - otValue = self.makeOpenTypeValueRecord(location, 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) @@ -1497,7 +1515,9 @@ class Builder(object): for markClassDef in markClass.definitions: for mark in markClassDef.glyphs.glyphSet(): if mark not in lookupBuilder.marks: - otMarkAnchor = self.makeOpenTypeAnchor(location, markClassDef.anchor) + otMarkAnchor = self.makeOpenTypeAnchor( + location, markClassDef.anchor + ) lookupBuilder.marks[mark] = (markClass.name, otMarkAnchor) else: existingMarkClass = lookupBuilder.marks[mark][0] @@ -1538,7 +1558,16 @@ class Builder(object): if glyph not in self.ligCaretPoints_: self.ligCaretPoints_[glyph] = carets + def makeLigCaret(self, location, caret): + if not isinstance(caret, VariableScalar): + return caret + default, device = self.makeVariablePos(location, caret) + if device is not None: + return (default, device) + return default + def add_ligatureCaretByPos_(self, location, glyphs, carets): + carets = [self.makeLigCaret(location, caret) for caret in carets] for glyph in glyphs: if glyph not in self.ligCaretCoords_: self.ligCaretCoords_[glyph] = carets @@ -1555,10 +1584,11 @@ 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: + def add_conditionset(self, location, key, value): + if "fvar" not in self.font: raise FeatureLibError( - "Cannot add feature variations to a font without an 'fvar' table" + "Cannot add feature variations to a font without an 'fvar' table", + location, ) # Normalize @@ -1575,8 +1605,41 @@ class Builder(object): for tag, (bottom, top) in value.items() } + # NOTE: This might result in rounding errors (off-by-ones) compared to + # rules in Designspace files, since we're working with what's in the + # `avar` table rather than the original values. + if "avar" in self.font: + mapping = self.font["avar"].segments + value = { + axis: tuple( + piecewiseLinearMap(v, mapping[axis]) if axis in mapping else v + for v in condition_range + ) + for axis, condition_range in value.items() + } + self.conditionsets_[key] = value + def makeVariablePos(self, location, varscalar): + if not self.varstorebuilder: + raise FeatureLibError( + "Can't define a variable scalar in a non-variable font", location + ) + + varscalar.axes = self.axes + if not varscalar.does_vary: + return varscalar.default, None + + default, index = varscalar.add_to_variation_store( + self.varstorebuilder, self.model_cache, self.font.get("avar") + ) + + device = None + if index is not None and index != 0xFFFFFFFF: + device = buildVarDevTable(index) + + return default, device + def makeOpenTypeAnchor(self, location, anchor): """ast.Anchor --> otTables.Anchor""" if anchor is None: @@ -1588,24 +1651,25 @@ class Builder(object): if anchor.yDeviceTable is not None: deviceY = otl.buildDevice(dict(anchor.yDeviceTable)) for dim in ("x", "y"): - if not isinstance(getattr(anchor, dim), VariableScalar): + varscalar = getattr(anchor, dim) + if not isinstance(varscalar, 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) + if getattr(anchor, dim + "DeviceTable") is not None: + raise FeatureLibError( + "Can't define a device coordinate and variable scalar", location + ) + default, device = self.makeVariablePos(location, varscalar) setattr(anchor, dim, default) - if index is not None and index != 0xFFFFFFFF: + if device is not None: if dim == "x": - deviceX = buildVarDevTable(index) + deviceX = device else: - deviceY = buildVarDevTable(index) + deviceY = device variable = True - otlanchor = otl.buildAnchor(anchor.x, anchor.y, anchor.contourpoint, deviceX, deviceY) + otlanchor = otl.buildAnchor( + anchor.x, anchor.y, anchor.contourpoint, deviceX, deviceY + ) if variable: otlanchor.Format = 3 return otlanchor @@ -1616,14 +1680,12 @@ class Builder(object): if not name.startswith("Reserved") } - 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: @@ -1634,15 +1696,12 @@ class Builder(object): 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 + raise FeatureLibError( + "Can't define a device coordinate and variable scalar", location + ) + vr[otName], device = self.makeVariablePos(location, val) + if device is not None: + vr[otDeviceName] = device else: vr[otName] = val diff --git a/Lib/fontTools/feaLib/lexer.py b/Lib/fontTools/feaLib/lexer.py index 140fbd82..e0ae0aef 100644 --- a/Lib/fontTools/feaLib/lexer.py +++ b/Lib/fontTools/feaLib/lexer.py @@ -3,6 +3,12 @@ from fontTools.feaLib.location import FeatureLibLocation import re import os +try: + import cython +except ImportError: + # if cython not installed, use mock module with no-op decorators and types + from fontTools.misc import cython + class Lexer(object): NUMBER = "NUMBER" @@ -191,7 +197,7 @@ class IncludingLexer(object): """A Lexer that follows include statements. The OpenType feature file specification states that due to - historical reasons, relative imports should be resolved in this + historical reasons, relative imports should be resolved in this order: 1. If the source font is UFO format, then relative to the UFO's diff --git a/Lib/fontTools/feaLib/lookupDebugInfo.py b/Lib/fontTools/feaLib/lookupDebugInfo.py index 876cadff..d4da7de0 100644 --- a/Lib/fontTools/feaLib/lookupDebugInfo.py +++ b/Lib/fontTools/feaLib/lookupDebugInfo.py @@ -1,7 +1,8 @@ from typing import NamedTuple LOOKUP_DEBUG_INFO_KEY = "com.github.fonttools.feaLib" -LOOKUP_DEBUG_ENV_VAR = "FONTTOOLS_LOOKUP_DEBUGGING" +LOOKUP_DEBUG_ENV_VAR = "FONTTOOLS_LOOKUP_DEBUGGING" + class LookupDebugInfo(NamedTuple): """Information about where a lookup came from, to be embedded in a font""" diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index 04ff6030..8ffdf644 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -45,7 +45,6 @@ class Parser(object): def __init__( self, featurefile, glyphNames=(), followIncludes=True, includeDir=None, **kwargs ): - if "glyphMap" in kwargs: from fontTools.misc.loggingTools import deprecateArgument @@ -134,7 +133,8 @@ class Parser(object): ] raise FeatureLibError( "The following glyph names are referenced but are missing from the " - "glyph set:\n" + ("\n".join(error)), None + "glyph set:\n" + ("\n".join(error)), + None, ) return self.doc_ @@ -396,7 +396,8 @@ class Parser(object): self.expect_symbol_("-") range_end = self.expect_cid_() self.check_glyph_name_in_glyph_set( - f"cid{range_start:05d}", f"cid{range_end:05d}", + f"cid{range_start:05d}", + f"cid{range_end:05d}", ) glyphs.add_cid_range( range_start, @@ -522,27 +523,33 @@ class Parser(object): ) return (prefix, glyphs, lookups, values, suffix, hasMarks) - def parse_chain_context_(self): + def parse_ignore_glyph_pattern_(self, sub): location = self.cur_token_location_ prefix, glyphs, lookups, values, suffix, hasMarks = self.parse_glyph_pattern_( vertical=False ) - chainContext = [(prefix, glyphs, suffix)] - hasLookups = any(lookups) + if any(lookups): + raise FeatureLibError( + f'No lookups can be specified for "ignore {sub}"', location + ) + if not hasMarks: + error = FeatureLibError( + f'Ambiguous "ignore {sub}", there should be least one marked glyph', + location, + ) + log.warning(str(error)) + suffix, glyphs = glyphs[1:], glyphs[0:1] + chainContext = (prefix, glyphs, suffix) + return chainContext + + def parse_ignore_context_(self, sub): + location = self.cur_token_location_ + chainContext = [self.parse_ignore_glyph_pattern_(sub)] while self.next_token_ == ",": self.expect_symbol_(",") - ( - prefix, - glyphs, - lookups, - values, - suffix, - hasMarks, - ) = self.parse_glyph_pattern_(vertical=False) - chainContext.append((prefix, glyphs, suffix)) - hasLookups = hasLookups or any(lookups) + chainContext.append(self.parse_ignore_glyph_pattern_(sub)) self.expect_symbol_(";") - return chainContext, hasLookups + return chainContext def parse_ignore_(self): # Parses an ignore sub/pos rule. @@ -550,18 +557,10 @@ class Parser(object): location = self.cur_token_location_ self.advance_lexer_() if self.cur_token_ in ["substitute", "sub"]: - chainContext, hasLookups = self.parse_chain_context_() - if hasLookups: - raise FeatureLibError( - 'No lookups can be specified for "ignore sub"', location - ) + chainContext = self.parse_ignore_context_("sub") return self.ast.IgnoreSubstStatement(chainContext, location=location) if self.cur_token_ in ["position", "pos"]: - chainContext, hasLookups = self.parse_chain_context_() - if hasLookups: - raise FeatureLibError( - 'No lookups can be specified for "ignore pos"', location - ) + chainContext = self.parse_ignore_context_("pos") return self.ast.IgnorePosStatement(chainContext, location=location) raise FeatureLibError( 'Expected "substitute" or "position"', self.cur_token_location_ @@ -603,9 +602,9 @@ class Parser(object): assert self.is_cur_keyword_("LigatureCaretByPos") location = self.cur_token_location_ glyphs = self.parse_glyphclass_(accept_glyphname=True) - carets = [self.expect_number_()] + carets = [self.expect_number_(variable=True)] while self.next_token_ != ";": - carets.append(self.expect_number_()) + carets.append(self.expect_number_(variable=True)) self.expect_symbol_(";") return self.ast.LigatureCaretByPosStatement(glyphs, carets, location=location) @@ -696,7 +695,9 @@ class Parser(object): location = self.cur_token_location_ glyphs = self.parse_glyphclass_(accept_glyphname=True) if not glyphs.glyphSet(): - raise FeatureLibError("Empty glyph class in mark class definition", location) + raise FeatureLibError( + "Empty glyph class in mark class definition", location + ) anchor = self.parse_anchor_() name = self.expect_class_name_() self.expect_symbol_(";") @@ -923,22 +924,27 @@ class Parser(object): # GSUB lookup type 2: Multiple substitution. # Format: "substitute f_f_i by f f i;" - if ( - not reverse - and len(old) == 1 - and len(old[0].glyphSet()) == 1 - and len(new) > 1 - and max([len(n.glyphSet()) for n in new]) == 1 - and num_lookups == 0 - ): + # + # GlyphsApp introduces two additional formats: + # Format 1: "substitute [f_i f_l] by [f f] [i l];" + # Format 2: "substitute [f_i f_l] by f [i l];" + # http://handbook.glyphsapp.com/en/layout/multiple-substitution-with-classes/ + if not reverse and len(old) == 1 and len(new) > 1 and num_lookups == 0: + count = len(old[0].glyphSet()) for n in new: if not list(n.glyphSet()): raise FeatureLibError("Empty class in replacement", location) + if len(n.glyphSet()) != 1 and len(n.glyphSet()) != count: + raise FeatureLibError( + f'Expected a glyph class with 1 or {count} elements after "by", ' + f"but found a glyph class with {len(n.glyphSet())} elements", + location, + ) return self.ast.MultipleSubstStatement( old_prefix, - tuple(old[0].glyphSet())[0], + old[0], old_suffix, - tuple([list(n.glyphSet())[0] for n in new]), + new, forceChain=hasMarks, location=location, ) @@ -1747,7 +1753,8 @@ class Parser(object): def parse_featureNames_(self, tag): """Parses a ``featureNames`` statement found in stylistic set features. - See section `8.c <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#8.c>`_.""" + See section `8.c <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#8.c>`_. + """ assert self.cur_token_ == "featureNames", self.cur_token_ block = self.ast.NestedBlock( tag, self.cur_token_, location=self.cur_token_location_ diff --git a/Lib/fontTools/feaLib/variableScalar.py b/Lib/fontTools/feaLib/variableScalar.py index a286568e..c97b4354 100644 --- a/Lib/fontTools/feaLib/variableScalar.py +++ b/Lib/fontTools/feaLib/variableScalar.py @@ -1,4 +1,4 @@ -from fontTools.varLib.models import VariationModel, normalizeValue +from fontTools.varLib.models import VariationModel, normalizeValue, piecewiseLinearMap def Location(loc): @@ -74,24 +74,39 @@ class VariableScalar: # I *guess* we could interpolate one, but I don't know how. return self.values[key] - def value_at_location(self, location): + def value_at_location(self, location, model_cache=None, avar=None): loc = location if loc in self.values.keys(): return self.values[loc] values = list(self.values.values()) - return self.model.interpolateFromMasters(loc, values) + return self.model(model_cache, avar).interpolateFromMasters(loc, values) - @property - def model(self): + def model(self, model_cache=None, avar=None): + if model_cache is not None: + key = tuple(self.values.keys()) + if key in model_cache: + return model_cache[key] locations = [dict(self._normalized_location(k)) for k in self.values.keys()] - return VariationModel(locations) - - def get_deltas_and_supports(self): + if avar is not None: + mapping = avar.segments + locations = [ + { + k: piecewiseLinearMap(v, mapping[k]) if k in mapping else v + for k, v in location.items() + } + for location in locations + ] + m = VariationModel(locations) + if model_cache is not None: + model_cache[key] = m + return m + + def get_deltas_and_supports(self, model_cache=None, avar=None): values = list(self.values.values()) - return self.model.getDeltasAndSupports(values) + return self.model(model_cache, avar).getDeltasAndSupports(values) - def add_to_variation_store(self, store_builder): - deltas, supports = self.get_deltas_and_supports() + def add_to_variation_store(self, store_builder, model_cache=None, avar=None): + deltas, supports = self.get_deltas_and_supports(model_cache, avar) store_builder.setSupports(supports) index = store_builder.storeDeltas(deltas) return int(self.default), index |