      1 /*
      2     Copyright 2008-2022
      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      *
     60      * @class JXG.CanvasRenderer
     61      * @augments JXG.AbstractRenderer
     62      * @param {Node} container Reference to a DOM node containing the board.
     63      * @param {Object} dim The dimensions of the board
     64      * @param {Number} dim.width
     65      * @param {Number} dim.height
     66      * @see JXG.AbstractRenderer
     67      */
     68     JXG.CanvasRenderer = function (container, dim) {
     69         this.type = 'canvas';
     70
     71         this.canvasRoot = null;
     72         this.suspendHandle = null;
     73         this.canvasId = UUID.genUUID();
     74
     75         this.canvasNamespace = null;
     76
     77         if (Env.isBrowser) {
     78             this.container = container;
     79             this.container.style.MozUserSelect = 'none';
     80             this.container.style.userSelect = '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,
     88                 '" width="', dim.width,
     89                 'px" height="', dim.height,
     90                 'px"><', '/canvas>'].join('');
     91             this.canvasRoot = this.container.ownerDocument.getElementById(this.canvasId);
     92             this.canvasRoot.style.display = 'block';
     93             this.context = this.canvasRoot.getContext('2d');
     94
     95         } else if (Env.isNode()) {
     96             try {
     97                 this.canvasId = (typeof module === 'object' ? module.require('canvas') : require('canvas'));
     98                 this.canvasRoot = new this.canvasId(500, 500);
     99                 this.context = this.canvasRoot.getContext('2d');
    100             } catch (err) {
    101                 console.log("Warning: 'canvas' not found. You might need to call 'npm install canvas'");
    102             }
    103         }
    104
    105         this.dashArray = [[2, 2], [5, 5], [10, 10], [20, 20], [20, 10, 10, 10], [20, 5, 10, 5]];
    106     };
    107
    108     JXG.CanvasRenderer.prototype = new AbstractRenderer();
    109
    110     JXG.extend(JXG.CanvasRenderer.prototype, /** @lends JXG.CanvasRenderer.prototype */ {
    111
    112         /* **************************
    113          *   private methods only used
    114          *   in this renderer. Should
    115          *   not be called from outside.
    116          * **************************/
    117
    118         /**
    119          * Draws a filled polygon.
    120          * @param {Array} shape A matrix presented by a two dimensional array of numbers.
    121          * @see JXG.AbstractRenderer#drawArrows
    122          * @private
    123          */
    124         _drawPolygon: function (shape, degree, doFill) {
    125             var i, len = shape.length,
    126                 context = this.context;
    127
    128             if (len > 0) {
    129                 if (doFill) {
    130                     context.lineWidth = 0;
    131                 }
    132                 context.beginPath();
    133                 context.moveTo(shape[0][0], shape[0][1]);
    134                 if (degree === 1) {
    135                     for (i = 1; i < len; i++) {
    136                         context.lineTo(shape[i][0], shape[i][1]);
    137                     }
    138                 } else {
    139                     for (i = 1; i < len; i += 3) {
    140                         context.bezierCurveTo(shape[i][0], shape[i][1], shape[i + 1][0], shape[i + 1][1], shape[i + 2][0], shape[i + 2][1]);
    141                     }
    142                 }
    143                 if (doFill) {
    144                     context.lineTo(shape[0][0], shape[0][1]);
    145                     context.closePath();
    146                     context.fill();
    147                 } else {
    148                     context.stroke();
    149                 }
    150             }
    151         },
    152
    153         /**
    154          * Sets the fill color and fills an area.
    155          * @param {JXG.GeometryElement} el An arbitrary JSXGraph element, preferably one with an area.
    156          * @private
    157          */
    158         _fill: function (el) {
    159             var context = this.context;
    160
    161             context.save();
    162             if (this._setColor(el, 'fill')) {
    163                 context.fill();
    164             }
    165             context.restore();
    166         },
    167
    168         /**
    169          * Rotates a point around <tt>(0, 0)</tt> by a given angle.
    170          * @param {Number} angle An angle, given in rad.
    171          * @param {Number} x X coordinate of the point.
    172          * @param {Number} y Y coordinate of the point.
    173          * @returns {Array} An array containing the x and y coordinate of the rotated point.
    174          * @private
    175          */
    176         _rotatePoint: function (angle, x, y) {
    177             return [
    178                 (x * Math.cos(angle)) - (y * Math.sin(angle)),
    179                 (x * Math.sin(angle)) + (y * Math.cos(angle))
    180             ];
    181         },
    182
    183         /**
    184          * Rotates an array of points around <tt>(0, 0)</tt>.
    185          * @param {Array} shape An array of array of point coordinates.
    186          * @param {Number} angle The angle in rad the points are rotated by.
    187          * @returns {Array} Array of array of two dimensional point coordinates.
    188          * @private
    189          */
    190         _rotateShape: function (shape, angle) {
    191             var i, rv = [], len = shape.length;
    192
    193             if (len <= 0) {
    194                 return shape;
    195             }
    196
    197             for (i = 0; i < len; i++) {
    198                 rv.push(this._rotatePoint(angle, shape[i][0], shape[i][1]));
    199             }
    200
    201             return rv;
    202         },
    203
    204         /**
    205          * Set the gradient angle for linear color gradients.
    206          *
    207          * @private
    208          * @param {JXG.GeometryElement} node An arbitrary JSXGraph element, preferably one with an area.
    209          * @param {Number} radians angle value in radians. 0 is horizontal from left to right, Pi/4 is vertical from top to bottom.
    210          */
    211         updateGradientAngle: function(el, radians) {
    212             // Angles:
    213             // 0: ->
    214             // 90: down
    215             // 180: <-
    216             // 90: up
    217             var f = 1.0,
    218                 co = Math.cos(-radians),
    219                 si = Math.sin(-radians),
    220                 bb = el.getBoundingBox(),
    221                 c1, c2, x1, x2, y1, y2, x1s, x2s, y1s, y2s, dx, dy;
    222
    223             if (Math.abs(co) > Math.abs(si)) {
    224                 f /= Math.abs(co);
    225             } else {
    226                 f /= Math.abs(si);
    227             }
    228             if (co >= 0) {
    229                 x1 = 0;
    230                 x2 = co * f;
    231             } else {
    232                 x1 = -co * f;
    233                 x2 = 0;
    234             }
    235             if (si >= 0) {
    236                 y1 = 0;
    237                 y2 = si * f;
    238             } else {
    239                 y1 = -si * f;
    240                 y2 = 0;
    241             }
    242
    243             c1 = new Coords(Const.COORDS_BY_USER, [bb[0], bb[1]], el.board);
    244             c2 = new Coords(Const.COORDS_BY_USER, [bb[2], bb[3]], el.board);
    245             dx = c2.scrCoords[1] - c1.scrCoords[1];
    246             dy = c2.scrCoords[2] - c1.scrCoords[2];
    247             x1s = c1.scrCoords[1] + dx * x1;
    248             y1s = c1.scrCoords[2] + dy * y1;
    249             x2s = c1.scrCoords[1] + dx * x2;
    250             y2s = c1.scrCoords[2] + dy * y2;
    251
    252             return this.context.createLinearGradient(x1s, y1s, x2s, y2s);
    253         },
    254
    255         /**
    256          * Set circles for radial color gradients.
    257          *
    258          * @private
    259          * @param {SVGnode} node SVG gradient node
    260          * @param {Number} cx Canvas value x1 (but value between 0 and 1)
    261          * @param {Number} cy  Canvas value y1 (but value between 0 and 1)
    262          * @param {Number} r  Canvas value r1 (but value between 0 and 1)
    263          * @param {Number} fx  Canvas value x0 (but value between 0 and 1)
    264          * @param {Number} fy  Canvas value x1 (but value between 0 and 1)
    265          * @param {Number} fr  Canvas value r0 (but value between 0 and 1)
    266          */
    267         updateGradientCircle: function(el, cx, cy, r, fx, fy, fr) {
    268             var bb = el.getBoundingBox(),
    269                 c1, c2, cxs, cys, rs, fxs, fys, frs, dx, dy;
    270
    271             c1 = new Coords(Const.COORDS_BY_USER, [bb[0], bb[1]], el.board);
    272             c2 = new Coords(Const.COORDS_BY_USER, [bb[2], bb[3]], el.board);
    273             dx = c2.scrCoords[1] - c1.scrCoords[1];
    274             dy = c1.scrCoords[2] - c2.scrCoords[2];
    275
    276             cxs = c1.scrCoords[1] + dx * cx;
    277             cys = c2.scrCoords[2] + dy * cy;
    278             fxs = c1.scrCoords[1] + dx * fx;
    279             fys = c2.scrCoords[2] + dy * fy;
    280             rs = r * (dx + dy) * 0.5;
    281             frs = fr * (dx + dy) * 0.5;
    282
    283             return this.context.createRadialGradient(fxs, fys, frs, cxs, cys, rs);
    284         },
    285
    286         // documented in JXG.AbstractRenderer
    287         updateGradient: function(el) {
    288             var col, op,
    289                 ev_g = Type.evaluate(el.visProp.gradient),
    290                 gradient;
    291
    292             op = Type.evaluate(el.visProp.fillopacity);
    293             op = (op > 0) ? op : 0;
    294             col = Type.evaluate(el.visProp.fillcolor);
    295
    296             if (ev_g === 'linear') {
    297                 gradient = this.updateGradientAngle(el, Type.evaluate(el.visProp.gradientangle));
    298             } else if (ev_g === 'radial') {
    299                 gradient = this.updateGradientCircle(el,
    300                     Type.evaluate(el.visProp.gradientcx),
    301                     Type.evaluate(el.visProp.gradientcy),
    302                     Type.evaluate(el.visProp.gradientr),
    303                     Type.evaluate(el.visProp.gradientfx),
    304                     Type.evaluate(el.visProp.gradientfy),
    305                     Type.evaluate(el.visProp.gradientfr)
    306                 );
    307             }
    308             gradient.addColorStop(Type.evaluate(el.visProp.gradientstartoffset), col);
    309             gradient.addColorStop(Type.evaluate(el.visProp.gradientendoffset),
    310                                   Type.evaluate(el.visProp.gradientsecondcolor));
    311         return gradient;
    312         },
    313
    314         /**
    315          * Sets color and opacity for filling and stroking.
    316          * type is the attribute from visProp and targetType the context[targetTypeStyle].
    317          * This is necessary, because the fill style of a text is set by the stroke attributes of the text element.
    318          * @param {JXG.GeometryElement} el Any JSXGraph element.
    319          * @param {String} [type='stroke'] Either <em>fill</em> or <em>stroke</em>.
    320          * @param {String} [targetType=type] (optional) Either <em>fill</em> or <em>stroke</em>.
    321          * @returns {Boolean} If the color could be set, <tt>true</tt> is returned.
    322          * @private
    323          */
    324         _setColor: function (el, type, targetType) {
    325             var hasColor = true,
    326                 ev = el.visProp, hl, sw,
    327                 rgba, rgbo, c, o, oo,
    328                 grad;
    329
    330             type = type || 'stroke';
    331             targetType = targetType || type;
    332
    333             hl = this._getHighlighted(el);
    334
    335             grad = Type.evaluate(el.visProp.gradient);
    336             if (grad === 'linear' || grad === 'radial') {
    337                 // TODO: opacity
    338                 this.context[targetType + 'Style'] = this.updateGradient(el);
    339                 return hasColor;
    340             }
    341
    342             // type is equal to 'fill' or 'stroke'
    343             rgba = Type.evaluate(ev[hl + type + 'color']);
    344             if (rgba !== 'none' && rgba !== false) {
    345                 o = Type.evaluate(ev[hl + type + 'opacity']);
    346                 o = (o > 0) ? o : 0;
    347
    348                 // RGB, not RGBA
    349                 if (rgba.length !== 9) {
    350                     c = rgba;
    351                     oo = o;
    352                 // True RGBA, not RGB
    353                 } else {
    354                     rgbo = Color.rgba2rgbo(rgba);
    355                     c = rgbo[0];
    356                     oo = o * rgbo[1];
    357                 }
    358                 this.context.globalAlpha = oo;
    359
    360                 this.context[targetType + 'Style'] = c;
    361
    362             } else {
    363                 hasColor = false;
    364             }
    365
    366             sw = parseFloat(Type.evaluate(ev[hl + 'strokewidth']));
    367             if (type === 'stroke' && !isNaN(sw)) {
    368                 if (sw === 0) {
    369                     this.context.globalAlpha = 0;
    370                 } else {
    371                     this.context.lineWidth = sw;
    372                 }
    373             }
    374
    375             if (type === 'stroke' && ev.linecap !== undefined && ev.linecap !== '') {
    376                 this.context.lineCap = ev.linecap;
    377             }
    378
    379             return hasColor;
    380         },
    381
    382         /**
    383          * Sets color and opacity for drawing paths and lines and draws the paths and lines.
    384          * @param {JXG.GeometryElement} el An JSXGraph element with a stroke.
    385          * @private
    386          */
    387         _stroke: function (el) {
    388             var context = this.context,
    389                 ev_dash = Type.evaluate(el.visProp.dash);
    390
    391             context.save();
    392
    393             if (ev_dash > 0) {
    394                 if (context.setLineDash) {
    395                     context.setLineDash(this.dashArray[ev_dash]);
    396                 }
    397             } else {
    398                 this.context.lineDashArray = [];
    399             }
    400
    401             if (this._setColor(el, 'stroke')) {
    402                 context.stroke();
    403             }
    404
    405             context.restore();
    406         },
    407
    408         /**
    409          * Translates a set of points.
    410          * @param {Array} shape An array of point coordinates.
    411          * @param {Number} x Translation in X direction.
    412          * @param {Number} y Translation in Y direction.
    413          * @returns {Array} An array of translated point coordinates.
    414          * @private
    415          */
    416         _translateShape: function (shape, x, y) {
    417             var i, rv = [], len = shape.length;
    418
    419             if (len <= 0) {
    420                 return shape;
    421             }
    422
    423             for (i = 0; i < len; i++) {
    424                 rv.push([ shape[i][0] + x, shape[i][1] + y ]);
    425             }
    426
    427             return rv;
    428         },
    429
    430         /* ******************************** *
    431          *    Point drawing and updating    *
    432          * ******************************** */
    433
    434         // documented in AbstractRenderer
    435         drawPoint: function (el) {
    436             var f = Type.evaluate(el.visProp.face),
    437                 size = Type.evaluate(el.visProp.size),
    438                 scr = el.coords.scrCoords,
    439                 sqrt32 = size * Math.sqrt(3) * 0.5,
    440                 s05 = size * 0.5,
    441                 stroke05 = parseFloat(Type.evaluate(el.visProp.strokewidth)) / 2.0,
    442                 context = this.context;
    443
    444             if (!el.visPropCalc.visible) {
    445                 return;
    446             }
    447
    448             switch (f) {
    449             case 'cross':  // x
    450             case 'x':
    451                 context.beginPath();
    452                 context.moveTo(scr[1] - size, scr[2] - size);
    453                 context.lineTo(scr[1] + size, scr[2] + size);
    454                 context.moveTo(scr[1] + size, scr[2] - size);
    455                 context.lineTo(scr[1] - size, scr[2] + size);
    456                 context.lineCap = 'round';
    457                 context.lineJoin = 'round';
    458                 context.closePath();
    459                 this._stroke(el);
    460                 break;
    461             case 'circle': // dot
    462             case 'o':
    463                 context.beginPath();
    464                 context.arc(scr[1], scr[2], size + 1 + stroke05, 0, 2 * Math.PI, false);
    465                 context.closePath();
    466                 this._fill(el);
    467                 this._stroke(el);
    468                 break;
    469             case 'square':  // rectangle
    470             case '[]':
    471                 if (size <= 0) {
    472                     break;
    473                 }
    474
    475                 context.save();
    476                 if (this._setColor(el, 'stroke', 'fill')) {
    477                     context.fillRect(scr[1] - size - stroke05, scr[2] - size - stroke05, size * 2 + 3 * stroke05, size * 2 + 3 * stroke05);
    478                 }
    479                 context.restore();
    480                 context.save();
    481                 this._setColor(el, 'fill');
    482                 context.fillRect(scr[1] - size + stroke05, scr[2] - size + stroke05, size * 2 - stroke05, size * 2 - stroke05);
    483                 context.restore();
    484                 break;
    485             case 'plus':  // +
    486             case '+':
    487                 context.beginPath();
    488                 context.moveTo(scr[1] - size, scr[2]);
    489                 context.lineTo(scr[1] + size, scr[2]);
    490                 context.moveTo(scr[1], scr[2] - size);
    491                 context.lineTo(scr[1], scr[2] + size);
    492                 context.lineCap = 'round';
    493                 context.lineJoin = 'round';
    494                 context.closePath();
    495                 this._stroke(el);
    496                 break;
    497             case 'diamond':   // <>
    498             case '<>':
    499                 context.beginPath();
    500                 context.moveTo(scr[1] - size, scr[2]);
    501                 context.lineTo(scr[1], scr[2] + size);
    502                 context.lineTo(scr[1] + size, scr[2]);
    503                 context.lineTo(scr[1], scr[2] - size);
    504                 context.closePath();
    505                 this._fill(el);
    506                 this._stroke(el);
    507                 break;
    508             case 'triangleup':
    509             case 'a':
    510             case '^':
    511                 context.beginPath();
    512                 context.moveTo(scr[1], scr[2] - size);
    513                 context.lineTo(scr[1] - sqrt32, scr[2] + s05);
    514                 context.lineTo(scr[1] + sqrt32, scr[2] + s05);
    515                 context.closePath();
    516                 this._fill(el);
    517                 this._stroke(el);
    518                 break;
    519             case 'triangledown':
    520             case 'v':
    521                 context.beginPath();
    522                 context.moveTo(scr[1], scr[2] + size);
    523                 context.lineTo(scr[1] - sqrt32, scr[2] - s05);
    524                 context.lineTo(scr[1] + sqrt32, scr[2] - s05);
    525                 context.closePath();
    526                 this._fill(el);
    527                 this._stroke(el);
    528                 break;
    529             case 'triangleleft':
    530             case '<':
    531                 context.beginPath();
    532                 context.moveTo(scr[1] - size, scr[2]);
    533                 context.lineTo(scr[1] + s05, scr[2] - sqrt32);
    534                 context.lineTo(scr[1] + s05, scr[2] + sqrt32);
    535                 context.closePath();
    536                 this._fill(el);
    537                 this._stroke(el);
    538                 break;
    539             case 'triangleright':
    540             case '>':
    541                 context.beginPath();
    542                 context.moveTo(scr[1] + size, scr[2]);
    543                 context.lineTo(scr[1] - s05, scr[2] - sqrt32);
    544                 context.lineTo(scr[1] - s05, scr[2] + sqrt32);
    545                 context.closePath();
    546                 this._fill(el);
    547                 this._stroke(el);
    548                 break;
    549             }
    550         },
    551
    552         // documented in AbstractRenderer
    553         updatePoint: function (el) {
    554             this.drawPoint(el);
    555         },
    556
    557         /* ******************************** *
    558          *           Lines                  *
    559          * ******************************** */
    560
    561         /**
    562          * Draws arrows of an element (usually a line) in canvas renderer.
    563          * @param {JXG.GeometryElement} el Line to be drawn.
    564          * @param {Array} scr1 Screen coordinates of the start position of the line or curve.
    565          * @param {Array} scr2 Screen coordinates of the end position of the line or curve.
    566          * @param {String} hl String which carries information if the element is highlighted. Used for getting the correct attribute.
    567          * @private
    568          */
    569         drawArrows: function (el, scr1, scr2, hl, a) {
    570              var x1, y1, x2, y2,
    571                  w0, w,
    572                  arrowHead,
    573                  arrowTail,
    574                  context = this.context,
    575                  size = 6,
    576                  type = 1,
    577                  type_fa, type_la,
    578                  degree_fa = 1,
    579                  degree_la = 1,
    580                  doFill,
    581                  i, len,
    582                  d1x, d1y, d2x, d2y, last,
    583                  ang1, ang2,
    584                  ev_fa = a.evFirst,
    585                  ev_la = a.evLast;
    586
    587             if (Type.evaluate(el.visProp.strokecolor) !== 'none' &&
    588                      (ev_fa || ev_la)) {
    589
    590                 if (el.elementClass === Const.OBJECT_CLASS_LINE) {
    591                     x1 = scr1.scrCoords[1];
    592                     y1 = scr1.scrCoords[2];
    593                     x2 = scr2.scrCoords[1];
    594                     y2 = scr2.scrCoords[2];
    595                     ang1 = ang2 = Math.atan2(y2 - y1, x2 - x1);
    596                 } else {
    597                     x1 = el.points[0].scrCoords[1];
    598                     y1 = el.points[0].scrCoords[2];
    599
    600                     last = el.points.length - 1;
    601                     if (last < 1) {
    602                         // No arrows for curves consisting of 1 point
    603                         return;
    604                     }
    605                     x2 = el.points[el.points.length - 1].scrCoords[1];
    606                     y2 = el.points[el.points.length - 1].scrCoords[2];
    607
    608                     d1x = el.points[1].scrCoords[1] - el.points[0].scrCoords[1];
    609                     d1y = el.points[1].scrCoords[2] - el.points[0].scrCoords[2];
    610                     d2x = el.points[last].scrCoords[1] - el.points[last - 1].scrCoords[1];
    611                     d2y = el.points[last].scrCoords[2] - el.points[last - 1].scrCoords[2];
    612                     if (ev_fa) {
    613                         ang1 = Math.atan2(d1y, d1x);
    614                     }
    615                     if (ev_la) {
    616                         ang2 = Math.atan2(d2y, d2x);
    617                     }
    618                 }
    619
    620                 w0 = Type.evaluate(el.visProp[hl + 'strokewidth']);
    621
    622                 if (ev_fa) {
    623                     size = a.sizeFirst;
    624
    625                     w = w0 * size;
    626
    627                     type = a.typeFirst;
    628                     type_fa = type;
    629
    630                     if (type === 2) {
    631                         arrowTail = [
    632                                  [ w,      -w * 0.5],
    633                                  [ 0.0,         0.0],
    634                                  [ w,       w * 0.5],
    635                                  [ w * 0.5,     0.0],
    636                              ];
    637                     } else if (type === 3) {
    638                         arrowTail = [
    639                                  [ w / 3.0,   -w * 0.5],
    640                                  [ 0.0,       -w * 0.5],
    641                                  [ 0.0,        w * 0.5],
    642                                  [ w / 3.0,    w * 0.5]
    643                              ];
    644                     } else if (type === 4) {
    645                         w /= 10;
    646                         degree_fa = 3;
    647                         arrowTail = [
    648                             [10.00, 3.31],
    649                             [6.47, 3.84],
    650                             [2.87, 4.50],
    651                             [0.00, 6.63],
    652                             [0.67, 5.52],
    653                             [1.33, 4.42],
    654                             [2.00, 3.31],
    655                             [1.33, 2.21],
    656                             [0.67, 1.10],
    657                             [0.00, 0.00],
    658                             [2.87, 2.13],
    659                             [6.47, 2.79],
    660                             [10.00, 3.31]
    661                         ];
    662                         len = arrowTail.length;
    663                         for (i = 0; i < len; i++) {
    664                             arrowTail[i][0] *= -w;
    665                             arrowTail[i][1] *= w;
    666                             arrowTail[i][0] += 10 * w;
    667                             arrowTail[i][1] -= 3.31 * w;
    668                         }
    669                     } else if (type === 5) {
    670                         w /= 10;
    671                         degree_fa = 3;
    672                         arrowTail = [
    673                             [10.00,3.28],
    674                             [6.61,4.19],
    675                             [3.19,5.07],
    676                             [0.00,6.55],
    677                             [0.62,5.56],
    678                             [1.00,4.44],
    679                             [1.00,3.28],
    680                             [1.00,2.11],
    681                             [0.62,0.99],
    682                             [0.00,0.00],
    683                             [3.19,1.49],
    684                             [6.61,2.37],
    685                             [10.00,3.28]
    686                         ];
    687                         len = arrowTail.length;
    688                         for (i = 0; i < len; i++) {
    689                             arrowTail[i][0] *= -w;
    690                             arrowTail[i][1] *= w;
    691                             arrowTail[i][0] += 10 * w;
    692                             arrowTail[i][1] -= 3.28 * w;
    693                         }
    694                     } else if (type === 6) {
    695                         w /= 10;
    696                         degree_fa = 3;
    697                         arrowTail = [
    698                             [10.00,2.84],
    699                             [6.61,3.59],
    700                             [3.21,4.35],
    701                             [0.00,5.68],
    702                             [0.33,4.73],
    703                             [0.67,3.78],
    704                             [1.00,2.84],
    705                             [0.67,1.89],
    706                             [0.33,0.95],
    707                             [0.00,0.00],
    708                             [3.21,1.33],
    709                             [6.61,2.09],
    710                             [10.00,2.84]
    711                         ];
    712                         len = arrowTail.length;
    713                         for (i = 0; i < len; i++) {
    714                             arrowTail[i][0] *= -w;
    715                             arrowTail[i][1] *= w;
    716                             arrowTail[i][0] += 10 * w;
    717                             arrowTail[i][1] -= 2.84 * w;
    718                         }
    719                     } else if (type === 7) {
    720                         w = w0;
    721                         degree_fa = 3;
    722                         arrowTail = [
    723                             [0.00,10.39],
    724                             [2.01,6.92],
    725                             [5.96,5.20],
    726                             [10.00,5.20],
    727                             [5.96,5.20],
    728                             [2.01,3.47],
    729                             [0.00,0.00]
    730                         ];
    731                         len = arrowTail.length;
    732                         for (i = 0; i < len; i++) {
    733                             arrowTail[i][0] *= -w;
    734                             arrowTail[i][1] *= w;
    735                             arrowTail[i][0] += 10 * w;
    736                             arrowTail[i][1] -= 5.20 * w;
    737                         }
    738                     } else {
    739                         arrowTail = [
    740                              [ w,   -w * 0.5],
    741                              [ 0.0,      0.0],
    742                              [ w,    w * 0.5]
    743                         ];
    744                     }
    745                 }
    746
    747                 if (ev_la) {
    748                     size = a.sizeLast;
    749                     w = w0 * size;
    750
    751                     type = a.typeLast;
    752                     type_la = type;
    753                     if (type === 2) {
    754                         arrowHead = [
    755                              [ -w, -w * 0.5],
    756                              [ 0.0,     0.0],
    757                              [ -w,  w * 0.5],
    758                              [ -w * 0.5, 0.0]
    759                         ];
    760                     } else if (type === 3) {
    761                         arrowHead = [
    762                                  [-w / 3.0,   -w * 0.5],
    763                                  [ 0.0,       -w * 0.5],
    764                                  [ 0.0,        w * 0.5],
    765                                  [-w / 3.0,    w * 0.5]
    766                              ];
    767                     } else if (type === 4) {
    768                         w /= 10;
    769                         degree_la = 3;
    770                         arrowHead = [
    771                             [10.00, 3.31],
    772                             [6.47, 3.84],
    773                             [2.87, 4.50],
    774                             [0.00, 6.63],
    775                             [0.67, 5.52],
    776                             [1.33, 4.42],
    777                             [2.00, 3.31],
    778                             [1.33, 2.21],
    779                             [0.67, 1.10],
    780                             [0.00, 0.00],
    781                             [2.87, 2.13],
    782                             [6.47, 2.79],
    783                             [10.00, 3.31]
    784                         ];
    785                         len = arrowHead.length;
    786                         for (i = 0; i < len; i++) {
    787                             arrowHead[i][0] *= w;
    788                             arrowHead[i][1] *= w;
    789                             arrowHead[i][0] -= 10 * w;
    790                             arrowHead[i][1] -= 3.31 * w;
    791
    792                         }
    793                     } else if (type === 5) {
    794                         w /= 10;
    795                         degree_la = 3;
    796                         arrowHead = [
    797                             [10.00,3.28],
    798                             [6.61,4.19],
    799                             [3.19,5.07],
    800                             [0.00,6.55],
    801                             [0.62,5.56],
    802                             [1.00,4.44],
    803                             [1.00,3.28],
    804                             [1.00,2.11],
    805                             [0.62,0.99],
    806                             [0.00,0.00],
    807                             [3.19,1.49],
    808                             [6.61,2.37],
    809                             [10.00,3.28]
    810                         ];
    811                         len = arrowHead.length;
    812                         for (i = 0; i < len; i++) {
    813                             arrowHead[i][0] *= w;
    814                             arrowHead[i][1] *= w;
    815                             arrowHead[i][0] -= 10 * w;
    816                             arrowHead[i][1] -= 3.28 * w;
    817
    818                         }
    819                     } else if (type === 6) {
    820                         w /= 10;
    821                         degree_la = 3;
    822                         arrowHead = [
    823                             [10.00,2.84],
    824                             [6.61,3.59],
    825                             [3.21,4.35],
    826                             [0.00,5.68],
    827                             [0.33,4.73],
    828                             [0.67,3.78],
    829                             [1.00,2.84],
    830                             [0.67,1.89],
    831                             [0.33,0.95],
    832                             [0.00,0.00],
    833                             [3.21,1.33],
    834                             [6.61,2.09],
    835                             [10.00,2.84]
    836                         ];
    837                         len = arrowHead.length;
    838                         for (i = 0; i < len; i++) {
    839                             arrowHead[i][0] *= w;
    840                             arrowHead[i][1] *= w;
    841                             arrowHead[i][0] -= 10 * w;
    842                             arrowHead[i][1] -= 2.84 * w;
    843
    844                         }
    845
    846                     } else if (type === 7) {
    847                         w = w0;
    848                         degree_la = 3;
    849                         arrowHead = [
    850                             [0.00,10.39],
    851                             [2.01,6.92],
    852                             [5.96,5.20],
    853                             [10.00,5.20],
    854                             [5.96,5.20],
    855                             [2.01,3.47],
    856                             [0.00,0.00]
    857                         ];
    858                         len = arrowHead.length;
    859                         for (i = 0; i < len; i++) {
    860                             arrowHead[i][0] *= w;
    861                             arrowHead[i][1] *= w;
    862                             arrowHead[i][0] -= 10 * w;
    863                             arrowHead[i][1] -= 5.20 * w;
    864
    865                         }
    866                     } else {
    867                         arrowHead = [
    868                              [ -w, -w * 0.5],
    869                              [ 0.0,     0.0],
    870                              [ -w,  w * 0.5]
    871                          ];
    872                     }
    873                 }
    874
    875                 context.save();
    876                 if (this._setColor(el, 'stroke', 'fill')) {
    877                     this._setColor(el, 'stroke');
    878                     if (ev_fa) {
    879                         if (type_fa === 7) {
    880                             doFill = false;
    881                         } else {
    882                             doFill = true;
    883                         }
    884                         this._drawPolygon(this._translateShape(this._rotateShape(arrowTail, ang1), x1, y1), degree_fa, doFill);
    885                     }
    886                     if (ev_la) {
    887                         if (type_la === 7) {
    888                             doFill = false;
    889                         } else {
    890                             doFill = true;
    891                         }
    892                         this._drawPolygon(this._translateShape(this._rotateShape(arrowHead, ang2), x2, y2), degree_la, doFill);
    893                     }
    894                 }
    895                 context.restore();
    896             }
    897         },
    898
    899         // documented in AbstractRenderer
    900         drawLine: function (el) {
    901             var c1_org, c2_org,
    902                 c1 = new Coords(Const.COORDS_BY_USER, el.point1.coords.usrCoords, el.board),
    903                 c2 = new Coords(Const.COORDS_BY_USER, el.point2.coords.usrCoords, el.board),
    904                 margin = null,
    905                 hl, w, arrowData;
    906
    907             if (!el.visPropCalc.visible) {
    908                 return;
    909             }
    910
    911             hl = this._getHighlighted(el);
    912             w = Type.evaluate(el.visProp[hl + 'strokewidth']);
    913             arrowData = this.getArrowHeadData(el, w, hl);
    914
    915             if (arrowData.evFirst || arrowData.evLast) {
    916                 margin = -4;
    917             }
    918             Geometry.calcStraight(el, c1, c2, margin);
    919             this.handleTouchpoints(el, c1, c2, arrowData);
    920
    921             c1_org = new Coords(Const.COORDS_BY_USER, c1.usrCoords, el.board);
    922             c2_org = new Coords(Const.COORDS_BY_USER, c2.usrCoords, el.board);
    923
    924             this.getPositionArrowHead(el, c1, c2, arrowData);
    925
    926             this.context.beginPath();
    927             this.context.moveTo(c1.scrCoords[1], c1.scrCoords[2]);
    928             this.context.lineTo(c2.scrCoords[1], c2.scrCoords[2]);
    929             this._stroke(el);
    930
    931             if ((arrowData.evFirst/* && obj.sFirst > 0*/) ||
    932                 (arrowData.evLast/* && obj.sLast > 0*/)) {
    933
    934                 this.drawArrows(el, c1_org, c2_org, hl, arrowData);
    935             }
    936         },
    937
    938         // documented in AbstractRenderer
    939         updateLine: function (el) {
    940             this.drawLine(el);
    941         },
    942
    943         // documented in AbstractRenderer
    944         drawTicks: function () {
    945             // this function is supposed to initialize the svg/vml nodes in the SVG/VMLRenderer.
    946             // but in canvas there are no such nodes, hence we just do nothing and wait until
    947             // updateTicks is called.
    948         },
    949
    950         // documented in AbstractRenderer
    951         updateTicks: function (ticks) {
    952             var i, c, x, y,
    953                 len = ticks.ticks.length,
    954                 len2, j,
    955                 context = this.context;
    956
    957             context.beginPath();
    958             for (i = 0; i < len; i++) {
    959                 c = ticks.ticks[i];
    960                 x = c[0];
    961                 y = c[1];
    962
    963                 // context.moveTo(x[0], y[0]);
    964                 // context.lineTo(x[1], y[1]);
    965                 len2 = x.length;
    966                 context.moveTo(x[0], y[0]);
    967                 for (j = 1; j < len2; ++j) {
    968                     context.lineTo(x[j], y[j]);
    969                 }
    970
    971             }
    972             // Labels
    973             // for (i = 0; i < len; i++) {
    974             //     c = ticks.ticks[i].scrCoords;
    975             //     if (ticks.ticks[i].major &&
    976             //             (ticks.board.needsFullUpdate || ticks.needsRegularUpdate) &&
    977             //             ticks.labels[i] &&
    978             //             ticks.labels[i].visPropCalc.visible) {
    979             //         this.updateText(ticks.labels[i]);
    980             //     }
    981             // }
    982             context.lineCap = 'round';
    983             this._stroke(ticks);
    984         },
    985
    986         /* **************************
    987          *    Curves
    988          * **************************/
    989
    990         // documented in AbstractRenderer
    991         drawCurve: function (el) {
    992             var hl, w, arrowData;
    993
    994             if (Type.evaluate(el.visProp.handdrawing)) {
    995                 this.updatePathStringBezierPrim(el);
    996             } else {
    997                 this.updatePathStringPrim(el);
    998             }
    999             if (el.numberPoints > 1) {
    1000                 hl = this._getHighlighted(el);
    1001                 w = Type.evaluate(el.visProp[hl + 'strokewidth']);
    1002                 arrowData = this.getArrowHeadData(el, w, hl);
    1003                 if ((arrowData.evFirst/* && obj.sFirst > 0*/) ||
    1004                     (arrowData.evLast/* && obj.sLast > 0*/)) {
    1005                     this.drawArrows(el, null, null, hl, arrowData);
    1006                 }
    1007             }
    1008         },
    1009
    1010         // documented in AbstractRenderer
    1011         updateCurve: function (el) {
    1012             this.drawCurve(el);
    1013         },
    1014
    1015         /* **************************
    1016          *    Circle related stuff
    1017          * **************************/
    1018
    1019         // documented in AbstractRenderer
    1020         drawEllipse: function (el) {
    1021             var m1 = el.center.coords.scrCoords[1],
    1022                 m2 = el.center.coords.scrCoords[2],
    1023                 sX = el.board.unitX,
    1024                 sY = el.board.unitY,
    1025                 rX = 2 * el.Radius(),
    1026                 rY = 2 * el.Radius(),
    1027                 aWidth = rX * sX,
    1028                 aHeight = rY * sY,
    1029                 aX = m1 - aWidth / 2,
    1030                 aY = m2 - aHeight / 2,
    1031                 hB = (aWidth / 2) * 0.5522848,
    1032                 vB = (aHeight / 2) * 0.5522848,
    1033                 eX = aX + aWidth,
    1034                 eY = aY + aHeight,
    1035                 mX = aX + aWidth / 2,
    1036                 mY = aY + aHeight / 2,
    1037                 context = this.context;
    1038
    1039             if (rX > 0.0 && rY > 0.0 && !isNaN(m1 + m2)) {
    1040                 context.beginPath();
    1041                 context.moveTo(aX, mY);
    1042                 context.bezierCurveTo(aX, mY - vB, mX - hB, aY, mX, aY);
    1043                 context.bezierCurveTo(mX + hB, aY, eX, mY - vB, eX, mY);
    1044                 context.bezierCurveTo(eX, mY + vB, mX + hB, eY, mX, eY);
    1045                 context.bezierCurveTo(mX - hB, eY, aX, mY + vB, aX, mY);
    1046                 context.closePath();
    1047                 this._fill(el);
    1048                 this._stroke(el);
    1049             }
    1050         },
    1051
    1052         // documented in AbstractRenderer
    1053         updateEllipse: function (el) {
    1054             return this.drawEllipse(el);
    1055         },
    1056
    1057         /* **************************
    1058          *    Polygon
    1059          * **************************/
    1060
    1061         // nothing here, using AbstractRenderer implementations
    1062
    1063         /* **************************
    1064          *    Text related stuff
    1065          * **************************/
    1066
    1067         // Already documented in JXG.AbstractRenderer
    1068         displayCopyright: function (str, fontSize) {
    1069             var context = this.context;
    1070
    1071             // this should be called on EVERY update, otherwise it won't be shown after the first update
    1072             context.save();
    1073             context.font = fontSize + 'px Arial';
    1074             context.fillStyle = '#aaa';
    1075             context.lineWidth = 0.5;
    1076             context.fillText(str, 10, 2 + fontSize);
    1077             context.restore();
    1078         },
    1079
    1080         // Already documented in JXG.AbstractRenderer
    1081         drawInternalText: function (el) {
    1082             var ev_fs = Type.evaluate(el.visProp.fontsize),
    1083                 fontUnit = Type.evaluate(el.visProp.fontunit),
    1084                 ev_ax = el.getAnchorX(),
    1085                 ev_ay = el.getAnchorY(),
    1086                 context = this.context;
    1087
    1088             context.save();
    1089             if (this._setColor(el, 'stroke', 'fill') &&
    1090                     !isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) {
    1091                 context.font = (ev_fs > 0 ? ev_fs : 0) + fontUnit + ' Arial';
    1092
    1093                 this.transformImage(el, el.transformations);
    1094                 if (ev_ax === 'left') {
    1095                     context.textAlign = 'left';
    1096                 } else if (ev_ax === 'right') {
    1097                     context.textAlign = 'right';
    1098                 } else if (ev_ax === 'middle') {
    1099                     context.textAlign = 'center';
    1100                 }
    1101                 if (ev_ay === 'bottom') {
    1102                     context.textBaseline = 'bottom';
    1103                 } else if (ev_ay === 'top') {
    1104                     context.textBaseline = 'top';
    1105                 } else if (ev_ay === 'middle') {
    1106                     context.textBaseline = 'middle';
    1107                 }
    1108                 context.fillText(el.plaintext, el.coords.scrCoords[1], el.coords.scrCoords[2]);
    1109             }
    1110             context.restore();
    1111             return null;
    1112         },
    1113
    1114         // Already documented in JXG.AbstractRenderer
    1115         updateInternalText: function (el) {
    1116             this.drawInternalText(el);
    1117         },
    1118
    1119         // documented in JXG.AbstractRenderer
    1120         // Only necessary for texts
    1121         setObjectStrokeColor: function (el, color, opacity) {
    1122             var rgba = Type.evaluate(color), c, rgbo,
    1123                 o = Type.evaluate(opacity), oo,
    1124                 node;
    1125
    1126             o = (o > 0) ? o : 0;
    1127
    1128             if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
    1129                 return;
    1130             }
    1131
    1132             // Check if this could be merged with _setColor
    1133
    1134             if (Type.exists(rgba) && rgba !== false) {
    1135                 // RGB, not RGBA
    1136                 if (rgba.length !== 9) {
    1137                     c = rgba;
    1138                     oo = o;
    1139                 // True RGBA, not RGB
    1140                 } else {
    1141                     rgbo = Color.rgba2rgbo(rgba);
    1142                     c = rgbo[0];
    1143                     oo = o * rgbo[1];
    1144                 }
    1145                 node = el.rendNode;
    1146                 if (el.elementClass === Const.OBJECT_CLASS_TEXT && Type.evaluate(el.visProp.display) === 'html') {
    1147                     node.style.color = c;
    1148                     node.style.opacity = oo;
    1149                 }
    1150             }
    1151
    1152             el.visPropOld.strokecolor = rgba;
    1153             el.visPropOld.strokeopacity = o;
    1154         },
    1155
    1156         /* **************************
    1157          *    Image related stuff
    1158          * **************************/
    1159
    1160         // Already documented in JXG.AbstractRenderer
    1161         drawImage: function (el) {
    1162             el.rendNode = new Image();
    1163             // Store the file name of the image.
    1164             // Before, this was done in el.rendNode.src
    1165             // But there, the file name is expanded to
    1166             // the full url. This may be different from
    1167             // the url computed in updateImageURL().
    1168             el._src = '';
    1169             this.updateImage(el);
    1170         },
    1171
    1172         // Already documented in JXG.AbstractRenderer
    1173         updateImage: function (el) {
    1174             var context = this.context,
    1175                 o = Type.evaluate(el.visProp.fillopacity),
    1176                 paintImg = Type.bind(function () {
    1177                     el.imgIsLoaded = true;
    1178                     if (el.size[0] <= 0 || el.size[1] <= 0) {
    1179                         return;
    1180                     }
    1181                     context.save();
    1182                     context.globalAlpha = o;
    1183                     // If det(el.transformations)=0, FireFox 3.6. breaks down.
    1184                     // This is tested in transformImage
    1185                     this.transformImage(el, el.transformations);
    1186                     context.drawImage(el.rendNode,
    1187                         el.coords.scrCoords[1],
    1188                         el.coords.scrCoords[2] - el.size[1],
    1189                         el.size[0],
    1190                         el.size[1]);
    1191                     context.restore();
    1192                 }, this);
    1193
    1194             if (this.updateImageURL(el)) {
    1195                 el.rendNode.onload = paintImg;
    1196             } else {
    1197                 if (el.imgIsLoaded) {
    1198                     paintImg();
    1199                 }
    1200             }
    1201         },
    1202
    1203         // Already documented in JXG.AbstractRenderer
    1204         transformImage: function (el, t) {
    1205             var m, len = t.length,
    1206                 ctx = this.context;
    1207
    1208             if (len > 0) {
    1209                 m = this.joinTransforms(el, t);
    1210                 if (Math.abs(Numerics.det(m)) >= Mat.eps) {
    1211                     ctx.transform(m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]);
    1212                 }
    1213             }
    1214         },
    1215
    1216         // Already documented in JXG.AbstractRenderer
    1217         updateImageURL: function (el) {
    1218             var url;
    1219
    1220             url = Type.evaluate(el.url);
    1221             if (el._src !== url) {
    1222                 el.imgIsLoaded = false;
    1223                 el.rendNode.src = url;
    1224                 el._src = url;
    1225                 return true;
    1226             }
    1227
    1228             return false;
    1229         },
    1230
    1231         /* **************************
    1232          * Render primitive objects
    1233          * **************************/
    1234
    1235         // documented in AbstractRenderer
    1236         remove: function (shape) {
    1237             // sounds odd for a pixel based renderer but we need this for html texts
    1238             if (Type.exists(shape) && Type.exists(shape.parentNode)) {
    1239                 shape.parentNode.removeChild(shape);
    1240             }
    1241         },
    1242
    1243         // documented in AbstractRenderer
    1244         updatePathStringPrim: function (el) {
    1245             var i, scr, scr1, scr2, len,
    1246                 symbm = 'M',
    1247                 symbl = 'L',
    1248                 symbc = 'C',
    1249                 nextSymb = symbm,
    1250                 maxSize = 5000.0,
    1251                 context = this.context;
    1252
    1253             if (el.numberPoints <= 0) {
    1254                 return;
    1255             }
    1256
    1257             len = Math.min(el.points.length, el.numberPoints);
    1258             context.beginPath();
    1259
    1260             if (el.bezierDegree === 1) {
    1261                 /*
    1262                 if (isNotPlot && el.board.options.curve.RDPsmoothing) {
    1263                     el.points = Numerics.RamerDouglasPeucker(el.points, 0.5);
    1264                 }
    1265                 */
    1266
    1267                 for (i = 0; i < len; i++) {
    1268                     scr = el.points[i].scrCoords;
    1269
    1270                     if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
    1271                         nextSymb = symbm;
    1272                     } else {
    1273                         // Chrome has problems with values  being too far away.
    1274                         if (scr[1] > maxSize) {
    1275                             scr[1] = maxSize;
    1276                         } else if (scr[1] < -maxSize) {
    1277                             scr[1] = -maxSize;
    1278                         }
    1279
    1280                         if (scr[2] > maxSize) {
    1281                             scr[2] = maxSize;
    1282                         } else if (scr[2] < -maxSize) {
    1283                             scr[2] = -maxSize;
    1284                         }
    1285
    1286                         if (nextSymb === symbm) {
    1287                             context.moveTo(scr[1], scr[2]);
    1288                         } else {
    1289                             context.lineTo(scr[1], scr[2]);
    1290                         }
    1291                         nextSymb = symbl;
    1292                     }
    1293                 }
    1294             } else if (el.bezierDegree === 3) {
    1295                 i = 0;
    1296                 while (i < len) {
    1297                     scr = el.points[i].scrCoords;
    1298                     if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
    1299                         nextSymb = symbm;
    1300                     } else {
    1301                         if (nextSymb === symbm) {
    1302                             context.moveTo(scr[1], scr[2]);
    1303                         } else {
    1304                             i += 1;
    1305                             scr1 = el.points[i].scrCoords;
    1306                             i += 1;
    1307                             scr2 = el.points[i].scrCoords;
    1308                             context.bezierCurveTo(scr[1], scr[2], scr1[1], scr1[2], scr2[1], scr2[2]);
    1309                         }
    1310                         nextSymb = symbc;
    1311                     }
    1312                     i += 1;
    1313                 }
    1314             }
    1315             context.lineCap = 'round';
    1316             this._fill(el);
    1317             this._stroke(el);
    1318         },
    1319
    1320         // Already documented in JXG.AbstractRenderer
    1321         updatePathStringBezierPrim: function (el) {
    1322             var i, j, k, scr, lx, ly, len,
    1323                 symbm = 'M',
    1324                 symbl = 'C',
    1325                 nextSymb = symbm,
    1326                 maxSize = 5000.0,
    1327                 f = Type.evaluate(el.visProp.strokewidth),
    1328                 isNoPlot = (Type.evaluate(el.visProp.curvetype) !== 'plot'),
    1329                 context = this.context;
    1330
    1331             if (el.numberPoints <= 0) {
    1332                 return;
    1333             }
    1334
    1335             if (isNoPlot && el.board.options.curve.RDPsmoothing) {
    1336                 el.points = Numerics.RamerDouglasPeucker(el.points, 0.5);
    1337             }
    1338
    1339             len = Math.min(el.points.length, el.numberPoints);
    1340             context.beginPath();
    1341
    1342             for (j = 1; j < 3; j++) {
    1343                 nextSymb = symbm;
    1344                 for (i = 0; i < len; i++) {
    1345                     scr = el.points[i].scrCoords;
    1346
    1347                     if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
    1348                         nextSymb = symbm;
    1349                     } else {
    1350                         // Chrome has problems with values being too far away.
    1351                         if (scr[1] > maxSize) {
    1352                             scr[1] = maxSize;
    1353                         } else if (scr[1] < -maxSize) {
    1354                             scr[1] = -maxSize;
    1355                         }
    1356
    1357                         if (scr[2] > maxSize) {
    1358                             scr[2] = maxSize;
    1359                         } else if (scr[2] < -maxSize) {
    1360                             scr[2] = -maxSize;
    1361                         }
    1362
    1363                         if (nextSymb === symbm) {
    1364                             context.moveTo(scr[1], scr[2]);
    1365                         } else {
    1366                             k = 2 * j;
    1367                             context.bezierCurveTo(
    1368                                 (lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j)),
    1369                                 (ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j)),
    1370                                 (lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j)),
    1371                                 (ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j)),
    1372                                 scr[1],
    1373                                 scr[2]
    1374                             );
    1375                         }
    1376                         nextSymb = symbl;
    1377                         lx = scr[1];
    1378                         ly = scr[2];
    1379                     }
    1380                 }
    1381             }
    1382             context.lineCap = 'round';
    1383             this._fill(el);
    1384             this._stroke(el);
    1385         },
    1386
    1387         // documented in AbstractRenderer
    1388         updatePolygonPrim: function (node, el) {
    1389             var scrCoords, i, j,
    1390                 len = el.vertices.length,
    1391                 context = this.context,
    1392                 isReal = true;
    1393
    1394             if (len <= 0 || !el.visPropCalc.visible) {
    1395                 return;
    1396             }
    1397             if (el.elType === 'polygonalchain') {
    1398                 len++;
    1399             }
    1400
    1401             context.beginPath();
    1402             i = 0;
    1403             while (!el.vertices[i].isReal && i < len - 1) {
    1404                 i++;
    1405                 isReal = false;
    1406             }
    1407             scrCoords = el.vertices[i].coords.scrCoords;
    1408             context.moveTo(scrCoords[1], scrCoords[2]);
    1409
    1410             for (j = i; j < len - 1; j++) {
    1411                 if (!el.vertices[j].isReal) {
    1412                     isReal = false;
    1413                 }
    1414                 scrCoords = el.vertices[j].coords.scrCoords;
    1415                 context.lineTo(scrCoords[1], scrCoords[2]);
    1416             }
    1417             context.closePath();
    1418
    1419             if (isReal) {
    1420                 this._fill(el);    // The edges of a polygon are displayed separately (as segments).
    1421             }
    1422         },
    1423
    1424         // **************************  Set Attributes *************************
    1425
    1426         // Already documented in JXG.AbstractRenderer
    1427         display: function(el, val) {
    1428              if (el && el.rendNode) {
    1429                  el.visPropOld.visible = val;
    1430                  if (val) {
    1431                      el.rendNode.style.visibility = "inherit";
    1432                  } else {
    1433                      el.rendNode.style.visibility = "hidden";
    1434                  }
    1435              }
    1436          },
    1437
    1438         // documented in AbstractRenderer
    1439         show: function (el) {
    1440             JXG.deprecated('Board.renderer.show()', 'Board.renderer.display()');
    1441
    1442             if (Type.exists(el.rendNode)) {
    1443                 el.rendNode.style.visibility = "inherit";
    1444             }
    1445         },
    1446
    1447         // documented in AbstractRenderer
    1448         hide: function (el) {
    1449             JXG.deprecated('Board.renderer.hide()', 'Board.renderer.display()');
    1450
    1451             if (Type.exists(el.rendNode)) {
    1452                 el.rendNode.style.visibility = "hidden";
    1453             }
    1454         },
    1455
    1456         // documented in AbstractRenderer
    1457         setGradient: function (el) {
    1458             var // col,
    1459                 op;
    1460
    1461             op = Type.evaluate(el.visProp.fillopacity);
    1462             op = (op > 0) ? op : 0;
    1463
    1464             // col = Type.evaluate(el.visProp.fillcolor);
    1465         },
    1466
    1467         // documented in AbstractRenderer
    1468         setShadow: function (el) {
    1469             if (el.visPropOld.shadow === el.visProp.shadow) {
    1470                 return;
    1471             }
    1472
    1473             // not implemented yet
    1474             // we simply have to redraw the element
    1475             // probably the best way to do so would be to call el.updateRenderer(), i think.
    1476
    1477             el.visPropOld.shadow = el.visProp.shadow;
    1478         },
    1479
    1480         // documented in AbstractRenderer
    1481         highlight: function (obj) {
    1482             if (obj.elementClass === Const.OBJECT_CLASS_TEXT && Type.evaluate(obj.visProp.display) === 'html') {
    1483                 this.updateTextStyle(obj, true);
    1484             } else {
    1485                 obj.board.prepareUpdate();
    1486                 obj.board.renderer.suspendRedraw(obj.board);
    1487                 obj.board.updateRenderer();
    1488                 obj.board.renderer.unsuspendRedraw();
    1489             }
    1490             return this;
    1491         },
    1492
    1493         // documented in AbstractRenderer
    1494         noHighlight: function (obj) {
    1495             if (obj.elementClass === Const.OBJECT_CLASS_TEXT && Type.evaluate(obj.visProp.display) === 'html') {
    1496                 this.updateTextStyle(obj, false);
    1497             } else {
    1498                 obj.board.prepareUpdate();
    1499                 obj.board.renderer.suspendRedraw(obj.board);
    1500                 obj.board.updateRenderer();
    1501                 obj.board.renderer.unsuspendRedraw();
    1502             }
    1503             return this;
    1504         },
    1505
    1506         /* **************************
    1507          * renderer control
    1508          * **************************/
    1509
    1510         // documented in AbstractRenderer
    1511         suspendRedraw: function (board) {
    1512             this.context.save();
    1513             this.context.clearRect(0, 0, this.canvasRoot.width, this.canvasRoot.height);
    1514
    1515             if (board && board.attr.showcopyright) {
    1516                 this.displayCopyright(JXG.licenseText, 12);
    1517             }
    1518         },
    1519
    1520         // documented in AbstractRenderer
    1521         unsuspendRedraw: function () {
    1522             this.context.restore();
    1523         },
    1524
    1525         // document in AbstractRenderer
    1526         resize: function (w, h) {
    1527             if (this.container) {
    1528                 this.canvasRoot.style.width = parseFloat(w) + 'px';
    1529                 this.canvasRoot.style.height = parseFloat(h) + 'px';
    1530
    1531                 this.canvasRoot.setAttribute('width', (2 * parseFloat(w)) + 'px');
    1532                 this.canvasRoot.setAttribute('height',(2 * parseFloat(h)) + 'px');
    1533             } else {
    1534                 this.canvasRoot.width = 2 * parseFloat(w);
    1535                 this.canvasRoot.height = 2 * parseFloat(h);
    1536             }
    1537             this.context = this.canvasRoot.getContext('2d');
    1538             // The width and height of the canvas is set to twice the CSS values,
    1539             // followed by an appropiate scaling.
    1540             // See http://stackoverflow.com/questions/22416462/canvas-element-with-blurred-lines
    1541             this.context.scale(2, 2);
    1542         },
    1543
    1544         removeToInsertLater: function () {
    1545             return function () {};
    1546         }
    1547     });
    1548
    1549     return JXG.CanvasRenderer;
    1550 });
    1551