Example #1
0
class PointsModel(object):
    """
    See: https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview

    >>> #model = Model(ds)
    >>> #model.getScalars(dict(SHPE=0.25, wght=0.25))

    >>> masterValues = [0, 100, 200, 100, 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100]
    >>> location = dict(SHPE=0.5, wght=0.5)
    >>> #model.interpolatePoints(location, masters)
    25.0

    """
    def __init__(self, designSpace, masters):
        self.ds = designSpace
        self.vm = VariationModel(self.ds.normalizedLocations,
                                 axisOrder=self.ds.axisOrder)
        self.masters = masters  # List of master fonts, in the right order.
        self._masterValues = {}  # Cache of master values. Keys id

    def __repr__(self):
        return '<PageBot %s %s>' % (self.__class__.__name__,
                                    self.ds.familyName)

    def getScalars(self, location):
        return self.vm.getScalars(location)

    def getDeltas(self, glyphName):
        deltas = []
        mvsX, mvsY = self.getMasterValues(glyphName)
        #print(len(self.vm.deltaWeights), len(mvsX))
        for index in range(len(mvsX)):
            deltas.append((self.vm.getDeltas(mvsX[index]),
                           self.vm.getDeltas(mvsY[index])))
        return deltas

    def getMasterValues(self, glyphName):
        if self._masterValues is None:
            mvx = []
            mvy = []
            self._masterValues = mvx, mvy
            for master in self.masters:
                points = getPoints(master[glyphName])
                for pIndex, point in enumerate(points):
                    if len(mvx) <= pIndex:
                        mvx.append([])
                        mvy.append([])
                    mvx[pIndex].append(point.x)
                    mvy[pIndex].append(point.y)
        return self._masterValues

    def interpolatePoints(self, glyphName, location):
        interpolatedPoints = []
        mvsX, mvsY = self.getMasterValues(glyphName)
        for index in range(len(mvsX)):
            interpolatedPoints.append(
                (self.vm.interpolateFromMasters(location, mvsX[index]),
                 self.vm.interpolateFromMasters(location, mvsY[index])))
        return interpolatedPoints
Example #2
0
    def preview(self, position: dict = {}, font=None, forceRefresh=True):
        locationKey = ','.join(
            [k + ':' + str(v)
             for k, v in position.items()]) if position else ','.join([
                 k + ':' + str(v) for k, v in
                 self.normalizedValueToMinMaxValue(position, self).items()
             ])
        if locationKey in self.previewLocationsStore:
            for p in self.previewLocationsStore[locationKey]:
                yield p
            return
        # if not forceRefresh and self.previewGlyph:
        #     print('AE has previewGlyph', self.previewGlyph)
        #     return self.previewGlyph
        # if not position:
        #     position = self.getLocation()
        # print(position)
        # print("AE %s position"%self.name, position, "\n")
        position = self.normalizedValueToMinMaxValue_clamped(position, self)
        # position = self._clampLocation(position)
        # for k in position:
        #     if position[k] > 1:
        #         position[k] = 1

        locations = [{}]
        locations.extend([
            self.normalizedValueToMinMaxValue(x["location"], self)
            for x in self._glyphVariations.getList() if x["on"]
        ])
        # print("AE %s locations"%self.name, locations, "\n")
        # print(locations,'\n')
        # locations.extend([{k:self.normalizedValueToMinMaxValue(v, self) for k, v in x["location"].items()} for x in self._glyphVariations.getList() if x["on"]])

        # self.frozenPreview = []
        self.previewGlyph = []
        if font is None:
            font = self.getParent()
        model = VariationModel(locations)
        layerGlyphs = []
        for variation in self._glyphVariations.getList():
            if not variation.get("on"): continue
            try:
                g = font._RFont.getLayer(variation["layerName"])[self.name]
            except Exception as e:
                print(e)
                continue
            layerGlyphs.append(
                CustomMathGlyph(
                    font._RFont.getLayer(variation["layerName"])[self.name]))
        resultGlyph = model.interpolateFromMasters(
            position, [CustomMathGlyph(self._RGlyph), *layerGlyphs])
        # resultGlyph.removeOverlap()
        # self.frozenPreview.append(resultGlyph)
        resultGlyph = self.ResultGlyph(resultGlyph)
        self.previewGlyph = [resultGlyph]
        self.previewLocationsStore[','.join(
            [k + ':' + str(v) for k, v in position.items()])] = [resultGlyph]
        yield resultGlyph
Example #3
0
 def __init__(self, items, axes, model=None):
     # items: list of locationdict, value tuples
     # axes: list of axis dictionaried, not axisdescriptor objects.
     # model: a model, if we want to share one
     self.axisOrder = [a.name for a in axes]
     self.axisMapper = AxisMapper(axes)
     self.axes = {}
     for a in axes:
         self.axes[a.name] = (a.minimum, a.default, a.maximum)
     if model is None:
         self.model = VariationModel([self._normalize(a) for a, b in items],
                                     axisOrder=self.axisOrder)
     else:
         self.model = model
     self.masters = [b for a, b in items]
Example #4
0
def test_onlineVarStoreBuilder(locations, masterValues):
    axisTags = sorted({k for loc in locations for k in loc})
    model = VariationModel(locations)
    builder = OnlineVarStoreBuilder(axisTags)
    builder.setModel(model)
    varIdxs = []
    for masters in masterValues:
        _, varIdx = builder.storeMasters(masters)
        varIdxs.append(varIdx)

    varStore = builder.finish()
    mapping = varStore.optimize()
    varIdxs = [mapping[varIdx] for varIdx in varIdxs]

    dummyFont = TTFont()
    writer = OTTableWriter()
    varStore.compile(writer, dummyFont)
    data = writer.getAllData()
    reader = OTTableReader(data)
    varStore = VarStore()
    varStore.decompile(reader, dummyFont)

    fvarAxes = [buildAxis(axisTag) for axisTag in axisTags]
    instancer = VarStoreInstancer(varStore, fvarAxes)
    for masters, varIdx in zip(masterValues, varIdxs):
        base, *rest = masters
        for expectedValue, loc in zip(masters, locations):
            instancer.setLocation(loc)
            value = base + instancer[varIdx]
            assert expectedValue == value
Example #5
0
    def test_init(self, locations, axisOrder, sortedLocs, supports,
                  deltaWeights):
        model = VariationModel(locations, axisOrder=axisOrder)

        assert model.locations == sortedLocs
        assert model.supports == supports
        assert model.deltaWeights == deltaWeights
