/[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 2480 - (hide annotations)
Sat Dec 18 00:29:04 2004 UTC (20 years, 2 months ago) by bernhard
File MIME type: text/x-python
File size: 26108 byte(s)
	Refactored in svgexport:
	DrawPath replaces DrawPolygon; added newline in front of "M" in paths.

	* Extensions/svgexport/svgmapwriter.py
	Added verbosity level 3 to print out polygon points.
	(class Point): added __repr__
	(class Brush, __str__()): Added space after ,.
	(DrawPolygon): Renamed to DrawPath()
	(DrawPath): Takes list of polygons as input now, adds \n before "M"s.
	(DrawLines): Using DrawPath now.

	* Extensions/svgexport/test/test_svgmapwriter.py:
	Replaced DrawPolygon() calls with DrawPath() and put the first argument
	inside another list. Adapted test data with a newline before "M".

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     """Return the font object for the label layer"""
193     return Font()
194    
195     def tools_for_property(self, prop):
196 bh 2082 """Return a pen/brush tuple build from a property object."""
197 bh 2074 fill = prop.GetFill()
198     if fill is Transparent:
199     brush = TRANSPARENT_BRUSH
200     else:
201     brush = Brush(fill, SOLID)
202    
203     stroke = prop.GetLineColor()
204     if stroke is Transparent:
205     pen = TRANSPARENT_PEN
206     else:
207     pen = Pen(stroke, prop.GetLineWidth() * self.factor, SOLID)
208     return pen, brush
209    
210     def draw_polygon_shape(self, layer, points, pen, brush):
211     """Draw a polygon shape from layer with the given brush and pen
212    
213     The shape is given by points argument which is a the return
214     value of the shape's Points() method. The coordinates in the
215     DC's coordinate system are determined with
216     self.projected_points.
217     """
218     points = self.projected_points(layer, points)
219    
220 bernhard 2414 if verbose > 1:
221     log("drawing polygon with brush %s and pen %s\n" %
222     (str(brush), str(pen)) )
223 bernhard 2480 if verbose > 2:
224     log("points: %s\n" %(repr(points)))
225 bernhard 2414
226     self.dc.SetBrush(brush)
227     self.dc.SetPen(pen)
228     for parts in points:
229 bernhard 2480 self.dc.DrawPath([parts],closed=1)
230    
231 bernhard 2445 def draw_point_shape(self, layer, points, pen, brush, size=2):
232 bh 2074 """Draw a point shape from layer with the given brush and pen
233    
234     The shape is given by points argument which is a the return
235     value of the shape's Points() method. The coordinates in the
236     DC's coordinate system are determined with
237     self.projected_points.
238    
239     The point is drawn as a circle centered on the point.
240     """
241     points = self.projected_points(layer, points)
242     if not points:
243     return
244 bernhard 2480
245 bernhard 2445 radius = self.factor * size
246 bh 2074 self.dc.SetBrush(brush)
247     self.dc.SetPen(pen)
248     for part in points:
249     for p in part:
250     self.dc.DrawCircle(p.x - radius, p.y - radius,
251     2.0 * radius)
252    
253     def draw_shape_layer_incrementally(self, layer):
254     """Draw a shapelayer incrementally.
255     """
256     dc = self.dc
257     brush = TRANSPARENT_BRUSH
258     pen = TRANSPARENT_PEN
259 bernhard 2344
260 bh 2074 value = None
261     field = None
262     lc = layer.GetClassification()
263     field = layer.GetClassificationColumn()
264     defaultGroup = lc.GetDefaultGroup()
265     table = layer.ShapeStore().Table()
266    
267     if lc.GetNumGroups() == 0:
268     # There's only the default group, so we can pretend that
269     # there is no field to classifiy on which makes things
270     # faster since we don't need the attribute information at
271     # all.
272     field = None
273 bernhard 2414 if verbose > 0:
274     log("layer %s has no classification\n" % layer.Title())
275 bh 2074
276     # Determine which render function to use.
277     useraw, draw_func, draw_func_param = \
278     self.low_level_renderer(layer)
279 bernhard 2445 if verbose > 0 : log("Using draw_func %s\n"%(repr(draw_func)))
280    
281 bh 2074 tool_cache = {}
282    
283 bernhard 2352 new_baseid=dc.SetBaseID(layer.title)
284     if new_baseid in self.used_baseids:
285 bernhard 2344 raise SVGMapWriterError(_("Clash of layer names!\n")+ \
286     _("Two layers probably have the same name, try renaming one."))
287     # prefix of a shape id to be unique
288 bernhard 2352 self.used_baseids.append(new_baseid)
289 bh 2074 # Titel of current layer to the groups meta informations
290     dc.BeginGroup(meta={'Layer':layer.Title(), })
291     # Delete all MetaData
292     dc.FlushMeta()
293     for shape in self.layer_shapes(layer):
294     if field is None:
295     group = defaultGroup
296     value = group.GetDisplayText()
297     else:
298     value = table.ReadValue(shape.ShapeID(), field)
299     group = lc.FindGroup(value)
300    
301     if not group.IsVisible():
302     continue
303 bernhard 2414
304 bh 2074 # Render classification
305     shapeType = layer.ShapeType()
306     props = group.GetProperties()
307 bernhard 2414
308 bh 2074 # put meta infos into DC
309     if field and value:
310     dc.SetMeta({field:value, })
311     # set current shape id
312     dc.SetID(shape.ShapeID())
313    
314     try:
315     pen, brush = tool_cache[id(group)]
316     except KeyError:
317     pen, brush = tool_cache[id(group)] \
318     = self.tools_for_property(group.GetProperties())
319    
320     if useraw:
321     data = shape.RawData()
322     else:
323     data = shape.Points()
324 bernhard 2445
325     if shapeType==SHAPETYPE_POINT:
326     draw_func(draw_func_param, data, pen, brush,
327     size = group.GetProperties().GetSize())
328     else:
329     draw_func(draw_func_param, data, pen, brush)
330 bh 2074 # compatibility
331     if 0:
332     yield True
333     # reset shape id
334     dc.SetID(-1)
335     dc.SetBaseID("")
336     dc.EndGroup()
337    
338     def draw_raster_layer(self, layer):
339 bh 2082 """Draw the raster layer"""
340 bh 2074 # For now we cannot draw raster layers onto our VirtualDC
341     pass
342    
343     def draw_raster_data(self, data, format="BMP"):
344     """Draw the raster image in data onto the DC"""
345     # For now we cannot draw raster data onto our VirtualDC
346     pass
347    
348     def RenderMap(self, selected_layer, selected_shapes):
349 bh 2082 """Overriden to avoid automatic rendering of legend,
350 bh 2074 scalbar and frame.
351 bh 2082 """
352 bh 2074 dc = self.dc
353     self.selected_layer = selected_layer
354     self.selected_shapes = selected_shapes
355     minx, miny, width, height = self.region
356     # scale down to a size of 1000
357     trans = Trafo()
358     trans.Append('scale', (1000.0 / ((width + height) / 2.0)))
359     #
360     dc.BeginClipPath('mapclip')
361     dc.DrawRectangle(0, 0, width, height)
362     dc.EndClipPath()
363     #
364     dc.BeginGroup(meta={'Object':'map', }, clipid='mapclip', \
365     transform=trans)
366     self.render_map()
367     dc.EndGroup()
368    
369    
370     class VirtualDC(XMLWriter):
371 bh 2082 """This class imitates a DC and writes SVG instead.
372 bh 2074
373     All shapes and graphic objects will be turned into
374     SVG elements and will be written into a file.
375     Any properties, such as stroke width or stroke color,
376     will be written together with the SVG elementents.
377 bh 2082 """
378 bh 2074 def __init__(self, file, dim=(0,0), units=''):
379 bh 2082 """Setup some variables and objects for property collection."""
380 bh 2074 XMLWriter.__init__(self)
381     self.dim = dim
382     self.units = units
383     self.pen = {}
384     self.brush = {}
385     self.font = {}
386     self.meta = {}
387     self.style = {}
388     # Some buffers
389     self.points = []
390     self.id = -1
391     self.flush_meta = 1
392     self.write(file)
393    
394     def write_indent(self, str):
395 bh 2082 """Write a string to the file with the current indention level.
396     """
397 bh 2074 from Thuban.Model.xmlwriter import TAB
398     self.file.write("%s%s" % (TAB*self.indent_level, str))
399    
400     def AddMeta(self, key, val):
401 bh 2082 """Append some metadata to the array that will be
402 bh 2074 written with the next svg-element
403 bh 2082 """
404 bh 2074 if key is '' or val is '':
405     return
406     self.meta[key] = val
407    
408     def SetMeta(self, pairs, flush_after=1):
409 bh 2082 """Delete old meta informations and set the new ones."""
410 bh 2074 self.meta = {}
411     self.flush_meta = flush_after
412     for key, val in pairs.items():
413     self.AddMeta(key, val)
414    
415     def FlushMeta(self):
416 bh 2082 """Drop collected metadata."""
417 bh 2074 self.meta = {}
418    
419     def BeginGroup(self, **args):
420 bh 2082 """Begin a group of elements.
421 bh 2074
422     Possible arguments:
423     meta A list of key, value metadata pairs
424     style A list of key, value style attributes
425     clipid The ID of a clipPath definition to be
426     applied to this group
427 bh 2082 """
428 bh 2074 self.FlushMeta()
429     # adding meta data
430     if args.has_key('meta'):
431     for key, val in args['meta'].items():
432     self.AddMeta(key, val)
433     attribs = " "
434     # adding style attributes
435     if args.has_key('style'):
436     for key, val in args['style'].items():
437     attribs += '%s="%s" ' % (key, val)
438     # adding clip informations
439     if args.has_key("clipid"):
440     attribs += ' clip-path="url(#%s)"' % (args['clipid'],)
441     # FIXME: this shouldn't be static
442     attribs += ' clip-rule="evenodd"'
443     if args.has_key('transform'):
444     trafostr = self.parse_trafo(args['transform'])
445     if trafostr:
446     attribs += ' transform="%s"' % (trafostr)
447     # put everything together
448     self.write_indent('<g %s%s>\n' % (self.make_meta(), attribs))
449     self.indent_level += 1
450    
451     def parse_trafo(self, trafo):
452 bh 2082 """Examine a trafo object for asigned transformations details."""
453 bh 2074 if not trafo:
454     return ''
455     retval = ''
456     while trafo.Count() > 0:
457     trans, coeffs = tuple(trafo.Pop())
458     if isinstance(coeffs, ListType):
459     retval += " %s%s" % (trans, join(coeffs, ', '))
460     else: retval += " %s(%s)" % (trans, coeffs)
461     # return the string
462     return retval
463    
464     def EndGroup(self):
465 bh 2082 """End a group of elements"""
466 bh 2074 self.indent_level -= 1
467     self.write_indent('</g>\n')
468     self.FlushMeta()
469    
470     def BeginExport(self):
471 bh 2082 """Start the export process and write basic document
472 bh 2074 informations to the file.
473 bh 2082 """
474 bh 2074 self.write_indent('<?xml version="1.0" encoding="ISO-8859-1" '
475     'standalone="yes"?>\n')
476     width, height = self.dim
477     self.write_indent('<svg>\n')
478     self.indent_level += 1
479    
480     def EndExport(self):
481 bh 2082 """End the export process with closing the SVG tag and close
482     the file accessor"""
483 bh 2074 self.indent_level -= 1
484     self.write_indent('</svg>\n')
485     self.close()
486    
487     def Close(self):
488 bh 2082 """Close the file."""
489 bh 2074 self.close()
490    
491     def BeginDrawing(self):
492 bh 2082 """Dummy function to work with the Thuban renderers."""
493 bh 2074 pass
494    
495     def EndDrawing(self):
496 bh 2082 """Dummy function to work with the Thuban renderers."""
497 bh 2074 pass
498    
499     def GetSizeTuple(self):
500 bh 2082 """Return the dimension of this virtual canvas."""
501 bh 2074 return self.dim
502    
503     def GetTextExtent(self, text):
504 bh 2082 """Return the dimension of the given text."""
505 bh 2074 # FIXME: find something more appropriate
506     try:
507     if self.font:
508     return (int(self.font["font-size"]),
509     len(text) * int(self.font["font-size"]))
510     else: return (12,len(text) * 10)
511     except ValueError:
512     return (12,len(text) * 10)
513    
514 bernhard 2352
515     def SetBaseID(self, id):
516     """Set first part of ID stored by the svg elements. Return it.
517    
518     Will be used in make_id() as first part of an XML attribute "id".
519     The second part is set by SetID().
520     Check comments at make_id().
521    
522     We might add an abritrary "t" for thuban if the parameter
523     starts with XML, which is not allowed in XML 1.0.
524    
525     We need to ensure that all characters are okay as XML id attribute.
526     As there seem no easy way in Python (today 20040925) to check
527     for compliance with XML 1.0 character classes like NameChar,
528     we use Python's string method isalnum() as approximation.
529     (See http://mail.python.org/pipermail/xml-sig/2002-January/006981.html
530     and xmlgenchar.py. To be better we would need to hold our own
531     huge table of allowed unicode characters.
532     FIXME if python comes with a better funcation for XML 1.0 NameChar)
533    
534     Characters that are not in our approx of NameChar get transformed
535     get escaped to their hex value.
536     """
537     # an ID Name shall not start with xml.
538     if id[0:3].lower() == "xml":
539     id = "t" + id
540    
541     self.baseid = ""
542     for c in id:
543     if c.isalnum() or c in ".-_":
544     self.baseid += c
545     else:
546     self.baseid += binascii.b2a_hex(c)
547     return self.baseid
548    
549 bh 2074 def SetID(self, id):
550 bernhard 2350 """Set second part of ID stored by the svg elements.
551    
552     Will be used in make_id() as first part of an XML attribute "id".
553 bernhard 2352 Only set this to positive integer numbers.
554 bernhard 2350 Read comments at SetBaseID() and make_id().
555     """
556 bh 2074 self.id = id
557    
558     def SetFont(self, font):
559 bh 2082 """Set the fontproperties to use with text elements."""
560 bh 2074 if font is not None:
561     fontname = font.GetFaceName()
562     size = font.GetPointSize()
563     for svgfont, pattern in fontMap.items():
564     if pattern.match(fontname):
565     fontname = svgfont
566     break
567     if fontname:
568     self.font["font-family"] = fontname
569     else: self.font["font-family"] = None
570     if size:
571     self.font["font-size"] = str(size)
572     else: self.font["font-size"] = None
573    
574     def SetPen(self, pen):
575 bh 2082 """Set the style of the pen used to draw graphics."""
576 bh 2074 if pen is TRANSPARENT_PEN:
577     self.pen = {}
578     else:
579     self.pen["stroke"] = pen.GetColor().hex()
580     self.pen["stroke-dasharray"] = join(pen.GetDashes(), ',')
581     self.pen["stroke-width"] = pen.GetWidth()
582     self.pen["stroke-linejoin"] = svg_joins[pen.GetJoin()]
583     self.pen["stroke-linecap"] = svg_caps[pen.GetCap()]
584    
585     def SetBrush(self, brush):
586 bh 2082 """Set the fill properties."""
587 bh 2074 if brush is TRANSPARENT_BRUSH:
588     self.brush['fill'] = 'none'
589     elif brush.GetPattern() is SOLID:
590     self.brush['fill'] = brush.GetColor().hex()
591     else: # TODO Handle Patterns
592     pass
593    
594     def SetTextForeground(self, color):
595 bh 2082 """Set the color of the text foreground."""
596 bh 2074 self.font['fill'] = color.hex()
597    
598     def make_style(self, line=0, fill=0, font=0):
599 bh 2082 """Build the style attribute including desired properties
600     such as fill, forground, stroke, etc."""
601 bh 2074 result = []
602     # little helper function
603     def append(pairs):
604     for key, val in pairs.items():
605     if not val in [None, '']:
606     result.append('%s:%s' % (key, val))
607     #
608     if line and len(self.pen) > 0:
609     append(self.pen)
610     if fill and len(self.brush) > 0:
611     append(self.brush)
612     if font and len(self.font) > 0:
613     append(self.font)
614     style = join(result, '; ')
615     if style:
616     return 'style="%s"' % (style, )
617     else:
618     return ''
619    
620     def make_meta(self, meta=None):
621 bh 2082 """Build the meta attribute."""
622 bh 2074 result = []
623     if not meta:
624     meta = self.meta
625     if len(meta) is 0:
626     return ''
627     for key, val in meta.items():
628     if not val in [None, '', 'none']:
629     result.append('%s:%s' % (key, val))
630     if self.flush_meta:
631     self.meta = {}
632     return 'meta="%s"' % (join(result, '; '))
633    
634     def make_id(self):
635 bernhard 2344 """Return id= string for object out of currently set baseid and id.
636    
637     Return the empty string if no id was set.
638 bernhard 2350
639     In an XML file each id should be unique
640     (see XML 1.0 section 3.3.1 Attribute Types, Validity constraint: ID)
641     So this function should only return a unique values.
642     which also coforms to the the XML "Name production" (section 3.2).
643    
644     For this it completely depends
645     on what has been set by SetBaseID() and SetID().
646     Only call this function if you have called them w unique values before
647 bernhard 2352 (or negative x in SetID(x) to get an empty result)
648     Will raise SVGMapWriterError if SetID value cannot be converted to %d.
649 bernhard 2350
650     A check of uniqueness in this function might be time consuming,
651     because it would require to hold and search through a complete table.
652 bernhard 2344 """
653 bh 2074 if self.id < 0:
654     return ''
655 bernhard 2352 try:
656     id= 'id="%s_%d"' % (self.baseid, self.id)
657     except TypeError, inst:
658     raise SVGMapWriterError(_("Internal make_id() failure: ") \
659     + repr(inst))
660     return id
661 bh 2074
662     def DrawEllipse(self, x, y, dx, dy):
663 bh 2082 """Draw an ellipse."""
664 bh 2074 elips = '<ellipse cx="%s" cy="%s" rx="%s" ry="%s" %s %s %s/>\n'
665     self.write_indent(elips % (x, y, dx, dy, self.make_id(),
666     self.make_style(1,1,0), self.make_meta()) )
667    
668     def DrawCircle(self, x, y, radius):
669 bh 2082 """Draw a circle onto the virtual dc."""
670 bh 2074 self.write_indent('<circle cx="%s" cy="%s" r="%s" %s %s %s/>\n' %
671     (x, y, radius, self.make_id(), self.make_style(1,1,0),
672     self.make_meta()) )
673    
674     def DrawRectangle(self, x, y, width, height):
675 bh 2082 """Draw a rectangle with the given parameters."""
676 bh 2074 rect = '<rect x="%s" y="%s" width="%s" height="%s" %s %s %s/>\n'
677     self.write_indent(rect % ( x, y, width, height, self.make_id(),
678     self.make_style(1,1,0), self.make_meta()) )
679    
680     def DrawText(self, text, x, y):
681 bh 2082 """Draw Text at the given position."""
682 bh 2074 beginText = '<text x="%s" y="%s" %s %s %s>'
683     self.write_indent(beginText % ( x, y, self.make_id(),
684     self.make_style(0,0,1), self.make_meta()) )
685     self.file.write(escape(text))
686     self.file.write('</text>\n')
687    
688     def DrawLines(self, points):
689 bh 2082 """Draw some points into a Buffer that will be
690 bh 2074 written before the next object.
691 bh 2082 """
692 bernhard 2480 self.DrawPath([points],0)
693 bh 2074
694 bernhard 2480 def DrawPath(self, path, closed=1):
695     """Draw a polygon with sonto the virtual dc."""
696 bh 2074 self.write_indent('<path %s ' % (self.make_style(1,1,0)))
697     data = []
698 bernhard 2480 for subpath in path:
699     i = 0
700     for point in subpath:
701     if i is 0:
702     data.append('\nM %s %s' % (point.x, point.y))
703     i+=1
704     else:
705     # SVG 1.1 Spec 8.3.1 recommends that lines length <= 255
706     data.append('\nL %s %s' % (point.x, point.y))
707     if closed:
708     data.append(' Z')
709    
710 bh 2074 # Put everything together and write it to the file
711     self.file.write('%s %s d="%s"/>\n' % (self.make_id(),
712     self.make_meta(), join(data, '') ) )
713 bernhard 2432
714 bh 2074 def DrawSpline(self, points, closed=0):
715 bernhard 2443 """Calculate square bezier points for an xfig approximated spline.
716    
717     DrawSpline() needs to do the same as the function of the Device Context
718     of wxWidgets. Code inspection shows it uses the "approximated
719     splines" of xfig <= 3.1. This can be mapped
720     on SVG's squared bezier curves,
721     by doing the same calculation like wxPostScriptDC::DoDrawSpline() in
722     wxWidgets src/generic/dcpsg.cpp.
723     Which is derived from xfig's 3.1.4 u_draw.c(draw_open_spline()).
724    
725     And then leave out the last translation to cubic beziers
726     done in the postscript code of DrawSplineSection.
727 bh 2082 """
728 bernhard 2432 self.write_indent('<path %s ' % (self.make_style(1,1,0)))
729     datastr = ""
730 bernhard 2443
731     x1=points[0].x
732     y1=points[0].y
733    
734     datastr+=('M %s %s ' % (x1, y1))
735    
736     c=points[1].x
737     d=points[1].y
738    
739     x3 = (x1 + c) / 2;
740     y3 = (y1 + d) / 2;
741    
742     datastr+=('L %s %s ' % (x3,y3))
743    
744     for point in points[2:]:
745     x2 = c
746     y2 = d
747     c = point.x
748     d = point.y
749     x3 = (x2 + c) / 2;
750     y3 = (y2 + d) / 2;
751    
752     # With SVG's bezier commands, the last point becomes the next start
753     # so no new L necessary
754     # SVG Spec 1.1 recommends to not uses lines longer than 255 chars
755     datastr+=('Q %s %s %s %s\n' % (x2,y2,x3,y3))
756    
757     datastr+=('L %s %s' % (c,d))
758    
759 bernhard 2432 self.file.write('%s %s d="%s"/>\n' % (self.make_id(),
760     self.make_meta(), datastr ) )
761 bh 2074
762     def BeginClipPath(self, id):
763 bh 2082 """Build a clipping region to draw in."""
764 bh 2074 self.write_indent('<clipPath id="%s">\n' % id)
765     self.indent_level += 1
766 bernhard 2432
767 bh 2074 def EndClipPath(self):
768 bh 2082 """End a clip path."""
769 bh 2074 self.indent_level -= 1
770     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