#!/usr/bin/env python
###########################################################################
#
# xasy2asy provides a Python interface to Asymptote
#
#
# Author: Orest Shardt
# Created: June 29, 2007
#
###########################################################################
import sys,os,signal,threading
from subprocess import *
from string import *
import xasyOptions
import Queue
from Tkinter import *
from tempfile import mkdtemp

# PIL support is now mandatory due to rotations
try:
  import ImageTk
  import Image
except:
   pass

import CubicBezier

class asyProcessFailure(Exception):
  """asy could not be invoked to execute a command because the process has failed."""
  pass

idCounter = 0;
randString = 'wGd3I26kOcu4ZI4arZZMqoJufO2h1QE2D728f1Lai3aqeTQC9'

quickAsyFailed = True
global AsyTempDir

console=None

def startQuickAsy():
  global quickAsy
  global quickAsyFailed
  global AsyTempDir
  if quickAsyRunning():
    return
  try:
    quickAsy.stdin.close()
    quickAsy.wait()
  except:
    pass
  try:
    quickAsyFailed = False
    if os.name == "nt":
      AsyTempDir=mkdtemp(prefix="asy_", dir="./")
    else:
      AsyTempDir=mkdtemp(prefix="asy_")+os.sep
    quickAsy = Popen([xasyOptions.options['asyPath'],"-noV","-multiline",
                      "-interactive","-o"+AsyTempDir],
                     stdin=PIPE,stdout=PIPE,stderr=STDOUT)
    if quickAsy.returncode != None:
      quickAsyFailed = True
  except:
    quickAsyFailed = True
  
def getAsyTempDir():
  return AsyTempDir

def quickAsyRunning():
  if quickAsyFailed or quickAsy.returncode != None:
    return False
  else:
    return True

def asyExecute(command):
  if not quickAsyRunning():
    startQuickAsy()
  syncQuickAsyOutput()
  quickAsy.stdin.write(command)

def closeConsole(event):
  global console
  console = None

def consoleOutput(line):
  global console
  global ctl
  if console == None:
    ctl=Toplevel()
    ctl.title("Asymptote Console")
    ctl.bind("<Destroy>",closeConsole)
    yscrollbar=Scrollbar(ctl)
    yscrollbar.pack(side=RIGHT,fill=Y)
    console=Text(ctl,yscrollcommand=yscrollbar.set)
    console.pack()
    yscrollbar.config(command=console.yview)
  console.insert(END,line)
  ctl.lift()

def syncQuickAsyOutput(verbose=False,queue=None):
  global idCounter
  idStr = randString+"-id "+str(idCounter)
  idCounter += 1
  quickAsy.stdin.write("\nwrite(\""+idStr+"\");\n")
  quickAsy.stdin.flush()
  line = quickAsy.stdout.readline()
  while not line.endswith(idStr+'\n'):
    if verbose:
      queue.put(("OUTPUT",line))
    line = quickAsy.stdout.readline()
    quickAsy.stdin.flush()

class asyTransform:
  """A python implementation of an asy transform"""
  def __init__(self,initTuple,delete=False):
    """Initialize the transform with a 6 entry tuple"""
    if type(initTuple) == type((0,)) and len(initTuple) == 6:
      self.t = initTuple
      self.x,self.y,self.xx,self.xy,self.yx,self.yy = initTuple
      self.deleted = delete
    else:
      raise Exception,"Illegal initializer for asyTransform"

  def getCode(self):
    """Obtain the asy code that represents this transform"""
    if self.deleted:
      return str(self.t) + ", false"
    else:
      return str(self.t)

  def scale(self,s):
    #return asyTransform((s*self.t[0],s*self.t[1],s*self.t[2],s*self.t[3],s*self.t[4],s*self.t[5]))
    return asyTransform((0,0,s,0,0,s))*self

  def __str__(self):
    """Equivalent functionality to getCode(). It allows the expression str(asyTransform) to be meaningful."""
    return self.getCode()

  def __mul__(self,other):
    """Define multiplication of transforms as composition."""
    if type(other)==type((0,)):
      if len(other) == 6:
        return self*asyTransform(other)
      elif len(other) == 2:
        return ((self.t[0]+self.t[2]*other[0]+self.t[3]*other[1]),(self.t[1]+self.t[4]*other[0]+self.t[5]*other[1]))
      else:
        raise Exception, "Illegal multiplier of %s"%str(type(other))
    elif isinstance(other,asyTransform):
      result = asyTransform((0,0,0,0,0,0))
      result.x = self.x+self.xx*other.x+self.xy*other.y
      result.y = self.y+self.yx*other.x+self.yy*other.y
      result.xx = self.xx*other.xx+self.xy*other.yx
      result.xy = self.xx*other.xy+self.xy*other.yy
      result.yx = self.yx*other.xx+self.yy*other.yx
      result.yy = self.yx*other.xy+self.yy*other.yy
      result.t = (result.x,result.y,result.xx,result.xy,result.yx,result.yy)
      return result
    else:
      raise Exception, "Illegal multiplier of %s"%str(type(other))

