1 /*    Copyright 2008-2011
  2         Matthias Ehmann,
  3         Michael Gerhaeuser,
  4         Carsten Miller,
  5         Bianca Valentin,
  6         Alfred Wassermann,
  7         Peter Wilfahrt
  8 
  9     This file is part of JSXGraph.
 10 
 11     JSXGraph is free software: you can redistribute it and/or modify
 12     it under the terms of the GNU Lesser General Public License as published by
 13     the Free Software Foundation, either version 3 of the License, or
 14     (at your option) any later version.
 15 
 16     JSXGraph is distributed in the hope that it will be useful,
 17     but WITHOUT ANY WARRANTY; without even the implied warranty of
 18     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 19     GNU Lesser General Public License for more details.
 20 
 21     You should have received a copy of the GNU Lesser General Public License
 22     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
 23 */
 24 
 25 /*jshint bitwise: false, curly: true, debug: false, eqeqeq: true, devel: false, evil: false,
 26   forin: false, immed: true, laxbreak: false, newcap: false, noarg: true, nonew: true, onevar: true,
 27    undef: true, white: false, sub: false*/
 28 /*global JXG: true, AMprocessNode: true, MathJax: true, document: true */
 29 
 30 /**
 31  * Uses VML to implement the rendering methods defined in {@link JXG.AbstractRenderer}.
 32  * @class JXG.AbstractRenderer
 33  * @augments JXG.AbstractRenderer
 34  * @param {Node} container Reference to a DOM node containing the board.
 35  * @see JXG.AbstractRenderer
 36  */
 37 JXG.VMLRenderer = function (container) {
 38     this.type = 'vml';
 39     
 40     this.container = container;
 41     this.container.style.overflow = 'hidden';
 42     this.container.onselectstart = function () {
 43         return false;
 44     };
 45     
 46     this.resolution = 10; // Paths are drawn with a a resolution of this.resolution/pixel.
 47   
 48     // Add VML includes and namespace
 49     // Original: IE <=7
 50     //container.ownerDocument.createStyleSheet().addRule("v\\:*", "behavior: url(#default#VML);");
 51     if (!JXG.exists(JXG.vmlStylesheet)) {
 52         container.ownerDocument.namespaces.add("jxgvml", "urn:schemas-microsoft-com:vml");
 53         JXG.vmlStylesheet = this.container.ownerDocument.createStyleSheet();
 54         JXG.vmlStylesheet.addRule(".jxgvml", "behavior:url(#default#VML)");
 55     }
 56 
 57     try {
 58         !container.ownerDocument.namespaces.jxgvml && container.ownerDocument.namespaces.add("jxgvml", "urn:schemas-microsoft-com:vml");
 59         this.createNode = function (tagName) {
 60             return container.ownerDocument.createElement('<jxgvml:' + tagName + ' class="jxgvml">');
 61         };
 62     } catch (e) {
 63         this.createNode = function (tagName) {
 64             return container.ownerDocument.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="jxgvml">');
 65         };
 66     }
 67 
 68     // dash styles
 69     this.dashArray = ['Solid', '1 1', 'ShortDash', 'Dash', 'LongDash', 'ShortDashDot', 'LongDashDot'];    
 70 };
 71 
 72 JXG.VMLRenderer.prototype = new JXG.AbstractRenderer();
 73 
 74 JXG.extend(JXG.VMLRenderer.prototype, /** @lends JXG.VMLRenderer */ {
 75 
 76     /**
 77      * Sets attribute <tt>key</tt> of node <tt>node</tt> to <tt>value</tt>.
 78      * @param {Node} node A DOM node.
 79      * @param {String} key Name of the attribute.
 80      * @param {String} val New value of the attribute.
 81      * @param {Boolean} [iFlag=false] If false, the attribute's name is case insensitive.
 82      */
 83     _setAttr: function (node, key, val, iFlag) {
 84         try {
 85             if (document.documentMode === 8) {
 86                 node[key] = val;
 87             } else {
 88                 node.setAttribute(key, val, iFlag);
 89             }
 90         } catch (e) {
 91             JXG.debug('_setAttr:'/*node.id*/ + ' ' + key + ' ' + val + '<br>\n');
 92         }
 93     },
 94 
 95     /* ******************************** *
 96      *  This renderer does not need to
 97      *  override draw/update* methods
 98      *  since it provides draw/update*Prim
 99      *  methods.
100      * ******************************** */
101 
102     /* **************************
103      *    Lines
104      * **************************/
105 
106     // documented in AbstractRenderer
107     updateTicks: function (axis, dxMaj, dyMaj, dxMin, dyMin) {
108         var tickArr = [], i, len, c, ticks, r = this.resolution;
109 
110         len = axis.ticks.length;
111         for (i = 0; i < len; i++) {
112             c = axis.ticks[i];
113             x = c[0];
114             y = c[1];
115             if (typeof x[0] != 'undefined' && typeof x[1] != 'undefined') {
116                 tickArr.push(
117                     ' m ' + Math.round(r * x[0]) + ', ' + Math.round(r * y[0]) +
118                     ' l ' + Math.round(r * x[1]) + ', ' + Math.round(r * y[1]) + ' '
119                 );
120             }
121         }
122         // Labels
123         for (i = 0; i < len; i++) {
124             c = axis.ticks[i].scrCoords;
125             if (axis.ticks[i].major 
126                 && (axis.board.needsFullUpdate || axis.needsRegularUpdate) 
127                 && axis.labels[i] 
128                 && axis.labels[i].visProp.visible) {
129                     this.updateText(axis.labels[i]);
130             } 
131         }
132          
133         //ticks = this.getElementById(axis.id);
134         if (!JXG.exists(axis)) {
135             ticks = this.createPrim('path', axis.id);
136             this.appendChildPrim(ticks, axis.visProp.layer);
137             this.appendNodesToElement(axis, 'path');
138         }
139         this._setAttr(axis.rendNode, 'stroked', 'true');
140         this._setAttr(axis.rendNode, 'strokecolor', axis.visProp.strokecolor, 1);
141         this._setAttr(axis.rendNode, 'strokeweight', axis.visProp.strokewidth);
142         this._setAttr(axis.rendNodeStroke, 'opacity', (axis.visProp.strokeopacity * 100) + '%');
143         this.updatePathPrim(axis.rendNode, tickArr, axis.board);
144     },
145 
146     /* **************************
147      *    Text related stuff
148      * **************************/
149 
150     // already documented in JXG.AbstractRenderer
151     displayCopyright: function (str, fontsize) {
152         var node, t;
153 
154         node = this.createNode('textbox');
155         node.style.position = 'absolute';
156         this._setAttr(node, 'id', this.container.id + '_' + 'licenseText');
157 
158         node.style.left = 20;
159         node.style.top = 2;
160         node.style.fontSize = fontsize;
161         node.style.color = '#356AA0';
162         node.style.fontFamily = 'Arial,Helvetica,sans-serif';
163         this._setAttr(node, 'opacity', '30%');
164         node.style.filter = 'alpha(opacity = 30)';
165 
166         t = document.createTextNode(str);
167         node.appendChild(t);
168         this.appendChildPrim(node, 0);
169     },
170 
171     // documented in AbstractRenderer
172     drawInternalText: function (el) {
173         var node;
174         node = this.createNode('textbox');
175         node.style.position = 'absolute';
176         /*
177         if (document.documentMode === 8) {                 // IE 8
178             node.setAttribute('class', el.visProp.cssclass);
179         } else {
180             node.setAttribute(document.all ? 'className' : 'class', el.visProp.cssclass);
181         }
182         */
183         el.rendNodeText = document.createTextNode('');
184         node.appendChild(el.rendNodeText);
185         this.appendChildPrim(node, 9);
186         return node;
187     },
188 
189     // documented in AbstractRenderer
190     updateInternalText: function (el) {
191         var content = el.plaintext;
192         /*
193         if (document.documentMode === 8) {                 // IE 8
194             el.rendNode.setAttribute('class', el.visProp.cssclass);
195         } else {
196             el.rendNode.setAttribute(document.all ? 'className' : 'class', el.visProp.cssclass);
197         }
198         */
199         if (!isNaN(el.coords.scrCoords[1]+el.coords.scrCoords[2])) {
200             if (el.visProp.anchorx === 'right') {
201                 el.rendNode.style.right = parseInt(el.board.canvasWidth - el.coords.scrCoords[1]) + 'px';
202             } else if (el.visProp.anchorx === 'middle') {
203                 el.rendNode.style.left = parseInt(el.coords.scrCoords[1]-0.5*el.size[0]) + 'px';
204             } else {
205                 el.rendNode.style.left = parseInt(el.coords.scrCoords[1]) + 'px';
206             }
207             el.rendNode.style.top = parseInt(el.coords.scrCoords[2] - el.visProp.fontsize + this.vOffsetText) + 'px';
208             if (el.visProp.anchory === 'top') {
209                 el.rendNode.style.top = parseInt(el.coords.scrCoords[2] + this.vOffsetText) + 'px';
210             } else if (el.visProp.anchory === 'middle') {
211                 el.rendNode.style.top = parseInt(el.coords.scrCoords[2] - 0.5*el.size[1] + this.vOffsetText) + 'px';
212             } else {
213                 el.rendNode.style.top = parseInt(el.coords.scrCoords[2] - el.size[1] + this.vOffsetText) + 'px';
214             }
215  
216         }
217         
218         if (el.htmlStr !== content) {
219             el.rendNodeText.data = content;
220             el.htmlStr = content;
221         }
222 
223         this.transformImage(el, el.transformations);
224     },
225 
226     /* **************************
227      *    Image related stuff
228      * **************************/
229 
230     // already documented in JXG.AbstractRenderer
231     drawImage: function (el) {
232         // IE 8: Bilder ueber data URIs werden bis 32kB unterstuetzt.
233         var node;
234 
235         node = this.container.ownerDocument.createElement('img');
236         node.style.position = 'absolute';
237         this._setAttr(node, 'id', this.container.id + '_' + el.id);
238 
239         this.container.appendChild(node);
240         this.appendChildPrim(node, el.visProp.layer);
241 
242         // Adding the rotation filter. This is always filter item 0:
243         // node.filters.item(0), see transformImage
244         //node.style.filter = node.style['-ms-filter'] = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand')";
245         node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand')";
246         el.rendNode = node;
247         this.updateImage(el);
248     },
249 
250     // already documented in JXG.AbstractRenderer
251     transformImage: function (el, t) {
252         var node = el.rendNode,
253             m, p = [], s, len = t.length,
254             maxX, maxY, minX, minY, i, nt;
255 
256         if (el.type === JXG.OBJECT_TYPE_TEXT) {
257             el.updateSize();
258         }
259         if (len > 0) {
260             nt = el.rendNode.style.filter.toString();
261             if (!nt.match(/DXImageTransform/)) {
262                 node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand') " + nt;
263             }
264 
265             m = this.joinTransforms(el, t);
266             p[0] = JXG.Math.matVecMult(m, el.coords.scrCoords);
267             p[0][1] /= p[0][0];
268             p[0][2] /= p[0][0];
269             p[1] = JXG.Math.matVecMult(m, [1, el.coords.scrCoords[1] + el.size[0], el.coords.scrCoords[2]]);
270             p[1][1] /= p[1][0];
271             p[1][2] /= p[1][0];
272             p[2] = JXG.Math.matVecMult(m, [1, el.coords.scrCoords[1] + el.size[0], el.coords.scrCoords[2] - el.size[1]]);
273             p[2][1] /= p[2][0];
274             p[2][2] /= p[2][0];
275             p[3] = JXG.Math.matVecMult(m, [1, el.coords.scrCoords[1], el.coords.scrCoords[2] - el.size[1]]);
276             p[3][1] /= p[3][0];
277             p[3][2] /= p[3][0];
278             maxX = p[0][1];
279             minX = p[0][1];
280             maxY = p[0][2];
281             minY = p[0][2];
282             for (i = 1; i < 4; i++) {
283                 maxX = Math.max(maxX, p[i][1]);
284                 minX = Math.min(minX, p[i][1]);
285                 maxY = Math.max(maxY, p[i][2]);
286                 minY = Math.min(minY, p[i][2]);
287             }
288             node.style.left = parseInt(minX) + 'px';
289             node.style.top  = parseInt(minY) + 'px';
290 
291             node.filters.item(0).M11 = m[1][1];
292             node.filters.item(0).M12 = m[1][2];
293             node.filters.item(0).M21 = m[2][1];
294             node.filters.item(0).M22 = m[2][2];
295         }
296     },
297 
298     // already documented in JXG.AbstractRenderer
299     updateImageURL: function (el) {
300         var url = JXG.evaluate(el.url);
301         this._setAttr(el.rendNode, 'src', url);
302     },
303 
304     /* **************************
305      * Render primitive objects
306      * **************************/
307 
308     // already documented in JXG.AbstractRenderer
309     appendChildPrim: function (node, level) {
310         if (!JXG.exists(level)) {  // For trace nodes
311             level = 0;
312         }
313         node.style.zIndex = level;
314         this.container.appendChild(node);
315     },
316 
317     // already documented in JXG.AbstractRenderer
318     appendNodesToElement: function (element, type) {
319         if (type === 'shape' || type === 'path' || type === 'polygon') {
320             element.rendNodePath = this.getElementById(element.id + '_path');
321         }
322         element.rendNodeFill = this.getElementById(element.id + '_fill');
323         element.rendNodeStroke = this.getElementById(element.id + '_stroke');
324         element.rendNodeShadow = this.getElementById(element.id + '_shadow');
325         element.rendNode = this.getElementById(element.id);
326     },
327 
328     // already documented in JXG.AbstractRenderer
329     createPrim: function (type, id) {
330         var node,
331             fillNode = this.createNode('fill'),
332             strokeNode = this.createNode('stroke'),
333             shadowNode = this.createNode('shadow'),
334             pathNode;
335 
336         this._setAttr(fillNode, 'id', this.container.id + '_' + id + '_fill');
337         this._setAttr(strokeNode, 'id', this.container.id + '_' + id + '_stroke');
338         this._setAttr(shadowNode, 'id', this.container.id + '_' + id + '_shadow');
339 
340         if (type === 'circle' || type === 'ellipse') {
341             node = this.createNode('oval');
342             node.appendChild(fillNode);
343             node.appendChild(strokeNode);
344             node.appendChild(shadowNode);
345         } else if (type === 'polygon' || type === 'path' || type === 'shape' || type === 'line') {
346             node = this.createNode('shape');
347             node.appendChild(fillNode);
348             node.appendChild(strokeNode);
349             node.appendChild(shadowNode);
350             pathNode = this.createNode('path');
351             this._setAttr(pathNode, 'id', this.container.id + '_' + id + '_path');
352             node.appendChild(pathNode);
353         } else {
354             node = this.createNode(type);
355             node.appendChild(fillNode);
356             node.appendChild(strokeNode);
357             node.appendChild(shadowNode);
358         }
359         node.style.position = 'absolute';
360         node.style.left = '0px';
361         node.style.top = '0px';
362         this._setAttr(node, 'id', this.container.id + '_' + id);
363 
364         return node;
365     },
366 
367     // already documented in JXG.AbstractRenderer
368     remove: function (node) {
369         if (JXG.exists(node)) {
370             node.removeNode(true);
371         }
372     },
373 
374     // already documented in JXG.AbstractRenderer
375     makeArrows: function (el) {
376         var nodeStroke;
377 
378         if (el.visPropOld.firstarrow === el.visProp.firstarrow && el.visPropOld.lastarrow === el.visProp.lastarrow) {
379             return;
380         }
381 
382         if (el.visProp.firstarrow) {
383             nodeStroke = el.rendNodeStroke;
384             this._setAttr(nodeStroke, 'startarrow', 'block');
385             this._setAttr(nodeStroke, 'startarrowlength', 'long');
386         } else {
387             nodeStroke = el.rendNodeStroke;
388             if (JXG.exists(nodeStroke)) {
389                 this._setAttr(nodeStroke, 'startarrow', 'none');
390             }
391         }
392 
393         if (el.visProp.lastarrow) {
394             nodeStroke = el.rendNodeStroke;
395             this._setAttr(nodeStroke, 'id', this.container.id + '_' + el.id + "stroke");
396             this._setAttr(nodeStroke, 'endarrow', 'block');
397             this._setAttr(nodeStroke, 'endarrowlength', 'long');
398         } else {
399             nodeStroke = el.rendNodeStroke;
400             if (JXG.exists(nodeStroke)) {
401                 this._setAttr(nodeStroke, 'endarrow', 'none');
402             }
403         }
404         el.visPropOld.firstarrow = el.visProp.firstarrow;
405         el.visPropOld.lastarrow = el.visProp.lastarrow;
406     },
407 
408     // already documented in JXG.AbstractRenderer
409     updateEllipsePrim: function (node, x, y, rx, ry) {
410         node.style.left = parseInt(x - rx) + 'px';
411         node.style.top =  parseInt(y - ry) + 'px';
412         node.style.width = parseInt(Math.abs(rx) * 2) + 'px';
413         node.style.height = parseInt(Math.abs(ry) * 2) + 'px';
414     },
415 
416     // already documented in JXG.AbstractRenderer
417     updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) {
418         var s, r = this.resolution;
419 
420         if (!isNaN(p1x+p1y+p2x+p2y)) {
421             s = ['m ', parseInt(r * p1x), ', ', parseInt(r * p1y), ' l ', parseInt(r * p2x), ', ', parseInt(r * p2y)];
422             this.updatePathPrim(node, s, board);
423         }
424     },
425 
426     // already documented in JXG.AbstractRenderer
427     updatePathPrim: function (node, pointString, board) {
428         var x = board.canvasWidth,
429             y = board.canvasHeight;
430         if (pointString.length <= 0) {
431             pointString = ['m 0,0'];
432         }
433         node.style.width = x;
434         node.style.height = y;
435         this._setAttr(node, 'coordsize', [parseInt(this.resolution * x), parseInt(this.resolution * y)].join(','));
436         this._setAttr(node, 'path', pointString.join(""));
437     },
438 
439     // already documented in JXG.AbstractRenderer
440     updatePathStringPoint: function (el, size, type) {
441         var s = [],
442             mround = Math.round,
443             scr = el.coords.scrCoords,
444             sqrt32 = size * Math.sqrt(3) * 0.5,
445             s05 = size * 0.5,
446             r = this.resolution;
447 
448         if (type === 'x') {
449             s.push([
450                 ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2] - size)),
451                 ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2] + size)),
452                 ' m ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2] - size)),
453                 ' l ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2] + size))
454             ].join(''));
455         }
456         else if (type === '+') {
457             s.push([
458                 ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])),
459                 ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])),
460                 ' m ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] - size)),
461                 ' l ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] + size))
462             ].join(''));
463         }
464         else if (type === '<>') {
465             
466             s.push([
467                 ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])),
468                 ' l ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] + size)),
469                 ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])),
470                 ' l ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] - size)),
471                 ' x e '
472             ].join(''));
473         }
474         else if (type === '^') {
475             s.push([
476                 ' m ', mround(r * (scr[1])),          ', ', mround(r * (scr[2] - size)),
477                 ' l ', mround(r * (scr[1] - sqrt32)), ', ', mround(r * (scr[2] + s05)),
478                 ' l ', mround(r * (scr[1] + sqrt32)), ', ', mround(r * (scr[2] + s05)),
479                 ' x e '
480             ].join(''));
481         }
482         else if (type === 'v') {
483             s.push([
484                 ' m ', mround(r * (scr[1])),          ', ', mround(r * (scr[2] + size)),
485                 ' l ', mround(r * (scr[1] - sqrt32)), ', ', mround(r * (scr[2] - s05)),
486                 ' l ', mround(r * (scr[1] + sqrt32)), ', ', mround(r * (scr[2] - s05)),
487                 ' x e '
488             ].join(''));
489         }
490         else if (type === '>') {
491             s.push([
492                 ' m ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])),
493                 ' l ', mround(r * (scr[1] - s05)),  ', ', mround(r * (scr[2] - sqrt32)),
494                 ' l ', mround(r * (scr[1] - s05)),  ', ', mround(r * (scr[2] + sqrt32)),
495                 ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2]))
496             ].join(''));
497         }
498         else if (type === '<') {
499             s.push([
500                 ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])),
501                 ' l ', mround(r * (scr[1] + s05)),  ', ', mround(r * (scr[2] - sqrt32)),
502                 ' l ', mround(r * (scr[1] + s05)),  ', ', mround(r * (scr[2] + sqrt32)),
503                 ' x e '
504             ].join(''));
505         }
506         return s;
507     },
508 
509     // already documented in JXG.AbstractRenderer
510     updatePathStringPrim: function (el) {
511         var pStr = [],
512             i, scr,
513             r = this.resolution,
514             mround = Math.round,
515             symbm = ' m ',
516             symbl = ' l ',
517             symbc = ' c ',
518             nextSymb = symbm,
519             isNotPlot = (el.visProp.curvetype !== 'plot'),
520             len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html
521 
522         if (el.numberPoints <= 0) {
523             return '';
524         }
525         len = Math.min(len, el.points.length);
526 
527         if (el.bezierDegree == 1) {
528             if (isNotPlot && el.board.options.curve.RDPsmoothing) {
529                 el.points = JXG.Math.Numerics.RamerDouglasPeuker(el.points, 1.0);
530             }
531 
532             for (i = 0; i < len; i++) {
533                 scr = el.points[i].scrCoords;
534                 if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
535                     nextSymb = symbm;
536                 } else {
537                     // IE has problems with values  being too far away.
538                     if (scr[1] > 20000.0) {
539                         scr[1] = 20000.0;
540                     } else if (scr[1] < -20000.0) {
541                         scr[1] = -20000.0;
542                     }
543 
544                     if (scr[2] > 20000.0) {
545                         scr[2] = 20000.0;
546                     } else if (scr[2] < -20000.0) {
547                         scr[2] = -20000.0;
548                     }
549 
550                     pStr.push([nextSymb, mround(r * scr[1]), ', ', mround(r * scr[2])].join(''));
551                     nextSymb = symbl;
552                 }
553             }
554         } else if (el.bezierDegree==3) {
555             i = 0;
556             while (i < len) {
557                 scr = el.points[i].scrCoords;
558                 if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
559                     nextSymb = symbm;
560                 } else {
561                     pStr.push([nextSymb, mround(r * scr[1]), ', ', mround(r * scr[2])].join(''));
562                     if (nextSymb==symbc){
563                         i++;
564                         scr = el.points[i].scrCoords;
565                         pStr.push([' ', mround(r * scr[1]), ', ', mround(r * scr[2])].join(''));
566                         i++;
567                         scr = el.points[i].scrCoords;
568                         pStr.push([' ', mround(r * scr[1]), ', ', mround(r * scr[2])].join(''));
569                     }
570                     nextSymb = symbc;
571                 }
572                 i++;
573             }
574         }
575         pStr.push(' e');
576         return pStr;
577     },
578 
579     // already documented in JXG.AbstractRenderer
580     updatePathStringBezierPrim: function (el) {
581         var pStr = [],
582             i, j, scr,
583             lx, ly, f = el.visProp.strokewidth, 
584             r = this.resolution,
585             mround = Math.round,
586             symbm = ' m ',
587             symbl = ' c ',
588             nextSymb = symbm,
589             isNoPlot = (el.visProp.curvetype !== 'plot'),
590             len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html
591 
592         if (el.numberPoints <= 0) {
593             return '';
594         }
595         if (isNoPlot && el.board.options.curve.RDPsmoothing) {
596             el.points = JXG.Math.Numerics.RamerDouglasPeuker(el.points, 1.0);
597         }
598         len = Math.min(len, el.points.length);
599 
600         for (j=1; j<3; j++) {
601             nextSymb = symbm;
602             for (i = 0; i < len; i++) {
603                 scr = el.points[i].scrCoords;
604                 if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
605                     nextSymb = symbm;
606                 } else {
607                     // IE has problems with values  being too far away.
608                     if (scr[1] > 20000.0) {
609                         scr[1] = 20000.0;
610                     } else if (scr[1] < -20000.0) {
611                         scr[1] = -20000.0;
612                     }
613     
614                     if (scr[2] > 20000.0) {
615                         scr[2] = 20000.0;
616                     } else if (scr[2] < -20000.0) {
617                         scr[2] = -20000.0;
618                     }
619     
620                     if (nextSymb == symbm) {
621                         pStr.push([nextSymb, 
622                             mround(r * (scr[1]+0*f*(2*j*Math.random()-j))), ' ', 
623                             mround(r * (scr[2]+0*f*(2*j*Math.random()-j)))].join(''));
624                     } else {
625                         pStr.push([nextSymb, 
626                             mround(r * (lx + (scr[1]-lx)*0.333 + f*(2*j*Math.random()-j))), ' ',
627                             mround(r * (ly + (scr[2]-ly)*0.333 + f*(2*j*Math.random()-j))), ' ',
628                             mround(r * (lx + 2*(scr[1]-lx)*0.333 + f*(2*j*Math.random()-j))), ' ',
629                             mround(r * (ly + 2*(scr[2]-ly)*0.333 + f*(2*j*Math.random()-j))), ' ',
630                             mround(r * scr[1]), ' ', 
631                             mround(r * scr[2])
632                             ].join(''));
633                     }
634                     nextSymb = symbl;
635                     lx = scr[1];
636                     ly = scr[2];
637                 }
638             }
639         }      
640         pStr.push(' e');
641         return pStr;
642     },
643     
644     // already documented in JXG.AbstractRenderer
645     updatePolygonPrim: function (node, el) {
646         var i,
647             len = el.vertices.length,
648             r = this.resolution,
649             scr,
650             pStr = [];
651 
652         this._setAttr(node, 'stroked', 'false');
653 
654         scr = el.vertices[0].coords.scrCoords;
655         if (isNaN(scr[1]+scr[2])) return;
656         pStr.push(["m ", parseInt(r * scr[1]), ",", parseInt(r * scr[2]), " l "].join(''));
657 
658         for (i = 1; i < len - 1; i++) {
659             if (el.vertices[i].isReal) {
660                 scr = el.vertices[i].coords.scrCoords;
661                 if (isNaN(scr[1]+scr[2])) return;
662                 pStr.push(parseInt(r * scr[1]) + "," + parseInt(r * scr[2]));
663             } else {
664                 this.updatePathPrim(node, '', el.board);
665                 return;
666             }
667             if (i < len - 2) {
668                     pStr.push(", ");
669             }
670         }
671         pStr.push(" x e");
672         this.updatePathPrim(node, pStr, el.board);
673     },
674 
675     // already documented in JXG.AbstractRenderer
676     updateRectPrim: function (node, x, y, w, h) {
677         node.style.left = parseInt(x) + 'px';
678         node.style.top = parseInt(y) + 'px';
679 
680         if (w >= 0) {
681             node.style.width = w + 'px';
682         }
683         
684         if (h >= 0) {
685             node.style.height = h + 'px';
686         }
687     },
688 
689     /* **************************
690      *  Set Attributes
691      * **************************/
692 
693     // already documented in JXG.AbstractRenderer
694     setPropertyPrim: function (node, key, val) {
695         var keyVml = '',
696             v;
697 
698         switch (key) {
699             case 'stroke':
700                 keyVml = 'strokecolor';
701                 break;
702             case 'stroke-width':
703                 keyVml = 'strokeweight';
704                 break;
705             case 'stroke-dasharray':
706                 keyVml = 'dashstyle';
707                 break;
708         }
709 
710         if (keyVml !== '') {
711             v = JXG.evaluate(val);
712             this._setAttr(node, keyVml, v);
713         }
714     },
715 
716     // already documented in JXG.AbstractRenderer
717     show: function (el) {
718         if (el && el.rendNode) {
719             el.rendNode.style.visibility = "inherit";
720         }
721     },
722 
723     // already documented in JXG.AbstractRenderer
724     hide: function (el) {
725         if (el && el.rendNode) {
726             el.rendNode.style.visibility = "hidden";
727         }
728     },
729 
730     // already documented in JXG.AbstractRenderer
731     setDashStyle: function (el, visProp) {
732         var node;
733         if (visProp.dash >= 0) {
734             node = el.rendNodeStroke;
735             this._setAttr(node, 'dashstyle', this.dashArray[visProp.dash]);
736         }
737     },
738 
739     // already documented in JXG.AbstractRenderer
740     setGradient: function (el) {
741         var nodeFill = el.rendNodeFill;
742         
743         if (el.visProp.gradient === 'linear') {
744             this._setAttr(nodeFill, 'type', 'gradient');
745             this._setAttr(nodeFill, 'color2', el.visProp.gradientsecondcolor);
746             this._setAttr(nodeFill, 'opacity2', el.visProp.gradientsecondopacity);
747             this._setAttr(nodeFill, 'angle', el.visProp.gradientangle);
748         } else if (el.visProp.gradient === 'radial') {
749             this._setAttr(nodeFill, 'type', 'gradientradial');
750             this._setAttr(nodeFill, 'color2', el.visProp.gradientsecondcolor);
751             this._setAttr(nodeFill, 'opacity2', el.visProp.gradientsecondopacity);
752             this._setAttr(nodeFill, 'focusposition', el.visProp.gradientpositionx * 100 + '%,' + el.visProp.gradientpositiony * 100 + '%');
753             this._setAttr(nodeFill, 'focussize', '0,0');
754         } else {
755             this._setAttr(nodeFill, 'type', 'solid');
756         }
757     },
758 
759     // already documented in JXG.AbstractRenderer
760     setObjectFillColor: function (el, color, opacity) {
761         var rgba = JXG.evaluate(color), c, rgbo,
762             o = JXG.evaluate(opacity), oo,
763             node = el.rendNode, 
764             t;
765 
766         o = (o > 0) ? o : 0;
767 
768         if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) {
769             return;
770         }
771 
772         if (JXG.exists(rgba) && rgba !== false) {
773             if (rgba.length!=9) {          // RGB, not RGBA
774                 c = rgba;
775                 oo = o;
776             } else {                       // True RGBA, not RGB
777                 rgbo = JXG.rgba2rgbo(rgba);
778                 c = rgbo[0];
779                 oo = o*rgbo[1];
780             }
781             if (c === 'none' || c === false) {
782                 this._setAttr(el.rendNode, 'filled', 'false');
783             } else {
784                 this._setAttr(el.rendNode, 'filled', 'true');
785                 this._setAttr(el.rendNode, 'fillcolor', c);
786 
787                 if (JXG.exists(oo) && el.rendNodeFill) {
788                     this._setAttr(el.rendNodeFill, 'opacity', (oo * 100) + '%');
789                 }
790             }
791             if (el.type === JXG.OBJECT_TYPE_IMAGE) {
792                 t = el.rendNode.style.filter.toString();
793                 if (t.match(/alpha/)) {
794                     el.rendNode.style.filter = t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + (oo * 100) + ')');
795                 } else {
796                     el.rendNode.style.filter += ' alpha(opacity = ' + (oo * 100) +')';
797                 }
798             }
799         }
800         el.visPropOld.fillcolor = rgba;
801         el.visPropOld.fillopacity = o;
802     },
803 
804     // already documented in JXG.AbstractRenderer
805     setObjectStrokeColor: function (el, color, opacity) {
806         var rgba = JXG.evaluate(color), c, rgbo,
807             o = JXG.evaluate(opacity), oo,
808             node = el.rendNode, nodeStroke;
809 
810         o = (o > 0) ? o : 0;
811 
812         if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
813             return;
814         }
815 
816         if (JXG.exists(rgba) && rgba !== false) {
817             if (rgba.length!=9) {          // RGB, not RGBA
818                 c = rgba;
819                 oo = o;
820             } else {                       // True RGBA, not RGB
821                 rgbo = JXG.rgba2rgbo(rgba);
822                 c = rgbo[0];
823                 oo = o*rgbo[1];
824             }
825             if (el.type === JXG.OBJECT_TYPE_TEXT) {
826                 oo = Math.round(oo*100);
827                 node.style.filter = ' alpha(opacity = ' + oo +')';
828                 //node.style.filter = node.style['-ms-filter'] = "progid:DXImageTransform.Microsoft.Alpha(Opacity="+oo+")";
829                 node.style.color = c;
830             } else {
831                 if (c !== false) {
832                     this._setAttr(node, 'stroked', 'true');
833                     this._setAttr(node, 'strokecolor', c);
834                 }
835 
836                 nodeStroke = el.rendNodeStroke;
837                 if (JXG.exists(oo) && el.type !== JXG.OBJECT_TYPE_IMAGE) {
838                     this._setAttr(nodeStroke, 'opacity', (oo * 100) + '%');
839                 }
840             }
841         }
842         el.visPropOld.strokecolor = rgba;
843         el.visPropOld.strokeopacity = o;
844     },
845 
846     // already documented in JXG.AbstractRenderer
847     setObjectStrokeWidth: function (el, width) {
848         var w = JXG.evaluate(width),
849             node;
850 
851         if (el.visPropOld.strokewidth === w) {
852             return;
853         }
854 
855         node = el.rendNode;
856         this.setPropertyPrim(node, 'stroked', 'true');
857         if (JXG.exists(w)) {
858             this.setPropertyPrim(node, 'stroke-width', w);
859         }
860         el.visPropOld.strokewidth = w;
861     },
862 
863     // already documented in JXG.AbstractRenderer
864     setShadow: function (el) {
865         var nodeShadow = el.rendNodeShadow;
866 
867         if (!nodeShadow || el.visPropOld.shadow === el.visProp.shadow) {
868             return;
869         }
870 
871         if (el.visProp.shadow) {
872             this._setAttr(nodeShadow, 'On', 'True');
873             this._setAttr(nodeShadow, 'Offset', '3pt,3pt');
874             this._setAttr(nodeShadow, 'Opacity', '60%');
875             this._setAttr(nodeShadow, 'Color', '#aaaaaa');
876         } else {
877             this._setAttr(nodeShadow, 'On', 'False');
878         }
879 
880         el.visPropOld.shadow = el.visProp.shadow;
881     },
882 
883     /* **************************
884      * renderer control
885      * **************************/
886 
887     // already documented in JXG.AbstractRenderer
888     suspendRedraw: function () {
889         this.container.style.display = 'none';
890     },
891 
892     // already documented in JXG.AbstractRenderer
893     unsuspendRedraw: function () {
894         this.container.style.display = '';
895     }
896 
897 });