Example #6
0
    def __init__(self, designSpace, masters, instances=None):
        """Create a VariableFont interpolation model. DesignSpace """
        assert masters, ValueError('%s No master fonts defined. The dictionay shouls master the design space.' % self)
        self.ds = designSpace
        # Property self.masterList creates font list in order of the design space.
        self.masters = masters # Can be an empty list, if the design space is new.

        self.vm = VariationModel(self.ds.normalizedLocations, axisOrder=self.ds.axisOrder)
        # Property self.instanceList creates font list in order of the design space.
        if instances is None:
            instances = {}
        self.instances = instances
        # Initialize cached property values.
        self.clear()
        # Set the self.paths (path->fontInfo) dictionary, so all fonts are enabled by default.
        # Set the path->fontInfo dictionary of fonts from the design space that are disable.
        # This allows the calling function to enable/disable fonts from interpolation.
        self.disabledMasterPaths = [] # Paths of masters not to be used in delta calculation.
Example #7
0
 def test_init_duplicate_locations(self):
     with pytest.raises(VariationModelError, match="Locations must be unique."):
         VariationModel(
             [
                 {"foo": 0.0, "bar": 0.0},
                 {"foo": 1.0, "bar": 1.0},
                 {"bar": 1.0, "foo": 1.0},
             ]
         )
Example #8
0
    def __init__(self, items, axes, model=None):
        # items: list of locationdict, value tuples
        # axes: list of axis dictionaried, not axisdescriptor objects.
        # model: a model, if we want to share one
        self.axisOrder = [a.name for a in axes]
        self.axisMapper = AxisMapper(axes)
        self.axes = {}
        for a in axes:
            mappedMinimum, mappedDefault, mappedMaximum = a.map_forward(
                a.minimum), a.map_forward(a.default), a.map_forward(a.maximum)
            #self.axes[a.name] = (a.minimum, a.default, a.maximum)
            self.axes[a.name] = (mappedMinimum, mappedDefault, mappedMaximum)

        if model is None:
            dd = [self._normalize(a) for a, b in items]
            ee = self.axisOrder
            self.model = VariationModel(dd, axisOrder=ee)
        else:
            self.model = model
        self.masters = [b for a, b in items]
        self.locations = [a for a, b in items]
Example #9
0
class VariationModelMutator(object):
    """ a thing that looks like a mutator on the outside,
        but uses the fonttools varlib logic to calculate.
    """
    def __init__(self, items, axes, model=None):
        # items: list of locationdict, value tuples
        # axes: list of axis dictionaried, not axisdescriptor objects.
        # model: a model, if we want to share one
        self.axisOrder = [a.name for a in axes]
        self.axisMapper = AxisMapper(axes)
        self.axes = {}
        for a in axes:
            self.axes[a.name] = (a.minimum, a.default, a.maximum)
        if model is None:
            self.model = VariationModel([self._normalize(a) for a, b in items],
                                        axisOrder=self.axisOrder)
        else:
            self.model = model
        self.masters = [b for a, b in items]

    def get(self, key):
        if key in self.model.locations:
            i = self.model.locations.index(key)
            return self.masters[i]
        return None

    def getFactors(self, location):
        nl = self._normalize(location)
        return self.model.getScalars(nl)

    def makeInstance(self, location, bend=False):
        # check for anisotropic locations here
        if bend:
            location = self.axisMapper(location)
        nl = self._normalize(location)
        return self.model.interpolateFromMasters(nl, self.masters)

    def _normalize(self, location):
        return normalizeLocation(location, self.axes)
Example #10
0
def precompileAllComponents(vcData, allLocations, axisTags):
    precompiled = {}
    masterModel = VariationModel(allLocations, axisTags)
    storeBuilder = OnlineVarStoreBuilder(axisTags)
    for gn in vcData.keys():
        components, locations = vcData[gn]
        sparseMapping = [None] * len(allLocations)
        for locIndex, loc in enumerate(locations):
            allIndex = allLocations.index(loc)
            sparseMapping[allIndex] = locIndex
        subModel, mapping = masterModel.getSubModel(sparseMapping)
        storeBuilder.setModel(subModel)

        # reorder master values according to allLocations
        components = [[c[i] for i in mapping] for c in components]

        precompiledGlyph = precompileVarComponents(
            gn, components, storeBuilder, axisTags
        )
        if precompiledGlyph is not None:
            # glyph components do not contain data that has to go to the 'VarC' table
            precompiled[gn] = precompiledGlyph
    return precompiled, storeBuilder.finish()
Example #11
0
def plotLocationsSurfaces(locations, fig, names=None, **kwargs):

    assert len(locations[0].keys()) == 2

    if names is None:
        names = ['']

    n = len(locations)
    cols = math.ceil(n**.5)
    rows = math.ceil(n / cols)

    model = VariationModel(locations)
    names = [names[model.reverseMapping[i]] for i in range(len(names))]

    ax1, ax2 = sorted(locations[0].keys())
    for i, (support, color, name) in enumerate(
            zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names))):

        axis3D = fig.add_subplot(rows, cols, i + 1, projection='3d')
        axis3D.set_title(name)
        axis3D.set_xlabel(ax1)
        axis3D.set_ylabel(ax2)
        pyplot.xlim(-1., +1.)
        pyplot.ylim(-1., +1.)

        Xs = support.get(ax1, (-1., 0., +1.))
        Ys = support.get(ax2, (-1., 0., +1.))
        for x in stops(Xs):
            X, Y, Z = [], [], []
            for y in Ys:
                z = supportScalar({ax1: x, ax2: y}, support)
                X.append(x)
                Y.append(y)
                Z.append(z)
            axis3D.plot(X, Y, Z, color=color, **kwargs)
        for y in stops(Ys):
            X, Y, Z = [], [], []
            for x in Xs:
                z = supportScalar({ax1: x, ax2: y}, support)
                X.append(x)
                Y.append(y)
                Z.append(z)
            axis3D.plot(X, Y, Z, color=color, **kwargs)

        plotLocations(model.locations, [ax1, ax2], axis3D)
def test_VariationModel():
    locations = [
        {},
        {
            'bar': 0.5
        },
        {
            'bar': 1.0
        },
        {
            'foo': 1.0
        },
        {
            'bar': 0.5,
            'foo': 1.0
        },
        {
            'bar': 1.0,
            'foo': 1.0
        },
    ]
    model = VariationModel(locations)

    assert model.locations == locations

    assert model.supports == [
        {},
        {
            'bar': (0, 0.5, 1.0)
        },
        {
            'bar': (0.5, 1.0, 1.0)
        },
        {
            'foo': (0, 1.0, 1.0)
        },
        {
            'bar': (0, 0.5, 1.0),
            'foo': (0, 1.0, 1.0)
        },
        {
            'bar': (0.5, 1.0, 1.0),
            'foo': (0, 1.0, 1.0)
        },
    ]
