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