77
84
self.__doc.appendChild(self.__svg)
78
85
self.__defs = self.__doc.createElement('defs') # for clip paths
79
86
self.__svg.appendChild(self.__defs)
80
self.__currElt = self.__svg
87
self.__cur_element = self.__svg
81
88
self.gsave() # create top-level group for dflt styles
82
self._updateStyle(font_family = theme.default_font_family,
83
font_size = theme.default_font_size,
84
font_style = 'normal',
85
font_weight = 'normal',
86
font_stretch = 'normal',
88
stroke = 'rgb(0,0,0)', #SVG dflt none, PS dflt blk
89
stroke_width = theme.default_line_width,
90
stroke_linejoin = 'miter',
91
stroke_linecap = 'butt',
92
stroke_dasharray = 'none')
94
def _updateStyle(self, **addstyledict):
95
elt = _protectCurrentChildren(self.__currElt)
89
self.__update_style(font_family = theme.default_font_family,
90
font_size = theme.default_font_size,
91
font_style = 'normal',
92
font_weight = 'normal',
93
font_stretch = 'normal',
95
stroke = 'rgb(0,0,0)', #SVG dflt none, PS dflt blk
96
stroke_width = theme.default_line_width,
97
stroke_linejoin = 'miter',
98
stroke_linecap = 'butt',
99
stroke_dasharray = 'none')
101
def __update_style(self, **addstyledict):
102
elt = _protect_current_children(self.__cur_element)
97
104
# fetch the current styles for this node
98
mystyledict = _parseStyleStr(elt.getAttribute('style'))
105
my_style_dict = _parse_style_str(elt.getAttribute('style'))
100
# concat all parent style strings to get dflt styles for this node
101
parent,s = elt.parentNode,''
107
# concat all ancestor style strings to get default styles for this node
108
parent, s = elt.parentNode, ''
102
109
while parent.nodeType != Document.nodeType :
103
110
# prepend parent str so later keys will override earlier ones
104
111
s = parent.getAttribute('style') + s
105
112
parent = parent.parentNode
106
dfltstyledict = _parseStyleStr(s)
113
default_style_dict = _parse_style_str(s)
108
115
# Do some pre-processing on the caller-supplied add'l styles
109
116
# Convert '_' to '-' so caller can specify style tags as python
110
117
# variable names, eg. stroke_width => stroke-width.
111
# Also convert all RHS values to strs
118
# Also convert all RHS values to strs
112
119
for key in addstyledict.keys():
113
120
k = re.sub('_','-',key)
114
121
addstyledict[k] = str(addstyledict[key]) # all vals => strs
115
122
if (k != key) : del addstyledict[key]
117
124
for k in addstyledict.keys() :
118
if (mystyledict.has_key(k) or # need to overwrite it
119
(not dfltstyledict.has_key(k)) or # need to set it
120
dfltstyledict[k] != addstyledict[k]) : # need to override it
121
mystyledict[k] = addstyledict[k]
123
s = _makeStyleStr(mystyledict)
125
if (my_style_dict.has_key(k) or # need to overwrite it
126
(not default_style_dict.has_key(k)) or # need to set it
127
default_style_dict[k] != addstyledict[k]) : # need to override it
128
my_style_dict[k] = addstyledict[k]
130
s = _make_style_str(my_style_dict)
124
131
if s : elt.setAttribute('style',s)
133
self.__cur_element = elt
128
135
####################################################################
129
136
# methods below define the pychart backend device API
131
138
# First are a set of methods to start, construct and finalize a path
133
140
def newpath(self): # Start a new path
134
if (self.__currElt.nodeName != 'g') :
141
if (self.__cur_element.nodeName != 'g') :
135
142
raise OverflowError, "No containing group for newpath"
136
143
# Just insert a new 'path' element into the document
137
144
p = self.__doc.createElement('path')
138
self.__currElt.appendChild(p)
145
self.__cur_element.appendChild(p)
146
self.__cur_element = p
141
148
# This set of methods add data to an existing path element,
142
149
# simply add to the 'd' (data) attribute of the path elt
144
def moveto(self, x, y): #
145
if (self.__currElt.nodeName != 'path') :
151
def moveto(self, x, y): #
152
if (self.__cur_element.nodeName != 'path') :
146
153
raise OverflowError, "No path for moveto"
147
d = ' '.join([self.__currElt.getAttribute('d'),'M',`x`,`-y`]).strip()
148
self.__currElt.setAttribute('d', d)
154
d = ' '.join([self.__cur_element.getAttribute('d'),'M',`x`,`-y`]).strip()
155
self.__cur_element.setAttribute('d', d)
149
156
def lineto(self, x, y):
150
if (self.__currElt.nodeName != 'path') :
157
if (self.__cur_element.nodeName != 'path') :
151
158
raise OverflowError, "No path for lineto"
152
d = ' '.join([self.__currElt.getAttribute('d'),'L',`x`,`-y`]).strip()
153
self.__currElt.setAttribute('d', d)
159
d = ' '.join([self.__cur_element.getAttribute('d'),'L',`x`,`-y`]).strip()
160
self.__cur_element.setAttribute('d', d)
154
161
def path_arc(self, x, y, radius, ratio, start_angle, end_angle):
155
162
# mimic PS 'arc' given radius, yr/xr (=eccentricity), start and
156
163
# end angles. PS arc draws from CP (if exists) to arc start,
190
197
d += ' A %g %g 0 %d 0 %g %g' % (radius,radius*ratio,
191
198
end_angle-start_angle>180,
193
self.__currElt.setAttribute('d',d.strip())
200
self.__cur_element.setAttribute('d',d.strip())
194
201
def curveto(self, x1,y1,x2,y2,x3,y3):
195
202
# Equivalent of PostScript's x1 y1 x2 y2 x3 y3 curveto which
196
203
# draws a cubic bezier curve from curr pt to x3,y3 with ctrl points
197
204
# x1,y1, and x2,y2
198
205
# In SVG this is just d='[M x0 y0] C x1 y1 x2 y2 x3 y3'
199
206
#! I can't find an example of this being used to test it
200
if (self.__currElt.nodeNode != 'path') :
207
if (self.__cur_element.nodeNode != 'path') :
201
208
raise OverflowError, "No path for curveto"
202
d = ' '.join([self.__currElt.getAttribute('d'),'C',
209
d = ' '.join([self.__cur_element.getAttribute('d'),'C',
203
210
`x1`,`-y1`,`x2`,`-y2`,`x3`,`-y3`,]).strip()
204
self.__currElt.setAttribute('d', d)
211
self.__cur_element.setAttribute('d', d)
205
212
def closepath(self): # close back to start of path
206
if (self.__currElt.nodeName != 'path') :
213
if (self.__cur_element.nodeName != 'path') :
207
214
raise OverflowError, "No path for closepath"
208
d = ' '.join([self.__currElt.getAttribute('d'),'Z']).strip()
209
self.__currElt.setAttribute('d', d)
215
d = ' '.join([self.__cur_element.getAttribute('d'),'Z']).strip()
216
self.__cur_element.setAttribute('d', d)
211
218
# Next we have three methods for finalizing a path element,
212
219
# either fill it, clip to it, or draw it (stroke)
213
220
# canvas.polygon() can generate fill/clip cmds with
214
221
# no corresponding path so just ignore them
215
222
def stroke(self):
216
if (self.__currElt.nodeName != 'path') :
223
if (self.__cur_element.nodeName != 'path') :
217
224
self.comment('No path - ignoring stroke')
219
self._updateStyle(fill='none')
220
self.__currElt = self.__currElt.parentNode
226
self.__update_style(fill='none')
227
self.__cur_element = self.__cur_element.parentNode
222
if (self.__currElt.nodeName != 'path') :
229
if (self.__cur_element.nodeName != 'path') :
223
230
self.comment('No path - ignoring fill')
225
self._updateStyle(stroke='none')
226
self.__currElt = self.__currElt.parentNode
232
self.__update_style(stroke='none')
233
self.__cur_element = self.__cur_element.parentNode
227
234
def clip_sub(self):
228
if (self.__currElt.nodeName != 'path') :
235
if (self.__cur_element.nodeName != 'path') :
229
236
self.comment('No path - ignoring clip')
232
239
# remove the current path from the tree ...
234
self.__currElt=p.parentNode
235
self.__currElt.removeChild(p)
240
p = self.__cur_element
241
self.__cur_element=p.parentNode
242
self.__cur_element.removeChild(p)
237
244
# ... add it to a clipPath elt in the defs section
238
245
clip = self.__doc.createElement('clipPath')
242
249
self.__defs.appendChild(clip)
244
251
# ... update the local style to point to it
245
self._updateStyle(clip_path = 'url(#%s)'%clipid)
252
self.__update_style(clip_path = 'url(#%s)'%clipid)
247
254
# The text_xxx routines specify the start/end and contents of text
248
255
def text_begin(self):
249
if (self.__currElt.nodeName != 'g') :
256
if (self.__cur_element.nodeName != 'g') :
250
257
raise ValueError, "No group for text block"
251
258
t = self.__doc.createElement('text')
252
self.__currElt.appendChild(t)
259
self.__cur_element.appendChild(t)
260
self.__cur_element = t
254
261
def text_moveto(self, x, y, angle):
255
if (self.__currElt.nodeName != 'text') :
262
if (self.__cur_element.nodeName != 'text') :
256
263
raise ValueError, "No text for moveto"
257
self.__currElt.setAttribute('x',`x`)
258
self.__currElt.setAttribute('y',`-y`)
264
self.__cur_element.setAttribute('x',`x`)
265
self.__cur_element.setAttribute('y',`-y`)
260
self.__currElt.setAttribute('transform',
267
self.__cur_element.setAttribute('transform',
261
268
'rotate(%g,%g,%g)' % (-angle,x,-y))
262
def text_show(self, font_name, size, color, str):
263
if (self.__currElt.nodeName != 'text') :
269
def text_show(self, font_name, size, color, string):
270
if (self.__cur_element.nodeName != 'text') :
264
271
raise ValueError, "No text for show"
266
273
# PyChart constructs a postscript font name, for example:
296
301
#! translate ascii symbol font chars -> unicode (see www.unicode.org)
297
302
#! http://www.unicode.org/Public/MAPPINGS/VENDORS/ADOBE/symbol.txt
298
303
#! but xml Text element writes unicode chars as '?' to XML file...
299
str = re.sub(r'\\([()])',r'\1',str) # unescape brackets
300
self._updateStyle(fill=_svgcolor(color),
304
string = re.sub(r'\\([()])',r'\1',string) # unescape brackets
305
self.__update_style(fill=_svgcolor(color),
302
307
font_family=font_name,
304
309
font_style=font_style,
305
310
font_weight=font_weight,
306
311
font_stretch=font_stretch)
307
self.__currElt.appendChild(self.__doc.createTextNode(str))
312
self.__cur_element.appendChild(self.__doc.createTextNode(string.encode('utf-8')))
308
313
def text_end(self):
309
if (self.__currElt.nodeName != 'text') :
314
if (self.__cur_element.nodeName != 'text') :
310
315
raise ValueError, "No text for close"
311
self.__currElt = self.__currElt.parentNode
316
self.__cur_element = self.__cur_element.parentNode
314
319
# Three methods that change the local style of elements
317
322
# although this may not in general correspond to (say) PostScript
318
323
# behavior, it appears to correspond to reflect mode of use of this API
319
324
def set_fill_color(self, color):
320
self._updateStyle(fill=_svgcolor(color))
325
self.__update_style(fill=_svgcolor(color))
321
326
def set_stroke_color(self, color):
322
self._updateStyle(stroke=_svgcolor(color))
327
self.__update_style(stroke=_svgcolor(color))
323
328
def set_line_style(self, style): # see line_style.py
324
329
linecap = {0:'butt', 1:'round', 2:'square'}
325
330
linejoin = {0:'miter', 1:'round', 2:'bevel'}
326
331
if style.dash: dash = ','.join(map(str,style.dash))
327
332
else : dash = 'none'
328
self._updateStyle(stroke_width = style.width,
333
self.__update_style(stroke_width = style.width,
329
334
stroke = _svgcolor(style.color),
330
335
stroke_linecap = linecap[style.cap_style],
331
336
stroke_linejoin = linejoin[style.join_style],
336
341
# similar but explicitly specify a coordinate transform at the
339
if (self.__currElt.nodeName not in ['g','svg']) :
344
if (self.__cur_element.nodeName not in ['g','svg']) :
340
345
raise ValueError, "No group for gsave"
341
346
g = self.__doc.createElement('g')
342
self.__currElt.appendChild(g)
347
self.__cur_element.appendChild(g)
348
self.__cur_element = g
344
349
def grestore(self):
345
if (self.__currElt.nodeName != 'g'):
350
if (self.__cur_element.nodeName != 'g'):
346
351
raise ValueError, "No group for grestore"
347
352
# first pop off any auto-generated groups (see protectCurrentChildren)
348
while (self.__currElt.hasAttribute('auto')) :
349
self.__currElt.removeAttribute('auto')
350
self.__currElt = self.__currElt.parentNode
353
while (self.__cur_element.hasAttribute('auto')) :
354
self.__cur_element.removeAttribute('auto')
355
self.__cur_element = self.__cur_element.parentNode
351
356
# then pop off the original caller-generated group
352
self.__currElt = self.__currElt.parentNode
357
self.__cur_element = self.__cur_element.parentNode
354
359
def push_transformation(self, baseloc, scale, angle, in_text=0):
355
360
#? in_text arg appears to always be ignored
381
386
t += 'rotate(%g) '%-angle
383
388
t += 'scale(%g,%g) '%tuple(scale)
386
self.__currElt.setAttribute('transform',t.strip())
391
self.__cur_element.setAttribute('transform',t.strip())
387
392
if elt: # elt has incomplete 'path' or None
388
self.__currElt.appendChild(elt)
393
self.__cur_element.appendChild(elt)
394
self.__cur_element = elt
391
396
def pop_transformation(self, in_text=0): #? in_text unused?
394
399
# If verbose, add comments to the output stream (helps debugging)
395
def comment(self, str):
397
self.__currElt.appendChild(self.__doc.createComment(str))
400
def comment(self, string):
402
self.__cur_element.appendChild(self.__doc.createComment(string))
399
404
# The verbatim method is currently not supported - presumably with
400
405
# the SVG backend the user would require access to the DOM since
401
406
# we're not directly outputting plain text here
402
def verbatim(self, str):
403
self.__currElt.appendChild(self.__doc.createComment('verbatim not implemented: ' + str))
407
def verbatim(self, string):
408
self.__cur_element.appendChild(self.__doc.createComment('verbatim not implemented: ' + string))
405
410
# The close() method finalizes the SVG document and flattens the
406
411
# DOM document to XML text to the specified file (or stdout)
408
413
basecanvas.T.close(self)
409
414
self.grestore() # matching the gsave in __init__
410
if (self.__currElt.nodeName != 'svg') :
415
if (self.__cur_element.nodeName != 'svg') :
411
416
raise ValueError, "Incomplete document at close!"
413
418
# Don't bother to output an empty document - this can happen
414
419
# when we get close()d immediately by theme reinit
415
420
if (len(self.__svg.childNodes[-1].childNodes) == 0) :
418
423
fp, need_close = self.open_output(self.__out_fname)
419
424
bbox = theme.adjust_bounding_box([self.__xmin, self.__ymin,
420
425
self.__xmax, self.__ymax])