Example #13
0
    def _makeWarpFromList(self, axisName, mapData):
        # check for the extremes, add if necessary
        minimum, default, maximum = self.axes[axisName]
        if not sum([a == minimum for a, b in mapData]):
            mapData = [(minimum, minimum)] + mapData
        if not sum([a == maximum for a, b in mapData]):
            mapData.append((maximum, maximum))
        if not (default, default) in mapData:
            mapData.append((default, default))

        mapLocations = []
        mapValues = []
        for x, y in mapData:
            l = normalizeLocation(dict(w=x),
                                  dict(w=[minimum, default, maximum]))
            mapLocations.append(l)
            mapValues.append(y)
        self.models[axisName] = VariationModel(mapLocations, axisOrder=['w'])
        self.values[axisName] = mapValues
Example #14
0
def test_modeling_error(numLocations, numSamples):
    # https://github.com/fonttools/fonttools/issues/2213
    locations = [{
        "axis": float(i) / numLocations
    } for i in range(numLocations)]
    masterValues = [100.0 if i else 0.0 for i in range(numLocations)]

    model = VariationModel(locations)

    for i in range(numSamples):
        loc = {"axis": float(i) / numSamples}
        scalars = model.getScalars(loc)

        deltas_float = model.getDeltas(masterValues)
        deltas_round = model.getDeltas(masterValues, round=round)

        expected = model.interpolateFromDeltasAndScalars(deltas_float, scalars)
        actual = model.interpolateFromDeltasAndScalars(deltas_round, scalars)

        err = abs(actual - expected)
        assert err <= 0.5, (i, err)
Example #15
0
def plotLocations(locations, fig, names=None, **kwargs):
    n = len(locations)
    cols = math.ceil(n**.5)
    rows = math.ceil(n / cols)

    if names is None:
        names = [None] * len(locations)

    model = VariationModel(locations)
    names = [names[model.reverseMapping[i]] for i in range(len(names))]

    axes = sorted(locations[0].keys())
    if len(axes) == 1:
        _plotLocations2D(model,
                         axes[0],
                         fig,
                         cols,
                         rows,
                         names=names,
                         **kwargs)
    elif len(axes) == 2:
        _plotLocations3D(model, axes, fig, cols, rows, names=names, **kwargs)
    else:
        raise ValueError("Only 1 or 2 axes are supported")
Example #16
0
def prepareVariableComponentData(vcFont,
                                 axisTags,
                                 globalAxisNames,
                                 neutralOnly=False):
    storeBuilder = OnlineVarStoreBuilder(axisTags)

    vcData = {}
    for glyphName in sorted(vcFont.keys()):
        glyph = vcFont[glyphName]
        axisTags = {
            axisTag
            for v in glyph.variations for axisTag in v.location
        }
        if neutralOnly and not axisTags - globalAxisNames:
            masters = [glyph]
        else:
            masters = [glyph] + glyph.variations

        if not glyph.outline.isEmpty() and glyph.components:
            # This glyph mixes outlines and classic components, it will have been
            # flattened upon TTF compilation
            continue

        locations = [m.location for m in masters]
        storeBuilder.setModel(VariationModel(locations))
        components = []
        for i in range(len(glyph.components)):
            assert allEqual([m.components[i].name for m in masters])
            baseName = masters[0].components[i].name

            coords = [dict(m.components[i].coord) for m in masters]
            sanitizeCoords(coords, vcFont[baseName])
            transforms = [m.components[i].transform for m in masters]
            for t in transforms[1:]:
                assert t.keys() == transforms[0].keys()

            coordMasterValues = {
                k: [coord[k] for coord in coords]
                for k in coords[0].keys()
            }
            transformMasterValues = {
                k: [transform[k] for transform in transforms]
                for k in transforms[0].keys()
            }

            coord = compileMasterValuesDict(storeBuilder, coordMasterValues,
                                            14)  # 2.14
            transform = compileMasterValuesDict(storeBuilder,
                                                transformMasterValues,
                                                16)  # 16.16

            components.append((baseName, coord, transform))
        if components:
            vcData[glyphName] = components

    varStore = storeBuilder.finish()
    mapping = varStore.optimize()
    assert 0xFFFFFFFF not in mapping
    mapping[0xFFFFFFFF] = 0xFFFFFFFF
    for glyphName, components in vcData.items():
        for baseName, coord, transform in components:
            remapValuesDict(coord, mapping)
            remapValuesDict(transform, mapping)

    return vcData, varStore
def test_VariationModel():
    locations = [
        {
            'wght': 100
        },
        {
            'wght': -100
        },
        {
            'wght': -180
        },
        {
            'wdth': +.3
        },
        {
            'wght': +120,
            'wdth': .3
        },
        {
            'wght': +120,
            'wdth': .2
        },
        {},
        {
            'wght': +180,
            'wdth': .3
        },
        {
            'wght': +180
        },
    ]
    model = VariationModel(locations, axisOrder=['wght'])

    assert model.locations == [{}, {
        'wght': -100
    }, {
        'wght': -180
    }, {
        'wght': 100
    }, {
        'wght': 180
    }, {
        'wdth': 0.3
    }, {
        'wdth': 0.3,
        'wght': 180
    }, {
        'wdth': 0.3,
        'wght': 120
    }, {
        'wdth': 0.2,
        'wght': 120
    }]

    assert model.deltaWeights == [{}, {
        0: 1.0
    }, {
        0: 1.0
    }, {
        0: 1.0
    }, {
        0: 1.0
    }, {
        0: 1.0
    }, {
        0: 1.0,
        4: 1.0,
        5: 1.0
    }, {
        0: 1.0,
        3: 0.75,
        4: 0.25,
        5: 1.0,
        6: 0.6666666666666666
    }, {
        0: 1.0,
        3: 0.75,
        4: 0.25,
        5: 0.6666666666666667,
        6: 0.4444444444444445,
        7: 0.6666666666666667
    }]
Example #18
0
 def model(self):
     """Returns a :py:class:`fontTools.varLib.models.VariationModel` object
     used for interpolating values in this variation space."""
     locations = list(self.values.keys())
     return VariationModel(locations)
 def model(self):
     locations = [dict(self._normalized_location(k)) for k in self.values.keys()]
     return VariationModel(locations)
