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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2490 - (show annotations)
Mon Dec 20 10:08:58 2004 UTC (20 years, 2 months ago) by bernhard
File MIME type: text/x-python
File size: 26531 byte(s)
	* Extensions/svgexport/TODO: updated to add support for
	raster layers and labels for 1.0.1.

	* Extensions/svgexport/svgmapwriter.py (draw_raster_layer):
	Issue a warning now.

1 # Copyright (c) 2001, 2002, 2003, 2004 by Intevation GmbH
2 # Authors:
3 # Markus Rechtien <[email protected]>
4 # Bernhard Reiter <[email protected]>
5 #
6 # This program is free software under the GPL (>=v2)
7 # Read the file COPYING coming with Thuban for details.
8
9
10 """
11 Classes needed to write a session in SVG format.
12 """
13
14 # For compatibility with python 2.2
15 from __future__ import generators
16
17
18 __version__ = "$Revision$"
19 # $Source$
20 # $Id$
21
22 import sys
23
24 # Verboseness level for debugging. Useful values: 0,1,2,3
25 verbose=0
26 log=sys.stdout.write
27
28 # 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 from types import ListType
34 # for SetBaseID
35 import binascii
36
37 from Thuban import _
38 from Thuban.Model.data import SHAPETYPE_POINT
39 # VirtualDC extends XMLWriter
40 from Thuban.Model.xmlwriter import XMLWriter, escape
41 # Color related classes from the model of thuban
42 from Thuban.Model.color import Transparent, Black
43 # 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 """Simple Point class to save x,y coordinates."""
62 def __init__(self, xp=0, yp=0):
63 self.x = xp
64 self.y = yp
65
66 def __repr__(self):
67 return "Point(%s, %s)" % (str(self.x), str(self.y))
68
69 class Trafo:
70 """Class for transformation properties transfer."""
71 def __init__(self):
72 self.trafos = []
73
74 def Append(self, type, coeffs):
75 """Append a transformation to the list."""
76 self.trafos.append((type, coeffs))
77
78 def Count(self):
79 """Get the number of transformations in list."""
80 return len(self.trafos)
81
82 def Pop(self):
83 """Pop and return a transformation from the end of the list."""
84 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 """Pen object for property transfer."""
94 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
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
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 """Brush property class."""
125 def __init__(self, bfill=Black, bpattern=None):
126 """Init the brush with the given values."""
127 self.fill = bfill
128 self.pattern = bpattern
129
130 def __str__(self):
131 return "Brush(" + str(self.fill) + ", " + str(self.pattern) + ")"
132
133 def GetColor(self):
134 return self.fill
135
136 def GetPattern(self):
137 return self.pattern
138
139 class Font:
140 """Font class that accts as property object."""
141 def __init__(self, ffamily='Helvetica', fsize=12):
142 """Init the font with the given values."""
143 self.family = ffamily
144 self.size = fsize
145
146 def GetFaceName(self):
147 """Return the fontfamily the font belongs to."""
148 return self.family
149
150 def GetPointSize(self):
151 """Return the size of the font in points."""
152 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 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 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 """Init SVGRenderer and call superclass init."""
181 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 self.used_baseids=[] # needed for name clash check
186
187 def make_point(self, x, y):
188 """Return a Point object from two values."""
189 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 """Return a pen/brush tuple build from a property object."""
197 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 if verbose > 1:
221 log("drawing polygon with brush %s and pen %s\n" %
222 (str(brush), str(pen)) )
223 if verbose > 2:
224 log("points: %s\n" %(repr(points)))
225
226 self.dc.SetBrush(brush)
227 self.dc.SetPen(pen)
228 self.dc.DrawPolygonPath(points)
229
230 def draw_point_shape(self, layer, points, pen, brush, size=2):
231 """Draw a point shape from layer with the given brush and pen
232
233 The shape is given by points argument which is a the return
234 value of the shape's Points() method. The coordinates in the
235 DC's coordinate system are determined with
236 self.projected_points.
237
238 The point is drawn as a circle centered on the point.
239 """
240 points = self.projected_points(layer, points)
241 if not points:
242 return
243
244 radius = self.factor * size
245 self.dc.SetBrush(brush)
246 self.dc.SetPen(pen)
247 for part in points:
248 for p in part:
249 self.dc.DrawCircle(p.x - radius, p.y - radius,
250 2.0 * radius)
251
252 def draw_shape_layer_incrementally(self, layer):
253 """Draw a shapelayer incrementally.
254 """
255 dc = self.dc
256 brush = TRANSPARENT_BRUSH
257 pen = TRANSPARENT_PEN
258
259 value = None
260 field = None
261 lc = layer.GetClassification()
262 field = layer.GetClassificationColumn()
263 defaultGroup = lc.GetDefaultGroup()
264 table = layer.ShapeStore().Table()
265
266 if lc.GetNumGroups() == 0:
267 # There's only the default group, so we can pretend that
268 # there is no field to classifiy on which makes things
269 # faster since we don't need the attribute information at
270 # all.
271 field = None
272 if verbose > 0:
273 log("layer %s has no classification\n" % layer.Title())
274
275 # Determine which render function to use.
276 useraw, draw_func, draw_func_param = \
277 self.low_level_renderer(layer)
278 if verbose > 0 : log("Using draw_func %s\n"%(repr(draw_func)))
279
280 tool_cache = {}
281
282 new_baseid=dc.SetBaseID(layer.title)
283 if new_baseid in self.used_baseids:
284 raise SVGMapWriterError(_("Clash of layer names!\n")+ \
285 _("Two layers probably have the same name, try renaming one."))
286 # prefix of a shape id to be unique
287 self.used_baseids.append(new_baseid)
288 # Titel of current layer to the groups meta informations
289 dc.BeginGroup(meta={'Layer':layer.Title(), })
290 # Delete all MetaData
291 dc.FlushMeta()
292 for shape in self.layer_shapes(layer):
293 if field is None:
294 group = defaultGroup
295 value = group.GetDisplayText()
296 else:
297 value = table.ReadValue(shape.ShapeID(), field)
298 group = lc.FindGroup(value)
299
300 if not group.IsVisible():
301 continue
302
303 # Render classification
304 shapeType = layer.ShapeType()
305 props = group.GetProperties()
306
307 # put meta infos into DC
308 if field and value:
309 dc.SetMeta({field:value, })
310 # set current shape id
311 dc.SetID(shape.ShapeID())
312
313 try:
314 pen, brush = tool_cache[id(group)]
315 except KeyError:
316 pen, brush = tool_cache[id(group)] \
317 = self.tools_for_property(group.GetProperties())
318
319 if useraw:
320 data = shape.RawData()
321 else:
322 data = shape.Points()
323
324 if shapeType==SHAPETYPE_POINT:
325 draw_func(draw_func_param, data, pen, brush,
326 size = group.GetProperties().GetSize())
327 else:
328 draw_func(draw_func_param, data, pen, brush)
329 # compatibility
330 if 0:
331 yield True
332 # reset shape id
333 dc.SetID(-1)
334 dc.SetBaseID("")
335 dc.EndGroup()
336
337 def draw_raster_layer(self, layer):
338 """Draw the raster layer"""
339 # TODO: For now we cannot draw raster layers onto our VirtualDC
340 log(_("Warning: Raster layer not written as " +
341 "svgexport does not support this yet!\n"))
342
343 def draw_raster_data(self, data, format="BMP"):
344 """Draw the raster image in data onto the DC"""
345 # TODO: For now we cannot draw raster data onto our VirtualDC
346 pass
347
348 def RenderMap(self, selected_layer, selected_shapes):
349 """Overriden to avoid automatic rendering of legend,
350 scalbar and frame.
351 """
352 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 """This class imitates a DC and writes SVG instead.
372
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 """
378 def __init__(self, file, dim=(0,0), units=''):
379 """Setup some variables and objects for property collection."""
380 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 """Write a string to the file with the current indention level.
396 """
397 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 """Append some metadata to the array that will be
402 written with the next svg-element
403 """
404 if key is '' or val is '':
405 return
406 self.meta[key] = val
407
408 def SetMeta(self, pairs, flush_after=1):
409 """Delete old meta informations and set the new ones."""
410 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 """Drop collected metadata."""
417 self.meta = {}
418
419 def BeginGroup(self, **args):
420 """Begin a group of elements.
421
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 """
428 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 """Examine a trafo object for asigned transformations details."""
453 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 """End a group of elements"""
466 self.indent_level -= 1
467 self.write_indent('</g>\n')
468 self.FlushMeta()
469
470 def BeginExport(self):
471 """Start the export process and write basic document
472 informations to the file.
473 """
474 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 """End the export process with closing the SVG tag and close
482 the file accessor"""
483 self.indent_level -= 1
484 self.write_indent('</svg>\n')
485 self.close()
486
487 def Close(self):
488 """Close the file."""
489 self.close()
490
491 def BeginDrawing(self):
492 """Dummy function to work with the Thuban renderers."""
493 pass
494
495 def EndDrawing(self):
496 """Dummy function to work with the Thuban renderers."""
497 pass
498
499 def GetSizeTuple(self):
500 """Return the dimension of this virtual canvas."""
501 return self.dim
502
503 def GetTextExtent(self, text):
504 """Return the dimension of the given text."""
505 # 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
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 def SetID(self, id):
550 """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 Only set this to positive integer numbers.
554 Read comments at SetBaseID() and make_id().
555 """
556 self.id = id
557
558 def SetFont(self, font):
559 """Set the fontproperties to use with text elements."""
560 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 """Set the style of the pen used to draw graphics."""
576 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 """Set the fill properties."""
587 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 """Set the color of the text foreground."""
596 self.font['fill'] = color.hex()
597
598 def make_style(self, line=0, fill=0, font=0):
599 """Build the style attribute including desired properties
600 such as fill, forground, stroke, etc."""
601 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 """Build the meta attribute."""
622 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 """Return id= string for object out of currently set baseid and id.
636
637 Return the empty string if no id was set.
638
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 (or negative x in SetID(x) to get an empty result)
648 Will raise SVGMapWriterError if SetID value cannot be converted to %d.
649
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 """
653 if self.id < 0:
654 return ''
655 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
662 def DrawEllipse(self, x, y, dx, dy):
663 """Draw an ellipse."""
664 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 """Draw a circle onto the virtual dc."""
670 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 """Draw a rectangle with the given parameters."""
676 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 """Draw Text at the given position."""
682 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 """Draw some points into a Buffer that will be
690 written before the next object.
691 """
692 self.DrawPolygonPath([points], closed=False)
693
694 def DrawPolygonPath(self, polys, closed=True):
695 """Draw a list of polygons or polylines as one SVG path.
696
697 Parameter:
698 polys list of poly- gons/lines; each consisting of a list of points
699 closed Boolean; optional; Default: True
700 False will leave each subpath open thus making it polylines.
701 """
702 self.write_indent('<path %s ' % (self.make_style(1,1,0)))
703 data = []
704 for poly in polys:
705 i = 0
706 for point in poly:
707 if i is 0:
708 data.append('\nM %s %s' % (point.x, point.y))
709 i+=1
710 else:
711 # SVG 1.1 Spec 8.3.1 recommends that lines length <= 255
712 # we make a best effort in throwing in a few newlines
713 data.append('\nL %s %s' % (point.x, point.y))
714 if closed:
715 data.append(' Z')
716
717 # Put everything together and write it to the file
718 self.file.write('%s %s d="%s"/>\n' % (self.make_id(),
719 self.make_meta(), join(data, '') ) )
720
721 def DrawSpline(self, points, closed=0):
722 """Calculate square bezier points for an xfig approximated spline.
723
724 DrawSpline() needs to do the same as the function of the Device Context
725 of wxWidgets. Code inspection shows it uses the "approximated
726 splines" of xfig <= 3.1. This can be mapped
727 on SVG's squared bezier curves,
728 by doing the same calculation like wxPostScriptDC::DoDrawSpline() in
729 wxWidgets src/generic/dcpsg.cpp.
730 Which is derived from xfig's 3.1.4 u_draw.c(draw_open_spline()).
731
732 And then leave out the last translation to cubic beziers
733 done in the postscript code of DrawSplineSection.
734 """
735 self.write_indent('<path %s ' % (self.make_style(1,1,0)))
736 datastr = ""
737
738 x1=points[0].x
739 y1=points[0].y
740
741 datastr+=('M %s %s ' % (x1, y1))
742
743 c=points[1].x
744 d=points[1].y
745
746 x3 = (x1 + c) / 2;
747 y3 = (y1 + d) / 2;
748
749 datastr+=('L %s %s ' % (x3,y3))
750
751 for point in points[2:]:
752 x2 = c
753 y2 = d
754 c = point.x
755 d = point.y
756 x3 = (x2 + c) / 2;
757 y3 = (y2 + d) / 2;
758
759 # With SVG's bezier commands, the last point becomes the next start
760 # so no new L necessary
761 # SVG Spec 1.1 recommends to not uses lines longer than 255 chars
762 datastr+=('Q %s %s %s %s\n' % (x2,y2,x3,y3))
763
764 datastr+=('L %s %s' % (c,d))
765
766 self.file.write('%s %s d="%s"/>\n' % (self.make_id(),
767 self.make_meta(), datastr ) )
768
769 def BeginClipPath(self, id):
770 """Build a clipping region to draw in."""
771 self.write_indent('<clipPath id="%s">\n' % id)
772 self.indent_level += 1
773
774 def EndClipPath(self):
775 """End a clip path."""
776 self.indent_level -= 1
777 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