• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""psCharStrings.py -- module implementing various kinds of CharStrings:
2CFF dictionary data and Type1/Type2 CharStrings.
3"""
4
5from fontTools.misc.py23 import bytechr, byteord, bytesjoin, strjoin
6from fontTools.misc.fixedTools import (
7	fixedToFloat, floatToFixed, floatToFixedToStr, strToFixedToFloat,
8)
9from fontTools.pens.boundsPen import BoundsPen
10import struct
11import logging
12
13
14log = logging.getLogger(__name__)
15
16
17def read_operator(self, b0, data, index):
18	if b0 == 12:
19		op = (b0, byteord(data[index]))
20		index = index+1
21	else:
22		op = b0
23	try:
24		operator = self.operators[op]
25	except KeyError:
26		return None, index
27	value = self.handle_operator(operator)
28	return value, index
29
30def read_byte(self, b0, data, index):
31	return b0 - 139, index
32
33def read_smallInt1(self, b0, data, index):
34	b1 = byteord(data[index])
35	return (b0-247)*256 + b1 + 108, index+1
36
37def read_smallInt2(self, b0, data, index):
38	b1 = byteord(data[index])
39	return -(b0-251)*256 - b1 - 108, index+1
40
41def read_shortInt(self, b0, data, index):
42	value, = struct.unpack(">h", data[index:index+2])
43	return value, index+2
44
45def read_longInt(self, b0, data, index):
46	value, = struct.unpack(">l", data[index:index+4])
47	return value, index+4
48
49def read_fixed1616(self, b0, data, index):
50	value, = struct.unpack(">l", data[index:index+4])
51	return fixedToFloat(value, precisionBits=16), index+4
52
53def read_reserved(self, b0, data, index):
54	assert NotImplementedError
55	return NotImplemented, index
56
57def read_realNumber(self, b0, data, index):
58	number = ''
59	while True:
60		b = byteord(data[index])
61		index = index + 1
62		nibble0 = (b & 0xf0) >> 4
63		nibble1 = b & 0x0f
64		if nibble0 == 0xf:
65			break
66		number = number + realNibbles[nibble0]
67		if nibble1 == 0xf:
68			break
69		number = number + realNibbles[nibble1]
70	return float(number), index
71
72
73t1OperandEncoding = [None] * 256
74t1OperandEncoding[0:32] = (32) * [read_operator]
75t1OperandEncoding[32:247] = (247 - 32) * [read_byte]
76t1OperandEncoding[247:251] = (251 - 247) * [read_smallInt1]
77t1OperandEncoding[251:255] = (255 - 251) * [read_smallInt2]
78t1OperandEncoding[255] = read_longInt
79assert len(t1OperandEncoding) == 256
80
81t2OperandEncoding = t1OperandEncoding[:]
82t2OperandEncoding[28] = read_shortInt
83t2OperandEncoding[255] = read_fixed1616
84
85cffDictOperandEncoding = t2OperandEncoding[:]
86cffDictOperandEncoding[29] = read_longInt
87cffDictOperandEncoding[30] = read_realNumber
88cffDictOperandEncoding[255] = read_reserved
89
90
91realNibbles = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
92		'.', 'E', 'E-', None, '-']
93realNibblesDict = {v:i for i,v in enumerate(realNibbles)}
94
95maxOpStack = 193
96
97
98def buildOperatorDict(operatorList):
99	oper = {}
100	opc = {}
101	for item in operatorList:
102		if len(item) == 2:
103			oper[item[0]] = item[1]
104		else:
105			oper[item[0]] = item[1:]
106		if isinstance(item[0], tuple):
107			opc[item[1]] = item[0]
108		else:
109			opc[item[1]] = (item[0],)
110	return oper, opc
111
112
113t2Operators = [
114#	opcode		name
115	(1,		'hstem'),
116	(3,		'vstem'),
117	(4,		'vmoveto'),
118	(5,		'rlineto'),
119	(6,		'hlineto'),
120	(7,		'vlineto'),
121	(8,		'rrcurveto'),
122	(10,		'callsubr'),
123	(11,		'return'),
124	(14,		'endchar'),
125	(15,		'vsindex'),
126	(16,		'blend'),
127	(18,		'hstemhm'),
128	(19,		'hintmask'),
129	(20,		'cntrmask'),
130	(21,		'rmoveto'),
131	(22,		'hmoveto'),
132	(23,		'vstemhm'),
133	(24,		'rcurveline'),
134	(25,		'rlinecurve'),
135	(26,		'vvcurveto'),
136	(27,		'hhcurveto'),
137#	(28,		'shortint'),  # not really an operator
138	(29,		'callgsubr'),
139	(30,		'vhcurveto'),
140	(31,		'hvcurveto'),
141	((12, 0),	'ignore'),	# dotsection. Yes, there a few very early OTF/CFF
142							# fonts with this deprecated operator. Just ignore it.
143	((12, 3),	'and'),
144	((12, 4),	'or'),
145	((12, 5),	'not'),
146	((12, 8),	'store'),
147	((12, 9),	'abs'),
148	((12, 10),	'add'),
149	((12, 11),	'sub'),
150	((12, 12),	'div'),
151	((12, 13),	'load'),
152	((12, 14),	'neg'),
153	((12, 15),	'eq'),
154	((12, 18),	'drop'),
155	((12, 20),	'put'),
156	((12, 21),	'get'),
157	((12, 22),	'ifelse'),
158	((12, 23),	'random'),
159	((12, 24),	'mul'),
160	((12, 26),	'sqrt'),
161	((12, 27),	'dup'),
162	((12, 28),	'exch'),
163	((12, 29),	'index'),
164	((12, 30),	'roll'),
165	((12, 34),	'hflex'),
166	((12, 35),	'flex'),
167	((12, 36),	'hflex1'),
168	((12, 37),	'flex1'),
169]
170
171def getIntEncoder(format):
172	if format == "cff":
173		fourByteOp = bytechr(29)
174	elif format == "t1":
175		fourByteOp = bytechr(255)
176	else:
177		assert format == "t2"
178		fourByteOp = None
179
180	def encodeInt(value, fourByteOp=fourByteOp, bytechr=bytechr,
181			pack=struct.pack, unpack=struct.unpack):
182		if -107 <= value <= 107:
183			code = bytechr(value + 139)
184		elif 108 <= value <= 1131:
185			value = value - 108
186			code = bytechr((value >> 8) + 247) + bytechr(value & 0xFF)
187		elif -1131 <= value <= -108:
188			value = -value - 108
189			code = bytechr((value >> 8) + 251) + bytechr(value & 0xFF)
190		elif fourByteOp is None:
191			# T2 only supports 2 byte ints
192			if -32768 <= value <= 32767:
193				code = bytechr(28) + pack(">h", value)
194			else:
195				# Backwards compatible hack: due to a previous bug in FontTools,
196				# 16.16 fixed numbers were written out as 4-byte ints. When
197				# these numbers were small, they were wrongly written back as
198				# small ints instead of 4-byte ints, breaking round-tripping.
199				# This here workaround doesn't do it any better, since we can't
200				# distinguish anymore between small ints that were supposed to
201				# be small fixed numbers and small ints that were just small
202				# ints. Hence the warning.
203				log.warning("4-byte T2 number got passed to the "
204					"IntType handler. This should happen only when reading in "
205					"old XML files.\n")
206				code = bytechr(255) + pack(">l", value)
207		else:
208			code = fourByteOp + pack(">l", value)
209		return code
210
211	return encodeInt
212
213
214encodeIntCFF = getIntEncoder("cff")
215encodeIntT1 = getIntEncoder("t1")
216encodeIntT2 = getIntEncoder("t2")
217
218def encodeFixed(f, pack=struct.pack):
219	"""For T2 only"""
220	value = floatToFixed(f, precisionBits=16)
221	if value & 0xFFFF == 0:  # check if the fractional part is zero
222		return encodeIntT2(value >> 16)  # encode only the integer part
223	else:
224		return b"\xff" + pack(">l", value)  # encode the entire fixed point value
225
226
227realZeroBytes = bytechr(30) + bytechr(0xf)
228
229def encodeFloat(f):
230	# For CFF only, used in cffLib
231	if f == 0.0: # 0.0 == +0.0 == -0.0
232		return realZeroBytes
233	# Note: 14 decimal digits seems to be the limitation for CFF real numbers
234	# in macOS. However, we use 8 here to match the implementation of AFDKO.
235	s = "%.8G" % f
236	if s[:2] == "0.":
237		s = s[1:]
238	elif s[:3] == "-0.":
239		s = "-" + s[2:]
240	nibbles = []
241	while s:
242		c = s[0]
243		s = s[1:]
244		if c == "E":
245			c2 = s[:1]
246			if c2 == "-":
247				s = s[1:]
248				c = "E-"
249			elif c2 == "+":
250				s = s[1:]
251		nibbles.append(realNibblesDict[c])
252	nibbles.append(0xf)
253	if len(nibbles) % 2:
254		nibbles.append(0xf)
255	d = bytechr(30)
256	for i in range(0, len(nibbles), 2):
257		d = d + bytechr(nibbles[i] << 4 | nibbles[i+1])
258	return d
259
260
261class CharStringCompileError(Exception): pass
262
263
264class SimpleT2Decompiler(object):
265
266	def __init__(self, localSubrs, globalSubrs, private=None):
267		self.localSubrs = localSubrs
268		self.localBias = calcSubrBias(localSubrs)
269		self.globalSubrs = globalSubrs
270		self.globalBias = calcSubrBias(globalSubrs)
271		self.private = private
272		self.reset()
273
274	def reset(self):
275		self.callingStack = []
276		self.operandStack = []
277		self.hintCount = 0
278		self.hintMaskBytes = 0
279		self.numRegions = 0
280
281	def execute(self, charString):
282		self.callingStack.append(charString)
283		needsDecompilation = charString.needsDecompilation()
284		if needsDecompilation:
285			program = []
286			pushToProgram = program.append
287		else:
288			pushToProgram = lambda x: None
289		pushToStack = self.operandStack.append
290		index = 0
291		while True:
292			token, isOperator, index = charString.getToken(index)
293			if token is None:
294				break  # we're done!
295			pushToProgram(token)
296			if isOperator:
297				handlerName = "op_" + token
298				handler = getattr(self, handlerName, None)
299				if handler is not None:
300					rv = handler(index)
301					if rv:
302						hintMaskBytes, index = rv
303						pushToProgram(hintMaskBytes)
304				else:
305					self.popall()
306			else:
307				pushToStack(token)
308		if needsDecompilation:
309			charString.setProgram(program)
310		del self.callingStack[-1]
311
312	def pop(self):
313		value = self.operandStack[-1]
314		del self.operandStack[-1]
315		return value
316
317	def popall(self):
318		stack = self.operandStack[:]
319		self.operandStack[:] = []
320		return stack
321
322	def push(self, value):
323		self.operandStack.append(value)
324
325	def op_return(self, index):
326		if self.operandStack:
327			pass
328
329	def op_endchar(self, index):
330		pass
331
332	def op_ignore(self, index):
333		pass
334
335	def op_callsubr(self, index):
336		subrIndex = self.pop()
337		subr = self.localSubrs[subrIndex+self.localBias]
338		self.execute(subr)
339
340	def op_callgsubr(self, index):
341		subrIndex = self.pop()
342		subr = self.globalSubrs[subrIndex+self.globalBias]
343		self.execute(subr)
344
345	def op_hstem(self, index):
346		self.countHints()
347	def op_vstem(self, index):
348		self.countHints()
349	def op_hstemhm(self, index):
350		self.countHints()
351	def op_vstemhm(self, index):
352		self.countHints()
353
354	def op_hintmask(self, index):
355		if not self.hintMaskBytes:
356			self.countHints()
357			self.hintMaskBytes = (self.hintCount + 7) // 8
358		hintMaskBytes, index = self.callingStack[-1].getBytes(index, self.hintMaskBytes)
359		return hintMaskBytes, index
360
361	op_cntrmask = op_hintmask
362
363	def countHints(self):
364		args = self.popall()
365		self.hintCount = self.hintCount + len(args) // 2
366
367	# misc
368	def op_and(self, index):
369		raise NotImplementedError
370	def op_or(self, index):
371		raise NotImplementedError
372	def op_not(self, index):
373		raise NotImplementedError
374	def op_store(self, index):
375		raise NotImplementedError
376	def op_abs(self, index):
377		raise NotImplementedError
378	def op_add(self, index):
379		raise NotImplementedError
380	def op_sub(self, index):
381		raise NotImplementedError
382	def op_div(self, index):
383		raise NotImplementedError
384	def op_load(self, index):
385		raise NotImplementedError
386	def op_neg(self, index):
387		raise NotImplementedError
388	def op_eq(self, index):
389		raise NotImplementedError
390	def op_drop(self, index):
391		raise NotImplementedError
392	def op_put(self, index):
393		raise NotImplementedError
394	def op_get(self, index):
395		raise NotImplementedError
396	def op_ifelse(self, index):
397		raise NotImplementedError
398	def op_random(self, index):
399		raise NotImplementedError
400	def op_mul(self, index):
401		raise NotImplementedError
402	def op_sqrt(self, index):
403		raise NotImplementedError
404	def op_dup(self, index):
405		raise NotImplementedError
406	def op_exch(self, index):
407		raise NotImplementedError
408	def op_index(self, index):
409		raise NotImplementedError
410	def op_roll(self, index):
411		raise NotImplementedError
412
413	# TODO(behdad): move to T2OutlineExtractor and add a 'setVariation'
414	# method that takes VarStoreData and a location
415	def op_blend(self, index):
416		if self.numRegions == 0:
417			self.numRegions = self.private.getNumRegions()
418		numBlends = self.pop()
419		numOps = numBlends * (self.numRegions + 1)
420		del self.operandStack[-(numOps-numBlends):] # Leave the default operands on the stack.
421
422	def op_vsindex(self, index):
423		vi = self.pop()
424		self.numRegions = self.private.getNumRegions(vi)
425
426
427t1Operators = [
428#	opcode		name
429	(1,		'hstem'),
430	(3,		'vstem'),
431	(4,		'vmoveto'),
432	(5,		'rlineto'),
433	(6,		'hlineto'),
434	(7,		'vlineto'),
435	(8,		'rrcurveto'),
436	(9,		'closepath'),
437	(10,		'callsubr'),
438	(11,		'return'),
439	(13,		'hsbw'),
440	(14,		'endchar'),
441	(21,		'rmoveto'),
442	(22,		'hmoveto'),
443	(30,		'vhcurveto'),
444	(31,		'hvcurveto'),
445	((12, 0),	'dotsection'),
446	((12, 1),	'vstem3'),
447	((12, 2),	'hstem3'),
448	((12, 6),	'seac'),
449	((12, 7),	'sbw'),
450	((12, 12),	'div'),
451	((12, 16),	'callothersubr'),
452	((12, 17),	'pop'),
453	((12, 33),	'setcurrentpoint'),
454]
455
456
457class T2WidthExtractor(SimpleT2Decompiler):
458
459	def __init__(self, localSubrs, globalSubrs, nominalWidthX, defaultWidthX, private=None):
460		SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs, private)
461		self.nominalWidthX = nominalWidthX
462		self.defaultWidthX = defaultWidthX
463
464	def reset(self):
465		SimpleT2Decompiler.reset(self)
466		self.gotWidth = 0
467		self.width = 0
468
469	def popallWidth(self, evenOdd=0):
470		args = self.popall()
471		if not self.gotWidth:
472			if evenOdd ^ (len(args) % 2):
473				# For CFF2 charstrings, this should never happen
474				assert self.defaultWidthX is not None, "CFF2 CharStrings must not have an initial width value"
475				self.width = self.nominalWidthX + args[0]
476				args = args[1:]
477			else:
478				self.width = self.defaultWidthX
479			self.gotWidth = 1
480		return args
481
482	def countHints(self):
483		args = self.popallWidth()
484		self.hintCount = self.hintCount + len(args) // 2
485
486	def op_rmoveto(self, index):
487		self.popallWidth()
488
489	def op_hmoveto(self, index):
490		self.popallWidth(1)
491
492	def op_vmoveto(self, index):
493		self.popallWidth(1)
494
495	def op_endchar(self, index):
496		self.popallWidth()
497
498
499class T2OutlineExtractor(T2WidthExtractor):
500
501	def __init__(self, pen, localSubrs, globalSubrs, nominalWidthX, defaultWidthX, private=None):
502		T2WidthExtractor.__init__(
503			self, localSubrs, globalSubrs, nominalWidthX, defaultWidthX, private)
504		self.pen = pen
505
506	def reset(self):
507		T2WidthExtractor.reset(self)
508		self.currentPoint = (0, 0)
509		self.sawMoveTo = 0
510
511	def _nextPoint(self, point):
512		x, y = self.currentPoint
513		point = x + point[0], y + point[1]
514		self.currentPoint = point
515		return point
516
517	def rMoveTo(self, point):
518		self.pen.moveTo(self._nextPoint(point))
519		self.sawMoveTo = 1
520
521	def rLineTo(self, point):
522		if not self.sawMoveTo:
523			self.rMoveTo((0, 0))
524		self.pen.lineTo(self._nextPoint(point))
525
526	def rCurveTo(self, pt1, pt2, pt3):
527		if not self.sawMoveTo:
528			self.rMoveTo((0, 0))
529		nextPoint = self._nextPoint
530		self.pen.curveTo(nextPoint(pt1), nextPoint(pt2), nextPoint(pt3))
531
532	def closePath(self):
533		if self.sawMoveTo:
534			self.pen.closePath()
535		self.sawMoveTo = 0
536
537	def endPath(self):
538		# In T2 there are no open paths, so always do a closePath when
539		# finishing a sub path.
540		self.closePath()
541
542	#
543	# hint operators
544	#
545	#def op_hstem(self, index):
546	#	self.countHints()
547	#def op_vstem(self, index):
548	#	self.countHints()
549	#def op_hstemhm(self, index):
550	#	self.countHints()
551	#def op_vstemhm(self, index):
552	#	self.countHints()
553	#def op_hintmask(self, index):
554	#	self.countHints()
555	#def op_cntrmask(self, index):
556	#	self.countHints()
557
558	#
559	# path constructors, moveto
560	#
561	def op_rmoveto(self, index):
562		self.endPath()
563		self.rMoveTo(self.popallWidth())
564	def op_hmoveto(self, index):
565		self.endPath()
566		self.rMoveTo((self.popallWidth(1)[0], 0))
567	def op_vmoveto(self, index):
568		self.endPath()
569		self.rMoveTo((0, self.popallWidth(1)[0]))
570	def op_endchar(self, index):
571		self.endPath()
572		args = self.popallWidth()
573		if args:
574			from fontTools.encodings.StandardEncoding import StandardEncoding
575			# endchar can do seac accent bulding; The T2 spec says it's deprecated,
576			# but recent software that shall remain nameless does output it.
577			adx, ady, bchar, achar = args
578			baseGlyph = StandardEncoding[bchar]
579			self.pen.addComponent(baseGlyph, (1, 0, 0, 1, 0, 0))
580			accentGlyph = StandardEncoding[achar]
581			self.pen.addComponent(accentGlyph, (1, 0, 0, 1, adx, ady))
582
583	#
584	# path constructors, lines
585	#
586	def op_rlineto(self, index):
587		args = self.popall()
588		for i in range(0, len(args), 2):
589			point = args[i:i+2]
590			self.rLineTo(point)
591
592	def op_hlineto(self, index):
593		self.alternatingLineto(1)
594	def op_vlineto(self, index):
595		self.alternatingLineto(0)
596
597	#
598	# path constructors, curves
599	#
600	def op_rrcurveto(self, index):
601		"""{dxa dya dxb dyb dxc dyc}+ rrcurveto"""
602		args = self.popall()
603		for i in range(0, len(args), 6):
604			dxa, dya, dxb, dyb, dxc, dyc, = args[i:i+6]
605			self.rCurveTo((dxa, dya), (dxb, dyb), (dxc, dyc))
606
607	def op_rcurveline(self, index):
608		"""{dxa dya dxb dyb dxc dyc}+ dxd dyd rcurveline"""
609		args = self.popall()
610		for i in range(0, len(args)-2, 6):
611			dxb, dyb, dxc, dyc, dxd, dyd = args[i:i+6]
612			self.rCurveTo((dxb, dyb), (dxc, dyc), (dxd, dyd))
613		self.rLineTo(args[-2:])
614
615	def op_rlinecurve(self, index):
616		"""{dxa dya}+ dxb dyb dxc dyc dxd dyd rlinecurve"""
617		args = self.popall()
618		lineArgs = args[:-6]
619		for i in range(0, len(lineArgs), 2):
620			self.rLineTo(lineArgs[i:i+2])
621		dxb, dyb, dxc, dyc, dxd, dyd = args[-6:]
622		self.rCurveTo((dxb, dyb), (dxc, dyc), (dxd, dyd))
623
624	def op_vvcurveto(self, index):
625		"dx1? {dya dxb dyb dyc}+ vvcurveto"
626		args = self.popall()
627		if len(args) % 2:
628			dx1 = args[0]
629			args = args[1:]
630		else:
631			dx1 = 0
632		for i in range(0, len(args), 4):
633			dya, dxb, dyb, dyc = args[i:i+4]
634			self.rCurveTo((dx1, dya), (dxb, dyb), (0, dyc))
635			dx1 = 0
636
637	def op_hhcurveto(self, index):
638		"""dy1? {dxa dxb dyb dxc}+ hhcurveto"""
639		args = self.popall()
640		if len(args) % 2:
641			dy1 = args[0]
642			args = args[1:]
643		else:
644			dy1 = 0
645		for i in range(0, len(args), 4):
646			dxa, dxb, dyb, dxc = args[i:i+4]
647			self.rCurveTo((dxa, dy1), (dxb, dyb), (dxc, 0))
648			dy1 = 0
649
650	def op_vhcurveto(self, index):
651		"""dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf? vhcurveto (30)
652		{dya dxb dyb dxc dxd dxe dye dyf}+ dxf? vhcurveto
653		"""
654		args = self.popall()
655		while args:
656			args = self.vcurveto(args)
657			if args:
658				args = self.hcurveto(args)
659
660	def op_hvcurveto(self, index):
661		"""dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf?
662		{dxa dxb dyb dyc dyd dxe dye dxf}+ dyf?
663		"""
664		args = self.popall()
665		while args:
666			args = self.hcurveto(args)
667			if args:
668				args = self.vcurveto(args)
669
670	#
671	# path constructors, flex
672	#
673	def op_hflex(self, index):
674		dx1, dx2, dy2, dx3, dx4, dx5, dx6 = self.popall()
675		dy1 = dy3 = dy4 = dy6 = 0
676		dy5 = -dy2
677		self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
678		self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))
679	def op_flex(self, index):
680		dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4, dx5, dy5, dx6, dy6, fd = self.popall()
681		self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
682		self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))
683	def op_hflex1(self, index):
684		dx1, dy1, dx2, dy2, dx3, dx4, dx5, dy5, dx6 = self.popall()
685		dy3 = dy4 = 0
686		dy6 = -(dy1 + dy2 + dy3 + dy4 + dy5)
687
688		self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
689		self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))
690	def op_flex1(self, index):
691		dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4, dx5, dy5, d6 = self.popall()
692		dx = dx1 + dx2 + dx3 + dx4 + dx5
693		dy = dy1 + dy2 + dy3 + dy4 + dy5
694		if abs(dx) > abs(dy):
695			dx6 = d6
696			dy6 = -dy
697		else:
698			dx6 = -dx
699			dy6 = d6
700		self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
701		self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))
702
703	# misc
704	def op_and(self, index):
705		raise NotImplementedError
706	def op_or(self, index):
707		raise NotImplementedError
708	def op_not(self, index):
709		raise NotImplementedError
710	def op_store(self, index):
711		raise NotImplementedError
712	def op_abs(self, index):
713		raise NotImplementedError
714	def op_add(self, index):
715		raise NotImplementedError
716	def op_sub(self, index):
717		raise NotImplementedError
718	def op_div(self, index):
719		num2 = self.pop()
720		num1 = self.pop()
721		d1 = num1//num2
722		d2 = num1/num2
723		if d1 == d2:
724			self.push(d1)
725		else:
726			self.push(d2)
727	def op_load(self, index):
728		raise NotImplementedError
729	def op_neg(self, index):
730		raise NotImplementedError
731	def op_eq(self, index):
732		raise NotImplementedError
733	def op_drop(self, index):
734		raise NotImplementedError
735	def op_put(self, index):
736		raise NotImplementedError
737	def op_get(self, index):
738		raise NotImplementedError
739	def op_ifelse(self, index):
740		raise NotImplementedError
741	def op_random(self, index):
742		raise NotImplementedError
743	def op_mul(self, index):
744		raise NotImplementedError
745	def op_sqrt(self, index):
746		raise NotImplementedError
747	def op_dup(self, index):
748		raise NotImplementedError
749	def op_exch(self, index):
750		raise NotImplementedError
751	def op_index(self, index):
752		raise NotImplementedError
753	def op_roll(self, index):
754		raise NotImplementedError
755
756	#
757	# miscellaneous helpers
758	#
759	def alternatingLineto(self, isHorizontal):
760		args = self.popall()
761		for arg in args:
762			if isHorizontal:
763				point = (arg, 0)
764			else:
765				point = (0, arg)
766			self.rLineTo(point)
767			isHorizontal = not isHorizontal
768
769	def vcurveto(self, args):
770		dya, dxb, dyb, dxc = args[:4]
771		args = args[4:]
772		if len(args) == 1:
773			dyc = args[0]
774			args = []
775		else:
776			dyc = 0
777		self.rCurveTo((0, dya), (dxb, dyb), (dxc, dyc))
778		return args
779
780	def hcurveto(self, args):
781		dxa, dxb, dyb, dyc = args[:4]
782		args = args[4:]
783		if len(args) == 1:
784			dxc = args[0]
785			args = []
786		else:
787			dxc = 0
788		self.rCurveTo((dxa, 0), (dxb, dyb), (dxc, dyc))
789		return args
790
791class T1OutlineExtractor(T2OutlineExtractor):
792
793	def __init__(self, pen, subrs):
794		self.pen = pen
795		self.subrs = subrs
796		self.reset()
797
798	def reset(self):
799		self.flexing = 0
800		self.width = 0
801		self.sbx = 0
802		T2OutlineExtractor.reset(self)
803
804	def endPath(self):
805		if self.sawMoveTo:
806			self.pen.endPath()
807		self.sawMoveTo = 0
808
809	def popallWidth(self, evenOdd=0):
810		return self.popall()
811
812	def exch(self):
813		stack = self.operandStack
814		stack[-1], stack[-2] = stack[-2], stack[-1]
815
816	#
817	# path constructors
818	#
819	def op_rmoveto(self, index):
820		if self.flexing:
821			return
822		self.endPath()
823		self.rMoveTo(self.popall())
824	def op_hmoveto(self, index):
825		if self.flexing:
826			# We must add a parameter to the stack if we are flexing
827			self.push(0)
828			return
829		self.endPath()
830		self.rMoveTo((self.popall()[0], 0))
831	def op_vmoveto(self, index):
832		if self.flexing:
833			# We must add a parameter to the stack if we are flexing
834			self.push(0)
835			self.exch()
836			return
837		self.endPath()
838		self.rMoveTo((0, self.popall()[0]))
839	def op_closepath(self, index):
840		self.closePath()
841	def op_setcurrentpoint(self, index):
842		args = self.popall()
843		x, y = args
844		self.currentPoint = x, y
845
846	def op_endchar(self, index):
847		self.endPath()
848
849	def op_hsbw(self, index):
850		sbx, wx = self.popall()
851		self.width = wx
852		self.sbx = sbx
853		self.currentPoint = sbx, self.currentPoint[1]
854	def op_sbw(self, index):
855		self.popall()  # XXX
856
857	#
858	def op_callsubr(self, index):
859		subrIndex = self.pop()
860		subr = self.subrs[subrIndex]
861		self.execute(subr)
862	def op_callothersubr(self, index):
863		subrIndex = self.pop()
864		nArgs = self.pop()
865		#print nArgs, subrIndex, "callothersubr"
866		if subrIndex == 0 and nArgs == 3:
867			self.doFlex()
868			self.flexing = 0
869		elif subrIndex == 1 and nArgs == 0:
870			self.flexing = 1
871		# ignore...
872	def op_pop(self, index):
873		pass  # ignore...
874
875	def doFlex(self):
876		finaly = self.pop()
877		finalx = self.pop()
878		self.pop()	# flex height is unused
879
880		p3y = self.pop()
881		p3x = self.pop()
882		bcp4y = self.pop()
883		bcp4x = self.pop()
884		bcp3y = self.pop()
885		bcp3x = self.pop()
886		p2y = self.pop()
887		p2x = self.pop()
888		bcp2y = self.pop()
889		bcp2x = self.pop()
890		bcp1y = self.pop()
891		bcp1x = self.pop()
892		rpy = self.pop()
893		rpx = self.pop()
894
895		# call rrcurveto
896		self.push(bcp1x+rpx)
897		self.push(bcp1y+rpy)
898		self.push(bcp2x)
899		self.push(bcp2y)
900		self.push(p2x)
901		self.push(p2y)
902		self.op_rrcurveto(None)
903
904		# call rrcurveto
905		self.push(bcp3x)
906		self.push(bcp3y)
907		self.push(bcp4x)
908		self.push(bcp4y)
909		self.push(p3x)
910		self.push(p3y)
911		self.op_rrcurveto(None)
912
913		# Push back final coords so subr 0 can find them
914		self.push(finalx)
915		self.push(finaly)
916
917	def op_dotsection(self, index):
918		self.popall()  # XXX
919	def op_hstem3(self, index):
920		self.popall()  # XXX
921	def op_seac(self, index):
922		"asb adx ady bchar achar seac"
923		from fontTools.encodings.StandardEncoding import StandardEncoding
924		asb, adx, ady, bchar, achar = self.popall()
925		baseGlyph = StandardEncoding[bchar]
926		self.pen.addComponent(baseGlyph, (1, 0, 0, 1, 0, 0))
927		accentGlyph = StandardEncoding[achar]
928		adx = adx + self.sbx - asb  # seac weirdness
929		self.pen.addComponent(accentGlyph, (1, 0, 0, 1, adx, ady))
930	def op_vstem3(self, index):
931		self.popall()  # XXX
932
933class T2CharString(object):
934
935	operandEncoding = t2OperandEncoding
936	operators, opcodes = buildOperatorDict(t2Operators)
937	decompilerClass = SimpleT2Decompiler
938	outlineExtractor = T2OutlineExtractor
939
940	def __init__(self, bytecode=None, program=None, private=None, globalSubrs=None):
941		if program is None:
942			program = []
943		self.bytecode = bytecode
944		self.program = program
945		self.private = private
946		self.globalSubrs = globalSubrs if globalSubrs is not None else []
947		self._cur_vsindex = None
948
949	def getNumRegions(self, vsindex=None):
950		pd = self.private
951		assert(pd is not None)
952		if vsindex is not None:
953			self._cur_vsindex = vsindex
954		elif self._cur_vsindex is None:
955			self._cur_vsindex = pd.vsindex if hasattr(pd, 'vsindex') else 0
956		return pd.getNumRegions(self._cur_vsindex)
957
958	def __repr__(self):
959		if self.bytecode is None:
960			return "<%s (source) at %x>" % (self.__class__.__name__, id(self))
961		else:
962			return "<%s (bytecode) at %x>" % (self.__class__.__name__, id(self))
963
964	def getIntEncoder(self):
965		return encodeIntT2
966
967	def getFixedEncoder(self):
968		return encodeFixed
969
970	def decompile(self):
971		if not self.needsDecompilation():
972			return
973		subrs = getattr(self.private, "Subrs", [])
974		decompiler = self.decompilerClass(subrs, self.globalSubrs, self.private)
975		decompiler.execute(self)
976
977	def draw(self, pen):
978		subrs = getattr(self.private, "Subrs", [])
979		extractor = self.outlineExtractor(pen, subrs, self.globalSubrs,
980				self.private.nominalWidthX, self.private.defaultWidthX,
981				self.private)
982		extractor.execute(self)
983		self.width = extractor.width
984
985	def calcBounds(self, glyphSet):
986		boundsPen = BoundsPen(glyphSet)
987		self.draw(boundsPen)
988		return boundsPen.bounds
989
990	def compile(self, isCFF2=False):
991		if self.bytecode is not None:
992			return
993		opcodes = self.opcodes
994		program = self.program
995
996		if isCFF2:
997			# If present, remove return and endchar operators.
998			if program and program[-1] in ("return", "endchar"):
999				program = program[:-1]
1000		elif program and not isinstance(program[-1], str):
1001			raise CharStringCompileError(
1002				"T2CharString or Subr has items on the stack after last operator."
1003			)
1004
1005		bytecode = []
1006		encodeInt = self.getIntEncoder()
1007		encodeFixed = self.getFixedEncoder()
1008		i = 0
1009		end = len(program)
1010		while i < end:
1011			token = program[i]
1012			i = i + 1
1013			if isinstance(token, str):
1014				try:
1015					bytecode.extend(bytechr(b) for b in opcodes[token])
1016				except KeyError:
1017					raise CharStringCompileError("illegal operator: %s" % token)
1018				if token in ('hintmask', 'cntrmask'):
1019					bytecode.append(program[i])  # hint mask
1020					i = i + 1
1021			elif isinstance(token, int):
1022				bytecode.append(encodeInt(token))
1023			elif isinstance(token, float):
1024				bytecode.append(encodeFixed(token))
1025			else:
1026				assert 0, "unsupported type: %s" % type(token)
1027		try:
1028			bytecode = bytesjoin(bytecode)
1029		except TypeError:
1030			log.error(bytecode)
1031			raise
1032		self.setBytecode(bytecode)
1033
1034	def needsDecompilation(self):
1035		return self.bytecode is not None
1036
1037	def setProgram(self, program):
1038		self.program = program
1039		self.bytecode = None
1040
1041	def setBytecode(self, bytecode):
1042		self.bytecode = bytecode
1043		self.program = None
1044
1045	def getToken(self, index,
1046			len=len, byteord=byteord, isinstance=isinstance):
1047		if self.bytecode is not None:
1048			if index >= len(self.bytecode):
1049				return None, 0, 0
1050			b0 = byteord(self.bytecode[index])
1051			index = index + 1
1052			handler = self.operandEncoding[b0]
1053			token, index = handler(self, b0, self.bytecode, index)
1054		else:
1055			if index >= len(self.program):
1056				return None, 0, 0
1057			token = self.program[index]
1058			index = index + 1
1059		isOperator = isinstance(token, str)
1060		return token, isOperator, index
1061
1062	def getBytes(self, index, nBytes):
1063		if self.bytecode is not None:
1064			newIndex = index + nBytes
1065			bytes = self.bytecode[index:newIndex]
1066			index = newIndex
1067		else:
1068			bytes = self.program[index]
1069			index = index + 1
1070		assert len(bytes) == nBytes
1071		return bytes, index
1072
1073	def handle_operator(self, operator):
1074		return operator
1075
1076	def toXML(self, xmlWriter, ttFont=None):
1077		from fontTools.misc.textTools import num2binary
1078		if self.bytecode is not None:
1079			xmlWriter.dumphex(self.bytecode)
1080		else:
1081			index = 0
1082			args = []
1083			while True:
1084				token, isOperator, index = self.getToken(index)
1085				if token is None:
1086					break
1087				if isOperator:
1088					if token in ('hintmask', 'cntrmask'):
1089						hintMask, isOperator, index = self.getToken(index)
1090						bits = []
1091						for byte in hintMask:
1092							bits.append(num2binary(byteord(byte), 8))
1093						hintMask = strjoin(bits)
1094						line = ' '.join(args + [token, hintMask])
1095					else:
1096						line = ' '.join(args + [token])
1097					xmlWriter.write(line)
1098					xmlWriter.newline()
1099					args = []
1100				else:
1101					if isinstance(token, float):
1102						token = floatToFixedToStr(token, precisionBits=16)
1103					else:
1104						token = str(token)
1105					args.append(token)
1106			if args:
1107				# NOTE: only CFF2 charstrings/subrs can have numeric arguments on
1108				# the stack after the last operator. Compiling this would fail if
1109				# this is part of CFF 1.0 table.
1110				line = ' '.join(args)
1111				xmlWriter.write(line)
1112
1113	def fromXML(self, name, attrs, content):
1114		from fontTools.misc.textTools import binary2num, readHex
1115		if attrs.get("raw"):
1116			self.setBytecode(readHex(content))
1117			return
1118		content = strjoin(content)
1119		content = content.split()
1120		program = []
1121		end = len(content)
1122		i = 0
1123		while i < end:
1124			token = content[i]
1125			i = i + 1
1126			try:
1127				token = int(token)
1128			except ValueError:
1129				try:
1130					token = strToFixedToFloat(token, precisionBits=16)
1131				except ValueError:
1132					program.append(token)
1133					if token in ('hintmask', 'cntrmask'):
1134						mask = content[i]
1135						maskBytes = b""
1136						for j in range(0, len(mask), 8):
1137							maskBytes = maskBytes + bytechr(binary2num(mask[j:j+8]))
1138						program.append(maskBytes)
1139						i = i + 1
1140				else:
1141					program.append(token)
1142			else:
1143				program.append(token)
1144		self.setProgram(program)
1145
1146class T1CharString(T2CharString):
1147
1148	operandEncoding = t1OperandEncoding
1149	operators, opcodes = buildOperatorDict(t1Operators)
1150
1151	def __init__(self, bytecode=None, program=None, subrs=None):
1152		super().__init__(bytecode, program)
1153		self.subrs = subrs
1154
1155	def getIntEncoder(self):
1156		return encodeIntT1
1157
1158	def getFixedEncoder(self):
1159		def encodeFixed(value):
1160			raise TypeError("Type 1 charstrings don't support floating point operands")
1161
1162	def decompile(self):
1163		if self.bytecode is None:
1164			return
1165		program = []
1166		index = 0
1167		while True:
1168			token, isOperator, index = self.getToken(index)
1169			if token is None:
1170				break
1171			program.append(token)
1172		self.setProgram(program)
1173
1174	def draw(self, pen):
1175		extractor = T1OutlineExtractor(pen, self.subrs)
1176		extractor.execute(self)
1177		self.width = extractor.width
1178
1179class DictDecompiler(object):
1180
1181	operandEncoding = cffDictOperandEncoding
1182
1183	def __init__(self, strings, parent=None):
1184		self.stack = []
1185		self.strings = strings
1186		self.dict = {}
1187		self.parent = parent
1188
1189	def getDict(self):
1190		assert len(self.stack) == 0, "non-empty stack"
1191		return self.dict
1192
1193	def decompile(self, data):
1194		index = 0
1195		lenData = len(data)
1196		push = self.stack.append
1197		while index < lenData:
1198			b0 = byteord(data[index])
1199			index = index + 1
1200			handler = self.operandEncoding[b0]
1201			value, index = handler(self, b0, data, index)
1202			if value is not None:
1203				push(value)
1204	def pop(self):
1205		value = self.stack[-1]
1206		del self.stack[-1]
1207		return value
1208
1209	def popall(self):
1210		args = self.stack[:]
1211		del self.stack[:]
1212		return args
1213
1214	def handle_operator(self, operator):
1215		operator, argType = operator
1216		if isinstance(argType, tuple):
1217			value = ()
1218			for i in range(len(argType)-1, -1, -1):
1219				arg = argType[i]
1220				arghandler = getattr(self, "arg_" + arg)
1221				value = (arghandler(operator),) + value
1222		else:
1223			arghandler = getattr(self, "arg_" + argType)
1224			value = arghandler(operator)
1225		if operator == "blend":
1226			self.stack.extend(value)
1227		else:
1228			self.dict[operator] = value
1229
1230	def arg_number(self, name):
1231		if isinstance(self.stack[0], list):
1232			out = self.arg_blend_number(self.stack)
1233		else:
1234			out = self.pop()
1235		return out
1236
1237	def arg_blend_number(self, name):
1238		out = []
1239		blendArgs = self.pop()
1240		numMasters = len(blendArgs)
1241		out.append(blendArgs)
1242		out.append("blend")
1243		dummy = self.popall()
1244		return blendArgs
1245
1246	def arg_SID(self, name):
1247		return self.strings[self.pop()]
1248	def arg_array(self, name):
1249		return self.popall()
1250	def arg_blendList(self, name):
1251		"""
1252		There may be non-blend args at the top of the stack. We first calculate
1253		where the blend args start in the stack. These are the last
1254		numMasters*numBlends) +1 args.
1255		The blend args starts with numMasters relative coordinate values, the  BlueValues in the list from the default master font. This is followed by
1256		numBlends list of values. Each of  value in one of these lists is the
1257		Variable Font delta for the matching region.
1258
1259		We re-arrange this to be a list of numMaster entries. Each entry starts with the corresponding default font relative value, and is followed by
1260		the delta values. We then convert the default values, the first item in each entry, to an absolute value.
1261		"""
1262		vsindex = self.dict.get('vsindex', 0)
1263		numMasters = self.parent.getNumRegions(vsindex) + 1 # only a PrivateDict has blended ops.
1264		numBlends = self.pop()
1265		args = self.popall()
1266		numArgs = len(args)
1267		# The spec says that there should be no non-blended Blue Values,.
1268		assert(numArgs == numMasters * numBlends)
1269		value = [None]*numBlends
1270		numDeltas = numMasters-1
1271		i = 0
1272		prevVal = 0
1273		while i < numBlends:
1274			newVal = args[i] + prevVal
1275			prevVal = newVal
1276			masterOffset = numBlends + (i* numDeltas)
1277			blendList = [newVal] + args[masterOffset:masterOffset+numDeltas]
1278			value[i] = blendList
1279			i += 1
1280		return value
1281
1282	def arg_delta(self, name):
1283		valueList = self.popall()
1284		out = []
1285		if valueList and isinstance(valueList[0], list):
1286			# arg_blendList() has already converted these to absolute values.
1287			out = valueList
1288		else:
1289			current = 0
1290			for v in valueList:
1291				current = current + v
1292				out.append(current)
1293		return out
1294
1295
1296def calcSubrBias(subrs):
1297	nSubrs = len(subrs)
1298	if nSubrs < 1240:
1299		bias = 107
1300	elif nSubrs < 33900:
1301		bias = 1131
1302	else:
1303		bias = 32768
1304	return bias
1305