]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Renderer/SVG.js
fixes notices
[syp.git] / openlayers / lib / OpenLayers / Renderer / SVG.js
1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2  * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3  * full text of the license. */
4
5 /**
6  * @requires OpenLayers/Renderer/Elements.js
7  */
8
9 /**
10  * Class: OpenLayers.Renderer.SVG
11  * 
12  * Inherits:
13  *  - <OpenLayers.Renderer.Elements>
14  */
15 OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
16
17     /** 
18      * Property: xmlns
19      * {String}
20      */
21     xmlns: "http://www.w3.org/2000/svg",
22     
23     /**
24      * Property: xlinkns
25      * {String}
26      */
27     xlinkns: "http://www.w3.org/1999/xlink",
28
29     /**
30      * Constant: MAX_PIXEL
31      * {Integer} Firefox has a limitation where values larger or smaller than  
32      *           about 15000 in an SVG document lock the browser up. This 
33      *           works around it.
34      */
35     MAX_PIXEL: 15000,
36
37     /**
38      * Property: translationParameters
39      * {Object} Hash with "x" and "y" properties
40      */
41     translationParameters: null,
42     
43     /**
44      * Property: symbolSize
45      * {Object} Cache for symbol sizes according to their svg coordinate space
46      */
47     symbolSize: {},
48     
49     /**
50      * Property: isGecko
51      * {Boolean}
52      */
53     isGecko: null,
54
55     /**
56      * Constructor: OpenLayers.Renderer.SVG
57      * 
58      * Parameters:
59      * containerID - {String}
60      */
61     initialize: function(containerID) {
62         if (!this.supported()) { 
63             return; 
64         }
65         OpenLayers.Renderer.Elements.prototype.initialize.apply(this, 
66                                                                 arguments);
67         this.translationParameters = {x: 0, y: 0};
68         this.isGecko = (navigator.userAgent.toLowerCase().indexOf("gecko/") != -1);
69     },
70
71     /**
72      * APIMethod: destroy
73      */
74     destroy: function() {
75         OpenLayers.Renderer.Elements.prototype.destroy.apply(this, arguments);
76     },
77     
78     /**
79      * APIMethod: supported
80      * 
81      * Returns:
82      * {Boolean} Whether or not the browser supports the SVG renderer
83      */
84     supported: function() {
85         var svgFeature = "http://www.w3.org/TR/SVG11/feature#";
86         return (document.implementation && 
87            (document.implementation.hasFeature("org.w3c.svg", "1.0") || 
88             document.implementation.hasFeature(svgFeature + "SVG", "1.1") || 
89             document.implementation.hasFeature(svgFeature + "BasicStructure", "1.1") ));
90     },    
91
92     /**
93      * Method: inValidRange
94      * See #669 for more information
95      *
96      * Parameters:
97      * x      - {Integer}
98      * y      - {Integer}
99      * xyOnly - {Boolean} whether or not to just check for x and y, which means
100      *     to not take the current translation parameters into account if true.
101      * 
102      * Returns:
103      * {Boolean} Whether or not the 'x' and 'y' coordinates are in the  
104      *           valid range.
105      */ 
106     inValidRange: function(x, y, xyOnly) {
107         var left = x + (xyOnly ? 0 : this.translationParameters.x);
108         var top = y + (xyOnly ? 0 : this.translationParameters.y);
109         return (left >= -this.MAX_PIXEL && left <= this.MAX_PIXEL &&
110                 top >= -this.MAX_PIXEL && top <= this.MAX_PIXEL);
111     },
112
113     /**
114      * Method: setExtent
115      * 
116      * Parameters:
117      * extent - {<OpenLayers.Bounds>}
118      * resolutionChanged - {Boolean}
119      * 
120      * Returns:
121      * {Boolean} true to notify the layer that the new extent does not exceed
122      *     the coordinate range, and the features will not need to be redrawn.
123      *     False otherwise.
124      */
125     setExtent: function(extent, resolutionChanged) {
126         OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, 
127                                                                arguments);
128         
129         var resolution = this.getResolution();
130         var left = -extent.left / resolution;
131         var top = extent.top / resolution;
132
133         // If the resolution has changed, start over changing the corner, because
134         // the features will redraw.
135         if (resolutionChanged) {
136             this.left = left;
137             this.top = top;
138             // Set the viewbox
139             var extentString = "0 0 " + this.size.w + " " + this.size.h;
140
141             this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
142             this.translate(0, 0);
143             return true;
144         } else {
145             var inRange = this.translate(left - this.left, top - this.top);
146             if (!inRange) {
147                 // recenter the coordinate system
148                 this.setExtent(extent, true);
149             }
150             return inRange;
151         }
152     },
153     
154     /**
155      * Method: translate
156      * Transforms the SVG coordinate system
157      * 
158      * Parameters:
159      * x - {Float}
160      * y - {Float}
161      * 
162      * Returns:
163      * {Boolean} true if the translation parameters are in the valid coordinates
164      *     range, false otherwise.
165      */
166     translate: function(x, y) {
167         if (!this.inValidRange(x, y, true)) {
168             return false;
169         } else {
170             var transformString = "";
171             if (x || y) {
172                 transformString = "translate(" + x + "," + y + ")";
173             }
174             this.root.setAttributeNS(null, "transform", transformString);
175             this.translationParameters = {x: x, y: y};
176             return true;
177         }
178     },
179
180     /**
181      * Method: setSize
182      * Sets the size of the drawing surface.
183      * 
184      * Parameters:
185      * size - {<OpenLayers.Size>} The size of the drawing surface
186      */
187     setSize: function(size) {
188         OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
189         
190         this.rendererRoot.setAttributeNS(null, "width", this.size.w);
191         this.rendererRoot.setAttributeNS(null, "height", this.size.h);
192     },
193
194     /** 
195      * Method: getNodeType 
196      * 
197      * Parameters:
198      * geometry - {<OpenLayers.Geometry>}
199      * style - {Object}
200      * 
201      * Returns:
202      * {String} The corresponding node type for the specified geometry
203      */
204     getNodeType: function(geometry, style) {
205         var nodeType = null;
206         switch (geometry.CLASS_NAME) {
207             case "OpenLayers.Geometry.Point":
208                 if (style.externalGraphic) {
209                     nodeType = "image";
210                 } else if (this.isComplexSymbol(style.graphicName)) {
211                     nodeType = "use";
212                 } else {
213                     nodeType = "circle";
214                 }
215                 break;
216             case "OpenLayers.Geometry.Rectangle":
217                 nodeType = "rect";
218                 break;
219             case "OpenLayers.Geometry.LineString":
220                 nodeType = "polyline";
221                 break;
222             case "OpenLayers.Geometry.LinearRing":
223                 nodeType = "polygon";
224                 break;
225             case "OpenLayers.Geometry.Polygon":
226             case "OpenLayers.Geometry.Curve":
227             case "OpenLayers.Geometry.Surface":
228                 nodeType = "path";
229                 break;
230             default:
231                 break;
232         }
233         return nodeType;
234     },
235
236     /** 
237      * Method: setStyle
238      * Use to set all the style attributes to a SVG node.
239      * 
240      * Takes care to adjust stroke width and point radius to be
241      * resolution-relative
242      *
243      * Parameters:
244      * node - {SVGDomElement} An SVG element to decorate
245      * style - {Object}
246      * options - {Object} Currently supported options include 
247      *                              'isFilled' {Boolean} and
248      *                              'isStroked' {Boolean}
249      */
250     setStyle: function(node, style, options) {
251         style = style  || node._style;
252         options = options || node._options;
253         var r = parseFloat(node.getAttributeNS(null, "r"));
254         var widthFactor = 1;
255         var pos;
256         if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
257             node.style.visibility = "";
258             if (style.graphic === false) {
259                 node.style.visibility = "hidden";
260             } else if (style.externalGraphic) {
261                 pos = this.getPosition(node);
262                 
263                         if (style.graphicTitle) {
264                     node.setAttributeNS(null, "title", style.graphicTitle);
265                 }
266                 if (style.graphicWidth && style.graphicHeight) {
267                   node.setAttributeNS(null, "preserveAspectRatio", "none");
268                 }
269                 var width = style.graphicWidth || style.graphicHeight;
270                 var height = style.graphicHeight || style.graphicWidth;
271                 width = width ? width : style.pointRadius*2;
272                 height = height ? height : style.pointRadius*2;
273                 var xOffset = (style.graphicXOffset != undefined) ?
274                     style.graphicXOffset : -(0.5 * width);
275                 var yOffset = (style.graphicYOffset != undefined) ?
276                     style.graphicYOffset : -(0.5 * height);
277
278                 var opacity = style.graphicOpacity || style.fillOpacity;
279                 
280                 node.setAttributeNS(null, "x", (pos.x + xOffset).toFixed());
281                 node.setAttributeNS(null, "y", (pos.y + yOffset).toFixed());
282                 node.setAttributeNS(null, "width", width);
283                 node.setAttributeNS(null, "height", height);
284                 node.setAttributeNS(this.xlinkns, "href", style.externalGraphic);
285                 node.setAttributeNS(null, "style", "opacity: "+opacity);
286             } else if (this.isComplexSymbol(style.graphicName)) {
287                 // the symbol viewBox is three times as large as the symbol
288                 var offset = style.pointRadius * 3;
289                 var size = offset * 2;
290                 var id = this.importSymbol(style.graphicName);
291                 var href = "#" + id;
292                 pos = this.getPosition(node);
293                 widthFactor = this.symbolSize[id] / size;
294                 
295                 // remove the node from the dom before we modify it. This
296                 // prevents various rendering issues in Safari and FF
297                 var parent = node.parentNode;
298                 var nextSibling = node.nextSibling;
299                 if(parent) {
300                     parent.removeChild(node);
301                 }
302                 
303                 node.setAttributeNS(this.xlinkns, "href", href);
304                 node.setAttributeNS(null, "width", size);
305                 node.setAttributeNS(null, "height", size);
306                 node.setAttributeNS(null, "x", pos.x - offset);
307                 node.setAttributeNS(null, "y", pos.y - offset);
308                 
309                 // now that the node has all its new properties, insert it
310                 // back into the dom where it was
311                 if(nextSibling) {
312                     parent.insertBefore(node, nextSibling);
313                 } else if(parent) {
314                     parent.appendChild(node);
315                 }
316             } else {
317                 node.setAttributeNS(null, "r", style.pointRadius);
318             }
319
320             if (typeof style.rotation != "undefined" && pos) {
321                 var rotation = OpenLayers.String.format(
322                     "rotate(${0} ${1} ${2})", [style.rotation, pos.x, pos.y]);
323                 node.setAttributeNS(null, "transform", rotation);
324             }
325         }
326         
327         if (options.isFilled) {
328             node.setAttributeNS(null, "fill", style.fillColor);
329             node.setAttributeNS(null, "fill-opacity", style.fillOpacity);
330         } else {
331             node.setAttributeNS(null, "fill", "none");
332         }
333
334         if (options.isStroked) {
335             node.setAttributeNS(null, "stroke", style.strokeColor);
336             node.setAttributeNS(null, "stroke-opacity", style.strokeOpacity);
337             node.setAttributeNS(null, "stroke-width", style.strokeWidth * widthFactor);
338             node.setAttributeNS(null, "stroke-linecap", style.strokeLinecap);
339             // Hard-coded linejoin for now, to make it look the same as in VML.
340             // There is no strokeLinejoin property yet for symbolizers.
341             node.setAttributeNS(null, "stroke-linejoin", "round");
342             node.setAttributeNS(null, "stroke-dasharray", this.dashStyle(style,
343                 widthFactor));
344         } else {
345             node.setAttributeNS(null, "stroke", "none");
346         }
347         
348         if (style.pointerEvents) {
349             node.setAttributeNS(null, "pointer-events", style.pointerEvents);
350         }
351                         
352         if (style.cursor != null) {
353             node.setAttributeNS(null, "cursor", style.cursor);
354         }
355         
356         return node;
357     },
358
359     /** 
360      * Method: dashStyle
361      * 
362      * Parameters:
363      * style - {Object}
364      * widthFactor - {Number}
365      * 
366      * Returns:
367      * {String} A SVG compliant 'stroke-dasharray' value
368      */
369     dashStyle: function(style, widthFactor) {
370         var w = style.strokeWidth * widthFactor;
371
372         switch (style.strokeDashstyle) {
373             case 'solid':
374                 return 'none';
375             case 'dot':
376                 return [1, 4 * w].join();
377             case 'dash':
378                 return [4 * w, 4 * w].join();
379             case 'dashdot':
380                 return [4 * w, 4 * w, 1, 4 * w].join();
381             case 'longdash':
382                 return [8 * w, 4 * w].join();
383             case 'longdashdot':
384                 return [8 * w, 4 * w, 1, 4 * w].join();
385             default:
386                 return style.strokeDashstyle.replace(/ /g, ",");
387         }
388     },
389     
390     /** 
391      * Method: createNode
392      * 
393      * Parameters:
394      * type - {String} Kind of node to draw
395      * id - {String} Id for node
396      * 
397      * Returns:
398      * {DOMElement} A new node of the given type and id
399      */
400     createNode: function(type, id) {
401         var node = document.createElementNS(this.xmlns, type);
402         if (id) {
403             node.setAttributeNS(null, "id", id);
404         }
405         return node;    
406     },
407     
408     /** 
409      * Method: nodeTypeCompare
410      * 
411      * Parameters:
412      * node - {SVGDomElement} An SVG element
413      * type - {String} Kind of node
414      * 
415      * Returns:
416      * {Boolean} Whether or not the specified node is of the specified type
417      */
418     nodeTypeCompare: function(node, type) {
419         return (type == node.nodeName);
420     },
421    
422     /**
423      * Method: createRenderRoot
424      * 
425      * Returns:
426      * {DOMElement} The specific render engine's root element
427      */
428     createRenderRoot: function() {
429         return this.nodeFactory(this.container.id + "_svgRoot", "svg");
430     },
431
432     /**
433      * Method: createRoot
434      * 
435      * Parameter:
436      * suffix - {String} suffix to append to the id
437      * 
438      * Returns:
439      * {DOMElement}
440      */
441     createRoot: function(suffix) {
442         return this.nodeFactory(this.container.id + suffix, "g");
443     },
444
445     /**
446      * Method: createDefs
447      *
448      * Returns:
449      * {DOMElement} The element to which we'll add the symbol definitions
450      */
451     createDefs: function() {
452         var defs = this.nodeFactory(this.container.id + "_defs", "defs");
453         this.rendererRoot.appendChild(defs);
454         return defs;
455     },
456
457     /**************************************
458      *                                    *
459      *     GEOMETRY DRAWING FUNCTIONS     *
460      *                                    *
461      **************************************/
462
463     /**
464      * Method: drawPoint
465      * This method is only called by the renderer itself.
466      * 
467      * Parameters: 
468      * node - {DOMElement}
469      * geometry - {<OpenLayers.Geometry>}
470      * 
471      * Returns:
472      * {DOMElement} or false if the renderer could not draw the point
473      */ 
474     drawPoint: function(node, geometry) {
475         return this.drawCircle(node, geometry, 1);
476     },
477
478     /**
479      * Method: drawCircle
480      * This method is only called by the renderer itself.
481      * 
482      * Parameters: 
483      * node - {DOMElement}
484      * geometry - {<OpenLayers.Geometry>}
485      * radius - {Float}
486      * 
487      * Returns:
488      * {DOMElement} or false if the renderer could not draw the circle
489      */
490     drawCircle: function(node, geometry, radius) {
491         var resolution = this.getResolution();
492         var x = (geometry.x / resolution + this.left);
493         var y = (this.top - geometry.y / resolution);
494
495         if (this.inValidRange(x, y)) { 
496             node.setAttributeNS(null, "cx", x);
497             node.setAttributeNS(null, "cy", y);
498             node.setAttributeNS(null, "r", radius);
499             return node;
500         } else {
501             return false;
502         }    
503             
504     },
505     
506     /**
507      * Method: drawLineString
508      * This method is only called by the renderer itself.
509      * 
510      * Parameters: 
511      * node - {DOMElement}
512      * geometry - {<OpenLayers.Geometry>}
513      * 
514      * Returns:
515      * {DOMElement} or null if the renderer could not draw all components of
516      *     the linestring, or false if nothing could be drawn
517      */ 
518     drawLineString: function(node, geometry) {
519         var componentsResult = this.getComponentsString(geometry.components);
520         if (componentsResult.path) {
521             node.setAttributeNS(null, "points", componentsResult.path);
522             return (componentsResult.complete ? node : null);  
523         } else {
524             return false;
525         }
526     },
527     
528     /**
529      * Method: drawLinearRing
530      * This method is only called by the renderer itself.
531      * 
532      * Parameters: 
533      * node - {DOMElement}
534      * geometry - {<OpenLayers.Geometry>}
535      * 
536      * Returns:
537      * {DOMElement} or null if the renderer could not draw all components
538      *     of the linear ring, or false if nothing could be drawn
539      */ 
540     drawLinearRing: function(node, geometry) {
541         var componentsResult = this.getComponentsString(geometry.components);
542         if (componentsResult.path) {
543             node.setAttributeNS(null, "points", componentsResult.path);
544             return (componentsResult.complete ? node : null);  
545         } else {
546             return false;
547         }
548     },
549     
550     /**
551      * Method: drawPolygon
552      * This method is only called by the renderer itself.
553      * 
554      * Parameters: 
555      * node - {DOMElement}
556      * geometry - {<OpenLayers.Geometry>}
557      * 
558      * Returns:
559      * {DOMElement} or null if the renderer could not draw all components
560      *     of the polygon, or false if nothing could be drawn
561      */ 
562     drawPolygon: function(node, geometry) {
563         var d = "";
564         var draw = true;
565         var complete = true;
566         var linearRingResult, path;
567         for (var j=0, len=geometry.components.length; j<len; j++) {
568             d += " M";
569             linearRingResult = this.getComponentsString(
570                 geometry.components[j].components, " ");
571             path = linearRingResult.path;
572             if (path) {
573                 d += " " + path;
574                 complete = linearRingResult.complete && complete;
575             } else {
576                 draw = false;
577             }
578         }
579         d += " z";
580         if (draw) {
581             node.setAttributeNS(null, "d", d);
582             node.setAttributeNS(null, "fill-rule", "evenodd");
583             return complete ? node : null;
584         } else {
585             return false;
586         }    
587     },
588     
589     /**
590      * Method: drawRectangle
591      * This method is only called by the renderer itself.
592      * 
593      * Parameters: 
594      * node - {DOMElement}
595      * geometry - {<OpenLayers.Geometry>}
596      * 
597      * Returns:
598      * {DOMElement} or false if the renderer could not draw the rectangle
599      */ 
600     drawRectangle: function(node, geometry) {
601         var resolution = this.getResolution();
602         var x = (geometry.x / resolution + this.left);
603         var y = (this.top - geometry.y / resolution);
604
605         if (this.inValidRange(x, y)) { 
606             node.setAttributeNS(null, "x", x);
607             node.setAttributeNS(null, "y", y);
608             node.setAttributeNS(null, "width", geometry.width / resolution);
609             node.setAttributeNS(null, "height", geometry.height / resolution);
610             return node;
611         } else {
612             return false;
613         }
614     },
615     
616     /**
617      * Method: drawSurface
618      * This method is only called by the renderer itself.
619      * 
620      * Parameters: 
621      * node - {DOMElement}
622      * geometry - {<OpenLayers.Geometry>}
623      * 
624      * Returns:
625      * {DOMElement} or false if the renderer could not draw the surface
626      */ 
627     drawSurface: function(node, geometry) {
628
629         // create the svg path string representation
630         var d = null;
631         var draw = true;
632         for (var i=0, len=geometry.components.length; i<len; i++) {
633             if ((i%3) == 0 && (i/3) == 0) {
634                 var component = this.getShortString(geometry.components[i]);
635                 if (!component) { draw = false; }
636                 d = "M " + component;
637             } else if ((i%3) == 1) {
638                 var component = this.getShortString(geometry.components[i]);
639                 if (!component) { draw = false; }
640                 d += " C " + component;
641             } else {
642                 var component = this.getShortString(geometry.components[i]);
643                 if (!component) { draw = false; }
644                 d += " " + component;
645             }
646         }
647         d += " Z";
648         if (draw) {
649             node.setAttributeNS(null, "d", d);
650             return node;
651         } else {
652             return false;
653         }    
654     },
655     
656     /**
657      * Method: drawText
658      * This method is only called by the renderer itself.
659      * 
660      * Parameters: 
661      * featureId - {String}
662      * style -
663      * location - {<OpenLayers.Geometry.Point>}
664      */
665     drawText: function(featureId, style, location) {
666         var resolution = this.getResolution();
667         
668         var x = (location.x / resolution + this.left);
669         var y = (location.y / resolution - this.top);
670         
671         var label = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX, "text");
672         var tspan = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_tspan", "tspan");
673
674         label.setAttributeNS(null, "x", x);
675         label.setAttributeNS(null, "y", -y);
676         label.setAttributeNS(null, "pointer-events", "none");
677         
678         if (style.fontColor) {
679             label.setAttributeNS(null, "fill", style.fontColor);
680         }
681         if (style.fontFamily) {
682             label.setAttributeNS(null, "font-family", style.fontFamily);
683         }
684         if (style.fontSize) {
685             label.setAttributeNS(null, "font-size", style.fontSize);
686         }
687         if (style.fontWeight) {
688             label.setAttributeNS(null, "font-weight", style.fontWeight);
689         }
690         var align = style.labelAlign || "cm";
691         label.setAttributeNS(null, "text-anchor",
692             OpenLayers.Renderer.SVG.LABEL_ALIGN[align[0]] || "middle");
693
694         if (this.isGecko) {
695             label.setAttributeNS(null, "dominant-baseline",
696                 OpenLayers.Renderer.SVG.LABEL_ALIGN[align[1]] || "central");
697         } else {
698             tspan.setAttributeNS(null, "baseline-shift",
699                 OpenLayers.Renderer.SVG.LABEL_VSHIFT[align[1]] || "-35%");
700         }
701
702         tspan.textContent = style.label;
703         
704         if(!label.parentNode) {
705             label.appendChild(tspan);
706             this.textRoot.appendChild(label);
707         }   
708     },
709     
710     /** 
711      * Method: getComponentString
712      * 
713      * Parameters:
714      * components - {Array(<OpenLayers.Geometry.Point>)} Array of points
715      * separator - {String} character between coordinate pairs. Defaults to ","
716      * 
717      * Returns:
718      * {Object} hash with properties "path" (the string created from the
719      *     components and "complete" (false if the renderer was unable to
720      *     draw all components)
721      */
722     getComponentsString: function(components, separator) {
723         var renderCmp = [];
724         var complete = true;
725         var len = components.length;
726         var strings = [];
727         var str, component, j;
728         for(var i=0; i<len; i++) {
729             component = components[i];
730             renderCmp.push(component);
731             str = this.getShortString(component);
732             if (str) {
733                 strings.push(str);
734             } else {
735                 // The current component is outside the valid range. Let's
736                 // see if the previous or next component is inside the range.
737                 // If so, add the coordinate of the intersection with the
738                 // valid range bounds.
739                 if (i > 0) {
740                     if (this.getShortString(components[i - 1])) {
741                         strings.push(this.clipLine(components[i],
742                             components[i-1]));
743                     }
744                 }
745                 if (i < len - 1) {
746                     if (this.getShortString(components[i + 1])) {
747                         strings.push(this.clipLine(components[i],
748                             components[i+1]));
749                     }
750                 }
751                 complete = false;
752             }
753         }
754
755         return {
756             path: strings.join(separator || ","),
757             complete: complete
758         };
759     },
760     
761     /**
762      * Method: clipLine
763      * Given two points (one inside the valid range, and one outside),
764      * clips the line betweeen the two points so that the new points are both
765      * inside the valid range.
766      * 
767      * Parameters:
768      * badComponent - {<OpenLayers.Geometry.Point>)} original geometry of the
769      *     invalid point
770      * goodComponent - {<OpenLayers.Geometry.Point>)} original geometry of the
771      *     valid point
772      * Returns
773      * {String} the SVG coordinate pair of the clipped point (like
774      *     getShortString), or an empty string if both passed componets are at
775      *     the same point.
776      */
777     clipLine: function(badComponent, goodComponent) {
778         if (goodComponent.equals(badComponent)) {
779             return "";
780         }
781         var resolution = this.getResolution();
782         var maxX = this.MAX_PIXEL - this.translationParameters.x;
783         var maxY = this.MAX_PIXEL - this.translationParameters.y;
784         var x1 = goodComponent.x / resolution + this.left;
785         var y1 = this.top - goodComponent.y / resolution;
786         var x2 = badComponent.x / resolution + this.left;
787         var y2 = this.top - badComponent.y / resolution;
788         var k;
789         if (x2 < -maxX || x2 > maxX) {
790             k = (y2 - y1) / (x2 - x1);
791             x2 = x2 < 0 ? -maxX : maxX;
792             y2 = y1 + (x2 - x1) * k;
793         }
794         if (y2 < -maxY || y2 > maxY) {
795             k = (x2 - x1) / (y2 - y1);
796             y2 = y2 < 0 ? -maxY : maxY;
797             x2 = x1 + (y2 - y1) * k;
798         }
799         return x2 + "," + y2;
800     },
801
802     /** 
803      * Method: getShortString
804      * 
805      * Parameters:
806      * point - {<OpenLayers.Geometry.Point>}
807      * 
808      * Returns:
809      * {String} or false if point is outside the valid range
810      */
811     getShortString: function(point) {
812         var resolution = this.getResolution();
813         var x = (point.x / resolution + this.left);
814         var y = (this.top - point.y / resolution);
815
816         if (this.inValidRange(x, y)) { 
817             return x + "," + y;
818         } else {
819             return false;
820         }
821     },
822     
823     /**
824      * Method: getPosition
825      * Finds the position of an svg node.
826      * 
827      * Parameters:
828      * node - {DOMElement}
829      * 
830      * Returns:
831      * {Object} hash with x and y properties, representing the coordinates
832      *     within the svg coordinate system
833      */
834     getPosition: function(node) {
835         return({
836             x: parseFloat(node.getAttributeNS(null, "cx")),
837             y: parseFloat(node.getAttributeNS(null, "cy"))
838         });
839     },
840
841     /**
842      * Method: importSymbol
843      * add a new symbol definition from the rendererer's symbol hash
844      * 
845      * Parameters:
846      * graphicName - {String} name of the symbol to import
847      * 
848      * Returns:
849      * {String} - id of the imported symbol
850      */      
851     importSymbol: function (graphicName)  {
852         if (!this.defs) {
853             // create svg defs tag
854             this.defs = this.createDefs();
855         }
856         var id = this.container.id + "-" + graphicName;
857         
858         // check if symbol already exists in the defs
859         if (document.getElementById(id) != null) {
860             return id;
861         }
862         
863         var symbol = OpenLayers.Renderer.symbol[graphicName];
864         if (!symbol) {
865             throw new Error(graphicName + ' is not a valid symbol name');
866             return;
867         }
868
869         var symbolNode = this.nodeFactory(id, "symbol");
870         var node = this.nodeFactory(null, "polygon");
871         symbolNode.appendChild(node);
872         var symbolExtent = new OpenLayers.Bounds(
873                                     Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
874
875         var points = "";
876         var x,y;
877         for (var i=0; i<symbol.length; i=i+2) {
878             x = symbol[i];
879             y = symbol[i+1];
880             symbolExtent.left = Math.min(symbolExtent.left, x);
881             symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
882             symbolExtent.right = Math.max(symbolExtent.right, x);
883             symbolExtent.top = Math.max(symbolExtent.top, y);
884             points += " " + x + "," + y;
885         }
886         
887         node.setAttributeNS(null, "points", points);
888         
889         var width = symbolExtent.getWidth();
890         var height = symbolExtent.getHeight();
891         // create a viewBox three times as large as the symbol itself,
892         // to allow for strokeWidth being displayed correctly at the corners.
893         var viewBox = [symbolExtent.left - width,
894                         symbolExtent.bottom - height, width * 3, height * 3];
895         symbolNode.setAttributeNS(null, "viewBox", viewBox.join(" "));
896         this.symbolSize[id] = Math.max(width, height) * 3;
897         
898         this.defs.appendChild(symbolNode);
899         return symbolNode.id;
900     },
901
902     CLASS_NAME: "OpenLayers.Renderer.SVG"
903 });
904
905 /**
906  * Constant: OpenLayers.Renderer.SVG.LABEL_ALIGN
907  * {Object}
908  */
909 OpenLayers.Renderer.SVG.LABEL_ALIGN = {
910     "l": "start",
911     "r": "end",
912     "b": "bottom",
913     "t": "hanging"
914 };
915
916 /**
917  * Constant: OpenLayers.Renderer.SVG.LABEL_VSHIFT
918  * {Object}
919  */
920 OpenLayers.Renderer.SVG.LABEL_VSHIFT = {
921     // according to
922     // http://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-text-align-02-b.html
923     // a baseline-shift of -70% shifts the text exactly from the
924     // bottom to the top of the baseline, so -35% moves the text to
925     // the center of the baseline.
926     "t": "-70%",
927     "b": "0"    
928 };