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