cffLib.py revision a2a75b348da46073cdd1dea50915384eabfff7c9
1"""cffLib.py -- read/write tools for Adobe CFF fonts."""
2
3#
4# $Id: cffLib.py,v 1.7 2002-05-13 11:24:43 jvr Exp $
5#
6
7import struct, sstruct
8import string
9import types
10from fontTools.misc import psCharStrings
11
12
13cffHeaderFormat = """
14	major:   B
15	minor:   B
16	hdrSize: B
17	offSize: B
18"""
19
20class CFFFontSet:
21
22	def __init__(self):
23		self.fonts = {}
24
25	def decompile(self, file):
26		sstruct.unpack(cffHeaderFormat, file.read(4), self)
27		assert self.major == 1 and self.minor == 0, \
28				"unknown CFF format: %d.%d" % (self.major, self.minor)
29
30		self.fontNames = readINDEX(file)
31		topDicts = readINDEX(file)
32		strings = IndexedStrings(readINDEX(file))
33		globalSubrs = readINDEX(file)
34
35		self.GlobalSubrs = map(psCharStrings.T2CharString, globalSubrs)
36
37		file.seek(0, 0)
38
39		for i in range(len(topDicts)):
40			font = self.fonts[self.fontNames[i]] = CFFFont()
41			font.GlobalSubrs = self.GlobalSubrs  # Hmm.
42			font.decompile(file, topDicts[i], strings, self)  # maybe only 'on demand'?
43
44	def compile(self):
45		strings = IndexedStrings()
46		XXXX
47
48	def toXML(self, xmlWriter, progress=None):
49		xmlWriter.newline()
50		for fontName in self.fontNames:
51			xmlWriter.begintag("CFFFont", name=fontName)
52			xmlWriter.newline()
53			font = self.fonts[fontName]
54			font.toXML(xmlWriter, progress)
55			xmlWriter.endtag("CFFFont")
56			xmlWriter.newline()
57		xmlWriter.newline()
58		xmlWriter.begintag("GlobalSubrs")
59		xmlWriter.newline()
60		for i in range(len(self.GlobalSubrs)):
61			xmlWriter.newline()
62			xmlWriter.begintag("CharString", id=i)
63			xmlWriter.newline()
64			self.GlobalSubrs[i].toXML(xmlWriter)
65			xmlWriter.endtag("CharString")
66			xmlWriter.newline()
67		xmlWriter.newline()
68		xmlWriter.endtag("GlobalSubrs")
69		xmlWriter.newline()
70		xmlWriter.newline()
71
72	def fromXML(self, (name, attrs, content)):
73		xxx
74
75
76class IndexedStrings:
77
78	def __init__(self, strings=None):
79		if strings is None:
80			strings = []
81		self.strings = strings
82
83	def __getitem__(self, SID):
84		if SID < cffStandardStringCount:
85			return cffStandardStrings[SID]
86		else:
87			return self.strings[SID - cffStandardStringCount]
88
89	def getSID(self, s):
90		if not hasattr(self, "stringMapping"):
91			self.buildStringMapping()
92		if cffStandardStringMapping.has_key(s):
93			SID = cffStandardStringMapping[s]
94		if self.stringMapping.has_key(s):
95			SID = self.stringMapping[s]
96		else:
97			SID = len(self.strings) + cffStandardStringCount
98			self.strings.append(s)
99			self.stringMapping[s] = SID
100		return SID
101
102	def getStrings(self):
103		return self.strings
104
105	def buildStringMapping(self):
106		self.stringMapping = {}
107		for index in range(len(self.strings)):
108			self.stringMapping[self.strings[index]] = index + cffStandardStringCount
109
110
111class CFFFont:
112
113	defaults = psCharStrings.topDictDefaults
114
115	def __init__(self):
116		pass
117
118	def __getattr__(self, attr):
119		if not self.defaults.has_key(attr):
120			raise AttributeError, attr
121		return self.defaults[attr]
122
123	def fromDict(self, dict):
124		self.__dict__.update(dict)
125
126	def decompileCID(self, data, strings):
127		offset = self.FDArray
128		fontDicts, restdata = readINDEX(data[offset:])
129		subFonts = []
130		for topDictData in fontDicts:
131			subFont = CFFFont()
132			subFonts.append(subFont)
133			subFont.decompile(data, topDictData, strings, None)
134
135		raise NotImplementedError
136
137	def decompile(self, file, topDictData, strings, fontSet):
138		top = psCharStrings.TopDictDecompiler(strings)
139		top.decompile(topDictData)
140		self.fromDict(top.getDict())
141
142		if hasattr(self, "ROS"):
143			isCID = 1
144			# XXX CID subFonts
145		else:
146			isCID = 0
147			size, offset = self.Private
148			file.seek(offset, 0)
149			privateData = file.read(size)
150			file.seek(offset, 0)
151			assert len(privateData) == size
152			self.Private = PrivateDict()
153			self.Private.decompile(file, privateData, strings)
154
155		file.seek(self.CharStrings)
156		rawCharStrings = readINDEX(file)
157		nGlyphs = len(rawCharStrings)
158
159		# get charset (or rather: get glyphNames)
160		if self.charset == 0:
161			xxx  # standard charset
162		else:
163			file.seek(self.charset)
164			format = ord(file.read(1))
165			if format == 0:
166				xxx
167			elif format == 1:
168				charset = parseCharsetFormat1(nGlyphs, file, strings, isCID)
169			elif format == 2:
170				charset = parseCharsetFormat2(nGlyphs, file, strings, isCID)
171			elif format == 3:
172				xxx
173			else:
174				xxx
175		self.charset = charset
176
177		assert len(charset) == nGlyphs
178		self.CharStrings = charStrings = {}
179		if self.CharstringType == 2:
180			# Type 2 CharStrings
181			charStringClass = psCharStrings.T2CharString
182		else:
183			# Type 1 CharStrings
184			charStringClass = psCharStrings.T1CharString
185		for i in range(nGlyphs):
186			charStrings[charset[i]] = charStringClass(rawCharStrings[i])
187		assert len(charStrings) == nGlyphs
188
189		# XXX Encoding!
190		encoding = self.Encoding
191		if encoding not in (0, 1):
192			# encoding is an _offset_ from the beginning of 'data' to an encoding subtable
193			XXX
194			self.Encoding = encoding
195
196	def getGlyphOrder(self):
197		return self.charset
198
199	def setGlyphOrder(self, glyphOrder):
200		self.charset = glyphOrder
201
202	def decompileAllCharStrings(self):
203		if self.CharstringType == 2:
204			# Type 2 CharStrings
205			decompiler = psCharStrings.SimpleT2Decompiler(self.Private.Subrs, self.GlobalSubrs)
206			for charString in self.CharStrings.values():
207				if charString.needsDecompilation():
208					decompiler.reset()
209					decompiler.execute(charString)
210		else:
211			# Type 1 CharStrings
212			for charString in self.CharStrings.values():
213				charString.decompile()
214
215	def toXML(self, xmlWriter, progress=None):
216		xmlWriter.newline()
217		# first dump the simple values
218		self.toXMLSimpleValues(xmlWriter)
219
220		# dump charset
221		# XXX
222
223		# decompile all charstrings
224		if progress:
225			progress.setlabel("Decompiling CharStrings...")
226		self.decompileAllCharStrings()
227
228		# dump private dict
229		xmlWriter.begintag("Private")
230		xmlWriter.newline()
231		self.Private.toXML(xmlWriter)
232		xmlWriter.endtag("Private")
233		xmlWriter.newline()
234
235		self.toXMLCharStrings(xmlWriter, progress)
236
237	def toXMLSimpleValues(self, xmlWriter):
238		keys = self.__dict__.keys()
239		keys.remove("CharStrings")
240		keys.remove("Private")
241		keys.remove("charset")
242		keys.remove("GlobalSubrs")
243		keys.sort()
244		for key in keys:
245			value = getattr(self, key)
246			if key == "Encoding":
247				if value == 0:
248					# encoding is (Adobe) Standard Encoding
249					value = "StandardEncoding"
250				elif value == 1:
251					# encoding is Expert Encoding
252					value = "ExpertEncoding"
253			if type(value) == types.ListType:
254				value = string.join(map(str, value), " ")
255			else:
256				value = str(value)
257			xmlWriter.begintag(key)
258			if hasattr(value, "toXML"):
259				xmlWriter.newline()
260				value.toXML(xmlWriter)
261				xmlWriter.newline()
262			else:
263				xmlWriter.write(value)
264			xmlWriter.endtag(key)
265			xmlWriter.newline()
266		xmlWriter.newline()
267
268	def toXMLCharStrings(self, xmlWriter, progress=None):
269		charStrings = self.CharStrings
270		xmlWriter.newline()
271		xmlWriter.begintag("CharStrings")
272		xmlWriter.newline()
273		glyphNames = charStrings.keys()
274		glyphNames.sort()
275		for glyphName in glyphNames:
276			if progress:
277				progress.setlabel("Dumping 'CFF ' table... (%s)" % glyphName)
278				progress.increment()
279			xmlWriter.newline()
280			charString = charStrings[glyphName]
281			xmlWriter.begintag("CharString", name=glyphName)
282			xmlWriter.newline()
283			charString.toXML(xmlWriter)
284			xmlWriter.endtag("CharString")
285			xmlWriter.newline()
286		xmlWriter.newline()
287		xmlWriter.endtag("CharStrings")
288		xmlWriter.newline()
289
290
291class PrivateDict:
292
293	defaults = psCharStrings.privateDictDefaults
294
295	def __init__(self):
296		pass
297
298	def decompile(self, data, privateData, strings):
299		p = psCharStrings.PrivateDictDecompiler(strings)
300		p.decompile(privateData)
301		self.fromDict(p.getDict())
302
303		# get local subrs
304		#print "YYY Private.Subrs:", self.Subrs
305		if hasattr(self, "Subrs"):
306			chunk = data[self.Subrs:]
307			localSubrs, restdata = readINDEX(chunk)
308			self.Subrs = map(psCharStrings.T2CharString, localSubrs)
309		else:
310			self.Subrs = []
311
312	def toXML(self, xmlWriter):
313		xmlWriter.newline()
314		keys = self.__dict__.keys()
315		keys.remove("Subrs")
316		for key in keys:
317			value = getattr(self, key)
318			if type(value) == types.ListType:
319				value = string.join(map(str, value), " ")
320			else:
321				value = str(value)
322			xmlWriter.begintag(key)
323			xmlWriter.write(value)
324			xmlWriter.endtag(key)
325			xmlWriter.newline()
326		# write subroutines
327		xmlWriter.newline()
328		xmlWriter.begintag("Subrs")
329		xmlWriter.newline()
330		for i in range(len(self.Subrs)):
331			xmlWriter.newline()
332			xmlWriter.begintag("CharString", id=i)
333			xmlWriter.newline()
334			self.Subrs[i].toXML(xmlWriter)
335			xmlWriter.endtag("CharString")
336			xmlWriter.newline()
337		xmlWriter.newline()
338		xmlWriter.endtag("Subrs")
339		xmlWriter.newline()
340		xmlWriter.newline()
341
342	def __getattr__(self, attr):
343		if not self.defaults.has_key(attr):
344			raise AttributeError, attr
345		return self.defaults[attr]
346
347	def fromDict(self, dict):
348		self.__dict__.update(dict)
349
350
351def readINDEX(file):
352	count, = struct.unpack(">H", file.read(2))
353	offSize = ord(file.read(1))
354	offsets = []
355	for index in range(count+1):
356		chunk = file.read(offSize)
357		chunk = '\0' * (4 - offSize) + chunk
358		offset, = struct.unpack(">L", chunk)
359		offset = int(offset)
360		offsets.append(offset)
361	prev = offsets[0]
362	stuff = []
363	next = offsets[0]
364	for next in offsets[1:]:
365		chunk = file.read(next - prev)
366		assert len(chunk) == next - prev
367		stuff.append(chunk)
368		prev = next
369	return stuff
370
371
372def parseCharsetFormat1(nGlyphs, file, strings, isCID):
373	charset = ['.notdef']
374	count = 1
375	while count < nGlyphs:
376		first, = struct.unpack(">H", file.read(2))
377		nLeft = ord(file.read(1))
378		if isCID:
379			for CID in range(first, first+nLeft+1):
380				charset.append(CID)
381		else:
382			for SID in range(first, first+nLeft+1):
383				charset.append(strings[SID])
384		count = count + nLeft + 1
385	return charset
386
387
388def parseCharsetFormat2(nGlyphs, file, strings, isCID):
389	charset = ['.notdef']
390	count = 1
391	while count < nGlyphs:
392		first, = struct.unpack(">H", file.read(2))
393		nLeft, = struct.unpack(">H", file.read(2))
394		if isCID:
395			for CID in range(first, first+nLeft+1):
396				charset.append(CID)
397		else:
398			for SID in range(first, first+nLeft+1):
399				charset.append(strings[SID])
400		count = count + nLeft + 1
401	return charset
402
403
404# The 391 Standard Strings as used in the CFF format.
405# from Adobe Technical None #5176, version 1.0, 18 March 1998
406
407cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
408		'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
409		'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
410		'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
411		'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
412		'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
413		'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
414		'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
415		'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
416		's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
417		'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
418		'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
419		'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
420		'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
421		'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
422		'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
423		'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
424		'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
425		'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
426		'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
427		'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
428		'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
429		'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
430		'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
431		'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
432		'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
433		'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
434		'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
435		'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
436		'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
437		'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
438		'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
439		'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
440		'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
441		'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
442		'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
443		'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
444		'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
445		'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
446		'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
447		'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
448		'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
449		'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
450		'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
451		'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
452		'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
453		'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
454		'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
455		'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
456		'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
457		'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
458		'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
459		'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
460		'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
461		'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
462		'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
463		'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
464		'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
465		'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
466		'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
467		'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
468		'001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
469		'Semibold'
470]
471
472cffStandardStringCount = 391
473assert len(cffStandardStrings) == cffStandardStringCount
474# build reverse mapping
475cffStandardStringMapping = {}
476for _i in range(cffStandardStringCount):
477	cffStandardStringMapping[cffStandardStrings[_i]] = _i
478
479
480