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