• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""cffLib: read/write Adobe CFF fonts
2
3OpenType fonts with PostScript outlines contain a completely independent
4font file, Adobe's *Compact Font Format*. So dealing with OpenType fonts
5requires also dealing with CFF. This module allows you to read and write
6fonts written in the CFF format.
7
8In 2016, OpenType 1.8 introduced the `CFF2 <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2>`_
9format which, along with other changes, extended the CFF format to deal with
10the demands of variable fonts. This module parses both original CFF and CFF2.
11
12"""
13
14from fontTools.misc import sstruct
15from fontTools.misc import psCharStrings
16from fontTools.misc.arrayTools import unionRect, intRect
17from fontTools.misc.textTools import bytechr, byteord, bytesjoin, tobytes, tostr, safeEval
18from fontTools.ttLib import TTFont
19from fontTools.ttLib.tables.otBase import OTTableWriter
20from fontTools.ttLib.tables.otBase import OTTableReader
21from fontTools.ttLib.tables import otTables as ot
22from io import BytesIO
23import struct
24import logging
25import re
26
27# mute cffLib debug messages when running ttx in verbose mode
28DEBUG = logging.DEBUG - 1
29log = logging.getLogger(__name__)
30
31cffHeaderFormat = """
32	major:   B
33	minor:   B
34	hdrSize: B
35"""
36
37maxStackLimit = 513
38# maxstack operator has been deprecated. max stack is now always 513.
39
40
41class StopHintCountEvent(Exception):
42	pass
43
44
45class _DesubroutinizingT2Decompiler(psCharStrings.SimpleT2Decompiler):
46	stop_hintcount_ops = ("op_hintmask", "op_cntrmask", "op_rmoveto", "op_hmoveto",
47							"op_vmoveto")
48
49	def __init__(self, localSubrs, globalSubrs, private=None):
50		psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs,
51												private)
52
53	def execute(self, charString):
54		self.need_hintcount = True  # until proven otherwise
55		for op_name in self.stop_hintcount_ops:
56			setattr(self, op_name, self.stop_hint_count)
57
58		if hasattr(charString, '_desubroutinized'):
59			# If a charstring has already been desubroutinized, we will still
60			# need to execute it if we need to count hints in order to
61			# compute the byte length for mask arguments, and haven't finished
62			# counting hints pairs.
63			if self.need_hintcount and self.callingStack:
64				try:
65					psCharStrings.SimpleT2Decompiler.execute(self, charString)
66				except StopHintCountEvent:
67					del self.callingStack[-1]
68			return
69
70		charString._patches = []
71		psCharStrings.SimpleT2Decompiler.execute(self, charString)
72		desubroutinized = charString.program[:]
73		for idx, expansion in reversed(charString._patches):
74			assert idx >= 2
75			assert desubroutinized[idx - 1] in ['callsubr', 'callgsubr'], desubroutinized[idx - 1]
76			assert type(desubroutinized[idx - 2]) == int
77			if expansion[-1] == 'return':
78				expansion = expansion[:-1]
79			desubroutinized[idx-2:idx] = expansion
80		if not self.private.in_cff2:
81			if 'endchar' in desubroutinized:
82				# Cut off after first endchar
83				desubroutinized = desubroutinized[:desubroutinized.index('endchar') + 1]
84			else:
85				if not len(desubroutinized) or desubroutinized[-1] != 'return':
86					desubroutinized.append('return')
87
88		charString._desubroutinized = desubroutinized
89		del charString._patches
90
91	def op_callsubr(self, index):
92		subr = self.localSubrs[self.operandStack[-1]+self.localBias]
93		psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
94		self.processSubr(index, subr)
95
96	def op_callgsubr(self, index):
97		subr = self.globalSubrs[self.operandStack[-1]+self.globalBias]
98		psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
99		self.processSubr(index, subr)
100
101	def stop_hint_count(self, *args):
102		self.need_hintcount = False
103		for op_name in self.stop_hintcount_ops:
104			setattr(self, op_name, None)
105		cs = self.callingStack[-1]
106		if hasattr(cs, '_desubroutinized'):
107			raise StopHintCountEvent()
108
109	def op_hintmask(self, index):
110		psCharStrings.SimpleT2Decompiler.op_hintmask(self, index)
111		if self.need_hintcount:
112			self.stop_hint_count()
113
114	def processSubr(self, index, subr):
115		cs = self.callingStack[-1]
116		if not hasattr(cs, '_desubroutinized'):
117			cs._patches.append((index, subr._desubroutinized))
118
119
120class CFFFontSet(object):
121	"""A CFF font "file" can contain more than one font, although this is
122	extremely rare (and not allowed within OpenType fonts).
123
124	This class is the entry point for parsing a CFF table. To actually
125	manipulate the data inside the CFF font, you will want to access the
126	``CFFFontSet``'s :class:`TopDict` object. To do this, a ``CFFFontSet``
127	object can either be treated as a dictionary (with appropriate
128	``keys()`` and ``values()`` methods) mapping font names to :class:`TopDict`
129	objects, or as a list.
130
131	.. code:: python
132
133		from fontTools import ttLib
134		tt = ttLib.TTFont("Tests/cffLib/data/LinLibertine_RBI.otf")
135		tt["CFF "].cff
136		# <fontTools.cffLib.CFFFontSet object at 0x101e24c90>
137		tt["CFF "].cff[0] # Here's your actual font data
138		# <fontTools.cffLib.TopDict object at 0x1020f1fd0>
139
140	"""
141
142	def decompile(self, file, otFont, isCFF2=None):
143		"""Parse a binary CFF file into an internal representation. ``file``
144		should be a file handle object. ``otFont`` is the top-level
145		:py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file.
146
147		If ``isCFF2`` is passed and set to ``True`` or ``False``, then the
148		library makes an assertion that the CFF header is of the appropriate
149		version.
150		"""
151
152		self.otFont = otFont
153		sstruct.unpack(cffHeaderFormat, file.read(3), self)
154		if isCFF2 is not None:
155			# called from ttLib: assert 'major' as read from file matches the
156			# expected version
157			expected_major = (2 if isCFF2 else 1)
158			if self.major != expected_major:
159				raise ValueError(
160					"Invalid CFF 'major' version: expected %d, found %d" %
161					(expected_major, self.major))
162		else:
163			# use 'major' version from file to determine if isCFF2
164			assert self.major in (1, 2), "Unknown CFF format"
165			isCFF2 = self.major == 2
166		if not isCFF2:
167			self.offSize = struct.unpack("B", file.read(1))[0]
168			file.seek(self.hdrSize)
169			self.fontNames = list(tostr(s) for s in Index(file, isCFF2=isCFF2))
170			self.topDictIndex = TopDictIndex(file, isCFF2=isCFF2)
171			self.strings = IndexedStrings(file)
172		else:  # isCFF2
173			self.topDictSize = struct.unpack(">H", file.read(2))[0]
174			file.seek(self.hdrSize)
175			self.fontNames = ["CFF2Font"]
176			cff2GetGlyphOrder = otFont.getGlyphOrder
177			# in CFF2, offsetSize is the size of the TopDict data.
178			self.topDictIndex = TopDictIndex(
179				file, cff2GetGlyphOrder, self.topDictSize, isCFF2=isCFF2)
180			self.strings = None
181		self.GlobalSubrs = GlobalSubrsIndex(file, isCFF2=isCFF2)
182		self.topDictIndex.strings = self.strings
183		self.topDictIndex.GlobalSubrs = self.GlobalSubrs
184
185	def __len__(self):
186		return len(self.fontNames)
187
188	def keys(self):
189		return list(self.fontNames)
190
191	def values(self):
192		return self.topDictIndex
193
194	def __getitem__(self, nameOrIndex):
195		""" Return TopDict instance identified by name (str) or index (int
196		or any object that implements `__index__`).
197		"""
198		if hasattr(nameOrIndex, "__index__"):
199			index = nameOrIndex.__index__()
200		elif isinstance(nameOrIndex, str):
201			name = nameOrIndex
202			try:
203				index = self.fontNames.index(name)
204			except ValueError:
205				raise KeyError(nameOrIndex)
206		else:
207			raise TypeError(nameOrIndex)
208		return self.topDictIndex[index]
209
210	def compile(self, file, otFont, isCFF2=None):
211		"""Write the object back into binary representation onto the given file.
212		``file`` should be a file handle object. ``otFont`` is the top-level
213		:py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file.
214
215		If ``isCFF2`` is passed and set to ``True`` or ``False``, then the
216		library makes an assertion that the CFF header is of the appropriate
217		version.
218		"""
219		self.otFont = otFont
220		if isCFF2 is not None:
221			# called from ttLib: assert 'major' value matches expected version
222			expected_major = (2 if isCFF2 else 1)
223			if self.major != expected_major:
224				raise ValueError(
225					"Invalid CFF 'major' version: expected %d, found %d" %
226					(expected_major, self.major))
227		else:
228			# use current 'major' value to determine output format
229			assert self.major in (1, 2), "Unknown CFF format"
230			isCFF2 = self.major == 2
231
232		if otFont.recalcBBoxes and not isCFF2:
233			for topDict in self.topDictIndex:
234				topDict.recalcFontBBox()
235
236		if not isCFF2:
237			strings = IndexedStrings()
238		else:
239			strings = None
240		writer = CFFWriter(isCFF2)
241		topCompiler = self.topDictIndex.getCompiler(strings, self, isCFF2=isCFF2)
242		if isCFF2:
243			self.hdrSize = 5
244			writer.add(sstruct.pack(cffHeaderFormat, self))
245			# Note: topDictSize will most likely change in CFFWriter.toFile().
246			self.topDictSize = topCompiler.getDataLength()
247			writer.add(struct.pack(">H", self.topDictSize))
248		else:
249			self.hdrSize = 4
250			self.offSize = 4  # will most likely change in CFFWriter.toFile().
251			writer.add(sstruct.pack(cffHeaderFormat, self))
252			writer.add(struct.pack("B", self.offSize))
253		if not isCFF2:
254			fontNames = Index()
255			for name in self.fontNames:
256				fontNames.append(name)
257			writer.add(fontNames.getCompiler(strings, self, isCFF2=isCFF2))
258		writer.add(topCompiler)
259		if not isCFF2:
260			writer.add(strings.getCompiler())
261		writer.add(self.GlobalSubrs.getCompiler(strings, self, isCFF2=isCFF2))
262
263		for topDict in self.topDictIndex:
264			if not hasattr(topDict, "charset") or topDict.charset is None:
265				charset = otFont.getGlyphOrder()
266				topDict.charset = charset
267		children = topCompiler.getChildren(strings)
268		for child in children:
269			writer.add(child)
270
271		writer.toFile(file)
272
273	def toXML(self, xmlWriter):
274		"""Write the object into XML representation onto the given
275		:class:`fontTools.misc.xmlWriter.XMLWriter`.
276
277		.. code:: python
278
279			writer = xmlWriter.XMLWriter(sys.stdout)
280			tt["CFF "].cff.toXML(writer)
281
282		"""
283
284		xmlWriter.simpletag("major", value=self.major)
285		xmlWriter.newline()
286		xmlWriter.simpletag("minor", value=self.minor)
287		xmlWriter.newline()
288		for fontName in self.fontNames:
289			xmlWriter.begintag("CFFFont", name=tostr(fontName))
290			xmlWriter.newline()
291			font = self[fontName]
292			font.toXML(xmlWriter)
293			xmlWriter.endtag("CFFFont")
294			xmlWriter.newline()
295		xmlWriter.newline()
296		xmlWriter.begintag("GlobalSubrs")
297		xmlWriter.newline()
298		self.GlobalSubrs.toXML(xmlWriter)
299		xmlWriter.endtag("GlobalSubrs")
300		xmlWriter.newline()
301
302	def fromXML(self, name, attrs, content, otFont=None):
303		"""Reads data from the XML element into the ``CFFFontSet`` object."""
304		self.otFont = otFont
305
306		# set defaults. These will be replaced if there are entries for them
307		# in the XML file.
308		if not hasattr(self, "major"):
309			self.major = 1
310		if not hasattr(self, "minor"):
311			self.minor = 0
312
313		if name == "CFFFont":
314			if self.major == 1:
315				if not hasattr(self, "offSize"):
316					# this will be recalculated when the cff is compiled.
317					self.offSize = 4
318				if not hasattr(self, "hdrSize"):
319					self.hdrSize = 4
320				if not hasattr(self, "GlobalSubrs"):
321					self.GlobalSubrs = GlobalSubrsIndex()
322				if not hasattr(self, "fontNames"):
323					self.fontNames = []
324					self.topDictIndex = TopDictIndex()
325				fontName = attrs["name"]
326				self.fontNames.append(fontName)
327				topDict = TopDict(GlobalSubrs=self.GlobalSubrs)
328				topDict.charset = None  # gets filled in later
329			elif self.major == 2:
330				if not hasattr(self, "hdrSize"):
331					self.hdrSize = 5
332				if not hasattr(self, "GlobalSubrs"):
333					self.GlobalSubrs = GlobalSubrsIndex()
334				if not hasattr(self, "fontNames"):
335					self.fontNames = ["CFF2Font"]
336				cff2GetGlyphOrder = self.otFont.getGlyphOrder
337				topDict = TopDict(
338					GlobalSubrs=self.GlobalSubrs,
339					cff2GetGlyphOrder=cff2GetGlyphOrder)
340				self.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder, None)
341			self.topDictIndex.append(topDict)
342			for element in content:
343				if isinstance(element, str):
344					continue
345				name, attrs, content = element
346				topDict.fromXML(name, attrs, content)
347
348			if hasattr(topDict, "VarStore") and topDict.FDArray[0].vstore is None:
349				fdArray = topDict.FDArray
350				for fontDict in fdArray:
351					if hasattr(fontDict, "Private"):
352						fontDict.Private.vstore = topDict.VarStore
353
354		elif name == "GlobalSubrs":
355			subrCharStringClass = psCharStrings.T2CharString
356			if not hasattr(self, "GlobalSubrs"):
357				self.GlobalSubrs = GlobalSubrsIndex()
358			for element in content:
359				if isinstance(element, str):
360					continue
361				name, attrs, content = element
362				subr = subrCharStringClass()
363				subr.fromXML(name, attrs, content)
364				self.GlobalSubrs.append(subr)
365		elif name == "major":
366			self.major = int(attrs['value'])
367		elif name == "minor":
368			self.minor = int(attrs['value'])
369
370	def convertCFFToCFF2(self, otFont):
371		"""Converts this object from CFF format to CFF2 format. This conversion
372		is done 'in-place'. The conversion cannot be reversed.
373
374		This assumes a decompiled CFF table. (i.e. that the object has been
375		filled via :meth:`decompile`.)"""
376		self.major = 2
377		cff2GetGlyphOrder = self.otFont.getGlyphOrder
378		topDictData = TopDictIndex(None, cff2GetGlyphOrder, None)
379		topDictData.items = self.topDictIndex.items
380		self.topDictIndex = topDictData
381		topDict = topDictData[0]
382		if hasattr(topDict, 'Private'):
383			privateDict = topDict.Private
384		else:
385			privateDict = None
386		opOrder = buildOrder(topDictOperators2)
387		topDict.order = opOrder
388		topDict.cff2GetGlyphOrder = cff2GetGlyphOrder
389		for entry in topDictOperators:
390			key = entry[1]
391			if key not in opOrder:
392				if key in topDict.rawDict:
393					del topDict.rawDict[key]
394				if hasattr(topDict, key):
395					delattr(topDict, key)
396
397		if not hasattr(topDict, "FDArray"):
398			fdArray = topDict.FDArray = FDArrayIndex()
399			fdArray.strings = None
400			fdArray.GlobalSubrs = topDict.GlobalSubrs
401			topDict.GlobalSubrs.fdArray = fdArray
402			charStrings = topDict.CharStrings
403			if charStrings.charStringsAreIndexed:
404				charStrings.charStringsIndex.fdArray = fdArray
405			else:
406				charStrings.fdArray = fdArray
407			fontDict = FontDict()
408			fontDict.setCFF2(True)
409			fdArray.append(fontDict)
410			fontDict.Private = privateDict
411			privateOpOrder = buildOrder(privateDictOperators2)
412			for entry in privateDictOperators:
413				key = entry[1]
414				if key not in privateOpOrder:
415					if key in privateDict.rawDict:
416						# print "Removing private dict", key
417						del privateDict.rawDict[key]
418					if hasattr(privateDict, key):
419						delattr(privateDict, key)
420						# print "Removing privateDict attr", key
421		else:
422			# clean up the PrivateDicts in the fdArray
423			fdArray = topDict.FDArray
424			privateOpOrder = buildOrder(privateDictOperators2)
425			for fontDict in fdArray:
426				fontDict.setCFF2(True)
427				for key in fontDict.rawDict.keys():
428					if key not in fontDict.order:
429						del fontDict.rawDict[key]
430						if hasattr(fontDict, key):
431							delattr(fontDict, key)
432
433				privateDict = fontDict.Private
434				for entry in privateDictOperators:
435					key = entry[1]
436					if key not in privateOpOrder:
437						if key in privateDict.rawDict:
438							# print "Removing private dict", key
439							del privateDict.rawDict[key]
440						if hasattr(privateDict, key):
441							delattr(privateDict, key)
442							# print "Removing privateDict attr", key
443		# At this point, the Subrs and Charstrings are all still T2Charstring class
444		# easiest to fix this by compiling, then decompiling again
445		file = BytesIO()
446		self.compile(file, otFont, isCFF2=True)
447		file.seek(0)
448		self.decompile(file, otFont, isCFF2=True)
449
450	def desubroutinize(self):
451		for fontName in self.fontNames:
452			font = self[fontName]
453			cs = font.CharStrings
454			for g in font.charset:
455				c, _ = cs.getItemAndSelector(g)
456				c.decompile()
457				subrs = getattr(c.private, "Subrs", [])
458				decompiler = _DesubroutinizingT2Decompiler(subrs, c.globalSubrs, c.private)
459				decompiler.execute(c)
460				c.program = c._desubroutinized
461				del c._desubroutinized
462			# Delete all the local subrs
463			if hasattr(font, 'FDArray'):
464				for fd in font.FDArray:
465					pd = fd.Private
466					if hasattr(pd, 'Subrs'):
467						del pd.Subrs
468					if 'Subrs' in pd.rawDict:
469						del pd.rawDict['Subrs']
470			else:
471				pd = font.Private
472				if hasattr(pd, 'Subrs'):
473					del pd.Subrs
474				if 'Subrs' in pd.rawDict:
475					del pd.rawDict['Subrs']
476		# as well as the global subrs
477		self.GlobalSubrs.clear()
478
479
480class CFFWriter(object):
481	"""Helper class for serializing CFF data to binary. Used by
482	:meth:`CFFFontSet.compile`."""
483	def __init__(self, isCFF2):
484		self.data = []
485		self.isCFF2 = isCFF2
486
487	def add(self, table):
488		self.data.append(table)
489
490	def toFile(self, file):
491		lastPosList = None
492		count = 1
493		while True:
494			log.log(DEBUG, "CFFWriter.toFile() iteration: %d", count)
495			count = count + 1
496			pos = 0
497			posList = [pos]
498			for item in self.data:
499				if hasattr(item, "getDataLength"):
500					endPos = pos + item.getDataLength()
501					if isinstance(item, TopDictIndexCompiler) and item.isCFF2:
502						self.topDictSize = item.getDataLength()
503				else:
504					endPos = pos + len(item)
505				if hasattr(item, "setPos"):
506					item.setPos(pos, endPos)
507				pos = endPos
508				posList.append(pos)
509			if posList == lastPosList:
510				break
511			lastPosList = posList
512		log.log(DEBUG, "CFFWriter.toFile() writing to file.")
513		begin = file.tell()
514		if self.isCFF2:
515			self.data[1] = struct.pack(">H", self.topDictSize)
516		else:
517			self.offSize = calcOffSize(lastPosList[-1])
518			self.data[1] = struct.pack("B", self.offSize)
519		posList = [0]
520		for item in self.data:
521			if hasattr(item, "toFile"):
522				item.toFile(file)
523			else:
524				file.write(item)
525			posList.append(file.tell() - begin)
526		assert posList == lastPosList
527
528
529def calcOffSize(largestOffset):
530	if largestOffset < 0x100:
531		offSize = 1
532	elif largestOffset < 0x10000:
533		offSize = 2
534	elif largestOffset < 0x1000000:
535		offSize = 3
536	else:
537		offSize = 4
538	return offSize
539
540
541class IndexCompiler(object):
542	"""Base class for writing CFF `INDEX data <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#5-index-data>`_
543	to binary."""
544
545	def __init__(self, items, strings, parent, isCFF2=None):
546		if isCFF2 is None and hasattr(parent, "isCFF2"):
547			isCFF2 = parent.isCFF2
548			assert isCFF2 is not None
549		self.isCFF2 = isCFF2
550		self.items = self.getItems(items, strings)
551		self.parent = parent
552
553	def getItems(self, items, strings):
554		return items
555
556	def getOffsets(self):
557		# An empty INDEX contains only the count field.
558		if self.items:
559			pos = 1
560			offsets = [pos]
561			for item in self.items:
562				if hasattr(item, "getDataLength"):
563					pos = pos + item.getDataLength()
564				else:
565					pos = pos + len(item)
566				offsets.append(pos)
567		else:
568			offsets = []
569		return offsets
570
571	def getDataLength(self):
572		if self.isCFF2:
573			countSize = 4
574		else:
575			countSize = 2
576
577		if self.items:
578			lastOffset = self.getOffsets()[-1]
579			offSize = calcOffSize(lastOffset)
580			dataLength = (
581				countSize +                        # count
582				1 +                                # offSize
583				(len(self.items) + 1) * offSize +  # the offsets
584				lastOffset - 1                     # size of object data
585			)
586		else:
587			# count. For empty INDEX tables, this is the only entry.
588			dataLength = countSize
589
590		return dataLength
591
592	def toFile(self, file):
593		offsets = self.getOffsets()
594		if self.isCFF2:
595			writeCard32(file, len(self.items))
596		else:
597			writeCard16(file, len(self.items))
598		# An empty INDEX contains only the count field.
599		if self.items:
600			offSize = calcOffSize(offsets[-1])
601			writeCard8(file, offSize)
602			offSize = -offSize
603			pack = struct.pack
604			for offset in offsets:
605				binOffset = pack(">l", offset)[offSize:]
606				assert len(binOffset) == -offSize
607				file.write(binOffset)
608			for item in self.items:
609				if hasattr(item, "toFile"):
610					item.toFile(file)
611				else:
612					data = tobytes(item, encoding="latin1")
613					file.write(data)
614
615
616class IndexedStringsCompiler(IndexCompiler):
617
618	def getItems(self, items, strings):
619		return items.strings
620
621
622class TopDictIndexCompiler(IndexCompiler):
623	"""Helper class for writing the TopDict to binary."""
624
625	def getItems(self, items, strings):
626		out = []
627		for item in items:
628			out.append(item.getCompiler(strings, self))
629		return out
630
631	def getChildren(self, strings):
632		children = []
633		for topDict in self.items:
634			children.extend(topDict.getChildren(strings))
635		return children
636
637	def getOffsets(self):
638		if self.isCFF2:
639			offsets = [0, self.items[0].getDataLength()]
640			return offsets
641		else:
642			return super(TopDictIndexCompiler, self).getOffsets()
643
644	def getDataLength(self):
645		if self.isCFF2:
646			dataLength = self.items[0].getDataLength()
647			return dataLength
648		else:
649			return super(TopDictIndexCompiler, self).getDataLength()
650
651	def toFile(self, file):
652		if self.isCFF2:
653			self.items[0].toFile(file)
654		else:
655			super(TopDictIndexCompiler, self).toFile(file)
656
657
658class FDArrayIndexCompiler(IndexCompiler):
659	"""Helper class for writing the
660	`Font DICT INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#10-font-dict-index-font-dicts-and-fdselect>`_
661	to binary."""
662
663	def getItems(self, items, strings):
664		out = []
665		for item in items:
666			out.append(item.getCompiler(strings, self))
667		return out
668
669	def getChildren(self, strings):
670		children = []
671		for fontDict in self.items:
672			children.extend(fontDict.getChildren(strings))
673		return children
674
675	def toFile(self, file):
676		offsets = self.getOffsets()
677		if self.isCFF2:
678			writeCard32(file, len(self.items))
679		else:
680			writeCard16(file, len(self.items))
681		offSize = calcOffSize(offsets[-1])
682		writeCard8(file, offSize)
683		offSize = -offSize
684		pack = struct.pack
685		for offset in offsets:
686			binOffset = pack(">l", offset)[offSize:]
687			assert len(binOffset) == -offSize
688			file.write(binOffset)
689		for item in self.items:
690			if hasattr(item, "toFile"):
691				item.toFile(file)
692			else:
693				file.write(item)
694
695	def setPos(self, pos, endPos):
696		self.parent.rawDict["FDArray"] = pos
697
698
699class GlobalSubrsCompiler(IndexCompiler):
700	"""Helper class for writing the `global subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
701	to binary."""
702
703	def getItems(self, items, strings):
704		out = []
705		for cs in items:
706			cs.compile(self.isCFF2)
707			out.append(cs.bytecode)
708		return out
709
710
711class SubrsCompiler(GlobalSubrsCompiler):
712	"""Helper class for writing the `local subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
713	to binary."""
714
715	def setPos(self, pos, endPos):
716		offset = pos - self.parent.pos
717		self.parent.rawDict["Subrs"] = offset
718
719
720class CharStringsCompiler(GlobalSubrsCompiler):
721	"""Helper class for writing the `CharStrings INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
722	to binary."""
723	def getItems(self, items, strings):
724		out = []
725		for cs in items:
726			cs.compile(self.isCFF2)
727			out.append(cs.bytecode)
728		return out
729
730	def setPos(self, pos, endPos):
731		self.parent.rawDict["CharStrings"] = pos
732
733
734class Index(object):
735	"""This class represents what the CFF spec calls an INDEX (an array of
736	variable-sized objects). `Index` items can be addressed and set using
737	Python list indexing."""
738
739	compilerClass = IndexCompiler
740
741	def __init__(self, file=None, isCFF2=None):
742		assert (isCFF2 is None) == (file is None)
743		self.items = []
744		name = self.__class__.__name__
745		if file is None:
746			return
747		self._isCFF2 = isCFF2
748		log.log(DEBUG, "loading %s at %s", name, file.tell())
749		self.file = file
750		if isCFF2:
751			count = readCard32(file)
752		else:
753			count = readCard16(file)
754		if count == 0:
755			return
756		self.items = [None] * count
757		offSize = readCard8(file)
758		log.log(DEBUG, "    index count: %s offSize: %s", count, offSize)
759		assert offSize <= 4, "offSize too large: %s" % offSize
760		self.offsets = offsets = []
761		pad = b'\0' * (4 - offSize)
762		for index in range(count + 1):
763			chunk = file.read(offSize)
764			chunk = pad + chunk
765			offset, = struct.unpack(">L", chunk)
766			offsets.append(int(offset))
767		self.offsetBase = file.tell() - 1
768		file.seek(self.offsetBase + offsets[-1])  # pretend we've read the whole lot
769		log.log(DEBUG, "    end of %s at %s", name, file.tell())
770
771	def __len__(self):
772		return len(self.items)
773
774	def __getitem__(self, index):
775		item = self.items[index]
776		if item is not None:
777			return item
778		offset = self.offsets[index] + self.offsetBase
779		size = self.offsets[index + 1] - self.offsets[index]
780		file = self.file
781		file.seek(offset)
782		data = file.read(size)
783		assert len(data) == size
784		item = self.produceItem(index, data, file, offset)
785		self.items[index] = item
786		return item
787
788	def __setitem__(self, index, item):
789		self.items[index] = item
790
791	def produceItem(self, index, data, file, offset):
792		return data
793
794	def append(self, item):
795		"""Add an item to an INDEX."""
796		self.items.append(item)
797
798	def getCompiler(self, strings, parent, isCFF2=None):
799		return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
800
801	def clear(self):
802		"""Empty the INDEX."""
803		del self.items[:]
804
805
806class GlobalSubrsIndex(Index):
807	"""This index contains all the global subroutines in the font. A global
808	subroutine is a set of ``CharString`` data which is accessible to any
809	glyph in the font, and are used to store repeated instructions - for
810	example, components may be encoded as global subroutines, but so could
811	hinting instructions.
812
813	Remember that when interpreting a ``callgsubr`` instruction (or indeed
814	a ``callsubr`` instruction) that you will need to add the "subroutine
815	number bias" to number given:
816
817	.. code:: python
818
819		tt = ttLib.TTFont("Almendra-Bold.otf")
820		u = tt["CFF "].cff[0].CharStrings["udieresis"]
821		u.decompile()
822
823		u.toXML(XMLWriter(sys.stdout))
824		# <some stuff>
825		# -64 callgsubr <-- Subroutine which implements the dieresis mark
826		# <other stuff>
827
828		tt["CFF "].cff[0].GlobalSubrs[-64] # <-- WRONG
829		# <T2CharString (bytecode) at 103451d10>
830
831		tt["CFF "].cff[0].GlobalSubrs[-64 + 107] # <-- RIGHT
832		# <T2CharString (source) at 103451390>
833
834	("The bias applied depends on the number of subrs (gsubrs). If the number of
835	subrs (gsubrs) is less than 1240, the bias is 107. Otherwise if it is less
836	than 33900, it is 1131; otherwise it is 32768.",
837	`Subroutine Operators <https://docs.microsoft.com/en-us/typography/opentype/otspec180/cff2charstr#section4.4>`)
838	"""
839
840	compilerClass = GlobalSubrsCompiler
841	subrClass = psCharStrings.T2CharString
842	charStringClass = psCharStrings.T2CharString
843
844	def __init__(self, file=None, globalSubrs=None, private=None,
845			fdSelect=None, fdArray=None, isCFF2=None):
846		super(GlobalSubrsIndex, self).__init__(file, isCFF2=isCFF2)
847		self.globalSubrs = globalSubrs
848		self.private = private
849		if fdSelect:
850			self.fdSelect = fdSelect
851		if fdArray:
852			self.fdArray = fdArray
853
854	def produceItem(self, index, data, file, offset):
855		if self.private is not None:
856			private = self.private
857		elif hasattr(self, 'fdArray') and self.fdArray is not None:
858			if hasattr(self, 'fdSelect') and self.fdSelect is not None:
859				fdIndex = self.fdSelect[index]
860			else:
861				fdIndex = 0
862			private = self.fdArray[fdIndex].Private
863		else:
864			private = None
865		return self.subrClass(data, private=private, globalSubrs=self.globalSubrs)
866
867	def toXML(self, xmlWriter):
868		"""Write the subroutines index into XML representation onto the given
869		:class:`fontTools.misc.xmlWriter.XMLWriter`.
870
871		.. code:: python
872
873			writer = xmlWriter.XMLWriter(sys.stdout)
874			tt["CFF "].cff[0].GlobalSubrs.toXML(writer)
875
876		"""
877		xmlWriter.comment(
878			"The 'index' attribute is only for humans; "
879			"it is ignored when parsed.")
880		xmlWriter.newline()
881		for i in range(len(self)):
882			subr = self[i]
883			if subr.needsDecompilation():
884				xmlWriter.begintag("CharString", index=i, raw=1)
885			else:
886				xmlWriter.begintag("CharString", index=i)
887			xmlWriter.newline()
888			subr.toXML(xmlWriter)
889			xmlWriter.endtag("CharString")
890			xmlWriter.newline()
891
892	def fromXML(self, name, attrs, content):
893		if name != "CharString":
894			return
895		subr = self.subrClass()
896		subr.fromXML(name, attrs, content)
897		self.append(subr)
898
899	def getItemAndSelector(self, index):
900		sel = None
901		if hasattr(self, 'fdSelect'):
902			sel = self.fdSelect[index]
903		return self[index], sel
904
905
906class SubrsIndex(GlobalSubrsIndex):
907	"""This index contains a glyph's local subroutines. A local subroutine is a
908	private set of ``CharString`` data which is accessible only to the glyph to
909	which the index is attached."""
910
911	compilerClass = SubrsCompiler
912
913
914class TopDictIndex(Index):
915	"""This index represents the array of ``TopDict`` structures in the font
916	(again, usually only one entry is present). Hence the following calls are
917	equivalent:
918
919	.. code:: python
920
921		tt["CFF "].cff[0]
922		# <fontTools.cffLib.TopDict object at 0x102ed6e50>
923		tt["CFF "].cff.topDictIndex[0]
924		# <fontTools.cffLib.TopDict object at 0x102ed6e50>
925
926	"""
927
928	compilerClass = TopDictIndexCompiler
929
930	def __init__(self, file=None, cff2GetGlyphOrder=None, topSize=0,
931			isCFF2=None):
932		assert (isCFF2 is None) == (file is None)
933		self.cff2GetGlyphOrder = cff2GetGlyphOrder
934		if file is not None and isCFF2:
935			self._isCFF2 = isCFF2
936			self.items = []
937			name = self.__class__.__name__
938			log.log(DEBUG, "loading %s at %s", name, file.tell())
939			self.file = file
940			count = 1
941			self.items = [None] * count
942			self.offsets = [0, topSize]
943			self.offsetBase = file.tell()
944			# pretend we've read the whole lot
945			file.seek(self.offsetBase + topSize)
946			log.log(DEBUG, "    end of %s at %s", name, file.tell())
947		else:
948			super(TopDictIndex, self).__init__(file, isCFF2=isCFF2)
949
950	def produceItem(self, index, data, file, offset):
951		top = TopDict(
952			self.strings, file, offset, self.GlobalSubrs,
953			self.cff2GetGlyphOrder, isCFF2=self._isCFF2)
954		top.decompile(data)
955		return top
956
957	def toXML(self, xmlWriter):
958		for i in range(len(self)):
959			xmlWriter.begintag("FontDict", index=i)
960			xmlWriter.newline()
961			self[i].toXML(xmlWriter)
962			xmlWriter.endtag("FontDict")
963			xmlWriter.newline()
964
965
966class FDArrayIndex(Index):
967
968	compilerClass = FDArrayIndexCompiler
969
970	def toXML(self, xmlWriter):
971		for i in range(len(self)):
972			xmlWriter.begintag("FontDict", index=i)
973			xmlWriter.newline()
974			self[i].toXML(xmlWriter)
975			xmlWriter.endtag("FontDict")
976			xmlWriter.newline()
977
978	def produceItem(self, index, data, file, offset):
979		fontDict = FontDict(
980			self.strings, file, offset, self.GlobalSubrs, isCFF2=self._isCFF2,
981			vstore=self.vstore)
982		fontDict.decompile(data)
983		return fontDict
984
985	def fromXML(self, name, attrs, content):
986		if name != "FontDict":
987			return
988		fontDict = FontDict()
989		for element in content:
990			if isinstance(element, str):
991				continue
992			name, attrs, content = element
993			fontDict.fromXML(name, attrs, content)
994		self.append(fontDict)
995
996
997class VarStoreData(object):
998
999	def __init__(self, file=None, otVarStore=None):
1000		self.file = file
1001		self.data = None
1002		self.otVarStore = otVarStore
1003		self.font = TTFont()  # dummy font for the decompile function.
1004
1005	def decompile(self):
1006		if self.file:
1007			class GlobalState(object):
1008				def __init__(self, tableType, cachingStats):
1009					self.tableType = tableType
1010					self.cachingStats = cachingStats
1011			globalState = GlobalState(tableType="VarStore", cachingStats={})
1012			# read data in from file. Assume position is correct.
1013			length = readCard16(self.file)
1014			self.data = self.file.read(length)
1015			globalState = {}
1016			reader = OTTableReader(self.data, globalState)
1017			self.otVarStore = ot.VarStore()
1018			self.otVarStore.decompile(reader, self.font)
1019		return self
1020
1021	def compile(self):
1022		writer = OTTableWriter()
1023		self.otVarStore.compile(writer, self.font)
1024		# Note that this omits the initial Card16 length from the CFF2
1025		# VarStore data block
1026		self.data = writer.getAllData()
1027
1028	def writeXML(self, xmlWriter, name):
1029		self.otVarStore.toXML(xmlWriter, self.font)
1030
1031	def xmlRead(self, name, attrs, content, parent):
1032		self.otVarStore = ot.VarStore()
1033		for element in content:
1034			if isinstance(element, tuple):
1035				name, attrs, content = element
1036				self.otVarStore.fromXML(name, attrs, content, self.font)
1037			else:
1038				pass
1039		return None
1040
1041	def __len__(self):
1042		return len(self.data)
1043
1044	def getNumRegions(self, vsIndex):
1045		varData = self.otVarStore.VarData[vsIndex]
1046		numRegions = varData.VarRegionCount
1047		return numRegions
1048
1049
1050class FDSelect(object):
1051
1052	def __init__(self, file=None, numGlyphs=None, format=None):
1053		if file:
1054			# read data in from file
1055			self.format = readCard8(file)
1056			if self.format == 0:
1057				from array import array
1058				self.gidArray = array("B", file.read(numGlyphs)).tolist()
1059			elif self.format == 3:
1060				gidArray = [None] * numGlyphs
1061				nRanges = readCard16(file)
1062				fd = None
1063				prev = None
1064				for i in range(nRanges):
1065					first = readCard16(file)
1066					if prev is not None:
1067						for glyphID in range(prev, first):
1068							gidArray[glyphID] = fd
1069					prev = first
1070					fd = readCard8(file)
1071				if prev is not None:
1072					first = readCard16(file)
1073					for glyphID in range(prev, first):
1074						gidArray[glyphID] = fd
1075				self.gidArray = gidArray
1076			elif self.format == 4:
1077				gidArray = [None] * numGlyphs
1078				nRanges = readCard32(file)
1079				fd = None
1080				prev = None
1081				for i in range(nRanges):
1082					first = readCard32(file)
1083					if prev is not None:
1084						for glyphID in range(prev, first):
1085							gidArray[glyphID] = fd
1086					prev = first
1087					fd = readCard16(file)
1088				if prev is not None:
1089					first = readCard32(file)
1090					for glyphID in range(prev, first):
1091						gidArray[glyphID] = fd
1092				self.gidArray = gidArray
1093			else:
1094				assert False, "unsupported FDSelect format: %s" % format
1095		else:
1096			# reading from XML. Make empty gidArray, and leave format as passed in.
1097			# format is None will result in the smallest representation being used.
1098			self.format = format
1099			self.gidArray = []
1100
1101	def __len__(self):
1102		return len(self.gidArray)
1103
1104	def __getitem__(self, index):
1105		return self.gidArray[index]
1106
1107	def __setitem__(self, index, fdSelectValue):
1108		self.gidArray[index] = fdSelectValue
1109
1110	def append(self, fdSelectValue):
1111		self.gidArray.append(fdSelectValue)
1112
1113
1114class CharStrings(object):
1115	"""The ``CharStrings`` in the font represent the instructions for drawing
1116	each glyph. This object presents a dictionary interface to the font's
1117	CharStrings, indexed by glyph name:
1118
1119	.. code:: python
1120
1121		tt["CFF "].cff[0].CharStrings["a"]
1122		# <T2CharString (bytecode) at 103451e90>
1123
1124	See :class:`fontTools.misc.psCharStrings.T1CharString` and
1125	:class:`fontTools.misc.psCharStrings.T2CharString` for how to decompile,
1126	compile and interpret the glyph drawing instructions in the returned objects.
1127
1128	"""
1129
1130	def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray,
1131			isCFF2=None):
1132		self.globalSubrs = globalSubrs
1133		if file is not None:
1134			self.charStringsIndex = SubrsIndex(
1135				file, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2)
1136			self.charStrings = charStrings = {}
1137			for i in range(len(charset)):
1138				charStrings[charset[i]] = i
1139			# read from OTF file: charStrings.values() are indices into
1140			# charStringsIndex.
1141			self.charStringsAreIndexed = 1
1142		else:
1143			self.charStrings = {}
1144			# read from ttx file: charStrings.values() are actual charstrings
1145			self.charStringsAreIndexed = 0
1146			self.private = private
1147			if fdSelect is not None:
1148				self.fdSelect = fdSelect
1149			if fdArray is not None:
1150				self.fdArray = fdArray
1151
1152	def keys(self):
1153		return list(self.charStrings.keys())
1154
1155	def values(self):
1156		if self.charStringsAreIndexed:
1157			return self.charStringsIndex
1158		else:
1159			return list(self.charStrings.values())
1160
1161	def has_key(self, name):
1162		return name in self.charStrings
1163
1164	__contains__ = has_key
1165
1166	def __len__(self):
1167		return len(self.charStrings)
1168
1169	def __getitem__(self, name):
1170		charString = self.charStrings[name]
1171		if self.charStringsAreIndexed:
1172			charString = self.charStringsIndex[charString]
1173		return charString
1174
1175	def __setitem__(self, name, charString):
1176		if self.charStringsAreIndexed:
1177			index = self.charStrings[name]
1178			self.charStringsIndex[index] = charString
1179		else:
1180			self.charStrings[name] = charString
1181
1182	def getItemAndSelector(self, name):
1183		if self.charStringsAreIndexed:
1184			index = self.charStrings[name]
1185			return self.charStringsIndex.getItemAndSelector(index)
1186		else:
1187			if hasattr(self, 'fdArray'):
1188				if hasattr(self, 'fdSelect'):
1189					sel = self.charStrings[name].fdSelectIndex
1190				else:
1191					sel = 0
1192			else:
1193				sel = None
1194			return self.charStrings[name], sel
1195
1196	def toXML(self, xmlWriter):
1197		names = sorted(self.keys())
1198		for name in names:
1199			charStr, fdSelectIndex = self.getItemAndSelector(name)
1200			if charStr.needsDecompilation():
1201				raw = [("raw", 1)]
1202			else:
1203				raw = []
1204			if fdSelectIndex is None:
1205				xmlWriter.begintag("CharString", [('name', name)] + raw)
1206			else:
1207				xmlWriter.begintag(
1208					"CharString",
1209					[('name', name), ('fdSelectIndex', fdSelectIndex)] + raw)
1210			xmlWriter.newline()
1211			charStr.toXML(xmlWriter)
1212			xmlWriter.endtag("CharString")
1213			xmlWriter.newline()
1214
1215	def fromXML(self, name, attrs, content):
1216		for element in content:
1217			if isinstance(element, str):
1218				continue
1219			name, attrs, content = element
1220			if name != "CharString":
1221				continue
1222			fdID = -1
1223			if hasattr(self, "fdArray"):
1224				try:
1225					fdID = safeEval(attrs["fdSelectIndex"])
1226				except KeyError:
1227					fdID = 0
1228				private = self.fdArray[fdID].Private
1229			else:
1230				private = self.private
1231
1232			glyphName = attrs["name"]
1233			charStringClass = psCharStrings.T2CharString
1234			charString = charStringClass(
1235					private=private,
1236					globalSubrs=self.globalSubrs)
1237			charString.fromXML(name, attrs, content)
1238			if fdID >= 0:
1239				charString.fdSelectIndex = fdID
1240			self[glyphName] = charString
1241
1242
1243def readCard8(file):
1244	return byteord(file.read(1))
1245
1246
1247def readCard16(file):
1248	value, = struct.unpack(">H", file.read(2))
1249	return value
1250
1251
1252def readCard32(file):
1253	value, = struct.unpack(">L", file.read(4))
1254	return value
1255
1256
1257def writeCard8(file, value):
1258	file.write(bytechr(value))
1259
1260
1261def writeCard16(file, value):
1262	file.write(struct.pack(">H", value))
1263
1264
1265def writeCard32(file, value):
1266	file.write(struct.pack(">L", value))
1267
1268
1269def packCard8(value):
1270	return bytechr(value)
1271
1272
1273def packCard16(value):
1274	return struct.pack(">H", value)
1275
1276
1277def packCard32(value):
1278	return struct.pack(">L", value)
1279
1280
1281def buildOperatorDict(table):
1282	d = {}
1283	for op, name, arg, default, conv in table:
1284		d[op] = (name, arg)
1285	return d
1286
1287
1288def buildOpcodeDict(table):
1289	d = {}
1290	for op, name, arg, default, conv in table:
1291		if isinstance(op, tuple):
1292			op = bytechr(op[0]) + bytechr(op[1])
1293		else:
1294			op = bytechr(op)
1295		d[name] = (op, arg)
1296	return d
1297
1298
1299def buildOrder(table):
1300	l = []
1301	for op, name, arg, default, conv in table:
1302		l.append(name)
1303	return l
1304
1305
1306def buildDefaults(table):
1307	d = {}
1308	for op, name, arg, default, conv in table:
1309		if default is not None:
1310			d[name] = default
1311	return d
1312
1313
1314def buildConverters(table):
1315	d = {}
1316	for op, name, arg, default, conv in table:
1317		d[name] = conv
1318	return d
1319
1320
1321class SimpleConverter(object):
1322
1323	def read(self, parent, value):
1324		if not hasattr(parent, "file"):
1325			return self._read(parent, value)
1326		file = parent.file
1327		pos = file.tell()
1328		try:
1329			return self._read(parent, value)
1330		finally:
1331			file.seek(pos)
1332
1333	def _read(self, parent, value):
1334		return value
1335
1336	def write(self, parent, value):
1337		return value
1338
1339	def xmlWrite(self, xmlWriter, name, value):
1340		xmlWriter.simpletag(name, value=value)
1341		xmlWriter.newline()
1342
1343	def xmlRead(self, name, attrs, content, parent):
1344		return attrs["value"]
1345
1346
1347class ASCIIConverter(SimpleConverter):
1348
1349	def _read(self, parent, value):
1350		return tostr(value, encoding='ascii')
1351
1352	def write(self, parent, value):
1353		return tobytes(value, encoding='ascii')
1354
1355	def xmlWrite(self, xmlWriter, name, value):
1356		xmlWriter.simpletag(name, value=tostr(value, encoding="ascii"))
1357		xmlWriter.newline()
1358
1359	def xmlRead(self, name, attrs, content, parent):
1360		return tobytes(attrs["value"], encoding=("ascii"))
1361
1362
1363class Latin1Converter(SimpleConverter):
1364
1365	def _read(self, parent, value):
1366		return tostr(value, encoding='latin1')
1367
1368	def write(self, parent, value):
1369		return tobytes(value, encoding='latin1')
1370
1371	def xmlWrite(self, xmlWriter, name, value):
1372		value = tostr(value, encoding="latin1")
1373		if name in ['Notice', 'Copyright']:
1374			value = re.sub(r"[\r\n]\s+", " ", value)
1375		xmlWriter.simpletag(name, value=value)
1376		xmlWriter.newline()
1377
1378	def xmlRead(self, name, attrs, content, parent):
1379		return tobytes(attrs["value"], encoding=("latin1"))
1380
1381
1382def parseNum(s):
1383	try:
1384		value = int(s)
1385	except:
1386		value = float(s)
1387	return value
1388
1389
1390def parseBlendList(s):
1391	valueList = []
1392	for element in s:
1393		if isinstance(element, str):
1394			continue
1395		name, attrs, content = element
1396		blendList = attrs["value"].split()
1397		blendList = [eval(val) for val in blendList]
1398		valueList.append(blendList)
1399	if len(valueList) == 1:
1400		valueList = valueList[0]
1401	return valueList
1402
1403
1404class NumberConverter(SimpleConverter):
1405	def xmlWrite(self, xmlWriter, name, value):
1406		if isinstance(value, list):
1407			xmlWriter.begintag(name)
1408			xmlWriter.newline()
1409			xmlWriter.indent()
1410			blendValue = " ".join([str(val) for val in value])
1411			xmlWriter.simpletag(kBlendDictOpName, value=blendValue)
1412			xmlWriter.newline()
1413			xmlWriter.dedent()
1414			xmlWriter.endtag(name)
1415			xmlWriter.newline()
1416		else:
1417			xmlWriter.simpletag(name, value=value)
1418			xmlWriter.newline()
1419
1420	def xmlRead(self, name, attrs, content, parent):
1421		valueString = attrs.get("value", None)
1422		if valueString is None:
1423			value = parseBlendList(content)
1424		else:
1425			value = parseNum(attrs["value"])
1426		return value
1427
1428
1429class ArrayConverter(SimpleConverter):
1430	def xmlWrite(self, xmlWriter, name, value):
1431		if value and isinstance(value[0], list):
1432			xmlWriter.begintag(name)
1433			xmlWriter.newline()
1434			xmlWriter.indent()
1435			for valueList in value:
1436				blendValue = " ".join([str(val) for val in valueList])
1437				xmlWriter.simpletag(kBlendDictOpName, value=blendValue)
1438				xmlWriter.newline()
1439			xmlWriter.dedent()
1440			xmlWriter.endtag(name)
1441			xmlWriter.newline()
1442		else:
1443			value = " ".join([str(val) for val in value])
1444			xmlWriter.simpletag(name, value=value)
1445			xmlWriter.newline()
1446
1447	def xmlRead(self, name, attrs, content, parent):
1448		valueString = attrs.get("value", None)
1449		if valueString is None:
1450			valueList = parseBlendList(content)
1451		else:
1452			values = valueString.split()
1453			valueList = [parseNum(value) for value in values]
1454		return valueList
1455
1456
1457class TableConverter(SimpleConverter):
1458
1459	def xmlWrite(self, xmlWriter, name, value):
1460		xmlWriter.begintag(name)
1461		xmlWriter.newline()
1462		value.toXML(xmlWriter)
1463		xmlWriter.endtag(name)
1464		xmlWriter.newline()
1465
1466	def xmlRead(self, name, attrs, content, parent):
1467		ob = self.getClass()()
1468		for element in content:
1469			if isinstance(element, str):
1470				continue
1471			name, attrs, content = element
1472			ob.fromXML(name, attrs, content)
1473		return ob
1474
1475
1476class PrivateDictConverter(TableConverter):
1477
1478	def getClass(self):
1479		return PrivateDict
1480
1481	def _read(self, parent, value):
1482		size, offset = value
1483		file = parent.file
1484		isCFF2 = parent._isCFF2
1485		try:
1486			vstore = parent.vstore
1487		except AttributeError:
1488			vstore = None
1489		priv = PrivateDict(
1490			parent.strings, file, offset, isCFF2=isCFF2, vstore=vstore)
1491		file.seek(offset)
1492		data = file.read(size)
1493		assert len(data) == size
1494		priv.decompile(data)
1495		return priv
1496
1497	def write(self, parent, value):
1498		return (0, 0)  # dummy value
1499
1500
1501class SubrsConverter(TableConverter):
1502
1503	def getClass(self):
1504		return SubrsIndex
1505
1506	def _read(self, parent, value):
1507		file = parent.file
1508		isCFF2 = parent._isCFF2
1509		file.seek(parent.offset + value)  # Offset(self)
1510		return SubrsIndex(file, isCFF2=isCFF2)
1511
1512	def write(self, parent, value):
1513		return 0  # dummy value
1514
1515
1516class CharStringsConverter(TableConverter):
1517
1518	def _read(self, parent, value):
1519		file = parent.file
1520		isCFF2 = parent._isCFF2
1521		charset = parent.charset
1522		globalSubrs = parent.GlobalSubrs
1523		if hasattr(parent, "FDArray"):
1524			fdArray = parent.FDArray
1525			if hasattr(parent, "FDSelect"):
1526				fdSelect = parent.FDSelect
1527			else:
1528				fdSelect = None
1529			private = None
1530		else:
1531			fdSelect, fdArray = None, None
1532			private = parent.Private
1533		file.seek(value)  # Offset(0)
1534		charStrings = CharStrings(
1535			file, charset, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2)
1536		return charStrings
1537
1538	def write(self, parent, value):
1539		return 0  # dummy value
1540
1541	def xmlRead(self, name, attrs, content, parent):
1542		if hasattr(parent, "FDArray"):
1543			# if it is a CID-keyed font, then the private Dict is extracted from the
1544			# parent.FDArray
1545			fdArray = parent.FDArray
1546			if hasattr(parent, "FDSelect"):
1547				fdSelect = parent.FDSelect
1548			else:
1549				fdSelect = None
1550			private = None
1551		else:
1552			# if it is a name-keyed font, then the private dict is in the top dict,
1553			# and
1554			# there is no fdArray.
1555			private, fdSelect, fdArray = parent.Private, None, None
1556		charStrings = CharStrings(
1557			None, None, parent.GlobalSubrs, private, fdSelect, fdArray)
1558		charStrings.fromXML(name, attrs, content)
1559		return charStrings
1560
1561
1562class CharsetConverter(SimpleConverter):
1563	def _read(self, parent, value):
1564		isCID = hasattr(parent, "ROS")
1565		if value > 2:
1566			numGlyphs = parent.numGlyphs
1567			file = parent.file
1568			file.seek(value)
1569			log.log(DEBUG, "loading charset at %s", value)
1570			format = readCard8(file)
1571			if format == 0:
1572				charset = parseCharset0(numGlyphs, file, parent.strings, isCID)
1573			elif format == 1 or format == 2:
1574				charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
1575			else:
1576				raise NotImplementedError
1577			assert len(charset) == numGlyphs
1578			log.log(DEBUG, "    charset end at %s", file.tell())
1579			# make sure glyph names are unique
1580			allNames = {}
1581			newCharset = []
1582			for glyphName in charset:
1583				if glyphName in allNames:
1584					# make up a new glyphName that's unique
1585					n = allNames[glyphName]
1586					while (glyphName + "#" + str(n)) in allNames:
1587						n += 1
1588					allNames[glyphName] = n + 1
1589					glyphName = glyphName + "#" + str(n)
1590				allNames[glyphName] = 1
1591				newCharset.append(glyphName)
1592			charset = newCharset
1593		else:  # offset == 0 -> no charset data.
1594			if isCID or "CharStrings" not in parent.rawDict:
1595				# We get here only when processing fontDicts from the FDArray of
1596				# CFF-CID fonts. Only the real topDict references the chrset.
1597				assert value == 0
1598				charset = None
1599			elif value == 0:
1600				charset = cffISOAdobeStrings
1601			elif value == 1:
1602				charset = cffIExpertStrings
1603			elif value == 2:
1604				charset = cffExpertSubsetStrings
1605		if charset and (len(charset) != parent.numGlyphs):
1606			charset = charset[:parent.numGlyphs]
1607		return charset
1608
1609	def write(self, parent, value):
1610		return 0  # dummy value
1611
1612	def xmlWrite(self, xmlWriter, name, value):
1613		# XXX only write charset when not in OT/TTX context, where we
1614		# dump charset as a separate "GlyphOrder" table.
1615		# # xmlWriter.simpletag("charset")
1616		xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element")
1617		xmlWriter.newline()
1618
1619	def xmlRead(self, name, attrs, content, parent):
1620		pass
1621
1622
1623class CharsetCompiler(object):
1624
1625	def __init__(self, strings, charset, parent):
1626		assert charset[0] == '.notdef'
1627		isCID = hasattr(parent.dictObj, "ROS")
1628		data0 = packCharset0(charset, isCID, strings)
1629		data = packCharset(charset, isCID, strings)
1630		if len(data) < len(data0):
1631			self.data = data
1632		else:
1633			self.data = data0
1634		self.parent = parent
1635
1636	def setPos(self, pos, endPos):
1637		self.parent.rawDict["charset"] = pos
1638
1639	def getDataLength(self):
1640		return len(self.data)
1641
1642	def toFile(self, file):
1643		file.write(self.data)
1644
1645
1646def getStdCharSet(charset):
1647	# check to see if we can use a predefined charset value.
1648	predefinedCharSetVal = None
1649	predefinedCharSets = [
1650		(cffISOAdobeStringCount, cffISOAdobeStrings, 0),
1651		(cffExpertStringCount, cffIExpertStrings, 1),
1652		(cffExpertSubsetStringCount, cffExpertSubsetStrings, 2)]
1653	lcs = len(charset)
1654	for cnt, pcs, csv in predefinedCharSets:
1655		if predefinedCharSetVal is not None:
1656			break
1657		if lcs > cnt:
1658			continue
1659		predefinedCharSetVal = csv
1660		for i in range(lcs):
1661			if charset[i] != pcs[i]:
1662				predefinedCharSetVal = None
1663				break
1664	return predefinedCharSetVal
1665
1666
1667def getCIDfromName(name, strings):
1668	return int(name[3:])
1669
1670
1671def getSIDfromName(name, strings):
1672	return strings.getSID(name)
1673
1674
1675def packCharset0(charset, isCID, strings):
1676	fmt = 0
1677	data = [packCard8(fmt)]
1678	if isCID:
1679		getNameID = getCIDfromName
1680	else:
1681		getNameID = getSIDfromName
1682
1683	for name in charset[1:]:
1684		data.append(packCard16(getNameID(name, strings)))
1685	return bytesjoin(data)
1686
1687
1688def packCharset(charset, isCID, strings):
1689	fmt = 1
1690	ranges = []
1691	first = None
1692	end = 0
1693	if isCID:
1694		getNameID = getCIDfromName
1695	else:
1696		getNameID = getSIDfromName
1697
1698	for name in charset[1:]:
1699		SID = getNameID(name, strings)
1700		if first is None:
1701			first = SID
1702		elif end + 1 != SID:
1703			nLeft = end - first
1704			if nLeft > 255:
1705				fmt = 2
1706			ranges.append((first, nLeft))
1707			first = SID
1708		end = SID
1709	if end:
1710		nLeft = end - first
1711		if nLeft > 255:
1712			fmt = 2
1713		ranges.append((first, nLeft))
1714
1715	data = [packCard8(fmt)]
1716	if fmt == 1:
1717		nLeftFunc = packCard8
1718	else:
1719		nLeftFunc = packCard16
1720	for first, nLeft in ranges:
1721		data.append(packCard16(first) + nLeftFunc(nLeft))
1722	return bytesjoin(data)
1723
1724
1725def parseCharset0(numGlyphs, file, strings, isCID):
1726	charset = [".notdef"]
1727	if isCID:
1728		for i in range(numGlyphs - 1):
1729			CID = readCard16(file)
1730			charset.append("cid" + str(CID).zfill(5))
1731	else:
1732		for i in range(numGlyphs - 1):
1733			SID = readCard16(file)
1734			charset.append(strings[SID])
1735	return charset
1736
1737
1738def parseCharset(numGlyphs, file, strings, isCID, fmt):
1739	charset = ['.notdef']
1740	count = 1
1741	if fmt == 1:
1742		nLeftFunc = readCard8
1743	else:
1744		nLeftFunc = readCard16
1745	while count < numGlyphs:
1746		first = readCard16(file)
1747		nLeft = nLeftFunc(file)
1748		if isCID:
1749			for CID in range(first, first + nLeft + 1):
1750				charset.append("cid" + str(CID).zfill(5))
1751		else:
1752			for SID in range(first, first + nLeft + 1):
1753				charset.append(strings[SID])
1754		count = count + nLeft + 1
1755	return charset
1756
1757
1758class EncodingCompiler(object):
1759
1760	def __init__(self, strings, encoding, parent):
1761		assert not isinstance(encoding, str)
1762		data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings)
1763		data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings)
1764		if len(data0) < len(data1):
1765			self.data = data0
1766		else:
1767			self.data = data1
1768		self.parent = parent
1769
1770	def setPos(self, pos, endPos):
1771		self.parent.rawDict["Encoding"] = pos
1772
1773	def getDataLength(self):
1774		return len(self.data)
1775
1776	def toFile(self, file):
1777		file.write(self.data)
1778
1779
1780class EncodingConverter(SimpleConverter):
1781
1782	def _read(self, parent, value):
1783		if value == 0:
1784			return "StandardEncoding"
1785		elif value == 1:
1786			return "ExpertEncoding"
1787		else:
1788			assert value > 1
1789			file = parent.file
1790			file.seek(value)
1791			log.log(DEBUG, "loading Encoding at %s", value)
1792			fmt = readCard8(file)
1793			haveSupplement = fmt & 0x80
1794			if haveSupplement:
1795				raise NotImplementedError("Encoding supplements are not yet supported")
1796			fmt = fmt & 0x7f
1797			if fmt == 0:
1798				encoding = parseEncoding0(parent.charset, file, haveSupplement,
1799						parent.strings)
1800			elif fmt == 1:
1801				encoding = parseEncoding1(parent.charset, file, haveSupplement,
1802						parent.strings)
1803			return encoding
1804
1805	def write(self, parent, value):
1806		if value == "StandardEncoding":
1807			return 0
1808		elif value == "ExpertEncoding":
1809			return 1
1810		return 0  # dummy value
1811
1812	def xmlWrite(self, xmlWriter, name, value):
1813		if value in ("StandardEncoding", "ExpertEncoding"):
1814			xmlWriter.simpletag(name, name=value)
1815			xmlWriter.newline()
1816			return
1817		xmlWriter.begintag(name)
1818		xmlWriter.newline()
1819		for code in range(len(value)):
1820			glyphName = value[code]
1821			if glyphName != ".notdef":
1822				xmlWriter.simpletag("map", code=hex(code), name=glyphName)
1823				xmlWriter.newline()
1824		xmlWriter.endtag(name)
1825		xmlWriter.newline()
1826
1827	def xmlRead(self, name, attrs, content, parent):
1828		if "name" in attrs:
1829			return attrs["name"]
1830		encoding = [".notdef"] * 256
1831		for element in content:
1832			if isinstance(element, str):
1833				continue
1834			name, attrs, content = element
1835			code = safeEval(attrs["code"])
1836			glyphName = attrs["name"]
1837			encoding[code] = glyphName
1838		return encoding
1839
1840
1841def parseEncoding0(charset, file, haveSupplement, strings):
1842	nCodes = readCard8(file)
1843	encoding = [".notdef"] * 256
1844	for glyphID in range(1, nCodes + 1):
1845		code = readCard8(file)
1846		if code != 0:
1847			encoding[code] = charset[glyphID]
1848	return encoding
1849
1850
1851def parseEncoding1(charset, file, haveSupplement, strings):
1852	nRanges = readCard8(file)
1853	encoding = [".notdef"] * 256
1854	glyphID = 1
1855	for i in range(nRanges):
1856		code = readCard8(file)
1857		nLeft = readCard8(file)
1858		for glyphID in range(glyphID, glyphID + nLeft + 1):
1859			encoding[code] = charset[glyphID]
1860			code = code + 1
1861		glyphID = glyphID + 1
1862	return encoding
1863
1864
1865def packEncoding0(charset, encoding, strings):
1866	fmt = 0
1867	m = {}
1868	for code in range(len(encoding)):
1869		name = encoding[code]
1870		if name != ".notdef":
1871			m[name] = code
1872	codes = []
1873	for name in charset[1:]:
1874		code = m.get(name)
1875		codes.append(code)
1876
1877	while codes and codes[-1] is None:
1878		codes.pop()
1879
1880	data = [packCard8(fmt), packCard8(len(codes))]
1881	for code in codes:
1882		if code is None:
1883			code = 0
1884		data.append(packCard8(code))
1885	return bytesjoin(data)
1886
1887
1888def packEncoding1(charset, encoding, strings):
1889	fmt = 1
1890	m = {}
1891	for code in range(len(encoding)):
1892		name = encoding[code]
1893		if name != ".notdef":
1894			m[name] = code
1895	ranges = []
1896	first = None
1897	end = 0
1898	for name in charset[1:]:
1899		code = m.get(name, -1)
1900		if first is None:
1901			first = code
1902		elif end + 1 != code:
1903			nLeft = end - first
1904			ranges.append((first, nLeft))
1905			first = code
1906		end = code
1907	nLeft = end - first
1908	ranges.append((first, nLeft))
1909
1910	# remove unencoded glyphs at the end.
1911	while ranges and ranges[-1][0] == -1:
1912		ranges.pop()
1913
1914	data = [packCard8(fmt), packCard8(len(ranges))]
1915	for first, nLeft in ranges:
1916		if first == -1:  # unencoded
1917			first = 0
1918		data.append(packCard8(first) + packCard8(nLeft))
1919	return bytesjoin(data)
1920
1921
1922class FDArrayConverter(TableConverter):
1923
1924	def _read(self, parent, value):
1925		try:
1926			vstore = parent.VarStore
1927		except AttributeError:
1928			vstore = None
1929		file = parent.file
1930		isCFF2 = parent._isCFF2
1931		file.seek(value)
1932		fdArray = FDArrayIndex(file, isCFF2=isCFF2)
1933		fdArray.vstore = vstore
1934		fdArray.strings = parent.strings
1935		fdArray.GlobalSubrs = parent.GlobalSubrs
1936		return fdArray
1937
1938	def write(self, parent, value):
1939		return 0  # dummy value
1940
1941	def xmlRead(self, name, attrs, content, parent):
1942		fdArray = FDArrayIndex()
1943		for element in content:
1944			if isinstance(element, str):
1945				continue
1946			name, attrs, content = element
1947			fdArray.fromXML(name, attrs, content)
1948		return fdArray
1949
1950
1951class FDSelectConverter(SimpleConverter):
1952
1953	def _read(self, parent, value):
1954		file = parent.file
1955		file.seek(value)
1956		fdSelect = FDSelect(file, parent.numGlyphs)
1957		return fdSelect
1958
1959	def write(self, parent, value):
1960		return 0  # dummy value
1961
1962	# The FDSelect glyph data is written out to XML in the charstring keys,
1963	# so we write out only the format selector
1964	def xmlWrite(self, xmlWriter, name, value):
1965		xmlWriter.simpletag(name, [('format', value.format)])
1966		xmlWriter.newline()
1967
1968	def xmlRead(self, name, attrs, content, parent):
1969		fmt = safeEval(attrs["format"])
1970		file = None
1971		numGlyphs = None
1972		fdSelect = FDSelect(file, numGlyphs, fmt)
1973		return fdSelect
1974
1975
1976class VarStoreConverter(SimpleConverter):
1977
1978	def _read(self, parent, value):
1979		file = parent.file
1980		file.seek(value)
1981		varStore = VarStoreData(file)
1982		varStore.decompile()
1983		return varStore
1984
1985	def write(self, parent, value):
1986		return 0  # dummy value
1987
1988	def xmlWrite(self, xmlWriter, name, value):
1989		value.writeXML(xmlWriter, name)
1990
1991	def xmlRead(self, name, attrs, content, parent):
1992		varStore = VarStoreData()
1993		varStore.xmlRead(name, attrs, content, parent)
1994		return varStore
1995
1996
1997def packFDSelect0(fdSelectArray):
1998	fmt = 0
1999	data = [packCard8(fmt)]
2000	for index in fdSelectArray:
2001		data.append(packCard8(index))
2002	return bytesjoin(data)
2003
2004
2005def packFDSelect3(fdSelectArray):
2006	fmt = 3
2007	fdRanges = []
2008	lenArray = len(fdSelectArray)
2009	lastFDIndex = -1
2010	for i in range(lenArray):
2011		fdIndex = fdSelectArray[i]
2012		if lastFDIndex != fdIndex:
2013			fdRanges.append([i, fdIndex])
2014			lastFDIndex = fdIndex
2015	sentinelGID = i + 1
2016
2017	data = [packCard8(fmt)]
2018	data.append(packCard16(len(fdRanges)))
2019	for fdRange in fdRanges:
2020		data.append(packCard16(fdRange[0]))
2021		data.append(packCard8(fdRange[1]))
2022	data.append(packCard16(sentinelGID))
2023	return bytesjoin(data)
2024
2025
2026def packFDSelect4(fdSelectArray):
2027	fmt = 4
2028	fdRanges = []
2029	lenArray = len(fdSelectArray)
2030	lastFDIndex = -1
2031	for i in range(lenArray):
2032		fdIndex = fdSelectArray[i]
2033		if lastFDIndex != fdIndex:
2034			fdRanges.append([i, fdIndex])
2035			lastFDIndex = fdIndex
2036	sentinelGID = i + 1
2037
2038	data = [packCard8(fmt)]
2039	data.append(packCard32(len(fdRanges)))
2040	for fdRange in fdRanges:
2041		data.append(packCard32(fdRange[0]))
2042		data.append(packCard16(fdRange[1]))
2043	data.append(packCard32(sentinelGID))
2044	return bytesjoin(data)
2045
2046
2047class FDSelectCompiler(object):
2048
2049	def __init__(self, fdSelect, parent):
2050		fmt = fdSelect.format
2051		fdSelectArray = fdSelect.gidArray
2052		if fmt == 0:
2053			self.data = packFDSelect0(fdSelectArray)
2054		elif fmt == 3:
2055			self.data = packFDSelect3(fdSelectArray)
2056		elif fmt == 4:
2057			self.data = packFDSelect4(fdSelectArray)
2058		else:
2059			# choose smaller of the two formats
2060			data0 = packFDSelect0(fdSelectArray)
2061			data3 = packFDSelect3(fdSelectArray)
2062			if len(data0) < len(data3):
2063				self.data = data0
2064				fdSelect.format = 0
2065			else:
2066				self.data = data3
2067				fdSelect.format = 3
2068
2069		self.parent = parent
2070
2071	def setPos(self, pos, endPos):
2072		self.parent.rawDict["FDSelect"] = pos
2073
2074	def getDataLength(self):
2075		return len(self.data)
2076
2077	def toFile(self, file):
2078		file.write(self.data)
2079
2080
2081class VarStoreCompiler(object):
2082
2083	def __init__(self, varStoreData, parent):
2084		self.parent = parent
2085		if not varStoreData.data:
2086			varStoreData.compile()
2087		data = [
2088			packCard16(len(varStoreData.data)),
2089			varStoreData.data
2090		]
2091		self.data = bytesjoin(data)
2092
2093	def setPos(self, pos, endPos):
2094		self.parent.rawDict["VarStore"] = pos
2095
2096	def getDataLength(self):
2097		return len(self.data)
2098
2099	def toFile(self, file):
2100		file.write(self.data)
2101
2102
2103class ROSConverter(SimpleConverter):
2104
2105	def xmlWrite(self, xmlWriter, name, value):
2106		registry, order, supplement = value
2107		xmlWriter.simpletag(
2108			name,
2109			[
2110				('Registry', tostr(registry)),
2111				('Order', tostr(order)),
2112				('Supplement', supplement)
2113			])
2114		xmlWriter.newline()
2115
2116	def xmlRead(self, name, attrs, content, parent):
2117		return (attrs['Registry'], attrs['Order'], safeEval(attrs['Supplement']))
2118
2119topDictOperators = [
2120#	opcode		name			argument type	default	converter
2121	(25,		'maxstack',		'number',	None,	None),
2122	((12, 30),	'ROS',	('SID', 'SID', 'number'),	None,	ROSConverter()),
2123	((12, 20),	'SyntheticBase',	'number',	None,	None),
2124	(0,		'version',		'SID',		None,	None),
2125	(1,		'Notice',		'SID',		None,	Latin1Converter()),
2126	((12, 0),	'Copyright',		'SID',		None,	Latin1Converter()),
2127	(2,		'FullName',		'SID',		None,	None),
2128	((12, 38),	'FontName',		'SID',		None,	None),
2129	(3,		'FamilyName',		'SID',		None,	None),
2130	(4,		'Weight',		'SID',		None,	None),
2131	((12, 1),	'isFixedPitch',		'number',	0,	None),
2132	((12, 2),	'ItalicAngle',		'number',	0,	None),
2133	((12, 3),	'UnderlinePosition',	'number',	-100,	None),
2134	((12, 4),	'UnderlineThickness',	'number',	50,	None),
2135	((12, 5),	'PaintType',		'number',	0,	None),
2136	((12, 6),	'CharstringType',	'number',	2,	None),
2137	((12, 7),	'FontMatrix',		'array',	[0.001, 0, 0, 0.001, 0, 0],	None),
2138	(13,		'UniqueID',		'number',	None,	None),
2139	(5,		'FontBBox',		'array',	[0, 0, 0, 0],	None),
2140	((12, 8),	'StrokeWidth',		'number',	0,	None),
2141	(14,		'XUID',			'array',	None,	None),
2142	((12, 21),	'PostScript',		'SID',		None,	None),
2143	((12, 22),	'BaseFontName',		'SID',		None,	None),
2144	((12, 23),	'BaseFontBlend',	'delta',	None,	None),
2145	((12, 31),	'CIDFontVersion',	'number',	0,	None),
2146	((12, 32),	'CIDFontRevision',	'number',	0,	None),
2147	((12, 33),	'CIDFontType',		'number',	0,	None),
2148	((12, 34),	'CIDCount',		'number',	8720,	None),
2149	(15,		'charset',		'number',	None,	CharsetConverter()),
2150	((12, 35),	'UIDBase',		'number',	None,	None),
2151	(16,		'Encoding',		'number',	0,	EncodingConverter()),
2152	(18,		'Private',	('number', 'number'),	None,	PrivateDictConverter()),
2153	((12, 37),	'FDSelect',		'number',	None,	FDSelectConverter()),
2154	((12, 36),	'FDArray',		'number',	None,	FDArrayConverter()),
2155	(17,		'CharStrings',		'number',	None,	CharStringsConverter()),
2156	(24,		'VarStore',		'number',	None,	VarStoreConverter()),
2157]
2158
2159topDictOperators2 = [
2160#	opcode		name			argument type	default	converter
2161	(25,		'maxstack',		'number',	None,	None),
2162	((12, 7),	'FontMatrix',		'array',	[0.001, 0, 0, 0.001, 0, 0],	None),
2163	((12, 37),	'FDSelect',		'number',	None,	FDSelectConverter()),
2164	((12, 36),	'FDArray',		'number',	None,	FDArrayConverter()),
2165	(17,		'CharStrings',		'number',	None,	CharStringsConverter()),
2166	(24,		'VarStore',		'number',	None,	VarStoreConverter()),
2167]
2168
2169# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order,
2170# in order for the font to compile back from xml.
2171
2172kBlendDictOpName = "blend"
2173blendOp = 23
2174
2175privateDictOperators = [
2176#	opcode		name			argument type	default	converter
2177	(22,	"vsindex",		'number',	None,	None),
2178	(blendOp,	kBlendDictOpName,		'blendList',	None,	None), # This is for reading to/from XML: it not written to CFF.
2179	(6,		'BlueValues',		'delta',	None,	None),
2180	(7,		'OtherBlues',		'delta',	None,	None),
2181	(8,		'FamilyBlues',		'delta',	None,	None),
2182	(9,		'FamilyOtherBlues',	'delta',	None,	None),
2183	((12, 9),	'BlueScale',		'number',	0.039625, None),
2184	((12, 10),	'BlueShift',		'number',	7,	None),
2185	((12, 11),	'BlueFuzz',		'number',	1,	None),
2186	(10,		'StdHW',		'number',	None,	None),
2187	(11,		'StdVW',		'number',	None,	None),
2188	((12, 12),	'StemSnapH',		'delta',	None,	None),
2189	((12, 13),	'StemSnapV',		'delta',	None,	None),
2190	((12, 14),	'ForceBold',		'number',	0,	None),
2191	((12, 15),	'ForceBoldThreshold',	'number',	None,	None), # deprecated
2192	((12, 16),	'lenIV',		'number',	None,	None), # deprecated
2193	((12, 17),	'LanguageGroup',	'number',	0,	None),
2194	((12, 18),	'ExpansionFactor',	'number',	0.06,	None),
2195	((12, 19),	'initialRandomSeed',	'number',	0,	None),
2196	(20,		'defaultWidthX',	'number',	0,	None),
2197	(21,		'nominalWidthX',	'number',	0,	None),
2198	(19,		'Subrs',		'number',	None,	SubrsConverter()),
2199]
2200
2201privateDictOperators2 = [
2202#	opcode		name			argument type	default	converter
2203	(22,	"vsindex",		'number',	None,	None),
2204	(blendOp,	kBlendDictOpName,		'blendList',	None,	None), # This is for reading to/from XML: it not written to CFF.
2205	(6,		'BlueValues',		'delta',	None,	None),
2206	(7,		'OtherBlues',		'delta',	None,	None),
2207	(8,		'FamilyBlues',		'delta',	None,	None),
2208	(9,		'FamilyOtherBlues',	'delta',	None,	None),
2209	((12, 9),	'BlueScale',		'number',	0.039625, None),
2210	((12, 10),	'BlueShift',		'number',	7,	None),
2211	((12, 11),	'BlueFuzz',		'number',	1,	None),
2212	(10,		'StdHW',		'number',	None,	None),
2213	(11,		'StdVW',		'number',	None,	None),
2214	((12, 12),	'StemSnapH',		'delta',	None,	None),
2215	((12, 13),	'StemSnapV',		'delta',	None,	None),
2216	((12, 17),	'LanguageGroup',	'number',	0,	None),
2217	((12, 18),	'ExpansionFactor',	'number',	0.06,	None),
2218	(19,		'Subrs',		'number',	None,	SubrsConverter()),
2219]
2220
2221
2222def addConverters(table):
2223	for i in range(len(table)):
2224		op, name, arg, default, conv = table[i]
2225		if conv is not None:
2226			continue
2227		if arg in ("delta", "array"):
2228			conv = ArrayConverter()
2229		elif arg == "number":
2230			conv = NumberConverter()
2231		elif arg == "SID":
2232			conv = ASCIIConverter()
2233		elif arg == 'blendList':
2234			conv = None
2235		else:
2236			assert False
2237		table[i] = op, name, arg, default, conv
2238
2239
2240addConverters(privateDictOperators)
2241addConverters(topDictOperators)
2242
2243
2244class TopDictDecompiler(psCharStrings.DictDecompiler):
2245	operators = buildOperatorDict(topDictOperators)
2246
2247
2248class PrivateDictDecompiler(psCharStrings.DictDecompiler):
2249	operators = buildOperatorDict(privateDictOperators)
2250
2251
2252class DictCompiler(object):
2253	maxBlendStack = 0
2254
2255	def __init__(self, dictObj, strings, parent, isCFF2=None):
2256		if strings:
2257			assert isinstance(strings, IndexedStrings)
2258		if isCFF2 is None and hasattr(parent, "isCFF2"):
2259			isCFF2 = parent.isCFF2
2260			assert isCFF2 is not None
2261		self.isCFF2 = isCFF2
2262		self.dictObj = dictObj
2263		self.strings = strings
2264		self.parent = parent
2265		rawDict = {}
2266		for name in dictObj.order:
2267			value = getattr(dictObj, name, None)
2268			if value is None:
2269				continue
2270			conv = dictObj.converters[name]
2271			value = conv.write(dictObj, value)
2272			if value == dictObj.defaults.get(name):
2273				continue
2274			rawDict[name] = value
2275		self.rawDict = rawDict
2276
2277	def setPos(self, pos, endPos):
2278		pass
2279
2280	def getDataLength(self):
2281		return len(self.compile("getDataLength"))
2282
2283	def compile(self, reason):
2284		log.log(DEBUG, "-- compiling %s for %s", self.__class__.__name__, reason)
2285		rawDict = self.rawDict
2286		data = []
2287		for name in self.dictObj.order:
2288			value = rawDict.get(name)
2289			if value is None:
2290				continue
2291			op, argType = self.opcodes[name]
2292			if isinstance(argType, tuple):
2293				l = len(argType)
2294				assert len(value) == l, "value doesn't match arg type"
2295				for i in range(l):
2296					arg = argType[i]
2297					v = value[i]
2298					arghandler = getattr(self, "arg_" + arg)
2299					data.append(arghandler(v))
2300			else:
2301				arghandler = getattr(self, "arg_" + argType)
2302				data.append(arghandler(value))
2303			data.append(op)
2304		data = bytesjoin(data)
2305		return data
2306
2307	def toFile(self, file):
2308		data = self.compile("toFile")
2309		file.write(data)
2310
2311	def arg_number(self, num):
2312		if isinstance(num, list):
2313			data = [encodeNumber(val) for val in num]
2314			data.append(encodeNumber(1))
2315			data.append(bytechr(blendOp))
2316			datum = bytesjoin(data)
2317		else:
2318			datum = encodeNumber(num)
2319		return datum
2320
2321	def arg_SID(self, s):
2322		return psCharStrings.encodeIntCFF(self.strings.getSID(s))
2323
2324	def arg_array(self, value):
2325		data = []
2326		for num in value:
2327			data.append(self.arg_number(num))
2328		return bytesjoin(data)
2329
2330	def arg_delta(self, value):
2331		if not value:
2332			return b""
2333		val0 = value[0]
2334		if isinstance(val0, list):
2335			data = self.arg_delta_blend(value)
2336		else:
2337			out = []
2338			last = 0
2339			for v in value:
2340				out.append(v - last)
2341				last = v
2342			data = []
2343			for num in out:
2344				data.append(encodeNumber(num))
2345		return bytesjoin(data)
2346
2347
2348	def arg_delta_blend(self, value):
2349		"""A delta list with blend lists has to be *all* blend lists.
2350
2351		The value is a list is arranged as follows::
2352
2353			[
2354				[V0, d0..dn]
2355				[V1, d0..dn]
2356				...
2357				[Vm, d0..dn]
2358			]
2359
2360		``V`` is the absolute coordinate value from the default font, and ``d0-dn``
2361		are the delta values from the *n* regions. Each ``V`` is an absolute
2362		coordinate from the default font.
2363
2364		We want to return a list::
2365
2366			[
2367				[v0, v1..vm]
2368				[d0..dn]
2369				...
2370				[d0..dn]
2371				numBlends
2372				blendOp
2373			]
2374
2375		where each ``v`` is relative to the previous default font value.
2376		"""
2377		numMasters = len(value[0])
2378		numBlends = len(value)
2379		numStack = (numBlends * numMasters) + 1
2380		if numStack > self.maxBlendStack:
2381			# Figure out the max number of value we can blend
2382			# and divide this list up into chunks of that size.
2383
2384			numBlendValues = int((self.maxBlendStack - 1) / numMasters)
2385			out = []
2386			while True:
2387				numVal = min(len(value), numBlendValues)
2388				if numVal == 0:
2389					break
2390				valList = value[0:numVal]
2391				out1 = self.arg_delta_blend(valList)
2392				out.extend(out1)
2393				value = value[numVal:]
2394		else:
2395			firstList = [0] * numBlends
2396			deltaList = [None] * numBlends
2397			i = 0
2398			prevVal = 0
2399			while i < numBlends:
2400				# For PrivateDict BlueValues, the default font
2401				# values are absolute, not relative.
2402				# Must convert these back to relative coordinates
2403				# befor writing to CFF2.
2404				defaultValue = value[i][0]
2405				firstList[i] = defaultValue - prevVal
2406				prevVal = defaultValue
2407				deltaList[i] = value[i][1:]
2408				i += 1
2409
2410			relValueList = firstList
2411			for blendList in deltaList:
2412				relValueList.extend(blendList)
2413			out = [encodeNumber(val) for val in relValueList]
2414			out.append(encodeNumber(numBlends))
2415			out.append(bytechr(blendOp))
2416		return out
2417
2418
2419def encodeNumber(num):
2420	if isinstance(num, float):
2421		return psCharStrings.encodeFloat(num)
2422	else:
2423		return psCharStrings.encodeIntCFF(num)
2424
2425
2426class TopDictCompiler(DictCompiler):
2427
2428	opcodes = buildOpcodeDict(topDictOperators)
2429
2430	def getChildren(self, strings):
2431		isCFF2 = self.isCFF2
2432		children = []
2433		if self.dictObj.cff2GetGlyphOrder is None:
2434			if hasattr(self.dictObj, "charset") and self.dictObj.charset:
2435				if hasattr(self.dictObj, "ROS"):  # aka isCID
2436					charsetCode = None
2437				else:
2438					charsetCode = getStdCharSet(self.dictObj.charset)
2439				if charsetCode is None:
2440					children.append(CharsetCompiler(strings, self.dictObj.charset, self))
2441				else:
2442					self.rawDict["charset"] = charsetCode
2443			if hasattr(self.dictObj, "Encoding") and self.dictObj.Encoding:
2444				encoding = self.dictObj.Encoding
2445				if not isinstance(encoding, str):
2446					children.append(EncodingCompiler(strings, encoding, self))
2447		else:
2448			if hasattr(self.dictObj, "VarStore"):
2449				varStoreData = self.dictObj.VarStore
2450				varStoreComp = VarStoreCompiler(varStoreData, self)
2451				children.append(varStoreComp)
2452		if hasattr(self.dictObj, "FDSelect"):
2453			# I have not yet supported merging a ttx CFF-CID font, as there are
2454			# interesting issues about merging the FDArrays. Here I assume that
2455			# either the font was read from XML, and the FDSelect indices are all
2456			# in the charstring data, or the FDSelect array is already fully defined.
2457			fdSelect = self.dictObj.FDSelect
2458			# probably read in from XML; assume fdIndex in CharString data
2459			if len(fdSelect) == 0:
2460				charStrings = self.dictObj.CharStrings
2461				for name in self.dictObj.charset:
2462					fdSelect.append(charStrings[name].fdSelectIndex)
2463			fdSelectComp = FDSelectCompiler(fdSelect, self)
2464			children.append(fdSelectComp)
2465		if hasattr(self.dictObj, "CharStrings"):
2466			items = []
2467			charStrings = self.dictObj.CharStrings
2468			for name in self.dictObj.charset:
2469				items.append(charStrings[name])
2470			charStringsComp = CharStringsCompiler(
2471				items, strings, self, isCFF2=isCFF2)
2472			children.append(charStringsComp)
2473		if hasattr(self.dictObj, "FDArray"):
2474			# I have not yet supported merging a ttx CFF-CID font, as there are
2475			# interesting issues about merging the FDArrays. Here I assume that the
2476			# FDArray info is correct and complete.
2477			fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self)
2478			children.append(fdArrayIndexComp)
2479			children.extend(fdArrayIndexComp.getChildren(strings))
2480		if hasattr(self.dictObj, "Private"):
2481			privComp = self.dictObj.Private.getCompiler(strings, self)
2482			children.append(privComp)
2483			children.extend(privComp.getChildren(strings))
2484		return children
2485
2486
2487class FontDictCompiler(DictCompiler):
2488	opcodes = buildOpcodeDict(topDictOperators)
2489
2490	def __init__(self, dictObj, strings, parent, isCFF2=None):
2491		super(FontDictCompiler, self).__init__(dictObj, strings, parent, isCFF2=isCFF2)
2492		#
2493		# We now take some effort to detect if there were any key/value pairs
2494		# supplied that were ignored in the FontDict context, and issue a warning
2495		# for those cases.
2496		#
2497		ignoredNames = []
2498		dictObj = self.dictObj
2499		for name in sorted(set(dictObj.converters) - set(dictObj.order)):
2500			if name in dictObj.rawDict:
2501				# The font was directly read from binary. In this
2502				# case, we want to report *all* "useless" key/value
2503				# pairs that are in the font, not just the ones that
2504				# are different from the default.
2505				ignoredNames.append(name)
2506			else:
2507				# The font was probably read from a TTX file. We only
2508				# warn about keys whos value is not the default. The
2509				# ones that have the default value will not be written
2510				# to binary anyway.
2511				default = dictObj.defaults.get(name)
2512				if default is not None:
2513					conv = dictObj.converters[name]
2514					default = conv.read(dictObj, default)
2515				if getattr(dictObj, name, None) != default:
2516					ignoredNames.append(name)
2517		if ignoredNames:
2518			log.warning(
2519				"Some CFF FDArray/FontDict keys were ignored upon compile: " +
2520				" ".join(sorted(ignoredNames)))
2521
2522	def getChildren(self, strings):
2523		children = []
2524		if hasattr(self.dictObj, "Private"):
2525			privComp = self.dictObj.Private.getCompiler(strings, self)
2526			children.append(privComp)
2527			children.extend(privComp.getChildren(strings))
2528		return children
2529
2530
2531class PrivateDictCompiler(DictCompiler):
2532
2533	maxBlendStack = maxStackLimit
2534	opcodes = buildOpcodeDict(privateDictOperators)
2535
2536	def setPos(self, pos, endPos):
2537		size = endPos - pos
2538		self.parent.rawDict["Private"] = size, pos
2539		self.pos = pos
2540
2541	def getChildren(self, strings):
2542		children = []
2543		if hasattr(self.dictObj, "Subrs"):
2544			children.append(self.dictObj.Subrs.getCompiler(strings, self))
2545		return children
2546
2547
2548class BaseDict(object):
2549
2550	def __init__(self, strings=None, file=None, offset=None, isCFF2=None):
2551		assert (isCFF2 is None) == (file is None)
2552		self.rawDict = {}
2553		self.skipNames = []
2554		self.strings = strings
2555		if file is None:
2556			return
2557		self._isCFF2 = isCFF2
2558		self.file = file
2559		if offset is not None:
2560			log.log(DEBUG, "loading %s at %s", self.__class__.__name__, offset)
2561			self.offset = offset
2562
2563	def decompile(self, data):
2564		log.log(DEBUG, "    length %s is %d", self.__class__.__name__, len(data))
2565		dec = self.decompilerClass(self.strings, self)
2566		dec.decompile(data)
2567		self.rawDict = dec.getDict()
2568		self.postDecompile()
2569
2570	def postDecompile(self):
2571		pass
2572
2573	def getCompiler(self, strings, parent, isCFF2=None):
2574		return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
2575
2576	def __getattr__(self, name):
2577		if name[:2] == name[-2:] == "__":
2578			# to make deepcopy() and pickle.load() work, we need to signal with
2579			# AttributeError that dunder methods like '__deepcopy__' or '__getstate__'
2580			# aren't implemented. For more details, see:
2581			# https://github.com/fonttools/fonttools/pull/1488
2582			raise AttributeError(name)
2583		value = self.rawDict.get(name, None)
2584		if value is None:
2585			value = self.defaults.get(name)
2586		if value is None:
2587			raise AttributeError(name)
2588		conv = self.converters[name]
2589		value = conv.read(self, value)
2590		setattr(self, name, value)
2591		return value
2592
2593	def toXML(self, xmlWriter):
2594		for name in self.order:
2595			if name in self.skipNames:
2596				continue
2597			value = getattr(self, name, None)
2598			# XXX For "charset" we never skip calling xmlWrite even if the
2599			# value is None, so we always write the following XML comment:
2600			#
2601			# <!-- charset is dumped separately as the 'GlyphOrder' element -->
2602			#
2603			# Charset is None when 'CFF ' table is imported from XML into an
2604			# empty TTFont(). By writing this comment all the time, we obtain
2605			# the same XML output whether roundtripping XML-to-XML or
2606			# dumping binary-to-XML
2607			if value is None and name != "charset":
2608				continue
2609			conv = self.converters[name]
2610			conv.xmlWrite(xmlWriter, name, value)
2611		ignoredNames = set(self.rawDict) - set(self.order)
2612		if ignoredNames:
2613			xmlWriter.comment(
2614				"some keys were ignored: %s" % " ".join(sorted(ignoredNames)))
2615			xmlWriter.newline()
2616
2617	def fromXML(self, name, attrs, content):
2618		conv = self.converters[name]
2619		value = conv.xmlRead(name, attrs, content, self)
2620		setattr(self, name, value)
2621
2622
2623class TopDict(BaseDict):
2624	"""The ``TopDict`` represents the top-level dictionary holding font
2625	information. CFF2 tables contain a restricted set of top-level entries
2626	as described `here <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#7-top-dict-data>`_,
2627	but CFF tables may contain a wider range of information. This information
2628	can be accessed through attributes or through the dictionary returned
2629	through the ``rawDict`` property:
2630
2631	.. code:: python
2632
2633		font = tt["CFF "].cff[0]
2634		font.FamilyName
2635		# 'Linux Libertine O'
2636		font.rawDict["FamilyName"]
2637		# 'Linux Libertine O'
2638
2639	More information is available in the CFF file's private dictionary, accessed
2640	via the ``Private`` property:
2641
2642	.. code:: python
2643
2644		tt["CFF "].cff[0].Private.BlueValues
2645		# [-15, 0, 515, 515, 666, 666]
2646
2647	"""
2648
2649	defaults = buildDefaults(topDictOperators)
2650	converters = buildConverters(topDictOperators)
2651	compilerClass = TopDictCompiler
2652	order = buildOrder(topDictOperators)
2653	decompilerClass = TopDictDecompiler
2654
2655	def __init__(self, strings=None, file=None, offset=None,
2656			GlobalSubrs=None, cff2GetGlyphOrder=None, isCFF2=None):
2657		super(TopDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2658		self.cff2GetGlyphOrder = cff2GetGlyphOrder
2659		self.GlobalSubrs = GlobalSubrs
2660		if isCFF2:
2661			self.defaults = buildDefaults(topDictOperators2)
2662			self.charset = cff2GetGlyphOrder()
2663			self.order = buildOrder(topDictOperators2)
2664		else:
2665			self.defaults = buildDefaults(topDictOperators)
2666			self.order = buildOrder(topDictOperators)
2667
2668	def getGlyphOrder(self):
2669		"""Returns a list of glyph names in the CFF font."""
2670		return self.charset
2671
2672	def postDecompile(self):
2673		offset = self.rawDict.get("CharStrings")
2674		if offset is None:
2675			return
2676		# get the number of glyphs beforehand.
2677		self.file.seek(offset)
2678		if self._isCFF2:
2679			self.numGlyphs = readCard32(self.file)
2680		else:
2681			self.numGlyphs = readCard16(self.file)
2682
2683	def toXML(self, xmlWriter):
2684		if hasattr(self, "CharStrings"):
2685			self.decompileAllCharStrings()
2686		if hasattr(self, "ROS"):
2687			self.skipNames = ['Encoding']
2688		if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
2689			# these values have default values, but I only want them to show up
2690			# in CID fonts.
2691			self.skipNames = [
2692				'CIDFontVersion', 'CIDFontRevision', 'CIDFontType', 'CIDCount']
2693		BaseDict.toXML(self, xmlWriter)
2694
2695	def decompileAllCharStrings(self):
2696		# Make sure that all the Private Dicts have been instantiated.
2697		for i, charString in enumerate(self.CharStrings.values()):
2698			try:
2699				charString.decompile()
2700			except:
2701				log.error("Error in charstring %s", i)
2702				raise
2703
2704	def recalcFontBBox(self):
2705		fontBBox = None
2706		for charString in self.CharStrings.values():
2707			bounds = charString.calcBounds(self.CharStrings)
2708			if bounds is not None:
2709				if fontBBox is not None:
2710					fontBBox = unionRect(fontBBox, bounds)
2711				else:
2712					fontBBox = bounds
2713
2714		if fontBBox is None:
2715			self.FontBBox = self.defaults['FontBBox'][:]
2716		else:
2717			self.FontBBox = list(intRect(fontBBox))
2718
2719
2720class FontDict(BaseDict):
2721	#
2722	# Since fonttools used to pass a lot of fields that are not relevant in the FDArray
2723	# FontDict, there are 'ttx' files in the wild that contain all these. These got in
2724	# the ttx files because fonttools writes explicit values for all the TopDict default
2725	# values. These are not actually illegal in the context of an FDArray FontDict - you
2726	# can legally, per spec, put any arbitrary key/value pair in a FontDict - but are
2727	# useless since current major company CFF interpreters ignore anything but the set
2728	# listed in this file. So, we just silently skip them. An exception is Weight: this
2729	# is not used by any interpreter, but some foundries have asked that this be
2730	# supported in FDArray FontDicts just to preserve information about the design when
2731	# the font is being inspected.
2732	#
2733	# On top of that, there are fonts out there that contain such useless FontDict values.
2734	#
2735	# By subclassing TopDict, we *allow* all key/values from TopDict, both when reading
2736	# from binary or when reading from XML, but by overriding `order` with a limited
2737	# list of names, we ensure that only the useful names ever get exported to XML and
2738	# ever get compiled into the binary font.
2739	#
2740	# We override compilerClass so we can warn about "useless" key/value pairs, either
2741	# from the original binary font or from TTX input.
2742	#
2743	# See:
2744	# - https://github.com/fonttools/fonttools/issues/740
2745	# - https://github.com/fonttools/fonttools/issues/601
2746	# - https://github.com/adobe-type-tools/afdko/issues/137
2747	#
2748	defaults = {}
2749	converters = buildConverters(topDictOperators)
2750	compilerClass = FontDictCompiler
2751	orderCFF = ['FontName', 'FontMatrix', 'Weight', 'Private']
2752	orderCFF2 = ['Private']
2753	decompilerClass = TopDictDecompiler
2754
2755	def __init__(self, strings=None, file=None, offset=None,
2756			GlobalSubrs=None, isCFF2=None, vstore=None):
2757		super(FontDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2758		self.vstore = vstore
2759		self.setCFF2(isCFF2)
2760
2761	def setCFF2(self, isCFF2):
2762		# isCFF2 may be None.
2763		if isCFF2:
2764			self.order = self.orderCFF2
2765			self._isCFF2 = True
2766		else:
2767			self.order = self.orderCFF
2768			self._isCFF2 = False
2769
2770
2771class PrivateDict(BaseDict):
2772	defaults = buildDefaults(privateDictOperators)
2773	converters = buildConverters(privateDictOperators)
2774	order = buildOrder(privateDictOperators)
2775	decompilerClass = PrivateDictDecompiler
2776	compilerClass = PrivateDictCompiler
2777
2778	def __init__(self, strings=None, file=None, offset=None, isCFF2=None,
2779			vstore=None):
2780		super(PrivateDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2781		self.vstore = vstore
2782		if isCFF2:
2783			self.defaults = buildDefaults(privateDictOperators2)
2784			self.order = buildOrder(privateDictOperators2)
2785			# Provide dummy values. This avoids needing to provide
2786			# an isCFF2 state in a lot of places.
2787			self.nominalWidthX = self.defaultWidthX = None
2788		else:
2789			self.defaults = buildDefaults(privateDictOperators)
2790			self.order = buildOrder(privateDictOperators)
2791
2792	@property
2793	def in_cff2(self):
2794		return self._isCFF2
2795
2796	def getNumRegions(self, vi=None):  # called from misc/psCharStrings.py
2797		# if getNumRegions is being called, we can assume that VarStore exists.
2798		if vi is None:
2799			if hasattr(self, 'vsindex'):
2800				vi = self.vsindex
2801			else:
2802				vi = 0
2803		numRegions = self.vstore.getNumRegions(vi)
2804		return numRegions
2805
2806
2807class IndexedStrings(object):
2808
2809	"""SID -> string mapping."""
2810
2811	def __init__(self, file=None):
2812		if file is None:
2813			strings = []
2814		else:
2815			strings = [
2816				tostr(s, encoding="latin1")
2817				for s in Index(file, isCFF2=False)
2818			]
2819		self.strings = strings
2820
2821	def getCompiler(self):
2822		return IndexedStringsCompiler(self, None, self, isCFF2=False)
2823
2824	def __len__(self):
2825		return len(self.strings)
2826
2827	def __getitem__(self, SID):
2828		if SID < cffStandardStringCount:
2829			return cffStandardStrings[SID]
2830		else:
2831			return self.strings[SID - cffStandardStringCount]
2832
2833	def getSID(self, s):
2834		if not hasattr(self, "stringMapping"):
2835			self.buildStringMapping()
2836		s = tostr(s, encoding="latin1")
2837		if s in cffStandardStringMapping:
2838			SID = cffStandardStringMapping[s]
2839		elif s in self.stringMapping:
2840			SID = self.stringMapping[s]
2841		else:
2842			SID = len(self.strings) + cffStandardStringCount
2843			self.strings.append(s)
2844			self.stringMapping[s] = SID
2845		return SID
2846
2847	def getStrings(self):
2848		return self.strings
2849
2850	def buildStringMapping(self):
2851		self.stringMapping = {}
2852		for index in range(len(self.strings)):
2853			self.stringMapping[self.strings[index]] = index + cffStandardStringCount
2854
2855
2856# The 391 Standard Strings as used in the CFF format.
2857# from Adobe Technical None #5176, version 1.0, 18 March 1998
2858
2859cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
2860		'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
2861		'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
2862		'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
2863		'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
2864		'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
2865		'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
2866		'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
2867		'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
2868		's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
2869		'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
2870		'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
2871		'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
2872		'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
2873		'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
2874		'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
2875		'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
2876		'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
2877		'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
2878		'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
2879		'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
2880		'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
2881		'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
2882		'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
2883		'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
2884		'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
2885		'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
2886		'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
2887		'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
2888		'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
2889		'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
2890		'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
2891		'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
2892		'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
2893		'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
2894		'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
2895		'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
2896		'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
2897		'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
2898		'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
2899		'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
2900		'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
2901		'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
2902		'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
2903		'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
2904		'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
2905		'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
2906		'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
2907		'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
2908		'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
2909		'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
2910		'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
2911		'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
2912		'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
2913		'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
2914		'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
2915		'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
2916		'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
2917		'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
2918		'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
2919		'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
2920		'001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
2921		'Semibold'
2922]
2923
2924cffStandardStringCount = 391
2925assert len(cffStandardStrings) == cffStandardStringCount
2926# build reverse mapping
2927cffStandardStringMapping = {}
2928for _i in range(cffStandardStringCount):
2929	cffStandardStringMapping[cffStandardStrings[_i]] = _i
2930
2931cffISOAdobeStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign",
2932"dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright",
2933"asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two",
2934"three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon",
2935"less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G",
2936"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
2937"X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
2938"underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
2939"k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
2940"braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent",
2941"sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle",
2942"quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl",
2943"endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet",
2944"quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis",
2945"perthousand", "questiondown", "grave", "acute", "circumflex", "tilde",
2946"macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut",
2947"ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE",
2948"ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls",
2949"onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus",
2950"Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn",
2951"threequarters", "twosuperior", "registered", "minus", "eth", "multiply",
2952"threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave",
2953"Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave",
2954"Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute",
2955"Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute",
2956"Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute",
2957"acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute",
2958"ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis",
2959"igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde",
2960"scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis",
2961"zcaron"]
2962
2963cffISOAdobeStringCount = 229
2964assert len(cffISOAdobeStrings) == cffISOAdobeStringCount
2965
2966cffIExpertStrings = [".notdef", "space", "exclamsmall", "Hungarumlautsmall",
2967"dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall",
2968"parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader",
2969"comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle",
2970"twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle",
2971"sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon",
2972"commasuperior", "threequartersemdash", "periodsuperior", "questionsmall",
2973"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
2974"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
2975"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
2976"parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall",
2977"Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall",
2978"Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall",
2979"Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall",
2980"Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall",
2981"exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall",
2982"Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall",
2983"figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall",
2984"onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth",
2985"threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds",
2986"zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior",
2987"fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior",
2988"zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior",
2989"fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior",
2990"centinferior", "dollarinferior", "periodinferior", "commainferior",
2991"Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall",
2992"Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall",
2993"Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall",
2994"Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall",
2995"Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall",
2996"Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall",
2997"Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall",
2998"Ydieresissmall"]
2999
3000cffExpertStringCount = 166
3001assert len(cffIExpertStrings) == cffExpertStringCount
3002
3003cffExpertSubsetStrings = [".notdef", "space", "dollaroldstyle",
3004"dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader",
3005"onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle",
3006"oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle",
3007"sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon",
3008"semicolon", "commasuperior", "threequartersemdash", "periodsuperior",
3009"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
3010"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
3011"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
3012"parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah",
3013"centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf",
3014"threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths",
3015"onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior",
3016"threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior",
3017"eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior",
3018"threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior",
3019"eightinferior", "nineinferior", "centinferior", "dollarinferior",
3020"periodinferior", "commainferior"]
3021
3022cffExpertSubsetStringCount = 87
3023assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount
3024