def identity():
  return asyTransform((0,0,1,0,0,1))

class asyObj:
  """A base class for asy objects: an item represented by asymptote code."""
  def __init__(self):
    """Initialize the object"""
    self.asyCode = ""

  def updateCode(self,mag=1.0):
    """Update the object's code: should be overriden."""
    pass

  def getCode(self):
    """Return the code describing the object"""
    self.updateCode()
    return self.asyCode

class asyPen(asyObj):
  """A python wrapper for an asymptote pen"""
  def __init__(self,color=(0,0,0),width=0.5,options=""):
    """Initialize the pen"""
    asyObj.__init__(self)
    self.options=options
    self.width=width
    self.setColor(color)
    self.updateCode()
    if options != "":
      self.computeColor()

  def updateCode(self,mag=1.0):
    """Generate the pen's code"""
    self.asyCode = "rgb(%g,%g,%g)"%self.color+"+"+str(self.width)
    if len(self.options) > 0:
      self.asyCode += "+"+self.options

  def setWidth(self,newWidth):
    """Set the pen's width"""
    self.width=newWidth
    self.updateCode()

  def setColor(self,color):
    """Set the pen's color"""
    if type(color) == type((1,)) and len(color) == 3:
      self.color = color
    else:
      self.color = "(0,0,0)"
    self.updateCode()

  def computeColor(self):
    """Find out the color of an arbitrary asymptote pen."""
    syncQuickAsyOutput()
    quickAsy.stdin.write("pen p="+self.getCode()+';\n')
    quickAsy.stdin.write("write(\";\n\");write(colorspace(p));\n")
    quickAsy.stdin.write("write(colors(p));\n")
    quickAsy.stdin.flush()
    testline = quickAsy.stdout.readline()
    if testline.startswith("> -:"):
      raise Exception,"Invalid pen options"
    quickAsy.stdout.readline()
    colorspace = quickAsy.stdout.readline()
    if colorspace.find("cmyk") != -1:
      lines = quickAsy.stdout.readline()+quickAsy.stdout.readline()+quickAsy.stdout.readline()+quickAsy.stdout.readline()
      parts = lines.split()
      c,m,y,k = eval(parts[2]),eval(parts[4]),eval(parts[6]),eval(parts[8])
      k = 1-k
      r,g,b = ((1-c)*k,(1-m)*k,(1-y)*k)
    elif colorspace.find("rgb") != -1:
      lines = quickAsy.stdout.readline()+quickAsy.stdout.readline()+quickAsy.stdout.readline()
      parts = lines.split()
      r,g,b = eval(parts[2]),eval(parts[4]),eval(parts[6])
    elif colorspace.find("gray") != -1:
      lines = quickAsy.stdout.readline()
      parts = lines.split()
      r = g = b = eval(parts[2])
    self.color = (r,g,b)

  def tkColor(self):
    """Return the tk version of the pen's color"""
    self.computeColor()
    r,g,b = self.color
    r,g,b = int(256*r),int(256*g),int(256*b)
    if r == 256:
      r = 255
    if g == 256:
      g = 255
    if b == 256:
      b = 255
    r,g,b = map(hex,(r,g,b))
    r,g,b = r[2:],g[2:],b[2:]
    if len(r) < 2:
      r += '0'
    if len(g) < 2:
      g += '0'
    if len(b) < 2:
      b += '0'
    return'#'+r+g+b

