blob: 0a37de5d85087bab2b94b0257abce3c7550ea245 [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#
jvra2a75b32002-05-13 11:25:17 +00004# $Id: cffLib.py,v 1.7 2002-05-13 11:24:43 jvr 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
jvra2a75b32002-05-13 11:25:17 +000025 def decompile(self, file):
26 sstruct.unpack(cffHeaderFormat, file.read(4), self)
Just7842e561999-12-16 21:34:53 +000027 assert self.major == 1 and self.minor == 0, \
28 "unknown CFF format: %d.%d" % (self.major, self.minor)
Just7842e561999-12-16 21:34:53 +000029
jvra2a75b32002-05-13 11:25:17 +000030 self.fontNames = readINDEX(file)
31 topDicts = readINDEX(file)
32 strings = IndexedStrings(readINDEX(file))
33 globalSubrs = readINDEX(file)
34
Just7842e561999-12-16 21:34:53 +000035 self.GlobalSubrs = map(psCharStrings.T2CharString, globalSubrs)
36
jvra2a75b32002-05-13 11:25:17 +000037 file.seek(0, 0)
38
Just7842e561999-12-16 21:34:53 +000039 for i in range(len(topDicts)):
40 font = self.fonts[self.fontNames[i]] = CFFFont()
41 font.GlobalSubrs = self.GlobalSubrs # Hmm.
jvra2a75b32002-05-13 11:25:17 +000042 font.decompile(file, topDicts[i], strings, self) # maybe only 'on demand'?
Just7842e561999-12-16 21:34:53 +000043
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
jvra2a75b32002-05-13 11:25:17 +0000126 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):
Just7842e561999-12-16 21:34:53 +0000138 top = psCharStrings.TopDictDecompiler(strings)
139 top.decompile(topDictData)
140 self.fromDict(top.getDict())
141
jvra2a75b32002-05-13 11:25:17 +0000142 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)
Just7842e561999-12-16 21:34:53 +0000154
jvra2a75b32002-05-13 11:25:17 +0000155 file.seek(self.CharStrings)
156 rawCharStrings = readINDEX(file)
Just7842e561999-12-16 21:34:53 +0000157 nGlyphs = len(rawCharStrings)
158
159 # get charset (or rather: get glyphNames)
jvra2a75b32002-05-13 11:25:17 +0000160 if self.charset == 0:
Just7842e561999-12-16 21:34:53 +0000161 xxx # standard charset
162 else:
jvra2a75b32002-05-13 11:25:17 +0000163 file.seek(self.charset)
164 format = ord(file.read(1))
Just7842e561999-12-16 21:34:53 +0000165 if format == 0:
166 xxx
167 elif format == 1:
jvra2a75b32002-05-13 11:25:17 +0000168 charset = parseCharsetFormat1(nGlyphs, file, strings, isCID)
Just7842e561999-12-16 21:34:53 +0000169 elif format == 2:
jvra2a75b32002-05-13 11:25:17 +0000170 charset = parseCharsetFormat2(nGlyphs, file, strings, isCID)
Just7842e561999-12-16 21:34:53 +0000171 elif format == 3:
172 xxx
173 else:
174 xxx
jvra2a75b32002-05-13 11:25:17 +0000175 self.charset = charset
Just7842e561999-12-16 21:34:53 +0000176
jvra2a75b32002-05-13 11:25:17 +0000177 assert len(charset) == nGlyphs
Just7842e561999-12-16 21:34:53 +0000178 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):
jvra2a75b32002-05-13 11:25:17 +0000186 charStrings[charset[i]] = charStringClass(rawCharStrings[i])
Just7842e561999-12-16 21:34:53 +0000187 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
Just8ab68262000-03-28 10:37:25 +0000305 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 = []
Just7842e561999-12-16 21:34:53 +0000311
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
jvra2a75b32002-05-13 11:25:17 +0000351def readINDEX(file):
352 count, = struct.unpack(">H", file.read(2))
353 offSize = ord(file.read(1))
Just7842e561999-12-16 21:34:53 +0000354 offsets = []
355 for index in range(count+1):
jvra2a75b32002-05-13 11:25:17 +0000356 chunk = file.read(offSize)
Just7842e561999-12-16 21:34:53 +0000357 chunk = '\0' * (4 - offSize) + chunk
358 offset, = struct.unpack(">L", chunk)
359 offset = int(offset)
360 offsets.append(offset)
Just7842e561999-12-16 21:34:53 +0000361 prev = offsets[0]
362 stuff = []
Just8ab68262000-03-28 10:37:25 +0000363 next = offsets[0]
Just7842e561999-12-16 21:34:53 +0000364 for next in offsets[1:]:
jvra2a75b32002-05-13 11:25:17 +0000365 chunk = file.read(next - prev)
Just7842e561999-12-16 21:34:53 +0000366 assert len(chunk) == next - prev
367 stuff.append(chunk)
368 prev = next
jvra2a75b32002-05-13 11:25:17 +0000369 return stuff
Just7842e561999-12-16 21:34:53 +0000370
371
jvra2a75b32002-05-13 11:25:17 +0000372def parseCharsetFormat1(nGlyphs, file, strings, isCID):
373 charset = ['.notdef']
Just7842e561999-12-16 21:34:53 +0000374 count = 1
375 while count < nGlyphs:
jvra2a75b32002-05-13 11:25:17 +0000376 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])
Just7842e561999-12-16 21:34:53 +0000384 count = count + nLeft + 1
jvra2a75b32002-05-13 11:25:17 +0000385 return charset
Just7842e561999-12-16 21:34:53 +0000386
387
jvra2a75b32002-05-13 11:25:17 +0000388def parseCharsetFormat2(nGlyphs, file, strings, isCID):
389 charset = ['.notdef']
Just7842e561999-12-16 21:34:53 +0000390 count = 1
391 while count < nGlyphs:
jvra2a75b32002-05-13 11:25:17 +0000392 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])
Just7842e561999-12-16 21:34:53 +0000400 count = count + nLeft + 1
jvra2a75b32002-05-13 11:25:17 +0000401 return charset
Just7842e561999-12-16 21:34:53 +0000402
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