/[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 2732 - (show annotations)
Mon Feb 26 23:02:25 2007 UTC (18 years ago) by bernhard
File MIME type: text/x-python
File size: 26897 byte(s)
* Extensions/svgexport/TODO: Added findings about how to write raster
in SVG.

* Extensions/svgexport/svgmapwriter.py: untabified two lines. 
Fixed a typo.

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