      1 /*
      2     Copyright 2008-2015
      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 (typeof x[0] === 'number' && typeof x[1] === 'number') {
    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, ticks.visProp.layer);
    155             }
    156
    157             this._setAttr(ticks.rendNode, 'stroked', 'true');
    158             this._setAttr(ticks.rendNode, 'strokecolor', ticks.visProp.strokecolor, 1);
    159             this._setAttr(ticks.rendNode, 'strokeweight', ticks.visProp.strokewidth);
    160             this._setAttr(ticks.rendNodeStroke, 'opacity', (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             /*
    195              if (this.container.ownerDocument.documentMode === 8) {                 // IE 8
    196              node.setAttribute('class', el.visProp.cssclass);
    197              } else {
    198              node.setAttribute(this.container.ownerDocument.all ? 'className' : 'class', el.visProp.cssclass);
    199              }
    200              */
    201             el.rendNodeText = this.container.ownerDocument.createTextNode('');
    202             node.appendChild(el.rendNodeText);
    203             this.appendChildPrim(node, 9);
    204             node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand', enabled = false) progid:DXImageTransform.Microsoft.Alpha(opacity = 100, enabled = false)";
    205
    206             return node;
    207         },
    208
    209         // documented in AbstractRenderer
    210         updateInternalText: function (el) {
    211             var v, content = el.plaintext,
    212                 m = this.joinTransforms(el, el.transformations),
    213                 offset = [0, 0],
    214                 maxX, maxY, minX, minY, i,
    215                 node = el.rendNode,
    216                 p = [];
    217
    218             if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) {
    219                 // Horizontal
    220                 if (el.visProp.anchorx === 'right') {
    221                     offset[0] = 1;
    222                 } else if (el.visProp.anchorx === 'middle') {
    223                     offset[0] = 0.5;
    224                 } // default (el.visProp.anchorx === 'left') offset[0] = 0;
    225
    226                 // Vertical
    227                 if (el.visProp.anchory === 'bottom') {
    228                     offset[1] = 1;
    229                 } else if (el.visProp.anchory === 'middle') {
    230                     offset[1] = 0.5;
    231                 } // default (el.visProp.anchory === 'top') offset[1] = 0;
    232
    233                 // Compute maxX, maxY, minX, minY
    234                 p[0] = Mat.matVecMult(m, [1,
    235                                           el.coords.scrCoords[1] - offset[0] * el.size[0],
    236                                           el.coords.scrCoords[2] + (1 - offset[1]) * el.size[1] + this.vOffsetText]);
    237                 p[0][1] /= p[0][0];
    238                 p[0][2] /= p[0][0];
    239                 p[1] = Mat.matVecMult(m, [1,
    240                                           el.coords.scrCoords[1] + (1 - offset[0]) * el.size[0],
    241                                           el.coords.scrCoords[2] + (1 - offset[1]) * el.size[1] + this.vOffsetText]);
    242                 p[1][1] /= p[1][0];
    243                 p[1][2] /= p[1][0];
    244                 p[2] = Mat.matVecMult(m, [1,
    245                                           el.coords.scrCoords[1] + (1 - offset[0]) * el.size[0],
    246                                           el.coords.scrCoords[2] - offset[1] * el.size[1] + this.vOffsetText]);
    247                 p[2][1] /= p[2][0];
    248                 p[2][2] /= p[2][0];
    249                 p[3] = Mat.matVecMult(m, [1,
    250                                           el.coords.scrCoords[1] - offset[0] * el.size[0],
    251                                           el.coords.scrCoords[2] - offset[1] * el.size[1] + this.vOffsetText]);
    252                 p[3][1] /= p[3][0];
    253                 p[3][2] /= p[3][0];
    254                 maxX = p[0][1];
    255                 minX = p[0][1];
    256                 maxY = p[0][2];
    257                 minY = p[0][2];
    258
    259                 for (i = 1; i < 4; i++) {
    260                     maxX = Math.max(maxX, p[i][1]);
    261                     minX = Math.min(minX, p[i][1]);
    262                     maxY = Math.max(maxY, p[i][2]);
    263                     minY = Math.min(minY, p[i][2]);
    264                 }
    265
    266                 // Horizontal
    267                 v = offset[0] === 1 ? Math.floor(el.board.canvasWidth - maxX) : Math.floor(minX);
    268                 if (el.visPropOld.left !== (el.visProp.anchorx + v)) {
    269                     if (offset[0] === 1) {
    270                         el.rendNode.style.right = v + 'px';
    271                         el.rendNode.style.left = 'auto';
    272                     } else {
    273                         el.rendNode.style.left = v + 'px';
    274                         el.rendNode.style.right = 'auto';
    275                     }
    276                     el.visPropOld.left = el.visProp.anchorx + v;
    277                 }
    278
    279                 // Vertical
    280                 v = offset[1] === 1 ? Math.floor(el.board.canvasHeight - maxY) : Math.floor(minY);
    281                 if (el.visPropOld.top !== (el.visProp.anchory + v)) {
    282                     if (offset[1] === 1) {
    283                         el.rendNode.style.bottom = v + 'px';
    284                         el.rendNode.style.top = 'auto';
    285                     } else {
    286                         el.rendNode.style.top = v + 'px';
    287                         el.rendNode.style.bottom = 'auto';
    288                     }
    289                     el.visPropOld.top = el.visProp.anchory + v;
    290                 }
    291
    292             }
    293
    294             if (el.htmlStr !== content) {
    295                 el.rendNodeText.data = content;
    296                 el.htmlStr = content;
    297             }
    298
    299             //this.transformImage(el, el.transformations);
    300             node.filters.item(0).M11 = m[1][1];
    301             node.filters.item(0).M12 = m[1][2];
    302             node.filters.item(0).M21 = m[2][1];
    303             node.filters.item(0).M22 = m[2][2];
    304             node.filters.item(0).enabled = true;
    305         },
    306
    307         /* **************************
    308          *    Image related stuff
    309          * **************************/
    310
    311         // already documented in JXG.AbstractRenderer
    312         drawImage: function (el) {
    313             // IE 8: Bilder ueber data URIs werden bis 32kB unterstuetzt.
    314             var node;
    315
    316             node = this.container.ownerDocument.createElement('img');
    317             node.style.position = 'absolute';
    318             this._setAttr(node, 'id', this.container.id + '_' + el.id);
    319
    320             this.container.appendChild(node);
    321             this.appendChildPrim(node, el.visProp.layer);
    322
    323             // Adding the rotation filter. This is always filter item 0:
    324             // node.filters.item(0), see transformImage
    325             // Also add the alpha filter. This is always filter item 1
    326             // node.filters.item(1), see setObjectFillColor and setObjectSTrokeColor
    327             //node.style.filter = node.style['-ms-filter'] = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand')";
    328             node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand') progid:DXImageTransform.Microsoft.Alpha(opacity = 100, enabled = false)";
    329             el.rendNode = node;
    330             this.updateImage(el);
    331         },
    332
    333         // already documented in JXG.AbstractRenderer
    334         transformImage: function (el, t) {
    335             var m, s, maxX, maxY, minX, minY, i, nt,
    336                 node = el.rendNode,
    337                 p = [],
    338                 len = t.length;
    339
    340             if (len > 0) {
    341                 /*
    342                 nt = el.rendNode.style.filter.toString();
    343                 if (!nt.match(/DXImageTransform/)) {
    344                     node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand') " + nt;
    345                 }
    346                 */
    347
    348                 m = this.joinTransforms(el, t);
    349                 p[0] = Mat.matVecMult(m, el.coords.scrCoords);
    350                 p[0][1] /= p[0][0];
    351                 p[0][2] /= p[0][0];
    352                 p[1] = Mat.matVecMult(m, [1, el.coords.scrCoords[1] + el.size[0], el.coords.scrCoords[2]]);
    353                 p[1][1] /= p[1][0];
    354                 p[1][2] /= p[1][0];
    355                 p[2] = Mat.matVecMult(m, [1, el.coords.scrCoords[1] + el.size[0], el.coords.scrCoords[2] - el.size[1]]);
    356                 p[2][1] /= p[2][0];
    357                 p[2][2] /= p[2][0];
    358                 p[3] = Mat.matVecMult(m, [1, el.coords.scrCoords[1], el.coords.scrCoords[2] - el.size[1]]);
    359                 p[3][1] /= p[3][0];
    360                 p[3][2] /= p[3][0];
    361                 maxX = p[0][1];
    362                 minX = p[0][1];
    363                 maxY = p[0][2];
    364                 minY = p[0][2];
    365
    366                 for (i = 1; i < 4; i++) {
    367                     maxX = Math.max(maxX, p[i][1]);
    368                     minX = Math.min(minX, p[i][1]);
    369                     maxY = Math.max(maxY, p[i][2]);
    370                     minY = Math.min(minY, p[i][2]);
    371                 }
    372                 node.style.left = Math.floor(minX) + 'px';
    373                 node.style.top  = Math.floor(minY) + 'px';
    374
    375                 node.filters.item(0).M11 = m[1][1];
    376                 node.filters.item(0).M12 = m[1][2];
    377                 node.filters.item(0).M21 = m[2][1];
    378                 node.filters.item(0).M22 = m[2][2];
    379                 node.filters.item(0).enabled = true;
    380             }
    381         },
    382
    383         // already documented in JXG.AbstractRenderer
    384         updateImageURL: function (el) {
    385             var url = Type.evaluate(el.url);
    386
    387             this._setAttr(el.rendNode, 'src', url);
    388         },
    389
    390         /* **************************
    391          * Render primitive objects
    392          * **************************/
    393
    394         // already documented in JXG.AbstractRenderer
    395         appendChildPrim: function (node, level) {
    396             // For trace nodes
    397             if (!Type.exists(level)) {
    398                 level = 0;
    399             }
    400
    401             node.style.zIndex = level;
    402             this.container.appendChild(node);
    403
    404             return node;
    405         },
    406
    407         // already documented in JXG.AbstractRenderer
    408         appendNodesToElement: function (element, type) {
    409             if (type === 'shape' || type === 'path' || type === 'polygon') {
    410                 element.rendNodePath = this.getElementById(element.id + '_path');
    411             }
    412             element.rendNodeFill = this.getElementById(element.id + '_fill');
    413             element.rendNodeStroke = this.getElementById(element.id + '_stroke');
    414             element.rendNodeShadow = this.getElementById(element.id + '_shadow');
    415             element.rendNode = this.getElementById(element.id);
    416         },
    417
    418         // already documented in JXG.AbstractRenderer
    419         createPrim: function (type, id) {
    420             var node, pathNode,
    421                 fillNode = this.createNode('fill'),
    422                 strokeNode = this.createNode('stroke'),
    423                 shadowNode = this.createNode('shadow');
    424
    425             this._setAttr(fillNode, 'id', this.container.id + '_' + id + '_fill');
    426             this._setAttr(strokeNode, 'id', this.container.id + '_' + id + '_stroke');
    427             this._setAttr(shadowNode, 'id', this.container.id + '_' + id + '_shadow');
    428
    429             if (type === 'circle' || type === 'ellipse') {
    430                 node = this.createNode('oval');
    431                 node.appendChild(fillNode);
    432                 node.appendChild(strokeNode);
    433                 node.appendChild(shadowNode);
    434             } else if (type === 'polygon' || type === 'path' || type === 'shape' || type === 'line') {
    435                 node = this.createNode('shape');
    436                 node.appendChild(fillNode);
    437                 node.appendChild(strokeNode);
    438                 node.appendChild(shadowNode);
    439                 pathNode = this.createNode('path');
    440                 this._setAttr(pathNode, 'id', this.container.id + '_' + id + '_path');
    441                 node.appendChild(pathNode);
    442             } else {
    443                 node = this.createNode(type);
    444                 node.appendChild(fillNode);
    445                 node.appendChild(strokeNode);
    446                 node.appendChild(shadowNode);
    447             }
    448
    449             node.style.position = 'absolute';
    450             node.style.left = '0px';
    451             node.style.top = '0px';
    452             this._setAttr(node, 'id', this.container.id + '_' + id);
    453
    454             return node;
    455         },
    456
    457         // already documented in JXG.AbstractRenderer
    458         remove: function (node) {
    459             if (Type.exists(node)) {
    460                 node.removeNode(true);
    461             }
    462         },
    463
    464         // already documented in JXG.AbstractRenderer
    465         makeArrows: function (el) {
    466             var nodeStroke;
    467
    468             if (el.visPropOld.firstarrow === el.visProp.firstarrow && el.visPropOld.lastarrow === el.visProp.lastarrow) {
    469                 return;
    470             }
    471
    472             if (el.visProp.firstarrow) {
    473                 nodeStroke = el.rendNodeStroke;
    474                 this._setAttr(nodeStroke, 'startarrow', 'block');
    475                 this._setAttr(nodeStroke, 'startarrowlength', 'long');
    476             } else {
    477                 nodeStroke = el.rendNodeStroke;
    478                 if (Type.exists(nodeStroke)) {
    479                     this._setAttr(nodeStroke, 'startarrow', 'none');
    480                 }
    481             }
    482
    483             if (el.visProp.lastarrow) {
    484                 nodeStroke = el.rendNodeStroke;
    485                 this._setAttr(nodeStroke, 'id', this.container.id + '_' + el.id + "stroke");
    486                 this._setAttr(nodeStroke, 'endarrow', 'block');
    487                 this._setAttr(nodeStroke, 'endarrowlength', 'long');
    488             } else {
    489                 nodeStroke = el.rendNodeStroke;
    490                 if (Type.exists(nodeStroke)) {
    491                     this._setAttr(nodeStroke, 'endarrow', 'none');
    492                 }
    493             }
    494             el.visPropOld.firstarrow = el.visProp.firstarrow;
    495             el.visPropOld.lastarrow = el.visProp.lastarrow;
    496         },
    497
    498         // already documented in JXG.AbstractRenderer
    499         updateEllipsePrim: function (node, x, y, rx, ry) {
    500             node.style.left = Math.floor(x - rx) + 'px';
    501             node.style.top =  Math.floor(y - ry) + 'px';
    502             node.style.width = Math.floor(Math.abs(rx) * 2) + 'px';
    503             node.style.height = Math.floor(Math.abs(ry) * 2) + 'px';
    504         },
    505
    506         // already documented in JXG.AbstractRenderer
    507         updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) {
    508             var s, r = this.resolution;
    509
    510             if (!isNaN(p1x + p1y + p2x + p2y)) {
    511                 s = ['m ', Math.floor(r * p1x), ', ', Math.floor(r * p1y), ' l ', Math.floor(r * p2x), ', ', Math.floor(r * p2y)];
    512                 this.updatePathPrim(node, s, board);
    513             }
    514         },
    515
    516         // already documented in JXG.AbstractRenderer
    517         updatePathPrim: function (node, pointString, board) {
    518             var x = board.canvasWidth,
    519                 y = board.canvasHeight;
    520             if (pointString.length <= 0) {
    521                 pointString = ['m 0,0'];
    522             }
    523             node.style.width = x;
    524             node.style.height = y;
    525             this._setAttr(node, 'coordsize', [Math.floor(this.resolution * x), Math.floor(this.resolution * y)].join(','));
    526             this._setAttr(node, 'path', pointString.join(""));
    527         },
    528
    529         // already documented in JXG.AbstractRenderer
    530         updatePathStringPoint: function (el, size, type) {
    531             var s = [],
    532                 mround = Math.round,
    533                 scr = el.coords.scrCoords,
    534                 sqrt32 = size * Math.sqrt(3) * 0.5,
    535                 s05 = size * 0.5,
    536                 r = this.resolution;
    537
    538             if (type === 'x') {
    539                 s.push([
    540                     ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2] - size)),
    541                     ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2] + size)),
    542                     ' m ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2] - size)),
    543                     ' l ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2] + size))
    544                 ].join(''));
    545             } else if (type === '+') {
    546                 s.push([
    547                     ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])),
    548                     ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])),
    549                     ' m ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] - size)),
    550                     ' l ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] + size))
    551                 ].join(''));
    552             } else if (type === '<>') {
    553
    554                 s.push([
    555                     ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])),
    556                     ' l ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] + size)),
    557                     ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])),
    558                     ' l ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] - size)),
    559                     ' x e '
    560                 ].join(''));
    561             } else if (type === '^') {
    562                 s.push([
    563                     ' m ', mround(r * (scr[1])),          ', ', mround(r * (scr[2] - size)),
    564                     ' l ', mround(r * (scr[1] - sqrt32)), ', ', mround(r * (scr[2] + s05)),
    565                     ' l ', mround(r * (scr[1] + sqrt32)), ', ', mround(r * (scr[2] + s05)),
    566                     ' x e '
    567                 ].join(''));
    568             } else if (type === 'v') {
    569                 s.push([
    570                     ' m ', mround(r * (scr[1])),          ', ', mround(r * (scr[2] + size)),
    571                     ' l ', mround(r * (scr[1] - sqrt32)), ', ', mround(r * (scr[2] - s05)),
    572                     ' l ', mround(r * (scr[1] + sqrt32)), ', ', mround(r * (scr[2] - s05)),
    573                     ' x e '
    574                 ].join(''));
    575             } else if (type === '>') {
    576                 s.push([
    577                     ' m ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])),
    578                     ' l ', mround(r * (scr[1] - s05)),  ', ', mround(r * (scr[2] - sqrt32)),
    579                     ' l ', mround(r * (scr[1] - s05)),  ', ', mround(r * (scr[2] + sqrt32)),
    580                     ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2]))
    581                 ].join(''));
    582             } else if (type === '<') {
    583                 s.push([
    584                     ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])),
    585                     ' l ', mround(r * (scr[1] + s05)),  ', ', mround(r * (scr[2] - sqrt32)),
    586                     ' l ', mround(r * (scr[1] + s05)),  ', ', mround(r * (scr[2] + sqrt32)),
    587                     ' x e '
    588                 ].join(''));
    589             }
    590
    591             return s;
    592         },
    593
    594         // already documented in JXG.AbstractRenderer
    595         updatePathStringPrim: function (el) {
    596             var i, scr,
    597                 pStr = [],
    598                 r = this.resolution,
    599                 mround = Math.round,
    600                 symbm = ' m ',
    601                 symbl = ' l ',
    602                 symbc = ' c ',
    603                 nextSymb = symbm,
    604                 // isNotPlot = (el.visProp.curvetype !== 'plot'),
    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 = el.visProp.strokewidth,
    671                 r = this.resolution,
    672                 mround = Math.round,
    673                 symbm = ' m ',
    674                 symbl = ' c ',
    675                 nextSymb = symbm,
    676                 isNoPlot = (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         show: function (el) {
    811             if (el && el.rendNode) {
    812                 el.rendNode.style.visibility = "inherit";
    813             }
    814         },
    815
    816         // already documented in JXG.AbstractRenderer
    817         hide: function (el) {
    818             if (el && el.rendNode) {
    819                 el.rendNode.style.visibility = "hidden";
    820             }
    821         },
    822
    823         // already documented in JXG.AbstractRenderer
    824         setDashStyle: function (el, visProp) {
    825             var node;
    826             if (visProp.dash >= 0) {
    827                 node = el.rendNodeStroke;
    828                 this._setAttr(node, 'dashstyle', this.dashArray[visProp.dash]);
    829             }
    830         },
    831
    832         // already documented in JXG.AbstractRenderer
    833         setGradient: function (el) {
    834             var nodeFill = el.rendNodeFill;
    835
    836             if (el.visProp.gradient === 'linear') {
    837                 this._setAttr(nodeFill, 'type', 'gradient');
    838                 this._setAttr(nodeFill, 'color2', el.visProp.gradientsecondcolor);
    839                 this._setAttr(nodeFill, 'opacity2', el.visProp.gradientsecondopacity);
    840                 this._setAttr(nodeFill, 'angle', el.visProp.gradientangle);
    841             } else if (el.visProp.gradient === 'radial') {
    842                 this._setAttr(nodeFill, 'type', 'gradientradial');
    843                 this._setAttr(nodeFill, 'color2', el.visProp.gradientsecondcolor);
    844                 this._setAttr(nodeFill, 'opacity2', el.visProp.gradientsecondopacity);
    845                 this._setAttr(nodeFill, 'focusposition', el.visProp.gradientpositionx * 100 + '%,' + el.visProp.gradientpositiony * 100 + '%');
    846                 this._setAttr(nodeFill, 'focussize', '0,0');
    847             } else {
    848                 this._setAttr(nodeFill, 'type', 'solid');
    849             }
    850         },
    851
    852         // already documented in JXG.AbstractRenderer
    853         setObjectFillColor: function (el, color, opacity) {
    854             var rgba = Type.evaluate(color), c, rgbo,
    855                 o = Type.evaluate(opacity), oo,
    856                 node = el.rendNode,
    857                 t;
    858
    859             o = (o > 0) ? o : 0;
    860
    861             if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) {
    862                 return;
    863             }
    864
    865             if (Type.exists(rgba) && rgba !== false) {
    866                 // RGB, not RGBA
    867                 if (rgba.length !== 9) {
    868                     c = rgba;
    869                     oo = o;
    870                 // True RGBA, not RGB
    871                 } else {
    872                     rgbo = Color.rgba2rgbo(rgba);
    873                     c = rgbo[0];
    874                     oo = o * rgbo[1];
    875                 }
    876                 if (c === 'none' || c === false) {
    877                     this._setAttr(el.rendNode, 'filled', 'false');
    878                 } else {
    879                     this._setAttr(el.rendNode, 'filled', 'true');
    880                     this._setAttr(el.rendNode, 'fillcolor', c);
    881
    882                     if (Type.exists(oo) && el.rendNodeFill) {
    883                         this._setAttr(el.rendNodeFill, 'opacity', (oo * 100) + '%');
    884                     }
    885                 }
    886                 if (el.type === Const.OBJECT_TYPE_IMAGE) {
    887                     /*
    888                     t = el.rendNode.style.filter.toString();
    889                     if (t.match(/alpha/)) {
    890                         el.rendNode.style.filter = t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + (oo * 100) + ')');
    891                     } else {
    892                         el.rendNode.style.filter += ' alpha(opacity = ' + (oo * 100) + ')';
    893                     }
    894                     */
    895                     if (node.filters.length > 1) {
    896                         // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]?
    897                         // Setting axes:true shows text labels!
    898                         node.filters.item(1).opacity = Math.round(oo * 100); // Why does setObjectFillColor not use Math.round?
    899                         node.filters.item(1).enabled = true;
    900                     }
    901                 }
    902             }
    903             el.visPropOld.fillcolor = rgba;
    904             el.visPropOld.fillopacity = o;
    905         },
    906
    907         // already documented in JXG.AbstractRenderer
    908         setObjectStrokeColor: function (el, color, opacity) {
    909             var rgba = Type.evaluate(color), c, rgbo, t,
    910                 o = Type.evaluate(opacity), oo,
    911                 node = el.rendNode, nodeStroke;
    912
    913             o = (o > 0) ? o : 0;
    914
    915             if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
    916                 return;
    917             }
    918
    919             // this looks like it could be merged with parts of VMLRenderer.setObjectFillColor
    920
    921             if (Type.exists(rgba) && rgba !== false) {
    922                 // RGB, not RGBA
    923                 if (rgba.length !== 9) {
    924                     c = rgba;
    925                     oo = o;
    926                 // True RGBA, not RGB
    927                 } else {
    928                     rgbo = color.rgba2rgbo(rgba);
    929                     c = rgbo[0];
    930                     oo = o * rgbo[1];
    931                 }
    932                 if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
    933                     //node.style.filter = ' alpha(opacity = ' + oo + ')';
    934                     /*
    935                     t = node.style.filter.toString();
    936                     if (t.match(/alpha/)) {
    937                         node.style.filter =
    938                         t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + oo + ')');
    939                     } else {
    940                         node.style.filter += ' alpha(opacity = ' + oo + ')';
    941                     }
    942                     */
    943                     if (node.filters.length > 1) {
    944                         // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]?
    945                         // Setting axes:true shows text labels!
    946                         node.filters.item(1).opacity = Math.round(oo * 100);
    947                         node.filters.item(1).enabled = true;
    948                     }
    949
    950                     node.style.color = c;
    951                 } else {
    952                     if (c !== false) {
    953                         this._setAttr(node, 'stroked', 'true');
    954                         this._setAttr(node, 'strokecolor', c);
    955                     }
    956
    957                     nodeStroke = el.rendNodeStroke;
    958                     if (Type.exists(oo) && el.type !== Const.OBJECT_TYPE_IMAGE) {
    959                         this._setAttr(nodeStroke, 'opacity', (oo * 100) + '%');
    960                     }
    961                 }
    962             }
    963             el.visPropOld.strokecolor = rgba;
    964             el.visPropOld.strokeopacity = o;
    965         },
    966
    967         // already documented in JXG.AbstractRenderer
    968         setObjectStrokeWidth: function (el, width) {
    969             var w = Type.evaluate(width),
    970                 node;
    971
    972             if (isNaN(w) || el.visPropOld.strokewidth === w) {
    973                 return;
    974             }
    975
    976             node = el.rendNode;
    977             this.setPropertyPrim(node, 'stroked', 'true');
    978
    979             if (Type.exists(w)) {
    980
    981                 this.setPropertyPrim(node, 'stroke-width', w);
    982                 if (w === 0 && Type.exists(el.rendNodeStroke)) {
    983                     this._setAttr(node, 'stroked', 'false');
    984                 }
    985             }
    986
    987             el.visPropOld.strokewidth = w;
    988
    989         },
    990
    991         // already documented in JXG.AbstractRenderer
    992         setShadow: function (el) {
    993             var nodeShadow = el.rendNodeShadow;
    994
    995             if (!nodeShadow || el.visPropOld.shadow === el.visProp.shadow) {
    996                 return;
    997             }
    998
    999             if (el.visProp.shadow) {
    1000                 this._setAttr(nodeShadow, 'On', 'True');
    1001                 this._setAttr(nodeShadow, 'Offset', '3pt,3pt');
    1002                 this._setAttr(nodeShadow, 'Opacity', '60%');
    1003                 this._setAttr(nodeShadow, 'Color', '#aaaaaa');
    1004             } else {
    1005                 this._setAttr(nodeShadow, 'On', 'False');
    1006             }
    1007
    1008             el.visPropOld.shadow = el.visProp.shadow;
    1009         },
    1010
    1011         /* **************************
    1012          * renderer control
    1013          * **************************/
    1014
    1015         // already documented in JXG.AbstractRenderer
    1016         suspendRedraw: function () {
    1017             this.container.style.display = 'none';
    1018         },
    1019
    1020         // already documented in JXG.AbstractRenderer
    1021         unsuspendRedraw: function () {
    1022             this.container.style.display = '';
    1023         }
    1024     });
    1025
    1026     return JXG.VMLRenderer;
    1027 });
    1028