1 /*
  2     Copyright 2008-2021
  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 (Type.isNumber(x[0]) && Type.isNumber(x[1])) {
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, Type.evaluate(ticks.visProp.layer));
155             }
156 
157             this._setAttr(ticks.rendNode, 'stroked', 'true');
158             this._setAttr(ticks.rendNode, 'strokecolor', Type.evaluate(ticks.visProp.strokecolor), 1);
159             this._setAttr(ticks.rendNode, 'strokeweight', Type.evaluate(ticks.visProp.strokewidth));
160             this._setAttr(ticks.rendNodeStroke, 'opacity', (Type.evaluate(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 = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand', enabled = false) progid:DXImageTransform.Microsoft.Alpha(opacity = 30, enabled = true)";
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             el.rendNodeText = this.container.ownerDocument.createTextNode('');
195             node.appendChild(el.rendNodeText);
196             this.appendChildPrim(node, 9);
197             node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand', enabled = false) progid:DXImageTransform.Microsoft.Alpha(opacity = 100, enabled = false)";
198 
199             return node;
200         },
201 
202         // documented in AbstractRenderer
203         updateInternalText: function (el) {
204             var v, content = el.plaintext,
205                 m = this.joinTransforms(el, el.transformations),
206                 offset = [0, 0],
207                 maxX, maxY, minX, minY, i,
208                 node = el.rendNode,
209                 p = [],
210                 ev_ax = el.getAnchorX(),
211                 ev_ay = el.getAnchorY();
212 
213             if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) {
214                 // Horizontal
215                 if (ev_ax === 'right') {
216                     offset[0] = 1;
217                 } else if (ev_ax === 'middle') {
218                     offset[0] = 0.5;
219                 } // default (ev_ax === 'left') offset[0] = 0;
220 
221                 // Vertical
222                 if (ev_ay === 'bottom') {
223                     offset[1] = 1;
224                 } else if (ev_ay === 'middle') {
225                     offset[1] = 0.5;
226                 } // default (ev_ay === 'top') offset[1] = 0;
227 
228                 // Compute maxX, maxY, minX, minY
229                 p[0] = Mat.matVecMult(m, [1,
230                                           el.coords.scrCoords[1] - offset[0] * el.size[0],
231                                           el.coords.scrCoords[2] + (1 - offset[1]) * el.size[1] + this.vOffsetText]);
232                 p[0][1] /= p[0][0];
233                 p[0][2] /= p[0][0];
234                 p[1] = Mat.matVecMult(m, [1,
235                                           el.coords.scrCoords[1] + (1 - offset[0]) * el.size[0],
236                                           el.coords.scrCoords[2] + (1 - offset[1]) * el.size[1] + this.vOffsetText]);
237                 p[1][1] /= p[1][0];
238                 p[1][2] /= p[1][0];
239                 p[2] = Mat.matVecMult(m, [1,
240                                           el.coords.scrCoords[1] + (1 - offset[0]) * el.size[0],
241                                           el.coords.scrCoords[2] - offset[1] * el.size[1] + this.vOffsetText]);
242                 p[2][1] /= p[2][0];
243                 p[2][2] /= p[2][0];
244                 p[3] = Mat.matVecMult(m, [1,
245                                           el.coords.scrCoords[1] - offset[0] * el.size[0],
246                                           el.coords.scrCoords[2] - offset[1] * el.size[1] + this.vOffsetText]);
247                 p[3][1] /= p[3][0];
248                 p[3][2] /= p[3][0];
249                 maxX = p[0][1];
250                 minX = p[0][1];
251                 maxY = p[0][2];
252                 minY = p[0][2];
253 
254                 for (i = 1; i < 4; i++) {
255                     maxX = Math.max(maxX, p[i][1]);
256                     minX = Math.min(minX, p[i][1]);
257                     maxY = Math.max(maxY, p[i][2]);
258                     minY = Math.min(minY, p[i][2]);
259                 }
260 
261                 // Horizontal
262                 v = offset[0] === 1 ? Math.floor(el.board.canvasWidth - maxX) : Math.floor(minX);
263                 if (el.visPropOld.left !== (ev_ax + v)) {
264                     if (offset[0] === 1) {
265                         el.rendNode.style.right = v + 'px';
266                         el.rendNode.style.left = 'auto';
267                     } else {
268                         el.rendNode.style.left = v + 'px';
269                         el.rendNode.style.right = 'auto';
270                     }
271                     el.visPropOld.left = ev_ax + v;
272                 }
273 
274                 // Vertical
275                 v = offset[1] === 1 ? Math.floor(el.board.canvasHeight - maxY) : Math.floor(minY);
276                 if (el.visPropOld.top !== (ev_ay + v)) {
277                     if (offset[1] === 1) {
278                         el.rendNode.style.bottom = v + 'px';
279                         el.rendNode.style.top = 'auto';
280                     } else {
281                         el.rendNode.style.top = v + 'px';
282                         el.rendNode.style.bottom = 'auto';
283                     }
284                     el.visPropOld.top = ev_ay + v;
285                 }
286 
287             }
288 
289             if (el.htmlStr !== content) {
290                 el.rendNodeText.data = content;
291                 el.htmlStr = content;
292             }
293 
294             //this.transformImage(el, el.transformations);
295             node.filters.item(0).M11 = m[1][1];
296             node.filters.item(0).M12 = m[1][2];
297             node.filters.item(0).M21 = m[2][1];
298             node.filters.item(0).M22 = m[2][2];
299             node.filters.item(0).enabled = true;
300         },
301 
302         /* **************************
303          *    Image related stuff
304          * **************************/
305 
306         // already documented in JXG.AbstractRenderer
307         drawImage: function (el) {
308             // IE 8: Bilder ueber data URIs werden bis 32kB unterstuetzt.
309             var node;
310 
311             node = this.container.ownerDocument.createElement('img');
312             node.style.position = 'absolute';
313             this._setAttr(node, 'id', this.container.id + '_' + el.id);
314 
315             this.container.appendChild(node);
316             this.appendChildPrim(node, Type.evaluate(el.visProp.layer));
317 
318             // Adding the rotation filter. This is always filter item 0:
319             // node.filters.item(0), see transformImage
320             // Also add the alpha filter. This is always filter item 1
321             // node.filters.item(1), see setObjectFillColor and setObjectSTrokeColor
322             //node.style.filter = node.style['-ms-filter'] = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand')";
323             node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand') progid:DXImageTransform.Microsoft.Alpha(opacity = 100, enabled = false)";
324             el.rendNode = node;
325             this.updateImage(el);
326         },
327 
328         // already documented in JXG.AbstractRenderer
329         transformImage: function (el, t) {
330             var m, s, maxX, maxY, minX, minY, i, nt,
331                 node = el.rendNode,
332                 p = [],
333                 len = t.length;
334 
335             if (len > 0) {
336                 /*
337                 nt = el.rendNode.style.filter.toString();
338                 if (!nt.match(/DXImageTransform/)) {
339                     node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand') " + nt;
340                 }
341                 */
342 
343                 m = this.joinTransforms(el, t);
344                 p[0] = Mat.matVecMult(m, el.coords.scrCoords);
345                 p[0][1] /= p[0][0];
346                 p[0][2] /= p[0][0];
347                 p[1] = Mat.matVecMult(m, [1, el.coords.scrCoords[1] + el.size[0], el.coords.scrCoords[2]]);
348                 p[1][1] /= p[1][0];
349                 p[1][2] /= p[1][0];
350                 p[2] = Mat.matVecMult(m, [1, el.coords.scrCoords[1] + el.size[0], el.coords.scrCoords[2] - el.size[1]]);
351                 p[2][1] /= p[2][0];
352                 p[2][2] /= p[2][0];
353                 p[3] = Mat.matVecMult(m, [1, el.coords.scrCoords[1], el.coords.scrCoords[2] - el.size[1]]);
354                 p[3][1] /= p[3][0];
355                 p[3][2] /= p[3][0];
356                 maxX = p[0][1];
357                 minX = p[0][1];
358                 maxY = p[0][2];
359                 minY = p[0][2];
360 
361                 for (i = 1; i < 4; i++) {
362                     maxX = Math.max(maxX, p[i][1]);
363                     minX = Math.min(minX, p[i][1]);
364                     maxY = Math.max(maxY, p[i][2]);
365                     minY = Math.min(minY, p[i][2]);
366                 }
367                 node.style.left = Math.floor(minX) + 'px';
368                 node.style.top  = Math.floor(minY) + 'px';
369 
370                 node.filters.item(0).M11 = m[1][1];
371                 node.filters.item(0).M12 = m[1][2];
372                 node.filters.item(0).M21 = m[2][1];
373                 node.filters.item(0).M22 = m[2][2];
374                 node.filters.item(0).enabled = true;
375             }
376         },
377 
378         // already documented in JXG.AbstractRenderer
379         updateImageURL: function (el) {
380             var url = Type.evaluate(el.url);
381 
382             this._setAttr(el.rendNode, 'src', url);
383         },
384 
385         /* **************************
386          * Render primitive objects
387          * **************************/
388 
389         // already documented in JXG.AbstractRenderer
390         appendChildPrim: function (node, level) {
391             // For trace nodes
392             if (!Type.exists(level)) {
393                 level = 0;
394             }
395 
396             node.style.zIndex = level;
397             this.container.appendChild(node);
398 
399             return node;
400         },
401 
402         // already documented in JXG.AbstractRenderer
403         appendNodesToElement: function (el, type) {
404             if (type === 'shape' || type === 'path' || type === 'polygon') {
405                 el.rendNodePath = this.getElementById(el.id + '_path');
406             }
407             el.rendNodeFill = this.getElementById(el.id + '_fill');
408             el.rendNodeStroke = this.getElementById(el.id + '_stroke');
409             el.rendNodeShadow = this.getElementById(el.id + '_shadow');
410             el.rendNode = this.getElementById(el.id);
411         },
412 
413         // already documented in JXG.AbstractRenderer
414         createPrim: function (type, id) {
415             var node, pathNode,
416                 fillNode = this.createNode('fill'),
417                 strokeNode = this.createNode('stroke'),
418                 shadowNode = this.createNode('shadow');
419 
420             this._setAttr(fillNode, 'id', this.container.id + '_' + id + '_fill');
421             this._setAttr(strokeNode, 'id', this.container.id + '_' + id + '_stroke');
422             this._setAttr(shadowNode, 'id', this.container.id + '_' + id + '_shadow');
423 
424             if (type === 'circle' || type === 'ellipse') {
425                 node = this.createNode('oval');
426                 node.appendChild(fillNode);
427                 node.appendChild(strokeNode);
428                 node.appendChild(shadowNode);
429             } else if (type === 'polygon' || type === 'path' || type === 'shape' || type === 'line') {
430                 node = this.createNode('shape');
431                 node.appendChild(fillNode);
432                 node.appendChild(strokeNode);
433                 node.appendChild(shadowNode);
434                 pathNode = this.createNode('path');
435                 this._setAttr(pathNode, 'id', this.container.id + '_' + id + '_path');
436                 node.appendChild(pathNode);
437             } else {
438                 node = this.createNode(type);
439                 node.appendChild(fillNode);
440                 node.appendChild(strokeNode);
441                 node.appendChild(shadowNode);
442             }
443 
444             node.style.position = 'absolute';
445             node.style.left = '0px';
446             node.style.top = '0px';
447             this._setAttr(node, 'id', this.container.id + '_' + id);
448 
449             return node;
450         },
451 
452         // already documented in JXG.AbstractRenderer
453         remove: function (node) {
454             if (Type.exists(node)) {
455                 node.removeNode(true);
456             }
457         },
458 
459         // already documented in JXG.AbstractRenderer
460         makeArrows: function (el) {
461             var nodeStroke,
462                 ev_fa = Type.evaluate(el.visProp.firstarrow),
463                 ev_la = Type.evaluate(el.visProp.lastarrow);
464 
465             if (el.visPropOld.firstarrow === ev_fa && el.visPropOld.lastarrow === ev_la) {
466                 return;
467             }
468 
469             if (ev_fa) {
470                 nodeStroke = el.rendNodeStroke;
471                 this._setAttr(nodeStroke, 'startarrow', 'block');
472                 this._setAttr(nodeStroke, 'startarrowlength', 'long');
473             } else {
474                 nodeStroke = el.rendNodeStroke;
475                 if (Type.exists(nodeStroke)) {
476                     this._setAttr(nodeStroke, 'startarrow', 'none');
477                 }
478             }
479 
480             if (ev_la) {
481                 nodeStroke = el.rendNodeStroke;
482                 this._setAttr(nodeStroke, 'id', this.container.id + '_' + el.id + "stroke");
483                 this._setAttr(nodeStroke, 'endarrow', 'block');
484                 this._setAttr(nodeStroke, 'endarrowlength', 'long');
485             } else {
486                 nodeStroke = el.rendNodeStroke;
487                 if (Type.exists(nodeStroke)) {
488                     this._setAttr(nodeStroke, 'endarrow', 'none');
489                 }
490             }
491             el.visPropOld.firstarrow = ev_fa;
492             el.visPropOld.lastarrow = ev_la;
493         },
494 
495         // already documented in JXG.AbstractRenderer
496         updateEllipsePrim: function (node, x, y, rx, ry) {
497             node.style.left = Math.floor(x - rx) + 'px';
498             node.style.top =  Math.floor(y - ry) + 'px';
499             node.style.width = Math.floor(Math.abs(rx) * 2) + 'px';
500             node.style.height = Math.floor(Math.abs(ry) * 2) + 'px';
501         },
502 
503         // already documented in JXG.AbstractRenderer
504         updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) {
505             var s, r = this.resolution;
506 
507             if (!isNaN(p1x + p1y + p2x + p2y)) {
508                 s = ['m ', Math.floor(r * p1x), ', ', Math.floor(r * p1y), ' l ', Math.floor(r * p2x), ', ', Math.floor(r * p2y)];
509                 this.updatePathPrim(node, s, board);
510             }
511         },
512 
513         // already documented in JXG.AbstractRenderer
514         updatePathPrim: function (node, pointString, board) {
515             var x = board.canvasWidth,
516                 y = board.canvasHeight;
517             if (pointString.length <= 0) {
518                 pointString = ['m 0,0'];
519             }
520             node.style.width = x;
521             node.style.height = y;
522             this._setAttr(node, 'coordsize', [Math.floor(this.resolution * x), Math.floor(this.resolution * y)].join(','));
523             this._setAttr(node, 'path', pointString.join(""));
524         },
525 
526         // already documented in JXG.AbstractRenderer
527         updatePathStringPoint: function (el, size, type) {
528             var s = [],
529                 mround = Math.round,
530                 scr = el.coords.scrCoords,
531                 sqrt32 = size * Math.sqrt(3) * 0.5,
532                 s05 = size * 0.5,
533                 r = this.resolution;
534 
535             if (type === 'x') {
536                 s.push([
537                     ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2] - size)),
538                     ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2] + size)),
539                     ' m ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2] - size)),
540                     ' l ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2] + size))
541                 ].join(''));
542             } else if (type === '+') {
543                 s.push([
544                     ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])),
545                     ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])),
546                     ' m ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] - size)),
547                     ' l ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] + size))
548                 ].join(''));
549             } else if (type === '<>') {
550 
551                 s.push([
552                     ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])),
553                     ' l ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] + size)),
554                     ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])),
555                     ' l ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] - size)),
556                     ' x e '
557                 ].join(''));
558             } else if (type === '^') {
559                 s.push([
560                     ' m ', mround(r * (scr[1])),          ', ', mround(r * (scr[2] - size)),
561                     ' l ', mround(r * (scr[1] - sqrt32)), ', ', mround(r * (scr[2] + s05)),
562                     ' l ', mround(r * (scr[1] + sqrt32)), ', ', mround(r * (scr[2] + s05)),
563                     ' x e '
564                 ].join(''));
565             } else if (type === 'v') {
566                 s.push([
567                     ' m ', mround(r * (scr[1])),          ', ', mround(r * (scr[2] + size)),
568                     ' l ', mround(r * (scr[1] - sqrt32)), ', ', mround(r * (scr[2] - s05)),
569                     ' l ', mround(r * (scr[1] + sqrt32)), ', ', mround(r * (scr[2] - s05)),
570                     ' x e '
571                 ].join(''));
572             } else if (type === '>') {
573                 s.push([
574                     ' m ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])),
575                     ' l ', mround(r * (scr[1] - s05)),  ', ', mround(r * (scr[2] - sqrt32)),
576                     ' l ', mround(r * (scr[1] - s05)),  ', ', mround(r * (scr[2] + sqrt32)),
577                     ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2]))
578                 ].join(''));
579             } else if (type === '<') {
580                 s.push([
581                     ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])),
582                     ' l ', mround(r * (scr[1] + s05)),  ', ', mround(r * (scr[2] - sqrt32)),
583                     ' l ', mround(r * (scr[1] + s05)),  ', ', mround(r * (scr[2] + sqrt32)),
584                     ' x e '
585                 ].join(''));
586             }
587 
588             return s;
589         },
590 
591         // already documented in JXG.AbstractRenderer
592         updatePathStringPrim: function (el) {
593             var i, scr,
594                 pStr = [],
595                 r = this.resolution,
596                 mround = Math.round,
597                 symbm = ' m ',
598                 symbl = ' l ',
599                 symbc = ' c ',
600                 nextSymb = symbm,
601                 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html
602 
603             if (el.numberPoints <= 0) {
604                 return '';
605             }
606             len = Math.min(len, el.points.length);
607 
608             if (el.bezierDegree === 1) {
609                 /*
610                 if (isNotPlot && el.board.options.curve.RDPsmoothing) {
611                     el.points = Numerics.RamerDouglasPeucker(el.points, 1.0);
612                 }
613                 */
614 
615                 for (i = 0; i < len; i++) {
616                     scr = el.points[i].scrCoords;
617                     if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
618                         nextSymb = symbm;
619                     } else {
620                         // IE has problems with values  being too far away.
621                         if (scr[1] > 20000.0) {
622                             scr[1] = 20000.0;
623                         } else if (scr[1] < -20000.0) {
624                             scr[1] = -20000.0;
625                         }
626 
627                         if (scr[2] > 20000.0) {
628                             scr[2] = 20000.0;
629                         } else if (scr[2] < -20000.0) {
630                             scr[2] = -20000.0;
631                         }
632 
633                         pStr.push([nextSymb, mround(r * scr[1]), ', ', mround(r * scr[2])].join(''));
634                         nextSymb = symbl;
635                     }
636                 }
637             } else if (el.bezierDegree === 3) {
638                 i = 0;
639                 while (i < len) {
640                     scr = el.points[i].scrCoords;
641                     if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
642                         nextSymb = symbm;
643                     } else {
644                         pStr.push([nextSymb, mround(r * scr[1]), ', ', mround(r * scr[2])].join(''));
645                         if (nextSymb === symbc) {
646                             i += 1;
647                             scr = el.points[i].scrCoords;
648                             pStr.push([' ', mround(r * scr[1]), ', ', mround(r * scr[2])].join(''));
649                             i += 1;
650                             scr = el.points[i].scrCoords;
651                             pStr.push([' ', mround(r * scr[1]), ', ', mround(r * scr[2])].join(''));
652                         }
653                         nextSymb = symbc;
654                     }
655                     i += 1;
656                 }
657             }
658             pStr.push(' e');
659             return pStr;
660         },
661 
662         // already documented in JXG.AbstractRenderer
663         updatePathStringBezierPrim: function (el) {
664             var i, j, k, scr, lx, ly,
665                 pStr = [],
666                 f = Type.evaluate(el.visProp.strokewidth),
667                 r = this.resolution,
668                 mround = Math.round,
669                 symbm = ' m ',
670                 symbl = ' c ',
671                 nextSymb = symbm,
672                 isNoPlot = (Type.evaluate(el.visProp.curvetype) !== 'plot'),
673                 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html
674 
675             if (el.numberPoints <= 0) {
676                 return '';
677             }
678             if (isNoPlot && el.board.options.curve.RDPsmoothing) {
679                 el.points = Numerics.RamerDouglasPeucker(el.points, 1.0);
680             }
681             len = Math.min(len, el.points.length);
682 
683             for (j = 1; j < 3; j++) {
684                 nextSymb = symbm;
685                 for (i = 0; i < len; i++) {
686                     scr = el.points[i].scrCoords;
687                     if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
688                         nextSymb = symbm;
689                     } else {
690                         // IE has problems with values  being too far away.
691                         if (scr[1] > 20000.0) {
692                             scr[1] = 20000.0;
693                         } else if (scr[1] < -20000.0) {
694                             scr[1] = -20000.0;
695                         }
696 
697                         if (scr[2] > 20000.0) {
698                             scr[2] = 20000.0;
699                         } else if (scr[2] < -20000.0) {
700                             scr[2] = -20000.0;
701                         }
702 
703                         if (nextSymb === symbm) {
704                             pStr.push([nextSymb,
705                                 mround(r * (scr[1])), ' ', mround(r * (scr[2]))].join(''));
706                         } else {
707                             k = 2 * j;
708                             pStr.push([nextSymb,
709                                 mround(r * (lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j))), ' ',
710                                 mround(r * (ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j))), ' ',
711                                 mround(r * (lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j))), ' ',
712                                 mround(r * (ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j))), ' ',
713                                 mround(r * scr[1]), ' ',
714                                 mround(r * scr[2])].join(''));
715                         }
716                         nextSymb = symbl;
717                         lx = scr[1];
718                         ly = scr[2];
719                     }
720                 }
721             }
722             pStr.push(' e');
723             return pStr;
724         },
725 
726         // already documented in JXG.AbstractRenderer
727         updatePolygonPrim: function (node, el) {
728             var i,
729                 len = el.vertices.length,
730                 r = this.resolution,
731                 scr,
732                 pStr = [];
733 
734             this._setAttr(node, 'stroked', 'false');
735             scr = el.vertices[0].coords.scrCoords;
736 
737             if (isNaN(scr[1] + scr[2])) {
738                 return;
739             }
740 
741             pStr.push(["m ", Math.floor(r * scr[1]), ",", Math.floor(r * scr[2]), " l "].join(''));
742 
743             for (i = 1; i < len - 1; i++) {
744                 if (el.vertices[i].isReal) {
745                     scr = el.vertices[i].coords.scrCoords;
746 
747                     if (isNaN(scr[1] + scr[2])) {
748                         return;
749                     }
750 
751                     pStr.push(Math.floor(r * scr[1]) + "," + Math.floor(r * scr[2]));
752                 } else {
753                     this.updatePathPrim(node, '', el.board);
754                     return;
755                 }
756                 if (i < len - 2) {
757                     pStr.push(", ");
758                 }
759             }
760             pStr.push(" x e");
761             this.updatePathPrim(node, pStr, el.board);
762         },
763 
764         // already documented in JXG.AbstractRenderer
765         updateRectPrim: function (node, x, y, w, h) {
766             node.style.left = Math.floor(x) + 'px';
767             node.style.top = Math.floor(y) + 'px';
768 
769             if (w >= 0) {
770                 node.style.width = w + 'px';
771             }
772 
773             if (h >= 0) {
774                 node.style.height = h + 'px';
775             }
776         },
777 
778         /* **************************
779          *  Set Attributes
780          * **************************/
781 
782         // already documented in JXG.AbstractRenderer
783         setPropertyPrim: function (node, key, val) {
784             var keyVml = '',
785                 v;
786 
787             switch (key) {
788             case 'stroke':
789                 keyVml = 'strokecolor';
790                 break;
791             case 'stroke-width':
792                 keyVml = 'strokeweight';
793                 break;
794             case 'stroke-dasharray':
795                 keyVml = 'dashstyle';
796                 break;
797             }
798 
799             if (keyVml !== '') {
800                 v = Type.evaluate(val);
801                 this._setAttr(node, keyVml, v);
802             }
803         },
804 
805         // already documented in JXG.AbstractRenderer
806         display: function(el, val) {
807             if (el && el.rendNode) {
808                 el.visPropOld.visible = val;
809                 if (val) {
810                     el.rendNode.style.visibility = "inherit";
811                 } else {
812                     el.rendNode.style.visibility = "hidden";
813                 }
814             }
815         },
816 
817         // already documented in JXG.AbstractRenderer
818         show: function (el) {
819             JXG.deprecated('Board.renderer.show()', 'Board.renderer.display()');
820 
821             if (el && el.rendNode) {
822                 el.rendNode.style.visibility = "inherit";
823             }
824         },
825 
826         // already documented in JXG.AbstractRenderer
827         hide: function (el) {
828             JXG.deprecated('Board.renderer.hide()', 'Board.renderer.display()');
829 
830             if (el && el.rendNode) {
831                 el.rendNode.style.visibility = "hidden";
832             }
833         },
834 
835         // already documented in JXG.AbstractRenderer
836         setDashStyle: function (el, visProp) {
837             var node;
838             if (visProp.dash >= 0) {
839                 node = el.rendNodeStroke;
840                 this._setAttr(node, 'dashstyle', this.dashArray[visProp.dash]);
841             }
842         },
843 
844         // already documented in JXG.AbstractRenderer
845         setGradient: function (el) {
846             var nodeFill = el.rendNodeFill,
847                 ev_g = Type.evaluate(el.visProp.gradient);
848 
849             if (ev_g === 'linear') {
850                 this._setAttr(nodeFill, 'type', 'gradient');
851                 this._setAttr(nodeFill, 'color2', Type.evaluate(el.visProp.gradientsecondcolor));
852                 this._setAttr(nodeFill, 'opacity2', Type.evaluate(el.visProp.gradientsecondopacity));
853                 this._setAttr(nodeFill, 'angle', Type.evaluate(el.visProp.gradientangle));
854             } else if (ev_g === 'radial') {
855                 this._setAttr(nodeFill, 'type', 'gradientradial');
856                 this._setAttr(nodeFill, 'color2', Type.evaluate(el.visProp.gradientsecondcolor));
857                 this._setAttr(nodeFill, 'opacity2', Type.evaluate(el.visProp.gradientsecondopacity));
858                 this._setAttr(nodeFill, 'focusposition', Type.evaluate(el.visProp.gradientpositionx) * 100 + '%,' +
859                             Type.evaluate(el.visProp.gradientpositiony) * 100 + '%');
860                 this._setAttr(nodeFill, 'focussize', '0,0');
861             } else {
862                 this._setAttr(nodeFill, 'type', 'solid');
863             }
864         },
865 
866         // already documented in JXG.AbstractRenderer
867         setObjectFillColor: function (el, color, opacity) {
868             var rgba = Type.evaluate(color), c, rgbo,
869                 o = Type.evaluate(opacity), oo,
870                 node = el.rendNode,
871                 t;
872 
873             o = (o > 0) ? o : 0;
874 
875             if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) {
876                 return;
877             }
878 
879             if (Type.exists(rgba) && rgba !== false) {
880                 // RGB, not RGBA
881                 if (rgba.length !== 9) {
882                     c = rgba;
883                     oo = o;
884                 // True RGBA, not RGB
885                 } else {
886                     rgbo = Color.rgba2rgbo(rgba);
887                     c = rgbo[0];
888                     oo = o * rgbo[1];
889                 }
890                 if (c === 'none' || c === false) {
891                     this._setAttr(el.rendNode, 'filled', 'false');
892                 } else {
893                     this._setAttr(el.rendNode, 'filled', 'true');
894                     this._setAttr(el.rendNode, 'fillcolor', c);
895 
896                     if (Type.exists(oo) && el.rendNodeFill) {
897                         this._setAttr(el.rendNodeFill, 'opacity', (oo * 100) + '%');
898                     }
899                 }
900                 if (el.type === Const.OBJECT_TYPE_IMAGE) {
901                     /*
902                     t = el.rendNode.style.filter.toString();
903                     if (t.match(/alpha/)) {
904                         el.rendNode.style.filter = t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + (oo * 100) + ')');
905                     } else {
906                         el.rendNode.style.filter += ' alpha(opacity = ' + (oo * 100) + ')';
907                     }
908                     */
909                     if (node.filters.length > 1) {
910                         // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]?
911                         // Setting axes:true shows text labels!
912                         node.filters.item(1).opacity = Math.round(oo * 100); // Why does setObjectFillColor not use Math.round?
913                         node.filters.item(1).enabled = true;
914                     }
915                 }
916             }
917             el.visPropOld.fillcolor = rgba;
918             el.visPropOld.fillopacity = o;
919         },
920 
921         // already documented in JXG.AbstractRenderer
922         setObjectStrokeColor: function (el, color, opacity) {
923             var rgba = Type.evaluate(color), c, rgbo, t,
924                 o = Type.evaluate(opacity), oo,
925                 node = el.rendNode, nodeStroke;
926 
927             o = (o > 0) ? o : 0;
928 
929             if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
930                 return;
931             }
932 
933             // this looks like it could be merged with parts of VMLRenderer.setObjectFillColor
934 
935             if (Type.exists(rgba) && rgba !== false) {
936                 // RGB, not RGBA
937                 if (rgba.length !== 9) {
938                     c = rgba;
939                     oo = o;
940                 // True RGBA, not RGB
941                 } else {
942                     rgbo = color.rgba2rgbo(rgba);
943                     c = rgbo[0];
944                     oo = o * rgbo[1];
945                 }
946                 if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
947                     //node.style.filter = ' alpha(opacity = ' + oo + ')';
948                     /*
949                     t = node.style.filter.toString();
950                     if (t.match(/alpha/)) {
951                         node.style.filter =
952                         t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + oo + ')');
953                     } else {
954                         node.style.filter += ' alpha(opacity = ' + oo + ')';
955                     }
956                     */
957                     if (node.filters.length > 1) {
958                         // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]?
959                         // Setting axes:true shows text labels!
960                         node.filters.item(1).opacity = Math.round(oo * 100);
961                         node.filters.item(1).enabled = true;
962                     }
963 
964                     node.style.color = c;
965                 } else {
966                     if (c !== false) {
967                         this._setAttr(node, 'stroked', 'true');
968                         this._setAttr(node, 'strokecolor', c);
969                     }
970 
971                     nodeStroke = el.rendNodeStroke;
972                     if (Type.exists(oo) && el.type !== Const.OBJECT_TYPE_IMAGE) {
973                         this._setAttr(nodeStroke, 'opacity', (oo * 100) + '%');
974                     }
975                 }
976             }
977             el.visPropOld.strokecolor = rgba;
978             el.visPropOld.strokeopacity = o;
979         },
980 
981         // already documented in JXG.AbstractRenderer
982         setObjectStrokeWidth: function (el, width) {
983             var w = Type.evaluate(width),
984                 node;
985 
986             if (isNaN(w) || el.visPropOld.strokewidth === w) {
987                 return;
988             }
989 
990             node = el.rendNode;
991             this.setPropertyPrim(node, 'stroked', 'true');
992 
993             if (Type.exists(w)) {
994 
995                 this.setPropertyPrim(node, 'stroke-width', w);
996                 if (w === 0 && Type.exists(el.rendNodeStroke)) {
997                     this._setAttr(node, 'stroked', 'false');
998                 }
999             }
1000 
1001             el.visPropOld.strokewidth = w;
1002 
1003         },
1004 
1005         // already documented in JXG.AbstractRenderer
1006         setShadow: function (el) {
1007             var nodeShadow = el.rendNodeShadow,
1008                 ev_s = Type.evaluate(el.visProp.shadow);
1009 
1010             if (!nodeShadow || el.visPropOld.shadow === ev_s) {
1011                 return;
1012             }
1013 
1014             if (ev_s) {
1015                 this._setAttr(nodeShadow, 'On', 'True');
1016                 this._setAttr(nodeShadow, 'Offset', '3pt,3pt');
1017                 this._setAttr(nodeShadow, 'Opacity', '60%');
1018                 this._setAttr(nodeShadow, 'Color', '#aaaaaa');
1019             } else {
1020                 this._setAttr(nodeShadow, 'On', 'False');
1021             }
1022 
1023             el.visPropOld.shadow = ev_s;
1024         },
1025 
1026         /* **************************
1027          * renderer control
1028          * **************************/
1029 
1030         // already documented in JXG.AbstractRenderer
1031         suspendRedraw: function () {
1032             this.container.style.display = 'none';
1033         },
1034 
1035         // already documented in JXG.AbstractRenderer
1036         unsuspendRedraw: function () {
1037             this.container.style.display = '';
1038         }
1039     });
1040 
1041     return JXG.VMLRenderer;
1042 });
1043