class asyPath(asyObj):
  """A python wrapper for an asymptote path"""
  def __init__(self):
    """Initialize the path to be an empty path: a path with no nodes, control points, or links."""
    asyObj.__init__(self)
    self.nodeSet = []
    self.linkSet = []
    self.controlSet = []
    self.computed = False

  def initFromNodeList(self,nodeSet,linkSet):
    """Initialize the path from a set of nodes and link types, "--", "..", or "::" """
    if len(nodeSet)>0:
      self.nodeSet = nodeSet[:]
      self.linkSet = linkSet[:]
      self.computed = False

  def initFromControls(self,nodeSet,controlSet):
    """Initialize the path from nodes and control points"""
    self.controlSet = controlSet[:]
    self.nodeSet = nodeSet[:]
    self.computed = True

  def makeNodeStr(self,node):
    """Represent a node as a string"""
    if node == 'cycle':
      return node
    else:
      return "("+str(node[0])+","+str(node[1])+")"

  def updateCode(self,mag=1.0):
    """Generate the code describing the path"""
    if not self.computed:
      count = 0
      #this string concatenation could be optimised
      self.asyCode = self.makeNodeStr(self.nodeSet[0])
      for node in self.nodeSet[1:]:
        self.asyCode += self.linkSet[count]+self.makeNodeStr(node)
        count += 1
    else:
      count = 0
      #this string concatenation could be optimised
      self.asyCode = self.makeNodeStr(self.nodeSet[0])
      for node in self.nodeSet[1:]:
        self.asyCode += "..controls"
        self.asyCode += self.makeNodeStr(self.controlSet[count][0])
        self.asyCode += "and"
        self.asyCode += self.makeNodeStr(self.controlSet[count][1])
        self.asyCode += ".." + self.makeNodeStr(node) + "\n"
        count += 1

  def getNode(self,index):
    """Return the requested node"""
    return self.nodeSet[index]

  def getLink(self,index):
    """Return the requested link"""
    return self.linkSet[index]

  def setNode(self,index,newNode):
    """Set a node to a new position"""
    self.nodeSet[index] = newNode

  def moveNode(self,index,offset):
    """Translate a node"""
    if self.nodeSet[index] != "cycle":
      self.nodeSet[index] = (self.nodeSet[index][0]+offset[0],self.nodeSet[1]+offset[1])

  def setLink(self,index,ltype):
    """Change the specified link"""
    self.linkSet[index] = ltype

  def addNode(self,point,ltype):
    """Add a node to the end of a path"""
    self.nodeSet.append(point)
    if len(self.nodeSet) != 1:
      self.linkSet.append(ltype)
    if self.computed:
      self.computeControls()

  def insertNode(self,index,point,ltype=".."):
    """Insert a node, and its corresponding link, at the given index"""
    self.nodeSet.insert(index,point)
    self.linkSet.insert(index,ltype)
    if self.computed:
      self.computeControls()

  def setControl(self,index,position):
    """Set a control point to a new position"""
    self.controlSet[index] = position

  def moveControl(self,index,offset):
    """Translate a control point"""
    self.controlSet[index] = (self.controlSet[index][0]+offset[0],self.controlSet[index][1]+offset[1])

  def computeControls(self):
    """Evaluate the code of the path to obtain its control points"""
    syncQuickAsyOutput()
    quickAsy.stdin.write("path p="+self.getCode()+';\n')
    quickAsy.stdin.write("write(length(p));\n")
    quickAsy.stdin.write("write(unstraighten(p));write(\"\");\n")
    quickAsy.stdin.flush()
    lengthStr = quickAsy.stdout.readline()
    pathSegments = eval(lengthStr.split()[-1])
    pathStrLines = []
    for i in range(pathSegments+1):
      pathStrLines.append(quickAsy.stdout.readline())
    oneLiner = "".join(split(join(pathStrLines)))
    oneLiner = oneLiner.replace(">","")
    splitList = oneLiner.split("..")
    nodes = [a for a in splitList if a.find("controls")==-1]
    self.nodeSet = []
    for a in nodes:
      if a == 'cycle':
        self.nodeSet.append(a)
      else:
        self.nodeSet.append(eval(a))
    controls = [a.replace("controls","").split("and") for a in splitList if a.find("controls") != -1]
    self.controlSet = [[eval(a[0]),eval(a[1])] for a in controls]
    self.computed = True

