From 23297c71789ef04c9f220ae5278a0496c366f3de Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Wed, 8 Jun 2022 16:20:42 +0200 Subject: [PATCH 01/26] Preparatory work for DS5 * generator for test ds5 designspace * some exploring in sorting axes, discrete locations and masters --- Lib/ufoProcessor/__init__.py | 273 +++++++++++------- .../benderTest1.ufo/fontinfo.plist | 34 --- .../glyphs.background/contents.plist | 5 - .../benderTest1.ufo/glyphs/a.glif | 19 -- .../benderTest1.ufo/lib.plist | 236 --------------- .../benderTest2.ufo/fontinfo.plist | 34 --- .../glyphs.background/contents.plist | 5 - .../benderTest2.ufo/glyphs/a.glif | 19 -- .../benderTest2.ufo/kerning.plist | 11 - .../benderTest2.ufo/lib.plist | 236 --------------- .../benderTest3.ufo/fontinfo.plist | 34 --- .../glyphs.background/contents.plist | 5 - .../benderTest3.ufo/glyphs/a.glif | 19 -- .../benderTest3.ufo/kerning.plist | 11 - .../benderTest3.ufo/lib.plist | 236 --------------- .../BenderTest-FarOut.ufo/fontinfo.plist | 34 --- .../BenderTest-FarOut.ufo/glyphs/a.glif | 19 -- .../BenderTest-FarOut.ufo/kerning.plist | 11 - .../instances/BenderTest-FarOut.ufo/lib.plist | 243 ---------------- .../fontinfo.plist | 34 --- .../BenderTest-Intermediate.ufo/glyphs/a.glif | 19 -- .../glyphs/contents.plist | 8 - .../BenderTest-Intermediate.ufo/kerning.plist | 11 - .../BenderTest-Intermediate.ufo/lib.plist | 243 ---------------- Tests/20190830 benders/test.py | 80 ----- .../202206 discrete spaces/ds5_makeTestDoc.py | 106 +++++++ .../features.fea | 1 + .../fontinfo.plist | 53 ++++ .../glyphs/contents.plist | 4 +- .../glyphs/glyphO_ne.glif | 19 ++ .../glyphs/layerinfo.plist | 0 .../groups.plist | 16 + .../kerning.plist | 20 ++ .../layercontents.plist | 0 .../lib.plist | 30 ++ .../metainfo.plist | 0 .../features.fea | 1 + .../fontinfo.plist | 53 ++++ .../glyphs/contents.plist | 4 +- .../glyphs/glyphO_ne.glif | 13 + .../glyphs/layerinfo.plist | 0 .../groups.plist | 16 + .../kerning.plist | 20 ++ .../layercontents.plist | 0 .../lib.plist | 30 ++ .../metainfo.plist | 0 .../features.fea | 1 + .../fontinfo.plist | 53 ++++ .../glyphs/contents.plist | 4 +- .../glyphs/glyphO_ne.glif | 31 ++ .../glyphs/layerinfo.plist | 0 .../groups.plist | 16 + .../kerning.plist | 20 ++ .../layercontents.plist | 6 +- .../lib.plist | 30 ++ .../metainfo.plist | 0 .../features.fea | 1 + .../fontinfo.plist | 53 ++++ .../glyphs/contents.plist | 8 + .../glyphs/glyphO_ne.glif | 19 ++ .../glyphs}/layerinfo.plist | 2 +- .../groups.plist | 16 + .../kerning.plist | 20 ++ .../layercontents.plist | 6 +- .../lib.plist | 30 ++ .../metainfo.plist | 0 .../features.fea | 1 + .../fontinfo.plist | 53 ++++ .../glyphs/contents.plist | 8 + .../glyphs/glyphO_ne.glif | 43 +++ .../glyphs}/layerinfo.plist | 2 +- .../groups.plist | 16 + .../kerning.plist | 20 ++ .../layercontents.plist | 6 +- .../lib.plist | 30 ++ .../metainfo.plist | 0 .../features.fea | 1 + .../fontinfo.plist | 53 ++++ .../glyphs/contents.plist | 8 + .../glyphs/glyphO_ne.glif | 25 ++ .../glyphs}/layerinfo.plist | 2 +- .../groups.plist | 16 + .../kerning.plist | 20 ++ .../layercontents.plist | 10 + .../lib.plist | 30 ++ .../metainfo.plist} | 9 +- .../features.fea | 1 + .../fontinfo.plist | 53 ++++ .../glyphs/contents.plist | 8 + .../glyphs/glyphO_ne.glif | 19 ++ .../glyphs/layerinfo.plist} | 4 +- .../groups.plist | 16 + .../kerning.plist | 20 ++ .../layercontents.plist | 10 + .../lib.plist | 30 ++ .../metainfo.plist | 10 + .../features.fea | 1 + .../fontinfo.plist | 53 ++++ .../glyphs/contents.plist | 8 + .../glyphs/glyphO_ne.glif | 13 + .../glyphs/layerinfo.plist | 8 + .../groups.plist | 16 + .../kerning.plist | 20 ++ .../layercontents.plist | 10 + .../lib.plist | 30 ++ .../metainfo.plist | 10 + .../features.fea | 1 + .../fontinfo.plist | 53 ++++ .../glyphs/contents.plist | 8 + .../glyphs/glyphO_ne.glif | 31 ++ .../glyphs/layerinfo.plist | 8 + .../groups.plist | 16 + .../kerning.plist | 20 ++ .../layercontents.plist | 10 + .../lib.plist | 30 ++ .../metainfo.plist | 10 + .../features.fea | 1 + .../fontinfo.plist | 53 ++++ .../glyphs/contents.plist | 8 + .../glyphs/glyphO_ne.glif | 19 ++ .../glyphs/layerinfo.plist | 8 + .../groups.plist | 16 + .../kerning.plist | 20 ++ .../layercontents.plist | 10 + .../lib.plist | 30 ++ .../metainfo.plist | 10 + .../features.fea | 1 + .../fontinfo.plist | 53 ++++ .../glyphs/contents.plist | 8 + .../glyphs/glyphO_ne.glif | 43 +++ .../glyphs/layerinfo.plist | 8 + .../groups.plist | 16 + .../kerning.plist | 20 ++ .../layercontents.plist | 10 + .../lib.plist | 30 ++ .../metainfo.plist | 10 + .../features.fea | 1 + .../fontinfo.plist | 53 ++++ .../glyphs/contents.plist | 8 + .../glyphs/glyphO_ne.glif | 25 ++ .../glyphs/layerinfo.plist | 8 + .../groups.plist | 16 + .../kerning.plist | 20 ++ .../layercontents.plist | 10 + .../lib.plist | 30 ++ .../metainfo.plist | 10 + ...r_testdoc1_output_roundtripped.designspace | 125 -------- 147 files changed, 2272 insertions(+), 1869 deletions(-) delete mode 100644 Tests/20190830 benders/benderTest1.ufo/fontinfo.plist delete mode 100644 Tests/20190830 benders/benderTest1.ufo/glyphs.background/contents.plist delete mode 100644 Tests/20190830 benders/benderTest1.ufo/glyphs/a.glif delete mode 100644 Tests/20190830 benders/benderTest1.ufo/lib.plist delete mode 100644 Tests/20190830 benders/benderTest2.ufo/fontinfo.plist delete mode 100644 Tests/20190830 benders/benderTest2.ufo/glyphs.background/contents.plist delete mode 100644 Tests/20190830 benders/benderTest2.ufo/glyphs/a.glif delete mode 100644 Tests/20190830 benders/benderTest2.ufo/kerning.plist delete mode 100644 Tests/20190830 benders/benderTest2.ufo/lib.plist delete mode 100644 Tests/20190830 benders/benderTest3.ufo/fontinfo.plist delete mode 100644 Tests/20190830 benders/benderTest3.ufo/glyphs.background/contents.plist delete mode 100644 Tests/20190830 benders/benderTest3.ufo/glyphs/a.glif delete mode 100644 Tests/20190830 benders/benderTest3.ufo/kerning.plist delete mode 100644 Tests/20190830 benders/benderTest3.ufo/lib.plist delete mode 100644 Tests/20190830 benders/instances/BenderTest-FarOut.ufo/fontinfo.plist delete mode 100644 Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/a.glif delete mode 100644 Tests/20190830 benders/instances/BenderTest-FarOut.ufo/kerning.plist delete mode 100644 Tests/20190830 benders/instances/BenderTest-FarOut.ufo/lib.plist delete mode 100644 Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/fontinfo.plist delete mode 100644 Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/a.glif delete mode 100644 Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/contents.plist delete mode 100644 Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/kerning.plist delete mode 100644 Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/lib.plist delete mode 100644 Tests/20190830 benders/test.py create mode 100644 Tests/202206 discrete spaces/ds5_makeTestDoc.py create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/features.fea create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist rename Tests/{20190830 benders/benderTest1.ufo => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo}/glyphs/contents.plist (76%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif rename Tests/{20190830 benders/benderTest1.ufo => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo}/glyphs/layerinfo.plist (100%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/groups.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist rename Tests/{20190830 benders/instances/BenderTest-FarOut.ufo => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo}/layercontents.plist (100%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/lib.plist rename Tests/{20190830 benders/benderTest1.ufo => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo}/metainfo.plist (100%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/features.fea create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist rename Tests/{20190830 benders/benderTest3.ufo => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo}/glyphs/contents.plist (76%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif rename Tests/{20190830 benders/benderTest2.ufo => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo}/glyphs/layerinfo.plist (100%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/groups.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist rename Tests/{20190830 benders/instances/BenderTest-Intermediate.ufo => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo}/layercontents.plist (100%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/lib.plist rename Tests/{20190830 benders/benderTest2.ufo => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo}/metainfo.plist (100%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/features.fea create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist rename Tests/{20190830 benders/instances/BenderTest-FarOut.ufo => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo}/glyphs/contents.plist (76%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif rename Tests/{20190830 benders/benderTest3.ufo => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo}/glyphs/layerinfo.plist (100%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/groups.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist rename Tests/{20190830 benders/benderTest1.ufo => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo}/layercontents.plist (65%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/lib.plist rename Tests/{20190830 benders/benderTest3.ufo => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo}/metainfo.plist (100%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/features.fea create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif rename Tests/{20190830 benders/benderTest3.ufo/glyphs.background => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs}/layerinfo.plist (85%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/groups.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist rename Tests/{20190830 benders/benderTest2.ufo => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo}/layercontents.plist (65%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/lib.plist rename Tests/{20190830 benders/instances/BenderTest-FarOut.ufo => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo}/metainfo.plist (100%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/features.fea create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif rename Tests/{20190830 benders/benderTest2.ufo/glyphs.background => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs}/layerinfo.plist (85%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/groups.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist rename Tests/{20190830 benders/benderTest3.ufo => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo}/layercontents.plist (65%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/lib.plist rename Tests/{20190830 benders/instances/BenderTest-Intermediate.ufo => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo}/metainfo.plist (100%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/features.fea create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif rename Tests/{20190830 benders/benderTest1.ufo/glyphs.background => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs}/layerinfo.plist (85%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/groups.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/layercontents.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/lib.plist rename Tests/{20190830 benders/benderTest1.ufo/kerning.plist => 202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/metainfo.plist} (60%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/features.fea create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/contents.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif rename Tests/{20190830 benders/benderTest2.ufo/glyphs/contents.plist => 202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist} (77%) create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/groups.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/layercontents.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/lib.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/metainfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/features.fea create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/contents.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/groups.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/layercontents.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/lib.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/metainfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/features.fea create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/contents.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/groups.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/layercontents.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/lib.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/metainfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/features.fea create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/contents.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/groups.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/layercontents.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/lib.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/metainfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/features.fea create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/contents.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/groups.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/layercontents.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/lib.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/metainfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/features.fea create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/contents.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/groups.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/layercontents.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/lib.plist create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/metainfo.plist delete mode 100644 Tests/spReader_testdocs/superpolator_testdoc1_output_roundtripped.designspace diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index 3357fe9..7755b2b 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -5,6 +5,7 @@ import os import logging, traceback import collections +import itertools # from pprint import pprint from fontTools.designspaceLib import DesignSpaceDocument, SourceDescriptor, InstanceDescriptor, AxisDescriptor, RuleDescriptor, processRules @@ -141,108 +142,108 @@ def getUFOVersion(ufoPath): return p.get('formatVersion') -def swapGlyphNames(font, oldName, newName, swapNameExtension = "_______________swap"): - # In font swap the glyphs oldName and newName. - # Also swap the names in components in order to preserve appearance. - # Also swap the names in font groups. - if not oldName in font or not newName in font: - return None - swapName = oldName + swapNameExtension - # 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() - font[oldName].drawPoints(p) - font[swapName].width = font[oldName].width - # lib? - font[oldName].clear() - 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: - for c in g.components: - if c.baseGlyph == oldName: - c.baseGlyph = swapName - continue - for g in font: - for c in g.components: - if c.baseGlyph == newName: - c.baseGlyph = oldName - continue - for g in font: - for c in g.components: - if c.baseGlyph == swapName: - c.baseGlyph = newName - - # change the names in groups - # the shapes will swap, that will invalidate the kerning - # so the names need to swap in the kerning as well. - newKerning = {} - for first, second in font.kerning.keys(): - value = font.kerning[(first,second)] - if first == oldName: - first = newName - elif first == newName: - first = oldName - if second == oldName: - second = newName - elif second == newName: - second = oldName - newKerning[(first, second)] = value - font.kerning.clear() - font.kerning.update(newKerning) - - for groupName, members in font.groups.items(): - newMembers = [] - for name in members: - if name == oldName: - newMembers.append(newName) - elif name == newName: - newMembers.append(oldName) - else: - newMembers.append(name) - font.groups[groupName] = newMembers - - remove = [] - for g in font: - if g.name.find(swapNameExtension)!=-1: - remove.append(g.name) - for r in remove: - del font[r] +# def swapGlyphNames(font, oldName, newName, swapNameExtension = "_______________swap"): +# # In font swap the glyphs oldName and newName. +# # Also swap the names in components in order to preserve appearance. +# # Also swap the names in font groups. +# if not oldName in font or not newName in font: +# return None +# swapName = oldName + swapNameExtension +# # 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() +# font[oldName].drawPoints(p) +# font[swapName].width = font[oldName].width +# # lib? +# font[oldName].clear() +# 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: +# for c in g.components: +# if c.baseGlyph == oldName: +# c.baseGlyph = swapName +# continue +# for g in font: +# for c in g.components: +# if c.baseGlyph == newName: +# c.baseGlyph = oldName +# continue +# for g in font: +# for c in g.components: +# if c.baseGlyph == swapName: +# c.baseGlyph = newName + +# # change the names in groups +# # the shapes will swap, that will invalidate the kerning +# # so the names need to swap in the kerning as well. +# newKerning = {} +# for first, second in font.kerning.keys(): +# value = font.kerning[(first,second)] +# if first == oldName: +# first = newName +# elif first == newName: +# first = oldName +# if second == oldName: +# second = newName +# elif second == newName: +# second = oldName +# newKerning[(first, second)] = value +# font.kerning.clear() +# font.kerning.update(newKerning) + +# for groupName, members in font.groups.items(): +# newMembers = [] +# for name in members: +# if name == oldName: +# newMembers.append(newName) +# elif name == newName: +# newMembers.append(oldName) +# else: +# newMembers.append(name) +# font.groups[groupName] = newMembers + +# remove = [] +# for g in font: +# if g.name.find(swapNameExtension)!=-1: +# remove.append(g.name) +# for r in remove: +# del font[r] class DecomposePointPen(object): @@ -869,11 +870,11 @@ def makeInstance(self, instanceDescriptor, glyphInstanceObject.drawPoints(pPen) font[glyphName].width = glyphInstanceObject.width font[glyphName].unicodes = glyphInstanceUnicodes - if doRules: - resultNames = processRules(self.rules, loc, self.glyphNames) - for oldName, newName in zip(self.glyphNames, resultNames): - if oldName != newName: - swapGlyphNames(font, oldName, newName) + # if doRules: + # resultNames = processRules(self.rules, loc, self.glyphNames) + # for oldName, newName in zip(self.glyphNames, resultNames): + # if oldName != newName: + # swapGlyphNames(font, oldName, newName) # copy the glyph lib? #for sourceDescriptor in self.sources: # if sourceDescriptor.copyLib: @@ -971,3 +972,61 @@ def _copyFontInfo(self, sourceInfo, targetInfo): setattr(targetInfo, infoAttribute, value) + # some ds5 work + def getOrderedDiscreteAxes(self): + # return the list of discrete axis objects, in the right order + axes = [] + for axisName in self.getAxisOrder(): + axisObj = self.getAxis(axisName) + if hasattr(axisObj, "values"): + axes.append(axisObj) + return axes + + def getDiscreteLocations(self): + # return a list of all permutated discrete locations + # do we have a list of ordered axes? + #print("ordered axes", self.getOrderedDiscreteAxes()) + values = [] + names = [] + discreteCoordinates = [] + dd = [] + for axis in self.getOrderedDiscreteAxes(): + values.append(axis.values) + names.append(axis.name) + for r in itertools.product(*values): + # make a small dict for the discrete location values + discreteCoordinates.append({a:b for a,b in zip(names,r)}) + return discreteCoordinates + + def findSourcesForDiscreteLocation(self, discreteLocDict): + # return a list of all sourcedescriptors that share the values in the discrete loc tuple + # discreteLocDict {'countedItems': 1.0, 'outlined': 0.0}, {'countedItems': 1.0, 'outlined': 1.0} + sources = [] + for s in self.sources: + ok = True + for name, value in discreteLocDict.items(): + if name in s.location: + if s.location[name] != value: + ok = False + else: + ok = False + continue + if ok: + sources.append(s) + return sources + + +if __name__ == "__main__": + # while we're testing + dsp = DesignSpaceProcessor() + ds5Path = "../../Tests/202206 discrete spaces/test.ds5.designspace" + print(f"Can we find the ds5 example at {ds5Path}? {os.path.exists(ds5Path)}") + + dsp.read(ds5Path) + print('all the axes\n', dsp.axes) + print(f'\nSo we will have {len(dsp.getDiscreteLocations())} continuous designspaces in this document') + for loc in dsp.getDiscreteLocations(): + print() + print(f'For this discrete location {loc} we have these masters available:') + print(dsp.findSourcesForDiscreteLocation(loc)) + diff --git a/Tests/20190830 benders/benderTest1.ufo/fontinfo.plist b/Tests/20190830 benders/benderTest1.ufo/fontinfo.plist deleted file mode 100644 index 09549d9..0000000 --- a/Tests/20190830 benders/benderTest1.ufo/fontinfo.plist +++ /dev/null @@ -1,34 +0,0 @@ - - - - - ascender - 750 - capHeight - 500 - descender - -250 - familyName - BenderTest - guidelines - - postscriptBlueValues - - postscriptFamilyBlues - - postscriptFamilyOtherBlues - - postscriptOtherBlues - - postscriptStemSnapH - - postscriptStemSnapV - - styleName - One - unitsPerEm - 1000 - xHeight - 500 - - diff --git a/Tests/20190830 benders/benderTest1.ufo/glyphs.background/contents.plist b/Tests/20190830 benders/benderTest1.ufo/glyphs.background/contents.plist deleted file mode 100644 index 1e96c94..0000000 --- a/Tests/20190830 benders/benderTest1.ufo/glyphs.background/contents.plist +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/Tests/20190830 benders/benderTest1.ufo/glyphs/a.glif b/Tests/20190830 benders/benderTest1.ufo/glyphs/a.glif deleted file mode 100644 index 20407c7..0000000 --- a/Tests/20190830 benders/benderTest1.ufo/glyphs/a.glif +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/Tests/20190830 benders/benderTest1.ufo/lib.plist b/Tests/20190830 benders/benderTest1.ufo/lib.plist deleted file mode 100644 index d4b67bb..0000000 --- a/Tests/20190830 benders/benderTest1.ufo/lib.plist +++ /dev/null @@ -1,236 +0,0 @@ - - - - - com.defcon.sortDescriptor - - - ascending - Latin-1 - type - characterSet - - - com.typemytype.robofont.compileSettings.autohint - - com.typemytype.robofont.compileSettings.checkOutlines - - com.typemytype.robofont.compileSettings.createDummyDSIG - - com.typemytype.robofont.compileSettings.decompose - - com.typemytype.robofont.compileSettings.generateFormat - 0 - com.typemytype.robofont.compileSettings.releaseMode - - com.typemytype.robofont.italicSlantOffset - 0 - com.typemytype.robofont.segmentType - curve - com.typemytype.robofont.shouldAddPointsInSplineConversion - 1 - public.glyphOrder - - space - exclam - quotedbl - numbersign - dollar - percent - ampersand - parenleft - parenright - asterisk - plus - comma - hyphen - period - slash - zero - one - two - three - four - five - six - seven - eight - nine - colon - semicolon - less - equal - greater - question - at - A - B - C - D - E - F - G - H - I - J - K - L - M - N - O - P - Q - R - S - T - U - V - W - X - Y - Z - bracketleft - backslash - bracketright - asciicircum - underscore - grave - a - b - c - d - e - f - g - h - i - j - k - l - m - n - o - p - q - r - s - t - u - v - w - x - y - z - braceleft - bar - braceright - asciitilde - exclamdown - cent - sterling - currency - yen - brokenbar - section - dieresis - copyright - ordfeminine - guillemotleft - logicalnot - registered - macron - degree - plusminus - twosuperior - threesuperior - acute - mu - paragraph - periodcentered - cedilla - onesuperior - ordmasculine - guillemotright - onequarter - onehalf - threequarters - questiondown - Agrave - Aacute - Acircumflex - Atilde - Adieresis - Aring - AE - Ccedilla - Egrave - Eacute - Ecircumflex - Edieresis - Igrave - Iacute - Icircumflex - Idieresis - Eth - Ntilde - Ograve - Oacute - Ocircumflex - Otilde - Odieresis - multiply - Oslash - Ugrave - Uacute - Ucircumflex - Udieresis - Yacute - Thorn - germandbls - agrave - aacute - acircumflex - atilde - adieresis - aring - ae - ccedilla - egrave - eacute - ecircumflex - edieresis - igrave - iacute - icircumflex - idieresis - eth - ntilde - ograve - oacute - ocircumflex - otilde - odieresis - divide - oslash - ugrave - uacute - ucircumflex - udieresis - yacute - thorn - ydieresis - dotlessi - circumflex - caron - breve - dotaccent - ring - ogonek - tilde - hungarumlaut - quoteleft - quoteright - minus - - - diff --git a/Tests/20190830 benders/benderTest2.ufo/fontinfo.plist b/Tests/20190830 benders/benderTest2.ufo/fontinfo.plist deleted file mode 100644 index 21747a9..0000000 --- a/Tests/20190830 benders/benderTest2.ufo/fontinfo.plist +++ /dev/null @@ -1,34 +0,0 @@ - - - - - ascender - 750 - capHeight - 601 - descender - -250 - familyName - BenderTest - guidelines - - postscriptBlueValues - - postscriptFamilyBlues - - postscriptFamilyOtherBlues - - postscriptOtherBlues - - postscriptStemSnapH - - postscriptStemSnapV - - styleName - Two - unitsPerEm - 1000 - xHeight - 500 - - diff --git a/Tests/20190830 benders/benderTest2.ufo/glyphs.background/contents.plist b/Tests/20190830 benders/benderTest2.ufo/glyphs.background/contents.plist deleted file mode 100644 index 1e96c94..0000000 --- a/Tests/20190830 benders/benderTest2.ufo/glyphs.background/contents.plist +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/Tests/20190830 benders/benderTest2.ufo/glyphs/a.glif b/Tests/20190830 benders/benderTest2.ufo/glyphs/a.glif deleted file mode 100644 index d0bbac1..0000000 --- a/Tests/20190830 benders/benderTest2.ufo/glyphs/a.glif +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/Tests/20190830 benders/benderTest2.ufo/kerning.plist b/Tests/20190830 benders/benderTest2.ufo/kerning.plist deleted file mode 100644 index c3bce48..0000000 --- a/Tests/20190830 benders/benderTest2.ufo/kerning.plist +++ /dev/null @@ -1,11 +0,0 @@ - - - - - a - - a - -200 - - - diff --git a/Tests/20190830 benders/benderTest2.ufo/lib.plist b/Tests/20190830 benders/benderTest2.ufo/lib.plist deleted file mode 100644 index d4b67bb..0000000 --- a/Tests/20190830 benders/benderTest2.ufo/lib.plist +++ /dev/null @@ -1,236 +0,0 @@ - - - - - com.defcon.sortDescriptor - - - ascending - Latin-1 - type - characterSet - - - com.typemytype.robofont.compileSettings.autohint - - com.typemytype.robofont.compileSettings.checkOutlines - - com.typemytype.robofont.compileSettings.createDummyDSIG - - com.typemytype.robofont.compileSettings.decompose - - com.typemytype.robofont.compileSettings.generateFormat - 0 - com.typemytype.robofont.compileSettings.releaseMode - - com.typemytype.robofont.italicSlantOffset - 0 - com.typemytype.robofont.segmentType - curve - com.typemytype.robofont.shouldAddPointsInSplineConversion - 1 - public.glyphOrder - - space - exclam - quotedbl - numbersign - dollar - percent - ampersand - parenleft - parenright - asterisk - plus - comma - hyphen - period - slash - zero - one - two - three - four - five - six - seven - eight - nine - colon - semicolon - less - equal - greater - question - at - A - B - C - D - E - F - G - H - I - J - K - L - M - N - O - P - Q - R - S - T - U - V - W - X - Y - Z - bracketleft - backslash - bracketright - asciicircum - underscore - grave - a - b - c - d - e - f - g - h - i - j - k - l - m - n - o - p - q - r - s - t - u - v - w - x - y - z - braceleft - bar - braceright - asciitilde - exclamdown - cent - sterling - currency - yen - brokenbar - section - dieresis - copyright - ordfeminine - guillemotleft - logicalnot - registered - macron - degree - plusminus - twosuperior - threesuperior - acute - mu - paragraph - periodcentered - cedilla - onesuperior - ordmasculine - guillemotright - onequarter - onehalf - threequarters - questiondown - Agrave - Aacute - Acircumflex - Atilde - Adieresis - Aring - AE - Ccedilla - Egrave - Eacute - Ecircumflex - Edieresis - Igrave - Iacute - Icircumflex - Idieresis - Eth - Ntilde - Ograve - Oacute - Ocircumflex - Otilde - Odieresis - multiply - Oslash - Ugrave - Uacute - Ucircumflex - Udieresis - Yacute - Thorn - germandbls - agrave - aacute - acircumflex - atilde - adieresis - aring - ae - ccedilla - egrave - eacute - ecircumflex - edieresis - igrave - iacute - icircumflex - idieresis - eth - ntilde - ograve - oacute - ocircumflex - otilde - odieresis - divide - oslash - ugrave - uacute - ucircumflex - udieresis - yacute - thorn - ydieresis - dotlessi - circumflex - caron - breve - dotaccent - ring - ogonek - tilde - hungarumlaut - quoteleft - quoteright - minus - - - diff --git a/Tests/20190830 benders/benderTest3.ufo/fontinfo.plist b/Tests/20190830 benders/benderTest3.ufo/fontinfo.plist deleted file mode 100644 index 28c3286..0000000 --- a/Tests/20190830 benders/benderTest3.ufo/fontinfo.plist +++ /dev/null @@ -1,34 +0,0 @@ - - - - - ascender - 750 - capHeight - 700 - descender - -250 - familyName - BenderTest - guidelines - - postscriptBlueValues - - postscriptFamilyBlues - - postscriptFamilyOtherBlues - - postscriptOtherBlues - - postscriptStemSnapH - - postscriptStemSnapV - - styleName - Three - unitsPerEm - 1000 - xHeight - 500 - - diff --git a/Tests/20190830 benders/benderTest3.ufo/glyphs.background/contents.plist b/Tests/20190830 benders/benderTest3.ufo/glyphs.background/contents.plist deleted file mode 100644 index 1e96c94..0000000 --- a/Tests/20190830 benders/benderTest3.ufo/glyphs.background/contents.plist +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/Tests/20190830 benders/benderTest3.ufo/glyphs/a.glif b/Tests/20190830 benders/benderTest3.ufo/glyphs/a.glif deleted file mode 100644 index 4720db2..0000000 --- a/Tests/20190830 benders/benderTest3.ufo/glyphs/a.glif +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/Tests/20190830 benders/benderTest3.ufo/kerning.plist b/Tests/20190830 benders/benderTest3.ufo/kerning.plist deleted file mode 100644 index 23382cc..0000000 --- a/Tests/20190830 benders/benderTest3.ufo/kerning.plist +++ /dev/null @@ -1,11 +0,0 @@ - - - - - a - - a - -100 - - - diff --git a/Tests/20190830 benders/benderTest3.ufo/lib.plist b/Tests/20190830 benders/benderTest3.ufo/lib.plist deleted file mode 100644 index d4b67bb..0000000 --- a/Tests/20190830 benders/benderTest3.ufo/lib.plist +++ /dev/null @@ -1,236 +0,0 @@ - - - - - com.defcon.sortDescriptor - - - ascending - Latin-1 - type - characterSet - - - com.typemytype.robofont.compileSettings.autohint - - com.typemytype.robofont.compileSettings.checkOutlines - - com.typemytype.robofont.compileSettings.createDummyDSIG - - com.typemytype.robofont.compileSettings.decompose - - com.typemytype.robofont.compileSettings.generateFormat - 0 - com.typemytype.robofont.compileSettings.releaseMode - - com.typemytype.robofont.italicSlantOffset - 0 - com.typemytype.robofont.segmentType - curve - com.typemytype.robofont.shouldAddPointsInSplineConversion - 1 - public.glyphOrder - - space - exclam - quotedbl - numbersign - dollar - percent - ampersand - parenleft - parenright - asterisk - plus - comma - hyphen - period - slash - zero - one - two - three - four - five - six - seven - eight - nine - colon - semicolon - less - equal - greater - question - at - A - B - C - D - E - F - G - H - I - J - K - L - M - N - O - P - Q - R - S - T - U - V - W - X - Y - Z - bracketleft - backslash - bracketright - asciicircum - underscore - grave - a - b - c - d - e - f - g - h - i - j - k - l - m - n - o - p - q - r - s - t - u - v - w - x - y - z - braceleft - bar - braceright - asciitilde - exclamdown - cent - sterling - currency - yen - brokenbar - section - dieresis - copyright - ordfeminine - guillemotleft - logicalnot - registered - macron - degree - plusminus - twosuperior - threesuperior - acute - mu - paragraph - periodcentered - cedilla - onesuperior - ordmasculine - guillemotright - onequarter - onehalf - threequarters - questiondown - Agrave - Aacute - Acircumflex - Atilde - Adieresis - Aring - AE - Ccedilla - Egrave - Eacute - Ecircumflex - Edieresis - Igrave - Iacute - Icircumflex - Idieresis - Eth - Ntilde - Ograve - Oacute - Ocircumflex - Otilde - Odieresis - multiply - Oslash - Ugrave - Uacute - Ucircumflex - Udieresis - Yacute - Thorn - germandbls - agrave - aacute - acircumflex - atilde - adieresis - aring - ae - ccedilla - egrave - eacute - ecircumflex - edieresis - igrave - iacute - icircumflex - idieresis - eth - ntilde - ograve - oacute - ocircumflex - otilde - odieresis - divide - oslash - ugrave - uacute - ucircumflex - udieresis - yacute - thorn - ydieresis - dotlessi - circumflex - caron - breve - dotaccent - ring - ogonek - tilde - hungarumlaut - quoteleft - quoteright - minus - - - diff --git a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/fontinfo.plist b/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/fontinfo.plist deleted file mode 100644 index de783e4..0000000 --- a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/fontinfo.plist +++ /dev/null @@ -1,34 +0,0 @@ - - - - - ascender - 750 - capHeight - 997 - descender - -250 - familyName - BenderTest - guidelines - - postscriptBlueValues - - postscriptFamilyBlues - - postscriptFamilyOtherBlues - - postscriptOtherBlues - - postscriptStemSnapH - - postscriptStemSnapV - - styleName - FarOut - unitsPerEm - 1000.0 - xHeight - 500 - - diff --git a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/a.glif b/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/a.glif deleted file mode 100644 index 418b368..0000000 --- a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/a.glif +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/kerning.plist b/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/kerning.plist deleted file mode 100644 index 08fe439..0000000 --- a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/kerning.plist +++ /dev/null @@ -1,11 +0,0 @@ - - - - - a - - a - 200 - - - diff --git a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/lib.plist b/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/lib.plist deleted file mode 100644 index b2efd11..0000000 --- a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/lib.plist +++ /dev/null @@ -1,243 +0,0 @@ - - - - - com.defcon.sortDescriptor - - - ascending - Latin-1 - type - characterSet - - - com.typemytype.robofont.compileSettings.autohint - - com.typemytype.robofont.compileSettings.checkOutlines - - com.typemytype.robofont.compileSettings.createDummyDSIG - - com.typemytype.robofont.compileSettings.decompose - - com.typemytype.robofont.compileSettings.generateFormat - 0 - com.typemytype.robofont.compileSettings.releaseMode - - com.typemytype.robofont.italicSlantOffset - 0 - com.typemytype.robofont.segmentType - curve - com.typemytype.robofont.shouldAddPointsInSplineConversion - 1 - designspace.location - - - test - 2500.0 - - - public.glyphOrder - - space - exclam - quotedbl - numbersign - dollar - percent - ampersand - parenleft - parenright - asterisk - plus - comma - hyphen - period - slash - zero - one - two - three - four - five - six - seven - eight - nine - colon - semicolon - less - equal - greater - question - at - A - B - C - D - E - F - G - H - I - J - K - L - M - N - O - P - Q - R - S - T - U - V - W - X - Y - Z - bracketleft - backslash - bracketright - asciicircum - underscore - grave - a - b - c - d - e - f - g - h - i - j - k - l - m - n - o - p - q - r - s - t - u - v - w - x - y - z - braceleft - bar - braceright - asciitilde - exclamdown - cent - sterling - currency - yen - brokenbar - section - dieresis - copyright - ordfeminine - guillemotleft - logicalnot - registered - macron - degree - plusminus - twosuperior - threesuperior - acute - mu - paragraph - periodcentered - cedilla - onesuperior - ordmasculine - guillemotright - onequarter - onehalf - threequarters - questiondown - Agrave - Aacute - Acircumflex - Atilde - Adieresis - Aring - AE - Ccedilla - Egrave - Eacute - Ecircumflex - Edieresis - Igrave - Iacute - Icircumflex - Idieresis - Eth - Ntilde - Ograve - Oacute - Ocircumflex - Otilde - Odieresis - multiply - Oslash - Ugrave - Uacute - Ucircumflex - Udieresis - Yacute - Thorn - germandbls - agrave - aacute - acircumflex - atilde - adieresis - aring - ae - ccedilla - egrave - eacute - ecircumflex - edieresis - igrave - iacute - icircumflex - idieresis - eth - ntilde - ograve - oacute - ocircumflex - otilde - odieresis - divide - oslash - ugrave - uacute - ucircumflex - udieresis - yacute - thorn - ydieresis - dotlessi - circumflex - caron - breve - dotaccent - ring - ogonek - tilde - hungarumlaut - quoteleft - quoteright - minus - - - diff --git a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/fontinfo.plist b/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/fontinfo.plist deleted file mode 100644 index 137f807..0000000 --- a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/fontinfo.plist +++ /dev/null @@ -1,34 +0,0 @@ - - - - - ascender - 750 - capHeight - 601 - descender - -250 - familyName - BenderTest - guidelines - - postscriptBlueValues - - postscriptFamilyBlues - - postscriptFamilyOtherBlues - - postscriptOtherBlues - - postscriptStemSnapH - - postscriptStemSnapV - - styleName - Intermediate - unitsPerEm - 1000 - xHeight - 500 - - diff --git a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/a.glif b/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/a.glif deleted file mode 100644 index 376c59d..0000000 --- a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/a.glif +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/contents.plist b/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/contents.plist deleted file mode 100644 index 63a44ac..0000000 --- a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/contents.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - a - a.glif - - diff --git a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/kerning.plist b/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/kerning.plist deleted file mode 100644 index c3bce48..0000000 --- a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/kerning.plist +++ /dev/null @@ -1,11 +0,0 @@ - - - - - a - - a - -200 - - - diff --git a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/lib.plist b/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/lib.plist deleted file mode 100644 index acfaec2..0000000 --- a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/lib.plist +++ /dev/null @@ -1,243 +0,0 @@ - - - - - com.defcon.sortDescriptor - - - ascending - Latin-1 - type - characterSet - - - com.typemytype.robofont.compileSettings.autohint - - com.typemytype.robofont.compileSettings.checkOutlines - - com.typemytype.robofont.compileSettings.createDummyDSIG - - com.typemytype.robofont.compileSettings.decompose - - com.typemytype.robofont.compileSettings.generateFormat - 0 - com.typemytype.robofont.compileSettings.releaseMode - - com.typemytype.robofont.italicSlantOffset - 0 - com.typemytype.robofont.segmentType - curve - com.typemytype.robofont.shouldAddPointsInSplineConversion - 1 - designspace.location - - - test - 500.0 - - - public.glyphOrder - - space - exclam - quotedbl - numbersign - dollar - percent - ampersand - parenleft - parenright - asterisk - plus - comma - hyphen - period - slash - zero - one - two - three - four - five - six - seven - eight - nine - colon - semicolon - less - equal - greater - question - at - A - B - C - D - E - F - G - H - I - J - K - L - M - N - O - P - Q - R - S - T - U - V - W - X - Y - Z - bracketleft - backslash - bracketright - asciicircum - underscore - grave - a - b - c - d - e - f - g - h - i - j - k - l - m - n - o - p - q - r - s - t - u - v - w - x - y - z - braceleft - bar - braceright - asciitilde - exclamdown - cent - sterling - currency - yen - brokenbar - section - dieresis - copyright - ordfeminine - guillemotleft - logicalnot - registered - macron - degree - plusminus - twosuperior - threesuperior - acute - mu - paragraph - periodcentered - cedilla - onesuperior - ordmasculine - guillemotright - onequarter - onehalf - threequarters - questiondown - Agrave - Aacute - Acircumflex - Atilde - Adieresis - Aring - AE - Ccedilla - Egrave - Eacute - Ecircumflex - Edieresis - Igrave - Iacute - Icircumflex - Idieresis - Eth - Ntilde - Ograve - Oacute - Ocircumflex - Otilde - Odieresis - multiply - Oslash - Ugrave - Uacute - Ucircumflex - Udieresis - Yacute - Thorn - germandbls - agrave - aacute - acircumflex - atilde - adieresis - aring - ae - ccedilla - egrave - eacute - ecircumflex - edieresis - igrave - iacute - icircumflex - idieresis - eth - ntilde - ograve - oacute - ocircumflex - otilde - odieresis - divide - oslash - ugrave - uacute - ucircumflex - udieresis - yacute - thorn - ydieresis - dotlessi - circumflex - caron - breve - dotaccent - ring - ogonek - tilde - hungarumlaut - quoteleft - quoteright - minus - - - diff --git a/Tests/20190830 benders/test.py b/Tests/20190830 benders/test.py deleted file mode 100644 index e5e46db..0000000 --- a/Tests/20190830 benders/test.py +++ /dev/null @@ -1,80 +0,0 @@ -""" - - - test with these 3 masters - on 1 axis that has a map that maps to a different range - - axis values are in user coordinates - designpsace problems should check with the proper mapped values - masters and instancees are in designspace coordinates - - goals: - * the designspace should validate - * the generated intermediate should have touching shapes, just like master 2 - * determine if we can get rid of the bend=True/False flags - - Suppose the numbers in an axis map are messed up, it's then impossible - to find the default. -""" - -import importlib -import ufoProcessor -importlib.reload(ufoProcessor) - - -import mutatorMath -print(mutatorMath.__file__) -import mutatorMath.objects.mutator -importlib.reload(mutatorMath.objects.mutator) -from mutatorMath.objects.mutator import Location -from designspaceProblems import DesignSpaceChecker -import collections -from ufoProcessor import DesignSpaceProcessor -from pprint import pprint - -path = "Test.designspace" - -dp = DesignSpaceProcessor() -dp.read(path) -dp.loadFonts() - -dsc = DesignSpaceChecker(dp) -dsc.checkEverything() -pprint(dsc.problems) -print('hasStructuralProblems', dsc.hasStructuralProblems()) - - -print(dp.newDefaultLocation()) -print(dp.instances) -print('findDefault', dp.findDefault()) -dp.useVarlib = False -print('varlib', dp.useVarlib) - -axisMapper = ufoProcessor.varModels.AxisMapper(dp.axes) -print('axisMapper', axisMapper.getMappedAxisValues()) -r = axisMapper(Location(test=1)) - -default = dp.getNeutralFont() -print('default.path', default.path) -dp.generateUFO() - -glyphName = "a" -print('mutator for a', dp.getGlyphMutator(glyphName)) -print('-'*40) -print('problems') -for p in dp.problems: - print(p) -print('-'*40) -print('toollog') -for line in dp.toolLog: - print("\t" + line) - - -instancePath = "instances/BenderTest-Intermediate.ufo" -instance = RFont(instancePath, showUI=False) -print(instance.info.capHeight) -print(instance.kerning.items()) - -from mutatorMath.objects.mutator import Location -l = Location(test=0) -print(l.isOrigin()) \ No newline at end of file diff --git a/Tests/202206 discrete spaces/ds5_makeTestDoc.py b/Tests/202206 discrete spaces/ds5_makeTestDoc.py new file mode 100644 index 0000000..330ff87 --- /dev/null +++ b/Tests/202206 discrete spaces/ds5_makeTestDoc.py @@ -0,0 +1,106 @@ +# make a test designspace format 5 with 1 continuous and 2 discrete axes. + +# axis width is a normal interpolation with a change in width +# axis DSC1 is a discrete axis showing 1, 2, 3 items in the glyph +# axis DSC2 is a discrete axis showing a solid or outlined shape + +from fontTools.designspaceLib import DesignSpaceDocument, SourceDescriptor, InstanceDescriptor, AxisDescriptor, RuleDescriptor, processRules, DiscreteAxisDescriptor + +import os +import fontTools +print(fontTools.version) + +doc = DesignSpaceDocument() + +#https://fonttools.readthedocs.io/en/latest/designspaceLib/python.html#axisdescriptor +a1 = AxisDescriptor() +a1.minimum = 400 +a1.maximum = 1000 +a1.default = 400 +a1.name = "width" +a1.tag = "wdth" +a1.axisOrdering = 1 +doc.addAxis(a1) + +a2 = DiscreteAxisDescriptor() +a2.values = [1, 2, 3] +a2.default = 1 +a2.name = "countedItems" +a2.tag = "DSC1" +a2.axisOrdering = 2 +doc.addAxis(a2) + +a3 = DiscreteAxisDescriptor() +a3.values = [0, 1] +a3.default = 0 +a3.name = "outlined" +a3.tag = "DSC2" +a3.axisOrdering = 3 +doc.addAxis(a3) + + +# add sources +for c in [a1.minimum, a1.maximum]: + for d1 in a2.values: + for d2 in a3.values: + + s1 = SourceDescriptor() + s1.path = os.path.join("masters", f"geometryMaster_c_{c}_d1_{d1}_d2_{d2}.ufo") + print(s1.path, os.path.exists(s1.path)) + s1.name = f"geometryMaster{c} {d1} {d2}" + s1.location = dict(width=c, countedItems=d1, outlined=d2) + s1.familyName = "MasterFamilyName" + td1 = ["One", "Two", "Three"][(d1-1)] + if c == 400: + tc = "Narrow" + elif c == 1000: + tc = "Wide" + if d2 == 0: + td2 = "solid" + else: + td2 = "open" + s1.styleName = f"{td1}{tc}{td2}" + doc.addSource(s1) + +def ip(a,b,f): + return a + f*(b-a) + +# add instances +steps = 4 +for s in range(steps+1): + factor = s / steps + c = int(ip(a1.minimum, a1.maximum, factor)) + for d1 in a2.values: + for d2 in a3.values: + + s1 = InstanceDescriptor() + s1.path = os.path.join("instances", f"geometryInstance_c_{c}_d1_{d1}_d2_{d2}.ufo") + print(s1.path, os.path.exists(s1.path)) + s1.location = dict(width=c, countedItems=d1, outlined=d2) + s1.familyName = "InstanceFamilyName" + td1 = ["One", "Two", "Three"][(d1-1)] + if c == 400: + tc = "Narrow" + elif c == 1000: + tc = "Wide" + if d2 == 0: + td2 = "Solid" + else: + td2 = "Open" + s1.name = f"geometryInstance {td1} {tc} {td2}" + s1.styleName = f"{td1}{tc}{td2}" + doc.addInstance(s1) + + +path = "test.ds5.designspace" +doc.write(path) + + +for a in doc.axes: + if hasattr(a, "values"): + print(a.name, "d", a.values) + else: + print(a.name, "r", a.minimum, a.maximum) + +for s in doc.sources: + print(s.location) diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/features.fea new file mode 100644 index 0000000..fca011f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/features.fea @@ -0,0 +1 @@ +# features text from master 1 \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist new file mode 100644 index 0000000..f941297 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist @@ -0,0 +1,53 @@ + + + + + ascender + 400 + capHeight + 400 + copyright + This is the copyright notice from master 1 + descender + -200 + familyName + One_wide_open + guidelines + + openTypeHheaAscender + 1036 + openTypeHheaDescender + -335 + openTypeOS2TypoAscender + 730 + openTypeOS2TypoDescender + -270 + openTypeOS2WinAscent + 1036 + openTypeOS2WinDescent + 335 + postscriptBlueFuzz + 0 + postscriptBlueScale + 0.22 + postscriptBlueValues + + 100 + 110 + + postscriptFamilyBlues + + postscriptFamilyOtherBlues + + postscriptOtherBlues + + postscriptStemSnapH + + postscriptStemSnapV + + unitsPerEm + 1000 + xHeight + 200 + + diff --git a/Tests/20190830 benders/benderTest1.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist similarity index 76% rename from Tests/20190830 benders/benderTest1.ufo/glyphs/contents.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist index 63a44ac..229927d 100644 --- a/Tests/20190830 benders/benderTest1.ufo/glyphs/contents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist @@ -2,7 +2,7 @@ - a - a.glif + glyphOne + glyphO_ne.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif new file mode 100644 index 0000000..56f17c6 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Tests/20190830 benders/benderTest1.ufo/glyphs/layerinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/20190830 benders/benderTest1.ufo/glyphs/layerinfo.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/layerinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/groups.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/groups.plist new file mode 100644 index 0000000..4dff22d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/groups.plist @@ -0,0 +1,16 @@ + + + + + public.kern1.groupA + + glyphOne + glyphTwo + + public.kern2.groupB + + glyphThree + glyphFour + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist new file mode 100644 index 0000000..46756bb --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist @@ -0,0 +1,20 @@ + + + + + glyphOne + + glyphFour + 10 + glyphOne + -100 + glyphThree + 10 + + public.kern1.groupA + + public.kern2.groupB + -100 + + + diff --git a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/layercontents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/layercontents.plist similarity index 100% rename from Tests/20190830 benders/instances/BenderTest-FarOut.ufo/layercontents.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/layercontents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/lib.plist new file mode 100644 index 0000000..60019e8 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/lib.plist @@ -0,0 +1,30 @@ + + + + + com.typemytype.robofont.compileSettings.autohint + + com.typemytype.robofont.compileSettings.checkOutlines + + com.typemytype.robofont.compileSettings.createDummyDSIG + + com.typemytype.robofont.compileSettings.decompose + + com.typemytype.robofont.compileSettings.generateFormat + 0 + com.typemytype.robofont.compileSettings.releaseMode + + com.typemytype.robofont.generateFeaturesWithFontTools + + com.typemytype.robofont.italicSlantOffset + 0 + com.typemytype.robofont.shouldAddPointsInSplineConversion + 1 + public.glyphOrder + + glyphOne + + ufoProcessor.test.lib.entry + Lib entry for master 1 + + diff --git a/Tests/20190830 benders/benderTest1.ufo/metainfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/metainfo.plist similarity index 100% rename from Tests/20190830 benders/benderTest1.ufo/metainfo.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/features.fea new file mode 100644 index 0000000..fca011f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/features.fea @@ -0,0 +1 @@ +# features text from master 1 \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist new file mode 100644 index 0000000..9733123 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist @@ -0,0 +1,53 @@ + + + + + ascender + 400 + capHeight + 400 + copyright + This is the copyright notice from master 1 + descender + -200 + familyName + One_wide_solid + guidelines + + openTypeHheaAscender + 1036 + openTypeHheaDescender + -335 + openTypeOS2TypoAscender + 730 + openTypeOS2TypoDescender + -270 + openTypeOS2WinAscent + 1036 + openTypeOS2WinDescent + 335 + postscriptBlueFuzz + 0 + postscriptBlueScale + 0.22 + postscriptBlueValues + + 100 + 110 + + postscriptFamilyBlues + + postscriptFamilyOtherBlues + + postscriptOtherBlues + + postscriptStemSnapH + + postscriptStemSnapV + + unitsPerEm + 1000 + xHeight + 200 + + diff --git a/Tests/20190830 benders/benderTest3.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist similarity index 76% rename from Tests/20190830 benders/benderTest3.ufo/glyphs/contents.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist index 63a44ac..229927d 100644 --- a/Tests/20190830 benders/benderTest3.ufo/glyphs/contents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist @@ -2,7 +2,7 @@ - a - a.glif + glyphOne + glyphO_ne.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif new file mode 100644 index 0000000..205dd1f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/Tests/20190830 benders/benderTest2.ufo/glyphs/layerinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/20190830 benders/benderTest2.ufo/glyphs/layerinfo.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/layerinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/groups.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/groups.plist new file mode 100644 index 0000000..4dff22d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/groups.plist @@ -0,0 +1,16 @@ + + + + + public.kern1.groupA + + glyphOne + glyphTwo + + public.kern2.groupB + + glyphThree + glyphFour + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist new file mode 100644 index 0000000..46756bb --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist @@ -0,0 +1,20 @@ + + + + + glyphOne + + glyphFour + 10 + glyphOne + -100 + glyphThree + 10 + + public.kern1.groupA + + public.kern2.groupB + -100 + + + diff --git a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/layercontents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/layercontents.plist similarity index 100% rename from Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/layercontents.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/layercontents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/lib.plist new file mode 100644 index 0000000..60019e8 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/lib.plist @@ -0,0 +1,30 @@ + + + + + com.typemytype.robofont.compileSettings.autohint + + com.typemytype.robofont.compileSettings.checkOutlines + + com.typemytype.robofont.compileSettings.createDummyDSIG + + com.typemytype.robofont.compileSettings.decompose + + com.typemytype.robofont.compileSettings.generateFormat + 0 + com.typemytype.robofont.compileSettings.releaseMode + + com.typemytype.robofont.generateFeaturesWithFontTools + + com.typemytype.robofont.italicSlantOffset + 0 + com.typemytype.robofont.shouldAddPointsInSplineConversion + 1 + public.glyphOrder + + glyphOne + + ufoProcessor.test.lib.entry + Lib entry for master 1 + + diff --git a/Tests/20190830 benders/benderTest2.ufo/metainfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/metainfo.plist similarity index 100% rename from Tests/20190830 benders/benderTest2.ufo/metainfo.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/features.fea new file mode 100644 index 0000000..fca011f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/features.fea @@ -0,0 +1 @@ +# features text from master 1 \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist new file mode 100644 index 0000000..325840e --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist @@ -0,0 +1,53 @@ + + + + + ascender + 400 + capHeight + 400 + copyright + This is the copyright notice from master 1 + descender + -200 + familyName + Two_wide_open + guidelines + + openTypeHheaAscender + 1036 + openTypeHheaDescender + -335 + openTypeOS2TypoAscender + 730 + openTypeOS2TypoDescender + -270 + openTypeOS2WinAscent + 1036 + openTypeOS2WinDescent + 335 + postscriptBlueFuzz + 0 + postscriptBlueScale + 0.22 + postscriptBlueValues + + 100 + 110 + + postscriptFamilyBlues + + postscriptFamilyOtherBlues + + postscriptOtherBlues + + postscriptStemSnapH + + postscriptStemSnapV + + unitsPerEm + 1000 + xHeight + 200 + + diff --git a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist similarity index 76% rename from Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/contents.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist index 63a44ac..229927d 100644 --- a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/contents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist @@ -2,7 +2,7 @@ - a - a.glif + glyphOne + glyphO_ne.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif new file mode 100644 index 0000000..24ade3f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/20190830 benders/benderTest3.ufo/glyphs/layerinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/20190830 benders/benderTest3.ufo/glyphs/layerinfo.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/layerinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/groups.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/groups.plist new file mode 100644 index 0000000..4dff22d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/groups.plist @@ -0,0 +1,16 @@ + + + + + public.kern1.groupA + + glyphOne + glyphTwo + + public.kern2.groupB + + glyphThree + glyphFour + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist new file mode 100644 index 0000000..46756bb --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist @@ -0,0 +1,20 @@ + + + + + glyphOne + + glyphFour + 10 + glyphOne + -100 + glyphThree + 10 + + public.kern1.groupA + + public.kern2.groupB + -100 + + + diff --git a/Tests/20190830 benders/benderTest1.ufo/layercontents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/layercontents.plist similarity index 65% rename from Tests/20190830 benders/benderTest1.ufo/layercontents.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/layercontents.plist index e9a336b..b9c1a4f 100644 --- a/Tests/20190830 benders/benderTest1.ufo/layercontents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/layercontents.plist @@ -3,12 +3,8 @@ - foreground + public.default glyphs - - background - glyphs.background - diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/lib.plist new file mode 100644 index 0000000..60019e8 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/lib.plist @@ -0,0 +1,30 @@ + + + + + com.typemytype.robofont.compileSettings.autohint + + com.typemytype.robofont.compileSettings.checkOutlines + + com.typemytype.robofont.compileSettings.createDummyDSIG + + com.typemytype.robofont.compileSettings.decompose + + com.typemytype.robofont.compileSettings.generateFormat + 0 + com.typemytype.robofont.compileSettings.releaseMode + + com.typemytype.robofont.generateFeaturesWithFontTools + + com.typemytype.robofont.italicSlantOffset + 0 + com.typemytype.robofont.shouldAddPointsInSplineConversion + 1 + public.glyphOrder + + glyphOne + + ufoProcessor.test.lib.entry + Lib entry for master 1 + + diff --git a/Tests/20190830 benders/benderTest3.ufo/metainfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/metainfo.plist similarity index 100% rename from Tests/20190830 benders/benderTest3.ufo/metainfo.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/features.fea new file mode 100644 index 0000000..fca011f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/features.fea @@ -0,0 +1 @@ +# features text from master 1 \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist new file mode 100644 index 0000000..218a997 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist @@ -0,0 +1,53 @@ + + + + + ascender + 400 + capHeight + 400 + copyright + This is the copyright notice from master 1 + descender + -200 + familyName + Two_wide_solid + guidelines + + openTypeHheaAscender + 1036 + openTypeHheaDescender + -335 + openTypeOS2TypoAscender + 730 + openTypeOS2TypoDescender + -270 + openTypeOS2WinAscent + 1036 + openTypeOS2WinDescent + 335 + postscriptBlueFuzz + 0 + postscriptBlueScale + 0.22 + postscriptBlueValues + + 100 + 110 + + postscriptFamilyBlues + + postscriptFamilyOtherBlues + + postscriptOtherBlues + + postscriptStemSnapH + + postscriptStemSnapV + + unitsPerEm + 1000 + xHeight + 200 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist new file mode 100644 index 0000000..229927d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist @@ -0,0 +1,8 @@ + + + + + glyphOne + glyphO_ne.glif + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif new file mode 100644 index 0000000..a14c6f2 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Tests/20190830 benders/benderTest3.ufo/glyphs.background/layerinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/layerinfo.plist similarity index 85% rename from Tests/20190830 benders/benderTest3.ufo/glyphs.background/layerinfo.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/layerinfo.plist index 87752a4..3cf39b4 100644 --- a/Tests/20190830 benders/benderTest3.ufo/glyphs.background/layerinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/layerinfo.plist @@ -3,6 +3,6 @@ color - 0,0.8,0.2,0.7 + 1,0.75,0,0.7 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/groups.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/groups.plist new file mode 100644 index 0000000..4dff22d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/groups.plist @@ -0,0 +1,16 @@ + + + + + public.kern1.groupA + + glyphOne + glyphTwo + + public.kern2.groupB + + glyphThree + glyphFour + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist new file mode 100644 index 0000000..46756bb --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist @@ -0,0 +1,20 @@ + + + + + glyphOne + + glyphFour + 10 + glyphOne + -100 + glyphThree + 10 + + public.kern1.groupA + + public.kern2.groupB + -100 + + + diff --git a/Tests/20190830 benders/benderTest2.ufo/layercontents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/layercontents.plist similarity index 65% rename from Tests/20190830 benders/benderTest2.ufo/layercontents.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/layercontents.plist index e9a336b..b9c1a4f 100644 --- a/Tests/20190830 benders/benderTest2.ufo/layercontents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/layercontents.plist @@ -3,12 +3,8 @@ - foreground + public.default glyphs - - background - glyphs.background - diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/lib.plist new file mode 100644 index 0000000..60019e8 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/lib.plist @@ -0,0 +1,30 @@ + + + + + com.typemytype.robofont.compileSettings.autohint + + com.typemytype.robofont.compileSettings.checkOutlines + + com.typemytype.robofont.compileSettings.createDummyDSIG + + com.typemytype.robofont.compileSettings.decompose + + com.typemytype.robofont.compileSettings.generateFormat + 0 + com.typemytype.robofont.compileSettings.releaseMode + + com.typemytype.robofont.generateFeaturesWithFontTools + + com.typemytype.robofont.italicSlantOffset + 0 + com.typemytype.robofont.shouldAddPointsInSplineConversion + 1 + public.glyphOrder + + glyphOne + + ufoProcessor.test.lib.entry + Lib entry for master 1 + + diff --git a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/metainfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/metainfo.plist similarity index 100% rename from Tests/20190830 benders/instances/BenderTest-FarOut.ufo/metainfo.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/features.fea new file mode 100644 index 0000000..fca011f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/features.fea @@ -0,0 +1 @@ +# features text from master 1 \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist new file mode 100644 index 0000000..8bef8d0 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist @@ -0,0 +1,53 @@ + + + + + ascender + 400 + capHeight + 400 + copyright + This is the copyright notice from master 1 + descender + -200 + familyName + Three_wide_open + guidelines + + openTypeHheaAscender + 1036 + openTypeHheaDescender + -335 + openTypeOS2TypoAscender + 730 + openTypeOS2TypoDescender + -270 + openTypeOS2WinAscent + 1036 + openTypeOS2WinDescent + 335 + postscriptBlueFuzz + 0 + postscriptBlueScale + 0.22 + postscriptBlueValues + + 100 + 110 + + postscriptFamilyBlues + + postscriptFamilyOtherBlues + + postscriptOtherBlues + + postscriptStemSnapH + + postscriptStemSnapV + + unitsPerEm + 1000 + xHeight + 200 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist new file mode 100644 index 0000000..229927d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist @@ -0,0 +1,8 @@ + + + + + glyphOne + glyphO_ne.glif + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif new file mode 100644 index 0000000..1cb076d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/20190830 benders/benderTest2.ufo/glyphs.background/layerinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/layerinfo.plist similarity index 85% rename from Tests/20190830 benders/benderTest2.ufo/glyphs.background/layerinfo.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/layerinfo.plist index 87752a4..3cf39b4 100644 --- a/Tests/20190830 benders/benderTest2.ufo/glyphs.background/layerinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/layerinfo.plist @@ -3,6 +3,6 @@ color - 0,0.8,0.2,0.7 + 1,0.75,0,0.7 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/groups.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/groups.plist new file mode 100644 index 0000000..4dff22d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/groups.plist @@ -0,0 +1,16 @@ + + + + + public.kern1.groupA + + glyphOne + glyphTwo + + public.kern2.groupB + + glyphThree + glyphFour + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist new file mode 100644 index 0000000..46756bb --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist @@ -0,0 +1,20 @@ + + + + + glyphOne + + glyphFour + 10 + glyphOne + -100 + glyphThree + 10 + + public.kern1.groupA + + public.kern2.groupB + -100 + + + diff --git a/Tests/20190830 benders/benderTest3.ufo/layercontents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/layercontents.plist similarity index 65% rename from Tests/20190830 benders/benderTest3.ufo/layercontents.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/layercontents.plist index e9a336b..b9c1a4f 100644 --- a/Tests/20190830 benders/benderTest3.ufo/layercontents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/layercontents.plist @@ -3,12 +3,8 @@ - foreground + public.default glyphs - - background - glyphs.background - diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/lib.plist new file mode 100644 index 0000000..60019e8 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/lib.plist @@ -0,0 +1,30 @@ + + + + + com.typemytype.robofont.compileSettings.autohint + + com.typemytype.robofont.compileSettings.checkOutlines + + com.typemytype.robofont.compileSettings.createDummyDSIG + + com.typemytype.robofont.compileSettings.decompose + + com.typemytype.robofont.compileSettings.generateFormat + 0 + com.typemytype.robofont.compileSettings.releaseMode + + com.typemytype.robofont.generateFeaturesWithFontTools + + com.typemytype.robofont.italicSlantOffset + 0 + com.typemytype.robofont.shouldAddPointsInSplineConversion + 1 + public.glyphOrder + + glyphOne + + ufoProcessor.test.lib.entry + Lib entry for master 1 + + diff --git a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/metainfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/metainfo.plist similarity index 100% rename from Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/metainfo.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/features.fea new file mode 100644 index 0000000..fca011f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/features.fea @@ -0,0 +1 @@ +# features text from master 1 \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist new file mode 100644 index 0000000..4544632 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist @@ -0,0 +1,53 @@ + + + + + ascender + 400 + capHeight + 400 + copyright + This is the copyright notice from master 1 + descender + -200 + familyName + Three_wide_solid + guidelines + + openTypeHheaAscender + 1036 + openTypeHheaDescender + -335 + openTypeOS2TypoAscender + 730 + openTypeOS2TypoDescender + -270 + openTypeOS2WinAscent + 1036 + openTypeOS2WinDescent + 335 + postscriptBlueFuzz + 0 + postscriptBlueScale + 0.22 + postscriptBlueValues + + 100 + 110 + + postscriptFamilyBlues + + postscriptFamilyOtherBlues + + postscriptOtherBlues + + postscriptStemSnapH + + postscriptStemSnapV + + unitsPerEm + 1000 + xHeight + 200 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist new file mode 100644 index 0000000..229927d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist @@ -0,0 +1,8 @@ + + + + + glyphOne + glyphO_ne.glif + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif new file mode 100644 index 0000000..b353c48 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/20190830 benders/benderTest1.ufo/glyphs.background/layerinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/layerinfo.plist similarity index 85% rename from Tests/20190830 benders/benderTest1.ufo/glyphs.background/layerinfo.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/layerinfo.plist index 87752a4..3cf39b4 100644 --- a/Tests/20190830 benders/benderTest1.ufo/glyphs.background/layerinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/layerinfo.plist @@ -3,6 +3,6 @@ color - 0,0.8,0.2,0.7 + 1,0.75,0,0.7 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/groups.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/groups.plist new file mode 100644 index 0000000..4dff22d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/groups.plist @@ -0,0 +1,16 @@ + + + + + public.kern1.groupA + + glyphOne + glyphTwo + + public.kern2.groupB + + glyphThree + glyphFour + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist new file mode 100644 index 0000000..46756bb --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist @@ -0,0 +1,20 @@ + + + + + glyphOne + + glyphFour + 10 + glyphOne + -100 + glyphThree + 10 + + public.kern1.groupA + + public.kern2.groupB + -100 + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/layercontents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/layercontents.plist new file mode 100644 index 0000000..b9c1a4f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/layercontents.plist @@ -0,0 +1,10 @@ + + + + + + public.default + glyphs + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/lib.plist new file mode 100644 index 0000000..60019e8 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/lib.plist @@ -0,0 +1,30 @@ + + + + + com.typemytype.robofont.compileSettings.autohint + + com.typemytype.robofont.compileSettings.checkOutlines + + com.typemytype.robofont.compileSettings.createDummyDSIG + + com.typemytype.robofont.compileSettings.decompose + + com.typemytype.robofont.compileSettings.generateFormat + 0 + com.typemytype.robofont.compileSettings.releaseMode + + com.typemytype.robofont.generateFeaturesWithFontTools + + com.typemytype.robofont.italicSlantOffset + 0 + com.typemytype.robofont.shouldAddPointsInSplineConversion + 1 + public.glyphOrder + + glyphOne + + ufoProcessor.test.lib.entry + Lib entry for master 1 + + diff --git a/Tests/20190830 benders/benderTest1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/metainfo.plist similarity index 60% rename from Tests/20190830 benders/benderTest1.ufo/kerning.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/metainfo.plist index 23382cc..7b8b34a 100644 --- a/Tests/20190830 benders/benderTest1.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/metainfo.plist @@ -2,10 +2,9 @@ - a - - a - -100 - + creator + com.github.fonttools.ufoLib + formatVersion + 3 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/features.fea new file mode 100644 index 0000000..fca011f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/features.fea @@ -0,0 +1 @@ +# features text from master 1 \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist new file mode 100644 index 0000000..dc0a084 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist @@ -0,0 +1,53 @@ + + + + + ascender + 400 + capHeight + 400 + copyright + This is the copyright notice from master 1 + descender + -200 + familyName + One_narrow_open + guidelines + + openTypeHheaAscender + 1036 + openTypeHheaDescender + -335 + openTypeOS2TypoAscender + 730 + openTypeOS2TypoDescender + -270 + openTypeOS2WinAscent + 1036 + openTypeOS2WinDescent + 335 + postscriptBlueFuzz + 0 + postscriptBlueScale + 0.22 + postscriptBlueValues + + 100 + 110 + + postscriptFamilyBlues + + postscriptFamilyOtherBlues + + postscriptOtherBlues + + postscriptStemSnapH + + postscriptStemSnapV + + unitsPerEm + 1000 + xHeight + 200 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/contents.plist new file mode 100644 index 0000000..229927d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/contents.plist @@ -0,0 +1,8 @@ + + + + + glyphOne + glyphO_ne.glif + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif new file mode 100644 index 0000000..8e28310 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Tests/20190830 benders/benderTest2.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist similarity index 77% rename from Tests/20190830 benders/benderTest2.ufo/glyphs/contents.plist rename to Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist index 63a44ac..3cf39b4 100644 --- a/Tests/20190830 benders/benderTest2.ufo/glyphs/contents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist @@ -2,7 +2,7 @@ - a - a.glif + color + 1,0.75,0,0.7 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/groups.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/groups.plist new file mode 100644 index 0000000..4dff22d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/groups.plist @@ -0,0 +1,16 @@ + + + + + public.kern1.groupA + + glyphOne + glyphTwo + + public.kern2.groupB + + glyphThree + glyphFour + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist new file mode 100644 index 0000000..46756bb --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist @@ -0,0 +1,20 @@ + + + + + glyphOne + + glyphFour + 10 + glyphOne + -100 + glyphThree + 10 + + public.kern1.groupA + + public.kern2.groupB + -100 + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/layercontents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/layercontents.plist new file mode 100644 index 0000000..b9c1a4f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/layercontents.plist @@ -0,0 +1,10 @@ + + + + + + public.default + glyphs + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/lib.plist new file mode 100644 index 0000000..60019e8 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/lib.plist @@ -0,0 +1,30 @@ + + + + + com.typemytype.robofont.compileSettings.autohint + + com.typemytype.robofont.compileSettings.checkOutlines + + com.typemytype.robofont.compileSettings.createDummyDSIG + + com.typemytype.robofont.compileSettings.decompose + + com.typemytype.robofont.compileSettings.generateFormat + 0 + com.typemytype.robofont.compileSettings.releaseMode + + com.typemytype.robofont.generateFeaturesWithFontTools + + com.typemytype.robofont.italicSlantOffset + 0 + com.typemytype.robofont.shouldAddPointsInSplineConversion + 1 + public.glyphOrder + + glyphOne + + ufoProcessor.test.lib.entry + Lib entry for master 1 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/metainfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/metainfo.plist new file mode 100644 index 0000000..7b8b34a --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/metainfo.plist @@ -0,0 +1,10 @@ + + + + + creator + com.github.fonttools.ufoLib + formatVersion + 3 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/features.fea new file mode 100644 index 0000000..fca011f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/features.fea @@ -0,0 +1 @@ +# features text from master 1 \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist new file mode 100644 index 0000000..c7b12d6 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist @@ -0,0 +1,53 @@ + + + + + ascender + 400 + capHeight + 400 + copyright + This is the copyright notice from master 1 + descender + -200 + familyName + One_narrow_solid + guidelines + + openTypeHheaAscender + 1036 + openTypeHheaDescender + -335 + openTypeOS2TypoAscender + 730 + openTypeOS2TypoDescender + -270 + openTypeOS2WinAscent + 1036 + openTypeOS2WinDescent + 335 + postscriptBlueFuzz + 0 + postscriptBlueScale + 0.22 + postscriptBlueValues + + 100 + 110 + + postscriptFamilyBlues + + postscriptFamilyOtherBlues + + postscriptOtherBlues + + postscriptStemSnapH + + postscriptStemSnapV + + unitsPerEm + 1000 + xHeight + 200 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/contents.plist new file mode 100644 index 0000000..229927d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/contents.plist @@ -0,0 +1,8 @@ + + + + + glyphOne + glyphO_ne.glif + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif new file mode 100644 index 0000000..752f91b --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist new file mode 100644 index 0000000..3cf39b4 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist @@ -0,0 +1,8 @@ + + + + + color + 1,0.75,0,0.7 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/groups.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/groups.plist new file mode 100644 index 0000000..4dff22d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/groups.plist @@ -0,0 +1,16 @@ + + + + + public.kern1.groupA + + glyphOne + glyphTwo + + public.kern2.groupB + + glyphThree + glyphFour + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist new file mode 100644 index 0000000..46756bb --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist @@ -0,0 +1,20 @@ + + + + + glyphOne + + glyphFour + 10 + glyphOne + -100 + glyphThree + 10 + + public.kern1.groupA + + public.kern2.groupB + -100 + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/layercontents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/layercontents.plist new file mode 100644 index 0000000..b9c1a4f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/layercontents.plist @@ -0,0 +1,10 @@ + + + + + + public.default + glyphs + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/lib.plist new file mode 100644 index 0000000..60019e8 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/lib.plist @@ -0,0 +1,30 @@ + + + + + com.typemytype.robofont.compileSettings.autohint + + com.typemytype.robofont.compileSettings.checkOutlines + + com.typemytype.robofont.compileSettings.createDummyDSIG + + com.typemytype.robofont.compileSettings.decompose + + com.typemytype.robofont.compileSettings.generateFormat + 0 + com.typemytype.robofont.compileSettings.releaseMode + + com.typemytype.robofont.generateFeaturesWithFontTools + + com.typemytype.robofont.italicSlantOffset + 0 + com.typemytype.robofont.shouldAddPointsInSplineConversion + 1 + public.glyphOrder + + glyphOne + + ufoProcessor.test.lib.entry + Lib entry for master 1 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/metainfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/metainfo.plist new file mode 100644 index 0000000..7b8b34a --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/metainfo.plist @@ -0,0 +1,10 @@ + + + + + creator + com.github.fonttools.ufoLib + formatVersion + 3 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/features.fea new file mode 100644 index 0000000..fca011f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/features.fea @@ -0,0 +1 @@ +# features text from master 1 \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist new file mode 100644 index 0000000..43298fa --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist @@ -0,0 +1,53 @@ + + + + + ascender + 400 + capHeight + 400 + copyright + This is the copyright notice from master 1 + descender + -200 + familyName + Two_narrow_open + guidelines + + openTypeHheaAscender + 1036 + openTypeHheaDescender + -335 + openTypeOS2TypoAscender + 730 + openTypeOS2TypoDescender + -270 + openTypeOS2WinAscent + 1036 + openTypeOS2WinDescent + 335 + postscriptBlueFuzz + 0 + postscriptBlueScale + 0.22 + postscriptBlueValues + + 100 + 110 + + postscriptFamilyBlues + + postscriptFamilyOtherBlues + + postscriptOtherBlues + + postscriptStemSnapH + + postscriptStemSnapV + + unitsPerEm + 1000 + xHeight + 200 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/contents.plist new file mode 100644 index 0000000..229927d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/contents.plist @@ -0,0 +1,8 @@ + + + + + glyphOne + glyphO_ne.glif + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif new file mode 100644 index 0000000..0fa1933 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist new file mode 100644 index 0000000..3cf39b4 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist @@ -0,0 +1,8 @@ + + + + + color + 1,0.75,0,0.7 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/groups.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/groups.plist new file mode 100644 index 0000000..4dff22d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/groups.plist @@ -0,0 +1,16 @@ + + + + + public.kern1.groupA + + glyphOne + glyphTwo + + public.kern2.groupB + + glyphThree + glyphFour + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist new file mode 100644 index 0000000..46756bb --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist @@ -0,0 +1,20 @@ + + + + + glyphOne + + glyphFour + 10 + glyphOne + -100 + glyphThree + 10 + + public.kern1.groupA + + public.kern2.groupB + -100 + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/layercontents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/layercontents.plist new file mode 100644 index 0000000..b9c1a4f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/layercontents.plist @@ -0,0 +1,10 @@ + + + + + + public.default + glyphs + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/lib.plist new file mode 100644 index 0000000..60019e8 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/lib.plist @@ -0,0 +1,30 @@ + + + + + com.typemytype.robofont.compileSettings.autohint + + com.typemytype.robofont.compileSettings.checkOutlines + + com.typemytype.robofont.compileSettings.createDummyDSIG + + com.typemytype.robofont.compileSettings.decompose + + com.typemytype.robofont.compileSettings.generateFormat + 0 + com.typemytype.robofont.compileSettings.releaseMode + + com.typemytype.robofont.generateFeaturesWithFontTools + + com.typemytype.robofont.italicSlantOffset + 0 + com.typemytype.robofont.shouldAddPointsInSplineConversion + 1 + public.glyphOrder + + glyphOne + + ufoProcessor.test.lib.entry + Lib entry for master 1 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/metainfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/metainfo.plist new file mode 100644 index 0000000..7b8b34a --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/metainfo.plist @@ -0,0 +1,10 @@ + + + + + creator + com.github.fonttools.ufoLib + formatVersion + 3 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/features.fea new file mode 100644 index 0000000..fca011f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/features.fea @@ -0,0 +1 @@ +# features text from master 1 \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist new file mode 100644 index 0000000..82f1b17 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist @@ -0,0 +1,53 @@ + + + + + ascender + 400 + capHeight + 400 + copyright + This is the copyright notice from master 1 + descender + -200 + familyName + Two_narrow_solid + guidelines + + openTypeHheaAscender + 1036 + openTypeHheaDescender + -335 + openTypeOS2TypoAscender + 730 + openTypeOS2TypoDescender + -270 + openTypeOS2WinAscent + 1036 + openTypeOS2WinDescent + 335 + postscriptBlueFuzz + 0 + postscriptBlueScale + 0.22 + postscriptBlueValues + + 100 + 110 + + postscriptFamilyBlues + + postscriptFamilyOtherBlues + + postscriptOtherBlues + + postscriptStemSnapH + + postscriptStemSnapV + + unitsPerEm + 1000 + xHeight + 200 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/contents.plist new file mode 100644 index 0000000..229927d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/contents.plist @@ -0,0 +1,8 @@ + + + + + glyphOne + glyphO_ne.glif + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif new file mode 100644 index 0000000..a45f6d3 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist new file mode 100644 index 0000000..3cf39b4 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist @@ -0,0 +1,8 @@ + + + + + color + 1,0.75,0,0.7 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/groups.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/groups.plist new file mode 100644 index 0000000..4dff22d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/groups.plist @@ -0,0 +1,16 @@ + + + + + public.kern1.groupA + + glyphOne + glyphTwo + + public.kern2.groupB + + glyphThree + glyphFour + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist new file mode 100644 index 0000000..46756bb --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist @@ -0,0 +1,20 @@ + + + + + glyphOne + + glyphFour + 10 + glyphOne + -100 + glyphThree + 10 + + public.kern1.groupA + + public.kern2.groupB + -100 + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/layercontents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/layercontents.plist new file mode 100644 index 0000000..b9c1a4f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/layercontents.plist @@ -0,0 +1,10 @@ + + + + + + public.default + glyphs + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/lib.plist new file mode 100644 index 0000000..60019e8 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/lib.plist @@ -0,0 +1,30 @@ + + + + + com.typemytype.robofont.compileSettings.autohint + + com.typemytype.robofont.compileSettings.checkOutlines + + com.typemytype.robofont.compileSettings.createDummyDSIG + + com.typemytype.robofont.compileSettings.decompose + + com.typemytype.robofont.compileSettings.generateFormat + 0 + com.typemytype.robofont.compileSettings.releaseMode + + com.typemytype.robofont.generateFeaturesWithFontTools + + com.typemytype.robofont.italicSlantOffset + 0 + com.typemytype.robofont.shouldAddPointsInSplineConversion + 1 + public.glyphOrder + + glyphOne + + ufoProcessor.test.lib.entry + Lib entry for master 1 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/metainfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/metainfo.plist new file mode 100644 index 0000000..7b8b34a --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/metainfo.plist @@ -0,0 +1,10 @@ + + + + + creator + com.github.fonttools.ufoLib + formatVersion + 3 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/features.fea new file mode 100644 index 0000000..fca011f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/features.fea @@ -0,0 +1 @@ +# features text from master 1 \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist new file mode 100644 index 0000000..95c0a24 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist @@ -0,0 +1,53 @@ + + + + + ascender + 400 + capHeight + 400 + copyright + This is the copyright notice from master 1 + descender + -200 + familyName + Three_narrow_open + guidelines + + openTypeHheaAscender + 1036 + openTypeHheaDescender + -335 + openTypeOS2TypoAscender + 730 + openTypeOS2TypoDescender + -270 + openTypeOS2WinAscent + 1036 + openTypeOS2WinDescent + 335 + postscriptBlueFuzz + 0 + postscriptBlueScale + 0.22 + postscriptBlueValues + + 100 + 110 + + postscriptFamilyBlues + + postscriptFamilyOtherBlues + + postscriptOtherBlues + + postscriptStemSnapH + + postscriptStemSnapV + + unitsPerEm + 1000 + xHeight + 200 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/contents.plist new file mode 100644 index 0000000..229927d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/contents.plist @@ -0,0 +1,8 @@ + + + + + glyphOne + glyphO_ne.glif + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif new file mode 100644 index 0000000..76f611f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist new file mode 100644 index 0000000..3cf39b4 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist @@ -0,0 +1,8 @@ + + + + + color + 1,0.75,0,0.7 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/groups.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/groups.plist new file mode 100644 index 0000000..4dff22d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/groups.plist @@ -0,0 +1,16 @@ + + + + + public.kern1.groupA + + glyphOne + glyphTwo + + public.kern2.groupB + + glyphThree + glyphFour + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist new file mode 100644 index 0000000..46756bb --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist @@ -0,0 +1,20 @@ + + + + + glyphOne + + glyphFour + 10 + glyphOne + -100 + glyphThree + 10 + + public.kern1.groupA + + public.kern2.groupB + -100 + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/layercontents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/layercontents.plist new file mode 100644 index 0000000..b9c1a4f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/layercontents.plist @@ -0,0 +1,10 @@ + + + + + + public.default + glyphs + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/lib.plist new file mode 100644 index 0000000..60019e8 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/lib.plist @@ -0,0 +1,30 @@ + + + + + com.typemytype.robofont.compileSettings.autohint + + com.typemytype.robofont.compileSettings.checkOutlines + + com.typemytype.robofont.compileSettings.createDummyDSIG + + com.typemytype.robofont.compileSettings.decompose + + com.typemytype.robofont.compileSettings.generateFormat + 0 + com.typemytype.robofont.compileSettings.releaseMode + + com.typemytype.robofont.generateFeaturesWithFontTools + + com.typemytype.robofont.italicSlantOffset + 0 + com.typemytype.robofont.shouldAddPointsInSplineConversion + 1 + public.glyphOrder + + glyphOne + + ufoProcessor.test.lib.entry + Lib entry for master 1 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/metainfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/metainfo.plist new file mode 100644 index 0000000..7b8b34a --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/metainfo.plist @@ -0,0 +1,10 @@ + + + + + creator + com.github.fonttools.ufoLib + formatVersion + 3 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/features.fea new file mode 100644 index 0000000..fca011f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/features.fea @@ -0,0 +1 @@ +# features text from master 1 \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist new file mode 100644 index 0000000..1483423 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist @@ -0,0 +1,53 @@ + + + + + ascender + 400 + capHeight + 400 + copyright + This is the copyright notice from master 1 + descender + -200 + familyName + Three_narrow_solid + guidelines + + openTypeHheaAscender + 1036 + openTypeHheaDescender + -335 + openTypeOS2TypoAscender + 730 + openTypeOS2TypoDescender + -270 + openTypeOS2WinAscent + 1036 + openTypeOS2WinDescent + 335 + postscriptBlueFuzz + 0 + postscriptBlueScale + 0.22 + postscriptBlueValues + + 100 + 110 + + postscriptFamilyBlues + + postscriptFamilyOtherBlues + + postscriptOtherBlues + + postscriptStemSnapH + + postscriptStemSnapV + + unitsPerEm + 1000 + xHeight + 200 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/contents.plist new file mode 100644 index 0000000..229927d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/contents.plist @@ -0,0 +1,8 @@ + + + + + glyphOne + glyphO_ne.glif + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif new file mode 100644 index 0000000..63e4b51 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist new file mode 100644 index 0000000..3cf39b4 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist @@ -0,0 +1,8 @@ + + + + + color + 1,0.75,0,0.7 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/groups.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/groups.plist new file mode 100644 index 0000000..4dff22d --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/groups.plist @@ -0,0 +1,16 @@ + + + + + public.kern1.groupA + + glyphOne + glyphTwo + + public.kern2.groupB + + glyphThree + glyphFour + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist new file mode 100644 index 0000000..46756bb --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist @@ -0,0 +1,20 @@ + + + + + glyphOne + + glyphFour + 10 + glyphOne + -100 + glyphThree + 10 + + public.kern1.groupA + + public.kern2.groupB + -100 + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/layercontents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/layercontents.plist new file mode 100644 index 0000000..b9c1a4f --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/layercontents.plist @@ -0,0 +1,10 @@ + + + + + + public.default + glyphs + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/lib.plist new file mode 100644 index 0000000..60019e8 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/lib.plist @@ -0,0 +1,30 @@ + + + + + com.typemytype.robofont.compileSettings.autohint + + com.typemytype.robofont.compileSettings.checkOutlines + + com.typemytype.robofont.compileSettings.createDummyDSIG + + com.typemytype.robofont.compileSettings.decompose + + com.typemytype.robofont.compileSettings.generateFormat + 0 + com.typemytype.robofont.compileSettings.releaseMode + + com.typemytype.robofont.generateFeaturesWithFontTools + + com.typemytype.robofont.italicSlantOffset + 0 + com.typemytype.robofont.shouldAddPointsInSplineConversion + 1 + public.glyphOrder + + glyphOne + + ufoProcessor.test.lib.entry + Lib entry for master 1 + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/metainfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/metainfo.plist new file mode 100644 index 0000000..7b8b34a --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/metainfo.plist @@ -0,0 +1,10 @@ + + + + + creator + com.github.fonttools.ufoLib + formatVersion + 3 + + diff --git a/Tests/spReader_testdocs/superpolator_testdoc1_output_roundtripped.designspace b/Tests/spReader_testdocs/superpolator_testdoc1_output_roundtripped.designspace deleted file mode 100644 index 39fbf32..0000000 --- a/Tests/spReader_testdocs/superpolator_testdoc1_output_roundtripped.designspace +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - com.letterror.skateboard.interactionSources - - horizontal - - width - - ignore - - vertical - - weight - - - com.letterror.skateboard.mutedSources - - - ufo/MutatorSansLightCondensed.ufo - foreground - - - ufo/MutatorSansBoldCondensed.ufo - foreground - - - com.letterror.skateboard.previewText - VA - com.superpolator.data - - expandRules - - horizontalPreviewAxis - width - includeLegacyRules - - instancefolder - instances - keepWorkFiles - - lineInverted - - lineStacked - lined - lineViewFilled - - outputFormatUFO - 3.0 - previewtext - VA - roundGeometry - - verticalPreviewAxis - weight - - - - From 54bcda24dc82a4dad4511f58bd00faa0ce96be0b Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Wed, 8 Jun 2022 17:16:48 +0200 Subject: [PATCH 02/26] WIP handle serialized for discrete axis object follow the process into getGlyphMutator and see what happens. --- Lib/ufoProcessor/__init__.py | 119 ++++++++++++++++++++++++----------- 1 file changed, 83 insertions(+), 36 deletions(-) diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index 7755b2b..71f8d92 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -341,7 +341,26 @@ def generateUFO(self, processRules=True, glyphNames=None, pairs=None, bend=False return True def getSerializedAxes(self): - return [a.serialize() for a in self.axes] + serialized = [] + for axis in self.axes: + if hasattr(axis, "serialize"): + serialized.append(axis.serialize()) + else: + if hasattr(axis, "values"): + # discrete axis, make sure + d = dict( + tag=axis.tag, + name=axis.name, + labelNames=axis.labelNames, + values=axis.values, + default=axis.default, + hidden=axis.hidden, + map=axis.map, + axisOrdering=axis.axisOrdering, + axisLabels=axis.axisLabels, + ) + serialized.append(d) + return serialized def getMutatorAxes(self): # map the axis values? @@ -466,12 +485,15 @@ def filterThisLocation(self, location, mutedAxes): def getGlyphMutator(self, glyphName, decomposeComponents=False, - fromCache=None): - # make a mutator / varlib object for glyphName. - cacheKey = (glyphName, decomposeComponents) - if cacheKey in self._glyphMutators and fromCache: - return self._glyphMutators[cacheKey] - items = self.collectMastersForGlyph(glyphName, decomposeComponents=decomposeComponents) + fromCache=None, + discreteLocation=None, + ): + # make a mutator / varlib object for glyphName, with the masters for the given discrete location + #cacheKey = (glyphName, decomposeComponents) + #if cacheKey in self._glyphMutators and fromCache: + # return self._glyphMutators[cacheKey] + items = self.collectMastersForGlyph(glyphName, decomposeComponents=decomposeComponents, discreteLocation=discreteLocation) + #print('items', items) new = [] for a, b, c in items: if hasattr(b, "toMathGlyph"): @@ -483,14 +505,17 @@ def getGlyphMutator(self, glyphName, thing = None try: bias, thing = self.getVariationModel(new, axes=self.serializedAxes, bias=self.newDefaultLocation(bend=True)) #xx + print(bias, thing) except TypeError: + print('problems') self.toolLog.append("getGlyphMutator %s items: %s new: %s" % (glyphName, items, new)) self.problems.append("\tCan't make processor for glyph %s" % (glyphName)) if thing is not None: self._glyphMutators[cacheKey] = thing + print('xxxx', thing) return thing - def collectMastersForGlyph(self, glyphName, decomposeComponents=False): + def collectMastersForGlyph(self, glyphName, decomposeComponents=False, discreteLocation=None): """ Return a glyph mutator.defaultLoc decomposeComponents = True causes the source glyphs to be decomposed first before building the mutator. That gives you instances that do not depend @@ -501,7 +526,15 @@ def collectMastersForGlyph(self, glyphName, decomposeComponents=False): items = [] empties = [] foundEmpty = False - for sourceDescriptor in self.sources: + # + print(f'collectMastersForGlyph discreteLocation: {discreteLocation}') + if discreteLocation is not None: + sources = self.findSourcesForDiscreteLocation(discreteLocation) + else: + sources = self.sources + # + print(f'collectMastersForGlyph sources: {sources}') + for sourceDescriptor in sources: if not os.path.exists(sourceDescriptor.path): #kthxbai p = "\tMissing UFO at %s" % sourceDescriptor.path @@ -766,32 +799,32 @@ def makeInstance(self, instanceDescriptor, except: self.problems.append("Could not make mutator for glyph %s %s" % (glyphName, traceback.format_exc())) continue - if glyphName in instanceDescriptor.glyphs.keys(): - # XXX this should be able to go now that we have full rule support. - # reminder: this is what the glyphData can look like - # {'instanceLocation': {'custom': 0.0, 'weight': 824.0}, - # 'masters': [{'font': 'master.Adobe VF Prototype.Master_0.0', - # 'glyphName': 'dollar.nostroke', - # 'location': {'custom': 0.0, 'weight': 0.0}}, - # {'font': 'master.Adobe VF Prototype.Master_1.1', - # 'glyphName': 'dollar.nostroke', - # 'location': {'custom': 0.0, 'weight': 368.0}}, - # {'font': 'master.Adobe VF Prototype.Master_2.2', - # 'glyphName': 'dollar.nostroke', - # 'location': {'custom': 0.0, 'weight': 1000.0}}, - # {'font': 'master.Adobe VF Prototype.Master_3.3', - # 'glyphName': 'dollar.nostroke', - # 'location': {'custom': 100.0, 'weight': 1000.0}}, - # {'font': 'master.Adobe VF Prototype.Master_0.4', - # 'glyphName': 'dollar.nostroke', - # 'location': {'custom': 100.0, 'weight': 0.0}}, - # {'font': 'master.Adobe VF Prototype.Master_4.5', - # 'glyphName': 'dollar.nostroke', - # 'location': {'custom': 100.0, 'weight': 368.0}}], - # 'unicodes': [36]} - glyphData = instanceDescriptor.glyphs[glyphName] - else: - glyphData = {} + # if glyphName in instanceDescriptor.glyphs.keys(): + # # XXX this should be able to go now that we have full rule support. + # # reminder: this is what the glyphData can look like + # # {'instanceLocation': {'custom': 0.0, 'weight': 824.0}, + # # 'masters': [{'font': 'master.Adobe VF Prototype.Master_0.0', + # # 'glyphName': 'dollar.nostroke', + # # 'location': {'custom': 0.0, 'weight': 0.0}}, + # # {'font': 'master.Adobe VF Prototype.Master_1.1', + # # 'glyphName': 'dollar.nostroke', + # # 'location': {'custom': 0.0, 'weight': 368.0}}, + # # {'font': 'master.Adobe VF Prototype.Master_2.2', + # # 'glyphName': 'dollar.nostroke', + # # 'location': {'custom': 0.0, 'weight': 1000.0}}, + # # {'font': 'master.Adobe VF Prototype.Master_3.3', + # # 'glyphName': 'dollar.nostroke', + # # 'location': {'custom': 100.0, 'weight': 1000.0}}, + # # {'font': 'master.Adobe VF Prototype.Master_0.4', + # # 'glyphName': 'dollar.nostroke', + # # 'location': {'custom': 100.0, 'weight': 0.0}}, + # # {'font': 'master.Adobe VF Prototype.Master_4.5', + # # 'glyphName': 'dollar.nostroke', + # # 'location': {'custom': 100.0, 'weight': 368.0}}], + # # 'unicodes': [36]} + # glyphData = instanceDescriptor.glyphs[glyphName] + # else: + glyphData = {} font.newGlyph(glyphName) font[glyphName].clear() if glyphData.get('mute', False): @@ -1023,10 +1056,24 @@ def findSourcesForDiscreteLocation(self, discreteLocDict): print(f"Can we find the ds5 example at {ds5Path}? {os.path.exists(ds5Path)}") dsp.read(ds5Path) + dsp.loadFonts() print('all the axes\n', dsp.axes) print(f'\nSo we will have {len(dsp.getDiscreteLocations())} continuous designspaces in this document') + + testGlyphName = "glyphOne" + for loc in dsp.getDiscreteLocations(): print() + print("#"*60) print(f'For this discrete location {loc} we have these masters available:') - print(dsp.findSourcesForDiscreteLocation(loc)) + #print(dsp.findSourcesForDiscreteLocation(loc)) + + mastersForGlyph = dsp.collectMastersForGlyph(testGlyphName, discreteLocation=loc) + print('mastersForGlyph') + for item in mastersForGlyph: + for i in item: + print('\t\t', i) + # note for later: look into newDefaultLocation + mut = dsp.getGlyphMutator(testGlyphName, discreteLocation=loc) + print(f'mutator for {testGlyphName} at {loc}', mut) \ No newline at end of file From 3e457f147c02329e1d6f47c18b115ab1a27b0494 Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Fri, 10 Jun 2022 11:49:52 +0200 Subject: [PATCH 03/26] WIP this prepares the glyph data for consumption by mutatormath and varlib. Working variationmodels and mutators! --- Lib/ufoProcessor/__init__.py | 157 ++++++++++++++++--------- Lib/ufoProcessor/ufoProcessorSketch.py | 94 +++++++++++++++ Lib/ufoProcessor/varModels.py | 14 ++- 3 files changed, 209 insertions(+), 56 deletions(-) create mode 100644 Lib/ufoProcessor/ufoProcessorSketch.py diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index 71f8d92..5c2150b 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -26,8 +26,13 @@ # if you only intend to use varLib.model then importing mutatorMath is not necessary. from mutatorMath.objects.mutator import buildMutator from mutatorMath.objects.location import Location -from ufoProcessor.varModels import VariationModelMutator -from ufoProcessor.emptyPen import checkGlyphIsEmpty + + +# back to these when we're running as a package +#from ufoProcessor.varModels import VariationModelMutator +#from ufoProcessor.emptyPen import checkGlyphIsEmpty +from varModels import VariationModelMutator +from emptyPen import checkGlyphIsEmpty try: @@ -77,6 +82,10 @@ def getLayer(f, layerName): Notes Parking the glyphs under a swapname is a bit lazy, but at least it guarantees the glyphs have the right parent. + 2022 work on support for DS5 + https://github.com/fonttools/fonttools/blob/main/Lib/fontTools/designspaceLib/split.py#L53 + + """ @@ -340,33 +349,37 @@ def generateUFO(self, processRules=True, glyphNames=None, pairs=None, bend=False self.problems.append("Generated %s as UFO%d"%(os.path.basename(path), self.ufoVersion)) return True + def _serializeAnyAxis(self, axis): + if hasattr(axis, "serialize"): + return axis.serialize() + else: + if hasattr(axis, "values"): + # discrete axis does not have serialize method, meh + return dict( + tag=axis.tag, + name=axis.name, + labelNames=axis.labelNames, + minimum = min(axis.values), # XX is this allowed + maximum = max(axis.values), # XX is this allowed + values=axis.values, + default=axis.default, + hidden=axis.hidden, + map=axis.map, + axisOrdering=axis.axisOrdering, + axisLabels=axis.axisLabels, + ) + def getSerializedAxes(self): serialized = [] for axis in self.axes: - if hasattr(axis, "serialize"): - serialized.append(axis.serialize()) - else: - if hasattr(axis, "values"): - # discrete axis, make sure - d = dict( - tag=axis.tag, - name=axis.name, - labelNames=axis.labelNames, - values=axis.values, - default=axis.default, - hidden=axis.hidden, - map=axis.map, - axisOrdering=axis.axisOrdering, - axisLabels=axis.axisLabels, - ) - serialized.append(d) + serialized.append(self._serializeAnyAxis(axis)) return serialized def getMutatorAxes(self): # map the axis values? d = collections.OrderedDict() for a in self.axes: - d[a.name] = a.serialize() + d[a.name] = self._serializeAnyAxis(a) return d def _getAxisOrder(self): @@ -378,9 +391,11 @@ def _getAxisOrder(self): def getVariationModel(self, items, axes, bias=None): # Return either a mutatorMath or a varlib.model object for calculating. + print(f"\t## into getVariationModel bias {bias}") try: if self.useVarlib: # use the varlib variation model + print("\t\t## into getVariationModel varlib") try: return dict(), VariationModelMutator(items, self.axes) except (KeyError, AssertionError): @@ -390,10 +405,15 @@ def getVariationModel(self, items, axes, bias=None): return {}, None else: # use mutatormath model + print("\t\t## into getVariationModel mutatormath") axesForMutator = self.getMutatorAxes() + print("\t\t## into getVariationModel axesForMutator", axesForMutator) + print("\t\t## into getVariationModel items", items) + print("\t\t## into getVariationModel bias", bias) return buildMutator(items, axes=axesForMutator, bias=bias) except: error = traceback.format_exc() + print("\t\t## into getVariationModel Error", error) self.toolLog.append("UFOProcessor.getVariationModel error: %s" % error) return {}, None @@ -493,7 +513,8 @@ def getGlyphMutator(self, glyphName, #if cacheKey in self._glyphMutators and fromCache: # return self._glyphMutators[cacheKey] items = self.collectMastersForGlyph(glyphName, decomposeComponents=decomposeComponents, discreteLocation=discreteLocation) - #print('items', items) + for item in items: + print('\t\t## etGlyphMutator items', item) new = [] for a, b, c in items: if hasattr(b, "toMathGlyph"): @@ -503,16 +524,17 @@ def getGlyphMutator(self, glyphName, else: new.append((a,self.mathGlyphClass(b))) thing = None + print('\t\t## getGlyphMutator new', new) try: - bias, thing = self.getVariationModel(new, axes=self.serializedAxes, bias=self.newDefaultLocation(bend=True)) #xx - print(bias, thing) + bias, thing = self.getVariationModel(new, axes=self.serializedAxes, bias=self.newDefaultLocation(bend=True, discreteLocation=discreteLocation)) #xx + print('\t\t## getGlyphMutator bias', bias) + print('\t\t## getGlyphMutator thing', thing) except TypeError: print('problems') self.toolLog.append("getGlyphMutator %s items: %s new: %s" % (glyphName, items, new)) self.problems.append("\tCan't make processor for glyph %s" % (glyphName)) - if thing is not None: - self._glyphMutators[cacheKey] = thing - print('xxxx', thing) + #if thing is not None: + # self._glyphMutators[cacheKey] = thing return thing def collectMastersForGlyph(self, glyphName, decomposeComponents=False, discreteLocation=None): @@ -598,6 +620,10 @@ def collectMastersForGlyph(self, glyphName, decomposeComponents=False, discreteL processThis = processThis.toMathGlyph() else: processThis = self.mathGlyphClass(processThis) + # this is where the location is linked to the glyph + # this loc needs to have the discrete location subtracted + if discreteLocation: + loc = {k:v for k, v in loc.items() if k not in discreteLocation} items.append((loc, processThis, sourceInfo)) empties.append((thisIsDefault, foundEmpty)) # check the empties: @@ -622,6 +648,7 @@ def collectMastersForGlyph(self, glyphName, decomposeComponents=False, discreteL isDefault, isEmpty = p if isEmpty: checkedItems.append(items[i]) + print('checkedItems', checkedItems) return checkedItems def getNeutralFont(self): @@ -654,11 +681,14 @@ def findDefault(self): return None - def newDefaultLocation(self, bend=False): + def newDefaultLocation(self, bend=False, discreteLocation=None): # overwrite from fontTools.newDefaultLocation # we do not want this default location to be mapped. loc = collections.OrderedDict() for axisDescriptor in self.axes: + if discreteLocation is not None: + if axisDescriptor.name in discreteLocation: + continue if bend: loc[axisDescriptor.name] = axisDescriptor.map_forward( axisDescriptor.default @@ -1015,6 +1045,11 @@ def getOrderedDiscreteAxes(self): axes.append(axisObj) return axes + #def splitLocation(self, location): + # # split a location in a continouous and a discrete part + # continuous = {} + # discrete = {} + def getDiscreteLocations(self): # return a list of all permutated discrete locations # do we have a list of ordered axes? @@ -1049,31 +1084,45 @@ def findSourcesForDiscreteLocation(self, discreteLocDict): return sources -if __name__ == "__main__": - # while we're testing - dsp = DesignSpaceProcessor() - ds5Path = "../../Tests/202206 discrete spaces/test.ds5.designspace" - print(f"Can we find the ds5 example at {ds5Path}? {os.path.exists(ds5Path)}") - - dsp.read(ds5Path) - dsp.loadFonts() - print('all the axes\n', dsp.axes) - print(f'\nSo we will have {len(dsp.getDiscreteLocations())} continuous designspaces in this document') - - testGlyphName = "glyphOne" - - for loc in dsp.getDiscreteLocations(): - print() - print("#"*60) - print(f'For this discrete location {loc} we have these masters available:') - #print(dsp.findSourcesForDiscreteLocation(loc)) - - mastersForGlyph = dsp.collectMastersForGlyph(testGlyphName, discreteLocation=loc) - print('mastersForGlyph') - for item in mastersForGlyph: - for i in item: - print('\t\t', i) +# while we're testing +dsp = DesignSpaceProcessor(useVarlib=True) +print(f'useVarLib {dsp.useVarlib}') +ds5Path = "../../Tests/202206 discrete spaces/test.ds5.designspace" +print(f"Can we find the ds5 example at {ds5Path}? {os.path.exists(ds5Path)}") + +dsp.read(ds5Path) +dsp.loadFonts() +print('all the axes\n', dsp.axes) +print(f'\nSo we will have {len(dsp.getDiscreteLocations())} continuous designspaces in this document') + +testGlyphName = "glyphOne" +makeFonts = False + +for loc in dsp.getDiscreteLocations(): + print() + print("#"*60) + print(f'For this discrete location {loc} we have these masters available:') + #print(dsp.findSourcesForDiscreteLocation(loc)) + + mastersForGlyph = dsp.collectMastersForGlyph(testGlyphName, discreteLocation=loc) + print('mastersForGlyph') + for item in mastersForGlyph: + for i in item: + print('\t\t', i) + + # note for later: look into newDefaultLocation + mut = dsp.getGlyphMutator(testGlyphName, discreteLocation=loc) + print(f'mutator for {testGlyphName} at {loc}', mut) + print(f'newDefaultLocation {dsp.newDefaultLocation(discreteLocation=loc)}') + + if makeFonts: + f = NewFont() + extrapol = 200 + for v in range(400-extrapol, 1000+extrapol, 100): + n = f"result_glyph{v}" + f.newGlyph(n) + g = f[n] + r = mut.makeInstance({'width':v}) + g.fromMathGlyph(r) + g.width = r.width - # note for later: look into newDefaultLocation - mut = dsp.getGlyphMutator(testGlyphName, discreteLocation=loc) - print(f'mutator for {testGlyphName} at {loc}', mut) \ No newline at end of file diff --git a/Lib/ufoProcessor/ufoProcessorSketch.py b/Lib/ufoProcessor/ufoProcessorSketch.py new file mode 100644 index 0000000..0bcbf00 --- /dev/null +++ b/Lib/ufoProcessor/ufoProcessorSketch.py @@ -0,0 +1,94 @@ +## caching +import functools + + +_memoizeCache = dict() + + +def memoize(function): + @functools.wraps(function) + def wrapper(self, *args, **kwargs): + key = (function.__name__, self, args, tuple((key, kwargs[key]) for key in sorted(kwargs.keys()))) + if key in _memoizeCache: + return _memoizeCache[key] + else: + result = function(self, *args, **kwargs) + _memoizeCache[key] = result + return result + return wrapper + + +##### + +from fontTools.designspaceLib import DesignSpaceDocument + + +class DesignSpaceProcessor(DesignSpaceDocument): + + @memoize + def getGlyphMutator(self, glyphName, decomposeComponents=False, **discreteLocation): + glyphs = self.collectSourcesForGlyph(glyphName, decomposeComponents=decomposeComponents, **discreteLocation) + + print("build for glyphName", glyphName, discreteLocation) + return "a mutator" + + @memoize + def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, **discreteLocation): + discreteLocation = self.buildDiscreteLocation(discreteLocation) + sources = self.findSourcesForDiscreteLocation(**discreteLocation) + return [] + + @memoize + def findSourcesForDiscreteLocation(self, **discreteLocation): + discreteLocation = self.buildDiscreteLocation(discreteLocation) + sources = [] + for source in self.sources: + # check if part of a dict is inside an other dict + if discreteLocation.items() <= source.designLocation.items(): + sources.append(source) + return sources + + def buildDiscreteLocation(self, partialDiscretelocation): + return {**self.getDiscreteDefaultLocation(), **partialDiscretelocation} + + @property + def discreteAxes(self): + return [axis for axis in self.axes if hasattr(axis, "values")] + + def getDiscreteDefaultLocation(self): + discreteDefault = dict() + for axis in self.discreteAxes: + discreteDefault[axis.name] = axis.default + return discreteDefault + + def getDiscreteLocations(self): + for axis in self.discreteAxes: + print(axis) + + # chaching tools + + def changed(self): + _memoizeCache.clear() + + def glyphChanged(self, glyphName): + for key in list(_memoizeCache.keys()): + if key[0] in ("getGlyphMutator", "collectSourcesForGlyph") and key[1] == glyphName: + del _memoizeCache[key] + + + +d = DesignSpaceProcessor() +ds5Path = "../../Tests/202206 discrete spaces/test.ds5.designspace" +d.read(ds5Path) +r = d.getGlyphMutator("a", italic=0) +print(r) + +r = d.getGlyphMutator("a", italic=0) +print(r) +r = d.getGlyphMutator("a", italic=1) +print(r) + +print(d.getDiscreteDefaultLocation()) + +print(d.findSourcesForDiscreteLocation(countedItems=1)) +print(d.getDiscreteLocations()) \ No newline at end of file diff --git a/Lib/ufoProcessor/varModels.py b/Lib/ufoProcessor/varModels.py index 9deef48..1433d5a 100644 --- a/Lib/ufoProcessor/varModels.py +++ b/Lib/ufoProcessor/varModels.py @@ -65,19 +65,29 @@ def __init__(self, items, axes, model=None): 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) + axisMinimum, axisMaximum = self.getAxisMinMax(a) + mappedMinimum, mappedDefault, mappedMaximum = a.map_forward(axisMinimum), a.map_forward(a.default), a.map_forward(axisMaximum) self.axes[a.name] = (mappedMinimum, mappedDefault, mappedMaximum) if model is None: dd = [self._normalize(a) for a,b in items] ee = self.axisOrder + print('VariationModelMutator:', dd) + print('VariationModelMutator:', ee) 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 getAxisMinMax(self, axis): + # return tha axis.minimum and axis.maximum for continuous axes + # return the min(axis.values), max(axis.values) for discrete axes + if hasattr(axis, "values"): + return min(axis.values), max(axis.values) + return axis.minimum, axis.maximum + + def get(self, key): if key in self.model.locations: i = self.model.locations.index(key) From 20747f57ec3c53c071af91482ae61192122e558d Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Fri, 10 Jun 2022 16:27:35 +0200 Subject: [PATCH 04/26] WIP Add another test glyph Add kerning pairs Kerning mutator + Varlib, --- Lib/ufoProcessor/__init__.py | 286 ++++++++++-------- Tests/202206 discrete spaces/addGlyphTwo.py | 9 + .../202206 discrete spaces/ds5_makeTestDoc.py | 10 +- .../fontinfo.plist | 2 + .../glyphs/contents.plist | 2 + .../glyphs/glyphT_wo.glif | 7 + .../kerning.plist | 14 +- .../lib.plist | 1 + .../fontinfo.plist | 2 + .../glyphs/contents.plist | 2 + .../glyphs/glyphT_wo.glif | 7 + .../kerning.plist | 14 +- .../lib.plist | 1 + .../fontinfo.plist | 2 + .../glyphs/contents.plist | 2 + .../glyphs/glyphT_wo.glif | 7 + .../kerning.plist | 14 +- .../lib.plist | 1 + .../fontinfo.plist | 2 + .../glyphs/contents.plist | 2 + .../glyphs/glyphT_wo.glif | 7 + .../kerning.plist | 14 +- .../lib.plist | 1 + .../fontinfo.plist | 2 + .../glyphs/contents.plist | 2 + .../glyphs/glyphT_wo.glif | 7 + .../kerning.plist | 14 +- .../lib.plist | 1 + .../fontinfo.plist | 2 + .../glyphs/contents.plist | 2 + .../glyphs/glyphT_wo.glif | 7 + .../kerning.plist | 14 +- .../lib.plist | 1 + .../fontinfo.plist | 2 + .../glyphs/contents.plist | 2 + .../glyphs/glyphT_wo.glif | 7 + .../kerning.plist | 14 +- .../lib.plist | 1 + .../fontinfo.plist | 2 + .../glyphs/contents.plist | 2 + .../glyphs/glyphT_wo.glif | 7 + .../kerning.plist | 14 +- .../lib.plist | 1 + .../fontinfo.plist | 2 + .../glyphs/contents.plist | 2 + .../glyphs/glyphT_wo.glif | 7 + .../kerning.plist | 14 +- .../lib.plist | 1 + .../fontinfo.plist | 2 + .../glyphs/contents.plist | 2 + .../glyphs/glyphT_wo.glif | 7 + .../kerning.plist | 14 +- .../lib.plist | 1 + .../fontinfo.plist | 2 + .../glyphs/contents.plist | 2 + .../glyphs/glyphT_wo.glif | 7 + .../kerning.plist | 14 +- .../lib.plist | 1 + .../fontinfo.plist | 2 + .../glyphs/contents.plist | 2 + .../glyphs/glyphT_wo.glif | 7 + .../kerning.plist | 14 +- .../lib.plist | 1 + 63 files changed, 400 insertions(+), 217 deletions(-) create mode 100644 Tests/202206 discrete spaces/addGlyphTwo.py create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif create mode 100644 Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index 5c2150b..fde253f 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -437,28 +437,40 @@ def getInfoMutator(self): bias, self._infoMutator = self.getVariationModel(infoItems, axes=self.serializedAxes, bias=infoBias) return self._infoMutator - def getKerningMutator(self, pairs=None): + def getKerningMutator(self, pairs=None, discreteLocation=None): """ Return a kerning mutator, collect the sources, build mathGlyphs. If no pairs are given: calculate the whole table. If pairs are given then query the sources for a value and make a mutator only with those values. """ - if self._kerningMutator and pairs == self._kerningMutatorPairs: - return self._kerningMutator + #if self._kerningMutator and pairs == self._kerningMutatorPairs: + # return self._kerningMutator + #@@ + + + + print(f"\t\t## @@ into getKerningMutator {discreteLocation}") + if discreteLocation is not None: + sources = self.findSourcesForDiscreteLocation(discreteLocation) + else: + sources = self.sources kerningItems = [] foregroundLayers = [None, 'foreground', 'public.default'] if pairs is None: - for sourceDescriptor in self.sources: + for sourceDescriptor in sources: + print(f"\t\t##---> into getKerningMutator {sourceDescriptor.name}") if sourceDescriptor.layerName not in foregroundLayers: continue if not sourceDescriptor.muteKerning: - loc = Location(sourceDescriptor.location) + # filter this XX @@ + continuous, discrete = self.splitLocation(sourceDescriptor.location) + loc = Location(continuous) sourceFont = self.fonts[sourceDescriptor.name] if sourceFont is None: continue # this makes assumptions about the groups of all sources being the same. kerningItems.append((loc, self.mathKerningClass(sourceFont.kerning, sourceFont.groups))) else: self._kerningMutatorPairs = pairs - for sourceDescriptor in self.sources: + for sourceDescriptor in sources: # XXX check sourceDescriptor layerName, only foreground should contribute if sourceDescriptor.layerName is not None: continue @@ -468,7 +480,8 @@ def getKerningMutator(self, pairs=None): sourceFont = self.fonts[sourceDescriptor.name] if sourceFont is None: continue - loc = Location(sourceDescriptor.location) + continuous, discrete = self.splitLocation(sourceDescriptor.location) + loc = Location(continuous) # XXX can we get the kern value from the fontparts kerning object? kerningItem = self.mathKerningClass(sourceFont.kerning, sourceFont.groups) if kerningItem is not None: @@ -478,8 +491,13 @@ def getKerningMutator(self, pairs=None): if v is not None: sparseKerning[pair] = v kerningItems.append((loc, self.mathKerningClass(sparseKerning))) - kerningBias = self.newDefaultLocation(bend=True) + #kerningBias = self.newDefaultLocation(bend=True) + + kerningBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) + bias, thing = self.getVariationModel(kerningItems, axes=self.serializedAxes, bias=kerningBias) #xx + bias, self._kerningMutator = self.getVariationModel(kerningItems, axes=self.serializedAxes, bias=kerningBias) + print('self._kerningMutator', self._kerningMutator) return self._kerningMutator def filterThisLocation(self, location, mutedAxes): @@ -514,7 +532,7 @@ def getGlyphMutator(self, glyphName, # return self._glyphMutators[cacheKey] items = self.collectMastersForGlyph(glyphName, decomposeComponents=decomposeComponents, discreteLocation=discreteLocation) for item in items: - print('\t\t## etGlyphMutator items', item) + print('\t\t## getGlyphMutator items', item) new = [] for a, b, c in items: if hasattr(b, "toMathGlyph"): @@ -622,9 +640,9 @@ def collectMastersForGlyph(self, glyphName, decomposeComponents=False, discreteL processThis = self.mathGlyphClass(processThis) # this is where the location is linked to the glyph # this loc needs to have the discrete location subtracted - if discreteLocation: - loc = {k:v for k, v in loc.items() if k not in discreteLocation} - items.append((loc, processThis, sourceInfo)) + # XX @@ + continuous, discrete = self.splitLocation(loc) + items.append((continuous, processThis, sourceInfo)) empties.append((thisIsDefault, foundEmpty)) # check the empties: # if the default glyph is empty, then all must be empty @@ -648,7 +666,7 @@ def collectMastersForGlyph(self, glyphName, decomposeComponents=False, discreteL isDefault, isEmpty = p if isEmpty: checkedItems.append(items[i]) - print('checkedItems', checkedItems) + #print('checkedItems', checkedItems) return checkedItems def getNeutralFont(self): @@ -732,86 +750,91 @@ def makeInstance(self, instanceDescriptor, pairs=None, bend=False): """ Generate a font object for this instance """ + continuousLocation, discreteLocation = self.splitLocation(instanceDescriptor.location) + print(f"\t ## @@@ makeInstance continuousLocation {continuousLocation}") + print(f"\t ## makeInstance discreteLocation {discreteLocation}") font = self._instantiateFont(None) # make fonty things here - loc = Location(instanceDescriptor.location) + loc = Location(continuousLocation) anisotropic = False locHorizontal = locVertical = loc if self.isAnisotropic(loc): anisotropic = True locHorizontal, locVertical = self.splitAnisotropic(loc) # groups - renameMap = getattr(self.fonts[self.default.name], "kerningGroupConversionRenameMaps", None) - font.kerningGroupConversionRenameMaps = renameMap if renameMap is not None else {'side1': {}, 'side2': {}} + #renameMap = getattr(self.fonts[self.default.name], "kerningGroupConversionRenameMaps", None) + #font.kerningGroupConversionRenameMaps = renameMap if renameMap is not None else {'side1': {}, 'side2': {}} # make the kerning # this kerning is always horizontal. We can take the horizontal location # filter the available pairs? + # @@ if instanceDescriptor.kerning: if pairs: try: - kerningMutator = self.getKerningMutator(pairs=pairs) + kerningMutator = self.getKerningMutator(pairs=pairs, discreteLocation=discreteLocation) kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend) kerningObject.extractKerning(font) except: self.problems.append("Could not make kerning for %s. %s" % (loc, traceback.format_exc())) else: - kerningMutator = self.getKerningMutator() + kerningMutator = self.getKerningMutator(discreteLocation=discreteLocation) if kerningMutator is not None: + print("-------------------- making the kerning!") kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend) kerningObject.extractKerning(font) - # make the info - try: - infoMutator = self.getInfoMutator() - if infoMutator is not None: - if not anisotropic: - infoInstanceObject = infoMutator.makeInstance(loc, bend=bend) - else: - horizontalInfoInstanceObject = infoMutator.makeInstance(locHorizontal, bend=bend) - verticalInfoInstanceObject = infoMutator.makeInstance(locVertical, bend=bend) - # merge them again - infoInstanceObject = (1,0)*horizontalInfoInstanceObject + (0,1)*verticalInfoInstanceObject - if self.roundGeometry: - try: - infoInstanceObject = infoInstanceObject.round() - except AttributeError: - pass - infoInstanceObject.extractInfo(font.info) - font.info.familyName = instanceDescriptor.familyName - font.info.styleName = instanceDescriptor.styleName - font.info.postscriptFontName = instanceDescriptor.postScriptFontName # yikes, note the differences in capitalisation.. - font.info.styleMapFamilyName = instanceDescriptor.styleMapFamilyName - font.info.styleMapStyleName = instanceDescriptor.styleMapStyleName - # NEED SOME HELP WITH THIS - # localised names need to go to the right openTypeNameRecords - # records = [] - # nameID = 1 - # platformID = - # for languageCode, name in instanceDescriptor.localisedStyleMapFamilyName.items(): - # # Name ID 1 (font family name) is found at the generic styleMapFamily attribute. - # records.append((nameID, )) - except: - self.problems.append("Could not make fontinfo for %s. %s" % (loc, traceback.format_exc())) - for sourceDescriptor in self.sources: - if sourceDescriptor.copyInfo: - # this is the source - if self.fonts[sourceDescriptor.name] is not None: - self._copyFontInfo(self.fonts[sourceDescriptor.name].info, font.info) - if sourceDescriptor.copyLib: - # excplicitly copy the font.lib items - if self.fonts[sourceDescriptor.name] is not None: - for key, value in self.fonts[sourceDescriptor.name].lib.items(): - font.lib[key] = value - if sourceDescriptor.copyGroups: - if self.fonts[sourceDescriptor.name] is not None: - sides = font.kerningGroupConversionRenameMaps.get('side1', {}) - sides.update(font.kerningGroupConversionRenameMaps.get('side2', {})) - for key, value in self.fonts[sourceDescriptor.name].groups.items(): - if key not in sides: - font.groups[key] = value - if sourceDescriptor.copyFeatures: - if self.fonts[sourceDescriptor.name] is not None: - featuresText = self.fonts[sourceDescriptor.name].features.text - font.features.text = featuresText + # # make the info + # try: + # infoMutator = self.getInfoMutator() + # if infoMutator is not None: + # if not anisotropic: + # infoInstanceObject = infoMutator.makeInstance(loc, bend=bend) + # else: + # horizontalInfoInstanceObject = infoMutator.makeInstance(locHorizontal, bend=bend) + # verticalInfoInstanceObject = infoMutator.makeInstance(locVertical, bend=bend) + # # merge them again + # infoInstanceObject = (1,0)*horizontalInfoInstanceObject + (0,1)*verticalInfoInstanceObject + # if self.roundGeometry: + # try: + # infoInstanceObject = infoInstanceObject.round() + # except AttributeError: + # pass + # infoInstanceObject.extractInfo(font.info) + # font.info.familyName = instanceDescriptor.familyName + # font.info.styleName = instanceDescriptor.styleName + # font.info.postscriptFontName = instanceDescriptor.postScriptFontName # yikes, note the differences in capitalisation.. + # font.info.styleMapFamilyName = instanceDescriptor.styleMapFamilyName + # font.info.styleMapStyleName = instanceDescriptor.styleMapStyleName + # # NEED SOME HELP WITH THIS + # # localised names need to go to the right openTypeNameRecords + # # records = [] + # # nameID = 1 + # # platformID = + # # for languageCode, name in instanceDescriptor.localisedStyleMapFamilyName.items(): + # # # Name ID 1 (font family name) is found at the generic styleMapFamily attribute. + # # records.append((nameID, )) + # except: + # self.problems.append("Could not make fontinfo for %s. %s" % (loc, traceback.format_exc())) + # for sourceDescriptor in self.sources: + # if sourceDescriptor.copyInfo: + # # this is the source + # if self.fonts[sourceDescriptor.name] is not None: + # self._copyFontInfo(self.fonts[sourceDescriptor.name].info, font.info) + # if sourceDescriptor.copyLib: + # # excplicitly copy the font.lib items + # if self.fonts[sourceDescriptor.name] is not None: + # for key, value in self.fonts[sourceDescriptor.name].lib.items(): + # font.lib[key] = value + # if sourceDescriptor.copyGroups: + # if self.fonts[sourceDescriptor.name] is not None: + # sides = font.kerningGroupConversionRenameMaps.get('side1', {}) + # sides.update(font.kerningGroupConversionRenameMaps.get('side2', {})) + # for key, value in self.fonts[sourceDescriptor.name].groups.items(): + # if key not in sides: + # font.groups[key] = value + # if sourceDescriptor.copyFeatures: + # if self.fonts[sourceDescriptor.name] is not None: + # featuresText = self.fonts[sourceDescriptor.name].features.text + # font.features.text = featuresText # glyphs if glyphNames: selectedGlyphNames = glyphNames @@ -822,7 +845,7 @@ def makeInstance(self, instanceDescriptor, font.lib['public.glyphOrder'] = selectedGlyphNames for glyphName in selectedGlyphNames: try: - glyphMutator = self.getGlyphMutator(glyphName) + glyphMutator = self.getGlyphMutator(glyphName, discreteLocation=discreteLocation) if glyphMutator is None: self.problems.append("Could not make mutator for glyph %s" % (glyphName)) continue @@ -876,22 +899,22 @@ def makeInstance(self, instanceDescriptor, font[glyphName] = note # XXXX phase out support for instance-specific masters # this should be handled by the rules system. - masters = glyphData.get("masters", None) - if masters is not None: - items = [] - for glyphMaster in masters: - sourceGlyphFont = glyphMaster.get("font") - sourceGlyphName = glyphMaster.get("glyphName", glyphName) - m = self.fonts.get(sourceGlyphFont) - if not sourceGlyphName in m: - continue - if hasattr(m[sourceGlyphName], "toMathGlyph"): - sourceGlyph = m[sourceGlyphName].toMathGlyph() - else: - sourceGlyph = MathGlyph(m[sourceGlyphName]) - sourceGlyphLocation = glyphMaster.get("location") - items.append((Location(sourceGlyphLocation), sourceGlyph)) - bias, glyphMutator = self.getVariationModel(items, axes=self.serializedAxes, bias=self.newDefaultLocation(bend=True)) + # masters = glyphData.get("masters", None) + # if masters is not None: + # items = [] + # for glyphMaster in masters: + # sourceGlyphFont = glyphMaster.get("font") + # sourceGlyphName = glyphMaster.get("glyphName", glyphName) + # m = self.fonts.get(sourceGlyphFont) + # if not sourceGlyphName in m: + # continue + # if hasattr(m[sourceGlyphName], "toMathGlyph"): + # sourceGlyph = m[sourceGlyphName].toMathGlyph() + # else: + # sourceGlyph = MathGlyph(m[sourceGlyphName]) + # sourceGlyphLocation = glyphMaster.get("location") + # items.append((Location(sourceGlyphLocation), sourceGlyph)) + # bias, glyphMutator = self.getVariationModel(items, axes=self.serializedAxes, bias=self.newDefaultLocation(bend=True)) try: if not self.isAnisotropic(glyphInstanceLocation): glyphInstanceObject = glyphMutator.makeInstance(glyphInstanceLocation, bend=bend) @@ -1045,10 +1068,24 @@ def getOrderedDiscreteAxes(self): axes.append(axisObj) return axes - #def splitLocation(self, location): - # # split a location in a continouous and a discrete part - # continuous = {} - # discrete = {} + #def removeDiscreteValuesFromLocation(self, location, discreteLocation=None): + # if discreteLocation: + # return {k:v for k, v in location.items() if k not in discreteLocation} + # return location + + def splitLocation(self, location): + # split a location in a continouous and a discrete part + discreteAxes = [a.name for a in self.getOrderedDiscreteAxes()] + continuous = {} + discrete = {} + for name, value in location.items(): + if name in discreteAxes: + discrete[name] = value + else: + continuous[name] = value + if not discrete: + return continuous, None + return continuous, discrete def getDiscreteLocations(self): # return a list of all permutated discrete locations @@ -1098,31 +1135,40 @@ def findSourcesForDiscreteLocation(self, discreteLocDict): testGlyphName = "glyphOne" makeFonts = False -for loc in dsp.getDiscreteLocations(): - print() - print("#"*60) - print(f'For this discrete location {loc} we have these masters available:') - #print(dsp.findSourcesForDiscreteLocation(loc)) - - mastersForGlyph = dsp.collectMastersForGlyph(testGlyphName, discreteLocation=loc) - print('mastersForGlyph') - for item in mastersForGlyph: - for i in item: - print('\t\t', i) - - # note for later: look into newDefaultLocation - mut = dsp.getGlyphMutator(testGlyphName, discreteLocation=loc) - print(f'mutator for {testGlyphName} at {loc}', mut) - print(f'newDefaultLocation {dsp.newDefaultLocation(discreteLocation=loc)}') - - if makeFonts: - f = NewFont() - extrapol = 200 - for v in range(400-extrapol, 1000+extrapol, 100): - n = f"result_glyph{v}" - f.newGlyph(n) - g = f[n] - r = mut.makeInstance({'width':v}) - g.fromMathGlyph(r) - g.width = r.width - +if False: + for loc in dsp.getDiscreteLocations(): + print() + print("#"*60) + print(f'For this discrete location {loc} we have these masters available:') + #print(dsp.findSourcesForDiscreteLocation(loc)) + + mastersForGlyph = dsp.collectMastersForGlyph(testGlyphName, discreteLocation=loc) + print('mastersForGlyph') + for item in mastersForGlyph: + for i in item: + print('\t\t', i) + + # note for later: look into newDefaultLocation + mut = dsp.getGlyphMutator(testGlyphName, discreteLocation=loc) + print(f'mutator for {testGlyphName} at {loc}', mut) + print(f'newDefaultLocation {dsp.newDefaultLocation(discreteLocation=loc)}') + + if makeFonts: + f = NewFont() + extrapol = 200 + for v in range(400-extrapol, 1000+extrapol, 100): + n = f"result_glyph{v}" + f.newGlyph(n) + g = f[n] + r = mut.makeInstance({'width':v}) + g.fromMathGlyph(r) + g.width = r.width + + #for instanceD in dsp.instances: + # print(instanceD.styleName, dsp.splitLocation(instanceD.location)) + + + #dsp.makeInstance(instanceD) + + +dsp.generateUFO() \ No newline at end of file diff --git a/Tests/202206 discrete spaces/addGlyphTwo.py b/Tests/202206 discrete spaces/addGlyphTwo.py new file mode 100644 index 0000000..f4f6228 --- /dev/null +++ b/Tests/202206 discrete spaces/addGlyphTwo.py @@ -0,0 +1,9 @@ + +name = "glyphTwo" + +for f in AllFonts(): + + f.newGlyph(name) + f[name].appendComponent("glyphOne") + f[name].width = f['glyphOne'].width + f.save() \ No newline at end of file diff --git a/Tests/202206 discrete spaces/ds5_makeTestDoc.py b/Tests/202206 discrete spaces/ds5_makeTestDoc.py index 330ff87..332ef4d 100644 --- a/Tests/202206 discrete spaces/ds5_makeTestDoc.py +++ b/Tests/202206 discrete spaces/ds5_makeTestDoc.py @@ -38,8 +38,10 @@ a3.axisOrdering = 3 doc.addAxis(a3) +default = {a1.name: a1.default, a2.name: a2.default, a3.name: a3.default} # add sources + for c in [a1.minimum, a1.maximum]: for d1 in a2.values: for d2 in a3.values: @@ -48,8 +50,12 @@ s1.path = os.path.join("masters", f"geometryMaster_c_{c}_d1_{d1}_d2_{d2}.ufo") print(s1.path, os.path.exists(s1.path)) s1.name = f"geometryMaster{c} {d1} {d2}" - s1.location = dict(width=c, countedItems=d1, outlined=d2) + masterLocation = dict(width=c, countedItems=d1, outlined=d2) + s1.location = masterLocation + s1.kerning = True s1.familyName = "MasterFamilyName" + if default == masterLocation: + s1.copyGroups = True td1 = ["One", "Two", "Three"][(d1-1)] if c == 400: tc = "Narrow" @@ -89,6 +95,8 @@ def ip(a,b,f): td2 = "Open" s1.name = f"geometryInstance {td1} {tc} {td2}" s1.styleName = f"{td1}{tc}{td2}" + s1.kerning = True + s1.info = True doc.addInstance(s1) diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist index f941297..7acbc8e 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist @@ -45,6 +45,8 @@ postscriptStemSnapV + styleName + c_1000_d1_1_d2_0 unitsPerEm 1000 xHeight diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist index 229927d..4cb18bb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist @@ -4,5 +4,7 @@ glyphOne glyphO_ne.glif + glyphTwo + glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif new file mode 100644 index 0000000..c4fc582 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist index 46756bb..9dc8cd5 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist @@ -4,17 +4,15 @@ glyphOne - glyphFour - 10 glyphOne - -100 - glyphThree - 10 + 1000 + glyphTwo + 1000 - public.kern1.groupA + glyphTwo - public.kern2.groupB - -100 + glyphOne + 1000 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/lib.plist index 60019e8..ad24abb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/lib.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/lib.plist @@ -23,6 +23,7 @@ public.glyphOrder glyphOne + glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist index 9733123..30e4d46 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist @@ -45,6 +45,8 @@ postscriptStemSnapV + styleName + c_1000_d1_1_d2_1 unitsPerEm 1000 xHeight diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist index 229927d..4cb18bb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist @@ -4,5 +4,7 @@ glyphOne glyphO_ne.glif + glyphTwo + glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif new file mode 100644 index 0000000..c4fc582 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist index 46756bb..9dc8cd5 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist @@ -4,17 +4,15 @@ glyphOne - glyphFour - 10 glyphOne - -100 - glyphThree - 10 + 1000 + glyphTwo + 1000 - public.kern1.groupA + glyphTwo - public.kern2.groupB - -100 + glyphOne + 1000 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/lib.plist index 60019e8..ad24abb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/lib.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/lib.plist @@ -23,6 +23,7 @@ public.glyphOrder glyphOne + glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist index 325840e..15dac0b 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist @@ -45,6 +45,8 @@ postscriptStemSnapV + styleName + c_1000_d1_2_d2_0 unitsPerEm 1000 xHeight diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist index 229927d..4cb18bb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist @@ -4,5 +4,7 @@ glyphOne glyphO_ne.glif + glyphTwo + glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif new file mode 100644 index 0000000..c4fc582 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist index 46756bb..9dc8cd5 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist @@ -4,17 +4,15 @@ glyphOne - glyphFour - 10 glyphOne - -100 - glyphThree - 10 + 1000 + glyphTwo + 1000 - public.kern1.groupA + glyphTwo - public.kern2.groupB - -100 + glyphOne + 1000 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/lib.plist index 60019e8..ad24abb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/lib.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/lib.plist @@ -23,6 +23,7 @@ public.glyphOrder glyphOne + glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist index 218a997..d80c13f 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist @@ -45,6 +45,8 @@ postscriptStemSnapV + styleName + c_1000_d1_2_d2_1 unitsPerEm 1000 xHeight diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist index 229927d..4cb18bb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist @@ -4,5 +4,7 @@ glyphOne glyphO_ne.glif + glyphTwo + glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif new file mode 100644 index 0000000..c4fc582 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist index 46756bb..9dc8cd5 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist @@ -4,17 +4,15 @@ glyphOne - glyphFour - 10 glyphOne - -100 - glyphThree - 10 + 1000 + glyphTwo + 1000 - public.kern1.groupA + glyphTwo - public.kern2.groupB - -100 + glyphOne + 1000 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/lib.plist index 60019e8..ad24abb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/lib.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/lib.plist @@ -23,6 +23,7 @@ public.glyphOrder glyphOne + glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist index 8bef8d0..076b747 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist @@ -45,6 +45,8 @@ postscriptStemSnapV + styleName + c_1000_d1_3_d2_0 unitsPerEm 1000 xHeight diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist index 229927d..4cb18bb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist @@ -4,5 +4,7 @@ glyphOne glyphO_ne.glif + glyphTwo + glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif new file mode 100644 index 0000000..c4fc582 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist index 46756bb..9dc8cd5 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist @@ -4,17 +4,15 @@ glyphOne - glyphFour - 10 glyphOne - -100 - glyphThree - 10 + 1000 + glyphTwo + 1000 - public.kern1.groupA + glyphTwo - public.kern2.groupB - -100 + glyphOne + 1000 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/lib.plist index 60019e8..ad24abb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/lib.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/lib.plist @@ -23,6 +23,7 @@ public.glyphOrder glyphOne + glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist index 4544632..a8e4d59 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist @@ -45,6 +45,8 @@ postscriptStemSnapV + styleName + c_1000_d1_3_d2_1 unitsPerEm 1000 xHeight diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist index 229927d..4cb18bb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist @@ -4,5 +4,7 @@ glyphOne glyphO_ne.glif + glyphTwo + glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif new file mode 100644 index 0000000..c4fc582 --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist index 46756bb..9dc8cd5 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist @@ -4,17 +4,15 @@ glyphOne - glyphFour - 10 glyphOne - -100 - glyphThree - 10 + 1000 + glyphTwo + 1000 - public.kern1.groupA + glyphTwo - public.kern2.groupB - -100 + glyphOne + 1000 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/lib.plist index 60019e8..ad24abb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/lib.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/lib.plist @@ -23,6 +23,7 @@ public.glyphOrder glyphOne + glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist index dc0a084..6d990c5 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist @@ -45,6 +45,8 @@ postscriptStemSnapV + styleName + c_400_d1_1_d2_0 unitsPerEm 1000 xHeight diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/contents.plist index 229927d..4cb18bb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/contents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/contents.plist @@ -4,5 +4,7 @@ glyphOne glyphO_ne.glif + glyphTwo + glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif new file mode 100644 index 0000000..fd2803b --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist index 46756bb..a77d59e 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist @@ -4,17 +4,15 @@ glyphOne - glyphFour - 10 glyphOne - -100 - glyphThree - 10 + 400 + glyphTwo + 400 - public.kern1.groupA + glyphTwo - public.kern2.groupB - -100 + glyphOne + 400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/lib.plist index 60019e8..ad24abb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/lib.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/lib.plist @@ -23,6 +23,7 @@ public.glyphOrder glyphOne + glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist index c7b12d6..063eb83 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist @@ -45,6 +45,8 @@ postscriptStemSnapV + styleName + c_400_d1_1_d2_1 unitsPerEm 1000 xHeight diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/contents.plist index 229927d..4cb18bb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/contents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/contents.plist @@ -4,5 +4,7 @@ glyphOne glyphO_ne.glif + glyphTwo + glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif new file mode 100644 index 0000000..fd2803b --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist index 46756bb..a77d59e 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist @@ -4,17 +4,15 @@ glyphOne - glyphFour - 10 glyphOne - -100 - glyphThree - 10 + 400 + glyphTwo + 400 - public.kern1.groupA + glyphTwo - public.kern2.groupB - -100 + glyphOne + 400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/lib.plist index 60019e8..ad24abb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/lib.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/lib.plist @@ -23,6 +23,7 @@ public.glyphOrder glyphOne + glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist index 43298fa..66cc0a9 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist @@ -45,6 +45,8 @@ postscriptStemSnapV + styleName + c_400_d1_2_d2_0 unitsPerEm 1000 xHeight diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/contents.plist index 229927d..4cb18bb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/contents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/contents.plist @@ -4,5 +4,7 @@ glyphOne glyphO_ne.glif + glyphTwo + glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif new file mode 100644 index 0000000..fd2803b --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist index 46756bb..a77d59e 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist @@ -4,17 +4,15 @@ glyphOne - glyphFour - 10 glyphOne - -100 - glyphThree - 10 + 400 + glyphTwo + 400 - public.kern1.groupA + glyphTwo - public.kern2.groupB - -100 + glyphOne + 400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/lib.plist index 60019e8..ad24abb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/lib.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/lib.plist @@ -23,6 +23,7 @@ public.glyphOrder glyphOne + glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist index 82f1b17..bb03f26 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist @@ -45,6 +45,8 @@ postscriptStemSnapV + styleName + c_400_d1_2_d2_1 unitsPerEm 1000 xHeight diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/contents.plist index 229927d..4cb18bb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/contents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/contents.plist @@ -4,5 +4,7 @@ glyphOne glyphO_ne.glif + glyphTwo + glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif new file mode 100644 index 0000000..fd2803b --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist index 46756bb..a77d59e 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist @@ -4,17 +4,15 @@ glyphOne - glyphFour - 10 glyphOne - -100 - glyphThree - 10 + 400 + glyphTwo + 400 - public.kern1.groupA + glyphTwo - public.kern2.groupB - -100 + glyphOne + 400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/lib.plist index 60019e8..ad24abb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/lib.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/lib.plist @@ -23,6 +23,7 @@ public.glyphOrder glyphOne + glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist index 95c0a24..b2f6c8f 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist @@ -45,6 +45,8 @@ postscriptStemSnapV + styleName + c_400_d1_3_d2_0 unitsPerEm 1000 xHeight diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/contents.plist index 229927d..4cb18bb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/contents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/contents.plist @@ -4,5 +4,7 @@ glyphOne glyphO_ne.glif + glyphTwo + glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif new file mode 100644 index 0000000..fd2803b --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist index 46756bb..a77d59e 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist @@ -4,17 +4,15 @@ glyphOne - glyphFour - 10 glyphOne - -100 - glyphThree - 10 + 400 + glyphTwo + 400 - public.kern1.groupA + glyphTwo - public.kern2.groupB - -100 + glyphOne + 400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/lib.plist index 60019e8..ad24abb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/lib.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/lib.plist @@ -23,6 +23,7 @@ public.glyphOrder glyphOne + glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist index 1483423..9c44f52 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist @@ -45,6 +45,8 @@ postscriptStemSnapV + styleName + c_400_d1_3_d2_1 unitsPerEm 1000 xHeight diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/contents.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/contents.plist index 229927d..4cb18bb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/contents.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/contents.plist @@ -4,5 +4,7 @@ glyphOne glyphO_ne.glif + glyphTwo + glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif new file mode 100644 index 0000000..fd2803b --- /dev/null +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist index 46756bb..a77d59e 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist @@ -4,17 +4,15 @@ glyphOne - glyphFour - 10 glyphOne - -100 - glyphThree - 10 + 400 + glyphTwo + 400 - public.kern1.groupA + glyphTwo - public.kern2.groupB - -100 + glyphOne + 400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/lib.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/lib.plist index 60019e8..ad24abb 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/lib.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/lib.plist @@ -23,6 +23,7 @@ public.glyphOrder glyphOne + glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 From 75b667f973810973e14f06c31d27347596124364 Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Fri, 10 Jun 2022 16:43:27 +0200 Subject: [PATCH 05/26] WIP generates kerning generates info Needs serious clean up. --- .gitignore | 1 + Lib/ufoProcessor/__init__.py | 111 ++++++++++++++++++---------------- Lib/ufoProcessor/varModels.py | 4 +- 3 files changed, 61 insertions(+), 55 deletions(-) diff --git a/.gitignore b/.gitignore index 1ff6359..b775bca 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ test*.sp3 Tests/Instances Tests/_* Tests/20190830 benders/Skateboard Previews +/Tests/202206 discrete spaces/instances diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index fde253f..cf8e414 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -391,11 +391,11 @@ def _getAxisOrder(self): def getVariationModel(self, items, axes, bias=None): # Return either a mutatorMath or a varlib.model object for calculating. - print(f"\t## into getVariationModel bias {bias}") + #print(f"\t## into getVariationModel bias {bias}") try: if self.useVarlib: # use the varlib variation model - print("\t\t## into getVariationModel varlib") + #print("\t\t## into getVariationModel varlib") try: return dict(), VariationModelMutator(items, self.axes) except (KeyError, AssertionError): @@ -405,11 +405,11 @@ def getVariationModel(self, items, axes, bias=None): return {}, None else: # use mutatormath model - print("\t\t## into getVariationModel mutatormath") + #print("\t\t## into getVariationModel mutatormath") axesForMutator = self.getMutatorAxes() - print("\t\t## into getVariationModel axesForMutator", axesForMutator) - print("\t\t## into getVariationModel items", items) - print("\t\t## into getVariationModel bias", bias) + #print("\t\t## into getVariationModel axesForMutator", axesForMutator) + #print("\t\t## into getVariationModel items", items) + #print("\t\t## into getVariationModel bias", bias) return buildMutator(items, axes=axesForMutator, bias=bias) except: error = traceback.format_exc() @@ -417,15 +417,22 @@ def getVariationModel(self, items, axes, bias=None): self.toolLog.append("UFOProcessor.getVariationModel error: %s" % error) return {}, None - def getInfoMutator(self): + def getInfoMutator(self, discreteLocation=None): """ Returns a info mutator """ - if self._infoMutator: - return self._infoMutator + print(f"\t\t## @@ into getInfoMutator {discreteLocation}") + #if self._infoMutator: + # return self._infoMutator infoItems = [] - for sourceDescriptor in self.sources: + if discreteLocation is not None: + sources = self.findSourcesForDiscreteLocation(discreteLocation) + else: + sources = self.sources + for sourceDescriptor in sources: + print(f"\t\t##---> into getInfoMutator {sourceDescriptor.name}") if sourceDescriptor.layerName is not None: continue - loc = Location(sourceDescriptor.location) + continuous, discrete = self.splitLocation(sourceDescriptor.location) + loc = Location(continuous) sourceFont = self.fonts[sourceDescriptor.name] if sourceFont is None: continue @@ -433,7 +440,9 @@ def getInfoMutator(self): infoItems.append((loc, sourceFont.info.toMathInfo())) else: infoItems.append((loc, self.mathInfoClass(sourceFont.info))) - infoBias = self.newDefaultLocation(bend=True) + print(f"\t\t##---> getInfoMutator {loc}") + + infoBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) bias, self._infoMutator = self.getVariationModel(infoItems, axes=self.serializedAxes, bias=infoBias) return self._infoMutator @@ -445,10 +454,7 @@ def getKerningMutator(self, pairs=None, discreteLocation=None): #if self._kerningMutator and pairs == self._kerningMutatorPairs: # return self._kerningMutator #@@ - - - - print(f"\t\t## @@ into getKerningMutator {discreteLocation}") + #print(f"\t\t## @@ into getKerningMutator {discreteLocation}") if discreteLocation is not None: sources = self.findSourcesForDiscreteLocation(discreteLocation) else: @@ -457,7 +463,7 @@ def getKerningMutator(self, pairs=None, discreteLocation=None): foregroundLayers = [None, 'foreground', 'public.default'] if pairs is None: for sourceDescriptor in sources: - print(f"\t\t##---> into getKerningMutator {sourceDescriptor.name}") + #print(f"\t\t##---> into getKerningMutator {sourceDescriptor.name}") if sourceDescriptor.layerName not in foregroundLayers: continue if not sourceDescriptor.muteKerning: @@ -497,7 +503,7 @@ def getKerningMutator(self, pairs=None, discreteLocation=None): bias, thing = self.getVariationModel(kerningItems, axes=self.serializedAxes, bias=kerningBias) #xx bias, self._kerningMutator = self.getVariationModel(kerningItems, axes=self.serializedAxes, bias=kerningBias) - print('self._kerningMutator', self._kerningMutator) + #print('self._kerningMutator', self._kerningMutator) return self._kerningMutator def filterThisLocation(self, location, mutedAxes): @@ -531,8 +537,8 @@ def getGlyphMutator(self, glyphName, #if cacheKey in self._glyphMutators and fromCache: # return self._glyphMutators[cacheKey] items = self.collectMastersForGlyph(glyphName, decomposeComponents=decomposeComponents, discreteLocation=discreteLocation) - for item in items: - print('\t\t## getGlyphMutator items', item) + #for item in items: + # print('\t\t## getGlyphMutator items', item) new = [] for a, b, c in items: if hasattr(b, "toMathGlyph"): @@ -542,13 +548,13 @@ def getGlyphMutator(self, glyphName, else: new.append((a,self.mathGlyphClass(b))) thing = None - print('\t\t## getGlyphMutator new', new) + # print('\t\t## getGlyphMutator new', new) try: bias, thing = self.getVariationModel(new, axes=self.serializedAxes, bias=self.newDefaultLocation(bend=True, discreteLocation=discreteLocation)) #xx - print('\t\t## getGlyphMutator bias', bias) - print('\t\t## getGlyphMutator thing', thing) + #print('\t\t## getGlyphMutator bias', bias) + #print('\t\t## getGlyphMutator thing', thing) except TypeError: - print('problems') + #print('problems') self.toolLog.append("getGlyphMutator %s items: %s new: %s" % (glyphName, items, new)) self.problems.append("\tCan't make processor for glyph %s" % (glyphName)) #if thing is not None: @@ -567,13 +573,13 @@ def collectMastersForGlyph(self, glyphName, decomposeComponents=False, discreteL empties = [] foundEmpty = False # - print(f'collectMastersForGlyph discreteLocation: {discreteLocation}') + #print(f'collectMastersForGlyph discreteLocation: {discreteLocation}') if discreteLocation is not None: sources = self.findSourcesForDiscreteLocation(discreteLocation) else: sources = self.sources # - print(f'collectMastersForGlyph sources: {sources}') + #print(f'collectMastersForGlyph sources: {sources}') for sourceDescriptor in sources: if not os.path.exists(sourceDescriptor.path): #kthxbai @@ -751,8 +757,8 @@ def makeInstance(self, instanceDescriptor, bend=False): """ Generate a font object for this instance """ continuousLocation, discreteLocation = self.splitLocation(instanceDescriptor.location) - print(f"\t ## @@@ makeInstance continuousLocation {continuousLocation}") - print(f"\t ## makeInstance discreteLocation {discreteLocation}") + #print(f"\t ## @@@ makeInstance continuousLocation {continuousLocation}") + #print(f"\t ## makeInstance discreteLocation {discreteLocation}") font = self._instantiateFont(None) # make fonty things here loc = Location(continuousLocation) @@ -779,31 +785,30 @@ def makeInstance(self, instanceDescriptor, else: kerningMutator = self.getKerningMutator(discreteLocation=discreteLocation) if kerningMutator is not None: - print("-------------------- making the kerning!") kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend) kerningObject.extractKerning(font) # # make the info - # try: - # infoMutator = self.getInfoMutator() - # if infoMutator is not None: - # if not anisotropic: - # infoInstanceObject = infoMutator.makeInstance(loc, bend=bend) - # else: - # horizontalInfoInstanceObject = infoMutator.makeInstance(locHorizontal, bend=bend) - # verticalInfoInstanceObject = infoMutator.makeInstance(locVertical, bend=bend) - # # merge them again - # infoInstanceObject = (1,0)*horizontalInfoInstanceObject + (0,1)*verticalInfoInstanceObject - # if self.roundGeometry: - # try: - # infoInstanceObject = infoInstanceObject.round() - # except AttributeError: - # pass - # infoInstanceObject.extractInfo(font.info) - # font.info.familyName = instanceDescriptor.familyName - # font.info.styleName = instanceDescriptor.styleName - # font.info.postscriptFontName = instanceDescriptor.postScriptFontName # yikes, note the differences in capitalisation.. - # font.info.styleMapFamilyName = instanceDescriptor.styleMapFamilyName - # font.info.styleMapStyleName = instanceDescriptor.styleMapStyleName + try: + infoMutator = self.getInfoMutator(discreteLocation=discreteLocation) + if infoMutator is not None: + if not anisotropic: + infoInstanceObject = infoMutator.makeInstance(loc, bend=bend) + else: + horizontalInfoInstanceObject = infoMutator.makeInstance(locHorizontal, bend=bend) + verticalInfoInstanceObject = infoMutator.makeInstance(locVertical, bend=bend) + # merge them again + infoInstanceObject = (1,0)*horizontalInfoInstanceObject + (0,1)*verticalInfoInstanceObject + if self.roundGeometry: + try: + infoInstanceObject = infoInstanceObject.round() + except AttributeError: + pass + infoInstanceObject.extractInfo(font.info) + font.info.familyName = instanceDescriptor.familyName + font.info.styleName = instanceDescriptor.styleName + font.info.postscriptFontName = instanceDescriptor.postScriptFontName # yikes, note the differences in capitalisation.. + font.info.styleMapFamilyName = instanceDescriptor.styleMapFamilyName + font.info.styleMapStyleName = instanceDescriptor.styleMapStyleName # # NEED SOME HELP WITH THIS # # localised names need to go to the right openTypeNameRecords # # records = [] @@ -812,8 +817,8 @@ def makeInstance(self, instanceDescriptor, # # for languageCode, name in instanceDescriptor.localisedStyleMapFamilyName.items(): # # # Name ID 1 (font family name) is found at the generic styleMapFamily attribute. # # records.append((nameID, )) - # except: - # self.problems.append("Could not make fontinfo for %s. %s" % (loc, traceback.format_exc())) + except: + self.problems.append("Could not make fontinfo for %s. %s" % (loc, traceback.format_exc())) # for sourceDescriptor in self.sources: # if sourceDescriptor.copyInfo: # # this is the source @@ -1122,7 +1127,7 @@ def findSourcesForDiscreteLocation(self, discreteLocDict): # while we're testing -dsp = DesignSpaceProcessor(useVarlib=True) +dsp = DesignSpaceProcessor(useVarlib=False) print(f'useVarLib {dsp.useVarlib}') ds5Path = "../../Tests/202206 discrete spaces/test.ds5.designspace" print(f"Can we find the ds5 example at {ds5Path}? {os.path.exists(ds5Path)}") diff --git a/Lib/ufoProcessor/varModels.py b/Lib/ufoProcessor/varModels.py index 1433d5a..9209511 100644 --- a/Lib/ufoProcessor/varModels.py +++ b/Lib/ufoProcessor/varModels.py @@ -72,8 +72,8 @@ def __init__(self, items, axes, model=None): if model is None: dd = [self._normalize(a) for a,b in items] ee = self.axisOrder - print('VariationModelMutator:', dd) - print('VariationModelMutator:', ee) + #print('VariationModelMutator:', dd) + #print('VariationModelMutator:', ee) self.model = VariationModel(dd, axisOrder=ee) else: self.model = model From 7971bc8e1feabcb5a7b1c28957a58fcb55ef449f Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Wed, 20 Jul 2022 16:01:01 +0200 Subject: [PATCH 06/26] WIP --- Lib/ufoProcessor/__init__.py | 117 +++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 55 deletions(-) diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index cf8e414..315072a 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -29,10 +29,10 @@ # back to these when we're running as a package -#from ufoProcessor.varModels import VariationModelMutator -#from ufoProcessor.emptyPen import checkGlyphIsEmpty -from varModels import VariationModelMutator -from emptyPen import checkGlyphIsEmpty +from ufoProcessor.varModels import VariationModelMutator +from ufoProcessor.emptyPen import checkGlyphIsEmpty +#from varModels import VariationModelMutator +#from emptyPen import checkGlyphIsEmpty try: @@ -318,6 +318,12 @@ def __init__(self, readerClass=None, writerClass=None, fontClass=None, ufoVersio self.problems = [] # receptacle for problem notifications. Not big enough to break, but also not small enough to ignore. self.toolLog = [] + def hasDiscreteAxes(self): + # return True if this designspace has > 0 discrete axes + for axis in self.getOrderedDiscreteAxes(): + return True + return False + def generateUFO(self, processRules=True, glyphNames=None, pairs=None, bend=False): # makes the instances # option to execute the rules @@ -1125,55 +1131,56 @@ def findSourcesForDiscreteLocation(self, discreteLocDict): sources.append(s) return sources - -# while we're testing -dsp = DesignSpaceProcessor(useVarlib=False) -print(f'useVarLib {dsp.useVarlib}') -ds5Path = "../../Tests/202206 discrete spaces/test.ds5.designspace" -print(f"Can we find the ds5 example at {ds5Path}? {os.path.exists(ds5Path)}") - -dsp.read(ds5Path) -dsp.loadFonts() -print('all the axes\n', dsp.axes) -print(f'\nSo we will have {len(dsp.getDiscreteLocations())} continuous designspaces in this document') - -testGlyphName = "glyphOne" -makeFonts = False - if False: - for loc in dsp.getDiscreteLocations(): - print() - print("#"*60) - print(f'For this discrete location {loc} we have these masters available:') - #print(dsp.findSourcesForDiscreteLocation(loc)) - - mastersForGlyph = dsp.collectMastersForGlyph(testGlyphName, discreteLocation=loc) - print('mastersForGlyph') - for item in mastersForGlyph: - for i in item: - print('\t\t', i) - - # note for later: look into newDefaultLocation - mut = dsp.getGlyphMutator(testGlyphName, discreteLocation=loc) - print(f'mutator for {testGlyphName} at {loc}', mut) - print(f'newDefaultLocation {dsp.newDefaultLocation(discreteLocation=loc)}') - - if makeFonts: - f = NewFont() - extrapol = 200 - for v in range(400-extrapol, 1000+extrapol, 100): - n = f"result_glyph{v}" - f.newGlyph(n) - g = f[n] - r = mut.makeInstance({'width':v}) - g.fromMathGlyph(r) - g.width = r.width - - #for instanceD in dsp.instances: - # print(instanceD.styleName, dsp.splitLocation(instanceD.location)) - - - #dsp.makeInstance(instanceD) - - -dsp.generateUFO() \ No newline at end of file + # while we're testing + dsp = DesignSpaceProcessor(useVarlib=False) + print(f'useVarLib {dsp.useVarlib}') + ds5Path = "../../Tests/202206 discrete spaces/test.ds5.designspace" + print(f"Can we find the ds5 example at {ds5Path}? {os.path.exists(ds5Path)}") + + dsp.read(ds5Path) + dsp.loadFonts() + print(f"has discrete axes: {dsp.hasDiscreteAxes()}") + print('all the axes\n', dsp.axes) + print(f'\nSo we will have {len(dsp.getDiscreteLocations())} continuous designspaces in this document') + + testGlyphName = "glyphOne" + makeFonts = False + + if False: + for loc in dsp.getDiscreteLocations(): + print() + print("#"*60) + print(f'For this discrete location {loc} we have these masters available:') + #print(dsp.findSourcesForDiscreteLocation(loc)) + + mastersForGlyph = dsp.collectMastersForGlyph(testGlyphName, discreteLocation=loc) + print('mastersForGlyph') + for item in mastersForGlyph: + for i in item: + print('\t\t', i) + + # note for later: look into newDefaultLocation + mut = dsp.getGlyphMutator(testGlyphName, discreteLocation=loc) + print(f'mutator for {testGlyphName} at {loc}', mut) + print(f'newDefaultLocation {dsp.newDefaultLocation(discreteLocation=loc)}') + + if makeFonts: + f = NewFont() + extrapol = 200 + for v in range(400-extrapol, 1000+extrapol, 100): + n = f"result_glyph{v}" + f.newGlyph(n) + g = f[n] + r = mut.makeInstance({'width':v}) + g.fromMathGlyph(r) + g.width = r.width + + #for instanceD in dsp.instances: + # print(instanceD.styleName, dsp.splitLocation(instanceD.location)) + + + #dsp.makeInstance(instanceD) + + + dsp.generateUFO() \ No newline at end of file From e70382c936b16c1e1872f0c72a02ddeac932c70c Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Tue, 11 Oct 2022 20:58:43 +0200 Subject: [PATCH 07/26] WIP Lots of work on designspaceObject work with discrete axes. Cleaning up. Tests need to be run from this __init__. Lots of printing as well, sorry. Script that makes test designspace: 1 interpolating + 2 discrete axes. Hand tuned masters. Some notes in the readme about the space and the tests Found an unexpected extrapolation feature in varlib. Back to mutatorMath. --- Lib/ufoProcessor/__init__.py | 344 +++++------------- Lib/ufoProcessor/varModels.py | 10 +- .../202206 discrete spaces/ds5_makeTestDoc.py | 31 +- .../202206 discrete spaces/extrapolateTest.py | 27 ++ Tests/202206 discrete spaces/masters.jpg | Bin 0 -> 77822 bytes .../kerning.plist | 8 +- .../kerning.plist | 8 +- .../kerning.plist | 8 +- .../kerning.plist | 8 +- .../kerning.plist | 8 +- .../kerning.plist | 8 +- .../kerning.plist | 6 +- .../kerning.plist | 6 +- .../kerning.plist | 6 +- .../kerning.plist | 6 +- .../kerning.plist | 6 +- .../kerning.plist | 6 +- Tests/202206 discrete spaces/readme.md | 13 + 18 files changed, 208 insertions(+), 301 deletions(-) create mode 100644 Tests/202206 discrete spaces/extrapolateTest.py create mode 100644 Tests/202206 discrete spaces/masters.jpg create mode 100644 Tests/202206 discrete spaces/readme.md diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index 315072a..8f7a8bb 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -2,11 +2,12 @@ from __future__ import print_function, division, absolute_import + +from warnings import warn import os import logging, traceback import collections import itertools -# from pprint import pprint from fontTools.designspaceLib import DesignSpaceDocument, SourceDescriptor, InstanceDescriptor, AxisDescriptor, RuleDescriptor, processRules from fontTools.misc import plistlib @@ -27,12 +28,10 @@ from mutatorMath.objects.mutator import buildMutator from mutatorMath.objects.location import Location - # back to these when we're running as a package +import ufoProcessor.varModels from ufoProcessor.varModels import VariationModelMutator from ufoProcessor.emptyPen import checkGlyphIsEmpty -#from varModels import VariationModelMutator -#from emptyPen import checkGlyphIsEmpty try: @@ -124,7 +123,7 @@ def build( document.roundGeometry = roundGeometry document.read(path) try: - r = document.generateUFO(processRules=processRules) + r = document.generateUFO() results.append(r) except: if logger: @@ -151,110 +150,6 @@ def getUFOVersion(ufoPath): return p.get('formatVersion') -# def swapGlyphNames(font, oldName, newName, swapNameExtension = "_______________swap"): -# # In font swap the glyphs oldName and newName. -# # Also swap the names in components in order to preserve appearance. -# # Also swap the names in font groups. -# if not oldName in font or not newName in font: -# return None -# swapName = oldName + swapNameExtension -# # 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() -# font[oldName].drawPoints(p) -# font[swapName].width = font[oldName].width -# # lib? -# font[oldName].clear() -# 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: -# for c in g.components: -# if c.baseGlyph == oldName: -# c.baseGlyph = swapName -# continue -# for g in font: -# for c in g.components: -# if c.baseGlyph == newName: -# c.baseGlyph = oldName -# continue -# for g in font: -# for c in g.components: -# if c.baseGlyph == swapName: -# c.baseGlyph = newName - -# # change the names in groups -# # the shapes will swap, that will invalidate the kerning -# # so the names need to swap in the kerning as well. -# newKerning = {} -# for first, second in font.kerning.keys(): -# value = font.kerning[(first,second)] -# if first == oldName: -# first = newName -# elif first == newName: -# first = oldName -# if second == oldName: -# second = newName -# elif second == newName: -# second = oldName -# newKerning[(first, second)] = value -# font.kerning.clear() -# font.kerning.update(newKerning) - -# for groupName, members in font.groups.items(): -# newMembers = [] -# for name in members: -# if name == oldName: -# newMembers.append(newName) -# elif name == newName: -# newMembers.append(oldName) -# else: -# newMembers.append(name) -# font.groups[groupName] = newMembers - -# remove = [] -# for g in font: -# if g.name.find(swapNameExtension)!=-1: -# remove.append(g.name) -# for r in remove: -# del font[r] - - class DecomposePointPen(object): def __init__(self, glyphSet, outPointPen): @@ -397,13 +292,21 @@ def _getAxisOrder(self): def getVariationModel(self, items, axes, bias=None): # Return either a mutatorMath or a varlib.model object for calculating. - #print(f"\t## into getVariationModel bias {bias}") + print(f"getVariationModel items {items}") + print(f"getVariationModel bias {bias}") try: if self.useVarlib: # use the varlib variation model - #print("\t\t## into getVariationModel varlib") + print("\t\t## into getVariationModel varlib") try: - return dict(), VariationModelMutator(items, self.axes) + return dict(), VariationModelMutator(items, axes=self.axes, extrapolate=True) + except TypeError: + print(f"@@ typeError in getVariationModel {ufoProcessor.varModels.__file__}") + import fontTools.varLib.models + print(f"@@ {fontTools.varLib.models.__file__}") + error = traceback.format_exc() + print(error) + return {}, None except (KeyError, AssertionError): error = traceback.format_exc() self.toolLog.append("UFOProcessor.getVariationModel error: %s" % error) @@ -449,6 +352,7 @@ def getInfoMutator(self, discreteLocation=None): print(f"\t\t##---> getInfoMutator {loc}") infoBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) + print("@@5") bias, self._infoMutator = self.getVariationModel(infoItems, axes=self.serializedAxes, bias=infoBias) return self._infoMutator @@ -460,19 +364,25 @@ def getKerningMutator(self, pairs=None, discreteLocation=None): #if self._kerningMutator and pairs == self._kerningMutatorPairs: # return self._kerningMutator #@@ - #print(f"\t\t## @@ into getKerningMutator {discreteLocation}") + print(f"\t\t## @@ into getKerningMutator {discreteLocation}") if discreteLocation is not None: sources = self.findSourcesForDiscreteLocation(discreteLocation) + print(f"\t\t## @@ into getKerningMutator sources 1 {sources}") else: sources = self.sources + print(f"\t\t## @@ into getKerningMutator sources 2 {sources}") kerningItems = [] foregroundLayers = [None, 'foreground', 'public.default'] if pairs is None: + print("\t\t\t\t\t ** 1") for sourceDescriptor in sources: - #print(f"\t\t##---> into getKerningMutator {sourceDescriptor.name}") + print("\t\t\t\t\t ** 2") + print(f"\t\t##---> into getKerningMutator {sourceDescriptor.name}") if sourceDescriptor.layerName not in foregroundLayers: + print("\t\t\t\t\t ** 3") continue if not sourceDescriptor.muteKerning: + print("\t\t\t\t\t ** 4") # filter this XX @@ continuous, discrete = self.splitLocation(sourceDescriptor.location) loc = Location(continuous) @@ -480,6 +390,8 @@ def getKerningMutator(self, pairs=None, discreteLocation=None): if sourceFont is None: continue # this makes assumptions about the groups of all sources being the same. kerningItems.append((loc, self.mathKerningClass(sourceFont.kerning, sourceFont.groups))) + print("\t\t\t\t\t ** 5") + print(f"\t\t##---> into kerningItems {kerningItems}") else: self._kerningMutatorPairs = pairs for sourceDescriptor in sources: @@ -503,11 +415,10 @@ def getKerningMutator(self, pairs=None, discreteLocation=None): if v is not None: sparseKerning[pair] = v kerningItems.append((loc, self.mathKerningClass(sparseKerning))) - #kerningBias = self.newDefaultLocation(bend=True) - kerningBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) + print("@@6") + print(f"\t\t## getKerningMutator {kerningItems}") bias, thing = self.getVariationModel(kerningItems, axes=self.serializedAxes, bias=kerningBias) #xx - bias, self._kerningMutator = self.getVariationModel(kerningItems, axes=self.serializedAxes, bias=kerningBias) #print('self._kerningMutator', self._kerningMutator) return self._kerningMutator @@ -555,14 +466,20 @@ def getGlyphMutator(self, glyphName, new.append((a,self.mathGlyphClass(b))) thing = None # print('\t\t## getGlyphMutator new', new) - try: - bias, thing = self.getVariationModel(new, axes=self.serializedAxes, bias=self.newDefaultLocation(bend=True, discreteLocation=discreteLocation)) #xx + #try: + + print(f'@@4 {glyphName}') + print(f'@@4 {glyphName} new', new) + #print('@@4 serializedAxes', serializedAxes) + thisBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) + print('@@4 thisBias', thisBias) + bias, thing = self.getVariationModel(new, axes=self.serializedAxes, bias=thisBias) #xx #print('\t\t## getGlyphMutator bias', bias) #print('\t\t## getGlyphMutator thing', thing) - except TypeError: - #print('problems') - self.toolLog.append("getGlyphMutator %s items: %s new: %s" % (glyphName, items, new)) - self.problems.append("\tCan't make processor for glyph %s" % (glyphName)) + #except TypeError: + # #print('problems') + # self.toolLog.append("getGlyphMutator %s items: %s new: %s" % (glyphName, items, new)) + # self.problems.append("\tCan't make processor for glyph %s" % (glyphName)) #if thing is not None: # self._glyphMutators[cacheKey] = thing return thing @@ -757,14 +674,16 @@ def getFonts(self): return fonts def makeInstance(self, instanceDescriptor, - doRules=False, + doRules=None, glyphNames=None, pairs=None, bend=False): """ Generate a font object for this instance """ + if doRules is not None: + warn('The doRules argument is deprecated', DeprecationWarning, stacklevel=2) continuousLocation, discreteLocation = self.splitLocation(instanceDescriptor.location) - #print(f"\t ## @@@ makeInstance continuousLocation {continuousLocation}") - #print(f"\t ## makeInstance discreteLocation {discreteLocation}") + print(f"\t ## @@@ makeInstance continuousLocation {continuousLocation}") + print(f"\t ## makeInstance discreteLocation {discreteLocation}") font = self._instantiateFont(None) # make fonty things here loc = Location(continuousLocation) @@ -773,19 +692,15 @@ def makeInstance(self, instanceDescriptor, if self.isAnisotropic(loc): anisotropic = True locHorizontal, locVertical = self.splitAnisotropic(loc) - # groups - #renameMap = getattr(self.fonts[self.default.name], "kerningGroupConversionRenameMaps", None) - #font.kerningGroupConversionRenameMaps = renameMap if renameMap is not None else {'side1': {}, 'side2': {}} - # make the kerning - # this kerning is always horizontal. We can take the horizontal location - # filter the available pairs? - # @@ + print("anisotropic", anisotropic, locHorizontal, locVertical) + print("instanceDescriptor.kerning", instanceDescriptor.kerning) if instanceDescriptor.kerning: if pairs: try: kerningMutator = self.getKerningMutator(pairs=pairs, discreteLocation=discreteLocation) kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend) kerningObject.extractKerning(font) + print('\t\t\tinstance kerning @@2b: ', font.kerning.items()) except: self.problems.append("Could not make kerning for %s. %s" % (loc, traceback.format_exc())) else: @@ -793,6 +708,7 @@ def makeInstance(self, instanceDescriptor, if kerningMutator is not None: kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend) kerningObject.extractKerning(font) + print('\t\t\tinstance kerning @@2a: ', font.kerning.items()) # # make the info try: infoMutator = self.getInfoMutator(discreteLocation=discreteLocation) @@ -853,6 +769,7 @@ def makeInstance(self, instanceDescriptor, selectedGlyphNames = self.glyphNames # add the glyphnames to the font.lib['public.glyphOrder'] if not 'public.glyphOrder' in font.lib.keys(): + # should be the glyphorder from the default, yes? font.lib['public.glyphOrder'] = selectedGlyphNames for glyphName in selectedGlyphNames: try: @@ -863,89 +780,35 @@ def makeInstance(self, instanceDescriptor, except: self.problems.append("Could not make mutator for glyph %s %s" % (glyphName, traceback.format_exc())) continue - # if glyphName in instanceDescriptor.glyphs.keys(): - # # XXX this should be able to go now that we have full rule support. - # # reminder: this is what the glyphData can look like - # # {'instanceLocation': {'custom': 0.0, 'weight': 824.0}, - # # 'masters': [{'font': 'master.Adobe VF Prototype.Master_0.0', - # # 'glyphName': 'dollar.nostroke', - # # 'location': {'custom': 0.0, 'weight': 0.0}}, - # # {'font': 'master.Adobe VF Prototype.Master_1.1', - # # 'glyphName': 'dollar.nostroke', - # # 'location': {'custom': 0.0, 'weight': 368.0}}, - # # {'font': 'master.Adobe VF Prototype.Master_2.2', - # # 'glyphName': 'dollar.nostroke', - # # 'location': {'custom': 0.0, 'weight': 1000.0}}, - # # {'font': 'master.Adobe VF Prototype.Master_3.3', - # # 'glyphName': 'dollar.nostroke', - # # 'location': {'custom': 100.0, 'weight': 1000.0}}, - # # {'font': 'master.Adobe VF Prototype.Master_0.4', - # # 'glyphName': 'dollar.nostroke', - # # 'location': {'custom': 100.0, 'weight': 0.0}}, - # # {'font': 'master.Adobe VF Prototype.Master_4.5', - # # 'glyphName': 'dollar.nostroke', - # # 'location': {'custom': 100.0, 'weight': 368.0}}], - # # 'unicodes': [36]} - # glyphData = instanceDescriptor.glyphs[glyphName] - # else: glyphData = {} font.newGlyph(glyphName) font[glyphName].clear() - if glyphData.get('mute', False): - # mute this glyph, skip - continue - glyphInstanceLocation = glyphData.get("instanceLocation", instanceDescriptor.location) - glyphInstanceLocation = Location(glyphInstanceLocation) - uniValues = [] - neutral = glyphMutator.get(()) - if neutral is not None: - uniValues = neutral[0].unicodes - else: - neutralFont = self.getNeutralFont() - if glyphName in neutralFont: - uniValues = neutralFont[glyphName].unicodes - glyphInstanceUnicodes = glyphData.get("unicodes", uniValues) - note = glyphData.get("note") - if note: - font[glyphName] = note - # XXXX phase out support for instance-specific masters - # this should be handled by the rules system. - # masters = glyphData.get("masters", None) - # if masters is not None: - # items = [] - # for glyphMaster in masters: - # sourceGlyphFont = glyphMaster.get("font") - # sourceGlyphName = glyphMaster.get("glyphName", glyphName) - # m = self.fonts.get(sourceGlyphFont) - # if not sourceGlyphName in m: - # continue - # if hasattr(m[sourceGlyphName], "toMathGlyph"): - # sourceGlyph = m[sourceGlyphName].toMathGlyph() - # else: - # sourceGlyph = MathGlyph(m[sourceGlyphName]) - # sourceGlyphLocation = glyphMaster.get("location") - # items.append((Location(sourceGlyphLocation), sourceGlyph)) - # bias, glyphMutator = self.getVariationModel(items, axes=self.serializedAxes, bias=self.newDefaultLocation(bend=True)) + glyphInstanceUnicodes = [] + neutralFont = self.getNeutralFont() + # get the unicodes from the default + if glyphName in neutralFont: + glyphInstanceUnicodes = neutralFont[glyphName].unicodes try: - if not self.isAnisotropic(glyphInstanceLocation): - glyphInstanceObject = glyphMutator.makeInstance(glyphInstanceLocation, bend=bend) + if not self.isAnisotropic(continuousLocation): + glyphInstanceObject = glyphMutator.makeInstance(continuousLocation, bend=bend) else: # split anisotropic location into horizontal and vertical components - horizontal, vertical = self.splitAnisotropic(glyphInstanceLocation) - horizontalGlyphInstanceObject = glyphMutator.makeInstance(horizontal, bend=bend) - verticalGlyphInstanceObject = glyphMutator.makeInstance(vertical, bend=bend) + print(f"\t\t\tmaking anisotropic glyph at {locHorizontal} {locVertical}") + #horizontal, vertical = self.splitAnisotropic(continuousLocation) + horizontalGlyphInstanceObject = glyphMutator.makeInstance(locHorizontal, bend=bend) + verticalGlyphInstanceObject = glyphMutator.makeInstance(locVertical, bend=bend) # merge them again glyphInstanceObject = (1,0)*horizontalGlyphInstanceObject + (0,1)*verticalGlyphInstanceObject except IndexError: # alignment problem with the data? self.problems.append("Quite possibly some sort of data alignment error in %s" % glyphName) continue - font.newGlyph(glyphName) - font[glyphName].clear() if self.roundGeometry: try: glyphInstanceObject = glyphInstanceObject.round() except AttributeError: + # what are we catching here? + print(f"no round method for {glyphInstanceObject} ?") pass try: # File "/Users/erik/code/ufoProcessor/Lib/ufoProcessor/__init__.py", line 649, in makeInstance @@ -967,23 +830,13 @@ def makeInstance(self, instanceDescriptor, glyphInstanceObject.drawPoints(pPen) font[glyphName].width = glyphInstanceObject.width font[glyphName].unicodes = glyphInstanceUnicodes - # if doRules: - # resultNames = processRules(self.rules, loc, self.glyphNames) - # for oldName, newName in zip(self.glyphNames, resultNames): - # if oldName != newName: - # swapGlyphNames(font, oldName, newName) - # copy the glyph lib? - #for sourceDescriptor in self.sources: - # if sourceDescriptor.copyLib: - # pass - # pass - # store designspace location in the font.lib font.lib['designspace.location'] = list(instanceDescriptor.location.items()) + return font def isAnisotropic(self, location): for v in location.values(): - if type(v)==tuple: + if isinstance(v, (list, tuple)): return True return False @@ -1079,11 +932,6 @@ def getOrderedDiscreteAxes(self): axes.append(axisObj) return axes - #def removeDiscreteValuesFromLocation(self, location, discreteLocation=None): - # if discreteLocation: - # return {k:v for k, v in location.items() if k not in discreteLocation} - # return location - def splitLocation(self, location): # split a location in a continouous and a discrete part discreteAxes = [a.name for a in self.getOrderedDiscreteAxes()] @@ -1131,9 +979,17 @@ def findSourcesForDiscreteLocation(self, discreteLocDict): sources.append(s) return sources -if False: +if __name__ == "__main__": # while we're testing - dsp = DesignSpaceProcessor(useVarlib=False) + + import ufoProcessor + print(ufoProcessor.__file__) + import ufoProcessor.varModels + print(ufoProcessor.varModels.__file__) + + useVarlibPref = False + + dsp = DesignSpaceProcessor(useVarlib=useVarlibPref) print(f'useVarLib {dsp.useVarlib}') ds5Path = "../../Tests/202206 discrete spaces/test.ds5.designspace" print(f"Can we find the ds5 example at {ds5Path}? {os.path.exists(ds5Path)}") @@ -1141,46 +997,14 @@ def findSourcesForDiscreteLocation(self, discreteLocDict): dsp.read(ds5Path) dsp.loadFonts() print(f"has discrete axes: {dsp.hasDiscreteAxes()}") - print('all the axes\n', dsp.axes) + print('These are the axes\n', dsp.axes) print(f'\nSo we will have {len(dsp.getDiscreteLocations())} continuous designspaces in this document') - testGlyphName = "glyphOne" - makeFonts = False - - if False: - for loc in dsp.getDiscreteLocations(): - print() - print("#"*60) - print(f'For this discrete location {loc} we have these masters available:') - #print(dsp.findSourcesForDiscreteLocation(loc)) - - mastersForGlyph = dsp.collectMastersForGlyph(testGlyphName, discreteLocation=loc) - print('mastersForGlyph') - for item in mastersForGlyph: - for i in item: - print('\t\t', i) - - # note for later: look into newDefaultLocation - mut = dsp.getGlyphMutator(testGlyphName, discreteLocation=loc) - print(f'mutator for {testGlyphName} at {loc}', mut) - print(f'newDefaultLocation {dsp.newDefaultLocation(discreteLocation=loc)}') - - if makeFonts: - f = NewFont() - extrapol = 200 - for v in range(400-extrapol, 1000+extrapol, 100): - n = f"result_glyph{v}" - f.newGlyph(n) - g = f[n] - r = mut.makeInstance({'width':v}) - g.fromMathGlyph(r) - g.width = r.width - - #for instanceD in dsp.instances: - # print(instanceD.styleName, dsp.splitLocation(instanceD.location)) - - - #dsp.makeInstance(instanceD) - - - dsp.generateUFO() \ No newline at end of file + dsp.generateUFO() + + #for discreteLocation in dsp.getDiscreteLocations(): + # print(f"\titerating through discreteLocations: {discreteLocation}") + # aGlyphs = dsp.collectMastersForGlyph("glyphOne", discreteLocation=discreteLocation) + # for a in aGlyphs: + # print(f"\t\t{a}") + \ No newline at end of file diff --git a/Lib/ufoProcessor/varModels.py b/Lib/ufoProcessor/varModels.py index 9209511..33d1620 100644 --- a/Lib/ufoProcessor/varModels.py +++ b/Lib/ufoProcessor/varModels.py @@ -57,10 +57,11 @@ class VariationModelMutator(object): but uses the fonttools varlib logic to calculate. """ - def __init__(self, items, axes, model=None): + def __init__(self, items, axes, model=None, extrapolate=True): # items: list of locationdict, value tuples # axes: list of axis dictionaried, not axisdescriptor objects. # model: a model, if we want to share one + self.extrapolate = extrapolate self.axisOrder = [a.name for a in axes] self.axisMapper = AxisMapper(axes) self.axes = {} @@ -74,7 +75,7 @@ def __init__(self, items, axes, model=None): ee = self.axisOrder #print('VariationModelMutator:', dd) #print('VariationModelMutator:', ee) - self.model = VariationModel(dd, axisOrder=ee) + self.model = VariationModel(dd, axisOrder=ee, extrapolate=self.extrapolate) else: self.model = model self.masters = [b for a, b in items] @@ -87,7 +88,6 @@ def getAxisMinMax(self, axis): return min(axis.values), max(axis.values) return axis.minimum, axis.maximum - def get(self, key): if key in self.model.locations: i = self.model.locations.index(key) @@ -113,10 +113,10 @@ def getReach(self): items.append((self.masters[sortedOrder], s)) return items - def makeInstance(self, location, bend=False): # check for anisotropic locations here #print("\t1", location) + print(f"------ makeInstance is mapping: {location} -> mapped -> {self.axisMapper(location)}") if bend: location = self.axisMapper(location) #print("\t2", location) @@ -216,7 +216,7 @@ def _normalize(self, location): assert mm.makeInstance(dict(Weight=0, Width=10)) == 13 - l = dict(Weight=400, Width=200) + l = dict(Weight=400, Width=20) lmapped = aam(l) print('0 loc', l) print('0 loc mapped', lmapped) diff --git a/Tests/202206 discrete spaces/ds5_makeTestDoc.py b/Tests/202206 discrete spaces/ds5_makeTestDoc.py index 332ef4d..69c2a4c 100644 --- a/Tests/202206 discrete spaces/ds5_makeTestDoc.py +++ b/Tests/202206 discrete spaces/ds5_makeTestDoc.py @@ -10,6 +10,9 @@ import fontTools print(fontTools.version) +import ufoProcessor +print(ufoProcessor.__file__) + doc = DesignSpaceDocument() #https://fonttools.readthedocs.io/en/latest/designspaceLib/python.html#axisdescriptor @@ -72,16 +75,27 @@ def ip(a,b,f): return a + f*(b-a) # add instances -steps = 4 -for s in range(steps+1): - factor = s / steps - c = int(ip(a1.minimum, a1.maximum, factor)) +steps = 8 +extrapolateAmount = 100 + + +interestingWeightValues = [(200, 1200), 300, 400, 700, 1000, 1100] + +mathModelPrefKey = "com.letterror.mathModelPref" +mathModelVarlibPref = "previewVarLib" +mathModelMutatorMathPref = "previewMutatorMath" + +# com.letterror.mathModelPref +# previewVarLib + + +for c in interestingWeightValues: for d1 in a2.values: for d2 in a3.values: s1 = InstanceDescriptor() s1.path = os.path.join("instances", f"geometryInstance_c_{c}_d1_{d1}_d2_{d2}.ufo") - print(s1.path, os.path.exists(s1.path)) + #print(s1.path, os.path.exists(s1.path)) s1.location = dict(width=c, countedItems=d1, outlined=d2) s1.familyName = "InstanceFamilyName" td1 = ["One", "Two", "Three"][(d1-1)] @@ -99,8 +113,8 @@ def ip(a,b,f): s1.info = True doc.addInstance(s1) - path = "test.ds5.designspace" +print(doc.lib) doc.write(path) @@ -112,3 +126,8 @@ def ip(a,b,f): for s in doc.sources: print(s.location) + +# ok. now about generating the instances. + +udoc = ufoProcessor.DesignSpaceProcessor() +udoc.read(path) diff --git a/Tests/202206 discrete spaces/extrapolateTest.py b/Tests/202206 discrete spaces/extrapolateTest.py new file mode 100644 index 0000000..4149561 --- /dev/null +++ b/Tests/202206 discrete spaces/extrapolateTest.py @@ -0,0 +1,27 @@ +from fontTools.varLib.models import VariationModel + +locations = [ + dict(wgth=0), + dict(wght=1000), +] + +values = [10, 20] + +m = VariationModel(locations, extrapolate=True) + +# interpolating +assert m.interpolateFromMasters(dict(wght=0), values) == 10 +assert m.interpolateFromMasters(dict(wght=500), values) == 15 +assert m.interpolateFromMasters(dict(wght=1000), values) == 20 + +# extrapolate over max +assert m.interpolateFromMasters(dict(wght=1500), values) == 25 +assert m.interpolateFromMasters(dict(wght=2000), values) == 30 + +# extrapolation over min gets stuck +print(m.interpolateFromMasters(dict(wght=-500), values), m.interpolateFromMasters(dict(wght=-1000), values)) + +# would expect: +assert m.interpolateFromMasters(dict(wght=-500), values) == -5 +assert m.interpolateFromMasters(dict(wght=-1000), values) == -10 + diff --git a/Tests/202206 discrete spaces/masters.jpg b/Tests/202206 discrete spaces/masters.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5b9857831e8f496bbed436373c510da63c4baf62 GIT binary patch literal 77822 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 literal 0 HcmV?d00001 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist index 9dc8cd5..b54ab73 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist @@ -5,14 +5,16 @@ glyphOne glyphOne - 1000 + 400 glyphTwo - 1000 + 800 glyphTwo glyphOne - 1000 + -800 + glyphTwo + -400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist index 9dc8cd5..b54ab73 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist @@ -5,14 +5,16 @@ glyphOne glyphOne - 1000 + 400 glyphTwo - 1000 + 800 glyphTwo glyphOne - 1000 + -800 + glyphTwo + -400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist index 9dc8cd5..b54ab73 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist @@ -5,14 +5,16 @@ glyphOne glyphOne - 1000 + 400 glyphTwo - 1000 + 800 glyphTwo glyphOne - 1000 + -800 + glyphTwo + -400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist index 9dc8cd5..b54ab73 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist @@ -5,14 +5,16 @@ glyphOne glyphOne - 1000 + 400 glyphTwo - 1000 + 800 glyphTwo glyphOne - 1000 + -800 + glyphTwo + -400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist index 9dc8cd5..b54ab73 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist @@ -5,14 +5,16 @@ glyphOne glyphOne - 1000 + 400 glyphTwo - 1000 + 800 glyphTwo glyphOne - 1000 + -800 + glyphTwo + -400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist index 9dc8cd5..b54ab73 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist @@ -5,14 +5,16 @@ glyphOne glyphOne - 1000 + 400 glyphTwo - 1000 + 800 glyphTwo glyphOne - 1000 + -800 + glyphTwo + -400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist index a77d59e..ff4ce7d 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist @@ -7,12 +7,14 @@ glyphOne 400 glyphTwo - 400 + 100 glyphTwo glyphOne - 400 + -100 + glyphTwo + -400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist index a77d59e..ff4ce7d 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist @@ -7,12 +7,14 @@ glyphOne 400 glyphTwo - 400 + 100 glyphTwo glyphOne - 400 + -100 + glyphTwo + -400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist index a77d59e..ff4ce7d 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist @@ -7,12 +7,14 @@ glyphOne 400 glyphTwo - 400 + 100 glyphTwo glyphOne - 400 + -100 + glyphTwo + -400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist index a77d59e..ff4ce7d 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist @@ -7,12 +7,14 @@ glyphOne 400 glyphTwo - 400 + 100 glyphTwo glyphOne - 400 + -100 + glyphTwo + -400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist index a77d59e..ff4ce7d 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist @@ -7,12 +7,14 @@ glyphOne 400 glyphTwo - 400 + 100 glyphTwo glyphOne - 400 + -100 + glyphTwo + -400 diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist index a77d59e..ff4ce7d 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist @@ -7,12 +7,14 @@ glyphOne 400 glyphTwo - 400 + 100 glyphTwo glyphOne - 400 + -100 + glyphTwo + -400 diff --git a/Tests/202206 discrete spaces/readme.md b/Tests/202206 discrete spaces/readme.md new file mode 100644 index 0000000..72231a0 --- /dev/null +++ b/Tests/202206 discrete spaces/readme.md @@ -0,0 +1,13 @@ +# Test for designspace 5 format with discrete and interpolating axes + +I need to keep notes somewhere. + +The script `ds5_makeTestDoc.py` makes a new DS5 designspace file with 3 axes. + +* `wdth` with minimum at `400`, default `400`, maximum at `1000`. An interpolating axis. +* `DSC1`, a discrete axis. Values at `1,2,3`. Default at `1`. Named `countedItems` Variations along this axis consist of 1, 2, and 3 boxes. +* `DSC2`, a discrete axis. Values at `0, 1`. Default at `0`. Named `outlined`. Variations along this axis consist of solid shapes and outlined shapes. + +The `masters` folder has sources for all intersections of these axes. The default of the whole system is at `wdth: 400, countedItems: 1, outlined: 0` +![](masters.jpg) + From 442091a6753e60e78b810721d5d97cae91f738e3 Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Sat, 15 Oct 2022 20:12:40 +0200 Subject: [PATCH 08/26] WIP mutatormath outline generation works mutatormath anisotropic works removed lots of prints starting adding memoize but doesn't seem to work --- Lib/ufoProcessor/__init__.py | 142 +++++++----------- .../addKerningToTheseMasters.py | 15 ++ 2 files changed, 73 insertions(+), 84 deletions(-) create mode 100644 Tests/202206 discrete spaces/addKerningToTheseMasters.py diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index 8f7a8bb..b2f4e58 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -2,6 +2,8 @@ from __future__ import print_function, division, absolute_import +## caching +import functools from warnings import warn import os @@ -34,6 +36,30 @@ from ufoProcessor.emptyPen import checkGlyphIsEmpty +_memoizeCache = dict() + +def memoize(function): + global _memoizeCache + print("##################### calling memoize #####################", function) + @functools.wraps(function) + def wrapper(self, *args, **kwargs): + global _memoizeCache + key = (function.__name__, self, args, tuple((key, kwargs[key]) for key in sorted(kwargs.keys()))) + print("##################### calling wrapper #####################", key) + if key in _memoizeCache: + print("##################### cached result #####################", key) + return _memoizeCache[key] + else: + print("##################### calling wrapped #####################", function) + result = function(self, *args, **kwargs) + _memoizeCache[key] = result + print("##################### cached added key #####################", key) + return result + return wrapper +##### + + + try: from ._version import version as __version__ except ImportError: @@ -83,6 +109,8 @@ def getLayer(f, layerName): 2022 work on support for DS5 https://github.com/fonttools/fonttools/blob/main/Lib/fontTools/designspaceLib/split.py#L53 + + 2022 10 extrapolate in varlib only works for -1, 0, 1 systems. So we continue to rely on MutatorMath. """ @@ -292,18 +320,13 @@ def _getAxisOrder(self): def getVariationModel(self, items, axes, bias=None): # Return either a mutatorMath or a varlib.model object for calculating. - print(f"getVariationModel items {items}") - print(f"getVariationModel bias {bias}") try: if self.useVarlib: # use the varlib variation model - print("\t\t## into getVariationModel varlib") try: return dict(), VariationModelMutator(items, axes=self.axes, extrapolate=True) except TypeError: - print(f"@@ typeError in getVariationModel {ufoProcessor.varModels.__file__}") import fontTools.varLib.models - print(f"@@ {fontTools.varLib.models.__file__}") error = traceback.format_exc() print(error) return {}, None @@ -314,30 +337,24 @@ def getVariationModel(self, items, axes, bias=None): return {}, None else: # use mutatormath model - #print("\t\t## into getVariationModel mutatormath") axesForMutator = self.getMutatorAxes() - #print("\t\t## into getVariationModel axesForMutator", axesForMutator) - #print("\t\t## into getVariationModel items", items) - #print("\t\t## into getVariationModel bias", bias) - return buildMutator(items, axes=axesForMutator, bias=bias) + # mutator will be confused by discrete axis values. + # the bias needs to be for the continuour axes only + biasForMutator, _ = self.splitLocation(bias) + return buildMutator(items, axes=axesForMutator, bias=biasForMutator) except: error = traceback.format_exc() - print("\t\t## into getVariationModel Error", error) self.toolLog.append("UFOProcessor.getVariationModel error: %s" % error) return {}, None def getInfoMutator(self, discreteLocation=None): """ Returns a info mutator """ - print(f"\t\t## @@ into getInfoMutator {discreteLocation}") - #if self._infoMutator: - # return self._infoMutator infoItems = [] if discreteLocation is not None: sources = self.findSourcesForDiscreteLocation(discreteLocation) else: sources = self.sources for sourceDescriptor in sources: - print(f"\t\t##---> into getInfoMutator {sourceDescriptor.name}") if sourceDescriptor.layerName is not None: continue continuous, discrete = self.splitLocation(sourceDescriptor.location) @@ -349,10 +366,8 @@ def getInfoMutator(self, discreteLocation=None): infoItems.append((loc, sourceFont.info.toMathInfo())) else: infoItems.append((loc, self.mathInfoClass(sourceFont.info))) - print(f"\t\t##---> getInfoMutator {loc}") infoBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) - print("@@5") bias, self._infoMutator = self.getVariationModel(infoItems, axes=self.serializedAxes, bias=infoBias) return self._infoMutator @@ -361,28 +376,17 @@ def getKerningMutator(self, pairs=None, discreteLocation=None): If no pairs are given: calculate the whole table. If pairs are given then query the sources for a value and make a mutator only with those values. """ - #if self._kerningMutator and pairs == self._kerningMutatorPairs: - # return self._kerningMutator - #@@ - print(f"\t\t## @@ into getKerningMutator {discreteLocation}") if discreteLocation is not None: sources = self.findSourcesForDiscreteLocation(discreteLocation) - print(f"\t\t## @@ into getKerningMutator sources 1 {sources}") else: sources = self.sources - print(f"\t\t## @@ into getKerningMutator sources 2 {sources}") kerningItems = [] foregroundLayers = [None, 'foreground', 'public.default'] if pairs is None: - print("\t\t\t\t\t ** 1") for sourceDescriptor in sources: - print("\t\t\t\t\t ** 2") - print(f"\t\t##---> into getKerningMutator {sourceDescriptor.name}") if sourceDescriptor.layerName not in foregroundLayers: - print("\t\t\t\t\t ** 3") continue if not sourceDescriptor.muteKerning: - print("\t\t\t\t\t ** 4") # filter this XX @@ continuous, discrete = self.splitLocation(sourceDescriptor.location) loc = Location(continuous) @@ -390,8 +394,6 @@ def getKerningMutator(self, pairs=None, discreteLocation=None): if sourceFont is None: continue # this makes assumptions about the groups of all sources being the same. kerningItems.append((loc, self.mathKerningClass(sourceFont.kerning, sourceFont.groups))) - print("\t\t\t\t\t ** 5") - print(f"\t\t##---> into kerningItems {kerningItems}") else: self._kerningMutatorPairs = pairs for sourceDescriptor in sources: @@ -416,11 +418,8 @@ def getKerningMutator(self, pairs=None, discreteLocation=None): sparseKerning[pair] = v kerningItems.append((loc, self.mathKerningClass(sparseKerning))) kerningBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) - print("@@6") - print(f"\t\t## getKerningMutator {kerningItems}") bias, thing = self.getVariationModel(kerningItems, axes=self.serializedAxes, bias=kerningBias) #xx bias, self._kerningMutator = self.getVariationModel(kerningItems, axes=self.serializedAxes, bias=kerningBias) - #print('self._kerningMutator', self._kerningMutator) return self._kerningMutator def filterThisLocation(self, location, mutedAxes): @@ -444,18 +443,22 @@ def filterThisLocation(self, location, mutedAxes): del new[mutedAxisName] return ignoreMaster, new + # @memoize + # def getGlyphMutator(self, glyphName, decomposeComponents=False, **discreteLocation): + # glyphs = self.collectSourcesForGlyph(glyphName, decomposeComponents=decomposeComponents, **discreteLocation) + + # print("build for glyphName", glyphName, discreteLocation) + # return "a mutator" + + #@memoize def getGlyphMutator(self, glyphName, decomposeComponents=False, - fromCache=None, - discreteLocation=None, + #fromCache=None, + **discreteLocation, ): + fromCache = False # make a mutator / varlib object for glyphName, with the masters for the given discrete location - #cacheKey = (glyphName, decomposeComponents) - #if cacheKey in self._glyphMutators and fromCache: - # return self._glyphMutators[cacheKey] - items = self.collectMastersForGlyph(glyphName, decomposeComponents=decomposeComponents, discreteLocation=discreteLocation) - #for item in items: - # print('\t\t## getGlyphMutator items', item) + items = self.collectSourcesForGlyph(glyphName, decomposeComponents=decomposeComponents, **discreteLocation) new = [] for a, b, c in items: if hasattr(b, "toMathGlyph"): @@ -465,26 +468,18 @@ def getGlyphMutator(self, glyphName, else: new.append((a,self.mathGlyphClass(b))) thing = None - # print('\t\t## getGlyphMutator new', new) - #try: - - print(f'@@4 {glyphName}') - print(f'@@4 {glyphName} new', new) - #print('@@4 serializedAxes', serializedAxes) thisBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) - print('@@4 thisBias', thisBias) bias, thing = self.getVariationModel(new, axes=self.serializedAxes, bias=thisBias) #xx - #print('\t\t## getGlyphMutator bias', bias) - #print('\t\t## getGlyphMutator thing', thing) - #except TypeError: - # #print('problems') - # self.toolLog.append("getGlyphMutator %s items: %s new: %s" % (glyphName, items, new)) - # self.problems.append("\tCan't make processor for glyph %s" % (glyphName)) - #if thing is not None: - # self._glyphMutators[cacheKey] = thing return thing - def collectMastersForGlyph(self, glyphName, decomposeComponents=False, discreteLocation=None): + # @memoize + # def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, **discreteLocation): + # discreteLocation = self.buildDiscreteLocation(discreteLocation) + # sources = self.findSourcesForDiscreteLocation(**discreteLocation) + # return [] + + # @memoize + def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, discreteLocation=None): """ Return a glyph mutator.defaultLoc decomposeComponents = True causes the source glyphs to be decomposed first before building the mutator. That gives you instances that do not depend @@ -496,13 +491,10 @@ def collectMastersForGlyph(self, glyphName, decomposeComponents=False, discreteL empties = [] foundEmpty = False # - #print(f'collectMastersForGlyph discreteLocation: {discreteLocation}') if discreteLocation is not None: sources = self.findSourcesForDiscreteLocation(discreteLocation) else: sources = self.sources - # - #print(f'collectMastersForGlyph sources: {sources}') for sourceDescriptor in sources: if not os.path.exists(sourceDescriptor.path): #kthxbai @@ -595,9 +587,10 @@ def collectMastersForGlyph(self, glyphName, decomposeComponents=False, discreteL isDefault, isEmpty = p if isEmpty: checkedItems.append(items[i]) - #print('checkedItems', checkedItems) return checkedItems + #collectMastersForGlyph = collectSourcesForGlyph + def getNeutralFont(self): # Return a font object for the neutral font # self.fonts[self.default.name] ? @@ -613,7 +606,6 @@ def getNeutralFont(self): def findDefault(self): """Set and return SourceDescriptor at the default location or None. - The default location is the set of all `default` values in user space of all axes. """ self.default = None @@ -680,10 +672,8 @@ def makeInstance(self, instanceDescriptor, bend=False): """ Generate a font object for this instance """ if doRules is not None: - warn('The doRules argument is deprecated', DeprecationWarning, stacklevel=2) + warn('The doRules argument in DesignSpaceProcessor.makeInstance() is deprecated', DeprecationWarning, stacklevel=2) continuousLocation, discreteLocation = self.splitLocation(instanceDescriptor.location) - print(f"\t ## @@@ makeInstance continuousLocation {continuousLocation}") - print(f"\t ## makeInstance discreteLocation {discreteLocation}") font = self._instantiateFont(None) # make fonty things here loc = Location(continuousLocation) @@ -692,15 +682,12 @@ def makeInstance(self, instanceDescriptor, if self.isAnisotropic(loc): anisotropic = True locHorizontal, locVertical = self.splitAnisotropic(loc) - print("anisotropic", anisotropic, locHorizontal, locVertical) - print("instanceDescriptor.kerning", instanceDescriptor.kerning) if instanceDescriptor.kerning: if pairs: try: kerningMutator = self.getKerningMutator(pairs=pairs, discreteLocation=discreteLocation) kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend) kerningObject.extractKerning(font) - print('\t\t\tinstance kerning @@2b: ', font.kerning.items()) except: self.problems.append("Could not make kerning for %s. %s" % (loc, traceback.format_exc())) else: @@ -708,7 +695,6 @@ def makeInstance(self, instanceDescriptor, if kerningMutator is not None: kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend) kerningObject.extractKerning(font) - print('\t\t\tinstance kerning @@2a: ', font.kerning.items()) # # make the info try: infoMutator = self.getInfoMutator(discreteLocation=discreteLocation) @@ -793,8 +779,6 @@ def makeInstance(self, instanceDescriptor, glyphInstanceObject = glyphMutator.makeInstance(continuousLocation, bend=bend) else: # split anisotropic location into horizontal and vertical components - print(f"\t\t\tmaking anisotropic glyph at {locHorizontal} {locVertical}") - #horizontal, vertical = self.splitAnisotropic(continuousLocation) horizontalGlyphInstanceObject = glyphMutator.makeInstance(locHorizontal, bend=bend) verticalGlyphInstanceObject = glyphMutator.makeInstance(locVertical, bend=bend) # merge them again @@ -949,7 +933,6 @@ def splitLocation(self, location): def getDiscreteLocations(self): # return a list of all permutated discrete locations # do we have a list of ordered axes? - #print("ordered axes", self.getOrderedDiscreteAxes()) values = [] names = [] discreteCoordinates = [] @@ -992,19 +975,10 @@ def findSourcesForDiscreteLocation(self, discreteLocDict): dsp = DesignSpaceProcessor(useVarlib=useVarlibPref) print(f'useVarLib {dsp.useVarlib}') ds5Path = "../../Tests/202206 discrete spaces/test.ds5.designspace" - print(f"Can we find the ds5 example at {ds5Path}? {os.path.exists(ds5Path)}") dsp.read(ds5Path) dsp.loadFonts() - print(f"has discrete axes: {dsp.hasDiscreteAxes()}") - print('These are the axes\n', dsp.axes) - print(f'\nSo we will have {len(dsp.getDiscreteLocations())} continuous designspaces in this document') - dsp.generateUFO() - - #for discreteLocation in dsp.getDiscreteLocations(): - # print(f"\titerating through discreteLocations: {discreteLocation}") - # aGlyphs = dsp.collectMastersForGlyph("glyphOne", discreteLocation=discreteLocation) - # for a in aGlyphs: - # print(f"\t\t{a}") - \ No newline at end of file + +print(_memoizeCache) +print('done') \ No newline at end of file diff --git a/Tests/202206 discrete spaces/addKerningToTheseMasters.py b/Tests/202206 discrete spaces/addKerningToTheseMasters.py new file mode 100644 index 0000000..a9cc9b0 --- /dev/null +++ b/Tests/202206 discrete spaces/addKerningToTheseMasters.py @@ -0,0 +1,15 @@ + +p1 = ('glyphOne', 'glyphTwo') +p2 = ('glyphTwo', 'glyphOne') + +g = "glyphOne" + +for f in AllFonts(): + print(f.path, f.kerning.items()) + f.kerning[p1] = f[g].width + f.kerning[p2] = -f[g].width + f.kerning[('glyphTwo', 'glyphTwo')] = -400 + f.kerning[('glyphOne', 'glyphOne')] = 400 + f.save() + f.close() + From 56e5c7fa38a1214498a60dd57c94896bac3b24fb Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Sun, 16 Oct 2022 13:20:12 +0200 Subject: [PATCH 09/26] WIP * stricter immutable kwargs for better keying * make the anisotropic test value not an interpolation --- Lib/ufoProcessor/__init__.py | 52 ++++++++++++------- .../202206 discrete spaces/ds5_makeTestDoc.py | 2 +- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index b2f4e58..8b469d3 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -38,22 +38,37 @@ _memoizeCache = dict() +def immutify(d): + new = [] + for k, v in d.items(): + if isinstance(v, dict): + new.append((k, immutify(v))) + elif isinstance(v, list): + new.append((k, tuple(v))) + else: + new.append((k, v)) + return tuple(new) + +#d = {'name': {'other': 12, 'list': [1,2,4,None], 'location': {'wdth':3, 'wght':(400,100)}}} +#print(d) +#print(immutify(d)) + def memoize(function): global _memoizeCache - print("##################### calling memoize #####################", function) + #print("##################### calling memoize #####################", function) @functools.wraps(function) def wrapper(self, *args, **kwargs): - global _memoizeCache - key = (function.__name__, self, args, tuple((key, kwargs[key]) for key in sorted(kwargs.keys()))) - print("##################### calling wrapper #####################", key) + immutablekwargs = immutify(kwargs) + key = (function.__name__, self, args, immutify(kwargs)) + #print("##################### calling wrapper #####################", key, key in _memoizeCache) if key in _memoizeCache: - print("##################### cached result #####################", key) + #print("##################### cached result #####################", key) return _memoizeCache[key] else: - print("##################### calling wrapped #####################", function) + #print("##################### wrapped #####################", function) result = function(self, *args, **kwargs) _memoizeCache[key] = result - print("##################### cached added key #####################", key) + #print("##################### added key #####################", key) return result return wrapper ##### @@ -225,7 +240,6 @@ class DesignSpaceProcessor(DesignSpaceDocument): def __init__(self, readerClass=None, writerClass=None, fontClass=None, ufoVersion=3, useVarlib=False): super(DesignSpaceProcessor, self).__init__(readerClass=readerClass, writerClass=writerClass) - self.ufoVersion = ufoVersion # target UFO version self.useVarlib = useVarlib self.roundGeometry = False @@ -320,7 +334,8 @@ def _getAxisOrder(self): def getVariationModel(self, items, axes, bias=None): # Return either a mutatorMath or a varlib.model object for calculating. - try: + #try: + if True: if self.useVarlib: # use the varlib variation model try: @@ -342,9 +357,9 @@ def getVariationModel(self, items, axes, bias=None): # the bias needs to be for the continuour axes only biasForMutator, _ = self.splitLocation(bias) return buildMutator(items, axes=axesForMutator, bias=biasForMutator) - except: - error = traceback.format_exc() - self.toolLog.append("UFOProcessor.getVariationModel error: %s" % error) + #except: + # error = traceback.format_exc() + # self.toolLog.append("UFOProcessor.getVariationModel error: %s" % error) return {}, None def getInfoMutator(self, discreteLocation=None): @@ -450,7 +465,7 @@ def filterThisLocation(self, location, mutedAxes): # print("build for glyphName", glyphName, discreteLocation) # return "a mutator" - #@memoize + @memoize def getGlyphMutator(self, glyphName, decomposeComponents=False, #fromCache=None, @@ -478,7 +493,7 @@ def getGlyphMutator(self, glyphName, # sources = self.findSourcesForDiscreteLocation(**discreteLocation) # return [] - # @memoize + @memoize def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, discreteLocation=None): """ Return a glyph mutator.defaultLoc decomposeComponents = True causes the source glyphs to be decomposed first @@ -758,14 +773,15 @@ def makeInstance(self, instanceDescriptor, # should be the glyphorder from the default, yes? font.lib['public.glyphOrder'] = selectedGlyphNames for glyphName in selectedGlyphNames: - try: + #try: + if True: glyphMutator = self.getGlyphMutator(glyphName, discreteLocation=discreteLocation) if glyphMutator is None: self.problems.append("Could not make mutator for glyph %s" % (glyphName)) continue - except: - self.problems.append("Could not make mutator for glyph %s %s" % (glyphName, traceback.format_exc())) - continue + #except: + # self.problems.append("Could not make mutator for glyph %s %s" % (glyphName, traceback.format_exc())) + # continue glyphData = {} font.newGlyph(glyphName) font[glyphName].clear() diff --git a/Tests/202206 discrete spaces/ds5_makeTestDoc.py b/Tests/202206 discrete spaces/ds5_makeTestDoc.py index 69c2a4c..9397dd6 100644 --- a/Tests/202206 discrete spaces/ds5_makeTestDoc.py +++ b/Tests/202206 discrete spaces/ds5_makeTestDoc.py @@ -79,7 +79,7 @@ def ip(a,b,f): extrapolateAmount = 100 -interestingWeightValues = [(200, 1200), 300, 400, 700, 1000, 1100] +interestingWeightValues = [(400, 700), 300, 400, 700, 1000, 1100] mathModelPrefKey = "com.letterror.mathModelPref" mathModelVarlibPref = "previewVarLib" From ace699cadd3fdada30d546cf9f087bd68cd38232 Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Sun, 16 Oct 2022 14:38:04 +0200 Subject: [PATCH 10/26] WIP * also immutify the args. --- Lib/ufoProcessor/__init__.py | 54 +++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index 8b469d3..e36cc7a 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -6,7 +6,7 @@ import functools from warnings import warn -import os +import os, time import logging, traceback import collections import itertools @@ -40,13 +40,18 @@ def immutify(d): new = [] - for k, v in d.items(): - if isinstance(v, dict): - new.append((k, immutify(v))) - elif isinstance(v, list): - new.append((k, tuple(v))) - else: - new.append((k, v)) + if isinstance(d, dict): + for k, v in d.items(): + if isinstance(v, dict): + new.append((k, immutify(v))) + elif isinstance(v, list): + new.append((k, tuple(v))) + else: + new.append((k, v)) + elif isinstance(d, list): + [new.append(immutify(a)) for a in d] + else: + new.append(d) return tuple(new) #d = {'name': {'other': 12, 'list': [1,2,4,None], 'location': {'wdth':3, 'wght':(400,100)}}} @@ -54,21 +59,19 @@ def immutify(d): #print(immutify(d)) def memoize(function): - global _memoizeCache - #print("##################### calling memoize #####################", function) @functools.wraps(function) def wrapper(self, *args, **kwargs): + #print('args', args) + #print('kwargs', kwargs) + immutableargs = tuple([immutify(a) for a in args]) immutablekwargs = immutify(kwargs) - key = (function.__name__, self, args, immutify(kwargs)) - #print("##################### calling wrapper #####################", key, key in _memoizeCache) + #print('immutableargs', immutableargs) + key = (function.__name__, self, immutableargs, immutify(kwargs)) if key in _memoizeCache: - #print("##################### cached result #####################", key) return _memoizeCache[key] else: - #print("##################### wrapped #####################", function) result = function(self, *args, **kwargs) _memoizeCache[key] = result - #print("##################### added key #####################", key) return result return wrapper ##### @@ -961,6 +964,7 @@ def getDiscreteLocations(self): discreteCoordinates.append({a:b for a,b in zip(names,r)}) return discreteCoordinates + @memoize def findSourcesForDiscreteLocation(self, discreteLocDict): # return a list of all sourcedescriptors that share the values in the discrete loc tuple # discreteLocDict {'countedItems': 1.0, 'outlined': 0.0}, {'countedItems': 1.0, 'outlined': 1.0} @@ -978,6 +982,10 @@ def findSourcesForDiscreteLocation(self, discreteLocDict): sources.append(s) return sources + def changed(self): + _memoizeCache.clear() + + if __name__ == "__main__": # while we're testing @@ -994,7 +1002,21 @@ def findSourcesForDiscreteLocation(self, discreteLocDict): dsp.read(ds5Path) dsp.loadFonts() + s1 = time.process_time() dsp.generateUFO() + s2 = time.process_time() + d1 = s2-s1 + print('duration 1', d1) + dsp.changed() + dsp.generateUFO() + dsp.generateUFO() + dsp.generateUFO() + dsp.generateUFO() + d3 = .25*(time.process_time()-s2) + print('duration 2', d3) + -print(_memoizeCache) +#print(_memoizeCache.keys()) +print(len(_memoizeCache)) + print('done') \ No newline at end of file From 2ef574c1cb790a3a75206e19d17d1cf23812940b Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Sun, 16 Oct 2022 14:40:03 +0200 Subject: [PATCH 11/26] WIP * Frederik's immutify --- Lib/ufoProcessor/__init__.py | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index e36cc7a..6bfc163 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -38,34 +38,30 @@ _memoizeCache = dict() -def immutify(d): - new = [] - if isinstance(d, dict): - for k, v in d.items(): - if isinstance(v, dict): - new.append((k, immutify(v))) - elif isinstance(v, list): - new.append((k, tuple(v))) - else: - new.append((k, v)) - elif isinstance(d, list): - [new.append(immutify(a)) for a in d] + +def immutify(obj): + hashValues = [] + + if isinstance(obj, dict): + for key, value in obj.items(): + hashValues.extend([key, immutify(value)]) + elif isinstance(obj, list): + for value in obj: + hashValues.extend(immutify(value)) else: - new.append(d) - return tuple(new) + hashValues.append(obj) -#d = {'name': {'other': 12, 'list': [1,2,4,None], 'location': {'wdth':3, 'wght':(400,100)}}} -#print(d) -#print(immutify(d)) + return tuple(hashValues) + +#assert immutify(10) == (10,) +#assert immutify([10, 20, "a"]) == (10, 20, 'a') +#assert immutify(dict(foo="bar", world=["a", "b"])) == ('foo', ('bar',), 'world', ('a', 'b')) def memoize(function): @functools.wraps(function) def wrapper(self, *args, **kwargs): - #print('args', args) - #print('kwargs', kwargs) immutableargs = tuple([immutify(a) for a in args]) immutablekwargs = immutify(kwargs) - #print('immutableargs', immutableargs) key = (function.__name__, self, immutableargs, immutify(kwargs)) if key in _memoizeCache: return _memoizeCache[key] From d1da04e8ab5c0da768ab0660e6e3acc7cb8c6828 Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Sun, 16 Oct 2022 21:05:53 +0200 Subject: [PATCH 12/26] WIP * reorganise * include font.info. features and groups --- Lib/ufoProcessor/__init__.py | 166 +++++++----------- .../202206 discrete spaces/ds5_makeTestDoc.py | 2 + 2 files changed, 68 insertions(+), 100 deletions(-) diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index 6bfc163..8aac770 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -1,7 +1,28 @@ # coding: utf-8 - from __future__ import print_function, division, absolute_import +""" + UFOProcessor no longer executes the rules in the designspace. Rules now have complex behaviour and need + to be properly compiled and executed by something that has an overview of all the features. + + 2022 work on support for DS5 + https://github.com/fonttools/fonttools/blob/main/Lib/fontTools/designspaceLib/split.py#L53 + + 2022 10 extrapolate in varlib only works for -1, 0, 1 systems. So we continue to rely on MutatorMath. + + +""" + + +""" + build() is a convenience function for reading and executing a designspace file. + documentPath: path to the designspace file. + outputUFOFormatVersion: integer, 2, 3. Format for generated UFOs. Note: can be different from source UFO format. + useVarlib: True if you want the geometry to be generated with varLib.model instead of mutatorMath. +""" + + + ## caching import functools @@ -19,7 +40,7 @@ import defcon import fontParts.fontshell.font import defcon.objects.font -from defcon.objects.font import Font +#from defcon.objects.font import Font from defcon.pens.transformPointPen import TransformPointPen from defcon.objects.component import _defaultTransformation from fontMath.mathGlyph import MathGlyph @@ -36,12 +57,20 @@ from ufoProcessor.emptyPen import checkGlyphIsEmpty -_memoizeCache = dict() +try: + from ._version import version as __version__ +except ImportError: + __version__ = "0.0.0+unknown" + +_memoizeCache = dict() def immutify(obj): + # make an immutable version of this object. + # assert immutify(10) == (10,) + # assert immutify([10, 20, "a"]) == (10, 20, 'a') + # assert immutify(dict(foo="bar", world=["a", "b"])) == ('foo', ('bar',), 'world', ('a', 'b')) hashValues = [] - if isinstance(obj, dict): for key, value in obj.items(): hashValues.extend([key, immutify(value)]) @@ -50,12 +79,7 @@ def immutify(obj): hashValues.extend(immutify(value)) else: hashValues.append(obj) - return tuple(hashValues) - -#assert immutify(10) == (10,) -#assert immutify([10, 20, "a"]) == (10, 20, 'a') -#assert immutify(dict(foo="bar", world=["a", "b"])) == ('foo', ('bar',), 'world', ('a', 'b')) def memoize(function): @functools.wraps(function) @@ -72,14 +96,6 @@ def wrapper(self, *args, **kwargs): return wrapper ##### - - -try: - from ._version import version as __version__ -except ImportError: - __version__ = "0.0.0+unknown" - - class UFOProcessorError(Exception): def __init__(self, msg, obj=None): self.msg = msg @@ -106,36 +122,6 @@ def getLayer(f, layerName): return f.getLayer(layerName) return None -""" - Processing of rules when generating UFOs. - Swap the contents of two glyphs. - - contours - - components - - width - - group membership - - kerning - - + Remap components so that glyphs that reference either of the swapped glyphs maintain appearance - + Keep the unicode value of the original glyph. - - Notes - Parking the glyphs under a swapname is a bit lazy, but at least it guarantees the glyphs have the right parent. - - 2022 work on support for DS5 - https://github.com/fonttools/fonttools/blob/main/Lib/fontTools/designspaceLib/split.py#L53 - - 2022 10 extrapolate in varlib only works for -1, 0, 1 systems. So we continue to rely on MutatorMath. - - -""" - - -""" - build() is a convenience function for reading and executing a designspace file. - documentPath: path to the designspace file. - outputUFOFormatVersion: integer, 2, 3. Format for generated UFOs. Note: can be different from source UFO format. - useVarlib: True if you want the geometry to be generated with varLib.model instead of mutatorMath. -""" def build( documentPath, @@ -265,7 +251,7 @@ def generateUFO(self, processRules=True, glyphNames=None, pairs=None, bend=False # option to execute the rules # make sure we're not trying to overwrite a newer UFO format self.loadFonts() - self.findDefault() + #self.findDefault() if self.default is None: # we need one to genenerate raise UFOProcessorError("Can't generate UFO from this designspace: no default font.", self) @@ -353,13 +339,13 @@ def getVariationModel(self, items, axes, bias=None): # use mutatormath model axesForMutator = self.getMutatorAxes() # mutator will be confused by discrete axis values. - # the bias needs to be for the continuour axes only + # the bias needs to be for the continuous axes only biasForMutator, _ = self.splitLocation(bias) return buildMutator(items, axes=axesForMutator, bias=biasForMutator) #except: # error = traceback.format_exc() # self.toolLog.append("UFOProcessor.getVariationModel error: %s" % error) - return {}, None + return {}, None def getInfoMutator(self, discreteLocation=None): """ Returns a info mutator """ @@ -380,7 +366,6 @@ def getInfoMutator(self, discreteLocation=None): infoItems.append((loc, sourceFont.info.toMathInfo())) else: infoItems.append((loc, self.mathInfoClass(sourceFont.info))) - infoBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) bias, self._infoMutator = self.getVariationModel(infoItems, axes=self.serializedAxes, bias=infoBias) return self._infoMutator @@ -467,7 +452,6 @@ def filterThisLocation(self, location, mutedAxes): @memoize def getGlyphMutator(self, glyphName, decomposeComponents=False, - #fromCache=None, **discreteLocation, ): fromCache = False @@ -486,12 +470,6 @@ def getGlyphMutator(self, glyphName, bias, thing = self.getVariationModel(new, axes=self.serializedAxes, bias=thisBias) #xx return thing - # @memoize - # def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, **discreteLocation): - # discreteLocation = self.buildDiscreteLocation(discreteLocation) - # sources = self.findSourcesForDiscreteLocation(**discreteLocation) - # return [] - @memoize def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, discreteLocation=None): """ Return a glyph mutator.defaultLoc @@ -741,27 +719,24 @@ def makeInstance(self, instanceDescriptor, # # records.append((nameID, )) except: self.problems.append("Could not make fontinfo for %s. %s" % (loc, traceback.format_exc())) - # for sourceDescriptor in self.sources: - # if sourceDescriptor.copyInfo: - # # this is the source - # if self.fonts[sourceDescriptor.name] is not None: - # self._copyFontInfo(self.fonts[sourceDescriptor.name].info, font.info) - # if sourceDescriptor.copyLib: - # # excplicitly copy the font.lib items - # if self.fonts[sourceDescriptor.name] is not None: - # for key, value in self.fonts[sourceDescriptor.name].lib.items(): - # font.lib[key] = value - # if sourceDescriptor.copyGroups: - # if self.fonts[sourceDescriptor.name] is not None: - # sides = font.kerningGroupConversionRenameMaps.get('side1', {}) - # sides.update(font.kerningGroupConversionRenameMaps.get('side2', {})) - # for key, value in self.fonts[sourceDescriptor.name].groups.items(): - # if key not in sides: - # font.groups[key] = value - # if sourceDescriptor.copyFeatures: - # if self.fonts[sourceDescriptor.name] is not None: - # featuresText = self.fonts[sourceDescriptor.name].features.text - # font.features.text = featuresText + for sourceDescriptor in self.sources: + if sourceDescriptor.copyInfo: + # this is the source + if self.fonts[sourceDescriptor.name] is not None: + self._copyFontInfo(self.fonts[sourceDescriptor.name].info, font.info) + if sourceDescriptor.copyLib: + # excplicitly copy the font.lib items + if self.fonts[sourceDescriptor.name] is not None: + for key, value in self.fonts[sourceDescriptor.name].lib.items(): + font.lib[key] = value + if sourceDescriptor.copyGroups: + if self.fonts[sourceDescriptor.name] is not None: + for key, value in self.fonts[sourceDescriptor.name].groups.items(): + font.groups[key] = value + if sourceDescriptor.copyFeatures: + if self.fonts[sourceDescriptor.name] is not None: + featuresText = self.fonts[sourceDescriptor.name].features.text + font.features.text = featuresText # glyphs if glyphNames: selectedGlyphNames = glyphNames @@ -919,8 +894,7 @@ def _copyFontInfo(self, sourceInfo, targetInfo): if copy: value = getattr(sourceInfo, infoAttribute) setattr(targetInfo, infoAttribute, value) - - + # some ds5 work def getOrderedDiscreteAxes(self): # return the list of discrete axis objects, in the right order @@ -978,41 +952,33 @@ def findSourcesForDiscreteLocation(self, discreteLocDict): sources.append(s) return sources + # caching def changed(self): _memoizeCache.clear() + + def glyphChanged(self, glyphName): + for key in list(_memoizeCache.keys()): + if key[0] in ("getGlyphMutator", "collectSourcesForGlyph") and key[1] == glyphName: + del _memoizeCache[key] if __name__ == "__main__": # while we're testing import ufoProcessor - print(ufoProcessor.__file__) + #print(ufoProcessor.__file__) import ufoProcessor.varModels - print(ufoProcessor.varModels.__file__) + #print(ufoProcessor.varModels.__file__) - useVarlibPref = False + useVarlibPref = True dsp = DesignSpaceProcessor(useVarlib=useVarlibPref) - print(f'useVarLib {dsp.useVarlib}') + #print(f'useVarLib {dsp.useVarlib}') ds5Path = "../../Tests/202206 discrete spaces/test.ds5.designspace" dsp.read(ds5Path) dsp.loadFonts() - s1 = time.process_time() - dsp.generateUFO() - s2 = time.process_time() - d1 = s2-s1 - print('duration 1', d1) - dsp.changed() - dsp.generateUFO() dsp.generateUFO() - dsp.generateUFO() - dsp.generateUFO() - d3 = .25*(time.process_time()-s2) - print('duration 2', d3) - -#print(_memoizeCache.keys()) -print(len(_memoizeCache)) - +print(f"{len(_memoizeCache)} items in _memoizeCache") print('done') \ No newline at end of file diff --git a/Tests/202206 discrete spaces/ds5_makeTestDoc.py b/Tests/202206 discrete spaces/ds5_makeTestDoc.py index 9397dd6..fc63440 100644 --- a/Tests/202206 discrete spaces/ds5_makeTestDoc.py +++ b/Tests/202206 discrete spaces/ds5_makeTestDoc.py @@ -59,6 +59,8 @@ s1.familyName = "MasterFamilyName" if default == masterLocation: s1.copyGroups = True + s1.copyFeatures = True + s1.copyInfo = True td1 = ["One", "Two", "Three"][(d1-1)] if c == 400: tc = "Narrow" From cf87575e303ae339580f219f3a59077d85a9c1fb Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Mon, 17 Oct 2022 10:31:00 +0200 Subject: [PATCH 13/26] WIP * generate mutmath as well as varlib instances in separate directories * clean up * add axis mapping to test document, compare results mm and vl * make useVarLib a property that then also clears the memocache --- .gitignore | 2 ++ Lib/ufoProcessor/__init__.py | 34 ++++++++++++------- Lib/ufoProcessor/varModels.py | 7 ---- .../202206 discrete spaces/ds5_makeTestDoc.py | 7 ++-- .../features.fea | 2 +- .../fontinfo.plist | 2 +- .../features.fea | 2 +- .../fontinfo.plist | 2 +- .../features.fea | 2 +- .../fontinfo.plist | 2 +- .../features.fea | 2 +- .../fontinfo.plist | 2 +- .../features.fea | 2 +- .../fontinfo.plist | 2 +- .../features.fea | 2 +- .../fontinfo.plist | 2 +- .../features.fea | 2 +- .../fontinfo.plist | 2 +- .../glyphs/glyphO_ne.glif | 8 ++--- .../features.fea | 2 +- .../fontinfo.plist | 2 +- .../glyphs/glyphO_ne.glif | 4 +-- .../features.fea | 2 +- .../fontinfo.plist | 2 +- .../glyphs/glyphO_ne.glif | 16 ++++----- .../features.fea | 2 +- .../fontinfo.plist | 2 +- .../glyphs/glyphO_ne.glif | 8 ++--- .../features.fea | 2 +- .../fontinfo.plist | 2 +- .../glyphs/glyphO_ne.glif | 24 ++++++------- .../features.fea | 2 +- .../fontinfo.plist | 2 +- .../glyphs/glyphO_ne.glif | 12 +++---- 34 files changed, 89 insertions(+), 81 deletions(-) diff --git a/.gitignore b/.gitignore index b775bca..24538b3 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,5 @@ Tests/Instances Tests/_* Tests/20190830 benders/Skateboard Previews /Tests/202206 discrete spaces/instances +/Tests/202206 discrete spaces/instances_mutMath +/Tests/202206 discrete spaces/instances_varlib diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index 8aac770..8a45f39 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -316,7 +316,14 @@ def _getAxisOrder(self): axisOrder = property(_getAxisOrder, doc="get the axis order from the axis descriptors") serializedAxes = property(getSerializedAxes, doc="a list of dicts with the axis values") - + + def _setUseVarLib(self, useVarLib=True): + print("setting _setUseVarLib") + self.changed() + self.useVarLib = True + + useVarLib = property(_setUseVarLib, doc="set useVarLib to True use the varlib mathmodel. Set to False to use MutatorMath.") + def getVariationModel(self, items, axes, bias=None): # Return either a mutatorMath or a varlib.model object for calculating. #try: @@ -964,21 +971,24 @@ def glyphChanged(self, glyphName): if __name__ == "__main__": # while we're testing - + import shutil + import ufoProcessor - #print(ufoProcessor.__file__) import ufoProcessor.varModels - #print(ufoProcessor.varModels.__file__) - useVarlibPref = True - - dsp = DesignSpaceProcessor(useVarlib=useVarlibPref) - #print(f'useVarLib {dsp.useVarlib}') ds5Path = "../../Tests/202206 discrete spaces/test.ds5.designspace" - - dsp.read(ds5Path) - dsp.loadFonts() - dsp.generateUFO() + instancesPath = "../../Tests/202206 discrete spaces/instances" + instancesPathMutMath = "../../Tests/202206 discrete spaces/instances_mutMath" + instancesPathVarLib = "../../Tests/202206 discrete spaces/instances_varlib" + + for useVarlibPref, renameInstancesPath in [(True, instancesPathVarLib), (False, instancesPathMutMath)]: + dsp = DesignSpaceProcessor(useVarlib=useVarlibPref) + dsp.read(ds5Path) + dsp.loadFonts() + dsp.generateUFO() + if os.path.exists(renameInstancesPath): + shutil.rmtree(renameInstancesPath) + shutil.move(instancesPath, renameInstancesPath) print(f"{len(_memoizeCache)} items in _memoizeCache") print('done') \ No newline at end of file diff --git a/Lib/ufoProcessor/varModels.py b/Lib/ufoProcessor/varModels.py index 33d1620..565b463 100644 --- a/Lib/ufoProcessor/varModels.py +++ b/Lib/ufoProcessor/varModels.py @@ -73,8 +73,6 @@ def __init__(self, items, axes, model=None, extrapolate=True): if model is None: dd = [self._normalize(a) for a,b in items] ee = self.axisOrder - #print('VariationModelMutator:', dd) - #print('VariationModelMutator:', ee) self.model = VariationModel(dd, axisOrder=ee, extrapolate=self.extrapolate) else: self.model = model @@ -108,18 +106,13 @@ 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) - print(f"------ makeInstance is mapping: {location} -> mapped -> {self.axisMapper(location)}") if bend: location = self.axisMapper(location) - #print("\t2", location) nl = self._normalize(location) return self.model.interpolateFromMasters(nl, self.masters) diff --git a/Tests/202206 discrete spaces/ds5_makeTestDoc.py b/Tests/202206 discrete spaces/ds5_makeTestDoc.py index fc63440..fc43ddf 100644 --- a/Tests/202206 discrete spaces/ds5_makeTestDoc.py +++ b/Tests/202206 discrete spaces/ds5_makeTestDoc.py @@ -20,6 +20,7 @@ a1.minimum = 400 a1.maximum = 1000 a1.default = 400 +a1.map = ((400,400), (700,900), (1000,1000)) a1.name = "width" a1.tag = "wdth" a1.axisOrdering = 1 @@ -45,13 +46,15 @@ # add sources + +# public.skipExportGlyphs + for c in [a1.minimum, a1.maximum]: for d1 in a2.values: for d2 in a3.values: s1 = SourceDescriptor() s1.path = os.path.join("masters", f"geometryMaster_c_{c}_d1_{d1}_d2_{d2}.ufo") - print(s1.path, os.path.exists(s1.path)) s1.name = f"geometryMaster{c} {d1} {d2}" masterLocation = dict(width=c, countedItems=d1, outlined=d2) s1.location = masterLocation @@ -81,7 +84,7 @@ def ip(a,b,f): extrapolateAmount = 100 -interestingWeightValues = [(400, 700), 300, 400, 700, 1000, 1100] +interestingWeightValues = [(400, 700), 300, 400, 550, 700, 1000, 1100] mathModelPrefKey = "com.letterror.mathModelPref" mathModelVarlibPref = "previewVarLib" diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/features.fea index fca011f..a3f867d 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/features.fea +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/features.fea @@ -1 +1 @@ -# features text from master 1 \ No newline at end of file +# features from ufo: geometryMaster_c_1000_d1_1_d2_0.ufo \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist index 7acbc8e..02c1c20 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist @@ -7,7 +7,7 @@ capHeight 400 copyright - This is the copyright notice from master 1 + # font.info from ufo: geometryMaster_c_1000_d1_1_d2_0.ufo descender -200 familyName diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/features.fea index fca011f..27b0ba6 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/features.fea +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/features.fea @@ -1 +1 @@ -# features text from master 1 \ No newline at end of file +# features from ufo: geometryMaster_c_1000_d1_1_d2_1.ufo \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist index 30e4d46..999a9f3 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist @@ -7,7 +7,7 @@ capHeight 400 copyright - This is the copyright notice from master 1 + # font.info from ufo: geometryMaster_c_1000_d1_1_d2_1.ufo descender -200 familyName diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/features.fea index fca011f..1f64fc1 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/features.fea +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/features.fea @@ -1 +1 @@ -# features text from master 1 \ No newline at end of file +# features from ufo: geometryMaster_c_1000_d1_2_d2_0.ufo \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist index 15dac0b..3f02085 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist @@ -7,7 +7,7 @@ capHeight 400 copyright - This is the copyright notice from master 1 + # font.info from ufo: geometryMaster_c_1000_d1_2_d2_0.ufo descender -200 familyName diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/features.fea index fca011f..c866ad3 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/features.fea +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/features.fea @@ -1 +1 @@ -# features text from master 1 \ No newline at end of file +# features from ufo: geometryMaster_c_1000_d1_2_d2_1.ufo \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist index d80c13f..ff11f80 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist @@ -7,7 +7,7 @@ capHeight 400 copyright - This is the copyright notice from master 1 + # font.info from ufo: geometryMaster_c_1000_d1_2_d2_1.ufo descender -200 familyName diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/features.fea index fca011f..b0404fc 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/features.fea +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/features.fea @@ -1 +1 @@ -# features text from master 1 \ No newline at end of file +# features from ufo: geometryMaster_c_1000_d1_3_d2_0.ufo \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist index 076b747..992889c 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist @@ -7,7 +7,7 @@ capHeight 400 copyright - This is the copyright notice from master 1 + # font.info from ufo: geometryMaster_c_1000_d1_3_d2_0.ufo descender -200 familyName diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/features.fea index fca011f..95c19dd 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/features.fea +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/features.fea @@ -1 +1 @@ -# features text from master 1 \ No newline at end of file +# features from ufo: geometryMaster_c_1000_d1_3_d2_1.ufo \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist index a8e4d59..140c4bf 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist @@ -7,7 +7,7 @@ capHeight 400 copyright - This is the copyright notice from master 1 + # font.info from ufo: geometryMaster_c_1000_d1_3_d2_1.ufo descender -200 familyName diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/features.fea index fca011f..cf9f1f4 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/features.fea +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/features.fea @@ -1 +1 @@ -# features text from master 1 \ No newline at end of file +# features from ufo: geometryMaster_c_400_d1_1_d2_0.ufo \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist index 6d990c5..11ef8cf 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist @@ -7,7 +7,7 @@ capHeight 400 copyright - This is the copyright notice from master 1 + # font.info from ufo: geometryMaster_c_400_d1_1_d2_0.ufo descender -200 familyName diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif index 8e28310..0f9d7d2 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif @@ -5,15 +5,15 @@ - - + + - - + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/features.fea index fca011f..ed083e0 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/features.fea +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/features.fea @@ -1 +1 @@ -# features text from master 1 \ No newline at end of file +# features from ufo: geometryMaster_c_400_d1_1_d2_1.ufo \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist index 063eb83..4758a3e 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist @@ -7,7 +7,7 @@ capHeight 400 copyright - This is the copyright notice from master 1 + # font.info from ufo: geometryMaster_c_400_d1_1_d2_1.ufo descender -200 familyName diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif index 752f91b..5167d41 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif @@ -6,8 +6,8 @@ - - + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/features.fea index fca011f..4d4466a 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/features.fea +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/features.fea @@ -1 +1 @@ -# features text from master 1 \ No newline at end of file +# features from ufo: geometryMaster_c_400_d1_2_d2_0.ufo \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist index 66cc0a9..05fa9c4 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist @@ -7,7 +7,7 @@ capHeight 400 copyright - This is the copyright notice from master 1 + # font.info from ufo: geometryMaster_c_400_d1_2_d2_0.ufo descender -200 familyName diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif index 0fa1933..6e7e8f6 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif @@ -5,27 +5,27 @@ - - + + - - + + - - + + - - + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/features.fea index fca011f..4affb7c 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/features.fea +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/features.fea @@ -1 +1 @@ -# features text from master 1 \ No newline at end of file +# features from ufo: geometryMaster_c_400_d1_2_d2_1.ufo \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist index bb03f26..ce0452a 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist @@ -7,7 +7,7 @@ capHeight 400 copyright - This is the copyright notice from master 1 + # font.info from ufo: geometryMaster_c_400_d1_2_d2_1.ufo descender -200 familyName diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif index a45f6d3..abedbd8 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif @@ -6,14 +6,14 @@ - - + + - - + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/features.fea index fca011f..88a4bea 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/features.fea +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/features.fea @@ -1 +1 @@ -# features text from master 1 \ No newline at end of file +# features from ufo: geometryMaster_c_400_d1_3_d2_0.ufo \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist index b2f6c8f..4162a2c 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist @@ -7,7 +7,7 @@ capHeight 400 copyright - This is the copyright notice from master 1 + # font.info from ufo: geometryMaster_c_400_d1_3_d2_0.ufo descender -200 familyName diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif index 76f611f..8de8555 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif @@ -5,39 +5,39 @@ - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/features.fea b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/features.fea index fca011f..a91bb83 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/features.fea +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/features.fea @@ -1 +1 @@ -# features text from master 1 \ No newline at end of file +# features from ufo: geometryMaster_c_400_d1_3_d2_1.ufo \ No newline at end of file diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist index 9c44f52..ff94547 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist @@ -7,7 +7,7 @@ capHeight 400 copyright - This is the copyright notice from master 1 + # font.info from ufo: geometryMaster_c_400_d1_3_d2_1.ufo descender -200 familyName diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif index 63e4b51..4e4561c 100644 --- a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif +++ b/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif @@ -6,20 +6,20 @@ - - + + - - + + - - + + From f1a04a87c289cc333175a964cf40c5f7dd35572d Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Mon, 17 Oct 2022 16:44:56 +0200 Subject: [PATCH 14/26] WIP * idea for updating the loaded fonts: first we open with all the files on disk. Then we update with fontobjects that happen to be open. Clear the cache. --- Lib/ufoProcessor/__init__.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index 8a45f39..635311c 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -635,6 +635,18 @@ def newDefaultLocation(self, bend=False, discreteLocation=None): loc[axisDescriptor.name] = axisDescriptor.default return loc + def updateFonts(self, fontObjects): + #self.fonts[sourceDescriptor.name] = None + hasUpdated = False + for newFont in fontObjects: + for fontName, haveFont in self.fonts.items(): + if haveFont.path == newFont.path and id(haveFont)!=id(newFont): + print(f"updating {self.fonts[fontName]} with {newFont}") + self.fonts[fontName] = newFont + hasUpdated = True + if hasUpdated: + self.changed() + def loadFonts(self, reload=False): # Load the fonts and find the default candidate based on the info flag if self._fontsLoaded and not reload: @@ -645,6 +657,7 @@ def loadFonts(self, reload=False): # make sure it has a unique name sourceDescriptor.name = "master.%d" % i if sourceDescriptor.name not in self.fonts: + # if os.path.exists(sourceDescriptor.path): self.fonts[sourceDescriptor.name] = self._instantiateFont(sourceDescriptor.path) self.problems.append("loaded master from %s, layer %s, format %d" % (sourceDescriptor.path, sourceDescriptor.layerName, getUFOVersion(sourceDescriptor.path))) @@ -982,9 +995,11 @@ def glyphChanged(self, glyphName): instancesPathVarLib = "../../Tests/202206 discrete spaces/instances_varlib" for useVarlibPref, renameInstancesPath in [(True, instancesPathVarLib), (False, instancesPathMutMath)]: + print(f"\n\n\t\t{useVarlibPref}") dsp = DesignSpaceProcessor(useVarlib=useVarlibPref) dsp.read(ds5Path) dsp.loadFonts() + dsp.updateFonts(AllFonts()) dsp.generateUFO() if os.path.exists(renameInstancesPath): shutil.rmtree(renameInstancesPath) From b2bf40057dcca66cc195648c7310fa25767fc1f2 Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Mon, 17 Oct 2022 18:08:03 +0200 Subject: [PATCH 15/26] WIP * dsp.glyphChanged() checks for the right value in key. --- Lib/ufoProcessor/__init__.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index 635311c..78a9cb5 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -81,6 +81,7 @@ def immutify(obj): hashValues.append(obj) return tuple(hashValues) + def memoize(function): @functools.wraps(function) def wrapper(self, *args, **kwargs): @@ -634,7 +635,7 @@ def newDefaultLocation(self, bend=False, discreteLocation=None): else: loc[axisDescriptor.name] = axisDescriptor.default return loc - + def updateFonts(self, fontObjects): #self.fonts[sourceDescriptor.name] = None hasUpdated = False @@ -974,20 +975,24 @@ def findSourcesForDiscreteLocation(self, discreteLocDict): # caching def changed(self): + # clears everything _memoizeCache.clear() def glyphChanged(self, glyphName): + # clears this one specific glyph for key in list(_memoizeCache.keys()): - if key[0] in ("getGlyphMutator", "collectSourcesForGlyph") and key[1] == glyphName: + #print(f"glyphChanged {[(i,m) for i, m in enumerate(key)]} {glyphName}") + # the glyphname is hiding quite deep in key[2] + # (('glyphTwo',),) + # this is because of how immutify does it. Could be different I suppose but this works + if key[0] in ("getGlyphMutator", "collectSourcesForGlyph") and key[2][0][0] == glyphName: del _memoizeCache[key] if __name__ == "__main__": # while we're testing import shutil - import ufoProcessor - import ufoProcessor.varModels ds5Path = "../../Tests/202206 discrete spaces/test.ds5.designspace" instancesPath = "../../Tests/202206 discrete spaces/instances" @@ -999,11 +1004,14 @@ def glyphChanged(self, glyphName): dsp = DesignSpaceProcessor(useVarlib=useVarlibPref) dsp.read(ds5Path) dsp.loadFonts() + print(dsp.glyphNames) dsp.updateFonts(AllFonts()) dsp.generateUFO() + dsp.glyphChanged("glyphOne") if os.path.exists(renameInstancesPath): shutil.rmtree(renameInstancesPath) shutil.move(instancesPath, renameInstancesPath) print(f"{len(_memoizeCache)} items in _memoizeCache") -print('done') \ No newline at end of file +print('done') + From d05759c65dff24fd9da6ff4d2928bb79f7cb9a27 Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Fri, 18 Nov 2022 14:35:45 +0100 Subject: [PATCH 16/26] WIP Rename folders, move things, cleanup --- Designspaces with python.md | 120 ++++++++++++++++++ Lib/ufoProcessor/__init__.py | 70 ++++++---- Tests/202206 discrete spaces/addGlyphTwo.py | 9 -- .../ds5_addKerningToTheseMasters.py} | 0 .../ds5_extrapolateTest.py} | 2 + .../ds5_makeTestDoc.py | 0 .../masters.jpg | Bin .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../{202206 discrete spaces => ds5}/readme.md | 0 Lib/ufoProcessor/sp3.py => sp3.py | 0 141 files changed, 166 insertions(+), 35 deletions(-) create mode 100644 Designspaces with python.md delete mode 100644 Tests/202206 discrete spaces/addGlyphTwo.py rename Tests/{202206 discrete spaces/addKerningToTheseMasters.py => ds5/ds5_addKerningToTheseMasters.py} (100%) rename Tests/{202206 discrete spaces/extrapolateTest.py => ds5/ds5_extrapolateTest.py} (94%) rename Tests/{202206 discrete spaces => ds5}/ds5_makeTestDoc.py (100%) rename Tests/{202206 discrete spaces => ds5}/masters.jpg (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/features.fea (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/layerinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/groups.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/layercontents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/lib.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/metainfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/features.fea (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/layerinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/groups.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/layercontents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/lib.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/metainfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/features.fea (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/layerinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/groups.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/layercontents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/lib.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/metainfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/features.fea (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/layerinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/groups.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/layercontents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/lib.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/metainfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/features.fea (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/layerinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/groups.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/layercontents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/lib.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/metainfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/features.fea (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/layerinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/groups.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/layercontents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/lib.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/metainfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_0.ufo/features.fea (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/contents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_0.ufo/groups.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_0.ufo/layercontents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_0.ufo/lib.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_0.ufo/metainfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_1.ufo/features.fea (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/contents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_1.ufo/groups.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_1.ufo/layercontents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_1.ufo/lib.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_1_d2_1.ufo/metainfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_0.ufo/features.fea (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/contents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_0.ufo/groups.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_0.ufo/layercontents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_0.ufo/lib.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_0.ufo/metainfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_1.ufo/features.fea (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/contents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_1.ufo/groups.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_1.ufo/layercontents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_1.ufo/lib.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_2_d2_1.ufo/metainfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_0.ufo/features.fea (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/contents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_0.ufo/groups.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_0.ufo/layercontents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_0.ufo/lib.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_0.ufo/metainfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_1.ufo/features.fea (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/contents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_1.ufo/groups.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_1.ufo/layercontents.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_1.ufo/lib.plist (100%) rename Tests/{202206 discrete spaces => ds5}/masters/geometryMaster_c_400_d1_3_d2_1.ufo/metainfo.plist (100%) rename Tests/{202206 discrete spaces => ds5}/readme.md (100%) rename Lib/ufoProcessor/sp3.py => sp3.py (100%) diff --git a/Designspaces with python.md b/Designspaces with python.md new file mode 100644 index 0000000..53eca94 --- /dev/null +++ b/Designspaces with python.md @@ -0,0 +1,120 @@ +# Designspaces and python + +Designspaces can do different things in different processes. Maybe you want to generate a variable font. Maybe you want to generate UFOs. Maybe you want to resample an existing designspace into something else. +While [fonttools.designspacelib](https://fonttools.readthedocs.io/en/latest/designspaceLib/index.html) contains the basic objects to construct, read and write designspaces, the [ufoProcessor package](https://github.com/LettError/ufoProcessor) can also generate instances. + +## Basics +First I have to make a `DesignSpaceDocument` object. This is an empty container, it has no masters, no axes, no path. + + from fontTools.designspaceLib import * + ds = DesignSpaceDocument() + +Now I will add an axis to the document by making an `AxisDescriptor` object and adding some values to its attributes. + + ad = AxisDescriptor() + ad.name = "weight" # readable name + ad.tag = "wght" # 4 letter tag + ad.minimum = 200 + ad.maximum = 1000 + ad.default = 400 + +Finally we add the axisDescriptor to the document: + + ds.addAxis(ad) + print(ds) + path = "my.designspace" + ds.write(path) + +This writes a very small designspace file: + + + + + + + + +Let's add some sources to the designspace: this needs the absolute path to the file (usually a ufo). When the document is saved the paths will be written as relative to the designspace document. A `SourceDescriptor` object has a lot of attributes, but `path` and `location` are the most important ones. + + s1 = SourceDescriptor() + s1.path = "geometryMaster1.ufo" + s1.location = dict(weight=200) + ds.addSource(s1) + + s2 = SourceDescriptor() + s2.path = "geometryMaster2.ufo" + s2.location = dict(weight=1000) + ds.addSource(s2) + +Let's add some instances. Instances are specific locations in the designspace with names and sometimes paths associated with them. In a variable font you might want these to show up as styles in a menu. But you could also generate UFOs from them. + + for w in [ad.minimum, .5*(ad.minimum + ad.default), ad.default, .5*(ad.maximum + ad.default), ad.maximum]: + # you will probably know more compact + # and easier ways to write this, go ahead! + i = InstanceDescriptor() + i.fileName = "InstanceFamily" + i.styleName = "Weight_%d" % w + i.location = dict(weight = w) + i.filename = "instance_%s.ufo" % i.styleName + ds.addInstance(i) + +The XML now has all it needs: an axis, some sources and ome instances. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Whoop well done. + diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index 78a9cb5..eb12296 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -319,7 +319,6 @@ def _getAxisOrder(self): serializedAxes = property(getSerializedAxes, doc="a list of dicts with the axis values") def _setUseVarLib(self, useVarLib=True): - print("setting _setUseVarLib") self.changed() self.useVarLib = True @@ -355,6 +354,7 @@ def getVariationModel(self, items, axes, bias=None): # self.toolLog.append("UFOProcessor.getVariationModel error: %s" % error) return {}, None + @memoize def getInfoMutator(self, discreteLocation=None): """ Returns a info mutator """ infoItems = [] @@ -378,6 +378,7 @@ def getInfoMutator(self, discreteLocation=None): bias, self._infoMutator = self.getVariationModel(infoItems, axes=self.serializedAxes, bias=infoBias) return self._infoMutator + @memoize def getKerningMutator(self, pairs=None, discreteLocation=None): """ Return a kerning mutator, collect the sources, build mathGlyphs. If no pairs are given: calculate the whole table. @@ -429,13 +430,14 @@ def getKerningMutator(self, pairs=None, discreteLocation=None): bias, self._kerningMutator = self.getVariationModel(kerningItems, axes=self.serializedAxes, bias=kerningBias) return self._kerningMutator + @memoize def filterThisLocation(self, location, mutedAxes): # return location with axes is mutedAxes removed # this means checking if the location is a non-default value if not mutedAxes: return False, location defaults = {} - ignoreMaster = False + ignoreSource = False for aD in self.axes: defaults[aD.name] = aD.default new = {} @@ -446,16 +448,9 @@ def filterThisLocation(self, location, mutedAxes): if mutedAxisName not in defaults: continue if location[mutedAxisName] != defaults.get(mutedAxisName): - ignoreMaster = True + ignoreSource = True del new[mutedAxisName] - return ignoreMaster, new - - # @memoize - # def getGlyphMutator(self, glyphName, decomposeComponents=False, **discreteLocation): - # glyphs = self.collectSourcesForGlyph(glyphName, decomposeComponents=decomposeComponents, **discreteLocation) - - # print("build for glyphName", glyphName, discreteLocation) - # return "a mutator" + return ignoreSource, new @memoize def getGlyphMutator(self, glyphName, @@ -463,7 +458,7 @@ def getGlyphMutator(self, glyphName, **discreteLocation, ): fromCache = False - # make a mutator / varlib object for glyphName, with the masters for the given discrete location + # make a mutator / varlib object for glyphName, with the sources for the given discrete location items = self.collectSourcesForGlyph(glyphName, decomposeComponents=decomposeComponents, **discreteLocation) new = [] for a, b, c in items: @@ -505,8 +500,8 @@ def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, discreteL if glyphName in sourceDescriptor.mutedGlyphNames: continue thisIsDefault = self.default == sourceDescriptor - ignoreMaster, filteredLocation = self.filterThisLocation(sourceDescriptor.location, self.mutedAxisNames) - if ignoreMaster: + ignoreSource, filteredLocation = self.filterThisLocation(sourceDescriptor.location, self.mutedAxisNames) + if ignoreSource: continue f = self.fonts.get(sourceDescriptor.name) if f is None: continue @@ -589,7 +584,7 @@ def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, discreteL checkedItems.append(items[i]) return checkedItems - #collectMastersForGlyph = collectSourcesForGlyph + collectMastersForGlyph = collectSourcesForGlyph def getNeutralFont(self): # Return a font object for the neutral font @@ -637,12 +632,14 @@ def newDefaultLocation(self, bend=False, discreteLocation=None): return loc def updateFonts(self, fontObjects): + # this is to update the loaded fonts. + # it should be the way for an editor to provide a list of fonts that are open #self.fonts[sourceDescriptor.name] = None hasUpdated = False for newFont in fontObjects: for fontName, haveFont in self.fonts.items(): if haveFont.path == newFont.path and id(haveFont)!=id(newFont): - print(f"updating {self.fonts[fontName]} with {newFont}") + #print(f"updating {self.fonts[fontName]} with {newFont}") self.fonts[fontName] = newFont hasUpdated = True if hasUpdated: @@ -656,12 +653,12 @@ def loadFonts(self, reload=False): for i, sourceDescriptor in enumerate(self.sources): if sourceDescriptor.name is None: # make sure it has a unique name - sourceDescriptor.name = "master.%d" % i + sourceDescriptor.name = "source.%d" % i if sourceDescriptor.name not in self.fonts: # if os.path.exists(sourceDescriptor.path): self.fonts[sourceDescriptor.name] = self._instantiateFont(sourceDescriptor.path) - self.problems.append("loaded master from %s, layer %s, format %d" % (sourceDescriptor.path, sourceDescriptor.layerName, getUFOVersion(sourceDescriptor.path))) + self.problems.append("loaded source from %s, layer %s, format %d" % (sourceDescriptor.path, sourceDescriptor.layerName, getUFOVersion(sourceDescriptor.path))) names |= set(self.fonts[sourceDescriptor.name].keys()) else: self.fonts[sourceDescriptor.name] = None @@ -764,6 +761,7 @@ def makeInstance(self, instanceDescriptor, else: selectedGlyphNames = self.glyphNames # add the glyphnames to the font.lib['public.glyphOrder'] + # TODO load ignored glyphs from designspace? if not 'public.glyphOrder' in font.lib.keys(): # should be the glyphorder from the default, yes? font.lib['public.glyphOrder'] = selectedGlyphNames @@ -829,12 +827,14 @@ def makeInstance(self, instanceDescriptor, return font + @memoize def isAnisotropic(self, location): for v in location.values(): if isinstance(v, (list, tuple)): return True return False + @memoize def splitAnisotropic(self, location): x = Location() y = Location() @@ -975,8 +975,12 @@ def findSourcesForDiscreteLocation(self, discreteLocDict): # caching def changed(self): - # clears everything - _memoizeCache.clear() + # clears everything relating to this designspacedocument + # the cache could contain more designspacedocument objects. + for key in list(_memoizeCache.keys()): + if key[1] == self: + del _memoizeCache[key] + #_memoizeCache.clear() def glyphChanged(self, glyphName): # clears this one specific glyph @@ -994,11 +998,21 @@ def glyphChanged(self, glyphName): import shutil import ufoProcessor - ds5Path = "../../Tests/202206 discrete spaces/test.ds5.designspace" - instancesPath = "../../Tests/202206 discrete spaces/instances" - instancesPathMutMath = "../../Tests/202206 discrete spaces/instances_mutMath" - instancesPathVarLib = "../../Tests/202206 discrete spaces/instances_varlib" - + ds5Path = "../../Tests/ds5/test.ds5.designspace" + instancesPath = "../../Tests/ds5/instances" + instancesPathMutMath = "../../Tests/ds5/instances_mutMath" + instancesPathVarLib = "../../Tests/ds5/instances_varlib" + + def memoizeStats(): + from pprint import pprint + print("_memoizeCache:") + items = {} + for key, value in _memoizeCache.items(): + if key[0] not in items: + items[key[0]] = 0 + items[key[0]] +=1 + pprint(items) + for useVarlibPref, renameInstancesPath in [(True, instancesPathVarLib), (False, instancesPathMutMath)]: print(f"\n\n\t\t{useVarlibPref}") dsp = DesignSpaceProcessor(useVarlib=useVarlibPref) @@ -1011,7 +1025,11 @@ def glyphChanged(self, glyphName): if os.path.exists(renameInstancesPath): shutil.rmtree(renameInstancesPath) shutil.move(instancesPath, renameInstancesPath) + if useVarlibPref: + # clear only the cached items that belong to the varlib test + # just to see if we can + dsp.changed() + memoizeStats() print(f"{len(_memoizeCache)} items in _memoizeCache") print('done') - diff --git a/Tests/202206 discrete spaces/addGlyphTwo.py b/Tests/202206 discrete spaces/addGlyphTwo.py deleted file mode 100644 index f4f6228..0000000 --- a/Tests/202206 discrete spaces/addGlyphTwo.py +++ /dev/null @@ -1,9 +0,0 @@ - -name = "glyphTwo" - -for f in AllFonts(): - - f.newGlyph(name) - f[name].appendComponent("glyphOne") - f[name].width = f['glyphOne'].width - f.save() \ No newline at end of file diff --git a/Tests/202206 discrete spaces/addKerningToTheseMasters.py b/Tests/ds5/ds5_addKerningToTheseMasters.py similarity index 100% rename from Tests/202206 discrete spaces/addKerningToTheseMasters.py rename to Tests/ds5/ds5_addKerningToTheseMasters.py diff --git a/Tests/202206 discrete spaces/extrapolateTest.py b/Tests/ds5/ds5_extrapolateTest.py similarity index 94% rename from Tests/202206 discrete spaces/extrapolateTest.py rename to Tests/ds5/ds5_extrapolateTest.py index 4149561..921f36c 100644 --- a/Tests/202206 discrete spaces/extrapolateTest.py +++ b/Tests/ds5/ds5_extrapolateTest.py @@ -1,3 +1,5 @@ +# test the extrapolation in VariationModel + from fontTools.varLib.models import VariationModel locations = [ diff --git a/Tests/202206 discrete spaces/ds5_makeTestDoc.py b/Tests/ds5/ds5_makeTestDoc.py similarity index 100% rename from Tests/202206 discrete spaces/ds5_makeTestDoc.py rename to Tests/ds5/ds5_makeTestDoc.py diff --git a/Tests/202206 discrete spaces/masters.jpg b/Tests/ds5/masters.jpg similarity index 100% rename from Tests/202206 discrete spaces/masters.jpg rename to Tests/ds5/masters.jpg diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/features.fea b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/features.fea similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/features.fea rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/features.fea diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/layerinfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/layerinfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/layerinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/groups.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/groups.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/groups.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/groups.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/layercontents.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/layercontents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/layercontents.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/layercontents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/lib.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/lib.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/lib.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/lib.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/metainfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/metainfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/metainfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/features.fea b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/features.fea similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/features.fea rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/features.fea diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/layerinfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/layerinfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/layerinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/groups.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/groups.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/groups.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/groups.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/layercontents.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/layercontents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/layercontents.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/layercontents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/lib.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/lib.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/lib.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/lib.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/metainfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/metainfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/metainfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/features.fea b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/features.fea similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/features.fea rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/features.fea diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/layerinfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/layerinfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/layerinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/groups.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/groups.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/groups.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/groups.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/layercontents.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/layercontents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/layercontents.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/layercontents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/lib.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/lib.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/lib.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/lib.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/metainfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/metainfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/metainfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/features.fea b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/features.fea similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/features.fea rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/features.fea diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/layerinfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/layerinfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/layerinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/groups.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/groups.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/groups.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/groups.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/layercontents.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/layercontents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/layercontents.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/layercontents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/lib.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/lib.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/lib.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/lib.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/metainfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/metainfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/metainfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/features.fea b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/features.fea similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/features.fea rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/features.fea diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/layerinfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/layerinfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/layerinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/groups.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/groups.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/groups.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/groups.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/layercontents.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/layercontents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/layercontents.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/layercontents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/lib.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/lib.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/lib.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/lib.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/metainfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/metainfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/metainfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/features.fea b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/features.fea similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/features.fea rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/features.fea diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/layerinfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/layerinfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/layerinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/groups.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/groups.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/groups.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/groups.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/layercontents.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/layercontents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/layercontents.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/layercontents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/lib.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/lib.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/lib.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/lib.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/metainfo.plist b/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/metainfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/metainfo.plist rename to Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/features.fea b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/features.fea similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/features.fea rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/features.fea diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/contents.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/contents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/contents.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/contents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/groups.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/groups.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/groups.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/groups.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/layercontents.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/layercontents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/layercontents.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/layercontents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/lib.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/lib.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/lib.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/lib.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/metainfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/metainfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_0.ufo/metainfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/features.fea b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/features.fea similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/features.fea rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/features.fea diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/contents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/contents.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/contents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/groups.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/groups.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/groups.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/groups.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/layercontents.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/layercontents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/layercontents.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/layercontents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/lib.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/lib.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/lib.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/lib.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/metainfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/metainfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_1_d2_1.ufo/metainfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/features.fea b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/features.fea similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/features.fea rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/features.fea diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/contents.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/contents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/contents.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/contents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/groups.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/groups.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/groups.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/groups.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/layercontents.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/layercontents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/layercontents.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/layercontents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/lib.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/lib.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/lib.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/lib.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/metainfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/metainfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_0.ufo/metainfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/features.fea b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/features.fea similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/features.fea rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/features.fea diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/contents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/contents.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/contents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/groups.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/groups.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/groups.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/groups.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/layercontents.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/layercontents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/layercontents.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/layercontents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/lib.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/lib.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/lib.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/lib.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/metainfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/metainfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_2_d2_1.ufo/metainfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/features.fea b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/features.fea similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/features.fea rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/features.fea diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/contents.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/contents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/contents.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/contents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/groups.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/groups.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/groups.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/groups.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/layercontents.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/layercontents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/layercontents.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/layercontents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/lib.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/lib.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/lib.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/lib.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/metainfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/metainfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_0.ufo/metainfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/features.fea b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/features.fea similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/features.fea rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/features.fea diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/contents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/contents.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/contents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/groups.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/groups.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/groups.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/groups.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/layercontents.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/layercontents.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/layercontents.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/layercontents.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/lib.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/lib.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/lib.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/lib.plist diff --git a/Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/metainfo.plist b/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/metainfo.plist similarity index 100% rename from Tests/202206 discrete spaces/masters/geometryMaster_c_400_d1_3_d2_1.ufo/metainfo.plist rename to Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/metainfo.plist diff --git a/Tests/202206 discrete spaces/readme.md b/Tests/ds5/readme.md similarity index 100% rename from Tests/202206 discrete spaces/readme.md rename to Tests/ds5/readme.md diff --git a/Lib/ufoProcessor/sp3.py b/sp3.py similarity index 100% rename from Lib/ufoProcessor/sp3.py rename to sp3.py From 40550b39873ea9ca14283649271103dcb4af21c2 Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Fri, 18 Nov 2022 14:48:44 +0100 Subject: [PATCH 17/26] Cleanup Remove sp3 reader + test files Remove some old issue folders from tests Remove tests that don't work with fontparts. --- Tests/ds5/ds5.designspace | 402 ++++++++++++++ Tests/ds5/ds5_makeTestDoc.py | 15 +- Tests/ds5/ds5_test_designspaceProblems.py | 12 + Tests/ds5/masters.jpg | Bin 77822 -> 0 bytes .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 .../features.fea | 0 .../fontinfo.plist | 0 .../glyphs/contents.plist | 0 .../glyphs/glyphO_ne.glif | 0 .../glyphs/glyphT_wo.glif | 0 .../glyphs/layerinfo.plist | 0 .../groups.plist | 0 .../kerning.plist | 0 .../layercontents.plist | 0 .../lib.plist | 0 .../metainfo.plist | 0 Tests/kerningTest.py | 62 --- Tests/mathKerningTest.py | 14 - .../superpolator_testdoc1.sp3 | 92 ---- ...uperpolator_testdoc1_converted.designspace | 125 ----- Tests/tests.py | 304 ++--------- Tests/tests_fp.py | 303 ----------- sp3.py | 494 ------------------ 143 files changed, 477 insertions(+), 1346 deletions(-) create mode 100644 Tests/ds5/ds5.designspace create mode 100644 Tests/ds5/ds5_test_designspaceProblems.py delete mode 100644 Tests/ds5/masters.jpg rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_0.ufo => sources/geometrySource_c_1000_d1_1_d2_0.ufo}/features.fea (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_0.ufo => sources/geometrySource_c_1000_d1_1_d2_0.ufo}/fontinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_0.ufo => sources/geometrySource_c_1000_d1_1_d2_0.ufo}/glyphs/contents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_0.ufo => sources/geometrySource_c_1000_d1_1_d2_0.ufo}/glyphs/glyphO_ne.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_0.ufo => sources/geometrySource_c_1000_d1_1_d2_0.ufo}/glyphs/glyphT_wo.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_0.ufo => sources/geometrySource_c_1000_d1_1_d2_0.ufo}/glyphs/layerinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_0.ufo => sources/geometrySource_c_1000_d1_1_d2_0.ufo}/groups.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_0.ufo => sources/geometrySource_c_1000_d1_1_d2_0.ufo}/kerning.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_0.ufo => sources/geometrySource_c_1000_d1_1_d2_0.ufo}/layercontents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_0.ufo => sources/geometrySource_c_1000_d1_1_d2_0.ufo}/lib.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_0.ufo => sources/geometrySource_c_1000_d1_1_d2_0.ufo}/metainfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_1.ufo => sources/geometrySource_c_1000_d1_1_d2_1.ufo}/features.fea (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_1.ufo => sources/geometrySource_c_1000_d1_1_d2_1.ufo}/fontinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_1.ufo => sources/geometrySource_c_1000_d1_1_d2_1.ufo}/glyphs/contents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_1.ufo => sources/geometrySource_c_1000_d1_1_d2_1.ufo}/glyphs/glyphO_ne.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_1.ufo => sources/geometrySource_c_1000_d1_1_d2_1.ufo}/glyphs/glyphT_wo.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_1.ufo => sources/geometrySource_c_1000_d1_1_d2_1.ufo}/glyphs/layerinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_1.ufo => sources/geometrySource_c_1000_d1_1_d2_1.ufo}/groups.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_1.ufo => sources/geometrySource_c_1000_d1_1_d2_1.ufo}/kerning.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_1.ufo => sources/geometrySource_c_1000_d1_1_d2_1.ufo}/layercontents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_1.ufo => sources/geometrySource_c_1000_d1_1_d2_1.ufo}/lib.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_1_d2_1.ufo => sources/geometrySource_c_1000_d1_1_d2_1.ufo}/metainfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_0.ufo => sources/geometrySource_c_1000_d1_2_d2_0.ufo}/features.fea (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_0.ufo => sources/geometrySource_c_1000_d1_2_d2_0.ufo}/fontinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_0.ufo => sources/geometrySource_c_1000_d1_2_d2_0.ufo}/glyphs/contents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_0.ufo => sources/geometrySource_c_1000_d1_2_d2_0.ufo}/glyphs/glyphO_ne.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_0.ufo => sources/geometrySource_c_1000_d1_2_d2_0.ufo}/glyphs/glyphT_wo.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_0.ufo => sources/geometrySource_c_1000_d1_2_d2_0.ufo}/glyphs/layerinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_0.ufo => sources/geometrySource_c_1000_d1_2_d2_0.ufo}/groups.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_0.ufo => sources/geometrySource_c_1000_d1_2_d2_0.ufo}/kerning.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_0.ufo => sources/geometrySource_c_1000_d1_2_d2_0.ufo}/layercontents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_0.ufo => sources/geometrySource_c_1000_d1_2_d2_0.ufo}/lib.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_0.ufo => sources/geometrySource_c_1000_d1_2_d2_0.ufo}/metainfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_1.ufo => sources/geometrySource_c_1000_d1_2_d2_1.ufo}/features.fea (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_1.ufo => sources/geometrySource_c_1000_d1_2_d2_1.ufo}/fontinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_1.ufo => sources/geometrySource_c_1000_d1_2_d2_1.ufo}/glyphs/contents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_1.ufo => sources/geometrySource_c_1000_d1_2_d2_1.ufo}/glyphs/glyphO_ne.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_1.ufo => sources/geometrySource_c_1000_d1_2_d2_1.ufo}/glyphs/glyphT_wo.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_1.ufo => sources/geometrySource_c_1000_d1_2_d2_1.ufo}/glyphs/layerinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_1.ufo => sources/geometrySource_c_1000_d1_2_d2_1.ufo}/groups.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_1.ufo => sources/geometrySource_c_1000_d1_2_d2_1.ufo}/kerning.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_1.ufo => sources/geometrySource_c_1000_d1_2_d2_1.ufo}/layercontents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_1.ufo => sources/geometrySource_c_1000_d1_2_d2_1.ufo}/lib.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_2_d2_1.ufo => sources/geometrySource_c_1000_d1_2_d2_1.ufo}/metainfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_0.ufo => sources/geometrySource_c_1000_d1_3_d2_0.ufo}/features.fea (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_0.ufo => sources/geometrySource_c_1000_d1_3_d2_0.ufo}/fontinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_0.ufo => sources/geometrySource_c_1000_d1_3_d2_0.ufo}/glyphs/contents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_0.ufo => sources/geometrySource_c_1000_d1_3_d2_0.ufo}/glyphs/glyphO_ne.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_0.ufo => sources/geometrySource_c_1000_d1_3_d2_0.ufo}/glyphs/glyphT_wo.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_0.ufo => sources/geometrySource_c_1000_d1_3_d2_0.ufo}/glyphs/layerinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_0.ufo => sources/geometrySource_c_1000_d1_3_d2_0.ufo}/groups.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_0.ufo => sources/geometrySource_c_1000_d1_3_d2_0.ufo}/kerning.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_0.ufo => sources/geometrySource_c_1000_d1_3_d2_0.ufo}/layercontents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_0.ufo => sources/geometrySource_c_1000_d1_3_d2_0.ufo}/lib.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_0.ufo => sources/geometrySource_c_1000_d1_3_d2_0.ufo}/metainfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_1.ufo => sources/geometrySource_c_1000_d1_3_d2_1.ufo}/features.fea (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_1.ufo => sources/geometrySource_c_1000_d1_3_d2_1.ufo}/fontinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_1.ufo => sources/geometrySource_c_1000_d1_3_d2_1.ufo}/glyphs/contents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_1.ufo => sources/geometrySource_c_1000_d1_3_d2_1.ufo}/glyphs/glyphO_ne.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_1.ufo => sources/geometrySource_c_1000_d1_3_d2_1.ufo}/glyphs/glyphT_wo.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_1.ufo => sources/geometrySource_c_1000_d1_3_d2_1.ufo}/glyphs/layerinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_1.ufo => sources/geometrySource_c_1000_d1_3_d2_1.ufo}/groups.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_1.ufo => sources/geometrySource_c_1000_d1_3_d2_1.ufo}/kerning.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_1.ufo => sources/geometrySource_c_1000_d1_3_d2_1.ufo}/layercontents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_1.ufo => sources/geometrySource_c_1000_d1_3_d2_1.ufo}/lib.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_1000_d1_3_d2_1.ufo => sources/geometrySource_c_1000_d1_3_d2_1.ufo}/metainfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_0.ufo => sources/geometrySource_c_400_d1_1_d2_0.ufo}/features.fea (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_0.ufo => sources/geometrySource_c_400_d1_1_d2_0.ufo}/fontinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_0.ufo => sources/geometrySource_c_400_d1_1_d2_0.ufo}/glyphs/contents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_0.ufo => sources/geometrySource_c_400_d1_1_d2_0.ufo}/glyphs/glyphO_ne.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_0.ufo => sources/geometrySource_c_400_d1_1_d2_0.ufo}/glyphs/glyphT_wo.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_0.ufo => sources/geometrySource_c_400_d1_1_d2_0.ufo}/glyphs/layerinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_0.ufo => sources/geometrySource_c_400_d1_1_d2_0.ufo}/groups.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_0.ufo => sources/geometrySource_c_400_d1_1_d2_0.ufo}/kerning.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_0.ufo => sources/geometrySource_c_400_d1_1_d2_0.ufo}/layercontents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_0.ufo => sources/geometrySource_c_400_d1_1_d2_0.ufo}/lib.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_0.ufo => sources/geometrySource_c_400_d1_1_d2_0.ufo}/metainfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_1.ufo => sources/geometrySource_c_400_d1_1_d2_1.ufo}/features.fea (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_1.ufo => sources/geometrySource_c_400_d1_1_d2_1.ufo}/fontinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_1.ufo => sources/geometrySource_c_400_d1_1_d2_1.ufo}/glyphs/contents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_1.ufo => sources/geometrySource_c_400_d1_1_d2_1.ufo}/glyphs/glyphO_ne.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_1.ufo => sources/geometrySource_c_400_d1_1_d2_1.ufo}/glyphs/glyphT_wo.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_1.ufo => sources/geometrySource_c_400_d1_1_d2_1.ufo}/glyphs/layerinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_1.ufo => sources/geometrySource_c_400_d1_1_d2_1.ufo}/groups.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_1.ufo => sources/geometrySource_c_400_d1_1_d2_1.ufo}/kerning.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_1.ufo => sources/geometrySource_c_400_d1_1_d2_1.ufo}/layercontents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_1.ufo => sources/geometrySource_c_400_d1_1_d2_1.ufo}/lib.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_1_d2_1.ufo => sources/geometrySource_c_400_d1_1_d2_1.ufo}/metainfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_0.ufo => sources/geometrySource_c_400_d1_2_d2_0.ufo}/features.fea (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_0.ufo => sources/geometrySource_c_400_d1_2_d2_0.ufo}/fontinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_0.ufo => sources/geometrySource_c_400_d1_2_d2_0.ufo}/glyphs/contents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_0.ufo => sources/geometrySource_c_400_d1_2_d2_0.ufo}/glyphs/glyphO_ne.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_0.ufo => sources/geometrySource_c_400_d1_2_d2_0.ufo}/glyphs/glyphT_wo.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_0.ufo => sources/geometrySource_c_400_d1_2_d2_0.ufo}/glyphs/layerinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_0.ufo => sources/geometrySource_c_400_d1_2_d2_0.ufo}/groups.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_0.ufo => sources/geometrySource_c_400_d1_2_d2_0.ufo}/kerning.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_0.ufo => sources/geometrySource_c_400_d1_2_d2_0.ufo}/layercontents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_0.ufo => sources/geometrySource_c_400_d1_2_d2_0.ufo}/lib.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_0.ufo => sources/geometrySource_c_400_d1_2_d2_0.ufo}/metainfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_1.ufo => sources/geometrySource_c_400_d1_2_d2_1.ufo}/features.fea (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_1.ufo => sources/geometrySource_c_400_d1_2_d2_1.ufo}/fontinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_1.ufo => sources/geometrySource_c_400_d1_2_d2_1.ufo}/glyphs/contents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_1.ufo => sources/geometrySource_c_400_d1_2_d2_1.ufo}/glyphs/glyphO_ne.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_1.ufo => sources/geometrySource_c_400_d1_2_d2_1.ufo}/glyphs/glyphT_wo.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_1.ufo => sources/geometrySource_c_400_d1_2_d2_1.ufo}/glyphs/layerinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_1.ufo => sources/geometrySource_c_400_d1_2_d2_1.ufo}/groups.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_1.ufo => sources/geometrySource_c_400_d1_2_d2_1.ufo}/kerning.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_1.ufo => sources/geometrySource_c_400_d1_2_d2_1.ufo}/layercontents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_1.ufo => sources/geometrySource_c_400_d1_2_d2_1.ufo}/lib.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_2_d2_1.ufo => sources/geometrySource_c_400_d1_2_d2_1.ufo}/metainfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_0.ufo => sources/geometrySource_c_400_d1_3_d2_0.ufo}/features.fea (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_0.ufo => sources/geometrySource_c_400_d1_3_d2_0.ufo}/fontinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_0.ufo => sources/geometrySource_c_400_d1_3_d2_0.ufo}/glyphs/contents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_0.ufo => sources/geometrySource_c_400_d1_3_d2_0.ufo}/glyphs/glyphO_ne.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_0.ufo => sources/geometrySource_c_400_d1_3_d2_0.ufo}/glyphs/glyphT_wo.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_0.ufo => sources/geometrySource_c_400_d1_3_d2_0.ufo}/glyphs/layerinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_0.ufo => sources/geometrySource_c_400_d1_3_d2_0.ufo}/groups.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_0.ufo => sources/geometrySource_c_400_d1_3_d2_0.ufo}/kerning.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_0.ufo => sources/geometrySource_c_400_d1_3_d2_0.ufo}/layercontents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_0.ufo => sources/geometrySource_c_400_d1_3_d2_0.ufo}/lib.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_0.ufo => sources/geometrySource_c_400_d1_3_d2_0.ufo}/metainfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_1.ufo => sources/geometrySource_c_400_d1_3_d2_1.ufo}/features.fea (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_1.ufo => sources/geometrySource_c_400_d1_3_d2_1.ufo}/fontinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_1.ufo => sources/geometrySource_c_400_d1_3_d2_1.ufo}/glyphs/contents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_1.ufo => sources/geometrySource_c_400_d1_3_d2_1.ufo}/glyphs/glyphO_ne.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_1.ufo => sources/geometrySource_c_400_d1_3_d2_1.ufo}/glyphs/glyphT_wo.glif (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_1.ufo => sources/geometrySource_c_400_d1_3_d2_1.ufo}/glyphs/layerinfo.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_1.ufo => sources/geometrySource_c_400_d1_3_d2_1.ufo}/groups.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_1.ufo => sources/geometrySource_c_400_d1_3_d2_1.ufo}/kerning.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_1.ufo => sources/geometrySource_c_400_d1_3_d2_1.ufo}/layercontents.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_1.ufo => sources/geometrySource_c_400_d1_3_d2_1.ufo}/lib.plist (100%) rename Tests/ds5/{masters/geometryMaster_c_400_d1_3_d2_1.ufo => sources/geometrySource_c_400_d1_3_d2_1.ufo}/metainfo.plist (100%) delete mode 100644 Tests/kerningTest.py delete mode 100644 Tests/mathKerningTest.py delete mode 100644 Tests/spReader_testdocs/superpolator_testdoc1.sp3 delete mode 100644 Tests/spReader_testdocs/superpolator_testdoc1_converted.designspace delete mode 100644 Tests/tests_fp.py delete mode 100644 sp3.py diff --git a/Tests/ds5/ds5.designspace b/Tests/ds5/ds5.designspace new file mode 100644 index 0000000..7379a40 --- /dev/null +++ b/Tests/ds5/ds5.designspace @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/ds5/ds5_makeTestDoc.py b/Tests/ds5/ds5_makeTestDoc.py index fc43ddf..ed9d074 100644 --- a/Tests/ds5/ds5_makeTestDoc.py +++ b/Tests/ds5/ds5_makeTestDoc.py @@ -54,13 +54,13 @@ for d2 in a3.values: s1 = SourceDescriptor() - s1.path = os.path.join("masters", f"geometryMaster_c_{c}_d1_{d1}_d2_{d2}.ufo") - s1.name = f"geometryMaster{c} {d1} {d2}" - masterLocation = dict(width=c, countedItems=d1, outlined=d2) - s1.location = masterLocation + s1.path = os.path.join("sources", f"geometrySource_c_{c}_d1_{d1}_d2_{d2}.ufo") + s1.name = f"geometrySource{c} {d1} {d2}" + sourceLocation = dict(width=c, countedItems=d1, outlined=d2) + s1.location = sourceLocation s1.kerning = True - s1.familyName = "MasterFamilyName" - if default == masterLocation: + s1.familyName = "SourceFamilyName" + if default == sourceLocation: s1.copyGroups = True s1.copyFeatures = True s1.copyInfo = True @@ -100,7 +100,6 @@ def ip(a,b,f): s1 = InstanceDescriptor() s1.path = os.path.join("instances", f"geometryInstance_c_{c}_d1_{d1}_d2_{d2}.ufo") - #print(s1.path, os.path.exists(s1.path)) s1.location = dict(width=c, countedItems=d1, outlined=d2) s1.familyName = "InstanceFamilyName" td1 = ["One", "Two", "Three"][(d1-1)] @@ -118,7 +117,7 @@ def ip(a,b,f): s1.info = True doc.addInstance(s1) -path = "test.ds5.designspace" +path = "ds5.designspace" print(doc.lib) doc.write(path) diff --git a/Tests/ds5/ds5_test_designspaceProblems.py b/Tests/ds5/ds5_test_designspaceProblems.py new file mode 100644 index 0000000..fd3f11e --- /dev/null +++ b/Tests/ds5/ds5_test_designspaceProblems.py @@ -0,0 +1,12 @@ +import designspaceProblems +print(designspaceProblems.__file__) + +path= "ds5.designspace" +checker = designspaceProblems.DesignSpaceChecker(path) +checker.checkEverything() +print(checker.checkDesignSpaceGeometry()) +checker.checkSources() +checker.checkInstances() +print("hasStructuralProblems", checker.hasStructuralProblems()) + +print(checker.problems) \ No newline at end of file diff --git a/Tests/ds5/masters.jpg b/Tests/ds5/masters.jpg deleted file mode 100644 index 5b9857831e8f496bbed436373c510da63c4baf62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77822 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 diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/features.fea similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/features.fea rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/features.fea diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/fontinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/fontinfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/fontinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/glyphs/layerinfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/layerinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/groups.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/groups.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/groups.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/kerning.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/kerning.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/kerning.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/layercontents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/layercontents.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/layercontents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/lib.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/lib.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/lib.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/metainfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_0.ufo/metainfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/metainfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/features.fea similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/features.fea rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/features.fea diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/fontinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/fontinfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/fontinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/glyphs/layerinfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/layerinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/groups.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/groups.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/groups.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/kerning.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/kerning.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/kerning.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/layercontents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/layercontents.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/layercontents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/lib.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/lib.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/lib.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/metainfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_1_d2_1.ufo/metainfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/metainfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/features.fea similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/features.fea rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/features.fea diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/fontinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/fontinfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/fontinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/glyphs/layerinfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/layerinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/groups.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/groups.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/groups.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/kerning.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/kerning.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/kerning.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/layercontents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/layercontents.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/layercontents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/lib.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/lib.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/lib.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/metainfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_0.ufo/metainfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/metainfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/features.fea similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/features.fea rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/features.fea diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/fontinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/fontinfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/fontinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/glyphs/layerinfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/layerinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/groups.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/groups.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/groups.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/kerning.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/kerning.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/kerning.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/layercontents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/layercontents.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/layercontents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/lib.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/lib.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/lib.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/metainfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_2_d2_1.ufo/metainfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/metainfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/features.fea similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/features.fea rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/features.fea diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/fontinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/fontinfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/fontinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/glyphs/layerinfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/layerinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/groups.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/groups.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/groups.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/kerning.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/kerning.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/kerning.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/layercontents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/layercontents.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/layercontents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/lib.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/lib.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/lib.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/metainfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_0.ufo/metainfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/metainfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/features.fea similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/features.fea rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/features.fea diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/fontinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/fontinfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/fontinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/glyphs/layerinfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/layerinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/groups.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/groups.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/groups.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/kerning.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/kerning.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/kerning.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/layercontents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/layercontents.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/layercontents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/lib.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/lib.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/lib.plist diff --git a/Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/metainfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_1000_d1_3_d2_1.ufo/metainfo.plist rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/metainfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/features.fea similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/features.fea rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/features.fea diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/fontinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/fontinfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/fontinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/contents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/contents.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/contents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/groups.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/groups.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/groups.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/kerning.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/kerning.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/kerning.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/layercontents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/layercontents.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/layercontents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/lib.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/lib.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/lib.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/metainfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_0.ufo/metainfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/metainfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/features.fea similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/features.fea rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/features.fea diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/fontinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/fontinfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/fontinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/contents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/contents.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/contents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/groups.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/groups.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/groups.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/kerning.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/kerning.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/kerning.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/layercontents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/layercontents.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/layercontents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/lib.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/lib.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/lib.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/metainfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_1_d2_1.ufo/metainfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/metainfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/features.fea similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/features.fea rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/features.fea diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/fontinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/fontinfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/fontinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/contents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/contents.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/contents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/groups.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/groups.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/groups.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/kerning.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/kerning.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/kerning.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/layercontents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/layercontents.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/layercontents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/lib.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/lib.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/lib.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/metainfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_0.ufo/metainfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/metainfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/features.fea similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/features.fea rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/features.fea diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/fontinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/fontinfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/fontinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/contents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/contents.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/contents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/groups.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/groups.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/groups.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/kerning.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/kerning.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/kerning.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/layercontents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/layercontents.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/layercontents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/lib.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/lib.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/lib.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/metainfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_2_d2_1.ufo/metainfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/metainfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/features.fea similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/features.fea rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/features.fea diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/fontinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/fontinfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/fontinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/contents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/contents.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/contents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/groups.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/groups.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/groups.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/kerning.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/kerning.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/kerning.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/layercontents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/layercontents.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/layercontents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/lib.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/lib.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/lib.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/metainfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_0.ufo/metainfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/metainfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/features.fea similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/features.fea rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/features.fea diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/fontinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/fontinfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/fontinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/contents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/contents.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/contents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/groups.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/groups.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/groups.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/kerning.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/kerning.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/kerning.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/layercontents.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/layercontents.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/layercontents.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/lib.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/lib.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/lib.plist diff --git a/Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/metainfo.plist similarity index 100% rename from Tests/ds5/masters/geometryMaster_c_400_d1_3_d2_1.ufo/metainfo.plist rename to Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/metainfo.plist diff --git a/Tests/kerningTest.py b/Tests/kerningTest.py deleted file mode 100644 index 3f6490c..0000000 --- a/Tests/kerningTest.py +++ /dev/null @@ -1,62 +0,0 @@ -from fontMath.mathKerning import MathKerning - -import fontMath.mathKerning -from defcon.objects.font import Font -from fontParts.fontshell import RFont -from ufoProcessor.varModels import VariationModelMutator -from mutatorMath.objects.mutator import buildMutator, Location -from fontTools.designspaceLib import AxisDescriptor - -# kerning exception value. Different results for 1 and 0 -value = 0 - -#f = Font() -f = RFont() # doesn't make a difference -f.groups["public.kern1.groupA"] = ['one', 'Bee'] -f.groups["public.kern2.groupB"] = ['two', 'Three'] -f.kerning[('public.kern1.groupA', 'public.kern2.groupB')] = -100 -f.kerning[("one", "two")] = value - -m = MathKerning(f.kerning, f.groups) -print("mathKerning object items:", m.items()) -print("\tpair", ('public.kern1.groupA', 'public.kern2.groupB'), m[('public.kern1.groupA', 'public.kern2.groupB')]) -print("\tpair", ('public.kern1.groupA', 'two'), m[('public.kern1.groupA', 'two')]) -print("\tpair", ('one', 'public.kern2.groupB'), m[('one', 'public.kern2.groupB')]) -print("\tpair", ('one', 'two'), m[('one', 'two')]) - -items = [(Location(w=0), m), (Location(w=1), m)] -a = AxisDescriptor() -a.name = "w" -a.minimum = 0 -a.default = 0 -a.maximum = 1 - -# process with varlib.model -mut1 = VariationModelMutator(items, [a]) -m1i = mut1.makeInstance(dict(w=1)) -print("\n#varlib") -print(m1i.items()) - -# process with mutator -bias, mut2 = buildMutator(items) -m2i = mut2.makeInstance(dict(w=1)) -print("\n#mutator") -print(m2i.items()) - -# process with the same mathematical operations on a naked mathKerning object -v = None -deltas = [m, m] -scalars = [1.0, 1.0] -assert len(deltas) == len(scalars) -for i,(delta,scalar) in enumerate(zip(deltas, scalars)): - if not scalar: continue - contribution = delta * scalar - if v is None: - v = contribution - else: - v += contribution -print("\n#doing the math that varlib does") -print(v.items()) - -print(m.groups()) -print((m*2.0).groups()) diff --git a/Tests/mathKerningTest.py b/Tests/mathKerningTest.py deleted file mode 100644 index fd9e2d1..0000000 --- a/Tests/mathKerningTest.py +++ /dev/null @@ -1,14 +0,0 @@ -from fontMath.mathKerning import MathKerning -from defcon.objects.font import Font - -f = Font() -f.groups["public.kern1.groupA"] = ['one', 'Bee'] -f.groups["public.kern2.groupB"] = ['two', 'Three'] -f.kerning[('public.kern1.groupA', 'public.kern2.groupB')] = -100 -f.kerning[('one', 'two')] = 0 -m = MathKerning(f.kerning, f.groups) - -print(m.items()) -print((m*1.0).items()) - - diff --git a/Tests/spReader_testdocs/superpolator_testdoc1.sp3 b/Tests/spReader_testdocs/superpolator_testdoc1.sp3 deleted file mode 100644 index 7f4a599..0000000 --- a/Tests/spReader_testdocs/superpolator_testdoc1.sp3 +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tests/spReader_testdocs/superpolator_testdoc1_converted.designspace b/Tests/spReader_testdocs/superpolator_testdoc1_converted.designspace deleted file mode 100644 index 560b3af..0000000 --- a/Tests/spReader_testdocs/superpolator_testdoc1_converted.designspace +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - com.letterror.skateboard.interactionSources - - horizontal - - width - - ignore - - vertical - - weight - - - com.letterror.skateboard.mutedSources - - - ufo/MutatorSansLightCondensed.ufo - foreground - - - ufo/MutatorSansBoldCondensed.ufo - foreground - - - com.letterror.skateboard.previewText - VA - com.superpolator.data - - expandRules - - horizontalPreviewAxis - width - includeLegacyRules - - instancefolder - instances - keepWorkFiles - - lineInverted - - lineStacked - lined - lineViewFilled - - outputFormatUFO - 3.0 - previewtext - VA - roundGeometry - - verticalPreviewAxis - weight - - - - diff --git a/Tests/tests.py b/Tests/tests.py index 142769b..36af9a2 100644 --- a/Tests/tests.py +++ b/Tests/tests.py @@ -1,33 +1,21 @@ # standalone test import shutil import os -import defcon.objects.font -import fontParts.fontshell.font +#from defcon.objects.font import Font import logging from ufoProcessor import * +import fontParts.fontshell # new place for ufoProcessor tests. # Run in regular python of choice, not ready for pytest just yet. # You may ask "why not?" - you may ask indeed. -# make the tests w ork with defcon as well as fontparts - -def addExtraGlyph(font, name, s=200): - font.newGlyph(name) - g = font[name] - p = g.getPen() - p.moveTo((0,0)) - p.lineTo((s,0)) - p.lineTo((s,s)) - p.lineTo((0,s)) - p.closePath() - g.width = s +# Now based on fontParts. def addGlyphs(font, s, addSupportLayer=True): # we need to add the glyphs step = 0 - uni = 95 for n in ['glyphOne', 'glyphTwo', 'glyphThree', 'glyphFour', 'glyphFive']: font.newGlyph(n) g = font[n] @@ -39,8 +27,6 @@ def addGlyphs(font, s, addSupportLayer=True): p.closePath() g.move((0,s+step)) g.width = s - g.unicode = uni - uni += 1 step += 50 for n, w in [('wide', 800), ('narrow', 100)]: font.newGlyph(n) @@ -52,15 +38,12 @@ 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) + g.appendAnchor("top", (0, w)) if addSupportLayer: font.newLayer('support') - layer = font.layers['support'] + print(n for n in font.layers if n.name == 'support') + layer = font.getLayer('support') layer.newGlyph('glyphFive') layer.newGlyph('glyphOne') # add an empty glyph to see how it is treated lg = layer['glyphFive'] @@ -76,17 +59,19 @@ def addGlyphs(font, s, addSupportLayer=True): font.newGlyph("wide.component") g = font["wide.component"] - comp = g.instantiateComponent() - comp.baseGlyph = "wide" - comp.offset = (0,0) - g.appendComponent(comp) + g.appendComponent("wide", offset=(0,0)) + #comp = g.instantiateComponent() + #comp.baseGlyph = "wide" + #comp.offset = (0,0) + #g.appendComponent(comp) g.width = font['wide'].width font.newGlyph("narrow.component") g = font["narrow.component"] - comp = g.instantiateComponent() - comp.baseGlyph = "narrow" - comp.offset = (0,0) - g.appendComponent(comp) + g.appendComponent("narrow", offset=(0,0)) + #comp = g.instantiateComponent() + #comp.baseGlyph = "narrow" + #comp.offset = (0,0) + #g.appendComponent(comp) g.width = font['narrow'].width uniValue = 200 for g in font: @@ -99,16 +84,6 @@ def fillInfo(font): font.info.ascender = 800 font.info.descender = -200 -def _create_parent_dir(ufo_path): - """ - Creates the parent directory where the UFO will be saved, in case it - doesn't exist already. This is required because fontTools.ufoLib no - longer calls os.makedirs. - """ - directory = os.path.dirname(os.path.normpath(ufo_path)) - if directory and not os.path.exists(directory): - os.makedirs(directory) - def _makeTestFonts(rootPath): """ Make some test fonts that have the kerning problem.""" path1 = os.path.join(rootPath, "masters", "geometryMaster1.ufo") @@ -116,43 +91,26 @@ 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") - path6 = os.path.join(rootPath, "instances", "extrapolate", "geometryInstance%s.ufo") - f1 = Font() + 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) - addExtraGlyph(f1, "extra.glyph.for.neutral") f1.features.text = u"# features text from master 1" - f2 = Font() + f2 = fontParts.fontshell.RFont() fillInfo(f2) addGlyphs(f2, 500, addSupportLayer=True) - addExtraGlyph(f2, "extra.glyph.for.master2") f2.features.text = u"# features text from master 2" f1.info.ascender = 400 f1.info.descender = -200 - f1.info.xHeight = 200 - f1.info.capHeight = 400 f2.info.ascender = 600 f2.info.descender = -100 - f2.info.xHeight = 200 - f2.info.capHeight = 600 f1.info.copyright = u"This is the copyright notice from master 1" f2.info.copyright = u"This is the copyright notice from master 2" f1.lib['ufoProcessor.test.lib.entry'] = "Lib entry for master 1" f2.lib['ufoProcessor.test.lib.entry'] = "Lib entry for master 2" - - f1.info.postscriptBlueValues = [100, 110] - f2.info.postscriptBlueValues = [120, 125] - f1.info.postscriptBlueFuzz = 0 - f2.info.postscriptBlueFuzz = 1 - f1.info.postscriptBlueScale = 0.11 # should not round - f1.info.postscriptBlueScale = 0.22 - - f1.info.openTypeHheaAscender = 1036 - f1.info.openTypeHheaDescender = -335 - f1.info.openTypeOS2TypoAscender = 730 - f1.info.openTypeOS2TypoDescender = -270 - f1.info.openTypeOS2WinAscent = 1036 - f1.info.openTypeOS2WinDescent = 335 f1.groups["public.kern1.groupA"] = ['glyphOne', 'glyphTwo'] f1.groups["public.kern2.groupB"] = ['glyphThree', 'glyphFour'] @@ -170,17 +128,15 @@ def _makeTestFonts(rootPath): f2.kerning[('glyphOne', 'glyphFour')] = 0 print([l.name for l in f1.layers], [l.name for l in f2.layers]) - _create_parent_dir(path1) - _create_parent_dir(path2) f1.save(path1, 3) f2.save(path2, 3) - return path1, path2, path3, path4, path5, path6 + return path1, path2, path3, path4, path5 def _makeSwapFonts(rootPath): """ Make some test fonts that have the kerning problem.""" path1 = os.path.join(rootPath, "Swap.ufo") path2 = os.path.join(rootPath, "Swapped.ufo") - f1 = Font() + f1 = fontParts.fontshell.RFont() fillInfo(f1) addGlyphs(f1, 100) f1.features.text = u"# features text from master 1" @@ -191,40 +147,27 @@ def _makeSwapFonts(rootPath): f1.save(path1, 2) return path1, path2 -class DesignSpaceProcessor_using_defcon(DesignSpaceProcessor): - def _instantiateFont(self, path): - return defcon.objects.font.Font(path) - -class DesignSpaceProcessor_using_fontparts(DesignSpaceProcessor): - def _instantiateFont(self, path): - return fontParts.fontshell.font.RFont(path) - -def _makeTestDocument(docPath, useVarlib=True, useDefcon=True): +def _makeTestDocument(docPath, useVarlib=True): # make the test fonts and a test document if useVarlib: extension = "varlib" else: extension = "mutator" - testFontPath = os.path.join(os.path.dirname(docPath), "automatic_testfonts_%s" % extension) - print("\ttestFontPath:", testFontPath) - m1, m2, i1, anisotropicInstancePath1, anisotropicInstancePath2, extrapolatePath = _makeTestFonts(testFontPath) - if useDefcon: - d = DesignSpaceProcessor_using_defcon(useVarlib=useVarlib) - else: - d = DesignSpaceProcessor_using_fontparts(useVarlib=useVarlib) - print("\td", d, type(d)) + testFontPath = os.path.join(os.getcwd(), "automatic_testfonts_%s" % extension) + m1, m2, i1, anisotropicInstancePath1, anisotropicInstancePath2 = _makeTestFonts(testFontPath) + d = DesignSpaceProcessor(useVarlib=useVarlib) a = AxisDescriptor() a.name = "pop" a.minimum = 0 a.maximum = 1000 a.default = 0 a.tag = "pop*" - a.map = [(0,10),(500,250),(1000,990)] + a.map = [(0,0),(500,250),(1000,1000)] d.addAxis(a) s1 = SourceDescriptor() s1.path = m1 - s1.location = dict(pop=a.map_forward(a.default)) + s1.location = dict(pop=a.default) s1.name = "test.master.1" s1.copyInfo = True s1.copyFeatures = True @@ -235,7 +178,6 @@ def _makeTestDocument(docPath, useVarlib=True, useDefcon=True): s2.path = m2 s2.location = dict(pop=1000) s2.name = "test.master.2" - s2.muteKerning = True d.addSource(s2) s3 = SourceDescriptor() @@ -245,27 +187,10 @@ def _makeTestDocument(docPath, useVarlib=True, useDefcon=True): s3.layerName = "support" d.addSource(s3) - s4 = SourceDescriptor() - s4.path = "missing.ufo" - s4.location = dict(pop=600) - s4.name = "test.missing.master" - d.addSource(s4) - - s5 = SourceDescriptor() - s5.path = m2 - s5.location = dict(pop=620) - s5.name = "test.existing.ufo_missing.layer" - s5.layerName = "missing.layer" - d.addSource(s5) - d.findDefault() - # make sure the default location is bend and unbend as we want. - assert d.newDefaultLocation().get('pop') == 0 - assert d.newDefaultLocation(bend=True).get('pop') == 10 - - steps = 6 - for counter in range(steps): - factor = counter / steps + + for counter in range(3): + factor = counter / 2 i = InstanceDescriptor() v = a.minimum+factor*(a.maximum-a.minimum) i.path = i1 % v @@ -275,7 +200,6 @@ def _makeTestDocument(docPath, useVarlib=True, useDefcon=True): i.location = dict(pop=v) i.info = True i.kerning = True - i.postScriptFontName = "TestFamily PSName %s" % i.styleName if counter == 2: i.glyphs['glyphTwo'] = dict(name="glyphTwo", mute=True) i.copyLib = True @@ -283,20 +207,9 @@ def _makeTestDocument(docPath, useVarlib=True, useDefcon=True): i.glyphs['narrow'] = dict(instanceLocation=dict(pop=400), unicodes=[0x123, 0x124, 0x125]) d.addInstance(i) - # add extrapolatiing location - i = InstanceDescriptor() - i.path = extrapolatePath % "TestStyle_Extrapolate" - print('i.path', i.path) - i.familyName = "TestFamily" - i.styleName = "TestStyle_Extrapolate" - i.name = "%s-%s" % (i.familyName, i.styleName) - i.location = dict(pop=3000) - i.info = True - i.kerning = True - d.addInstance(i) - # add anisotropic locations i = InstanceDescriptor() + v = a.minimum+0.5*(a.maximum-a.minimum) i.path = anisotropicInstancePath1 i.familyName = "TestFamily" i.styleName = "TestStyle_pop_anisotropic1" @@ -307,6 +220,7 @@ def _makeTestDocument(docPath, useVarlib=True, useDefcon=True): d.addInstance(i) i = InstanceDescriptor() + v = a.minimum+0.5*(a.maximum-a.minimum) i.path = anisotropicInstancePath2 i.familyName = "TestFamily" i.styleName = "TestStyle_pop_anisotropic2" @@ -317,107 +231,19 @@ def _makeTestDocument(docPath, useVarlib=True, useDefcon=True): d.addInstance(i) # add data to the document lib - d.lib['ufoprocessor.testdata'] = dict(pop=500, name="This is a named location, stored in the document lib.") + d.lib['ufoprocessor.testdata'] = dict(width=500, weight=500, name="This is a named location, stored in the document lib.") d.write(docPath) -def _testGenerateInstances(docPath, useVarlib=True, useDefcon=True, roundGeometry=False): +def _testGenerateInstances(docPath, useVarlib=True): # execute the test document - if useDefcon: - d = DesignSpaceProcessor_using_defcon(useVarlib=useVarlib) - else: - d = DesignSpaceProcessor_using_fontparts(useVarlib=useVarlib) + d = DesignSpaceProcessor(useVarlib=useVarlib) d.read(docPath) - d.loadFonts() - print('---', d.newDefaultLocation()) - d.roundGeometry = roundGeometry - objectFlavor = [type(f).__name__ for f in d.fonts.values()][0] - print("objectFlavor", objectFlavor) d.generateUFO() if d.problems: - print("log:") for p in d.problems: print("\t",p) -def testSwap(docPath): - srcPath, dstPath = _makeSwapFonts(os.path.dirname(docPath)) - f = Font(srcPath) - swapGlyphNames(f, "narrow", "wide") - f.info.styleName = "Swapped" - f.save(dstPath) - # test the results in newly opened fonts - old = Font(srcPath) - new = Font(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 - assert old['narrow'].width == new['wide'].width - assert old['wide'].width == new['narrow'].width - # The following test may be a bit counterintuitive: - # the rule swaps the glyphs, but we do not want glyphs that are not - # specifically affected by the rule to *appear* any different. - # 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) - - a = AxisDescriptor() - a.name = "pop" - a.minimum = 0 - a.maximum = 1000 - a.default = 0 - a.tag = "pop*" - d.addAxis(a) - - a = AxisDescriptor() - a.name = "snap" - a.minimum = 100 - a.maximum = 200 - a.default = 150 - a.tag = "snap" - d.addAxis(a) - - a = AxisDescriptor() - a.name = "crackle" - a.minimum = -1 - a.maximum = 1 - a.default = 0 - a.tag = "krak" - d.addAxis(a) - - shouldIgnore, loc = d.filterThisLocation(dict(snap=150, crackle=0, pop=0), []) - assert shouldIgnore == False - assert loc == {'snap': 150, 'crackle': 0, 'pop': 0} - - shouldIgnore, loc = d.filterThisLocation(dict(snap=150, crackle=0, pop=0), ['pop']) - assert shouldIgnore == False - assert loc == {'snap': 150, 'crackle': 0} - - shouldIgnore, loc = d.filterThisLocation(dict(snap=150, crackle=0, pop=1), ['pop']) - assert shouldIgnore == True - assert loc == {'snap': 150, 'crackle': 0} - - shouldIgnore, loc = d.filterThisLocation(dict(snap=150, crackle=0, pop=0), ['pop', 'crackle']) - assert shouldIgnore == False - assert loc == {'snap': 150} - - shouldIgnore, loc = d.filterThisLocation(dict(snap=150, crackle=0, pop=1), ['pop', 'crackle', 'snap']) - assert shouldIgnore == True - assert loc == {} - - shouldIgnore, loc = d.filterThisLocation(dict(snap=150, crackle=0, pop=0), ['one', 'two', 'three']) - assert shouldIgnore == False - assert loc == {'snap': 150, 'crackle': 0, 'pop': 0} - - shouldIgnore, loc = d.filterThisLocation(dict(snap=150, crackle=0, pop=1), ['one', 'two', 'three']) - assert shouldIgnore == False - assert loc == {'snap': 150, 'crackle': 0, 'pop': 1} - def testUnicodes(docPath, useVarlib=True): # after executing testSwap there should be some test fonts # let's check if the unicode values for glyph "narrow" arrive at the right place. @@ -425,46 +251,28 @@ def testUnicodes(docPath, useVarlib=True): d.read(docPath) for instance in d.instances: if os.path.exists(instance.path): - f = 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": - assert f['narrow'].unicodes == [291, 292, 293] - else: - assert f['narrow'].unicodes == [207] + #if instance.name == "TestFamily-TestStyle_pop1000.000": + # assert f['narrow'].unicodes == [291, 292, 293] + #else: + # #assert f['narrow'].unicodes == [207] else: print("Missing test font at %s" % instance.path) selfTest = True if selfTest: - for extension in ['mutator', 'varlib']: - for objectFlavor in ['defcon', 'fontparts']: - for roundGeometry in [True, False]: - # which object model to use for **executuing** the designspace. - # all the objects in **this test** are defcon. - - print("\n\nRunning the test with ", extension, "and", objectFlavor, "roundGeometry:", roundGeometry) - print("-"*40) - USEVARLIBMODEL = extension == 'varlib' - if roundGeometry: - roundingTag = "_rounded_geometry" - else: - roundingTag = "" - testRoot = os.path.join(os.getcwd(), "automatic_testfonts_%s_%s%s" % (extension, objectFlavor, roundingTag)) - print("\ttestRoot", testRoot) - if os.path.exists(testRoot): - shutil.rmtree(testRoot) - docPath = os.path.join(testRoot, "automatic_test.designspace") - print("\tdocPath", docPath) - print("-"*40) - print("Generate document, masters") - _makeTestDocument(docPath, useVarlib=USEVARLIBMODEL, useDefcon=objectFlavor=="defcon") - print("-"*40) - print("Generate instances", docPath) - _testGenerateInstances(docPath, useVarlib=USEVARLIBMODEL, useDefcon=objectFlavor=="defcon", roundGeometry=roundGeometry) - testSwap(docPath) - #_makeTestDocument(docPath, useVarlib=USEVARLIBMODEL, useDefcon=objectFlavor=="defcon") - #_testGenerateInstances(docPath, useVarlib=USEVARLIBMODEL, useDefcon=objectFlavor=="defcon") - - -testAxisMuting() + for extension in ['varlib', 'mutator']: + print("\n\n", extension) + USEVARLIBMODEL = extension == 'varlib' + testRoot = os.path.join(os.getcwd(), "automatic_testfonts_%s" % extension) + if os.path.exists(testRoot): + shutil.rmtree(testRoot) + docPath = os.path.join(testRoot, "automatic_test.designspace") + _makeTestDocument(docPath, useVarlib=USEVARLIBMODEL) + _testGenerateInstances(docPath, useVarlib=USEVARLIBMODEL) + + _makeTestDocument(docPath, useVarlib=USEVARLIBMODEL) + _testGenerateInstances(docPath, useVarlib=USEVARLIBMODEL) + testUnicodes(docPath, useVarlib=USEVARLIBMODEL) diff --git a/Tests/tests_fp.py b/Tests/tests_fp.py deleted file mode 100644 index b882898..0000000 --- a/Tests/tests_fp.py +++ /dev/null @@ -1,303 +0,0 @@ -# standalone test -import shutil -import os -#from defcon.objects.font import Font -import logging -from ufoProcessor import * -import fontParts.fontshell - - -# new place for ufoProcessor tests. -# Run in regular python of choice, not ready for pytest just yet. -# You may ask "why not?" - you may ask indeed. - -# Now based on fontParts. - -def addGlyphs(font, s, addSupportLayer=True): - # we need to add the glyphs - step = 0 - for n in ['glyphOne', 'glyphTwo', 'glyphThree', 'glyphFour', 'glyphFive']: - font.newGlyph(n) - g = font[n] - p = g.getPen() - p.moveTo((0,0)) - p.lineTo((s,0)) - p.lineTo((s,s)) - p.lineTo((0,s)) - p.closePath() - g.move((0,s+step)) - g.width = s - step += 50 - for n, w in [('wide', 800), ('narrow', 100)]: - font.newGlyph(n) - g = font[n] - p = g.getPen() - p.moveTo((0,0)) - p.lineTo((w,0)) - p.lineTo((w,font.info.ascender)) - 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.getLayer('support') - layer.newGlyph('glyphFive') - layer.newGlyph('glyphOne') # add an empty glyph to see how it is treated - lg = layer['glyphFive'] - p = lg.getPen() - w = 10 - y = -400 - p.moveTo((0,y)) - p.lineTo((s,y)) - p.lineTo((s,y+100)) - p.lineTo((0,y+100)) - p.closePath() - lg.width = s - - font.newGlyph("wide.component") - g = font["wide.component"] - g.appendComponent("wide", offset=(0,0)) - #comp = g.instantiateComponent() - #comp.baseGlyph = "wide" - #comp.offset = (0,0) - #g.appendComponent(comp) - g.width = font['wide'].width - font.newGlyph("narrow.component") - g = font["narrow.component"] - g.appendComponent("narrow", offset=(0,0)) - #comp = g.instantiateComponent() - #comp.baseGlyph = "narrow" - #comp.offset = (0,0) - #g.appendComponent(comp) - g.width = font['narrow'].width - uniValue = 200 - for g in font: - g.unicode = uniValue - uniValue += 1 - - -def fillInfo(font): - font.info.unitsPerEm = 1000 - font.info.ascender = 800 - font.info.descender = -200 - -def _makeTestFonts(rootPath): - """ Make some test fonts that have the kerning problem.""" - path1 = os.path.join(rootPath, "masters", "geometryMaster1.ufo") - path2 = os.path.join(rootPath, "masters", "geometryMaster2.ufo") - 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) - f1.features.text = u"# features text from master 1" - f2 = fontParts.fontshell.RFont() - fillInfo(f2) - addGlyphs(f2, 500, addSupportLayer=True) - f2.features.text = u"# features text from master 2" - f1.info.ascender = 400 - f1.info.descender = -200 - f2.info.ascender = 600 - f2.info.descender = -100 - f1.info.copyright = u"This is the copyright notice from master 1" - f2.info.copyright = u"This is the copyright notice from master 2" - f1.lib['ufoProcessor.test.lib.entry'] = "Lib entry for master 1" - f2.lib['ufoProcessor.test.lib.entry'] = "Lib entry for master 2" - - f1.groups["public.kern1.groupA"] = ['glyphOne', 'glyphTwo'] - f1.groups["public.kern2.groupB"] = ['glyphThree', 'glyphFour'] - f2.groups.update(f1.groups) - - f1.kerning[('public.kern1.groupA', 'public.kern2.groupB')] = -100 - f2.kerning[('public.kern1.groupA', 'public.kern2.groupB')] = -200 - - f1.kerning[('glyphOne', 'glyphOne')] = -100 - f2.kerning[('glyphOne', 'glyphOne')] = 0 - f1.kerning[('glyphOne', 'glyphThree')] = 10 - f1.kerning[('glyphOne', 'glyphFour')] = 10 - # exception - f2.kerning[('glyphOne', 'glyphThree')] = 1 - f2.kerning[('glyphOne', 'glyphFour')] = 0 - print([l.name for l in f1.layers], [l.name for l in f2.layers]) - - f1.save(path1, 3) - f2.save(path2, 3) - return path1, path2, path3, path4, path5 - -def _makeSwapFonts(rootPath): - """ Make some test fonts that have the kerning problem.""" - path1 = os.path.join(rootPath, "Swap.ufo") - path2 = os.path.join(rootPath, "Swapped.ufo") - f1 = fontParts.fontshell.RFont() - fillInfo(f1) - addGlyphs(f1, 100) - f1.features.text = u"# features text from master 1" - f1.info.ascender = 800 - f1.info.descender = -200 - f1.kerning[('glyphOne', 'glyphOne')] = -10 - f1.kerning[('glyphTwo', 'glyphTwo')] = 10 - f1.save(path1, 2) - return path1, path2 - -def _makeTestDocument(docPath, useVarlib=True): - # make the test fonts and a test document - if useVarlib: - extension = "varlib" - else: - extension = "mutator" - testFontPath = os.path.join(os.getcwd(), "automatic_testfonts_%s" % extension) - m1, m2, i1, anisotropicInstancePath1, anisotropicInstancePath2 = _makeTestFonts(testFontPath) - d = DesignSpaceProcessor(useVarlib=useVarlib) - a = AxisDescriptor() - a.name = "pop" - a.minimum = 0 - a.maximum = 1000 - a.default = 0 - a.tag = "pop*" - a.map = [(0,0),(500,250),(1000,1000)] - d.addAxis(a) - - s1 = SourceDescriptor() - s1.path = m1 - s1.location = dict(pop=a.default) - s1.name = "test.master.1" - s1.copyInfo = True - s1.copyFeatures = True - s1.copyLib = True - d.addSource(s1) - - s2 = SourceDescriptor() - s2.path = m2 - s2.location = dict(pop=1000) - s2.name = "test.master.2" - d.addSource(s2) - - s3 = SourceDescriptor() - s3.path = m2 - s3.location = dict(pop=500) - s3.name = "test.master.support.1" - s3.layerName = "support" - d.addSource(s3) - - d.findDefault() - - for counter in range(3): - factor = counter / 2 - i = InstanceDescriptor() - v = a.minimum+factor*(a.maximum-a.minimum) - i.path = i1 % v - i.familyName = "TestFamily" - i.styleName = "TestStyle_pop%3.3f" % (v) - i.name = "%s-%s" % (i.familyName, i.styleName) - i.location = dict(pop=v) - i.info = True - i.kerning = True - if counter == 2: - i.glyphs['glyphTwo'] = dict(name="glyphTwo", mute=True) - i.copyLib = True - if counter == 2: - i.glyphs['narrow'] = dict(instanceLocation=dict(pop=400), unicodes=[0x123, 0x124, 0x125]) - d.addInstance(i) - - # add anisotropic locations - i = InstanceDescriptor() - v = a.minimum+0.5*(a.maximum-a.minimum) - i.path = anisotropicInstancePath1 - i.familyName = "TestFamily" - i.styleName = "TestStyle_pop_anisotropic1" - i.name = "%s-%s" % (i.familyName, i.styleName) - i.location = dict(pop=(1000, 0)) - i.info = True - i.kerning = True - d.addInstance(i) - - i = InstanceDescriptor() - v = a.minimum+0.5*(a.maximum-a.minimum) - i.path = anisotropicInstancePath2 - i.familyName = "TestFamily" - i.styleName = "TestStyle_pop_anisotropic2" - i.name = "%s-%s" % (i.familyName, i.styleName) - i.location = dict(pop=(0, 1000)) - i.info = True - i.kerning = True - d.addInstance(i) - - # add data to the document lib - d.lib['ufoprocessor.testdata'] = dict(width=500, weight=500, name="This is a named location, stored in the document lib.") - - d.write(docPath) - -def _testGenerateInstances(docPath, useVarlib=True): - # execute the test document - d = DesignSpaceProcessor(useVarlib=useVarlib) - d.read(docPath) - d.generateUFO() - if d.problems: - for p in d.problems: - print("\t",p) - -def testSwap(docPath): - srcPath, dstPath = _makeSwapFonts(os.path.dirname(docPath)) - 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.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 - assert old['narrow'].width == new['wide'].width - assert old['wide'].width == new['narrow'].width - # The following test may be a bit counterintuitive: - # the rule swaps the glyphs, but we do not want glyphs that are not - # specifically affected by the rule to *appear* any different. - # 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 - # let's check if the unicode values for glyph "narrow" arrive at the right place. - d = DesignSpaceProcessor(useVarlib=useVarlib) - d.read(docPath) - for instance in d.instances: - if os.path.exists(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": - # assert f['narrow'].unicodes == [291, 292, 293] - #else: - # #assert f['narrow'].unicodes == [207] - else: - print("Missing test font at %s" % instance.path) - -selfTest = True -if selfTest: - for extension in ['varlib', 'mutator']: - print("\n\n", extension) - USEVARLIBMODEL = extension == 'varlib' - testRoot = os.path.join(os.getcwd(), "automatic_testfonts_%s" % extension) - if os.path.exists(testRoot): - shutil.rmtree(testRoot) - docPath = os.path.join(testRoot, "automatic_test.designspace") - _makeTestDocument(docPath, useVarlib=USEVARLIBMODEL) - _testGenerateInstances(docPath, useVarlib=USEVARLIBMODEL) - - testSwap(docPath) - _makeTestDocument(docPath, useVarlib=USEVARLIBMODEL) - _testGenerateInstances(docPath, useVarlib=USEVARLIBMODEL) - testUnicodes(docPath, useVarlib=USEVARLIBMODEL) diff --git a/sp3.py b/sp3.py deleted file mode 100644 index 46ca167..0000000 --- a/sp3.py +++ /dev/null @@ -1,494 +0,0 @@ -import os -import glob - -from fontTools.misc.loggingTools import LogMixin -from fontTools.designspaceLib import DesignSpaceDocument, AxisDescriptor, SourceDescriptor, RuleDescriptor, InstanceDescriptor - -try: - import xml.etree.cElementTree as ET -except ImportError: - import xml.etree.ElementTree as ET - -# Reader that parses Superpolator documents and buidls designspace objects. -# Note: the Superpolator document format precedes the designspace documnt format. -# For now I just want to migrate data out of Superpolator into designspace. -# So not all data will migrate, just the stuff we can use. - -""" - - - - - com.letterror.skateboard.interactionSources - - horizontal - - ignore - - vertical - - - com.letterror.skateboard.mutedSources - - - IBM Plex Sans Condensed-Bold.ufo - foreground - - - com.letterror.skateboard.previewLocation - - weight - 0.0 - - com.letterror.skateboard.previewText - SKATE - - - - - -""" - -superpolatorDataLibKey = "com.superpolator.data" # lib key for Sp data in .designspace -skateboardInteractionSourcesKey = "com.letterror.skateboard.interactionSources" -skateboardMutedSourcesKey = "com.letterror.skateboard.mutedSources" -skipExportKey = "public.skipExportGlyphs" -skateboardPreviewLocationsKey = "com.letterror.skateboard.previewLocation" -skateboardPreviewTextKey = "com.letterror.skateboard.previewText" - -class SuperpolatorReader(LogMixin): - ruleDescriptorClass = RuleDescriptor - axisDescriptorClass = AxisDescriptor - sourceDescriptorClass = SourceDescriptor - instanceDescriptorClass = InstanceDescriptor - - def __init__(self, documentPath, documentObject, convertRules=True, convertData=True, anisotropic=False): - self.path = documentPath - self.documentObject = documentObject - self.convertRules = convertRules - self.convertData = convertData - self.allowAnisotropic = anisotropic # maybe add conversion options later - tree = ET.parse(self.path) - self.root = tree.getroot() - self.documentObject.formatVersion = self.root.attrib.get("format", "3.0") - self.axisDefaults = {} - self._strictAxisNames = True - - - @classmethod - def fromstring(cls, string, documentObject): - f = BytesIO(tobytes(string, encoding="utf-8")) - self = cls(f, documentObject) - self.path = None - return self - - def read(self): - self.readAxes() - if self.convertData: - self.readData() - if self.convertRules: - self.readOldRules() - self.readSimpleRules() - self.readSources() - self.readInstances() - - def readData(self): - # read superpolator specific data, view prefs etc. - # if possible convert it to skateboard - interactionSources = {'horizontal': [], 'vertical': [], 'ignore': []} - ignoreElements = self.root.findall(".ignore") - ignoreGlyphs = [] - for ignoreElement in ignoreElements: - names = ignoreElement.attrib.get('glyphs') - if names: - ignoreGlyphs = names.split(",") - if ignoreGlyphs: - self.documentObject.lib[skipExportKey] = ignoreGlyphs - dataElements = self.root.findall(".data") - if not dataElements: - return - newLib = {} - interactionSourcesAdded = False - for dataElement in dataElements: - name = dataElement.attrib.get('name') - value = dataElement.attrib.get('value') - if value in ['True', 'False']: - value = value == "True" - else: - try: - value = float(value) - except ValueError: - pass - if name == "previewtext": - self.documentObject.lib[skateboardPreviewTextKey] = value - elif name == "horizontalPreviewAxis": - interactionSources['horizontal'].append(value) - interactionSourcesAdded = True - elif name == "verticalPreviewAxis": - interactionSources['vertical'].append(value) - interactionSourcesAdded = True - - newLib[name] = value - if interactionSourcesAdded: - self.documentObject.lib[skateboardInteractionSourcesKey] = interactionSources - if newLib: - self.documentObject.lib[superpolatorDataLibKey] = newLib - - - def readOldRules(self): - # read the old rules - # - # - # - - # superpolator old rule to simple rule - # if op in ['<', '<=']: - # # old style data - # axes[axisName]['maximum'] = conditionDict['values'] - # newRule.name = "converted %s < and <= "%(axisName) - # elif op in ['>', '>=']: - # # old style data - # axes[axisName]['minimum'] = conditionDict['values'] - # newRule.name = "converted %s > and >= "%(axisName) - # elif op == "==": - # axes[axisName]['maximum'] = conditionDict['values'] - # axes[axisName]['minimum'] = conditionDict['values'] - # newRule.name = "converted %s == "%(axisName) - # newRule.enabled = False - # elif op == "!=": - # axes[axisName]['maximum'] = conditionDict['values'] - # axes[axisName]['minimum'] = conditionDict['values'] - # newRule.name = "unsupported %s != "%(axisName) - # newRule.enabled = False - # else: - # axes[axisName]['maximum'] = conditionDict['minimum'] - # axes[axisName]['minimum'] = conditionDict['maximum'] - # newRule.name = "minmax legacy rule for %s"%axisName - # newRule.enabled = False - - rules = [] - for oldRuleElement in self.root.findall(".rule"): - ruleObject = self.ruleDescriptorClass() - # only one condition set in these old rules - cds = [] - a = oldRuleElement.attrib['resultfalse'] - b = oldRuleElement.attrib['resulttrue'] - ruleObject.subs.append((a,b)) - for oldConditionElement in oldRuleElement.findall(".condition"): - cd = {} - operator = oldConditionElement.attrib['operator'] - axisValue = float(oldConditionElement.attrib['xvalue']) - axisName = oldConditionElement.attrib['axisname'] - if operator in ['<', '<=']: - cd['maximum'] = axisValue - cd['minimum'] = None - cd['name'] = axisName - ruleObject.name = "converted %s < and <= "%(axisName) - elif operator in ['>', '>=']: - cd['maximum'] = None - cd['minimum'] = axisValue - cd['name'] = axisName - ruleObject.name = "converted %s > and >= "%(axisName) - elif operator in ["==", "!="]: - # can't convert this one - continue - cds.append(cd) - if cds: - ruleObject.conditionSets.append(cds) - self.documentObject.addRule(ruleObject) - - def readSimpleRules(self): - # read the simple rule elements - # - # - # - # - # - # - # - - - rulesContainerElements = self.root.findall(".simplerules") - rules = [] - for rulesContainerElement in rulesContainerElements: - for ruleElement in rulesContainerElement: - ruleObject = self.ruleDescriptorClass() - ruleName = ruleObject.name = ruleElement.attrib['name'] - # subs - for subElement in ruleElement.findall('.sub'): - a = subElement.attrib['name'] - b = subElement.attrib['with'] - ruleObject.subs.append((a, b)) - # condition sets, .sp3 had none - externalConditions = self._readConditionElements( - ruleElement, - ruleName, - ) - if externalConditions: - ruleObject.conditionSets.append(externalConditions) - self.log.info( - "Found stray rule conditions outside a conditionset. " - "Wrapped them in a new conditionset." - ) - self.documentObject.addRule(ruleObject) - - def _readConditionElements(self, parentElement, ruleName=None): - # modified from the method from fonttools.designspaceLib - # it's not the same! - cds = [] - for conditionElement in parentElement.findall('.condition'): - cd = {} - cdMin = conditionElement.attrib.get("minimum") - if cdMin is not None: - cd['minimum'] = float(cdMin) - else: - # will allow these to be None, assume axis.minimum - cd['minimum'] = None - cdMax = conditionElement.attrib.get("maximum") - if cdMax is not None: - cd['maximum'] = float(cdMax) - else: - # will allow these to be None, assume axis.maximum - cd['maximum'] = None - cd['name'] = conditionElement.attrib.get("axisname") - # # test for things - if cd.get('minimum') is None and cd.get('maximum') is None: - raise DesignSpaceDocumentError( - "condition missing required minimum or maximum in rule" + - (" '%s'" % ruleName if ruleName is not None else "")) - cds.append(cd) - return cds - - def readAxes(self): - # read the axes elements, including the warp map. - axisElements = self.root.findall(".axis") - if not axisElements: - # raise error, we need axes - return - for axisElement in axisElements: - axisObject = self.axisDescriptorClass() - axisObject.name = axisElement.attrib.get("name") - axisObject.tag = axisElement.attrib.get("shortname") - axisObject.minimum = float(axisElement.attrib.get("minimum")) - axisObject.maximum = float(axisElement.attrib.get("maximum")) - axisObject.default = float(axisElement.attrib.get("initialvalue", axisObject.minimum)) - self.documentObject.axes.append(axisObject) - self.axisDefaults[axisObject.name] = axisObject.default - self.documentObject.defaultLoc = self.axisDefaults - - def colorFromElement(self, element): - elementColor = None - for colorElement in element.findall('.color'): - elementColor = self.readColorElement(colorElement) - - def readColorElement(self, colorElement): - pass - - def locationFromElement(self, element): - elementLocation = None - for locationElement in element.findall('.location'): - elementLocation = self.readLocationElement(locationElement) - break - if not self.allowAnisotropic: - # don't want any anisotropic values here - split = {} - for k, v in elementLocation.items(): - if type(v) == type(()): - split[k] = v[0] - else: - split[k] = v - elementLocation = split - return elementLocation - - def readLocationElement(self, locationElement): - """ Format 0 location reader """ - if self._strictAxisNames and not self.documentObject.axes: - raise DesignSpaceDocumentError("No axes defined") - loc = {} - for dimensionElement in locationElement.findall(".dimension"): - dimName = dimensionElement.attrib.get("name") - if self._strictAxisNames and dimName not in self.axisDefaults: - # In case the document contains no axis definitions, - self.log.warning("Location with undefined axis: \"%s\".", dimName) - continue - xValue = yValue = None - try: - xValue = dimensionElement.attrib.get('xvalue') - xValue = float(xValue) - except ValueError: - self.log.warning("KeyError in readLocation xValue %3.3f", xValue) - try: - yValue = dimensionElement.attrib.get('yvalue') - if yValue is not None: - yValue = float(yValue) - except ValueError: - pass - if yValue is not None: - loc[dimName] = (xValue, yValue) - else: - loc[dimName] = xValue - return loc - - def readSources(self): - for sourceCount, sourceElement in enumerate(self.root.findall(".master")): - filename = sourceElement.attrib.get('filename') - if filename is not None and self.path is not None: - sourcePath = os.path.abspath(os.path.join(os.path.dirname(self.path), filename)) - else: - sourcePath = None - sourceName = sourceElement.attrib.get('name') - if sourceName is None: - # add a temporary source name - sourceName = "temp_master.%d" % (sourceCount) - sourceObject = self.sourceDescriptorClass() - sourceObject.path = sourcePath # absolute path to the ufo source - sourceObject.filename = filename # path as it is stored in the document - sourceObject.name = sourceName - familyName = sourceElement.attrib.get("familyname") - if familyName is not None: - sourceObject.familyName = familyName - styleName = sourceElement.attrib.get("stylename") - if styleName is not None: - sourceObject.styleName = styleName - sourceObject.location = self.locationFromElement(sourceElement) - isMuted = False - for maskedElement in sourceElement.findall('.maskedfont'): - # mute isn't stored in the sourceDescriptor, but we can store it in the lib - if maskedElement.attrib.get('font') == "1": - isMuted = True - for libElement in sourceElement.findall('.provideLib'): - if libElement.attrib.get('state') == '1': - sourceObject.copyLib = True - for groupsElement in sourceElement.findall('.provideGroups'): - if groupsElement.attrib.get('state') == '1': - sourceObject.copyGroups = True - for infoElement in sourceElement.findall(".provideInfo"): - if infoElement.attrib.get('state') == '1': - sourceObject.copyInfo = True - for featuresElement in sourceElement.findall(".provideFeatures"): - if featuresElement.attrib.get('state') == '1': - sourceObject.copyFeatures = True - for glyphElement in sourceElement.findall(".glyph"): - glyphName = glyphElement.attrib.get('name') - if glyphName is None: - continue - if glyphElement.attrib.get('mute') == '1': - sourceObject.mutedGlyphNames.append(glyphName) - self.documentObject.sources.append(sourceObject) - if isMuted: - if not skateboardMutedSourcesKey in self.documentObject.lib: - self.documentObject.lib[skateboardMutedSourcesKey] = [] - item = (sourceObject.filename, "foreground") - self.documentObject.lib[skateboardMutedSourcesKey].append(item) - - def readInstances(self): - for instanceCount, instanceElement in enumerate(self.root.findall(".instance")): - instanceObject = self.instanceDescriptorClass() - if instanceElement.attrib.get("familyname"): - instanceObject.familyName = instanceElement.attrib.get("familyname") - if instanceElement.attrib.get("stylename"): - instanceObject.styleName = instanceElement.attrib.get("stylename") - if instanceElement.attrib.get("styleMapFamilyName"): - instanceObject.styleMapFamilyName = instanceElement.attrib.get("styleMapFamilyName") - if instanceElement.attrib.get("styleMapStyleName"): - instanceObject.styleMapStyleName = instanceElement.attrib.get("styleMapStyleName") - if instanceElement.attrib.get("styleMapFamilyName"): - instanceObject.styleMapFamilyName = instanceElement.attrib.get("styleMapFamilyName") - instanceObject.location = self.locationFromElement(instanceElement) - instanceObject.filename = instanceElement.attrib.get('filename') - for libElement in instanceElement.findall('.provideLib'): - if libElement.attrib.get('state') == '1': - instanceObject.lib = True - for libElement in instanceElement.findall('.provideInfo'): - if libElement.attrib.get('state') == '1': - instanceObject.info = True - self.documentObject.instances.append(instanceObject) - -def sp3_to_designspace(sp3path, designspacePath=None): - if designspacePath is None: - designspacePath = sp3path.replace(".sp3", ".designspace") - doc = DesignSpaceDocument() - reader = SuperpolatorReader(sp3path, doc) - reader.read() - doc.write(designspacePath) - - -if __name__ == "__main__": - - def test_superpolator_testdoc1(): - # read superpolator_testdoc1.sp3 - # and test all the values - testDoc = DesignSpaceDocument() - testPath = "../../Tests/spReader_testdocs/superpolator_testdoc1.sp3" - reader = SuperpolatorReader(testPath, testDoc) - reader.read() - - # check the axes - names = [a.name for a in reader.documentObject.axes] - names.sort() - assert names == ['grade', 'space', 'weight', 'width'] - tags = [a.tag for a in reader.documentObject.axes] - tags.sort() - assert tags == ['SPCE', 'grad', 'wdth', 'wght'] - - # check the data items - assert superpolatorDataLibKey in reader.documentObject.lib - items = list(reader.documentObject.lib[superpolatorDataLibKey].items()) - items.sort() - assert items == [('expandRules', False), ('horizontalPreviewAxis', 'width'), ('includeLegacyRules', False), ('instancefolder', 'instances'), ('keepWorkFiles', True), ('lineInverted', True), ('lineStacked', 'lined'), ('lineViewFilled', True), ('outputFormatUFO', 3.0), ('previewtext', 'VA'), ('roundGeometry', False), ('verticalPreviewAxis', 'weight')] - - # check the sources - for sd in reader.documentObject.sources: - assert sd.familyName == "MutatorMathTest_SourceFamilyName" - if sd.styleName == "Default": - assert sd.location == {'width': 0.0, 'weight': 0.0, 'space': 0.0, 'grade': -0.5} - assert sd.copyLib == True - assert sd.copyGroups == True - assert sd.copyInfo == True - assert sd.copyFeatures == True - elif sd.styleName == "TheOther": - assert sd.location == {'width': 0.0, 'weight': 1000.0, 'space': 0.0, 'grade': -0.5} - assert sd.copyLib == False - assert sd.copyGroups == False - assert sd.copyInfo == False - assert sd.copyFeatures == False - - # check the instances - for nd in reader.documentObject.instances: - assert nd.familyName == "MutatorMathTest_InstanceFamilyName" - if nd.styleName == "AWeightThatILike": - assert nd.location == {'width': 133.152174, 'weight': 723.981097, 'space': 0.0, 'grade': -0.5} - assert nd.filename == "instances/MutatorMathTest_InstanceFamilyName-AWeightThatILike.ufo" - assert nd.styleMapFamilyName == None - assert nd.styleMapStyleName == None - if nd.styleName == "wdth759.79_SPCE0.00_wght260.72": - # note the converted anisotropic location in the width axis. - assert nd.location == {'grade': -0.5, 'width': 500.0, 'weight': 260.7217, 'space': 0.0} - assert nd.filename == "instances/MutatorMathTest_InstanceFamilyName-wdth759.79_SPCE0.00_wght260.72.ufo" - assert nd.styleMapFamilyName == "StyleMappedFamily" - assert nd.styleMapStyleName == "bold" - - # check the rules - for rd in reader.documentObject.rules: - assert rd.name == "width: < 500.0" - assert len(rd.conditionSets) == 1 - assert rd.subs == [('I', 'I.narrow')] - for conditionSet in rd.conditionSets: - for cd in conditionSet: - if cd['name'] == "width": - assert cd == {'minimum': None, 'maximum': 500.0, 'name': 'width'} - if cd['name'] == "grade": - assert cd == {'minimum': 0.0, 'maximum': 500.0, 'name': 'grade'} - - - testDoc.write(testPath.replace(".sp3", "_output_roundtripped.designspace")) - - def test_testDocs(): - # read the test files and convert them - # no tests - root = "../../Tests/spReader_testdocs/test*.sp3" - for path in glob.glob(root): - sp3_to_designspace(path) - - test_superpolator_testdoc1() - #test_testDocs() \ No newline at end of file From 74bc1f872015a932f2f03ad60465224ee88af1e7 Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Fri, 18 Nov 2022 14:48:56 +0100 Subject: [PATCH 18/26] add ignore --- .gitignore | 2 ++ Tests/ds5/sources.jpg | Bin 0 -> 77822 bytes 2 files changed, 2 insertions(+) create mode 100644 Tests/ds5/sources.jpg diff --git a/.gitignore b/.gitignore index 24538b3..5772591 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ Tests/20190830 benders/Skateboard Previews /Tests/202206 discrete spaces/instances /Tests/202206 discrete spaces/instances_mutMath /Tests/202206 discrete spaces/instances_varlib +/_issues +/_old_stuff diff --git a/Tests/ds5/sources.jpg b/Tests/ds5/sources.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5b9857831e8f496bbed436373c510da63c4baf62 GIT binary patch literal 77822 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 literal 0 HcmV?d00001 From 5803c4d9d2aa8d24ab11db4bbf4506b0edefde80 Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Sun, 20 Nov 2022 10:17:12 +0100 Subject: [PATCH 19/26] WIP Restructure findDefault to work with discreteLocation. Support stuff for Batch --- Lib/ufoProcessor/__init__.py | 36 ++++++++++++++++---------- Lib/ufoProcessor/ufoProcessorSketch.py | 6 ++--- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index eb12296..f4c6009 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -359,7 +359,7 @@ def getInfoMutator(self, discreteLocation=None): """ Returns a info mutator """ infoItems = [] if discreteLocation is not None: - sources = self.findSourcesForDiscreteLocation(discreteLocation) + sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation) else: sources = self.sources for sourceDescriptor in sources: @@ -385,7 +385,7 @@ def getKerningMutator(self, pairs=None, discreteLocation=None): If pairs are given then query the sources for a value and make a mutator only with those values. """ if discreteLocation is not None: - sources = self.findSourcesForDiscreteLocation(discreteLocation) + sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation) else: sources = self.sources kerningItems = [] @@ -475,19 +475,19 @@ def getGlyphMutator(self, glyphName, @memoize def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, discreteLocation=None): - """ Return a glyph mutator.defaultLoc + """ Return a glyph mutator decomposeComponents = True causes the source glyphs to be decomposed first before building the mutator. That gives you instances that do not depend on a complete font. If you're calculating previews for instance. - XXX check glyphs in layers + findSourceDescriptorsForDiscreteLocation returns sources from layers as well """ items = [] empties = [] foundEmpty = False # if discreteLocation is not None: - sources = self.findSourcesForDiscreteLocation(discreteLocation) + sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation) else: sources = self.sources for sourceDescriptor in sources: @@ -599,7 +599,7 @@ def getNeutralFont(self): return self.fonts[sd.name] return None - def findDefault(self): + def findDefault(self, discreteLocation=None): """Set and return SourceDescriptor at the default location or None. The default location is the set of all `default` values in user space of all axes. """ @@ -607,9 +607,11 @@ def findDefault(self): # Convert the default location from user space to design space before comparing # it against the SourceDescriptor locations (always in design space). - default_location_design = self.newDefaultLocation(bend=True) + default_location_design = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) + print(f"findDefault for {discreteLocation}: {default_location_design}") for sourceDescriptor in self.sources: if sourceDescriptor.location == default_location_design: + print(f"findDefault found {sourceDescriptor.location} {default_location_design} {sourceDescriptor.location == default_location_design}") self.default = sourceDescriptor return sourceDescriptor @@ -617,18 +619,25 @@ def findDefault(self): def newDefaultLocation(self, bend=False, discreteLocation=None): # overwrite from fontTools.newDefaultLocation - # we do not want this default location to be mapped. + # we do not want this default location always to be mapped. loc = collections.OrderedDict() for axisDescriptor in self.axes: + axisName = axisDescriptor.name + axisValue = axisDescriptor.default if discreteLocation is not None: + # if we want to find the default for a specific discreteLoation + # we can not use the discrete axis' default value + # -> we have to use the value in the given discreteLocation if axisDescriptor.name in discreteLocation: - continue + axisValue = discreteLocation[axisDescriptor.name] + else: + axisValue = axisDescriptor.default if bend: - loc[axisDescriptor.name] = axisDescriptor.map_forward( - axisDescriptor.default + loc[axisName] = axisDescriptor.map_forward( + axisValue ) else: - loc[axisDescriptor.name] = axisDescriptor.default + loc[axisName] = axisValue return loc def updateFonts(self, fontObjects): @@ -956,8 +965,9 @@ def getDiscreteLocations(self): return discreteCoordinates @memoize - def findSourcesForDiscreteLocation(self, discreteLocDict): + def findSourceDescriptorsForDiscreteLocation(self, discreteLocDict): # return a list of all sourcedescriptors that share the values in the discrete loc tuple + # so this includes all sourcedescriptors that point to layers # discreteLocDict {'countedItems': 1.0, 'outlined': 0.0}, {'countedItems': 1.0, 'outlined': 1.0} sources = [] for s in self.sources: diff --git a/Lib/ufoProcessor/ufoProcessorSketch.py b/Lib/ufoProcessor/ufoProcessorSketch.py index 0bcbf00..29d1c33 100644 --- a/Lib/ufoProcessor/ufoProcessorSketch.py +++ b/Lib/ufoProcessor/ufoProcessorSketch.py @@ -35,11 +35,11 @@ def getGlyphMutator(self, glyphName, decomposeComponents=False, **discreteLocati @memoize def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, **discreteLocation): discreteLocation = self.buildDiscreteLocation(discreteLocation) - sources = self.findSourcesForDiscreteLocation(**discreteLocation) + sources = self.findSourceDescriptorsForDiscreteLocation(**discreteLocation) return [] @memoize - def findSourcesForDiscreteLocation(self, **discreteLocation): + def findSourceDescriptorsForDiscreteLocation(self, **discreteLocation): discreteLocation = self.buildDiscreteLocation(discreteLocation) sources = [] for source in self.sources: @@ -90,5 +90,5 @@ def glyphChanged(self, glyphName): print(d.getDiscreteDefaultLocation()) -print(d.findSourcesForDiscreteLocation(countedItems=1)) +print(d.findSourceDescriptorsForDiscreteLocation(countedItems=1)) print(d.getDiscreteLocations()) \ No newline at end of file From db40898dc3b25caa4c5cc4b74093f751ddf2861c Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Wed, 4 Jan 2023 21:46:56 +0100 Subject: [PATCH 20/26] WIP Refactoring This refactors UFOprocessor from subclassing from to wrapping a fonttools designspace object in the hopes to have some buffer between the projects. In this state ufoProcessor supports discrete axes: it will collect the right sources for the continuous bits and make glyphs, font info and kerning. Todo: all the testing. Also the live-reloading thing is not part of this. --- Lib/ufoProcessor/__init__.py | 16 +- Lib/ufoProcessor/logger.py | 78 ++++ Lib/ufoProcessor/refactor.py | 863 +++++++++++++++++++++++++++++++++++ Tests/ds5/ds5.designspace | 9 + Tests/ds5/ds5_makeTestDoc.py | 26 ++ 5 files changed, 986 insertions(+), 6 deletions(-) create mode 100644 Lib/ufoProcessor/logger.py create mode 100644 Lib/ufoProcessor/refactor.py diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index f4c6009..a8b3289 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -247,7 +247,7 @@ def hasDiscreteAxes(self): return True return False - def generateUFO(self, processRules=True, glyphNames=None, pairs=None, bend=False): + def generateUFO(self, processRules=True, glyphNames=None, pairs=None, bend=False, discreteLocation=None): # makes the instances # option to execute the rules # make sure we're not trying to overwrite a newer UFO format @@ -264,7 +264,9 @@ def generateUFO(self, processRules=True, glyphNames=None, pairs=None, bend=False processRules, glyphNames=glyphNames, pairs=pairs, - bend=bend) + bend=bend, + discreteLocation=discreteLocation + ) folder = os.path.dirname(os.path.abspath(instanceDescriptor.path)) path = instanceDescriptor.path if not os.path.exists(folder): @@ -693,6 +695,7 @@ def makeInstance(self, instanceDescriptor, if doRules is not None: warn('The doRules argument in DesignSpaceProcessor.makeInstance() is deprecated', DeprecationWarning, stacklevel=2) continuousLocation, discreteLocation = self.splitLocation(instanceDescriptor.location) + print('makeInstance', continuousLocation, discreteLocation) font = self._instantiateFont(None) # make fonty things here loc = Location(continuousLocation) @@ -781,9 +784,7 @@ def makeInstance(self, instanceDescriptor, if glyphMutator is None: self.problems.append("Could not make mutator for glyph %s" % (glyphName)) continue - #except: - # self.problems.append("Could not make mutator for glyph %s %s" % (glyphName, traceback.format_exc())) - # continue + glyphData = {} font.newGlyph(glyphName) font[glyphName].clear() @@ -972,6 +973,9 @@ def findSourceDescriptorsForDiscreteLocation(self, discreteLocDict): sources = [] for s in self.sources: ok = True + if discreteLocDict is None: + sources.append(s) + continue for name, value in discreteLocDict.items(): if name in s.location: if s.location[name] != value: @@ -1001,7 +1005,7 @@ def glyphChanged(self, glyphName): # this is because of how immutify does it. Could be different I suppose but this works if key[0] in ("getGlyphMutator", "collectSourcesForGlyph") and key[2][0][0] == glyphName: del _memoizeCache[key] - + if __name__ == "__main__": # while we're testing diff --git a/Lib/ufoProcessor/logger.py b/Lib/ufoProcessor/logger.py new file mode 100644 index 0000000..d530408 --- /dev/null +++ b/Lib/ufoProcessor/logger.py @@ -0,0 +1,78 @@ +import sys +import time +import os +import logging + +class Logger: + + def __init__(self, path, rootDirectory, nest=0): + self.path = path + self.rootDirectory = rootDirectory + self.nest = nest + if not nest: + if path is not None: + # if os.path.exists(path): + # os.remove(path) + if not os.path.exists(path): + f = open(path, "w") + f.close() + + def child(self, text=None): + logger = Logger( + self.path, + self.rootDirectory, + nest=self.nest + 1 + ) + if text: + logger.info(text) + return logger + + def relativePath(self, path): + return os.path.relpath(path, self.rootDirectory) + + def _makeText(self, text): + if self.nest: + text = f"{('| ' * self.nest).strip()} {text}" + return text + + def _toConsole(self, text): + print(text) + + def _toFile(self, text): + if self.path is None: + return + text += "\n" + f = open(self.path, "a") + f.write(text) + f.close() + + def time(self, prefix=None): + now = time.strftime("%Y-%m-%d %H:%M") + if prefix: + now = prefix + " " + now + self.info(now) + + def info(self, text): + text = self._makeText(text) + self._toConsole(text) + self._toFile(text) + + def infoItem(self, text): + text = f"\t- {text}" + self.info(text) + + def infoPath(self, path): + text = self.relativePath(path) + self.infoItem(text) + + def detail(self, text): + text = self._makeText(text) + self._toFile(text) + + def detailItem(self, text): + text = f"- {text}" + self.detail(text) + + def detailPath(self, path): + text = self.relativePath(path) + self.detailItem(text) diff --git a/Lib/ufoProcessor/refactor.py b/Lib/ufoProcessor/refactor.py new file mode 100644 index 0000000..2b94c98 --- /dev/null +++ b/Lib/ufoProcessor/refactor.py @@ -0,0 +1,863 @@ +import os +import glob +import functools + + +import defcon +from warnings import warn +import collections +import logging, traceback + +from ufoProcessor import DesignSpaceProcessor +from fontTools.designspaceLib import DesignSpaceDocument, SourceDescriptor, InstanceDescriptor, AxisDescriptor, RuleDescriptor, processRules +from fontTools.designspaceLib.split import splitInterpolable +from fontTools.ufoLib import fontInfoAttributesVersion1, fontInfoAttributesVersion2, fontInfoAttributesVersion3 + +from fontTools.misc import plistlib +from fontMath.mathGlyph import MathGlyph +from fontMath.mathInfo import MathInfo +from fontMath.mathKerning import MathKerning +from mutatorMath.objects.mutator import buildMutator +from mutatorMath.objects.location import Location + +import ufoProcessor.varModels +from ufoProcessor.varModels import VariationModelMutator +from ufoProcessor.emptyPen import checkGlyphIsEmpty + +from ufoProcessor.logger import Logger + +_memoizeCache = dict() + +def immutify(obj): + # make an immutable version of this object. + # assert immutify(10) == (10,) + # assert immutify([10, 20, "a"]) == (10, 20, 'a') + # assert immutify(dict(foo="bar", world=["a", "b"])) == ('foo', ('bar',), 'world', ('a', 'b')) + hashValues = [] + if isinstance(obj, dict): + for key, value in obj.items(): + hashValues.extend([key, immutify(value)]) + elif isinstance(obj, list): + for value in obj: + hashValues.extend(immutify(value)) + else: + hashValues.append(obj) + return tuple(hashValues) + + +def memoize(function): + @functools.wraps(function) + def wrapper(self, *args, **kwargs): + immutableargs = tuple([immutify(a) for a in args]) + immutablekwargs = immutify(kwargs) + key = (function.__name__, self, immutableargs, immutify(kwargs)) + if key in _memoizeCache: + return _memoizeCache[key] + else: + result = function(self, *args, **kwargs) + _memoizeCache[key] = result + return result + return wrapper + + +def getUFOVersion(ufoPath): + # Peek into a ufo to read its format version. + # + # + # + # + # creator + # org.robofab.ufoLib + # formatVersion + # 2 + # + # + metaInfoPath = os.path.join(ufoPath, "metainfo.plist") + with open(metaInfoPath, 'rb') as f: + p = plistlib.load(f) + return p.get('formatVersion') + +def getDefaultLayerName(f): + # get the name of the default layer from a defcon font and from a fontparts font + if issubclass(type(f), defcon.objects.font.Font): + return f.layers.defaultLayer.name + elif issubclass(type(f), fontParts.fontshell.font.RFont): + return f.defaultLayer.name + return None + +# wrapped, not inherited +class NewUFOProcessor(object): + + fontClass = defcon.Font + layerClass = defcon.Layer + glyphClass = defcon.Glyph + libClass = defcon.Lib + glyphContourClass = defcon.Contour + glyphPointClass = defcon.Point + glyphComponentClass = defcon.Component + glyphAnchorClass = defcon.Anchor + kerningClass = defcon.Kerning + groupsClass = defcon.Groups + infoClass = defcon.Info + featuresClass = defcon.Features + + mathInfoClass = MathInfo + mathGlyphClass = MathGlyph + mathKerningClass = MathKerning + + def __init__(self, pathOrObject=None, ufoVersion=3, useVarlib=True, debug =False): + self.ufoVersion = ufoVersion + self.useVarlib = useVarlib + self._fontsLoaded = False + self.problems = [] + self.fonts = {} + self.roundGeometry = False + self.mutedAxisNames = None # list of axisname that need to be muted + self.debug = debug + self.logger = None + + if isinstance(pathOrObject, str): + self.doc = DesignSpaceDocument() + self.doc.read(pathOrObject) + else: + # XX test this + self.doc = pathOrObject + + if self.debug: + docBaseName = os.path.splitext(self.doc.path)[0] + logPath = f"{docBaseName}_log.txt" + self.logger = Logger(path=logPath, rootDirectory=None) + self.logger.time() + self.logger.info(f"## {self.doc.path}") + self.logger.info(f"\tUFO version: {self.ufoVersion}") + self.logger.info(f"\tround Geometry: {self.roundGeometry}") + if self.useVarlib: + self.logger.info(f"\tinterpolating with varlib") + else: + self.logger.info(f"\tinterpolating with mutatorMath") + + def _instantiateFont(self, path): + """ Return a instance of a font object with all the given subclasses""" + try: + return self.fontClass(path, + layerClass=self.layerClass, + libClass=self.libClass, + kerningClass=self.kerningClass, + groupsClass=self.groupsClass, + infoClass=self.infoClass, + featuresClass=self.featuresClass, + glyphClass=self.glyphClass, + glyphContourClass=self.glyphContourClass, + glyphPointClass=self.glyphPointClass, + glyphComponentClass=self.glyphComponentClass, + glyphAnchorClass=self.glyphAnchorClass) + except TypeError: + # if our fontClass doesnt support all the additional classes + return self.fontClass(path) + + def loadFonts(self, reload=False): + # Load the fonts and find the default candidate based on the info flag + if self._fontsLoaded and not reload: + return + names = set() + actions = [] + if self.debug: + self.logger.info("## loadFonts") + for i, sourceDescriptor in enumerate(self.doc.sources): + if sourceDescriptor.name is None: + # make sure it has a unique name + sourceDescriptor.name = "source.%d" % i + if sourceDescriptor.name not in self.fonts: + # + if os.path.exists(sourceDescriptor.path): + self.fonts[sourceDescriptor.name] = self._instantiateFont(sourceDescriptor.path) + actions.append("loaded source from %s, layer %s, format %d" % (sourceDescriptor.path, sourceDescriptor.layerName, getUFOVersion(sourceDescriptor.path))) + names |= set(self.fonts[sourceDescriptor.name].keys()) + else: + self.fonts[sourceDescriptor.name] = None + actions.append("source ufo not found at %s" % (sourceDescriptor.path)) + self.glyphNames = list(names) + if self.debug: + for item in actions: + self.logger.infoItem(item) + + self._fontsLoaded = True + + def splitLocation(self, location): + # split a location in a continouous and a discrete part + discreteAxes = [a.name for a in self.getOrderedDiscreteAxes()] + continuous = {} + discrete = {} + for name, value in location.items(): + if name in discreteAxes: + discrete[name] = value + else: + continuous[name] = value + if not discrete: + return continuous, None + return continuous, discrete + + def _serializeAnyAxis(self, axis): + if hasattr(axis, "serialize"): + return axis.serialize() + else: + if hasattr(axis, "values"): + # discrete axis does not have serialize method, meh + return dict( + tag=axis.tag, + name=axis.name, + labelNames=axis.labelNames, + minimum = min(axis.values), # XX is this allowed + maximum = max(axis.values), # XX is this allowed + values=axis.values, + default=axis.default, + hidden=axis.hidden, + map=axis.map, + axisOrdering=axis.axisOrdering, + axisLabels=axis.axisLabels, + ) + + def getSerializedAxes(self, discreteLocation=None): + serialized = [] + for axis in self.getOrderedContinuousAxes(): + serialized.append(self._serializeAnyAxis(axis)) + return serialized + + def getContinuousAxesForMutator(self): + # map the axis values? + d = collections.OrderedDict() + for axis in self.getOrderedContinuousAxes(): + d[axis.name] = self._serializeAnyAxis(axis) + return d + + def _getAxisOrder(self): + return [a.name for a in self.doc.axes] + + axisOrder = property(_getAxisOrder, doc="get the axis order from the axis descriptors") + + #serializedAxes = property(getSerializedAxes, doc="a list of dicts with the axis values") + + # some ds5 work + def getOrderedDiscreteAxes(self): + # return the list of discrete axis objects, in the right order + axes = [] + for axisName in self.doc.getAxisOrder(): + axisObj = self.doc.getAxis(axisName) + if hasattr(axisObj, "values"): + axes.append(axisObj) + return axes + + def getOrderedContinuousAxes(self): + # return the list of continuous axis objects, in the right order + axes = [] + for axisName in self.doc.getAxisOrder(): + axisObj = self.doc.getAxis(axisName) + if not hasattr(axisObj, "values"): + axes.append(axisObj) + return axes + + @memoize + def findSourceDescriptorsForDiscreteLocation(self, discreteLocDict): + # return a list of all sourcedescriptors that share the values in the discrete loc tuple + # so this includes all sourcedescriptors that point to layers + # discreteLocDict {'countedItems': 1.0, 'outlined': 0.0}, {'countedItems': 1.0, 'outlined': 1.0} + sources = [] + for s in self.doc.sources: + ok = True + if discreteLocDict is None: + sources.append(s) + continue + for name, value in discreteLocDict.items(): + if name in s.location: + if s.location[name] != value: + ok = False + else: + ok = False + continue + if ok: + sources.append(s) + return sources + + def getVariationModel(self, items, axes, bias=None): + # Return either a mutatorMath or a varlib.model object for calculating. + #try: + if True: + if self.useVarlib: + # use the varlib variation model + try: + return dict(), VariationModelMutator(items, axes=self.doc.axes, extrapolate=True) + #return dict(), VariationModelMutator(items, axes=self.doc.axes) + except TypeError: + import fontTools.varLib.models + error = traceback.format_exc() + note = "Error while making VariationModelMutator for {loc}:\n{error}" + if self.debug: + self.logger.info(note) + return {}, None + except (KeyError, AssertionError): + error = traceback.format_exc() + self.toolLog.append("UFOProcessor.getVariationModel error: %s" % error) + self.toolLog.append(items) + return {}, None + else: + # use mutatormath model + axesForMutator = self.getContinuousAxesForMutator() + # mutator will be confused by discrete axis values. + # the bias needs to be for the continuous axes only + biasForMutator, _ = self.splitLocation(bias) + return buildMutator(items, axes=axesForMutator, bias=biasForMutator) + #except: + # error = traceback.format_exc() + # self.toolLog.append("UFOProcessor.getVariationModel error: %s" % error) + return {}, None + + @memoize + def newDefaultLocation(self, bend=False, discreteLocation=None): + # overwrite from fontTools.newDefaultLocation + # we do not want this default location always to be mapped. + loc = collections.OrderedDict() + for axisDescriptor in self.doc.axes: + axisName = axisDescriptor.name + axisValue = axisDescriptor.default + if discreteLocation is not None: + # if we want to find the default for a specific discreteLoation + # we can not use the discrete axis' default value + # -> we have to use the value in the given discreteLocation + if axisDescriptor.name in discreteLocation: + axisValue = discreteLocation[axisDescriptor.name] + else: + axisValue = axisDescriptor.default + if bend: + loc[axisName] = axisDescriptor.map_forward( + axisValue + ) + else: + loc[axisName] = axisValue + return loc + + @memoize + def isAnisotropic(self, location): + for v in location.values(): + if isinstance(v, (list, tuple)): + return True + return False + + @memoize + def splitAnisotropic(self, location): + x = Location() + y = Location() + for dim, val in location.items(): + if type(val)==tuple: + x[dim] = val[0] + y[dim] = val[1] + else: + x[dim] = y[dim] = val + return x, y + + @memoize + def _getAxisOrder(self): + return [a.name for a in self.doc.axes] + + def generateUFO(self): + self.loadFonts() + if self.debug: + self.logger.info("## generateUFO") + for loc, space in splitInterpolable(self.doc): + spaceDoc = self.__class__(pathOrObject=space) + if self.debug: + self.logger.infoItem(f"Generating UFO for continuous space {loc}") + v = 0 + for instanceDescriptor in self.doc.instances: + if instanceDescriptor.path is None: + continue + pairs = None + bend = False + font = self.makeInstance(instanceDescriptor, + processRules, + glyphNames=self.glyphNames, + pairs=pairs, + bend=bend, + ) + if self.debug: + self.logger.info(f"\t\t{instanceDescriptor.path}") + instanceFolder = os.path.dirname(instanceDescriptor.path) + if not os.path.exists(instanceFolder): + os.makedirs(instanceFolder) + font.save(instanceDescriptor.path) + + @memoize + def getInfoMutator(self, discreteLocation=None): + """ Returns a info mutator """ + infoItems = [] + if discreteLocation is not None: + sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation) + else: + sources = self.doc.sources + for sourceDescriptor in sources: + if sourceDescriptor.layerName is not None: + continue + continuous, discrete = self.splitLocation(sourceDescriptor.location) + loc = Location(continuous) + sourceFont = self.fonts[sourceDescriptor.name] + if sourceFont is None: + continue + if hasattr(sourceFont.info, "toMathInfo"): + infoItems.append((loc, sourceFont.info.toMathInfo())) + else: + infoItems.append((loc, self.mathInfoClass(sourceFont.info))) + infoBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) + bias, self._infoMutator = self.getVariationModel(infoItems, axes=self.getSerializedAxes(), bias=infoBias) + return self._infoMutator + + @memoize + def getKerningMutator(self, pairs=None, discreteLocation=None): + """ Return a kerning mutator, collect the sources, build mathGlyphs. + If no pairs are given: calculate the whole table. + If pairs are given then query the sources for a value and make a mutator only with those values. + """ + if discreteLocation is not None: + sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation) + else: + sources = self.sources + kerningItems = [] + foregroundLayers = [None, 'foreground', 'public.default'] + if pairs is None: + for sourceDescriptor in sources: + if sourceDescriptor.layerName not in foregroundLayers: + continue + if not sourceDescriptor.muteKerning: + # filter this XX @@ + continuous, discrete = self.splitLocation(sourceDescriptor.location) + loc = Location(continuous) + sourceFont = self.fonts[sourceDescriptor.name] + if sourceFont is None: continue + # this makes assumptions about the groups of all sources being the same. + kerningItems.append((loc, self.mathKerningClass(sourceFont.kerning, sourceFont.groups))) + else: + self._kerningMutatorPairs = pairs + for sourceDescriptor in sources: + # XXX check sourceDescriptor layerName, only foreground should contribute + if sourceDescriptor.layerName is not None: + continue + if not os.path.exists(sourceDescriptor.path): + continue + if not sourceDescriptor.muteKerning: + sourceFont = self.fonts[sourceDescriptor.name] + if sourceFont is None: + continue + continuous, discrete = self.splitLocation(sourceDescriptor.location) + loc = Location(continuous) + # XXX can we get the kern value from the fontparts kerning object? + kerningItem = self.mathKerningClass(sourceFont.kerning, sourceFont.groups) + if kerningItem is not None: + sparseKerning = {} + for pair in pairs: + v = kerningItem.get(pair) + if v is not None: + sparseKerning[pair] = v + kerningItems.append((loc, self.mathKerningClass(sparseKerning))) + kerningBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) + bias, thing = self.getVariationModel(kerningItems, axes=self.getSerializedAxes(), bias=kerningBias) #xx + bias, self._kerningMutator = self.getVariationModel(kerningItems, axes=self.getSerializedAxes(), bias=kerningBias) + return self._kerningMutator + + @memoize + def getGlyphMutator(self, glyphName, + decomposeComponents=False, + **discreteLocation, + ): + fromCache = False + # make a mutator / varlib object for glyphName, with the sources for the given discrete location + items, unicodes = self.collectSourcesForGlyph(glyphName, decomposeComponents=decomposeComponents, **discreteLocation) + new = [] + for a, b, c in items: + if hasattr(b, "toMathGlyph"): + # note: calling toMathGlyph ignores the mathGlyphClass preference + # maybe the self.mathGlyphClass is not necessary? + new.append((a,b.toMathGlyph())) + else: + new.append((a,self.mathGlyphClass(b))) + thing = None + thisBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) + try: + bias, thing = self.getVariationModel(new, axes=self.getSerializedAxes(), bias=thisBias) #xx + except: + error = traceback.format_exc() + note = f"Error in getGlyphMutator for {glyphName}:\n{error}" + if self.debug: + self.logger.info(note) + return thing, unicodes + + @memoize + def isLocalDefault(self, location): + # return True if location is a local default + defaults = {} + for aD in self.doc.axes: + defaults[aD.name] = aD.default + for axisName, value in location.items(): + if defaults[axisName] != value: + return False + return True + + @memoize + def filterThisLocation(self, location, mutedAxes=None): + # return location with axes is mutedAxes removed + # this means checking if the location is a non-default value + if not mutedAxes: + return False, location + defaults = {} + ignoreSource = False + for aD in self.doc.axes: + defaults[aD.name] = aD.default + new = {} + new.update(location) + for mutedAxisName in mutedAxes: + if mutedAxisName not in location: + continue + if mutedAxisName not in defaults: + continue + if location[mutedAxisName] != defaults.get(mutedAxisName): + ignoreSource = True + del new[mutedAxisName] + return ignoreSource, new + + @memoize + def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, discreteLocation=None): + """ Return a glyph mutator + decomposeComponents = True causes the source glyphs to be decomposed first + before building the mutator. That gives you instances that do not depend + on a complete font. If you're calculating previews for instance. + + findSourceDescriptorsForDiscreteLocation returns sources from layers as well + """ + items = [] + empties = [] + foundEmpty = False + # is bend=True necessary here? + defaultLocation = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) + # + if discreteLocation is not None: + sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation) + else: + sources = self.doc.sources + unicodes = set() # unicodes for this glyph + for sourceDescriptor in sources: + if not os.path.exists(sourceDescriptor.path): + #kthxbai + p = "\tMissing UFO at %s" % sourceDescriptor.path + if p not in self.problems: + self.problems.append(p) + continue + if glyphName in sourceDescriptor.mutedGlyphNames: + continue + thisIsDefault = self.isLocalDefault(sourceDescriptor.location) + ignoreSource, filteredLocation = self.filterThisLocation(sourceDescriptor.location, self.mutedAxisNames) + if ignoreSource: + continue + f = self.fonts.get(sourceDescriptor.name) + if f is None: continue + loc = Location(sourceDescriptor.location) + sourceLayer = f + if not glyphName in f: + # log this> + continue + layerName = getDefaultLayerName(f) + sourceGlyphObject = None + # handle source layers + if sourceDescriptor.layerName is not None: + # start looking for a layer + # Do not bother for mutatorMath designspaces + layerName = sourceDescriptor.layerName + sourceLayer = getLayer(f, sourceDescriptor.layerName) + if sourceLayer is None: + continue + if glyphName not in sourceLayer: + # start looking for a glyph + # this might be a support in a sparse layer + # so we're skipping! + continue + # still have to check if the sourcelayer glyph is empty + if not glyphName in sourceLayer: + continue + else: + sourceGlyphObject = sourceLayer[glyphName] + if sourceGlyphObject.unicodes is not None: + for u in sourceGlyphObject.unicodes: + unicodes.add(u) + if checkGlyphIsEmpty(sourceGlyphObject, allowWhiteSpace=True): + foundEmpty = True + #sourceGlyphObject = None + #continue + if decomposeComponents: + # what about decomposing glyphs in a partial font? + temp = self.glyphClass() + p = temp.getPointPen() + dpp = DecomposePointPen(sourceLayer, p) + sourceGlyphObject.drawPoints(dpp) + temp.width = sourceGlyphObject.width + temp.name = sourceGlyphObject.name + processThis = temp + else: + processThis = sourceGlyphObject + sourceInfo = dict(source=f.path, glyphName=glyphName, + layerName=layerName, + location=filteredLocation, # sourceDescriptor.location, + sourceName=sourceDescriptor.name, + ) + if hasattr(processThis, "toMathGlyph"): + processThis = processThis.toMathGlyph() + else: + processThis = self.mathGlyphClass(processThis) + # this is where the location is linked to the glyph + # this loc needs to have the discrete location subtracted + # XX @@ + continuous, discrete = self.splitLocation(loc) + items.append((continuous, processThis, sourceInfo)) + empties.append((thisIsDefault, foundEmpty)) + # check the empties: + # if the default glyph is empty, then all must be empty + # if the default glyph is not empty then none can be empty + checkedItems = [] + emptiesAllowed = False + # first check if the default is empty. + # remember that the sources can be in any order + for i, p in enumerate(empties): + isDefault, isEmpty = p + if isDefault and isEmpty: + emptiesAllowed = True + # now we know what to look for + if not emptiesAllowed: + for i, p in enumerate(empties): + isDefault, isEmpty = p + if not isEmpty: + checkedItems.append(items[i]) + else: + for i, p in enumerate(empties): + isDefault, isEmpty = p + if isEmpty: + checkedItems.append(items[i]) + return checkedItems, unicodes + + collectMastersForGlyph = collectSourcesForGlyph + + def makeInstance(self, instanceDescriptor, + doRules=None, + glyphNames=None, + pairs=None, + bend=False): + """ Generate a font object for this instance """ + if doRules is not None: + warn('The doRules argument in DesignSpaceProcessor.makeInstance() is deprecated', DeprecationWarning, stacklevel=2) + continuousLocation, discreteLocation = self.splitLocation(instanceDescriptor.location) + + font = self._instantiateFont(None) + + loc = Location(continuousLocation) + anisotropic = False + locHorizontal = locVertical = loc + if self.isAnisotropic(loc): + anisotropic = True + locHorizontal, locVertical = self.splitAnisotropic(loc) + if self.debug: + self.logger.info(f"Anisotropic location for {instanceDescriptor.name} at {instanceDescriptor.location}") + if instanceDescriptor.kerning: + if pairs: + try: + kerningMutator = self.getKerningMutator(pairs=pairs, discreteLocation=discreteLocation) + kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend) + kerningObject.extractKerning(font) + except: + error = traceback.format_exc() + note = f"Could not make kerning for {loc}\n{error}" + if self.debug: + self.logger.info(note) + else: + kerningMutator = self.getKerningMutator(discreteLocation=discreteLocation) + if kerningMutator is not None: + kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend) + kerningObject.extractKerning(font) + + + # # make the info + infoMutator = self.getInfoMutator(discreteLocation=discreteLocation) + if infoMutator is not None: + if not anisotropic: + infoInstanceObject = infoMutator.makeInstance(loc, bend=bend) + else: + horizontalInfoInstanceObject = infoMutator.makeInstance(locHorizontal, bend=bend) + verticalInfoInstanceObject = infoMutator.makeInstance(locVertical, bend=bend) + # merge them again + infoInstanceObject = (1,0)*horizontalInfoInstanceObject + (0,1)*verticalInfoInstanceObject + if self.roundGeometry: + infoInstanceObject = infoInstanceObject.round() + infoInstanceObject.extractInfo(font.info) + font.info.familyName = instanceDescriptor.familyName + font.info.styleName = instanceDescriptor.styleName + font.info.postscriptFontName = instanceDescriptor.postScriptFontName # yikes, note the differences in capitalisation.. + font.info.styleMapFamilyName = instanceDescriptor.styleMapFamilyName + font.info.styleMapStyleName = instanceDescriptor.styleMapStyleName + + for sourceDescriptor in self.doc.sources: + if sourceDescriptor.copyInfo: + # this is the source + if self.fonts[sourceDescriptor.name] is not None: + self._copyFontInfo(self.fonts[sourceDescriptor.name].info, font.info) + if sourceDescriptor.copyLib: + # excplicitly copy the font.lib items + if self.fonts[sourceDescriptor.name] is not None: + for key, value in self.fonts[sourceDescriptor.name].lib.items(): + font.lib[key] = value + if sourceDescriptor.copyGroups: + if self.fonts[sourceDescriptor.name] is not None: + for key, value in self.fonts[sourceDescriptor.name].groups.items(): + font.groups[key] = value + if sourceDescriptor.copyFeatures: + if self.fonts[sourceDescriptor.name] is not None: + featuresText = self.fonts[sourceDescriptor.name].features.text + font.features.text = featuresText + + # ok maybe now it is time to calculate some glyphs + # glyphs + if glyphNames: + selectedGlyphNames = glyphNames + else: + selectedGlyphNames = self.glyphNames + if not 'public.glyphOrder' in font.lib.keys(): + # should be the glyphorder from the default, yes? + font.lib['public.glyphOrder'] = selectedGlyphNames + + for glyphName in selectedGlyphNames: + glyphMutator, unicodes = self.getGlyphMutator(glyphName, discreteLocation=discreteLocation) + if glyphMutator is None: + self.problems.append("Could not make mutator for glyph %s" % (glyphName)) + continue + + font.newGlyph(glyphName) + font[glyphName].clear() + glyphInstanceUnicodes = [] + #neutralFont = self.getNeutralFont() + font[glyphName].unicodes = unicodes + + try: + if not self.isAnisotropic(continuousLocation): + glyphInstanceObject = glyphMutator.makeInstance(continuousLocation, bend=bend) + else: + # split anisotropic location into horizontal and vertical components + horizontalGlyphInstanceObject = glyphMutator.makeInstance(locHorizontal, bend=bend) + verticalGlyphInstanceObject = glyphMutator.makeInstance(locVertical, bend=bend) + # merge them again + glyphInstanceObject = (1,0)*horizontalGlyphInstanceObject + (0,1)*verticalGlyphInstanceObject + except IndexError: + # alignment problem with the data? + note = "Quite possibly some sort of data alignment error in %s" % glyphName + if self.debug: + self.logger.info(note) + self.problems.append(note) + continue + if self.roundGeometry: + try: + glyphInstanceObject = glyphInstanceObject.round() + except AttributeError: + # what are we catching here? + print(f"no round method for {glyphInstanceObject} ?") + pass + try: + # File "/Users/erik/code/ufoProcessor/Lib/ufoProcessor/__init__.py", line 649, in makeInstance + # glyphInstanceObject.extractGlyph(font[glyphName], onlyGeometry=True) + # File "/Applications/RoboFont.app/Contents/Resources/lib/python3.6/fontMath/mathGlyph.py", line 315, in extractGlyph + # glyph.anchors = [dict(anchor) for anchor in self.anchors] + # File "/Applications/RoboFont.app/Contents/Resources/lib/python3.6/fontParts/base/base.py", line 103, in __set__ + # raise FontPartsError("no setter for %r" % self.name) + # fontParts.base.errors.FontPartsError: no setter for 'anchors' + if hasattr(font[glyphName], "fromMathGlyph"): + font[glyphName].fromMathGlyph(glyphInstanceObject) + else: + glyphInstanceObject.extractGlyph(font[glyphName], onlyGeometry=True) + except TypeError: + # this causes ruled glyphs to end up in the wrong glyphname + # but defcon2 objects don't support it + pPen = font[glyphName].getPointPen() + font[glyphName].clear() + glyphInstanceObject.drawPoints(pPen) + font[glyphName].width = glyphInstanceObject.width + + return font + + def _copyFontInfo(self, sourceInfo, targetInfo): + """ Copy the non-calculating fields from the source info.""" + infoAttributes = [ + "versionMajor", + "versionMinor", + "copyright", + "trademark", + "note", + "openTypeGaspRangeRecords", + "openTypeHeadCreated", + "openTypeHeadFlags", + "openTypeNameDesigner", + "openTypeNameDesignerURL", + "openTypeNameManufacturer", + "openTypeNameManufacturerURL", + "openTypeNameLicense", + "openTypeNameLicenseURL", + "openTypeNameVersion", + "openTypeNameUniqueID", + "openTypeNameDescription", + "#openTypeNamePreferredFamilyName", + "#openTypeNamePreferredSubfamilyName", + "#openTypeNameCompatibleFullName", + "openTypeNameSampleText", + "openTypeNameWWSFamilyName", + "openTypeNameWWSSubfamilyName", + "openTypeNameRecords", + "openTypeOS2Selection", + "openTypeOS2VendorID", + "openTypeOS2Panose", + "openTypeOS2FamilyClass", + "openTypeOS2UnicodeRanges", + "openTypeOS2CodePageRanges", + "openTypeOS2Type", + "postscriptIsFixedPitch", + "postscriptForceBold", + "postscriptDefaultCharacter", + "postscriptWindowsCharacterSet" + ] + for infoAttribute in infoAttributes: + copy = False + if self.ufoVersion == 1 and infoAttribute in fontInfoAttributesVersion1: + copy = True + elif self.ufoVersion == 2 and infoAttribute in fontInfoAttributesVersion2: + copy = True + elif self.ufoVersion == 3 and infoAttribute in fontInfoAttributesVersion3: + copy = True + if copy: + value = getattr(sourceInfo, infoAttribute) + setattr(targetInfo, infoAttribute, value) + +t = """include(display.fea); + +#include(stat_upright.fea); +#include(stat_italic.fea); + + +include(features-LTRPrincipia-LargeItalic/markclasses.fea); + +include(features-LTRPrincipia-LargeItalic/mark.fea); + +include(features-LTRPrincipia-LargeItalic/mkmk.fea); + +#include(features-LTRPrincipia-LargeItalic/triplets.fea); + +""" + + + +#ds5Path = "/Users/erik/code/type2/Principia/sources/Principia_wdth.designspace" +ds5Path = "../../Tests/ds5/ds5.designspace" + +import os +print(os.path.exists(ds5Path)) + +doc = NewUFOProcessor(ds5Path, useVarlib=False, debug=True) +doc.generateUFO() +doc.collectSourcesForGlyph("space.tight") \ No newline at end of file diff --git a/Tests/ds5/ds5.designspace b/Tests/ds5/ds5.designspace index 7379a40..d986b71 100644 --- a/Tests/ds5/ds5.designspace +++ b/Tests/ds5/ds5.designspace @@ -103,6 +103,15 @@ + + + + + + + + + diff --git a/Tests/ds5/ds5_makeTestDoc.py b/Tests/ds5/ds5_makeTestDoc.py index ed9d074..c4d9963 100644 --- a/Tests/ds5/ds5_makeTestDoc.py +++ b/Tests/ds5/ds5_makeTestDoc.py @@ -5,6 +5,7 @@ # axis DSC2 is a discrete axis showing a solid or outlined shape from fontTools.designspaceLib import DesignSpaceDocument, SourceDescriptor, InstanceDescriptor, AxisDescriptor, RuleDescriptor, processRules, DiscreteAxisDescriptor +from fontTools.designspaceLib.split import splitInterpolable import os import fontTools @@ -117,9 +118,34 @@ def ip(a,b,f): s1.info = True doc.addInstance(s1) +# add variable font descriptors + +splits = splitInterpolable(doc) +for discreteLocation, subSpace in splitInterpolable(doc): + print(discreteLocation, subSpace) + +#print(doc.getVariableFonts()) + +#for item in doc.getVariableFonts(): +# doc.addVariableFont(item) + +doc.variableFonts.clear() +print(doc.variableFonts) + + +variableFonts = doc.getVariableFonts() +print("variableFonts", variableFonts) + +doc.addVariableFont(variableFonts[0]) + +for i, item in enumerate(variableFonts): + print(i, item) + + path = "ds5.designspace" print(doc.lib) doc.write(path) +print(dir(doc)) for a in doc.axes: From 2aef56ad27026e9ca35ac8c1d3ebea0b21484c59 Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Wed, 4 Jan 2023 23:50:05 +0100 Subject: [PATCH 21/26] WIP Fix missing space.tight issue So, if a source ufo has a variation on a space glyph or some other whitespace, like "space.tight", this will fail the checkGlyphIsEmpty test because it has no reognised whitespace unicode. This fix bluntly looks for "space" in the glyphname. While I'm aware this is not a comprehensive approach, it is low risk. --- Lib/ufoProcessor/emptyPen.py | 6 +++- Lib/ufoProcessor/refactor.py | 70 ++++++++++++++++++++++-------------- 2 files changed, 49 insertions(+), 27 deletions(-) diff --git a/Lib/ufoProcessor/emptyPen.py b/Lib/ufoProcessor/emptyPen.py index 42459d1..96d2cbf 100755 --- a/Lib/ufoProcessor/emptyPen.py +++ b/Lib/ufoProcessor/emptyPen.py @@ -24,7 +24,7 @@ def addPoint(self, pt, segmentType=None, smooth=False, name=None, identifier=Non def addComponent(self, baseGlyphName=None, transformation=None, identifier=None, **kwargs): self.components+=1 - + def getCount(self): return self.points, self.contours, self.components @@ -71,6 +71,10 @@ def checkGlyphIsEmpty(glyph, allowWhiteSpace=True): if glyph.unicode in whiteSpace and allowWhiteSpace: # are we allowed to be? return False + if "space" in glyph.name: + # this is a bold assumption, + # and certainly not inclusive + return False return True return False diff --git a/Lib/ufoProcessor/refactor.py b/Lib/ufoProcessor/refactor.py index 2b94c98..1ec664c 100644 --- a/Lib/ufoProcessor/refactor.py +++ b/Lib/ufoProcessor/refactor.py @@ -171,7 +171,8 @@ def loadFonts(self, reload=False): # if os.path.exists(sourceDescriptor.path): self.fonts[sourceDescriptor.name] = self._instantiateFont(sourceDescriptor.path) - actions.append("loaded source from %s, layer %s, format %d" % (sourceDescriptor.path, sourceDescriptor.layerName, getUFOVersion(sourceDescriptor.path))) + thisLayerName = getDefaultLayerName(self.fonts[sourceDescriptor.name]) + actions.append(f"loaded: {os.path.basename(sourceDescriptor.path)}, layer: {thisLayerName}, format: {getUFOVersion(sourceDescriptor.path)}") names |= set(self.fonts[sourceDescriptor.name].keys()) else: self.fonts[sourceDescriptor.name] = None @@ -358,14 +359,14 @@ def splitAnisotropic(self, location): def _getAxisOrder(self): return [a.name for a in self.doc.axes] - def generateUFO(self): + def generateUFOs(self): self.loadFonts() if self.debug: self.logger.info("## generateUFO") for loc, space in splitInterpolable(self.doc): spaceDoc = self.__class__(pathOrObject=space) if self.debug: - self.logger.infoItem(f"Generating UFO for continuous space {loc}") + self.logger.infoItem(f"Generating UFOs for continuous space at discrete location {loc}") v = 0 for instanceDescriptor in self.doc.instances: if instanceDescriptor.path is None: @@ -379,12 +380,14 @@ def generateUFO(self): bend=bend, ) if self.debug: - self.logger.info(f"\t\t{instanceDescriptor.path}") + self.logger.info(f"\t\t{os.path.basename(instanceDescriptor.path)}") instanceFolder = os.path.dirname(instanceDescriptor.path) if not os.path.exists(instanceFolder): os.makedirs(instanceFolder) font.save(instanceDescriptor.path) + generateUFO = generateUFOs + @memoize def getInfoMutator(self, discreteLocation=None): """ Returns a info mutator """ @@ -549,6 +552,7 @@ def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, discreteL self.problems.append(p) continue if glyphName in sourceDescriptor.mutedGlyphNames: + self.logger.info(f"\t\tglyphName {glyphName} is muted") continue thisIsDefault = self.isLocalDefault(sourceDescriptor.location) ignoreSource, filteredLocation = self.filterThisLocation(sourceDescriptor.location, self.mutedAxisNames) @@ -659,7 +663,7 @@ def makeInstance(self, instanceDescriptor, anisotropic = True locHorizontal, locVertical = self.splitAnisotropic(loc) if self.debug: - self.logger.info(f"Anisotropic location for {instanceDescriptor.name} at {instanceDescriptor.location}") + self.logger.info(f"\t\t\tAnisotropic location for {instanceDescriptor.name}\n\t\t\t{instanceDescriptor.location}") if instanceDescriptor.kerning: if pairs: try: @@ -676,6 +680,8 @@ def makeInstance(self, instanceDescriptor, if kerningMutator is not None: kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend) kerningObject.extractKerning(font) + if self.debug: + self.logger.info(f"\t\t\t{len(font.kerning)} kerning pairs added") # # make the info @@ -781,6 +787,9 @@ def makeInstance(self, instanceDescriptor, glyphInstanceObject.drawPoints(pPen) font[glyphName].width = glyphInstanceObject.width + if self.debug: + self.logger.info(f"\t\t\t{len(selectedGlyphNames)} glyphs added") + return font def _copyFontInfo(self, sourceInfo, targetInfo): @@ -834,30 +843,39 @@ def _copyFontInfo(self, sourceInfo, targetInfo): value = getattr(sourceInfo, infoAttribute) setattr(targetInfo, infoAttribute, value) -t = """include(display.fea); - -#include(stat_upright.fea); -#include(stat_italic.fea); - - -include(features-LTRPrincipia-LargeItalic/markclasses.fea); - -include(features-LTRPrincipia-LargeItalic/mark.fea); - -include(features-LTRPrincipia-LargeItalic/mkmk.fea); - -#include(features-LTRPrincipia-LargeItalic/triplets.fea); + # updating fonts + def updateFonts(self, fontObjects): + # this is to update the loaded fonts. + # it should be the way for an editor to provide a list of fonts that are open + #self.fonts[sourceDescriptor.name] = None + hasUpdated = False + for newFont in fontObjects: + for fontName, haveFont in self.fonts.items(): + if haveFont.path == newFont.path and id(haveFont)!=id(newFont): + note = f"## updating source {self.fonts[fontName]} with {newFont.path}" + if self.debug: + self.logger.time() + self.logger.info(note) + self.fonts[fontName] = newFont + hasUpdated = True + if hasUpdated: + self.changed() -""" +if __name__ == "__main__": + ds5Path = "/Users/erik/code/type2/Principia/sources/Principia_wdth.designspace" + #ds5Path = "../../Tests/ds5/ds5.designspace" -#ds5Path = "/Users/erik/code/type2/Principia/sources/Principia_wdth.designspace" -ds5Path = "../../Tests/ds5/ds5.designspace" + dumpCacheLog = False + import os + if os.path.exists(ds5Path): + doc = NewUFOProcessor(ds5Path, useVarlib=False, debug=True) + doc.generateUFOs() + print("sources for space.tight ", doc.collectSourcesForGlyph("space.tight")) -import os -print(os.path.exists(ds5Path)) + if dumpCacheLog: + doc.logger.info(f"Test: cached {len(_memoizeCache)} items") + for key, item in _memoizeCache.items(): + doc.logger.info(f"\t\t{key} {item}") -doc = NewUFOProcessor(ds5Path, useVarlib=False, debug=True) -doc.generateUFO() -doc.collectSourcesForGlyph("space.tight") \ No newline at end of file From 9290b11ca385381095df74d74d1083a900410872 Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Thu, 5 Jan 2023 00:18:12 +0100 Subject: [PATCH 22/26] WIP report on which fonts are loaded + add updateFonts(), changed(), glyphChanged() updateFonts takes a list of fontobjects and it will drop them in the doc.fonts if the fonts have the same path and call changed(). This does not need to check layernames. --- Lib/ufoProcessor/refactor.py | 92 ++++++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 24 deletions(-) diff --git a/Lib/ufoProcessor/refactor.py b/Lib/ufoProcessor/refactor.py index 1ec664c..376a2f7 100644 --- a/Lib/ufoProcessor/refactor.py +++ b/Lib/ufoProcessor/refactor.py @@ -158,6 +158,8 @@ def _instantiateFont(self, path): def loadFonts(self, reload=False): # Load the fonts and find the default candidate based on the info flag if self._fontsLoaded and not reload: + if self.debug: + self.logger("\t\t-- loadFonts requested, but fonts are loaded already and no reload requested") return names = set() actions = [] @@ -170,9 +172,9 @@ def loadFonts(self, reload=False): if sourceDescriptor.name not in self.fonts: # if os.path.exists(sourceDescriptor.path): - self.fonts[sourceDescriptor.name] = self._instantiateFont(sourceDescriptor.path) - thisLayerName = getDefaultLayerName(self.fonts[sourceDescriptor.name]) - actions.append(f"loaded: {os.path.basename(sourceDescriptor.path)}, layer: {thisLayerName}, format: {getUFOVersion(sourceDescriptor.path)}") + f = self.fonts[sourceDescriptor.name] = self._instantiateFont(sourceDescriptor.path) + thisLayerName = getDefaultLayerName(f) + actions.append(f"loaded: {os.path.basename(sourceDescriptor.path)}, layer: {thisLayerName}, format: {getUFOVersion(sourceDescriptor.path)}, id: {id(f)}") names |= set(self.fonts[sourceDescriptor.name].keys()) else: self.fonts[sourceDescriptor.name] = None @@ -181,8 +183,51 @@ def loadFonts(self, reload=False): if self.debug: for item in actions: self.logger.infoItem(item) - self._fontsLoaded = True + + def _logLoadedFonts(self): + # dump info about the loaded fonts to the log + items = [] + self.logger.info("\t# font status:") + for name, fontObj in self.fonts.items(): + self.logger.info(f"\t\tloaded: , id: {id(fontObj)}, {os.path.basename(fontObj.path)}, format: {getUFOVersion(fontObj.path)}") + + # updating fonts + def updateFonts(self, fontObjects): + # this is to update the loaded fonts. + # it should be the way for an editor to provide a list of fonts that are open + #self.fonts[sourceDescriptor.name] = None + hasUpdated = False + for newFont in fontObjects: + for fontName, haveFont in self.fonts.items(): + if haveFont.path == newFont.path and id(haveFont)!=id(newFont): + note = f"## updating source {self.fonts[fontName]} with {newFont}" + if self.debug: + self.logger.time() + self.logger.info(note) + self.fonts[fontName] = newFont + hasUpdated = True + if hasUpdated: + self.changed() + + # caching + def changed(self): + # clears everything relating to this designspacedocument + # the cache could contain more designspacedocument objects. + for key in list(_memoizeCache.keys()): + if key[1] == self: + del _memoizeCache[key] + #_memoizeCache.clear() + + def glyphChanged(self, glyphName): + # clears this one specific glyph + for key in list(_memoizeCache.keys()): + #print(f"glyphChanged {[(i,m) for i, m in enumerate(key)]} {glyphName}") + # the glyphname is hiding quite deep in key[2] + # (('glyphTwo',),) + # this is because of how immutify does it. Could be different I suppose but this works + if key[0] in ("getGlyphMutator", "collectSourcesForGlyph") and key[2][0][0] == glyphName: + del _memoizeCache[key] def splitLocation(self, location): # split a location in a continouous and a discrete part @@ -360,6 +405,7 @@ def _getAxisOrder(self): return [a.name for a in self.doc.axes] def generateUFOs(self): + glyphCount = 0 self.loadFonts() if self.debug: self.logger.info("## generateUFO") @@ -385,6 +431,9 @@ def generateUFOs(self): if not os.path.exists(instanceFolder): os.makedirs(instanceFolder) font.save(instanceDescriptor.path) + glyphCount += len(font) + if self.debug: + self.logger.info(f"\t\tGenerated {glyphCount} glyphs altogether.") generateUFO = generateUFOs @@ -843,39 +892,34 @@ def _copyFontInfo(self, sourceInfo, targetInfo): value = getattr(sourceInfo, infoAttribute) setattr(targetInfo, infoAttribute, value) - # updating fonts - def updateFonts(self, fontObjects): - # this is to update the loaded fonts. - # it should be the way for an editor to provide a list of fonts that are open - #self.fonts[sourceDescriptor.name] = None - hasUpdated = False - for newFont in fontObjects: - for fontName, haveFont in self.fonts.items(): - if haveFont.path == newFont.path and id(haveFont)!=id(newFont): - note = f"## updating source {self.fonts[fontName]} with {newFont.path}" - if self.debug: - self.logger.time() - self.logger.info(note) - self.fonts[fontName] = newFont - hasUpdated = True - if hasUpdated: - self.changed() if __name__ == "__main__": - + import time, random + from fontParts.world import RFont ds5Path = "/Users/erik/code/type2/Principia/sources/Principia_wdth.designspace" #ds5Path = "../../Tests/ds5/ds5.designspace" dumpCacheLog = False + makeUFOs = True import os if os.path.exists(ds5Path): + startTime = time.time() doc = NewUFOProcessor(ds5Path, useVarlib=False, debug=True) - doc.generateUFOs() - print("sources for space.tight ", doc.collectSourcesForGlyph("space.tight")) + if makeUFOs: + doc.generateUFOs() if dumpCacheLog: doc.logger.info(f"Test: cached {len(_memoizeCache)} items") for key, item in _memoizeCache.items(): doc.logger.info(f"\t\t{key} {item}") + endTime = time.time() + duration = endTime - startTime + print(f"duration: {duration}" ) + + sourcePaths = [s.path for s in doc.doc.sources] + f = RFont(random.choice(sourcePaths)) + print(f) + doc.updateFonts([f]) + doc._logLoadedFonts() From f6a0eea079e80525e4d65ee8aa4f83c9311700ab Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Thu, 5 Jan 2023 15:22:19 +0100 Subject: [PATCH 23/26] WIP + Rename emptyPen to the more generic pens so that the decomposepen can also move there. + Remove self.problems, do all reporting to the logger. + makeOneGlyph() a one stop method for fulfilling all orders for a specific glyph. Accept every location, math model, rounding and decompose. + inspectMemoizeCache() Convenience method for dumping the memoize cache keys to get an impression of what happened. + collectBaseGlyphs() does some work towards finding out which glyphs are needed to build all components. But it would overload makeOneGlyph so it has to be its own thing. --- Lib/ufoProcessor/{emptyPen.py => pens.py} | 26 +++ Lib/ufoProcessor/refactor.py | 199 +++++++++++++++------- 2 files changed, 166 insertions(+), 59 deletions(-) rename Lib/ufoProcessor/{emptyPen.py => pens.py} (78%) diff --git a/Lib/ufoProcessor/emptyPen.py b/Lib/ufoProcessor/pens.py similarity index 78% rename from Lib/ufoProcessor/emptyPen.py rename to Lib/ufoProcessor/pens.py index 96d2cbf..db067d0 100755 --- a/Lib/ufoProcessor/emptyPen.py +++ b/Lib/ufoProcessor/pens.py @@ -1,5 +1,31 @@ # coding: utf-8 + from fontTools.pens.pointPen import AbstractPointPen +from defcon.pens.transformPointPen import TransformPointPen +from defcon.objects.component import _defaultTransformation + +""" + Decompose + +""" + +class DecomposePointPen(object): + + def __init__(self, glyphSet, outPointPen): + self._glyphSet = glyphSet + self._outPointPen = outPointPen + self.beginPath = outPointPen.beginPath + self.endPath = outPointPen.endPath + self.addPoint = outPointPen.addPoint + + def addComponent(self, baseGlyphName, transformation): + if baseGlyphName in self._glyphSet: + baseGlyph = self._glyphSet[baseGlyphName] + if transformation == _defaultTransformation: + baseGlyph.drawPoints(self) + else: + transformPointPen = TransformPointPen(self, transformation) + baseGlyph.drawPoints(transformPointPen) """ Simple pen object to determine if a glyph contains any geometry. diff --git a/Lib/ufoProcessor/refactor.py b/Lib/ufoProcessor/refactor.py index 376a2f7..662096e 100644 --- a/Lib/ufoProcessor/refactor.py +++ b/Lib/ufoProcessor/refactor.py @@ -8,21 +8,26 @@ import collections import logging, traceback -from ufoProcessor import DesignSpaceProcessor +#from ufoProcessor import DesignSpaceProcessor + from fontTools.designspaceLib import DesignSpaceDocument, SourceDescriptor, InstanceDescriptor, AxisDescriptor, RuleDescriptor, processRules from fontTools.designspaceLib.split import splitInterpolable from fontTools.ufoLib import fontInfoAttributesVersion1, fontInfoAttributesVersion2, fontInfoAttributesVersion3 - from fontTools.misc import plistlib + from fontMath.mathGlyph import MathGlyph from fontMath.mathInfo import MathInfo from fontMath.mathKerning import MathKerning from mutatorMath.objects.mutator import buildMutator from mutatorMath.objects.location import Location +import fontParts.fontshell.font + import ufoProcessor.varModels +import ufoProcessor.pens + from ufoProcessor.varModels import VariationModelMutator -from ufoProcessor.emptyPen import checkGlyphIsEmpty +from ufoProcessor.pens import checkGlyphIsEmpty, DecomposePointPen from ufoProcessor.logger import Logger @@ -44,7 +49,6 @@ def immutify(obj): hashValues.append(obj) return tuple(hashValues) - def memoize(function): @functools.wraps(function) def wrapper(self, *args, **kwargs): @@ -59,6 +63,15 @@ def wrapper(self, *args, **kwargs): return result return wrapper +def inspectMemoizeCache(): + functionNames = [] + stats = {} + for k in _memoizeCache.keys(): + functionName = k[0] + if not functionName in stats: + stats[functionName] = 0 + stats[functionName] += 1 + print(stats) def getUFOVersion(ufoPath): # Peek into a ufo to read its format version. @@ -109,7 +122,6 @@ def __init__(self, pathOrObject=None, ufoVersion=3, useVarlib=True, debug =False self.ufoVersion = ufoVersion self.useVarlib = useVarlib self._fontsLoaded = False - self.problems = [] self.fonts = {} self.roundGeometry = False self.mutedAxisNames = None # list of axisname that need to be muted @@ -159,7 +171,7 @@ def loadFonts(self, reload=False): # Load the fonts and find the default candidate based on the info flag if self._fontsLoaded and not reload: if self.debug: - self.logger("\t\t-- loadFonts requested, but fonts are loaded already and no reload requested") + self.logger.info("\t\t-- loadFonts requested, but fonts are loaded already and no reload requested") return names = set() actions = [] @@ -174,7 +186,7 @@ def loadFonts(self, reload=False): if os.path.exists(sourceDescriptor.path): f = self.fonts[sourceDescriptor.name] = self._instantiateFont(sourceDescriptor.path) thisLayerName = getDefaultLayerName(f) - actions.append(f"loaded: {os.path.basename(sourceDescriptor.path)}, layer: {thisLayerName}, format: {getUFOVersion(sourceDescriptor.path)}, id: {id(f)}") + actions.append(f"loaded: {os.path.basename(sourceDescriptor.path)}, layer: {thisLayerName}, format: {getUFOVersion(sourceDescriptor.path)}, id: {id(f):X}") names |= set(self.fonts[sourceDescriptor.name].keys()) else: self.fonts[sourceDescriptor.name] = None @@ -190,7 +202,7 @@ def _logLoadedFonts(self): items = [] self.logger.info("\t# font status:") for name, fontObj in self.fonts.items(): - self.logger.info(f"\t\tloaded: , id: {id(fontObj)}, {os.path.basename(fontObj.path)}, format: {getUFOVersion(fontObj.path)}") + self.logger.info(f"\t\tloaded: , id: {id(fontObj):X}, {os.path.basename(fontObj.path)}, format: {getUFOVersion(fontObj.path)}") # updating fonts def updateFonts(self, fontObjects): @@ -220,7 +232,7 @@ def changed(self): #_memoizeCache.clear() def glyphChanged(self, glyphName): - # clears this one specific glyph + # clears this one specific glyph from the memoize cache for key in list(_memoizeCache.keys()): #print(f"glyphChanged {[(i,m) for i, m in enumerate(key)]} {glyphName}") # the glyphname is hiding quite deep in key[2] @@ -302,8 +314,35 @@ def getOrderedContinuousAxes(self): axes.append(axisObj) return axes + def checkDiscreteAxisValues(self, location): + # check if the discrete values in this location are allowed + for discreteAxis in self.getOrderedDiscreteAxes(): + testValue = location.get(discreteAxis.name) + if not testValue in discreteAxis.values: + return False + return True + + def collectBaseGlyphs(self, glyphName, location): + # make a list of all baseglyphs needed to build this glyph, at this location + # Note: different discrete values mean that the glyph component set up can be different too + continuous, discrete = self.splitLocation(location) + names = set() + def _getComponentNames(glyph): + names = set() + for comp in glyph.components: + names.add(comp.baseGlyph) + for n in _getComponentNames(glyph.font[comp.baseGlyph]): + names.add(n) + return list(names) + + for sourceDescriptor in self.findSourceDescriptorsForDiscreteLocation(discrete): + sourceFont = self.fonts[sourceDescriptor.name] + if not glyphName in sourceFont: continue + [names.add(n) for n in _getComponentNames(sourceFont[glyphName])] + return list(names) + @memoize - def findSourceDescriptorsForDiscreteLocation(self, discreteLocDict): + def findSourceDescriptorsForDiscreteLocation(self, discreteLocDict=None): # return a list of all sourcedescriptors that share the values in the discrete loc tuple # so this includes all sourcedescriptors that point to layers # discreteLocDict {'countedItems': 1.0, 'outlined': 0.0}, {'countedItems': 1.0, 'outlined': 1.0} @@ -326,35 +365,29 @@ def findSourceDescriptorsForDiscreteLocation(self, discreteLocDict): def getVariationModel(self, items, axes, bias=None): # Return either a mutatorMath or a varlib.model object for calculating. - #try: - if True: - if self.useVarlib: - # use the varlib variation model - try: - return dict(), VariationModelMutator(items, axes=self.doc.axes, extrapolate=True) - #return dict(), VariationModelMutator(items, axes=self.doc.axes) - except TypeError: - import fontTools.varLib.models + if self.useVarlib: + # use the varlib variation model + try: + return dict(), VariationModelMutator(items, axes=self.doc.axes, extrapolate=True) + except TypeError: + if self.debug: error = traceback.format_exc() note = "Error while making VariationModelMutator for {loc}:\n{error}" - if self.debug: - self.logger.info(note) - return {}, None - except (KeyError, AssertionError): + self.logger.info(note) + return {}, None + except (KeyError, AssertionError): + if self.debug: error = traceback.format_exc() - self.toolLog.append("UFOProcessor.getVariationModel error: %s" % error) - self.toolLog.append(items) - return {}, None - else: - # use mutatormath model - axesForMutator = self.getContinuousAxesForMutator() - # mutator will be confused by discrete axis values. - # the bias needs to be for the continuous axes only - biasForMutator, _ = self.splitLocation(bias) - return buildMutator(items, axes=axesForMutator, bias=biasForMutator) - #except: - # error = traceback.format_exc() - # self.toolLog.append("UFOProcessor.getVariationModel error: %s" % error) + note = "UFOProcessor.getVariationModel error: {error}" + self.logger.info(note) + return {}, None + else: + # use mutatormath model + axesForMutator = self.getContinuousAxesForMutator() + # mutator will be confused by discrete axis values. + # the bias needs to be for the continuous axes only + biasForMutator, _ = self.splitLocation(bias) + return buildMutator(items, axes=axesForMutator, bias=biasForMutator) return {}, None @memoize @@ -596,9 +629,9 @@ def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, discreteL for sourceDescriptor in sources: if not os.path.exists(sourceDescriptor.path): #kthxbai - p = "\tMissing UFO at %s" % sourceDescriptor.path - if p not in self.problems: - self.problems.append(p) + note = "\tMissing UFO at %s" % sourceDescriptor.path + if self.debug: + self.logger.info(note) continue if glyphName in sourceDescriptor.mutedGlyphNames: self.logger.info(f"\t\tglyphName {glyphName} is muted") @@ -721,7 +754,7 @@ def makeInstance(self, instanceDescriptor, kerningObject.extractKerning(font) except: error = traceback.format_exc() - note = f"Could not make kerning for {loc}\n{error}" + note = f"makeInstance: Could not make kerning for {loc}\n{error}" if self.debug: self.logger.info(note) else: @@ -732,7 +765,6 @@ def makeInstance(self, instanceDescriptor, if self.debug: self.logger.info(f"\t\t\t{len(font.kerning)} kerning pairs added") - # # make the info infoMutator = self.getInfoMutator(discreteLocation=discreteLocation) if infoMutator is not None: @@ -782,9 +814,12 @@ def makeInstance(self, instanceDescriptor, font.lib['public.glyphOrder'] = selectedGlyphNames for glyphName in selectedGlyphNames: + # can we take all this into a separate method for making a preview glyph object? glyphMutator, unicodes = self.getGlyphMutator(glyphName, discreteLocation=discreteLocation) if glyphMutator is None: - self.problems.append("Could not make mutator for glyph %s" % (glyphName)) + if self.debug: + note = f"makeInstance: Could not make mutator for glyph {glyphName}" + self.logger.info(note) continue font.newGlyph(glyphName) @@ -800,22 +835,23 @@ def makeInstance(self, instanceDescriptor, # split anisotropic location into horizontal and vertical components horizontalGlyphInstanceObject = glyphMutator.makeInstance(locHorizontal, bend=bend) verticalGlyphInstanceObject = glyphMutator.makeInstance(locVertical, bend=bend) - # merge them again + # merge them again in a beautiful single line: glyphInstanceObject = (1,0)*horizontalGlyphInstanceObject + (0,1)*verticalGlyphInstanceObject except IndexError: # alignment problem with the data? - note = "Quite possibly some sort of data alignment error in %s" % glyphName if self.debug: + note = "makeInstance: Quite possibly some sort of data alignment error in %s" % glyphName self.logger.info(note) - self.problems.append(note) continue if self.roundGeometry: try: glyphInstanceObject = glyphInstanceObject.round() except AttributeError: - # what are we catching here? - print(f"no round method for {glyphInstanceObject} ?") - pass + # what are we catching here? + # math objects without a round method? + if self.debug: + note = f"makeInstance: no round method for {glyphInstanceObject} ?" + self.logger.info(note) try: # File "/Users/erik/code/ufoProcessor/Lib/ufoProcessor/__init__.py", line 649, in makeInstance # glyphInstanceObject.extractGlyph(font[glyphName], onlyGeometry=True) @@ -838,9 +874,53 @@ def makeInstance(self, instanceDescriptor, if self.debug: self.logger.info(f"\t\t\t{len(selectedGlyphNames)} glyphs added") - return font + # cache? could cause a lot of material in memory that we don't really need. Test this! + + @memoize + def makeOneGlyph(self, glyphName, location, bend=False, decomposeComponents=True, useVarlib=False, roundGeometry=False): + # make me one glyph with everything + # Unlike makeInstance(), this is focussed on a single glyph, for previewing, + # and the location will be the driving factor + continuousLocation, discreteLocation = self.splitLocation(location) + # check if the discreteLocation is within limits + if not self.checkDiscreteAxisValues(discreteLocation): + if self.debug: + self.logger.info(f"\t\tmakeOneGlyph reports: {location} has illegal value for discrete location") + return None + previousModel = self.useVarlib + self.useVarlib = useVarlib + glyphMutator, unicodes = self.getGlyphMutator(glyphName, decomposeComponents=decomposeComponents, discreteLocation=discreteLocation) + if not glyphMutator: return None + try: + if not self.isAnisotropic(location): + glyphInstanceObject = glyphMutator.makeInstance(continuousLocation, bend=bend) + else: + anisotropic = True + if self.debug: + self.logger.info(f"\t\tmakeOneGlyph anisotropic location: {location}") + loc = Location(continuousLocation) + locHorizontal, locVertical = self.splitAnisotropic(loc) + # split anisotropic location into horizontal and vertical components + horizontalGlyphInstanceObject = glyphMutator.makeInstance(locHorizontal, bend=bend) + verticalGlyphInstanceObject = glyphMutator.makeInstance(locVertical, bend=bend) + # merge them again + glyphInstanceObject = (1,0)*horizontalGlyphInstanceObject + (0,1)*verticalGlyphInstanceObject + if self.debug: + self.logger.info(f"makeOneGlyph anisotropic glyphInstanceObject {glyphInstanceObject}") + except IndexError: + # alignment problem with the data? + if self.debug: + note = "makeOneGlyph: Quite possibly some sort of data alignment error in %s" % glyphName + self.logger.info(note) + return None + glyphInstanceObject.unicodes = unicodes + if roundGeometry: + glyphInstanceObject.round() + self.useVarlib = previousModel + return glyphInstanceObject + def _copyFontInfo(self, sourceInfo, targetInfo): """ Copy the non-calculating fields from the source info.""" infoAttributes = [ @@ -897,18 +977,24 @@ def _copyFontInfo(self, sourceInfo, targetInfo): if __name__ == "__main__": import time, random from fontParts.world import RFont - ds5Path = "/Users/erik/code/type2/Principia/sources/Principia_wdth.designspace" - #ds5Path = "../../Tests/ds5/ds5.designspace" + #ds5Path = "/Users/erik/code/type2/Principia/sources/Principia_wdth.designspace" + ds5Path = "../../Tests/ds5/ds5.designspace" + + import ufoProcessor - dumpCacheLog = False + dumpCacheLog = True makeUFOs = True import os if os.path.exists(ds5Path): startTime = time.time() doc = NewUFOProcessor(ds5Path, useVarlib=False, debug=True) + doc.loadFonts() if makeUFOs: doc.generateUFOs() - + #doc.updateFonts([f]) + #doc._logLoadedFonts() + loc = doc.newDefaultLocation() + res = doc.makeOneGlyph("glyphOne", location=loc) if dumpCacheLog: doc.logger.info(f"Test: cached {len(_memoizeCache)} items") for key, item in _memoizeCache.items(): @@ -917,9 +1003,4 @@ def _copyFontInfo(self, sourceInfo, targetInfo): duration = endTime - startTime print(f"duration: {duration}" ) - sourcePaths = [s.path for s in doc.doc.sources] - f = RFont(random.choice(sourcePaths)) - print(f) - - doc.updateFonts([f]) - doc._logLoadedFonts() + inspectMemoizeCache() \ No newline at end of file From f8f838eb842be5088a99a44406043e7928a568cf Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Fri, 6 Jan 2023 00:26:57 +0100 Subject: [PATCH 24/26] Change the object name to UFOProcessor + because that would be a new name for the thing because it may not be backwards compatible anymore + collectForegroundLayerNames to make a slightly more robust last of foreground layer names. It's a bit more complicated because it may be defcon fonts as well as fontparts wrapped things, inside and outside of RF + some tweaks, comments --- Lib/ufoProcessor/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py index a8b3289..a85db62 100644 --- a/Lib/ufoProcessor/__init__.py +++ b/Lib/ufoProcessor/__init__.py @@ -54,7 +54,7 @@ # back to these when we're running as a package import ufoProcessor.varModels from ufoProcessor.varModels import VariationModelMutator -from ufoProcessor.emptyPen import checkGlyphIsEmpty +from ufoProcessor.pens import checkGlyphIsEmpty try: From 3da305463381d6bd3a97849783b57c209ac93feb Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Fri, 6 Jan 2023 11:42:15 +0100 Subject: [PATCH 25/26] rename to UFOOperator I like this name better. --- .../{refactor.py => ufoOperator.py} | 65 +++++++++------- testRefactor_RF.py | 77 +++++++++++++++++++ 2 files changed, 113 insertions(+), 29 deletions(-) rename Lib/ufoProcessor/{refactor.py => ufoOperator.py} (96%) create mode 100644 testRefactor_RF.py diff --git a/Lib/ufoProcessor/refactor.py b/Lib/ufoProcessor/ufoOperator.py similarity index 96% rename from Lib/ufoProcessor/refactor.py rename to Lib/ufoProcessor/ufoOperator.py index 662096e..e9cf37a 100644 --- a/Lib/ufoProcessor/refactor.py +++ b/Lib/ufoProcessor/ufoOperator.py @@ -8,8 +8,6 @@ import collections import logging, traceback -#from ufoProcessor import DesignSpaceProcessor - from fontTools.designspaceLib import DesignSpaceDocument, SourceDescriptor, InstanceDescriptor, AxisDescriptor, RuleDescriptor, processRules from fontTools.designspaceLib.split import splitInterpolable from fontTools.ufoLib import fontInfoAttributesVersion1, fontInfoAttributesVersion2, fontInfoAttributesVersion3 @@ -25,13 +23,12 @@ import ufoProcessor.varModels import ufoProcessor.pens - from ufoProcessor.varModels import VariationModelMutator from ufoProcessor.pens import checkGlyphIsEmpty, DecomposePointPen - from ufoProcessor.logger import Logger _memoizeCache = dict() +_memoizeStats = dict() def immutify(obj): # make an immutable version of this object. @@ -56,6 +53,9 @@ def wrapper(self, *args, **kwargs): immutablekwargs = immutify(kwargs) key = (function.__name__, self, immutableargs, immutify(kwargs)) if key in _memoizeCache: + if not key in _memoizeStats: + _memoizeStats[key] = 0 + _memoizeStats[key] += 1 return _memoizeCache[key] else: result = function(self, *args, **kwargs) @@ -91,7 +91,7 @@ def getUFOVersion(ufoPath): return p.get('formatVersion') def getDefaultLayerName(f): - # get the name of the default layer from a defcon font and from a fontparts font + # get the name of the default layer from a defcon font (outside RF) and from a fontparts font (outside and inside RF) if issubclass(type(f), defcon.objects.font.Font): return f.layers.defaultLayer.name elif issubclass(type(f), fontParts.fontshell.font.RFont): @@ -99,7 +99,7 @@ def getDefaultLayerName(f): return None # wrapped, not inherited -class NewUFOProcessor(object): +class UFOOperator(object): fontClass = defcon.Font layerClass = defcon.Layer @@ -167,6 +167,7 @@ def _instantiateFont(self, path): # if our fontClass doesnt support all the additional classes return self.fontClass(path) + # loading and updating fonts def loadFonts(self, reload=False): # Load the fonts and find the default candidate based on the info flag if self._fontsLoaded and not reload: @@ -182,7 +183,6 @@ def loadFonts(self, reload=False): # make sure it has a unique name sourceDescriptor.name = "source.%d" % i if sourceDescriptor.name not in self.fonts: - # if os.path.exists(sourceDescriptor.path): f = self.fonts[sourceDescriptor.name] = self._instantiateFont(sourceDescriptor.path) thisLayerName = getDefaultLayerName(f) @@ -204,7 +204,6 @@ def _logLoadedFonts(self): for name, fontObj in self.fonts.items(): self.logger.info(f"\t\tloaded: , id: {id(fontObj):X}, {os.path.basename(fontObj.path)}, format: {getUFOVersion(fontObj.path)}") - # updating fonts def updateFonts(self, fontObjects): # this is to update the loaded fonts. # it should be the way for an editor to provide a list of fonts that are open @@ -241,6 +240,7 @@ def glyphChanged(self, glyphName): if key[0] in ("getGlyphMutator", "collectSourcesForGlyph") and key[2][0][0] == glyphName: del _memoizeCache[key] + # manipulate locations and axes def splitLocation(self, location): # split a location in a continouous and a discrete part discreteAxes = [a.name for a in self.getOrderedDiscreteAxes()] @@ -293,9 +293,6 @@ def _getAxisOrder(self): axisOrder = property(_getAxisOrder, doc="get the axis order from the axis descriptors") - #serializedAxes = property(getSerializedAxes, doc="a list of dicts with the axis values") - - # some ds5 work def getOrderedDiscreteAxes(self): # return the list of discrete axis objects, in the right order axes = [] @@ -325,17 +322,17 @@ def checkDiscreteAxisValues(self, location): def collectBaseGlyphs(self, glyphName, location): # make a list of all baseglyphs needed to build this glyph, at this location # Note: different discrete values mean that the glyph component set up can be different too - continuous, discrete = self.splitLocation(location) + continuousLocation, discreteLocation = self.splitLocation(location) names = set() def _getComponentNames(glyph): + # so we can do recursion names = set() for comp in glyph.components: names.add(comp.baseGlyph) for n in _getComponentNames(glyph.font[comp.baseGlyph]): names.add(n) return list(names) - - for sourceDescriptor in self.findSourceDescriptorsForDiscreteLocation(discrete): + for sourceDescriptor in self.findSourceDescriptorsForDiscreteLocation(discreteLocation): sourceFont = self.fonts[sourceDescriptor.name] if not glyphName in sourceFont: continue [names.add(n) for n in _getComponentNames(sourceFont[glyphName])] @@ -416,6 +413,7 @@ def newDefaultLocation(self, bend=False, discreteLocation=None): @memoize def isAnisotropic(self, location): + # check if the location has anisotropic values for v in location.values(): if isinstance(v, (list, tuple)): return True @@ -423,6 +421,7 @@ def isAnisotropic(self, location): @memoize def splitAnisotropic(self, location): + # split the anisotropic location into a horizontal and vertical component x = Location() y = Location() for dim, val in location.items(): @@ -438,6 +437,7 @@ def _getAxisOrder(self): return [a.name for a in self.doc.axes] def generateUFOs(self): + # generate an UFO for each of the instance locations glyphCount = 0 self.loadFonts() if self.debug: @@ -472,7 +472,7 @@ def generateUFOs(self): @memoize def getInfoMutator(self, discreteLocation=None): - """ Returns a info mutator """ + """ Returns a info mutator for this discrete location """ infoItems = [] if discreteLocation is not None: sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation) @@ -494,6 +494,15 @@ def getInfoMutator(self, discreteLocation=None): bias, self._infoMutator = self.getVariationModel(infoItems, axes=self.getSerializedAxes(), bias=infoBias) return self._infoMutator + def collectForegroundLayerNames(self): + """Return list of names of the default layers of all the fonts in this system. + Include None and foreground. XX Why + """ + names = set([None, 'foreground']) + for key, font in self.fonts.items(): + names.add(getDefaultLayerName(font)) + return list(names) + @memoize def getKerningMutator(self, pairs=None, discreteLocation=None): """ Return a kerning mutator, collect the sources, build mathGlyphs. @@ -505,7 +514,7 @@ def getKerningMutator(self, pairs=None, discreteLocation=None): else: sources = self.sources kerningItems = [] - foregroundLayers = [None, 'foreground', 'public.default'] + foregroundLayers = self.collectForegroundLayerNames() if pairs is None: for sourceDescriptor in sources: if sourceDescriptor.layerName not in foregroundLayers: @@ -551,8 +560,7 @@ def getGlyphMutator(self, glyphName, decomposeComponents=False, **discreteLocation, ): - fromCache = False - # make a mutator / varlib object for glyphName, with the sources for the given discrete location + """make a mutator / varlib object for glyphName, with the sources for the given discrete location""" items, unicodes = self.collectSourcesForGlyph(glyphName, decomposeComponents=decomposeComponents, **discreteLocation) new = [] for a, b, c in items: @@ -573,7 +581,8 @@ def getGlyphMutator(self, glyphName, self.logger.info(note) return thing, unicodes - @memoize + # stats indicate this does not get called very often, so caching may not be useful + #@memoize def isLocalDefault(self, location): # return True if location is a local default defaults = {} @@ -584,7 +593,8 @@ def isLocalDefault(self, location): return False return True - @memoize + # stats indicate this does not get called very often, so caching may not be useful + #@memoize def filterThisLocation(self, location, mutedAxes=None): # return location with axes is mutedAxes removed # this means checking if the location is a non-default value @@ -694,9 +704,6 @@ def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, discreteL processThis = processThis.toMathGlyph() else: processThis = self.mathGlyphClass(processThis) - # this is where the location is linked to the glyph - # this loc needs to have the discrete location subtracted - # XX @@ continuous, discrete = self.splitLocation(loc) items.append((continuous, processThis, sourceInfo)) empties.append((thisIsDefault, foundEmpty)) @@ -977,24 +984,22 @@ def _copyFontInfo(self, sourceInfo, targetInfo): if __name__ == "__main__": import time, random from fontParts.world import RFont - #ds5Path = "/Users/erik/code/type2/Principia/sources/Principia_wdth.designspace" ds5Path = "../../Tests/ds5/ds5.designspace" - - import ufoProcessor - dumpCacheLog = True makeUFOs = True import os if os.path.exists(ds5Path): startTime = time.time() - doc = NewUFOProcessor(ds5Path, useVarlib=False, debug=True) + doc = UFOOperator(ds5Path, useVarlib=True, debug=True) doc.loadFonts() + print("collectForegroundLayerNames", doc.collectForegroundLayerNames()) if makeUFOs: doc.generateUFOs() #doc.updateFonts([f]) #doc._logLoadedFonts() loc = doc.newDefaultLocation() res = doc.makeOneGlyph("glyphOne", location=loc) + if dumpCacheLog: doc.logger.info(f"Test: cached {len(_memoizeCache)} items") for key, item in _memoizeCache.items(): @@ -1003,4 +1008,6 @@ def _copyFontInfo(self, sourceInfo, targetInfo): duration = endTime - startTime print(f"duration: {duration}" ) - inspectMemoizeCache() \ No newline at end of file + inspectMemoizeCache() + for key, value in _memoizeStats.items(): + print(key[0], value) \ No newline at end of file diff --git a/testRefactor_RF.py b/testRefactor_RF.py new file mode 100644 index 0000000..e864b4a --- /dev/null +++ b/testRefactor_RF.py @@ -0,0 +1,77 @@ +from random import randint +import ufoProcessor +import ufoProcessor.ufoOperator +import importlib +importlib.reload(ufoProcessor.ufoOperator) +print(ufoProcessor.__file__) +ds5Path = "/Users/erik/code/type2/Principia/sources/Principia_wdth.designspace" +doc = ufoProcessor.ufoOperator.UFOOperator(ds5Path, useVarlib=False, debug=True) +doc.loadFonts() +#doc.generateUFOs() + +def ip(a, b, f): + return a+f*(b-a) + +font = CurrentFont() + +loc = doc.newDefaultLocation() +loc['width'] = randint(50, 100) +print(loc) + +# make some tests at different layers +test = [ + ("foreground", dict(width=75, italic=0), False), + ("background", dict(width=75, italic=1), False), + ("random_width_inter_MM", dict(width=randint(50,100), italic=1), False), + ("random_width_xtr_MM", dict(width=randint(10,150), italic=1), False), + ("random_width_xtr_narrow_VL", dict(width=randint(10,50), italic=1), True), + ("random_width_xtr_wide_VL", dict(width=randint(100,500), italic=1), True), + + ("10_width_xtr_VL", dict(width=10, italic=1), True), + ("10_width_xtr_MM", dict(width=10, italic=1), False), + ("200_width_xtr_wide_VL", dict(width=200, italic=1), True), + ("200_width_xtr_wide_MM", dict(width=200, italic=1), False), + + ("aniso_width_inter_MM", dict(width=(50,100), italic=0), False), + ("aniso_width_inter_VL", dict(width=(50,100), italic=0), True), + + ("aniso_width_xtra_MM", dict(width=(-50,200), italic=0), False), + ("aniso_width_xtra_VL", dict(width=(-50,200), italic=0), True), + + ] + +g = CurrentGlyph() +dstName = g.name + +for layerName, loc, useVarlib in test: + res = doc.makeOneGlyph(dstName, location=loc, bend=True, decomposeComponents=False, useVarlib=useVarlib, roundGeometry=True) + dst = font[dstName].getLayer(layerName) + dst.clear() + res.guidelines = [] # delete guidelines in mathglyph until fontparts issue is solved + dst.fromMathGlyph(res) + dst.width = max(0, res.width) + + print(len(dst.components)) + for comp in dst.components: + print("-- processing baseglyph", comp.baseGlyph) + res2 = doc.makeOneGlyph(comp.baseGlyph, location=loc, bend=True, decomposeComponents=False, useVarlib=useVarlib, roundGeometry=True) + # let's make sure the glyph exists in the layer + print('layerName:', layerName) + dstLayer = font.getLayer(layerName) + if not comp.baseGlyph in dstLayer: + dstLayer.newGlyph(comp.baseGlyph) + dst2 = dstLayer[comp.baseGlyph] + dst2.clear() + print('dst.anchors:', dst.anchors) + print('dst.guidelines:', dst.guidelines) + for item in res2.guidelines: + print(item) + res2.guidelines = [] # delete guidelines in mathglyph until fontparts issue is solved + print('dst.guidelines:', res2.guidelines) + dst2.fromMathGlyph(res2) + dst2.width = max(0, res2.width) + + dst2.update() + dst.update() + +ufoProcessor.refactor.inspectMemoizeCache() From 45cef3ebd82ba0db8178580df9d2bfaa88b2b2a8 Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Fri, 6 Jan 2023 11:54:27 +0100 Subject: [PATCH 26/26] Update to test files --- .../geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif | 2 +- .../geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif | 1 + .../geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif | 2 +- .../geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif | 1 + .../geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif | 2 +- .../geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif | 1 + .../geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif | 2 +- .../geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif | 1 + .../geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif | 2 +- .../geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif | 1 + .../geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif | 2 +- .../geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif | 1 + .../geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif | 2 +- .../geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif | 1 + .../geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif | 2 +- .../geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif | 1 + .../geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif | 2 +- .../geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif | 1 + .../geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif | 2 +- .../geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif | 1 + .../geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif | 2 +- .../geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif | 1 + .../geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif | 2 +- .../geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif | 1 + 24 files changed, 24 insertions(+), 12 deletions(-) diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif index 56f17c6..ab82b9d 100644 --- a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif +++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif @@ -1,7 +1,7 @@ - + diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif index c4fc582..c523ee7 100644 --- a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif +++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif @@ -1,6 +1,7 @@ + diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif index 205dd1f..c7da8f6 100644 --- a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif +++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif @@ -1,7 +1,7 @@ - + diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif index c4fc582..c523ee7 100644 --- a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif +++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif @@ -1,6 +1,7 @@ + diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif index 24ade3f..42f461a 100644 --- a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif +++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif @@ -1,7 +1,7 @@ - + diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif index c4fc582..c523ee7 100644 --- a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif +++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif @@ -1,6 +1,7 @@ + diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif index a14c6f2..928f76f 100644 --- a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif +++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif @@ -1,7 +1,7 @@ - + diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif index c4fc582..c523ee7 100644 --- a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif +++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif @@ -1,6 +1,7 @@ + diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif index 1cb076d..baf7268 100644 --- a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif +++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif @@ -1,7 +1,7 @@ - + diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif index c4fc582..c523ee7 100644 --- a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif +++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif @@ -1,6 +1,7 @@ + diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif index b353c48..9d65323 100644 --- a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif +++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif @@ -1,7 +1,7 @@ - + diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif index c4fc582..c523ee7 100644 --- a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif +++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif @@ -1,6 +1,7 @@ + diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif index 0f9d7d2..cc09903 100644 --- a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif +++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif @@ -1,7 +1,7 @@ - + diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif index fd2803b..7eea472 100644 --- a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif +++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif @@ -1,6 +1,7 @@ + diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif index 5167d41..f9c3a61 100644 --- a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif +++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif @@ -1,7 +1,7 @@ - + diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif index fd2803b..7eea472 100644 --- a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif +++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif @@ -1,6 +1,7 @@ + diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif index 6e7e8f6..978f672 100644 --- a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif +++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif @@ -1,7 +1,7 @@ - + diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif index fd2803b..7eea472 100644 --- a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif +++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif @@ -1,6 +1,7 @@ + diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif index abedbd8..a74b7e0 100644 --- a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif +++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif @@ -1,7 +1,7 @@ - + diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif index fd2803b..7eea472 100644 --- a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif +++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif @@ -1,6 +1,7 @@ + diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif index 8de8555..efe81ee 100644 --- a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif +++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif @@ -1,7 +1,7 @@ - + diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif index fd2803b..7eea472 100644 --- a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif +++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif @@ -1,6 +1,7 @@ + diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif index 4e4561c..54862c0 100644 --- a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif +++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif @@ -1,7 +1,7 @@ - + diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif index fd2803b..7eea472 100644 --- a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif +++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif @@ -1,6 +1,7 @@ +