diff options
| author | pinky <> | 2006-12-07 13:41:40 +0000 |
|---|---|---|
| committer | pinky <> | 2006-12-07 13:41:40 +0000 |
| commit | 004a0b996ff8f269451e07346f71a129a1f3fbaf (patch) | |
| tree | 48f43c1fc2ca4c1f4d5648e7e73553d3f4ccae06 /bin/reportlab/graphics/renderbase.py | |
New trunk
bzr revid: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
Diffstat (limited to 'bin/reportlab/graphics/renderbase.py')
| -rw-r--r-- | bin/reportlab/graphics/renderbase.py | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/bin/reportlab/graphics/renderbase.py b/bin/reportlab/graphics/renderbase.py new file mode 100644 index 00000000000..51f73f4d2a5 --- /dev/null +++ b/bin/reportlab/graphics/renderbase.py @@ -0,0 +1,315 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/renderbase.py +""" +Superclass for renderers to factor out common functionality and default implementations. +""" + + +__version__=''' $Id $ ''' + +from reportlab.graphics.shapes import * +from reportlab import rl_config + +def inverse(A): + "For A affine 2D represented as 6vec return 6vec version of A**(-1)" + # I checked this RGB + det = float(A[0]*A[3] - A[2]*A[1]) + R = [A[3]/det, -A[1]/det, -A[2]/det, A[0]/det] + return tuple(R+[-R[0]*A[4]-R[2]*A[5],-R[1]*A[4]-R[3]*A[5]]) + +def mmult(A, B): + "A postmultiplied by B" + # I checked this RGB + # [a0 a2 a4] [b0 b2 b4] + # [a1 a3 a5] * [b1 b3 b5] + # [ 1 ] [ 1 ] + # + return (A[0]*B[0] + A[2]*B[1], + A[1]*B[0] + A[3]*B[1], + A[0]*B[2] + A[2]*B[3], + A[1]*B[2] + A[3]*B[3], + A[0]*B[4] + A[2]*B[5] + A[4], + A[1]*B[4] + A[3]*B[5] + A[5]) + + +def getStateDelta(shape): + """Used to compute when we need to change the graphics state. + For example, if we have two adjacent red shapes we don't need + to set the pen color to red in between. Returns the effect + the given shape would have on the graphics state""" + delta = {} + for (prop, value) in shape.getProperties().items(): + if STATE_DEFAULTS.has_key(prop): + delta[prop] = value + return delta + + +class StateTracker: + """Keeps a stack of transforms and state + properties. It can contain any properties you + want, but the keys 'transform' and 'ctm' have + special meanings. The getCTM() + method returns the current transformation + matrix at any point, without needing to + invert matrixes when you pop.""" + def __init__(self, defaults=None): + # one stack to keep track of what changes... + self.__deltas = [] + + # and another to keep track of cumulative effects. Last one in + # list is the current graphics state. We put one in to simplify + # loops below. + self.__combined = [] + if defaults is None: + defaults = STATE_DEFAULTS.copy() + #ensure that if we have a transform, we have a CTM + if defaults.has_key('transform'): + defaults['ctm'] = defaults['transform'] + self.__combined.append(defaults) + + def push(self,delta): + """Take a new state dictionary of changes and push it onto + the stack. After doing this, the combined state is accessible + through getState()""" + + newstate = self.__combined[-1].copy() + for (key, value) in delta.items(): + if key == 'transform': #do cumulative matrix + newstate['transform'] = delta['transform'] + newstate['ctm'] = mmult(self.__combined[-1]['ctm'], delta['transform']) + #print 'statetracker transform = (%0.2f, %0.2f, %0.2f, %0.2f, %0.2f, %0.2f)' % tuple(newstate['transform']) + #print 'statetracker ctm = (%0.2f, %0.2f, %0.2f, %0.2f, %0.2f, %0.2f)' % tuple(newstate['ctm']) + + else: #just overwrite it + newstate[key] = value + + self.__combined.append(newstate) + self.__deltas.append(delta) + + def pop(self): + """steps back one, and returns a state dictionary with the + deltas to reverse out of wherever you are. Depending + on your back end, you may not need the return value, + since you can get the complete state afterwards with getState()""" + del self.__combined[-1] + newState = self.__combined[-1] + lastDelta = self.__deltas[-1] + del self.__deltas[-1] + #need to diff this against the last one in the state + reverseDelta = {} + #print 'pop()...' + for key, curValue in lastDelta.items(): + #print ' key=%s, value=%s' % (key, curValue) + prevValue = newState[key] + if prevValue <> curValue: + #print ' state popping "%s"="%s"' % (key, curValue) + if key == 'transform': + reverseDelta[key] = inverse(lastDelta['transform']) + else: #just return to previous state + reverseDelta[key] = prevValue + return reverseDelta + + def getState(self): + "returns the complete graphics state at this point" + return self.__combined[-1] + + def getCTM(self): + "returns the current transformation matrix at this point""" + return self.__combined[-1]['ctm'] + + def __getitem__(self,key): + "returns the complete graphics state value of key at this point" + return self.__combined[-1][key] + + def __setitem__(self,key,value): + "sets the complete graphics state value of key to value" + self.__combined[-1][key] = value + +def testStateTracker(): + print 'Testing state tracker' + defaults = {'fillColor':None, 'strokeColor':None,'fontName':None, 'transform':[1,0,0,1,0,0]} + deltas = [ + {'fillColor':'red'}, + {'fillColor':'green', 'strokeColor':'blue','fontName':'Times-Roman'}, + {'transform':[0.5,0,0,0.5,0,0]}, + {'transform':[0.5,0,0,0.5,2,3]}, + {'strokeColor':'red'} + ] + + st = StateTracker(defaults) + print 'initial:', st.getState() + print + for delta in deltas: + print 'pushing:', delta + st.push(delta) + print 'state: ',st.getState(),'\n' + + for delta in deltas: + print 'popping:',st.pop() + print 'state: ',st.getState(),'\n' + + +def _expandUserNode(node,canvas): + if isinstance(node, UserNode): + try: + if hasattr(node,'_canvas'): + ocanvas = 1 + else: + node._canvas = canvas + ocanvas = None + onode = node + node = node.provideNode() + finally: + if not ocanvas: del onode._canvas + return node + +class Renderer: + """Virtual superclass for graphics renderers.""" + + def __init__(self): + self._tracker = StateTracker() + + def undefined(self, operation): + raise ValueError, "%s operation not defined at superclass class=%s" %(operation, self.__class__) + + def draw(self, drawing, canvas, x=0, y=0, showBoundary=rl_config._unset_): + """This is the top level function, which draws the drawing at the given + location. The recursive part is handled by drawNode.""" + #stash references for ease of communication + if showBoundary is rl_config._unset_: showBoundary=rl_config.showBoundary + self._canvas = canvas + canvas.__dict__['_drawing'] = self._drawing = drawing + drawing._parent = None + try: + #bounding box + if showBoundary: canvas.rect(x, y, drawing.width, drawing.height) + canvas.saveState() + self.initState(x,y) + self.drawNode(drawing) + self.pop() + canvas.restoreState() + finally: + #remove any circular references + del self._canvas, self._drawing, canvas._drawing, drawing._parent + + def initState(self,x,y): + deltas = STATE_DEFAULTS.copy() + deltas['transform'] = [1,0,0,1,x,y] + self._tracker.push(deltas) + self.applyStateChanges(deltas, {}) + + def pop(self): + self._tracker.pop() + + def drawNode(self, node): + """This is the recursive method called for each node + in the tree""" + # Undefined here, but with closer analysis probably can be handled in superclass + self.undefined("drawNode") + + def drawNodeDispatcher(self, node): + """dispatch on the node's (super) class: shared code""" + + canvas = getattr(self,'_canvas',None) + # replace UserNode with its contents + + try: + node = _expandUserNode(node,canvas) + if hasattr(node,'_canvas'): + ocanvas = 1 + else: + node._canvas = canvas + ocanvas = None + + #draw the object, or recurse + if isinstance(node, Line): + self.drawLine(node) + elif isinstance(node, Image): + self.drawImage(node) + elif isinstance(node, Rect): + self.drawRect(node) + elif isinstance(node, Circle): + self.drawCircle(node) + elif isinstance(node, Ellipse): + self.drawEllipse(node) + elif isinstance(node, PolyLine): + self.drawPolyLine(node) + elif isinstance(node, Polygon): + self.drawPolygon(node) + elif isinstance(node, Path): + self.drawPath(node) + elif isinstance(node, String): + self.drawString(node) + elif isinstance(node, Group): + self.drawGroup(node) + elif isinstance(node, Wedge): + self.drawWedge(node) + else: + print 'DrawingError','Unexpected element %s in drawing!' % str(node) + finally: + if not ocanvas: del node._canvas + + _restores = {'stroke':'_stroke','stroke_width': '_lineWidth','stroke_linecap':'_lineCap', + 'stroke_linejoin':'_lineJoin','fill':'_fill','font_family':'_font', + 'font_size':'_fontSize'} + + def drawGroup(self, group): + # just do the contents. Some renderers might need to override this + # if they need a flipped transform + canvas = getattr(self,'_canvas',None) + for node in group.getContents(): + node = _expandUserNode(node,canvas) + try: + if hasattr(node,'_canvas'): + ocanvas = 1 + else: + node._canvas = canvas + ocanvas = None + node._parent = group + self.drawNode(node) + finally: + del node._parent + if not ocanvas: del node._canvas + + def drawWedge(self, wedge): + # by default ask the wedge to make a polygon of itself and draw that! + #print "drawWedge" + polygon = wedge.asPolygon() + self.drawPolygon(polygon) + + def drawPath(self, path): + polygons = path.asPolygons() + for polygon in polygons: + self.drawPolygon(polygon) + + def drawRect(self, rect): + # could be implemented in terms of polygon + self.undefined("drawRect") + + def drawLine(self, line): + self.undefined("drawLine") + + def drawCircle(self, circle): + self.undefined("drawCircle") + + def drawPolyLine(self, p): + self.undefined("drawPolyLine") + + def drawEllipse(self, ellipse): + self.undefined("drawEllipse") + + def drawPolygon(self, p): + self.undefined("drawPolygon") + + def drawString(self, stringObj): + self.undefined("drawString") + + def applyStateChanges(self, delta, newState): + """This takes a set of states, and outputs the operators + needed to set those properties""" + self.undefined("applyStateChanges") + +if __name__=='__main__': + print "this file has no script interpretation" + print __doc__ |
