      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 === "<>" || 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