/[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 2443 - (show 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 # 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.
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 # VirtualDC extends XMLWriter
39 from Thuban.Model.xmlwriter import XMLWriter, escape
40 # Color related classes from the model of thuban
41 from Thuban.Model.color import Transparent, Black
42 # 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 """Simple Point class to save x,y coordinates."""
61 def __init__(self, xp=0, yp=0):
62 self.x = xp
63 self.y = yp
64
65 class Trafo:
66 """Class for transformation properties transfer."""
67 def __init__(self):
68 self.trafos = []
69
70 def Append(self, type, coeffs):
71 """Append a transformation to the list."""
72 self.trafos.append((type, coeffs))
73
74 def Count(self):
75 """Get the number of transformations in list."""
76 return len(self.trafos)
77
78 def Pop(self):
79 """Pop and return a transformation from the end of the list."""
80 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 """Pen object for property transfer."""
90 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
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
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 """Brush property class."""
121 def __init__(self, bfill=Black, bpattern=None):
122 """Init the brush with the given values."""
123 self.fill = bfill
124 self.pattern = bpattern
125
126 def __str__(self):
127 return "Brush(" + str(self.fill) + "," + str(self.pattern) + ")"
128
129 def GetColor(self):
130 return self.fill
131
132 def GetPattern(self):
133 return self.pattern
134
135 class Font:
136 """Font class that accts as property object."""
137 def __init__(self, ffamily='Helvetica', fsize=12):
138 """Init the font with the given values."""
139 self.family = ffamily
140 self.size = fsize
141
142 def GetFaceName(self):
143 """Return the fontfamily the font belongs to."""
144 return self.family
145
146 def GetPointSize(self):
147 """Return the size of the font in points."""
148 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 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 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 """Init SVGRenderer and call superclass init."""
177 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 self.used_baseids=[] # needed for name clash check
182
183 def make_point(self, x, y):
184 """Return a Point object from two values."""
185 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 """Return a pen/brush tuple build from a property object."""
193 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 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
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
257 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 if verbose > 0:
271 log("layer %s has no classification\n" % layer.Title())
272
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 new_baseid=dc.SetBaseID(layer.title)
279 if new_baseid in self.used_baseids:
280 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 self.used_baseids.append(new_baseid)
284 # 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
299 # Render classification
300 shapeType = layer.ShapeType()
301 props = group.GetProperties()
302
303 # 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 if verbose > 0 : log("Using draw_func %s\n"%(repr(draw_func)))
320 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 """Draw the raster layer"""
331 # 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 """Overriden to avoid automatic rendering of legend,
341 scalbar and frame.
342 """
343 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 """This class imitates a DC and writes SVG instead.
363
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 """
369 def __init__(self, file, dim=(0,0), units=''):
370 """Setup some variables and objects for property collection."""
371 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 """Write a string to the file with the current indention level.
387 """
388 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 """Append some metadata to the array that will be
393 written with the next svg-element
394 """
395 if key is '' or val is '':
396 return
397 self.meta[key] = val
398
399 def SetMeta(self, pairs, flush_after=1):
400 """Delete old meta informations and set the new ones."""
401 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 """Drop collected metadata."""
408 self.meta = {}
409
410 def BeginGroup(self, **args):
411 """Begin a group of elements.
412
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 """
419 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 """Examine a trafo object for asigned transformations details."""
444 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 """End a group of elements"""
457 self.indent_level -= 1
458 self.write_indent('</g>\n')
459 self.FlushMeta()
460
461 def BeginExport(self):
462 """Start the export process and write basic document
463 informations to the file.
464 """
465 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 """End the export process with closing the SVG tag and close
473 the file accessor"""
474 self.indent_level -= 1
475 self.write_indent('</svg>\n')
476 self.close()
477
478 def Close(self):
479 """Close the file."""
480 self.close()
481
482 def BeginDrawing(self):
483 """Dummy function to work with the Thuban renderers."""
484 pass
485
486 def EndDrawing(self):
487 """Dummy function to work with the Thuban renderers."""
488 pass
489
490 def GetSizeTuple(self):
491 """Return the dimension of this virtual canvas."""
492 return self.dim
493
494 def GetTextExtent(self, text):
495 """Return the dimension of the given text."""
496 # 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
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 def SetID(self, id):
541 """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 Only set this to positive integer numbers.
545 Read comments at SetBaseID() and make_id().
546 """
547 self.id = id
548
549 def SetFont(self, font):
550 """Set the fontproperties to use with text elements."""
551 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 """Set the style of the pen used to draw graphics."""
567 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 """Set the fill properties."""
578 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 """Set the color of the text foreground."""
587 self.font['fill'] = color.hex()
588
589 def make_style(self, line=0, fill=0, font=0):
590 """Build the style attribute including desired properties
591 such as fill, forground, stroke, etc."""
592 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 """Build the meta attribute."""
613 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 """Return id= string for object out of currently set baseid and id.
627
628 Return the empty string if no id was set.
629
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 (or negative x in SetID(x) to get an empty result)
639 Will raise SVGMapWriterError if SetID value cannot be converted to %d.
640
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 """
644 if self.id < 0:
645 return ''
646 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
653 def DrawEllipse(self, x, y, dx, dy):
654 """Draw an ellipse."""
655 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 """Draw a circle onto the virtual dc."""
661 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 """Draw a rectangle with the given parameters."""
667 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 """Draw Text at the given position."""
673 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 """Draw some points into a Buffer that will be
681 written before the next object.
682 """
683 self.DrawPolygon(points,0)
684
685 def DrawPolygon(self, polygon, closed=1):
686 """Draw a polygon onto the virtual dc."""
687 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 i+=1
694 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
703 def DrawSpline(self, points, closed=0):
704 """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 """
717 self.write_indent('<path %s ' % (self.make_style(1,1,0)))
718 datastr = ""
719
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 self.file.write('%s %s d="%s"/>\n' % (self.make_id(),
749 self.make_meta(), datastr ) )
750
751 def BeginClipPath(self, id):
752 """Build a clipping region to draw in."""
753 self.write_indent('<clipPath id="%s">\n' % id)
754 self.indent_level += 1
755
756 def EndClipPath(self):
757 """End a clip path."""
758 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