568
|
1 |
###############################################################################
|
|
2 |
# $Log $
|
|
3 |
#
|
|
4 |
#
|
|
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()
|
|
66 |
self.__combined.append(defaults)
|
|
67 |
|
|
68 |
def push(self,delta):
|
|
69 |
"""Take a new state dictionary of changes and push it onto
|
|
70 |
the stack. After doing this, the combined state is accessible
|
|
71 |
through getState()"""
|
|
72 |
|
|
73 |
newstate = self.__combined[-1].copy()
|
|
74 |
for (key, value) in delta.items():
|
|
75 |
if key == 'transform': #do cumulative matrix
|
|
76 |
newstate['transform'] = delta['transform']
|
|
77 |
newstate['ctm'] = mmult(self.__combined[-1]['transform'], delta['transform'])
|
|
78 |
else: #just overwrite it
|
|
79 |
newstate[key] = value
|
|
80 |
|
|
81 |
self.__combined.append(newstate)
|
|
82 |
self.__deltas.append(delta)
|
|
83 |
|
|
84 |
def pop(self):
|
|
85 |
"""steps back one, and returns a state dictionary with the
|
|
86 |
deltas to reverse out of wherever you are. Depending
|
|
87 |
on your back endm, you may not need the return value,
|
|
88 |
since you can get the complete state afterwards with getState()"""
|
|
89 |
del self.__combined[-1]
|
|
90 |
newState = self.__combined[-1]
|
|
91 |
lastDelta = self.__deltas[-1]
|
|
92 |
del self.__deltas[-1]
|
|
93 |
#need to diff this against the last one in the state
|
|
94 |
reverseDelta = {}
|
|
95 |
for key, curValue in lastDelta.items():
|
|
96 |
prevValue = newState[key]
|
|
97 |
if prevValue <> curValue:
|
|
98 |
if key == 'transform':
|
|
99 |
reverseDelta[key] = inverse(lastDelta['transform'])
|
|
100 |
else: #just return to previous state
|
|
101 |
reverseDelta[key] = prevValue
|
|
102 |
return reverseDelta
|
|
103 |
|
|
104 |
def getState(self):
|
|
105 |
"returns the complete graphics state at this point"
|
|
106 |
return self.__combined[-1]
|
|
107 |
|
|
108 |
def getCTM(self):
|
|
109 |
"returns the current transformation matrix at this point"""
|
|
110 |
return self.__combined[-1]['ctm']
|
|
111 |
|
|
112 |
|
|
113 |
def testStateTracker():
|
|
114 |
print 'Testing state tracker'
|
|
115 |
defaults = {'fillColor':None, 'strokeColor':None,'fontName':None, 'transform':[1,0,0,1,0,0]}
|
|
116 |
deltas = [
|
|
117 |
{'fillColor':'red'},
|
|
118 |
{'fillColor':'green', 'strokeColor':'blue','fontName':'Times-Roman'},
|
|
119 |
{'transform':[0.5,0,0,0.5,0,0]},
|
|
120 |
{'transform':[0.5,0,0,0.5,2,3]},
|
|
121 |
{'strokeColor':'red'}
|
|
122 |
]
|
|
123 |
|
|
124 |
st = StateTracker(defaults)
|
|
125 |
print 'initial:', st.getState()
|
|
126 |
print
|
|
127 |
for delta in deltas:
|
|
128 |
print 'pushing:', delta
|
|
129 |
st.push(delta)
|
|
130 |
print 'state: ',st.getState(),'\n'
|
|
131 |
|
|
132 |
for delta in deltas:
|
|
133 |
print 'popping:',st.pop()
|
|
134 |
print 'state: ',st.getState(),'\n'
|
|
135 |
|
|
136 |
|
|
137 |
class Renderer:
|
|
138 |
"""Virtual superclass for Pingo renderers."""
|
|
139 |
|
|
140 |
def __init__(self):
|
|
141 |
self._tracker = StateTracker()
|
|
142 |
|
|
143 |
def undefined(self, operation):
|
|
144 |
raise ValueError, "%s operation not defined at superclass class=%s" %(operation, self.__class__)
|
|
145 |
|
|
146 |
def draw(self, drawing, canvas, x, y):
|
|
147 |
"""This is the top level function, which
|
|
148 |
draws the drawing at the given location.
|
|
149 |
The recursive part is handled by drawNode."""
|
|
150 |
self.undefined("draw")
|
|
151 |
|
|
152 |
def drawNode(self, node):
|
|
153 |
"""This is the recursive method called for each node
|
|
154 |
in the tree"""
|
|
155 |
# Undefined here, but with closer analysis probably can be handled in superclass
|
|
156 |
self.undefined("drawNode")
|
|
157 |
|
|
158 |
def drawNodeDispatcher(self, node):
|
|
159 |
"""dispatch on the node's (super) class: shared code"""
|
|
160 |
#print "drawNodeDispatcher", self, node.__class__
|
|
161 |
|
|
162 |
# replace UserNode with its contents
|
|
163 |
if isinstance(node, UserNode):
|
|
164 |
node = node.provideNode()
|
|
165 |
|
|
166 |
#draw the object, or recurse
|
|
167 |
|
|
168 |
if isinstance(node, Line):
|
|
169 |
self.drawLine(node)
|
|
170 |
elif isinstance(node, Rect):
|
|
171 |
self.drawRect(node)
|
|
172 |
elif isinstance(node, Circle):
|
|
173 |
self.drawCircle(node)
|
|
174 |
elif isinstance(node, Ellipse):
|
|
175 |
self.drawEllipse(node)
|
|
176 |
elif isinstance(node, PolyLine):
|
|
177 |
self.drawPolyLine(node)
|
|
178 |
elif isinstance(node, Polygon):
|
|
179 |
self.drawPolygon(node)
|
|
180 |
elif isinstance(node, Path):
|
|
181 |
self.drawPath(node)
|
|
182 |
elif isinstance(node, String):
|
|
183 |
self.drawString(node)
|
|
184 |
elif isinstance(node, Group):
|
|
185 |
for childNode in node.contents:
|
|
186 |
self.drawNode(childNode)
|
|
187 |
elif isinstance(node, Wedge):
|
|
188 |
#print "drawWedge"
|
|
189 |
self.drawWedge(node)
|
|
190 |
else:
|
|
191 |
print 'DrawingError','Unexpected element %s in pingo drawing!' % str(node)
|
|
192 |
#print "done dispatching"
|
|
193 |
|
|
194 |
_restores = {'stroke':'_stroke','stroke_width': '_lineWidth','stroke_linecap':'_lineCap',
|
|
195 |
'stroke_linejoin':'_lineJoin','fill':'_fill','font_family':'_font',
|
|
196 |
'font_size':'_fontSize'}
|
|
197 |
|
|
198 |
def drawWedge(self, wedge):
|
|
199 |
# by default ask the wedge to make a polygon of itself and draw that!
|
|
200 |
#print "drawWedge"
|
|
201 |
polygon = wedge.asPolygon()
|
|
202 |
self.drawPolygon(polygon)
|
|
203 |
|
|
204 |
def drawPath(self, path):
|
|
205 |
polygons = path.asPolygons()
|
|
206 |
for polygon in polygons:
|
|
207 |
self.drawPolygon(polygon)
|
|
208 |
|
|
209 |
def drawRect(self, rect):
|
|
210 |
# could be implemented in terms of polygon
|
|
211 |
self.undefined("drawRect")
|
|
212 |
|
|
213 |
def drawLine(self, line):
|
|
214 |
self.undefined("drawLine")
|
|
215 |
|
|
216 |
def drawCircle(self, circle):
|
|
217 |
self.undefined("drawCircle")
|
|
218 |
|
|
219 |
def drawPolyLine(self, p):
|
|
220 |
self.undefined("drawPolyLine")
|
|
221 |
|
|
222 |
def drawEllipse(self, ellipse):
|
|
223 |
self.undefined("drawEllipse")
|
|
224 |
|
|
225 |
def drawPolygon(self, p):
|
|
226 |
self.undefined("drawPolygon")
|
|
227 |
|
|
228 |
def drawString(self, stringObj):
|
|
229 |
self.undefined("drawString")
|
|
230 |
|
|
231 |
def applyStateChanges(self, delta, newState):
|
|
232 |
"""This takes a set of states, and outputs the operators
|
|
233 |
needed to set those properties"""
|
|
234 |
self.undefined("applyStateChanges")
|
|
235 |
|
|
236 |
if __name__=='__main__':
|
|
237 |
print "this file has no script interpretation"
|
|
238 |
print __doc__
|