/[thuban]/trunk/thuban/Extensions/svgexport/svgmapwriter.py
ViewVC logotype

Annotation of /trunk/thuban/Extensions/svgexport/svgmapwriter.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2513 - (hide annotations)
Mon Dec 27 17:00:15 2004 UTC (20 years, 2 months ago) by bernhard
File MIME type: text/x-python
File size: 26690 byte(s)
	svgexport 1.0.0cvs: Fixed label export.

	* Extensions/svgexport/test/test_svgmapwriter.py:
	class TestSVGRenderer(): New class; new test test_label_font().

	* Extensions/svgexport/svgmapwriter.py
	(SVGRenderer.label_font()): initialised Font size with self.factor now,
	makes test_label_font happy.

	* Extensions/svgexport/TODO: crossed out fixed label export item.
	Added item for options.

	* Extensions/svgexport/__init__.py: Bumped version to 1.0.0cvs.

1 bh 2082 # Copyright (c) 2001, 2002, 2003, 2004 by Intevation GmbH
2 bh 2074 # Authors:
3     # Markus Rechtien <[email protected]>
4 bernhard 2352 # Bernhard Reiter <[email protected]>
5 bh 2074 #
6     # This program is free software under the GPL (>=v2)
7     # Read the file COPYING coming with Thuban for details.
8    
9    
10     """
11 bernhard 2388 Classes needed to write a session in SVG format.
12 bh 2074 """
13    
14 bh 2082 # For compatibility with python 2.2
15     from __future__ import generators
16 bh 2074
17 bh 2082
18     __version__ = "$Revision$"
19     # $Source$
20     # $Id$
21    
22 bernhard 2414 import sys
23 bh 2082
24 bernhard 2480 # Verboseness level for debugging. Useful values: 0,1,2,3
25 bernhard 2414 verbose=0
26     log=sys.stdout.write
27    
28 bh 2074 # Regular expressions used with Fontnames
29     import re
30     # Combining strings
31     from string import join
32     # We need to determine some object types
33 bh 2082 from types import ListType
34 bernhard 2352 # for SetBaseID
35     import binascii
36 bernhard 2344
37     from Thuban import _
38 bernhard 2445 from Thuban.Model.data import SHAPETYPE_POINT
39 bh 2074 # VirtualDC extends XMLWriter
40     from Thuban.Model.xmlwriter import XMLWriter, escape
41     # Color related classes from the model of thuban
42 bh 2082 from Thuban.Model.color import Transparent, Black
43 bh 2074 # The SVGRenderer is subclass of BaseRenderer
44     from Thuban.UI.baserenderer import BaseRenderer
45    
46     # Basic font map.
47     fontMap = { "Times" : re.compile("Times-Roman.*"),
48     "Helvetica" : re.compile("Helvetica.*"),
49     "Courier" : re.compile("Courier.*"),
50     }
51    
52     # Possible values for svg line joins.
53     svg_joins = {'miter':'miter', 'round':'round', 'bevel':'bevel'}
54     # Possible values for svg line caps.
55     svg_caps = {'':'', 'butt':'butt', 'round':'round', 'square':'square'}
56    
57     #
58     # Some pseudo classes to be compatible with the Baserenderer-class.
59     #
60     class Point:
61 bernhard 2388 """Simple Point class to save x,y coordinates."""
62 bh 2074 def __init__(self, xp=0, yp=0):
63     self.x = xp
64     self.y = yp
65    
66 bernhard 2480 def __repr__(self):
67     return "Point(%s, %s)" % (str(self.x), str(self.y))
68    
69 bh 2074 class Trafo:
70 bernhard 2388 """Class for transformation properties transfer."""
71 bh 2074 def __init__(self):
72     self.trafos = []
73    
74     def Append(self, type, coeffs):
75 bh 2082 """Append a transformation to the list."""
76 bh 2074 self.trafos.append((type, coeffs))
77    
78     def Count(self):
79 bh 2082 """Get the number of transformations in list."""
80 bh 2074 return len(self.trafos)
81    
82     def Pop(self):
83 bh 2082 """Pop and return a transformation from the end of the list."""
84 bh 2074 if len(self.trafos) > 0:
85     return self.trafos.pop()
86     else: return None
87    
88     class Pattern:
89     def __init__(self, solid=1):
90     self.solid = solid
91    
92     class Pen:
93 bh 2082 """Pen object for property transfer."""
94 bh 2074 def __init__(self, pcolor = Black, pwidth = 1, pdashes = None):
95     self.color = pcolor
96     self.width = pwidth
97     self.dashes = pdashes
98     self.join = 'round'
99     self.cap = 'round'
100 bernhard 2414
101     def __str__(self):
102     return "Pen(%s,%s,%s,%s,%s)" % \
103     (str(self.color), str(self.width), str(self.dashes),
104     str(self.join), str(self.cap))
105 bh 2074
106     def GetColor(self):
107     return self.color
108    
109     def GetWidth(self):
110     return self.width
111    
112     def GetJoin(self):
113     return self.join
114    
115     def GetCap(self):
116     return self.cap
117    
118     def GetDashes(self):
119     if self.dashes is None or self.dashes is SOLID:
120     return []
121     else: return self.dashes
122    
123     class Brush:
124 bh 2082 """Brush property class."""
125 bh 2074 def __init__(self, bfill=Black, bpattern=None):
126 bh 2082 """Init the brush with the given values."""
127 bh 2074 self.fill = bfill
128     self.pattern = bpattern
129 bernhard 2414
130     def __str__(self):
131 bernhard 2480 return "Brush(" + str(self.fill) + ", " + str(self.pattern) + ")"
132 bernhard 2414
133 bh 2074 def GetColor(self):
134     return self.fill
135 bernhard 2414
136 bh 2074 def GetPattern(self):
137     return self.pattern
138    
139     class Font:
140 bh 2082 """Font class that accts as property object."""
141 bh 2074 def __init__(self, ffamily='Helvetica', fsize=12):
142 bh 2082 """Init the font with the given values."""
143 bh 2074 self.family = ffamily
144     self.size = fsize
145    
146     def GetFaceName(self):
147 bh 2082 """Return the fontfamily the font belongs to."""
148 bh 2074 return self.family
149    
150     def GetPointSize(self):
151 bh 2082 """Return the size of the font in points."""
152 bh 2074 return self.size
153    
154     # Instantiate an empty pen.
155     TRANSPARENT_PEN = Pen(None, 0, None)
156     # Instantiate an empty brush.
157     TRANSPARENT_BRUSH = Brush(None, None)
158     # Instantiate a solid pattern.
159     SOLID = Pattern()
160    
161 bernhard 2344 class SVGMapWriterError(Exception):
162     """Get raised for problems when writing map-svg files.
163    
164     Occasion when this exception is raised:
165     Two layers have the same name to be used as BaseId: Name Clash
166     """
167    
168    
169 bh 2074 class SVGRenderer(BaseRenderer):
170     """Class to render a map onto a VirtualDC.
171    
172     This class, derived from BaseRenderer, will render a hole
173     session onto the VirtualDC to write all shapes as SVG code
174     to a file.
175     In opposite to other renderers it includes metadata, such as
176     shape ids and classification, when rendering the shapes.
177     """
178     def __init__(self, dc, map, scale, offset, region,
179     resolution = 1.0, honor_visibility = 1):
180 bh 2082 """Init SVGRenderer and call superclass init."""
181 bh 2074 BaseRenderer.__init__(self, dc, map, scale, offset, region,
182     resolution, honor_visibility)
183     #
184     self.factor = (abs(region[2]) + abs(region[3])) / (2.0 * 1000.0)
185 bernhard 2344 self.used_baseids=[] # needed for name clash check
186    
187 bh 2074 def make_point(self, x, y):
188 bh 2082 """Return a Point object from two values."""
189 bh 2074 return Point(x, y)
190    
191     def label_font(self):
192 bernhard 2513 """Return the font object for the label layer.
193    
194     As we scale stuff, the fontsize also needs to be scaled."""
195     if verbose>1:
196     log("label_font() called.\n")
197     return Font(fsize=12*self.factor)
198 bh 2074
199     def tools_for_property(self, prop):
200 bh 2082 """Return a pen/brush tuple build from a property object."""
201 bh 2074 fill = prop.GetFill()
202     if fill is Transparent:
203     brush = TRANSPARENT_BRUSH
204     else:
205     brush = Brush(fill, SOLID)
206    
207     stroke = prop.GetLineColor()
208     if stroke is Transparent:
209     pen = TRANSPARENT_PEN
210     else:
211     pen = Pen(stroke, prop.GetLineWidth() * self.factor, SOLID)
212     return pen, brush
213    
214     def draw_polygon_shape(self, layer, points, pen, brush):
215     """Draw a polygon shape from layer with the given brush and pen
216    
217     The shape is given by points argument which is a the return
218     value of the shape's Points() method. The coordinates in the
219     DC's coordinate system are determined with
220     self.projected_points.
221     """
222     points = self.projected_points(layer, points)
223    
224 bernhard 2414 if verbose > 1:
225     log("drawing polygon with brush %s and pen %s\n" %
226     (str(brush), str(pen)) )
227 bernhard 2480 if verbose > 2:
228     log("points: %s\n" %(repr(points)))
229 bernhard 2414
230     self.dc.SetBrush(brush)
231     self.dc.SetPen(pen)
232 bernhard 2488 self.dc.DrawPolygonPath(points)
233 bernhard 2480
234 bernhard 2445 def draw_point_shape(self, layer, points, pen, brush, size=2):
235 bh 2074 """Draw a point shape from layer with the given brush and pen
236    
237     The shape is given by points argument which is a the return
238     value of the shape's Points() method. The coordinates in the
239     DC's coordinate system are determined with
240     self.projected_points.
241    
242     The point is drawn as a circle centered on the point.
243     """
244     points = self.projected_points(layer, points)
245     if not points:
246     return
247 bernhard 2480
248 bernhard 2445 radius = self.factor * size
249 bh 2074 self.dc.SetBrush(brush)
250     self.dc.SetPen(pen)
251     for part in points:
252     for p in part:
253     self.dc.DrawCircle(p.x - radius, p.y - radius,
254     2.0 * radius)
255    
256     def draw_shape_layer_incrementally(self, layer):
257     """Draw a shapelayer incrementally.
258     """
259     dc = self.dc
260     brush = TRANSPARENT_BRUSH
261     pen = TRANSPARENT_PEN
262 bernhard 2344
263 bh 2074 value = None
264     field = None
265     lc = layer.GetClassification()
266     field = layer.GetClassificationColumn()
267     defaultGroup = lc.GetDefaultGroup()
268     table = layer.ShapeStore().Table()
269    
270     if lc.GetNumGroups() == 0:
271     # There's only the default group, so we can pretend that
272     # there is no field to classifiy on which makes things
273     # faster since we don't need the attribute information at
274     # all.
275     field = None
276 bernhard 2414 if verbose > 0:
277     log("layer %s has no classification\n" % layer.Title())
278 bh 2074
279     # Determine which render function to use.
280     useraw, draw_func, draw_func_param = \
281     self.low_level_renderer(layer)
282 bernhard 2445 if verbose > 0 : log("Using draw_func %s\n"%(repr(draw_func)))
283    
284 bh 2074 tool_cache = {}
285    
286 bernhard 2352 new_baseid=dc.SetBaseID(layer.title)
287     if new_baseid in self.used_baseids:
288 bernhard 2344 raise SVGMapWriterError(_("Clash of layer names!\n")+ \
289     _("Two layers probably have the same name, try renaming one."))
290     # prefix of a shape id to be unique
291 bernhard 2352 self.used_baseids.append(new_baseid)
292 bh 2074 # Titel of current layer to the groups meta informations
293     dc.BeginGroup(meta={'Layer':layer.Title(), })
294     # Delete all MetaData
295     dc.FlushMeta()
296     for shape in self.layer_shapes(layer):
297     if field is None:
298     group = defaultGroup
299     value = group.GetDisplayText()
300     else:
301     value = table.ReadValue(shape.ShapeID(), field)
302     group = lc.FindGroup(value)
303    
304     if not group.IsVisible():
305     continue
306 bernhard 2414
307 bh 2074 # Render classification
308     shapeType = layer.ShapeType()
309     props = group.GetProperties()
310 bernhard 2414
311 bh 2074 # put meta infos into DC
312     if field and value:
313     dc.SetMeta({field:value, })
314     # set current shape id
315     dc.SetID(shape.ShapeID())
316    
317     try:
318     pen, brush = tool_cache[id(group)]
319     except KeyError:
320     pen, brush = tool_cache[id(group)] \
321     = self.tools_for_property(group.GetProperties())
322    
323     if useraw:
324     data = shape.RawData()
325     else:
326     data = shape.Points()
327 bernhard 2445
328     if shapeType==SHAPETYPE_POINT:
329     draw_func(draw_func_param, data, pen, brush,
330     size = group.GetProperties().GetSize())
331     else:
332     draw_func(draw_func_param, data, pen, brush)
333 bh 2074 # compatibility
334     if 0:
335     yield True
336     # reset shape id
337     dc.SetID(-1)
338     dc.SetBaseID("")
339     dc.EndGroup()
340    
341     def draw_raster_layer(self, layer):
342 bh 2082 """Draw the raster layer"""
343 bernhard 2490 # TODO: For now we cannot draw raster layers onto our VirtualDC
344     log(_("Warning: Raster layer not written as " +
345     "svgexport does not support this yet!\n"))
346 bh 2074
347     def draw_raster_data(self, data, format="BMP"):
348     """Draw the raster image in data onto the DC"""
349 bernhard 2490 # TODO: For now we cannot draw raster data onto our VirtualDC
350 bh 2074 pass
351    
352     def RenderMap(self, selected_layer, selected_shapes):
353 bh 2082 """Overriden to avoid automatic rendering of legend,
354 bh 2074 scalbar and frame.
355 bh 2082 """
356 bh 2074 dc = self.dc
357     self.selected_layer = selected_layer
358     self.selected_shapes = selected_shapes
359     minx, miny, width, height = self.region
360     # scale down to a size of 1000
361     trans = Trafo()
362     trans.Append('scale', (1000.0 / ((width + height) / 2.0)))
363     #
364     dc.BeginClipPath('mapclip')
365     dc.DrawRectangle(0, 0, width, height)
366     dc.EndClipPath()
367     #
368     dc.BeginGroup(meta={'Object':'map', }, clipid='mapclip', \
369     transform=trans)
370     self.render_map()
371     dc.EndGroup()
372    
373    
374     class VirtualDC(XMLWriter):
375 bh 2082 """This class imitates a DC and writes SVG instead.
376 bh 2074
377     All shapes and graphic objects will be turned into
378     SVG elements and will be written into a file.
379     Any properties, such as stroke width or stroke color,
380     will be written together with the SVG elementents.
381 bh 2082 """
382 bh 2074 def __init__(self, file, dim=(0,0), units=''):
383 bh 2082 """Setup some variables and objects for property collection."""
384 bh 2074 XMLWriter.__init__(self)
385     self.dim = dim
386     self.units = units
387     self.pen = {}
388     self.brush = {}
389     self.font = {}
390     self.meta = {}
391     self.style = {}
392     # Some buffers
393     self.points = []
394     self.id = -1
395     self.flush_meta = 1
396     self.write(file)
397    
398     def write_indent(self, str):
399 bh 2082 """Write a string to the file with the current indention level.
400     """
401 bh 2074 from Thuban.Model.xmlwriter import TAB
402     self.file.write("%s%s" % (TAB*self.indent_level, str))
403    
404     def AddMeta(self, key, val):
405 bh 2082 """Append some metadata to the array that will be
406 bh 2074 written with the next svg-element
407 bh 2082 """
408 bh 2074 if key is '' or val is '':
409     return
410     self.meta[key] = val
411    
412     def SetMeta(self, pairs, flush_after=1):
413 bh 2082 """Delete old meta informations and set the new ones."""
414 bh 2074 self.meta = {}
415     self.flush_meta = flush_after
416     for key, val in pairs.items():
417     self.AddMeta(key, val)
418    
419     def FlushMeta(self):
420 bh 2082 """Drop collected metadata."""
421 bh 2074 self.meta = {}
422    
423     def BeginGroup(self, **args):
424 bh 2082 """Begin a group of elements.
425 bh 2074
426     Possible arguments:
427     meta A list of key, value metadata pairs
428     style A list of key, value style attributes
429     clipid The ID of a clipPath definition to be
430     applied to this group
431 bh 2082 """
432 bh 2074 self.FlushMeta()
433     # adding meta data
434     if args.has_key('meta'):
435     for key, val in args['meta'].items():
436     self.AddMeta(key, val)
437     attribs = " "
438     # adding style attributes
439     if args.has_key('style'):
440     for key, val in args['style'].items():
441     attribs += '%s="%s" ' % (key, val)
442     # adding clip informations
443     if args.has_key("clipid"):
444     attribs += ' clip-path="url(#%s)"' % (args['clipid'],)
445     # FIXME: this shouldn't be static
446     attribs += ' clip-rule="evenodd"'
447     if args.has_key('transform'):
448     trafostr = self.parse_trafo(args['transform'])
449     if trafostr:
450     attribs += ' transform="%s"' % (trafostr)
451     # put everything together
452     self.write_indent('<g %s%s>\n' % (self.make_meta(), attribs))
453     self.indent_level += 1
454    
455     def parse_trafo(self, trafo):
456 bh 2082 """Examine a trafo object for asigned transformations details."""
457 bh 2074 if not trafo:
458     return ''
459     retval = ''
460     while trafo.Count() > 0:
461     trans, coeffs = tuple(trafo.Pop())
462     if isinstance(coeffs, ListType):
463     retval += " %s%s" % (trans, join(coeffs, ', '))
464     else: retval += " %s(%s)" % (trans, coeffs)
465     # return the string
466     return retval
467    
468     def EndGroup(self):
469 bh 2082 """End a group of elements"""
470 bh 2074 self.indent_level -= 1
471     self.write_indent('</g>\n')
472     self.FlushMeta()
473    
474     def BeginExport(self):
475 bh 2082 """Start the export process and write basic document
476 bh 2074 informations to the file.
477 bh 2082 """
478 bh 2074 self.write_indent('<?xml version="1.0" encoding="ISO-8859-1" '
479     'standalone="yes"?>\n')
480     width, height = self.dim
481     self.write_indent('<svg>\n')
482     self.indent_level += 1
483    
484     def EndExport(self):
485 bh 2082 """End the export process with closing the SVG tag and close
486     the file accessor"""
487 bh 2074 self.indent_level -= 1
488     self.write_indent('</svg>\n')
489     self.close()
490    
491     def Close(self):
492 bh 2082 """Close the file."""
493 bh 2074 self.close()
494    
495     def BeginDrawing(self):
496 bh 2082 """Dummy function to work with the Thuban renderers."""
497 bh 2074 pass
498    
499     def EndDrawing(self):
500 bh 2082 """Dummy function to work with the Thuban renderers."""
501 bh 2074 pass
502    
503     def GetSizeTuple(self):
504 bh 2082 """Return the dimension of this virtual canvas."""
505 bh 2074 return self.dim
506    
507     def GetTextExtent(self, text):
508 bh 2082 """Return the dimension of the given text."""
509 bh 2074 # FIXME: find something more appropriate
510     try:
511     if self.font:
512     return (int(self.font["font-size"]),
513     len(text) * int(self.font["font-size"]))
514     else: return (12,len(text) * 10)
515     except ValueError:
516     return (12,len(text) * 10)
517    
518 bernhard 2352
519     def SetBaseID(self, id):
520     """Set first part of ID stored by the svg elements. Return it.
521    
522     Will be used in make_id() as first part of an XML attribute "id".
523     The second part is set by SetID().
524     Check comments at make_id().
525    
526     We might add an abritrary "t" for thuban if the parameter
527     starts with XML, which is not allowed in XML 1.0.
528    
529     We need to ensure that all characters are okay as XML id attribute.
530     As there seem no easy way in Python (today 20040925) to check
531     for compliance with XML 1.0 character classes like NameChar,
532     we use Python's string method isalnum() as approximation.
533     (See http://mail.python.org/pipermail/xml-sig/2002-January/006981.html
534     and xmlgenchar.py. To be better we would need to hold our own
535     huge table of allowed unicode characters.
536     FIXME if python comes with a better funcation for XML 1.0 NameChar)
537    
538     Characters that are not in our approx of NameChar get transformed
539     get escaped to their hex value.
540     """
541     # an ID Name shall not start with xml.
542     if id[0:3].lower() == "xml":
543     id = "t" + id
544    
545     self.baseid = ""
546     for c in id:
547     if c.isalnum() or c in ".-_":
548     self.baseid += c
549     else:
550     self.baseid += binascii.b2a_hex(c)
551     return self.baseid
552    
553 bh 2074 def SetID(self, id):
554 bernhard 2350 """Set second part of ID stored by the svg elements.
555    
556     Will be used in make_id() as first part of an XML attribute "id".
557 bernhard 2352 Only set this to positive integer numbers.
558 bernhard 2350 Read comments at SetBaseID() and make_id().
559     """
560 bh 2074 self.id = id
561    
562     def SetFont(self, font):
563 bh 2082 """Set the fontproperties to use with text elements."""
564 bh 2074 if font is not None:
565     fontname = font.GetFaceName()
566     size = font.GetPointSize()
567     for svgfont, pattern in fontMap.items():
568     if pattern.match(fontname):
569     fontname = svgfont
570     break
571     if fontname:
572     self.font["font-family"] = fontname
573     else: self.font["font-family"] = None
574     if size:
575     self.font["font-size"] = str(size)
576     else: self.font["font-size"] = None
577    
578     def SetPen(self, pen):
579 bh 2082 """Set the style of the pen used to draw graphics."""
580 bh 2074 if pen is TRANSPARENT_PEN:
581     self.pen = {}
582     else:
583     self.pen["stroke"] = pen.GetColor().hex()
584     self.pen["stroke-dasharray"] = join(pen.GetDashes(), ',')
585     self.pen["stroke-width"] = pen.GetWidth()
586     self.pen["stroke-linejoin"] = svg_joins[pen.GetJoin()]
587     self.pen["stroke-linecap"] = svg_caps[pen.GetCap()]
588    
589     def SetBrush(self, brush):
590 bh 2082 """Set the fill properties."""
591 bh 2074 if brush is TRANSPARENT_BRUSH:
592     self.brush['fill'] = 'none'
593     elif brush.GetPattern() is SOLID:
594     self.brush['fill'] = brush.GetColor().hex()
595     else: # TODO Handle Patterns
596     pass
597    
598     def SetTextForeground(self, color):
599 bh 2082 """Set the color of the text foreground."""
600 bh 2074 self.font['fill'] = color.hex()
601    
602     def make_style(self, line=0, fill=0, font=0):
603 bh 2082 """Build the style attribute including desired properties
604     such as fill, forground, stroke, etc."""
605 bh 2074 result = []
606     # little helper function
607     def append(pairs):
608     for key, val in pairs.items():
609     if not val in [None, '']:
610     result.append('%s:%s' % (key, val))
611     #
612     if line and len(self.pen) > 0:
613     append(self.pen)
614     if fill and len(self.brush) > 0:
615     append(self.brush)
616     if font and len(self.font) > 0:
617     append(self.font)
618     style = join(result, '; ')
619     if style:
620     return 'style="%s"' % (style, )
621     else:
622     return ''
623    
624     def make_meta(self, meta=None):
625 bh 2082 """Build the meta attribute."""
626 bh 2074 result = []
627     if not meta:
628     meta = self.meta
629     if len(meta) is 0:
630     return ''
631     for key, val in meta.items():
632     if not val in [None, '', 'none']:
633     result.append('%s:%s' % (key, val))
634     if self.flush_meta:
635     self.meta = {}
636     return 'meta="%s"' % (join(result, '; '))
637    
638     def make_id(self):
639 bernhard 2344 """Return id= string for object out of currently set baseid and id.
640    
641     Return the empty string if no id was set.
642 bernhard 2350
643     In an XML file each id should be unique
644     (see XML 1.0 section 3.3.1 Attribute Types, Validity constraint: ID)
645     So this function should only return a unique values.
646     which also coforms to the the XML "Name production" (section 3.2).
647    
648     For this it completely depends
649     on what has been set by SetBaseID() and SetID().
650     Only call this function if you have called them w unique values before
651 bernhard 2352 (or negative x in SetID(x) to get an empty result)
652     Will raise SVGMapWriterError if SetID value cannot be converted to %d.
653 bernhard 2350
654     A check of uniqueness in this function might be time consuming,
655     because it would require to hold and search through a complete table.
656 bernhard 2344 """
657 bh 2074 if self.id < 0:
658     return ''
659 bernhard 2352 try:
660     id= 'id="%s_%d"' % (self.baseid, self.id)
661     except TypeError, inst:
662     raise SVGMapWriterError(_("Internal make_id() failure: ") \
663     + repr(inst))
664     return id
665 bh 2074
666     def DrawEllipse(self, x, y, dx, dy):
667 bh 2082 """Draw an ellipse."""
668 bh 2074 elips = '<ellipse cx="%s" cy="%s" rx="%s" ry="%s" %s %s %s/>\n'
669     self.write_indent(elips % (x, y, dx, dy, self.make_id(),
670     self.make_style(1,1,0), self.make_meta()) )
671    
672     def DrawCircle(self, x, y, radius):
673 bh 2082 """Draw a circle onto the virtual dc."""
674 bh 2074 self.write_indent('<circle cx="%s" cy="%s" r="%s" %s %s %s/>\n' %
675     (x, y, radius, self.make_id(), self.make_style(1,1,0),
676     self.make_meta()) )
677    
678     def DrawRectangle(self, x, y, width, height):
679 bh 2082 """Draw a rectangle with the given parameters."""
680 bh 2074 rect = '<rect x="%s" y="%s" width="%s" height="%s" %s %s %s/>\n'
681     self.write_indent(rect % ( x, y, width, height, self.make_id(),
682     self.make_style(1,1,0), self.make_meta()) )
683    
684     def DrawText(self, text, x, y):
685 bh 2082 """Draw Text at the given position."""
686 bh 2074 beginText = '<text x="%s" y="%s" %s %s %s>'
687     self.write_indent(beginText % ( x, y, self.make_id(),
688     self.make_style(0,0,1), self.make_meta()) )
689     self.file.write(escape(text))
690     self.file.write('</text>\n')
691    
692     def DrawLines(self, points):
693 bh 2082 """Draw some points into a Buffer that will be
694 bh 2074 written before the next object.
695 bh 2082 """
696 bernhard 2488 self.DrawPolygonPath([points], closed=False)
697 bh 2074
698 bernhard 2488 def DrawPolygonPath(self, polys, closed=True):
699     """Draw a list of polygons or polylines as one SVG path.
700    
701     Parameter:
702     polys list of poly- gons/lines; each consisting of a list of points
703     closed Boolean; optional; Default: True
704     False will leave each subpath open thus making it polylines.
705     """
706 bh 2074 self.write_indent('<path %s ' % (self.make_style(1,1,0)))
707     data = []
708 bernhard 2488 for poly in polys:
709 bernhard 2480 i = 0
710 bernhard 2488 for point in poly:
711 bernhard 2480 if i is 0:
712     data.append('\nM %s %s' % (point.x, point.y))
713     i+=1
714     else:
715     # SVG 1.1 Spec 8.3.1 recommends that lines length <= 255
716 bernhard 2488 # we make a best effort in throwing in a few newlines
717 bernhard 2480 data.append('\nL %s %s' % (point.x, point.y))
718     if closed:
719     data.append(' Z')
720    
721 bh 2074 # Put everything together and write it to the file
722     self.file.write('%s %s d="%s"/>\n' % (self.make_id(),
723     self.make_meta(), join(data, '') ) )
724 bernhard 2432
725 bh 2074 def DrawSpline(self, points, closed=0):
726 bernhard 2443 """Calculate square bezier points for an xfig approximated spline.
727    
728     DrawSpline() needs to do the same as the function of the Device Context
729     of wxWidgets. Code inspection shows it uses the "approximated
730     splines" of xfig <= 3.1. This can be mapped
731     on SVG's squared bezier curves,
732     by doing the same calculation like wxPostScriptDC::DoDrawSpline() in
733     wxWidgets src/generic/dcpsg.cpp.
734     Which is derived from xfig's 3.1.4 u_draw.c(draw_open_spline()).
735    
736     And then leave out the last translation to cubic beziers
737     done in the postscript code of DrawSplineSection.
738 bh 2082 """
739 bernhard 2432 self.write_indent('<path %s ' % (self.make_style(1,1,0)))
740     datastr = ""
741 bernhard 2443
742     x1=points[0].x
743     y1=points[0].y
744    
745     datastr+=('M %s %s ' % (x1, y1))
746    
747     c=points[1].x
748     d=points[1].y
749    
750     x3 = (x1 + c) / 2;
751     y3 = (y1 + d) / 2;
752    
753     datastr+=('L %s %s ' % (x3,y3))
754    
755     for point in points[2:]:
756     x2 = c
757     y2 = d
758     c = point.x
759     d = point.y
760     x3 = (x2 + c) / 2;
761     y3 = (y2 + d) / 2;
762    
763     # With SVG's bezier commands, the last point becomes the next start
764     # so no new L necessary
765     # SVG Spec 1.1 recommends to not uses lines longer than 255 chars
766     datastr+=('Q %s %s %s %s\n' % (x2,y2,x3,y3))
767    
768     datastr+=('L %s %s' % (c,d))
769    
770 bernhard 2432 self.file.write('%s %s d="%s"/>\n' % (self.make_id(),
771     self.make_meta(), datastr ) )
772 bh 2074
773     def BeginClipPath(self, id):
774 bh 2082 """Build a clipping region to draw in."""
775 bh 2074 self.write_indent('<clipPath id="%s">\n' % id)
776     self.indent_level += 1
777 bernhard 2432
778 bh 2074 def EndClipPath(self):
779 bh 2082 """End a clip path."""
780 bh 2074 self.indent_level -= 1
781     self.write_indent("</clipPath>\n")

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26