Repository: fonttools/fonttools

Issue:
T2CharString.draw not drawing properly
Hi, I want to take draw contour from cff font into `defcon's Glyph` object. It's it now drawn correctly as you can see in the attached pictures. Am I doing something wrong or is it something with fontTools? The fact that the same happens in RoboFont makes me believe that I am either one of more people doing something wrong or that something is wrong in FonTools. What do you think? Thanks, Jan

```from fontTools.ttLib import TTFont
from fontTools.cffLib import PrivateDict
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.pens.pointPen import SegmentToPointPen
from fontTools.pens.t2CharStringPen import T2CharStringPen
from datetime import datetime
from defcon.objects.glyph import Glyph


font = TTFont("sourceSerif.otf")
cff2 = font["CFF2"]

if hasattr(cff2, "desubroutinize"):
    print(cff2.desubroutinize())


content = cff2.cff[cff2.cff.keys()[0]]
letter = content.CharStrings["A"]

print(letter)
glyph = Glyph()
pen = glyph.getPen()
letter.draw(pen)
go = cff2.getGlyphOrder()
cff_pen = T2CharStringPen(None, go, CFF2=True)
glyph.draw(cff_pen)

private = content.CharStrings["A"].private
content.CharStrings["O"] = cff_pen.getCharString(private=private)

font.save("output.otf")
```
drawing shape of A into glyph O
<img width="296" alt="Screenshot 2021-11-20 at 19 29 57" src="https://user-images.githubusercontent.com/44162266/142737294-654c5157-fa98-4d53-a814-9b31524e3d25.png">

the same issue happens when opening sourceSerif.otf in RoboFont
<img width="83" alt="Screenshot 2021-11-20 at 19 30 28" src="https://user-images.githubusercontent.com/44162266/142737309-65175eb7-0e75-4b18-b179-19cdee053b1a.png">
[bug.zip](https://github.com/fonttools/fonttools/files/7575039/bug.zip)





Discussion:
The problem seems to be that for CFF2, charstring.draw() doesn't output the final `pen.closePath()`. Looks like a bug to me.
Adding `extractor.endPath()` after this line seems to fix it:
https://github.com/fonttools/fonttools/blob/0a7164a45299a1012e75832b70f91b8721962819/Lib/fontTools/misc/psCharStrings.py#L982
Oh wow, thanks! I was suspecting the subroutines got mixed up, makes sense now 
are you making PR for that as you are the one who solved it or shall I? 
PR welcome, especially if it adds a test case for this.
> Adding `extractor.endPath()` after this line seems to fix it:

This makes sense to me. In CFF2 there's no `endchar` operator, which in CFF1 was emitting an `endPath`.
At this point in the code we don't distinguish between CFF1 and CFF2. My proposed patch would call `pen.closePath()` redundantly for CFF1. Perhaps the extractor should be responsible for this instead, and synthesize `endchar` in case it is missing.
Your proposed change was fine indeed. 
My concern is that I'm not sure it is a good idea to call `pen.closePath()` twice in the case of CFF1. I think we should avoid that.
The code takes care of itself in my reading. 
The `T2OutlineExtractor` calls closePath unconditionally:

https://github.com/fonttools/fonttools/blob/4bb0e776a72b508856f91ac40ea150772a3d4a80/Lib/fontTools/misc/psCharStrings.py#L537-L540
closePath can be called twice alright. 
Shall we do this?

Resolving patch:
diff --git a/Lib/fontTools/misc/psCharStrings.py b/Lib/fontTools/misc/psCharStrings.py
--- a/Lib/fontTools/misc/psCharStrings.py
+++ b/Lib/fontTools/misc/psCharStrings.py
@@ -980,6 +980,7 @@ def draw(self, pen):
 				self.private.nominalWidthX, self.private.defaultWidthX,
 				self.private)
 		extractor.execute(self)
+		extractor.endPath()
 		self.width = extractor.width
 
 	def calcBounds(self, glyphSet):


Test patch:
diff --git a/Tests/misc/psCharStrings_test.py b/Tests/misc/psCharStrings_test.py
--- a/Tests/misc/psCharStrings_test.py
+++ b/Tests/misc/psCharStrings_test.py
@@ -8,6 +8,7 @@
     read_fixed1616,
     read_realNumber,
 )
+from fontTools.pens.recordingPen import RecordingPen
 import unittest
 
 
@@ -158,6 +159,14 @@ def test_fromXML(self):
                 self.assertNotIsInstance(expected_arg, str)
                 self.assertAlmostEqual(arg, expected_arg)
 
+    def test_pen_closePath(self):
+        # Test CFF2/T2 charstring: it does NOT end in "endchar"
+        # https://github.com/fonttools/fonttools/issues/2455
+        cs = self.stringToT2CharString("100 100 rmoveto -50 -150 200 0 -50 150 rrcurveto")
+        pen = RecordingPen()
+        cs.draw(pen)
+        self.assertEqual(pen.value[-1], ('closePath', ()))
+
 
 if __name__ == "__main__":
     import sys