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 /*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";
 36 import AbstractRenderer from "./abstract";
 37 import Const from "../base/constants";
 38 import Type from "../utils/type";
 39 import Color from "../utils/color";
 40 import Mat from "../math/math";
 41 import Numerics from "../math/numerics";
 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 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, Type.evaluate(ticks.visProp.layer));
176             }
177 
178             this._setAttr(ticks.rendNode, "stroked", "true");
179             this._setAttr(
180                 ticks.rendNode,
181                 "strokecolor",
182                 Type.evaluate(ticks.visProp.strokecolor),
183                 1
184             );
185             this._setAttr(
186                 ticks.rendNode,
187                 "strokeweight",
188                 Type.evaluate(ticks.visProp.strokewidth)
189             );
190             this._setAttr(
191                 ticks.rendNodeStroke,
192                 "opacity",
193                 Type.evaluate(ticks.visProp.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.transformImage(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, Type.evaluate(el.visProp.layer));
371 
372             // Adding the rotation filter. This is always filter item 0:
373             // node.filters.item(0), see transformImage
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         transformImage: function (el, t) {
385             var m,
386                 s,
387                 maxX,
388                 maxY,
389                 minX,
390                 minY,
391                 i,
392                 nt,
393                 node = el.rendNode,
394                 p = [],
395                 len = t.length;
396 
397             if (len > 0) {
398                 /*
399                 nt = el.rendNode.style.filter.toString();
400                 if (!nt.match(/DXImageTransform/)) {
401                     node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand') " + nt;
402                 }
403                 */
404 
405                 m = this.joinTransforms(el, t);
406                 p[0] = Mat.matVecMult(m, el.coords.scrCoords);
407                 p[0][1] /= p[0][0];
408                 p[0][2] /= p[0][0];
409                 p[1] = Mat.matVecMult(m, [
410                     1,
411                     el.coords.scrCoords[1] + el.size[0],
412                     el.coords.scrCoords[2]
413                 ]);
414                 p[1][1] /= p[1][0];
415                 p[1][2] /= p[1][0];
416                 p[2] = Mat.matVecMult(m, [
417                     1,
418                     el.coords.scrCoords[1] + el.size[0],
419                     el.coords.scrCoords[2] - el.size[1]
420                 ]);
421                 p[2][1] /= p[2][0];
422                 p[2][2] /= p[2][0];
423                 p[3] = Mat.matVecMult(m, [
424                     1,
425                     el.coords.scrCoords[1],
426                     el.coords.scrCoords[2] - el.size[1]
427                 ]);
428                 p[3][1] /= p[3][0];
429                 p[3][2] /= p[3][0];
430                 maxX = p[0][1];
431                 minX = p[0][1];
432                 maxY = p[0][2];
433                 minY = p[0][2];
434 
435                 for (i = 1; i < 4; i++) {
436                     maxX = Math.max(maxX, p[i][1]);
437                     minX = Math.min(minX, p[i][1]);
438                     maxY = Math.max(maxY, p[i][2]);
439                     minY = Math.min(minY, p[i][2]);
440                 }
441                 node.style.left = Math.floor(minX) + "px";
442                 node.style.top = Math.floor(minY) + "px";
443 
444                 node.filters.item(0).M11 = m[1][1];
445                 node.filters.item(0).M12 = m[1][2];
446                 node.filters.item(0).M21 = m[2][1];
447                 node.filters.item(0).M22 = m[2][2];
448                 node.filters.item(0).enabled = true;
449             }
450         },
451 
452         // Already documented in JXG.AbstractRenderer
453         updateImageURL: function (el) {
454             var url = Type.evaluate(el.url);
455 
456             this._setAttr(el.rendNode, "src", url);
457         },
458 
459         /* **************************
460          * Render primitive objects
461          * **************************/
462 
463         // Already documented in JXG.AbstractRenderer
464         appendChildPrim: function (node, level) {
465             // For trace nodes
466             if (!Type.exists(level)) {
467                 level = 0;
468             }
469 
470             node.style.zIndex = level;
471             this.container.appendChild(node);
472 
473             return node;
474         },
475 
476         // Already documented in JXG.AbstractRenderer
477         appendNodesToElement: function (el, type) {
478             if (type === "shape" || type === "path" || type === "polygon") {
479                 el.rendNodePath = this.getElementById(el.id + "_path");
480             }
481             el.rendNodeFill = this.getElementById(el.id + "_fill");
482             el.rendNodeStroke = this.getElementById(el.id + "_stroke");
483             el.rendNodeShadow = this.getElementById(el.id + "_shadow");
484             el.rendNode = this.getElementById(el.id);
485         },
486 
487         // Already documented in JXG.AbstractRenderer
488         createPrim: function (type, id) {
489             var node,
490                 pathNode,
491                 fillNode = this.createNode("fill"),
492                 strokeNode = this.createNode("stroke"),
493                 shadowNode = this.createNode("shadow");
494 
495             this._setAttr(fillNode, "id", this.container.id + "_" + id + "_fill");
496             this._setAttr(strokeNode, "id", this.container.id + "_" + id + "_stroke");
497             this._setAttr(shadowNode, "id", this.container.id + "_" + id + "_shadow");
498 
499             if (type === "circle" || type === "ellipse") {
500                 node = this.createNode("oval");
501                 node.appendChild(fillNode);
502                 node.appendChild(strokeNode);
503                 node.appendChild(shadowNode);
504             } else if (
505                 type === "polygon" ||
506                 type === "path" ||
507                 type === "shape" ||
508                 type === "line"
509             ) {
510                 node = this.createNode("shape");
511                 node.appendChild(fillNode);
512                 node.appendChild(strokeNode);
513                 node.appendChild(shadowNode);
514                 pathNode = this.createNode("path");
515                 this._setAttr(pathNode, "id", this.container.id + "_" + id + "_path");
516                 node.appendChild(pathNode);
517             } else {
518                 node = this.createNode(type);
519                 node.appendChild(fillNode);
520                 node.appendChild(strokeNode);
521                 node.appendChild(shadowNode);
522             }
523 
524             node.style.position = "absolute";
525             node.style.left = "0px";
526             node.style.top = "0px";
527             this._setAttr(node, "id", this.container.id + "_" + id);
528 
529             return node;
530         },
531 
532         // Already documented in JXG.AbstractRenderer
533         remove: function (node) {
534             if (Type.exists(node)) {
535                 node.removeNode(true);
536             }
537         },
538 
539         // Already documented in JXG.AbstractRenderer
540         makeArrows: function (el) {
541             var nodeStroke,
542                 ev_fa = Type.evaluate(el.visProp.firstarrow),
543                 ev_la = Type.evaluate(el.visProp.lastarrow);
544 
545             if (el.visPropOld.firstarrow === ev_fa && el.visPropOld.lastarrow === ev_la) {
546                 return;
547             }
548 
549             if (ev_fa) {
550                 nodeStroke = el.rendNodeStroke;
551                 this._setAttr(nodeStroke, "startarrow", "block");
552                 this._setAttr(nodeStroke, "startarrowlength", "long");
553             } else {
554                 nodeStroke = el.rendNodeStroke;
555                 if (Type.exists(nodeStroke)) {
556                     this._setAttr(nodeStroke, "startarrow", "none");
557                 }
558             }
559 
560             if (ev_la) {
561                 nodeStroke = el.rendNodeStroke;
562                 this._setAttr(nodeStroke, "id", this.container.id + "_" + el.id + "stroke");
563                 this._setAttr(nodeStroke, "endarrow", "block");
564                 this._setAttr(nodeStroke, "endarrowlength", "long");
565             } else {
566                 nodeStroke = el.rendNodeStroke;
567                 if (Type.exists(nodeStroke)) {
568                     this._setAttr(nodeStroke, "endarrow", "none");
569                 }
570             }
571             el.visPropOld.firstarrow = ev_fa;
572             el.visPropOld.lastarrow = ev_la;
573         },
574 
575         // Already documented in JXG.AbstractRenderer
576         updateEllipsePrim: function (node, x, y, rx, ry) {
577             node.style.left = Math.floor(x - rx) + "px";
578             node.style.top = Math.floor(y - ry) + "px";
579             node.style.width = Math.floor(Math.abs(rx) * 2) + "px";
580             node.style.height = Math.floor(Math.abs(ry) * 2) + "px";
581         },
582 
583         // Already documented in JXG.AbstractRenderer
584         updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) {
585             var s,
586                 r = this.resolution;
587 
588             if (!isNaN(p1x + p1y + p2x + p2y)) {
589                 s = [
590                     "m ",
591                     Math.floor(r * p1x),
592                     ", ",
593                     Math.floor(r * p1y),
594                     " l ",
595                     Math.floor(r * p2x),
596                     ", ",
597                     Math.floor(r * p2y)
598                 ];
599                 this.updatePathPrim(node, s, board);
600             }
601         },
602 
603         // Already documented in JXG.AbstractRenderer
604         updatePathPrim: function (node, pointString, board) {
605             var x = board.canvasWidth,
606                 y = board.canvasHeight;
607             if (pointString.length <= 0) {
608                 pointString = ["m 0,0"];
609             }
610             node.style.width = x;
611             node.style.height = y;
612             this._setAttr(
613                 node,
614                 "coordsize",
615                 [Math.floor(this.resolution * x), Math.floor(this.resolution * y)].join(",")
616             );
617             this._setAttr(node, "path", pointString.join(""));
618         },
619 
620         // Already documented in JXG.AbstractRenderer
621         updatePathStringPoint: function (el, size, type) {
622             var s = [],
623                 mround = Math.round,
624                 scr = el.coords.scrCoords,
625                 sqrt32 = size * Math.sqrt(3) * 0.5,
626                 s05 = size * 0.5,
627                 r = this.resolution;
628 
629             if (type === "x") {
630                 s.push(
631                     [
632                         " m ",
633                         mround(r * (scr[1] - size)),
634                         ", ",
635                         mround(r * (scr[2] - size)),
636                         " l ",
637                         mround(r * (scr[1] + size)),
638                         ", ",
639                         mround(r * (scr[2] + size)),
640                         " m ",
641                         mround(r * (scr[1] + size)),
642                         ", ",
643                         mround(r * (scr[2] - size)),
644                         " l ",
645                         mround(r * (scr[1] - size)),
646                         ", ",
647                         mround(r * (scr[2] + size))
648                     ].join("")
649                 );
650             } else if (type === "+") {
651                 s.push(
652                     [
653                         " m ",
654                         mround(r * (scr[1] - size)),
655                         ", ",
656                         mround(r * scr[2]),
657                         " l ",
658                         mround(r * (scr[1] + size)),
659                         ", ",
660                         mround(r * scr[2]),
661                         " m ",
662                         mround(r * scr[1]),
663                         ", ",
664                         mround(r * (scr[2] - size)),
665                         " l ",
666                         mround(r * scr[1]),
667                         ", ",
668                         mround(r * (scr[2] + size))
669                     ].join("")
670                 );
671             } else if (type === "<>") {
672                 s.push(
673                     [
674                         " m ",
675                         mround(r * (scr[1] - size)),
676                         ", ",
677                         mround(r * scr[2]),
678                         " l ",
679                         mround(r * scr[1]),
680                         ", ",
681                         mround(r * (scr[2] + size)),
682                         " l ",
683                         mround(r * (scr[1] + size)),
684                         ", ",
685                         mround(r * scr[2]),
686                         " l ",
687                         mround(r * scr[1]),
688                         ", ",
689                         mround(r * (scr[2] - size)),
690                         " x e "
691                     ].join("")
692                 );
693             } else if (type === "^") {
694                 s.push(
695                     [
696                         " m ",
697                         mround(r * scr[1]),
698                         ", ",
699                         mround(r * (scr[2] - size)),
700                         " l ",
701                         mround(r * (scr[1] - sqrt32)),
702                         ", ",
703                         mround(r * (scr[2] + s05)),
704                         " l ",
705                         mround(r * (scr[1] + sqrt32)),
706                         ", ",
707                         mround(r * (scr[2] + s05)),
708                         " x e "
709                     ].join("")
710                 );
711             } else if (type === "v") {
712                 s.push(
713                     [
714                         " m ",
715                         mround(r * scr[1]),
716                         ", ",
717                         mround(r * (scr[2] + size)),
718                         " l ",
719                         mround(r * (scr[1] - sqrt32)),
720                         ", ",
721                         mround(r * (scr[2] - s05)),
722                         " l ",
723                         mround(r * (scr[1] + sqrt32)),
724                         ", ",
725                         mround(r * (scr[2] - s05)),
726                         " x e "
727                     ].join("")
728                 );
729             } else if (type === ">") {
730                 s.push(
731                     [
732                         " m ",
733                         mround(r * (scr[1] + size)),
734                         ", ",
735                         mround(r * scr[2]),
736                         " l ",
737                         mround(r * (scr[1] - s05)),
738                         ", ",
739                         mround(r * (scr[2] - sqrt32)),
740                         " l ",
741                         mround(r * (scr[1] - s05)),
742                         ", ",
743                         mround(r * (scr[2] + sqrt32)),
744                         " l ",
745                         mround(r * (scr[1] + size)),
746                         ", ",
747                         mround(r * scr[2])
748                     ].join("")
749                 );
750             } else if (type === "<") {
751                 s.push(
752                     [
753                         " m ",
754                         mround(r * (scr[1] - size)),
755                         ", ",
756                         mround(r * scr[2]),
757                         " l ",
758                         mround(r * (scr[1] + s05)),
759                         ", ",
760                         mround(r * (scr[2] - sqrt32)),
761                         " l ",
762                         mround(r * (scr[1] + s05)),
763                         ", ",
764                         mround(r * (scr[2] + sqrt32)),
765                         " x e "
766                     ].join("")
767                 );
768             }
769 
770             return s;
771         },
772 
773         // Already documented in JXG.AbstractRenderer
774         updatePathStringPrim: function (el) {
775             var i,
776                 scr,
777                 pStr = [],
778                 r = this.resolution,
779                 mround = Math.round,
780                 symbm = " m ",
781                 symbl = " l ",
782                 symbc = " c ",
783                 nextSymb = symbm,
784                 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html
785 
786             if (el.numberPoints <= 0) {
787                 return "";
788             }
789             len = Math.min(len, el.points.length);
790 
791             if (el.bezierDegree === 1) {
792                 /*
793                 if (isNotPlot && el.board.options.curve.RDPsmoothing) {
794                     el.points = Numerics.RamerDouglasPeucker(el.points, 1.0);
795                 }
796                 */
797 
798                 for (i = 0; i < len; i++) {
799                     scr = el.points[i].scrCoords;
800                     if (isNaN(scr[1]) || isNaN(scr[2])) {
801                         // PenUp
802                         nextSymb = symbm;
803                     } else {
804                         // IE has problems with values  being too far away.
805                         if (scr[1] > 20000.0) {
806                             scr[1] = 20000.0;
807                         } else if (scr[1] < -20000.0) {
808                             scr[1] = -20000.0;
809                         }
810 
811                         if (scr[2] > 20000.0) {
812                             scr[2] = 20000.0;
813                         } else if (scr[2] < -20000.0) {
814                             scr[2] = -20000.0;
815                         }
816 
817                         pStr.push(
818                             [nextSymb, mround(r * scr[1]), ", ", mround(r * scr[2])].join("")
819                         );
820                         nextSymb = symbl;
821                     }
822                 }
823             } else if (el.bezierDegree === 3) {
824                 i = 0;
825                 while (i < len) {
826                     scr = el.points[i].scrCoords;
827                     if (isNaN(scr[1]) || isNaN(scr[2])) {
828                         // PenUp
829                         nextSymb = symbm;
830                     } else {
831                         pStr.push(
832                             [nextSymb, mround(r * scr[1]), ", ", mround(r * scr[2])].join("")
833                         );
834                         if (nextSymb === symbc) {
835                             i += 1;
836                             scr = el.points[i].scrCoords;
837                             pStr.push(
838                                 [" ", mround(r * scr[1]), ", ", mround(r * scr[2])].join("")
839                             );
840                             i += 1;
841                             scr = el.points[i].scrCoords;
842                             pStr.push(
843                                 [" ", mround(r * scr[1]), ", ", mround(r * scr[2])].join("")
844                             );
845                         }
846                         nextSymb = symbc;
847                     }
848                     i += 1;
849                 }
850             }
851             pStr.push(" e");
852             return pStr;
853         },
854 
855         // Already documented in JXG.AbstractRenderer
856         updatePathStringBezierPrim: function (el) {
857             var i,
858                 j,
859                 k,
860                 scr,
861                 lx,
862                 ly,
863                 pStr = [],
864                 f = Type.evaluate(el.visProp.strokewidth),
865                 r = this.resolution,
866                 mround = Math.round,
867                 symbm = " m ",
868                 symbl = " c ",
869                 nextSymb = symbm,
870                 isNoPlot = Type.evaluate(el.visProp.curvetype) !== "plot",
871                 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html
872 
873             if (el.numberPoints <= 0) {
874                 return "";
875             }
876             if (isNoPlot && el.board.options.curve.RDPsmoothing) {
877                 el.points = Numerics.RamerDouglasPeucker(el.points, 1.0);
878             }
879             len = Math.min(len, el.points.length);
880 
881             for (j = 1; j < 3; j++) {
882                 nextSymb = symbm;
883                 for (i = 0; i < len; i++) {
884                     scr = el.points[i].scrCoords;
885                     if (isNaN(scr[1]) || isNaN(scr[2])) {
886                         // PenUp
887                         nextSymb = symbm;
888                     } else {
889                         // IE has problems with values  being too far away.
890                         if (scr[1] > 20000.0) {
891                             scr[1] = 20000.0;
892                         } else if (scr[1] < -20000.0) {
893                             scr[1] = -20000.0;
894                         }
895 
896                         if (scr[2] > 20000.0) {
897                             scr[2] = 20000.0;
898                         } else if (scr[2] < -20000.0) {
899                             scr[2] = -20000.0;
900                         }
901 
902                         if (nextSymb === symbm) {
903                             pStr.push(
904                                 [nextSymb, mround(r * scr[1]), " ", mround(r * scr[2])].join("")
905                             );
906                         } else {
907                             k = 2 * j;
908                             pStr.push(
909                                 [
910                                     nextSymb,
911                                     mround(
912                                         r *
913                                             (lx +
914                                                 (scr[1] - lx) * 0.333 +
915                                                 f * (k * Math.random() - j))
916                                     ),
917                                     " ",
918                                     mround(
919                                         r *
920                                             (ly +
921                                                 (scr[2] - ly) * 0.333 +
922                                                 f * (k * Math.random() - j))
923                                     ),
924                                     " ",
925                                     mround(
926                                         r *
927                                             (lx +
928                                                 (scr[1] - lx) * 0.666 +
929                                                 f * (k * Math.random() - j))
930                                     ),
931                                     " ",
932                                     mround(
933                                         r *
934                                             (ly +
935                                                 (scr[2] - ly) * 0.666 +
936                                                 f * (k * Math.random() - j))
937                                     ),
938                                     " ",
939                                     mround(r * scr[1]),
940                                     " ",
941                                     mround(r * scr[2])
942                                 ].join("")
943                             );
944                         }
945                         nextSymb = symbl;
946                         lx = scr[1];
947                         ly = scr[2];
948                     }
949                 }
950             }
951             pStr.push(" e");
952             return pStr;
953         },
954 
955         // Already documented in JXG.AbstractRenderer
956         updatePolygonPrim: function (node, el) {
957             var i,
958                 len = el.vertices.length,
959                 r = this.resolution,
960                 scr,
961                 pStr = [];
962 
963             this._setAttr(node, "stroked", "false");
964             scr = el.vertices[0].coords.scrCoords;
965 
966             if (isNaN(scr[1] + scr[2])) {
967                 return;
968             }
969 
970             pStr.push(
971                 ["m ", Math.floor(r * scr[1]), ",", Math.floor(r * scr[2]), " l "].join("")
972             );
973 
974             for (i = 1; i < len - 1; i++) {
975                 if (el.vertices[i].isReal) {
976                     scr = el.vertices[i].coords.scrCoords;
977 
978                     if (isNaN(scr[1] + scr[2])) {
979                         return;
980                     }
981 
982                     pStr.push(Math.floor(r * scr[1]) + "," + Math.floor(r * scr[2]));
983                 } else {
984                     this.updatePathPrim(node, "", el.board);
985                     return;
986                 }
987                 if (i < len - 2) {
988                     pStr.push(", ");
989                 }
990             }
991             pStr.push(" x e");
992             this.updatePathPrim(node, pStr, el.board);
993         },
994 
995         // Already documented in JXG.AbstractRenderer
996         updateRectPrim: function (node, x, y, w, h) {
997             node.style.left = Math.floor(x) + "px";
998             node.style.top = Math.floor(y) + "px";
999 
1000             if (w >= 0) {
1001                 node.style.width = w + "px";
1002             }
1003 
1004             if (h >= 0) {
1005                 node.style.height = h + "px";
1006             }
1007         },
1008 
1009         /* **************************
1010          *  Set Attributes
1011          * **************************/
1012 
1013         // Already documented in JXG.AbstractRenderer
1014         setPropertyPrim: function (node, key, val) {
1015             var keyVml = "",
1016                 v;
1017 
1018             switch (key) {
1019                 case "stroke":
1020                     keyVml = "strokecolor";
1021                     break;
1022                 case "stroke-width":
1023                     keyVml = "strokeweight";
1024                     break;
1025                 case "stroke-dasharray":
1026                     keyVml = "dashstyle";
1027                     break;
1028             }
1029 
1030             if (keyVml !== "") {
1031                 v = Type.evaluate(val);
1032                 this._setAttr(node, keyVml, v);
1033             }
1034         },
1035 
1036         // Already documented in JXG.AbstractRenderer
1037         display: function (el, val) {
1038             if (el && el.rendNode) {
1039                 el.visPropOld.visible = val;
1040                 if (val) {
1041                     el.rendNode.style.visibility = "inherit";
1042                 } else {
1043                     el.rendNode.style.visibility = "hidden";
1044                 }
1045             }
1046         },
1047 
1048         // Already documented in JXG.AbstractRenderer
1049         show: function (el) {
1050             JXG.deprecated("Board.renderer.show()", "Board.renderer.display()");
1051 
1052             if (el && el.rendNode) {
1053                 el.rendNode.style.visibility = "inherit";
1054             }
1055         },
1056 
1057         // Already documented in JXG.AbstractRenderer
1058         hide: function (el) {
1059             JXG.deprecated("Board.renderer.hide()", "Board.renderer.display()");
1060 
1061             if (el && el.rendNode) {
1062                 el.rendNode.style.visibility = "hidden";
1063             }
1064         },
1065 
1066         // Already documented in JXG.AbstractRenderer
1067         setDashStyle: function (el, visProp) {
1068             var node;
1069             if (visProp.dash >= 0) {
1070                 node = el.rendNodeStroke;
1071                 this._setAttr(node, "dashstyle", this.dashArray[visProp.dash]);
1072             }
1073         },
1074 
1075         // Already documented in JXG.AbstractRenderer
1076         setGradient: function (el) {
1077             var nodeFill = el.rendNodeFill,
1078                 ev_g = Type.evaluate(el.visProp.gradient);
1079 
1080             if (ev_g === "linear") {
1081                 this._setAttr(nodeFill, "type", "gradient");
1082                 this._setAttr(
1083                     nodeFill,
1084                     "color2",
1085                     Type.evaluate(el.visProp.gradientsecondcolor)
1086                 );
1087                 this._setAttr(
1088                     nodeFill,
1089                     "opacity2",
1090                     Type.evaluate(el.visProp.gradientsecondopacity)
1091                 );
1092                 this._setAttr(nodeFill, "angle", Type.evaluate(el.visProp.gradientangle));
1093             } else if (ev_g === "radial") {
1094                 this._setAttr(nodeFill, "type", "gradientradial");
1095                 this._setAttr(
1096                     nodeFill,
1097                     "color2",
1098                     Type.evaluate(el.visProp.gradientsecondcolor)
1099                 );
1100                 this._setAttr(
1101                     nodeFill,
1102                     "opacity2",
1103                     Type.evaluate(el.visProp.gradientsecondopacity)
1104                 );
1105                 this._setAttr(
1106                     nodeFill,
1107                     "focusposition",
1108                     Type.evaluate(el.visProp.gradientpositionx) * 100 +
1109                         "%," +
1110                         Type.evaluate(el.visProp.gradientpositiony) * 100 +
1111                         "%"
1112                 );
1113                 this._setAttr(nodeFill, "focussize", "0,0");
1114             } else {
1115                 this._setAttr(nodeFill, "type", "solid");
1116             }
1117         },
1118 
1119         // Already documented in JXG.AbstractRenderer
1120         setObjectFillColor: function (el, color, opacity) {
1121             var rgba = Type.evaluate(color),
1122                 c,
1123                 rgbo,
1124                 o = Type.evaluate(opacity),
1125                 oo,
1126                 node = el.rendNode,
1127                 t;
1128 
1129             o = o > 0 ? o : 0;
1130 
1131             if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) {
1132                 return;
1133             }
1134 
1135             if (Type.exists(rgba) && rgba !== false) {
1136                 // RGB, not RGBA
1137                 if (rgba.length !== 9) {
1138                     c = rgba;
1139                     oo = o;
1140                     // True RGBA, not RGB
1141                 } else {
1142                     rgbo = Color.rgba2rgbo(rgba);
1143                     c = rgbo[0];
1144                     oo = o * rgbo[1];
1145                 }
1146                 if (c === "none" || c === false) {
1147                     this._setAttr(el.rendNode, "filled", "false");
1148                 } else {
1149                     this._setAttr(el.rendNode, "filled", "true");
1150                     this._setAttr(el.rendNode, "fillcolor", c);
1151 
1152                     if (Type.exists(oo) && el.rendNodeFill) {
1153                         this._setAttr(el.rendNodeFill, "opacity", oo * 100 + "%");
1154                     }
1155                 }
1156                 if (el.type === Const.OBJECT_TYPE_IMAGE) {
1157                     /*
1158                     t = el.rendNode.style.filter.toString();
1159                     if (t.match(/alpha/)) {
1160                         el.rendNode.style.filter = t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + (oo * 100) + ')');
1161                     } else {
1162                         el.rendNode.style.filter += ' alpha(opacity = ' + (oo * 100) + ')';
1163                     }
1164                     */
1165                     if (node.filters.length > 1) {
1166                         // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]?
1167                         // Setting axes:true shows text labels!
1168                         node.filters.item(1).opacity = Math.round(oo * 100); // Why does setObjectFillColor not use Math.round?
1169                         node.filters.item(1).enabled = true;
1170                     }
1171                 }
1172             }
1173             el.visPropOld.fillcolor = rgba;
1174             el.visPropOld.fillopacity = o;
1175         },
1176 
1177         // Already documented in JXG.AbstractRenderer
1178         setObjectStrokeColor: function (el, color, opacity) {
1179             var rgba = Type.evaluate(color),
1180                 c,
1181                 rgbo,
1182                 t,
1183                 o = Type.evaluate(opacity),
1184                 oo,
1185                 node = el.rendNode,
1186                 nodeStroke;
1187 
1188             o = o > 0 ? o : 0;
1189 
1190             if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
1191                 return;
1192             }
1193 
1194             // this looks like it could be merged with parts of VMLRenderer.setObjectFillColor
1195 
1196             if (Type.exists(rgba) && rgba !== false) {
1197                 // RGB, not RGBA
1198                 if (rgba.length !== 9) {
1199                     c = rgba;
1200                     oo = o;
1201                     // True RGBA, not RGB
1202                 } else {
1203                     rgbo = color.rgba2rgbo(rgba);
1204                     c = rgbo[0];
1205                     oo = o * rgbo[1];
1206                 }
1207                 if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
1208                     //node.style.filter = ' alpha(opacity = ' + oo + ')';
1209                     /*
1210                     t = node.style.filter.toString();
1211                     if (t.match(/alpha/)) {
1212                         node.style.filter =
1213                         t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + oo + ')');
1214                     } else {
1215                         node.style.filter += ' alpha(opacity = ' + oo + ')';
1216                     }
1217                     */
1218                     if (node.filters.length > 1) {
1219                         // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]?
1220                         // Setting axes:true shows text labels!
1221                         node.filters.item(1).opacity = Math.round(oo * 100);
1222                         node.filters.item(1).enabled = true;
1223                     }
1224 
1225                     node.style.color = c;
1226                 } else {
1227                     if (c !== false) {
1228                         this._setAttr(node, "stroked", "true");
1229                         this._setAttr(node, "strokecolor", c);
1230                     }
1231 
1232                     nodeStroke = el.rendNodeStroke;
1233                     if (Type.exists(oo) && el.type !== Const.OBJECT_TYPE_IMAGE) {
1234                         this._setAttr(nodeStroke, "opacity", oo * 100 + "%");
1235                     }
1236                 }
1237             }
1238             el.visPropOld.strokecolor = rgba;
1239             el.visPropOld.strokeopacity = o;
1240         },
1241 
1242         // Already documented in JXG.AbstractRenderer
1243         setObjectStrokeWidth: function (el, width) {
1244             var w = Type.evaluate(width),
1245                 node;
1246 
1247             if (isNaN(w) || el.visPropOld.strokewidth === w) {
1248                 return;
1249             }
1250 
1251             node = el.rendNode;
1252             this.setPropertyPrim(node, "stroked", "true");
1253 
1254             if (Type.exists(w)) {
1255                 this.setPropertyPrim(node, "stroke-width", w);
1256                 if (w === 0 && Type.exists(el.rendNodeStroke)) {
1257                     this._setAttr(node, "stroked", "false");
1258                 }
1259             }
1260 
1261             el.visPropOld.strokewidth = w;
1262         },
1263 
1264         // Already documented in JXG.AbstractRenderer
1265         setShadow: function (el) {
1266             var nodeShadow = el.rendNodeShadow,
1267                 ev_s = Type.evaluate(el.visProp.shadow);
1268 
1269             if (!nodeShadow || el.visPropOld.shadow === ev_s) {
1270                 return;
1271             }
1272 
1273             if (ev_s) {
1274                 this._setAttr(nodeShadow, "On", "True");
1275                 this._setAttr(nodeShadow, "Offset", "3pt,3pt");
1276                 this._setAttr(nodeShadow, "Opacity", "60%");
1277                 this._setAttr(nodeShadow, "Color", "#aaaaaa");
1278             } else {
1279                 this._setAttr(nodeShadow, "On", "False");
1280             }
1281 
1282             el.visPropOld.shadow = ev_s;
1283         },
1284 
1285         /* **************************
1286          * renderer control
1287          * **************************/
1288 
1289         // Already documented in JXG.AbstractRenderer
1290         suspendRedraw: function () {
1291             this.container.style.display = "none";
1292         },
1293 
1294         // Already documented in JXG.AbstractRenderer
1295         unsuspendRedraw: function () {
1296             this.container.style.display = "";
1297         }
1298     }
1299 );
1300 
1301 export default JXG.VMLRenderer;
1302