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

Benjamin Mako Hill || Want to submit a patch?