cffLib.py revision 7ce0a139ab67dd30614e728a1ef897e53ad805ae
1"""cffLib.py -- read/write tools for Adobe CFF fonts."""
2
3#
4# $Id: cffLib.py,v 1.26 2002-07-23 16:42:11 jvr Exp $
5#
6
7import struct, sstruct
8import string
9from types import FloatType, ListType, StringType, TupleType
10from fontTools.misc import psCharStrings
11from fontTools.misc.textTools import safeEval
12
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 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, StringType):
116					continue
117				topDict.fromXML(element)
118		elif name == "GlobalSubrs":
119			for element in content:
120				if isinstance(element, StringType):
121					continue
122				name, attrs, content = element
123				subr = psCharStrings.T2CharString(None, subrs=None, globalSubrs=None)
124				subr.fromXML((name, attrs, content))
125				self.GlobalSubrs.append(subr)
126
127
128class CFFWriter:
129
130	def __init__(self):
131		self.data = []
132
133	def add(self, table):
134		self.data.append(table)
135
136	def toFile(self, file):
137		lastPosList = None
138		count = 1
139		while 1:
140			if DEBUG:
141				print "CFFWriter.toFile() iteration:", count
142			count = count + 1
143			pos = 0
144			posList = [pos]
145			for item in self.data:
146				if hasattr(item, "getDataLength"):
147					endPos = pos + item.getDataLength()
148				else:
149					endPos = pos + len(item)
150				if hasattr(item, "setPos"):
151					item.setPos(pos, endPos)
152				pos = endPos
153				posList.append(pos)
154			if posList == lastPosList:
155				break
156			lastPosList = posList
157		if DEBUG:
158			print "CFFWriter.toFile() writing to file."
159		begin = file.tell()
160		posList = [0]
161		for item in self.data:
162			if hasattr(item, "toFile"):
163				item.toFile(file)
164			else:
165				file.write(item)
166			posList.append(file.tell() - begin)
167		assert posList == lastPosList
168
169
170def calcOffSize(largestOffset):
171	if largestOffset < 0x100:
172		offSize = 1
173	elif largestOffset < 0x10000:
174		offSize = 2
175	elif largestOffset < 0x1000000:
176		offSize = 3
177	else:
178		offSize = 4
179	return offSize
180
181
182class IndexCompiler:
183
184	def __init__(self, items, strings, parent):
185		self.items = self.getItems(items, strings)
186		self.parent = parent
187
188	def getItems(self, items, strings):
189		return items
190
191	def getOffsets(self):
192		pos = 1
193		offsets = [pos]
194		for item in self.items:
195			if hasattr(item, "getDataLength"):
196				pos = pos + item.getDataLength()
197			else:
198				pos = pos + len(item)
199			offsets.append(pos)
200		return offsets
201
202	def getDataLength(self):
203		lastOffset = self.getOffsets()[-1]
204		offSize = calcOffSize(lastOffset)
205		dataLength = (
206			2 +                                # count
207			1 +                                # offSize
208			(len(self.items) + 1) * offSize +  # the offsets
209			lastOffset - 1                     # size of object data
210		)
211		return dataLength
212
213	def toFile(self, file):
214		offsets = self.getOffsets()
215		writeCard16(file, len(self.items))
216		offSize = calcOffSize(offsets[-1])
217		writeCard8(file, offSize)
218		offSize = -offSize
219		pack = struct.pack
220		for offset in offsets:
221			binOffset = pack(">l", offset)[offSize:]
222			assert len(binOffset) == -offSize
223			file.write(binOffset)
224		for item in self.items:
225			if hasattr(item, "toFile"):
226				item.toFile(file)
227			else:
228				file.write(item)
229
230
231class IndexedStringsCompiler(IndexCompiler):
232
233	def getItems(self, items, strings):
234		return items.strings
235
236
237class TopDictIndexCompiler(IndexCompiler):
238
239	def getItems(self, items, strings):
240		out = []
241		for item in items:
242			out.append(item.getCompiler(strings, self))
243		return out
244
245	def getChildren(self, strings):
246		children = []
247		for topDict in self.items:
248			children.extend(topDict.getChildren(strings))
249		return children
250
251
252class GlobalSubrsCompiler(IndexCompiler):
253	def getItems(self, items, strings):
254		out = []
255		for cs in items:
256			cs.compile()
257			out.append(cs.bytecode)
258		return out
259
260class SubrsCompiler(GlobalSubrsCompiler):
261	def setPos(self, pos, endPos):
262		offset = pos - self.parent.pos
263		self.parent.rawDict["Subrs"] = offset
264
265class CharStringsCompiler(GlobalSubrsCompiler):
266	def setPos(self, pos, endPos):
267		self.parent.rawDict["CharStrings"] = pos
268
269
270class Index:
271
272	"""This class represents what the CFF spec calls an INDEX."""
273
274	compilerClass = IndexCompiler
275
276	def __init__(self, file=None):
277		name = self.__class__.__name__
278		if file is None:
279			self.items = []
280			return
281		if DEBUG:
282			print "loading %s at %s" % (name, file.tell())
283		self.file = file
284		count = readCard16(file)
285		self.count = count
286		self.items = [None] * count
287		if count == 0:
288			self.items = []
289			return
290		offSize = readCard8(file)
291		if DEBUG:
292			print "    index count: %s offSize: %s" % (count, offSize)
293		assert offSize <= 4, "offSize too large: %s" % offSize
294		self.offsets = offsets = []
295		pad = '\0' * (4 - offSize)
296		for index in range(count+1):
297			chunk = file.read(offSize)
298			chunk = pad + chunk
299			offset, = struct.unpack(">L", chunk)
300			offsets.append(int(offset))
301		self.offsetBase = file.tell() - 1
302		file.seek(self.offsetBase + offsets[-1])  # pretend we've read the whole lot
303		if DEBUG:
304			print "    end of %s at %s" % (name, file.tell())
305
306	def __len__(self):
307		return len(self.items)
308
309	def __getitem__(self, index):
310		item = self.items[index]
311		if item is not None:
312			return item
313		offset = self.offsets[index] + self.offsetBase
314		size = self.offsets[index+1] - self.offsets[index]
315		file = self.file
316		file.seek(offset)
317		data = file.read(size)
318		assert len(data) == size
319		item = self.produceItem(index, data, file, offset, size)
320		self.items[index] = item
321		return item
322
323	def produceItem(self, index, data, file, offset, size):
324		return data
325
326	def append(self, item):
327		self.items.append(item)
328
329	def getCompiler(self, strings, parent):
330		return self.compilerClass(self, strings, parent)
331
332
333class GlobalSubrsIndex(Index):
334
335	compilerClass = GlobalSubrsCompiler
336
337	def __init__(self, file=None, globalSubrs=None, private=None, fdSelect=None, fdArray=None):
338		Index.__init__(self, file)
339		self.globalSubrs = globalSubrs
340		self.private = private
341		self.fdSelect = fdSelect
342		self.fdArray = fdArray
343
344	def produceItem(self, index, data, file, offset, size):
345		if self.private is not None:
346			private = self.private
347		elif self.fdArray is not None:
348			private = self.fdArray[self.fdSelect[index]].Private
349		else:
350			private = None
351		if hasattr(private, "Subrs"):
352			subrs = private.Subrs
353		else:
354			subrs = []
355		return psCharStrings.T2CharString(data, subrs=subrs, globalSubrs=self.globalSubrs)
356
357	def toXML(self, xmlWriter, progress):
358		fdSelect = self.fdSelect
359		xmlWriter.comment("The 'index' attribute is only for humans; "
360				"it is ignored when parsed.")
361		xmlWriter.newline()
362		for i in range(len(self)):
363			subr = self[i]
364			if subr.needsDecompilation():
365				xmlWriter.begintag("CharString", index=i, raw=1)
366			else:
367				xmlWriter.begintag("CharString", index=i)
368			xmlWriter.newline()
369			subr.toXML(xmlWriter)
370			xmlWriter.endtag("CharString")
371			xmlWriter.newline()
372
373	def fromXML(self, (name, attrs, content)):
374		if name <> "CharString":
375			return
376		subr = psCharStrings.T2CharString(None, subrs=None, globalSubrs=None)
377		subr.fromXML((name, attrs, content))
378		self.append(subr)
379
380	def getItemAndSelector(self, index):
381		fdSelect = self.fdSelect
382		if fdSelect is None:
383			sel = None
384		else:
385			sel = fdSelect[index]
386		return self[index], sel
387
388class SubrsIndex(GlobalSubrsIndex):
389	compilerClass = SubrsCompiler
390
391
392class TopDictIndex(Index):
393
394	compilerClass = TopDictIndexCompiler
395
396	def produceItem(self, index, data, file, offset, size):
397		top = TopDict(self.strings, file, offset, self.GlobalSubrs)
398		top.decompile(data)
399		return top
400
401	def toXML(self, xmlWriter, progress):
402		for i in range(len(self)):
403			xmlWriter.begintag("FontDict", index=i)
404			xmlWriter.newline()
405			self[i].toXML(xmlWriter, progress)
406			xmlWriter.endtag("FontDict")
407			xmlWriter.newline()
408
409
410class CharStrings:
411
412	def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray):
413		if file is not None:
414			self.charStringsIndex = SubrsIndex(file, globalSubrs, private, fdSelect, fdArray)
415			self.charStrings = charStrings = {}
416			for i in range(len(charset)):
417				charStrings[charset[i]] = i
418			self.charStringsAreIndexed = 1
419		else:
420			self.charStrings = {}
421			self.charStringsAreIndexed = 0
422			self.globalSubrs = globalSubrs
423			self.private = private
424			self.fdSelect = fdSelect
425			self.fdArray = fdArray
426
427	def keys(self):
428		return self.charStrings.keys()
429
430	def values(self):
431		if self.charStringsAreIndexed:
432			return self.charStringsIndex
433		else:
434			return self.charStrings.values()
435
436	def has_key(self, name):
437		return self.charStrings.has_key(name)
438
439	def __len__(self):
440		return len(self.charStrings)
441
442	def __getitem__(self, name):
443		charString = self.charStrings[name]
444		if self.charStringsAreIndexed:
445			charString = self.charStringsIndex[charString]
446		return charString
447
448	def __setitem__(self, name, charString):
449		if self.charStringsAreIndexed:
450			index = self.charStrings[name]
451			self.charStringsIndex[index] = charString
452		else:
453			self.charStrings[name] = charString
454
455	def getItemAndSelector(self, name):
456		if self.charStringsAreIndexed:
457			index = self.charStrings[name]
458			return self.charStringsIndex.getItemAndSelector(index)
459		else:
460			# XXX needs work for CID fonts
461			return self.charStrings[name], None
462
463	def toXML(self, xmlWriter, progress):
464		names = self.keys()
465		names.sort()
466		i = 0
467		step = 10
468		numGlyphs = len(names)
469		for name in names:
470			charStr, fdSelect = self.getItemAndSelector(name)
471			if charStr.needsDecompilation():
472				raw = [("raw", 1)]
473			else:
474				raw = []
475			if fdSelect is None:
476				xmlWriter.begintag("CharString", [('name', name)] + raw)
477			else:
478				xmlWriter.begintag("CharString",
479						[('name', name), ('fdSelect', fdSelect)] + raw)
480			xmlWriter.newline()
481			charStr.toXML(xmlWriter)
482			xmlWriter.endtag("CharString")
483			xmlWriter.newline()
484			if not i % step and progress is not None:
485				progress.setLabel("Dumping 'CFF ' table... (%s)" % name)
486				progress.increment(step / float(numGlyphs))
487			i = i + 1
488
489	def fromXML(self, (name, attrs, content)):
490		for element in content:
491			if isinstance(element, StringType):
492				continue
493			name, attrs, content = element
494			if name <> "CharString":
495				continue
496			glyphName = attrs["name"]
497			if hasattr(self.private, "Subrs"):
498				subrs = self.private.Subrs
499			else:
500				subrs = []
501			globalSubrs = self.globalSubrs
502			charString = psCharStrings.T2CharString(None, subrs=subrs, globalSubrs=globalSubrs)
503			charString.fromXML((name, attrs, content))
504			self[glyphName] = charString
505
506
507def readCard8(file):
508	return ord(file.read(1))
509
510def readCard16(file):
511	value, = struct.unpack(">H", file.read(2))
512	return value
513
514def writeCard8(file, value):
515	file.write(chr(value))
516
517def writeCard16(file, value):
518	file.write(struct.pack(">H", value))
519
520def packCard8(value):
521	return chr(value)
522
523def packCard16(value):
524	return struct.pack(">H", value)
525
526def buildOperatorDict(table):
527	d = {}
528	for op, name, arg, default, conv in table:
529		d[op] = (name, arg)
530	return d
531
532def buildOpcodeDict(table):
533	d = {}
534	for op, name, arg, default, conv in table:
535		if type(op) == TupleType:
536			op = chr(op[0]) + chr(op[1])
537		else:
538			op = chr(op)
539		d[name] = (op, arg)
540	return d
541
542def buildOrder(table):
543	l = []
544	for op, name, arg, default, conv in table:
545		l.append(name)
546	return l
547
548def buildDefaults(table):
549	d = {}
550	for op, name, arg, default, conv in table:
551		if default is not None:
552			d[name] = default
553	return d
554
555def buildConverters(table):
556	d = {}
557	for op, name, arg, default, conv in table:
558		d[name] = conv
559	return d
560
561
562class SimpleConverter:
563	def read(self, parent, value):
564		return value
565	def write(self, parent, value):
566		return value
567	def xmlWrite(self, xmlWriter, name, value, progress):
568		xmlWriter.simpletag(name, value=value)
569		xmlWriter.newline()
570	def xmlRead(self, (name, attrs, content), parent):
571		return attrs["value"]
572
573def parseNum(s):
574	try:
575		value = int(s)
576	except:
577		value = float(s)
578	return value
579
580class NumberConverter(SimpleConverter):
581	def xmlRead(self, (name, attrs, content), parent):
582		return parseNum(attrs["value"])
583
584class ArrayConverter(SimpleConverter):
585	def xmlWrite(self, xmlWriter, name, value, progress):
586		value = map(str, value)
587		xmlWriter.simpletag(name, value=" ".join(value))
588		xmlWriter.newline()
589	def xmlRead(self, (name, attrs, content), parent):
590		values = attrs["value"].split()
591		return map(parseNum, values)
592
593class TableConverter(SimpleConverter):
594	def xmlWrite(self, xmlWriter, name, value, progress):
595		xmlWriter.begintag(name)
596		xmlWriter.newline()
597		value.toXML(xmlWriter, progress)
598		xmlWriter.endtag(name)
599		xmlWriter.newline()
600	def xmlRead(self, (name, attrs, content), parent):
601		ob = self.getClass()()
602		for element in content:
603			if isinstance(element, StringType):
604				continue
605			ob.fromXML(element)
606		return ob
607
608class PrivateDictConverter(TableConverter):
609	def getClass(self):
610		return PrivateDict
611	def read(self, parent, value):
612		size, offset = value
613		file = parent.file
614		priv = PrivateDict(parent.strings, file, offset)
615		file.seek(offset)
616		data = file.read(size)
617		len(data) == size
618		priv.decompile(data)
619		return priv
620	def write(self, parent, value):
621		return (0, 0)  # dummy value
622
623class SubrsConverter(TableConverter):
624	def getClass(self):
625		return SubrsIndex
626	def read(self, parent, value):
627		file = parent.file
628		file.seek(parent.offset + value)  # Offset(self)
629		return SubrsIndex(file)
630	def write(self, parent, value):
631		return 0  # dummy value
632
633class CharStringsConverter(TableConverter):
634	def read(self, parent, value):
635		file = parent.file
636		charset = parent.charset
637		globalSubrs = parent.GlobalSubrs
638		if hasattr(parent, "ROS"):
639			fdSelect, fdArray = parent.FDSelect, parent.FDArray
640			private = None
641		else:
642			fdSelect, fdArray = None, None
643			private = parent.Private
644		file.seek(value)  # Offset(0)
645		return CharStrings(file, charset, globalSubrs, private, fdSelect, fdArray)
646	def write(self, parent, value):
647		return 0  # dummy value
648	def xmlRead(self, (name, attrs, content), parent):
649		# XXX needs work for CID fonts
650		fdSelect, fdArray = None, None
651		charStrings = CharStrings(None, None, parent.GlobalSubrs, parent.Private, fdSelect, fdArray)
652		charStrings.fromXML((name, attrs, content))
653		return charStrings
654
655class CharsetConverter:
656	def read(self, parent, value):
657		isCID = hasattr(parent, "ROS")
658		if value > 2:
659			numGlyphs = parent.numGlyphs
660			file = parent.file
661			file.seek(value)
662			if DEBUG:
663				print "loading charset at %s" % value
664			format = readCard8(file)
665			if format == 0:
666				charset =parseCharset0(numGlyphs, file, parent.strings)
667			elif format == 1 or format == 2:
668				charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
669			else:
670				raise NotImplementedError
671			assert len(charset) == numGlyphs
672			if DEBUG:
673				print "    charset end at %s" % file.tell()
674		else:
675			if isCID or not hasattr(parent, "CharStrings"):
676				assert value == 0
677				charset = None
678			elif value == 0:
679				charset = ISOAdobe
680			elif value == 1:
681				charset = Expert
682			elif value == 2:
683				charset = ExpertSubset
684			# self.charset:
685			#   0: ISOAdobe (or CID font!)
686			#   1: Expert
687			#   2: ExpertSubset
688			charset = None  #
689		return charset
690	def write(self, parent, value):
691		return 0  # dummy value
692	def xmlWrite(self, xmlWriter, name, value, progress):
693		# XXX only write charset when not in OT/TTX context, where we
694		# dump charset as a separate "GlyphOrder" table.
695		##xmlWriter.simpletag("charset")
696		xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element")
697		xmlWriter.newline()
698	def xmlRead(self, (name, attrs, content), parent):
699		if 0:
700			return safeEval(attrs["value"])
701
702
703class CharsetCompiler:
704
705	def __init__(self, strings, charset, parent):
706		assert charset[0] == '.notdef'
707		data0 = packCharset0(charset, strings)
708		data = packCharset(charset, strings)
709		if len(data) < len(data0):
710			self.data = data
711		else:
712			self.data = data0
713		self.parent = parent
714
715	def setPos(self, pos, endPos):
716		self.parent.rawDict["charset"] = pos
717
718	def getDataLength(self):
719		return len(self.data)
720
721	def toFile(self, file):
722		file.write(self.data)
723
724
725def packCharset0(charset, strings):
726	format = 0
727	data = [packCard8(format)]
728	for name in charset[1:]:
729		data.append(packCard16(strings.getSID(name)))
730	return "".join(data)
731
732def packCharset(charset, strings):
733	format = 1
734	ranges = []
735	first = None
736	end = 0
737	for name in charset[1:]:
738		SID = strings.getSID(name)
739		if first is None:
740			first = SID
741		elif end + 1 <> SID:
742			nLeft = end - first
743			if nLeft > 255:
744				format = 2
745			ranges.append((first, nLeft))
746			first = SID
747		end = SID
748	nLeft = end - first
749	if nLeft > 255:
750		format = 2
751	ranges.append((first, nLeft))
752
753	data = [packCard8(format)]
754	if format == 1:
755		nLeftFunc = packCard8
756	else:
757		nLeftFunc = packCard16
758	for first, nLeft in ranges:
759		data.append(packCard16(first) + nLeftFunc(nLeft))
760	return "".join(data)
761
762def parseCharset0(numGlyphs, file, strings):
763	charset = [".notdef"]
764	for i in range(numGlyphs - 1):
765		SID = readCard16(file)
766		charset.append(strings[SID])
767	return charset
768
769def parseCharset(numGlyphs, file, strings, isCID, format):
770	charset = ['.notdef']
771	count = 1
772	if format == 1:
773		nLeftFunc = readCard8
774	else:
775		nLeftFunc = readCard16
776	while count < numGlyphs:
777		first = readCard16(file)
778		nLeft = nLeftFunc(file)
779		if isCID:
780			for CID in range(first, first+nLeft+1):
781				charset.append(CID)
782		else:
783			for SID in range(first, first+nLeft+1):
784				charset.append(strings[SID])
785		count = count + nLeft + 1
786	return charset
787
788
789class FDArrayConverter(TableConverter):
790	def read(self, parent, value):
791		file = parent.file
792		file.seek(value)
793		fdArray = TopDictIndex(file)
794		fdArray.strings = parent.strings
795		fdArray.GlobalSubrs = parent.GlobalSubrs
796		return fdArray
797
798
799class FDSelectConverter:
800	def read(self, parent, value):
801		file = parent.file
802		file.seek(value)
803		format = readCard8(file)
804		numGlyphs = parent.numGlyphs
805		if format == 0:
806			from array import array
807			fdSelect = array("B", file.read(numGlyphs)).tolist()
808		elif format == 3:
809			fdSelect = [None] * numGlyphs
810			nRanges = readCard16(file)
811			prev = None
812			for i in range(nRanges):
813				first = readCard16(file)
814				if prev is not None:
815					for glyphID in range(prev, first):
816						fdSelect[glyphID] = fd
817				prev = first
818				fd = readCard8(file)
819			if prev is not None:
820				first = readCard16(file)
821				for glyphID in range(prev, first):
822					fdSelect[glyphID] = fd
823		else:
824			assert 0, "unsupported FDSelect format: %s" % format
825		return fdSelect
826	def xmlWrite(self, xmlWriter, name, value, progress):
827		pass
828
829
830class ROSConverter(SimpleConverter):
831	def xmlWrite(self, xmlWriter, name, value, progress):
832		registry, order, supplement = value
833		xmlWriter.simpletag(name, [('Registry', registry), ('Order', order),
834			('Supplement', supplement)])
835		xmlWriter.newline()
836
837
838topDictOperators = [
839#	opcode     name                  argument type   default    converter
840	((12, 30), 'ROS',        ('SID','SID','number'), None,      ROSConverter()),
841	((12, 20), 'SyntheticBase',      'number',       None,      None),
842	(0,        'version',            'SID',          None,      None),
843	(1,        'Notice',             'SID',          None,      None),
844	((12, 0),  'Copyright',          'SID',          None,      None),
845	(2,        'FullName',           'SID',          None,      None),
846	((12, 38), 'FontName',           'SID',          None,      None),
847	(3,        'FamilyName',         'SID',          None,      None),
848	(4,        'Weight',             'SID',          None,      None),
849	((12, 1),  'isFixedPitch',       'number',       0,         None),
850	((12, 2),  'ItalicAngle',        'number',       0,         None),
851	((12, 3),  'UnderlinePosition',  'number',       None,      None),
852	((12, 4),  'UnderlineThickness', 'number',       50,        None),
853	((12, 5),  'PaintType',          'number',       0,         None),
854	((12, 6),  'CharstringType',     'number',       2,         None),
855	((12, 7),  'FontMatrix',         'array',  [0.001,0,0,0.001,0,0],  None),
856	(13,       'UniqueID',           'number',       None,      None),
857	(5,        'FontBBox',           'array',  [0,0,0,0],       None),
858	((12, 8),  'StrokeWidth',        'number',       0,         None),
859	(14,       'XUID',               'array',        None,      None),
860	(15,       'charset',            'number',       0,         CharsetConverter()),
861	((12, 21), 'PostScript',         'SID',          None,      None),
862	((12, 22), 'BaseFontName',       'SID',          None,      None),
863	((12, 23), 'BaseFontBlend',      'delta',        None,      None),
864	((12, 31), 'CIDFontVersion',     'number',       0,         None),
865	((12, 32), 'CIDFontRevision',    'number',       0,         None),
866	((12, 33), 'CIDFontType',        'number',       0,         None),
867	((12, 34), 'CIDCount',           'number',       8720,      None),
868	((12, 35), 'UIDBase',            'number',       None,      None),
869	(16,       'Encoding',           'number',       0,         None), # XXX
870	((12, 36), 'FDArray',            'number',       None,      FDArrayConverter()),
871	((12, 37), 'FDSelect',           'number',       None,      FDSelectConverter()),
872	(18,       'Private',       ('number','number'), None,      PrivateDictConverter()),
873	(17,       'CharStrings',        'number',       None,      CharStringsConverter()),
874]
875
876privateDictOperators = [
877#	opcode     name                  argument type   default    converter
878	(6,        'BlueValues',         'delta',        None,      None),
879	(7,        'OtherBlues',         'delta',        None,      None),
880	(8,        'FamilyBlues',        'delta',        None,      None),
881	(9,        'FamilyOtherBlues',   'delta',        None,      None),
882	((12, 9),  'BlueScale',          'number',       0.039625,  None),
883	((12, 10), 'BlueShift',          'number',       7,         None),
884	((12, 11), 'BlueFuzz',           'number',       1,         None),
885	(10,       'StdHW',              'number',       None,      None),
886	(11,       'StdVW',              'number',       None,      None),
887	((12, 12), 'StemSnapH',          'delta',        None,      None),
888	((12, 13), 'StemSnapV',          'delta',        None,      None),
889	((12, 14), 'ForceBold',          'number',       0,         None),
890	((12, 15), 'ForceBoldThreshold', 'number',       None,      None),  # deprecated
891	((12, 16), 'lenIV',              'number',       None,      None),  # deprecated
892	((12, 17), 'LanguageGroup',      'number',       0,         None),
893	((12, 18), 'ExpansionFactor',    'number',       0.06,      None),
894	((12, 19), 'initialRandomSeed',  'number',       0,         None),
895	(20,       'defaultWidthX',      'number',       0,         None),
896	(21,       'nominalWidthX',      'number',       0,         None),
897	(19,       'Subrs',              'number',       None,      SubrsConverter()),
898]
899
900def addConverters(table):
901	for i in range(len(table)):
902		op, name, arg, default, conv = table[i]
903		if conv is not None:
904			continue
905		if arg in ("delta", "array"):
906			conv = ArrayConverter()
907		elif arg == "number":
908			conv = NumberConverter()
909		elif arg == "SID":
910			conv = SimpleConverter()
911		else:
912			assert 0
913		table[i] = op, name, arg, default, conv
914
915addConverters(privateDictOperators)
916addConverters(topDictOperators)
917
918
919class TopDictDecompiler(psCharStrings.DictDecompiler):
920	operators = buildOperatorDict(topDictOperators)
921
922
923class PrivateDictDecompiler(psCharStrings.DictDecompiler):
924	operators = buildOperatorDict(privateDictOperators)
925
926
927class DictCompiler:
928
929	def __init__(self, dictObj, strings, parent):
930		assert isinstance(strings, IndexedStrings)
931		self.dictObj = dictObj
932		self.strings = strings
933		self.parent = parent
934		rawDict = {}
935		for name in dictObj.order:
936			value = getattr(dictObj, name, None)
937			if value is None:
938				continue
939			conv = dictObj.converters[name]
940			value = conv.write(dictObj, value)
941			if value == dictObj.defaults.get(name):
942				continue
943			rawDict[name] = value
944		self.rawDict = rawDict
945
946	def setPos(self, pos, endPos):
947		pass
948
949	def getDataLength(self):
950		return len(self.compile("getDataLength"))
951
952	def compile(self, reason):
953		if DEBUG:
954			print "-- compiling %s for %s" % (self.__class__.__name__, reason)
955		rawDict = self.rawDict
956		data = []
957		for name in self.dictObj.order:
958			value = rawDict.get(name)
959			if value is None:
960				continue
961			op, argType = self.opcodes[name]
962			if type(argType) == TupleType:
963				l = len(argType)
964				assert len(value) == l, "value doesn't match arg type"
965				for i in range(l):
966					arg = argType[l - i - 1]
967					v = value[i]
968					arghandler = getattr(self, "arg_" + arg)
969					data.append(arghandler(v))
970			else:
971				arghandler = getattr(self, "arg_" + argType)
972				data.append(arghandler(value))
973			data.append(op)
974		return "".join(data)
975
976	def toFile(self, file):
977		file.write(self.compile("toFile"))
978
979	def arg_number(self, num):
980		return encodeNumber(num)
981	def arg_SID(self, s):
982		return psCharStrings.encodeIntCFF(self.strings.getSID(s))
983	def arg_array(self, value):
984		data = []
985		for num in value:
986			data.append(encodeNumber(num))
987		return "".join(data)
988	def arg_delta(self, value):
989		out = []
990		last = 0
991		for v in value:
992			out.append(v - last)
993			last = v
994		data = []
995		for num in out:
996			data.append(encodeNumber(num))
997		return "".join(data)
998
999
1000def encodeNumber(num):
1001	if type(num) == FloatType:
1002		return psCharStrings.encodeFloat(num)
1003	else:
1004		return psCharStrings.encodeIntCFF(num)
1005
1006
1007class TopDictCompiler(DictCompiler):
1008
1009	opcodes = buildOpcodeDict(topDictOperators)
1010
1011	def getChildren(self, strings):
1012		children = []
1013		if hasattr(self.dictObj, "charset"):
1014			children.append(CharsetCompiler(strings, self.dictObj.charset, self))
1015		if hasattr(self.dictObj, "CharStrings"):
1016			items = []
1017			charStrings = self.dictObj.CharStrings
1018			for name in self.dictObj.charset:
1019				items.append(charStrings[name])
1020			charStringsComp = CharStringsCompiler(items, strings, self)
1021			children.append(charStringsComp)
1022		if hasattr(self.dictObj, "Private"):
1023			privComp = self.dictObj.Private.getCompiler(strings, self)
1024			children.append(privComp)
1025			children.extend(privComp.getChildren(strings))
1026		return children
1027
1028
1029class PrivateDictCompiler(DictCompiler):
1030
1031	opcodes = buildOpcodeDict(privateDictOperators)
1032
1033	def setPos(self, pos, endPos):
1034		size = endPos - pos
1035		self.parent.rawDict["Private"] = size, pos
1036		self.pos = pos
1037
1038	def getChildren(self, strings):
1039		children = []
1040		if hasattr(self.dictObj, "Subrs"):
1041			children.append(self.dictObj.Subrs.getCompiler(strings, self))
1042		return children
1043
1044
1045class BaseDict:
1046
1047	def __init__(self, strings=None, file=None, offset=None):
1048		self.rawDict = {}
1049		if DEBUG:
1050			print "loading %s at %s" % (self.__class__.__name__, offset)
1051		self.file = file
1052		self.offset = offset
1053		self.strings = strings
1054		self.skipNames = []
1055
1056	def decompile(self, data):
1057		if DEBUG:
1058			print "    length %s is %s" % (self.__class__.__name__, len(data))
1059		dec = self.decompilerClass(self.strings)
1060		dec.decompile(data)
1061		self.rawDict = dec.getDict()
1062		self.postDecompile()
1063
1064	def postDecompile(self):
1065		pass
1066
1067	def getCompiler(self, strings, parent):
1068		return self.compilerClass(self, strings, parent)
1069
1070	def __getattr__(self, name):
1071		value = self.rawDict.get(name)
1072		if value is None:
1073			value = self.defaults.get(name)
1074		if value is None:
1075			raise AttributeError, name
1076		conv = self.converters[name]
1077		value = conv.read(self, value)
1078		setattr(self, name, value)
1079		return value
1080
1081	def toXML(self, xmlWriter, progress):
1082		for name in self.order:
1083			if name in self.skipNames:
1084				continue
1085			value = getattr(self, name, None)
1086			if value is None:
1087				continue
1088			conv = self.converters[name]
1089			conv.xmlWrite(xmlWriter, name, value, progress)
1090
1091	def fromXML(self, (name, attrs, content)):
1092		conv = self.converters[name]
1093		value = conv.xmlRead((name, attrs, content), self)
1094		setattr(self, name, value)
1095
1096
1097class TopDict(BaseDict):
1098
1099	defaults = buildDefaults(topDictOperators)
1100	converters = buildConverters(topDictOperators)
1101	order = buildOrder(topDictOperators)
1102	decompilerClass = TopDictDecompiler
1103	compilerClass = TopDictCompiler
1104
1105	def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None):
1106		BaseDict.__init__(self, strings, file, offset)
1107		self.GlobalSubrs = GlobalSubrs
1108
1109	def getGlyphOrder(self):
1110		return self.charset
1111
1112	def postDecompile(self):
1113		offset = self.rawDict.get("CharStrings")
1114		if offset is None:
1115			return
1116		# get the number of glyphs beforehand.
1117		self.file.seek(offset)
1118		self.numGlyphs = readCard16(self.file)
1119
1120	def toXML(self, xmlWriter, progress):
1121		if hasattr(self, "CharStrings"):
1122			self.decompileAllCharStrings(progress)
1123		if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
1124			# these values have default values, but I only want them to show up
1125			# in CID fonts.
1126			self.skipNames = ['CIDFontVersion', 'CIDFontRevision', 'CIDFontType',
1127					'CIDCount']
1128		BaseDict.toXML(self, xmlWriter, progress)
1129
1130	def decompileAllCharStrings(self, progress):
1131		# XXX only when doing ttdump -i?
1132		i = 0
1133		for charString in self.CharStrings.values():
1134			charString.decompile()
1135			if not i % 30 and progress:
1136				progress.increment(0)  # update
1137			i = i + 1
1138
1139
1140class PrivateDict(BaseDict):
1141	defaults = buildDefaults(privateDictOperators)
1142	converters = buildConverters(privateDictOperators)
1143	order = buildOrder(privateDictOperators)
1144	decompilerClass = PrivateDictDecompiler
1145	compilerClass = PrivateDictCompiler
1146
1147
1148class IndexedStrings:
1149
1150	"""SID -> string mapping."""
1151
1152	def __init__(self, file=None):
1153		if file is None:
1154			strings = []
1155		else:
1156			strings = list(Index(file))
1157		self.strings = strings
1158
1159	def getCompiler(self):
1160		return IndexedStringsCompiler(self, None, None)
1161
1162	def __len__(self):
1163		return len(self.strings)
1164
1165	def __getitem__(self, SID):
1166		if SID < cffStandardStringCount:
1167			return cffStandardStrings[SID]
1168		else:
1169			return self.strings[SID - cffStandardStringCount]
1170
1171	def getSID(self, s):
1172		if not hasattr(self, "stringMapping"):
1173			self.buildStringMapping()
1174		if cffStandardStringMapping.has_key(s):
1175			SID = cffStandardStringMapping[s]
1176		elif self.stringMapping.has_key(s):
1177			SID = self.stringMapping[s]
1178		else:
1179			SID = len(self.strings) + cffStandardStringCount
1180			self.strings.append(s)
1181			self.stringMapping[s] = SID
1182		return SID
1183
1184	def getStrings(self):
1185		return self.strings
1186
1187	def buildStringMapping(self):
1188		self.stringMapping = {}
1189		for index in range(len(self.strings)):
1190			self.stringMapping[self.strings[index]] = index + cffStandardStringCount
1191
1192
1193# The 391 Standard Strings as used in the CFF format.
1194# from Adobe Technical None #5176, version 1.0, 18 March 1998
1195
1196cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
1197		'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
1198		'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
1199		'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
1200		'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
1201		'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
1202		'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
1203		'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
1204		'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
1205		's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
1206		'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
1207		'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
1208		'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
1209		'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
1210		'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
1211		'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
1212		'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
1213		'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
1214		'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
1215		'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
1216		'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
1217		'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
1218		'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
1219		'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
1220		'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
1221		'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
1222		'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
1223		'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
1224		'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
1225		'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
1226		'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
1227		'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
1228		'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
1229		'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
1230		'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
1231		'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
1232		'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
1233		'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
1234		'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
1235		'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
1236		'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
1237		'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
1238		'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
1239		'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
1240		'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
1241		'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
1242		'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
1243		'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
1244		'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
1245		'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
1246		'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
1247		'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
1248		'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
1249		'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
1250		'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
1251		'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
1252		'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
1253		'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
1254		'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
1255		'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
1256		'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
1257		'001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
1258		'Semibold'
1259]
1260
1261cffStandardStringCount = 391
1262assert len(cffStandardStrings) == cffStandardStringCount
1263# build reverse mapping
1264cffStandardStringMapping = {}
1265for _i in range(cffStandardStringCount):
1266	cffStandardStringMapping[cffStandardStrings[_i]] = _i
1267
1268