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