"""
BezTools.py v 1.11 Jan 23 2008

Utilities for converting between T2 charstrings and the bez data format.
Used by AC and focus/CheckOutlines.
"""
__copyright__ =  """
Copyright (c) 2006, 2008 Adobe Systems Incorporated

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

import sys
import re
import time
import os
from GSpsCharStrings import T2OutlineExtractor, SimpleT2Decompiler
#from fontTools.pens.basePen import BasePen
from types import FloatType, StringType, LongType
debug = 0
def debugMsg(*args):
	if debug:
		print args
kStackLimit = 46
kStemLimit = 96

class SEACError(KeyError):
	pass

def hintOn( i, hintMaskBytes):
	# used to add the active hints to the bez string, when a  T2 hintmask operator is encountered.
	byteIndex = i/8
	byteValue =  ord(hintMaskBytes[byteIndex])
	offset = 7 -  (i %8)
	return ((2**offset) & byteValue) > 0


class T2ToBezExtractor(T2OutlineExtractor):
	# The T2OutlineExtractor class calls a class method as the handler for each T2 operator.
	# I use this to convert the T2 operands and arguments to bez operators.
	# Note: flex is converted to regulsr rcurveto's.
	# cntrmasks just map to hint replacement blocks with the specified stems.
	def __init__(self, localSubrs, globalSubrs, nominalWidthX, defaultWidthX, removeHints = 0):
		T2OutlineExtractor.__init__(self, None, localSubrs, globalSubrs, nominalWidthX, defaultWidthX)
		self.vhints = []
		self.hhints = []
		self.bezProgram = []
		self.removeHints = removeHints
		self.firstMarkingOpSeen = 0
		self.closePathSeen = 0
		self.subrLevel = 0

	def execute(self, charString):
		self.subrLevel += 1
		SimpleT2Decompiler.execute(self,charString)
		self.subrLevel -= 1
		if (not self.closePathSeen) and (self.subrLevel == 0):
			 self.closePath()
			
	def rMoveTo(self, point):
		self._nextPoint(point)
		if not self.firstMarkingOpSeen :
			self.firstMarkingOpSeen = 1
			self.bezProgram.append("sc\n")
		debugMsg("moveto", point, "curpos", self.currentPoint)
		dx = point[0]
		dy = point[1]
		if dy == 0:
			if dx != 0:
				self.bezProgram.append("%s hmt\n" % dx)
			else:
				self.bezProgram.append("0 0 rmt\n" )

		elif  dx == 0:
			self.bezProgram.append("%s vmt\n" % dy)
		else:
			self.bezProgram.append("%s  %s rmt\n" % (point[0], point[1]))
		self.sawMoveTo = 1

	def rLineTo(self, point):
		self._nextPoint(point)
		if not self.firstMarkingOpSeen :
			self.firstMarkingOpSeen = 1
			self.bezProgram.append("sc\n")
			self.bezProgram.append("0 0 mt\n")
		debugMsg("lineto", point, "curpos", self.currentPoint)
		if not self.sawMoveTo:
			self.rMoveTo((0, 0))
		dx = point[0]
		dy = point[1]
		if dy == 0:
			if dx != 0:
				self.bezProgram.append("%s hdt\n" % dx)
			else:
				self.bezProgram.append("0 0 rdt\n")
			
		elif  dx == 0:
			self.bezProgram.append("%s vdt\n" % dy)
		else :
			self.bezProgram.append("%s  %s rdt\n" % (dx,dy))

	def rCurveTo(self, pt1, pt2, pt3):
		self._nextPoint(pt1)
		self._nextPoint(pt2)
		self._nextPoint(pt3)
		if not self.firstMarkingOpSeen :
			self.firstMarkingOpSeen = 1
			self.bezProgram.append("sc\n")
			self.bezProgram.append("0 0 mt\n")
		debugMsg("curveto", pt1, pt2, pt3, "curpos", self.currentPoint)
		if not self.sawMoveTo:
			self.rMoveTo((0, 0))
		if pt1[0] == 0 and pt3[1] == 0:
			self.bezProgram.append("%s %s %s %s vhct\n" %  (pt1[1],  pt2[0], pt2[1],  pt3[0]) )
		elif   pt1[1] == 0 and pt3[0] == 0:
			self.bezProgram.append("%s %s %s %s hvct\n" %  (pt1[0],  pt2[0],  pt2[1],  pt3[1]) )
		else:
			self.bezProgram.append("%s %s %s %s %s %s rct\n" %  (pt1[0],  pt1[1],  pt2[0],  pt2[1],  pt3[0],  pt3[1]) )

	def op_endchar(self, index):
		self.endPath()
		args = self.popallWidth()
		if args: #  It is a 'seac' composite character. Don't process
			raise SEACError

	def endPath(self):
		# In T2 there are no open paths, so always do a closePath when
		# finishing a sub path.
		if self.sawMoveTo:
			debugMsg("endPath")
			self.bezProgram.append("cp\n")
		self.sawMoveTo = 0
	def closePath(self):
		self.closePathSeen = 1
		debugMsg("closePath")
		if self.bezProgram and self.bezProgram[-1] != "cp\n":
			self.bezProgram.append("cp\n")
		self.bezProgram.append("ed\n")

	def updateHints(self, args,  hintList, bezCommand, writeHints = 1):
		self.countHints(args)

		# first hint value is absolute hint coordinate, second is hint width
		if self.removeHints:
			writeHints = 0

		if not writeHints:
			return

		lastval = args[0]
		if type(lastval) == LongType:
			lastval = float(lastval)/0x10000
			arg = str(lastval) + " 100 div"
		else:
			arg = str(lastval)
		hintList.append(arg)
		self.bezProgram.append(arg)

		for i in range(len(args))[1:]:
			val = args[i]
			if type(val) == LongType:
				val = float(val)/0x10000
			newVal =  lastval + val
			lastval = newVal

			if i % 2:
				if (type(val) == FloatType):
					if (int(val) != val):
						arg = str(int(val*100)) + " 100 div"
					else:
						arg = str(int(val))
				else:
					arg = str(val)
				hintList.append( arg )
				self.bezProgram.append(arg)
				self.bezProgram.append(bezCommand + "\n")
			else:
				if (type(newVal) == FloatType):
					if (int(newVal) != newVal):
						arg = str(int(newVal*100)) + " 100 div"
					else:
						arg = str(int(newVal))
				else:
					arg = str(newVal)
				hintList.append( arg )
				self.bezProgram.append(arg)


	def op_hstem(self, index):
		args = self.popallWidth()
		self.hhints = []
		self.updateHints(args, self.hhints, "rb")
		debugMsg("hstem", self.hhints)

	def op_vstem(self, index):
		args = self.popallWidth()
		self.vhints = []
		self.updateHints(args, self.vhints, "ry")
		debugMsg("vstem", self.vhints)

	def op_hstemhm(self, index):
		args = self.popallWidth()
		self.hhints = []
		self.updateHints(args, self.hhints, "rb")
		debugMsg("stemhm", self.hhints, args)

	def op_vstemhm(self, index):
		args = self.popallWidth()
		self.vhints = []
		self.updateHints(args, self.vhints, "ry")
		debugMsg("vstemhm", self.vhints, args)

	def getCurHints(self, hintMaskBytes):
		curhhints = []
		curvhints = []
		numhhints = len(self.hhints)

		for i in range(numhhints/2):
			if hintOn(i, hintMaskBytes):
				curhhints.extend(self.hhints[2*i:2*i+2])	
		numvhints = len(self.vhints)
		for i in range(numvhints/2):
			if hintOn(i + numhhints/2, hintMaskBytes):
				curvhints.extend(self.vhints[2*i:2*i+2])
		return curhhints, curvhints

	def doMask(self, index, bezCommand):
		args = []
		if not self.hintMaskBytes:
			args = self.popallWidth()
			if args:
				self.vhints = []
				self.updateHints(args, self.vhints, "ry")
			self.hintMaskBytes = (self.hintCount + 7) / 8

		self.hintMaskString, index = self.callingStack[-1].getBytes(index, self.hintMaskBytes)

		if not  self.removeHints:
			curhhints, curvhints = self.getCurHints( self.hintMaskString)
			strout = ""
			mask = [strout + hex(ord(ch)) for ch in self.hintMaskString]
			debugMsg(bezCommand, mask, curhhints, curvhints, args)
	
			self.bezProgram.append("beginsubr snc\n")
			i = 0
			for hint in curhhints:
				self.bezProgram.append(str(hint))
				if i %2:
					self.bezProgram.append("rb\n")
				i +=1 
			i = 0
			for hint in curvhints:
				self.bezProgram.append(str(hint))
				if i %2:
					self.bezProgram.append("ry\n")
				i +=1 
			self.bezProgram.extend(["endsubr enc\n", "newcolors\n"])
		return self.hintMaskString, index

	def op_hintmask(self, index):
		hintMaskString, index =  self.doMask(index, "hintmask")
		return hintMaskString, index

	def op_cntrmask(self, index):
		hintMaskString, index =   self.doMask(index, "cntrmask")
		return hintMaskString, index


	def countHints(self, args):
		self.hintCount = self.hintCount + len(args) / 2

def convertT2GlyphToBez(t2CharString, removeHints = 0):
	# wrapper for T2ToBezExtractor which applies it to the supplied T2 charstring
	bezString = ""
	subrs = getattr(t2CharString.private, "Subrs", [])
	extractor = T2ToBezExtractor(subrs, t2CharString.globalSubrs,
				t2CharString.private.nominalWidthX, t2CharString.private.defaultWidthX, removeHints)
	extractor.execute(t2CharString)
	if extractor.gotWidth:
		t2Wdth = extractor.width - t2CharString.private.nominalWidthX
	else:
		t2Wdth = None
	return "".join(extractor.bezProgram), extractor.hintCount > 0, t2Wdth
	
class HintMask:
	# class used to collect hints for the current hint mask when converting bez to T2.
	def __init__(self, listPos):
		self.listPos = listPos # The index into the t2list is kept so we can quickly find them later.
		self.hList = [] # These contain the actual hint values.
		self.vList = []

	def maskByte(self, hHints, vHints):
		# return hintmask bytes for known hints.
		numHHints = len(hHints)
		numVHints = len(vHints)
		maskVal = 0
		byteIndex = 0
		self.byteLength = byteLength = int((7 + numHHints + numVHints)/8)
		mask = ""
		self.hList.sort()
		for hint in self.hList:
			try:
				i = hHints.index(hint)
			except ValueError:
				continue	# we get here if some hints have been dropped because of the stack limit
			newbyteIndex = (i/8)
			if newbyteIndex != byteIndex:
				mask += chr(maskVal)
				byteIndex +=1
				while byteIndex < newbyteIndex:
					mask += "\0"
					byteIndex +=1
				maskVal = 0
			maskVal += 2**(7 - (i %8))

		self.vList.sort()
		for hint in self.vList:
			try:
				i = numHHints + vHints.index(hint)
			except ValueError:
				continue	# we get here if some hints have been dropped because of the stack limit
			newbyteIndex = (i/8)
			if newbyteIndex != byteIndex:
				mask += chr(maskVal)
				byteIndex +=1
				while byteIndex < newbyteIndex:
					mask += "\0"
					byteIndex +=1
				maskVal = 0
			maskVal += 2**(7 - (i %8))

		if maskVal:
			mask += chr(maskVal)

		if len(mask) < byteLength:
			mask += "\0"*(byteLength - len(mask))
		self.mask = mask
		return mask

def makeHintList(hints, needHintMasks, isH):
	# Add the list of T2 tokens that make up the initial hint operators
	hintList = []
	lastPos = 0
	# In bez terms, the first coordinate in each pair is absolute, second is relative.
	# In T2, each term is relative to the previous one.
	for hint in hints:
		if not hint:
			continue
		pos1 = hint[0]
		pos = pos1 - lastPos
		if (type(pos) == FloatType) and (int(pos) == pos):
			pos = int(pos)
		hintList.append(pos)
		pos2 = hint[1]
		if (type(pos2) == FloatType) and (int(pos2) == pos):
			pos2 = int(pos2)
		lastPos = pos1 + pos2
		hintList.append(pos2)

	if  needHintMasks:
		if isH:
			op = "hstemhm"
			hintList.append(op)
		# never need to append vstemhm: if we are using it, it is followed
		# by a mask command and vstemhm is inferred.
	else:
		if isH:
			op = "hstem"
		else: 
			op = "vstem"
		hintList.append(op)

	return hintList


bezToT2 = {
		"rmt" : 'rmoveto',
		 "hmt" : 'hmoveto',
		"vmt" : 'vmoveto',
		"rdt" : 'rlineto', 
		"hdt" : "hlineto", 
		"vdt" : "vlineto", 
		"rct" : 'rrcurveto', 
		"rcv" : 'rrcurveto',  # Morisawa's alternate name for 'rct'.
		"vhct": 'vhcurveto',
		"hvct": 'hvcurveto', 
		"cp" : "",
		"ed" : 'endchar'
		}


def optimizeT2Program(t2List):
	# Assumes T2 operands are in a list with one entry per operand, and each entry is a list of [argList, opToken].
	# Matches logic in tx, and Adobe low level library.
	# Note that I am expecting only rlineto,vlinteto,hlinto, vhcurveto,hvcurveto,rcurveto froom AC.
	# The other opimtized operators, specifically the line-curve combinations are not supported here.
	newT2List = []
	arglist = []
	kNoOp = "noop"
	pendingOp = kNoOp
	sequenceOp = kNoOp
	for entry in t2List:
		op =  entry[1]
		args = entry[0]
		if 0: #(len(args) >= 2):
			if (args[-1] == 17) and (args[-2] == -9):
				print "DEBUG", op, args
				import pdb
				pdb.set_trace()

		if op == "vlineto":
			dy = args[-1]
			if  (pendingOp in ["vlineto", "hlineto"]) and (sequenceOp ==  "hlineto"):
				arglist.append(dy)
				sequenceOp = "vlineto"
				if len(arglist) >= kStackLimit:
					newT2List.append([arglist[:-1], pendingOp])
					arglist = [dy]
					pendingOp = "vlineto"
				
			else:
				if  pendingOp != kNoOp:
					newT2List.append([arglist, pendingOp])
				arglist = [dy]
				pendingOp = sequenceOp = "vlineto"

		elif op ==  "hlineto":
			dx = args[-1]
			if  (pendingOp in ["vlineto", "hlineto"]) and (sequenceOp ==  "vlineto"):
				arglist.append(dx)
				sequenceOp = "hlineto"
				if len(arglist) >= kStackLimit:
					newT2List.append([arglist[:-1], pendingOp])
					arglist = [dx]
					pendingOp = "hlineto"
			else:
				if  pendingOp != kNoOp:
					newT2List.append([arglist, pendingOp])
				arglist = [dx]
				pendingOp = sequenceOp = "hlineto"

		elif op == "rlineto":
			dx = args[-2]
			dy = args[-1]
			if dx == 0:
				if  (pendingOp in ["vlineto", "hlineto"]) and (sequenceOp ==  "hlineto"):
					arglist.append(dy)
					sequenceOp =  "vlineto"
					if len(arglist) >= kStackLimit:
						newT2List.append([arglist[:-1], pendingOp])
						arglist = [dy]
						pendingOp = "vlineto"
				else:
					if  pendingOp != kNoOp:
						newT2List.append([arglist, pendingOp])
					arglist = [dy]
					pendingOp = sequenceOp = "vlineto"

			elif dy == 0:
				if  (pendingOp in ["vlineto", "hlineto"]) and (sequenceOp ==  "vlineto"):
					arglist.append(dx)
					sequenceOp =  "hlineto"
					if len(arglist) >= kStackLimit:
						newT2List.append([arglist[:-1], pendingOp])
						arglist = [dx]
						pendingOp = "hlineto"
				else:
					if  pendingOp != kNoOp:
						newT2List.append([arglist, pendingOp])
					arglist = [dx]
					pendingOp = sequenceOp = "hlineto"

			elif pendingOp == "rrcurveto":
				arglist.extend([dx,dy])

				if len(arglist) >= kStackLimit:
					newT2List.append([arglist[:-2], pendingOp])
					arglist = [dx, dy]
					pendingOp = sequenceOp = "rlineto"
				else:
					newT2List.append([arglist, "rcurveline"])
					arglist = []
					pendingOp = sequenceOp = kNoOp
					

			elif  (pendingOp == op) and (sequenceOp == op):
				arglist.extend([dx,dy])
				if len(arglist) >= kStackLimit:
					newT2List.append([arglist[:-2], pendingOp])
					arglist = [dx, dy]

			else:
				if pendingOp != kNoOp:
					newT2List.append([arglist, pendingOp])
				arglist = [dx,dy]
				pendingOp = sequenceOp = op


		elif op == "vhcurveto":
			if  (pendingOp in ["vhcurveto", "hvcurveto"]) and (sequenceOp ==  "hvcurveto"):
				sequenceOp = "vhcurveto"
				arglist.extend(args)
				if len(arglist) >= kStackLimit:
					newT2List.append([arglist[:-len(args)], pendingOp])
					arglist = args
					pendingOp = sequenceOp = op
			else:
				if  pendingOp != kNoOp:
					newT2List.append([arglist, pendingOp])
				arglist = args
				pendingOp = sequenceOp = "vhcurveto"
			if len(args) == 5:
				newT2List.append([arglist, pendingOp])
				arglist = []
				pendingOp = sequenceOp = kNoOp


		elif op == "hvcurveto":
			if  (pendingOp in ["vhcurveto", "hvcurveto"]) and (sequenceOp ==  "vhcurveto"):
				sequenceOp = "hvcurveto"
				arglist.extend(args)
				if len(arglist) >= kStackLimit:
					newT2List.append([arglist[:-len(args)], pendingOp])
					arglist = args
					pendingOp = sequenceOp = op
			else:
				if  pendingOp != kNoOp:
					newT2List.append([arglist, pendingOp])
				arglist = args
				pendingOp = sequenceOp = "hvcurveto"
			if len(args) == 5:
				newT2List.append([arglist, pendingOp])
				arglist = []
				pendingOp = sequenceOp = kNoOp


		elif op == "rrcurveto":
			dx1 = args[0]
			dy1 = args[1]
			dx2 = args[2]
			dy2 = args[3]
			dx3 = args[4]
			dy3 = args[5]

			if dx1 == 0:
				if dy3 == 0: # - dy1 dx2 dy2 dx3 - vhcurveto
					if  (pendingOp in ["vhcurveto", "hvcurveto"]) and (sequenceOp ==  "hvcurveto"):
						arglist.extend( [dy1, dx2, dy2, dx3])
						sequenceOp = "vhcurveto"
						if len(arglist) >= kStackLimit:
							newT2List.append([arglist[:-4], pendingOp])
							arglist = [dy1, dx2, dy2, dx3]
							pendingOp = "vhcurveto"
					else:
						if  pendingOp != kNoOp:
							newT2List.append([arglist, pendingOp])
						arglist = [dy1, dx2, dy2, dx3]
						pendingOp = sequenceOp = "vhcurveto"

				elif dx3 == 0: # - dy1 dx2 dy2 - dy3 vvcurveto
					if pendingOp not in ["vvcurveto", kNoOp]:
						newT2List.append([arglist, pendingOp])
						arglist = []
					arglist.extend([dy1, dx2, dy2, dy3 ])
					sequenceOp = "vvcurveto"
					if len(arglist) >= kStackLimit:
						newT2List.append([arglist[:-4], pendingOp])
						arglist = [dy1, dx2, dy2, dy3]
						pendingOp =  sequenceOp
					else:
						pendingOp = sequenceOp

				else:  # - dy1 dx2 dy2 dx3 dy3 vhcurveto (odd number of args, can't concatenate any more ops.)
					if  (pendingOp in ["vhcurveto", "hvcurveto"]) and (sequenceOp ==  "hvcurveto"):
						arglist.extend([dy1, dx2, dy2, dx3, dy3])
						if len(arglist) >= kStackLimit:
							newT2List.append([arglist[:-5], pendingOp])
							arglist = [dy1, dx2, dy2, dx3, dy3]
							pendingOp = "vhcurveto"
					else:
						if  pendingOp != kNoOp:
							newT2List.append([arglist, pendingOp])
						arglist = [dy1, dx2, dy2, dx3, dy3]
						pendingOp = "vhcurveto"
					newT2List.append([arglist, pendingOp])
					arglist = []
					pendingOp = sequenceOp = kNoOp

			elif dy1 == 0:
				if dx3 == 0: # dx1 - dx2 dy2 - dy3 hvcurveto
					if  (pendingOp in ["vhcurveto", "hvcurveto"]) and (sequenceOp ==  "vhcurveto"):
						arglist.extend([dx1, dx2, dy2, dy3])
						sequenceOp = "hvcurveto"
						if len(arglist) >= kStackLimit:
							newT2List.append([arglist[:-4], pendingOp])
							arglist = [dx1, dx2, dy2, dy3]
							pendingOp = "hvcurveto"
					else:
						if  pendingOp != kNoOp:
							newT2List.append([arglist, pendingOp])
						arglist = [dx1, dx2, dy2, dy3]
						pendingOp = sequenceOp = "hvcurveto"

				elif dy3 == 0: # dx1 - dx2 dy2 dx3 - hhcurveto
					if pendingOp not in ["hhcurveto", kNoOp]:
						newT2List.append([arglist, pendingOp])
						arglist = []
					arglist.extend([dx1, dx2, dy2, dx3 ])
					sequenceOp = "hhcurveto"
					if len(arglist) >= kStackLimit:
						newT2List.append([arglist[:-4], pendingOp]) # XXX Problem. Was vvcurveto
						arglist = [dx1, dx2, dy2, dx3]
						pendingOp = sequenceOp
					else:
						pendingOp = sequenceOp

				else:  # dx1 - dx2 dy2 dy3 dx3 hvcurveto (odd number of args, can't concatenate any more ops.)
					if  (pendingOp in ["vhcurveto", "hvcurveto"]) and (sequenceOp ==  "vhcurveto"):
						arglist.extend( [dx1, dx2, dy2, dy3, dx3])
						if len(arglist) >= kStackLimit:
							newT2List.append([arglist[:-5], pendingOp])
							arglist =  [dx1, dx2, dy2, dy3, dx3]
							pendingOp = "hvcurveto"
					else:
						if  pendingOp != kNoOp:
							newT2List.append([arglist, pendingOp])
						arglist = [dx1, dx2, dy2, dy3, dx3]
						pendingOp = "hvcurveto"
					newT2List.append([arglist, pendingOp])
					arglist = []
					pendingOp = sequenceOp = kNoOp

			elif dx3 == 0: #  dx1 dy1 dx2 dy2 - dy3 vvcurveto (odd args)
					if pendingOp != kNoOp:
						newT2List.append([arglist, pendingOp])
						arglist = []
					arglist = [dx1, dy1, dx2, dy2, dy3]
					pendingOp = "vvcurveto"
					newT2List.append([arglist, pendingOp])
					arglist = []
					pendingOp = sequenceOp = kNoOp
						
			elif dy3 == 0: #  dx1 dy1 dx2 dy2 dx3 - hhcurveto (odd args)
					if pendingOp != kNoOp:
						newT2List.append([arglist, pendingOp])
						arglist = []
					arglist = [dy1, dx1, dx2, dy2, dx3] # note arg order swap
					pendingOp = "hhcurveto"
					newT2List.append([arglist, pendingOp])
					arglist = []
					pendingOp = sequenceOp = kNoOp

			else:
				if pendingOp == "rlineto":
					arglist.extend(args)
					if len(arglist) >= kStackLimit:
						newT2List.append([arglist[:-len(args)], pendingOp])
						arglist = args
						pendingOp = sequenceOp = op
					else:
						newT2List.append([arglist, "rlinecurve"])
						arglist = []
						pendingOp = sequenceOp = kNoOp

				else:
					if pendingOp not in [kNoOp, "rrcurveto"]:
						newT2List.append([arglist, pendingOp])
						arglist = []
					arglist.extend(args)
					if len(arglist) >= kStackLimit:
						newT2List.append([arglist[:-len(args)], pendingOp])
						arglist = args
					pendingOp = sequenceOp = op

		elif op ==  "flex":
			dx1 = args[0]
			dy1 = args[1]
			dx2 = args[2]
			dy2 = args[3]
			dx3 = args[4]
			dy3 = args[5]
			dx4 = args[6]
			dy4 = args[7]
			dx5 = args[8]
			dy5 = args[9]
			dx6 = args[10]
			dy6 = args[11]
			if pendingOp != kNoOp:
				newT2List.append([arglist, pendingOp])
				arglist = []
			noFlex = 1
			noDY = 1
			if (dy3 == 0 == dy4):
				if (dy1 == dy6 == 0) and (dy2 == -dy5):
						newT2List.append([[dx1, dx2, dy2, dx3, dx4, dx5, dx6], "hflex"]) # the device pixel threshold is always 50 , when coming back from AC.
						noFlex = 0
				else:
					dy = dy1 + dy2 + dy3 + dy4 + dy5 + dy6
					noDY = 0
					if dy == 0:
						newT2List.append([[dx1, dy1, dx2, dy2, dx3, dx4, dx5, dy5, dx6], "hflex1"])
						noFlex = 0

			if noFlex:
				if 0:
					dx = dx1 + dx2 + dx3 + dx4 + dx5 
					dy = dy1 + dy2 + dy3 + dy4 + dy5
	
					if ((dy + dy6) == 0) or ((dx+dx6) == 0):
						if abs(dx) > abs(dy):
							lastArg = dx6
						else:
							lastArg = dy6
	
						newT2List.append([args[:10] + [lastArg], "flex1"]) 
						newLastArg = lastArg
					else:		
						newT2List.append([args, "flex"])
				else:
					newT2List.append([args, "flex"])
				
			arglist = []
			pendingOp = sequenceOp = kNoOp


		else:
			if pendingOp != kNoOp:
				newT2List.append([arglist, pendingOp])
			newT2List.append([args, op])
			arglist = []
			pendingOp = sequenceOp = kNoOp

	if pendingOp != kNoOp:
		newT2List.append([arglist, pendingOp])


	return newT2List

def needsDecryption(bezDataBuffer):
	lenBuf = len(bezDataBuffer)
	if lenBuf > 100:
		lenBuf = 100
	i = 0
	while i < lenBuf:
		ch = bezDataBuffer[i]
		i +=1
		if not (ch.isspace() or ch.isdigit()  or (ch in "ABCDEFabcdef") ):
			return 0
	return 1

LEN_IV  = 4 #/* Length of initial random byte sequence */
def  bezDecrypt(bezDataBuffer):
	r = 11586L
	i = 0 # input buffer byte position index
	lenBuffer = len(bezDataBuffer)
	byteCnt = 0 # output buffer byte count.
	newBuffer = ""
	while 1:
		cipher = 0 # restricted to int
		plain = 0 # restricted to int
		j = 2 # used to combine two successive bytes

		# process next two bytes, skipping whitespace.
		while j > 0:
			j -=1
			try:
				while  bezDataBuffer[i].isspace():
					i +=1
				ch = bezDataBuffer[i]
			except IndexError:
				return newBuffer

			if not ch.islower():
				ch = ch.lower() 
			if ch.isdigit():
				ch = ord(ch) - ord('0') 
			else:
				ch = ord(ch) - ord('a') + 10
			cipher = (cipher << 4) & 0xFFFFL
			cipher = cipher | ch
			i += 1

		plain = cipher ^ (r >> 8)
		r = (cipher + r) * 902381661L + 341529579L
		if r  > 0xFFFFL:
			r = r & 0xFFFFL
		byteCnt +=1
		if (byteCnt > LEN_IV):
			newBuffer += chr(plain)
		if i >= lenBuffer:
			break

	return newBuffer


kHintArgsNoOverlap = 0
kHintArgsOverLap = 1
kHintArgsMatch = 2

def checkStem3ArgsOverlap(argList, hintList):
	# status == 0 -> no overlap
	# status == 1 -> arg are the same
	# status = 2 -> args  overlap, and are not the same
	status = kHintArgsNoOverlap
	for x0,x1 in argList:
		x1 = x0 + x1
		for y0,y1 in hintList:
			y1 = y0 +y1
			if ( x0 == y0):
				if (x1 == y1):
					status = kHintArgsMatch
				else:
					return kHintArgsOverLap
			elif (x1 == y1):
				return kHintArgsOverLap
			else:
				if (x0 > y0) and (x0 < y1):
					return kHintArgsOverLap
				if (x1 > y0) and (x1 < y1):
					return kHintArgsOverLap
	return status
		

def buildControlMaskList(hStem3List, vStem3List):
	""" The deal is that a char string will use either ounter hihintstns, or stem 3 hints, but
	 not both. We examine all the arglists. If any are not a multiple of 3, then we use all the arglists as is as the args to a counter hint.
	 If all are a multiple of 3, then we divide them up into triplets, adn add a separate conter mask for each unique arg set.
	"""
		
	vControlMask =  HintMask(0)
	hControlMask = vControlMask
	controlMaskList = [hControlMask]
	for argList in hStem3List:
		for mask in controlMaskList:
			overlapStatus = kHintArgsNoOverlap
			if not mask.hList:
				mask.hList.extend(argList)
				overlapStatus = kHintArgsMatch
				break
			overlapStatus = checkStem3ArgsOverlap(argList, mask.hList)
			if overlapStatus == kHintArgsMatch: # The args match args in this control mask. 
				break				
		if overlapStatus != kHintArgsMatch: 
			mask = HintMask(0)
			controlMaskList.append(mask)
			mask.hList.extend(argList)
			
	for argList in vStem3List:
		for mask in controlMaskList:
			overlapStatus = kHintArgsNoOverlap
			if not mask.vList:
				mask.vList.extend(argList)
				overlapStatus = kHintArgsMatch
				break
			overlapStatus = checkStem3ArgsOverlap(argList, mask.vList)
			if overlapStatus == kHintArgsMatch: # The args match args in this control mask. 
				break				
		if overlapStatus != kHintArgsMatch: 
			mask = HintMask(0)
			controlMaskList.append(mask)
			mask.vList.extend(argList)
			
	return controlMaskList
				

def convertBezToT2(bezString, useStem3 = 1):
	# convert bez data to a T2 outline program, a list of operator tokens.
	#
	# Convert all bez ops to simplest T2 equivalent
	# Add all hints to vertical and horizontal hint lists as encountered; insert a HintMask class whenever a
	# new set of hints is encountered
	# after all operators have been processed, convert HintMask items into hintmask ops and hintmask bytes
	# add all hints as prefix
	# review operator list to optimize T2 operators.
	# if useStem3 == 1, then any counter hints must be processed as stem3 hints, else the opposite.
	
	bezString = re.sub(r"%.+?\n", "", bezString) # supress comments
	bezList = re.findall(r"(\S+)", bezString)
	if not bezList:
		return ""
	hhints = []
	vhints = []
	hintMask = HintMask(0) # Always assume a hint mask until proven otherwise.
	hintMaskList = [hintMask]
	vStem3Args = []
	hStem3Args = []
	vStem3List = []
	hStem3List = []
	argList = []
	
	t2List = []
	lastPathOp = None
	
	for token in bezList:
		try:
			val = int(token)
			argList.append(val)
			continue
		except ValueError:
			pass

		if token == "newcolors":
			lastPathOp = token
			pass
		elif token in ["beginsubr", "endsubr"]:
			lastPathOp = token
			pass
		elif token in ["snc"]:
			lastPathOp = token
			hintMask = HintMask(len(t2List)) # The index into the t2list is kept so we can quickly find them later.
			t2List.append([hintMask])
			hintMaskList.append(hintMask)
		elif token in ["enc"]:
			lastPathOp = token
			pass
		elif token == "div":
			# i specifically do NOT set lastPathOp for this.
			value = argList[-2]/float(argList[-1])
			argList[-2:] =[value]
		elif token == "rb":
			lastPathOp = token
			try:
				i = hhints.index(argList)
			except ValueError:
				i = len(hhints)
				hhints.append(argList)
			if hintMask:
				if hhints[i] not in hintMask.hList:
					hintMask.hList.append(hhints[i])
			argList = []
		elif token == "ry":
			lastPathOp = token
			try:
				i = vhints.index(argList)
			except ValueError:
				i = len(vhints)
				vhints.append(argList)
			if hintMask:
				if vhints[i] not in hintMask.vList:
					hintMask.vList.append(vhints[i])
			argList = []
		elif token == "rm": # vertical counters are vhints
			try:
				i = vhints.index(argList)
			except ValueError:
				i = len(vhints)
				vhints.append(argList)
			if hintMask:
				if vhints[i] not in hintMask.vList:
					hintMask.vList.append(vhints[i])
					
			if (lastPathOp != token) and vStem3Args:
				# first rv, must be start of a new h countermask
				if useStem3:
					if (len(vStem3Args) == 3):
						vStem3List.append(vStem3Args)
				else:
					vStem3List.append(vStem3Args)
				vStem3Args = []
				
			if useStem3 and (len(vStem3Args) == 3):
				vStem3List.append(vStem3Args)
				vStem3Args = []
				
			vStem3Args.append(argList)
			argList = []
			lastPathOp = token
		elif token == "rv": # h counters are hhints
			try:
				i = hhints.index(argList)
			except ValueError:
				i = len(hhints)
				hhints.append(argList)
			if hintMask:
				if hhints[i] not in hintMask.hList:
					hintMask.hList.append(hhints[i])
					
			if (lastPathOp != token) and hStem3Args:
				# first rv, must be start of a new h countermask
				if useStem3:
					if (len(vStem3Args) == 3):
						hStem3List.append(hStem3Args)
				else:
					hStem3List.append(hStem3Args)
				hStem3Args = []
			if useStem3 and (len(hStem3Args) == 3):
				hStem3List.append(hStem3Args)
				hStem3Args = []

			hStem3Args.append(argList)
			argList = []
			lastPathOp = token
		elif token == "preflx1":
			# the preflx1/  preflx2 sequence provides the same i as the flex sequence; the difference is that the 
			#  preflx1/preflx2 sequence provides the argument values needed for building a Type1 string
			# while the flex sequence is simply the 6 rcurveto points. Both sequences are always provided.
			lastPathOp = token
			argList = []
		elif token == "preflx2":
			lastPathOp = token
			del t2List[-1]
			argList = []
		elif token == "flx":
			lastPathOp = token
			t2List.append([argList[:12] + [50], "flex"]) 
			argList = []
		elif token == "sc":
			lastPathOp = token
			pass
		else: 
			if token[-2:] in ["mt", "dt", "ct", "cv"]:
				lastPathOp = token
			t2Op = bezToT2.get(token,None)
			if t2Op:
				t2List.append([argList, t2Op])
			elif t2Op == None:
				print "Unhandled operation", argList, token
				raise KeyError
			argList = []
			
		
	# add hints, if any
	# Must be done at the end of op processing to make sure we have seen all the
	# hints in the bez string.
	# Note that the hintmask are identified in the t2List by an index into the list
	# be careful to NOT change the t2List length until the hintmasks have been converted.
	numHintMasks = len(hintMaskList)
	needHintMasks = numHintMasks > 1

	if vStem3Args and not useStem3:
		# useStem3, then a non-empty vStem3Args has less than 3 hints, and is invalid. Ignore it.
		# Else, it is a valid  counter hint arg list
		vStem3List.append(vStem3Args)
	if hStem3Args and not useStem3:
		hStem3List.append(hStem3Args)


	t2Program = []
	hhints.sort()
	vhints.sort()
	numHHints = len(hhints) 
	numVHints =  len(vhints)
	hintLimit = (kStackLimit-2)/2
	if numHHints >=hintLimit:
		hhints = hhints[:hintLimit]
		numHHints = hintLimit
	if numVHints >=hintLimit:
		vhints = vhints[:hintLimit]
		numVHints = hintLimit
	if hhints:
		isH = 1
		t2Program = makeHintList(hhints, needHintMasks, isH)
	if vhints:
		isH = 0
		t2Program += makeHintList(vhints, needHintMasks, isH)
		
	if vStem3List or hStem3List:
		controlMaskList = buildControlMaskList(hStem3List, vStem3List)
		for cMask in controlMaskList:
			hBytes = cMask.maskByte(hhints, vhints)
			t2Program.extend(["cntrmask", hBytes])
		
	if needHintMasks:
		# If there is not a hintsub before any drawing operators, then
		# add an initial first hint mask to the t2Program.
		if hintMaskList[1].listPos != 0:
			hBytes = hintMaskList[0].maskByte(hhints, vhints)
			t2Program.extend(["hintmask", hBytes])

		# Convert the rest of the hint masks to a hintmask op and hintmask bytes.
		for hintMask in hintMaskList[1:]:
			pos = hintMask.listPos
			t2List[pos] = [["hintmask"], hintMask.maskByte(hhints, vhints)]

	t2List = optimizeT2Program(t2List)

	for entry in t2List:
		try:
			t2Program.extend(entry[0])
			t2Program.append(entry[1])
		except:
			print "Failed to extend t2Program with entry", entry
			raise KeyError
	
	return t2Program

if __name__=='__main__':
	import os

def test():
	# Test program. Takes first argument  font file path, optional second argument = glyph name.
	# use form "cid0769" for CID keys references.
	from GSttLib import TTFont
	path = sys.argv[1]
	ttFont = TTFont(path)
	if len(sys.argv) > 2:
		glyphNames = sys.argv[2:]
	else:
		glyphNames = ttFont.getGlyphOrder()
	cffTable = ttFont["CFF "]
	topDict =  cffTable.cff.topDictIndex[0]
	charStrings = topDict.CharStrings
	removeHints = 0

	for glyphName in glyphNames:
		print
		print glyphName
		t2CharString = charStrings[glyphName]
		bezString, hasHints, t2Width = convertT2GlyphToBez(t2CharString, removeHints)
		#print bezString
		t2Program = convertBezToT2(bezString)
		if t2Width != None:
			t2Program.insert(0,t2Width)

		#print len(t2Program),  ("t2Program",t2Program)

def test2():
	# Test program. Takes first argument = bez path, writes t2 string.
	# use form "cid0769" for CID keys references.
	# from GSttLib import TTFont
	# path = sys.argv[1]
	# fp = open(path, "rt")
	# bezString = fp.read()
	# fp.close()
	# if needsDecryption(bezString):
	# 	bezString = bezDecrypt(bezString)
	bezString = '''sc
227  367 rmt
3 -3 1 -2 -3 -3 rct
-84  -88 rdt
-43 -44 -33 -41 0 -56 rct
-80 29 -57 153 vhct
153 29 57 80 hvct
0 56 -32 41 -44 44 rct
-85  86 rdt
-2 2 -1 3 3 3 rct
90  89 rdt
33 35 26 32 0 63 rct
75 -37 48 -133 vhct
-133 -37 -48 -75 hvct
0 -63 28 -32 32 -35 rct
cp
114  -71 rmt
-4 -4 -1 0 -4 4 rct
-85  86 rdt
-26 29 -29 28 0 56 rct
66 26 35 119 vhct
119 26 -35 -66 hvct
0 -56 -28 -28 -27 -29 rct
cp
-7  -210 rmt
46 -42 28 -39 0 -49 rct
-69 -22 -43 -135 vhct
-135 -22 43 69 hvct
0 49 30 37 44 44 rct
77  80 rdt
3 3 2 0 3 -3 rct
cp
ed'''
	t2Program = convertBezToT2(bezString)
	print "__result", t2Program

if __name__=='__main__':
	test2()