Example #20
0
    def _postParse(self, glyphSet):
        """This gets called soon after parsing the .glif file. Any layer glyphs
        and variation info is unpacked here, and put into a subglyph, as part
        of the self.variations list.
        """
        self.outline, classicComponents = self.outline.splitComponents()
        for baseGlyphName, affineTransform in classicComponents:
            xx, xy, yx, yy, dx, dy = affineTransform
            rotation, scalex, scaley, skewx, skewy = decomposeTwoByTwo(
                (xx, xy, yx, yy))
            assert abs(
                skewx) < 0.00001, f"x skew is not supported ({self.name})"
            assert abs(
                skewy) < 0.00001, f"y skew is not supported ({self.name})"
            transform = MathDict(
                x=dx,
                y=dy,
                scalex=scalex,
                scaley=scaley,
                rotation=math.degrees(rotation),
                tcenterx=0,
                tcentery=0,
            )
            self.components.append(
                Component(baseGlyphName, MathDict(), transform))
        dcNames = []
        for dc in self.lib.get("robocjk.deepComponents", []):
            dcNames.append(dc["name"])
            self.components.append(_unpackDeepComponent(dc))

        axes = {}
        for axisDict in self.lib.get("robocjk.axes", []):
            minValue = axisDict["minValue"]
            maxValue = axisDict["maxValue"]
            defaultValue = axisDict.get("defaultValue", minValue)
            minValue, maxValue = sorted([minValue, maxValue])
            axes[axisDict["name"]] = minValue, defaultValue, maxValue
        self.axes = axes

        self.status = self.lib.get("robocjk.status", 0)

        variationGlyphs = self.lib.get("robocjk.variationGlyphs")
        if variationGlyphs is None:
            return

        self.glyphNotInLayer = []

        for varDict in variationGlyphs:
            if not varDict.get("on", True):
                # This source is "off", and should not be used.
                # They are a bit like background layers.
                continue
            layerName = varDict.get("layerName")
            if (not self.outline.isEmpty() or classicComponents) and layerName:
                layer = glyphSet.getLayer(layerName)
                if self.name in layer:
                    varGlyph = layer.getGlyphNoCache(self.name)
                else:
                    # Layer glyph does not exist, make one up by copying
                    # self.width and self.outline
                    self.glyphNotInLayer.append(layerName)
                    logger.warning(
                        f"glyph {self.name} not found in layer {layerName}")
                    varGlyph = self.__class__()
                    varGlyph.width = self.width
                    varGlyph.outline = self.outline
            else:
                varGlyph = self.__class__()
                varGlyph.width = self.width
            varGlyph.layerName = layerName
            varGlyph.sourceName = varDict.get("sourceName")
            if "width" in varDict:
                varGlyph.width = varDict["width"]

            varGlyph.status = varDict.get("status", 0)

            varGlyph.location = varDict["location"]
            if isLocationOutOfBounds(varGlyph.location, self.axes):
                logger.warning(f"location out of bounds for {self.name}; "
                               f"location: {_formatDict(varGlyph.location)} "
                               f"axes: {_formatDict(self.axes)}")

            deepComponents = varDict.get("deepComponents", [])
            if len(dcNames) != len(deepComponents):
                raise ComponentMismatchError(
                    "different number of components in variations: "
                    f"{len(dcNames)} vs {len(deepComponents)}")
            for dc, dcName in zip(deepComponents, dcNames):
                varGlyph.components.append(_unpackDeepComponent(dc, dcName))
            assert len(varGlyph.components) == len(self.components), (
                self.name,
                [c.name for c in varGlyph.components],
                [c.name for c in self.components],
            )

            self.variations.append(varGlyph)

        locations = [{}] + [
            normalizeLocation(variation.location, self.axes)
            for variation in self.variations
        ]
        self.model = VariationModel(locations)
Example #21
0
class VariationModelMutator(object):
    """ a thing that looks like a mutator on the outside,
        but uses the fonttools varlib logic to calculate.
    """
    def __init__(self, items, axes, model=None):
        # items: list of locationdict, value tuples
        # axes: list of axis dictionaried, not axisdescriptor objects.
        # model: a model, if we want to share one
        self.axisOrder = [a.name for a in axes]
        self.axisMapper = AxisMapper(axes)
        self.axes = {}
        for a in axes:
            mappedMinimum, mappedDefault, mappedMaximum = a.map_forward(
                a.minimum), a.map_forward(a.default), a.map_forward(a.maximum)
            #self.axes[a.name] = (a.minimum, a.default, a.maximum)
            self.axes[a.name] = (mappedMinimum, mappedDefault, mappedMaximum)

        if model is None:
            dd = [self._normalize(a) for a, b in items]
            ee = self.axisOrder
            self.model = VariationModel(dd, axisOrder=ee)
        else:
            self.model = model
        self.masters = [b for a, b in items]
        self.locations = [a for a, b in items]

    def get(self, key):
        if key in self.model.locations:
            i = self.model.locations.index(key)
            return self.masters[i]
        return None

    def getFactors(self, location):
        nl = self._normalize(location)
        return self.model.getScalars(nl)

    def getMasters(self):
        return self.masters

    def getSupports(self):
        return self.model.supports

    def getReach(self):
        items = []
        for supportIndex, s in enumerate(self.getSupports()):
            sortedOrder = self.model.reverseMapping[supportIndex]
            #print("getReach", self.masters[sortedOrder], s)
            #print("getReach", self.locations[sortedOrder])
            items.append((self.masters[sortedOrder], s))
        return items

    def makeInstance(self, location, bend=False):
        # check for anisotropic locations here
        #print("\t1", location)
        if bend:
            location = self.axisMapper(location)
        #print("\t2", location)
        nl = self._normalize(location)
        return self.model.interpolateFromMasters(nl, self.masters)

    def _normalize(self, location):
        return normalizeLocation(location, self.axes)
Example #22
0
 def __init__(self, designSpace, masters):
     self.ds = designSpace
     self.vm = VariationModel(self.ds.normalizedLocations,
                              axisOrder=self.ds.axisOrder)
     self.masters = masters  # List of master fonts, in the right order.
     self._masterValues = {}  # Cache of master values. Keys id
