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