author | dinu_gherman |
Wed, 15 May 2002 15:54:35 +0000 | |
changeset 1609 | 34ab63314b38 |
parent 1082 | 53caef7f1366 |
child 1665 | c2ce87221cf7 |
permissions | -rw-r--r-- |
817 | 1 |
#copyright ReportLab Inc. 2000-2001 |
2 |
#see license.txt for license details |
|
3 |
#history http://cvs.sourceforge.net/cgi-bin/cvsweb.cgi/reportlab/graphics/renderbase.py?cvsroot=reportlab |
|
1609
34ab63314b38
Added Images shape class, currently only rendered in PDF.
dinu_gherman
parents:
1082
diff
changeset
|
4 |
#$Header: /tmp/reportlab/reportlab/graphics/renderbase.py,v 1.11 2002/05/15 15:54:35 dinu_gherman Exp $ |
568 | 5 |
""" |
6 |
Superclass for renderers to factor out common functionality and default implementations. |
|
7 |
""" |
|
8 |
||
9 |
||
10 |
__version__=''' $Id $ ''' |
|
11 |
||
12 |
from reportlab.graphics.shapes import * |
|
13 |
||
14 |
def inverse(A): |
|
15 |
"For A affine 2D represented as 6vec return 6vec version of A**(-1)" |
|
16 |
# I checked this RGB |
|
17 |
det = float(A[0]*A[3] - A[2]*A[1]) |
|
18 |
R = [A[3]/det, -A[1]/det, -A[2]/det, A[0]/det] |
|
19 |
return tuple(R+[-R[0]*A[4]-R[2]*A[5],-R[1]*A[4]-R[3]*A[5]]) |
|
20 |
||
21 |
def mmult(A, B): |
|
22 |
"A postmultiplied by B" |
|
23 |
# I checked this RGB |
|
24 |
# [a0 a2 a4] [b0 b2 b4] |
|
25 |
# [a1 a3 a5] * [b1 b3 b5] |
|
26 |
# [ 1 ] [ 1 ] |
|
27 |
# |
|
28 |
return (A[0]*B[0] + A[2]*B[1], |
|
29 |
A[1]*B[0] + A[3]*B[1], |
|
30 |
A[0]*B[2] + A[2]*B[3], |
|
31 |
A[1]*B[2] + A[3]*B[3], |
|
32 |
A[0]*B[4] + A[2]*B[5] + A[4], |
|
33 |
A[1]*B[4] + A[3]*B[5] + A[5]) |
|
34 |
||
35 |
||
36 |
def getStateDelta(shape): |
|
37 |
"""Used to compute when we need to change the graphics state. |
|
38 |
For example, if we have two adjacent red shapes we don't need |
|
39 |
to set the pen color to red in between. Returns the effect |
|
40 |
the given shape would have on the graphics state""" |
|
41 |
delta = {} |
|
42 |
for (prop, value) in shape.getProperties().items(): |
|
43 |
if STATE_DEFAULTS.has_key(prop): |
|
44 |
delta[prop] = value |
|
45 |
return delta |
|
46 |
||
47 |
||
48 |
class StateTracker: |
|
49 |
"""Keeps a stack of transforms and state |
|
50 |
properties. It can contain any properties you |
|
51 |
want, but the keys 'transform' and 'ctm' have |
|
52 |
special meanings. The getCTM() |
|
53 |
method returns the current transformation |
|
54 |
matrix at any point, without needing to |
|
55 |
invert matrixes when you pop.""" |
|
56 |
def __init__(self, defaults=None): |
|
57 |
# one stack to keep track of what changes... |
|
58 |
self.__deltas = [] |
|
59 |
||
60 |
# and another to keep track of cumulative effects. Last one in |
|
61 |
# list is the current graphics state. We put one in to simplify |
|
62 |
# loops below. |
|
63 |
self.__combined = [] |
|
64 |
if defaults is None: |
|
65 |
defaults = STATE_DEFAULTS.copy() |
|
585
e0144950b3e2
Fixes to CTM to support bitmap renderer; extra string rotation
andy_robinson
parents:
580
diff
changeset
|
66 |
#ensure that if we have a transform, we have a CTM |
e0144950b3e2
Fixes to CTM to support bitmap renderer; extra string rotation
andy_robinson
parents:
580
diff
changeset
|
67 |
if defaults.has_key('transform'): |
e0144950b3e2
Fixes to CTM to support bitmap renderer; extra string rotation
andy_robinson
parents:
580
diff
changeset
|
68 |
defaults['ctm'] = defaults['transform'] |
568 | 69 |
self.__combined.append(defaults) |
70 |
||
71 |
def push(self,delta): |
|
72 |
"""Take a new state dictionary of changes and push it onto |
|
73 |
the stack. After doing this, the combined state is accessible |
|
74 |
through getState()""" |
|
75 |
||
76 |
newstate = self.__combined[-1].copy() |
|
77 |
for (key, value) in delta.items(): |
|
78 |
if key == 'transform': #do cumulative matrix |
|
79 |
newstate['transform'] = delta['transform'] |
|
585
e0144950b3e2
Fixes to CTM to support bitmap renderer; extra string rotation
andy_robinson
parents:
580
diff
changeset
|
80 |
newstate['ctm'] = mmult(self.__combined[-1]['ctm'], delta['transform']) |
587 | 81 |
#print 'statetracker transform = (%0.2f, %0.2f, %0.2f, %0.2f, %0.2f, %0.2f)' % tuple(newstate['transform']) |
82 |
#print 'statetracker ctm = (%0.2f, %0.2f, %0.2f, %0.2f, %0.2f, %0.2f)' % tuple(newstate['ctm']) |
|
585
e0144950b3e2
Fixes to CTM to support bitmap renderer; extra string rotation
andy_robinson
parents:
580
diff
changeset
|
83 |
|
568 | 84 |
else: #just overwrite it |
85 |
newstate[key] = value |
|
86 |
||
87 |
self.__combined.append(newstate) |
|
88 |
self.__deltas.append(delta) |
|
89 |
||
90 |
def pop(self): |
|
91 |
"""steps back one, and returns a state dictionary with the |
|
92 |
deltas to reverse out of wherever you are. Depending |
|
594 | 93 |
on your back end, you may not need the return value, |
568 | 94 |
since you can get the complete state afterwards with getState()""" |
95 |
del self.__combined[-1] |
|
96 |
newState = self.__combined[-1] |
|
97 |
lastDelta = self.__deltas[-1] |
|
98 |
del self.__deltas[-1] |
|
99 |
#need to diff this against the last one in the state |
|
100 |
reverseDelta = {} |
|
594 | 101 |
#print 'pop()...' |
568 | 102 |
for key, curValue in lastDelta.items(): |
604 | 103 |
#print ' key=%s, value=%s' % (key, curValue) |
568 | 104 |
prevValue = newState[key] |
105 |
if prevValue <> curValue: |
|
594 | 106 |
#print ' state popping "%s"="%s"' % (key, curValue) |
568 | 107 |
if key == 'transform': |
108 |
reverseDelta[key] = inverse(lastDelta['transform']) |
|
109 |
else: #just return to previous state |
|
110 |
reverseDelta[key] = prevValue |
|
111 |
return reverseDelta |
|
112 |
||
113 |
def getState(self): |
|
114 |
"returns the complete graphics state at this point" |
|
115 |
return self.__combined[-1] |
|
116 |
||
117 |
def getCTM(self): |
|
118 |
"returns the current transformation matrix at this point""" |
|
119 |
return self.__combined[-1]['ctm'] |
|
120 |
||
121 |
||
122 |
def testStateTracker(): |
|
123 |
print 'Testing state tracker' |
|
124 |
defaults = {'fillColor':None, 'strokeColor':None,'fontName':None, 'transform':[1,0,0,1,0,0]} |
|
125 |
deltas = [ |
|
126 |
{'fillColor':'red'}, |
|
127 |
{'fillColor':'green', 'strokeColor':'blue','fontName':'Times-Roman'}, |
|
128 |
{'transform':[0.5,0,0,0.5,0,0]}, |
|
129 |
{'transform':[0.5,0,0,0.5,2,3]}, |
|
130 |
{'strokeColor':'red'} |
|
131 |
] |
|
132 |
||
133 |
st = StateTracker(defaults) |
|
134 |
print 'initial:', st.getState() |
|
135 |
||
136 |
for delta in deltas: |
|
137 |
print 'pushing:', delta |
|
138 |
st.push(delta) |
|
139 |
print 'state: ',st.getState(),'\n' |
|
140 |
||
141 |
for delta in deltas: |
|
142 |
print 'popping:',st.pop() |
|
143 |
print 'state: ',st.getState(),'\n' |
|
144 |
||
145 |
||
146 |
class Renderer: |
|
580
62e61180ae41
Added postscript renderer and tests, fixed renderer bugs
andy_robinson
parents:
568
diff
changeset
|
147 |
"""Virtual superclass for graphics renderers.""" |
568 | 148 |
|
149 |
def __init__(self): |
|
150 |
self._tracker = StateTracker() |
|
151 |
||
152 |
def undefined(self, operation): |
|
153 |
raise ValueError, "%s operation not defined at superclass class=%s" %(operation, self.__class__) |
|
154 |
||
155 |
def draw(self, drawing, canvas, x, y): |
|
156 |
"""This is the top level function, which |
|
157 |
draws the drawing at the given location. |
|
158 |
The recursive part is handled by drawNode.""" |
|
604 | 159 |
print 'enter Renderer.draw()' |
568 | 160 |
self.undefined("draw") |
604 | 161 |
print 'enter Renderer.draw()' |
162 |
||
568 | 163 |
def drawNode(self, node): |
164 |
"""This is the recursive method called for each node |
|
165 |
in the tree""" |
|
166 |
# Undefined here, but with closer analysis probably can be handled in superclass |
|
167 |
self.undefined("drawNode") |
|
168 |
||
169 |
def drawNodeDispatcher(self, node): |
|
170 |
"""dispatch on the node's (super) class: shared code""" |
|
171 |
#print "drawNodeDispatcher", self, node.__class__ |
|
172 |
||
173 |
# replace UserNode with its contents |
|
174 |
if isinstance(node, UserNode): |
|
175 |
node = node.provideNode() |
|
176 |
||
177 |
#draw the object, or recurse |
|
178 |
||
179 |
if isinstance(node, Line): |
|
180 |
self.drawLine(node) |
|
1609
34ab63314b38
Added Images shape class, currently only rendered in PDF.
dinu_gherman
parents:
1082
diff
changeset
|
181 |
elif isinstance(node, Image): |
34ab63314b38
Added Images shape class, currently only rendered in PDF.
dinu_gherman
parents:
1082
diff
changeset
|
182 |
self.drawImage(node) |
568 | 183 |
elif isinstance(node, Rect): |
184 |
self.drawRect(node) |
|
185 |
elif isinstance(node, Circle): |
|
186 |
self.drawCircle(node) |
|
187 |
elif isinstance(node, Ellipse): |
|
188 |
self.drawEllipse(node) |
|
189 |
elif isinstance(node, PolyLine): |
|
190 |
self.drawPolyLine(node) |
|
191 |
elif isinstance(node, Polygon): |
|
192 |
self.drawPolygon(node) |
|
193 |
elif isinstance(node, Path): |
|
194 |
self.drawPath(node) |
|
195 |
elif isinstance(node, String): |
|
196 |
self.drawString(node) |
|
197 |
elif isinstance(node, Group): |
|
585
e0144950b3e2
Fixes to CTM to support bitmap renderer; extra string rotation
andy_robinson
parents:
580
diff
changeset
|
198 |
self.drawGroup(node) |
568 | 199 |
elif isinstance(node, Wedge): |
200 |
#print "drawWedge" |
|
201 |
self.drawWedge(node) |
|
202 |
else: |
|
580
62e61180ae41
Added postscript renderer and tests, fixed renderer bugs
andy_robinson
parents:
568
diff
changeset
|
203 |
print 'DrawingError','Unexpected element %s in drawing!' % str(node) |
568 | 204 |
#print "done dispatching" |
205 |
||
206 |
_restores = {'stroke':'_stroke','stroke_width': '_lineWidth','stroke_linecap':'_lineCap', |
|
207 |
'stroke_linejoin':'_lineJoin','fill':'_fill','font_family':'_font', |
|
208 |
'font_size':'_fontSize'} |
|
585
e0144950b3e2
Fixes to CTM to support bitmap renderer; extra string rotation
andy_robinson
parents:
580
diff
changeset
|
209 |
|
e0144950b3e2
Fixes to CTM to support bitmap renderer; extra string rotation
andy_robinson
parents:
580
diff
changeset
|
210 |
def drawGroup(self, group): |
e0144950b3e2
Fixes to CTM to support bitmap renderer; extra string rotation
andy_robinson
parents:
580
diff
changeset
|
211 |
# just do the contents. Some renderers might need to override this |
e0144950b3e2
Fixes to CTM to support bitmap renderer; extra string rotation
andy_robinson
parents:
580
diff
changeset
|
212 |
# if they need a flipped transform |
1082 | 213 |
for childNode in group.getContents(): |
592
35f70c45f74c
Fixed user node rendering bug, added barchart1
andy_robinson
parents:
590
diff
changeset
|
214 |
if isinstance(childNode, UserNode): |
35f70c45f74c
Fixed user node rendering bug, added barchart1
andy_robinson
parents:
590
diff
changeset
|
215 |
node2 = childNode.provideNode() |
35f70c45f74c
Fixed user node rendering bug, added barchart1
andy_robinson
parents:
590
diff
changeset
|
216 |
else: |
35f70c45f74c
Fixed user node rendering bug, added barchart1
andy_robinson
parents:
590
diff
changeset
|
217 |
node2 = childNode |
35f70c45f74c
Fixed user node rendering bug, added barchart1
andy_robinson
parents:
590
diff
changeset
|
218 |
self.drawNode(node2) |
585
e0144950b3e2
Fixes to CTM to support bitmap renderer; extra string rotation
andy_robinson
parents:
580
diff
changeset
|
219 |
|
568 | 220 |
def drawWedge(self, wedge): |
221 |
# by default ask the wedge to make a polygon of itself and draw that! |
|
222 |
#print "drawWedge" |
|
223 |
polygon = wedge.asPolygon() |
|
224 |
self.drawPolygon(polygon) |
|
225 |
||
226 |
def drawPath(self, path): |
|
227 |
polygons = path.asPolygons() |
|
228 |
for polygon in polygons: |
|
229 |
self.drawPolygon(polygon) |
|
230 |
||
231 |
def drawRect(self, rect): |
|
232 |
# could be implemented in terms of polygon |
|
233 |
self.undefined("drawRect") |
|
234 |
||
235 |
def drawLine(self, line): |
|
236 |
self.undefined("drawLine") |
|
237 |
||
238 |
def drawCircle(self, circle): |
|
239 |
self.undefined("drawCircle") |
|
240 |
||
241 |
def drawPolyLine(self, p): |
|
242 |
self.undefined("drawPolyLine") |
|
243 |
||
244 |
def drawEllipse(self, ellipse): |
|
245 |
self.undefined("drawEllipse") |
|
246 |
||
247 |
def drawPolygon(self, p): |
|
248 |
self.undefined("drawPolygon") |
|
249 |
||
250 |
def drawString(self, stringObj): |
|
251 |
self.undefined("drawString") |
|
252 |
||
253 |
def applyStateChanges(self, delta, newState): |
|
254 |
"""This takes a set of states, and outputs the operators |
|
255 |
needed to set those properties""" |
|
256 |
self.undefined("applyStateChanges") |
|
257 |
||
258 |
if __name__=='__main__': |
|
259 |
print "this file has no script interpretation" |
|
260 |
print __doc__ |