class asyLabel(asyObj):
  """A python wrapper for an asy label"""
  def __init__(self,text="",location=(0,0),pen=asyPen()):
    """Initialize the label with the given test, location, and pen"""
    asyObj.__init__(self)
    self.text = text
    self.location = location
    self.pen = pen

  def updateCode(self,mag=1.0):
    """Generate the code describing the label"""
    self.asyCode = "Label(\""+self.text+"\","+str((self.location[0],self.location[1]))+","+self.pen.getCode()+",align=SE)"

  def setText(self,text):
    """Set the label's text"""
    self.text = text
    self.updateCode()

  def setPen(self,pen):
    """Set the label's pen"""
    self.pen = pen
    self.updateCode()

  def moveTo(self,newl):
    """Translate the label's location"""
    self.location = newl

class asyImage:
  """A structure containing an image and its format, bbox, and IDTag"""
  def __init__(self,image,format,bbox):
    self.image = image
    self.format = format
    self.bbox = bbox
    self.IDTag = None

class xasyItem:
  """A base class for items in the xasy GUI"""
  def __init__(self,canvas=None):
    """Initialize the item to an empty item"""
    self.transform = [identity()]
    self.asyCode = ""
    self.imageList = []
    self.IDTag = None
    self.asyfied = False
    self.onCanvas = canvas

  def updateCode(self,mag=1.0):
    """Update the item's code: to be overriden"""
    pass

  def getCode(self):
    """Return the code describing the item"""
    self.updateCode()
    return self.asyCode

  def handleImageReception(self,file,format,bbox,count):
    """Receive an image from an asy deconstruction. It replaces the default in asyProcess."""
    image = Image.open(file)
    self.imageList.append(asyImage(image,format,bbox))
    if self.onCanvas != None:
      self.imageList[-1].itk = ImageTk.PhotoImage(image)
      self.imageList[-1].originalImage = image.copy()
      self.imageList[-1].originalImage.theta = 0.0
      self.imageList[-1].originalImage.bbox = bbox
      if count >= len(self.transform) or self.transform[count].deleted == False:
        self.imageList[-1].IDTag = self.onCanvas.create_image(bbox[0],-bbox[3],anchor=NW,tags=("image"),image=self.imageList[-1].itk)
      self.onCanvas.update()

  def asyfy(self,mag=1.0):
    self.removeFromCanvas()
    self.imageList = []
    self.imageHandleQueue = Queue.Queue()
    worker = threading.Thread(target=self.asyfyThread,args=(mag,))
    worker.start()
    item = self.imageHandleQueue.get()
    if console != None:
      console.delete(1.0,END)
    while item != (None,) and item[0] != "ERROR":
      if(item[0] == "OUTPUT"):
        consoleOutput(item[1])
      else:
        self.handleImageReception(*item)
        try:
          os.remove(item[0])
        except:
          pass
      item = self.imageHandleQueue.get()
    #self.imageHandleQueue.task_done()
    worker.join()

  def asyfyThread(self,mag=1.0):
    """Convert the item to a list of images by deconstructing this item's code"""
    quickAsy.stdin.write("\nreset;\n")
    quickAsy.stdin.write("initXasyMode();\n")
    quickAsy.stdin.write("atexit(null);\n")
    syncQuickAsyOutput()
    global console
    for line in self.getCode().splitlines():
      quickAsy.stdin.write(line+"\n");
    quickAsy.stdin.flush()
    syncQuickAsyOutput(verbose=True,queue=self.imageHandleQueue);
    quickAsy.stdin.write("deconstruct(%f);\n"%mag)
    quickAsy.stdin.flush()
    format = "png"
    maxargs = int(split(quickAsy.stdout.readline())[1])
    boxes=[]
    batch=0
    n=0
    text = quickAsy.stdout.readline()
    template=AsyTempDir+"%d_%d.%s"
    def render():
        for i in range(len(boxes)):
          l,b,r,t = [float(a) for a in split(boxes[i])]
          name=template%(batch,i+1,format)
          self.imageHandleQueue.put((name,format,(l,b,r,t),i))
    while text != "Done\n" and text != "Error\n":
      boxes.append(text)
      text = quickAsy.stdout.readline()
      n += 1
      if n >= maxargs:
        render()
        boxes=[]
        batch += 1
        n=0
    if text == "Error\n":
      self.imageHandleQueue.put(("ERROR",quickAsy.stdout.readline()))
    else:
      render()
    self.imageHandleQueue.put((None,))
    self.asyfied = True

  def drawOnCanvas(self,canvas,mag,forceAddition=False):
    pass
  def removeFromCanvas(self):
    pass

