blob: c145374a30dd6ae0c5351d2ceb43fcbaffbe9f92 [file] [log] [blame]
Just7842e561999-12-16 21:34:53 +00001"""cffLib.py -- read/write tools for Adobe CFF fonts."""
2
Justec46d161999-12-20 22:02:10 +00003#
Justfda65732000-01-19 20:44:33 +00004# $Id: cffLib.py,v 1.5 2000-01-19 20:44:33 Just Exp $
Justec46d161999-12-20 22:02:10 +00005#
Just7842e561999-12-16 21:34:53 +00006
7import struct, sstruct
8import string
9import types
Just528614e2000-01-16 22:14:02 +000010from fontTools.misc import psCharStrings
Just7842e561999-12-16 21:34:53 +000011
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