__all__='CanvasAdapter','TextObject'
from reportlab.pdfbase.pdfmetrics import stringWidth

class TextObject:
    def __init__(self, canv, x=0, y=0):
        self._code = []
        self._canvas = canv  #canvas sets this so it has access to size info
        self._fontname = canv.fontName
        self._fontsize = canv.fontSize
        self.setTextOrigin(x, y)

    def _code_append(self,k,*args,**kw):
        self._code.append((k,args,kw))

    def setTextOrigin(self, x, y):
        self._x0 = self._x = x
        self._y0 = self._y = y
        self._code_append('setTextX',x)
        self._code_append('setTextY',y)

    def moveCursor(self, dx, dy):
        #self._code_append(something)
        self._x0 += dx
        self._y0 += dy
        self._x = self._x0
        self._y = self._y0
        self._code_append('setTextX',self._x0)
        self._code_append('setTextY',self._y0)

    def setXPos(self, dx):
        """Starts a new line dx away from the start of the
        current line - NOT from the current point! So if
        you call it in mid-sentence, watch out."""
        self.moveCursor(dx,0)

    def getCursor(self):
        """Returns current text position relative to the last origin."""
        return (self._x, self._y)

    def getCursor(self):
        """Returns current text position relative to the last origin."""
        return (self._x, self._y)

    def getStartOfLine(self):
        """Returns a tuple giving the text position of the start of the
        current line."""
        return (self._x0, self._y0)

    def getX(self):
        """Returns current x position relative to the last origin."""
        return self._x

    def getY(self):
        """Returns current y position relative to the last origin."""
        return self._y

    def _setFont(self, psfontname, size):
        """Sets the font and fontSize
        Raises a readable exception if an illegal font
        is supplied.  Font names are case-sensitive! Keeps track
        of font anme and size for metrics."""
        self._fontname = psfontname
        self._fontsize = size
        self._code_append('setFont',psfontname,size)

    def setFont(self, psfontname, size, leading = None):
        """Sets the font.  If leading not specified, defaults to 1.2 x
        font size. Raises a readable exception if an illegal font
        is supplied.  Font names are case-sensitive! Keeps track
        of font anme and size for metrics."""
        self._fontname = psfontname
        self._fontsize = size
        self._code_append('setFont',psfontname,size)
        if leading is None:
            leading = size * 1.2
        self._leading = leading

    def setCharSpace(self, space):
        """Adjusts inter-character spacing"""
        raise NotImplementedError
        self._charSpace = space
        self._code_append('setCharSpace',space)

    def setWordSpace(self, space):
        """Adjust inter-word spacing.  This can be used
        to flush-justify text - you get the width of the
        words, and add some space between them."""
        self._wordSpace = space
        self._code_append('setWordSpace',space)

    def setLeading(self, leading):
        "How far to move down at the end of a line."
        self._leading = leading

    def setRise(self, rise):
        "Move text baseline up or down to allow superscrip/subscripts"
        self._rise = rise
        self._code_append('setTextY',self._y+self._rise)

    def setFillColor(self, aColor):
        """Takes a color object, allowing colors to be referred to by name"""
        self._fillColor = aColor
        self._code_append('setFillColor',aColor)

    def setStrokeColor(self, aColor):
        """Takes a color object, allowing colors to be referred to by name"""
        self._StrokeColor = aColor
        self._code_append('setStrokeColor',aColor)

    def setFillGray(self, gray):
        """Sets the gray level; 0.0=black, 1.0=white"""
        raise NotImplementedError
        self._code_append('setFillGray',gray)

    def setStrokeGray(self, gray):
        """Sets the gray level; 0.0=black, 1.0=white"""
        raise NotImplementedError
        self._code_append('setStrokeGray',gray)

    def _textOut(self, text, TStar=0):
        "prints string at current point, ignores text cursor"
        self._code_append('textOut',text)
        if TStar:
            self._x = self._x0
            self._y0 = self._y = self._y-self._leading
            self._code_append('setTextY',self._y)
        else:
            self._x = self._x + stringWidth(text, self._fontname, self._fontsize)
        self._code_append('setTextX',self._x)

    def textOut(self, text):
        """prints string at current point, text cursor moves across."""
        self._x = self._x + stringWidth(text, self._fontname, self._fontsize)
        self._code_append('textOut',text)
        self._code_append('setTextX',self._x)

class SimpleStateTracker:
    def __init__(self,state={},ignore=[],rename={}):
        _ = self.__state = state.copy()
        for k in ignore:
            del _[k]
        for k,v in rename.items():
            _[v] = _[k]
            del _[k]
        self.__stack = []

    def push(self,obj):
        _ = self.__state
        for k in self.__state.keys():
            _[k] = getattr(obj,k)
        self.__stack.append(_)
        self.__state = _.copy()

    def apply(self,obj):
        for k,v in self.__state.items():
            setattr(obj,k,v)

    def printState(self,msg):
        print '\n'+msg,len(self.__stack)
        for k,v in self.__state.items():
            print '%s=%s' % (k,v)

    def pop(self):
        self.__state = self.__stack.pop()
        return self.__state

    def __getitem__(self,a):
        return self.__state[a]

    def __setitem__(self,a,v):
        self.__state[a] = v

    def __len__(self):
        return len(self.__stack)

    def setLen(self,n):
        del self.__stack[n:]