class xasyDrawnItem(xasyItem):
  """A base class for GUI items was drawn by the user. It combines a path, a pen, and a transform."""
  def __init__(self,path,pen = asyPen(),transform = identity()):
    """Initialize the item with a path, pen, and transform"""
    xasyItem.__init__(self)
    self.path = path
    self.pen = pen
    self.transform = [transform]

  def appendPoint(self,point,link=None):
    """Append a point to the path. If the path is cyclic, add this point before the 'cycle' node."""
    if self.path.nodeSet[-1] == 'cycle':
      self.path.nodeSet[-1] = point
      self.path.nodeSet.append('cycle')
    else:
      self.path.nodeSet.append(point)
    self.path.computed = False
    if len(self.path.nodeSet) > 1 and link != None:
      self.path.linkSet.append(link)

  def clearTransform(self):
    """Reset the item's transform"""
    self.transform = [identity()]

  def removeLastPoint(self):
    """Remove the last point in the path. If the path is cyclic, remove the node before the 'cycle' node."""
    if self.path.nodeSet[-1] == 'cycle':
      del self.path.nodeSet[-2]
    else:
      del self.path.nodeSet[-1]
    del self.path.linkSet[-1]
    self.path.computed = False

  def setLastPoint(self,point):
    """Modify the last point in the path. If the path is cyclic, modify the node before the 'cycle' node."""
    if self.path.nodeSet[-1] == 'cycle':
      self.path.nodeSet[-2] = point
    else:
      self.path.nodeSet[-1] = point
    self.path.computed = False

class xasyShape(xasyDrawnItem):
  """An outlined shape drawn on the GUI"""
  def __init__(self,path,pen=asyPen(),transform=identity()):
    """Initialize the shape with a path, pen, and transform"""
    xasyDrawnItem.__init__(self,path,pen,transform)

  def updateCode(self,mag=1.0):
    """Generate the code to describe this shape"""
    self.asyCode = "xformStack.push("+self.transform[0].getCode()+");\n"
    self.asyCode += "draw("+self.path.getCode()+","+self.pen.getCode()+");"

  def removeFromCanvas(self,canvas):
    """Remove the shape's depiction from a tk canvas"""
    if self.IDTag != None:
      canvas.delete(self.IDTag)

  def drawOnCanvas(self,canvas,mag,asyFy=False,forceAddition=False):
    """Add this shape to a tk canvas"""
    if not asyFy:
      if self.IDTag == None or forceAddition:
        #add ourselves to the canvas
        self.path.computeControls()
        self.IDTag = canvas.create_line(0,0,0,0,tags=("drawn","xasyShape"),fill=self.pen.tkColor(),width=self.pen.width*mag)
        self.drawOnCanvas(canvas,mag)
      else:
        self.path.computeControls()
        pointSet = []
        previousNode = self.path.nodeSet[0]
        nodeCount = 0
        if len(self.path.nodeSet) == 0:
          pointSet = [0,0,0,0]
        elif len(self.path.nodeSet) == 1:
          if self.path.nodeSet[-1] != 'cycle':
            p = self.transform[0]*(self.path.nodeSet[0][0],self.path.nodeSet[0][1])
            pointSet = [p[0],-p[1],p[0],-p[1],p[0],-p[1]]
          else:
            pointSet = [0,0,0,0]
        else:
          for node in self.path.nodeSet[1:]:
            if node == 'cycle':
              node = self.path.nodeSet[0]
            transform = self.transform[0].scale(mag)
            points = CubicBezier.makeBezier(transform*previousNode,transform*self.path.controlSet[nodeCount][0],transform*self.path.controlSet[nodeCount][1],transform*node)
            for point in points:
              pointSet += [point[0],-point[1]]
            nodeCount += 1
            previousNode = node
        canvas.coords(self.IDTag,*pointSet)
        canvas.itemconfigure(self.IDTag,fill=self.pen.tkColor(),width=self.pen.width*mag)
    else:
      #first asyfy then add an image list
      pass

  def __str__(self):
    """Create a string describing this shape"""
    return "xasyShape code:%s"%("\n\t".join(self.getCode().splitlines()))

