ufoProcessor icon indicating copy to clipboard operation
ufoProcessor copied to clipboard

VariationModelMutator._normalize does not handle axis mapping

Open madig opened this issue 6 years ago • 3 comments

I am trying to turn the the UFOs plus Designspace at https://gitlab.gnome.org/GNOME/cantarell-fonts/tree/ufo-conversion/src into static instances, using varLib. This fails because VariationModelMutator._normalize does not take the axis mapping into consideration, it then passes bogus values to varLib that fails to find the neutral location.

Aside: A closer look at AxisMapper._makeWarpFromList shows that it appends a false default location: mapValues is [(100.0, 20.0), (300.0, 40.0), (400.0, 80.0), (700.0, 126.0), (800.0, 170.0), (400.0, 400.0)] because of https://github.com/LettError/ufoProcessor/blob/master/Lib/ufoProcessor/varModels.py#L29.

Reproduction code snippet:

from pathlib import Path
from typing import Optional

import fontTools.designspaceLib
import fontTools.varLib
import ufoProcessor

class InMemoryDesignspaceProcessor(ufoProcessor.DesignSpaceProcessor):
    def generateUFO(self, processRules=True, glyphNames=None, pairs=None, bend=True):
        self.loadFonts()
        self.findDefault()
        if self.default is None:
            raise ufoProcessor.UFOProcessorError(
                "Can't generate UFO from this designspace: no default font.", self
            )

        for instanceDescriptor in self.instances:
            instanceDescriptor.font = self.makeInstance(
                instanceDescriptor,
                processRules,
                glyphNames=glyphNames,
                pairs=pairs,
                bend=bend,
            )

        return True

    # XXX: Works around bug in varLib that compares sourceDescriptor.location in design
    # space to defaultLoc in user space.
    def findDefault(self) -> Optional[fontTools.designspaceLib.SourceDescriptor]:
        """Set and return SourceDescriptor at the default location or None.

        The default location is the set of all `default` values of all
        axes (user space).
        """
        self.default = None
        axis_name_to_axis = {axis.name: axis for axis in self.axes}
        default_design_location = {
            name: fontTools.varLib.models.piecewiseLinearMap(
                value, dict(reversed(axis_name_to_axis[name].map))
            )
            for name, value in self.defaultLoc.items()
        }

        for sourceDescriptor in self.sources:
            if sourceDescriptor.location == default_design_location:
                self.default = sourceDescriptor
                return sourceDescriptor

        return None


designspace_path = Path("Cantarell.designspace")
designspace = InMemoryDesignspaceProcessor.fromfile(designspace_path)
designspace.useVarlib = True
designspace.generateUFO()

madig avatar Mar 08 '19 20:03 madig

Uhm. Why does VariationModelMutator get a model when AxisMapper already creates one with warped locations?

Edit: slightly different purpose from the looks of it...

madig avatar Mar 08 '19 22:03 madig

It seems to me that AxisMapper only goes from user to design locations. Since in VariationModelMutator.__init__ we normalize the design locations but pass normalizeLocation the user min/max/default, we need to convert the design locations to user locations before passing them to _normalize.

madig avatar Mar 08 '19 22:03 madig

This has not been addressed for too long. Curious if the problem still exists when using UFOOperator.

LettError avatar Sep 22 '23 11:09 LettError