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