Example #23
0
    def _postParse(self, ufos, locations):
        # Filter out and collect component info from the outline
        self.outline, components = self.outline.splitComponents()

        # Build Component objects
        vcComponentData = self.lib.get("varco.components", [])
        if vcComponentData:
            assert len(components) == len(vcComponentData), (
                self.name,
                len(components),
                len(vcComponentData),
                components,
            )
        else:
            vcComponentData = [None] * len(components)
        assert len(self.components) == 0
        for (baseGlyph, affine), vcCompo in zip(components, vcComponentData):
            if vcCompo is None:
                xx, xy, yx, yy, dx, dy = affine
                assert xy == 0, "rotation and skew are not implemented"
                assert yx == 0, "rotation and skew are not implemented"
                coord = {}
                transform = MathDict(
                    x=dx,
                    y=dy,
                    rotation=0,
                    scalex=xx,
                    scaley=yy,
                    skewx=0,
                    skewy=0,
                    tcenterx=0,
                    tcentery=0,
                )
            else:
                assert affine[:4] == (1, 0, 0, 1)
                x, y = affine[4:]
                coord = vcCompo["coord"]
                transformDict = vcCompo["transform"]
                transform = MathDict(
                    x=affine[4],
                    y=affine[5],
                    rotation=transformDict.get("rotation", 0),
                    scalex=transformDict.get("scalex", 1),
                    scaley=transformDict.get("scaley", 1),
                    skewx=transformDict.get("skewx", 0),
                    skewy=transformDict.get("skewy", 0),
                    tcenterx=transformDict.get("tcenterx", 0),
                    tcentery=transformDict.get("tcentery", 0),
                )
            self.components.append(Component(baseGlyph, MathDict(coord), transform))

        assert len(self.variations) == 0
        if ufos:
            assert len(ufos) == len(locations)
            for ufo, location in zip(ufos[1:], locations[1:]):
                if self.name not in ufo:
                    continue
                for axisName, axisValue in location.items():
                    assert -1 <= axisValue <= 1, (axisName, axisValue)
                varGlyph = self.__class__.loadFromGlyphObject(ufo[self.name])
                varGlyph._postParse([], [])
                varGlyph.location = location
                self.variations.append(varGlyph)
            if self.variations:
                locations = [{}] + [variation.location for variation in self.variations]
                self.model = VariationModel(locations)
