Repository: fonttools/fonttools

Issue:
[designspaceLib] normalize() does not consider axisMap



Discussion:
is this still an issue? If so, can you elaborate what needs fixing? thanks
> is this still an issue? If so, can you elaborate what needs fixing? thanks

Yes, still valid.
Example, a system with 2 axes, `pop` and `snap` with masters at `0` and `100`. The snap axis has a map that scales `0` -> `33` and `100` to `66`.

![image](https://user-images.githubusercontent.com/1243961/45485420-dea20d00-b757-11e8-83fd-fdbc7b3f93d4.png)

The user sees the `snap` axis go between 33 and 66. The `pop` axis is unaffected and goes between `0` and `100`.

Normalising this system would only change the coordinates of the masters in the designspace. Not the mapped value in the UI. So how should normalise() use the axisMap?
Umm. I might be wrong but I thought axisMap works the other way around. Ie. it maps from user-visible system to internal linear-interpolation system.  Ie. in your example, user sees 0,100.
I need to look at the code and be back here.  I know there was a problem.  Details not fully in my head.
Reading the code, I see I remembered wrong. I'll take another look. 
There is a test with normalisation and a map. Does this not do the right thing?
https://github.com/fonttools/fonttools/blob/4bf522abe3b2c65f54699b748336f31a9ce8a3ea/Tests/designspaceLib/designspace_test.py#L607
@LettError that test case looks good to me

Resolving patch:
diff --git a/Lib/fontTools/designspaceLib/__init__.py b/Lib/fontTools/designspaceLib/__init__.py
--- a/Lib/fontTools/designspaceLib/__init__.py
+++ b/Lib/fontTools/designspaceLib/__init__.py
@@ -1188,31 +1188,21 @@ def findDefault(self):
         return None
 
     def normalizeLocation(self, location):
-        # adapted from fontTools.varlib.models.normalizeLocation because:
-        #   - this needs to work with axis names, not tags
-        #   - this needs to accomodate anisotropic locations
-        #   - the axes are stored differently here, it's just math
+        from fontTools.varLib.models import normalizeValue
+
         new = {}
         for axis in self.axes:
             if axis.name not in location:
                 # skipping this dimension it seems
                 continue
-            v = location.get(axis.name, axis.default)
-            if type(v) == tuple:
-                v = v[0]
-            if v == axis.default:
-                v = 0.0
-            elif v < axis.default:
-                if axis.default == axis.minimum:
-                    v = 0.0
-                else:
-                    v = (max(v, axis.minimum) - axis.default) / (axis.default - axis.minimum)
-            else:
-                if axis.default == axis.maximum:
-                    v = 0.0
-                else:
-                    v = (min(v, axis.maximum) - axis.default) / (axis.maximum - axis.default)
-            new[axis.name] = v
+            value = location[axis.name]
+            # 'anisotropic' location, take first coord only
+            if isinstance(value, tuple):
+                value = value[0]
+            triple = [
+                axis.map_forward(v) for v in (axis.minimum, axis.default, axis.maximum)
+            ]
+            new[axis.name] = normalizeValue(value, triple)
         return new
 
     def normalize(self):
diff --git a/Lib/fontTools/varLib/plot.py b/Lib/fontTools/varLib/plot.py
--- a/Lib/fontTools/varLib/plot.py
+++ b/Lib/fontTools/varLib/plot.py
@@ -35,7 +35,7 @@ def plotLocationsSurfaces(locations, fig, names=None, **kwargs):
 	assert len(locations[0].keys()) == 2
 
 	if names is None:
-		names = ['']
+		names = [None] * len(locations)
 
 	n = len(locations)
 	cols = math.ceil(n**.5)
@@ -45,10 +45,11 @@ def plotLocationsSurfaces(locations, fig, names=None, **kwargs):
 	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))):
+	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)
+		if name is not None:
+			axis3D.set_title(name)
 		axis3D.set_xlabel(ax1)
 		axis3D.set_ylabel(ax2)
 		pyplot.xlim(-1.,+1.)


Test patch:
diff --git a/Tests/designspaceLib/designspace_test.py b/Tests/designspaceLib/designspace_test.py
--- a/Tests/designspaceLib/designspace_test.py
+++ b/Tests/designspaceLib/designspace_test.py
@@ -619,7 +619,7 @@ def test_normalise4():
     for axis in doc.axes:
         r.append((axis.name, axis.map))
     r.sort()
-    assert r == [('ddd', [(0, 0.1), (300, 0.5), (600, 0.5), (1000, 0.9)])]
+    assert r == [('ddd', [(0, 0.0), (300, 0.5), (600, 0.5), (1000, 1.0)])]
 
 def test_axisMapping():
     # note: because designspance lib does not do any actual
@@ -638,7 +638,7 @@ def test_axisMapping():
     for axis in doc.axes:
         r.append((axis.name, axis.map))
     r.sort()
-    assert r == [('ddd', [(0, 0.1), (300, 0.5), (600, 0.5), (1000, 0.9)])]
+    assert r == [('ddd', [(0, 0.0), (300, 0.5), (600, 0.5), (1000, 1.0)])]
 
 def test_rulesConditions(tmpdir):
     # tests of rules, conditionsets and conditions