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