cffLib.py revision 8c5c96647147a4cfc13d31d17a64f501fa824994
1df83623cfe09733750eed2c2ed5f4bde674bb9f9Just"""cffLib.py -- read/write tools for Adobe CFF fonts.""" 2df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 38413c108d21e8cf0e9059bbfffde8d13f2616340Behdad Esfahbod# 4df83623cfe09733750eed2c2ed5f4bde674bb9f9Just# $Id: cffLib.py,v 1.34 2008-03-07 19:56:17 jvr Exp $ 5df83623cfe09733750eed2c2ed5f4bde674bb9f9Just# 6df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 7df83623cfe09733750eed2c2ed5f4bde674bb9f9Justimport struct 8df83623cfe09733750eed2c2ed5f4bde674bb9f9Justfrom fontTools.misc import sstruct 9df83623cfe09733750eed2c2ed5f4bde674bb9f9Justimport string 10df83623cfe09733750eed2c2ed5f4bde674bb9f9Justfrom fontTools.misc import psCharStrings 11df83623cfe09733750eed2c2ed5f4bde674bb9f9Justfrom fontTools.misc.textTools import safeEval 12df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 13df83623cfe09733750eed2c2ed5f4bde674bb9f9JustDEBUG = 0 14df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 15df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 16df83623cfe09733750eed2c2ed5f4bde674bb9f9JustcffHeaderFormat = """ 17df83623cfe09733750eed2c2ed5f4bde674bb9f9Just major: B 18df83623cfe09733750eed2c2ed5f4bde674bb9f9Just minor: B 19df83623cfe09733750eed2c2ed5f4bde674bb9f9Just hdrSize: B 20df83623cfe09733750eed2c2ed5f4bde674bb9f9Just offSize: B 21df83623cfe09733750eed2c2ed5f4bde674bb9f9Just""" 22df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 23df83623cfe09733750eed2c2ed5f4bde674bb9f9Justclass CFFFontSet: 24df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 25df83623cfe09733750eed2c2ed5f4bde674bb9f9Just def __init__(self): 26df83623cfe09733750eed2c2ed5f4bde674bb9f9Just pass 27df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 28df83623cfe09733750eed2c2ed5f4bde674bb9f9Just def decompile(self, file, otFont): 29df83623cfe09733750eed2c2ed5f4bde674bb9f9Just sstruct.unpack(cffHeaderFormat, file.read(4), self) 30df83623cfe09733750eed2c2ed5f4bde674bb9f9Just assert self.major == 1 and self.minor == 0, \ 31df83623cfe09733750eed2c2ed5f4bde674bb9f9Just "unknown CFF format: %d.%d" % (self.major, self.minor) 32df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 33df83623cfe09733750eed2c2ed5f4bde674bb9f9Just file.seek(self.hdrSize) 34df83623cfe09733750eed2c2ed5f4bde674bb9f9Just self.fontNames = list(Index(file)) 35df83623cfe09733750eed2c2ed5f4bde674bb9f9Just self.topDictIndex = TopDictIndex(file) 36df83623cfe09733750eed2c2ed5f4bde674bb9f9Just self.strings = IndexedStrings(file) 37df83623cfe09733750eed2c2ed5f4bde674bb9f9Just self.GlobalSubrs = GlobalSubrsIndex(file) 38df83623cfe09733750eed2c2ed5f4bde674bb9f9Just self.topDictIndex.strings = self.strings 39df83623cfe09733750eed2c2ed5f4bde674bb9f9Just self.topDictIndex.GlobalSubrs = self.GlobalSubrs 40df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 41df83623cfe09733750eed2c2ed5f4bde674bb9f9Just def __len__(self): 42df83623cfe09733750eed2c2ed5f4bde674bb9f9Just return len(self.fontNames) 43df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 44df83623cfe09733750eed2c2ed5f4bde674bb9f9Just def keys(self): 45df83623cfe09733750eed2c2ed5f4bde674bb9f9Just return list(self.fontNames) 46df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 47df83623cfe09733750eed2c2ed5f4bde674bb9f9Just def values(self): 48df83623cfe09733750eed2c2ed5f4bde674bb9f9Just return self.topDictIndex 49df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 50df83623cfe09733750eed2c2ed5f4bde674bb9f9Just def __getitem__(self, name): 51df83623cfe09733750eed2c2ed5f4bde674bb9f9Just try: 52df83623cfe09733750eed2c2ed5f4bde674bb9f9Just index = self.fontNames.index(name) 53df83623cfe09733750eed2c2ed5f4bde674bb9f9Just except ValueError: 54df83623cfe09733750eed2c2ed5f4bde674bb9f9Just raise KeyError, name 55df83623cfe09733750eed2c2ed5f4bde674bb9f9Just return self.topDictIndex[index] 56df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 57df83623cfe09733750eed2c2ed5f4bde674bb9f9Just def compile(self, file, otFont): 58df83623cfe09733750eed2c2ed5f4bde674bb9f9Just strings = IndexedStrings() 59df83623cfe09733750eed2c2ed5f4bde674bb9f9Just writer = CFFWriter() 60df83623cfe09733750eed2c2ed5f4bde674bb9f9Just writer.add(sstruct.pack(cffHeaderFormat, self)) 61df83623cfe09733750eed2c2ed5f4bde674bb9f9Just fontNames = Index() 62df83623cfe09733750eed2c2ed5f4bde674bb9f9Just for name in self.fontNames: 63df83623cfe09733750eed2c2ed5f4bde674bb9f9Just fontNames.append(name) 64df83623cfe09733750eed2c2ed5f4bde674bb9f9Just writer.add(fontNames.getCompiler(strings, None)) 65df83623cfe09733750eed2c2ed5f4bde674bb9f9Just topCompiler = self.topDictIndex.getCompiler(strings, None) 66df83623cfe09733750eed2c2ed5f4bde674bb9f9Just writer.add(topCompiler) 67df83623cfe09733750eed2c2ed5f4bde674bb9f9Just writer.add(strings.getCompiler()) 68df83623cfe09733750eed2c2ed5f4bde674bb9f9Just writer.add(self.GlobalSubrs.getCompiler(strings, None)) 69df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 70df83623cfe09733750eed2c2ed5f4bde674bb9f9Just for topDict in self.topDictIndex: 71df83623cfe09733750eed2c2ed5f4bde674bb9f9Just if not hasattr(topDict, "charset") or topDict.charset is None: 72df83623cfe09733750eed2c2ed5f4bde674bb9f9Just charset = otFont.getGlyphOrder() 73cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod topDict.charset = charset 74df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 75df83623cfe09733750eed2c2ed5f4bde674bb9f9Just for child in topCompiler.getChildren(strings): 76df83623cfe09733750eed2c2ed5f4bde674bb9f9Just writer.add(child) 77df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 78df83623cfe09733750eed2c2ed5f4bde674bb9f9Just writer.toFile(file) 79df83623cfe09733750eed2c2ed5f4bde674bb9f9Just 80df83623cfe09733750eed2c2ed5f4bde674bb9f9Just def toXML(self, xmlWriter, progress=None): 81df83623cfe09733750eed2c2ed5f4bde674bb9f9Just xmlWriter.newline() 82df83623cfe09733750eed2c2ed5f4bde674bb9f9Just for fontName in self.fontNames: 83df83623cfe09733750eed2c2ed5f4bde674bb9f9Just xmlWriter.begintag("CFFFont", name=fontName) 84df83623cfe09733750eed2c2ed5f4bde674bb9f9Just xmlWriter.newline() 85df83623cfe09733750eed2c2ed5f4bde674bb9f9Just font = self[fontName] 86df83623cfe09733750eed2c2ed5f4bde674bb9f9Just font.toXML(xmlWriter, progress) 87df83623cfe09733750eed2c2ed5f4bde674bb9f9Just xmlWriter.endtag("CFFFont") 88df83623cfe09733750eed2c2ed5f4bde674bb9f9Just xmlWriter.newline() 89df83623cfe09733750eed2c2ed5f4bde674bb9f9Just xmlWriter.newline() 90df83623cfe09733750eed2c2ed5f4bde674bb9f9Just xmlWriter.begintag("GlobalSubrs") 91df83623cfe09733750eed2c2ed5f4bde674bb9f9Just xmlWriter.newline() 92df83623cfe09733750eed2c2ed5f4bde674bb9f9Just self.GlobalSubrs.toXML(xmlWriter, progress) 93df83623cfe09733750eed2c2ed5f4bde674bb9f9Just xmlWriter.endtag("GlobalSubrs") 94df83623cfe09733750eed2c2ed5f4bde674bb9f9Just xmlWriter.newline() 95 xmlWriter.newline() 96 97 def fromXML(self, (name, attrs, content)): 98 if not hasattr(self, "GlobalSubrs"): 99 self.GlobalSubrs = GlobalSubrsIndex() 100 self.major = 1 101 self.minor = 0 102 self.hdrSize = 4 103 self.offSize = 4 # XXX ?? 104 if name == "CFFFont": 105 if not hasattr(self, "fontNames"): 106 self.fontNames = [] 107 self.topDictIndex = TopDictIndex() 108 fontName = attrs["name"] 109 topDict = TopDict(GlobalSubrs=self.GlobalSubrs) 110 topDict.charset = None # gets filled in later 111 self.fontNames.append(fontName) 112 self.topDictIndex.append(topDict) 113 for element in content: 114 if isinstance(element, basestring): 115 continue 116 topDict.fromXML(element) 117 elif name == "GlobalSubrs": 118 for element in content: 119 if isinstance(element, basestring): 120 continue 121 name, attrs, content = element 122 subr = psCharStrings.T2CharString() 123 subr.fromXML((name, attrs, content)) 124 self.GlobalSubrs.append(subr) 125 126 127class CFFWriter: 128 129 def __init__(self): 130 self.data = [] 131 132 def add(self, table): 133 self.data.append(table) 134 135 def toFile(self, file): 136 lastPosList = None 137 count = 1 138 while 1: 139 if DEBUG: 140 print "CFFWriter.toFile() iteration:", count 141 count = count + 1 142 pos = 0 143 posList = [pos] 144 for item in self.data: 145 if hasattr(item, "getDataLength"): 146 endPos = pos + item.getDataLength() 147 else: 148 endPos = pos + len(item) 149 if hasattr(item, "setPos"): 150 item.setPos(pos, endPos) 151 pos = endPos 152 posList.append(pos) 153 if posList == lastPosList: 154 break 155 lastPosList = posList 156 if DEBUG: 157 print "CFFWriter.toFile() writing to file." 158 begin = file.tell() 159 posList = [0] 160 for item in self.data: 161 if hasattr(item, "toFile"): 162 item.toFile(file) 163 else: 164 file.write(item) 165 posList.append(file.tell() - begin) 166 assert posList == lastPosList 167 168 169def calcOffSize(largestOffset): 170 if largestOffset < 0x100: 171 offSize = 1 172 elif largestOffset < 0x10000: 173 offSize = 2 174 elif largestOffset < 0x1000000: 175 offSize = 3 176 else: 177 offSize = 4 178 return offSize 179 180 181class IndexCompiler: 182 183 def __init__(self, items, strings, parent): 184 self.items = self.getItems(items, strings) 185 self.parent = parent 186 187 def getItems(self, items, strings): 188 return items 189 190 def getOffsets(self): 191 pos = 1 192 offsets = [pos] 193 for item in self.items: 194 if hasattr(item, "getDataLength"): 195 pos = pos + item.getDataLength() 196 else: 197 pos = pos + len(item) 198 offsets.append(pos) 199 return offsets 200 201 def getDataLength(self): 202 lastOffset = self.getOffsets()[-1] 203 offSize = calcOffSize(lastOffset) 204 dataLength = ( 205 2 + # count 206 1 + # offSize 207 (len(self.items) + 1) * offSize + # the offsets 208 lastOffset - 1 # size of object data 209 ) 210 return dataLength 211 212 def toFile(self, file): 213 offsets = self.getOffsets() 214 writeCard16(file, len(self.items)) 215 offSize = calcOffSize(offsets[-1]) 216 writeCard8(file, offSize) 217 offSize = -offSize 218 pack = struct.pack 219 for offset in offsets: 220 binOffset = pack(">l", offset)[offSize:] 221 assert len(binOffset) == -offSize 222 file.write(binOffset) 223 for item in self.items: 224 if hasattr(item, "toFile"): 225 item.toFile(file) 226 else: 227 file.write(item) 228 229 230class IndexedStringsCompiler(IndexCompiler): 231 232 def getItems(self, items, strings): 233 return items.strings 234 235 236class TopDictIndexCompiler(IndexCompiler): 237 238 def getItems(self, items, strings): 239 out = [] 240 for item in items: 241 out.append(item.getCompiler(strings, self)) 242 return out 243 244 def getChildren(self, strings): 245 children = [] 246 for topDict in self.items: 247 children.extend(topDict.getChildren(strings)) 248 return children 249 250 251class FDArrayIndexCompiler(IndexCompiler): 252 253 def getItems(self, items, strings): 254 out = [] 255 for item in items: 256 out.append(item.getCompiler(strings, self)) 257 return out 258 259 def getChildren(self, strings): 260 children = [] 261 for fontDict in self.items: 262 children.extend(fontDict.getChildren(strings)) 263 return children 264 265 def toFile(self, file): 266 offsets = self.getOffsets() 267 writeCard16(file, len(self.items)) 268 offSize = calcOffSize(offsets[-1]) 269 writeCard8(file, offSize) 270 offSize = -offSize 271 pack = struct.pack 272 for offset in offsets: 273 binOffset = pack(">l", offset)[offSize:] 274 assert len(binOffset) == -offSize 275 file.write(binOffset) 276 for item in self.items: 277 if hasattr(item, "toFile"): 278 item.toFile(file) 279 else: 280 file.write(item) 281 282 def setPos(self, pos, endPos): 283 self.parent.rawDict["FDArray"] = pos 284 285 286class GlobalSubrsCompiler(IndexCompiler): 287 def getItems(self, items, strings): 288 out = [] 289 for cs in items: 290 cs.compile() 291 out.append(cs.bytecode) 292 return out 293 294class SubrsCompiler(GlobalSubrsCompiler): 295 def setPos(self, pos, endPos): 296 offset = pos - self.parent.pos 297 self.parent.rawDict["Subrs"] = offset 298 299class CharStringsCompiler(GlobalSubrsCompiler): 300 def setPos(self, pos, endPos): 301 self.parent.rawDict["CharStrings"] = pos 302 303 304class Index: 305 306 """This class represents what the CFF spec calls an INDEX.""" 307 308 compilerClass = IndexCompiler 309 310 def __init__(self, file=None): 311 name = self.__class__.__name__ 312 if file is None: 313 self.items = [] 314 return 315 if DEBUG: 316 print "loading %s at %s" % (name, file.tell()) 317 self.file = file 318 count = readCard16(file) 319 self.count = count 320 self.items = [None] * count 321 if count == 0: 322 self.items = [] 323 return 324 offSize = readCard8(file) 325 if DEBUG: 326 print " index count: %s offSize: %s" % (count, offSize) 327 assert offSize <= 4, "offSize too large: %s" % offSize 328 self.offsets = offsets = [] 329 pad = '\0' * (4 - offSize) 330 for index in range(count+1): 331 chunk = file.read(offSize) 332 chunk = pad + chunk 333 offset, = struct.unpack(">L", chunk) 334 offsets.append(int(offset)) 335 self.offsetBase = file.tell() - 1 336 file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot 337 if DEBUG: 338 print " end of %s at %s" % (name, file.tell()) 339 340 def __len__(self): 341 return len(self.items) 342 343 def __getitem__(self, index): 344 item = self.items[index] 345 if item is not None: 346 return item 347 offset = self.offsets[index] + self.offsetBase 348 size = self.offsets[index+1] - self.offsets[index] 349 file = self.file 350 file.seek(offset) 351 data = file.read(size) 352 assert len(data) == size 353 item = self.produceItem(index, data, file, offset, size) 354 self.items[index] = item 355 return item 356 357 def produceItem(self, index, data, file, offset, size): 358 return data 359 360 def append(self, item): 361 self.items.append(item) 362 363 def getCompiler(self, strings, parent): 364 return self.compilerClass(self, strings, parent) 365 366 367class GlobalSubrsIndex(Index): 368 369 compilerClass = GlobalSubrsCompiler 370 371 def __init__(self, file=None, globalSubrs=None, private=None, fdSelect=None, fdArray=None): 372 Index.__init__(self, file) 373 self.globalSubrs = globalSubrs 374 self.private = private 375 if fdSelect: 376 self.fdSelect = fdSelect 377 if fdArray: 378 self.fdArray = fdArray 379 380 def produceItem(self, index, data, file, offset, size): 381 if self.private is not None: 382 private = self.private 383 elif hasattr(self, 'fdArray') and self.fdArray is not None: 384 private = self.fdArray[self.fdSelect[index]].Private 385 else: 386 private = None 387 return psCharStrings.T2CharString(data, private=private, globalSubrs=self.globalSubrs) 388 389 def toXML(self, xmlWriter, progress): 390 xmlWriter.comment("The 'index' attribute is only for humans; it is ignored when parsed.") 391 xmlWriter.newline() 392 for i in range(len(self)): 393 subr = self[i] 394 if subr.needsDecompilation(): 395 xmlWriter.begintag("CharString", index=i, raw=1) 396 else: 397 xmlWriter.begintag("CharString", index=i) 398 xmlWriter.newline() 399 subr.toXML(xmlWriter) 400 xmlWriter.endtag("CharString") 401 xmlWriter.newline() 402 403 def fromXML(self, (name, attrs, content)): 404 if name <> "CharString": 405 return 406 subr = psCharStrings.T2CharString() 407 subr.fromXML((name, attrs, content)) 408 self.append(subr) 409 410 def getItemAndSelector(self, index): 411 sel = None 412 if hasattr(self, 'fdSelect'): 413 sel = self.fdSelect[index] 414 return self[index], sel 415 416 417class SubrsIndex(GlobalSubrsIndex): 418 compilerClass = SubrsCompiler 419 420 421class TopDictIndex(Index): 422 423 compilerClass = TopDictIndexCompiler 424 425 def produceItem(self, index, data, file, offset, size): 426 top = TopDict(self.strings, file, offset, self.GlobalSubrs) 427 top.decompile(data) 428 return top 429 430 def toXML(self, xmlWriter, progress): 431 for i in range(len(self)): 432 xmlWriter.begintag("FontDict", index=i) 433 xmlWriter.newline() 434 self[i].toXML(xmlWriter, progress) 435 xmlWriter.endtag("FontDict") 436 xmlWriter.newline() 437 438 439class FDArrayIndex(TopDictIndex): 440 441 compilerClass = FDArrayIndexCompiler 442 443 def fromXML(self, (name, attrs, content)): 444 if name <> "FontDict": 445 return 446 fontDict = FontDict() 447 for element in content: 448 if isinstance(element, basestring): 449 continue 450 fontDict.fromXML(element) 451 self.append(fontDict) 452 453 454class FDSelect: 455 def __init__(self, file = None, numGlyphs = None, format=None): 456 if file: 457 # read data in from file 458 self.format = readCard8(file) 459 if self.format == 0: 460 from array import array 461 self.gidArray = array("B", file.read(numGlyphs)).tolist() 462 elif self.format == 3: 463 gidArray = [None] * numGlyphs 464 nRanges = readCard16(file) 465 prev = None 466 for i in range(nRanges): 467 first = readCard16(file) 468 if prev is not None: 469 for glyphID in range(prev, first): 470 gidArray[glyphID] = fd 471 prev = first 472 fd = readCard8(file) 473 if prev is not None: 474 first = readCard16(file) 475 for glyphID in range(prev, first): 476 gidArray[glyphID] = fd 477 self.gidArray = gidArray 478 else: 479 assert 0, "unsupported FDSelect format: %s" % format 480 else: 481 # reading from XML. Make empty gidArray,, and leave format as passed in. 482 # format == None will result in the smallest representation being used. 483 self.format = format 484 self.gidArray = [] 485 486 487 def __len__(self): 488 return len(self.gidArray) 489 490 def __getitem__(self, index): 491 return self.gidArray[index] 492 493 def __setitem__(self, index, fdSelectValue): 494 self.gidArray[index] = fdSelectValue 495 496 def append(self, fdSelectValue): 497 self.gidArray.append(fdSelectValue) 498 499 500class CharStrings: 501 502 def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray): 503 if file is not None: 504 self.charStringsIndex = SubrsIndex(file, globalSubrs, private, fdSelect, fdArray) 505 self.charStrings = charStrings = {} 506 for i in range(len(charset)): 507 charStrings[charset[i]] = i 508 self.charStringsAreIndexed = 1 509 else: 510 self.charStrings = {} 511 self.charStringsAreIndexed = 0 512 self.globalSubrs = globalSubrs 513 self.private = private 514 if fdSelect != None: 515 self.fdSelect = fdSelect 516 if fdArray!= None: 517 self.fdArray = fdArray 518 519 def keys(self): 520 return self.charStrings.keys() 521 522 def values(self): 523 if self.charStringsAreIndexed: 524 return self.charStringsIndex 525 else: 526 return self.charStrings.values() 527 528 def has_key(self, name): 529 return self.charStrings.has_key(name) 530 531 def __len__(self): 532 return len(self.charStrings) 533 534 def __getitem__(self, name): 535 charString = self.charStrings[name] 536 if self.charStringsAreIndexed: 537 charString = self.charStringsIndex[charString] 538 return charString 539 540 def __setitem__(self, name, charString): 541 if self.charStringsAreIndexed: 542 index = self.charStrings[name] 543 self.charStringsIndex[index] = charString 544 else: 545 self.charStrings[name] = charString 546 547 def getItemAndSelector(self, name): 548 if self.charStringsAreIndexed: 549 index = self.charStrings[name] 550 return self.charStringsIndex.getItemAndSelector(index) 551 else: 552 if hasattr(self, 'fdSelect'): 553 sel = self.fdSelect[index] # index is not defined at this point. Read R. ? 554 else: 555 raise KeyError("fdSelect array not yet defined.") 556 return self.charStrings[name], sel 557 558 def toXML(self, xmlWriter, progress): 559 names = self.keys() 560 names.sort() 561 i = 0 562 step = 10 563 numGlyphs = len(names) 564 for name in names: 565 charStr, fdSelectIndex = self.getItemAndSelector(name) 566 if charStr.needsDecompilation(): 567 raw = [("raw", 1)] 568 else: 569 raw = [] 570 if fdSelectIndex is None: 571 xmlWriter.begintag("CharString", [('name', name)] + raw) 572 else: 573 xmlWriter.begintag("CharString", 574 [('name', name), ('fdSelectIndex', fdSelectIndex)] + raw) 575 xmlWriter.newline() 576 charStr.toXML(xmlWriter) 577 xmlWriter.endtag("CharString") 578 xmlWriter.newline() 579 if not i % step and progress is not None: 580 progress.setLabel("Dumping 'CFF ' table... (%s)" % name) 581 progress.increment(step / float(numGlyphs)) 582 i = i + 1 583 584 def fromXML(self, (name, attrs, content)): 585 for element in content: 586 if isinstance(element, basestring): 587 continue 588 name, attrs, content = element 589 if name <> "CharString": 590 continue 591 fdID = -1 592 if hasattr(self, "fdArray"): 593 fdID = safeEval(attrs["fdSelectIndex"]) 594 private = self.fdArray[fdID].Private 595 else: 596 private = self.private 597 598 glyphName = attrs["name"] 599 charString = psCharStrings.T2CharString( 600 private=private, 601 globalSubrs=self.globalSubrs) 602 charString.fromXML((name, attrs, content)) 603 if fdID >= 0: 604 charString.fdSelectIndex = fdID 605 self[glyphName] = charString 606 607 608def readCard8(file): 609 return ord(file.read(1)) 610 611def readCard16(file): 612 value, = struct.unpack(">H", file.read(2)) 613 return value 614 615def writeCard8(file, value): 616 file.write(chr(value)) 617 618def writeCard16(file, value): 619 file.write(struct.pack(">H", value)) 620 621def packCard8(value): 622 return chr(value) 623 624def packCard16(value): 625 return struct.pack(">H", value) 626 627def buildOperatorDict(table): 628 d = {} 629 for op, name, arg, default, conv in table: 630 d[op] = (name, arg) 631 return d 632 633def buildOpcodeDict(table): 634 d = {} 635 for op, name, arg, default, conv in table: 636 if isinstance(op, tuple): 637 op = chr(op[0]) + chr(op[1]) 638 else: 639 op = chr(op) 640 d[name] = (op, arg) 641 return d 642 643def buildOrder(table): 644 l = [] 645 for op, name, arg, default, conv in table: 646 l.append(name) 647 return l 648 649def buildDefaults(table): 650 d = {} 651 for op, name, arg, default, conv in table: 652 if default is not None: 653 d[name] = default 654 return d 655 656def buildConverters(table): 657 d = {} 658 for op, name, arg, default, conv in table: 659 d[name] = conv 660 return d 661 662 663class SimpleConverter: 664 def read(self, parent, value): 665 return value 666 def write(self, parent, value): 667 return value 668 def xmlWrite(self, xmlWriter, name, value, progress): 669 xmlWriter.simpletag(name, value=value) 670 xmlWriter.newline() 671 def xmlRead(self, (name, attrs, content), parent): 672 return attrs["value"] 673 674class Latin1Converter(SimpleConverter): 675 def xmlWrite(self, xmlWriter, name, value, progress): 676 # Store as UTF-8 677 value = unicode(value, "latin-1").encode("utf-8") 678 xmlWriter.simpletag(name, value=value) 679 xmlWriter.newline() 680 def xmlRead(self, (name, attrs, content), parent): 681 s = unicode(attrs["value"], "utf-8") 682 return s.encode("latin-1") 683 684 685def parseNum(s): 686 try: 687 value = int(s) 688 except: 689 value = float(s) 690 return value 691 692class NumberConverter(SimpleConverter): 693 def xmlRead(self, (name, attrs, content), parent): 694 return parseNum(attrs["value"]) 695 696class ArrayConverter(SimpleConverter): 697 def xmlWrite(self, xmlWriter, name, value, progress): 698 value = map(str, value) 699 xmlWriter.simpletag(name, value=" ".join(value)) 700 xmlWriter.newline() 701 def xmlRead(self, (name, attrs, content), parent): 702 values = attrs["value"].split() 703 return map(parseNum, values) 704 705class TableConverter(SimpleConverter): 706 def xmlWrite(self, xmlWriter, name, value, progress): 707 xmlWriter.begintag(name) 708 xmlWriter.newline() 709 value.toXML(xmlWriter, progress) 710 xmlWriter.endtag(name) 711 xmlWriter.newline() 712 def xmlRead(self, (name, attrs, content), parent): 713 ob = self.getClass()() 714 for element in content: 715 if isinstance(element, basestring): 716 continue 717 ob.fromXML(element) 718 return ob 719 720class PrivateDictConverter(TableConverter): 721 def getClass(self): 722 return PrivateDict 723 def read(self, parent, value): 724 size, offset = value 725 file = parent.file 726 priv = PrivateDict(parent.strings, file, offset) 727 file.seek(offset) 728 data = file.read(size) 729 len(data) == size 730 priv.decompile(data) 731 return priv 732 def write(self, parent, value): 733 return (0, 0) # dummy value 734 735class SubrsConverter(TableConverter): 736 def getClass(self): 737 return SubrsIndex 738 def read(self, parent, value): 739 file = parent.file 740 file.seek(parent.offset + value) # Offset(self) 741 return SubrsIndex(file) 742 def write(self, parent, value): 743 return 0 # dummy value 744 745class CharStringsConverter(TableConverter): 746 def read(self, parent, value): 747 file = parent.file 748 charset = parent.charset 749 globalSubrs = parent.GlobalSubrs 750 if hasattr(parent, "ROS"): 751 fdSelect, fdArray = parent.FDSelect, parent.FDArray 752 private = None 753 else: 754 fdSelect, fdArray = None, None 755 private = parent.Private 756 file.seek(value) # Offset(0) 757 return CharStrings(file, charset, globalSubrs, private, fdSelect, fdArray) 758 def write(self, parent, value): 759 return 0 # dummy value 760 def xmlRead(self, (name, attrs, content), parent): 761 if hasattr(parent, "ROS"): 762 # if it is a CID-keyed font, then the private Dict is extracted from the parent.FDArray 763 private, fdSelect, fdArray = None, parent.FDSelect, parent.FDArray 764 else: 765 # if it is a name-keyed font, then the private dict is in the top dict, and there is no fdArray. 766 private, fdSelect, fdArray = parent.Private, None, None 767 charStrings = CharStrings(None, None, parent.GlobalSubrs, private, fdSelect, fdArray) 768 charStrings.fromXML((name, attrs, content)) 769 return charStrings 770 771class CharsetConverter: 772 def read(self, parent, value): 773 isCID = hasattr(parent, "ROS") 774 if value > 2: 775 numGlyphs = parent.numGlyphs 776 file = parent.file 777 file.seek(value) 778 if DEBUG: 779 print "loading charset at %s" % value 780 format = readCard8(file) 781 if format == 0: 782 charset = parseCharset0(numGlyphs, file, parent.strings, isCID) 783 elif format == 1 or format == 2: 784 charset = parseCharset(numGlyphs, file, parent.strings, isCID, format) 785 else: 786 raise NotImplementedError 787 assert len(charset) == numGlyphs 788 if DEBUG: 789 print " charset end at %s" % file.tell() 790 else: # offset == 0 -> no charset data. 791 if isCID or not parent.rawDict.has_key("CharStrings"): 792 assert value == 0 # We get here only when processing fontDicts from the FDArray of CFF-CID fonts. Only the real topDict references the chrset. 793 charset = None 794 elif value == 0: 795 charset = cffISOAdobeStrings 796 elif value == 1: 797 charset = cffIExpertStrings 798 elif value == 2: 799 charset = cffExpertSubsetStrings 800 return charset 801 802 def write(self, parent, value): 803 return 0 # dummy value 804 def xmlWrite(self, xmlWriter, name, value, progress): 805 # XXX only write charset when not in OT/TTX context, where we 806 # dump charset as a separate "GlyphOrder" table. 807 ##xmlWriter.simpletag("charset") 808 xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element") 809 xmlWriter.newline() 810 def xmlRead(self, (name, attrs, content), parent): 811 if 0: 812 return safeEval(attrs["value"]) 813 814 815class CharsetCompiler: 816 817 def __init__(self, strings, charset, parent): 818 assert charset[0] == '.notdef' 819 isCID = hasattr(parent.dictObj, "ROS") 820 data0 = packCharset0(charset, isCID, strings) 821 data = packCharset(charset, isCID, strings) 822 if len(data) < len(data0): 823 self.data = data 824 else: 825 self.data = data0 826 self.parent = parent 827 828 def setPos(self, pos, endPos): 829 self.parent.rawDict["charset"] = pos 830 831 def getDataLength(self): 832 return len(self.data) 833 834 def toFile(self, file): 835 file.write(self.data) 836 837 838def getCIDfromName(name, strings): 839 return int(name[3:]) 840 841def getSIDfromName(name, strings): 842 return strings.getSID(name) 843 844def packCharset0(charset, isCID, strings): 845 format = 0 846 data = [packCard8(format)] 847 if isCID: 848 getNameID = getCIDfromName 849 else: 850 getNameID = getSIDfromName 851 852 for name in charset[1:]: 853 data.append(packCard16(getNameID(name,strings))) 854 return "".join(data) 855 856 857def packCharset(charset, isCID, strings): 858 format = 1 859 ranges = [] 860 first = None 861 end = 0 862 if isCID: 863 getNameID = getCIDfromName 864 else: 865 getNameID = getSIDfromName 866 867 for name in charset[1:]: 868 SID = getNameID(name, strings) 869 if first is None: 870 first = SID 871 elif end + 1 <> SID: 872 nLeft = end - first 873 if nLeft > 255: 874 format = 2 875 ranges.append((first, nLeft)) 876 first = SID 877 end = SID 878 nLeft = end - first 879 if nLeft > 255: 880 format = 2 881 ranges.append((first, nLeft)) 882 883 data = [packCard8(format)] 884 if format == 1: 885 nLeftFunc = packCard8 886 else: 887 nLeftFunc = packCard16 888 for first, nLeft in ranges: 889 data.append(packCard16(first) + nLeftFunc(nLeft)) 890 return "".join(data) 891 892def parseCharset0(numGlyphs, file, strings, isCID): 893 charset = [".notdef"] 894 if isCID: 895 for i in range(numGlyphs - 1): 896 CID = readCard16(file) 897 charset.append("cid" + string.zfill(str(CID), 5) ) 898 else: 899 for i in range(numGlyphs - 1): 900 SID = readCard16(file) 901 charset.append(strings[SID]) 902 return charset 903 904def parseCharset(numGlyphs, file, strings, isCID, format): 905 charset = ['.notdef'] 906 count = 1 907 if format == 1: 908 nLeftFunc = readCard8 909 else: 910 nLeftFunc = readCard16 911 while count < numGlyphs: 912 first = readCard16(file) 913 nLeft = nLeftFunc(file) 914 if isCID: 915 for CID in range(first, first+nLeft+1): 916 charset.append("cid" + string.zfill(str(CID), 5) ) 917 else: 918 for SID in range(first, first+nLeft+1): 919 charset.append(strings[SID]) 920 count = count + nLeft + 1 921 return charset 922 923 924class EncodingCompiler: 925 926 def __init__(self, strings, encoding, parent): 927 assert not isinstance(encoding, basestring) 928 data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings) 929 data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings) 930 if len(data0) < len(data1): 931 self.data = data0 932 else: 933 self.data = data1 934 self.parent = parent 935 936 def setPos(self, pos, endPos): 937 self.parent.rawDict["Encoding"] = pos 938 939 def getDataLength(self): 940 return len(self.data) 941 942 def toFile(self, file): 943 file.write(self.data) 944 945 946class EncodingConverter(SimpleConverter): 947 948 def read(self, parent, value): 949 if value == 0: 950 return "StandardEncoding" 951 elif value == 1: 952 return "ExpertEncoding" 953 else: 954 assert value > 1 955 file = parent.file 956 file.seek(value) 957 if DEBUG: 958 print "loading Encoding at %s" % value 959 format = readCard8(file) 960 haveSupplement = format & 0x80 961 if haveSupplement: 962 raise NotImplementedError, "Encoding supplements are not yet supported" 963 format = format & 0x7f 964 if format == 0: 965 encoding = parseEncoding0(parent.charset, file, haveSupplement, 966 parent.strings) 967 elif format == 1: 968 encoding = parseEncoding1(parent.charset, file, haveSupplement, 969 parent.strings) 970 return encoding 971 972 def write(self, parent, value): 973 if value == "StandardEncoding": 974 return 0 975 elif value == "ExpertEncoding": 976 return 1 977 return 0 # dummy value 978 979 def xmlWrite(self, xmlWriter, name, value, progress): 980 if value in ("StandardEncoding", "ExpertEncoding"): 981 xmlWriter.simpletag(name, name=value) 982 xmlWriter.newline() 983 return 984 xmlWriter.begintag(name) 985 xmlWriter.newline() 986 for code in range(len(value)): 987 glyphName = value[code] 988 if glyphName != ".notdef": 989 xmlWriter.simpletag("map", code=hex(code), name=glyphName) 990 xmlWriter.newline() 991 xmlWriter.endtag(name) 992 xmlWriter.newline() 993 994 def xmlRead(self, (name, attrs, content), parent): 995 if attrs.has_key("name"): 996 return attrs["name"] 997 encoding = [".notdef"] * 256 998 for element in content: 999 if isinstance(element, basestring): 1000 continue 1001 name, attrs, content = element 1002 code = safeEval(attrs["code"]) 1003 glyphName = attrs["name"] 1004 encoding[code] = glyphName 1005 return encoding 1006 1007 1008def parseEncoding0(charset, file, haveSupplement, strings): 1009 nCodes = readCard8(file) 1010 encoding = [".notdef"] * 256 1011 for glyphID in range(1, nCodes + 1): 1012 code = readCard8(file) 1013 if code != 0: 1014 encoding[code] = charset[glyphID] 1015 return encoding 1016 1017def parseEncoding1(charset, file, haveSupplement, strings): 1018 nRanges = readCard8(file) 1019 encoding = [".notdef"] * 256 1020 glyphID = 1 1021 for i in range(nRanges): 1022 code = readCard8(file) 1023 nLeft = readCard8(file) 1024 for glyphID in range(glyphID, glyphID + nLeft + 1): 1025 encoding[code] = charset[glyphID] 1026 code = code + 1 1027 glyphID = glyphID + 1 1028 return encoding 1029 1030def packEncoding0(charset, encoding, strings): 1031 format = 0 1032 m = {} 1033 for code in range(len(encoding)): 1034 name = encoding[code] 1035 if name != ".notdef": 1036 m[name] = code 1037 codes = [] 1038 for name in charset[1:]: 1039 code = m.get(name) 1040 codes.append(code) 1041 1042 while codes and codes[-1] is None: 1043 codes.pop() 1044 1045 data = [packCard8(format), packCard8(len(codes))] 1046 for code in codes: 1047 if code is None: 1048 code = 0 1049 data.append(packCard8(code)) 1050 return "".join(data) 1051 1052def packEncoding1(charset, encoding, strings): 1053 format = 1 1054 m = {} 1055 for code in range(len(encoding)): 1056 name = encoding[code] 1057 if name != ".notdef": 1058 m[name] = code 1059 ranges = [] 1060 first = None 1061 end = 0 1062 for name in charset[1:]: 1063 code = m.get(name, -1) 1064 if first is None: 1065 first = code 1066 elif end + 1 <> code: 1067 nLeft = end - first 1068 ranges.append((first, nLeft)) 1069 first = code 1070 end = code 1071 nLeft = end - first 1072 ranges.append((first, nLeft)) 1073 1074 # remove unencoded glyphs at the end. 1075 while ranges and ranges[-1][0] == -1: 1076 ranges.pop() 1077 1078 data = [packCard8(format), packCard8(len(ranges))] 1079 for first, nLeft in ranges: 1080 if first == -1: # unencoded 1081 first = 0 1082 data.append(packCard8(first) + packCard8(nLeft)) 1083 return "".join(data) 1084 1085 1086class FDArrayConverter(TableConverter): 1087 1088 def read(self, parent, value): 1089 file = parent.file 1090 file.seek(value) 1091 fdArray = FDArrayIndex(file) 1092 fdArray.strings = parent.strings 1093 fdArray.GlobalSubrs = parent.GlobalSubrs 1094 return fdArray 1095 1096 def write(self, parent, value): 1097 return 0 # dummy value 1098 1099 def xmlRead(self, (name, attrs, content), parent): 1100 fdArray = FDArrayIndex() 1101 for element in content: 1102 if isinstance(element, basestring): 1103 continue 1104 fdArray.fromXML(element) 1105 return fdArray 1106 1107 1108class FDSelectConverter: 1109 1110 def read(self, parent, value): 1111 file = parent.file 1112 file.seek(value) 1113 fdSelect = FDSelect(file, parent.numGlyphs) 1114 return fdSelect 1115 1116 def write(self, parent, value): 1117 return 0 # dummy value 1118 1119 # The FDSelect glyph data is written out to XML in the charstring keys, 1120 # so we write out only the format selector 1121 def xmlWrite(self, xmlWriter, name, value, progress): 1122 xmlWriter.simpletag(name, [('format', value.format)]) 1123 xmlWriter.newline() 1124 1125 def xmlRead(self, (name, attrs, content), parent): 1126 format = safeEval(attrs["format"]) 1127 file = None 1128 numGlyphs = None 1129 fdSelect = FDSelect(file, numGlyphs, format) 1130 return fdSelect 1131 1132 1133def packFDSelect0(fdSelectArray): 1134 format = 0 1135 data = [packCard8(format)] 1136 for index in fdSelectArray: 1137 data.append(packCard8(index)) 1138 return "".join(data) 1139 1140 1141def packFDSelect3(fdSelectArray): 1142 format = 3 1143 fdRanges = [] 1144 first = None 1145 end = 0 1146 lenArray = len(fdSelectArray) 1147 lastFDIndex = -1 1148 for i in range(lenArray): 1149 fdIndex = fdSelectArray[i] 1150 if lastFDIndex != fdIndex: 1151 fdRanges.append([i, fdIndex]) 1152 lastFDIndex = fdIndex 1153 sentinelGID = i + 1 1154 1155 data = [packCard8(format)] 1156 data.append(packCard16( len(fdRanges) )) 1157 for fdRange in fdRanges: 1158 data.append(packCard16(fdRange[0])) 1159 data.append(packCard8(fdRange[1])) 1160 data.append(packCard16(sentinelGID)) 1161 return "".join(data) 1162 1163 1164class FDSelectCompiler: 1165 1166 def __init__(self, fdSelect, parent): 1167 format = fdSelect.format 1168 fdSelectArray = fdSelect.gidArray 1169 if format == 0: 1170 self.data = packFDSelect0(fdSelectArray) 1171 elif format == 3: 1172 self.data = packFDSelect3(fdSelectArray) 1173 else: 1174 # choose smaller of the two formats 1175 data0 = packFDSelect0(fdSelectArray) 1176 data3 = packFDSelect3(fdSelectArray) 1177 if len(data0) < len(data3): 1178 self.data = data0 1179 fdSelect.format = 0 1180 else: 1181 self.data = data3 1182 fdSelect.format = 3 1183 1184 self.parent = parent 1185 1186 def setPos(self, pos, endPos): 1187 self.parent.rawDict["FDSelect"] = pos 1188 1189 def getDataLength(self): 1190 return len(self.data) 1191 1192 def toFile(self, file): 1193 file.write(self.data) 1194 1195 1196class ROSConverter(SimpleConverter): 1197 1198 def xmlWrite(self, xmlWriter, name, value, progress): 1199 registry, order, supplement = value 1200 xmlWriter.simpletag(name, [('Registry', registry), ('Order', order), 1201 ('Supplement', supplement)]) 1202 xmlWriter.newline() 1203 1204 def xmlRead(self, (name, attrs, content), parent): 1205 return (attrs['Registry'], attrs['Order'], safeEval(attrs['Supplement'])) 1206 1207 1208 1209topDictOperators = [ 1210# opcode name argument type default converter 1211 ((12, 30), 'ROS', ('SID','SID','number'), None, ROSConverter()), 1212 ((12, 20), 'SyntheticBase', 'number', None, None), 1213 (0, 'version', 'SID', None, None), 1214 (1, 'Notice', 'SID', None, Latin1Converter()), 1215 ((12, 0), 'Copyright', 'SID', None, Latin1Converter()), 1216 (2, 'FullName', 'SID', None, None), 1217 ((12, 38), 'FontName', 'SID', None, None), 1218 (3, 'FamilyName', 'SID', None, None), 1219 (4, 'Weight', 'SID', None, None), 1220 ((12, 1), 'isFixedPitch', 'number', 0, None), 1221 ((12, 2), 'ItalicAngle', 'number', 0, None), 1222 ((12, 3), 'UnderlinePosition', 'number', None, None), 1223 ((12, 4), 'UnderlineThickness', 'number', 50, None), 1224 ((12, 5), 'PaintType', 'number', 0, None), 1225 ((12, 6), 'CharstringType', 'number', 2, None), 1226 ((12, 7), 'FontMatrix', 'array', [0.001,0,0,0.001,0,0], None), 1227 (13, 'UniqueID', 'number', None, None), 1228 (5, 'FontBBox', 'array', [0,0,0,0], None), 1229 ((12, 8), 'StrokeWidth', 'number', 0, None), 1230 (14, 'XUID', 'array', None, None), 1231 ((12, 21), 'PostScript', 'SID', None, None), 1232 ((12, 22), 'BaseFontName', 'SID', None, None), 1233 ((12, 23), 'BaseFontBlend', 'delta', None, None), 1234 ((12, 31), 'CIDFontVersion', 'number', 0, None), 1235 ((12, 32), 'CIDFontRevision', 'number', 0, None), 1236 ((12, 33), 'CIDFontType', 'number', 0, None), 1237 ((12, 34), 'CIDCount', 'number', 8720, None), 1238 (15, 'charset', 'number', 0, CharsetConverter()), 1239 ((12, 35), 'UIDBase', 'number', None, None), 1240 (16, 'Encoding', 'number', 0, EncodingConverter()), 1241 (18, 'Private', ('number','number'), None, PrivateDictConverter()), 1242 ((12, 37), 'FDSelect', 'number', None, FDSelectConverter()), 1243 ((12, 36), 'FDArray', 'number', None, FDArrayConverter()), 1244 (17, 'CharStrings', 'number', None, CharStringsConverter()), 1245] 1246 1247# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order, 1248# in order for the font to compile back from xml. 1249 1250 1251privateDictOperators = [ 1252# opcode name argument type default converter 1253 (6, 'BlueValues', 'delta', None, None), 1254 (7, 'OtherBlues', 'delta', None, None), 1255 (8, 'FamilyBlues', 'delta', None, None), 1256 (9, 'FamilyOtherBlues', 'delta', None, None), 1257 ((12, 9), 'BlueScale', 'number', 0.039625, None), 1258 ((12, 10), 'BlueShift', 'number', 7, None), 1259 ((12, 11), 'BlueFuzz', 'number', 1, None), 1260 (10, 'StdHW', 'number', None, None), 1261 (11, 'StdVW', 'number', None, None), 1262 ((12, 12), 'StemSnapH', 'delta', None, None), 1263 ((12, 13), 'StemSnapV', 'delta', None, None), 1264 ((12, 14), 'ForceBold', 'number', 0, None), 1265 ((12, 15), 'ForceBoldThreshold', 'number', None, None), # deprecated 1266 ((12, 16), 'lenIV', 'number', None, None), # deprecated 1267 ((12, 17), 'LanguageGroup', 'number', 0, None), 1268 ((12, 18), 'ExpansionFactor', 'number', 0.06, None), 1269 ((12, 19), 'initialRandomSeed', 'number', 0, None), 1270 (20, 'defaultWidthX', 'number', 0, None), 1271 (21, 'nominalWidthX', 'number', 0, None), 1272 (19, 'Subrs', 'number', None, SubrsConverter()), 1273] 1274 1275def addConverters(table): 1276 for i in range(len(table)): 1277 op, name, arg, default, conv = table[i] 1278 if conv is not None: 1279 continue 1280 if arg in ("delta", "array"): 1281 conv = ArrayConverter() 1282 elif arg == "number": 1283 conv = NumberConverter() 1284 elif arg == "SID": 1285 conv = SimpleConverter() 1286 else: 1287 assert 0 1288 table[i] = op, name, arg, default, conv 1289 1290addConverters(privateDictOperators) 1291addConverters(topDictOperators) 1292 1293 1294class TopDictDecompiler(psCharStrings.DictDecompiler): 1295 operators = buildOperatorDict(topDictOperators) 1296 1297 1298class PrivateDictDecompiler(psCharStrings.DictDecompiler): 1299 operators = buildOperatorDict(privateDictOperators) 1300 1301 1302class DictCompiler: 1303 1304 def __init__(self, dictObj, strings, parent): 1305 assert isinstance(strings, IndexedStrings) 1306 self.dictObj = dictObj 1307 self.strings = strings 1308 self.parent = parent 1309 rawDict = {} 1310 for name in dictObj.order: 1311 value = getattr(dictObj, name, None) 1312 if value is None: 1313 continue 1314 conv = dictObj.converters[name] 1315 value = conv.write(dictObj, value) 1316 if value == dictObj.defaults.get(name): 1317 continue 1318 rawDict[name] = value 1319 self.rawDict = rawDict 1320 1321 def setPos(self, pos, endPos): 1322 pass 1323 1324 def getDataLength(self): 1325 return len(self.compile("getDataLength")) 1326 1327 def compile(self, reason): 1328 if DEBUG: 1329 print "-- compiling %s for %s" % (self.__class__.__name__, reason) 1330 print "in baseDict: ", self 1331 rawDict = self.rawDict 1332 data = [] 1333 for name in self.dictObj.order: 1334 value = rawDict.get(name) 1335 if value is None: 1336 continue 1337 op, argType = self.opcodes[name] 1338 if isinstance(argType, tuple): 1339 l = len(argType) 1340 assert len(value) == l, "value doesn't match arg type" 1341 for i in range(l): 1342 arg = argType[i] 1343 v = value[i] 1344 arghandler = getattr(self, "arg_" + arg) 1345 data.append(arghandler(v)) 1346 else: 1347 arghandler = getattr(self, "arg_" + argType) 1348 data.append(arghandler(value)) 1349 data.append(op) 1350 return "".join(data) 1351 1352 def toFile(self, file): 1353 file.write(self.compile("toFile")) 1354 1355 def arg_number(self, num): 1356 return encodeNumber(num) 1357 def arg_SID(self, s): 1358 return psCharStrings.encodeIntCFF(self.strings.getSID(s)) 1359 def arg_array(self, value): 1360 data = [] 1361 for num in value: 1362 data.append(encodeNumber(num)) 1363 return "".join(data) 1364 def arg_delta(self, value): 1365 out = [] 1366 last = 0 1367 for v in value: 1368 out.append(v - last) 1369 last = v 1370 data = [] 1371 for num in out: 1372 data.append(encodeNumber(num)) 1373 return "".join(data) 1374 1375 1376def encodeNumber(num): 1377 if isinstance(num, float): 1378 return psCharStrings.encodeFloat(num) 1379 else: 1380 return psCharStrings.encodeIntCFF(num) 1381 1382 1383class TopDictCompiler(DictCompiler): 1384 1385 opcodes = buildOpcodeDict(topDictOperators) 1386 1387 def getChildren(self, strings): 1388 children = [] 1389 if hasattr(self.dictObj, "charset") and self.dictObj.charset: 1390 children.append(CharsetCompiler(strings, self.dictObj.charset, self)) 1391 if hasattr(self.dictObj, "Encoding"): 1392 encoding = self.dictObj.Encoding 1393 if not isinstance(encoding, basestring): 1394 children.append(EncodingCompiler(strings, encoding, self)) 1395 if hasattr(self.dictObj, "FDSelect"): 1396 # I have not yet supported merging a ttx CFF-CID font, as there are interesting 1397 # issues about merging the FDArrays. Here I assume that 1398 # either the font was read from XML, and teh FDSelect indices are all 1399 # in the charstring data, or the FDSelect array is already fully defined. 1400 fdSelect = self.dictObj.FDSelect 1401 if len(fdSelect) == 0: # probably read in from XML; assume fdIndex in CharString data 1402 charStrings = self.dictObj.CharStrings 1403 for name in self.dictObj.charset: 1404 charstring = charStrings[name] 1405 fdSelect.append(charStrings[name].fdSelectIndex) 1406 fdSelectComp = FDSelectCompiler(fdSelect, self) 1407 children.append(fdSelectComp) 1408 if hasattr(self.dictObj, "CharStrings"): 1409 items = [] 1410 charStrings = self.dictObj.CharStrings 1411 for name in self.dictObj.charset: 1412 items.append(charStrings[name]) 1413 charStringsComp = CharStringsCompiler(items, strings, self) 1414 children.append(charStringsComp) 1415 if hasattr(self.dictObj, "FDArray"): 1416 # I have not yet supported merging a ttx CFF-CID font, as there are interesting 1417 # issues about merging the FDArrays. Here I assume that the FDArray info is correct 1418 # and complete. 1419 fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self) 1420 children.append(fdArrayIndexComp) 1421 children.extend(fdArrayIndexComp.getChildren(strings)) 1422 if hasattr(self.dictObj, "Private"): 1423 privComp = self.dictObj.Private.getCompiler(strings, self) 1424 children.append(privComp) 1425 children.extend(privComp.getChildren(strings)) 1426 return children 1427 1428 1429class FontDictCompiler(DictCompiler): 1430 1431 opcodes = buildOpcodeDict(topDictOperators) 1432 1433 def getChildren(self, strings): 1434 children = [] 1435 if hasattr(self.dictObj, "Private"): 1436 privComp = self.dictObj.Private.getCompiler(strings, self) 1437 children.append(privComp) 1438 children.extend(privComp.getChildren(strings)) 1439 return children 1440 1441 1442class PrivateDictCompiler(DictCompiler): 1443 1444 opcodes = buildOpcodeDict(privateDictOperators) 1445 1446 def setPos(self, pos, endPos): 1447 size = endPos - pos 1448 self.parent.rawDict["Private"] = size, pos 1449 self.pos = pos 1450 1451 def getChildren(self, strings): 1452 children = [] 1453 if hasattr(self.dictObj, "Subrs"): 1454 children.append(self.dictObj.Subrs.getCompiler(strings, self)) 1455 return children 1456 1457 1458class BaseDict: 1459 1460 def __init__(self, strings=None, file=None, offset=None): 1461 self.rawDict = {} 1462 if DEBUG: 1463 print "loading %s at %s" % (self.__class__.__name__, offset) 1464 self.file = file 1465 self.offset = offset 1466 self.strings = strings 1467 self.skipNames = [] 1468 1469 def decompile(self, data): 1470 if DEBUG: 1471 print " length %s is %s" % (self.__class__.__name__, len(data)) 1472 dec = self.decompilerClass(self.strings) 1473 dec.decompile(data) 1474 self.rawDict = dec.getDict() 1475 self.postDecompile() 1476 1477 def postDecompile(self): 1478 pass 1479 1480 def getCompiler(self, strings, parent): 1481 return self.compilerClass(self, strings, parent) 1482 1483 def __getattr__(self, name): 1484 value = self.rawDict.get(name) 1485 if value is None: 1486 value = self.defaults.get(name) 1487 if value is None: 1488 raise AttributeError, name 1489 conv = self.converters[name] 1490 value = conv.read(self, value) 1491 setattr(self, name, value) 1492 return value 1493 1494 def toXML(self, xmlWriter, progress): 1495 for name in self.order: 1496 if name in self.skipNames: 1497 continue 1498 value = getattr(self, name, None) 1499 if value is None: 1500 continue 1501 conv = self.converters[name] 1502 conv.xmlWrite(xmlWriter, name, value, progress) 1503 1504 def fromXML(self, (name, attrs, content)): 1505 conv = self.converters[name] 1506 value = conv.xmlRead((name, attrs, content), self) 1507 setattr(self, name, value) 1508 1509 1510class TopDict(BaseDict): 1511 1512 defaults = buildDefaults(topDictOperators) 1513 converters = buildConverters(topDictOperators) 1514 order = buildOrder(topDictOperators) 1515 decompilerClass = TopDictDecompiler 1516 compilerClass = TopDictCompiler 1517 1518 def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None): 1519 BaseDict.__init__(self, strings, file, offset) 1520 self.GlobalSubrs = GlobalSubrs 1521 1522 def getGlyphOrder(self): 1523 return self.charset 1524 1525 def postDecompile(self): 1526 offset = self.rawDict.get("CharStrings") 1527 if offset is None: 1528 return 1529 # get the number of glyphs beforehand. 1530 self.file.seek(offset) 1531 self.numGlyphs = readCard16(self.file) 1532 1533 def toXML(self, xmlWriter, progress): 1534 if hasattr(self, "CharStrings"): 1535 self.decompileAllCharStrings(progress) 1536 if hasattr(self, "ROS"): 1537 self.skipNames = ['Encoding'] 1538 if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"): 1539 # these values have default values, but I only want them to show up 1540 # in CID fonts. 1541 self.skipNames = ['CIDFontVersion', 'CIDFontRevision', 'CIDFontType', 1542 'CIDCount'] 1543 BaseDict.toXML(self, xmlWriter, progress) 1544 1545 def decompileAllCharStrings(self, progress): 1546 # XXX only when doing ttdump -i? 1547 i = 0 1548 for charString in self.CharStrings.values(): 1549 try: 1550 charString.decompile() 1551 except: 1552 print "Error in charstring ", i 1553 import sys 1554 type, value = sys. exc_info()[0:2] 1555 raise type(value) 1556 if not i % 30 and progress: 1557 progress.increment(0) # update 1558 i = i + 1 1559 1560 1561class FontDict(BaseDict): 1562 1563 defaults = buildDefaults(topDictOperators) 1564 converters = buildConverters(topDictOperators) 1565 order = buildOrder(topDictOperators) 1566 decompilerClass = None 1567 compilerClass = FontDictCompiler 1568 1569 def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None): 1570 BaseDict.__init__(self, strings, file, offset) 1571 self.GlobalSubrs = GlobalSubrs 1572 1573 def getGlyphOrder(self): 1574 return self.charset 1575 1576 def toXML(self, xmlWriter, progress): 1577 self.skipNames = ['Encoding'] 1578 BaseDict.toXML(self, xmlWriter, progress) 1579 1580 1581 1582class PrivateDict(BaseDict): 1583 defaults = buildDefaults(privateDictOperators) 1584 converters = buildConverters(privateDictOperators) 1585 order = buildOrder(privateDictOperators) 1586 decompilerClass = PrivateDictDecompiler 1587 compilerClass = PrivateDictCompiler 1588 1589 1590class IndexedStrings: 1591 1592 """SID -> string mapping.""" 1593 1594 def __init__(self, file=None): 1595 if file is None: 1596 strings = [] 1597 else: 1598 strings = list(Index(file)) 1599 self.strings = strings 1600 1601 def getCompiler(self): 1602 return IndexedStringsCompiler(self, None, None) 1603 1604 def __len__(self): 1605 return len(self.strings) 1606 1607 def __getitem__(self, SID): 1608 if SID < cffStandardStringCount: 1609 return cffStandardStrings[SID] 1610 else: 1611 return self.strings[SID - cffStandardStringCount] 1612 1613 def getSID(self, s): 1614 if not hasattr(self, "stringMapping"): 1615 self.buildStringMapping() 1616 if cffStandardStringMapping.has_key(s): 1617 SID = cffStandardStringMapping[s] 1618 elif self.stringMapping.has_key(s): 1619 SID = self.stringMapping[s] 1620 else: 1621 SID = len(self.strings) + cffStandardStringCount 1622 self.strings.append(s) 1623 self.stringMapping[s] = SID 1624 return SID 1625 1626 def getStrings(self): 1627 return self.strings 1628 1629 def buildStringMapping(self): 1630 self.stringMapping = {} 1631 for index in range(len(self.strings)): 1632 self.stringMapping[self.strings[index]] = index + cffStandardStringCount 1633 1634 1635# The 391 Standard Strings as used in the CFF format. 1636# from Adobe Technical None #5176, version 1.0, 18 March 1998 1637 1638cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 1639 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 1640 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 1641 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 1642 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 1643 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 1644 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 1645 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 1646 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 1647 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 1648 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 1649 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 1650 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 1651 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 1652 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 1653 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 1654 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 1655 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 1656 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 1657 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 1658 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 1659 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 1660 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 1661 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 1662 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 1663 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 1664 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 1665 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 1666 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 1667 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis', 1668 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 1669 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 1670 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 1671 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 1672 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 1673 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 1674 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', 1675 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 1676 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 1677 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior', 1678 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 1679 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 1680 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 1681 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 1682 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 1683 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 1684 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall', 1685 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 1686 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 1687 'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior', 1688 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 1689 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 1690 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 1691 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 1692 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 1693 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 1694 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 1695 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 1696 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 1697 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 1698 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002', 1699 '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 1700 'Semibold' 1701] 1702 1703cffStandardStringCount = 391 1704assert len(cffStandardStrings) == cffStandardStringCount 1705# build reverse mapping 1706cffStandardStringMapping = {} 1707for _i in range(cffStandardStringCount): 1708 cffStandardStringMapping[cffStandardStrings[_i]] = _i 1709 1710cffISOAdobeStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign", 1711"dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", 1712"asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", 1713"three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", 1714"less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", 1715"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", 1716"X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", 1717"underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", 1718"k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", 1719"braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", 1720"sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", 1721"quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", 1722"endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet", 1723"quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", 1724"perthousand", "questiondown", "grave", "acute", "circumflex", "tilde", 1725"macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut", 1726"ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE", 1727"ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls", 1728"onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", 1729"Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn", 1730"threequarters", "twosuperior", "registered", "minus", "eth", "multiply", 1731"threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", 1732"Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", 1733"Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", 1734"Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", 1735"Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", 1736"acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute", 1737"ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis", 1738"igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde", 1739"scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis", 1740"zcaron"] 1741 1742cffISOAdobeStringCount = 229 1743assert len(cffISOAdobeStrings) == cffISOAdobeStringCount 1744 1745cffIExpertStrings = [".notdef", "space", "exclamsmall", "Hungarumlautsmall", 1746"dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", 1747"parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", 1748"comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", 1749"twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", 1750"sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", 1751"commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", 1752"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", 1753"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", 1754"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", 1755"parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", 1756"Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", 1757"Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", 1758"Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", 1759"Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", 1760"exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", 1761"Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", 1762"figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall", 1763"onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth", 1764"threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", 1765"zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior", 1766"fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", 1767"zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", 1768"fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", 1769"centinferior", "dollarinferior", "periodinferior", "commainferior", 1770"Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", 1771"Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", 1772"Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", 1773"Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", 1774"Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", 1775"Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", 1776"Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", 1777"Ydieresissmall"] 1778 1779cffExpertStringCount = 166 1780assert len(cffIExpertStrings) == cffExpertStringCount 1781 1782cffExpertSubsetStrings = [".notdef", "space", "dollaroldstyle", 1783"dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader", 1784"onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", 1785"oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", 1786"sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", 1787"semicolon", "commasuperior", "threequartersemdash", "periodsuperior", 1788"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", 1789"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", 1790"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", 1791"parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah", 1792"centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf", 1793"threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", 1794"onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior", 1795"threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", 1796"eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", 1797"threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", 1798"eightinferior", "nineinferior", "centinferior", "dollarinferior", 1799"periodinferior", "commainferior"] 1800 1801cffExpertSubsetStringCount = 87 1802assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount 1803