/[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 2443 - (hide annotations)
Sat Dec 11 02:03:47 2004 UTC (20 years, 2 months ago) by bernhard
File MIME type: text/x-python
File size: 25763 byte(s)
	* Doc/technotes/coding_guidelines.txt: easy typo fixed.

	* Extensions/svgexport/test/test_svgmapwriter.py:
	Removed test_drawbezier in favour of new test_drawspline3 and
	test_drawspline4 within new class TestDrawSplines(TestVirtualDC).
	All only to test DrawSpline.

	* Extensions/svgexport/svgmapwriter.py(DrawSpline):
	New implementation now really using the strange algorithm of
	xfig 3.1's approximated splines and its conversion to postscript
	with one twist: SVG can do quadratic beziers, so skipped translation
	to cubic beziers.
	(TestWithDC): Typo in comment fixed.

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