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