class xasyFilledShape(xasyShape):
  """A filled shape drawn on the GUI"""
  def __init__(self,path,pen=asyPen(),transform=identity()):
    """Initialize this shape with a path, pen, and transform"""
    if path.nodeSet[-1] != 'cycle':
      raise Exception,"Filled paths must be cyclic"
    xasyShape.__init__(self,path,pen,transform)

  def updateCode(self,mag=1.0):
    """Generate the code describing this shape"""
    self.asyCode = "xformStack.push("+self.transform[0].getCode()+");\n"
    self.asyCode += "fill("+self.path.getCode()+","+self.pen.getCode()+");"

  def removeFromCanvas(self,canvas):
    """Remove this shape's depiction from a tk canvas"""
    if self.IDTag != None:
      canvas.delete(self.IDTag)

  def drawOnCanvas(self,canvas,mag,asyFy=False,forceAddition=False):
    """Add this shape to a tk canvas"""
    if not asyFy:
      if self.IDTag == None or forceAddition:
        #add ourselves to the canvas
        self.path.computeControls()
        self.IDTag = canvas.create_polygon(0,0,0,0,0,0,tags=("drawn","xasyFilledShape"),fill=self.pen.tkColor(),outline=self.pen.tkColor(),width=1*mag)
        self.drawOnCanvas(canvas,mag)
      else:
        self.path.computeControls()
        pointSet = []
        previousNode = self.path.nodeSet[0]
        nodeCount = 0
        if len(self.path.nodeSet) == 0:
          pointSet = [0,0,0,0,0,0]
        elif len(self.path.nodeSet) == 1:
          if self.path.nodeSet[-1] != 'cycle':
            p = self.transform[0]*(self.path.nodeSet[0][0],self.path.nodeSet[0][1])
            pointSet = [p[0],-p[1],p[0],-p[1],p[0],-p[1]]
          else:
            pointSet = [0,0,0,0,0,0]
        elif len(self.path.nodeSet) == 2:
          if self.path.nodeSet[-1] != 'cycle':
            p = self.transform[0].scale(mag)*(self.path.nodeSet[0][0],self.path.nodeSet[0][1])
            p2 = self.transform[0].scale(mag)*(self.path.nodeSet[1][0],self.path.nodeSet[1][1])
            pointSet = [p[0],-p[1],p2[0],-p2[1],p[0],-p[1]]
          else:
            pointSet = [0,0,0,0,0,0]
        else:
          for node in self.path.nodeSet[1:]:
            if node == 'cycle':
              node = self.path.nodeSet[0]
            transform = self.transform[0].scale(mag)
            points = CubicBezier.makeBezier(transform*previousNode,transform*self.path.controlSet[nodeCount][0],transform*self.path.controlSet[nodeCount][1],transform*node)
            for point in points:
              pointSet += [point[0],-point[1]]
            nodeCount += 1
            previousNode = node
        canvas.coords(self.IDTag,*pointSet)
        canvas.itemconfigure(self.IDTag,fill=self.pen.tkColor(),outline=self.pen.tkColor(),width=1*mag)
    else:
      #first asyfy then add an image list
      pass

  def __str__(self):
    """Return a string describing this shape"""
    return "xasyFilledShape code:%s"%("\n\t".join(self.getCode().splitlines()))

