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