cffLib.py revision 18316aa769566eeb6f3f4a6ed2685fa8f8e861c2
1"""cffLib.py -- read/write tools for Adobe CFF fonts."""
2
3#
4# $Id: cffLib.py,v 1.34 2008-03-07 19:56:17 jvr Exp $
5#
6
7from __future__ import print_function, division
8from fontTools.misc.py23 import *
9from fontTools.misc import sstruct
10from fontTools.misc import psCharStrings
11from fontTools.misc.textTools import safeEval
12import struct
13
14DEBUG = 0
15
16
17cffHeaderFormat = """
18	major:   B
19	minor:   B
20	hdrSize: B
21	offSize: B
22"""
23
24class CFFFontSet:
25
26	def __init__(self):
27		pass
28
29	def decompile(self, file, otFont):
30		sstruct.unpack(cffHeaderFormat, file.read(4), self)
31		assert self.major == 1 and self.minor == 0, \
32				"unknown CFF format: %d.%d" % (self.major, self.minor)
33
34		file.seek(self.hdrSize)
35		self.fontNames = list(Index(file))
36		self.topDictIndex = TopDictIndex(file)
37		self.strings = IndexedStrings(file)
38		self.GlobalSubrs = GlobalSubrsIndex(file)
39		self.topDictIndex.strings = self.strings
40		self.topDictIndex.GlobalSubrs = self.GlobalSubrs
41
42	def __len__(self):
43		return len(self.fontNames)
44
45	def keys(self):
46		return list(self.fontNames)
47
48	def values(self):
49		return self.topDictIndex
50
51	def __getitem__(self, name):
52		try:
53			index = self.fontNames.index(name)
54		except ValueError:
55			raise KeyError(name)
56		return self.topDictIndex[index]
57
58	def compile(self, file, otFont):
59		strings = IndexedStrings()
60		writer = CFFWriter()
61		writer.add(sstruct.pack(cffHeaderFormat, self))
62		fontNames = Index()
63		for name in self.fontNames:
64			fontNames.append(name)
65		writer.add(fontNames.getCompiler(strings, None))
66		topCompiler = self.topDictIndex.getCompiler(strings, None)
67		writer.add(topCompiler)
68		writer.add(strings.getCompiler())
69		writer.add(self.GlobalSubrs.getCompiler(strings, None))
70
71		for topDict in self.topDictIndex:
72			if not hasattr(topDict, "charset") or topDict.charset is None:
73				charset = otFont.getGlyphOrder()
74				topDict.charset = charset
75
76		for child in topCompiler.getChildren(strings):
77			writer.add(child)
78
79		writer.toFile(file)
80
81	def toXML(self, xmlWriter, progress=None):
82		xmlWriter.newline()
83		for fontName in self.fontNames:
84			xmlWriter.begintag("CFFFont", name=fontName)
85			xmlWriter.newline()
86			font = self[fontName]
87			font.toXML(xmlWriter, progress)
88			xmlWriter.endtag("CFFFont")
89			xmlWriter.newline()
90		xmlWriter.newline()
91		xmlWriter.begintag("GlobalSubrs")
92		xmlWriter.newline()
93		self.GlobalSubrs.toXML(xmlWriter, progress)
94		xmlWriter.endtag("GlobalSubrs")
95		xmlWriter.newline()
96		xmlWriter.newline()
97
98	def fromXML(self, name, attrs, content):
99		if not hasattr(self, "GlobalSubrs"):
100			self.GlobalSubrs = GlobalSubrsIndex()
101			self.major = 1
102			self.minor = 0
103			self.hdrSize = 4
104			self.offSize = 4  # XXX ??
105		if name == "CFFFont":
106			if not hasattr(self, "fontNames"):
107				self.fontNames = []
108				self.topDictIndex = TopDictIndex()
109			fontName = attrs["name"]
110			topDict = TopDict(GlobalSubrs=self.GlobalSubrs)
111			topDict.charset = None  # gets filled in later
112			self.fontNames.append(fontName)
113			self.topDictIndex.append(topDict)
114			for element in content:
115				if isinstance(element, basestring):
116					continue
117				name, attrs, content = element
118				topDict.fromXML(name, attrs, content)
119		elif name == "GlobalSubrs":
120			for element in content:
121				if isinstance(element, basestring):
122					continue
123				name, attrs, content = element
124				subr = psCharStrings.T2CharString()
125				subr.fromXML(name, attrs, content)
126				self.GlobalSubrs.append(subr)
127
128
129class CFFWriter:
130
131	def __init__(self):
132		self.data = []
133
134	def add(self, table):
135		self.data.append(table)
136
137	def toFile(self, file):
138		lastPosList = None
139		count = 1
140		while True:
141			if DEBUG:
142				print("CFFWriter.toFile() iteration:", count)
143			count = count + 1
144			pos = 0
145			posList = [pos]
146			for item in self.data:
147				if hasattr(item, "getDataLength"):
148					endPos = pos + item.getDataLength()
149				else:
150					endPos = pos + len(item)
151				if hasattr(item, "setPos"):
152					item.setPos(pos, endPos)
153				pos = endPos
154				posList.append(pos)
155			if posList == lastPosList:
156				break
157			lastPosList = posList
158		if DEBUG:
159			print("CFFWriter.toFile() writing to file.")
160		begin = file.tell()
161		posList = [0]
162		for item in self.data:
163			if hasattr(item, "toFile"):
164				item.toFile(file)
165			else:
166				file.write(item)
167			posList.append(file.tell() - begin)
168		assert posList == lastPosList
169
170
171def calcOffSize(largestOffset):
172	if largestOffset < 0x100:
173		offSize = 1
174	elif largestOffset < 0x10000:
175		offSize = 2
176	elif largestOffset < 0x1000000:
177		offSize = 3
178	else:
179		offSize = 4
180	return offSize
181
182
183class IndexCompiler:
184
185	def __init__(self, items, strings, parent):
186		self.items = self.getItems(items, strings)
187		self.parent = parent
188
189	def getItems(self, items, strings):
190		return items
191
192	def getOffsets(self):
193		pos = 1
194		offsets = [pos]
195		for item in self.items:
196			if hasattr(item, "getDataLength"):
197				pos = pos + item.getDataLength()
198			else:
199				pos = pos + len(item)
200			offsets.append(pos)
201		return offsets
202
203	def getDataLength(self):
204		lastOffset = self.getOffsets()[-1]
205		offSize = calcOffSize(lastOffset)
206		dataLength = (
207			2 +                                # count
208			1 +                                # offSize
209			(len(self.items) + 1) * offSize +  # the offsets
210			lastOffset - 1                     # size of object data
211		)
212		return dataLength
213
214	def toFile(self, file):
215		offsets = self.getOffsets()
216		writeCard16(file, len(self.items))
217		offSize = calcOffSize(offsets[-1])
218		writeCard8(file, offSize)
219		offSize = -offSize
220		pack = struct.pack
221		for offset in offsets:
222			binOffset = pack(">l", offset)[offSize:]
223			assert len(binOffset) == -offSize
224			file.write(binOffset)
225		for item in self.items:
226			if hasattr(item, "toFile"):
227				item.toFile(file)
228			else:
229				file.write(item)
230
231
232class IndexedStringsCompiler(IndexCompiler):
233
234	def getItems(self, items, strings):
235		return items.strings
236
237
238class TopDictIndexCompiler(IndexCompiler):
239
240	def getItems(self, items, strings):
241		out = []
242		for item in items:
243			out.append(item.getCompiler(strings, self))
244		return out
245
246	def getChildren(self, strings):
247		children = []
248		for topDict in self.items:
249			children.extend(topDict.getChildren(strings))
250		return children
251
252
253class FDArrayIndexCompiler(IndexCompiler):
254
255	def getItems(self, items, strings):
256		out = []
257		for item in items:
258			out.append(item.getCompiler(strings, self))
259		return out
260
261	def getChildren(self, strings):
262		children = []
263		for fontDict in self.items:
264			children.extend(fontDict.getChildren(strings))
265		return children
266
267	def toFile(self, file):
268		offsets = self.getOffsets()
269		writeCard16(file, len(self.items))
270		offSize = calcOffSize(offsets[-1])
271		writeCard8(file, offSize)
272		offSize = -offSize
273		pack = struct.pack
274		for offset in offsets:
275			binOffset = pack(">l", offset)[offSize:]
276			assert len(binOffset) == -offSize
277			file.write(binOffset)
278		for item in self.items:
279			if hasattr(item, "toFile"):
280				item.toFile(file)
281			else:
282				file.write(item)
283
284	def setPos(self, pos, endPos):
285		self.parent.rawDict["FDArray"] = pos
286
287
288class GlobalSubrsCompiler(IndexCompiler):
289	def getItems(self, items, strings):
290		out = []
291		for cs in items:
292			cs.compile()
293			out.append(cs.bytecode)
294		return out
295
296class SubrsCompiler(GlobalSubrsCompiler):
297	def setPos(self, pos, endPos):
298		offset = pos - self.parent.pos
299		self.parent.rawDict["Subrs"] = offset
300
301class CharStringsCompiler(GlobalSubrsCompiler):
302	def setPos(self, pos, endPos):
303		self.parent.rawDict["CharStrings"] = pos
304
305
306class Index:
307
308	"""This class represents what the CFF spec calls an INDEX."""
309
310	compilerClass = IndexCompiler
311
312	def __init__(self, file=None):
313		name = self.__class__.__name__
314		if file is None:
315			self.items = []
316			return
317		if DEBUG:
318			print("loading %s at %s" % (name, file.tell()))
319		self.file = file
320		count = readCard16(file)
321		self.count = count
322		self.items = [None] * count
323		if count == 0:
324			self.items = []
325			return
326		offSize = readCard8(file)
327		if DEBUG:
328			print("    index count: %s offSize: %s" % (count, offSize))
329		assert offSize <= 4, "offSize too large: %s" % offSize
330		self.offsets = offsets = []
331		pad = b'\0' * (4 - offSize)
332		for index in range(count+1):
333			chunk = file.read(offSize)
334			chunk = pad + chunk
335			offset, = struct.unpack(">L", chunk)
336			offsets.append(int(offset))
337		self.offsetBase = file.tell() - 1
338		file.seek(self.offsetBase + offsets[-1])  # pretend we've read the whole lot
339		if DEBUG:
340			print("    end of %s at %s" % (name, file.tell()))
341
342	def __len__(self):
343		return len(self.items)
344
345	def __getitem__(self, index):
346		item = self.items[index]
347		if item is not None:
348			return item
349		offset = self.offsets[index] + self.offsetBase
350		size = self.offsets[index+1] - self.offsets[index]
351		file = self.file
352		file.seek(offset)
353		data = file.read(size)
354		assert len(data) == size
355		item = self.produceItem(index, data, file, offset, size)
356		self.items[index] = item
357		return item
358
359	def produceItem(self, index, data, file, offset, size):
360		return data
361
362	def append(self, item):
363		self.items.append(item)
364
365	def getCompiler(self, strings, parent):
366		return self.compilerClass(self, strings, parent)
367
368
369class GlobalSubrsIndex(Index):
370
371	compilerClass = GlobalSubrsCompiler
372
373	def __init__(self, file=None, globalSubrs=None, private=None, fdSelect=None, fdArray=None):
374		Index.__init__(self, file)
375		self.globalSubrs = globalSubrs
376		self.private = private
377		if fdSelect:
378			self.fdSelect = fdSelect
379		if fdArray:
380			self.fdArray = fdArray
381
382	def produceItem(self, index, data, file, offset, size):
383		if self.private is not None:
384			private = self.private
385		elif hasattr(self, 'fdArray') and self.fdArray is not None:
386			private = self.fdArray[self.fdSelect[index]].Private
387		else:
388			private = None
389		return psCharStrings.T2CharString(data, private=private, globalSubrs=self.globalSubrs)
390
391	def toXML(self, xmlWriter, progress):
392		xmlWriter.comment("The 'index' attribute is only for humans; it is ignored when parsed.")
393		xmlWriter.newline()
394		for i in range(len(self)):
395			subr = self[i]
396			if subr.needsDecompilation():
397				xmlWriter.begintag("CharString", index=i, raw=1)
398			else:
399				xmlWriter.begintag("CharString", index=i)
400			xmlWriter.newline()
401			subr.toXML(xmlWriter)
402			xmlWriter.endtag("CharString")
403			xmlWriter.newline()
404
405	def fromXML(self, name, attrs, content):
406		if name != "CharString":
407			return
408		subr = psCharStrings.T2CharString()
409		subr.fromXML(name, attrs, content)
410		self.append(subr)
411
412	def getItemAndSelector(self, index):
413		sel = None
414		if hasattr(self, 'fdSelect'):
415			sel = self.fdSelect[index]
416		return self[index], sel
417
418
419class SubrsIndex(GlobalSubrsIndex):
420	compilerClass = SubrsCompiler
421
422
423class TopDictIndex(Index):
424
425	compilerClass = TopDictIndexCompiler
426
427	def produceItem(self, index, data, file, offset, size):
428		top = TopDict(self.strings, file, offset, self.GlobalSubrs)
429		top.decompile(data)
430		return top
431
432	def toXML(self, xmlWriter, progress):
433		for i in range(len(self)):
434			xmlWriter.begintag("FontDict", index=i)
435			xmlWriter.newline()
436			self[i].toXML(xmlWriter, progress)
437			xmlWriter.endtag("FontDict")
438			xmlWriter.newline()
439
440
441class FDArrayIndex(TopDictIndex):
442
443	compilerClass = FDArrayIndexCompiler
444
445	def fromXML(self, name, attrs, content):
446		if name != "FontDict":
447			return
448		fontDict = FontDict()
449		for element in content:
450			if isinstance(element, basestring):
451				continue
452			name, attrs, content = element
453			fontDict.fromXML(name, attrs, content)
454		self.append(fontDict)
455
456
457class	FDSelect:
458	def __init__(self, file = None, numGlyphs = None, format=None):
459		if file:
460			# read data in from file
461			self.format = readCard8(file)
462			if self.format == 0:
463				from array import array
464				self.gidArray = array("B", file.read(numGlyphs)).tolist()
465			elif self.format == 3:
466				gidArray = [None] * numGlyphs
467				nRanges = readCard16(file)
468				prev = None
469				for i in range(nRanges):
470					first = readCard16(file)
471					if prev is not None:
472						for glyphID in range(prev, first):
473							gidArray[glyphID] = fd
474					prev = first
475					fd = readCard8(file)
476				if prev is not None:
477					first = readCard16(file)
478					for glyphID in range(prev, first):
479						gidArray[glyphID] = fd
480				self.gidArray = gidArray
481			else:
482				assert 0, "unsupported FDSelect format: %s" % format
483		else:
484			# reading from XML. Make empty gidArray,, and leave format as passed in.
485			# format == None will result in the smallest representation being used.
486			self.format = format
487			self.gidArray = []
488
489
490	def __len__(self):
491		return len(self.gidArray)
492
493	def __getitem__(self, index):
494		return self.gidArray[index]
495
496	def __setitem__(self, index, fdSelectValue):
497		self.gidArray[index] = fdSelectValue
498
499	def append(self, fdSelectValue):
500		self.gidArray.append(fdSelectValue)
501
502
503class CharStrings:
504
505	def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray):
506		if file is not None:
507			self.charStringsIndex = SubrsIndex(file, globalSubrs, private, fdSelect, fdArray)
508			self.charStrings = charStrings = {}
509			for i in range(len(charset)):
510				charStrings[charset[i]] = i
511			self.charStringsAreIndexed = 1
512		else:
513			self.charStrings = {}
514			self.charStringsAreIndexed = 0
515			self.globalSubrs = globalSubrs
516			self.private = private
517			if fdSelect != None:
518				self.fdSelect = fdSelect
519			if fdArray!= None:
520				self.fdArray = fdArray
521
522	def keys(self):
523		return list(self.charStrings.keys())
524
525	def values(self):
526		if self.charStringsAreIndexed:
527			return self.charStringsIndex
528		else:
529			return list(self.charStrings.values())
530
531	def has_key(self, name):
532		return name in self.charStrings
533
534	def __len__(self):
535		return len(self.charStrings)
536
537	def __getitem__(self, name):
538		charString = self.charStrings[name]
539		if self.charStringsAreIndexed:
540			charString = self.charStringsIndex[charString]
541		return charString
542
543	def __setitem__(self, name, charString):
544		if self.charStringsAreIndexed:
545			index = self.charStrings[name]
546			self.charStringsIndex[index] = charString
547		else:
548			self.charStrings[name] = charString
549
550	def getItemAndSelector(self, name):
551		if self.charStringsAreIndexed:
552			index = self.charStrings[name]
553			return self.charStringsIndex.getItemAndSelector(index)
554		else:
555			if hasattr(self, 'fdSelect'):
556				sel = self.fdSelect[index]  # index is not defined at this point. Read R. ?
557			else:
558				raise KeyError("fdSelect array not yet defined.")
559			return self.charStrings[name], sel
560
561	def toXML(self, xmlWriter, progress):
562		names = sorted(self.keys())
563		i = 0
564		step = 10
565		numGlyphs = len(names)
566		for name in names:
567			charStr, fdSelectIndex = self.getItemAndSelector(name)
568			if charStr.needsDecompilation():
569				raw = [("raw", 1)]
570			else:
571				raw = []
572			if fdSelectIndex is None:
573				xmlWriter.begintag("CharString", [('name', name)] + raw)
574			else:
575				xmlWriter.begintag("CharString",
576						[('name', name), ('fdSelectIndex', fdSelectIndex)] + raw)
577			xmlWriter.newline()
578			charStr.toXML(xmlWriter)
579			xmlWriter.endtag("CharString")
580			xmlWriter.newline()
581			if not i % step and progress is not None:
582				progress.setLabel("Dumping 'CFF ' table... (%s)" % name)
583				progress.increment(step / numGlyphs)
584			i = i + 1
585
586	def fromXML(self, name, attrs, content):
587		for element in content:
588			if isinstance(element, basestring):
589				continue
590			name, attrs, content = element
591			if name != "CharString":
592				continue
593			fdID = -1
594			if hasattr(self, "fdArray"):
595				fdID = safeEval(attrs["fdSelectIndex"])
596				private = self.fdArray[fdID].Private
597			else:
598				private = self.private
599
600			glyphName = attrs["name"]
601			charString = psCharStrings.T2CharString(
602					private=private,
603					globalSubrs=self.globalSubrs)
604			charString.fromXML(name, attrs, content)
605			if fdID >= 0:
606				charString.fdSelectIndex = fdID
607			self[glyphName] = charString
608
609
610def readCard8(file):
611	return byteord(file.read(1))
612
613def readCard16(file):
614	value, = struct.unpack(">H", file.read(2))
615	return value
616
617def writeCard8(file, value):
618	file.write(bytechr(value))
619
620def writeCard16(file, value):
621	file.write(struct.pack(">H", value))
622
623def packCard8(value):
624	return bytechr(value)
625
626def packCard16(value):
627	return struct.pack(">H", value)
628
629def buildOperatorDict(table):
630	d = {}
631	for op, name, arg, default, conv in table:
632		d[op] = (name, arg)
633	return d
634
635def buildOpcodeDict(table):
636	d = {}
637	for op, name, arg, default, conv in table:
638		if isinstance(op, tuple):
639			op = bytechr(op[0]) + bytechr(op[1])
640		else:
641			op = bytechr(op)
642		d[name] = (op, arg)
643	return d
644
645def buildOrder(table):
646	l = []
647	for op, name, arg, default, conv in table:
648		l.append(name)
649	return l
650
651def buildDefaults(table):
652	d = {}
653	for op, name, arg, default, conv in table:
654		if default is not None:
655			d[name] = default
656	return d
657
658def buildConverters(table):
659	d = {}
660	for op, name, arg, default, conv in table:
661		d[name] = conv
662	return d
663
664
665class SimpleConverter:
666	def read(self, parent, value):
667		return value
668	def write(self, parent, value):
669		return value
670	def xmlWrite(self, xmlWriter, name, value, progress):
671		xmlWriter.simpletag(name, value=value)
672		xmlWriter.newline()
673	def xmlRead(self, name, attrs, content, parent):
674		return attrs["value"]
675
676class Latin1Converter(SimpleConverter):
677	def xmlWrite(self, xmlWriter, name, value, progress):
678		# Store as UTF-8
679		value = value.decode("latin-1").encode("utf-8")
680		xmlWriter.simpletag(name, value=value)
681		xmlWriter.newline()
682	def xmlRead(self, name, attrs, content, parent):
683		return attrs["value"].decode("utf-8").encode("latin-1")
684
685
686def parseNum(s):
687	try:
688		value = int(s)
689	except:
690		value = float(s)
691	return value
692
693class NumberConverter(SimpleConverter):
694	def xmlRead(self, name, attrs, content, parent):
695		return parseNum(attrs["value"])
696
697class ArrayConverter(SimpleConverter):
698	def xmlWrite(self, xmlWriter, name, value, progress):
699		value = " ".join(map(str, value))
700		xmlWriter.simpletag(name, value=value)
701		xmlWriter.newline()
702	def xmlRead(self, name, attrs, content, parent):
703		values = attrs["value"].split()
704		return [parseNum(value) for value in values]
705
706class TableConverter(SimpleConverter):
707	def xmlWrite(self, xmlWriter, name, value, progress):
708		xmlWriter.begintag(name)
709		xmlWriter.newline()
710		value.toXML(xmlWriter, progress)
711		xmlWriter.endtag(name)
712		xmlWriter.newline()
713	def xmlRead(self, name, attrs, content, parent):
714		ob = self.getClass()()
715		for element in content:
716			if isinstance(element, basestring):
717				continue
718			name, attrs, content = element
719			ob.fromXML(name, attrs, content)
720		return ob
721
722class PrivateDictConverter(TableConverter):
723	def getClass(self):
724		return PrivateDict
725	def read(self, parent, value):
726		size, offset = value
727		file = parent.file
728		priv = PrivateDict(parent.strings, file, offset)
729		file.seek(offset)
730		data = file.read(size)
731		len(data) == size
732		priv.decompile(data)
733		return priv
734	def write(self, parent, value):
735		return (0, 0)  # dummy value
736
737class SubrsConverter(TableConverter):
738	def getClass(self):
739		return SubrsIndex
740	def read(self, parent, value):
741		file = parent.file
742		file.seek(parent.offset + value)  # Offset(self)
743		return SubrsIndex(file)
744	def write(self, parent, value):
745		return 0  # dummy value
746
747class CharStringsConverter(TableConverter):
748	def read(self, parent, value):
749		file = parent.file
750		charset = parent.charset
751		globalSubrs = parent.GlobalSubrs
752		if hasattr(parent, "ROS"):
753			fdSelect, fdArray = parent.FDSelect, parent.FDArray
754			private = None
755		else:
756			fdSelect, fdArray = None, None
757			private = parent.Private
758		file.seek(value)  # Offset(0)
759		return CharStrings(file, charset, globalSubrs, private, fdSelect, fdArray)
760	def write(self, parent, value):
761		return 0  # dummy value
762	def xmlRead(self, name, attrs, content, parent):
763		if hasattr(parent, "ROS"):
764			# if it is a CID-keyed font, then the private Dict is extracted from the parent.FDArray
765			private, fdSelect, fdArray = None, parent.FDSelect, parent.FDArray
766		else:
767			# if it is a name-keyed font, then the private dict is in the top dict, and there is no fdArray.
768			private, fdSelect, fdArray = parent.Private, None, None
769		charStrings = CharStrings(None, None, parent.GlobalSubrs, private, fdSelect, fdArray)
770		charStrings.fromXML(name, attrs, content)
771		return charStrings
772
773class CharsetConverter:
774	def read(self, parent, value):
775		isCID = hasattr(parent, "ROS")
776		if value > 2:
777			numGlyphs = parent.numGlyphs
778			file = parent.file
779			file.seek(value)
780			if DEBUG:
781				print("loading charset at %s" % value)
782			format = readCard8(file)
783			if format == 0:
784				charset = parseCharset0(numGlyphs, file, parent.strings, isCID)
785			elif format == 1 or format == 2:
786				charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
787			else:
788				raise NotImplementedError
789			assert len(charset) == numGlyphs
790			if DEBUG:
791				print("    charset end at %s" % file.tell())
792		else: # offset == 0 -> no charset data.
793			if isCID or "CharStrings" not in parent.rawDict:
794				assert value == 0 # We get here only when processing fontDicts from the FDArray of CFF-CID fonts. Only the real topDict references the chrset.
795				charset = None
796			elif value == 0:
797				charset = cffISOAdobeStrings
798			elif value == 1:
799				charset = cffIExpertStrings
800			elif value == 2:
801				charset = cffExpertSubsetStrings
802		return charset
803
804	def write(self, parent, value):
805		return 0  # dummy value
806	def xmlWrite(self, xmlWriter, name, value, progress):
807		# XXX only write charset when not in OT/TTX context, where we
808		# dump charset as a separate "GlyphOrder" table.
809		##xmlWriter.simpletag("charset")
810		xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element")
811		xmlWriter.newline()
812	def xmlRead(self, name, attrs, content, parent):
813		if 0:
814			return safeEval(attrs["value"])
815
816
817class CharsetCompiler:
818
819	def __init__(self, strings, charset, parent):
820		assert charset[0] == '.notdef'
821		isCID = hasattr(parent.dictObj, "ROS")
822		data0 = packCharset0(charset, isCID, strings)
823		data = packCharset(charset, isCID, strings)
824		if len(data) < len(data0):
825			self.data = data
826		else:
827			self.data = data0
828		self.parent = parent
829
830	def setPos(self, pos, endPos):
831		self.parent.rawDict["charset"] = pos
832
833	def getDataLength(self):
834		return len(self.data)
835
836	def toFile(self, file):
837		file.write(self.data)
838
839
840def getCIDfromName(name, strings):
841	return int(name[3:])
842
843def getSIDfromName(name, strings):
844	return strings.getSID(name)
845
846def packCharset0(charset, isCID, strings):
847	format = 0
848	data = [packCard8(format)]
849	if isCID:
850		getNameID = getCIDfromName
851	else:
852		getNameID = getSIDfromName
853
854	for name in charset[1:]:
855		data.append(packCard16(getNameID(name,strings)))
856	return bytesjoin(data)
857
858
859def packCharset(charset, isCID, strings):
860	format = 1
861	ranges = []
862	first = None
863	end = 0
864	if isCID:
865		getNameID = getCIDfromName
866	else:
867		getNameID = getSIDfromName
868
869	for name in charset[1:]:
870		SID = getNameID(name, strings)
871		if first is None:
872			first = SID
873		elif end + 1 != SID:
874			nLeft = end - first
875			if nLeft > 255:
876				format = 2
877			ranges.append((first, nLeft))
878			first = SID
879		end = SID
880	nLeft = end - first
881	if nLeft > 255:
882		format = 2
883	ranges.append((first, nLeft))
884
885	data = [packCard8(format)]
886	if format == 1:
887		nLeftFunc = packCard8
888	else:
889		nLeftFunc = packCard16
890	for first, nLeft in ranges:
891		data.append(packCard16(first) + nLeftFunc(nLeft))
892	return bytesjoin(data)
893
894def parseCharset0(numGlyphs, file, strings, isCID):
895	charset = [".notdef"]
896	if isCID:
897		for i in range(numGlyphs - 1):
898			CID = readCard16(file)
899			charset.append("cid" + str(CID).zfill(5))
900	else:
901		for i in range(numGlyphs - 1):
902			SID = readCard16(file)
903			charset.append(strings[SID])
904	return charset
905
906def parseCharset(numGlyphs, file, strings, isCID, format):
907	charset = ['.notdef']
908	count = 1
909	if format == 1:
910		nLeftFunc = readCard8
911	else:
912		nLeftFunc = readCard16
913	while count < numGlyphs:
914		first = readCard16(file)
915		nLeft = nLeftFunc(file)
916		if isCID:
917			for CID in range(first, first+nLeft+1):
918				charset.append("cid" + str(CID).zfill(5))
919		else:
920			for SID in range(first, first+nLeft+1):
921				charset.append(strings[SID])
922		count = count + nLeft + 1
923	return charset
924
925
926class EncodingCompiler:
927
928	def __init__(self, strings, encoding, parent):
929		assert not isinstance(encoding, basestring)
930		data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings)
931		data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings)
932		if len(data0) < len(data1):
933			self.data = data0
934		else:
935			self.data = data1
936		self.parent = parent
937
938	def setPos(self, pos, endPos):
939		self.parent.rawDict["Encoding"] = pos
940
941	def getDataLength(self):
942		return len(self.data)
943
944	def toFile(self, file):
945		file.write(self.data)
946
947
948class EncodingConverter(SimpleConverter):
949
950	def read(self, parent, value):
951		if value == 0:
952			return "StandardEncoding"
953		elif value == 1:
954			return "ExpertEncoding"
955		else:
956			assert value > 1
957			file = parent.file
958			file.seek(value)
959			if DEBUG:
960				print("loading Encoding at %s" % value)
961			format = readCard8(file)
962			haveSupplement = format & 0x80
963			if haveSupplement:
964				raise NotImplementedError("Encoding supplements are not yet supported")
965			format = format & 0x7f
966			if format == 0:
967				encoding = parseEncoding0(parent.charset, file, haveSupplement,
968						parent.strings)
969			elif format == 1:
970				encoding = parseEncoding1(parent.charset, file, haveSupplement,
971						parent.strings)
972			return encoding
973
974	def write(self, parent, value):
975		if value == "StandardEncoding":
976			return 0
977		elif value == "ExpertEncoding":
978			return 1
979		return 0  # dummy value
980
981	def xmlWrite(self, xmlWriter, name, value, progress):
982		if value in ("StandardEncoding", "ExpertEncoding"):
983			xmlWriter.simpletag(name, name=value)
984			xmlWriter.newline()
985			return
986		xmlWriter.begintag(name)
987		xmlWriter.newline()
988		for code in range(len(value)):
989			glyphName = value[code]
990			if glyphName != ".notdef":
991				xmlWriter.simpletag("map", code=hex(code), name=glyphName)
992				xmlWriter.newline()
993		xmlWriter.endtag(name)
994		xmlWriter.newline()
995
996	def xmlRead(self, name, attrs, content, parent):
997		if "name" in attrs:
998			return attrs["name"]
999		encoding = [".notdef"] * 256
1000		for element in content:
1001			if isinstance(element, basestring):
1002				continue
1003			name, attrs, content = element
1004			code = safeEval(attrs["code"])
1005			glyphName = attrs["name"]
1006			encoding[code] = glyphName
1007		return encoding
1008
1009
1010def parseEncoding0(charset, file, haveSupplement, strings):
1011	nCodes = readCard8(file)
1012	encoding = [".notdef"] * 256
1013	for glyphID in range(1, nCodes + 1):
1014		code = readCard8(file)
1015		if code != 0:
1016			encoding[code] = charset[glyphID]
1017	return encoding
1018
1019def parseEncoding1(charset, file, haveSupplement, strings):
1020	nRanges = readCard8(file)
1021	encoding = [".notdef"] * 256
1022	glyphID = 1
1023	for i in range(nRanges):
1024		code = readCard8(file)
1025		nLeft = readCard8(file)
1026		for glyphID in range(glyphID, glyphID + nLeft + 1):
1027			encoding[code] = charset[glyphID]
1028			code = code + 1
1029		glyphID = glyphID + 1
1030	return encoding
1031
1032def packEncoding0(charset, encoding, strings):
1033	format = 0
1034	m = {}
1035	for code in range(len(encoding)):
1036		name = encoding[code]
1037		if name != ".notdef":
1038			m[name] = code
1039	codes = []
1040	for name in charset[1:]:
1041		code = m.get(name)
1042		codes.append(code)
1043
1044	while codes and codes[-1] is None:
1045		codes.pop()
1046
1047	data = [packCard8(format), packCard8(len(codes))]
1048	for code in codes:
1049		if code is None:
1050			code = 0
1051		data.append(packCard8(code))
1052	return bytesjoin(data)
1053
1054def packEncoding1(charset, encoding, strings):
1055	format = 1
1056	m = {}
1057	for code in range(len(encoding)):
1058		name = encoding[code]
1059		if name != ".notdef":
1060			m[name] = code
1061	ranges = []
1062	first = None
1063	end = 0
1064	for name in charset[1:]:
1065		code = m.get(name, -1)
1066		if first is None:
1067			first = code
1068		elif end + 1 != code:
1069			nLeft = end - first
1070			ranges.append((first, nLeft))
1071			first = code
1072		end = code
1073	nLeft = end - first
1074	ranges.append((first, nLeft))
1075
1076	# remove unencoded glyphs at the end.
1077	while ranges and ranges[-1][0] == -1:
1078		ranges.pop()
1079
1080	data = [packCard8(format), packCard8(len(ranges))]
1081	for first, nLeft in ranges:
1082		if first == -1:  # unencoded
1083			first = 0
1084		data.append(packCard8(first) + packCard8(nLeft))
1085	return bytesjoin(data)
1086
1087
1088class FDArrayConverter(TableConverter):
1089
1090	def read(self, parent, value):
1091		file = parent.file
1092		file.seek(value)
1093		fdArray = FDArrayIndex(file)
1094		fdArray.strings = parent.strings
1095		fdArray.GlobalSubrs = parent.GlobalSubrs
1096		return fdArray
1097
1098	def write(self, parent, value):
1099		return 0  # dummy value
1100
1101	def xmlRead(self, name, attrs, content, parent):
1102		fdArray = FDArrayIndex()
1103		for element in content:
1104			if isinstance(element, basestring):
1105				continue
1106			name, attrs, content = element
1107			fdArray.fromXML(name, attrs, content)
1108		return fdArray
1109
1110
1111class FDSelectConverter:
1112
1113	def read(self, parent, value):
1114		file = parent.file
1115		file.seek(value)
1116		fdSelect = FDSelect(file, parent.numGlyphs)
1117		return 	fdSelect
1118
1119	def write(self, parent, value):
1120		return 0  # dummy value
1121
1122	# The FDSelect glyph data is written out to XML in the charstring keys,
1123	# so we write out only the format selector
1124	def xmlWrite(self, xmlWriter, name, value, progress):
1125		xmlWriter.simpletag(name, [('format', value.format)])
1126		xmlWriter.newline()
1127
1128	def xmlRead(self, name, attrs, content, parent):
1129		format = safeEval(attrs["format"])
1130		file = None
1131		numGlyphs = None
1132		fdSelect = FDSelect(file, numGlyphs, format)
1133		return fdSelect
1134
1135
1136def packFDSelect0(fdSelectArray):
1137	format = 0
1138	data = [packCard8(format)]
1139	for index in fdSelectArray:
1140		data.append(packCard8(index))
1141	return bytesjoin(data)
1142
1143
1144def packFDSelect3(fdSelectArray):
1145	format = 3
1146	fdRanges = []
1147	first = None
1148	end = 0
1149	lenArray = len(fdSelectArray)
1150	lastFDIndex = -1
1151	for i in range(lenArray):
1152		fdIndex = fdSelectArray[i]
1153		if lastFDIndex != fdIndex:
1154			fdRanges.append([i, fdIndex])
1155			lastFDIndex = fdIndex
1156	sentinelGID = i + 1
1157
1158	data = [packCard8(format)]
1159	data.append(packCard16( len(fdRanges) ))
1160	for fdRange in fdRanges:
1161		data.append(packCard16(fdRange[0]))
1162		data.append(packCard8(fdRange[1]))
1163	data.append(packCard16(sentinelGID))
1164	return bytesjoin(data)
1165
1166
1167class FDSelectCompiler:
1168
1169	def __init__(self, fdSelect, parent):
1170		format = fdSelect.format
1171		fdSelectArray = fdSelect.gidArray
1172		if format == 0:
1173			self.data = packFDSelect0(fdSelectArray)
1174		elif format == 3:
1175			self.data = packFDSelect3(fdSelectArray)
1176		else:
1177			# choose smaller of the two formats
1178			data0 = packFDSelect0(fdSelectArray)
1179			data3 = packFDSelect3(fdSelectArray)
1180			if len(data0) < len(data3):
1181				self.data = data0
1182				fdSelect.format = 0
1183			else:
1184				self.data = data3
1185				fdSelect.format = 3
1186
1187		self.parent = parent
1188
1189	def setPos(self, pos, endPos):
1190		self.parent.rawDict["FDSelect"] = pos
1191
1192	def getDataLength(self):
1193		return len(self.data)
1194
1195	def toFile(self, file):
1196		file.write(self.data)
1197
1198
1199class ROSConverter(SimpleConverter):
1200
1201	def xmlWrite(self, xmlWriter, name, value, progress):
1202		registry, order, supplement = value
1203		xmlWriter.simpletag(name, [('Registry', registry), ('Order', order),
1204			('Supplement', supplement)])
1205		xmlWriter.newline()
1206
1207	def xmlRead(self, name, attrs, content, parent):
1208		return (attrs['Registry'], attrs['Order'], safeEval(attrs['Supplement']))
1209
1210
1211
1212topDictOperators = [
1213#	opcode     name                  argument type   default    converter
1214	((12, 30), 'ROS',        ('SID','SID','number'), None,      ROSConverter()),
1215	((12, 20), 'SyntheticBase',      'number',       None,      None),
1216	(0,        'version',            'SID',          None,      None),
1217	(1,        'Notice',             'SID',          None,      Latin1Converter()),
1218	((12, 0),  'Copyright',          'SID',          None,      Latin1Converter()),
1219	(2,        'FullName',           'SID',          None,      None),
1220	((12, 38), 'FontName',           'SID',          None,      None),
1221	(3,        'FamilyName',         'SID',          None,      None),
1222	(4,        'Weight',             'SID',          None,      None),
1223	((12, 1),  'isFixedPitch',       'number',       0,         None),
1224	((12, 2),  'ItalicAngle',        'number',       0,         None),
1225	((12, 3),  'UnderlinePosition',  'number',       None,      None),
1226	((12, 4),  'UnderlineThickness', 'number',       50,        None),
1227	((12, 5),  'PaintType',          'number',       0,         None),
1228	((12, 6),  'CharstringType',     'number',       2,         None),
1229	((12, 7),  'FontMatrix',         'array',  [0.001,0,0,0.001,0,0],  None),
1230	(13,       'UniqueID',           'number',       None,      None),
1231	(5,        'FontBBox',           'array',  [0,0,0,0],       None),
1232	((12, 8),  'StrokeWidth',        'number',       0,         None),
1233	(14,       'XUID',               'array',        None,      None),
1234	((12, 21), 'PostScript',         'SID',          None,      None),
1235	((12, 22), 'BaseFontName',       'SID',          None,      None),
1236	((12, 23), 'BaseFontBlend',      'delta',        None,      None),
1237	((12, 31), 'CIDFontVersion',     'number',       0,         None),
1238	((12, 32), 'CIDFontRevision',    'number',       0,         None),
1239	((12, 33), 'CIDFontType',        'number',       0,         None),
1240	((12, 34), 'CIDCount',           'number',       8720,      None),
1241	(15,       'charset',            'number',       0,         CharsetConverter()),
1242	((12, 35), 'UIDBase',            'number',       None,      None),
1243	(16,       'Encoding',           'number',       0,         EncodingConverter()),
1244	(18,       'Private',       ('number','number'), None,      PrivateDictConverter()),
1245	((12, 37), 'FDSelect',           'number',       None,      FDSelectConverter()),
1246	((12, 36), 'FDArray',            'number',       None,      FDArrayConverter()),
1247	(17,       'CharStrings',        'number',       None,      CharStringsConverter()),
1248]
1249
1250# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order,
1251# in order for the font to compile back from xml.
1252
1253
1254privateDictOperators = [
1255#	opcode     name                  argument type   default    converter
1256	(6,        'BlueValues',         'delta',        None,      None),
1257	(7,        'OtherBlues',         'delta',        None,      None),
1258	(8,        'FamilyBlues',        'delta',        None,      None),
1259	(9,        'FamilyOtherBlues',   'delta',        None,      None),
1260	((12, 9),  'BlueScale',          'number',       0.039625,  None),
1261	((12, 10), 'BlueShift',          'number',       7,         None),
1262	((12, 11), 'BlueFuzz',           'number',       1,         None),
1263	(10,       'StdHW',              'number',       None,      None),
1264	(11,       'StdVW',              'number',       None,      None),
1265	((12, 12), 'StemSnapH',          'delta',        None,      None),
1266	((12, 13), 'StemSnapV',          'delta',        None,      None),
1267	((12, 14), 'ForceBold',          'number',       0,         None),
1268	((12, 15), 'ForceBoldThreshold', 'number',       None,      None),  # deprecated
1269	((12, 16), 'lenIV',              'number',       None,      None),  # deprecated
1270	((12, 17), 'LanguageGroup',      'number',       0,         None),
1271	((12, 18), 'ExpansionFactor',    'number',       0.06,      None),
1272	((12, 19), 'initialRandomSeed',  'number',       0,         None),
1273	(20,       'defaultWidthX',      'number',       0,         None),
1274	(21,       'nominalWidthX',      'number',       0,         None),
1275	(19,       'Subrs',              'number',       None,      SubrsConverter()),
1276]
1277
1278def addConverters(table):
1279	for i in range(len(table)):
1280		op, name, arg, default, conv = table[i]
1281		if conv is not None:
1282			continue
1283		if arg in ("delta", "array"):
1284			conv = ArrayConverter()
1285		elif arg == "number":
1286			conv = NumberConverter()
1287		elif arg == "SID":
1288			conv = SimpleConverter()
1289		else:
1290			assert 0
1291		table[i] = op, name, arg, default, conv
1292
1293addConverters(privateDictOperators)
1294addConverters(topDictOperators)
1295
1296
1297class TopDictDecompiler(psCharStrings.DictDecompiler):
1298	operators = buildOperatorDict(topDictOperators)
1299
1300
1301class PrivateDictDecompiler(psCharStrings.DictDecompiler):
1302	operators = buildOperatorDict(privateDictOperators)
1303
1304
1305class DictCompiler:
1306
1307	def __init__(self, dictObj, strings, parent):
1308		assert isinstance(strings, IndexedStrings)
1309		self.dictObj = dictObj
1310		self.strings = strings
1311		self.parent = parent
1312		rawDict = {}
1313		for name in dictObj.order:
1314			value = getattr(dictObj, name, None)
1315			if value is None:
1316				continue
1317			conv = dictObj.converters[name]
1318			value = conv.write(dictObj, value)
1319			if value == dictObj.defaults.get(name):
1320				continue
1321			rawDict[name] = value
1322		self.rawDict = rawDict
1323
1324	def setPos(self, pos, endPos):
1325		pass
1326
1327	def getDataLength(self):
1328		return len(self.compile("getDataLength"))
1329
1330	def compile(self, reason):
1331		if DEBUG:
1332			print("-- compiling %s for %s" % (self.__class__.__name__, reason))
1333			print("in baseDict: ", self)
1334		rawDict = self.rawDict
1335		data = []
1336		for name in self.dictObj.order:
1337			value = rawDict.get(name)
1338			if value is None:
1339				continue
1340			op, argType = self.opcodes[name]
1341			if isinstance(argType, tuple):
1342				l = len(argType)
1343				assert len(value) == l, "value doesn't match arg type"
1344				for i in range(l):
1345					arg = argType[i]
1346					v = value[i]
1347					arghandler = getattr(self, "arg_" + arg)
1348					data.append(arghandler(v))
1349			else:
1350				arghandler = getattr(self, "arg_" + argType)
1351				data.append(arghandler(value))
1352			data.append(op)
1353		return bytesjoin(data)
1354
1355	def toFile(self, file):
1356		file.write(self.compile("toFile"))
1357
1358	def arg_number(self, num):
1359		return encodeNumber(num)
1360	def arg_SID(self, s):
1361		return psCharStrings.encodeIntCFF(self.strings.getSID(s))
1362	def arg_array(self, value):
1363		data = []
1364		for num in value:
1365			data.append(encodeNumber(num))
1366		return bytesjoin(data)
1367	def arg_delta(self, value):
1368		out = []
1369		last = 0
1370		for v in value:
1371			out.append(v - last)
1372			last = v
1373		data = []
1374		for num in out:
1375			data.append(encodeNumber(num))
1376		return bytesjoin(data)
1377
1378
1379def encodeNumber(num):
1380	if isinstance(num, float):
1381		return psCharStrings.encodeFloat(num)
1382	else:
1383		return psCharStrings.encodeIntCFF(num)
1384
1385
1386class TopDictCompiler(DictCompiler):
1387
1388	opcodes = buildOpcodeDict(topDictOperators)
1389
1390	def getChildren(self, strings):
1391		children = []
1392		if hasattr(self.dictObj, "charset") and self.dictObj.charset:
1393			children.append(CharsetCompiler(strings, self.dictObj.charset, self))
1394		if hasattr(self.dictObj, "Encoding"):
1395			encoding = self.dictObj.Encoding
1396			if not isinstance(encoding, basestring):
1397				children.append(EncodingCompiler(strings, encoding, self))
1398		if hasattr(self.dictObj, "FDSelect"):
1399			# I have not yet supported merging a ttx CFF-CID font, as there are interesting
1400			# issues about merging the FDArrays. Here I assume that
1401			# either the font was read from XML, and teh FDSelect indices are all
1402			# in the charstring data, or the FDSelect array is already fully defined.
1403			fdSelect = self.dictObj.FDSelect
1404			if len(fdSelect) == 0: # probably read in from XML; assume fdIndex in CharString data
1405				charStrings = self.dictObj.CharStrings
1406				for name in self.dictObj.charset:
1407					charstring = charStrings[name]
1408					fdSelect.append(charStrings[name].fdSelectIndex)
1409			fdSelectComp = FDSelectCompiler(fdSelect, self)
1410			children.append(fdSelectComp)
1411		if hasattr(self.dictObj, "CharStrings"):
1412			items = []
1413			charStrings = self.dictObj.CharStrings
1414			for name in self.dictObj.charset:
1415				items.append(charStrings[name])
1416			charStringsComp = CharStringsCompiler(items, strings, self)
1417			children.append(charStringsComp)
1418		if hasattr(self.dictObj, "FDArray"):
1419			# I have not yet supported merging a ttx CFF-CID font, as there are interesting
1420			# issues about merging the FDArrays. Here I assume that the FDArray info is correct
1421			# and complete.
1422			fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self)
1423			children.append(fdArrayIndexComp)
1424			children.extend(fdArrayIndexComp.getChildren(strings))
1425		if hasattr(self.dictObj, "Private"):
1426			privComp = self.dictObj.Private.getCompiler(strings, self)
1427			children.append(privComp)
1428			children.extend(privComp.getChildren(strings))
1429		return children
1430
1431
1432class FontDictCompiler(DictCompiler):
1433
1434	opcodes = buildOpcodeDict(topDictOperators)
1435
1436	def getChildren(self, strings):
1437		children = []
1438		if hasattr(self.dictObj, "Private"):
1439			privComp = self.dictObj.Private.getCompiler(strings, self)
1440			children.append(privComp)
1441			children.extend(privComp.getChildren(strings))
1442		return children
1443
1444
1445class PrivateDictCompiler(DictCompiler):
1446
1447	opcodes = buildOpcodeDict(privateDictOperators)
1448
1449	def setPos(self, pos, endPos):
1450		size = endPos - pos
1451		self.parent.rawDict["Private"] = size, pos
1452		self.pos = pos
1453
1454	def getChildren(self, strings):
1455		children = []
1456		if hasattr(self.dictObj, "Subrs"):
1457			children.append(self.dictObj.Subrs.getCompiler(strings, self))
1458		return children
1459
1460
1461class BaseDict:
1462
1463	def __init__(self, strings=None, file=None, offset=None):
1464		self.rawDict = {}
1465		if DEBUG:
1466			print("loading %s at %s" % (self.__class__.__name__, offset))
1467		self.file = file
1468		self.offset = offset
1469		self.strings = strings
1470		self.skipNames = []
1471
1472	def decompile(self, data):
1473		if DEBUG:
1474			print("    length %s is %s" % (self.__class__.__name__, len(data)))
1475		dec = self.decompilerClass(self.strings)
1476		dec.decompile(data)
1477		self.rawDict = dec.getDict()
1478		self.postDecompile()
1479
1480	def postDecompile(self):
1481		pass
1482
1483	def getCompiler(self, strings, parent):
1484		return self.compilerClass(self, strings, parent)
1485
1486	def __getattr__(self, name):
1487		value = self.rawDict.get(name)
1488		if value is None:
1489			value = self.defaults.get(name)
1490		if value is None:
1491			raise AttributeError(name)
1492		conv = self.converters[name]
1493		value = conv.read(self, value)
1494		setattr(self, name, value)
1495		return value
1496
1497	def toXML(self, xmlWriter, progress):
1498		for name in self.order:
1499			if name in self.skipNames:
1500				continue
1501			value = getattr(self, name, None)
1502			if value is None:
1503				continue
1504			conv = self.converters[name]
1505			conv.xmlWrite(xmlWriter, name, value, progress)
1506
1507	def fromXML(self, name, attrs, content):
1508		conv = self.converters[name]
1509		value = conv.xmlRead(name, attrs, content, self)
1510		setattr(self, name, value)
1511
1512
1513class TopDict(BaseDict):
1514
1515	defaults = buildDefaults(topDictOperators)
1516	converters = buildConverters(topDictOperators)
1517	order = buildOrder(topDictOperators)
1518	decompilerClass = TopDictDecompiler
1519	compilerClass = TopDictCompiler
1520
1521	def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None):
1522		BaseDict.__init__(self, strings, file, offset)
1523		self.GlobalSubrs = GlobalSubrs
1524
1525	def getGlyphOrder(self):
1526		return self.charset
1527
1528	def postDecompile(self):
1529		offset = self.rawDict.get("CharStrings")
1530		if offset is None:
1531			return
1532		# get the number of glyphs beforehand.
1533		self.file.seek(offset)
1534		self.numGlyphs = readCard16(self.file)
1535
1536	def toXML(self, xmlWriter, progress):
1537		if hasattr(self, "CharStrings"):
1538			self.decompileAllCharStrings(progress)
1539		if hasattr(self, "ROS"):
1540			self.skipNames = ['Encoding']
1541		if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
1542			# these values have default values, but I only want them to show up
1543			# in CID fonts.
1544			self.skipNames = ['CIDFontVersion', 'CIDFontRevision', 'CIDFontType',
1545					'CIDCount']
1546		BaseDict.toXML(self, xmlWriter, progress)
1547
1548	def decompileAllCharStrings(self, progress):
1549		# XXX only when doing ttdump -i?
1550		i = 0
1551		for charString in self.CharStrings.values():
1552			try:
1553				charString.decompile()
1554			except:
1555				print("Error in charstring ", i)
1556				import sys
1557				type, value = sys. exc_info()[0:2]
1558				raise type(value)
1559			if not i % 30 and progress:
1560				progress.increment(0)  # update
1561			i = i + 1
1562
1563
1564class FontDict(BaseDict):
1565
1566	defaults = buildDefaults(topDictOperators)
1567	converters = buildConverters(topDictOperators)
1568	order = buildOrder(topDictOperators)
1569	decompilerClass = None
1570	compilerClass = FontDictCompiler
1571
1572	def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None):
1573		BaseDict.__init__(self, strings, file, offset)
1574		self.GlobalSubrs = GlobalSubrs
1575
1576	def getGlyphOrder(self):
1577		return self.charset
1578
1579	def toXML(self, xmlWriter, progress):
1580		self.skipNames = ['Encoding']
1581		BaseDict.toXML(self, xmlWriter, progress)
1582
1583
1584
1585class PrivateDict(BaseDict):
1586	defaults = buildDefaults(privateDictOperators)
1587	converters = buildConverters(privateDictOperators)
1588	order = buildOrder(privateDictOperators)
1589	decompilerClass = PrivateDictDecompiler
1590	compilerClass = PrivateDictCompiler
1591
1592
1593class IndexedStrings:
1594
1595	"""SID -> string mapping."""
1596
1597	def __init__(self, file=None):
1598		if file is None:
1599			strings = []
1600		else:
1601			strings = list(Index(file))
1602		self.strings = strings
1603
1604	def getCompiler(self):
1605		return IndexedStringsCompiler(self, None, None)
1606
1607	def __len__(self):
1608		return len(self.strings)
1609
1610	def __getitem__(self, SID):
1611		if SID < cffStandardStringCount:
1612			return cffStandardStrings[SID]
1613		else:
1614			return self.strings[SID - cffStandardStringCount]
1615
1616	def getSID(self, s):
1617		if not hasattr(self, "stringMapping"):
1618			self.buildStringMapping()
1619		if s in cffStandardStringMapping:
1620			SID = cffStandardStringMapping[s]
1621		elif s in self.stringMapping:
1622			SID = self.stringMapping[s]
1623		else:
1624			SID = len(self.strings) + cffStandardStringCount
1625			self.strings.append(s)
1626			self.stringMapping[s] = SID
1627		return SID
1628
1629	def getStrings(self):
1630		return self.strings
1631
1632	def buildStringMapping(self):
1633		self.stringMapping = {}
1634		for index in range(len(self.strings)):
1635			self.stringMapping[self.strings[index]] = index + cffStandardStringCount
1636
1637
1638# The 391 Standard Strings as used in the CFF format.
1639# from Adobe Technical None #5176, version 1.0, 18 March 1998
1640
1641cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
1642		'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
1643		'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
1644		'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
1645		'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
1646		'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
1647		'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
1648		'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
1649		'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
1650		's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
1651		'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
1652		'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
1653		'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
1654		'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
1655		'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
1656		'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
1657		'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
1658		'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
1659		'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
1660		'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
1661		'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
1662		'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
1663		'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
1664		'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
1665		'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
1666		'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
1667		'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
1668		'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
1669		'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
1670		'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
1671		'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
1672		'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
1673		'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
1674		'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
1675		'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
1676		'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
1677		'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
1678		'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
1679		'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
1680		'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
1681		'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
1682		'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
1683		'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
1684		'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
1685		'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
1686		'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
1687		'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
1688		'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
1689		'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
1690		'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
1691		'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
1692		'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
1693		'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
1694		'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
1695		'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
1696		'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
1697		'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
1698		'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
1699		'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
1700		'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
1701		'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
1702		'001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
1703		'Semibold'
1704]
1705
1706cffStandardStringCount = 391
1707assert len(cffStandardStrings) == cffStandardStringCount
1708# build reverse mapping
1709cffStandardStringMapping = {}
1710for _i in range(cffStandardStringCount):
1711	cffStandardStringMapping[cffStandardStrings[_i]] = _i
1712
1713cffISOAdobeStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign",
1714"dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright",
1715"asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two",
1716"three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon",
1717"less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G",
1718"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
1719"X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
1720"underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
1721"k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
1722"braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent",
1723"sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle",
1724"quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl",
1725"endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet",
1726"quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis",
1727"perthousand", "questiondown", "grave", "acute", "circumflex", "tilde",
1728"macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut",
1729"ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE",
1730"ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls",
1731"onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus",
1732"Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn",
1733"threequarters", "twosuperior", "registered", "minus", "eth", "multiply",
1734"threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave",
1735"Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave",
1736"Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute",
1737"Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute",
1738"Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute",
1739"acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute",
1740"ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis",
1741"igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde",
1742"scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis",
1743"zcaron"]
1744
1745cffISOAdobeStringCount = 229
1746assert len(cffISOAdobeStrings) == cffISOAdobeStringCount
1747
1748cffIExpertStrings = [".notdef", "space", "exclamsmall", "Hungarumlautsmall",
1749"dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall",
1750"parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader",
1751"comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle",
1752"twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle",
1753"sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon",
1754"commasuperior", "threequartersemdash", "periodsuperior", "questionsmall",
1755"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
1756"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
1757"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
1758"parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall",
1759"Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall",
1760"Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall",
1761"Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall",
1762"Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall",
1763"exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall",
1764"Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall",
1765"figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall",
1766"onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth",
1767"threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds",
1768"zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior",
1769"fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior",
1770"zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior",
1771"fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior",
1772"centinferior", "dollarinferior", "periodinferior", "commainferior",
1773"Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall",
1774"Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall",
1775"Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall",
1776"Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall",
1777"Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall",
1778"Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall",
1779"Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall",
1780"Ydieresissmall"]
1781
1782cffExpertStringCount = 166
1783assert len(cffIExpertStrings) == cffExpertStringCount
1784
1785cffExpertSubsetStrings = [".notdef", "space", "dollaroldstyle",
1786"dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader",
1787"onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle",
1788"oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle",
1789"sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon",
1790"semicolon", "commasuperior", "threequartersemdash", "periodsuperior",
1791"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
1792"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
1793"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
1794"parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah",
1795"centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf",
1796"threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths",
1797"onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior",
1798"threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior",
1799"eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior",
1800"threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior",
1801"eightinferior", "nineinferior", "centinferior", "dollarinferior",
1802"periodinferior", "commainferior"]
1803
1804cffExpertSubsetStringCount = 87
1805assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount
1806