class CanvasAdapter:
    def __init__(self,baseCanvas,trans={},**kw):
        self.__trans = trans
        self.__transKeys = trans.keys()
        self.__canv = baseCanvas
        from reportlab.graphics.shapes import STATE_DEFAULTS
        _ = self.__tracker = SimpleStateTracker(STATE_DEFAULTS,
                ignore=['textAnchor','strokeMiterLimit'],
                rename=dict(transform='ctm',))
        _.apply(self)
        self.__textWordSpace = 0
        self.__textX = 0
        self.__textY = 0
        self.__dict__['stringWidth'] = stringWidth

    def saveState(self):
        self.__canv.saveState()
        self.__tracker.push(self)

    def restoreState(self):
        self.__tracker.pop()
        self.__tracker.apply(self)
        self.__canv.restoreState()

    def beginText(self, x=0, y=0):
        return TextObject(self, x, y)

    def setTextX(self,x):
        self.__textX = x

    def setTextY(self,y):
        self.__textY = y

    def drawText(self, aTextObject):
        for k,args,kw in aTextObject._code:
            getattr(self,k)(*args,**kw)

    def setWordSpace(self,space):
        self.__textWordSpace = space

    def textOut(self,text):
        canv = self.__canv
        xws = self.__textWordSpace
        if not xws:
            canv.drawString(self.__textX,self.__textY,text)
            self.__textX += stringWidth(text,self.fontName,self.fontSize)+xws
        else:
            x = self.__textX
            y = self.__textY
            ws = stringWidth(' ',self.fontName,self.fontSize)+xws
            ww = 0
            for w in text.split():
                canv.drawString(x,y,w)
                ww = stringWidth(w,self.fontName,self.fontSize)+ws
                x += ww
            self.__textX = x-ww

    def __getattr__(self,name):
        if not (name.startswith('_CanvasAdapter__') or name.startswith('_%s__'%self.__class__.__name__)):
            try:
                if name in self.__transKeys:
                    t = self.__trans[name]
                    if callable(t): return t(self.__canv,name)
                    else:
                        return getattr(self.__canv,t)
            except:
                raise AttributeError('No attribute "%s"' % name)
        return getattr(self.__canv,name)

    def __setattr__(self,name,value):
        if name.startswith('_CanvasAdapter__') or name.startswith('_%s__'% self.__class__.__name__):
            self.__dict__[name] = value
        elif name in self.__transKeys:
            t = self.__trans[name]
            if callable(t): t(self.__canv,name,value)
            else:
                setattr(self.__canv,t,value)
        else:
            setattr(self.__canv,name,value)

    def _getCanvas(self):
        return self.__canv

class PMCanvasAdapter(CanvasAdapter):
    __TRANS = {
                'strokeDashArray': 'dashArray',
                'strokeLineCap': 'lineCap',
                'strokeLineJoin': 'lineJoin',
                }
    def __init__(self,pmCanv):
        D = self.__TRANS
        if not D.has_key('fontName'):
            def _(canv,name,*args):
                if args:
                    canv.setFont(args[0],canv.fontSize)
                else:
                    return canv.fontName
            D['fontName'] = D['_fontname'] = _
            def _(canv,name,*args):
                if args:
                    canv.setFont(canv.fontName,args[0])
                else:
                    return canv.fontSize
            D['fontSize'] = D['_fontsize'] = _
            def _(canv,name,*args):
                pass
            D['_leading'] = _   #ignored
        ctm = pmCanv.ctm
        CanvasAdapter.__init__(self,pmCanv,D)
        self.ctm = ctm

    def transform(self, a,b,c,d,e,f):
        a0,b0,c0,d0,e0,f0 = self.ctm
        self.ctm = a0*a+c0*b, b0*a+d0*b, a0*c+c0*d, b0*c+d0*d, a0*e+c0*f+e0, b0*e+d0*f+f0

    def translate(self,dx,dy):
        self.transform(1,0,0,1,dx,dy)

class PSCanvasAdapter(CanvasAdapter):
    __TRANS = {
                'strokeLineCap': '_lineCap',
                'strokeLineJoin': '_lineJoin',
                'fontSize': '_fontSize',
                }
    def __init__(self,psSepCanv):
        D = self.__TRANS
        if not D.has_key('fontName'):
            def _(canv,name,*args):
                if args:
                    canv.setFont(args[0],canv._fontSize)
                else:
                    return canv._font
            D['fontName'] = D['_fontname'] = D['_font'] = _
            def _(canv,name,*args):
                if args:
                    canv.setFont(canv._font,args[0])
                else:
                    return canv._fontSize
            D['fontSize'] = D['_fontsize'] = _
            def _(canv,name,*args):
                pass
            D['_leading'] = _   #ignored
            def _(canv,name,*args):
                if args:
                    canv.setDash(args[0])
                    self.__strokeDashArray = args[0]
                else:
                    return self.__strokeDashArray
            D['strokeDashArray'] = _
            def _(canv,name,*args):
                if args:
                    self.__ctm = args[0]
                else:
                    return self.__ctm
            D['ctm'] = _
        CanvasAdapter.__init__(self,psSepCanv,D)

    def rect(self,x1,y1,w,h,stroke=1,fill=1):
        self._getCanvas().rect(x1,y1,x1+w,y1+h,stroke=stroke,fill=fill)

    def transform(self, a,b,c,d,e,f):
        a0,b0,c0,d0,e0,f0 = self.ctm
        _ = self.ctm = a0*a+c0*b, b0*a+d0*b, a0*c+c0*d, b0*c+d0*d, a0*e+c0*f+e0, b0*e+d0*f+f0
        self._getCanvas().transform(*_)

    def translate(self,dx,dy):
        a0,b0,c0,d0,e0,f0 = self.ctm
        self.ctm = a0, b0, c0, d0, a0*dx+c0*dy+e0, b0*dx+d0*dy+f0
        self._getCanvas().translate(dx,dy)

from reportlab.platypus.flowables import Flowable
class FlowableCanvasAdapter(Flowable):
    '''Adapts a Flowable to it's drawing canvas'''
    def __init__(self,F):
        self.__F = F

    def wrap(self,aW,aH):
        try:
            return self.__w, self.__h
        except AttributeError:
            self.__w, self.__h = self.__F.wrap(aW,aH)
            return self.__w, self.__h

    def split(self,aW,aH):
        return self.__F.split(aW,aH)


if __name__=='__main__':
    import sys
    from reportlab.pdfgen.canvas import Canvas
    from reportlab.graphics.renderPM import PMCanvas
    from rlextra.graphics.renderPS_SEP import PSCanvas_SEP
    def getExemplar(n):
        if n==1:
            from chargestable import ChargesTable
            F = ChargesTable().asFlowable()
        elif n==2:
            from chargestable import JumboChargesTable
            F = JumboChargesTable().asFlowable()
        else:
            from reportlab.platypus.tables import Table, TableStyle
            from reportlab.platypus.paragraph import Paragraph
            from reportlab.platypus.flowables import CallerMacro
            from reportlab.lib.styles import getSampleStyleSheet
            from reportlab.lib.colors import pink, blue, lightgrey, red
            class callMacroAction:
                def __init__(self,msg):
                    self._msg = msg
                def __call__(self,macro,*args):
                    canv = macro.canv
                    ctm = getattr(canv,'ctm',None) or getattr(canv,'_currentMatrix')
                    print self._msg, ctm
            P0 = Paragraph('<b>Hello</b><font color="green">World</font>! This is the end! This is a<sub><font color="red">i</font></sub> subscript.',
                    style = getSampleStyleSheet()['Normal'])
            T0 = Table([ ['','Hello World'],
                        ['1','2'],
                        ],
                        style=TableStyle([
                            ('GRID',(0,0),(-1,-1),1,blue),
                            ('BACKGROUND',(0,0),(-1,0),lightgrey),
                            ('ALIGN',(0,1),(-1,1),'RIGHT'),
                            ]),
                        )
            F = Table([ ['ABCDEF','GHIJKL'],
                        ['next to a Para',[CallerMacro(callMacroAction('before drawing P0')),P0,CallerMacro(callMacroAction('after drawing P0'))]],
                        ['next to a TABLE',T0],
                        ['1','2'],
                        ],
                        colWidths = (None,144),
                        style=TableStyle([
                            ('GRID',(0,0),(-1,-1),0.6,red),
                            ]),
                        )
        return F
    for n in 0,1,2:
        pfx = 'dingo%d.'%n
        F = getExemplar(n)
        w, h = F.wrap(240,10000)
        c = PSCanvasAdapter(PSCanvas_SEP((w+2,h+2)))
        F.drawOn(c,1,1)
        c.save(pfx+'eps')

        F = getExemplar(n)
        w, h = F.wrap(240,10000)
        canv = PMCanvasAdapter(PMCanvas(w+2,h+2,dpi=96))
        F.drawOn(canv,1,1)
        canv.saveToFile(pfx+'gif')

        F = getExemplar(n)
        w, h = F.wrap(240,10000)
        c = Canvas(pfx+'pdf')
        F.drawOn(c,0,0)
        c.save()
