      1 /*
      2     Copyright 2010-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, document: true, Image: true */
     30
     31 /**
     32  * Uses HTML Canvas 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.CanvasRenderer = function (container) {
     39     var i;
     40
     41     this.type = 'canvas';
     42
     43     this.canvasRoot = null;
     44     this.suspendHandle = null;
     45     this.canvasId = JXG.Util.genUUID();
     46
     47     this.canvasNamespace = null;
     48
     49     this.container = container;
     50     this.container.style.MozUserSelect = 'none';
     51
     52     this.container.style.overflow = 'hidden';
     53     if (this.container.style.position === '') {
     54         this.container.style.position = 'relative';
     55     }
     56
     57     this.container.innerHTML = ['<canvas id="', this.canvasId, '" width="', JXG.getStyle(this.container, 'width'), '" height="', JXG.getStyle(this.container, 'height'), '"><', '/canvas>'].join('');
     58     this.canvasRoot = document.getElementById(this.canvasId);
     59     this.context =  this.canvasRoot.getContext('2d');
     60
     61     this.dashArray = [[2, 2], [5, 5], [10, 10], [20, 20], [20, 10, 10, 10], [20, 5, 10, 5]];
     62 };
     63
     64 JXG.CanvasRenderer.prototype = new JXG.AbstractRenderer();
     65
     66 JXG.extend(JXG.CanvasRenderer.prototype, /** @lends JXG.CanvasRenderer.prototype */ {
     67
     68     /* **************************
     69      *   private methods only used
     70      *   in this renderer. Should
     71      *   not be called from outside.
     72      * **************************/
     73
     74     /**
     75      * Draws a filled polygon.
     76      * @param {Array} shape A matrix presented by a two dimensional array of numbers.
     77      * @see JXG.AbstractRenderer#makeArrows
     78      * @private
     79      */
     80     _drawFilledPolygon: function (shape) {
     81         var i, len = shape.length,
     82             context = this.context;
     83
     84         if (len > 0) {
     85             context.beginPath();
     86             context.moveTo(shape[0][0], shape[0][1]);
     87             for (i = 0; i < len; i++) {
     88                 if (i > 0) {
     89                     context.lineTo(shape[i][0], shape[i][1]);
     90                 }
     91             }
     92             context.lineTo(shape[0][0], shape[0][1]);
     93             context.fill();
     94         }
     95     },
     96
     97     /**
     98      * Sets the fill color and fills an area.
     99      * @param {JXG.GeometryElement} element An arbitrary JSXGraph element, preferably one with an area.
    100      * @private
    101      */
    102     _fill: function (element) {
    103         var context = this.context;
    104
    105         context.save();
    106         if (this._setColor(element, 'fill')) {
    107             context.fill();
    108         }
    109         context.restore();
    110     },
    111
    112     /**
    113      * Rotates a point around <tt>(0, 0)</tt> by a given angle.
    114      * @param {Number} angle An angle, given in rad.
    115      * @param {Number} x X coordinate of the point.
    116      * @param {Number} y Y coordinate of the point.
    117      * @returns {Array} An array containing the x and y coordinate of the rotated point.
    118      * @private
    119      */
    120     _rotatePoint: function (angle, x, y) {
    121         return [
    122             (x * Math.cos(angle)) - (y * Math.sin(angle)),
    123             (x * Math.sin(angle)) + (y * Math.cos(angle))
    124         ];
    125     },
    126
    127     /**
    128      * Rotates an array of points around <tt>(0, 0)</tt>.
    129      * @param {Array} shape An array of array of point coordinates.
    130      * @param {Number} angle The angle in rad the points are rotated by.
    131      * @returns {Array} Array of array of two dimensional point coordinates.
    132      * @private
    133      */
    134     _rotateShape: function (shape, angle) {
    135         var i, rv = [], len = shape.length;
    136
    137         if (len <= 0) {
    138             return shape;
    139         }
    140
    141         for (i = 0; i < len; i++) {
    142             rv.push(this._rotatePoint(angle, shape[i][0], shape[i][1]));
    143         }
    144
    145         return rv;
    146     },
    147
    148     /**
    149      * Sets color and opacity for filling and stroking.
    150      * type is the attribute from visProp and targetType the context[targetTypeStyle].
    151      * This is necessary, because the fill style of a text is set by the stroke attributes of the text element.
    152      * @param {JXG.GeometryElement} element Any JSXGraph element.
    153      * @param {String} [type='stroke'] Either <em>fill</em> or <em>stroke</em>.
    154      * @param {String} [targetType=type] (optional) Either <em>fill</em> or <em>stroke</em>.
    155      * @returns {Boolean} If the color could be set, <tt>true</tt> is returned.
    156      * @private
    157      */
    158     _setColor: function (element, type, targetType) {
    159         var hasColor = true, isTrace = false,
    160             ev = element.visProp, hl,
    161             rgba, rgbo, c, o, oo;
    162
    163         type = type || 'stroke';
    164         targetType = targetType || type;
    165         if (!JXG.exists(element.board) || !JXG.exists(element.board.highlightedObjects)) {
    166             // This case handles trace elements.
    167             // To make them work, we simply neglect highlighting.
    168             isTrace = true;
    169         }
    170
    171         if (!isTrace && JXG.exists(element.board.highlightedObjects[element.id])) {
    172             hl = 'highlight';
    173         } else {
    174             hl = '';
    175         }
    176
    177         // type is equal to 'fill' or 'stroke'
    178         rgba = JXG.evaluate(ev[hl+type+'color']);
    179         if (rgba !== 'none' && rgba !== false ) {
    180             o = JXG.evaluate(ev[hl+type+'opacity']);
    181             o = (o > 0) ? o : 0;
    182             if (rgba.length!=9) {          // RGB, not RGBA
    183                 c = rgba;
    184                 oo = o;
    185             } else {                       // True RGBA, not RGB
    186                 rgbo = JXG.rgba2rgbo(rgba);
    187                 c = rgbo[0];
    188                 oo = o*rgbo[1];
    189             }
    190             this.context.globalAlpha = oo;
    191             this.context[targetType+'Style'] = c;
    192         } else {
    193             hasColor = false;
    194         }
    195         if (type === 'stroke') {
    196             this.context.lineWidth = parseFloat(ev.strokewidth);
    197         }
    198         return hasColor;
    199     },
    200
    201
    202     /**
    203      * Sets color and opacity for drawing paths and lines and draws the paths and lines.
    204      * @param {JXG.GeometryElement} element An JSXGraph element with a stroke.
    205      * @private
    206      */
    207     _stroke: function (element) {
    208         var context = this.context;
    209
    210         context.save();
    211
    212         if (element.visProp.dash > 0) {
    213             if (context.setLineDash) {
    214                 context.setLineDash(this.dashArray[element.visProp.dash]);
    215             }
    216         } else {
    217             this.context.lineDashArray = [];
    218         }
    219
    220         if (this._setColor(element, 'stroke')) {
    221             context.stroke();
    222         }
    223         context.restore();
    224     },
    225
    226     /**
    227      * Translates a set of points.
    228      * @param {Array} shape An array of point coordinates.
    229      * @param {Number} x Translation in X direction.
    230      * @param {Number} y Translation in Y direction.
    231      * @returns {Array} An array of translated point coordinates.
    232      * @private
    233      */
    234     _translateShape: function (shape, x, y) {
    235         var i, rv = [], len = shape.length;
    236
    237         if (len <= 0) {
    238             return shape;
    239         }
    240
    241         for (i = 0; i < len; i++) {
    242             rv.push([ shape[i][0] + x, shape[i][1] + y ]);
    243         }
    244
    245         return rv;
    246     },
    247
    248     /* ******************************** *
    249      *    Point drawing and updating    *
    250      * ******************************** */
    251
    252     // documented in AbstractRenderer
    253     drawPoint: function (el) {
    254         var f = el.visProp.face,
    255             size = el.visProp.size,
    256             scr = el.coords.scrCoords,
    257             sqrt32 = size * Math.sqrt(3) * 0.5,
    258             s05 = size * 0.5,
    259             stroke05 = parseFloat(el.visProp.strokewidth) / 2.0,
    260             context = this.context;
    261
    262         if (size <= 0 || !el.visProp.visible) {
    263             return;
    264         }
    265
    266         switch (f) {
    267             case 'cross':  // x
    268             case 'x':
    269                 context.beginPath();
    270                 context.moveTo(scr[1] - size, scr[2] - size);
    271                 context.lineTo(scr[1] + size, scr[2] + size);
    272                 context.moveTo(scr[1] + size, scr[2] - size);
    273                 context.lineTo(scr[1] - size, scr[2] + size);
    274                 context.closePath();
    275                 this._stroke(el);
    276                 break;
    277             case 'circle': // dot
    278             case 'o':
    279                 context.beginPath();
    280                 context.arc(scr[1], scr[2], size + 1 + stroke05, 0, 2 * Math.PI, false);
    281                 context.closePath();
    282                 this._fill(el);
    283                 this._stroke(el);
    284                 break;
    285             case 'square':  // rectangle
    286             case '[]':
    287                 if (size <= 0) {
    288                     break;
    289                 }
    290
    291                 context.save();
    292                 if (this._setColor(el, 'stroke', 'fill')) {
    293                     context.fillRect(scr[1] - size - stroke05, scr[2] - size - stroke05, size * 2 + 3 * stroke05, size * 2 + 3 * stroke05);
    294                 }
    295                 context.restore();
    296                 context.save();
    297                 this._setColor(el, 'fill');
    298                 context.fillRect(scr[1] - size + stroke05, scr[2] - size + stroke05, size * 2 - stroke05, size * 2 - stroke05);
    299                 context.restore();
    300                 break;
    301             case 'plus':  // +
    302             case '+':
    303                 context.beginPath();
    304                 context.moveTo(scr[1] - size, scr[2]);
    305                 context.lineTo(scr[1] + size, scr[2]);
    306                 context.moveTo(scr[1], scr[2] - size);
    307                 context.lineTo(scr[1], scr[2] + size);
    308                 context.closePath();
    309                 this._stroke(el);
    310                 break;
    311             case 'diamond':   // <>
    312             case '<>':
    313                 context.beginPath();
    314                 context.moveTo(scr[1] - size, scr[2]);
    315                 context.lineTo(scr[1], scr[2] + size);
    316                 context.lineTo(scr[1] + size, scr[2]);
    317                 context.lineTo(scr[1], scr[2] - size);
    318                 context.closePath();
    319                 this._fill(el);
    320                 this._stroke(el);
    321                 break;
    322             case 'triangleup':
    323             case 'a':
    324             case '^':
    325                 context.beginPath();
    326                 context.moveTo(scr[1], scr[2] - size);
    327                 context.lineTo(scr[1] - sqrt32, scr[2] + s05);
    328                 context.lineTo(scr[1] + sqrt32, scr[2] + s05);
    329                 context.closePath();
    330                 this._fill(el);
    331                 this._stroke(el);
    332                 break;
    333             case 'triangledown':
    334             case 'v':
    335                 context.beginPath();
    336                 context.moveTo(scr[1], scr[2] + size);
    337                 context.lineTo(scr[1] - sqrt32, scr[2] - s05);
    338                 context.lineTo(scr[1] + sqrt32, scr[2] - s05);
    339                 context.closePath();
    340                 this._fill(el);
    341                 this._stroke(el);
    342                 break;
    343             case 'triangleleft':
    344             case '<':
    345                 context.beginPath();
    346                 context.moveTo(scr[1] - size, scr[2]);
    347                 context.lineTo(scr[1] + s05, scr[2] - sqrt32);
    348                 context.lineTo(scr[1] + s05, scr[2] + sqrt32);
    349                 context.closePath();
    350                 this.fill(el);
    351                 this._stroke(el);
    352                 break;
    353             case 'triangleright':
    354             case '>':
    355                 context.beginPath();
    356                 context.moveTo(scr[1] + size, scr[2]);
    357                 context.lineTo(scr[1] - s05, scr[2] - sqrt32);
    358                 context.lineTo(scr[1] - s05, scr[2] + sqrt32);
    359                 context.closePath();
    360                 this._fill(el);
    361                 this._stroke(el);
    362                 break;
    363         }
    364     },
    365
    366     // documented in AbstractRenderer
    367     updatePoint: function (el) {
    368         this.drawPoint(el);
    369     },
    370
    371     /* ******************************** *
    372      *           Lines                  *
    373      * ******************************** */
    374
    375     // documented in AbstractRenderer
    376     drawLine: function (el) {
    377         var scr1 = new JXG.Coords(JXG.COORDS_BY_USER, el.point1.coords.usrCoords, el.board),
    378             scr2 = new JXG.Coords(JXG.COORDS_BY_USER, el.point2.coords.usrCoords, el.board);
    379
    380         JXG.Math.Geometry.calcStraight(el, scr1, scr2);
    381
    382         this.context.beginPath();
    383         this.context.moveTo(scr1.scrCoords[1], scr1.scrCoords[2]);
    384         this.context.lineTo(scr2.scrCoords[1], scr2.scrCoords[2]);
    385         this._stroke(el);
    386
    387         this.makeArrows(el, scr1, scr2);
    388     },
    389
    390     // documented in AbstractRenderer
    391     updateLine: function (el) {
    392         this.drawLine(el);
    393     },
    394
    395     // documented in AbstractRenderer
    396     drawTicks: function () {
    397         // this function is supposed to initialize the svg/vml nodes in the SVG/VMLRenderer.
    398         // but in canvas there are no such nodes, hence we just do nothing and wait until
    399         // updateTicks is called.
    400     },
    401
    402     // documented in AbstractRenderer
    403     updateTicks: function (axis, dxMaj, dyMaj, dxMin, dyMin) {
    404         var i, c,
    405             len = axis.ticks.length,
    406             context = this.context;
    407
    408         context.beginPath();
    409         for (i = 0; i < len; i++) {
    410             c = axis.ticks[i];
    411             x = c[0];
    412             y = c[1];
    413             context.moveTo(x[0], y[0]);
    414             context.lineTo(x[1], y[1]);
    415         }
    416         // Labels
    417         for (i = 0; i < len; i++) {
    418             c = axis.ticks[i].scrCoords;
    419             if (axis.ticks[i].major
    420                 && (axis.board.needsFullUpdate || axis.needsRegularUpdate)
    421                 && axis.labels[i]
    422                 && axis.labels[i].visProp.visible) {
    423                     this.updateText(axis.labels[i]);
    424             }
    425         }
    426         this._stroke(axis);
    427     },
    428
    429     /* **************************
    430      *    Curves
    431      * **************************/
    432
    433     // documented in AbstractRenderer
    434     drawCurve: function (el) {
    435         if (el.visProp.handdrawing) {
    436             this.updatePathStringBezierPrim(el);
    437         } else {
    438             this.updatePathStringPrim(el);
    439         }
    440     },
    441
    442     // documented in AbstractRenderer
    443     updateCurve: function (el) {
    444         this.drawCurve(el);
    445     },
    446
    447     /* **************************
    448      *    Circle related stuff
    449      * **************************/
    450
    451     // documented in AbstractRenderer
    452     drawEllipse: function (el) {
    453         var m1 = el.center.coords.scrCoords[1],
    454             m2 = el.center.coords.scrCoords[2],
    455             sX = el.board.unitX,
    456             sY = el.board.unitY,
    457             rX = 2 * el.Radius(),
    458             rY = 2 * el.Radius(),
    459             aWidth = rX * sX,
    460             aHeight = rY * sY,
    461             aX = m1 - aWidth / 2,
    462             aY = m2 - aHeight / 2,
    463             hB = (aWidth / 2) * 0.5522848,
    464             vB = (aHeight / 2) * 0.5522848,
    465             eX = aX + aWidth,
    466             eY = aY + aHeight,
    467             mX = aX + aWidth / 2,
    468             mY = aY + aHeight / 2,
    469             context = this.context;
    470
    471         if (rX > 0.0 && rY > 0.0 && !isNaN(m1 + m2)) {
    472             context.beginPath();
    473             context.moveTo(aX, mY);
    474             context.bezierCurveTo(aX, mY - vB, mX - hB, aY, mX, aY);
    475             context.bezierCurveTo(mX + hB, aY, eX, mY - vB, eX, mY);
    476             context.bezierCurveTo(eX, mY + vB, mX + hB, eY, mX, eY);
    477             context.bezierCurveTo(mX - hB, eY, aX, mY + vB, aX, mY);
    478             context.closePath();
    479             this._fill(el);
    480             this._stroke(el);
    481         }
    482     },
    483
    484     // documented in AbstractRenderer
    485     updateEllipse: function (el) {
    486         return this.drawEllipse(el);
    487     },
    488
    489     /* **************************
    490      *    Polygon
    491      * **************************/
    492
    493     // nothing here, using AbstractRenderer implementations
    494
    495     /* **************************
    496      *    Text related stuff
    497      * **************************/
    498
    499     // already documented in JXG.AbstractRenderer
    500     displayCopyright: function (str, fontSize) {
    501         var context = this.context;
    502
    503         // this should be called on EVERY update, otherwise it won't be shown after the first update
    504         context.save();
    505         context.font = fontSize + 'px Arial';
    506         context.fillStyle = '#aaa';
    507         context.lineWidth = 0.5;
    508         context.fillText(str, 10, 2 + fontSize);
    509         context.restore();
    510     },
    511
    512     // already documented in JXG.AbstractRenderer
    513     drawInternalText: function (el) {
    514         var fs, context = this.context;
    515
    516         context.save();
    517         // el.rendNode.setAttributeNS(null, "class", el.visProp.cssclass);
    518         if (this._setColor(el, 'stroke', 'fill') && !isNaN(el.coords.scrCoords[1]+el.coords.scrCoords[2]) ) {
    519             if (el.visProp.fontsize) {
    520                 if (typeof el.visProp.fontsize === 'function') {
    521                     fs = el.visProp.fontsize();
    522                     context.font = (fs > 0 ? fs : 0) + 'px Arial';
    523                 } else {
    524                     context.font = (el.visProp.fontsize) + 'px Arial';
    525                 }
    526             }
    527
    528             this.transformImage(el, el.transformations);
    529             context.fillText(el.plaintext, el.coords.scrCoords[1], el.coords.scrCoords[2]);
    530         }
    531         context.restore();
    532
    533         return null;
    534     },
    535
    536     // already documented in JXG.AbstractRenderer
    537     updateInternalText: function (element) {
    538         this.drawInternalText(element);
    539     },
    540
    541     // documented in JXG.AbstractRenderer
    542     // Only necessary for texts
    543     setObjectStrokeColor: function (el, color, opacity) {
    544         var rgba = JXG.evaluate(color), c, rgbo,
    545             o = JXG.evaluate(opacity), oo,
    546             node;
    547
    548         o = (o > 0) ? o : 0;
    549
    550         if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
    551             return;
    552         }
    553
    554         if (JXG.exists(rgba) && rgba !== false) {
    555             if (rgba.length!=9) {          // RGB, not RGBA
    556                 c = rgba;
    557                 oo = o;
    558             } else {                       // True RGBA, not RGB
    559                 rgbo = JXG.rgba2rgbo(rgba);
    560                 c = rgbo[0];
    561                 oo = o*rgbo[1];
    562             }
    563             node = el.rendNode;
    564             if (el.type === JXG.OBJECT_TYPE_TEXT && el.visProp.display === 'html') {
    565                 node.style.color = c;
    566                 node.style.opacity = oo;
    567             }
    568         }
    569
    570         el.visPropOld.strokecolor = rgba;
    571         el.visPropOld.strokeopacity = o;
    572     },
    573
    574     // already documented in JXG.AbstractRenderer
    575 /*
    576     updateTextStyle: function (element) {
    577         var fs = JXG.evaluate(element.visProp.fontsize);
    578
    579         if (element.visProp.display === 'html') {
    580             element.rendNode.style.fontSize = fs + 'px';
    581         }
    582     },
    583 */
    584     /* **************************
    585      *    Image related stuff
    586      * **************************/
    587
    588     // already documented in JXG.AbstractRenderer
    589     drawImage: function (el) {
    590         el.rendNode = new Image();
    591         // Store the file name of the image.
    592         // Before, this was done in el.rendNode.src
    593         // But there, the file name is expanded to
    594         // the full url. This may be different from
    595         // the url computed in updateImageURL().
    596         el._src = '';
    597         this.updateImage(el);
    598     },
    599
    600     // already documented in JXG.AbstractRenderer
    601     updateImage: function (el) {
    602         var context = this.context,
    603             o = JXG.evaluate(el.visProp.fillopacity),
    604             paintImg = JXG.bind(function () {
    605                 el.imgIsLoaded = true;
    606                 if (el.size[0] <= 0 || el.size[1] <= 0) {
    607                     return;
    608                 }
    609                 context.save();
    610                 context.globalAlpha = o;
    611                 // If det(el.transformations)=0, FireFox 3.6. breaks down.
    612                 // This is tested in transformImage
    613                 this.transformImage(el, el.transformations);
    614                 context.drawImage(el.rendNode,
    615                     el.coords.scrCoords[1],
    616                     el.coords.scrCoords[2] - el.size[1],
    617                     el.size[0],
    618                     el.size[1]);
    619                 context.restore();
    620             }, this);
    621
    622         if (this.updateImageURL(el)) {
    623             el.rendNode.onload = paintImg;
    624         } else {
    625             if (el.imgIsLoaded) {
    626                 paintImg();
    627             }
    628         }
    629     },
    630
    631     // already documented in JXG.AbstractRenderer
    632     transformImage: function (el, t) {
    633         var m, len = t.length,
    634             ctx = this.context;
    635
    636         if (len > 0) {
    637             m = this.joinTransforms(el, t);
    638             if (Math.abs(JXG.Math.Numerics.det(m)) >= JXG.Math.eps) {
    639                 ctx.transform(m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]);
    640             }
    641         }
    642     },
    643
    644     // already documented in JXG.AbstractRenderer
    645     updateImageURL: function (el) {
    646         var url;
    647
    648         url = JXG.evaluate(el.url);
    649         if (el._src !== url) {
    650             el.imgIsLoaded = false;
    651             el.rendNode.src = url;
    652             el._src = url;
    653             return true;
    654         }
    655
    656         return false;
    657     },
    658
    659     /* **************************
    660      * Render primitive objects
    661      * **************************/
    662
    663     // documented in AbstractRenderer
    664     remove: function (shape) {
    665         // sounds odd for a pixel based renderer but we need this for html texts
    666         if (JXG.exists(shape) && JXG.exists(shape.parentNode)) {
    667             shape.parentNode.removeChild(shape);
    668         }
    669     },
    670
    671     // documented in AbstractRenderer
    672     makeArrows: function (el, scr1, scr2) {
    673         // not done yet for curves and arcs.
    674         var arrowHead = [
    675             [ 2, 0 ],
    676             [ -10, -4 ],
    677             [ -10, 4]
    678         ],
    679             arrowTail = [
    680                 [ -2, 0 ],
    681                 [ 10, -4 ],
    682                 [ 10, 4]
    683             ],
    684             x1, y1, x2, y2, ang,
    685             context = this.context;
    686
    687         if (el.visProp.strokecolor !== 'none' && (el.visProp.lastarrow || el.visProp.firstarrow)) {
    688             if (el.elementClass === JXG.OBJECT_CLASS_LINE) {
    689                 x1 = scr1.scrCoords[1];
    690                 y1 = scr1.scrCoords[2];
    691                 x2 = scr2.scrCoords[1];
    692                 y2 = scr2.scrCoords[2];
    693             } else {
    694                 return;
    695             }
    696
    697             context.save();
    698             if (this._setColor(el, 'stroke', 'fill')) {
    699                 ang = Math.atan2(y2 - y1, x2 - x1);
    700                 if (el.visProp.lastarrow) {
    701                     this._drawFilledPolygon(this._translateShape(this._rotateShape(arrowHead, ang), x2, y2));
    702                 }
    703
    704                 if (el.visProp.firstarrow) {
    705                     this._drawFilledPolygon(this._translateShape(this._rotateShape(arrowTail, ang), x1, y1));
    706                 }
    707             }
    708             context.restore();
    709         }
    710     },
    711
    712     // documented in AbstractRenderer
    713     updatePathStringPrim: function (el) {
    714         var symbm = 'M',
    715             symbl = 'L',
    716             nextSymb = symbm,
    717             maxSize = 5000.0,
    718             i, scr,
    719             isNotPlot = (el.visProp.curvetype !== 'plot'),
    720             len,
    721             context = this.context;
    722
    723         if (el.numberPoints <= 0) {
    724             return;
    725         }
    726
    727         if (isNotPlot && el.board.options.curve.RDPsmoothing) {
    728             el.points = JXG.Math.Numerics.RamerDouglasPeuker(el.points, 0.5);
    729         }
    730         len = Math.min(el.points.length, el.numberPoints);
    731
    732         context.beginPath();
    733         for (i = 0; i < len; i++) {
    734             scr = el.points[i].scrCoords;
    735
    736             if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
    737                 nextSymb = symbm;
    738             } else {
    739                 // Chrome has problems with values  being too far away.
    740                 if (scr[1] > maxSize) {
    741                     scr[1] = maxSize;
    742                 } else if (scr[1] < -maxSize) {
    743                     scr[1] = -maxSize;
    744                 }
    745
    746                 if (scr[2] > maxSize) {
    747                     scr[2] = maxSize;
    748                 } else if (scr[2] < -maxSize) {
    749                     scr[2] = -maxSize;
    750                 }
    751
    752                 if (nextSymb === symbm) {
    753                     context.moveTo(scr[1], scr[2]);
    754                 } else {
    755                     context.lineTo(scr[1], scr[2]);
    756                 }
    757                 nextSymb = symbl;
    758             }
    759         }
    760         this._fill(el);
    761         this._stroke(el);
    762     },
    763
    764     // already documented in JXG.AbstractRenderer
    765     updatePathStringBezierPrim: function (el) {
    766         var symbm = 'M',
    767             symbl = 'C',
    768             nextSymb = symbm,
    769             maxSize = 5000.0,
    770             i, j, scr,
    771             lx, ly, f = el.visProp.strokewidth,
    772             isNoPlot = (el.visProp.curvetype !== 'plot'),
    773             len,
    774             context = this.context;
    775
    776         if (el.numberPoints <= 0) {
    777             return;
    778         }
    779
    780         if (isNoPlot && el.board.options.curve.RDPsmoothing) {
    781             el.points = JXG.Math.Numerics.RamerDouglasPeuker(el.points, 0.5);
    782         }
    783         len = Math.min(el.points.length, el.numberPoints);
    784
    785         context.beginPath();
    786         for (j=1; j<3; j++) {
    787             nextSymb = symbm;
    788             for (i = 0; i < len; i++) {
    789                 scr = el.points[i].scrCoords;
    790
    791                 if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
    792                     nextSymb = symbm;
    793                 } else {
    794                     // Chrome has problems with values  being too far away.
    795                     if (scr[1] > maxSize) {
    796                         scr[1] = maxSize;
    797                     } else if (scr[1] < -maxSize) {
    798                         scr[1] = -maxSize;
    799                     }
    800
    801                     if (scr[2] > maxSize) {
    802                         scr[2] = maxSize;
    803                     } else if (scr[2] < -maxSize) {
    804                         scr[2] = -maxSize;
    805                     }
    806
    807                     if (nextSymb == symbm) {
    808                         context.moveTo(scr[1]+0*f*(2*j*Math.random()-j),
    809                                        scr[2]+0*f*(2*j*Math.random()-j));
    810                     } else {
    811                         context.bezierCurveTo(
    812                             (lx + (scr[1]-lx)*0.333 + f*(2*j*Math.random()-j)),
    813                             (ly + (scr[2]-ly)*0.333 + f*(2*j*Math.random()-j)),
    814                             (lx + 2*(scr[1]-lx)*0.333 + f*(2*j*Math.random()-j)),
    815                             (ly + 2*(scr[2]-ly)*0.333 + f*(2*j*Math.random()-j)),
    816                             scr[1], scr[2]);
    817                     }
    818                     nextSymb = symbl;
    819                     lx = scr[1];
    820                     ly = scr[2];
    821                 }
    822             }
    823         }
    824         this._fill(el);
    825         this._stroke(el);
    826     },
    827
    828     // documented in AbstractRenderer
    829     updatePolygonPrim: function (node, el) {
    830         var scrCoords, i,
    831             len = el.vertices.length,
    832             context = this.context;
    833
    834         if (len <= 0) {
    835             return;
    836         }
    837
    838         context.beginPath();
    839         scrCoords = el.vertices[0].coords.scrCoords;
    840         context.moveTo(scrCoords[1], scrCoords[2]);
    841         for (i = 1; i < len; i++) {
    842             scrCoords = el.vertices[i].coords.scrCoords;
    843             context.lineTo(scrCoords[1], scrCoords[2]);
    844         }
    845         context.closePath();
    846
    847         this._fill(el);    // The edges of a polygon are displayed separately (as segments).
    848     },
    849
    850     /* **************************
    851      *  Set Attributes
    852      * **************************/
    853
    854     // documented in AbstractRenderer
    855     show: function (el) {
    856         // sounds odd for a pixel based renderer but we need this for html texts
    857         if (JXG.exists(el.rendNode)) {
    858             el.rendNode.style.visibility = "inherit";
    859         }
    860     },
    861
    862     // documented in AbstractRenderer
    863     hide: function (el) {
    864         // sounds odd for a pixel based renderer but we need this for html texts
    865         if (JXG.exists(el.rendNode)) {
    866             el.rendNode.style.visibility = "hidden";
    867         }
    868     },
    869
    870     // documented in AbstractRenderer
    871     setGradient: function (el) {
    872         var col, op;
    873
    874         op = JXG.evaluate(el.visProp.fillopacity);
    875         op = (op > 0) ? op : 0;
    876
    877         col = JXG.evaluate(el.visProp.fillcolor);
    878     },
    879
    880     // documented in AbstractRenderer
    881     setShadow: function (el) {
    882         if (el.visPropOld.shadow === el.visProp.shadow) {
    883             return;
    884         }
    885
    886         // not implemented yet
    887         // we simply have to redraw the element
    888         // probably the best way to do so would be to call el.updateRenderer(), i think.
    889
    890         el.visPropOld.shadow = el.visProp.shadow;
    891     },
    892
    893     // documented in AbstractRenderer
    894     highlight: function (obj) {
    895         obj.board.prepareUpdate();
    896         obj.board.renderer.suspendRedraw(obj.board);
    897         obj.board.updateRenderer();
    898         obj.board.renderer.unsuspendRedraw();
    899         return this;
    900     },
    901
    902     // documented in AbstractRenderer
    903     noHighlight: function (obj) {
    904         obj.board.prepareUpdate();
    905         obj.board.renderer.suspendRedraw(obj.board);
    906         obj.board.updateRenderer();
    907         obj.board.renderer.unsuspendRedraw();
    908         return this;
    909     },
    910
    911     /* **************************
    912      * renderer control
    913      * **************************/
    914
    915     // documented in AbstractRenderer
    916     suspendRedraw: function (board) {
    917         this.context.save();
    918         this.context.clearRect(0, 0, this.canvasRoot.width, this.canvasRoot.height);
    919
    920         if (board && board.showCopyright) {
    921             this.displayCopyright(JXG.JSXGraph.licenseText, 12);
    922         }
    923     },
    924
    925     // documented in AbstractRenderer
    926     unsuspendRedraw: function () {
    927         this.context.restore();
    928     },
    929
    930     // document in AbstractRenderer
    931     resize: function (w, h) {
    932         this.canvasRoot.style.width = parseFloat(w) + 'px';
    933         this.canvasRoot.style.height = parseFloat(h) + 'px';
    934
    935         this.canvasRoot.setAttribute('width', parseFloat(w) + 'px');
    936         this.canvasRoot.setAttribute('height', parseFloat(h) + 'px');
    937     }
    938
    939 });
    940