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