/[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 2445 - (hide annotations)
Sat Dec 11 03:24:36 2004 UTC (20 years, 2 months ago) by bernhard
File MIME type: text/x-python
File size: 26040 byte(s)
	 svgexport 0.9.2: Point size supports for maps.

	* Extensions/svgexport/svgmapwriter.py: Added import of SHAPETYPE_POINT
	(def draw_point_shape): new parameter size defaults to 2 as before.
	(draw_shape_layer_incrementally): Moved draw_func log line higher.
	Added draw_func call with size when dealing with a point layer.

	* Extensions/svgexport/__init__.py: bumped version to 0.9.2.

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