Example #24
0
class Model:
    """
    See: https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview
    """

    """
    FIXME: raises NotImplementedError, this only works if NewFont() is registered?
    Maybe create first and the run on a file?
    >>> import os
    >>> from fontParts.world import NewFont
    >>> x = y = 800
    >>> x2 = x//2
    >>> x4 = x//4
    >>> cy = y//2
    >>> FAMILY = 'PageBotTest'
    >>> PATH = '/tmp/%s-%s.ufo'
    >>> GNAME = 'Q'
    >>> fReg = NewFont()
    >>> fReg.axes = {} # Pretend to be a VF
    >>> fReg.info.familyName = FAMILY
    >>> fReg.info.styleName = 'Regular'
    >>> gReg = fReg.newGlyph(GNAME)
    >>> pen = gReg.getPen()
    >>> pen.moveTo((0, 0))
    >>> pen.lineTo((0, y))
    >>> pen.lineTo((x2, y))
    >>> pen.lineTo((x2, 0))
    >>> pen.lineTo((0, 0))
    >>> pen.closePath()
    >>> gReg.leftMargin = gReg.rightMargin = 50
    >>> gReg.width
    500
    >>> fReg.save(PATH % (FAMILY, fReg.info.styleName))

    >>> fBld = NewFont()
    >>> fBld.axes = {} # Pretend to be a VF
    >>> fBld.info.familyName = FAMILY
    >>> fBld.info.styleName = 'Bold'
    >>> gBld = fBld.newGlyph(GNAME)
    >>> pen = gBld.getPen()
    >>> pen.moveTo((0, 0))
    >>> pen.lineTo((0, y))
    >>> pen.lineTo((x, y))
    >>> pen.lineTo((x, 0))
    >>> pen.lineTo((0, 0))
    >>> pen.closePath()
    >>> gBld.leftMargin = gBld.rightMargin = 30
    >>> gBld.width
    860
    >>> fBld.save(PATH % (FAMILY, fBld.info.styleName))

    >>> fLght = NewFont()
    >>> fLght.axes = {} # Pretend to be a VF
    >>> fLght.info.familyName = FAMILY
    >>> fLght.info.styleName = 'Light'
    >>> gLght = fLght.newGlyph(GNAME)
    >>> pen = gLght.getPen()
    >>> pen.moveTo((0, 0))
    >>> pen.lineTo((0, y))
    >>> pen.lineTo((x4, y))
    >>> pen.lineTo((x4, 0))
    >>> pen.lineTo((0, 0))
    >>> pen.closePath()
    >>> gLght.leftMargin = gLght.rightMargin = 70
    >>> gLght.width
    340
    >>> fLght.save(PATH % (FAMILY, fLght.info.styleName))

    >>> fCnd = NewFont()
    >>> fCnd.info.familyName = FAMILY
    >>> fCnd.info.styleName = 'Condensed'
    >>> gCnd = fCnd.newGlyph(GNAME)
    >>> pen = gCnd.getPen()
    >>> pen.moveTo((0, 0))
    >>> pen.lineTo((0, y/2))
    >>> pen.lineTo((x, y/2))
    >>> pen.lineTo((x, 0))
    >>> pen.lineTo((0, 0))
    >>> pen.closePath()
    >>> gCnd.leftMargin = gCnd.rightMargin = 20
    >>> gCnd.width
    840
    >>> fCnd.save(PATH % (FAMILY, fCnd.info.styleName))
    >>> # We created the masters now build the design space from it.
    >>> ds = DesignSpace() # Start empty design space, not reading from a file.
    >>> # Construct axes as list, to maintain the defined order.
    >>> axis1 = Axis(tag='wght', name='Weight', minimum=100, default=400, maximum=900)
    >>> axis2 = Axis(tag='YTUC', name='Y-Transparancy-UC', minimum=0, default=100, maximum=100)
    >>> ds.appendAxes((axis1, axis2))
    >>> # Construct master info as list, to maintain the defined order.
    >>> # Default masters contains "info" attribute.
    >>> loc = Location(wght=100, YTUC=100)
    >>> iLght = FontInfo(name=fLght.info.styleName, familyName=fLght.info.familyName, path=fLght.path, location=loc, styleName=fLght.info.styleName)
    >>> loc = Location(wght=400, YTUC=100)
    >>> iReg = FontInfo(info=dict(copy=True), name=fReg.info.styleName, familyName=fReg.info.familyName, path=fReg.path, location=loc, styleName=fReg.info.styleName)
    >>> loc = Location(wght=900, YTUC=100)
    >>> iBld = FontInfo(name=fBld.info.styleName, familyName=fBld.info.familyName, path=fBld.path, location=loc, styleName=fBld.info.styleName)
    >>> loc = Location(wght=400, YTUC=0)
    >>> iCnd = FontInfo(name=fCnd.info.styleName, familyName=fCnd.info.familyName, path=fCnd.path, location=loc, styleName=fCnd.info.styleName)

    >>> ds.appendMasters((iLght, iReg, iBld, iCnd)) # Set list of master info
    >>> ds.masterList # DesignSpace.masterLust gives list of FontInfo items, in defined order.
    [<FontInfo PageBotTest-Light>, <FontInfo PageBotTest-Regular>, <FontInfo PageBotTest-Bold>, <FontInfo PageBotTest-Condensed>]
    >>> len(ds.masters)
    4
    >>> ds.save('/tmp/%s.designspace' % FAMILY)
    >>> masters = {fReg.path: fReg, fBld.path: fBld, fLght.path: fLght, fCnd.path: fCnd }
    >>> # Now we have a design space and master dictionary, we can create the model
    >>> m = Model(ds, masters)
    >>> m
    <PageBot Model PageBotTest axes:2 masters:4>
    >>> [f.info.styleName for f in m.masterList] # Sorted as defined, with Regular as #1.
    ['Regular', 'Light', 'Bold', 'Condensed']
    >>> # Get combined coordinates from all masters. This is why they need to be compatible.
    >>> mpx, mpy, mcx, mcy, mt = m.getMasterValues(GNAME) # Coordinates in order of masterList for (p0, p1, p2, p3)
    >>> mpx # [[px0, px0, px0, px0], [px1, px1, px1, px1], [px2, px2, px2, px2], [px3, px3, px3, px3]]
    [[50, 70, 30, 20], [450, 270, 830, 820], [450, 270, 830, 820], [50, 70, 30, 20]]
    >>> mpy # [[py1, py1, py1, py1], [py2, py2, py2, py2], ...]
    [[800, 800, 800, 400.0], [800, 800, 800, 400.0], [0, 0, 0, 0], [0, 0, 0, 0]]
    >>> mcx, mcy # No components here
    ([], [])
    >>> mt
    [[500, 340, 860, 840]]

    >>> dpx, dpy, dcx, dcy, dt = m.getDeltas(GNAME) # Point deltas, component deltas, metrics deltas
    >>> dpx # [[dx1, dx1, dx1, dx1], [dx2, dx2, dx2, dx2], ...]
    [[70, -20.0, -40.0, -50.0], [270, 180.0, 560.0, 550.0], [270, 180.0, 560.0, 550.0], [70, -20.0, -40.0, -50.0]]
    >>> dt
    [[340, 160.0, 520.0, 500.0]]
    >>> sorted(m.ds.axes.keys())
    ['YTUC', 'wght']
    >>> sorted(m.supports()) # List with normalized master locations
    [{}, {'YTUC': (-1.0, -1.0, 0)}, {'wght': (-1.0, -1.0, 0)}, {'wght': (0, 1.0, 1.0)}]
    >>> m.getScalars(dict(wght=1, YTUC=0)) # Order: Regular, Light, Bold, Condensed
    [1.0, 0.0, 1.0, 0.0]
    >>> m.getScalars(dict(wght=0, YTUC=0))
    [1.0, 0.0, 0.0, 0.0]
    >>> m.getScalars(dict(wght=-1, YTUC=0))
    [1.0, 1.0, 0.0, 0.0]
    >>> m.getScalars(dict(wght=1, YTUC=-1))
    [1.0, 0.0, 1.0, 1.0]
    >>> m.getScalars(dict(wght=0, YTUC=-1))
    [1.0, 0.0, 0.0, 1.0]
    >>> m.getScalars(dict(wght=-1, YTUC=-1))
    [1.0, 1.0, 0.0, 1.0]
    >>> m.getScalars(dict(wght=0.8, YTUC=-0.3))
    [1.0, 0.0, 0.8, 0.3]
    >>> from fontParts.world import NewFont
    >>> fInt = NewFont()
    >>> gInt = fInt.newGlyph(GNAME)
    >>> loc = dict(wght=0, YTUC=500)
    >>> #points, components, metrics = m.interpolateValues(GNAME, loc)
    >>> #points
    #[(50.0, 800.0), (450.0, 800.0), (450.0, 0.0), (50.0, 0.0)]
    >>> #components
    #[]
    >>> #metrics
    #[500.0]
    >>> loc = dict(wght=-1, YTUC=0) # Location outside the boundaries of an axis answers min/max of the axis
    >>> #points, components, metrics = m.interpolateValues(GNAME, loc)
    >>> #points
    [(0.0, 400.0), (1000.0, 400.0), (1000.0, 0.0), (0.0, 0.0)]
    >>> #components
    #[]
    >>> #metrics
    #[1000.0]
    """

    def __init__(self, designSpace, masters, instances=None):
        """Create a VariableFont interpolation model. DesignSpace """
        assert masters, ValueError('%s No master fonts defined. The dictionay shouls master the design space.' % self)
        self.ds = designSpace
        # Property self.masterList creates font list in order of the design space.
        self.masters = masters # Can be an empty list, if the design space is new.

        self.vm = VariationModel(self.ds.normalizedLocations, axisOrder=self.ds.axisOrder)
        # Property self.instanceList creates font list in order of the design space.
        if instances is None:
            instances = {}
        self.instances = instances
        # Initialize cached property values.
        self.clear()
        # Set the self.paths (path->fontInfo) dictionary, so all fonts are enabled by default.
        # Set the path->fontInfo dictionary of fonts from the design space that are disable.
        # This allows the calling function to enable/disable fonts from interpolation.
        self.disabledMasterPaths = [] # Paths of masters not to be used in delta calculation.

    def __repr__(self):
        return '<PageBot %s %s axes:%d masters:%d>' % (self.__class__.__name__, self.ds.familyName, len(self.ds.axes), len(self.masters))

    def _get_masters(self):
        return self._masters
    def _set_masters(self, masters):
        self._masters = masters
        self._masterList = None
    masters = property(_get_masters, _set_masters)

    def _get_defaultMaster(self):
        """Answers the master font at default location. Answer None if it does
        not exist or cannot be found."""
        defaultMaster = None
        defaultMasterInfo = self.ds.defaultMaster
        if defaultMasterInfo is not None:
            defaultMaster = self.masters.get(defaultMasterInfo.path)
        return defaultMaster
    defaultMaster = property(_get_defaultMaster)

    def _get_masterList(self):
        """Dictionary of real master Font instances, path is key. Always start with the default,
        then omit the default if it else where in the list.
        """
        defaultMaster = self.defaultMaster # Get the ufo master for the default location.
        if self._masterList is None:
            self._masterList = []
            if defaultMaster is not None:
                self._masterList.append(defaultMaster) # Force to be first in the list.
            for masterPath in self.ds.masterPaths:
                if defaultMaster is not None and masterPath == defaultMaster.path:
                    continue
                if not masterPath in self.disabledMasterPaths:
                    self._masterList.append(self.masters[masterPath])
        return self._masterList
    masterList = property(_get_masterList)

    def getAxisMasters(self, axis):
        """Answers a tuple of two lists of all masters on the axis, on either
        size of the default master, and including the default master. If
        mininum == default or default == maximum, then that side if the axis
        only contains the default master."""
        # FIXME: minMasters, maxMasters unused.
        minMasters = []
        maxMasters = []

        for axisSide in self.ds.getAxisMasters(axis.tag):
            for masterInfo in axisSide:
                if masterInfo.path:
                    # FIXME: masters doesn't exist.
                    #masters.append(self.masters.get(masterInfo.path))
                    pass
        return minMasters, maxMasters

    def _get_instanceList(self):
        """Dictionary of real master Font instance, path is key."""
        if self._instanceList is None:
            self._instanceList = []
            for instancePath in self.ds.instancePaths:
                self._instanceList.append(self.instance[instancePath])
        return self._instanceList
    instanceList = property(_get_instanceList)

    def clear(self):
        """Clear the cached master values for the last interpolated glyph.
        This forces the values to be collected for the next glyph interpolation."""
        self._masterList = None
        self._instanceList = None
        self._masterValues = None
        self._defaultMaster = None

    # Rendering

    def getScalars(self, location):
        """Answers the list of scalars (multipliers 0..1) for each master in the
        given nLocation (normalized values -1..0..1): [1.0, 0.0, 0.8, 0.3] with
        respectively Regular, Light, Bold, Condensed as master ordering.
        """
        return self.vm.getScalars(location)

    def supports(self):
        return self.vm.supports

    def getDeltas(self, glyphName):

        pointXDeltas = []
        pointYDeltas = []
        componentXDeltas = []
        componentYDeltas = []
        metricsDeltas = []
        mvx, mvy, mvCx, mvCy, mt = self.getMasterValues(glyphName)

        for pIndex, _ in enumerate(mvx):
            pointXDeltas.append(self.vm.getDeltas(mvx[pIndex]))
            pointYDeltas.append(self.vm.getDeltas(mvy[pIndex]))

        for cIndex, _ in enumerate(mvCx):
            componentXDeltas.append(self.vm.getDeltas(mvCx[cIndex]))
            componentYDeltas.append(self.vm.getDeltas(mvCy[cIndex]))

        for mIndex, _ in enumerate(mt):
            metricsDeltas.append(self.vm.getDeltas(mt[mIndex]))

        return pointXDeltas, pointYDeltas, componentXDeltas, componentYDeltas, metricsDeltas

    def getMasterValues(self, glyphName):
        """Answers the (mvx, mvy, mvCx, mvCy), for point and component
        transformation, lists of corresponding master glyphs, in the same order
        as the self.masterList."""
        if self._masterValues is None:
            mvx = [] # X values of point transformations. Length is the amount of masters
            mvy = [] # Y values
            mvCx = [] # X values of components transformation
            mvCy = [] # Y values
            mMt = [[]] # Metrics values
            self._masterValues = mvx, mvy, mvCx, mvCy, mMt # Initialize result tuple

            for master in self.masterList:
                if not glyphName in master:
                    continue

                g = master[glyphName]
                points = getPoints(g) # Collect the master points

                for pIndex, point in enumerate(points):
                    if len(mvx) <= pIndex:
                        mvx.append([])
                        mvy.append([])
                    mvx[pIndex].append(point.x)
                    mvy[pIndex].append(point.y)
                components = getComponents(g) # Collect the master components
                """
                for cIndex, component in enumerate(components):
                    t = component.transformation
                    if len(mvCx) < cIndex:
                        mvCx.append([])
                        mvCy.append([])
                    mvCx[cIndex].append(t[-2])
                    mvCy[cIndex].append(t[-1])
                """
                mMt[0].append(g.width) # Other interpolating metrics can be added later.

        return self._masterValues

    def interpolateValues(self, glyphName, location):
        """Interpolate the glyph from masters and answer the list of (x,y)
        points tuples, component transformation and metrics.

        The axis location in world values is normalized to (-1..0..1)."""
        nLocation = normalizeLocation(location, self.ds.tripleAxes)
        interpolatedPoints = []
        interpolatedComponents = []
        interpolatedMetrics = []
        mvx, mvy, mvCx, mvCy, mMt = self.getMasterValues(glyphName)

        for pIndex, _ in enumerate(mvx):
            interpolatedPoints.append((
                self.vm.interpolateFromMasters(nLocation, mvx[pIndex]),
                self.vm.interpolateFromMasters(nLocation, mvy[pIndex])
            ))

        for cIndex, _ in enumerate(mvCx):
            interpolatedComponents.append((
                self.vm.interpolateFromMasters(nLocation, mvCx[cIndex]),
                self.vm.interpolateFromMasters(nLocation, mvCy[cIndex])
            ))

        for mIndex, _ in enumerate(mMt):
            interpolatedMetrics.append(
                self.vm.interpolateFromMasters(nLocation, mMt[mIndex])
            )
        return interpolatedPoints, interpolatedComponents, interpolatedMetrics

    def interpolateGlyph(self, glyph, location):
        """Interpolate the glyph from the masters. If glyph is not compatible
        with the masters, then first copy one of the masters into glyphs.
        Location is normalized to (-1..0..1)."""

        # If there are components, make sure to interpolate them first, then
        # interpolate (dx, dy). Check if the referenced glyph exists.
        # Otherwise copy it from one of the masters into the parent of glyph.
        mvx, mvy, mvCx, mvCy, mMt = self.getMasterValues(glyph.name)
        points = getPoints(glyph)
        components = getComponents(glyph)

        if components:
            font = glyph.getParent()
            for component in components:
                if component.baseGlyph in font:
                    self.interpolateGlyph(font[component.baseGlyph], location)
        if len(points) != len(mvx): # Glyph may not exist, or not compatible.
            # Clear the glyph and copy from the first master in the list, so it always interpolates.
            glyph.clear()
            masterGlyph = self.masterList[0][glyph.name]
            masterGlyph.draw(glyph.getPen())
            points = getPoints(glyph) # Get new set of points

        # Normalize to location (-1..0..1)
        nLocation = normalizeLocation(location, self.ds.tripleAxes)
        # Get the point of the glyph, and set their (x,y) to the calculated variable points.
        for pIndex, _ in enumerate(mvx):
            p = points[pIndex]
            p.x = self.vm.interpolateFromMasters(nLocation, mvx[pIndex])
            p.y = self.vm.interpolateFromMasters(nLocation, mvy[pIndex])

        for cIndex, _ in enumerate(mvCx):
            c = components[cIndex]
            t = list(c.transformation)
            t[-2] = self.vm.interpolateFromMasters(nLocation, mvCx[cIndex])
            t[-1] = self.vm.interpolateFromMasters(nLocation, mvCy[cIndex])
            c.transformation = t

        glyph.width = self.vm.interpolateFromMasters(nLocation, mMt[0])
