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