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