blob: 6ff78a74f803a4580c953152d71230f7ae56d40b [file] [log] [blame]
Just7842e561999-12-16 21:34:53 +00001"""cffLib.py -- read/write tools for Adobe CFF fonts."""
2
Justec46d161999-12-20 22:02:10 +00003#
jvrf2cf9c52002-05-23 21:50:36 +00004# $Id: cffLib.py,v 1.21 2002-05-23 21:50:36 jvr Exp $
Justec46d161999-12-20 22:02:10 +00005#
Just7842e561999-12-16 21:34:53 +00006
7import struct, sstruct
8import string
jvrf2cf9c52002-05-23 21:50:36 +00009from types import FloatType, ListType, TupleType
Just528614e2000-01-16 22:14:02 +000010from fontTools.misc import psCharStrings
Just7842e561999-12-16 21:34:53 +000011
12
jvr767102e2002-05-17 07:06:32 +000013DEBUG = 0
14
15
Just7842e561999-12-16 21:34:53 +000016cffHeaderFormat = """
17 major: B
18 minor: B
19 hdrSize: B
20 offSize: B
21"""
22
23class CFFFontSet:
24
25 def __init__(self):
jvr4756b3a2002-05-16 18:17:32 +000026 pass
Just7842e561999-12-16 21:34:53 +000027
jvra2a75b32002-05-13 11:25:17 +000028 def decompile(self, file):
29 sstruct.unpack(cffHeaderFormat, file.read(4), self)
Just7842e561999-12-16 21:34:53 +000030 assert self.major == 1 and self.minor == 0, \
31 "unknown CFF format: %d.%d" % (self.major, self.minor)
Just7842e561999-12-16 21:34:53 +000032
jvrf2cf9c52002-05-23 21:50:36 +000033 file.seek(self.hdrSize)
jvr767102e2002-05-17 07:06:32 +000034 self.fontNames = list(Index(file, "fontNames"))
jvr4756b3a2002-05-16 18:17:32 +000035 self.topDictIndex = TopDictIndex(file)
jvr767102e2002-05-17 07:06:32 +000036 self.strings = IndexedStrings(file)
jvrf2cf9c52002-05-23 21:50:36 +000037 self.GlobalSubrs = GlobalSubrsIndex(file, name="GlobalSubrsIndex")
jvr4756b3a2002-05-16 18:17:32 +000038 self.topDictIndex.strings = self.strings
jvr016ca762002-05-16 18:38:03 +000039 self.topDictIndex.GlobalSubrs = self.GlobalSubrs
jvr4756b3a2002-05-16 18:17:32 +000040
41 def __len__(self):
42 return len(self.fontNames)
43
44 def keys(self):
45 return self.fontNames[:]
46
jvr767102e2002-05-17 07:06:32 +000047 def values(self):
48 return self.topDictIndex
49
jvr4756b3a2002-05-16 18:17:32 +000050 def __getitem__(self, name):
51 try:
52 index = self.fontNames.index(name)
53 except ValueError:
54 raise KeyError, name
jvr016ca762002-05-16 18:38:03 +000055 return self.topDictIndex[index]
Just7842e561999-12-16 21:34:53 +000056
jvrf2cf9c52002-05-23 21:50:36 +000057 def compile(self, file):
Just7842e561999-12-16 21:34:53 +000058 strings = IndexedStrings()
jvrf2cf9c52002-05-23 21:50:36 +000059 writer = CFFWriter()
60 writer.add(sstruct.pack(cffHeaderFormat, self))
61 fontNames = Index()
62 for name in self.fontNames:
63 fontNames.append(name)
64 writer.add(fontNames.getCompiler(strings, None))
65 topCompiler = self.topDictIndex.getCompiler(strings, None)
66 writer.add(topCompiler)
67 writer.add(strings.getCompiler())
68 writer.add(self.GlobalSubrs.getCompiler(strings, None))
69
70 for child in topCompiler.getChildren(strings):
71 writer.add(child)
72
73 print writer.data
74
75 writer.toFile(file)
Just7842e561999-12-16 21:34:53 +000076
77 def toXML(self, xmlWriter, progress=None):
78 xmlWriter.newline()
79 for fontName in self.fontNames:
80 xmlWriter.begintag("CFFFont", name=fontName)
81 xmlWriter.newline()
jvr4756b3a2002-05-16 18:17:32 +000082 font = self[fontName]
Just7842e561999-12-16 21:34:53 +000083 font.toXML(xmlWriter, progress)
84 xmlWriter.endtag("CFFFont")
85 xmlWriter.newline()
86 xmlWriter.newline()
87 xmlWriter.begintag("GlobalSubrs")
88 xmlWriter.newline()
jvr4756b3a2002-05-16 18:17:32 +000089 self.GlobalSubrs.toXML(xmlWriter, progress)
Just7842e561999-12-16 21:34:53 +000090 xmlWriter.endtag("GlobalSubrs")
91 xmlWriter.newline()
92 xmlWriter.newline()
93
94 def fromXML(self, (name, attrs, content)):
95 xxx
96
97
jvrf2cf9c52002-05-23 21:50:36 +000098class CFFWriter:
99
100 def __init__(self):
101 self.data = []
102
103 def add(self, table):
104 self.data.append(table)
105
106 def toFile(self, file):
107 lastPosList = None
108 count = 1
109 while 1:
110 print "XXX iteration", count
111 count += 1
112 pos = 0
113 posList = [pos]
114 for item in self.data:
115 if hasattr(item, "setPos"):
116 item.setPos(pos)
117 if hasattr(item, "getDataLength"):
118 pos = pos + item.getDataLength()
119 else:
120 pos = pos + len(item)
121 posList.append(pos)
122 if posList == lastPosList:
123 break
124 lastPosList = posList
125 begin = file.tell()
126 posList = [0]
127 for item in self.data:
128 if hasattr(item, "toFile"):
129 item.toFile(file)
130 else:
131 file.write(item)
132 posList.append(file.tell() - begin)
133 if posList != lastPosList:
134 print "++++"
135 print posList
136 print lastPosList
137 assert posList == lastPosList
138
139
140def calcOffSize(largestOffset):
141 if largestOffset < 0x100:
142 offSize = 1
143 elif largestOffset < 0x10000:
144 offSize = 2
145 elif largestOffset < 0x1000000:
146 offSize = 3
147 else:
148 offSize = 4
149 return offSize
150
151
152class IndexCompiler:
153
154 def __init__(self, items, strings, parent):
155 self.items = self.getItems(items, strings)
156 self.parent = parent
157
158 def getItems(self, items, strings):
159 return items
160
161 def getOffsets(self):
162 pos = 1
163 offsets = [pos]
164 for item in self.items:
165 if hasattr(item, "getDataLength"):
166 pos = pos + item.getDataLength()
167 else:
168 pos = pos + len(item)
169 offsets.append(pos)
170 return offsets
171
172 def getDataLength(self):
173 lastOffset = self.getOffsets()[-1]
174 offSize = calcOffSize(lastOffset)
175 dataLength = (
176 2 + # count
177 1 + # offSize
178 (len(self.items) + 1) * offSize + # the offsets
179 lastOffset - 1 # size of object data
180 )
181 return dataLength
182
183 def toFile(self, file):
184 size = self.getDataLength()
185 start = file.tell()
186 offsets = self.getOffsets()
187 writeCard16(file, len(self.items))
188 offSize = calcOffSize(offsets[-1])
189 writeCard8(file, offSize)
190 offSize = -offSize
191 pack = struct.pack
192 for offset in offsets:
193 binOffset = pack(">l", offset)[offSize:]
194 assert len(binOffset) == -offSize
195 file.write(binOffset)
196 for item in self.items:
197 if hasattr(item, "toFile"):
198 item.toFile(file)
199 else:
200 file.write(item)
201 assert start + size == file.tell()
202
203
204class IndexedStringsCompiler(IndexCompiler):
205
206 def getItems(self, items, strings):
207 return items.strings
208
209
210class TopDictIndexCompiler(IndexCompiler):
211
212 def getItems(self, items, strings):
213 out = []
214 for item in items:
215 out.append(item.getCompiler(strings, self))
216 return out
217
218 def getChildren(self, strings):
219 children = []
220 for topDict in self.items:
221 children.extend(topDict.getChildren(strings))
222 return children
223
224
225class GlobalSubrsCompiler(IndexCompiler):
226 def getItems(self, items, strings):
227 out = []
228 for cs in items:
229 cs.compile()
230 out.append(cs.bytecode)
231 return out
232
233class SubrsCompiler(GlobalSubrsCompiler):
234 def setPos(self, pos):
235 offset = pos - self.parent.pos
236 self.parent.rawDict["Subrs"] = offset
237
238class CharStringsCompiler(GlobalSubrsCompiler):
239 def setPos(self, pos):
240 self.parent.rawDict["CharStrings"] = pos
241
242
jvr4756b3a2002-05-16 18:17:32 +0000243class Index:
Just7842e561999-12-16 21:34:53 +0000244
jvr4756b3a2002-05-16 18:17:32 +0000245 """This class represents what the CFF spec calls an INDEX."""
Just7842e561999-12-16 21:34:53 +0000246
jvrf2cf9c52002-05-23 21:50:36 +0000247 compilerClass = IndexCompiler
248
249 def __init__(self, file=None, name=None):
jvr767102e2002-05-17 07:06:32 +0000250 if name is None:
251 name = self.__class__.__name__
jvrf2cf9c52002-05-23 21:50:36 +0000252 if file is None:
253 self.items = []
254 return
jvr767102e2002-05-17 07:06:32 +0000255 if DEBUG:
256 print "loading %s at %s" % (name, file.tell())
jvr4756b3a2002-05-16 18:17:32 +0000257 self.file = file
jvra2ad5442002-05-17 18:36:07 +0000258 count = readCard16(file)
jvr4756b3a2002-05-16 18:17:32 +0000259 self.count = count
260 self.items = [None] * count
261 if count == 0:
jvrf2cf9c52002-05-23 21:50:36 +0000262 self.items = []
jvr4756b3a2002-05-16 18:17:32 +0000263 return
jvra2ad5442002-05-17 18:36:07 +0000264 offSize = readCard8(file)
jvr767102e2002-05-17 07:06:32 +0000265 if DEBUG:
jvrf2cf9c52002-05-23 21:50:36 +0000266 print " index count: %s offSize: %s" % (count, offSize)
jvr767102e2002-05-17 07:06:32 +0000267 assert offSize <= 4, "offSize too large: %s" % offSize
jvr4756b3a2002-05-16 18:17:32 +0000268 self.offsets = offsets = []
269 pad = '\0' * (4 - offSize)
270 for index in range(count+1):
271 chunk = file.read(offSize)
272 chunk = pad + chunk
273 offset, = struct.unpack(">L", chunk)
274 offsets.append(int(offset))
275 self.offsetBase = file.tell() - 1
276 file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot
jvrf2cf9c52002-05-23 21:50:36 +0000277 if DEBUG:
278 print " end of %s at %s" % (name, file.tell())
Just7842e561999-12-16 21:34:53 +0000279
jvr4756b3a2002-05-16 18:17:32 +0000280 def __len__(self):
jvrf2cf9c52002-05-23 21:50:36 +0000281 return len(self.items)
jvra2a75b32002-05-13 11:25:17 +0000282
jvr4756b3a2002-05-16 18:17:32 +0000283 def __getitem__(self, index):
284 item = self.items[index]
285 if item is not None:
286 return item
287 offset = self.offsets[index] + self.offsetBase
288 size = self.offsets[index+1] - self.offsets[index]
289 file = self.file
290 file.seek(offset)
291 data = file.read(size)
292 assert len(data) == size
jvra2ad5442002-05-17 18:36:07 +0000293 item = self.produceItem(index, data, file, offset, size)
jvr4756b3a2002-05-16 18:17:32 +0000294 self.items[index] = item
295 return item
296
jvra2ad5442002-05-17 18:36:07 +0000297 def produceItem(self, index, data, file, offset, size):
jvr4756b3a2002-05-16 18:17:32 +0000298 return data
jvr4756b3a2002-05-16 18:17:32 +0000299
jvrf2cf9c52002-05-23 21:50:36 +0000300 def append(self, item):
301 self.items.append(item)
302
303 def getCompiler(self, strings, parent):
304 return self.compilerClass(self, strings, parent)
305
306
307class GlobalSubrsIndex(Index):
308
309 compilerClass = GlobalSubrsCompiler
310
311 def __init__(self, file=None, globalSubrs=None, private=None, fdSelect=None, fdArray=None,
jvra2ad5442002-05-17 18:36:07 +0000312 name=None):
313 Index.__init__(self, file, name)
314 self.globalSubrs = globalSubrs
315 self.private = private
316 self.fdSelect = fdSelect
317 self.fdArray = fdArray
318
319 def produceItem(self, index, data, file, offset, size):
320 if self.private is not None:
321 private = self.private
322 elif self.fdArray is not None:
323 private = self.fdArray[self.fdSelect[index]].Private
324 else:
325 private = None
326 if hasattr(private, "Subrs"):
327 subrs = private.Subrs
328 else:
329 subrs = []
330 return psCharStrings.T2CharString(data, subrs=subrs, globalSubrs=self.globalSubrs)
jvr4756b3a2002-05-16 18:17:32 +0000331
332 def toXML(self, xmlWriter, progress):
jvra2ad5442002-05-17 18:36:07 +0000333 fdSelect = self.fdSelect
jvr4756b3a2002-05-16 18:17:32 +0000334 for i in range(len(self)):
335 xmlWriter.begintag("CharString", index=i)
336 xmlWriter.newline()
337 self[i].toXML(xmlWriter)
338 xmlWriter.endtag("CharString")
339 xmlWriter.newline()
jvra2ad5442002-05-17 18:36:07 +0000340
341 def getItemAndSelector(self, index):
342 fdSelect = self.fdSelect
343 if fdSelect is None:
344 sel = None
345 else:
346 sel = fdSelect[index]
347 return self[index], sel
jvrf2cf9c52002-05-23 21:50:36 +0000348
349class SubrsIndex(GlobalSubrsIndex):
350 compilerClass = SubrsCompiler
351
jvr4756b3a2002-05-16 18:17:32 +0000352
jvr767102e2002-05-17 07:06:32 +0000353class TopDictIndex(Index):
jvra2ad5442002-05-17 18:36:07 +0000354
jvrf2cf9c52002-05-23 21:50:36 +0000355 compilerClass = TopDictIndexCompiler
356
jvra2ad5442002-05-17 18:36:07 +0000357 def produceItem(self, index, data, file, offset, size):
jvr767102e2002-05-17 07:06:32 +0000358 top = TopDict(self.strings, file, offset, self.GlobalSubrs)
359 top.decompile(data)
360 return top
jvra2ad5442002-05-17 18:36:07 +0000361
362 def toXML(self, xmlWriter, progress):
363 for i in range(len(self)):
364 xmlWriter.begintag("FontDict", index=i)
365 xmlWriter.newline()
366 self[i].toXML(xmlWriter, progress)
367 xmlWriter.endtag("FontDict")
368 xmlWriter.newline()
jvr767102e2002-05-17 07:06:32 +0000369
370
jvr4756b3a2002-05-16 18:17:32 +0000371class CharStrings:
372
jvra2ad5442002-05-17 18:36:07 +0000373 def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray):
jvrf2cf9c52002-05-23 21:50:36 +0000374 self.charStringsIndex = SubrsIndex(file, globalSubrs, private, fdSelect, fdArray)
jvr4756b3a2002-05-16 18:17:32 +0000375 self.nameToIndex = nameToIndex = {}
376 for i in range(len(charset)):
377 nameToIndex[charset[i]] = i
378
379 def keys(self):
380 return self.nameToIndex.keys()
381
jvr016ca762002-05-16 18:38:03 +0000382 def values(self):
jvr767102e2002-05-17 07:06:32 +0000383 return self.charStringsIndex
jvr016ca762002-05-16 18:38:03 +0000384
jvr4756b3a2002-05-16 18:17:32 +0000385 def has_key(self, name):
386 return self.nameToIndex.has_key(name)
387
jvr767102e2002-05-17 07:06:32 +0000388 def __len__(self):
389 return len(self.charStringsIndex)
390
jvr4756b3a2002-05-16 18:17:32 +0000391 def __getitem__(self, name):
392 index = self.nameToIndex[name]
393 return self.charStringsIndex[index]
394
jvra2ad5442002-05-17 18:36:07 +0000395 def getItemAndSelector(self, name):
396 index = self.nameToIndex[name]
397 return self.charStringsIndex.getItemAndSelector(index)
398
jvr4756b3a2002-05-16 18:17:32 +0000399 def toXML(self, xmlWriter, progress):
400 names = self.keys()
401 names.sort()
402 for name in names:
jvra2ad5442002-05-17 18:36:07 +0000403 charStr, fdSelect = self.getItemAndSelector(name)
404 if fdSelect is None:
405 xmlWriter.begintag("CharString", name=name)
406 else:
407 xmlWriter.begintag("CharString",
408 [('name', name), ('fdSelect', fdSelect)])
jvr4756b3a2002-05-16 18:17:32 +0000409 xmlWriter.newline()
410 self[name].toXML(xmlWriter)
411 xmlWriter.endtag("CharString")
412 xmlWriter.newline()
413
414
jvra2ad5442002-05-17 18:36:07 +0000415def readCard8(file):
416 return ord(file.read(1))
417
418def readCard16(file):
419 value, = struct.unpack(">H", file.read(2))
420 return value
421
jvrf2cf9c52002-05-23 21:50:36 +0000422def writeCard8(file, value):
423 file.write(chr(value))
424
425def writeCard16(file, value):
426 file.write(struct.pack(">H", value))
427
428def packCard8(value):
429 return chr(value)
430
431def packCard16(value):
432 return struct.pack(">H", value)
433
jvr4756b3a2002-05-16 18:17:32 +0000434def buildOperatorDict(table):
435 d = {}
436 for op, name, arg, default, conv in table:
437 d[op] = (name, arg)
438 return d
439
jvrf2cf9c52002-05-23 21:50:36 +0000440def buildOpcodeDict(table):
441 d = {}
442 for op, name, arg, default, conv in table:
443 if type(op) == TupleType:
444 op = chr(op[0]) + chr(op[1])
445 else:
446 op = chr(op)
447 d[name] = (op, arg)
448 return d
449
jvr4756b3a2002-05-16 18:17:32 +0000450def buildOrder(table):
451 l = []
452 for op, name, arg, default, conv in table:
453 l.append(name)
454 return l
455
456def buildDefaults(table):
457 d = {}
458 for op, name, arg, default, conv in table:
459 if default is not None:
460 d[name] = default
461 return d
462
463def buildConverters(table):
464 d = {}
465 for op, name, arg, default, conv in table:
466 d[name] = conv
467 return d
468
469
jvr7ce02ea2002-05-17 20:04:05 +0000470class BaseConverter:
471 def read(self, parent, value):
472 return value
jvrf2cf9c52002-05-23 21:50:36 +0000473 def write(self, parent, value):
474 return value
jvra2ad5442002-05-17 18:36:07 +0000475 def xmlWrite(self, xmlWriter, name, value):
476 xmlWriter.begintag(name)
477 xmlWriter.newline()
478 value.toXML(xmlWriter, None)
479 xmlWriter.endtag(name)
480 xmlWriter.newline()
481
jvr7ce02ea2002-05-17 20:04:05 +0000482class PrivateDictConverter(BaseConverter):
jvr4756b3a2002-05-16 18:17:32 +0000483 def read(self, parent, value):
484 size, offset = value
485 file = parent.file
486 pr = PrivateDict(parent.strings, file, offset)
487 file.seek(offset)
488 data = file.read(size)
489 len(data) == size
490 pr.decompile(data)
491 return pr
jvrf2cf9c52002-05-23 21:50:36 +0000492 def write(self, parent, value):
493 return (0, 0) # dummy value
jvr4756b3a2002-05-16 18:17:32 +0000494
jvr7ce02ea2002-05-17 20:04:05 +0000495class SubrsConverter(BaseConverter):
jvr4756b3a2002-05-16 18:17:32 +0000496 def read(self, parent, value):
497 file = parent.file
498 file.seek(parent.offset + value) # Offset(self)
jvrf2cf9c52002-05-23 21:50:36 +0000499 return SubrsIndex(file, name="SubrsIndex")
500 def write(self, parent, value):
501 return 0 # dummy value
jvr4756b3a2002-05-16 18:17:32 +0000502
jvr7ce02ea2002-05-17 20:04:05 +0000503class CharStringsConverter(BaseConverter):
jvr4756b3a2002-05-16 18:17:32 +0000504 def read(self, parent, value):
505 file = parent.file
jvr767102e2002-05-17 07:06:32 +0000506 charset = parent.charset
jvra2ad5442002-05-17 18:36:07 +0000507 globalSubrs = parent.GlobalSubrs
508 if hasattr(parent, "ROS"):
509 fdSelect, fdArray = parent.FDSelect, parent.FDArray
510 private = None
511 else:
512 fdSelect, fdArray = None, None
513 private = parent.Private
jvr4756b3a2002-05-16 18:17:32 +0000514 file.seek(value) # Offset(0)
jvra2ad5442002-05-17 18:36:07 +0000515 return CharStrings(file, charset, globalSubrs, private, fdSelect, fdArray)
jvrf2cf9c52002-05-23 21:50:36 +0000516 def write(self, parent, value):
517 return 0 # dummy value
jvr4756b3a2002-05-16 18:17:32 +0000518
519class CharsetConverter:
520 def read(self, parent, value):
521 isCID = hasattr(parent, "ROS")
522 if value > 2:
523 numGlyphs = parent.numGlyphs
524 file = parent.file
525 file.seek(value)
jvrf2cf9c52002-05-23 21:50:36 +0000526 if DEBUG:
527 print "loading charset at %s" % value
jvra2ad5442002-05-17 18:36:07 +0000528 format = readCard8(file)
Just7842e561999-12-16 21:34:53 +0000529 if format == 0:
jvrf2cf9c52002-05-23 21:50:36 +0000530 charset =parseCharset0(numGlyphs, file, parent.strings)
jvr4756b3a2002-05-16 18:17:32 +0000531 elif format == 1 or format == 2:
532 charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
Just7842e561999-12-16 21:34:53 +0000533 else:
jvr1890b952002-05-15 07:41:30 +0000534 raise NotImplementedError
jvr4756b3a2002-05-16 18:17:32 +0000535 assert len(charset) == numGlyphs
jvrf2cf9c52002-05-23 21:50:36 +0000536 if DEBUG:
537 print " charset end at %s" % file.tell()
jvr1890b952002-05-15 07:41:30 +0000538 else:
jvra2ad5442002-05-17 18:36:07 +0000539 if isCID or not hasattr(parent, "CharStrings"):
jvr4756b3a2002-05-16 18:17:32 +0000540 assert value == 0
541 charset = None
542 elif value == 0:
543 charset = ISOAdobe
544 elif value == 1:
545 charset = Expert
546 elif value == 2:
547 charset = ExpertSubset
jvr1890b952002-05-15 07:41:30 +0000548 # self.charset:
549 # 0: ISOAdobe (or CID font!)
550 # 1: Expert
551 # 2: ExpertSubset
jvr4756b3a2002-05-16 18:17:32 +0000552 charset = None #
553 return charset
jvrf2cf9c52002-05-23 21:50:36 +0000554 def write(self, parent, value):
555 return 0 # dummy value
jvr4756b3a2002-05-16 18:17:32 +0000556 def xmlWrite(self, xmlWriter, name, value):
557 # XXX GlyphOrder needs to be stored *somewhere*, but not here...
558 xmlWriter.simpletag("charset", value=value)
559 xmlWriter.newline()
560
561
jvrf2cf9c52002-05-23 21:50:36 +0000562class CharsetCompiler:
563
564 def __init__(self, strings, charset, parent):
565 assert charset[0] == '.notdef'
566 format = 0 # XXX!
567 data = [packCard8(format)]
568 for name in charset[1:]:
569 data.append(packCard16(strings.getSID(name)))
570 self.data = "".join(data)
571 self.parent = parent
572
573 def setPos(self, pos):
574 self.parent.rawDict["charset"] = pos
575
576 def getDataLength(self):
577 return len(self.data)
578
579 def toFile(self, file):
580 file.write(self.data)
581
582
583def parseCharset0(numGlyphs, file, strings):
584 charset = [".notdef"]
585 for i in range(numGlyphs - 1):
586 SID = readCard16(file)
587 charset.append(strings[SID])
588 return charset
589
jvr4756b3a2002-05-16 18:17:32 +0000590def parseCharset(numGlyphs, file, strings, isCID, format):
591 charset = ['.notdef']
592 count = 1
593 if format == 1:
jvra2ad5442002-05-17 18:36:07 +0000594 nLeftFunc = readCard8
jvr4756b3a2002-05-16 18:17:32 +0000595 else:
jvra2ad5442002-05-17 18:36:07 +0000596 nLeftFunc = readCard16
jvr4756b3a2002-05-16 18:17:32 +0000597 while count < numGlyphs:
jvra2ad5442002-05-17 18:36:07 +0000598 first = readCard16(file)
jvr4756b3a2002-05-16 18:17:32 +0000599 nLeft = nLeftFunc(file)
600 if isCID:
601 for CID in range(first, first+nLeft+1):
602 charset.append(CID)
jvr1890b952002-05-15 07:41:30 +0000603 else:
jvr4756b3a2002-05-16 18:17:32 +0000604 for SID in range(first, first+nLeft+1):
605 charset.append(strings[SID])
606 count = count + nLeft + 1
607 return charset
608
609
jvr7ce02ea2002-05-17 20:04:05 +0000610class FDArrayConverter(BaseConverter):
jvra2ad5442002-05-17 18:36:07 +0000611 def read(self, parent, value):
612 file = parent.file
613 file.seek(value)
614 fdArray = TopDictIndex(file)
615 fdArray.strings = parent.strings
616 fdArray.GlobalSubrs = parent.GlobalSubrs
617 return fdArray
618
619
620class FDSelectConverter:
621 def read(self, parent, value):
622 file = parent.file
623 file.seek(value)
624 format = readCard8(file)
625 numGlyphs = parent.numGlyphs
626 if format == 0:
627 from array import array
628 fdSelect = array("B", file.read(numGlyphs)).tolist()
629 elif format == 3:
630 fdSelect = [None] * numGlyphs
631 nRanges = readCard16(file)
632 prev = None
633 for i in range(nRanges):
634 first = readCard16(file)
635 if prev is not None:
636 for glyphID in range(prev, first):
637 fdSelect[glyphID] = fd
638 prev = first
639 fd = readCard8(file)
640 if prev is not None:
641 first = readCard16(file)
642 for glyphID in range(prev, first):
643 fdSelect[glyphID] = fd
644 else:
645 assert 0, "unsupported FDSelect format: %s" % format
646 return fdSelect
647 def xmlWrite(self, xmlWriter, name, value):
648 pass
649
650
jvr7ce02ea2002-05-17 20:04:05 +0000651class ROSConverter(BaseConverter):
jvr155aa752002-05-17 19:58:49 +0000652 def xmlWrite(self, xmlWriter, name, value):
653 registry, order, supplement = value
jvrf2cf9c52002-05-23 21:50:36 +0000654 xmlWriter.simpletag(name, [('Registry', registry), ('Order', order),
655 ('Supplement', supplement)])
jvr4afb2572002-05-18 20:07:01 +0000656 xmlWriter.newline()
jvr155aa752002-05-17 19:58:49 +0000657
658
jvr4756b3a2002-05-16 18:17:32 +0000659topDictOperators = [
660# opcode name argument type default converter
jvr155aa752002-05-17 19:58:49 +0000661 ((12, 30), 'ROS', ('SID','SID','number'), None, ROSConverter()),
jvrf2cf9c52002-05-23 21:50:36 +0000662 ((12, 20), 'SyntheticBase', 'number', None, None),
jvr4756b3a2002-05-16 18:17:32 +0000663 (0, 'version', 'SID', None, None),
664 (1, 'Notice', 'SID', None, None),
665 ((12, 0), 'Copyright', 'SID', None, None),
666 (2, 'FullName', 'SID', None, None),
jvr155aa752002-05-17 19:58:49 +0000667 ((12, 38), 'FontName', 'SID', None, None),
jvr4756b3a2002-05-16 18:17:32 +0000668 (3, 'FamilyName', 'SID', None, None),
669 (4, 'Weight', 'SID', None, None),
670 ((12, 1), 'isFixedPitch', 'number', 0, None),
671 ((12, 2), 'ItalicAngle', 'number', 0, None),
672 ((12, 3), 'UnderlinePosition', 'number', None, None),
673 ((12, 4), 'UnderlineThickness', 'number', 50, None),
674 ((12, 5), 'PaintType', 'number', 0, None),
675 ((12, 6), 'CharstringType', 'number', 2, None),
676 ((12, 7), 'FontMatrix', 'array', [0.001,0,0,0.001,0,0], None),
677 (13, 'UniqueID', 'number', None, None),
678 (5, 'FontBBox', 'array', [0,0,0,0], None),
679 ((12, 8), 'StrokeWidth', 'number', 0, None),
680 (14, 'XUID', 'array', None, None),
681 (15, 'charset', 'number', 0, CharsetConverter()),
jvr4756b3a2002-05-16 18:17:32 +0000682 ((12, 21), 'PostScript', 'SID', None, None),
683 ((12, 22), 'BaseFontName', 'SID', None, None),
684 ((12, 23), 'BaseFontBlend', 'delta', None, None),
jvr4756b3a2002-05-16 18:17:32 +0000685 ((12, 31), 'CIDFontVersion', 'number', 0, None),
686 ((12, 32), 'CIDFontRevision', 'number', 0, None),
687 ((12, 33), 'CIDFontType', 'number', 0, None),
688 ((12, 34), 'CIDCount', 'number', 8720, None),
689 ((12, 35), 'UIDBase', 'number', None, None),
jvr155aa752002-05-17 19:58:49 +0000690 (16, 'Encoding', 'number', 0, None), # XXX
jvra2ad5442002-05-17 18:36:07 +0000691 ((12, 36), 'FDArray', 'number', None, FDArrayConverter()),
692 ((12, 37), 'FDSelect', 'number', None, FDSelectConverter()),
jvr155aa752002-05-17 19:58:49 +0000693 (18, 'Private', ('number','number'), None, PrivateDictConverter()),
694 (17, 'CharStrings', 'number', None, CharStringsConverter()),
jvr4756b3a2002-05-16 18:17:32 +0000695]
696
697privateDictOperators = [
698# opcode name argument type default converter
699 (6, 'BlueValues', 'delta', None, None),
700 (7, 'OtherBlues', 'delta', None, None),
701 (8, 'FamilyBlues', 'delta', None, None),
702 (9, 'FamilyOtherBlues', 'delta', None, None),
703 ((12, 9), 'BlueScale', 'number', 0.039625, None),
704 ((12, 10), 'BlueShift', 'number', 7, None),
705 ((12, 11), 'BlueFuzz', 'number', 1, None),
706 (10, 'StdHW', 'number', None, None),
707 (11, 'StdVW', 'number', None, None),
708 ((12, 12), 'StemSnapH', 'delta', None, None),
709 ((12, 13), 'StemSnapV', 'delta', None, None),
710 ((12, 14), 'ForceBold', 'number', 0, None),
jvrf2cf9c52002-05-23 21:50:36 +0000711 ((12, 15), 'ForceBoldThreshold', 'number', None, None), # deprecated
712 ((12, 16), 'lenIV', 'number', None, None), # deprecated
jvr4756b3a2002-05-16 18:17:32 +0000713 ((12, 17), 'LanguageGroup', 'number', 0, None),
714 ((12, 18), 'ExpansionFactor', 'number', 0.06, None),
715 ((12, 19), 'initialRandomSeed', 'number', 0, None),
716 (20, 'defaultWidthX', 'number', 0, None),
717 (21, 'nominalWidthX', 'number', 0, None),
718 (19, 'Subrs', 'number', None, SubrsConverter()),
719]
720
721
722class TopDictDecompiler(psCharStrings.DictDecompiler):
723 operators = buildOperatorDict(topDictOperators)
724
725
726class PrivateDictDecompiler(psCharStrings.DictDecompiler):
727 operators = buildOperatorDict(privateDictOperators)
728
729
jvrf2cf9c52002-05-23 21:50:36 +0000730class DictCompiler:
731
732 def __init__(self, dictObj, strings, parent):
733 assert isinstance(strings, IndexedStrings)
734 self.dictObj = dictObj
735 self.strings = strings
736 self.parent = parent
737 rawDict = {}
738 for name in dictObj.order:
739 value = getattr(dictObj, name, None)
740 if value is None:
741 continue
742 conv = dictObj.converters[name]
743 if conv:
744 value = conv.write(dictObj, value)
745 if value == dictObj.defaults.get(name):
746 continue
747 rawDict[name] = value
748 self.rawDict = rawDict
749
750 def setPos(self, pos):
751 pass
752
753 def getDataLength(self):
754 return len(self.compile())
755
756 def compile(self):
757 rawDict = self.rawDict
758 data = []
759 for name in self.dictObj.order:
760 value = rawDict.get(name)
761 if value is None:
762 continue
763 op, argType = self.opcodes[name]
764 if type(argType) == TupleType:
765 l = len(argType)
766 assert len(value) == l, "value doesn't match arg type"
767 for i in range(l):
768 arg = argType[l - i - 1]
769 v = value[i]
770 arghandler = getattr(self, "arg_" + arg)
771 data.append(arghandler(v))
772 else:
773 arghandler = getattr(self, "arg_" + argType)
774 data.append(arghandler(value))
775 data.append(op)
776 return "".join(data)
777
778 def toFile(self, file):
779 file.write(self.compile())
780
781 def arg_number(self, num):
782 return encodeNumber(num)
783 def arg_SID(self, s):
784 return psCharStrings.encodeIntCFF(self.strings.getSID(s))
785 def arg_array(self, value):
786 data = []
787 for num in value:
788 data.append(encodeNumber(num))
789 return "".join(data)
790 def arg_delta(self, value):
791 out = []
792 last = 0
793 for v in value:
794 out.append(v - last)
795 last = v
796 data = []
797 for num in out:
798 data.append(encodeNumber(num))
799 return "".join(data)
800
801
802def encodeNumber(num):
803 if type(num) == FloatType:
804 return psCharStrings.encodeFloat(num)
805 else:
806 return psCharStrings.encodeIntCFF(num)
807
808
809class TopDictCompiler(DictCompiler):
810
811 opcodes = buildOpcodeDict(topDictOperators)
812
813 def getChildren(self, strings):
814 children = []
815 if hasattr(self.dictObj, "charset"):
816 children.append(CharsetCompiler(strings, self.dictObj.charset, self))
817 if hasattr(self.dictObj, "CharStrings"):
818 items = []
819 charStrings = self.dictObj.CharStrings
820 for name in self.dictObj.charset:
821 items.append(charStrings[name])
822 charStringsComp = CharStringsCompiler(items, strings, self)
823 children.append(charStringsComp)
824 if hasattr(self.dictObj, "Private"):
825 privComp = self.dictObj.Private.getCompiler(strings, self)
826 children.append(privComp)
827 children.extend(privComp.getChildren(strings))
828 return children
829
830
831class PrivateDictCompiler(DictCompiler):
832
833 opcodes = buildOpcodeDict(privateDictOperators)
834
835 def setPos(self, pos):
836 size = len(self.compile())
837 self.parent.rawDict["Private"] = size, pos
838 self.pos = pos
839
840 def getChildren(self, strings):
841 children = []
842 if hasattr(self.dictObj, "Subrs"):
843 children.append(self.dictObj.Subrs.getCompiler(strings, self))
844 return children
845
jvr4756b3a2002-05-16 18:17:32 +0000846
847class BaseDict:
848
849 def __init__(self, strings, file, offset):
850 self.rawDict = {}
jvr767102e2002-05-17 07:06:32 +0000851 if DEBUG:
jvrf2cf9c52002-05-23 21:50:36 +0000852 print "loading %s at %s" % (self.__class__.__name__, offset)
jvr4756b3a2002-05-16 18:17:32 +0000853 self.file = file
854 self.offset = offset
855 self.strings = strings
jvr155aa752002-05-17 19:58:49 +0000856 self.skipNames = []
jvr4756b3a2002-05-16 18:17:32 +0000857
858 def decompile(self, data):
jvrf2cf9c52002-05-23 21:50:36 +0000859 if DEBUG:
860 print " length %s is %s" % (self.__class__.__name__, len(data))
861 dec = self.decompilerClass(self.strings)
jvr4756b3a2002-05-16 18:17:32 +0000862 dec.decompile(data)
863 self.rawDict = dec.getDict()
864 self.postDecompile()
865
866 def postDecompile(self):
867 pass
868
jvrf2cf9c52002-05-23 21:50:36 +0000869 def getCompiler(self, strings, parent):
870 return self.compilerClass(self, strings, parent)
871
jvr4756b3a2002-05-16 18:17:32 +0000872 def __getattr__(self, name):
873 value = self.rawDict.get(name)
874 if value is None:
875 value = self.defaults.get(name)
876 if value is None:
877 raise AttributeError, name
878 conv = self.converters[name]
879 if conv is not None:
880 value = conv.read(self, value)
881 setattr(self, name, value)
882 return value
883
884 def toXML(self, xmlWriter, progress):
885 for name in self.order:
jvr155aa752002-05-17 19:58:49 +0000886 if name in self.skipNames:
887 continue
jvr4756b3a2002-05-16 18:17:32 +0000888 value = getattr(self, name, None)
889 if value is None:
890 continue
891 conv = self.converters.get(name)
892 if conv is not None:
893 conv.xmlWrite(xmlWriter, name, value)
894 else:
jvrf2cf9c52002-05-23 21:50:36 +0000895 if isinstance(value, ListType):
jvr4756b3a2002-05-16 18:17:32 +0000896 value = " ".join(map(str, value))
897 xmlWriter.simpletag(name, value=value)
898 xmlWriter.newline()
899
900
901class TopDict(BaseDict):
902
903 defaults = buildDefaults(topDictOperators)
904 converters = buildConverters(topDictOperators)
905 order = buildOrder(topDictOperators)
jvrf2cf9c52002-05-23 21:50:36 +0000906 decompilerClass = TopDictDecompiler
907 compilerClass = TopDictCompiler
Just7842e561999-12-16 21:34:53 +0000908
jvr016ca762002-05-16 18:38:03 +0000909 def __init__(self, strings, file, offset, GlobalSubrs):
910 BaseDict.__init__(self, strings, file, offset)
911 self.GlobalSubrs = GlobalSubrs
912
Just7842e561999-12-16 21:34:53 +0000913 def getGlyphOrder(self):
914 return self.charset
915
jvr4756b3a2002-05-16 18:17:32 +0000916 def postDecompile(self):
917 offset = self.rawDict.get("CharStrings")
918 if offset is None:
919 return
920 # get the number of glyphs beforehand.
921 self.file.seek(offset)
jvra2ad5442002-05-17 18:36:07 +0000922 self.numGlyphs = readCard16(self.file)
Just7842e561999-12-16 21:34:53 +0000923
jvr016ca762002-05-16 18:38:03 +0000924 def toXML(self, xmlWriter, progress):
jvr155aa752002-05-17 19:58:49 +0000925 if hasattr(self, "CharStrings"):
926 self.decompileAllCharStrings()
927 if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
928 # these values have default values, but I only want them to show up
929 # in CID fonts.
930 self.skipNames = ['CIDFontVersion', 'CIDFontRevision', 'CIDFontType',
931 'CIDCount']
jvr016ca762002-05-16 18:38:03 +0000932 BaseDict.toXML(self, xmlWriter, progress)
jvr767102e2002-05-17 07:06:32 +0000933
Just7842e561999-12-16 21:34:53 +0000934 def decompileAllCharStrings(self):
jvra2ad5442002-05-17 18:36:07 +0000935 for charString in self.CharStrings.values():
936 charString.decompile()
Just7842e561999-12-16 21:34:53 +0000937
938
jvr4756b3a2002-05-16 18:17:32 +0000939class PrivateDict(BaseDict):
940 defaults = buildDefaults(privateDictOperators)
941 converters = buildConverters(privateDictOperators)
942 order = buildOrder(privateDictOperators)
jvrf2cf9c52002-05-23 21:50:36 +0000943 decompilerClass = PrivateDictDecompiler
944 compilerClass = PrivateDictCompiler
Just7842e561999-12-16 21:34:53 +0000945
946
jvre3275582002-05-14 12:22:03 +0000947class IndexedStrings:
948
jvr767102e2002-05-17 07:06:32 +0000949 """SID -> string mapping."""
950
951 def __init__(self, file=None):
952 if file is None:
jvre3275582002-05-14 12:22:03 +0000953 strings = []
jvr767102e2002-05-17 07:06:32 +0000954 else:
955 strings = list(Index(file, "IndexedStrings"))
jvre3275582002-05-14 12:22:03 +0000956 self.strings = strings
957
jvrf2cf9c52002-05-23 21:50:36 +0000958 def getCompiler(self):
959 return IndexedStringsCompiler(self, None, None)
960
961 def __len__(self):
962 return len(self.strings)
963
jvre3275582002-05-14 12:22:03 +0000964 def __getitem__(self, SID):
965 if SID < cffStandardStringCount:
966 return cffStandardStrings[SID]
967 else:
968 return self.strings[SID - cffStandardStringCount]
969
970 def getSID(self, s):
971 if not hasattr(self, "stringMapping"):
972 self.buildStringMapping()
973 if cffStandardStringMapping.has_key(s):
974 SID = cffStandardStringMapping[s]
jvrf2cf9c52002-05-23 21:50:36 +0000975 elif self.stringMapping.has_key(s):
jvre3275582002-05-14 12:22:03 +0000976 SID = self.stringMapping[s]
977 else:
978 SID = len(self.strings) + cffStandardStringCount
979 self.strings.append(s)
980 self.stringMapping[s] = SID
981 return SID
982
983 def getStrings(self):
984 return self.strings
985
986 def buildStringMapping(self):
987 self.stringMapping = {}
988 for index in range(len(self.strings)):
989 self.stringMapping[self.strings[index]] = index + cffStandardStringCount
990
991
Just7842e561999-12-16 21:34:53 +0000992# The 391 Standard Strings as used in the CFF format.
993# from Adobe Technical None #5176, version 1.0, 18 March 1998
994
995cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
996 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
997 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
998 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
999 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
1000 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
1001 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
1002 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
1003 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
1004 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
1005 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
1006 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
1007 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
1008 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
1009 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
1010 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
1011 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
1012 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
1013 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
1014 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
1015 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
1016 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
1017 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
1018 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
1019 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
1020 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
1021 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
1022 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
1023 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
1024 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
1025 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
1026 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
1027 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
1028 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
1029 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
1030 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
1031 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
1032 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
1033 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
1034 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
1035 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
1036 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
1037 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
1038 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
1039 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
1040 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
1041 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
1042 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
1043 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
1044 'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
1045 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
1046 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
1047 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
1048 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
1049 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
1050 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
1051 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
1052 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
1053 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
1054 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
1055 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
1056 '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
1057 'Semibold'
1058]
1059
1060cffStandardStringCount = 391
1061assert len(cffStandardStrings) == cffStandardStringCount
1062# build reverse mapping
1063cffStandardStringMapping = {}
1064for _i in range(cffStandardStringCount):
1065 cffStandardStringMapping[cffStandardStrings[_i]] = _i
1066