]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Renderer/VML.js
initial commit
[syp.git] / openlayers / lib / OpenLayers / Renderer / VML.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.VML
11  * Render vector features in browsers with VML capability.  Construct a new
12  * VML renderer with the <OpenLayers.Renderer.VML> constructor.
13  * 
14  * Note that for all calculations in this class, we use toFixed() to round a 
15  * float value to an integer. This is done because it seems that VML doesn't 
16  * support float values.
17  *
18  * Inherits from:
19  *  - <OpenLayers.Renderer.Elements>
20  */
21 OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, {
22
23     /**
24      * Property: xmlns
25      * {String} XML Namespace URN
26      */
27     xmlns: "urn:schemas-microsoft-com:vml",
28     
29     /**
30      * Property: symbolCache
31      * {DOMElement} node holding symbols. This hash is keyed by symbol name,
32      *     and each value is a hash with a "path" and an "extent" property.
33      */
34     symbolCache: {},
35
36     /**
37      * Property: offset
38      * {Object} Hash with "x" and "y" properties
39      */
40     offset: null,
41     
42     /**
43      * Constructor: OpenLayers.Renderer.VML
44      * Create a new VML renderer.
45      *
46      * Parameters:
47      * containerID - {String} The id for the element that contains the renderer
48      */
49     initialize: function(containerID) {
50         if (!this.supported()) { 
51             return; 
52         }
53         if (!document.namespaces.olv) {
54             document.namespaces.add("olv", this.xmlns);
55             var style = document.createStyleSheet();
56             var shapes = ['shape','rect', 'oval', 'fill', 'stroke', 'imagedata', 'group','textbox']; 
57             for (var i = 0, len = shapes.length; i < len; i++) {
58
59                 style.addRule('olv\\:' + shapes[i], "behavior: url(#default#VML); " +
60                               "position: absolute; display: inline-block;");
61             }                  
62         }
63         
64         OpenLayers.Renderer.Elements.prototype.initialize.apply(this, 
65                                                                 arguments);
66         this.offset = {x: 0, y: 0};
67     },
68
69     /**
70      * APIMethod: destroy
71      * Deconstruct the renderer.
72      */
73     destroy: function() {
74         OpenLayers.Renderer.Elements.prototype.destroy.apply(this, arguments);
75     },
76
77     /**
78      * APIMethod: supported
79      * Determine whether a browser supports this renderer.
80      *
81      * Returns:
82      * {Boolean} The browser supports the VML renderer
83      */
84     supported: function() {
85         return !!(document.namespaces);
86     },    
87
88     /**
89      * Method: setExtent
90      * Set the renderer's extent
91      *
92      * Parameters:
93      * extent - {<OpenLayers.Bounds>}
94      * resolutionChanged - {Boolean}
95      * 
96      * Returns:
97      * {Boolean} true to notify the layer that the new extent does not exceed
98      *     the coordinate range, and the features will not need to be redrawn.
99      */
100     setExtent: function(extent, resolutionChanged) {
101         OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, 
102                                                                arguments);
103         var resolution = this.getResolution();
104     
105         var left = extent.left/resolution;
106         var top = extent.top/resolution - this.size.h;
107         if (resolutionChanged) {
108             this.offset = {x: left, y: top};
109             left = 0;
110             top = 0;
111         } else {
112             left = left - this.offset.x;
113             top = top - this.offset.y;
114         }
115
116         
117         var org = left + " " + top;
118         this.root.coordorigin = org;
119         var roots = [this.root, this.vectorRoot, this.textRoot];
120         var root;
121         for(var i=0, len=roots.length; i<len; ++i) {
122             root = roots[i];
123
124             var size = this.size.w + " " + this.size.h;
125             root.coordsize = size;
126             
127         }
128         // flip the VML display Y axis upside down so it 
129         // matches the display Y axis of the map
130         this.root.style.flip = "y";
131         
132         return true;
133     },
134
135
136     /**
137      * Method: setSize
138      * Set the size of the drawing surface
139      *
140      * Parameters:
141      * size - {<OpenLayers.Size>} the size of the drawing surface
142      */
143     setSize: function(size) {
144         OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
145         
146         // setting width and height on all roots to avoid flicker which we
147         // would get with 100% width and height on child roots
148         var roots = [
149             this.rendererRoot,
150             this.root,
151             this.vectorRoot,
152             this.textRoot
153         ];
154         var w = this.size.w + "px";
155         var h = this.size.h + "px";
156         var root;
157         for(var i=0, len=roots.length; i<len; ++i) {
158             root = roots[i];
159             root.style.width = w;
160             root.style.height = h;
161         }
162     },
163
164     /**
165      * Method: getNodeType
166      * Get the node type for a geometry and style
167      *
168      * Parameters:
169      * geometry - {<OpenLayers.Geometry>}
170      * style - {Object}
171      *
172      * Returns:
173      * {String} The corresponding node type for the specified geometry
174      */
175     getNodeType: function(geometry, style) {
176         var nodeType = null;
177         switch (geometry.CLASS_NAME) {
178             case "OpenLayers.Geometry.Point":
179                 if (style.externalGraphic) {
180                     nodeType = "olv:rect";
181                 } else if (this.isComplexSymbol(style.graphicName)) {
182                     nodeType = "olv:shape";
183                 } else {
184                     nodeType = "olv:oval";
185                 }
186                 break;
187             case "OpenLayers.Geometry.Rectangle":
188                 nodeType = "olv:rect";
189                 break;
190             case "OpenLayers.Geometry.LineString":
191             case "OpenLayers.Geometry.LinearRing":
192             case "OpenLayers.Geometry.Polygon":
193             case "OpenLayers.Geometry.Curve":
194             case "OpenLayers.Geometry.Surface":
195                 nodeType = "olv:shape";
196                 break;
197             default:
198                 break;
199         }
200         return nodeType;
201     },
202
203     /**
204      * Method: setStyle
205      * Use to set all the style attributes to a VML node.
206      *
207      * Parameters:
208      * node - {DOMElement} An VML element to decorate
209      * style - {Object}
210      * options - {Object} Currently supported options include 
211      *                              'isFilled' {Boolean} and
212      *                              'isStroked' {Boolean}
213      * geometry - {<OpenLayers.Geometry>}
214      */
215     setStyle: function(node, style, options, geometry) {
216         style = style  || node._style;
217         options = options || node._options;
218         var widthFactor = 1;
219         
220         if (node._geometryClass == "OpenLayers.Geometry.Point") {
221             if (style.externalGraphic) {
222                         if (style.graphicTitle) {
223                     node.title=style.graphicTitle;
224                 } 
225                 var width = style.graphicWidth || style.graphicHeight;
226                 var height = style.graphicHeight || style.graphicWidth;
227                 width = width ? width : style.pointRadius*2;
228                 height = height ? height : style.pointRadius*2;
229
230                 var resolution = this.getResolution();
231                 var xOffset = (style.graphicXOffset != undefined) ?
232                     style.graphicXOffset : -(0.5 * width);
233                 var yOffset = (style.graphicYOffset != undefined) ?
234                     style.graphicYOffset : -(0.5 * height);
235                 
236                 node.style.left = ((geometry.x/resolution - this.offset.x)+xOffset).toFixed();
237                 node.style.top = ((geometry.y/resolution - this.offset.y)-(yOffset+height)).toFixed();
238                 node.style.width = width + "px";
239                 node.style.height = height + "px";
240                 node.style.flip = "y";
241                 
242                 // modify style/options for fill and stroke styling below
243                 style.fillColor = "none";
244                 options.isStroked = false;
245             } else if (this.isComplexSymbol(style.graphicName)) {
246                 var cache = this.importSymbol(style.graphicName);
247                 node.path = cache.path;
248                 node.coordorigin = cache.left + "," + cache.bottom;
249                 var size = cache.size;
250                 node.coordsize = size + "," + size;        
251                 this.drawCircle(node, geometry, style.pointRadius);
252                 node.style.flip = "y";
253             } else {
254                 this.drawCircle(node, geometry, style.pointRadius);
255             }
256         }
257
258         // fill 
259         if (options.isFilled) { 
260             node.fillcolor = style.fillColor; 
261         } else { 
262             node.filled = "false"; 
263         }
264         var fills = node.getElementsByTagName("fill");
265         var fill = (fills.length == 0) ? null : fills[0];
266         if (!options.isFilled) {
267             if (fill) {
268                 node.removeChild(fill);
269             }
270         } else {
271             if (!fill) {
272                 fill = this.createNode('olv:fill', node.id + "_fill");
273             }
274             fill.opacity = style.fillOpacity;
275
276             if (node._geometryClass == "OpenLayers.Geometry.Point" &&
277                     style.externalGraphic) {
278
279                 // override fillOpacity
280                 if (style.graphicOpacity) {
281                     fill.opacity = style.graphicOpacity;
282                 }
283                 
284                 fill.src = style.externalGraphic;
285                 fill.type = "frame";
286                 
287                 if (!(style.graphicWidth && style.graphicHeight)) {
288                   fill.aspect = "atmost";
289                 }                
290             }
291             if (fill.parentNode != node) {
292                 node.appendChild(fill);
293             }
294         }
295
296         // additional rendering for rotated graphics or symbols
297         if (typeof style.rotation != "undefined") {
298             if (style.externalGraphic) {
299                 this.graphicRotate(node, xOffset, yOffset);
300                 // make the fill fully transparent, because we now have
301                 // the graphic as imagedata element. We cannot just remove
302                 // the fill, because this is part of the hack described
303                 // in graphicRotate
304                 fill.opacity = 0;
305             } else {
306                 node.style.rotation = style.rotation;
307             }
308         }
309
310         // stroke 
311         if (options.isStroked) { 
312             node.strokecolor = style.strokeColor; 
313             node.strokeweight = style.strokeWidth + "px"; 
314         } else { 
315             node.stroked = false; 
316         }
317         var strokes = node.getElementsByTagName("stroke");
318         var stroke = (strokes.length == 0) ? null : strokes[0];
319         if (!options.isStroked) {
320             if (stroke) {
321                 node.removeChild(stroke);
322             }
323         } else {
324             if (!stroke) {
325                 stroke = this.createNode('olv:stroke', node.id + "_stroke");
326                 node.appendChild(stroke);
327             }
328             stroke.opacity = style.strokeOpacity;
329             stroke.endcap = !style.strokeLinecap || style.strokeLinecap == 'butt' ? 'flat' : style.strokeLinecap;
330             stroke.dashstyle = this.dashStyle(style);
331         }
332         
333         if (style.cursor != "inherit" && style.cursor != null) {
334             node.style.cursor = style.cursor;
335         }
336         return node;
337     },
338
339     /**
340      * Method: graphicRotate
341      * If a point is to be styled with externalGraphic and rotation, VML fills
342      * cannot be used to display the graphic, because rotation of graphic
343      * fills is not supported by the VML implementation of Internet Explorer.
344      * This method creates a olv:imagedata element inside the VML node,
345      * DXImageTransform.Matrix and BasicImage filters for rotation and
346      * opacity, and a 3-step hack to remove rendering artefacts from the
347      * graphic and preserve the ability of graphics to trigger events.
348      * Finally, OpenLayers methods are used to determine the correct
349      * insertion point of the rotated image, because DXImageTransform.Matrix
350      * does the rotation without the ability to specify a rotation center
351      * point.
352      * 
353      * Parameters:
354      * node    - {DOMElement}
355      * xOffset - {Number} rotation center relative to image, x coordinate
356      * yOffset - {Number} rotation center relative to image, y coordinate
357      */
358     graphicRotate: function(node, xOffset, yOffset) {
359         var style = style || node._style;
360         var options = node._options;
361         
362         var aspectRatio, size;
363         if (!(style.graphicWidth && style.graphicHeight)) {
364             // load the image to determine its size
365             var img = new Image();
366             img.onreadystatechange = OpenLayers.Function.bind(function() {
367                 if(img.readyState == "complete" ||
368                         img.readyState == "interactive") {
369                     aspectRatio = img.width / img.height;
370                     size = Math.max(style.pointRadius * 2, 
371                         style.graphicWidth || 0,
372                         style.graphicHeight || 0);
373                     xOffset = xOffset * aspectRatio;
374                     style.graphicWidth = size * aspectRatio;
375                     style.graphicHeight = size;
376                     this.graphicRotate(node, xOffset, yOffset);
377                 }
378             }, this);
379             img.src = style.externalGraphic;
380             
381             // will be called again by the onreadystate handler
382             return;
383         } else {
384             size = Math.max(style.graphicWidth, style.graphicHeight);
385             aspectRatio = style.graphicWidth / style.graphicHeight;
386         }
387         
388         var width = Math.round(style.graphicWidth || size * aspectRatio);
389         var height = Math.round(style.graphicHeight || size);
390         node.style.width = width + "px";
391         node.style.height = height + "px";
392         
393         // Three steps are required to remove artefacts for images with
394         // transparent backgrounds (resulting from using DXImageTransform
395         // filters on svg objects), while preserving awareness for browser
396         // events on images:
397         // - Use the fill as usual (like for unrotated images) to handle
398         //   events
399         // - specify an imagedata element with the same src as the fill
400         // - style the imagedata element with an AlphaImageLoader filter
401         //   with empty src
402         var image = document.getElementById(node.id + "_image");
403         if (!image) {
404             image = this.createNode("olv:imagedata", node.id + "_image");
405             node.appendChild(image);
406         }
407         image.style.width = width + "px";
408         image.style.height = height + "px";
409         image.src = style.externalGraphic;
410         image.style.filter =
411             "progid:DXImageTransform.Microsoft.AlphaImageLoader(" + 
412             "src='', sizingMethod='scale')";
413
414         var rotation = style.rotation * Math.PI / 180;
415         var sintheta = Math.sin(rotation);
416         var costheta = Math.cos(rotation);
417
418         // do the rotation on the image
419         var filter =
420             "progid:DXImageTransform.Microsoft.Matrix(M11=" + costheta +
421             ",M12=" + (-sintheta) + ",M21=" + sintheta + ",M22=" + costheta +
422             ",SizingMethod='auto expand')\n";
423
424         // set the opacity (needed for the imagedata)
425         var opacity = style.graphicOpacity || style.fillOpacity;
426         if (opacity && opacity != 1) {
427             filter += 
428                 "progid:DXImageTransform.Microsoft.BasicImage(opacity=" + 
429                 opacity+")\n";
430         }
431         node.style.filter = filter;
432
433         // do the rotation again on a box, so we know the insertion point
434         var centerPoint = new OpenLayers.Geometry.Point(-xOffset, -yOffset);
435         var imgBox = new OpenLayers.Bounds(0, 0, width, height).toGeometry();
436         imgBox.rotate(style.rotation, centerPoint);
437         var imgBounds = imgBox.getBounds();
438
439         node.style.left = Math.round(
440             parseInt(node.style.left) + imgBounds.left) + "px";
441         node.style.top = Math.round(
442             parseInt(node.style.top) - imgBounds.bottom) + "px";
443     },
444
445     /**
446      * Method: postDraw
447      * Some versions of Internet Explorer seem to be unable to set fillcolor
448      * and strokecolor to "none" correctly before the fill node is appended to
449      * a visible vml node. This method takes care of that and sets fillcolor
450      * and strokecolor again if needed.
451      * 
452      * Parameters:
453      * node - {DOMElement}
454      */
455     postDraw: function(node) {
456         var fillColor = node._style.fillColor;
457         var strokeColor = node._style.strokeColor;
458         if (fillColor == "none" &&
459                 node.fillcolor != fillColor) {
460             node.fillcolor = fillColor;
461         }
462         if (strokeColor == "none" &&
463                 node.strokecolor != strokeColor) {
464             node.strokecolor = strokeColor;
465         }
466     },
467
468
469     /**
470      * Method: setNodeDimension
471      * Get the geometry's bounds, convert it to our vml coordinate system, 
472      * then set the node's position, size, and local coordinate system.
473      *   
474      * Parameters:
475      * node - {DOMElement}
476      * geometry - {<OpenLayers.Geometry>}
477      */
478     setNodeDimension: function(node, geometry) {
479
480         var bbox = geometry.getBounds();
481         if(bbox) {
482             var resolution = this.getResolution();
483         
484             var scaledBox = 
485                 new OpenLayers.Bounds((bbox.left/resolution - this.offset.x).toFixed(),
486                                       (bbox.bottom/resolution - this.offset.y).toFixed(),
487                                       (bbox.right/resolution - this.offset.x).toFixed(),
488                                       (bbox.top/resolution - this.offset.y).toFixed());
489             
490             // Set the internal coordinate system to draw the path
491             node.style.left = scaledBox.left + "px";
492             node.style.top = scaledBox.top + "px";
493             node.style.width = scaledBox.getWidth() + "px";
494             node.style.height = scaledBox.getHeight() + "px";
495     
496             node.coordorigin = scaledBox.left + " " + scaledBox.top;
497             node.coordsize = scaledBox.getWidth()+ " " + scaledBox.getHeight();
498         }
499     },
500     
501     /** 
502      * Method: dashStyle
503      * 
504      * Parameters:
505      * style - {Object}
506      * 
507      * Returns:
508      * {String} A VML compliant 'stroke-dasharray' value
509      */
510     dashStyle: function(style) {
511         var dash = style.strokeDashstyle;
512         switch (dash) {
513             case 'solid':
514             case 'dot':
515             case 'dash':
516             case 'dashdot':
517             case 'longdash':
518             case 'longdashdot':
519                 return dash;
520             default:
521                 // very basic guessing of dash style patterns
522                 var parts = dash.split(/[ ,]/);
523                 if (parts.length == 2) {
524                     if (1*parts[0] >= 2*parts[1]) {
525                         return "longdash";
526                     }
527                     return (parts[0] == 1 || parts[1] == 1) ? "dot" : "dash";
528                 } else if (parts.length == 4) {
529                     return (1*parts[0] >= 2*parts[1]) ? "longdashdot" :
530                         "dashdot";
531                 }
532                 return "solid";
533         }
534     },
535
536     /**
537      * Method: createNode
538      * Create a new node
539      *
540      * Parameters:
541      * type - {String} Kind of node to draw
542      * id - {String} Id for node
543      *
544      * Returns:
545      * {DOMElement} A new node of the given type and id
546      */
547     createNode: function(type, id) {
548         var node = document.createElement(type);
549         if (id) {
550             node.id = id;
551         }
552         
553         // IE hack to make elements unselectable, to prevent 'blue flash'
554         // while dragging vectors; #1410
555         node.unselectable = 'on';
556         node.onselectstart = function() { return(false); };
557         
558         return node;    
559     },
560     
561     /**
562      * Method: nodeTypeCompare
563      * Determine whether a node is of a given type
564      *
565      * Parameters:
566      * node - {DOMElement} An VML element
567      * type - {String} Kind of node
568      *
569      * Returns:
570      * {Boolean} Whether or not the specified node is of the specified type
571      */
572     nodeTypeCompare: function(node, type) {
573
574         //split type
575         var subType = type;
576         var splitIndex = subType.indexOf(":");
577         if (splitIndex != -1) {
578             subType = subType.substr(splitIndex+1);
579         }
580
581         //split nodeName
582         var nodeName = node.nodeName;
583         splitIndex = nodeName.indexOf(":");
584         if (splitIndex != -1) {
585             nodeName = nodeName.substr(splitIndex+1);
586         }
587
588         return (subType == nodeName);
589     },
590
591     /**
592      * Method: createRenderRoot
593      * Create the renderer root
594      *
595      * Returns:
596      * {DOMElement} The specific render engine's root element
597      */
598     createRenderRoot: function() {
599         return this.nodeFactory(this.container.id + "_vmlRoot", "div");
600     },
601
602     /**
603      * Method: createRoot
604      * Create the main root element
605      * 
606      * Parameters:
607      * suffix - {String} suffix to append to the id
608      *
609      * Returns:
610      * {DOMElement}
611      */
612     createRoot: function(suffix) {
613         return this.nodeFactory(this.container.id + suffix, "olv:group");
614     },
615     
616     /**************************************
617      *                                    *
618      *     GEOMETRY DRAWING FUNCTIONS     *
619      *                                    *
620      **************************************/
621     
622     /**
623      * Method: drawPoint
624      * Render a point
625      * 
626      * Parameters:
627      * node - {DOMElement}
628      * geometry - {<OpenLayers.Geometry>}
629      * 
630      * Returns:
631      * {DOMElement} or false if the point could not be drawn
632      */
633     drawPoint: function(node, geometry) {
634         return this.drawCircle(node, geometry, 1);
635     },
636
637     /**
638      * Method: drawCircle
639      * Render a circle.
640      * Size and Center a circle given geometry (x,y center) and radius
641      * 
642      * Parameters:
643      * node - {DOMElement}
644      * geometry - {<OpenLayers.Geometry>}
645      * radius - {float}
646      * 
647      * Returns:
648      * {DOMElement} or false if the circle could not ne drawn
649      */
650     drawCircle: function(node, geometry, radius) {
651         if(!isNaN(geometry.x)&& !isNaN(geometry.y)) {
652             var resolution = this.getResolution();
653
654             node.style.left = ((geometry.x /resolution - this.offset.x).toFixed() - radius) + "px";
655             node.style.top = ((geometry.y /resolution - this.offset.y).toFixed() - radius) + "px";
656     
657             var diameter = radius * 2;
658             
659             node.style.width = diameter + "px";
660             node.style.height = diameter + "px";
661             return node;
662         }
663         return false;
664     },
665
666
667     /**
668      * Method: drawLineString
669      * Render a linestring.
670      * 
671      * Parameters:
672      * node - {DOMElement}
673      * geometry - {<OpenLayers.Geometry>}
674      * 
675      * Returns:
676      * {DOMElement}
677      */
678     drawLineString: function(node, geometry) {
679         return this.drawLine(node, geometry, false);
680     },
681
682     /**
683      * Method: drawLinearRing
684      * Render a linearring
685      * 
686      * Parameters:
687      * node - {DOMElement}
688      * geometry - {<OpenLayers.Geometry>}
689      * 
690      * Returns:
691      * {DOMElement}
692      */
693     drawLinearRing: function(node, geometry) {
694         return this.drawLine(node, geometry, true);
695     },
696
697     /**
698      * Method: DrawLine
699      * Render a line.
700      * 
701      * Parameters:
702      * node - {DOMElement}
703      * geometry - {<OpenLayers.Geometry>}
704      * closeLine - {Boolean} Close the line? (make it a ring?)
705      * 
706      * Returns:
707      * {DOMElement}
708      */
709     drawLine: function(node, geometry, closeLine) {
710
711         this.setNodeDimension(node, geometry);
712
713         var resolution = this.getResolution();
714         var numComponents = geometry.components.length;
715         var parts = new Array(numComponents);
716
717         var comp, x, y;
718         for (var i = 0; i < numComponents; i++) {
719             comp = geometry.components[i];
720             x = (comp.x/resolution - this.offset.x);
721             y = (comp.y/resolution - this.offset.y);
722             parts[i] = " " + x.toFixed() + "," + y.toFixed() + " l ";
723         }
724         var end = (closeLine) ? " x e" : " e";
725         node.path = "m" + parts.join("") + end;
726         return node;
727     },
728
729     /**
730      * Method: drawPolygon
731      * Render a polygon
732      * 
733      * Parameters:
734      * node - {DOMElement}
735      * geometry - {<OpenLayers.Geometry>}
736      * 
737      * Returns:
738      * {DOMElement}
739      */
740     drawPolygon: function(node, geometry) {
741         this.setNodeDimension(node, geometry);
742
743         var resolution = this.getResolution();
744     
745         var path = [];
746         var linearRing, i, j, len, ilen, comp, x, y;
747         for (j = 0, len=geometry.components.length; j<len; j++) {
748             linearRing = geometry.components[j];
749
750             path.push("m");
751             for (i=0, ilen=linearRing.components.length; i<ilen; i++) {
752                 comp = linearRing.components[i];
753                 x = comp.x / resolution - this.offset.x;
754                 y = comp.y / resolution - this.offset.y;
755                 path.push(" " + x.toFixed() + "," + y.toFixed());
756                 if (i==0) {
757                     path.push(" l");
758                 }
759             }
760             path.push(" x ");
761         }
762         path.push("e");
763         node.path = path.join("");
764         return node;
765     },
766
767     /**
768      * Method: drawRectangle
769      * Render a rectangle
770      * 
771      * Parameters:
772      * node - {DOMElement}
773      * geometry - {<OpenLayers.Geometry>}
774      * 
775      * Returns:
776      * {DOMElement}
777      */
778     drawRectangle: function(node, geometry) {
779         var resolution = this.getResolution();
780     
781         node.style.left = (geometry.x/resolution - this.offset.x) + "px";
782         node.style.top = (geometry.y/resolution - this.offset.y) + "px";
783         node.style.width = geometry.width/resolution + "px";
784         node.style.height = geometry.height/resolution + "px";
785         
786         return node;
787     },
788     
789     /**
790      * Method: drawText
791      * This method is only called by the renderer itself.
792      * 
793      * Parameters: 
794      * featureId - {String}
795      * style -
796      * location - {<OpenLayers.Geometry.Point>}
797      */
798     drawText: function(featureId, style, location) {
799         var label = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX, "olv:rect");
800         var textbox = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_textbox", "olv:textbox");
801         
802         var resolution = this.getResolution();
803         label.style.left = (location.x/resolution - this.offset.x).toFixed() + "px";
804         label.style.top = (location.y/resolution - this.offset.y).toFixed() + "px";
805         label.style.flip = "y";
806
807         textbox.innerText = style.label;
808
809         if (style.fillColor) {
810             textbox.style.color = style.fontColor;
811         }
812         if (style.fontFamily) {
813             textbox.style.fontFamily = style.fontFamily;
814         }
815         if (style.fontSize) {
816             textbox.style.fontSize = style.fontSize;
817         }
818         if (style.fontWeight) {
819             textbox.style.fontWeight = style.fontWeight;
820         }
821         textbox.style.whiteSpace = "nowrap";
822         // fun with IE: IE7 in standards compliant mode does not display any
823         // text with a left inset of 0. So we set this to 1px and subtract one
824         // pixel later when we set label.style.left
825         textbox.inset = "1px,0px,0px,0px";
826
827         if(!label.parentNode) {
828             label.appendChild(textbox);
829             this.textRoot.appendChild(label);
830         }
831
832         var align = style.labelAlign || "cm";
833         var xshift = textbox.clientWidth *
834             (OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(0,1)]);
835         var yshift = textbox.clientHeight *
836             (OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(1,1)]);
837         label.style.left = parseInt(label.style.left)-xshift-1+"px";
838         label.style.top = parseInt(label.style.top)+yshift+"px";
839     },
840
841     /**
842      * Method: drawSurface
843      * 
844      * Parameters:
845      * node - {DOMElement}
846      * geometry - {<OpenLayers.Geometry>}
847      * 
848      * Returns:
849      * {DOMElement}
850      */
851     drawSurface: function(node, geometry) {
852
853         this.setNodeDimension(node, geometry);
854
855         var resolution = this.getResolution();
856     
857         var path = [];
858         var comp, x, y;
859         for (var i=0, len=geometry.components.length; i<len; i++) {
860             comp = geometry.components[i];
861             x = comp.x / resolution - this.offset.x;
862             y = comp.y / resolution - this.offset.y;
863             if ((i%3)==0 && (i/3)==0) {
864                 path.push("m");
865             } else if ((i%3)==1) {
866                 path.push(" c");
867             }
868             path.push(" " + x + "," + y);
869         }
870         path.push(" x e");
871
872         node.path = path.join("");
873         return node;
874     },
875     
876     /**
877      * Method: moveRoot
878      * moves this renderer's root to a different renderer.
879      * 
880      * Parameters:
881      * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
882      * root - {DOMElement} optional root node. To be used when this renderer
883      *     holds roots from multiple layers to tell this method which one to
884      *     detach
885      * 
886      * Returns:
887      * {Boolean} true if successful, false otherwise
888      */
889     moveRoot: function(renderer) {
890         var layer = this.map.getLayer(renderer.container.id);
891         if(layer instanceof OpenLayers.Layer.Vector.RootContainer) {
892             layer = this.map.getLayer(this.container.id);
893         }
894         layer && layer.renderer.clear();
895         OpenLayers.Renderer.Elements.prototype.moveRoot.apply(this, arguments);
896         layer && layer.redraw();
897     },
898     
899     /**
900      * Method: importSymbol
901      * add a new symbol definition from the rendererer's symbol hash
902      * 
903      * Parameters:
904      * graphicName - {String} name of the symbol to import
905      * 
906      * Returns:
907      * {Object} - hash of {DOMElement} "symbol" and {Number} "size"
908      */      
909     importSymbol: function (graphicName)  {
910         var id = this.container.id + "-" + graphicName;
911         
912         // check if symbol already exists in the cache
913         var cache = this.symbolCache[id];
914         if (cache) {
915             return cache;
916         }
917         
918         var symbol = OpenLayers.Renderer.symbol[graphicName];
919         if (!symbol) {
920             throw new Error(graphicName + ' is not a valid symbol name');
921             return;
922         }
923
924         var symbolExtent = new OpenLayers.Bounds(
925                                     Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
926         
927         var pathitems = ["m"];
928         for (var i=0; i<symbol.length; i=i+2) {
929             x = symbol[i];
930             y = symbol[i+1];
931             symbolExtent.left = Math.min(symbolExtent.left, x);
932             symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
933             symbolExtent.right = Math.max(symbolExtent.right, x);
934             symbolExtent.top = Math.max(symbolExtent.top, y);
935
936             pathitems.push(x);
937             pathitems.push(y);
938             if (i == 0) {
939                 pathitems.push("l");
940             }
941         }
942         pathitems.push("x e");
943         var path = pathitems.join(" ");
944
945         var diff = (symbolExtent.getWidth() - symbolExtent.getHeight()) / 2;
946         if(diff > 0) {
947             symbolExtent.bottom = symbolExtent.bottom - diff;
948             symbolExtent.top = symbolExtent.top + diff;
949         } else {
950             symbolExtent.left = symbolExtent.left - diff;
951             symbolExtent.right = symbolExtent.right + diff;
952         }
953         
954         cache = {
955             path: path,
956             size: symbolExtent.getWidth(), // equals getHeight() now
957             left: symbolExtent.left,
958             bottom: symbolExtent.bottom
959         };
960         this.symbolCache[id] = cache;
961         
962         return cache;
963     },
964     
965     CLASS_NAME: "OpenLayers.Renderer.VML"
966 });
967
968 /**
969  * Constant: OpenLayers.Renderer.VML.LABEL_SHIFT
970  * {Object}
971  */
972 OpenLayers.Renderer.VML.LABEL_SHIFT = {
973     "l": 0,
974     "c": .5,
975     "r": 1,
976     "t": 0,
977     "m": .5,
978     "b": 1
979 };