class xasyText(xasyItem):
  """Text created by the GUI"""
  def __init__(self,text,location,pen=asyPen(),transform=identity()):
    """Initialize this item with text, a location, pen, and transform"""
    xasyItem.__init__(self)
    self.label=asyLabel(text,location,pen)
    self.transform = [transform]
    self.onCanvas = None

  def updateCode(self,mag=1.0):
    """Generate the code describing this object"""
    self.asyCode = "xformStack.push("+self.transform[0].getCode()+");\n"
    self.asyCode += "label("+self.label.getCode()+");"

  def removeFromCanvas(self):
    """Removes the label's images from a tk canvas"""
    if self.onCanvas == None:
      return
    for image in self.imageList:
      if image.IDTag != None:
        self.onCanvas.delete(image.IDTag)

  def drawOnCanvas(self,canvas,mag,asyFy=True,forceAddition=False):
    """Adds the label's images to a tk canvas"""
    if self.onCanvas == None:
      self.onCanvas = canvas
    elif self.onCanvas != canvas:
      raise Exception,"Error: item cannot be added to more than one canvas"
    self.asyfy(mag)

  def __str__(self):
    return "xasyText code:%s"%("\n\t".join(self.getCode().splitlines()))

class xasyScript(xasyItem):
  """A set of images create from asymptote code. It is always deconstructed."""
  def __init__(self,canvas,script="",transforms=[]):
    """Initialize this script item"""
    xasyItem.__init__(self,canvas)
    self.transform = transforms[:]
    self.script = script

  def clearTransform(self):
    """Reset the transforms for each of the deconstructed images""" 
    self.transform = [identity() for im in self.imageList]

  def updateCode(self,mag=1.0):
    """Generate the code describing this script"""
    self.asyCode = "";
    if len(self.transform) > 0:
      self.asyCode = "xformStack.add("
      isFirst = True
      count = 0
      for xform in self.transform:
        if not isFirst:
          self.asyCode+=",\n"
        self.asyCode += "indexedTransform(%d,%s)"%(count,str(xform))
        isFirst = False
        count += 1
      self.asyCode += ");\n"
    self.asyCode += "startScript(); {\n"
    self.asyCode += self.script.replace("\t"," ")
    self.asyCode = self.asyCode.rstrip()
    self.asyCode += "\n} endScript();\n"

  def setScript(self,script):
    """Sets the content of the script item."""
    self.script = script
    self.updateCode()

  def removeFromCanvas(self):
    """Removes the script's images from a tk canvas"""
    if self.onCanvas == None:
      return
    for image in self.imageList:
      if image.IDTag != None:
        self.onCanvas.delete(image.IDTag)

  def asyfy(self,mag):
    """Generate the list of images described by this object and adjust the length of the transform list."""
    xasyItem.asyfy(self,mag)
    while len(self.imageList) > len(self.transform):
      self.transform.append(identity())
    while len(self.imageList) < len(self.transform):
      self.transform.pop()
    self.updateCode()

  def drawOnCanvas(self,canvas,mag,asyFy=True,forceAddition=False):
    """Adds the script's images to a tk canvas"""
    if self.onCanvas == None:
      self.onCanvas = canvas
    elif self.onCanvas != canvas:
      raise Exception,"Error: item cannot be added to more than one canvas"
    self.asyfy(mag)

  def __str__(self):
    """Return a string describing this script"""
    retVal = "xasyScript\n\tTransforms:\n"
    for xform in self.transform:
      retVal += "\t"+str(xform)+"\n"
    retVal += "\tCode Ommitted"
    return retVal

if __name__=='__main__':
  root = Tk()
  t=xasyText("test",(0,0))
  t.asyfy()
