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