cffLib.py revision a20285b7b490fc77b507cd12afdebba963f11c9a
1"""cffLib.py -- read/write tools for Adobe CFF fonts.""" 2 3# 4# $Id: cffLib.py,v 1.10 2002-05-14 12:37:36 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 self.GlobalSubrs = map(psCharStrings.T2CharString, globalSubrs) 35 36 for i in range(len(topDicts)): 37 font = self.fonts[self.fontNames[i]] = CFFFont() 38 font.GlobalSubrs = self.GlobalSubrs # Hmm. 39 file.seek(0, 0) 40 font.decompile(file, topDicts[i], strings, self) # maybe only 'on demand'? 41 42 def compile(self): 43 strings = IndexedStrings() 44 XXXX 45 46 def toXML(self, xmlWriter, progress=None): 47 xmlWriter.newline() 48 for fontName in self.fontNames: 49 xmlWriter.begintag("CFFFont", name=fontName) 50 xmlWriter.newline() 51 font = self.fonts[fontName] 52 font.toXML(xmlWriter, progress) 53 xmlWriter.endtag("CFFFont") 54 xmlWriter.newline() 55 xmlWriter.newline() 56 xmlWriter.begintag("GlobalSubrs") 57 xmlWriter.newline() 58 for i in range(len(self.GlobalSubrs)): 59 xmlWriter.newline() 60 xmlWriter.begintag("CharString", id=i) 61 xmlWriter.newline() 62 self.GlobalSubrs[i].toXML(xmlWriter) 63 xmlWriter.endtag("CharString") 64 xmlWriter.newline() 65 xmlWriter.newline() 66 xmlWriter.endtag("GlobalSubrs") 67 xmlWriter.newline() 68 xmlWriter.newline() 69 70 def fromXML(self, (name, attrs, content)): 71 xxx 72 73 74class CFFFont: 75 76 def __init__(self): 77 pass 78 79 def __getattr__(self, attr): 80 if not topDictDefaults.has_key(attr): 81 raise AttributeError, attr 82 return topDictDefaults[attr] 83 84 def fromDict(self, dict): 85 self.__dict__.update(dict) 86 87 def decompileCID(self, data, strings): 88 offset = self.FDArray 89 fontDicts, restdata = readINDEX(data[offset:]) 90 subFonts = [] 91 for topDictData in fontDicts: 92 subFont = CFFFont() 93 subFonts.append(subFont) 94 subFont.decompile(data, topDictData, strings, None) 95 96 raise NotImplementedError 97 98 def decompile(self, file, topDictData, strings, fontSet): 99 top = TopDictDecompiler(strings) 100 top.decompile(topDictData) 101 self.fromDict(top.getDict()) 102 103 if hasattr(self, "ROS"): 104 isCID = 1 105 # XXX CID subFonts 106 else: 107 isCID = 0 108 size, offset = self.Private 109 file.seek(offset, 0) 110 privateData = file.read(size) 111 file.seek(offset, 0) 112 assert len(privateData) == size 113 self.Private = PrivateDict() 114 self.Private.decompile(file, privateData, strings) 115 116 file.seek(self.CharStrings) 117 rawCharStrings = readINDEX(file) 118 nGlyphs = len(rawCharStrings) 119 120 # get charset (or rather: get glyphNames) 121 if self.charset == 0: 122 xxx # standard charset 123 else: 124 file.seek(self.charset) 125 format = ord(file.read(1)) 126 if format == 0: 127 xxx 128 elif format == 1: 129 charset = parseCharsetFormat1(nGlyphs, file, strings, isCID) 130 elif format == 2: 131 charset = parseCharsetFormat2(nGlyphs, file, strings, isCID) 132 elif format == 3: 133 xxx 134 else: 135 xxx 136 self.charset = charset 137 138 assert len(charset) == nGlyphs 139 self.CharStrings = charStrings = {} 140 if self.CharstringType == 2: 141 # Type 2 CharStrings 142 charStringClass = psCharStrings.T2CharString 143 else: 144 # Type 1 CharStrings 145 charStringClass = psCharStrings.T1CharString 146 for i in range(nGlyphs): 147 charStrings[charset[i]] = charStringClass(rawCharStrings[i]) 148 assert len(charStrings) == nGlyphs 149 150 # XXX Encoding! 151 encoding = self.Encoding 152 if encoding not in (0, 1): 153 # encoding is an _offset_ from the beginning of 'data' to an encoding subtable 154 XXX 155 self.Encoding = encoding 156 157 def getGlyphOrder(self): 158 return self.charset 159 160 def setGlyphOrder(self, glyphOrder): 161 self.charset = glyphOrder 162 163 def decompileAllCharStrings(self): 164 if self.CharstringType == 2: 165 # Type 2 CharStrings 166 decompiler = psCharStrings.SimpleT2Decompiler(self.Private.Subrs, self.GlobalSubrs) 167 for charString in self.CharStrings.values(): 168 if charString.needsDecompilation(): 169 decompiler.reset() 170 decompiler.execute(charString) 171 else: 172 # Type 1 CharStrings 173 for charString in self.CharStrings.values(): 174 charString.decompile() 175 176 def toXML(self, xmlWriter, progress=None): 177 xmlWriter.newline() 178 # first dump the simple values 179 self.toXMLSimpleValues(xmlWriter) 180 181 # dump charset 182 # XXX 183 184 # decompile all charstrings 185 if progress: 186 progress.setlabel("Decompiling CharStrings...") 187 self.decompileAllCharStrings() 188 189 # dump private dict 190 xmlWriter.begintag("Private") 191 xmlWriter.newline() 192 self.Private.toXML(xmlWriter) 193 xmlWriter.endtag("Private") 194 xmlWriter.newline() 195 196 self.toXMLCharStrings(xmlWriter, progress) 197 198 def toXMLSimpleValues(self, xmlWriter): 199 keys = self.__dict__.keys() 200 keys.remove("CharStrings") 201 keys.remove("Private") 202 keys.remove("charset") 203 keys.remove("GlobalSubrs") 204 keys.sort() 205 for key in keys: 206 value = getattr(self, key) 207 if key == "Encoding": 208 if value == 0: 209 # encoding is (Adobe) Standard Encoding 210 value = "StandardEncoding" 211 elif value == 1: 212 # encoding is Expert Encoding 213 value = "ExpertEncoding" 214 if type(value) == types.ListType: 215 value = string.join(map(str, value), " ") 216 else: 217 value = str(value) 218 xmlWriter.begintag(key) 219 if hasattr(value, "toXML"): 220 xmlWriter.newline() 221 value.toXML(xmlWriter) 222 xmlWriter.newline() 223 else: 224 xmlWriter.write(value) 225 xmlWriter.endtag(key) 226 xmlWriter.newline() 227 xmlWriter.newline() 228 229 def toXMLCharStrings(self, xmlWriter, progress=None): 230 charStrings = self.CharStrings 231 xmlWriter.newline() 232 xmlWriter.begintag("CharStrings") 233 xmlWriter.newline() 234 glyphNames = charStrings.keys() 235 glyphNames.sort() 236 for glyphName in glyphNames: 237 if progress: 238 progress.setlabel("Dumping 'CFF ' table... (%s)" % glyphName) 239 progress.increment() 240 xmlWriter.newline() 241 charString = charStrings[glyphName] 242 xmlWriter.begintag("CharString", name=glyphName) 243 xmlWriter.newline() 244 charString.toXML(xmlWriter) 245 xmlWriter.endtag("CharString") 246 xmlWriter.newline() 247 xmlWriter.newline() 248 xmlWriter.endtag("CharStrings") 249 xmlWriter.newline() 250 251 252class PrivateDict: 253 254 def __init__(self): 255 pass 256 257 def decompile(self, file, privateData, strings): 258 p = PrivateDictDecompiler(strings) 259 p.decompile(privateData) 260 self.fromDict(p.getDict()) 261 262 # get local subrs 263 #print "YYY Private.Subrs:", self.Subrs 264 if hasattr(self, "Subrs"): 265 file.seek(self.Subrs, 1) 266 localSubrs = readINDEX(file) 267 self.Subrs = map(psCharStrings.T2CharString, localSubrs) 268 else: 269 self.Subrs = [] 270 271 def toXML(self, xmlWriter): 272 xmlWriter.newline() 273 keys = self.__dict__.keys() 274 keys.remove("Subrs") 275 for key in keys: 276 value = getattr(self, key) 277 if type(value) == types.ListType: 278 value = string.join(map(str, value), " ") 279 else: 280 value = str(value) 281 xmlWriter.begintag(key) 282 xmlWriter.write(value) 283 xmlWriter.endtag(key) 284 xmlWriter.newline() 285 # write subroutines 286 xmlWriter.newline() 287 xmlWriter.begintag("Subrs") 288 xmlWriter.newline() 289 for i in range(len(self.Subrs)): 290 xmlWriter.newline() 291 xmlWriter.begintag("CharString", id=i) 292 xmlWriter.newline() 293 self.Subrs[i].toXML(xmlWriter) 294 xmlWriter.endtag("CharString") 295 xmlWriter.newline() 296 xmlWriter.newline() 297 xmlWriter.endtag("Subrs") 298 xmlWriter.newline() 299 xmlWriter.newline() 300 301 def __getattr__(self, attr): 302 if not privateDictDefaults.has_key(attr): 303 raise AttributeError, attr 304 return privateDictDefaults[attr] 305 306 def fromDict(self, dict): 307 self.__dict__.update(dict) 308 309 310def readINDEX(file): 311 count, = struct.unpack(">H", file.read(2)) 312 if count == 0: 313 return [] 314 offSize = ord(file.read(1)) 315 offsets = [] 316 for index in range(count+1): 317 chunk = file.read(offSize) 318 chunk = '\0' * (4 - offSize) + chunk 319 offset, = struct.unpack(">L", chunk) 320 offset = int(offset) 321 offsets.append(offset) 322 offsetBase = file.tell() - 1 323 prev = offsets[0] 324 stuff = [] 325 for next in offsets[1:]: 326 assert offsetBase + prev == file.tell() 327 chunk = file.read(next - prev) 328 assert len(chunk) == next - prev 329 stuff.append(chunk) 330 prev = next 331 return stuff 332 333 334def parseCharsetFormat1(nGlyphs, file, strings, isCID): 335 charset = ['.notdef'] 336 count = 1 337 while count < nGlyphs: 338 first, = struct.unpack(">H", file.read(2)) 339 nLeft = ord(file.read(1)) 340 if isCID: 341 for CID in range(first, first+nLeft+1): 342 charset.append(CID) 343 else: 344 for SID in range(first, first+nLeft+1): 345 charset.append(strings[SID]) 346 count = count + nLeft + 1 347 return charset 348 349 350def parseCharsetFormat2(nGlyphs, file, strings, isCID): 351 charset = ['.notdef'] 352 count = 1 353 while count < nGlyphs: 354 first, = struct.unpack(">H", file.read(2)) 355 nLeft, = struct.unpack(">H", file.read(2)) 356 if isCID: 357 for CID in range(first, first+nLeft+1): 358 charset.append(CID) 359 else: 360 for SID in range(first, first+nLeft+1): 361 charset.append(strings[SID]) 362 count = count + nLeft + 1 363 return charset 364 365 366topDictOperators = [ 367# opcode name argument type 368 (0, 'version', 'SID'), 369 (1, 'Notice', 'SID'), 370 (2, 'FullName', 'SID'), 371 (3, 'FamilyName', 'SID'), 372 (4, 'Weight', 'SID'), 373 (5, 'FontBBox', 'array'), 374 (13, 'UniqueID', 'number'), 375 (14, 'XUID', 'array'), 376 (15, 'charset', 'number'), 377 (16, 'Encoding', 'number'), 378 (17, 'CharStrings', 'number'), 379 (18, 'Private', ('number', 'number')), 380 ((12, 0), 'Copyright', 'SID'), 381 ((12, 1), 'isFixedPitch', 'number'), 382 ((12, 2), 'ItalicAngle', 'number'), 383 ((12, 3), 'UnderlinePosition', 'number'), 384 ((12, 4), 'UnderlineThickness', 'number'), 385 ((12, 5), 'PaintType', 'number'), 386 ((12, 6), 'CharstringType', 'number'), 387 ((12, 7), 'FontMatrix', 'array'), 388 ((12, 8), 'StrokeWidth', 'number'), 389 ((12, 20), 'SyntheticBase', 'number'), 390 ((12, 21), 'PostScript', 'SID'), 391 ((12, 22), 'BaseFontName', 'SID'), 392 # CID additions 393 ((12, 30), 'ROS', ('SID', 'SID', 'number')), 394 ((12, 31), 'CIDFontVersion', 'number'), 395 ((12, 32), 'CIDFontRevision', 'number'), 396 ((12, 33), 'CIDFontType', 'number'), 397 ((12, 34), 'CIDCount', 'number'), 398 ((12, 35), 'UIDBase', 'number'), 399 ((12, 36), 'FDArray', 'number'), 400 ((12, 37), 'FDSelect', 'number'), 401 ((12, 38), 'FontName', 'SID'), 402] 403 404topDictDefaults = { 405 'isFixedPitch': 0, 406 'ItalicAngle': 0, 407 'UnderlineThickness': 50, 408 'PaintType': 0, 409 'CharstringType': 2, 410 'FontMatrix': [0.001, 0, 0, 0.001, 0, 0], 411 'FontBBox': [0, 0, 0, 0], 412 'StrokeWidth': 0, 413 'charset': 0, 414 'Encoding': 0, 415 # CID defaults 416 'CIDFontVersion': 0, 417 'CIDFontRevision': 0, 418 'CIDFontType': 0, 419 'CIDCount': 8720, 420} 421 422class TopDictDecompiler(psCharStrings.DictDecompiler): 423 424 operators = psCharStrings.buildOperatorDict(topDictOperators) 425 dictDefaults = topDictDefaults 426 427 428privateDictOperators = [ 429# opcode name argument type 430 (6, 'BlueValues', 'array'), 431 (7, 'OtherBlues', 'array'), 432 (8, 'FamilyBlues', 'array'), 433 (9, 'FamilyOtherBlues', 'array'), 434 (10, 'StdHW', 'number'), 435 (11, 'StdVW', 'number'), 436 (19, 'Subrs', 'number'), 437 (20, 'defaultWidthX', 'number'), 438 (21, 'nominalWidthX', 'number'), 439 ((12, 9), 'BlueScale', 'number'), 440 ((12, 10), 'BlueShift', 'number'), 441 ((12, 11), 'BlueFuzz', 'number'), 442 ((12, 12), 'StemSnapH', 'array'), 443 ((12, 13), 'StemSnapV', 'array'), 444 ((12, 14), 'ForceBold', 'number'), 445 ((12, 17), 'LanguageGroup', 'number'), 446 ((12, 18), 'ExpansionFactor', 'number'), 447 ((12, 19), 'initialRandomSeed', 'number'), 448] 449 450privateDictDefaults = { 451 'defaultWidthX': 0, 452 'nominalWidthX': 0, 453 'BlueScale': 0.039625, 454 'BlueShift': 7, 455 'BlueFuzz': 1, 456 'ForceBold': 0, 457 'LanguageGroup': 0, 458 'ExpansionFactor': 0.06, 459 'initialRandomSeed': 0, 460} 461 462class PrivateDictDecompiler(psCharStrings.DictDecompiler): 463 464 operators = psCharStrings.buildOperatorDict(privateDictOperators) 465 dictDefaults = privateDictDefaults 466 467 468class IndexedStrings: 469 470 def __init__(self, strings=None): 471 if strings is None: 472 strings = [] 473 self.strings = strings 474 475 def __getitem__(self, SID): 476 if SID < cffStandardStringCount: 477 return cffStandardStrings[SID] 478 else: 479 return self.strings[SID - cffStandardStringCount] 480 481 def getSID(self, s): 482 if not hasattr(self, "stringMapping"): 483 self.buildStringMapping() 484 if cffStandardStringMapping.has_key(s): 485 SID = cffStandardStringMapping[s] 486 if self.stringMapping.has_key(s): 487 SID = self.stringMapping[s] 488 else: 489 SID = len(self.strings) + cffStandardStringCount 490 self.strings.append(s) 491 self.stringMapping[s] = SID 492 return SID 493 494 def getStrings(self): 495 return self.strings 496 497 def buildStringMapping(self): 498 self.stringMapping = {} 499 for index in range(len(self.strings)): 500 self.stringMapping[self.strings[index]] = index + cffStandardStringCount 501 502 503# The 391 Standard Strings as used in the CFF format. 504# from Adobe Technical None #5176, version 1.0, 18 March 1998 505 506cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 507 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 508 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 509 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 510 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 511 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 512 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 513 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 514 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 515 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 516 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 517 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 518 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 519 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 520 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 521 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 522 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 523 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 524 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 525 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 526 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 527 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 528 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 529 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 530 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 531 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 532 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 533 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 534 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 535 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis', 536 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 537 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 538 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 539 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 540 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 541 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 542 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', 543 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 544 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 545 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior', 546 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 547 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 548 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 549 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 550 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 551 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 552 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall', 553 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 554 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 555 'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior', 556 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 557 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 558 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 559 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 560 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 561 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 562 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 563 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 564 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 565 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 566 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002', 567 '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 568 'Semibold' 569] 570 571cffStandardStringCount = 391 572assert len(cffStandardStrings) == cffStandardStringCount 573# build reverse mapping 574cffStandardStringMapping = {} 575for _i in range(cffStandardStringCount): 576 cffStandardStringMapping[cffStandardStrings[_i]] = _i 577 578 579