first working version
[yourule] / SVGdraw.py
1 #!/usr/bin/env python
2 ##Copyright (c) 2002, Fedor Baart & Hans de Wit (Stichting Farmaceutische Kengetallen)
3 ##All rights reserved.
4 ##
5 ##Redistribution and use in source and binary forms, with or without modification,
6 ##are permitted provided that the following conditions are met:
7 ##
8 ##Redistributions of source code must retain the above copyright notice, this
9 ##list of conditions and the following disclaimer.
10 ##
11 ##Redistributions in binary form must reproduce the above copyright notice,
12 ##this list of conditions and the following disclaimer in the documentation and/or
13 ##other materials provided with the distribution.
14 ##
15 ##Neither the name of the Stichting Farmaceutische Kengetallen nor the names of
16 ##its contributors may be used to endorse or promote products derived from this
17 ##software without specific prior written permission.
18 ##
19 ##THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 ##AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 ##IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 ##DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
23 ##FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 ##DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 ##SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 ##CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 ##OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 ##OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 ##Thanks to Gerald Rosennfellner for his help and useful comments.
31
32 __doc__="""Use SVGdraw to generate your SVGdrawings.
33
34 SVGdraw uses an object model drawing and a method toXML to create SVG graphics
35 by using easy to use classes and methods usualy you start by creating a drawing eg
36
37     d=drawing()
38     #then you create a SVG root element
39     s=svg()
40     #then you add some elements eg a circle and add it to the svg root element
41     c=circle()
42     #you can supply attributes by using named arguments.
43     c=circle(fill='red',stroke='blue')
44     #or by updating the attributes attribute:
45     c.attributes['stroke-width']=1
46     s.addElement(c)
47     #then you add the svg root element to the drawing
48     d.setSVG(s)
49     #and finaly you xmlify the drawing
50     d.toXml()
51     
52
53 this results in the svg source of the drawing, which consists of a circle
54 on a white background. Its as easy as that;)
55 This module was created using the SVG specification of www.w3c.org and the
56 O'Reilly (www.oreilly.com) python books as information sources. A svg viewer
57 is available from www.adobe.com"""
58
59 __version__="1.0"
60
61 # there are two possibilities to generate svg:
62 # via a dom implementation and directly using <element>text</element> strings
63 # the latter is way faster (and shorter in coding)
64 # the former is only used in debugging svg programs
65 # maybe it will be removed alltogether after a while
66 # with the following variable you indicate whether to use the dom implementation
67 # Note that PyXML is required for using the dom implementation.
68 # It is also possible to use the standard minidom. But I didn't try that one.
69 # Anyway the text based approach is about 60 times faster than using the full dom implementation.
70 use_dom_implementation=0
71
72
73 import exceptions
74 if use_dom_implementation<>0:
75     try:
76         from xml.dom import implementation
77         from xml.dom.ext import PrettyPrint
78     except:
79         raise exceptions.ImportError, "PyXML is required for using the dom implementation"
80 #The implementation is used for the creating the XML document.
81 #The prettyprint module is used for converting the xml document object to a xml file
82
83 import sys
84 assert sys.version_info[0]>=2
85 if sys.version_info[1]<2:
86     True=1
87     False=0
88     file=open
89     
90 sys.setrecursionlimit=50
91 #The recursion limit is set conservative so mistakes like s=svg() s.addElement(s)
92 #won't eat up too much processor time.
93
94 #the following code is pasted form xml.sax.saxutils
95 #it makes it possible to run the code without the xml sax package installed
96 #To make it possible to have <rubbish> in your text elements, it is necessary to escape the texts
97 def _escape(data, entities={}):
98     """Escape &, <, and > in a string of data.
99
100     You can escape other strings of data by passing a dictionary as
101     the optional entities parameter.  The keys and values must all be
102     strings; each key will be replaced with its corresponding value.
103     """
104     data = data.replace("&", "&amp;")
105     data = data.replace("<", "&lt;")
106     data = data.replace(">", "&gt;")
107     for chars, entity in entities.items():
108         data = data.replace(chars, entity)
109     return data
110
111 def _quoteattr(data, entities={}):
112     """Escape and quote an attribute value.
113
114     Escape &, <, and > in a string of data, then quote it for use as
115     an attribute value.  The \" character will be escaped as well, if
116     necessary.
117
118     You can escape other strings of data by passing a dictionary as
119     the optional entities parameter.  The keys and values must all be
120     strings; each key will be replaced with its corresponding value.
121     """
122     data = _escape(data, entities)
123     if '"' in data:
124         if "'" in data:
125             data = '"%s"' % data.replace('"', "&quot;")
126         else:
127             data = "'%s'" % data
128     else:
129         data = '"%s"' % data
130     return data
131
132
133
134 def _xypointlist(a):
135     """formats a list of xy pairs"""
136     s=''
137     for e in a: #this could be done more elegant
138         s+=str(e)[1:-1] +'  '
139     return s
140
141 def _viewboxlist(a):
142     """formats a tuple"""
143     s=''
144     for e in a: 
145         s+=str(e)+' '
146     return s
147
148 def _pointlist(a):
149     """formats a list of numbers"""
150     return str(a)[1:-1]
151
152 class pathdata:
153     """class used to create a pathdata object which can be used for a path.
154     although most methods are pretty straightforward it might be useful to look at the SVG specification."""
155     #I didn't test the methods below. 
156     def __init__(self,x=None,y=None):
157         self.path=[]
158         if x is not None and y is not None:
159             self.path.append('M '+str(x)+' '+str(y))
160     def closepath(self):
161         """ends the path"""
162         self.path.append('z')
163     def move(self,x,y):
164         """move to absolute"""
165         self.path.append('M '+str(x)+' '+str(y))
166     def relmove(self,x,y):
167         """move to relative"""
168         self.path.append('m '+str(x)+' '+str(y))
169     def line(self,x,y):
170         """line to absolute"""
171         self.path.append('L '+str(x)+' '+str(y))
172     def relline(self,x,y):
173         """line to relative"""
174         self.path.append('l '+str(x)+' '+str(y))
175     def hline(self,x):
176         """horizontal line to absolute"""
177         self.path.append('H'+str(x))
178     def relhline(self,x):
179         """horizontal line to relative"""
180         self.path.append('h'+str(x))
181     def vline(self,y):
182         """verical line to absolute"""
183         self.path.append('V'+str(y))
184     def relvline(self,y):
185         """vertical line to relative"""
186         self.path.append('v'+str(y))
187     def bezier(self,x1,y1,x2,y2,x,y):
188         """bezier with xy1 and xy2 to xy absolut"""
189         self.path.append('C'+str(x1)+','+str(y1)+' '+str(x2)+','+str(y2)+' '+str(x)+','+str(y))
190     def relbezier(self,x1,y1,x2,y2,x,y):
191         """bezier with xy1 and xy2 to xy relative"""
192         self.path.append('c'+str(x1)+','+str(y1)+' '+str(x2)+','+str(y2)+' '+str(x)+','+str(y))
193     def smbezier(self,x2,y2,x,y):
194         """smooth bezier with xy2 to xy absolut"""
195         self.path.append('S'+str(x2)+','+str(y2)+' '+str(x)+','+str(y))
196     def relsmbezier(self,x2,y2,x,y):
197         """smooth bezier with xy2 to xy relative"""
198         self.path.append('s'+str(x2)+','+str(y2)+' '+str(x)+','+str(y))
199     def qbezier(self,x1,y1,x,y):
200         """quadratic bezier with xy1 to xy absolut"""
201         self.path.append('Q'+str(x1)+','+str(y1)+' '+str(x)+','+str(y))
202     def relqbezier(self,x1,y1,x,y):
203         """quadratic bezier with xy1 to xy relative"""
204         self.path.append('q'+str(x1)+','+str(y1)+' '+str(x)+','+str(y))
205     def smqbezier(self,x,y):
206         """smooth quadratic bezier to xy absolut"""
207         self.path.append('T'+str(x)+','+str(y))
208     def relsmqbezier(self,x,y):
209         """smooth quadratic bezier to xy relative"""
210         self.path.append('t'+str(x)+','+str(y))
211     def ellarc(self,rx,ry,xrot,laf,sf,x,y):
212         """elliptival arc with rx and ry rotating with xrot using large-arc-flag and sweep-flag  to xy absolut"""
213         self.path.append('A'+str(rx)+','+str(ry)+' '+str(xrot)+' '+str(laf)+' '+str(sf)+' '+str(x)+' '+str(y))
214     def relellarc(self,rx,ry,xrot,laf,sf,x,y):
215         """elliptival arc with rx and ry rotating with xrot using large-arc-flag and sweep-flag  to xy relative"""
216         self.path.append('a'+str(rx)+','+str(ry)+' '+str(xrot)+' '+str(laf)+' '+str(sf)+' '+str(x)+' '+str(y))
217     def __repr__(self):
218         return ' '.join(self.path)
219     
220
221
222       
223 class SVGelement:
224     """SVGelement(type,attributes,elements,text,namespace,**args)
225     Creates a arbitrary svg element and is intended to be subclassed not used on its own.
226     This element is the base of every svg element it defines a class which resembles
227     a xml-element. The main advantage of this kind of implementation is that you don't
228     have to create a toXML method for every different graph object. Every element
229     consists of a type, attribute, optional subelements, optional text and an optional
230     namespace. Note the elements==None, if elements = None:self.elements=[] construction.
231     This is done because if you default to elements=[] every object has a reference
232     to the same empty list."""
233     def __init__(self,type='',attributes=None,elements=None,text='',namespace='',cdata=None,**args):
234         self.type=type
235         if attributes==None:
236             self.attributes={}
237         else:
238             self.attributes=attributes
239         if elements==None:
240             self.elements=[]
241         else:
242             self.elements=elements
243         self.text=text
244         self.namespace=namespace
245         self.cdata=cdata
246         for arg in args.keys():
247             self.attributes[arg]=args[arg]
248     def addElement(self,SVGelement):
249         """adds an element to a SVGelement
250
251         SVGelement.addElement(SVGelement)
252         """
253         self.elements.append(SVGelement)
254
255     def toXml(self,level,f):
256         f.write('\t'*level)
257         f.write('<'+self.type)
258         for attkey in self.attributes.keys():
259             f.write(' '+_escape(str(attkey))+'='+_quoteattr(str(self.attributes[attkey])))
260         if self.namespace:
261             f.write(' xmlns="'+ _escape(str(self.namespace))+'" ')
262         if self.elements or self.text or self.cdata:
263             f.write('>')
264         if self.elements:
265             f.write('\n')
266         for element in self.elements:
267             element.toXml(level+1,f)
268         if self.cdata:
269             f.write('\n'+'\t'*(level+1)+'<![CDATA[')
270             for line in self.cdata.splitlines():
271                f.write('\n'+'\t'*(level+2)+line)
272             f.write('\n'+'\t'*(level+1)+']]>\n')
273         if self.text:
274             if type(self.text)==type(''): #If the text is only text
275                 f.write(_escape(str(self.text)))
276             else:                         #If the text is a spannedtext class
277                 f.write(str(self.text))
278         if self.elements:
279             f.write('\t'*level+'</'+self.type+'>\n')
280         elif self.text: 
281             f.write('</'+self.type+'>\n')
282         elif self.cdata:
283             f.write('\t'*level+'</'+self.type+'>\n')
284         else:
285             f.write('/>\n')
286             
287 class tspan(SVGelement):
288     """ts=tspan(text='',**args)
289
290     a tspan element can be used for applying formatting to a textsection
291     usage:
292     ts=tspan('this text is bold')
293     ts.attributes['font-weight']='bold'
294     st=spannedtext()
295     st.addtspan(ts)
296     t=text(3,5,st)
297     """
298     def __init__(self,text=None,**args):
299         SVGelement.__init__(self,'tspan',**args)
300         if self.text<>None:
301             self.text=text
302     def __repr__(self):
303         s="<tspan"
304         for key,value in self.attributes.items():
305          s+= ' %s="%s"' % (key,value)
306         s+='>'
307         s+=self.text
308         s+='</tspan>'
309         return s
310     
311 class tref(SVGelement):
312     """tr=tref(link='',**args)
313
314     a tref element can be used for referencing text by a link to its id.
315     usage:
316     tr=tref('#linktotext')
317     st=spannedtext()
318     st.addtref(tr)
319     t=text(3,5,st)
320     """
321     def __init__(self,link,**args):
322         SVGelement.__init__(self,'tref',{'xlink:href':link},**args)
323     def __repr__(self):
324         s="<tref"
325         
326         for key,value in self.attributes.items():
327          s+= ' %s="%s"' % (key,value)
328         s+='/>'
329         return s
330     
331 class spannedtext:
332     """st=spannedtext(textlist=[])
333
334     a spannedtext can be used for text which consists of text, tspan's and tref's
335     You can use it to add to a text element or path element. Don't add it directly
336     to a svg or a group element.
337     usage:
338     
339     ts=tspan('this text is bold')
340     ts.attributes['font-weight']='bold'
341     tr=tref('#linktotext')
342     tr.attributes['fill']='red'
343     st=spannedtext()
344     st.addtspan(ts)
345     st.addtref(tr)
346     st.addtext('This text is not bold')
347     t=text(3,5,st)
348     """
349     def __init__(self,textlist=None):
350         if textlist==None:
351             self.textlist=[]
352         else:
353             self.textlist=textlist
354     def addtext(self,text=''):
355         self.textlist.append(text)
356     def addtspan(self,tspan):
357         self.textlist.append(tspan)
358     def addtref(self,tref):
359         self.textlist.append(tref)
360     def __repr__(self):
361         s=""
362         for element in self.textlist:
363             s+=str(element)
364         return s
365     
366 class rect(SVGelement):
367     """r=rect(width,height,x,y,fill,stroke,stroke_width,**args)
368     
369     a rectangle is defined by a width and height and a xy pair 
370     """
371     def __init__(self,x=None,y=None,width=None,height=None,fill=None,stroke=None,stroke_width=None,**args):
372         if width==None or height==None:
373             if width<>None:
374                 raise ValueError, 'height is required'
375             if height<>None:
376                 raise ValueError, 'width is required'
377             else:
378                 raise ValueError, 'both height and width are required'
379         SVGelement.__init__(self,'rect',{'width':width,'height':height},**args)
380         if x<>None:
381             self.attributes['x']=x
382         if y<>None:
383             self.attributes['y']=y
384         if fill<>None:
385             self.attributes['fill']=fill
386         if stroke<>None:
387             self.attributes['stroke']=stroke
388         if stroke_width<>None:
389             self.attributes['stroke-width']=stroke_width
390             
391 class ellipse(SVGelement):
392     """e=ellipse(rx,ry,x,y,fill,stroke,stroke_width,**args)
393
394     an ellipse is defined as a center and a x and y radius.
395     """
396     def __init__(self,cx=None,cy=None,rx=None,ry=None,fill=None,stroke=None,stroke_width=None,**args):
397         if rx==None or ry== None:
398             if rx<>None:
399                 raise ValueError, 'rx is required'
400             if ry<>None:
401                 raise ValueError, 'ry is required'
402             else:
403                 raise ValueError, 'both rx and ry are required'
404         SVGelement.__init__(self,'ellipse',{'rx':rx,'ry':ry},**args)
405         if cx<>None:
406             self.attributes['cx']=cx
407         if cy<>None:
408             self.attributes['cy']=cy
409         if fill<>None:
410             self.attributes['fill']=fill
411         if stroke<>None:
412             self.attributes['stroke']=stroke
413         if stroke_width<>None:
414             self.attributes['stroke-width']=stroke_width
415         
416    
417 class circle(SVGelement):
418     """c=circle(x,y,radius,fill,stroke,stroke_width,**args)
419
420     The circle creates an element using a x, y and radius values eg
421     """
422     def __init__(self,cx=None,cy=None,r=None,fill=None,stroke=None,stroke_width=None,**args):
423         if r==None:
424             raise ValueError, 'r is required'
425         SVGelement.__init__(self,'circle',{'r':r},**args)
426         if cx<>None:
427             self.attributes['cx']=cx
428         if cy<>None:
429             self.attributes['cy']=cy
430         if fill<>None:
431             self.attributes['fill']=fill
432         if stroke<>None:
433             self.attributes['stroke']=stroke
434         if stroke_width<>None:
435             self.attributes['stroke-width']=stroke_width
436
437 class point(circle):
438     """p=point(x,y,color)
439     
440     A point is defined as a circle with a size 1 radius. It may be more efficient to use a
441     very small rectangle if you use many points because a circle is difficult to render.
442     """
443     def __init__(self,x,y,fill='black',**args):
444         circle.__init__(self,x,y,1,fill,**args)
445
446 class line(SVGelement):
447     """l=line(x1,y1,x2,y2,stroke,stroke_width,**args)
448     
449     A line is defined by a begin x,y pair and an end x,y pair
450     """
451     def __init__(self,x1=None,y1=None,x2=None,y2=None,stroke=None,stroke_width=None,**args):
452         SVGelement.__init__(self,'line',**args)
453         if x1<>None:
454             self.attributes['x1']=x1
455         if y1<>None:
456             self.attributes['y1']=y1
457         if x2<>None:
458             self.attributes['x2']=x2
459         if y2<>None:
460             self.attributes['y2']=y2
461         if stroke_width<>None:
462             self.attributes['stroke-width']=stroke_width
463         if stroke<>None:
464             self.attributes['stroke']=stroke
465             
466 class polyline(SVGelement):
467     """pl=polyline([[x1,y1],[x2,y2],...],fill,stroke,stroke_width,**args)
468     
469     a polyline is defined by a list of xy pairs
470     """
471     def __init__(self,points,fill=None,stroke=None,stroke_width=None,**args):
472         SVGelement.__init__(self,'polyline',{'points':_xypointlist(points)},**args)
473         if fill<>None:
474             self.attributes['fill']=fill
475         if stroke_width<>None:
476             self.attributes['stroke-width']=stroke_width
477         if stroke<>None:
478             self.attributes['stroke']=stroke
479
480 class polygon(SVGelement):
481     """pl=polyline([[x1,y1],[x2,y2],...],fill,stroke,stroke_width,**args)
482     
483     a polygon is defined by a list of xy pairs
484     """
485     def __init__(self,points,fill=None,stroke=None,stroke_width=None,**args):
486         SVGelement.__init__(self,'polygon',{'points':_xypointlist(points)},**args)
487         if fill<>None:
488             self.attributes['fill']=fill
489         if stroke_width<>None:
490             self.attributes['stroke-width']=stroke_width
491         if stroke<>None:
492             self.attributes['stroke']=stroke
493
494 class path(SVGelement):
495     """p=path(path,fill,stroke,stroke_width,**args)
496
497     a path is defined by a path object and optional width, stroke and fillcolor
498     """
499     def __init__(self,pathdata,fill=None,stroke=None,stroke_width=None,id=None,**args):
500         SVGelement.__init__(self,'path',{'d':str(pathdata)},**args)
501         if stroke<>None:
502             self.attributes['stroke']=stroke
503         if fill<>None:
504             self.attributes['fill']=fill
505         if stroke_width<>None:
506             self.attributes['stroke-width']=stroke_width
507         if id<>None:
508             self.attributes['id']=id
509         
510         
511 class text(SVGelement):
512     """t=text(x,y,text,font_size,font_family,**args)
513     
514     a text element can bge used for displaying text on the screen
515     """
516     def __init__(self,x=None,y=None,text=None,font_size=None,font_family=None,text_anchor=None,**args):
517         SVGelement.__init__(self,'text',**args)
518         if x<>None:
519             self.attributes['x']=x
520         if y<>None:
521             self.attributes['y']=y
522         if font_size<>None:
523             self.attributes['font-size']=font_size
524         if font_family<>None:
525             self.attributes['font-family']=font_family
526         if text<>None:
527             self.text=text
528         if text_anchor<>None:
529             self.attributes['text-anchor']=text_anchor
530
531
532 class textpath(SVGelement):
533     """tp=textpath(text,link,**args)
534
535     a textpath places a text on a path which is referenced by a link.   
536     """
537     def __init__(self,link,text=None,**args):
538         SVGelement.__init__(self,'textPath',{'xlink:href':link},**args)
539         if text<>None:
540             self.text=text
541
542 class pattern(SVGelement):
543     """p=pattern(x,y,width,height,patternUnits,**args)
544
545     A pattern is used to fill or stroke an object using a pre-defined
546     graphic object which can be replicated ("tiled") at fixed intervals
547     in x and y to cover the areas to be painted.
548     """
549     def __init__(self,x=None,y=None,width=None,height=None,patternUnits=None,**args):
550         SVGelement.__init__(self,'pattern',**args)
551         if x<>None:
552             self.attributes['x']=x
553         if y<>None:
554             self.attributes['y']=y
555         if width<>None:
556             self.attributes['width']=width
557         if height<>None:
558             self.attributes['height']=height
559         if patternUnits<>None:
560             self.attributes['patternUnits']=patternUnits
561
562 class title(SVGelement):
563     """t=title(text,**args)
564     
565     a title is a text element. The text is displayed in the title bar
566     add at least one to the root svg element
567     """
568     def __init__(self,text=None,**args):
569         SVGelement.__init__(self,'title',**args)
570         if text<>None:
571             self.text=text
572
573 class description(SVGelement):
574     """d=description(text,**args)
575     
576     a description can be added to any element and is used for a tooltip
577     Add this element before adding other elements.
578     """
579     def __init__(self,text=None,**args):
580         SVGelement.__init__(self,'desc',**args)
581         if text<>None:
582             self.text=text
583
584 class lineargradient(SVGelement):
585     """lg=lineargradient(x1,y1,x2,y2,id,**args)
586
587     defines a lineargradient using two xy pairs.
588     stop elements van be added to define the gradient colors.
589     """
590     def __init__(self,x1=None,y1=None,x2=None,y2=None,id=None,**args):
591         SVGelement.__init__(self,'linearGradient',**args)
592         if x1<>None:
593             self.attributes['x1']=x1
594         if y1<>None:
595             self.attributes['y1']=y1
596         if x2<>None:
597             self.attributes['x2']=x2
598         if y2<>None:
599             self.attributes['y2']=y2
600         if id<>None:
601             self.attributes['id']=id
602
603 class radialgradient(SVGelement):
604     """rg=radialgradient(cx,cy,r,fx,fy,id,**args)
605
606     defines a radial gradient using a outer circle which are defined by a cx,cy and r and by using a focalpoint.
607     stop elements van be added to define the gradient colors.
608     """
609     def __init__(self,cx=None,cy=None,r=None,fx=None,fy=None,id=None,**args):
610         SVGelement.__init__(self,'radialGradient',**args)
611         if cx<>None:
612             self.attributes['cx']=cx
613         if cy<>None:
614             self.attributes['cy']=cy
615         if r<>None:
616             self.attributes['r']=r
617         if fx<>None:
618             self.attributes['fx']=fx
619         if fy<>None:
620             self.attributes['fy']=fy
621         if id<>None:
622             self.attributes['id']=id
623             
624 class stop(SVGelement):
625     """st=stop(offset,stop_color,**args)
626
627     Puts a stop color at the specified radius
628     """
629     def __init__(self,offset,stop_color=None,**args):
630         SVGelement.__init__(self,'stop',{'offset':offset},**args)
631         if stop_color<>None:
632             self.attributes['stop-color']=stop_color
633             
634 class style(SVGelement):
635     """st=style(type,cdata=None,**args)
636
637     Add a CDATA element to this element for defing in line stylesheets etc..
638     """
639     def __init__(self,type,cdata=None,**args):
640         SVGelement.__init__(self,'style',{'type':type},cdata=cdata, **args)
641         
642             
643 class image(SVGelement):
644     """im=image(url,width,height,x,y,**args)
645
646     adds an image to the drawing. Supported formats are .png, .jpg and .svg.
647     """
648     def __init__(self,url,x=None,y=None,width=None,height=None,**args):
649         if width==None or height==None:
650             if width<>None:
651                 raise ValueError, 'height is required'
652             if height<>None:
653                 raise ValueError, 'width is required'
654             else:
655                 raise ValueError, 'both height and width are required'
656         SVGelement.__init__(self,'image',{'xlink:href':url,'width':width,'height':height},**args)
657         if x<>None:
658             self.attributes['x']=x
659         if y<>None:
660             self.attributes['y']=y
661  
662 class cursor(SVGelement):
663     """c=cursor(url,**args)
664
665     defines a custom cursor for a element or a drawing
666     """
667     def __init__(self,url,**args):
668         SVGelement.__init__(self,'cursor',{'xlink:href':url},**args)
669
670     
671 class marker(SVGelement):
672     """m=marker(id,viewbox,refX,refY,markerWidth,markerHeight,**args)
673     
674     defines a marker which can be used as an endpoint for a line or other pathtypes
675     add an element to it which should be used as a marker.
676     """
677     def __init__(self,id=None,viewBox=None,refx=None,refy=None,markerWidth=None,markerHeight=None,**args):
678         SVGelement.__init__(self,'marker',**args)
679         if id<>None:
680             self.attributes['id']=id
681         if viewBox<>None:
682             self.attributes['viewBox']=_viewboxlist(viewBox)
683         if refx<>None:
684             self.attributes['refX']=refx
685         if refy<>None:
686             self.attributes['refY']=refy
687         if markerWidth<>None:
688             self.attributes['markerWidth']=markerWidth
689         if markerHeight<>None:
690             self.attributes['markerHeight']=markerHeight
691         
692 class group(SVGelement):
693     """g=group(id,**args)
694     
695     a group is defined by an id and is used to contain elements
696     g.addElement(SVGelement)
697     """
698     def __init__(self,id=None,**args):
699         SVGelement.__init__(self,'g',**args)
700         if id<>None:
701             self.attributes['id']=id
702
703 class symbol(SVGelement):
704     """sy=symbol(id,viewbox,**args)
705
706     defines a symbol which can be used on different places in your graph using
707     the use element. A symbol is not rendered but you can use 'use' elements to
708     display it by referencing its id.
709     sy.addElement(SVGelement)
710     """
711     
712     def __init__(self,id=None,viewBox=None,**args):
713         SVGelement.__init__(self,'symbol',**args)
714         if id<>None:
715             self.attributes['id']=id
716         if viewBox<>None:
717             self.attributes['viewBox']=_viewboxlist(viewBox)
718             
719 class defs(SVGelement):
720     """d=defs(**args)
721
722     container for defining elements
723     """
724     def __init__(self,**args):
725         SVGelement.__init__(self,'defs',**args)
726
727 class switch(SVGelement):
728     """sw=switch(**args)
729
730     Elements added to a switch element which are "switched" by the attributes
731     requiredFeatures, requiredExtensions and systemLanguage.
732     Refer to the SVG specification for details.
733     """
734     def __init__(self,**args):
735         SVGelement.__init__(self,'switch',**args)
736
737         
738 class use(SVGelement):
739     """u=use(link,x,y,width,height,**args)
740     
741     references a symbol by linking to its id and its position, height and width
742     """
743     def __init__(self,link,x=None,y=None,width=None,height=None,**args):
744         SVGelement.__init__(self,'use',{'xlink:href':link},**args)
745         if x<>None:
746             self.attributes['x']=x
747         if y<>None:
748             self.attributes['y']=y
749
750         if width<>None:
751             self.attributes['width']=width
752         if height<>None:
753             self.attributes['height']=height
754             
755             
756 class link(SVGelement):
757     """a=link(url,**args)
758
759     a link  is defined by a hyperlink. add elements which have to be linked
760     a.addElement(SVGelement)
761     """
762     def __init__(self,link='',**args):
763         SVGelement.__init__(self,'a',{'xlink:href':link},**args)
764         
765 class view(SVGelement):
766     """v=view(id,**args)
767
768     a view can be used to create a view with different attributes"""
769     def __init__(self,id=None,**args):
770         SVGelement.__init__(self,'view',**args)
771         if id<>None:
772             self.attributes['id']=id
773
774 class script(SVGelement):
775     """sc=script(type,type,cdata,**args)
776
777     adds a script element which contains CDATA to the SVG drawing
778
779     """
780     def __init__(self,type,cdata=None,**args):
781         SVGelement.__init__(self,'script',{'type':type},cdata=cdata,**args)
782         
783 class animate(SVGelement):
784     """an=animate(attribute,from,to,during,**args)
785
786     animates an attribute.    
787     """
788     def __init__(self,attribute,fr=None,to=None,dur=None,**args):
789         SVGelement.__init__(self,'animate',{'attributeName':attribute},**args)
790         if fr<>None:
791             self.attributes['from']=fr
792         if to<>None:
793             self.attributes['to']=to
794         if dur<>None:
795             self.attributes['dur']=dur
796         
797 class animateMotion(SVGelement):
798     """an=animateMotion(pathdata,dur,**args)
799
800     animates a SVGelement over the given path in dur seconds
801     """
802     def __init__(self,pathdata,dur,**args):
803         SVGelement.__init__(self,'animateMotion',**args)
804         if pathdata<>None:
805             self.attributes['path']=str(pathdata)
806         if dur<>None:
807             self.attributes['dur']=dur
808
809 class animateTransform(SVGelement):
810     """antr=animateTransform(type,from,to,dur,**args)
811     
812     transform an element from and to a value.
813     """
814     def __init__(self,type=None,fr=None,to=None,dur=None,**args):
815         SVGelement.__init__(self,'animateTransform',{'attributeName':'transform'},**args)
816         #As far as I know the attributeName is always transform
817         if type<>None:
818             self.attributes['type']=type
819         if fr<>None:
820             self.attributes['from']=fr
821         if to<>None:
822             self.attributes['to']=to
823         if dur<>None:
824             self.attributes['dur']=dur
825 class animateColor(SVGelement):
826     """ac=animateColor(attribute,type,from,to,dur,**args)
827
828     Animates the color of a element
829     """
830     def __init__(self,attribute,type=None,fr=None,to=None,dur=None,**args):
831         SVGelement.__init__(self,'animateColor',{'attributeName':attribute},**args)
832         if type<>None:
833             self.attributes['type']=type
834         if fr<>None:
835             self.attributes['from']=fr
836         if to<>None:
837             self.attributes['to']=to
838         if dur<>None:
839             self.attributes['dur']=dur        
840 class set(SVGelement):
841     """st=set(attribute,to,during,**args)
842     
843     sets an attribute to a value for a
844     """
845     def __init__(self,attribute,to=None,dur=None,**args):
846         SVGelement.__init__(self,'set',{'attributeName':attribute},**args)
847         if to<>None:
848             self.attributes['to']=to
849         if dur<>None:
850             self.attributes['dur']=dur
851
852
853             
854 class svg(SVGelement):
855     """s=svg(viewbox,width,height,**args)
856     
857     a svg or element is the root of a drawing add all elements to a svg element.
858     You can have different svg elements in one svg file
859     s.addElement(SVGelement)
860
861     eg
862     d=drawing()
863     s=svg((0,0,100,100),'100%','100%')
864     c=circle(50,50,20)
865     s.addElement(c)
866     d.setSVG(s)
867     d.toXml()
868     """
869     def __init__(self,viewBox=None, width=None, height=None,**args):
870         SVGelement.__init__(self,'svg',**args)
871         if viewBox<>None:
872             self.attributes['viewBox']=_viewboxlist(viewBox)
873         if width<>None:
874             self.attributes['width']=width
875         if height<>None:
876             self.attributes['height']=height
877         self.namespace="http://www.w3.org/2000/svg"
878         
879 class drawing:
880     """d=drawing()
881
882     this is the actual SVG document. It needs a svg element as a root.
883     Use the addSVG method to set the svg to the root. Use the toXml method to write the SVG
884     source to the screen or to a file
885     d=drawing()
886     d.addSVG(svg)
887     d.toXml(optionalfilename)
888     """
889
890     def __init__(self):
891         self.svg=None
892     def setSVG(self,svg):
893         self.svg=svg
894         #Voeg een element toe aan de grafiek toe.
895     if use_dom_implementation==0:      
896         def toXml(self, filename='',compress=False):
897             import cStringIO
898             xml=cStringIO.StringIO()
899             xml.write("<?xml version='1.0' encoding='UTF-8'?>\n")
900             xml.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd \">\n")      
901             self.svg.toXml(0,xml)
902             if not filename:
903                 if compress:
904                     import gzip
905                     f=cStringIO.StringIO()
906                     zf=gzip.GzipFile(fileobj=f,mode='wb')
907                     zf.write(xml.getvalue())
908                     zf.close()
909                     f.seek(0)
910                     return f.read()
911                 else:
912                     return xml.getvalue()
913             else:
914                 if filename[-4:]=='svgz':
915                     import gzip
916                     f=gzip.GzipFile(filename=filename,mode="wb", compresslevel=9)
917                     f.write(xml.getvalue())
918                     f.close()
919                 else:
920                     f=file(filename,'w')
921                     f.write(xml.getvalue())
922                     f.close()
923
924     else:
925         def toXml(self,filename='',compress=False):
926             """drawing.toXml()        ---->to the screen
927             drawing.toXml(filename)---->to the file
928             writes a svg drawing to the screen or to a file
929             compresses if filename ends with svgz or if compress is true
930             """
931             doctype = implementation.createDocumentType('svg',"-//W3C//DTD SVG 1.0//EN""",'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd ')
932         
933             global root
934             #root is defined global so it can be used by the appender. Its also possible to use it as an arugument but
935             #that is a bit messy.
936             root=implementation.createDocument(None,None,doctype)
937             #Create the xml document.
938             global appender
939             def appender(element,elementroot):
940                 """This recursive function appends elements to an element and sets the attributes
941                 and type. It stops when alle elements have been appended"""
942                 if element.namespace:
943                     e=root.createElementNS(element.namespace,element.type)
944                 else:
945                     e=root.createElement(element.type)
946                 if element.text:
947                     textnode=root.createTextNode(element.text)
948                     e.appendChild(textnode)
949                 for attribute in element.attributes.keys():   #in element.attributes is supported from python 2.2
950                     e.setAttribute(attribute,str(element.attributes[attribute]))
951                 if element.elements:
952                     for el in element.elements:
953                         e=appender(el,e)
954                 elementroot.appendChild(e)
955                 return elementroot
956             root=appender(self.svg,root)
957             if not filename:
958                 import cStringIO
959                 xml=cStringIO.StringIO()
960                 PrettyPrint(root,xml)
961                 if compress:
962                     import gzip
963                     f=cStringIO.StringIO()
964                     zf=gzip.GzipFile(fileobj=f,mode='wb')
965                     zf.write(xml.getvalue())
966                     zf.close()
967                     f.seek(0)
968                     return f.read()
969                 else:
970                     return xml.getvalue()
971             else:
972                 try:
973                     if filename[-4:]=='svgz':
974                         import gzip
975                         import cStringIO
976                         xml=cStringIO.StringIO()
977                         PrettyPrint(root,xml)
978                         f=gzip.GzipFile(filename=filename,mode='wb',compresslevel=9)
979                         f.write(xml.getvalue())
980                         f.close()
981                     else:
982                         f=open(filename,'w')
983                         PrettyPrint(root,f)
984                         f.close()
985                 except:
986                     print "Cannot write SVG file: " + filename
987     def validate(self):
988         try:
989             import xml.parsers.xmlproc.xmlval
990         except:
991             raise exceptions.ImportError,'PyXml is required for validating SVG'
992         svg=self.toXml()
993         xv=xml.parsers.xmlproc.xmlval.XMLValidator()
994         try:
995             xv.feed(svg)
996         except:
997             raise "SVG is not well formed, see messages above"
998         else:
999             print "SVG well formed"
1000 if __name__=='__main__':
1001
1002     
1003     d=drawing()
1004     s=svg((0,0,100,100))
1005     r=rect(-100,-100,300,300,'cyan')
1006     s.addElement(r)
1007     
1008     t=title('SVGdraw Demo')
1009     s.addElement(t)
1010     g=group('animations')
1011     e=ellipse(0,0,5,2)
1012     g.addElement(e)
1013     c=circle(0,0,1,'red')
1014     g.addElement(c)
1015     pd=pathdata(0,-10)
1016     for i in range(6):
1017         pd.relsmbezier(10,5,0,10)
1018         pd.relsmbezier(-10,5,0,10)
1019     an=animateMotion(pd,10)
1020     an.attributes['rotate']='auto-reverse'
1021     an.attributes['repeatCount']="indefinite"
1022     g.addElement(an)
1023     s.addElement(g)
1024     for i in range(20,120,20):
1025         u=use('#animations',i,0)
1026         s.addElement(u)
1027     for i in range(0,120,20):
1028         for j in range(5,105,10):
1029             c=circle(i,j,1,'red','black',.5)
1030             s.addElement(c)
1031     d.setSVG(s)
1032      
1033     print d.toXml()
1034

Benjamin Mako Hill || Want to submit a patch?