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