1 /*
  2     Copyright 2008-2024
  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, 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                 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 = Type.evaluate(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 = Type.evaluate(el.visProp.firstarrow),
541                 ev_la = Type.evaluate(el.visProp.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                 /*
794                 if (isNotPlot && el.board.options.curve.RDPsmoothing) {
795                     el.points = Numerics.RamerDouglasPeucker(el.points, 1.0);
796                 }
797                 */
798 
799                 for (i = 0; i < len; i++) {
800                     scr = el.points[i].scrCoords;
801                     if (isNaN(scr[1]) || isNaN(scr[2])) {
802                         // PenUp
803                         nextSymb = symbm;
804                     } else {
805                         // IE has problems with values  being too far away.
806                         if (scr[1] > 20000.0) {
807                             scr[1] = 20000.0;
808                         } else if (scr[1] < -20000.0) {
809                             scr[1] = -20000.0;
810                         }
811 
812                         if (scr[2] > 20000.0) {
813                             scr[2] = 20000.0;
814                         } else if (scr[2] < -20000.0) {
815                             scr[2] = -20000.0;
816                         }
817 
818                         pStr.push(
819                             [nextSymb, mround(r * scr[1]), ", ", mround(r * scr[2])].join("")
820                         );
821                         nextSymb = symbl;
822                     }
823                 }
824             } else if (el.bezierDegree === 3) {
825                 i = 0;
826                 while (i < len) {
827                     scr = el.points[i].scrCoords;
828                     if (isNaN(scr[1]) || isNaN(scr[2])) {
829                         // PenUp
830                         nextSymb = symbm;
831                     } else {
832                         pStr.push(
833                             [nextSymb, mround(r * scr[1]), ", ", mround(r * scr[2])].join("")
834                         );
835                         if (nextSymb === symbc) {
836                             i += 1;
837                             scr = el.points[i].scrCoords;
838                             pStr.push(
839                                 [" ", mround(r * scr[1]), ", ", mround(r * scr[2])].join("")
840                             );
841                             i += 1;
842                             scr = el.points[i].scrCoords;
843                             pStr.push(
844                                 [" ", mround(r * scr[1]), ", ", mround(r * scr[2])].join("")
845                             );
846                         }
847                         nextSymb = symbc;
848                     }
849                     i += 1;
850                 }
851             }
852             pStr.push(" e");
853             return pStr;
854         },
855 
856         // Already documented in JXG.AbstractRenderer
857         updatePathStringBezierPrim: function (el) {
858             var i,
859                 j,
860                 k,
861                 scr,
862                 lx,
863                 ly,
864                 pStr = [],
865                 f = Type.evaluate(el.visProp.strokewidth),
866                 r = this.resolution,
867                 mround = Math.round,
868                 symbm = " m ",
869                 symbl = " c ",
870                 nextSymb = symbm,
871                 isNoPlot = Type.evaluate(el.visProp.curvetype) !== "plot",
872                 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html
873 
874             if (el.numberPoints <= 0) {
875                 return "";
876             }
877             if (isNoPlot && el.board.options.curve.RDPsmoothing) {
878                 el.points = Numerics.RamerDouglasPeucker(el.points, 1.0);
879             }
880             len = Math.min(len, el.points.length);
881 
882             for (j = 1; j < 3; j++) {
883                 nextSymb = symbm;
884                 for (i = 0; i < len; i++) {
885                     scr = el.points[i].scrCoords;
886                     if (isNaN(scr[1]) || isNaN(scr[2])) {
887                         // PenUp
888                         nextSymb = symbm;
889                     } else {
890                         // IE has problems with values  being too far away.
891                         if (scr[1] > 20000.0) {
892                             scr[1] = 20000.0;
893                         } else if (scr[1] < -20000.0) {
894                             scr[1] = -20000.0;
895                         }
896 
897                         if (scr[2] > 20000.0) {
898                             scr[2] = 20000.0;
899                         } else if (scr[2] < -20000.0) {
900                             scr[2] = -20000.0;
901                         }
902 
903                         if (nextSymb === symbm) {
904                             pStr.push(
905                                 [nextSymb, mround(r * scr[1]), " ", mround(r * scr[2])].join("")
906                             );
907                         } else {
908                             k = 2 * j;
909                             pStr.push(
910                                 [
911                                     nextSymb,
912                                     mround(
913                                         r *
914                                             (lx +
915                                                 (scr[1] - lx) * 0.333 +
916                                                 f * (k * Math.random() - j))
917                                     ),
918                                     " ",
919                                     mround(
920                                         r *
921                                             (ly +
922                                                 (scr[2] - ly) * 0.333 +
923                                                 f * (k * Math.random() - j))
924                                     ),
925                                     " ",
926                                     mround(
927                                         r *
928                                             (lx +
929                                                 (scr[1] - lx) * 0.666 +
930                                                 f * (k * Math.random() - j))
931                                     ),
932                                     " ",
933                                     mround(
934                                         r *
935                                             (ly +
936                                                 (scr[2] - ly) * 0.666 +
937                                                 f * (k * Math.random() - j))
938                                     ),
939                                     " ",
940                                     mround(r * scr[1]),
941                                     " ",
942                                     mround(r * scr[2])
943                                 ].join("")
944                             );
945                         }
946                         nextSymb = symbl;
947                         lx = scr[1];
948                         ly = scr[2];
949                     }
950                 }
951             }
952             pStr.push(" e");
953             return pStr;
954         },
955 
956         // Already documented in JXG.AbstractRenderer
957         updatePolygonPrim: function (node, el) {
958             var i,
959                 len = el.vertices.length,
960                 r = this.resolution,
961                 scr,
962                 pStr = [];
963 
964             this._setAttr(node, "stroked", "false");
965             scr = el.vertices[0].coords.scrCoords;
966 
967             if (isNaN(scr[1] + scr[2])) {
968                 return;
969             }
970 
971             pStr.push(
972                 ["m ", Math.floor(r * scr[1]), ",", Math.floor(r * scr[2]), " l "].join("")
973             );
974 
975             for (i = 1; i < len - 1; i++) {
976                 if (el.vertices[i].isReal) {
977                     scr = el.vertices[i].coords.scrCoords;
978 
979                     if (isNaN(scr[1] + scr[2])) {
980                         return;
981                     }
982 
983                     pStr.push(Math.floor(r * scr[1]) + "," + Math.floor(r * scr[2]));
984                 } else {
985                     this.updatePathPrim(node, "", el.board);
986                     return;
987                 }
988                 if (i < len - 2) {
989                     pStr.push(", ");
990                 }
991             }
992             pStr.push(" x e");
993             this.updatePathPrim(node, pStr, el.board);
994         },
995 
996         // Already documented in JXG.AbstractRenderer
997         updateRectPrim: function (node, x, y, w, h) {
998             node.style.left = Math.floor(x) + "px";
999             node.style.top = Math.floor(y) + "px";
1000 
1001             if (w >= 0) {
1002                 node.style.width = w + "px";
1003             }
1004 
1005             if (h >= 0) {
1006                 node.style.height = h + "px";
1007             }
1008         },
1009 
1010         /* **************************
1011          *  Set Attributes
1012          * **************************/
1013 
1014         // Already documented in JXG.AbstractRenderer
1015         setPropertyPrim: function (node, key, val) {
1016             var keyVml = "",
1017                 v;
1018 
1019             switch (key) {
1020                 case "stroke":
1021                     keyVml = "strokecolor";
1022                     break;
1023                 case "stroke-width":
1024                     keyVml = "strokeweight";
1025                     break;
1026                 case "stroke-dasharray":
1027                     keyVml = "dashstyle";
1028                     break;
1029             }
1030 
1031             if (keyVml !== "") {
1032                 v = Type.evaluate(val);
1033                 this._setAttr(node, keyVml, v);
1034             }
1035         },
1036 
1037         // Already documented in JXG.AbstractRenderer
1038         display: function (el, val) {
1039             if (el && el.rendNode) {
1040                 el.visPropOld.visible = val;
1041                 if (val) {
1042                     el.rendNode.style.visibility = "inherit";
1043                 } else {
1044                     el.rendNode.style.visibility = "hidden";
1045                 }
1046             }
1047         },
1048 
1049         // Already documented in JXG.AbstractRenderer
1050         show: function (el) {
1051             JXG.deprecated("Board.renderer.show()", "Board.renderer.display()");
1052 
1053             if (el && el.rendNode) {
1054                 el.rendNode.style.visibility = "inherit";
1055             }
1056         },
1057 
1058         // Already documented in JXG.AbstractRenderer
1059         hide: function (el) {
1060             JXG.deprecated("Board.renderer.hide()", "Board.renderer.display()");
1061 
1062             if (el && el.rendNode) {
1063                 el.rendNode.style.visibility = "hidden";
1064             }
1065         },
1066 
1067         // Already documented in JXG.AbstractRenderer
1068         setDashStyle: function (el, visProp) {
1069             var node;
1070             if (visProp.dash >= 0) {
1071                 node = el.rendNodeStroke;
1072                 this._setAttr(node, "dashstyle", this.dashArray[visProp.dash]);
1073             }
1074         },
1075 
1076         // Already documented in JXG.AbstractRenderer
1077         setGradient: function (el) {
1078             var nodeFill = el.rendNodeFill,
1079                 ev_g = Type.evaluate(el.visProp.gradient);
1080 
1081             if (ev_g === "linear") {
1082                 this._setAttr(nodeFill, "type", "gradient");
1083                 this._setAttr(
1084                     nodeFill,
1085                     "color2",
1086                     Type.evaluate(el.visProp.gradientsecondcolor)
1087                 );
1088                 this._setAttr(
1089                     nodeFill,
1090                     "opacity2",
1091                     Type.evaluate(el.visProp.gradientsecondopacity)
1092                 );
1093                 this._setAttr(nodeFill, "angle", Type.evaluate(el.visProp.gradientangle));
1094             } else if (ev_g === "radial") {
1095                 this._setAttr(nodeFill, "type", "gradientradial");
1096                 this._setAttr(
1097                     nodeFill,
1098                     "color2",
1099                     Type.evaluate(el.visProp.gradientsecondcolor)
1100                 );
1101                 this._setAttr(
1102                     nodeFill,
1103                     "opacity2",
1104                     Type.evaluate(el.visProp.gradientsecondopacity)
1105                 );
1106                 this._setAttr(
1107                     nodeFill,
1108                     "focusposition",
1109                     Type.evaluate(el.visProp.gradientpositionx) * 100 +
1110                         "%," +
1111                         Type.evaluate(el.visProp.gradientpositiony) * 100 +
1112                         "%"
1113                 );
1114                 this._setAttr(nodeFill, "focussize", "0,0");
1115             } else {
1116                 this._setAttr(nodeFill, "type", "solid");
1117             }
1118         },
1119 
1120         // Already documented in JXG.AbstractRenderer
1121         setObjectFillColor: function (el, color, opacity) {
1122             var rgba = Type.evaluate(color),
1123                 c,
1124                 rgbo,
1125                 o = Type.evaluate(opacity),
1126                 oo,
1127                 node = el.rendNode;
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                 o = Type.evaluate(opacity),
1183                 oo,
1184                 node = el.rendNode,
1185                 nodeStroke;
1186 
1187             o = o > 0 ? o : 0;
1188 
1189             if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
1190                 return;
1191             }
1192 
1193             // this looks like it could be merged with parts of VMLRenderer.setObjectFillColor
1194 
1195             if (Type.exists(rgba) && rgba !== false) {
1196                 // RGB, not RGBA
1197                 if (rgba.length !== 9) {
1198                     c = rgba;
1199                     oo = o;
1200                     // True RGBA, not RGB
1201                 } else {
1202                     rgbo = color.rgba2rgbo(rgba);
1203                     c = rgbo[0];
1204                     oo = o * rgbo[1];
1205                 }
1206                 if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
1207                     //node.style.filter = ' alpha(opacity = ' + oo + ')';
1208                     /*
1209                     t = node.style.filter.toString();
1210                     if (t.match(/alpha/)) {
1211                         node.style.filter =
1212                         t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + oo + ')');
1213                     } else {
1214                         node.style.filter += ' alpha(opacity = ' + oo + ')';
1215                     }
1216                     */
1217                     if (node.filters.length > 1) {
1218                         // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]?
1219                         // Setting axes:true shows text labels!
1220                         node.filters.item(1).opacity = Math.round(oo * 100);
1221                         node.filters.item(1).enabled = true;
1222                     }
1223 
1224                     node.style.color = c;
1225                 } else {
1226                     if (c !== false) {
1227                         this._setAttr(node, "stroked", "true");
1228                         this._setAttr(node, "strokecolor", c);
1229                     }
1230 
1231                     nodeStroke = el.rendNodeStroke;
1232                     if (Type.exists(oo) && el.type !== Const.OBJECT_TYPE_IMAGE) {
1233                         this._setAttr(nodeStroke, "opacity", oo * 100 + "%");
1234                     }
1235                 }
1236             }
1237             el.visPropOld.strokecolor = rgba;
1238             el.visPropOld.strokeopacity = o;
1239         },
1240 
1241         // Already documented in JXG.AbstractRenderer
1242         setObjectStrokeWidth: function (el, width) {
1243             var w = Type.evaluate(width),
1244                 node;
1245 
1246             if (isNaN(w) || el.visPropOld.strokewidth === w) {
1247                 return;
1248             }
1249 
1250             node = el.rendNode;
1251             this.setPropertyPrim(node, "stroked", "true");
1252 
1253             if (Type.exists(w)) {
1254                 this.setPropertyPrim(node, "stroke-width", w);
1255                 if (w === 0 && Type.exists(el.rendNodeStroke)) {
1256                     this._setAttr(node, "stroked", "false");
1257                 }
1258             }
1259 
1260             el.visPropOld.strokewidth = w;
1261         },
1262 
1263         // Already documented in JXG.AbstractRenderer
1264         setShadow: function (el) {
1265             var nodeShadow = el.rendNodeShadow,
1266                 ev_s = Type.evaluate(el.visProp.shadow);
1267 
1268             if (!nodeShadow || el.visPropOld.shadow === ev_s) {
1269                 return;
1270             }
1271 
1272             if (ev_s) {
1273                 this._setAttr(nodeShadow, "On", "True");
1274                 this._setAttr(nodeShadow, "Offset", "3pt,3pt");
1275                 this._setAttr(nodeShadow, "Opacity", "60%");
1276                 this._setAttr(nodeShadow, "Color", "#aaaaaa");
1277             } else {
1278                 this._setAttr(nodeShadow, "On", "False");
1279             }
1280 
1281             el.visPropOld.shadow = ev_s;
1282         },
1283 
1284         /* **************************
1285          * renderer control
1286          * **************************/
1287 
1288         // Already documented in JXG.AbstractRenderer
1289         suspendRedraw: function () {
1290             this.container.style.display = "none";
1291         },
1292 
1293         // Already documented in JXG.AbstractRenderer
1294         unsuspendRedraw: function () {
1295             this.container.style.display = "";
1296         }
1297     }
1298 );
1299 
1300 export default JXG.VMLRenderer;
1301