Example #25
0
    def preview(self,
                position: dict = {},
                deltasStore: dict = {},
                font=None,
                forceRefresh=True,
                axisPreview=False):

        locationKey = ','.join(
            [k + ':' + str(v)
             for k, v in position.items()]) if position else ','.join([
                 k + ':' + str(v)
                 for k, v in self.normalizedValueToMinMaxValue(
                     self.getLocation(), self).items()
             ])

        #### 3 CONDITIONS DE DESSIN POSSIBLE EN CAS D'ÉLEMENT SELECTIONNÉ ####
        redrawAndTransformAll = False
        redrawAndTransformSelected = False
        onlyTransformSelected = False

        if self.selectedElement and not self.reinterpolate:
            onlyTransformSelected = True
        elif self.selectedElement and self.reinterpolate and not axisPreview:
            redrawAndTransformAll = True
        elif self.selectedElement:
            redrawAndTransformSelected = True
        else:
            redrawAndTransformAll = True
        ############################################################

        #### S'IL N'Y A PAS DE SELECTION, RECHERCHE D'UN CACHE ####
        previewLocationsStore = self.previewLocationsStore.get(
            locationKey, False)
        if axisPreview:
            redrawSeletedElement = self.redrawSelectedElementSource
        else:
            redrawSeletedElement = self.redrawSelectedElementPreview
        if not self.redrawSelectedElementSource:
            if previewLocationsStore:
                # print('I have cache', locationKey)
                for p in previewLocationsStore:
                    yield p
                return
            if axisPreview and self.axisPreview:
                # print('DC has axisPreview', self.axisPreview)
                for e in self.axisPreview:
                    yield e
                return
            elif not forceRefresh and self.previewGlyph and not axisPreview:
                # print('DC has previewGlyph', self.previewGlyph)
                for e in self.previewGlyph:
                    yield e
                return
        ############################################################

        #### S'IL N'Y A UNE SELECTION MAIS PAS D'INSTRUCTION DE REDESSIN, RECHERCHE D'UN CACHE ####
        if not redrawAndTransformAll and not redrawAndTransformSelected and not onlyTransformSelected:
            if previewLocationsStore:
                # print('I have cache', locationKey)
                for p in previewLocationsStore:
                    yield p
                return
            ############################################################
        else:
            #### IL Y A DES INSTRUCTION DE REDESSIN ####
            if axisPreview:
                preview = self.axisPreview
            else:
                preview = self.previewGlyph
            """ Dans cette condition on ne ce soucis pas du cache, on redessine tout"""
            if redrawAndTransformAll:
                if axisPreview:
                    preview = self.axisPreview = []
                else:
                    preview = self.previewGlyph = []
            else:
                """Dans cette condition on récupère ce qu'il y a dans le cache et on travaillera 
                dessus après, soit pour modifier les instructions de transformation de l'element selectionné
                soit pour recalculer l'element et ses instructions de transformation. Les autres elements du cache ne seront
                pas recalculé"""
                if previewLocationsStore:
                    if axisPreview:
                        preview = self.axisPreview = previewLocationsStore
                    else:
                        preview = self.previewGlyph = previewLocationsStore

        if not position:
            position = self.getLocation()

        position = self.normalizedValueToMinMaxValue(position, self)
        # print("position", position, "\n")

        locations = [{}]
        locations.extend([
            self.normalizedValueToMinMaxValue(x["location"], self)
            for x in self._glyphVariations if x["on"]
        ])
        # print("location", locations, "\n\n")
        model = VariationModel(locations)

        if redrawAndTransformAll:
            masterDeepComponents = self._deepComponents
            axesDeepComponents = [
                variation.get("deepComponents")
                for variation in self._glyphVariations.getList()
                if variation.get("on") == 1
            ]
        else:
            masterDeepComponents = [
                x for i, x in enumerate(self._deepComponents)
                if i in self.selectedElement
            ]
            axesDeepComponents = [[
                x for i, x in enumerate(variation.get("deepComponents"))
                if i in self.selectedElement
            ] for variation in self._glyphVariations.getList()
                                  if variation.get("on") == 1]

        result = []
        deltasList = []
        for i, deepComponent in enumerate(masterDeepComponents):
            variations = []
            for gv in axesDeepComponents:
                variations.append(gv[i])
            deltas = model.getDeltas([deepComponent, *variations])

            # result.append(model.interpolateFromMasters(position, [deepComponent, *variations]))
            result.append(model.interpolateFromDeltas(position, deltas))
            deltasList.append(deltas)

        if font is None:
            font = self.getParent()
        for i, dc in enumerate(result):
            name = dc.get("name")
            if not set([name]) & (font.staticAtomicElementSet()
                                  | font.staticDeepComponentSet()
                                  | font.staticCharacterGlyphSet()):
                continue
            g = font[name]

            if onlyTransformSelected:
                preview[self.selectedElement[i]].transformation = dc.get(
                    "transform")
            else:
                resultGlyph = RGlyph()
                pos = dc.get('coord')
                g = g.preview(pos, font, forceRefresh=True)

                for c in g:
                    c = c.glyph
                    c.draw(resultGlyph.getPen())

                if redrawAndTransformSelected:
                    preview[self.selectedElement[i]].resultGlyph = resultGlyph
                    preview[self.selectedElement[i]].transformation = dc.get(
                        "transform")
                else:
                    preview.append(
                        self.ResultGlyph(resultGlyph, dc.get("transform")))

        self.previewLocationsStore[','.join(
            [k + ':' + str(v) for k, v in position.items()])] = preview

        if axisPreview:
            self.redrawSelectedElementSource = False
        else:
            self.redrawSelectedElementPreview = False

        for resultGlyph in preview:
            yield resultGlyph