ufoProcessor
ufoProcessor copied to clipboard
VariationModelMutator._normalize does not handle axis mapping
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()
Uhm. Why does VariationModelMutator get a model when AxisMapper already creates one with warped locations?
Edit: slightly different purpose from the looks of it...
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.
This has not been addressed for too long. Curious if the problem still exists when using UFOOperator.