def reorder_blend_args(self, commands, get_delta_func, round_func): """ We first re-order the master coordinate values. For a moveto to lineto, the args are now arranged as: [ [master_0 x,y], [master_1 x,y], [master_2 x,y] ] We re-arrange this to [ [master_0 x, master_1 x, master_2 x], [master_0 y, master_1 y, master_2 y] ] If the master values are all the same, we collapse the list to as single value instead of a list. We then convert this to: [ [master_0 x] + [x delta tuple] + [numBlends=1] [master_0 y] + [y delta tuple] + [numBlends=1] ] """ for cmd in commands: # arg[i] is the set of arguments for this operator from master i. args = cmd[1] m_args = zip(*args) # m_args[n] is now all num_master args for the i'th argument # for this operation. cmd[1] = list(m_args) lastOp = None for cmd in commands: op = cmd[0] # masks are represented by two cmd's: first has only op names, # second has only args. if lastOp in ['hintmask', 'cntrmask']: coord = list(cmd[1]) if not allEqual(coord): raise VarLibMergeError( "Hintmask values cannot differ between source fonts.") cmd[1] = [coord[0][0]] else: coords = cmd[1] new_coords = [] for coord in coords: if allEqual(coord): new_coords.append(coord[0]) else: # convert to deltas deltas = get_delta_func(coord)[1:] if round_func: deltas = [round_func(delta) for delta in deltas] coord = [coord[0]] + deltas new_coords.append(coord) cmd[1] = new_coords lastOp = op return commands
def _Lookup_PairPosFormat2_subtables_flatten(lst, font): assert allEqual([l.ValueFormat2 == 0 for l in lst if l.Class1Record]), "Report bug against fontemon_blender_addon.fontTools." self = ot.PairPos() self.Format = 2 self.Coverage = ot.Coverage() self.Coverage.Format = 1 self.ValueFormat1 = reduce(int.__or__, [l.ValueFormat1 for l in lst], 0) self.ValueFormat2 = reduce(int.__or__, [l.ValueFormat2 for l in lst], 0) # Align them glyphs, _ = _merge_GlyphOrders(font, [v.Coverage.glyphs for v in lst]) self.Coverage.glyphs = glyphs matrices = _PairPosFormat2_align_matrices(self, lst, font, transparent=True) matrix = self.Class1Record = [] for rows in zip(*matrices): row = ot.Class1Record() matrix.append(row) row.Class2Record = [] row = row.Class2Record for cols in zip(*list(r.Class2Record for r in rows)): col = next(iter(c for c in cols if c is not None)) row.append(col) return self
def _PairPosFormat2_merge(self, lst, merger): assert allEqual([l.ValueFormat2 == 0 for l in lst if l.Class1Record]), "Report bug against fontemon_blender_addon.fontTools." merger.mergeObjects(self, lst, exclude=('Coverage', 'ClassDef1', 'Class1Count', 'ClassDef2', 'Class2Count', 'Class1Record', 'ValueFormat1', 'ValueFormat2')) # Align coverages glyphs, _ = _merge_GlyphOrders(merger.font, [v.Coverage.glyphs for v in lst]) self.Coverage.glyphs = glyphs # Currently, if the coverage of PairPosFormat2 subtables are different, # we do NOT bother walking down the subtable list when filling in new # rows for alignment. As such, this is only correct if current subtable # is the last subtable in the lookup. Ensure that. # # Note that our canonicalization process merges trailing PairPosFormat2's, # so in reality this is rare. for l,subtables in zip(lst,merger.lookup_subtables): if l.Coverage.glyphs != glyphs: assert l == subtables[-1] matrices = _PairPosFormat2_align_matrices(self, lst, merger.font) self.Class1Record = list(matrices[0]) # TODO move merger to be selfless merger.mergeLists(self.Class1Record, matrices)
def _PairPosFormat1_merge(self, lst, merger): assert allEqual([l.ValueFormat2 == 0 for l in lst if l.PairSet]), "Report bug against fontemon_blender_addon.fontTools." # Merge everything else; makes sure Format is the same. merger.mergeObjects(self, lst, exclude=('Coverage', 'PairSet', 'PairSetCount', 'ValueFormat1', 'ValueFormat2')) empty = ot.PairSet() empty.PairValueRecord = [] empty.PairValueCount = 0 # Align them glyphs, padded = _merge_GlyphOrders(merger.font, [v.Coverage.glyphs for v in lst], [v.PairSet for v in lst], default=empty) self.Coverage.glyphs = glyphs self.PairSet = [ot.PairSet() for _ in glyphs] self.PairSetCount = len(self.PairSet) for glyph, ps in zip(glyphs, self.PairSet): ps._firstGlyph = glyph merger.mergeLists(self.PairSet, padded)
def _Lookup_PairPosFormat1_subtables_flatten(lst, font): assert allEqual([l.ValueFormat2 == 0 for l in lst if l.PairSet]), "Report bug against fontemon_blender_addon.fontTools." self = ot.PairPos() self.Format = 1 self.Coverage = ot.Coverage() self.Coverage.Format = 1 self.ValueFormat1 = reduce(int.__or__, [l.ValueFormat1 for l in lst], 0) self.ValueFormat2 = reduce(int.__or__, [l.ValueFormat2 for l in lst], 0) # Align them glyphs, padded = _merge_GlyphOrders(font, [v.Coverage.glyphs for v in lst], [v.PairSet for v in lst]) self.Coverage.glyphs = glyphs self.PairSet = [_PairSet_flatten([v for v in values if v is not None], font) for values in zip(*padded)] self.PairSetCount = len(self.PairSet) return self
def merge(merger, self, lst): if self is None: if not allNone(lst): raise VarLibMergeError(lst) return lst = [l.classDefs for l in lst] self.classDefs = {} # We only care about the .classDefs self = self.classDefs allKeys = set() allKeys.update(*[l.keys() for l in lst]) for k in allKeys: allValues = nonNone(l.get(k) for l in lst) if not allEqual(allValues): raise VarLibMergeError(allValues) if not allValues: self[k] = None else: self[k] = allValues[0]
def merge(merger, self, lst): subtables = merger.lookup_subtables = [l.SubTable for l in lst] # Remove Extension subtables for l,sts in list(zip(lst,subtables))+[(self,self.SubTable)]: if not sts: continue if sts[0].__class__.__name__.startswith('Extension'): if not allEqual([st.__class__ for st in sts]): raise VarLibMergeError( "Use of extensions inconsistent between masters: " f"{[st.__class__.__name__ for st in sts]}." ) if not allEqual([st.ExtensionLookupType for st in sts]): raise VarLibMergeError( "Extension lookup type differs between masters: " f"{[st.ExtensionLookupType for st in sts]}." ) l.LookupType = sts[0].ExtensionLookupType new_sts = [st.ExtSubTable for st in sts] del sts[:] sts.extend(new_sts) isPairPos = self.SubTable and isinstance(self.SubTable[0], ot.PairPos) if isPairPos: # AFDKO and feaLib sometimes generate two Format1 subtables instead of one. # Merge those before continuing. # https://github.com/fontemon_blender_addon.fontTools/fontemon_blender_addon.fontTools/issues/719 self.SubTable = _Lookup_PairPos_subtables_canonicalize(self.SubTable, merger.font) subtables = merger.lookup_subtables = [_Lookup_PairPos_subtables_canonicalize(st, merger.font) for st in subtables] else: isSinglePos = self.SubTable and isinstance(self.SubTable[0], ot.SinglePos) if isSinglePos: numSubtables = [len(st) for st in subtables] if not all([nums == numSubtables[0] for nums in numSubtables]): # Flatten list of SinglePos subtables to single Format 2 subtable, # with all value records set to the rec format type. # We use buildSinglePos() to optimize the lookup after merging. valueFormatList = [t.ValueFormat for st in subtables for t in st] # Find the minimum value record that can accomodate all the singlePos subtables. mirf = reduce(ior, valueFormatList) self.SubTable = _Lookup_SinglePos_subtables_flatten(self.SubTable, merger.font, mirf) subtables = merger.lookup_subtables = [ _Lookup_SinglePos_subtables_flatten(st, merger.font, mirf) for st in subtables] flattened = True else: flattened = False merger.mergeLists(self.SubTable, subtables) self.SubTableCount = len(self.SubTable) if isPairPos: # If format-1 subtable created during canonicalization is empty, remove it. assert len(self.SubTable) >= 1 and self.SubTable[0].Format == 1 if not self.SubTable[0].Coverage.glyphs: self.SubTable.pop(0) self.SubTableCount -= 1 # If format-2 subtable created during canonicalization is empty, remove it. assert len(self.SubTable) >= 1 and self.SubTable[-1].Format == 2 if not self.SubTable[-1].Coverage.glyphs: self.SubTable.pop(-1) self.SubTableCount -= 1 elif isSinglePos and flattened: singlePosTable = self.SubTable[0] glyphs = singlePosTable.Coverage.glyphs # We know that singlePosTable is Format 2, as this is set # in _Lookup_SinglePos_subtables_flatten. singlePosMapping = { gname: valRecord for gname, valRecord in zip(glyphs, singlePosTable.Value) } self.SubTable = buildSinglePos(singlePosMapping, merger.font.getReverseGlyphMap()) merger.mergeObjects(self, lst, exclude=['SubTable', 'SubTableCount']) del merger.lookup_subtables
def _MarkBasePosFormat1_merge(self, lst, merger, Mark='Mark', Base='Base'): self.ClassCount = max(l.ClassCount for l in lst) MarkCoverageGlyphs, MarkRecords = \ _merge_GlyphOrders(merger.font, [getattr(l, Mark+'Coverage').glyphs for l in lst], [getattr(l, Mark+'Array').MarkRecord for l in lst]) getattr(self, Mark+'Coverage').glyphs = MarkCoverageGlyphs BaseCoverageGlyphs, BaseRecords = \ _merge_GlyphOrders(merger.font, [getattr(l, Base+'Coverage').glyphs for l in lst], [getattr(getattr(l, Base+'Array'), Base+'Record') for l in lst]) getattr(self, Base+'Coverage').glyphs = BaseCoverageGlyphs # MarkArray records = [] for g,glyphRecords in zip(MarkCoverageGlyphs, zip(*MarkRecords)): allClasses = [r.Class for r in glyphRecords if r is not None] # TODO Right now we require that all marks have same class in # all masters that cover them. This is not required. # # We can relax that by just requiring that all marks that have # the same class in a master, have the same class in every other # master. Indeed, if, say, a sparse master only covers one mark, # that mark probably will get class 0, which would possibly be # different from its class in other masters. # # We can even go further and reclassify marks to support any # input. But, since, it's unlikely that two marks being both, # say, "top" in one master, and one being "top" and other being # "top-right" in another master, we shouldn't do that, as any # failures in that case will probably signify mistakes in the # input masters. if not allEqual(allClasses): raise VarLibMergeError(allClasses) if not allClasses: rec = None else: rec = ot.MarkRecord() rec.Class = allClasses[0] allAnchors = [None if r is None else r.MarkAnchor for r in glyphRecords] if allNone(allAnchors): anchor = None else: anchor = ot.Anchor() anchor.Format = 1 merger.mergeThings(anchor, allAnchors) rec.MarkAnchor = anchor records.append(rec) array = ot.MarkArray() array.MarkRecord = records array.MarkCount = len(records) setattr(self, Mark+"Array", array) # BaseArray records = [] for g,glyphRecords in zip(BaseCoverageGlyphs, zip(*BaseRecords)): if allNone(glyphRecords): rec = None else: rec = getattr(ot, Base+'Record')() anchors = [] setattr(rec, Base+'Anchor', anchors) glyphAnchors = [[] if r is None else getattr(r, Base+'Anchor') for r in glyphRecords] for l in glyphAnchors: l.extend([None] * (self.ClassCount - len(l))) for allAnchors in zip(*glyphAnchors): if allNone(allAnchors): anchor = None else: anchor = ot.Anchor() anchor.Format = 1 merger.mergeThings(anchor, allAnchors) anchors.append(anchor) records.append(rec) array = getattr(ot, Base+'Array')() setattr(array, Base+'Record', records) setattr(array, Base+'Count', len(records)) setattr(self, Base+'Array', array)
def buildVarDevTable(store_builder, master_values): if allEqual(master_values): return master_values[0], None base, varIdx = store_builder.storeMasters(master_values) return base, builder.buildVarDevTable(varIdx)
def merge_PrivateDicts(top_dicts, vsindex_dict, var_model, fd_map): """ I step through the FontDicts in the FDArray of the varfont TopDict. For each varfont FontDict: step through each key in FontDict.Private. For each key, step through each relevant source font Private dict, and build a list of values to blend. The 'relevant' source fonts are selected by first getting the right submodel using vsindex_dict[vsindex]. The indices of the subModel.locations are mapped to source font list indices by assuming the latter order is the same as the order of the var_model.locations. I can then get the index of each subModel location in the list of var_model.locations. """ topDict = top_dicts[0] region_top_dicts = top_dicts[1:] if hasattr(region_top_dicts[0], 'FDArray'): regionFDArrays = [fdTopDict.FDArray for fdTopDict in region_top_dicts] else: regionFDArrays = [[fdTopDict] for fdTopDict in region_top_dicts] for fd_index, font_dict in enumerate(topDict.FDArray): private_dict = font_dict.Private vsindex = getattr(private_dict, 'vsindex', 0) # At the moment, no PrivateDict has a vsindex key, but let's support # how it should work. See comment at end of # merge_charstrings() - still need to optimize use of vsindex. sub_model, _ = vsindex_dict[vsindex] master_indices = [] for loc in sub_model.locations[1:]: i = var_model.locations.index(loc) - 1 master_indices.append(i) pds = [private_dict] last_pd = private_dict for ri in master_indices: pd = get_private(regionFDArrays, fd_index, ri, fd_map) # If the region font doesn't have this FontDict, just reference # the last one used. if pd is None: pd = last_pd else: last_pd = pd pds.append(pd) num_masters = len(pds) for key, value in private_dict.rawDict.items(): dataList = [] if key not in pd_blend_fields: continue if isinstance(value, list): try: values = [pd.rawDict[key] for pd in pds] except KeyError: print("Warning: {key} in default font Private dict is " "missing from another font, and was " "discarded.".format(key=key)) continue try: values = zip(*values) except IndexError: raise VarLibCFFDictMergeError(key, value, values) """ Row 0 contains the first value from each master. Convert each row from absolute values to relative values from the previous row. e.g for three masters, a list of values was: master 0 OtherBlues = [-217,-205] master 1 OtherBlues = [-234,-222] master 1 OtherBlues = [-188,-176] The call to zip() converts this to: [(-217, -234, -188), (-205, -222, -176)] and is converted finally to: OtherBlues = [[-217, 17.0, 46.0], [-205, 0.0, 0.0]] """ prev_val_list = [0] * num_masters any_points_differ = False for val_list in values: rel_list = [(val - prev_val_list[i]) for (i, val) in enumerate(val_list)] if (not any_points_differ) and not allEqual(rel_list): any_points_differ = True prev_val_list = val_list deltas = sub_model.getDeltas(rel_list) # For PrivateDict BlueValues, the default font # values are absolute, not relative to the prior value. deltas[0] = val_list[0] dataList.append(deltas) # If there are no blend values,then # we can collapse the blend lists. if not any_points_differ: dataList = [data[0] for data in dataList] else: values = [pd.rawDict[key] for pd in pds] if not allEqual(values): dataList = sub_model.getDeltas(values) else: dataList = values[0] # Convert numbers with no decimal part to an int if isinstance(dataList, list): for i, item in enumerate(dataList): if isinstance(item, list): for j, jtem in enumerate(item): dataList[i][j] = conv_to_int(jtem) else: dataList[i] = conv_to_int(item) else: dataList = conv_to_int(dataList) private_dict.rawDict[key] = dataList
def _add_MVAR(font, masterModel, master_ttfs, axisTags): log.info("Generating MVAR") store_builder = varStore.OnlineVarStoreBuilder(axisTags) records = [] lastTableTag = None fontTable = None tables = None # HACK: we need to special-case post.underlineThickness and .underlinePosition # and unilaterally/arbitrarily define a sentinel value to distinguish the case # when a post table is present in a given master simply because that's where # the glyph names in TrueType must be stored, but the underline values are not # meant to be used for building MVAR's deltas. The value of -0x8000 (-36768) # the minimum FWord (int16) value, was chosen for its unlikelyhood to appear # in real-world underline position/thickness values. specialTags = {"unds": -0x8000, "undo": -0x8000} for tag, (tableTag, itemName) in sorted(MVAR_ENTRIES.items(), key=lambda kv: kv[1]): # For each tag, fetch the associated table from all fonts (or not when we are # still looking at a tag from the same tables) and set up the variation model # for them. if tableTag != lastTableTag: tables = fontTable = None if tableTag in font: fontTable = font[tableTag] tables = [] for master in master_ttfs: if tableTag not in master or ( tag in specialTags and getattr(master[tableTag], itemName) == specialTags[tag] ): tables.append(None) else: tables.append(master[tableTag]) model, tables = masterModel.getSubModel(tables) store_builder.setModel(model) lastTableTag = tableTag if tables is None: # Tag not applicable to the master font. continue # TODO support gasp entries master_values = [getattr(table, itemName) for table in tables] if models.allEqual(master_values): base, varIdx = master_values[0], None else: base, varIdx = store_builder.storeMasters(master_values) setattr(fontTable, itemName, base) if varIdx is None: continue log.info(' %s: %s.%s %s', tag, tableTag, itemName, master_values) rec = ot.MetricsValueRecord() rec.ValueTag = tag rec.VarIdx = varIdx records.append(rec) assert "MVAR" not in font if records: store = store_builder.finish() # Optimize mapping = store.optimize() for rec in records: rec.VarIdx = mapping[rec.VarIdx] MVAR = font["MVAR"] = newTable('MVAR') mvar = MVAR.table = ot.MVAR() mvar.Version = 0x00010000 mvar.Reserved = 0 mvar.VarStore = store # XXX these should not be hard-coded but computed automatically mvar.ValueRecordSize = 8 mvar.ValueRecordCount = len(records) mvar.ValueRecord = sorted(records, key=lambda r: r.ValueTag)
def _get_advance_metrics(font, masterModel, master_ttfs, axisTags, glyphOrder, advMetricses, vOrigMetricses=None): vhAdvanceDeltasAndSupports = {} vOrigDeltasAndSupports = {} for glyph in glyphOrder: vhAdvances = [metrics[glyph][0] if glyph in metrics else None for metrics in advMetricses] vhAdvanceDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(vhAdvances) singleModel = models.allEqual(id(v[1]) for v in vhAdvanceDeltasAndSupports.values()) if vOrigMetricses: singleModel = False for glyph in glyphOrder: # We need to supply a vOrigs tuple with non-None default values # for each glyph. vOrigMetricses contains values only for those # glyphs which have a non-default vOrig. vOrigs = [metrics[glyph] if glyph in metrics else defaultVOrig for metrics, defaultVOrig in vOrigMetricses] vOrigDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(vOrigs) directStore = None if singleModel: # Build direct mapping supports = next(iter(vhAdvanceDeltasAndSupports.values()))[1][1:] varTupleList = builder.buildVarRegionList(supports, axisTags) varTupleIndexes = list(range(len(supports))) varData = builder.buildVarData(varTupleIndexes, [], optimize=False) for glyphName in glyphOrder: varData.addItem(vhAdvanceDeltasAndSupports[glyphName][0]) varData.optimize() directStore = builder.buildVarStore(varTupleList, [varData]) # Build optimized indirect mapping storeBuilder = varStore.OnlineVarStoreBuilder(axisTags) advMapping = {} for glyphName in glyphOrder: deltas, supports = vhAdvanceDeltasAndSupports[glyphName] storeBuilder.setSupports(supports) advMapping[glyphName] = storeBuilder.storeDeltas(deltas) if vOrigMetricses: vOrigMap = {} for glyphName in glyphOrder: deltas, supports = vOrigDeltasAndSupports[glyphName] storeBuilder.setSupports(supports) vOrigMap[glyphName] = storeBuilder.storeDeltas(deltas) indirectStore = storeBuilder.finish() mapping2 = indirectStore.optimize() advMapping = [mapping2[advMapping[g]] for g in glyphOrder] advanceMapping = builder.buildVarIdxMap(advMapping, glyphOrder) if vOrigMetricses: vOrigMap = [mapping2[vOrigMap[g]] for g in glyphOrder] useDirect = False vOrigMapping = None if directStore: # Compile both, see which is more compact writer = OTTableWriter() directStore.compile(writer, font) directSize = len(writer.getAllData()) writer = OTTableWriter() indirectStore.compile(writer, font) advanceMapping.compile(writer, font) indirectSize = len(writer.getAllData()) useDirect = directSize < indirectSize if useDirect: metricsStore = directStore advanceMapping = None else: metricsStore = indirectStore if vOrigMetricses: vOrigMapping = builder.buildVarIdxMap(vOrigMap, glyphOrder) return metricsStore, advanceMapping, vOrigMapping
def _merge_TTHinting(font, masterModel, master_ttfs, tolerance=0.5): log.info("Merging TT hinting") assert "cvar" not in font # Check that the existing hinting is compatible # fpgm and prep table for tag in ("fpgm", "prep"): all_pgms = [m[tag].program for m in master_ttfs if tag in m] if len(all_pgms) == 0: continue if tag in font: font_pgm = font[tag].program else: font_pgm = Program() if any(pgm != font_pgm for pgm in all_pgms): log.warning("Masters have incompatible %s tables, hinting is discarded." % tag) _remove_TTHinting(font) return # glyf table for name, glyph in font["glyf"].glyphs.items(): all_pgms = [ m["glyf"][name].program for m in master_ttfs if name in m['glyf'] and hasattr(m["glyf"][name], "program") ] if not any(all_pgms): continue glyph.expand(font["glyf"]) if hasattr(glyph, "program"): font_pgm = glyph.program else: font_pgm = Program() if any(pgm != font_pgm for pgm in all_pgms if pgm): log.warning("Masters have incompatible glyph programs in glyph '%s', hinting is discarded." % name) # TODO Only drop hinting from this glyph. _remove_TTHinting(font) return # cvt table all_cvs = [Vector(m["cvt "].values) if 'cvt ' in m else None for m in master_ttfs] nonNone_cvs = models.nonNone(all_cvs) if not nonNone_cvs: # There is no cvt table to make a cvar table from, we're done here. return if not models.allEqual(len(c) for c in nonNone_cvs): log.warning("Masters have incompatible cvt tables, hinting is discarded.") _remove_TTHinting(font) return variations = [] deltas, supports = masterModel.getDeltasAndSupports(all_cvs) for i,(delta,support) in enumerate(zip(deltas[1:], supports[1:])): delta = [otRound(d) for d in delta] if all(abs(v) <= tolerance for v in delta): continue var = TupleVariation(support, delta) variations.append(var) # We can build the cvar table now. if variations: cvar = font["cvar"] = newTable('cvar') cvar.version = 1 cvar.variations = variations
def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True): if tolerance < 0: raise ValueError("`tolerance` must be a positive number.") log.info("Generating gvar") assert "gvar" not in font gvar = font["gvar"] = newTable('gvar') glyf = font['glyf'] defaultMasterIndex = masterModel.reverseMapping[0] # use hhea.ascent of base master as default vertical origin when vmtx is missing baseAscent = font['hhea'].ascent for glyph in font.getGlyphOrder(): isComposite = glyf[glyph].isComposite() allData = [ m["glyf"].getCoordinatesAndControls(glyph, m, defaultVerticalOrigin=baseAscent) for m in master_ttfs ] if allData[defaultMasterIndex][1].numberOfContours != 0: # If the default master is not empty, interpret empty non-default masters # as missing glyphs from a sparse master allData = [ d if d is not None and d[1].numberOfContours != 0 else None for d in allData ] model, allData = masterModel.getSubModel(allData) allCoords = [d[0] for d in allData] allControls = [d[1] for d in allData] control = allControls[0] if not models.allEqual(allControls): log.warning("glyph %s has incompatible masters; skipping" % glyph) continue del allControls # Update gvar gvar.variations[glyph] = [] deltas = model.getDeltas(allCoords) supports = model.supports assert len(deltas) == len(supports) # Prepare for IUP optimization origCoords = deltas[0] endPts = control.endPts for i,(delta,support) in enumerate(zip(deltas[1:], supports[1:])): if all(abs(v) <= tolerance for v in delta.array) and not isComposite: continue var = TupleVariation(support, delta) if optimize: delta_opt = iup_delta_optimize(delta, origCoords, endPts, tolerance=tolerance) if None in delta_opt: """In composite glyphs, there should be one 0 entry to make sure the gvar entry is written to the font. This is to work around an issue with macOS 10.14 and can be removed once the behaviour of macOS is changed. https://github.com/fontemon_blender_addon.fontTools/fontemon_blender_addon.fontTools/issues/1381 """ if all(d is None for d in delta_opt): delta_opt = [(0, 0)] + [None] * (len(delta_opt) - 1) # Use "optimized" version only if smaller... var_opt = TupleVariation(support, delta_opt) axis_tags = sorted(support.keys()) # Shouldn't matter that this is different from fvar...? tupleData, auxData, _ = var.compile(axis_tags, [], None) unoptimized_len = len(tupleData) + len(auxData) tupleData, auxData, _ = var_opt.compile(axis_tags, [], None) optimized_len = len(tupleData) + len(auxData) if optimized_len < unoptimized_len: var = var_opt gvar.variations[glyph].append(var)