• 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)
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)
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			# read data in from file. Assume position is correct.
1008			length = readCard16(self.file)
1009			self.data = self.file.read(length)
1010			globalState = {}
1011			reader = OTTableReader(self.data, globalState)
1012			self.otVarStore = ot.VarStore()
1013			self.otVarStore.decompile(reader, self.font)
1014		return self
1015
1016	def compile(self):
1017		writer = OTTableWriter()
1018		self.otVarStore.compile(writer, self.font)
1019		# Note that this omits the initial Card16 length from the CFF2
1020		# VarStore data block
1021		self.data = writer.getAllData()
1022
1023	def writeXML(self, xmlWriter, name):
1024		self.otVarStore.toXML(xmlWriter, self.font)
1025
1026	def xmlRead(self, name, attrs, content, parent):
1027		self.otVarStore = ot.VarStore()
1028		for element in content:
1029			if isinstance(element, tuple):
1030				name, attrs, content = element
1031				self.otVarStore.fromXML(name, attrs, content, self.font)
1032			else:
1033				pass
1034		return None
1035
1036	def __len__(self):
1037		return len(self.data)
1038
1039	def getNumRegions(self, vsIndex):
1040		if vsIndex is None:
1041			vsIndex = 0
1042		varData = self.otVarStore.VarData[vsIndex]
1043		numRegions = varData.VarRegionCount
1044		return numRegions
1045
1046
1047class FDSelect(object):
1048
1049	def __init__(self, file=None, numGlyphs=None, format=None):
1050		if file:
1051			# read data in from file
1052			self.format = readCard8(file)
1053			if self.format == 0:
1054				from array import array
1055				self.gidArray = array("B", file.read(numGlyphs)).tolist()
1056			elif self.format == 3:
1057				gidArray = [None] * numGlyphs
1058				nRanges = readCard16(file)
1059				fd = None
1060				prev = None
1061				for i in range(nRanges):
1062					first = readCard16(file)
1063					if prev is not None:
1064						for glyphID in range(prev, first):
1065							gidArray[glyphID] = fd
1066					prev = first
1067					fd = readCard8(file)
1068				if prev is not None:
1069					first = readCard16(file)
1070					for glyphID in range(prev, first):
1071						gidArray[glyphID] = fd
1072				self.gidArray = gidArray
1073			elif self.format == 4:
1074				gidArray = [None] * numGlyphs
1075				nRanges = readCard32(file)
1076				fd = None
1077				prev = None
1078				for i in range(nRanges):
1079					first = readCard32(file)
1080					if prev is not None:
1081						for glyphID in range(prev, first):
1082							gidArray[glyphID] = fd
1083					prev = first
1084					fd = readCard16(file)
1085				if prev is not None:
1086					first = readCard32(file)
1087					for glyphID in range(prev, first):
1088						gidArray[glyphID] = fd
1089				self.gidArray = gidArray
1090			else:
1091				assert False, "unsupported FDSelect format: %s" % format
1092		else:
1093			# reading from XML. Make empty gidArray, and leave format as passed in.
1094			# format is None will result in the smallest representation being used.
1095			self.format = format
1096			self.gidArray = []
1097
1098	def __len__(self):
1099		return len(self.gidArray)
1100
1101	def __getitem__(self, index):
1102		return self.gidArray[index]
1103
1104	def __setitem__(self, index, fdSelectValue):
1105		self.gidArray[index] = fdSelectValue
1106
1107	def append(self, fdSelectValue):
1108		self.gidArray.append(fdSelectValue)
1109
1110
1111class CharStrings(object):
1112	"""The ``CharStrings`` in the font represent the instructions for drawing
1113	each glyph. This object presents a dictionary interface to the font's
1114	CharStrings, indexed by glyph name:
1115
1116	.. code:: python
1117
1118		tt["CFF "].cff[0].CharStrings["a"]
1119		# <T2CharString (bytecode) at 103451e90>
1120
1121	See :class:`fontTools.misc.psCharStrings.T1CharString` and
1122	:class:`fontTools.misc.psCharStrings.T2CharString` for how to decompile,
1123	compile and interpret the glyph drawing instructions in the returned objects.
1124
1125	"""
1126
1127	def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray,
1128			isCFF2=None):
1129		self.globalSubrs = globalSubrs
1130		if file is not None:
1131			self.charStringsIndex = SubrsIndex(
1132				file, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2)
1133			self.charStrings = charStrings = {}
1134			for i in range(len(charset)):
1135				charStrings[charset[i]] = i
1136			# read from OTF file: charStrings.values() are indices into
1137			# charStringsIndex.
1138			self.charStringsAreIndexed = 1
1139		else:
1140			self.charStrings = {}
1141			# read from ttx file: charStrings.values() are actual charstrings
1142			self.charStringsAreIndexed = 0
1143			self.private = private
1144			if fdSelect is not None:
1145				self.fdSelect = fdSelect
1146			if fdArray is not None:
1147				self.fdArray = fdArray
1148
1149	def keys(self):
1150		return list(self.charStrings.keys())
1151
1152	def values(self):
1153		if self.charStringsAreIndexed:
1154			return self.charStringsIndex
1155		else:
1156			return list(self.charStrings.values())
1157
1158	def has_key(self, name):
1159		return name in self.charStrings
1160
1161	__contains__ = has_key
1162
1163	def __len__(self):
1164		return len(self.charStrings)
1165
1166	def __getitem__(self, name):
1167		charString = self.charStrings[name]
1168		if self.charStringsAreIndexed:
1169			charString = self.charStringsIndex[charString]
1170		return charString
1171
1172	def __setitem__(self, name, charString):
1173		if self.charStringsAreIndexed:
1174			index = self.charStrings[name]
1175			self.charStringsIndex[index] = charString
1176		else:
1177			self.charStrings[name] = charString
1178
1179	def getItemAndSelector(self, name):
1180		if self.charStringsAreIndexed:
1181			index = self.charStrings[name]
1182			return self.charStringsIndex.getItemAndSelector(index)
1183		else:
1184			if hasattr(self, 'fdArray'):
1185				if hasattr(self, 'fdSelect'):
1186					sel = self.charStrings[name].fdSelectIndex
1187				else:
1188					sel = 0
1189			else:
1190				sel = None
1191			return self.charStrings[name], sel
1192
1193	def toXML(self, xmlWriter):
1194		names = sorted(self.keys())
1195		for name in names:
1196			charStr, fdSelectIndex = self.getItemAndSelector(name)
1197			if charStr.needsDecompilation():
1198				raw = [("raw", 1)]
1199			else:
1200				raw = []
1201			if fdSelectIndex is None:
1202				xmlWriter.begintag("CharString", [('name', name)] + raw)
1203			else:
1204				xmlWriter.begintag(
1205					"CharString",
1206					[('name', name), ('fdSelectIndex', fdSelectIndex)] + raw)
1207			xmlWriter.newline()
1208			charStr.toXML(xmlWriter)
1209			xmlWriter.endtag("CharString")
1210			xmlWriter.newline()
1211
1212	def fromXML(self, name, attrs, content):
1213		for element in content:
1214			if isinstance(element, str):
1215				continue
1216			name, attrs, content = element
1217			if name != "CharString":
1218				continue
1219			fdID = -1
1220			if hasattr(self, "fdArray"):
1221				try:
1222					fdID = safeEval(attrs["fdSelectIndex"])
1223				except KeyError:
1224					fdID = 0
1225				private = self.fdArray[fdID].Private
1226			else:
1227				private = self.private
1228
1229			glyphName = attrs["name"]
1230			charStringClass = psCharStrings.T2CharString
1231			charString = charStringClass(
1232					private=private,
1233					globalSubrs=self.globalSubrs)
1234			charString.fromXML(name, attrs, content)
1235			if fdID >= 0:
1236				charString.fdSelectIndex = fdID
1237			self[glyphName] = charString
1238
1239
1240def readCard8(file):
1241	return byteord(file.read(1))
1242
1243
1244def readCard16(file):
1245	value, = struct.unpack(">H", file.read(2))
1246	return value
1247
1248
1249def readCard32(file):
1250	value, = struct.unpack(">L", file.read(4))
1251	return value
1252
1253
1254def writeCard8(file, value):
1255	file.write(bytechr(value))
1256
1257
1258def writeCard16(file, value):
1259	file.write(struct.pack(">H", value))
1260
1261
1262def writeCard32(file, value):
1263	file.write(struct.pack(">L", value))
1264
1265
1266def packCard8(value):
1267	return bytechr(value)
1268
1269
1270def packCard16(value):
1271	return struct.pack(">H", value)
1272
1273
1274def packCard32(value):
1275	return struct.pack(">L", value)
1276
1277
1278def buildOperatorDict(table):
1279	d = {}
1280	for op, name, arg, default, conv in table:
1281		d[op] = (name, arg)
1282	return d
1283
1284
1285def buildOpcodeDict(table):
1286	d = {}
1287	for op, name, arg, default, conv in table:
1288		if isinstance(op, tuple):
1289			op = bytechr(op[0]) + bytechr(op[1])
1290		else:
1291			op = bytechr(op)
1292		d[name] = (op, arg)
1293	return d
1294
1295
1296def buildOrder(table):
1297	l = []
1298	for op, name, arg, default, conv in table:
1299		l.append(name)
1300	return l
1301
1302
1303def buildDefaults(table):
1304	d = {}
1305	for op, name, arg, default, conv in table:
1306		if default is not None:
1307			d[name] = default
1308	return d
1309
1310
1311def buildConverters(table):
1312	d = {}
1313	for op, name, arg, default, conv in table:
1314		d[name] = conv
1315	return d
1316
1317
1318class SimpleConverter(object):
1319
1320	def read(self, parent, value):
1321		if not hasattr(parent, "file"):
1322			return self._read(parent, value)
1323		file = parent.file
1324		pos = file.tell()
1325		try:
1326			return self._read(parent, value)
1327		finally:
1328			file.seek(pos)
1329
1330	def _read(self, parent, value):
1331		return value
1332
1333	def write(self, parent, value):
1334		return value
1335
1336	def xmlWrite(self, xmlWriter, name, value):
1337		xmlWriter.simpletag(name, value=value)
1338		xmlWriter.newline()
1339
1340	def xmlRead(self, name, attrs, content, parent):
1341		return attrs["value"]
1342
1343
1344class ASCIIConverter(SimpleConverter):
1345
1346	def _read(self, parent, value):
1347		return tostr(value, encoding='ascii')
1348
1349	def write(self, parent, value):
1350		return tobytes(value, encoding='ascii')
1351
1352	def xmlWrite(self, xmlWriter, name, value):
1353		xmlWriter.simpletag(name, value=tostr(value, encoding="ascii"))
1354		xmlWriter.newline()
1355
1356	def xmlRead(self, name, attrs, content, parent):
1357		return tobytes(attrs["value"], encoding=("ascii"))
1358
1359
1360class Latin1Converter(SimpleConverter):
1361
1362	def _read(self, parent, value):
1363		return tostr(value, encoding='latin1')
1364
1365	def write(self, parent, value):
1366		return tobytes(value, encoding='latin1')
1367
1368	def xmlWrite(self, xmlWriter, name, value):
1369		value = tostr(value, encoding="latin1")
1370		if name in ['Notice', 'Copyright']:
1371			value = re.sub(r"[\r\n]\s+", " ", value)
1372		xmlWriter.simpletag(name, value=value)
1373		xmlWriter.newline()
1374
1375	def xmlRead(self, name, attrs, content, parent):
1376		return tobytes(attrs["value"], encoding=("latin1"))
1377
1378
1379def parseNum(s):
1380	try:
1381		value = int(s)
1382	except:
1383		value = float(s)
1384	return value
1385
1386
1387def parseBlendList(s):
1388	valueList = []
1389	for element in s:
1390		if isinstance(element, str):
1391			continue
1392		name, attrs, content = element
1393		blendList = attrs["value"].split()
1394		blendList = [eval(val) for val in blendList]
1395		valueList.append(blendList)
1396	if len(valueList) == 1:
1397		valueList = valueList[0]
1398	return valueList
1399
1400
1401class NumberConverter(SimpleConverter):
1402	def xmlWrite(self, xmlWriter, name, value):
1403		if isinstance(value, list):
1404			xmlWriter.begintag(name)
1405			xmlWriter.newline()
1406			xmlWriter.indent()
1407			blendValue = " ".join([str(val) for val in value])
1408			xmlWriter.simpletag(kBlendDictOpName, value=blendValue)
1409			xmlWriter.newline()
1410			xmlWriter.dedent()
1411			xmlWriter.endtag(name)
1412			xmlWriter.newline()
1413		else:
1414			xmlWriter.simpletag(name, value=value)
1415			xmlWriter.newline()
1416
1417	def xmlRead(self, name, attrs, content, parent):
1418		valueString = attrs.get("value", None)
1419		if valueString is None:
1420			value = parseBlendList(content)
1421		else:
1422			value = parseNum(attrs["value"])
1423		return value
1424
1425
1426class ArrayConverter(SimpleConverter):
1427	def xmlWrite(self, xmlWriter, name, value):
1428		if value and isinstance(value[0], list):
1429			xmlWriter.begintag(name)
1430			xmlWriter.newline()
1431			xmlWriter.indent()
1432			for valueList in value:
1433				blendValue = " ".join([str(val) for val in valueList])
1434				xmlWriter.simpletag(kBlendDictOpName, value=blendValue)
1435				xmlWriter.newline()
1436			xmlWriter.dedent()
1437			xmlWriter.endtag(name)
1438			xmlWriter.newline()
1439		else:
1440			value = " ".join([str(val) for val in value])
1441			xmlWriter.simpletag(name, value=value)
1442			xmlWriter.newline()
1443
1444	def xmlRead(self, name, attrs, content, parent):
1445		valueString = attrs.get("value", None)
1446		if valueString is None:
1447			valueList = parseBlendList(content)
1448		else:
1449			values = valueString.split()
1450			valueList = [parseNum(value) for value in values]
1451		return valueList
1452
1453
1454class TableConverter(SimpleConverter):
1455
1456	def xmlWrite(self, xmlWriter, name, value):
1457		xmlWriter.begintag(name)
1458		xmlWriter.newline()
1459		value.toXML(xmlWriter)
1460		xmlWriter.endtag(name)
1461		xmlWriter.newline()
1462
1463	def xmlRead(self, name, attrs, content, parent):
1464		ob = self.getClass()()
1465		for element in content:
1466			if isinstance(element, str):
1467				continue
1468			name, attrs, content = element
1469			ob.fromXML(name, attrs, content)
1470		return ob
1471
1472
1473class PrivateDictConverter(TableConverter):
1474
1475	def getClass(self):
1476		return PrivateDict
1477
1478	def _read(self, parent, value):
1479		size, offset = value
1480		file = parent.file
1481		isCFF2 = parent._isCFF2
1482		try:
1483			vstore = parent.vstore
1484		except AttributeError:
1485			vstore = None
1486		priv = PrivateDict(
1487			parent.strings, file, offset, isCFF2=isCFF2, vstore=vstore)
1488		file.seek(offset)
1489		data = file.read(size)
1490		assert len(data) == size
1491		priv.decompile(data)
1492		return priv
1493
1494	def write(self, parent, value):
1495		return (0, 0)  # dummy value
1496
1497
1498class SubrsConverter(TableConverter):
1499
1500	def getClass(self):
1501		return SubrsIndex
1502
1503	def _read(self, parent, value):
1504		file = parent.file
1505		isCFF2 = parent._isCFF2
1506		file.seek(parent.offset + value)  # Offset(self)
1507		return SubrsIndex(file, isCFF2=isCFF2)
1508
1509	def write(self, parent, value):
1510		return 0  # dummy value
1511
1512
1513class CharStringsConverter(TableConverter):
1514
1515	def _read(self, parent, value):
1516		file = parent.file
1517		isCFF2 = parent._isCFF2
1518		charset = parent.charset
1519		globalSubrs = parent.GlobalSubrs
1520		if hasattr(parent, "FDArray"):
1521			fdArray = parent.FDArray
1522			if hasattr(parent, "FDSelect"):
1523				fdSelect = parent.FDSelect
1524			else:
1525				fdSelect = None
1526			private = None
1527		else:
1528			fdSelect, fdArray = None, None
1529			private = parent.Private
1530		file.seek(value)  # Offset(0)
1531		charStrings = CharStrings(
1532			file, charset, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2)
1533		return charStrings
1534
1535	def write(self, parent, value):
1536		return 0  # dummy value
1537
1538	def xmlRead(self, name, attrs, content, parent):
1539		if hasattr(parent, "FDArray"):
1540			# if it is a CID-keyed font, then the private Dict is extracted from the
1541			# parent.FDArray
1542			fdArray = parent.FDArray
1543			if hasattr(parent, "FDSelect"):
1544				fdSelect = parent.FDSelect
1545			else:
1546				fdSelect = None
1547			private = None
1548		else:
1549			# if it is a name-keyed font, then the private dict is in the top dict,
1550			# and
1551			# there is no fdArray.
1552			private, fdSelect, fdArray = parent.Private, None, None
1553		charStrings = CharStrings(
1554			None, None, parent.GlobalSubrs, private, fdSelect, fdArray)
1555		charStrings.fromXML(name, attrs, content)
1556		return charStrings
1557
1558
1559class CharsetConverter(SimpleConverter):
1560	def _read(self, parent, value):
1561		isCID = hasattr(parent, "ROS")
1562		if value > 2:
1563			numGlyphs = parent.numGlyphs
1564			file = parent.file
1565			file.seek(value)
1566			log.log(DEBUG, "loading charset at %s", value)
1567			format = readCard8(file)
1568			if format == 0:
1569				charset = parseCharset0(numGlyphs, file, parent.strings, isCID)
1570			elif format == 1 or format == 2:
1571				charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
1572			else:
1573				raise NotImplementedError
1574			assert len(charset) == numGlyphs
1575			log.log(DEBUG, "    charset end at %s", file.tell())
1576			# make sure glyph names are unique
1577			allNames = {}
1578			newCharset = []
1579			for glyphName in charset:
1580				if glyphName in allNames:
1581					# make up a new glyphName that's unique
1582					n = allNames[glyphName]
1583					while (glyphName + "#" + str(n)) in allNames:
1584						n += 1
1585					allNames[glyphName] = n + 1
1586					glyphName = glyphName + "#" + str(n)
1587				allNames[glyphName] = 1
1588				newCharset.append(glyphName)
1589			charset = newCharset
1590		else:  # offset == 0 -> no charset data.
1591			if isCID or "CharStrings" not in parent.rawDict:
1592				# We get here only when processing fontDicts from the FDArray of
1593				# CFF-CID fonts. Only the real topDict references the chrset.
1594				assert value == 0
1595				charset = None
1596			elif value == 0:
1597				charset = cffISOAdobeStrings
1598			elif value == 1:
1599				charset = cffIExpertStrings
1600			elif value == 2:
1601				charset = cffExpertSubsetStrings
1602		if charset and (len(charset) != parent.numGlyphs):
1603			charset = charset[:parent.numGlyphs]
1604		return charset
1605
1606	def write(self, parent, value):
1607		return 0  # dummy value
1608
1609	def xmlWrite(self, xmlWriter, name, value):
1610		# XXX only write charset when not in OT/TTX context, where we
1611		# dump charset as a separate "GlyphOrder" table.
1612		# # xmlWriter.simpletag("charset")
1613		xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element")
1614		xmlWriter.newline()
1615
1616	def xmlRead(self, name, attrs, content, parent):
1617		pass
1618
1619
1620class CharsetCompiler(object):
1621
1622	def __init__(self, strings, charset, parent):
1623		assert charset[0] == '.notdef'
1624		isCID = hasattr(parent.dictObj, "ROS")
1625		data0 = packCharset0(charset, isCID, strings)
1626		data = packCharset(charset, isCID, strings)
1627		if len(data) < len(data0):
1628			self.data = data
1629		else:
1630			self.data = data0
1631		self.parent = parent
1632
1633	def setPos(self, pos, endPos):
1634		self.parent.rawDict["charset"] = pos
1635
1636	def getDataLength(self):
1637		return len(self.data)
1638
1639	def toFile(self, file):
1640		file.write(self.data)
1641
1642
1643def getStdCharSet(charset):
1644	# check to see if we can use a predefined charset value.
1645	predefinedCharSetVal = None
1646	predefinedCharSets = [
1647		(cffISOAdobeStringCount, cffISOAdobeStrings, 0),
1648		(cffExpertStringCount, cffIExpertStrings, 1),
1649		(cffExpertSubsetStringCount, cffExpertSubsetStrings, 2)]
1650	lcs = len(charset)
1651	for cnt, pcs, csv in predefinedCharSets:
1652		if predefinedCharSetVal is not None:
1653			break
1654		if lcs > cnt:
1655			continue
1656		predefinedCharSetVal = csv
1657		for i in range(lcs):
1658			if charset[i] != pcs[i]:
1659				predefinedCharSetVal = None
1660				break
1661	return predefinedCharSetVal
1662
1663
1664def getCIDfromName(name, strings):
1665	return int(name[3:])
1666
1667
1668def getSIDfromName(name, strings):
1669	return strings.getSID(name)
1670
1671
1672def packCharset0(charset, isCID, strings):
1673	fmt = 0
1674	data = [packCard8(fmt)]
1675	if isCID:
1676		getNameID = getCIDfromName
1677	else:
1678		getNameID = getSIDfromName
1679
1680	for name in charset[1:]:
1681		data.append(packCard16(getNameID(name, strings)))
1682	return bytesjoin(data)
1683
1684
1685def packCharset(charset, isCID, strings):
1686	fmt = 1
1687	ranges = []
1688	first = None
1689	end = 0
1690	if isCID:
1691		getNameID = getCIDfromName
1692	else:
1693		getNameID = getSIDfromName
1694
1695	for name in charset[1:]:
1696		SID = getNameID(name, strings)
1697		if first is None:
1698			first = SID
1699		elif end + 1 != SID:
1700			nLeft = end - first
1701			if nLeft > 255:
1702				fmt = 2
1703			ranges.append((first, nLeft))
1704			first = SID
1705		end = SID
1706	if end:
1707		nLeft = end - first
1708		if nLeft > 255:
1709			fmt = 2
1710		ranges.append((first, nLeft))
1711
1712	data = [packCard8(fmt)]
1713	if fmt == 1:
1714		nLeftFunc = packCard8
1715	else:
1716		nLeftFunc = packCard16
1717	for first, nLeft in ranges:
1718		data.append(packCard16(first) + nLeftFunc(nLeft))
1719	return bytesjoin(data)
1720
1721
1722def parseCharset0(numGlyphs, file, strings, isCID):
1723	charset = [".notdef"]
1724	if isCID:
1725		for i in range(numGlyphs - 1):
1726			CID = readCard16(file)
1727			charset.append("cid" + str(CID).zfill(5))
1728	else:
1729		for i in range(numGlyphs - 1):
1730			SID = readCard16(file)
1731			charset.append(strings[SID])
1732	return charset
1733
1734
1735def parseCharset(numGlyphs, file, strings, isCID, fmt):
1736	charset = ['.notdef']
1737	count = 1
1738	if fmt == 1:
1739		nLeftFunc = readCard8
1740	else:
1741		nLeftFunc = readCard16
1742	while count < numGlyphs:
1743		first = readCard16(file)
1744		nLeft = nLeftFunc(file)
1745		if isCID:
1746			for CID in range(first, first + nLeft + 1):
1747				charset.append("cid" + str(CID).zfill(5))
1748		else:
1749			for SID in range(first, first + nLeft + 1):
1750				charset.append(strings[SID])
1751		count = count + nLeft + 1
1752	return charset
1753
1754
1755class EncodingCompiler(object):
1756
1757	def __init__(self, strings, encoding, parent):
1758		assert not isinstance(encoding, str)
1759		data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings)
1760		data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings)
1761		if len(data0) < len(data1):
1762			self.data = data0
1763		else:
1764			self.data = data1
1765		self.parent = parent
1766
1767	def setPos(self, pos, endPos):
1768		self.parent.rawDict["Encoding"] = pos
1769
1770	def getDataLength(self):
1771		return len(self.data)
1772
1773	def toFile(self, file):
1774		file.write(self.data)
1775
1776
1777class EncodingConverter(SimpleConverter):
1778
1779	def _read(self, parent, value):
1780		if value == 0:
1781			return "StandardEncoding"
1782		elif value == 1:
1783			return "ExpertEncoding"
1784		else:
1785			assert value > 1
1786			file = parent.file
1787			file.seek(value)
1788			log.log(DEBUG, "loading Encoding at %s", value)
1789			fmt = readCard8(file)
1790			haveSupplement = fmt & 0x80
1791			if haveSupplement:
1792				raise NotImplementedError("Encoding supplements are not yet supported")
1793			fmt = fmt & 0x7f
1794			if fmt == 0:
1795				encoding = parseEncoding0(parent.charset, file, haveSupplement,
1796						parent.strings)
1797			elif fmt == 1:
1798				encoding = parseEncoding1(parent.charset, file, haveSupplement,
1799						parent.strings)
1800			return encoding
1801
1802	def write(self, parent, value):
1803		if value == "StandardEncoding":
1804			return 0
1805		elif value == "ExpertEncoding":
1806			return 1
1807		return 0  # dummy value
1808
1809	def xmlWrite(self, xmlWriter, name, value):
1810		if value in ("StandardEncoding", "ExpertEncoding"):
1811			xmlWriter.simpletag(name, name=value)
1812			xmlWriter.newline()
1813			return
1814		xmlWriter.begintag(name)
1815		xmlWriter.newline()
1816		for code in range(len(value)):
1817			glyphName = value[code]
1818			if glyphName != ".notdef":
1819				xmlWriter.simpletag("map", code=hex(code), name=glyphName)
1820				xmlWriter.newline()
1821		xmlWriter.endtag(name)
1822		xmlWriter.newline()
1823
1824	def xmlRead(self, name, attrs, content, parent):
1825		if "name" in attrs:
1826			return attrs["name"]
1827		encoding = [".notdef"] * 256
1828		for element in content:
1829			if isinstance(element, str):
1830				continue
1831			name, attrs, content = element
1832			code = safeEval(attrs["code"])
1833			glyphName = attrs["name"]
1834			encoding[code] = glyphName
1835		return encoding
1836
1837
1838def parseEncoding0(charset, file, haveSupplement, strings):
1839	nCodes = readCard8(file)
1840	encoding = [".notdef"] * 256
1841	for glyphID in range(1, nCodes + 1):
1842		code = readCard8(file)
1843		if code != 0:
1844			encoding[code] = charset[glyphID]
1845	return encoding
1846
1847
1848def parseEncoding1(charset, file, haveSupplement, strings):
1849	nRanges = readCard8(file)
1850	encoding = [".notdef"] * 256
1851	glyphID = 1
1852	for i in range(nRanges):
1853		code = readCard8(file)
1854		nLeft = readCard8(file)
1855		for glyphID in range(glyphID, glyphID + nLeft + 1):
1856			encoding[code] = charset[glyphID]
1857			code = code + 1
1858		glyphID = glyphID + 1
1859	return encoding
1860
1861
1862def packEncoding0(charset, encoding, strings):
1863	fmt = 0
1864	m = {}
1865	for code in range(len(encoding)):
1866		name = encoding[code]
1867		if name != ".notdef":
1868			m[name] = code
1869	codes = []
1870	for name in charset[1:]:
1871		code = m.get(name)
1872		codes.append(code)
1873
1874	while codes and codes[-1] is None:
1875		codes.pop()
1876
1877	data = [packCard8(fmt), packCard8(len(codes))]
1878	for code in codes:
1879		if code is None:
1880			code = 0
1881		data.append(packCard8(code))
1882	return bytesjoin(data)
1883
1884
1885def packEncoding1(charset, encoding, strings):
1886	fmt = 1
1887	m = {}
1888	for code in range(len(encoding)):
1889		name = encoding[code]
1890		if name != ".notdef":
1891			m[name] = code
1892	ranges = []
1893	first = None
1894	end = 0
1895	for name in charset[1:]:
1896		code = m.get(name, -1)
1897		if first is None:
1898			first = code
1899		elif end + 1 != code:
1900			nLeft = end - first
1901			ranges.append((first, nLeft))
1902			first = code
1903		end = code
1904	nLeft = end - first
1905	ranges.append((first, nLeft))
1906
1907	# remove unencoded glyphs at the end.
1908	while ranges and ranges[-1][0] == -1:
1909		ranges.pop()
1910
1911	data = [packCard8(fmt), packCard8(len(ranges))]
1912	for first, nLeft in ranges:
1913		if first == -1:  # unencoded
1914			first = 0
1915		data.append(packCard8(first) + packCard8(nLeft))
1916	return bytesjoin(data)
1917
1918
1919class FDArrayConverter(TableConverter):
1920
1921	def _read(self, parent, value):
1922		try:
1923			vstore = parent.VarStore
1924		except AttributeError:
1925			vstore = None
1926		file = parent.file
1927		isCFF2 = parent._isCFF2
1928		file.seek(value)
1929		fdArray = FDArrayIndex(file, isCFF2=isCFF2)
1930		fdArray.vstore = vstore
1931		fdArray.strings = parent.strings
1932		fdArray.GlobalSubrs = parent.GlobalSubrs
1933		return fdArray
1934
1935	def write(self, parent, value):
1936		return 0  # dummy value
1937
1938	def xmlRead(self, name, attrs, content, parent):
1939		fdArray = FDArrayIndex()
1940		for element in content:
1941			if isinstance(element, str):
1942				continue
1943			name, attrs, content = element
1944			fdArray.fromXML(name, attrs, content)
1945		return fdArray
1946
1947
1948class FDSelectConverter(SimpleConverter):
1949
1950	def _read(self, parent, value):
1951		file = parent.file
1952		file.seek(value)
1953		fdSelect = FDSelect(file, parent.numGlyphs)
1954		return fdSelect
1955
1956	def write(self, parent, value):
1957		return 0  # dummy value
1958
1959	# The FDSelect glyph data is written out to XML in the charstring keys,
1960	# so we write out only the format selector
1961	def xmlWrite(self, xmlWriter, name, value):
1962		xmlWriter.simpletag(name, [('format', value.format)])
1963		xmlWriter.newline()
1964
1965	def xmlRead(self, name, attrs, content, parent):
1966		fmt = safeEval(attrs["format"])
1967		file = None
1968		numGlyphs = None
1969		fdSelect = FDSelect(file, numGlyphs, fmt)
1970		return fdSelect
1971
1972
1973class VarStoreConverter(SimpleConverter):
1974
1975	def _read(self, parent, value):
1976		file = parent.file
1977		file.seek(value)
1978		varStore = VarStoreData(file)
1979		varStore.decompile()
1980		return varStore
1981
1982	def write(self, parent, value):
1983		return 0  # dummy value
1984
1985	def xmlWrite(self, xmlWriter, name, value):
1986		value.writeXML(xmlWriter, name)
1987
1988	def xmlRead(self, name, attrs, content, parent):
1989		varStore = VarStoreData()
1990		varStore.xmlRead(name, attrs, content, parent)
1991		return varStore
1992
1993
1994def packFDSelect0(fdSelectArray):
1995	fmt = 0
1996	data = [packCard8(fmt)]
1997	for index in fdSelectArray:
1998		data.append(packCard8(index))
1999	return bytesjoin(data)
2000
2001
2002def packFDSelect3(fdSelectArray):
2003	fmt = 3
2004	fdRanges = []
2005	lenArray = len(fdSelectArray)
2006	lastFDIndex = -1
2007	for i in range(lenArray):
2008		fdIndex = fdSelectArray[i]
2009		if lastFDIndex != fdIndex:
2010			fdRanges.append([i, fdIndex])
2011			lastFDIndex = fdIndex
2012	sentinelGID = i + 1
2013
2014	data = [packCard8(fmt)]
2015	data.append(packCard16(len(fdRanges)))
2016	for fdRange in fdRanges:
2017		data.append(packCard16(fdRange[0]))
2018		data.append(packCard8(fdRange[1]))
2019	data.append(packCard16(sentinelGID))
2020	return bytesjoin(data)
2021
2022
2023def packFDSelect4(fdSelectArray):
2024	fmt = 4
2025	fdRanges = []
2026	lenArray = len(fdSelectArray)
2027	lastFDIndex = -1
2028	for i in range(lenArray):
2029		fdIndex = fdSelectArray[i]
2030		if lastFDIndex != fdIndex:
2031			fdRanges.append([i, fdIndex])
2032			lastFDIndex = fdIndex
2033	sentinelGID = i + 1
2034
2035	data = [packCard8(fmt)]
2036	data.append(packCard32(len(fdRanges)))
2037	for fdRange in fdRanges:
2038		data.append(packCard32(fdRange[0]))
2039		data.append(packCard16(fdRange[1]))
2040	data.append(packCard32(sentinelGID))
2041	return bytesjoin(data)
2042
2043
2044class FDSelectCompiler(object):
2045
2046	def __init__(self, fdSelect, parent):
2047		fmt = fdSelect.format
2048		fdSelectArray = fdSelect.gidArray
2049		if fmt == 0:
2050			self.data = packFDSelect0(fdSelectArray)
2051		elif fmt == 3:
2052			self.data = packFDSelect3(fdSelectArray)
2053		elif fmt == 4:
2054			self.data = packFDSelect4(fdSelectArray)
2055		else:
2056			# choose smaller of the two formats
2057			data0 = packFDSelect0(fdSelectArray)
2058			data3 = packFDSelect3(fdSelectArray)
2059			if len(data0) < len(data3):
2060				self.data = data0
2061				fdSelect.format = 0
2062			else:
2063				self.data = data3
2064				fdSelect.format = 3
2065
2066		self.parent = parent
2067
2068	def setPos(self, pos, endPos):
2069		self.parent.rawDict["FDSelect"] = pos
2070
2071	def getDataLength(self):
2072		return len(self.data)
2073
2074	def toFile(self, file):
2075		file.write(self.data)
2076
2077
2078class VarStoreCompiler(object):
2079
2080	def __init__(self, varStoreData, parent):
2081		self.parent = parent
2082		if not varStoreData.data:
2083			varStoreData.compile()
2084		data = [
2085			packCard16(len(varStoreData.data)),
2086			varStoreData.data
2087		]
2088		self.data = bytesjoin(data)
2089
2090	def setPos(self, pos, endPos):
2091		self.parent.rawDict["VarStore"] = pos
2092
2093	def getDataLength(self):
2094		return len(self.data)
2095
2096	def toFile(self, file):
2097		file.write(self.data)
2098
2099
2100class ROSConverter(SimpleConverter):
2101
2102	def xmlWrite(self, xmlWriter, name, value):
2103		registry, order, supplement = value
2104		xmlWriter.simpletag(
2105			name,
2106			[
2107				('Registry', tostr(registry)),
2108				('Order', tostr(order)),
2109				('Supplement', supplement)
2110			])
2111		xmlWriter.newline()
2112
2113	def xmlRead(self, name, attrs, content, parent):
2114		return (attrs['Registry'], attrs['Order'], safeEval(attrs['Supplement']))
2115
2116topDictOperators = [
2117#	opcode		name			argument type	default	converter
2118	(25,		'maxstack',		'number',	None,	None),
2119	((12, 30),	'ROS',	('SID', 'SID', 'number'),	None,	ROSConverter()),
2120	((12, 20),	'SyntheticBase',	'number',	None,	None),
2121	(0,		'version',		'SID',		None,	None),
2122	(1,		'Notice',		'SID',		None,	Latin1Converter()),
2123	((12, 0),	'Copyright',		'SID',		None,	Latin1Converter()),
2124	(2,		'FullName',		'SID',		None,	None),
2125	((12, 38),	'FontName',		'SID',		None,	None),
2126	(3,		'FamilyName',		'SID',		None,	None),
2127	(4,		'Weight',		'SID',		None,	None),
2128	((12, 1),	'isFixedPitch',		'number',	0,	None),
2129	((12, 2),	'ItalicAngle',		'number',	0,	None),
2130	((12, 3),	'UnderlinePosition',	'number',	-100,	None),
2131	((12, 4),	'UnderlineThickness',	'number',	50,	None),
2132	((12, 5),	'PaintType',		'number',	0,	None),
2133	((12, 6),	'CharstringType',	'number',	2,	None),
2134	((12, 7),	'FontMatrix',		'array',	[0.001, 0, 0, 0.001, 0, 0],	None),
2135	(13,		'UniqueID',		'number',	None,	None),
2136	(5,		'FontBBox',		'array',	[0, 0, 0, 0],	None),
2137	((12, 8),	'StrokeWidth',		'number',	0,	None),
2138	(14,		'XUID',			'array',	None,	None),
2139	((12, 21),	'PostScript',		'SID',		None,	None),
2140	((12, 22),	'BaseFontName',		'SID',		None,	None),
2141	((12, 23),	'BaseFontBlend',	'delta',	None,	None),
2142	((12, 31),	'CIDFontVersion',	'number',	0,	None),
2143	((12, 32),	'CIDFontRevision',	'number',	0,	None),
2144	((12, 33),	'CIDFontType',		'number',	0,	None),
2145	((12, 34),	'CIDCount',		'number',	8720,	None),
2146	(15,		'charset',		'number',	None,	CharsetConverter()),
2147	((12, 35),	'UIDBase',		'number',	None,	None),
2148	(16,		'Encoding',		'number',	0,	EncodingConverter()),
2149	(18,		'Private',	('number', 'number'),	None,	PrivateDictConverter()),
2150	((12, 37),	'FDSelect',		'number',	None,	FDSelectConverter()),
2151	((12, 36),	'FDArray',		'number',	None,	FDArrayConverter()),
2152	(17,		'CharStrings',		'number',	None,	CharStringsConverter()),
2153	(24,		'VarStore',		'number',	None,	VarStoreConverter()),
2154]
2155
2156topDictOperators2 = [
2157#	opcode		name			argument type	default	converter
2158	(25,		'maxstack',		'number',	None,	None),
2159	((12, 7),	'FontMatrix',		'array',	[0.001, 0, 0, 0.001, 0, 0],	None),
2160	((12, 37),	'FDSelect',		'number',	None,	FDSelectConverter()),
2161	((12, 36),	'FDArray',		'number',	None,	FDArrayConverter()),
2162	(17,		'CharStrings',		'number',	None,	CharStringsConverter()),
2163	(24,		'VarStore',		'number',	None,	VarStoreConverter()),
2164]
2165
2166# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order,
2167# in order for the font to compile back from xml.
2168
2169kBlendDictOpName = "blend"
2170blendOp = 23
2171
2172privateDictOperators = [
2173#	opcode		name			argument type	default	converter
2174	(22,	"vsindex",		'number',	None,	None),
2175	(blendOp,	kBlendDictOpName,		'blendList',	None,	None), # This is for reading to/from XML: it not written to CFF.
2176	(6,		'BlueValues',		'delta',	None,	None),
2177	(7,		'OtherBlues',		'delta',	None,	None),
2178	(8,		'FamilyBlues',		'delta',	None,	None),
2179	(9,		'FamilyOtherBlues',	'delta',	None,	None),
2180	((12, 9),	'BlueScale',		'number',	0.039625, None),
2181	((12, 10),	'BlueShift',		'number',	7,	None),
2182	((12, 11),	'BlueFuzz',		'number',	1,	None),
2183	(10,		'StdHW',		'number',	None,	None),
2184	(11,		'StdVW',		'number',	None,	None),
2185	((12, 12),	'StemSnapH',		'delta',	None,	None),
2186	((12, 13),	'StemSnapV',		'delta',	None,	None),
2187	((12, 14),	'ForceBold',		'number',	0,	None),
2188	((12, 15),	'ForceBoldThreshold',	'number',	None,	None), # deprecated
2189	((12, 16),	'lenIV',		'number',	None,	None), # deprecated
2190	((12, 17),	'LanguageGroup',	'number',	0,	None),
2191	((12, 18),	'ExpansionFactor',	'number',	0.06,	None),
2192	((12, 19),	'initialRandomSeed',	'number',	0,	None),
2193	(20,		'defaultWidthX',	'number',	0,	None),
2194	(21,		'nominalWidthX',	'number',	0,	None),
2195	(19,		'Subrs',		'number',	None,	SubrsConverter()),
2196]
2197
2198privateDictOperators2 = [
2199#	opcode		name			argument type	default	converter
2200	(22,	"vsindex",		'number',	None,	None),
2201	(blendOp,	kBlendDictOpName,		'blendList',	None,	None), # This is for reading to/from XML: it not written to CFF.
2202	(6,		'BlueValues',		'delta',	None,	None),
2203	(7,		'OtherBlues',		'delta',	None,	None),
2204	(8,		'FamilyBlues',		'delta',	None,	None),
2205	(9,		'FamilyOtherBlues',	'delta',	None,	None),
2206	((12, 9),	'BlueScale',		'number',	0.039625, None),
2207	((12, 10),	'BlueShift',		'number',	7,	None),
2208	((12, 11),	'BlueFuzz',		'number',	1,	None),
2209	(10,		'StdHW',		'number',	None,	None),
2210	(11,		'StdVW',		'number',	None,	None),
2211	((12, 12),	'StemSnapH',		'delta',	None,	None),
2212	((12, 13),	'StemSnapV',		'delta',	None,	None),
2213	((12, 17),	'LanguageGroup',	'number',	0,	None),
2214	((12, 18),	'ExpansionFactor',	'number',	0.06,	None),
2215	(19,		'Subrs',		'number',	None,	SubrsConverter()),
2216]
2217
2218
2219def addConverters(table):
2220	for i in range(len(table)):
2221		op, name, arg, default, conv = table[i]
2222		if conv is not None:
2223			continue
2224		if arg in ("delta", "array"):
2225			conv = ArrayConverter()
2226		elif arg == "number":
2227			conv = NumberConverter()
2228		elif arg == "SID":
2229			conv = ASCIIConverter()
2230		elif arg == 'blendList':
2231			conv = None
2232		else:
2233			assert False
2234		table[i] = op, name, arg, default, conv
2235
2236
2237addConverters(privateDictOperators)
2238addConverters(topDictOperators)
2239
2240
2241class TopDictDecompiler(psCharStrings.DictDecompiler):
2242	operators = buildOperatorDict(topDictOperators)
2243
2244
2245class PrivateDictDecompiler(psCharStrings.DictDecompiler):
2246	operators = buildOperatorDict(privateDictOperators)
2247
2248
2249class DictCompiler(object):
2250	maxBlendStack = 0
2251
2252	def __init__(self, dictObj, strings, parent, isCFF2=None):
2253		if strings:
2254			assert isinstance(strings, IndexedStrings)
2255		if isCFF2 is None and hasattr(parent, "isCFF2"):
2256			isCFF2 = parent.isCFF2
2257			assert isCFF2 is not None
2258		self.isCFF2 = isCFF2
2259		self.dictObj = dictObj
2260		self.strings = strings
2261		self.parent = parent
2262		rawDict = {}
2263		for name in dictObj.order:
2264			value = getattr(dictObj, name, None)
2265			if value is None:
2266				continue
2267			conv = dictObj.converters[name]
2268			value = conv.write(dictObj, value)
2269			if value == dictObj.defaults.get(name):
2270				continue
2271			rawDict[name] = value
2272		self.rawDict = rawDict
2273
2274	def setPos(self, pos, endPos):
2275		pass
2276
2277	def getDataLength(self):
2278		return len(self.compile("getDataLength"))
2279
2280	def compile(self, reason):
2281		log.log(DEBUG, "-- compiling %s for %s", self.__class__.__name__, reason)
2282		rawDict = self.rawDict
2283		data = []
2284		for name in self.dictObj.order:
2285			value = rawDict.get(name)
2286			if value is None:
2287				continue
2288			op, argType = self.opcodes[name]
2289			if isinstance(argType, tuple):
2290				l = len(argType)
2291				assert len(value) == l, "value doesn't match arg type"
2292				for i in range(l):
2293					arg = argType[i]
2294					v = value[i]
2295					arghandler = getattr(self, "arg_" + arg)
2296					data.append(arghandler(v))
2297			else:
2298				arghandler = getattr(self, "arg_" + argType)
2299				data.append(arghandler(value))
2300			data.append(op)
2301		data = bytesjoin(data)
2302		return data
2303
2304	def toFile(self, file):
2305		data = self.compile("toFile")
2306		file.write(data)
2307
2308	def arg_number(self, num):
2309		if isinstance(num, list):
2310			data = [encodeNumber(val) for val in num]
2311			data.append(encodeNumber(1))
2312			data.append(bytechr(blendOp))
2313			datum = bytesjoin(data)
2314		else:
2315			datum = encodeNumber(num)
2316		return datum
2317
2318	def arg_SID(self, s):
2319		return psCharStrings.encodeIntCFF(self.strings.getSID(s))
2320
2321	def arg_array(self, value):
2322		data = []
2323		for num in value:
2324			data.append(self.arg_number(num))
2325		return bytesjoin(data)
2326
2327	def arg_delta(self, value):
2328		if not value:
2329			return b""
2330		val0 = value[0]
2331		if isinstance(val0, list):
2332			data = self.arg_delta_blend(value)
2333		else:
2334			out = []
2335			last = 0
2336			for v in value:
2337				out.append(v - last)
2338				last = v
2339			data = []
2340			for num in out:
2341				data.append(encodeNumber(num))
2342		return bytesjoin(data)
2343
2344
2345	def arg_delta_blend(self, value):
2346		"""A delta list with blend lists has to be *all* blend lists.
2347
2348		The value is a list is arranged as follows::
2349
2350			[
2351				[V0, d0..dn]
2352				[V1, d0..dn]
2353				...
2354				[Vm, d0..dn]
2355			]
2356
2357		``V`` is the absolute coordinate value from the default font, and ``d0-dn``
2358		are the delta values from the *n* regions. Each ``V`` is an absolute
2359		coordinate from the default font.
2360
2361		We want to return a list::
2362
2363			[
2364				[v0, v1..vm]
2365				[d0..dn]
2366				...
2367				[d0..dn]
2368				numBlends
2369				blendOp
2370			]
2371
2372		where each ``v`` is relative to the previous default font value.
2373		"""
2374		numMasters = len(value[0])
2375		numBlends = len(value)
2376		numStack = (numBlends * numMasters) + 1
2377		if numStack > self.maxBlendStack:
2378			# Figure out the max number of value we can blend
2379			# and divide this list up into chunks of that size.
2380
2381			numBlendValues = int((self.maxBlendStack - 1) / numMasters)
2382			out = []
2383			while True:
2384				numVal = min(len(value), numBlendValues)
2385				if numVal == 0:
2386					break
2387				valList = value[0:numVal]
2388				out1 = self.arg_delta_blend(valList)
2389				out.extend(out1)
2390				value = value[numVal:]
2391		else:
2392			firstList = [0] * numBlends
2393			deltaList = [None] * numBlends
2394			i = 0
2395			prevVal = 0
2396			while i < numBlends:
2397				# For PrivateDict BlueValues, the default font
2398				# values are absolute, not relative.
2399				# Must convert these back to relative coordinates
2400				# befor writing to CFF2.
2401				defaultValue = value[i][0]
2402				firstList[i] = defaultValue - prevVal
2403				prevVal = defaultValue
2404				deltaList[i] = value[i][1:]
2405				i += 1
2406
2407			relValueList = firstList
2408			for blendList in deltaList:
2409				relValueList.extend(blendList)
2410			out = [encodeNumber(val) for val in relValueList]
2411			out.append(encodeNumber(numBlends))
2412			out.append(bytechr(blendOp))
2413		return out
2414
2415
2416def encodeNumber(num):
2417	if isinstance(num, float):
2418		return psCharStrings.encodeFloat(num)
2419	else:
2420		return psCharStrings.encodeIntCFF(num)
2421
2422
2423class TopDictCompiler(DictCompiler):
2424
2425	opcodes = buildOpcodeDict(topDictOperators)
2426
2427	def getChildren(self, strings):
2428		isCFF2 = self.isCFF2
2429		children = []
2430		if self.dictObj.cff2GetGlyphOrder is None:
2431			if hasattr(self.dictObj, "charset") and self.dictObj.charset:
2432				if hasattr(self.dictObj, "ROS"):  # aka isCID
2433					charsetCode = None
2434				else:
2435					charsetCode = getStdCharSet(self.dictObj.charset)
2436				if charsetCode is None:
2437					children.append(CharsetCompiler(strings, self.dictObj.charset, self))
2438				else:
2439					self.rawDict["charset"] = charsetCode
2440			if hasattr(self.dictObj, "Encoding") and self.dictObj.Encoding:
2441				encoding = self.dictObj.Encoding
2442				if not isinstance(encoding, str):
2443					children.append(EncodingCompiler(strings, encoding, self))
2444		else:
2445			if hasattr(self.dictObj, "VarStore"):
2446				varStoreData = self.dictObj.VarStore
2447				varStoreComp = VarStoreCompiler(varStoreData, self)
2448				children.append(varStoreComp)
2449		if hasattr(self.dictObj, "FDSelect"):
2450			# I have not yet supported merging a ttx CFF-CID font, as there are
2451			# interesting issues about merging the FDArrays. Here I assume that
2452			# either the font was read from XML, and the FDSelect indices are all
2453			# in the charstring data, or the FDSelect array is already fully defined.
2454			fdSelect = self.dictObj.FDSelect
2455			# probably read in from XML; assume fdIndex in CharString data
2456			if len(fdSelect) == 0:
2457				charStrings = self.dictObj.CharStrings
2458				for name in self.dictObj.charset:
2459					fdSelect.append(charStrings[name].fdSelectIndex)
2460			fdSelectComp = FDSelectCompiler(fdSelect, self)
2461			children.append(fdSelectComp)
2462		if hasattr(self.dictObj, "CharStrings"):
2463			items = []
2464			charStrings = self.dictObj.CharStrings
2465			for name in self.dictObj.charset:
2466				items.append(charStrings[name])
2467			charStringsComp = CharStringsCompiler(
2468				items, strings, self, isCFF2=isCFF2)
2469			children.append(charStringsComp)
2470		if hasattr(self.dictObj, "FDArray"):
2471			# I have not yet supported merging a ttx CFF-CID font, as there are
2472			# interesting issues about merging the FDArrays. Here I assume that the
2473			# FDArray info is correct and complete.
2474			fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self)
2475			children.append(fdArrayIndexComp)
2476			children.extend(fdArrayIndexComp.getChildren(strings))
2477		if hasattr(self.dictObj, "Private"):
2478			privComp = self.dictObj.Private.getCompiler(strings, self)
2479			children.append(privComp)
2480			children.extend(privComp.getChildren(strings))
2481		return children
2482
2483
2484class FontDictCompiler(DictCompiler):
2485	opcodes = buildOpcodeDict(topDictOperators)
2486
2487	def __init__(self, dictObj, strings, parent, isCFF2=None):
2488		super(FontDictCompiler, self).__init__(dictObj, strings, parent, isCFF2=isCFF2)
2489		#
2490		# We now take some effort to detect if there were any key/value pairs
2491		# supplied that were ignored in the FontDict context, and issue a warning
2492		# for those cases.
2493		#
2494		ignoredNames = []
2495		dictObj = self.dictObj
2496		for name in sorted(set(dictObj.converters) - set(dictObj.order)):
2497			if name in dictObj.rawDict:
2498				# The font was directly read from binary. In this
2499				# case, we want to report *all* "useless" key/value
2500				# pairs that are in the font, not just the ones that
2501				# are different from the default.
2502				ignoredNames.append(name)
2503			else:
2504				# The font was probably read from a TTX file. We only
2505				# warn about keys whos value is not the default. The
2506				# ones that have the default value will not be written
2507				# to binary anyway.
2508				default = dictObj.defaults.get(name)
2509				if default is not None:
2510					conv = dictObj.converters[name]
2511					default = conv.read(dictObj, default)
2512				if getattr(dictObj, name, None) != default:
2513					ignoredNames.append(name)
2514		if ignoredNames:
2515			log.warning(
2516				"Some CFF FDArray/FontDict keys were ignored upon compile: " +
2517				" ".join(sorted(ignoredNames)))
2518
2519	def getChildren(self, strings):
2520		children = []
2521		if hasattr(self.dictObj, "Private"):
2522			privComp = self.dictObj.Private.getCompiler(strings, self)
2523			children.append(privComp)
2524			children.extend(privComp.getChildren(strings))
2525		return children
2526
2527
2528class PrivateDictCompiler(DictCompiler):
2529
2530	maxBlendStack = maxStackLimit
2531	opcodes = buildOpcodeDict(privateDictOperators)
2532
2533	def setPos(self, pos, endPos):
2534		size = endPos - pos
2535		self.parent.rawDict["Private"] = size, pos
2536		self.pos = pos
2537
2538	def getChildren(self, strings):
2539		children = []
2540		if hasattr(self.dictObj, "Subrs"):
2541			children.append(self.dictObj.Subrs.getCompiler(strings, self))
2542		return children
2543
2544
2545class BaseDict(object):
2546
2547	def __init__(self, strings=None, file=None, offset=None, isCFF2=None):
2548		assert (isCFF2 is None) == (file is None)
2549		self.rawDict = {}
2550		self.skipNames = []
2551		self.strings = strings
2552		if file is None:
2553			return
2554		self._isCFF2 = isCFF2
2555		self.file = file
2556		if offset is not None:
2557			log.log(DEBUG, "loading %s at %s", self.__class__.__name__, offset)
2558			self.offset = offset
2559
2560	def decompile(self, data):
2561		log.log(DEBUG, "    length %s is %d", self.__class__.__name__, len(data))
2562		dec = self.decompilerClass(self.strings, self)
2563		dec.decompile(data)
2564		self.rawDict = dec.getDict()
2565		self.postDecompile()
2566
2567	def postDecompile(self):
2568		pass
2569
2570	def getCompiler(self, strings, parent, isCFF2=None):
2571		return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
2572
2573	def __getattr__(self, name):
2574		if name[:2] == name[-2:] == "__":
2575			# to make deepcopy() and pickle.load() work, we need to signal with
2576			# AttributeError that dunder methods like '__deepcopy__' or '__getstate__'
2577			# aren't implemented. For more details, see:
2578			# https://github.com/fonttools/fonttools/pull/1488
2579			raise AttributeError(name)
2580		value = self.rawDict.get(name, None)
2581		if value is None:
2582			value = self.defaults.get(name)
2583		if value is None:
2584			raise AttributeError(name)
2585		conv = self.converters[name]
2586		value = conv.read(self, value)
2587		setattr(self, name, value)
2588		return value
2589
2590	def toXML(self, xmlWriter):
2591		for name in self.order:
2592			if name in self.skipNames:
2593				continue
2594			value = getattr(self, name, None)
2595			# XXX For "charset" we never skip calling xmlWrite even if the
2596			# value is None, so we always write the following XML comment:
2597			#
2598			# <!-- charset is dumped separately as the 'GlyphOrder' element -->
2599			#
2600			# Charset is None when 'CFF ' table is imported from XML into an
2601			# empty TTFont(). By writing this comment all the time, we obtain
2602			# the same XML output whether roundtripping XML-to-XML or
2603			# dumping binary-to-XML
2604			if value is None and name != "charset":
2605				continue
2606			conv = self.converters[name]
2607			conv.xmlWrite(xmlWriter, name, value)
2608		ignoredNames = set(self.rawDict) - set(self.order)
2609		if ignoredNames:
2610			xmlWriter.comment(
2611				"some keys were ignored: %s" % " ".join(sorted(ignoredNames)))
2612			xmlWriter.newline()
2613
2614	def fromXML(self, name, attrs, content):
2615		conv = self.converters[name]
2616		value = conv.xmlRead(name, attrs, content, self)
2617		setattr(self, name, value)
2618
2619
2620class TopDict(BaseDict):
2621	"""The ``TopDict`` represents the top-level dictionary holding font
2622	information. CFF2 tables contain a restricted set of top-level entries
2623	as described `here <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#7-top-dict-data>`_,
2624	but CFF tables may contain a wider range of information. This information
2625	can be accessed through attributes or through the dictionary returned
2626	through the ``rawDict`` property:
2627
2628	.. code:: python
2629
2630		font = tt["CFF "].cff[0]
2631		font.FamilyName
2632		# 'Linux Libertine O'
2633		font.rawDict["FamilyName"]
2634		# 'Linux Libertine O'
2635
2636	More information is available in the CFF file's private dictionary, accessed
2637	via the ``Private`` property:
2638
2639	.. code:: python
2640
2641		tt["CFF "].cff[0].Private.BlueValues
2642		# [-15, 0, 515, 515, 666, 666]
2643
2644	"""
2645
2646	defaults = buildDefaults(topDictOperators)
2647	converters = buildConverters(topDictOperators)
2648	compilerClass = TopDictCompiler
2649	order = buildOrder(topDictOperators)
2650	decompilerClass = TopDictDecompiler
2651
2652	def __init__(self, strings=None, file=None, offset=None,
2653			GlobalSubrs=None, cff2GetGlyphOrder=None, isCFF2=None):
2654		super(TopDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2655		self.cff2GetGlyphOrder = cff2GetGlyphOrder
2656		self.GlobalSubrs = GlobalSubrs
2657		if isCFF2:
2658			self.defaults = buildDefaults(topDictOperators2)
2659			self.charset = cff2GetGlyphOrder()
2660			self.order = buildOrder(topDictOperators2)
2661		else:
2662			self.defaults = buildDefaults(topDictOperators)
2663			self.order = buildOrder(topDictOperators)
2664
2665	def getGlyphOrder(self):
2666		"""Returns a list of glyph names in the CFF font."""
2667		return self.charset
2668
2669	def postDecompile(self):
2670		offset = self.rawDict.get("CharStrings")
2671		if offset is None:
2672			return
2673		# get the number of glyphs beforehand.
2674		self.file.seek(offset)
2675		if self._isCFF2:
2676			self.numGlyphs = readCard32(self.file)
2677		else:
2678			self.numGlyphs = readCard16(self.file)
2679
2680	def toXML(self, xmlWriter):
2681		if hasattr(self, "CharStrings"):
2682			self.decompileAllCharStrings()
2683		if hasattr(self, "ROS"):
2684			self.skipNames = ['Encoding']
2685		if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
2686			# these values have default values, but I only want them to show up
2687			# in CID fonts.
2688			self.skipNames = [
2689				'CIDFontVersion', 'CIDFontRevision', 'CIDFontType', 'CIDCount']
2690		BaseDict.toXML(self, xmlWriter)
2691
2692	def decompileAllCharStrings(self):
2693		# Make sure that all the Private Dicts have been instantiated.
2694		for i, charString in enumerate(self.CharStrings.values()):
2695			try:
2696				charString.decompile()
2697			except:
2698				log.error("Error in charstring %s", i)
2699				raise
2700
2701	def recalcFontBBox(self):
2702		fontBBox = None
2703		for charString in self.CharStrings.values():
2704			bounds = charString.calcBounds(self.CharStrings)
2705			if bounds is not None:
2706				if fontBBox is not None:
2707					fontBBox = unionRect(fontBBox, bounds)
2708				else:
2709					fontBBox = bounds
2710
2711		if fontBBox is None:
2712			self.FontBBox = self.defaults['FontBBox'][:]
2713		else:
2714			self.FontBBox = list(intRect(fontBBox))
2715
2716
2717class FontDict(BaseDict):
2718	#
2719	# Since fonttools used to pass a lot of fields that are not relevant in the FDArray
2720	# FontDict, there are 'ttx' files in the wild that contain all these. These got in
2721	# the ttx files because fonttools writes explicit values for all the TopDict default
2722	# values. These are not actually illegal in the context of an FDArray FontDict - you
2723	# can legally, per spec, put any arbitrary key/value pair in a FontDict - but are
2724	# useless since current major company CFF interpreters ignore anything but the set
2725	# listed in this file. So, we just silently skip them. An exception is Weight: this
2726	# is not used by any interpreter, but some foundries have asked that this be
2727	# supported in FDArray FontDicts just to preserve information about the design when
2728	# the font is being inspected.
2729	#
2730	# On top of that, there are fonts out there that contain such useless FontDict values.
2731	#
2732	# By subclassing TopDict, we *allow* all key/values from TopDict, both when reading
2733	# from binary or when reading from XML, but by overriding `order` with a limited
2734	# list of names, we ensure that only the useful names ever get exported to XML and
2735	# ever get compiled into the binary font.
2736	#
2737	# We override compilerClass so we can warn about "useless" key/value pairs, either
2738	# from the original binary font or from TTX input.
2739	#
2740	# See:
2741	# - https://github.com/fonttools/fonttools/issues/740
2742	# - https://github.com/fonttools/fonttools/issues/601
2743	# - https://github.com/adobe-type-tools/afdko/issues/137
2744	#
2745	defaults = {}
2746	converters = buildConverters(topDictOperators)
2747	compilerClass = FontDictCompiler
2748	orderCFF = ['FontName', 'FontMatrix', 'Weight', 'Private']
2749	orderCFF2 = ['Private']
2750	decompilerClass = TopDictDecompiler
2751
2752	def __init__(self, strings=None, file=None, offset=None,
2753			GlobalSubrs=None, isCFF2=None, vstore=None):
2754		super(FontDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2755		self.vstore = vstore
2756		self.setCFF2(isCFF2)
2757
2758	def setCFF2(self, isCFF2):
2759		# isCFF2 may be None.
2760		if isCFF2:
2761			self.order = self.orderCFF2
2762			self._isCFF2 = True
2763		else:
2764			self.order = self.orderCFF
2765			self._isCFF2 = False
2766
2767
2768class PrivateDict(BaseDict):
2769	defaults = buildDefaults(privateDictOperators)
2770	converters = buildConverters(privateDictOperators)
2771	order = buildOrder(privateDictOperators)
2772	decompilerClass = PrivateDictDecompiler
2773	compilerClass = PrivateDictCompiler
2774
2775	def __init__(self, strings=None, file=None, offset=None, isCFF2=None,
2776			vstore=None):
2777		super(PrivateDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2778		self.vstore = vstore
2779		if isCFF2:
2780			self.defaults = buildDefaults(privateDictOperators2)
2781			self.order = buildOrder(privateDictOperators2)
2782			# Provide dummy values. This avoids needing to provide
2783			# an isCFF2 state in a lot of places.
2784			self.nominalWidthX = self.defaultWidthX = None
2785		else:
2786			self.defaults = buildDefaults(privateDictOperators)
2787			self.order = buildOrder(privateDictOperators)
2788
2789	@property
2790	def in_cff2(self):
2791		return self._isCFF2
2792
2793	def getNumRegions(self, vi=None):  # called from misc/psCharStrings.py
2794		# if getNumRegions is being called, we can assume that VarStore exists.
2795		if vi is None:
2796			if hasattr(self, 'vsindex'):
2797				vi = self.vsindex
2798			else:
2799				vi = 0
2800		numRegions = self.vstore.getNumRegions(vi)
2801		return numRegions
2802
2803
2804class IndexedStrings(object):
2805
2806	"""SID -> string mapping."""
2807
2808	def __init__(self, file=None):
2809		if file is None:
2810			strings = []
2811		else:
2812			strings = [
2813				tostr(s, encoding="latin1")
2814				for s in Index(file, isCFF2=False)
2815			]
2816		self.strings = strings
2817
2818	def getCompiler(self):
2819		return IndexedStringsCompiler(self, None, self, isCFF2=False)
2820
2821	def __len__(self):
2822		return len(self.strings)
2823
2824	def __getitem__(self, SID):
2825		if SID < cffStandardStringCount:
2826			return cffStandardStrings[SID]
2827		else:
2828			return self.strings[SID - cffStandardStringCount]
2829
2830	def getSID(self, s):
2831		if not hasattr(self, "stringMapping"):
2832			self.buildStringMapping()
2833		s = tostr(s, encoding="latin1")
2834		if s in cffStandardStringMapping:
2835			SID = cffStandardStringMapping[s]
2836		elif s in self.stringMapping:
2837			SID = self.stringMapping[s]
2838		else:
2839			SID = len(self.strings) + cffStandardStringCount
2840			self.strings.append(s)
2841			self.stringMapping[s] = SID
2842		return SID
2843
2844	def getStrings(self):
2845		return self.strings
2846
2847	def buildStringMapping(self):
2848		self.stringMapping = {}
2849		for index in range(len(self.strings)):
2850			self.stringMapping[self.strings[index]] = index + cffStandardStringCount
2851
2852
2853# The 391 Standard Strings as used in the CFF format.
2854# from Adobe Technical None #5176, version 1.0, 18 March 1998
2855
2856cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
2857		'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
2858		'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
2859		'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
2860		'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
2861		'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
2862		'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
2863		'bracketright', 'asciicircum', 'underscore', 'quoteleft', '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', 'braceleft', 'bar', 'braceright',
2866		'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
2867		'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
2868		'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
2869		'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
2870		'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
2871		'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
2872		'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
2873		'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
2874		'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
2875		'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
2876		'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
2877		'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
2878		'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
2879		'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
2880		'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
2881		'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
2882		'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
2883		'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
2884		'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
2885		'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
2886		'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
2887		'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
2888		'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
2889		'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
2890		'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
2891		'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
2892		'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
2893		'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
2894		'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
2895		'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
2896		'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
2897		'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
2898		'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
2899		'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
2900		'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
2901		'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
2902		'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
2903		'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
2904		'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
2905		'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
2906		'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
2907		'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
2908		'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
2909		'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
2910		'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
2911		'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
2912		'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
2913		'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
2914		'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
2915		'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
2916		'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
2917		'001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
2918		'Semibold'
2919]
2920
2921cffStandardStringCount = 391
2922assert len(cffStandardStrings) == cffStandardStringCount
2923# build reverse mapping
2924cffStandardStringMapping = {}
2925for _i in range(cffStandardStringCount):
2926	cffStandardStringMapping[cffStandardStrings[_i]] = _i
2927
2928cffISOAdobeStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign",
2929"dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright",
2930"asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two",
2931"three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon",
2932"less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G",
2933"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
2934"X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
2935"underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
2936"k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
2937"braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent",
2938"sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle",
2939"quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl",
2940"endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet",
2941"quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis",
2942"perthousand", "questiondown", "grave", "acute", "circumflex", "tilde",
2943"macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut",
2944"ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE",
2945"ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls",
2946"onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus",
2947"Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn",
2948"threequarters", "twosuperior", "registered", "minus", "eth", "multiply",
2949"threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave",
2950"Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave",
2951"Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute",
2952"Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute",
2953"Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute",
2954"acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute",
2955"ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis",
2956"igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde",
2957"scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis",
2958"zcaron"]
2959
2960cffISOAdobeStringCount = 229
2961assert len(cffISOAdobeStrings) == cffISOAdobeStringCount
2962
2963cffIExpertStrings = [".notdef", "space", "exclamsmall", "Hungarumlautsmall",
2964"dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall",
2965"parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader",
2966"comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle",
2967"twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle",
2968"sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon",
2969"commasuperior", "threequartersemdash", "periodsuperior", "questionsmall",
2970"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
2971"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
2972"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
2973"parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall",
2974"Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall",
2975"Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall",
2976"Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall",
2977"Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall",
2978"exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall",
2979"Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall",
2980"figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall",
2981"onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth",
2982"threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds",
2983"zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior",
2984"fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior",
2985"zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior",
2986"fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior",
2987"centinferior", "dollarinferior", "periodinferior", "commainferior",
2988"Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall",
2989"Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall",
2990"Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall",
2991"Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall",
2992"Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall",
2993"Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall",
2994"Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall",
2995"Ydieresissmall"]
2996
2997cffExpertStringCount = 166
2998assert len(cffIExpertStrings) == cffExpertStringCount
2999
3000cffExpertSubsetStrings = [".notdef", "space", "dollaroldstyle",
3001"dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader",
3002"onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle",
3003"oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle",
3004"sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon",
3005"semicolon", "commasuperior", "threequartersemdash", "periodsuperior",
3006"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
3007"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
3008"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
3009"parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah",
3010"centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf",
3011"threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths",
3012"onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior",
3013"threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior",
3014"eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior",
3015"threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior",
3016"eightinferior", "nineinferior", "centinferior", "dollarinferior",
3017"periodinferior", "commainferior"]
3018
3019cffExpertSubsetStringCount = 87
3020assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount
3021