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