Repository: fonttools/fonttools

Issue:
[ttx] AttributeError "isCFF2" from T2CharString when dumping CFF2 toXML()
Dumping "SourceSerifVariable-Roman.otf" with ttx yields an AttributeError from T2CharString.toXML method:

```
$ ttx ~/Downloads/SourceSerifVariable-Roman.otf 
Dumping "/home/clupo/Downloads/SourceSerifVariable-Roman.otf" to "/home/clupo/Downloads/SourceSerifVariable-Roman.ttx"...
Dumping 'GlyphOrder' table...
Dumping 'head' table...
Dumping 'hhea' table...
Dumping 'maxp' table...
Dumping 'OS/2' table...
Dumping 'hmtx' table...
Dumping 'cmap' table...
Dumping 'name' table...
Dumping 'post' table...
Dumping 'BASE' table...
Dumping 'CFF2' table...
ERROR: Unhandled exception has occurred
Traceback (most recent call last):
  File "/home/clupo/Github/fonttools/Lib/fontTools/ttx.py", line 398, in main
    process(jobs, options)
  File "/home/clupo/Github/fonttools/Lib/fontTools/ttx.py", line 372, in process
    action(input, output, options)
  File "/home/clupo/Github/fonttools/Lib/fontTools/misc/loggingTools.py", line 375, in wrapper
    return func(*args, **kwds)
  File "/home/clupo/Github/fonttools/Lib/fontTools/ttx.py", line 269, in ttDump
    newlinestr=options.newlinestr)
  File "/home/clupo/Github/fonttools/Lib/fontTools/ttLib/ttFont.py", line 228, in saveXML
    self._saveXML(writer, **kwargs)
  File "/home/clupo/Github/fonttools/Lib/fontTools/ttLib/ttFont.py", line 283, in _saveXML
    self._tableToXML(tableWriter, tag, splitGlyphs=splitGlyphs)
  File "/home/clupo/Github/fonttools/Lib/fontTools/ttLib/ttFont.py", line 314, in _tableToXML
    table.toXML(writer, self)
  File "/home/clupo/Github/fonttools/Lib/fontTools/ttLib/tables/C_F_F_.py", line 42, in toXML
    self.cff.toXML(writer)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 156, in toXML
    font.toXML(xmlWriter)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 2330, in toXML
    BaseDict.toXML(self, xmlWriter)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 2272, in toXML
    conv.xmlWrite(xmlWriter, name, value)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 1175, in xmlWrite
    value.toXML(xmlWriter)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 722, in toXML
    self[i].toXML(xmlWriter)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 2272, in toXML
    conv.xmlWrite(xmlWriter, name, value)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 1175, in xmlWrite
    value.toXML(xmlWriter)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 2272, in toXML
    conv.xmlWrite(xmlWriter, name, value)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 1175, in xmlWrite
    value.toXML(xmlWriter)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 652, in toXML
    subr.toXML(xmlWriter)
  File "/home/clupo/Github/fonttools/Lib/fontTools/misc/psCharStrings.py", line 1091, in toXML
    if self.isCFF2:
AttributeError: 'T2CharString' object has no attribute 'isCFF2'
```

If we need to parametrize the toXML method depending on whether isCFF2 or not, then we need to have an extra isCFF2=False parameter in the top-level CFFFontSet class, and set that to True from the `table_C_F_F__2.toXML`
[ttx] AttributeError "isCFF2" from T2CharString when dumping CFF2 toXML()
Dumping "SourceSerifVariable-Roman.otf" with ttx yields an AttributeError from T2CharString.toXML method:

```
$ ttx ~/Downloads/SourceSerifVariable-Roman.otf 
Dumping "/home/clupo/Downloads/SourceSerifVariable-Roman.otf" to "/home/clupo/Downloads/SourceSerifVariable-Roman.ttx"...
Dumping 'GlyphOrder' table...
Dumping 'head' table...
Dumping 'hhea' table...
Dumping 'maxp' table...
Dumping 'OS/2' table...
Dumping 'hmtx' table...
Dumping 'cmap' table...
Dumping 'name' table...
Dumping 'post' table...
Dumping 'BASE' table...
Dumping 'CFF2' table...
ERROR: Unhandled exception has occurred
Traceback (most recent call last):
  File "/home/clupo/Github/fonttools/Lib/fontTools/ttx.py", line 398, in main
    process(jobs, options)
  File "/home/clupo/Github/fonttools/Lib/fontTools/ttx.py", line 372, in process
    action(input, output, options)
  File "/home/clupo/Github/fonttools/Lib/fontTools/misc/loggingTools.py", line 375, in wrapper
    return func(*args, **kwds)
  File "/home/clupo/Github/fonttools/Lib/fontTools/ttx.py", line 269, in ttDump
    newlinestr=options.newlinestr)
  File "/home/clupo/Github/fonttools/Lib/fontTools/ttLib/ttFont.py", line 228, in saveXML
    self._saveXML(writer, **kwargs)
  File "/home/clupo/Github/fonttools/Lib/fontTools/ttLib/ttFont.py", line 283, in _saveXML
    self._tableToXML(tableWriter, tag, splitGlyphs=splitGlyphs)
  File "/home/clupo/Github/fonttools/Lib/fontTools/ttLib/ttFont.py", line 314, in _tableToXML
    table.toXML(writer, self)
  File "/home/clupo/Github/fonttools/Lib/fontTools/ttLib/tables/C_F_F_.py", line 42, in toXML
    self.cff.toXML(writer)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 156, in toXML
    font.toXML(xmlWriter)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 2330, in toXML
    BaseDict.toXML(self, xmlWriter)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 2272, in toXML
    conv.xmlWrite(xmlWriter, name, value)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 1175, in xmlWrite
    value.toXML(xmlWriter)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 722, in toXML
    self[i].toXML(xmlWriter)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 2272, in toXML
    conv.xmlWrite(xmlWriter, name, value)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 1175, in xmlWrite
    value.toXML(xmlWriter)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 2272, in toXML
    conv.xmlWrite(xmlWriter, name, value)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 1175, in xmlWrite
    value.toXML(xmlWriter)
  File "/home/clupo/Github/fonttools/Lib/fontTools/cffLib/__init__.py", line 652, in toXML
    subr.toXML(xmlWriter)
  File "/home/clupo/Github/fonttools/Lib/fontTools/misc/psCharStrings.py", line 1091, in toXML
    if self.isCFF2:
AttributeError: 'T2CharString' object has no attribute 'isCFF2'
```

If we need to parametrize the toXML method depending on whether isCFF2 or not, then we need to have an extra isCFF2=False parameter in the top-level CFFFontSet class, and set that to True from the `table_C_F_F__2.toXML`


Discussion:

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
@@ -1005,6 +1005,10 @@ def compile(self, isCFF2=False):
 				bytecode.append(encodeFixed(token))
 			else:
 				assert 0, "unsupported type: %s" % type(token)
+		if not isCFF2 and program and not isinstance(program[-1], basestring):
+			raise CharStringCompileError(
+				"T2CharString or Subr has items on the stack after last operator."
+			)
 		try:
 			bytecode = bytesjoin(bytecode)
 		except TypeError:
@@ -1088,13 +1092,12 @@ def toXML(self, xmlWriter):
 				else:
 					args.append(token)
 			if args:
-				if self.isCFF2:
-					# CFF2Subr's can have numeric arguments on the stack after the last operator.
-					args = [str(arg) for arg in args]
-					line = ' '.join(args)
-					xmlWriter.write(line)
-				else:
-					assert 0, "T2Charstring or Subr has items on the stack after last operator."
+				# NOTE: only CFF2 charstrings/subrs can have numeric arguments on
+				# the stack after the last operator. Compiling this would fail if
+				# this is part of CFF 1.0 table.
+				args = [str(arg) for arg in args]
+				line = ' '.join(args)
+				xmlWriter.write(line)
 
 	def fromXML(self, name, attrs, content):
 		from fontTools.misc.textTools import binary2num, readHex


Test patch: