odoo/bin/reportlab/graphics/renderbase.py

352 lines
12 KiB
Python

#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.lib.validators import DerivedValue
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
def renderScaledDrawing(d):
renderScale = d.renderScale
if renderScale!=1.0:
d = d.copy()
d.width *= renderScale
d.height *= renderScale
d.scale(renderScale,renderScale)
d.renderScale = 1.0
return d
class Renderer:
"""Virtual superclass for graphics renderers."""
def __init__(self):
self._tracker = StateTracker()
self._nodeStack = [] #track nodes visited
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) #this is the push()
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 getStateValue(self, key):
"""Return current state parameter for given key"""
currentState = self._tracker._combined[-1]
return currentState[key]
def fillDerivedValues(self, node):
"""Examine a node for any values which are Derived,
and replace them with their calculated values.
Generally things may look at the drawing or their
parent.
"""
for (key, value) in node.__dict__.items():
if isinstance(value, DerivedValue):
#just replace with default for key?
#print ' fillDerivedValues(%s)' % key
newValue = value.getValue(self, key)
#print ' got value of %s' % newValue
node.__dict__[key] = newValue
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
self.fillDerivedValues(node)
#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)
#here is where we do derived values - this seems to get everything. Touch wood.
self.fillDerivedValues(node)
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__