blob: bc4cbbdb63fc3c934d9c0a5c136d05b52e8c8ca3 [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#
jvr2a9bcde2008-03-07 19:56:17 +00004# $Id: cffLib.py,v 1.34 2008-03-07 19:56:17 jvr Exp $
Justec46d161999-12-20 22:02:10 +00005#
Just7842e561999-12-16 21:34:53 +00006
Behdad Esfahbod8413c102013-09-17 16:59:39 -04007import struct
8from fontTools.misc import sstruct
Just528614e2000-01-16 22:14:02 +00009from fontTools.misc import psCharStrings
jvr4e5af602002-05-24 09:58:04 +000010from fontTools.misc.textTools import safeEval
Behdad Esfahbodbb0beb72013-11-27 14:37:28 -050011from fontTools.misc.py23 import *
Behdad Esfahbod2a9b8682013-11-27 05:52:33 -050012
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
jvr4e5af602002-05-24 09:58:04 +000028 def decompile(self, file, otFont):
jvra2a75b32002-05-13 11:25:17 +000029 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)
jvr4e5af602002-05-24 09:58:04 +000034 self.fontNames = list(Index(file))
jvr4756b3a2002-05-16 18:17:32 +000035 self.topDictIndex = TopDictIndex(file)
jvr767102e2002-05-17 07:06:32 +000036 self.strings = IndexedStrings(file)
jvr4e5af602002-05-24 09:58:04 +000037 self.GlobalSubrs = GlobalSubrsIndex(file)
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):
jvrce522412003-08-25 07:37:25 +000045 return list(self.fontNames)
jvr4756b3a2002-05-16 18:17:32 +000046
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:
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -050054 raise KeyError(name)
jvr016ca762002-05-16 18:38:03 +000055 return self.topDictIndex[index]
Just7842e561999-12-16 21:34:53 +000056
jvr4e5af602002-05-24 09:58:04 +000057 def compile(self, file, otFont):
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
jvr4e5af602002-05-24 09:58:04 +000070 for topDict in self.topDictIndex:
71 if not hasattr(topDict, "charset") or topDict.charset is None:
72 charset = otFont.getGlyphOrder()
73 topDict.charset = charset
74
jvrf2cf9c52002-05-23 21:50:36 +000075 for child in topCompiler.getChildren(strings):
76 writer.add(child)
77
jvrf2cf9c52002-05-23 21:50:36 +000078 writer.toFile(file)
Just7842e561999-12-16 21:34:53 +000079
80 def toXML(self, xmlWriter, progress=None):
81 xmlWriter.newline()
82 for fontName in self.fontNames:
83 xmlWriter.begintag("CFFFont", name=fontName)
84 xmlWriter.newline()
jvr4756b3a2002-05-16 18:17:32 +000085 font = self[fontName]
Just7842e561999-12-16 21:34:53 +000086 font.toXML(xmlWriter, progress)
87 xmlWriter.endtag("CFFFont")
88 xmlWriter.newline()
89 xmlWriter.newline()
90 xmlWriter.begintag("GlobalSubrs")
91 xmlWriter.newline()
jvr4756b3a2002-05-16 18:17:32 +000092 self.GlobalSubrs.toXML(xmlWriter, progress)
Just7842e561999-12-16 21:34:53 +000093 xmlWriter.endtag("GlobalSubrs")
94 xmlWriter.newline()
95 xmlWriter.newline()
96
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -050097 def fromXML(self, name, attrs, content):
jvr4e5af602002-05-24 09:58:04 +000098 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:
jvr2a9bcde2008-03-07 19:56:17 +0000114 if isinstance(element, basestring):
jvr4e5af602002-05-24 09:58:04 +0000115 continue
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500116 name, attrs, content = element
117 topDict.fromXML(name, attrs, content)
jvr4e5af602002-05-24 09:58:04 +0000118 elif name == "GlobalSubrs":
119 for element in content:
jvr2a9bcde2008-03-07 19:56:17 +0000120 if isinstance(element, basestring):
jvr4e5af602002-05-24 09:58:04 +0000121 continue
122 name, attrs, content = element
jvr489d76a2003-08-24 19:56:16 +0000123 subr = psCharStrings.T2CharString()
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500124 subr.fromXML(name, attrs, content)
jvr4e5af602002-05-24 09:58:04 +0000125 self.GlobalSubrs.append(subr)
Just7842e561999-12-16 21:34:53 +0000126
127
jvrf2cf9c52002-05-23 21:50:36 +0000128class CFFWriter:
129
130 def __init__(self):
131 self.data = []
132
133 def add(self, table):
134 self.data.append(table)
135
136 def toFile(self, file):
137 lastPosList = None
138 count = 1
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500139 while True:
jvr4e5af602002-05-24 09:58:04 +0000140 if DEBUG:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -0500141 print("CFFWriter.toFile() iteration:", count)
jvr4e5af602002-05-24 09:58:04 +0000142 count = count + 1
jvrf2cf9c52002-05-23 21:50:36 +0000143 pos = 0
144 posList = [pos]
145 for item in self.data:
jvrf2cf9c52002-05-23 21:50:36 +0000146 if hasattr(item, "getDataLength"):
jvr4e5af602002-05-24 09:58:04 +0000147 endPos = pos + item.getDataLength()
jvrf2cf9c52002-05-23 21:50:36 +0000148 else:
jvr4e5af602002-05-24 09:58:04 +0000149 endPos = pos + len(item)
150 if hasattr(item, "setPos"):
151 item.setPos(pos, endPos)
152 pos = endPos
jvrf2cf9c52002-05-23 21:50:36 +0000153 posList.append(pos)
154 if posList == lastPosList:
155 break
156 lastPosList = posList
jvr4e5af602002-05-24 09:58:04 +0000157 if DEBUG:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -0500158 print("CFFWriter.toFile() writing to file.")
jvrf2cf9c52002-05-23 21:50:36 +0000159 begin = file.tell()
160 posList = [0]
161 for item in self.data:
162 if hasattr(item, "toFile"):
163 item.toFile(file)
164 else:
165 file.write(item)
166 posList.append(file.tell() - begin)
jvrf2cf9c52002-05-23 21:50:36 +0000167 assert posList == lastPosList
168
169
170def calcOffSize(largestOffset):
171 if largestOffset < 0x100:
172 offSize = 1
173 elif largestOffset < 0x10000:
174 offSize = 2
175 elif largestOffset < 0x1000000:
176 offSize = 3
177 else:
178 offSize = 4
179 return offSize
180
181
182class IndexCompiler:
183
184 def __init__(self, items, strings, parent):
185 self.items = self.getItems(items, strings)
186 self.parent = parent
187
188 def getItems(self, items, strings):
189 return items
190
191 def getOffsets(self):
192 pos = 1
193 offsets = [pos]
194 for item in self.items:
195 if hasattr(item, "getDataLength"):
196 pos = pos + item.getDataLength()
197 else:
198 pos = pos + len(item)
199 offsets.append(pos)
200 return offsets
201
202 def getDataLength(self):
203 lastOffset = self.getOffsets()[-1]
204 offSize = calcOffSize(lastOffset)
205 dataLength = (
206 2 + # count
207 1 + # offSize
208 (len(self.items) + 1) * offSize + # the offsets
209 lastOffset - 1 # size of object data
210 )
211 return dataLength
212
213 def toFile(self, file):
jvrf2cf9c52002-05-23 21:50:36 +0000214 offsets = self.getOffsets()
215 writeCard16(file, len(self.items))
216 offSize = calcOffSize(offsets[-1])
217 writeCard8(file, offSize)
218 offSize = -offSize
219 pack = struct.pack
220 for offset in offsets:
221 binOffset = pack(">l", offset)[offSize:]
222 assert len(binOffset) == -offSize
223 file.write(binOffset)
224 for item in self.items:
225 if hasattr(item, "toFile"):
226 item.toFile(file)
227 else:
228 file.write(item)
jvrf2cf9c52002-05-23 21:50:36 +0000229
230
231class IndexedStringsCompiler(IndexCompiler):
232
233 def getItems(self, items, strings):
234 return items.strings
235
236
237class TopDictIndexCompiler(IndexCompiler):
238
239 def getItems(self, items, strings):
240 out = []
241 for item in items:
242 out.append(item.getCompiler(strings, self))
243 return out
244
245 def getChildren(self, strings):
246 children = []
247 for topDict in self.items:
248 children.extend(topDict.getChildren(strings))
249 return children
250
251
jvred101512003-08-22 19:53:32 +0000252class FDArrayIndexCompiler(IndexCompiler):
253
254 def getItems(self, items, strings):
255 out = []
256 for item in items:
257 out.append(item.getCompiler(strings, self))
258 return out
259
260 def getChildren(self, strings):
261 children = []
262 for fontDict in self.items:
263 children.extend(fontDict.getChildren(strings))
264 return children
265
jvred101512003-08-22 19:53:32 +0000266 def toFile(self, file):
267 offsets = self.getOffsets()
268 writeCard16(file, len(self.items))
269 offSize = calcOffSize(offsets[-1])
270 writeCard8(file, offSize)
271 offSize = -offSize
272 pack = struct.pack
273 for offset in offsets:
274 binOffset = pack(">l", offset)[offSize:]
275 assert len(binOffset) == -offSize
276 file.write(binOffset)
277 for item in self.items:
278 if hasattr(item, "toFile"):
279 item.toFile(file)
280 else:
281 file.write(item)
282
283 def setPos(self, pos, endPos):
284 self.parent.rawDict["FDArray"] = pos
285
286
jvrf2cf9c52002-05-23 21:50:36 +0000287class GlobalSubrsCompiler(IndexCompiler):
288 def getItems(self, items, strings):
289 out = []
290 for cs in items:
291 cs.compile()
292 out.append(cs.bytecode)
293 return out
294
295class SubrsCompiler(GlobalSubrsCompiler):
jvr4e5af602002-05-24 09:58:04 +0000296 def setPos(self, pos, endPos):
jvrf2cf9c52002-05-23 21:50:36 +0000297 offset = pos - self.parent.pos
298 self.parent.rawDict["Subrs"] = offset
299
300class CharStringsCompiler(GlobalSubrsCompiler):
jvr4e5af602002-05-24 09:58:04 +0000301 def setPos(self, pos, endPos):
jvrf2cf9c52002-05-23 21:50:36 +0000302 self.parent.rawDict["CharStrings"] = pos
303
304
jvr4756b3a2002-05-16 18:17:32 +0000305class Index:
Just7842e561999-12-16 21:34:53 +0000306
jvr4756b3a2002-05-16 18:17:32 +0000307 """This class represents what the CFF spec calls an INDEX."""
Just7842e561999-12-16 21:34:53 +0000308
jvrf2cf9c52002-05-23 21:50:36 +0000309 compilerClass = IndexCompiler
310
jvr4e5af602002-05-24 09:58:04 +0000311 def __init__(self, file=None):
312 name = self.__class__.__name__
jvrf2cf9c52002-05-23 21:50:36 +0000313 if file is None:
314 self.items = []
315 return
jvr767102e2002-05-17 07:06:32 +0000316 if DEBUG:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -0500317 print("loading %s at %s" % (name, file.tell()))
jvr4756b3a2002-05-16 18:17:32 +0000318 self.file = file
jvra2ad5442002-05-17 18:36:07 +0000319 count = readCard16(file)
jvr4756b3a2002-05-16 18:17:32 +0000320 self.count = count
321 self.items = [None] * count
322 if count == 0:
jvrf2cf9c52002-05-23 21:50:36 +0000323 self.items = []
jvr4756b3a2002-05-16 18:17:32 +0000324 return
jvra2ad5442002-05-17 18:36:07 +0000325 offSize = readCard8(file)
jvr767102e2002-05-17 07:06:32 +0000326 if DEBUG:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -0500327 print(" index count: %s offSize: %s" % (count, offSize))
jvr767102e2002-05-17 07:06:32 +0000328 assert offSize <= 4, "offSize too large: %s" % offSize
jvr4756b3a2002-05-16 18:17:32 +0000329 self.offsets = offsets = []
330 pad = '\0' * (4 - offSize)
331 for index in range(count+1):
332 chunk = file.read(offSize)
333 chunk = pad + chunk
334 offset, = struct.unpack(">L", chunk)
335 offsets.append(int(offset))
336 self.offsetBase = file.tell() - 1
337 file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot
jvrf2cf9c52002-05-23 21:50:36 +0000338 if DEBUG:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -0500339 print(" end of %s at %s" % (name, file.tell()))
Just7842e561999-12-16 21:34:53 +0000340
jvr4756b3a2002-05-16 18:17:32 +0000341 def __len__(self):
jvrf2cf9c52002-05-23 21:50:36 +0000342 return len(self.items)
jvra2a75b32002-05-13 11:25:17 +0000343
jvr4756b3a2002-05-16 18:17:32 +0000344 def __getitem__(self, index):
345 item = self.items[index]
346 if item is not None:
347 return item
348 offset = self.offsets[index] + self.offsetBase
349 size = self.offsets[index+1] - self.offsets[index]
350 file = self.file
351 file.seek(offset)
352 data = file.read(size)
353 assert len(data) == size
jvra2ad5442002-05-17 18:36:07 +0000354 item = self.produceItem(index, data, file, offset, size)
jvr4756b3a2002-05-16 18:17:32 +0000355 self.items[index] = item
356 return item
357
jvra2ad5442002-05-17 18:36:07 +0000358 def produceItem(self, index, data, file, offset, size):
jvr4756b3a2002-05-16 18:17:32 +0000359 return data
jvr4756b3a2002-05-16 18:17:32 +0000360
jvrf2cf9c52002-05-23 21:50:36 +0000361 def append(self, item):
362 self.items.append(item)
363
364 def getCompiler(self, strings, parent):
365 return self.compilerClass(self, strings, parent)
366
367
368class GlobalSubrsIndex(Index):
369
370 compilerClass = GlobalSubrsCompiler
371
jvr4e5af602002-05-24 09:58:04 +0000372 def __init__(self, file=None, globalSubrs=None, private=None, fdSelect=None, fdArray=None):
373 Index.__init__(self, file)
jvra2ad5442002-05-17 18:36:07 +0000374 self.globalSubrs = globalSubrs
375 self.private = private
jvred101512003-08-22 19:53:32 +0000376 if fdSelect:
377 self.fdSelect = fdSelect
378 if fdArray:
379 self.fdArray = fdArray
jvra2ad5442002-05-17 18:36:07 +0000380
381 def produceItem(self, index, data, file, offset, size):
382 if self.private is not None:
383 private = self.private
jvred101512003-08-22 19:53:32 +0000384 elif hasattr(self, 'fdArray') and self.fdArray is not None:
jvra2ad5442002-05-17 18:36:07 +0000385 private = self.fdArray[self.fdSelect[index]].Private
386 else:
387 private = None
jvr489d76a2003-08-24 19:56:16 +0000388 return psCharStrings.T2CharString(data, private=private, globalSubrs=self.globalSubrs)
jvr4756b3a2002-05-16 18:17:32 +0000389
390 def toXML(self, xmlWriter, progress):
jvred101512003-08-22 19:53:32 +0000391 xmlWriter.comment("The 'index' attribute is only for humans; it is ignored when parsed.")
jvr4e5af602002-05-24 09:58:04 +0000392 xmlWriter.newline()
jvr4756b3a2002-05-16 18:17:32 +0000393 for i in range(len(self)):
jvrb58176e2002-05-24 11:55:37 +0000394 subr = self[i]
395 if subr.needsDecompilation():
396 xmlWriter.begintag("CharString", index=i, raw=1)
397 else:
398 xmlWriter.begintag("CharString", index=i)
jvr4756b3a2002-05-16 18:17:32 +0000399 xmlWriter.newline()
jvrb58176e2002-05-24 11:55:37 +0000400 subr.toXML(xmlWriter)
jvr4756b3a2002-05-16 18:17:32 +0000401 xmlWriter.endtag("CharString")
402 xmlWriter.newline()
jvra2ad5442002-05-17 18:36:07 +0000403
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500404 def fromXML(self, name, attrs, content):
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500405 if name != "CharString":
jvr4e5af602002-05-24 09:58:04 +0000406 return
jvr489d76a2003-08-24 19:56:16 +0000407 subr = psCharStrings.T2CharString()
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500408 subr.fromXML(name, attrs, content)
jvr4e5af602002-05-24 09:58:04 +0000409 self.append(subr)
410
jvra2ad5442002-05-17 18:36:07 +0000411 def getItemAndSelector(self, index):
jvred101512003-08-22 19:53:32 +0000412 sel = None
413 if hasattr(self, 'fdSelect'):
414 sel = self.fdSelect[index]
jvra2ad5442002-05-17 18:36:07 +0000415 return self[index], sel
jvrf2cf9c52002-05-23 21:50:36 +0000416
jvre2ca9b52002-09-09 14:18:39 +0000417
jvrf2cf9c52002-05-23 21:50:36 +0000418class SubrsIndex(GlobalSubrsIndex):
419 compilerClass = SubrsCompiler
420
jvr4756b3a2002-05-16 18:17:32 +0000421
jvr767102e2002-05-17 07:06:32 +0000422class TopDictIndex(Index):
jvra2ad5442002-05-17 18:36:07 +0000423
jvrf2cf9c52002-05-23 21:50:36 +0000424 compilerClass = TopDictIndexCompiler
425
jvra2ad5442002-05-17 18:36:07 +0000426 def produceItem(self, index, data, file, offset, size):
jvr767102e2002-05-17 07:06:32 +0000427 top = TopDict(self.strings, file, offset, self.GlobalSubrs)
428 top.decompile(data)
429 return top
jvra2ad5442002-05-17 18:36:07 +0000430
431 def toXML(self, xmlWriter, progress):
432 for i in range(len(self)):
433 xmlWriter.begintag("FontDict", index=i)
434 xmlWriter.newline()
435 self[i].toXML(xmlWriter, progress)
436 xmlWriter.endtag("FontDict")
437 xmlWriter.newline()
jvr767102e2002-05-17 07:06:32 +0000438
439
jvred101512003-08-22 19:53:32 +0000440class FDArrayIndex(TopDictIndex):
441
442 compilerClass = FDArrayIndexCompiler
443
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500444 def fromXML(self, name, attrs, content):
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500445 if name != "FontDict":
jvred101512003-08-22 19:53:32 +0000446 return
447 fontDict = FontDict()
448 for element in content:
jvr2a9bcde2008-03-07 19:56:17 +0000449 if isinstance(element, basestring):
jvred101512003-08-22 19:53:32 +0000450 continue
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500451 name, attrs, content = element
452 fontDict.fromXML(name, attrs, content)
jvred101512003-08-22 19:53:32 +0000453 self.append(fontDict)
454
455
456class FDSelect:
457 def __init__(self, file = None, numGlyphs = None, format=None):
458 if file:
459 # read data in from file
460 self.format = readCard8(file)
461 if self.format == 0:
462 from array import array
463 self.gidArray = array("B", file.read(numGlyphs)).tolist()
464 elif self.format == 3:
465 gidArray = [None] * numGlyphs
466 nRanges = readCard16(file)
467 prev = None
468 for i in range(nRanges):
469 first = readCard16(file)
470 if prev is not None:
471 for glyphID in range(prev, first):
472 gidArray[glyphID] = fd
473 prev = first
474 fd = readCard8(file)
475 if prev is not None:
476 first = readCard16(file)
477 for glyphID in range(prev, first):
478 gidArray[glyphID] = fd
479 self.gidArray = gidArray
480 else:
481 assert 0, "unsupported FDSelect format: %s" % format
482 else:
483 # reading from XML. Make empty gidArray,, and leave format as passed in.
484 # format == None will result in the smallest representation being used.
485 self.format = format
486 self.gidArray = []
487
488
489 def __len__(self):
490 return len(self.gidArray)
491
492 def __getitem__(self, index):
493 return self.gidArray[index]
494
495 def __setitem__(self, index, fdSelectValue):
496 self.gidArray[index] = fdSelectValue
497
498 def append(self, fdSelectValue):
499 self.gidArray.append(fdSelectValue)
500
501
jvr4756b3a2002-05-16 18:17:32 +0000502class CharStrings:
503
jvra2ad5442002-05-17 18:36:07 +0000504 def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray):
jvr4e5af602002-05-24 09:58:04 +0000505 if file is not None:
506 self.charStringsIndex = SubrsIndex(file, globalSubrs, private, fdSelect, fdArray)
507 self.charStrings = charStrings = {}
508 for i in range(len(charset)):
509 charStrings[charset[i]] = i
510 self.charStringsAreIndexed = 1
511 else:
512 self.charStrings = {}
513 self.charStringsAreIndexed = 0
514 self.globalSubrs = globalSubrs
515 self.private = private
jvred101512003-08-22 19:53:32 +0000516 if fdSelect != None:
517 self.fdSelect = fdSelect
518 if fdArray!= None:
519 self.fdArray = fdArray
jvr4756b3a2002-05-16 18:17:32 +0000520
521 def keys(self):
Behdad Esfahbodc2297cd2013-11-27 06:26:55 -0500522 return list(self.charStrings.keys())
jvr4756b3a2002-05-16 18:17:32 +0000523
jvr016ca762002-05-16 18:38:03 +0000524 def values(self):
jvr4e5af602002-05-24 09:58:04 +0000525 if self.charStringsAreIndexed:
526 return self.charStringsIndex
527 else:
Behdad Esfahbodc2297cd2013-11-27 06:26:55 -0500528 return list(self.charStrings.values())
jvr016ca762002-05-16 18:38:03 +0000529
jvr4756b3a2002-05-16 18:17:32 +0000530 def has_key(self, name):
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -0500531 return name in self.charStrings
jvr4756b3a2002-05-16 18:17:32 +0000532
jvr767102e2002-05-17 07:06:32 +0000533 def __len__(self):
jvr4e5af602002-05-24 09:58:04 +0000534 return len(self.charStrings)
jvr767102e2002-05-17 07:06:32 +0000535
jvr4756b3a2002-05-16 18:17:32 +0000536 def __getitem__(self, name):
jvr4e5af602002-05-24 09:58:04 +0000537 charString = self.charStrings[name]
538 if self.charStringsAreIndexed:
539 charString = self.charStringsIndex[charString]
540 return charString
541
542 def __setitem__(self, name, charString):
543 if self.charStringsAreIndexed:
544 index = self.charStrings[name]
545 self.charStringsIndex[index] = charString
546 else:
547 self.charStrings[name] = charString
jvr4756b3a2002-05-16 18:17:32 +0000548
jvra2ad5442002-05-17 18:36:07 +0000549 def getItemAndSelector(self, name):
jvr4e5af602002-05-24 09:58:04 +0000550 if self.charStringsAreIndexed:
551 index = self.charStrings[name]
552 return self.charStringsIndex.getItemAndSelector(index)
553 else:
jvred101512003-08-22 19:53:32 +0000554 if hasattr(self, 'fdSelect'):
jvr91bca422012-10-18 12:49:22 +0000555 sel = self.fdSelect[index] # index is not defined at this point. Read R. ?
jvred101512003-08-22 19:53:32 +0000556 else:
557 raise KeyError("fdSelect array not yet defined.")
558 return self.charStrings[name], sel
jvra2ad5442002-05-17 18:36:07 +0000559
jvr4756b3a2002-05-16 18:17:32 +0000560 def toXML(self, xmlWriter, progress):
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500561 names = sorted(self.keys())
jvr7ce0a132002-07-23 16:42:11 +0000562 i = 0
563 step = 10
564 numGlyphs = len(names)
jvr4756b3a2002-05-16 18:17:32 +0000565 for name in names:
jvred101512003-08-22 19:53:32 +0000566 charStr, fdSelectIndex = self.getItemAndSelector(name)
jvrb58176e2002-05-24 11:55:37 +0000567 if charStr.needsDecompilation():
568 raw = [("raw", 1)]
569 else:
570 raw = []
jvred101512003-08-22 19:53:32 +0000571 if fdSelectIndex is None:
jvrb58176e2002-05-24 11:55:37 +0000572 xmlWriter.begintag("CharString", [('name', name)] + raw)
jvra2ad5442002-05-17 18:36:07 +0000573 else:
574 xmlWriter.begintag("CharString",
jvred101512003-08-22 19:53:32 +0000575 [('name', name), ('fdSelectIndex', fdSelectIndex)] + raw)
jvr4756b3a2002-05-16 18:17:32 +0000576 xmlWriter.newline()
jvrb58176e2002-05-24 11:55:37 +0000577 charStr.toXML(xmlWriter)
jvr4756b3a2002-05-16 18:17:32 +0000578 xmlWriter.endtag("CharString")
579 xmlWriter.newline()
jvr7ce0a132002-07-23 16:42:11 +0000580 if not i % step and progress is not None:
581 progress.setLabel("Dumping 'CFF ' table... (%s)" % name)
582 progress.increment(step / float(numGlyphs))
583 i = i + 1
jvr4e5af602002-05-24 09:58:04 +0000584
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500585 def fromXML(self, name, attrs, content):
jvr4e5af602002-05-24 09:58:04 +0000586 for element in content:
jvr2a9bcde2008-03-07 19:56:17 +0000587 if isinstance(element, basestring):
jvr4e5af602002-05-24 09:58:04 +0000588 continue
589 name, attrs, content = element
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500590 if name != "CharString":
jvr4e5af602002-05-24 09:58:04 +0000591 continue
jvred101512003-08-22 19:53:32 +0000592 fdID = -1
593 if hasattr(self, "fdArray"):
594 fdID = safeEval(attrs["fdSelectIndex"])
595 private = self.fdArray[fdID].Private
596 else:
597 private = self.private
598
jvr4e5af602002-05-24 09:58:04 +0000599 glyphName = attrs["name"]
jvr489d76a2003-08-24 19:56:16 +0000600 charString = psCharStrings.T2CharString(
601 private=private,
602 globalSubrs=self.globalSubrs)
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500603 charString.fromXML(name, attrs, content)
jvred101512003-08-22 19:53:32 +0000604 if fdID >= 0:
605 charString.fdSelectIndex = fdID
jvr4e5af602002-05-24 09:58:04 +0000606 self[glyphName] = charString
jvr4756b3a2002-05-16 18:17:32 +0000607
608
jvra2ad5442002-05-17 18:36:07 +0000609def readCard8(file):
610 return ord(file.read(1))
611
612def readCard16(file):
613 value, = struct.unpack(">H", file.read(2))
614 return value
615
jvrf2cf9c52002-05-23 21:50:36 +0000616def writeCard8(file, value):
Behdad Esfahbodb7a2d792013-11-27 15:19:40 -0500617 file.write(bytechr(value))
jvrf2cf9c52002-05-23 21:50:36 +0000618
619def writeCard16(file, value):
620 file.write(struct.pack(">H", value))
621
622def packCard8(value):
Behdad Esfahbodb7a2d792013-11-27 15:19:40 -0500623 return bytechr(value)
jvrf2cf9c52002-05-23 21:50:36 +0000624
625def packCard16(value):
626 return struct.pack(">H", value)
627
jvr4756b3a2002-05-16 18:17:32 +0000628def buildOperatorDict(table):
629 d = {}
630 for op, name, arg, default, conv in table:
631 d[op] = (name, arg)
632 return d
633
jvrf2cf9c52002-05-23 21:50:36 +0000634def buildOpcodeDict(table):
635 d = {}
636 for op, name, arg, default, conv in table:
jvr2a9bcde2008-03-07 19:56:17 +0000637 if isinstance(op, tuple):
Behdad Esfahbodb7a2d792013-11-27 15:19:40 -0500638 op = bytechr(op[0]) + bytechr(op[1])
jvrf2cf9c52002-05-23 21:50:36 +0000639 else:
Behdad Esfahbodb7a2d792013-11-27 15:19:40 -0500640 op = bytechr(op)
jvrf2cf9c52002-05-23 21:50:36 +0000641 d[name] = (op, arg)
642 return d
643
jvr4756b3a2002-05-16 18:17:32 +0000644def buildOrder(table):
645 l = []
646 for op, name, arg, default, conv in table:
647 l.append(name)
648 return l
649
650def buildDefaults(table):
651 d = {}
652 for op, name, arg, default, conv in table:
653 if default is not None:
654 d[name] = default
655 return d
656
657def buildConverters(table):
658 d = {}
659 for op, name, arg, default, conv in table:
660 d[name] = conv
661 return d
662
663
jvr4e5af602002-05-24 09:58:04 +0000664class SimpleConverter:
jvr7ce02ea2002-05-17 20:04:05 +0000665 def read(self, parent, value):
666 return value
jvrf2cf9c52002-05-23 21:50:36 +0000667 def write(self, parent, value):
668 return value
jvr7ce0a132002-07-23 16:42:11 +0000669 def xmlWrite(self, xmlWriter, name, value, progress):
jvr4e5af602002-05-24 09:58:04 +0000670 xmlWriter.simpletag(name, value=value)
671 xmlWriter.newline()
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500672 def xmlRead(self, name, attrs, content, parent):
jvr4e5af602002-05-24 09:58:04 +0000673 return attrs["value"]
674
jvre2ca9b52002-09-09 14:18:39 +0000675class Latin1Converter(SimpleConverter):
Behdad Esfahbod8c5c9662013-10-28 13:20:00 +0100676 def xmlWrite(self, xmlWriter, name, value, progress):
677 # Store as UTF-8
Behdad Esfahbod90057742013-11-27 13:58:09 -0500678 value = value.decode("latin-1").encode("utf-8")
Behdad Esfahbod8c5c9662013-10-28 13:20:00 +0100679 xmlWriter.simpletag(name, value=value)
680 xmlWriter.newline()
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500681 def xmlRead(self, name, attrs, content, parent):
Behdad Esfahbod90057742013-11-27 13:58:09 -0500682 return attrs["value"].decode("utf-8").encode("latin-1")
jvre2ca9b52002-09-09 14:18:39 +0000683
684
jvr4e5af602002-05-24 09:58:04 +0000685def parseNum(s):
686 try:
687 value = int(s)
688 except:
689 value = float(s)
690 return value
691
692class NumberConverter(SimpleConverter):
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500693 def xmlRead(self, name, attrs, content, parent):
jvr4e5af602002-05-24 09:58:04 +0000694 return parseNum(attrs["value"])
695
696class ArrayConverter(SimpleConverter):
jvr7ce0a132002-07-23 16:42:11 +0000697 def xmlWrite(self, xmlWriter, name, value, progress):
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500698 value = " ".join(map(str, value))
699 xmlWriter.simpletag(name, value=value)
jvr4e5af602002-05-24 09:58:04 +0000700 xmlWriter.newline()
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500701 def xmlRead(self, name, attrs, content, parent):
jvr4e5af602002-05-24 09:58:04 +0000702 values = attrs["value"].split()
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500703 return [parseNum(value) for value in values]
jvr4e5af602002-05-24 09:58:04 +0000704
705class TableConverter(SimpleConverter):
jvr7ce0a132002-07-23 16:42:11 +0000706 def xmlWrite(self, xmlWriter, name, value, progress):
jvra2ad5442002-05-17 18:36:07 +0000707 xmlWriter.begintag(name)
708 xmlWriter.newline()
jvr7ce0a132002-07-23 16:42:11 +0000709 value.toXML(xmlWriter, progress)
jvra2ad5442002-05-17 18:36:07 +0000710 xmlWriter.endtag(name)
711 xmlWriter.newline()
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500712 def xmlRead(self, name, attrs, content, parent):
jvr4e5af602002-05-24 09:58:04 +0000713 ob = self.getClass()()
714 for element in content:
jvr2a9bcde2008-03-07 19:56:17 +0000715 if isinstance(element, basestring):
jvr4e5af602002-05-24 09:58:04 +0000716 continue
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500717 name, attrs, content = element
718 ob.fromXML(name, attrs, content)
jvr4e5af602002-05-24 09:58:04 +0000719 return ob
jvra2ad5442002-05-17 18:36:07 +0000720
jvr4e5af602002-05-24 09:58:04 +0000721class PrivateDictConverter(TableConverter):
722 def getClass(self):
723 return PrivateDict
jvr4756b3a2002-05-16 18:17:32 +0000724 def read(self, parent, value):
725 size, offset = value
726 file = parent.file
jvr4e5af602002-05-24 09:58:04 +0000727 priv = PrivateDict(parent.strings, file, offset)
jvr4756b3a2002-05-16 18:17:32 +0000728 file.seek(offset)
729 data = file.read(size)
730 len(data) == size
jvr4e5af602002-05-24 09:58:04 +0000731 priv.decompile(data)
732 return priv
jvrf2cf9c52002-05-23 21:50:36 +0000733 def write(self, parent, value):
734 return (0, 0) # dummy value
jvr4756b3a2002-05-16 18:17:32 +0000735
jvr4e5af602002-05-24 09:58:04 +0000736class SubrsConverter(TableConverter):
737 def getClass(self):
738 return SubrsIndex
jvr4756b3a2002-05-16 18:17:32 +0000739 def read(self, parent, value):
740 file = parent.file
741 file.seek(parent.offset + value) # Offset(self)
jvr4e5af602002-05-24 09:58:04 +0000742 return SubrsIndex(file)
jvrf2cf9c52002-05-23 21:50:36 +0000743 def write(self, parent, value):
744 return 0 # dummy value
jvr4756b3a2002-05-16 18:17:32 +0000745
jvr4e5af602002-05-24 09:58:04 +0000746class CharStringsConverter(TableConverter):
jvr4756b3a2002-05-16 18:17:32 +0000747 def read(self, parent, value):
748 file = parent.file
jvr767102e2002-05-17 07:06:32 +0000749 charset = parent.charset
jvra2ad5442002-05-17 18:36:07 +0000750 globalSubrs = parent.GlobalSubrs
751 if hasattr(parent, "ROS"):
752 fdSelect, fdArray = parent.FDSelect, parent.FDArray
753 private = None
754 else:
755 fdSelect, fdArray = None, None
756 private = parent.Private
jvr4756b3a2002-05-16 18:17:32 +0000757 file.seek(value) # Offset(0)
jvra2ad5442002-05-17 18:36:07 +0000758 return CharStrings(file, charset, globalSubrs, private, fdSelect, fdArray)
jvrf2cf9c52002-05-23 21:50:36 +0000759 def write(self, parent, value):
760 return 0 # dummy value
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500761 def xmlRead(self, name, attrs, content, parent):
jvred101512003-08-22 19:53:32 +0000762 if hasattr(parent, "ROS"):
763 # if it is a CID-keyed font, then the private Dict is extracted from the parent.FDArray
764 private, fdSelect, fdArray = None, parent.FDSelect, parent.FDArray
765 else:
766 # if it is a name-keyed font, then the private dict is in the top dict, and there is no fdArray.
767 private, fdSelect, fdArray = parent.Private, None, None
768 charStrings = CharStrings(None, None, parent.GlobalSubrs, private, fdSelect, fdArray)
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500769 charStrings.fromXML(name, attrs, content)
jvr4e5af602002-05-24 09:58:04 +0000770 return charStrings
jvr4756b3a2002-05-16 18:17:32 +0000771
772class CharsetConverter:
773 def read(self, parent, value):
774 isCID = hasattr(parent, "ROS")
775 if value > 2:
776 numGlyphs = parent.numGlyphs
777 file = parent.file
778 file.seek(value)
jvrf2cf9c52002-05-23 21:50:36 +0000779 if DEBUG:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -0500780 print("loading charset at %s" % value)
jvra2ad5442002-05-17 18:36:07 +0000781 format = readCard8(file)
Just7842e561999-12-16 21:34:53 +0000782 if format == 0:
jvrc60a44f2006-10-21 13:41:18 +0000783 charset = parseCharset0(numGlyphs, file, parent.strings, isCID)
jvr4756b3a2002-05-16 18:17:32 +0000784 elif format == 1 or format == 2:
785 charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
Just7842e561999-12-16 21:34:53 +0000786 else:
jvr1890b952002-05-15 07:41:30 +0000787 raise NotImplementedError
jvr4756b3a2002-05-16 18:17:32 +0000788 assert len(charset) == numGlyphs
jvrf2cf9c52002-05-23 21:50:36 +0000789 if DEBUG:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -0500790 print(" charset end at %s" % file.tell())
jvrc60a44f2006-10-21 13:41:18 +0000791 else: # offset == 0 -> no charset data.
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -0500792 if isCID or "CharStrings" not in parent.rawDict:
jvrc60a44f2006-10-21 13:41:18 +0000793 assert value == 0 # We get here only when processing fontDicts from the FDArray of CFF-CID fonts. Only the real topDict references the chrset.
jvr4756b3a2002-05-16 18:17:32 +0000794 charset = None
795 elif value == 0:
jvrc60a44f2006-10-21 13:41:18 +0000796 charset = cffISOAdobeStrings
jvr4756b3a2002-05-16 18:17:32 +0000797 elif value == 1:
jvrc60a44f2006-10-21 13:41:18 +0000798 charset = cffIExpertStrings
jvr4756b3a2002-05-16 18:17:32 +0000799 elif value == 2:
jvrc60a44f2006-10-21 13:41:18 +0000800 charset = cffExpertSubsetStrings
jvr4756b3a2002-05-16 18:17:32 +0000801 return charset
jvrc60a44f2006-10-21 13:41:18 +0000802
jvrf2cf9c52002-05-23 21:50:36 +0000803 def write(self, parent, value):
804 return 0 # dummy value
jvr7ce0a132002-07-23 16:42:11 +0000805 def xmlWrite(self, xmlWriter, name, value, progress):
jvr4e5af602002-05-24 09:58:04 +0000806 # XXX only write charset when not in OT/TTX context, where we
807 # dump charset as a separate "GlyphOrder" table.
808 ##xmlWriter.simpletag("charset")
809 xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element")
jvr4756b3a2002-05-16 18:17:32 +0000810 xmlWriter.newline()
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500811 def xmlRead(self, name, attrs, content, parent):
jvr4e5af602002-05-24 09:58:04 +0000812 if 0:
813 return safeEval(attrs["value"])
jvr4756b3a2002-05-16 18:17:32 +0000814
815
jvrf2cf9c52002-05-23 21:50:36 +0000816class CharsetCompiler:
817
818 def __init__(self, strings, charset, parent):
819 assert charset[0] == '.notdef'
jvred101512003-08-22 19:53:32 +0000820 isCID = hasattr(parent.dictObj, "ROS")
821 data0 = packCharset0(charset, isCID, strings)
822 data = packCharset(charset, isCID, strings)
jvr6004baf2002-05-24 10:35:13 +0000823 if len(data) < len(data0):
824 self.data = data
825 else:
826 self.data = data0
jvrf2cf9c52002-05-23 21:50:36 +0000827 self.parent = parent
828
jvr4e5af602002-05-24 09:58:04 +0000829 def setPos(self, pos, endPos):
jvrf2cf9c52002-05-23 21:50:36 +0000830 self.parent.rawDict["charset"] = pos
831
832 def getDataLength(self):
833 return len(self.data)
834
835 def toFile(self, file):
836 file.write(self.data)
837
838
jvred101512003-08-22 19:53:32 +0000839def getCIDfromName(name, strings):
840 return int(name[3:])
841
842def getSIDfromName(name, strings):
843 return strings.getSID(name)
844
845def packCharset0(charset, isCID, strings):
jvr6004baf2002-05-24 10:35:13 +0000846 format = 0
847 data = [packCard8(format)]
jvred101512003-08-22 19:53:32 +0000848 if isCID:
849 getNameID = getCIDfromName
850 else:
851 getNameID = getSIDfromName
852
jvr6004baf2002-05-24 10:35:13 +0000853 for name in charset[1:]:
jvred101512003-08-22 19:53:32 +0000854 data.append(packCard16(getNameID(name,strings)))
jvr6004baf2002-05-24 10:35:13 +0000855 return "".join(data)
856
jvred101512003-08-22 19:53:32 +0000857
858def packCharset(charset, isCID, strings):
jvr6004baf2002-05-24 10:35:13 +0000859 format = 1
jvr6004baf2002-05-24 10:35:13 +0000860 ranges = []
861 first = None
862 end = 0
jvred101512003-08-22 19:53:32 +0000863 if isCID:
864 getNameID = getCIDfromName
865 else:
866 getNameID = getSIDfromName
867
jvr6004baf2002-05-24 10:35:13 +0000868 for name in charset[1:]:
jvred101512003-08-22 19:53:32 +0000869 SID = getNameID(name, strings)
jvr6004baf2002-05-24 10:35:13 +0000870 if first is None:
871 first = SID
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500872 elif end + 1 != SID:
jvr6004baf2002-05-24 10:35:13 +0000873 nLeft = end - first
874 if nLeft > 255:
875 format = 2
876 ranges.append((first, nLeft))
877 first = SID
878 end = SID
879 nLeft = end - first
880 if nLeft > 255:
881 format = 2
882 ranges.append((first, nLeft))
883
jvr74cd1ef2002-05-24 10:38:04 +0000884 data = [packCard8(format)]
jvr6004baf2002-05-24 10:35:13 +0000885 if format == 1:
886 nLeftFunc = packCard8
887 else:
888 nLeftFunc = packCard16
889 for first, nLeft in ranges:
890 data.append(packCard16(first) + nLeftFunc(nLeft))
jvrb58176e2002-05-24 11:55:37 +0000891 return "".join(data)
jvr6004baf2002-05-24 10:35:13 +0000892
jvrc60a44f2006-10-21 13:41:18 +0000893def parseCharset0(numGlyphs, file, strings, isCID):
jvrf2cf9c52002-05-23 21:50:36 +0000894 charset = [".notdef"]
jvrc60a44f2006-10-21 13:41:18 +0000895 if isCID:
896 for i in range(numGlyphs - 1):
897 CID = readCard16(file)
Behdad Esfahbod14fb0312013-11-27 05:47:34 -0500898 charset.append("cid" + str(CID).zfill(5))
jvrc60a44f2006-10-21 13:41:18 +0000899 else:
900 for i in range(numGlyphs - 1):
901 SID = readCard16(file)
902 charset.append(strings[SID])
jvrf2cf9c52002-05-23 21:50:36 +0000903 return charset
904
jvr4756b3a2002-05-16 18:17:32 +0000905def parseCharset(numGlyphs, file, strings, isCID, format):
906 charset = ['.notdef']
907 count = 1
908 if format == 1:
jvra2ad5442002-05-17 18:36:07 +0000909 nLeftFunc = readCard8
jvr4756b3a2002-05-16 18:17:32 +0000910 else:
jvra2ad5442002-05-17 18:36:07 +0000911 nLeftFunc = readCard16
jvr4756b3a2002-05-16 18:17:32 +0000912 while count < numGlyphs:
jvra2ad5442002-05-17 18:36:07 +0000913 first = readCard16(file)
jvr4756b3a2002-05-16 18:17:32 +0000914 nLeft = nLeftFunc(file)
915 if isCID:
916 for CID in range(first, first+nLeft+1):
Behdad Esfahbod14fb0312013-11-27 05:47:34 -0500917 charset.append("cid" + str(CID).zfill(5))
jvr1890b952002-05-15 07:41:30 +0000918 else:
jvr4756b3a2002-05-16 18:17:32 +0000919 for SID in range(first, first+nLeft+1):
920 charset.append(strings[SID])
921 count = count + nLeft + 1
922 return charset
923
924
jvrb9702ba2003-01-03 20:56:01 +0000925class EncodingCompiler:
926
927 def __init__(self, strings, encoding, parent):
jvr2a9bcde2008-03-07 19:56:17 +0000928 assert not isinstance(encoding, basestring)
jvrb9702ba2003-01-03 20:56:01 +0000929 data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings)
930 data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings)
931 if len(data0) < len(data1):
932 self.data = data0
933 else:
934 self.data = data1
935 self.parent = parent
936
937 def setPos(self, pos, endPos):
938 self.parent.rawDict["Encoding"] = pos
939
940 def getDataLength(self):
941 return len(self.data)
942
943 def toFile(self, file):
944 file.write(self.data)
945
946
947class EncodingConverter(SimpleConverter):
948
949 def read(self, parent, value):
950 if value == 0:
951 return "StandardEncoding"
952 elif value == 1:
953 return "ExpertEncoding"
954 else:
955 assert value > 1
956 file = parent.file
957 file.seek(value)
958 if DEBUG:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -0500959 print("loading Encoding at %s" % value)
jvrb9702ba2003-01-03 20:56:01 +0000960 format = readCard8(file)
961 haveSupplement = format & 0x80
962 if haveSupplement:
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -0500963 raise NotImplementedError("Encoding supplements are not yet supported")
jvrb9702ba2003-01-03 20:56:01 +0000964 format = format & 0x7f
965 if format == 0:
966 encoding = parseEncoding0(parent.charset, file, haveSupplement,
967 parent.strings)
968 elif format == 1:
969 encoding = parseEncoding1(parent.charset, file, haveSupplement,
970 parent.strings)
971 return encoding
972
973 def write(self, parent, value):
974 if value == "StandardEncoding":
975 return 0
976 elif value == "ExpertEncoding":
977 return 1
978 return 0 # dummy value
979
980 def xmlWrite(self, xmlWriter, name, value, progress):
981 if value in ("StandardEncoding", "ExpertEncoding"):
982 xmlWriter.simpletag(name, name=value)
983 xmlWriter.newline()
984 return
985 xmlWriter.begintag(name)
986 xmlWriter.newline()
987 for code in range(len(value)):
988 glyphName = value[code]
989 if glyphName != ".notdef":
990 xmlWriter.simpletag("map", code=hex(code), name=glyphName)
991 xmlWriter.newline()
992 xmlWriter.endtag(name)
993 xmlWriter.newline()
994
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500995 def xmlRead(self, name, attrs, content, parent):
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -0500996 if "name" in attrs:
jvrb9702ba2003-01-03 20:56:01 +0000997 return attrs["name"]
998 encoding = [".notdef"] * 256
999 for element in content:
jvr2a9bcde2008-03-07 19:56:17 +00001000 if isinstance(element, basestring):
jvrb9702ba2003-01-03 20:56:01 +00001001 continue
1002 name, attrs, content = element
1003 code = safeEval(attrs["code"])
1004 glyphName = attrs["name"]
1005 encoding[code] = glyphName
1006 return encoding
1007
1008
1009def parseEncoding0(charset, file, haveSupplement, strings):
1010 nCodes = readCard8(file)
1011 encoding = [".notdef"] * 256
1012 for glyphID in range(1, nCodes + 1):
1013 code = readCard8(file)
1014 if code != 0:
1015 encoding[code] = charset[glyphID]
1016 return encoding
1017
1018def parseEncoding1(charset, file, haveSupplement, strings):
1019 nRanges = readCard8(file)
1020 encoding = [".notdef"] * 256
1021 glyphID = 1
1022 for i in range(nRanges):
1023 code = readCard8(file)
1024 nLeft = readCard8(file)
1025 for glyphID in range(glyphID, glyphID + nLeft + 1):
1026 encoding[code] = charset[glyphID]
1027 code = code + 1
1028 glyphID = glyphID + 1
1029 return encoding
1030
1031def packEncoding0(charset, encoding, strings):
1032 format = 0
1033 m = {}
1034 for code in range(len(encoding)):
1035 name = encoding[code]
1036 if name != ".notdef":
1037 m[name] = code
1038 codes = []
1039 for name in charset[1:]:
1040 code = m.get(name)
1041 codes.append(code)
1042
1043 while codes and codes[-1] is None:
1044 codes.pop()
1045
1046 data = [packCard8(format), packCard8(len(codes))]
1047 for code in codes:
1048 if code is None:
1049 code = 0
1050 data.append(packCard8(code))
1051 return "".join(data)
1052
1053def packEncoding1(charset, encoding, strings):
1054 format = 1
1055 m = {}
1056 for code in range(len(encoding)):
1057 name = encoding[code]
1058 if name != ".notdef":
1059 m[name] = code
1060 ranges = []
1061 first = None
1062 end = 0
1063 for name in charset[1:]:
1064 code = m.get(name, -1)
1065 if first is None:
1066 first = code
Behdad Esfahbod180ace62013-11-27 02:40:30 -05001067 elif end + 1 != code:
jvrb9702ba2003-01-03 20:56:01 +00001068 nLeft = end - first
1069 ranges.append((first, nLeft))
1070 first = code
1071 end = code
1072 nLeft = end - first
1073 ranges.append((first, nLeft))
1074
1075 # remove unencoded glyphs at the end.
1076 while ranges and ranges[-1][0] == -1:
1077 ranges.pop()
1078
1079 data = [packCard8(format), packCard8(len(ranges))]
1080 for first, nLeft in ranges:
1081 if first == -1: # unencoded
1082 first = 0
1083 data.append(packCard8(first) + packCard8(nLeft))
1084 return "".join(data)
1085
1086
jvr4e5af602002-05-24 09:58:04 +00001087class FDArrayConverter(TableConverter):
jvred101512003-08-22 19:53:32 +00001088
jvra2ad5442002-05-17 18:36:07 +00001089 def read(self, parent, value):
1090 file = parent.file
1091 file.seek(value)
jvred101512003-08-22 19:53:32 +00001092 fdArray = FDArrayIndex(file)
jvra2ad5442002-05-17 18:36:07 +00001093 fdArray.strings = parent.strings
1094 fdArray.GlobalSubrs = parent.GlobalSubrs
1095 return fdArray
1096
jvred101512003-08-22 19:53:32 +00001097 def write(self, parent, value):
1098 return 0 # dummy value
1099
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -05001100 def xmlRead(self, name, attrs, content, parent):
jvred101512003-08-22 19:53:32 +00001101 fdArray = FDArrayIndex()
1102 for element in content:
jvr2a9bcde2008-03-07 19:56:17 +00001103 if isinstance(element, basestring):
jvred101512003-08-22 19:53:32 +00001104 continue
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -05001105 name, attrs, content = element
1106 fdArray.fromXML(name, attrs, content)
jvred101512003-08-22 19:53:32 +00001107 return fdArray
1108
jvra2ad5442002-05-17 18:36:07 +00001109
1110class FDSelectConverter:
jvred101512003-08-22 19:53:32 +00001111
jvra2ad5442002-05-17 18:36:07 +00001112 def read(self, parent, value):
1113 file = parent.file
1114 file.seek(value)
jvred101512003-08-22 19:53:32 +00001115 fdSelect = FDSelect(file, parent.numGlyphs)
1116 return fdSelect
1117
1118 def write(self, parent, value):
1119 return 0 # dummy value
1120
1121 # The FDSelect glyph data is written out to XML in the charstring keys,
1122 # so we write out only the format selector
jvr7ce0a132002-07-23 16:42:11 +00001123 def xmlWrite(self, xmlWriter, name, value, progress):
jvred101512003-08-22 19:53:32 +00001124 xmlWriter.simpletag(name, [('format', value.format)])
1125 xmlWriter.newline()
1126
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -05001127 def xmlRead(self, name, attrs, content, parent):
jvred101512003-08-22 19:53:32 +00001128 format = safeEval(attrs["format"])
1129 file = None
1130 numGlyphs = None
1131 fdSelect = FDSelect(file, numGlyphs, format)
1132 return fdSelect
1133
1134
1135def packFDSelect0(fdSelectArray):
1136 format = 0
1137 data = [packCard8(format)]
1138 for index in fdSelectArray:
1139 data.append(packCard8(index))
1140 return "".join(data)
1141
1142
1143def packFDSelect3(fdSelectArray):
1144 format = 3
1145 fdRanges = []
1146 first = None
1147 end = 0
1148 lenArray = len(fdSelectArray)
1149 lastFDIndex = -1
1150 for i in range(lenArray):
1151 fdIndex = fdSelectArray[i]
1152 if lastFDIndex != fdIndex:
1153 fdRanges.append([i, fdIndex])
1154 lastFDIndex = fdIndex
1155 sentinelGID = i + 1
1156
1157 data = [packCard8(format)]
1158 data.append(packCard16( len(fdRanges) ))
1159 for fdRange in fdRanges:
1160 data.append(packCard16(fdRange[0]))
1161 data.append(packCard8(fdRange[1]))
1162 data.append(packCard16(sentinelGID))
1163 return "".join(data)
1164
1165
1166class FDSelectCompiler:
1167
1168 def __init__(self, fdSelect, parent):
1169 format = fdSelect.format
1170 fdSelectArray = fdSelect.gidArray
1171 if format == 0:
1172 self.data = packFDSelect0(fdSelectArray)
1173 elif format == 3:
1174 self.data = packFDSelect3(fdSelectArray)
1175 else:
1176 # choose smaller of the two formats
1177 data0 = packFDSelect0(fdSelectArray)
1178 data3 = packFDSelect3(fdSelectArray)
1179 if len(data0) < len(data3):
1180 self.data = data0
1181 fdSelect.format = 0
1182 else:
1183 self.data = data3
1184 fdSelect.format = 3
1185
1186 self.parent = parent
1187
1188 def setPos(self, pos, endPos):
1189 self.parent.rawDict["FDSelect"] = pos
1190
1191 def getDataLength(self):
1192 return len(self.data)
1193
1194 def toFile(self, file):
1195 file.write(self.data)
jvra2ad5442002-05-17 18:36:07 +00001196
1197
jvr4e5af602002-05-24 09:58:04 +00001198class ROSConverter(SimpleConverter):
jvred101512003-08-22 19:53:32 +00001199
jvr7ce0a132002-07-23 16:42:11 +00001200 def xmlWrite(self, xmlWriter, name, value, progress):
jvr155aa752002-05-17 19:58:49 +00001201 registry, order, supplement = value
jvrf2cf9c52002-05-23 21:50:36 +00001202 xmlWriter.simpletag(name, [('Registry', registry), ('Order', order),
1203 ('Supplement', supplement)])
jvr4afb2572002-05-18 20:07:01 +00001204 xmlWriter.newline()
jvr155aa752002-05-17 19:58:49 +00001205
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -05001206 def xmlRead(self, name, attrs, content, parent):
jvred101512003-08-22 19:53:32 +00001207 return (attrs['Registry'], attrs['Order'], safeEval(attrs['Supplement']))
1208
1209
jvr155aa752002-05-17 19:58:49 +00001210
jvr4756b3a2002-05-16 18:17:32 +00001211topDictOperators = [
1212# opcode name argument type default converter
jvr155aa752002-05-17 19:58:49 +00001213 ((12, 30), 'ROS', ('SID','SID','number'), None, ROSConverter()),
jvrf2cf9c52002-05-23 21:50:36 +00001214 ((12, 20), 'SyntheticBase', 'number', None, None),
jvr4756b3a2002-05-16 18:17:32 +00001215 (0, 'version', 'SID', None, None),
jvre2ca9b52002-09-09 14:18:39 +00001216 (1, 'Notice', 'SID', None, Latin1Converter()),
1217 ((12, 0), 'Copyright', 'SID', None, Latin1Converter()),
jvr4756b3a2002-05-16 18:17:32 +00001218 (2, 'FullName', 'SID', None, None),
jvr155aa752002-05-17 19:58:49 +00001219 ((12, 38), 'FontName', 'SID', None, None),
jvr4756b3a2002-05-16 18:17:32 +00001220 (3, 'FamilyName', 'SID', None, None),
1221 (4, 'Weight', 'SID', None, None),
1222 ((12, 1), 'isFixedPitch', 'number', 0, None),
1223 ((12, 2), 'ItalicAngle', 'number', 0, None),
1224 ((12, 3), 'UnderlinePosition', 'number', None, None),
1225 ((12, 4), 'UnderlineThickness', 'number', 50, None),
1226 ((12, 5), 'PaintType', 'number', 0, None),
1227 ((12, 6), 'CharstringType', 'number', 2, None),
1228 ((12, 7), 'FontMatrix', 'array', [0.001,0,0,0.001,0,0], None),
1229 (13, 'UniqueID', 'number', None, None),
1230 (5, 'FontBBox', 'array', [0,0,0,0], None),
1231 ((12, 8), 'StrokeWidth', 'number', 0, None),
1232 (14, 'XUID', 'array', None, None),
jvr4756b3a2002-05-16 18:17:32 +00001233 ((12, 21), 'PostScript', 'SID', None, None),
1234 ((12, 22), 'BaseFontName', 'SID', None, None),
1235 ((12, 23), 'BaseFontBlend', 'delta', None, None),
jvr4756b3a2002-05-16 18:17:32 +00001236 ((12, 31), 'CIDFontVersion', 'number', 0, None),
1237 ((12, 32), 'CIDFontRevision', 'number', 0, None),
1238 ((12, 33), 'CIDFontType', 'number', 0, None),
1239 ((12, 34), 'CIDCount', 'number', 8720, None),
jvred101512003-08-22 19:53:32 +00001240 (15, 'charset', 'number', 0, CharsetConverter()),
jvr4756b3a2002-05-16 18:17:32 +00001241 ((12, 35), 'UIDBase', 'number', None, None),
jvrb9702ba2003-01-03 20:56:01 +00001242 (16, 'Encoding', 'number', 0, EncodingConverter()),
jvr155aa752002-05-17 19:58:49 +00001243 (18, 'Private', ('number','number'), None, PrivateDictConverter()),
jvred101512003-08-22 19:53:32 +00001244 ((12, 37), 'FDSelect', 'number', None, FDSelectConverter()),
1245 ((12, 36), 'FDArray', 'number', None, FDArrayConverter()),
jvr155aa752002-05-17 19:58:49 +00001246 (17, 'CharStrings', 'number', None, CharStringsConverter()),
jvr4756b3a2002-05-16 18:17:32 +00001247]
1248
jvred101512003-08-22 19:53:32 +00001249# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order,
1250# in order for the font to compile back from xml.
1251
1252
jvr4756b3a2002-05-16 18:17:32 +00001253privateDictOperators = [
1254# opcode name argument type default converter
1255 (6, 'BlueValues', 'delta', None, None),
1256 (7, 'OtherBlues', 'delta', None, None),
1257 (8, 'FamilyBlues', 'delta', None, None),
1258 (9, 'FamilyOtherBlues', 'delta', None, None),
1259 ((12, 9), 'BlueScale', 'number', 0.039625, None),
1260 ((12, 10), 'BlueShift', 'number', 7, None),
1261 ((12, 11), 'BlueFuzz', 'number', 1, None),
1262 (10, 'StdHW', 'number', None, None),
1263 (11, 'StdVW', 'number', None, None),
1264 ((12, 12), 'StemSnapH', 'delta', None, None),
1265 ((12, 13), 'StemSnapV', 'delta', None, None),
1266 ((12, 14), 'ForceBold', 'number', 0, None),
jvrf2cf9c52002-05-23 21:50:36 +00001267 ((12, 15), 'ForceBoldThreshold', 'number', None, None), # deprecated
1268 ((12, 16), 'lenIV', 'number', None, None), # deprecated
jvr4756b3a2002-05-16 18:17:32 +00001269 ((12, 17), 'LanguageGroup', 'number', 0, None),
1270 ((12, 18), 'ExpansionFactor', 'number', 0.06, None),
1271 ((12, 19), 'initialRandomSeed', 'number', 0, None),
1272 (20, 'defaultWidthX', 'number', 0, None),
1273 (21, 'nominalWidthX', 'number', 0, None),
1274 (19, 'Subrs', 'number', None, SubrsConverter()),
1275]
1276
jvr4e5af602002-05-24 09:58:04 +00001277def addConverters(table):
1278 for i in range(len(table)):
1279 op, name, arg, default, conv = table[i]
1280 if conv is not None:
1281 continue
1282 if arg in ("delta", "array"):
1283 conv = ArrayConverter()
1284 elif arg == "number":
1285 conv = NumberConverter()
1286 elif arg == "SID":
1287 conv = SimpleConverter()
1288 else:
1289 assert 0
1290 table[i] = op, name, arg, default, conv
1291
1292addConverters(privateDictOperators)
1293addConverters(topDictOperators)
1294
jvr4756b3a2002-05-16 18:17:32 +00001295
1296class TopDictDecompiler(psCharStrings.DictDecompiler):
1297 operators = buildOperatorDict(topDictOperators)
1298
1299
1300class PrivateDictDecompiler(psCharStrings.DictDecompiler):
1301 operators = buildOperatorDict(privateDictOperators)
1302
1303
jvrf2cf9c52002-05-23 21:50:36 +00001304class DictCompiler:
1305
1306 def __init__(self, dictObj, strings, parent):
1307 assert isinstance(strings, IndexedStrings)
1308 self.dictObj = dictObj
1309 self.strings = strings
1310 self.parent = parent
1311 rawDict = {}
1312 for name in dictObj.order:
1313 value = getattr(dictObj, name, None)
1314 if value is None:
1315 continue
1316 conv = dictObj.converters[name]
jvr4e5af602002-05-24 09:58:04 +00001317 value = conv.write(dictObj, value)
jvrf2cf9c52002-05-23 21:50:36 +00001318 if value == dictObj.defaults.get(name):
1319 continue
1320 rawDict[name] = value
1321 self.rawDict = rawDict
1322
jvr4e5af602002-05-24 09:58:04 +00001323 def setPos(self, pos, endPos):
jvrf2cf9c52002-05-23 21:50:36 +00001324 pass
1325
1326 def getDataLength(self):
jvr4e5af602002-05-24 09:58:04 +00001327 return len(self.compile("getDataLength"))
jvrf2cf9c52002-05-23 21:50:36 +00001328
jvr4e5af602002-05-24 09:58:04 +00001329 def compile(self, reason):
1330 if DEBUG:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -05001331 print("-- compiling %s for %s" % (self.__class__.__name__, reason))
1332 print("in baseDict: ", self)
jvrf2cf9c52002-05-23 21:50:36 +00001333 rawDict = self.rawDict
1334 data = []
1335 for name in self.dictObj.order:
1336 value = rawDict.get(name)
1337 if value is None:
1338 continue
1339 op, argType = self.opcodes[name]
jvr2a9bcde2008-03-07 19:56:17 +00001340 if isinstance(argType, tuple):
jvrf2cf9c52002-05-23 21:50:36 +00001341 l = len(argType)
1342 assert len(value) == l, "value doesn't match arg type"
1343 for i in range(l):
jvred101512003-08-22 19:53:32 +00001344 arg = argType[i]
jvrf2cf9c52002-05-23 21:50:36 +00001345 v = value[i]
1346 arghandler = getattr(self, "arg_" + arg)
1347 data.append(arghandler(v))
1348 else:
1349 arghandler = getattr(self, "arg_" + argType)
1350 data.append(arghandler(value))
1351 data.append(op)
1352 return "".join(data)
1353
1354 def toFile(self, file):
jvr4e5af602002-05-24 09:58:04 +00001355 file.write(self.compile("toFile"))
jvrf2cf9c52002-05-23 21:50:36 +00001356
1357 def arg_number(self, num):
1358 return encodeNumber(num)
1359 def arg_SID(self, s):
1360 return psCharStrings.encodeIntCFF(self.strings.getSID(s))
1361 def arg_array(self, value):
1362 data = []
1363 for num in value:
1364 data.append(encodeNumber(num))
1365 return "".join(data)
1366 def arg_delta(self, value):
1367 out = []
1368 last = 0
1369 for v in value:
1370 out.append(v - last)
1371 last = v
1372 data = []
1373 for num in out:
1374 data.append(encodeNumber(num))
1375 return "".join(data)
1376
1377
1378def encodeNumber(num):
jvrf6ff48b2008-03-07 19:49:25 +00001379 if isinstance(num, float):
jvrf2cf9c52002-05-23 21:50:36 +00001380 return psCharStrings.encodeFloat(num)
1381 else:
1382 return psCharStrings.encodeIntCFF(num)
1383
1384
1385class TopDictCompiler(DictCompiler):
1386
1387 opcodes = buildOpcodeDict(topDictOperators)
1388
1389 def getChildren(self, strings):
1390 children = []
jvred101512003-08-22 19:53:32 +00001391 if hasattr(self.dictObj, "charset") and self.dictObj.charset:
jvrf2cf9c52002-05-23 21:50:36 +00001392 children.append(CharsetCompiler(strings, self.dictObj.charset, self))
jvrb9702ba2003-01-03 20:56:01 +00001393 if hasattr(self.dictObj, "Encoding"):
1394 encoding = self.dictObj.Encoding
jvr2a9bcde2008-03-07 19:56:17 +00001395 if not isinstance(encoding, basestring):
jvrb9702ba2003-01-03 20:56:01 +00001396 children.append(EncodingCompiler(strings, encoding, self))
jvrce522412003-08-25 07:37:25 +00001397 if hasattr(self.dictObj, "FDSelect"):
jvred101512003-08-22 19:53:32 +00001398 # I have not yet supported merging a ttx CFF-CID font, as there are interesting
1399 # issues about merging the FDArrays. Here I assume that
1400 # either the font was read from XML, and teh FDSelect indices are all
1401 # in the charstring data, or the FDSelect array is already fully defined.
1402 fdSelect = self.dictObj.FDSelect
1403 if len(fdSelect) == 0: # probably read in from XML; assume fdIndex in CharString data
1404 charStrings = self.dictObj.CharStrings
1405 for name in self.dictObj.charset:
1406 charstring = charStrings[name]
1407 fdSelect.append(charStrings[name].fdSelectIndex)
1408 fdSelectComp = FDSelectCompiler(fdSelect, self)
1409 children.append(fdSelectComp)
jvrf2cf9c52002-05-23 21:50:36 +00001410 if hasattr(self.dictObj, "CharStrings"):
1411 items = []
1412 charStrings = self.dictObj.CharStrings
1413 for name in self.dictObj.charset:
1414 items.append(charStrings[name])
1415 charStringsComp = CharStringsCompiler(items, strings, self)
1416 children.append(charStringsComp)
jvrce522412003-08-25 07:37:25 +00001417 if hasattr(self.dictObj, "FDArray"):
jvred101512003-08-22 19:53:32 +00001418 # I have not yet supported merging a ttx CFF-CID font, as there are interesting
1419 # issues about merging the FDArrays. Here I assume that the FDArray info is correct
1420 # and complete.
1421 fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self)
1422 children.append(fdArrayIndexComp)
1423 children.extend(fdArrayIndexComp.getChildren(strings))
1424 if hasattr(self.dictObj, "Private"):
1425 privComp = self.dictObj.Private.getCompiler(strings, self)
1426 children.append(privComp)
1427 children.extend(privComp.getChildren(strings))
1428 return children
1429
1430
1431class FontDictCompiler(DictCompiler):
1432
1433 opcodes = buildOpcodeDict(topDictOperators)
1434
1435 def getChildren(self, strings):
1436 children = []
jvrf2cf9c52002-05-23 21:50:36 +00001437 if hasattr(self.dictObj, "Private"):
1438 privComp = self.dictObj.Private.getCompiler(strings, self)
1439 children.append(privComp)
1440 children.extend(privComp.getChildren(strings))
1441 return children
1442
1443
1444class PrivateDictCompiler(DictCompiler):
1445
1446 opcodes = buildOpcodeDict(privateDictOperators)
1447
jvr4e5af602002-05-24 09:58:04 +00001448 def setPos(self, pos, endPos):
1449 size = endPos - pos
jvrf2cf9c52002-05-23 21:50:36 +00001450 self.parent.rawDict["Private"] = size, pos
1451 self.pos = pos
1452
1453 def getChildren(self, strings):
1454 children = []
1455 if hasattr(self.dictObj, "Subrs"):
1456 children.append(self.dictObj.Subrs.getCompiler(strings, self))
1457 return children
1458
jvr4756b3a2002-05-16 18:17:32 +00001459
1460class BaseDict:
1461
jvr4e5af602002-05-24 09:58:04 +00001462 def __init__(self, strings=None, file=None, offset=None):
jvr4756b3a2002-05-16 18:17:32 +00001463 self.rawDict = {}
jvr767102e2002-05-17 07:06:32 +00001464 if DEBUG:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -05001465 print("loading %s at %s" % (self.__class__.__name__, offset))
jvr4756b3a2002-05-16 18:17:32 +00001466 self.file = file
1467 self.offset = offset
1468 self.strings = strings
jvr155aa752002-05-17 19:58:49 +00001469 self.skipNames = []
jvr4756b3a2002-05-16 18:17:32 +00001470
1471 def decompile(self, data):
jvrf2cf9c52002-05-23 21:50:36 +00001472 if DEBUG:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -05001473 print(" length %s is %s" % (self.__class__.__name__, len(data)))
jvrf2cf9c52002-05-23 21:50:36 +00001474 dec = self.decompilerClass(self.strings)
jvr4756b3a2002-05-16 18:17:32 +00001475 dec.decompile(data)
1476 self.rawDict = dec.getDict()
1477 self.postDecompile()
1478
1479 def postDecompile(self):
1480 pass
1481
jvrf2cf9c52002-05-23 21:50:36 +00001482 def getCompiler(self, strings, parent):
1483 return self.compilerClass(self, strings, parent)
1484
jvr4756b3a2002-05-16 18:17:32 +00001485 def __getattr__(self, name):
1486 value = self.rawDict.get(name)
1487 if value is None:
1488 value = self.defaults.get(name)
1489 if value is None:
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -05001490 raise AttributeError(name)
jvr4756b3a2002-05-16 18:17:32 +00001491 conv = self.converters[name]
jvr4e5af602002-05-24 09:58:04 +00001492 value = conv.read(self, value)
jvr4756b3a2002-05-16 18:17:32 +00001493 setattr(self, name, value)
1494 return value
1495
1496 def toXML(self, xmlWriter, progress):
1497 for name in self.order:
jvr155aa752002-05-17 19:58:49 +00001498 if name in self.skipNames:
1499 continue
jvr4756b3a2002-05-16 18:17:32 +00001500 value = getattr(self, name, None)
1501 if value is None:
1502 continue
jvr4e5af602002-05-24 09:58:04 +00001503 conv = self.converters[name]
jvr7ce0a132002-07-23 16:42:11 +00001504 conv.xmlWrite(xmlWriter, name, value, progress)
jvr4e5af602002-05-24 09:58:04 +00001505
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -05001506 def fromXML(self, name, attrs, content):
jvr4e5af602002-05-24 09:58:04 +00001507 conv = self.converters[name]
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -05001508 value = conv.xmlRead(name, attrs, content, self)
jvr4e5af602002-05-24 09:58:04 +00001509 setattr(self, name, value)
jvr4756b3a2002-05-16 18:17:32 +00001510
1511
1512class TopDict(BaseDict):
1513
1514 defaults = buildDefaults(topDictOperators)
1515 converters = buildConverters(topDictOperators)
1516 order = buildOrder(topDictOperators)
jvrf2cf9c52002-05-23 21:50:36 +00001517 decompilerClass = TopDictDecompiler
1518 compilerClass = TopDictCompiler
Just7842e561999-12-16 21:34:53 +00001519
jvr4e5af602002-05-24 09:58:04 +00001520 def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None):
jvr016ca762002-05-16 18:38:03 +00001521 BaseDict.__init__(self, strings, file, offset)
1522 self.GlobalSubrs = GlobalSubrs
1523
Just7842e561999-12-16 21:34:53 +00001524 def getGlyphOrder(self):
1525 return self.charset
1526
jvr4756b3a2002-05-16 18:17:32 +00001527 def postDecompile(self):
1528 offset = self.rawDict.get("CharStrings")
1529 if offset is None:
1530 return
1531 # get the number of glyphs beforehand.
1532 self.file.seek(offset)
jvra2ad5442002-05-17 18:36:07 +00001533 self.numGlyphs = readCard16(self.file)
Just7842e561999-12-16 21:34:53 +00001534
jvr016ca762002-05-16 18:38:03 +00001535 def toXML(self, xmlWriter, progress):
jvr155aa752002-05-17 19:58:49 +00001536 if hasattr(self, "CharStrings"):
jvr7ce0a132002-07-23 16:42:11 +00001537 self.decompileAllCharStrings(progress)
jvred101512003-08-22 19:53:32 +00001538 if hasattr(self, "ROS"):
1539 self.skipNames = ['Encoding']
jvr155aa752002-05-17 19:58:49 +00001540 if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
1541 # these values have default values, but I only want them to show up
1542 # in CID fonts.
1543 self.skipNames = ['CIDFontVersion', 'CIDFontRevision', 'CIDFontType',
1544 'CIDCount']
jvr016ca762002-05-16 18:38:03 +00001545 BaseDict.toXML(self, xmlWriter, progress)
jvr767102e2002-05-17 07:06:32 +00001546
jvr7ce0a132002-07-23 16:42:11 +00001547 def decompileAllCharStrings(self, progress):
jvr4e5af602002-05-24 09:58:04 +00001548 # XXX only when doing ttdump -i?
jvr7ce0a132002-07-23 16:42:11 +00001549 i = 0
jvra2ad5442002-05-17 18:36:07 +00001550 for charString in self.CharStrings.values():
jvred101512003-08-22 19:53:32 +00001551 try:
1552 charString.decompile()
1553 except:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -05001554 print("Error in charstring ", i)
jvred101512003-08-22 19:53:32 +00001555 import sys
1556 type, value = sys. exc_info()[0:2]
1557 raise type(value)
jvr7ce0a132002-07-23 16:42:11 +00001558 if not i % 30 and progress:
1559 progress.increment(0) # update
1560 i = i + 1
Just7842e561999-12-16 21:34:53 +00001561
1562
jvred101512003-08-22 19:53:32 +00001563class FontDict(BaseDict):
1564
1565 defaults = buildDefaults(topDictOperators)
1566 converters = buildConverters(topDictOperators)
1567 order = buildOrder(topDictOperators)
1568 decompilerClass = None
1569 compilerClass = FontDictCompiler
1570
1571 def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None):
1572 BaseDict.__init__(self, strings, file, offset)
1573 self.GlobalSubrs = GlobalSubrs
1574
1575 def getGlyphOrder(self):
1576 return self.charset
1577
1578 def toXML(self, xmlWriter, progress):
1579 self.skipNames = ['Encoding']
1580 BaseDict.toXML(self, xmlWriter, progress)
1581
1582
1583
jvr4756b3a2002-05-16 18:17:32 +00001584class PrivateDict(BaseDict):
1585 defaults = buildDefaults(privateDictOperators)
1586 converters = buildConverters(privateDictOperators)
1587 order = buildOrder(privateDictOperators)
jvrf2cf9c52002-05-23 21:50:36 +00001588 decompilerClass = PrivateDictDecompiler
1589 compilerClass = PrivateDictCompiler
Just7842e561999-12-16 21:34:53 +00001590
1591
jvre3275582002-05-14 12:22:03 +00001592class IndexedStrings:
1593
jvr767102e2002-05-17 07:06:32 +00001594 """SID -> string mapping."""
1595
1596 def __init__(self, file=None):
1597 if file is None:
jvre3275582002-05-14 12:22:03 +00001598 strings = []
jvr767102e2002-05-17 07:06:32 +00001599 else:
jvr4e5af602002-05-24 09:58:04 +00001600 strings = list(Index(file))
jvre3275582002-05-14 12:22:03 +00001601 self.strings = strings
1602
jvrf2cf9c52002-05-23 21:50:36 +00001603 def getCompiler(self):
1604 return IndexedStringsCompiler(self, None, None)
1605
1606 def __len__(self):
1607 return len(self.strings)
1608
jvre3275582002-05-14 12:22:03 +00001609 def __getitem__(self, SID):
1610 if SID < cffStandardStringCount:
1611 return cffStandardStrings[SID]
1612 else:
1613 return self.strings[SID - cffStandardStringCount]
1614
1615 def getSID(self, s):
1616 if not hasattr(self, "stringMapping"):
1617 self.buildStringMapping()
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -05001618 if s in cffStandardStringMapping:
jvre3275582002-05-14 12:22:03 +00001619 SID = cffStandardStringMapping[s]
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -05001620 elif s in self.stringMapping:
jvre3275582002-05-14 12:22:03 +00001621 SID = self.stringMapping[s]
1622 else:
1623 SID = len(self.strings) + cffStandardStringCount
1624 self.strings.append(s)
1625 self.stringMapping[s] = SID
1626 return SID
1627
1628 def getStrings(self):
1629 return self.strings
1630
1631 def buildStringMapping(self):
1632 self.stringMapping = {}
1633 for index in range(len(self.strings)):
1634 self.stringMapping[self.strings[index]] = index + cffStandardStringCount
1635
1636
Just7842e561999-12-16 21:34:53 +00001637# The 391 Standard Strings as used in the CFF format.
1638# from Adobe Technical None #5176, version 1.0, 18 March 1998
1639
1640cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
1641 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
1642 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
1643 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
1644 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
1645 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
1646 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
1647 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
1648 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
1649 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
1650 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
1651 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
1652 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
1653 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
1654 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
1655 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
1656 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
1657 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
1658 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
1659 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
1660 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
1661 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
1662 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
1663 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
1664 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
1665 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
1666 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
1667 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
1668 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
1669 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
1670 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
1671 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
1672 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
1673 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
1674 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
1675 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
1676 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
1677 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
1678 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
1679 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
1680 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
1681 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
1682 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
1683 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
1684 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
1685 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
1686 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
1687 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
1688 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
1689 'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
1690 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
1691 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
1692 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
1693 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
1694 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
1695 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
1696 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
1697 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
1698 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
1699 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
1700 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
1701 '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
1702 'Semibold'
1703]
1704
1705cffStandardStringCount = 391
1706assert len(cffStandardStrings) == cffStandardStringCount
1707# build reverse mapping
1708cffStandardStringMapping = {}
1709for _i in range(cffStandardStringCount):
1710 cffStandardStringMapping[cffStandardStrings[_i]] = _i
jvrc60a44f2006-10-21 13:41:18 +00001711
1712cffISOAdobeStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign",
1713"dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright",
1714"asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two",
1715"three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon",
1716"less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G",
1717"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
1718"X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
1719"underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
1720"k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
1721"braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent",
1722"sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle",
1723"quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl",
1724"endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet",
1725"quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis",
1726"perthousand", "questiondown", "grave", "acute", "circumflex", "tilde",
1727"macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut",
1728"ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE",
1729"ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls",
1730"onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus",
1731"Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn",
1732"threequarters", "twosuperior", "registered", "minus", "eth", "multiply",
1733"threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave",
1734"Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave",
1735"Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute",
1736"Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute",
1737"Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute",
1738"acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute",
1739"ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis",
1740"igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde",
1741"scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis",
1742"zcaron"]
1743
1744cffISOAdobeStringCount = 229
1745assert len(cffISOAdobeStrings) == cffISOAdobeStringCount
1746
1747cffIExpertStrings = [".notdef", "space", "exclamsmall", "Hungarumlautsmall",
1748"dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall",
1749"parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader",
1750"comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle",
1751"twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle",
1752"sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon",
1753"commasuperior", "threequartersemdash", "periodsuperior", "questionsmall",
1754"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
1755"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
1756"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
1757"parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall",
1758"Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall",
1759"Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall",
1760"Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall",
1761"Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall",
1762"exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall",
1763"Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall",
1764"figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall",
1765"onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth",
1766"threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds",
1767"zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior",
1768"fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior",
1769"zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior",
1770"fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior",
1771"centinferior", "dollarinferior", "periodinferior", "commainferior",
1772"Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall",
1773"Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall",
1774"Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall",
1775"Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall",
1776"Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall",
1777"Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall",
1778"Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall",
1779"Ydieresissmall"]
1780
1781cffExpertStringCount = 166
1782assert len(cffIExpertStrings) == cffExpertStringCount
1783
1784cffExpertSubsetStrings = [".notdef", "space", "dollaroldstyle",
1785"dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader",
1786"onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle",
1787"oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle",
1788"sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon",
1789"semicolon", "commasuperior", "threequartersemdash", "periodsuperior",
1790"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
1791"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
1792"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
1793"parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah",
1794"centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf",
1795"threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths",
1796"onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior",
1797"threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior",
1798"eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior",
1799"threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior",
1800"eightinferior", "nineinferior", "centinferior", "dollarinferior",
1801"periodinferior", "commainferior"]
1802
1803cffExpertSubsetStringCount = 87
1804assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount