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