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