Repository: LettError/ufoProcessor

Issue:
Swapping glyphs deletes anchors
1. ufoProcess [SwapGlyphNames.zip](https://github.com/LettError/ufoProcessor/files/4129654/SwapGlyphNames.zip)
2. Compare anchors of `a` and `a.swap` in C.ufo and D.ufo

Cosimo fixed this for fontmake over at https://github.com/googlefonts/fontmake/pull/632.

Discussion:

Patch:
diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py
index 4d5ec8c..3357fe9 100644
--- a/Lib/ufoProcessor/__init__.py
+++ b/Lib/ufoProcessor/__init__.py
@@ -151,6 +151,10 @@ def swapGlyphNames(font, oldName, newName, swapNameExtension = "_______________s
     # park the old glyph
     if not swapName in font:
         font.newGlyph(swapName)
+    # get anchors
+    oldAnchors = font[oldName].anchors
+    newAnchors = font[newName].anchors
+
     # swap the outlines
     font[swapName].clear()
     p = font[swapName].getPointPen()
@@ -161,11 +165,32 @@ def swapGlyphNames(font, oldName, newName, swapNameExtension = "_______________s
     p = font[oldName].getPointPen()
     font[newName].drawPoints(p)
     font[oldName].width = font[newName].width
+    for a in newAnchors:
+        na = defcon.Anchor()
+        na.name = a.name
+        na.x = a.x
+        na.y = a.y
+        # FontParts and Defcon add anchors in different ways
+        # this works around that.
+        try:
+            font[oldName].naked().appendAnchor(na)
+        except AttributeError:
+            font[oldName].appendAnchor(na)
 
     font[newName].clear()
     p = font[newName].getPointPen()
     font[swapName].drawPoints(p)
     font[newName].width = font[swapName].width
+    for a in oldAnchors:
+        na = defcon.Anchor()
+        na.name = a.name
+        na.x = a.x
+        na.y = a.y
+        try:
+            font[newName].naked().appendAnchor(na)
+        except AttributeError:
+            font[newName].appendAnchor(na)
+
 
     # remap the components
     for g in font:


Test patch:
diff --git a/Tests/tests.py b/Tests/tests.py
index cf59053..142769b 100644
--- a/Tests/tests.py
+++ b/Tests/tests.py
@@ -52,6 +52,11 @@ def addGlyphs(font, s, addSupportLayer=True):
         p.lineTo((0,font.info.ascender))
         p.closePath()
         g.width = w
+        na = defcon.Anchor()
+        na.name = "top"
+        na.x = 0
+        na.y = w
+        g.appendAnchor(na)
 
     if addSupportLayer:
         font.newLayer('support')
@@ -354,6 +359,9 @@ def testSwap(docPath):
     # So, components have to be remapped. 
     assert new['wide.component'].components[0].baseGlyph == "narrow"
     assert new['narrow.component'].components[0].baseGlyph == "wide"
+    # Check that anchors swapped
+    assert new['wide'].anchors[0].y == old['narrow'].anchors[0].y
+    assert new['narrow'].anchors[0].y == old['wide'].anchors[0].y
 
 def testAxisMuting():
     d = DesignSpaceProcessor_using_defcon(useVarlib=True)
diff --git a/Tests/tests_fp.py b/Tests/tests_fp.py
index 1121a98..b882898 100644
--- a/Tests/tests_fp.py
+++ b/Tests/tests_fp.py
@@ -38,11 +38,12 @@ def addGlyphs(font, s, addSupportLayer=True):
         p.lineTo((0,font.info.ascender))
         p.closePath()
         g.width = w
+        g.appendAnchor("top", (0, w))
 
     if addSupportLayer:
         font.newLayer('support')
         print(n for n in font.layers if n.name == 'support')
-        layer = font.layers['support']
+        layer = font.getLayer('support')
         layer.newGlyph('glyphFive')
         layer.newGlyph('glyphOne')  # add an empty glyph to see how it is treated
         lg = layer['glyphFive']
@@ -90,6 +91,10 @@ def _makeTestFonts(rootPath):
     path3 = os.path.join(rootPath, "instances", "geometryInstance%3.3f.ufo")
     path4 = os.path.join(rootPath, "anisotropic_instances", "geometryInstanceAnisotropic1.ufo")
     path5 = os.path.join(rootPath, "anisotropic_instances", "geometryInstanceAnisotropic2.ufo")
+    for path in [path1, path2, path3, path4, path5]:
+        d = os.path.dirname(path)
+        if not os.path.exists(d):
+            os.makedirs(d)
     f1 = fontParts.fontshell.RFont()
     fillInfo(f1)
     addGlyphs(f1, 100, addSupportLayer=False)
@@ -157,7 +162,7 @@ def _makeTestDocument(docPath, useVarlib=True):
     a.maximum = 1000
     a.default = 0
     a.tag = "pop*"
-    a.map = [(500,250)]
+    a.map = [(0,0),(500,250),(1000,1000)]
     d.addAxis(a)
 
     s1 = SourceDescriptor()
@@ -241,13 +246,13 @@ def _testGenerateInstances(docPath, useVarlib=True):
 
 def testSwap(docPath):
     srcPath, dstPath = _makeSwapFonts(os.path.dirname(docPath))
-    f = fontParts.fontshell.Font(srcPath)
+    f = fontParts.fontshell.RFont(srcPath)
     swapGlyphNames(f, "narrow", "wide")
     f.info.styleName = "Swapped"
     f.save(dstPath)
     # test the results in newly opened fonts
-    old = fontParts.fontshell.Font(srcPath)
-    new = fontParts.fontshell.Font(dstPath)
+    old = fontParts.fontshell.RFont(srcPath)
+    new = fontParts.fontshell.RFont(dstPath)
     assert new.kerning.get(("narrow", "narrow")) == old.kerning.get(("wide","wide"))
     assert new.kerning.get(("wide", "wide")) == old.kerning.get(("narrow","narrow"))
     # after the swap these widths should be the same
@@ -259,6 +264,9 @@ def testSwap(docPath):
     # So, components have to be remapped. 
     assert new['wide.component'].components[0].baseGlyph == "narrow"
     assert new['narrow.component'].components[0].baseGlyph == "wide"
+    # Check that anchors swapped
+    assert new['wide'].anchors[0].y == old['narrow'].anchors[0].y
+    assert new['narrow'].anchors[0].y == old['wide'].anchors[0].y
 
 def testUnicodes(docPath, useVarlib=True):
     # after executing testSwap there should be some test fonts
@@ -267,7 +275,7 @@ def testUnicodes(docPath, useVarlib=True):
     d.read(docPath)
     for instance in d.instances:
         if os.path.exists(instance.path):
-            f = fontParts.fontshell.Font(instance.path)
+            f = fontParts.fontshell.RFont(instance.path)
             print("instance.path", instance.path)
             print("instance.name", instance.name, "f['narrow'].unicodes", f['narrow'].unicodes)
             #if instance.name == "TestFamily-TestStyle_pop1000.000":