Repository: googlefonts/ufo2ft
Pull request #386 (2020-06-05 10:55:40)

postProcessor: strip charstring widths for CFF2; require cffsubr for CFF2
Fonttools `convertCFFtoCFF2` currently does not do it for us

https://github.com/fonttools/fonttools/issues/1835

Also because of the presence of widths in CFF2 is invalid, we currently can't convert CFF table that had been subroutinized with compreffor to CFF2. I haven't figured out a way to strip widths without alslo getting rid of the subroutines themselves. So for now, we require cffsubr for suborutinizing CFF2.


Patch:
diff --git a/Lib/ufo2ft/__init__.py b/Lib/ufo2ft/__init__.py
index e307c67..ec05afc 100644
--- a/Lib/ufo2ft/__init__.py
+++ b/Lib/ufo2ft/__init__.py
@@ -104,6 +104,8 @@ def compileOTF(
       compressing CFF charstrings, if subroutinization is enabled by optimizeCFF
       parameter. Choose between "compreffor" or "cffsubr".
       By default "compreffor" is used for CFF 1, and "cffsubr" for CFF 2.
+      NOTE: cffsubr is required for subroutinizing CFF2 tables, as compreffor
+      currently doesn't support it.
     """
     logger.info("Pre-processing glyphs")
 
diff --git a/Lib/ufo2ft/postProcessor.py b/Lib/ufo2ft/postProcessor.py
index 2120776..fa4c75f 100644
--- a/Lib/ufo2ft/postProcessor.py
+++ b/Lib/ufo2ft/postProcessor.py
@@ -296,12 +296,13 @@ class PostProcessor(object):
 
         logger.info("Converting CFF table to CFF2")
 
-        # convertCFFtoCFF2 expects a fully decompiled CFF table
-        otf["CFF "].cff[0].decompileAllCharStrings()
-
-        # need to decompile glyphOrder, otherwise I get AttribtueError('charset') when
-        # running convertCFFtoCFF2(). TODO: Fix it upstream and remove this
-        _ = otf.getGlyphOrder()
+        # convertCFFtoCFF2 doesn't strip T2CharStrings' widths, so we do it ourselves
+        # https://github.com/fonttools/fonttools/issues/1835
+        charstrings = otf["CFF "].cff[0].CharStrings
+        for glyph_name in otf.getGlyphOrder():
+            cs = charstrings[glyph_name]
+            cs.decompile()
+            cs.program = _stripCharStringWidth(cs.program)
 
         convertCFFtoCFF2(otf)
 
@@ -314,18 +315,15 @@ class PostProcessor(object):
     def _subroutinize_with_compreffor(cls, otf, cffVersion):
         from compreffor import compress
 
-        if cls._get_cff_version(otf) != CFFVersion.CFF:
+        if cls._get_cff_version(otf) != CFFVersion.CFF or cffVersion != CFFVersion.CFF:
             raise NotImplementedError(
-                "Only 'CFF ' 1.0 input is supported by compreffor; try using cffsubr"
+                "Only 'CFF ' 1.0 is supported by compreffor; try using cffsubr"
             )
 
         logger.info("Subroutinizing CFF table with compreffor")
 
         compress(otf)
 
-        if cffVersion == CFFVersion.CFF2:
-            cls._convert_cff_to_cff2(otf)
-
     @classmethod
     def _subroutinize_with_cffsubr(cls, otf, cffVersion):
         import cffsubr
@@ -339,3 +337,43 @@ class PostProcessor(object):
         logger.info(msg)
 
         return cffsubr.subroutinize(otf, cff_version=cffVersion, keep_glyph_names=False)
+
+
+# Adapted from fontTools.cff.specializer.programToCommands
+# https://github.com/fonttools/fonttools/blob/babca16/Lib/fontTools/cffLib/specializer.py#L40-L122
+# When converting from CFF to CFF2 we need to drop the charstrings' widths.
+# This function returns a new charstring program without the initial width value.
+# TODO: Move to fontTools?
+def _stripCharStringWidth(program):
+    seenWidthOp = False
+    result = []
+    stack = []
+    for token in program:
+        if not isinstance(token, str):
+            stack.append(token)
+            continue
+
+        if (not seenWidthOp) and token in {
+            "hstem",
+            "hstemhm",
+            "vstem",
+            "vstemhm",
+            "cntrmask",
+            "hintmask",
+            "hmoveto",
+            "vmoveto",
+            "rmoveto",
+            "endchar",
+        }:
+            seenWidthOp = True
+            parity = token in {"hmoveto", "vmoveto"}
+            numArgs = len(stack)
+            if numArgs and (numArgs % 2) ^ parity:
+                stack.pop(0)  # pop width
+
+        result.extend(stack)
+        result.append(token)
+        stack = []
+    if stack:
+        result.extend(stack)
+    return result


Test patch:
diff --git a/tests/data/TestFont-NoOptimize-CFF2.ttx b/tests/data/TestFont-NoOptimize-CFF2.ttx
index edd172f..8540b8e 100644
--- a/tests/data/TestFont-NoOptimize-CFF2.ttx
+++ b/tests/data/TestFont-NoOptimize-CFF2.ttx
@@ -299,7 +299,7 @@
       </FDArray>
       <CharStrings>
         <CharString name=".notdef">
-          100 450 0 rmoveto
+          450 0 rmoveto
           0 750 rlineto
           -400 0 rlineto
           0 -750 rlineto
@@ -309,33 +309,33 @@
           300 0 rlineto
         </CharString>
         <CharString name="uni0020">
-          -150</CharString>
+        </CharString>
         <CharString name="uni0061">
-          -12 66 0 rmoveto
+          66 0 rmoveto
           256 0 rlineto
           -128 510 rlineto
         </CharString>
         <CharString name="uni0062">
-          10 100 505 rmoveto
+          100 505 rmoveto
           0 -510 rlineto
           210 0 rlineto
           0 510 rlineto
         </CharString>
         <CharString name="uni0063">
-          -26 300 -10 rmoveto
+          300 -10 rmoveto
           0 510 rlineto
           -150 0 -50 -50 0 -205 rrcurveto
           0 -205 50 -50 150 0 rrcurveto
         </CharString>
         <CharString name="uni0064">
-          -26 151 197 rmoveto
+          151 197 rmoveto
           -34 0 -27 -27 0 -33 rrcurveto
           0 -33 27 -27 34 0 rrcurveto
           33 0 27 27 0 33 rrcurveto
           0 33 -27 27 -33 0 rrcurveto
         </CharString>
         <CharString name="uni0065">
-          -12 66 510 rmoveto
+          66 510 rmoveto
           128 -435 rlineto
           128 435 rlineto
           -377 -487 rmoveto
@@ -344,7 +344,7 @@
           -204 0 -50 -50 0 -150 rrcurveto
         </CharString>
         <CharString name="uni0066">
-          10 66 510 rmoveto
+          66 510 rmoveto
           256 0 rlineto
           -128 -435 rlineto
           -249 -52 rmoveto
@@ -353,12 +353,12 @@
           -204 0 -50 -50 0 -150 rrcurveto
         </CharString>
         <CharString name="uni0067">
-          -12 66 0 rmoveto
+          66 0 rmoveto
           256 0 rlineto
           -128 510 rlineto
         </CharString>
         <CharString name="uni0068">
-          10 211 657 rmoveto
+          211 657 rmoveto
           -34 0 -27 -27 0 -33 rrcurveto
           0 -33 27 -27 34 0 rrcurveto
           33 0 27 27 0 33 rrcurveto
@@ -369,7 +369,7 @@
           0 510 rlineto
         </CharString>
         <CharString name="uni0069">
-          200 -55 -80 rmoveto
+          -55 -80 rmoveto
           509 0 rlineto
           0 149 -50 50 -205 0 rrcurveto
           -204 0 -50 -50 0 -149 rrcurveto
@@ -378,7 +378,7 @@
           -128 510 rlineto
         </CharString>
         <CharString name="uni006A">
-          200 -55 -80 rmoveto
+          -55 -80 rmoveto
           509 0 rlineto
           0 149 -50 50 -205 0 rrcurveto
           -204 0 -50 -50 0 -149 rrcurveto
@@ -387,7 +387,7 @@
           128 510 rlineto
         </CharString>
         <CharString name="uni006B">
-          200 66 0 rmoveto
+          66 0 rmoveto
           256 0 rlineto
           -128 510 rlineto
           -28 -510 rmoveto
@@ -395,7 +395,7 @@
           -128 510 rlineto
         </CharString>
         <CharString name="uni006C">
-          200 334 0 rmoveto
+          334 0 rmoveto
           -128 510 rlineto
           -128 -510 rlineto
           88 0 rmoveto
diff --git a/tests/data/TestFont-Specialized-CFF2.ttx b/tests/data/TestFont-Specialized-CFF2.ttx
index ea68b3a..abfc01d 100644
--- a/tests/data/TestFont-Specialized-CFF2.ttx
+++ b/tests/data/TestFont-Specialized-CFF2.ttx
@@ -299,40 +299,40 @@
       </FDArray>
       <CharStrings>
         <CharString name=".notdef">
-          100 450 hmoveto
+          450 hmoveto
           750 -400 -750 vlineto
           350 50 rmoveto
           -300 650 300 hlineto
         </CharString>
         <CharString name="uni0020">
-          -150</CharString>
+        </CharString>
         <CharString name="uni0061">
-          -12 66 hmoveto
+          66 hmoveto
           256 hlineto
           -128 510 rlineto
         </CharString>
         <CharString name="uni0062">
-          10 100 505 rmoveto
+          100 505 rmoveto
           -510 210 510 vlineto
         </CharString>
         <CharString name="uni0063">
-          -26 300 -10 rmoveto
+          300 -10 rmoveto
           510 vlineto
           -150 -50 -50 -205 -205 50 -50 150 hvcurveto
         </CharString>
         <CharString name="uni0064">
-          -26 151 197 rmoveto
+          151 197 rmoveto
           -34 -27 -27 -33 -33 27 -27 34 33 27 27 33 33 -27 27 -33 hvcurveto
         </CharString>
         <CharString name="uni0065">
-          -12 66 510 rmoveto
+          66 510 rmoveto
           128 -435 128 435 rlineto
           -377 -487 rmoveto
           509 hlineto
           150 -50 50 -205 -204 -50 -50 -150 vhcurveto
         </CharString>
         <CharString name="uni0066">
-          10 66 510 rmoveto
+          66 510 rmoveto
           256 hlineto
           -128 -435 rlineto
           -249 -52 rmoveto
@@ -340,18 +340,18 @@
           150 -50 50 -205 -204 -50 -50 -150 vhcurveto
         </CharString>
         <CharString name="uni0067">
-          -12 66 hmoveto
+          66 hmoveto
           256 hlineto
           -128 510 rlineto
         </CharString>
         <CharString name="uni0068">
-          10 211 657 rmoveto
+          211 657 rmoveto
           -34 -27 -27 -33 -33 27 -27 34 33 27 27 33 33 -27 27 -33 hvcurveto
           -111 -152 rmoveto
           -510 210 510 vlineto
         </CharString>
         <CharString name="uni0069">
-          200 -55 -80 rmoveto
+          -55 -80 rmoveto
           509 hlineto
           149 -50 50 -205 -204 -50 -50 -149 vhcurveto
           121 80 rmoveto
@@ -359,14 +359,14 @@
           -128 510 rlineto
         </CharString>
         <CharString name="uni006A">
-          200 -55 -80 rmoveto
+          -55 -80 rmoveto
           509 hlineto
           149 -50 50 -205 -204 -50 -50 -149 vhcurveto
           121 310 rmoveto
           128 -510 128 510 rlineto
         </CharString>
         <CharString name="uni006B">
-          200 66 hmoveto
+          66 hmoveto
           256 hlineto
           -128 510 rlineto
           -28 -510 rmoveto
@@ -374,7 +374,7 @@
           -128 510 rlineto
         </CharString>
         <CharString name="uni006C">
-          200 334 hmoveto
+          334 hmoveto
           -128 510 -128 -510 rlineto
           88 hmoveto
           256 hlineto
diff --git a/tests/integration_test.py b/tests/integration_test.py
index 4b3b67d..993e219 100644
--- a/tests/integration_test.py
+++ b/tests/integration_test.py
@@ -151,7 +151,7 @@ class IntegrationTest(object):
             ("compreffor", 1, "TestFont-CFF.ttx"),
             ("cffsubr", 1, "TestFont-CFF-cffsubr.ttx"),
             (None, 2, "TestFont-CFF2-cffsubr.ttx"),
-            ("compreffor", 2, "TestFont-CFF2-compreffor.ttx"),
+            # ("compreffor", 2, "TestFont-CFF2-compreffor.ttx"),
             ("cffsubr", 2, "TestFont-CFF2-cffsubr.ttx"),
         ],
         ids=[
@@ -159,7 +159,7 @@ class IntegrationTest(object):
             "compreffor-cff1",
             "cffsubr-cff1",
             "default-cff2",
-            "compreffor-cff2",
+            # "compreffor-cff2",
             "cffsubr-cff2",
         ],
     )