1 /*
  2     Copyright 2008-2023
  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";
 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 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 === "<>") {
670                 s.push(
671                     [
672                         " m ",
673                         mround(r * (scr[1] - size)),
674                         ", ",
675                         mround(r * scr[2]),
676                         " l ",
677                         mround(r * scr[1]),
678                         ", ",
679                         mround(r * (scr[2] + size)),
680                         " l ",
681                         mround(r * (scr[1] + size)),
682                         ", ",
683                         mround(r * scr[2]),
684                         " l ",
685                         mround(r * scr[1]),
686                         ", ",
687                         mround(r * (scr[2] - size)),
688                         " x e "
689                     ].join("")
690                 );
691             } else if (type === "^") {
692                 s.push(
693                     [
694                         " m ",
695                         mround(r * scr[1]),
696                         ", ",
697                         mround(r * (scr[2] - size)),
698                         " l ",
699                         mround(r * (scr[1] - sqrt32)),
700                         ", ",
701                         mround(r * (scr[2] + s05)),
702                         " l ",
703                         mround(r * (scr[1] + sqrt32)),
704                         ", ",
705                         mround(r * (scr[2] + s05)),
706                         " x e "
707                     ].join("")
708                 );
709             } else if (type === "v") {
710                 s.push(
711                     [
712                         " m ",
713                         mround(r * scr[1]),
714                         ", ",
715                         mround(r * (scr[2] + size)),
716                         " l ",
717                         mround(r * (scr[1] - sqrt32)),
718                         ", ",
719                         mround(r * (scr[2] - s05)),
720                         " l ",
721                         mround(r * (scr[1] + sqrt32)),
722                         ", ",
723                         mround(r * (scr[2] - s05)),
724                         " x e "
725                     ].join("")
726                 );
727             } else if (type === ">") {
728                 s.push(
729                     [
730                         " m ",
731                         mround(r * (scr[1] + size)),
732                         ", ",
733                         mround(r * scr[2]),
734                         " l ",
735                         mround(r * (scr[1] - s05)),
736                         ", ",
737                         mround(r * (scr[2] - sqrt32)),
738                         " l ",
739                         mround(r * (scr[1] - s05)),
740                         ", ",
741                         mround(r * (scr[2] + sqrt32)),
742                         " l ",
743                         mround(r * (scr[1] + size)),
744                         ", ",
745                         mround(r * scr[2])
746                     ].join("")
747                 );
748             } else if (type === "<") {
749                 s.push(
750                     [
751                         " m ",
752                         mround(r * (scr[1] - size)),
753                         ", ",
754                         mround(r * scr[2]),
755                         " l ",
756                         mround(r * (scr[1] + s05)),
757                         ", ",
758                         mround(r * (scr[2] - sqrt32)),
759                         " l ",
760                         mround(r * (scr[1] + s05)),
761                         ", ",
762                         mround(r * (scr[2] + sqrt32)),
763                         " x e "
764                     ].join("")
765                 );
766             }
767 
768             return s;
769         },
770 
771         // Already documented in JXG.AbstractRenderer
772         updatePathStringPrim: function (el) {
773             var i,
774                 scr,
775                 pStr = [],
776                 r = this.resolution,
777                 mround = Math.round,
778                 symbm = " m ",
779                 symbl = " l ",
780                 symbc = " c ",
781                 nextSymb = symbm,
782                 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html
783 
784             if (el.numberPoints <= 0) {
785                 return "";
786             }
787             len = Math.min(len, el.points.length);
788 
789             if (el.bezierDegree === 1) {
790                 /*
791                 if (isNotPlot && el.board.options.curve.RDPsmoothing) {
792                     el.points = Numerics.RamerDouglasPeucker(el.points, 1.0);
793                 }
794                 */
795 
796                 for (i = 0; i < len; i++) {
797                     scr = el.points[i].scrCoords;
798                     if (isNaN(scr[1]) || isNaN(scr[2])) {
799                         // PenUp
800                         nextSymb = symbm;
801                     } else {
802                         // IE has problems with values  being too far away.
803                         if (scr[1] > 20000.0) {
804                             scr[1] = 20000.0;
805                         } else if (scr[1] < -20000.0) {
806                             scr[1] = -20000.0;
807                         }
808 
809                         if (scr[2] > 20000.0) {
810                             scr[2] = 20000.0;
811                         } else if (scr[2] < -20000.0) {
812                             scr[2] = -20000.0;
813                         }
814 
815                         pStr.push(
816                             [nextSymb, mround(r * scr[1]), ", ", mround(r * scr[2])].join("")
817                         );
818                         nextSymb = symbl;
819                     }
820                 }
821             } else if (el.bezierDegree === 3) {
822                 i = 0;
823                 while (i < len) {
824                     scr = el.points[i].scrCoords;
825                     if (isNaN(scr[1]) || isNaN(scr[2])) {
826                         // PenUp
827                         nextSymb = symbm;
828                     } else {
829                         pStr.push(
830                             [nextSymb, mround(r * scr[1]), ", ", mround(r * scr[2])].join("")
831                         );
832                         if (nextSymb === symbc) {
833                             i += 1;
834                             scr = el.points[i].scrCoords;
835                             pStr.push(
836                                 [" ", mround(r * scr[1]), ", ", mround(r * scr[2])].join("")
837                             );
838                             i += 1;
839                             scr = el.points[i].scrCoords;
840                             pStr.push(
841                                 [" ", mround(r * scr[1]), ", ", mround(r * scr[2])].join("")
842                             );
843                         }
844                         nextSymb = symbc;
845                     }
846                     i += 1;
847                 }
848             }
849             pStr.push(" e");
850             return pStr;
851         },
852 
853         // Already documented in JXG.AbstractRenderer
854         updatePathStringBezierPrim: function (el) {
855             var i,
856                 j,
857                 k,
858                 scr,
859                 lx,
860                 ly,
861                 pStr = [],
862                 f = Type.evaluate(el.visProp.strokewidth),
863                 r = this.resolution,
864                 mround = Math.round,
865                 symbm = " m ",
866                 symbl = " c ",
867                 nextSymb = symbm,
868                 isNoPlot = Type.evaluate(el.visProp.curvetype) !== "plot",
869                 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html
870 
871             if (el.numberPoints <= 0) {
872                 return "";
873             }
874             if (isNoPlot && el.board.options.curve.RDPsmoothing) {
875                 el.points = Numerics.RamerDouglasPeucker(el.points, 1.0);
876             }
877             len = Math.min(len, el.points.length);
878 
879             for (j = 1; j < 3; j++) {
880                 nextSymb = symbm;
881                 for (i = 0; i < len; i++) {
882                     scr = el.points[i].scrCoords;
883                     if (isNaN(scr[1]) || isNaN(scr[2])) {
884                         // PenUp
885                         nextSymb = symbm;
886                     } else {
887                         // IE has problems with values  being too far away.
888                         if (scr[1] > 20000.0) {
889                             scr[1] = 20000.0;
890                         } else if (scr[1] < -20000.0) {
891                             scr[1] = -20000.0;
892                         }
893 
894                         if (scr[2] > 20000.0) {
895                             scr[2] = 20000.0;
896                         } else if (scr[2] < -20000.0) {
897                             scr[2] = -20000.0;
898                         }
899 
900                         if (nextSymb === symbm) {
901                             pStr.push(
902                                 [nextSymb, mround(r * scr[1]), " ", mround(r * scr[2])].join("")
903                             );
904                         } else {
905                             k = 2 * j;
906                             pStr.push(
907                                 [
908                                     nextSymb,
909                                     mround(
910                                         r *
911                                             (lx +
912                                                 (scr[1] - lx) * 0.333 +
913                                                 f * (k * Math.random() - j))
914                                     ),
915                                     " ",
916                                     mround(
917                                         r *
918                                             (ly +
919                                                 (scr[2] - ly) * 0.333 +
920                                                 f * (k * Math.random() - j))
921                                     ),
922                                     " ",
923                                     mround(
924                                         r *
925                                             (lx +
926                                                 (scr[1] - lx) * 0.666 +
927                                                 f * (k * Math.random() - j))
928                                     ),
929                                     " ",
930                                     mround(
931                                         r *
932                                             (ly +
933                                                 (scr[2] - ly) * 0.666 +
934                                                 f * (k * Math.random() - j))
935                                     ),
936                                     " ",
937                                     mround(r * scr[1]),
938                                     " ",
939                                     mround(r * scr[2])
940                                 ].join("")
941                             );
942                         }
943                         nextSymb = symbl;
944                         lx = scr[1];
945                         ly = scr[2];
946                     }
947                 }
948             }
949             pStr.push(" e");
950             return pStr;
951         },
952 
953         // Already documented in JXG.AbstractRenderer
954         updatePolygonPrim: function (node, el) {
955             var i,
956                 len = el.vertices.length,
957                 r = this.resolution,
958                 scr,
959                 pStr = [];
960 
961             this._setAttr(node, "stroked", "false");
962             scr = el.vertices[0].coords.scrCoords;
963 
964             if (isNaN(scr[1] + scr[2])) {
965                 return;
966             }
967 
968             pStr.push(
969                 ["m ", Math.floor(r * scr[1]), ",", Math.floor(r * scr[2]), " l "].join("")
970             );
971 
972             for (i = 1; i < len - 1; i++) {
973                 if (el.vertices[i].isReal) {
974                     scr = el.vertices[i].coords.scrCoords;
975 
976                     if (isNaN(scr[1] + scr[2])) {
977                         return;
978                     }
979 
980                     pStr.push(Math.floor(r * scr[1]) + "," + Math.floor(r * scr[2]));
981                 } else {
982                     this.updatePathPrim(node, "", el.board);
983                     return;
984                 }
985                 if (i < len - 2) {
986                     pStr.push(", ");
987                 }
988             }
989             pStr.push(" x e");
990             this.updatePathPrim(node, pStr, el.board);
991         },
992 
993         // Already documented in JXG.AbstractRenderer
994         updateRectPrim: function (node, x, y, w, h) {
995             node.style.left = Math.floor(x) + "px";
996             node.style.top = Math.floor(y) + "px";
997 
998             if (w >= 0) {
999                 node.style.width = w + "px";
1000             }
1001 
1002             if (h >= 0) {
1003                 node.style.height = h + "px";
1004             }
1005         },
1006 
1007         /* **************************
1008          *  Set Attributes
1009          * **************************/
1010 
1011         // Already documented in JXG.AbstractRenderer
1012         setPropertyPrim: function (node, key, val) {
1013             var keyVml = "",
1014                 v;
1015 
1016             switch (key) {
1017                 case "stroke":
1018                     keyVml = "strokecolor";
1019                     break;
1020                 case "stroke-width":
1021                     keyVml = "strokeweight";
1022                     break;
1023                 case "stroke-dasharray":
1024                     keyVml = "dashstyle";
1025                     break;
1026             }
1027 
1028             if (keyVml !== "") {
1029                 v = Type.evaluate(val);
1030                 this._setAttr(node, keyVml, v);
1031             }
1032         },
1033 
1034         // Already documented in JXG.AbstractRenderer
1035         display: function (el, val) {
1036             if (el && el.rendNode) {
1037                 el.visPropOld.visible = val;
1038                 if (val) {
1039                     el.rendNode.style.visibility = "inherit";
1040                 } else {
1041                     el.rendNode.style.visibility = "hidden";
1042                 }
1043             }
1044         },
1045 
1046         // Already documented in JXG.AbstractRenderer
1047         show: function (el) {
1048             JXG.deprecated("Board.renderer.show()", "Board.renderer.display()");
1049 
1050             if (el && el.rendNode) {
1051                 el.rendNode.style.visibility = "inherit";
1052             }
1053         },
1054 
1055         // Already documented in JXG.AbstractRenderer
1056         hide: function (el) {
1057             JXG.deprecated("Board.renderer.hide()", "Board.renderer.display()");
1058 
1059             if (el && el.rendNode) {
1060                 el.rendNode.style.visibility = "hidden";
1061             }
1062         },
1063 
1064         // Already documented in JXG.AbstractRenderer
1065         setDashStyle: function (el, visProp) {
1066             var node;
1067             if (visProp.dash >= 0) {
1068                 node = el.rendNodeStroke;
1069                 this._setAttr(node, "dashstyle", this.dashArray[visProp.dash]);
1070             }
1071         },
1072 
1073         // Already documented in JXG.AbstractRenderer
1074         setGradient: function (el) {
1075             var nodeFill = el.rendNodeFill,
1076                 ev_g = Type.evaluate(el.visProp.gradient);
1077 
1078             if (ev_g === "linear") {
1079                 this._setAttr(nodeFill, "type", "gradient");
1080                 this._setAttr(
1081                     nodeFill,
1082                     "color2",
1083                     Type.evaluate(el.visProp.gradientsecondcolor)
1084                 );
1085                 this._setAttr(
1086                     nodeFill,
1087                     "opacity2",
1088                     Type.evaluate(el.visProp.gradientsecondopacity)
1089                 );
1090                 this._setAttr(nodeFill, "angle", Type.evaluate(el.visProp.gradientangle));
1091             } else if (ev_g === "radial") {
1092                 this._setAttr(nodeFill, "type", "gradientradial");
1093                 this._setAttr(
1094                     nodeFill,
1095                     "color2",
1096                     Type.evaluate(el.visProp.gradientsecondcolor)
1097                 );
1098                 this._setAttr(
1099                     nodeFill,
1100                     "opacity2",
1101                     Type.evaluate(el.visProp.gradientsecondopacity)
1102                 );
1103                 this._setAttr(
1104                     nodeFill,
1105                     "focusposition",
1106                     Type.evaluate(el.visProp.gradientpositionx) * 100 +
1107                         "%," +
1108                         Type.evaluate(el.visProp.gradientpositiony) * 100 +
1109                         "%"
1110                 );
1111                 this._setAttr(nodeFill, "focussize", "0,0");
1112             } else {
1113                 this._setAttr(nodeFill, "type", "solid");
1114             }
1115         },
1116 
1117         // Already documented in JXG.AbstractRenderer
1118         setObjectFillColor: function (el, color, opacity) {
1119             var rgba = Type.evaluate(color),
1120                 c,
1121                 rgbo,
1122                 o = Type.evaluate(opacity),
1123                 oo,
1124                 node = el.rendNode;
1125 
1126             o = o > 0 ? o : 0;
1127 
1128             if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) {
1129                 return;
1130             }
1131 
1132             if (Type.exists(rgba) && rgba !== false) {
1133                 // RGB, not RGBA
1134                 if (rgba.length !== 9) {
1135                     c = rgba;
1136                     oo = o;
1137                     // True RGBA, not RGB
1138                 } else {
1139                     rgbo = Color.rgba2rgbo(rgba);
1140                     c = rgbo[0];
1141                     oo = o * rgbo[1];
1142                 }
1143                 if (c === "none" || c === false) {
1144                     this._setAttr(el.rendNode, "filled", "false");
1145                 } else {
1146                     this._setAttr(el.rendNode, "filled", "true");
1147                     this._setAttr(el.rendNode, "fillcolor", c);
1148 
1149                     if (Type.exists(oo) && el.rendNodeFill) {
1150                         this._setAttr(el.rendNodeFill, "opacity", oo * 100 + "%");
1151                     }
1152                 }
1153                 if (el.type === Const.OBJECT_TYPE_IMAGE) {
1154                     /*
1155                     t = el.rendNode.style.filter.toString();
1156                     if (t.match(/alpha/)) {
1157                         el.rendNode.style.filter = t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + (oo * 100) + ')');
1158                     } else {
1159                         el.rendNode.style.filter += ' alpha(opacity = ' + (oo * 100) + ')';
1160                     }
1161                     */
1162                     if (node.filters.length > 1) {
1163                         // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]?
1164                         // Setting axes:true shows text labels!
1165                         node.filters.item(1).opacity = Math.round(oo * 100); // Why does setObjectFillColor not use Math.round?
1166                         node.filters.item(1).enabled = true;
1167                     }
1168                 }
1169             }
1170             el.visPropOld.fillcolor = rgba;
1171             el.visPropOld.fillopacity = o;
1172         },
1173 
1174         // Already documented in JXG.AbstractRenderer
1175         setObjectStrokeColor: function (el, color, opacity) {
1176             var rgba = Type.evaluate(color),
1177                 c,
1178                 rgbo,
1179                 o = Type.evaluate(opacity),
1180                 oo,
1181                 node = el.rendNode,
1182                 nodeStroke;
1183 
1184             o = o > 0 ? o : 0;
1185 
1186             if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
1187                 return;
1188             }
1189 
1190             // this looks like it could be merged with parts of VMLRenderer.setObjectFillColor
1191 
1192             if (Type.exists(rgba) && rgba !== false) {
1193                 // RGB, not RGBA
1194                 if (rgba.length !== 9) {
1195                     c = rgba;
1196                     oo = o;
1197                     // True RGBA, not RGB
1198                 } else {
1199                     rgbo = color.rgba2rgbo(rgba);
1200                     c = rgbo[0];
1201                     oo = o * rgbo[1];
1202                 }
1203                 if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
1204                     //node.style.filter = ' alpha(opacity = ' + oo + ')';
1205                     /*
1206                     t = node.style.filter.toString();
1207                     if (t.match(/alpha/)) {
1208                         node.style.filter =
1209                         t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + oo + ')');
1210                     } else {
1211                         node.style.filter += ' alpha(opacity = ' + oo + ')';
1212                     }
1213                     */
1214                     if (node.filters.length > 1) {
1215                         // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]?
1216                         // Setting axes:true shows text labels!
1217                         node.filters.item(1).opacity = Math.round(oo * 100);
1218                         node.filters.item(1).enabled = true;
1219                     }
1220 
1221                     node.style.color = c;
1222                 } else {
1223                     if (c !== false) {
1224                         this._setAttr(node, "stroked", "true");
1225                         this._setAttr(node, "strokecolor", c);
1226                     }
1227 
1228                     nodeStroke = el.rendNodeStroke;
1229                     if (Type.exists(oo) && el.type !== Const.OBJECT_TYPE_IMAGE) {
1230                         this._setAttr(nodeStroke, "opacity", oo * 100 + "%");
1231                     }
1232                 }
1233             }
1234             el.visPropOld.strokecolor = rgba;
1235             el.visPropOld.strokeopacity = o;
1236         },
1237 
1238         // Already documented in JXG.AbstractRenderer
1239         setObjectStrokeWidth: function (el, width) {
1240             var w = Type.evaluate(width),
1241                 node;
1242 
1243             if (isNaN(w) || el.visPropOld.strokewidth === w) {
1244                 return;
1245             }
1246 
1247             node = el.rendNode;
1248             this.setPropertyPrim(node, "stroked", "true");
1249 
1250             if (Type.exists(w)) {
1251                 this.setPropertyPrim(node, "stroke-width", w);
1252                 if (w === 0 && Type.exists(el.rendNodeStroke)) {
1253                     this._setAttr(node, "stroked", "false");
1254                 }
1255             }
1256 
1257             el.visPropOld.strokewidth = w;
1258         },
1259 
1260         // Already documented in JXG.AbstractRenderer
1261         setShadow: function (el) {
1262             var nodeShadow = el.rendNodeShadow,
1263                 ev_s = Type.evaluate(el.visProp.shadow);
1264 
1265             if (!nodeShadow || el.visPropOld.shadow === ev_s) {
1266                 return;
1267             }
1268 
1269             if (ev_s) {
1270                 this._setAttr(nodeShadow, "On", "True");
1271                 this._setAttr(nodeShadow, "Offset", "3pt,3pt");
1272                 this._setAttr(nodeShadow, "Opacity", "60%");
1273                 this._setAttr(nodeShadow, "Color", "#aaaaaa");
1274             } else {
1275                 this._setAttr(nodeShadow, "On", "False");
1276             }
1277 
1278             el.visPropOld.shadow = ev_s;
1279         },
1280 
1281         /* **************************
1282          * renderer control
1283          * **************************/
1284 
1285         // Already documented in JXG.AbstractRenderer
1286         suspendRedraw: function () {
1287             this.container.style.display = "none";
1288         },
1289 
1290         // Already documented in JXG.AbstractRenderer
1291         unsuspendRedraw: function () {
1292             this.container.style.display = "";
1293         }
1294     }
1295 );
1296 
1297 export default JXG.VMLRenderer;
1298