1 /*
  2     Copyright 2010-2011
  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: you can redistribute it and/or modify
 13     it under the terms of the GNU Lesser General Public License as published by
 14     the Free Software Foundation, either version 3 of the License, or
 15     (at your option) any later version.
 16 
 17     JSXGraph is distributed in the hope that it will be useful,
 18     but WITHOUT ANY WARRANTY; without even the implied warranty of
 19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 20     GNU Lesser General Public License for more details.
 21 
 22     You should have received a copy of the GNU Lesser General Public License
 23     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
 24 */
 25 
 26 /*jshint bitwise: false, curly: true, debug: false, eqeqeq: true, devel: false, evil: false,
 27   forin: false, immed: true, laxbreak: false, newcap: false, noarg: true, nonew: true, onevar: true,
 28    undef: true, white: false, sub: false*/
 29 /*global JXG: true, AMprocessNode: true, document: true, Image: true */
 30 
 31 /**
 32  * Uses HTML Canvas to implement the rendering methods defined in {@link JXG.AbstractRenderer}.
 33  * @class JXG.AbstractRenderer
 34  * @augments JXG.AbstractRenderer
 35  * @param {Node} container Reference to a DOM node containing the board.
 36  * @see JXG.AbstractRenderer
 37  */
 38 JXG.CanvasRenderer = function (container) {
 39     var i;
 40 
 41     this.type = 'canvas';
 42 
 43     this.canvasRoot = null;
 44     this.suspendHandle = null;
 45     this.canvasId = JXG.Util.genUUID();
 46 
 47     this.canvasNamespace = null;
 48 
 49     this.container = container;
 50     this.container.style.MozUserSelect = 'none';
 51 
 52     this.container.style.overflow = 'hidden';
 53     if (this.container.style.position === '') {
 54         this.container.style.position = 'relative';
 55     }
 56 
 57     this.container.innerHTML = ['<canvas id="', this.canvasId, '" width="', JXG.getStyle(this.container, 'width'), '" height="', JXG.getStyle(this.container, 'height'), '"><', '/canvas>'].join('');
 58     this.canvasRoot = document.getElementById(this.canvasId);
 59     this.context =  this.canvasRoot.getContext('2d');
 60 
 61     this.dashArray = [[2, 2], [5, 5], [10, 10], [20, 20], [20, 10, 10, 10], [20, 5, 10, 5]];
 62 };
 63 
 64 JXG.CanvasRenderer.prototype = new JXG.AbstractRenderer();
 65 
 66 JXG.extend(JXG.CanvasRenderer.prototype, /** @lends JXG.CanvasRenderer.prototype */ {
 67 
 68     /* **************************
 69      *   private methods only used
 70      *   in this renderer. Should
 71      *   not be called from outside.
 72      * **************************/
 73 
 74     /**
 75      * Draws a filled polygon.
 76      * @param {Array} shape A matrix presented by a two dimensional array of numbers.
 77      * @see JXG.AbstractRenderer#makeArrows
 78      * @private
 79      */
 80     _drawFilledPolygon: function (shape) {
 81         var i, len = shape.length,
 82             context = this.context;
 83 
 84         if (len > 0) {
 85             context.beginPath();
 86             context.moveTo(shape[0][0], shape[0][1]);
 87             for (i = 0; i < len; i++) {
 88                 if (i > 0) {
 89                     context.lineTo(shape[i][0], shape[i][1]);
 90                 }
 91             }
 92             context.lineTo(shape[0][0], shape[0][1]);
 93             context.fill();
 94         }
 95     },
 96 
 97     /**
 98      * Sets the fill color and fills an area.
 99      * @param {JXG.GeometryElement} element An arbitrary JSXGraph element, preferably one with an area.
100      * @private
101      */
102     _fill: function (element) {
103         var context = this.context;
104 
105         context.save();
106         if (this._setColor(element, 'fill')) {
107             context.fill();
108         }
109         context.restore();
110     },
111 
112     /**
113      * Rotates a point around <tt>(0, 0)</tt> by a given angle.
114      * @param {Number} angle An angle, given in rad.
115      * @param {Number} x X coordinate of the point.
116      * @param {Number} y Y coordinate of the point.
117      * @returns {Array} An array containing the x and y coordinate of the rotated point.
118      * @private
119      */
120     _rotatePoint: function (angle, x, y) {
121         return [
122             (x * Math.cos(angle)) - (y * Math.sin(angle)),
123             (x * Math.sin(angle)) + (y * Math.cos(angle))
124         ];
125     },
126 
127     /**
128      * Rotates an array of points around <tt>(0, 0)</tt>.
129      * @param {Array} shape An array of array of point coordinates.
130      * @param {Number} angle The angle in rad the points are rotated by.
131      * @returns {Array} Array of array of two dimensional point coordinates.
132      * @private
133      */
134     _rotateShape: function (shape, angle) {
135         var i, rv = [], len = shape.length;
136 
137         if (len <= 0) {
138             return shape;
139         }
140 
141         for (i = 0; i < len; i++) {
142             rv.push(this._rotatePoint(angle, shape[i][0], shape[i][1]));
143         }
144 
145         return rv;
146     },
147 
148     /**
149      * Sets color and opacity for filling and stroking.
150      * type is the attribute from visProp and targetType the context[targetTypeStyle].
151      * This is necessary, because the fill style of a text is set by the stroke attributes of the text element.
152      * @param {JXG.GeometryElement} element Any JSXGraph element.
153      * @param {String} [type='stroke'] Either <em>fill</em> or <em>stroke</em>.
154      * @param {String} [targetType=type] (optional) Either <em>fill</em> or <em>stroke</em>.
155      * @returns {Boolean} If the color could be set, <tt>true</tt> is returned.
156      * @private
157      */
158     _setColor: function (element, type, targetType) {
159         var hasColor = true, isTrace = false, 
160             ev = element.visProp, hl,
161             rgba, rgbo, c, o, oo;
162         
163         type = type || 'stroke';
164         targetType = targetType || type;
165         if (!JXG.exists(element.board) || !JXG.exists(element.board.highlightedObjects)) {
166             // This case handles trace elements.
167             // To make them work, we simply neglect highlighting.
168             isTrace = true;
169         }
170 
171         if (!isTrace && JXG.exists(element.board.highlightedObjects[element.id])) {
172             hl = 'highlight';
173         } else {
174             hl = '';
175         }
176         
177         // type is equal to 'fill' or 'stroke'
178         rgba = JXG.evaluate(ev[hl+type+'color']);
179         if (rgba !== 'none' && rgba !== false ) {
180             o = JXG.evaluate(ev[hl+type+'opacity']);
181             o = (o > 0) ? o : 0;
182             if (rgba.length!=9) {          // RGB, not RGBA
183                 c = rgba;
184                 oo = o;
185             } else {                       // True RGBA, not RGB
186                 rgbo = JXG.rgba2rgbo(rgba);
187                 c = rgbo[0];
188                 oo = o*rgbo[1];
189             }
190             this.context.globalAlpha = oo;
191             this.context[targetType+'Style'] = c;
192         } else {
193             hasColor = false;
194         }
195         if (type === 'stroke') {
196             this.context.lineWidth = parseFloat(ev.strokewidth);
197         }
198         return hasColor;
199     },
200 
201 
202     /**
203      * Sets color and opacity for drawing paths and lines and draws the paths and lines.
204      * @param {JXG.GeometryElement} element An JSXGraph element with a stroke.
205      * @private
206      */
207     _stroke: function (element) {
208         var context = this.context;
209 
210         context.save();
211 
212         if (element.visProp.dash > 0) {
213             if (context.setLineDash) {
214                 context.setLineDash(this.dashArray[element.visProp.dash]);
215             }
216         } else {
217             this.context.lineDashArray = [];
218         }
219 
220         if (this._setColor(element, 'stroke')) {
221             context.stroke();
222         }
223         context.restore();
224     },
225 
226     /**
227      * Translates a set of points.
228      * @param {Array} shape An array of point coordinates.
229      * @param {Number} x Translation in X direction.
230      * @param {Number} y Translation in Y direction.
231      * @returns {Array} An array of translated point coordinates.
232      * @private
233      */
234     _translateShape: function (shape, x, y) {
235         var i, rv = [], len = shape.length;
236 
237         if (len <= 0) {
238             return shape;
239         }
240 
241         for (i = 0; i < len; i++) {
242             rv.push([ shape[i][0] + x, shape[i][1] + y ]);
243         }
244 
245         return rv;
246     },
247 
248     /* ******************************** *
249      *    Point drawing and updating    *
250      * ******************************** */
251 
252     // documented in AbstractRenderer
253     drawPoint: function (el) {
254         var f = el.visProp.face,
255             size = el.visProp.size,
256             scr = el.coords.scrCoords,
257             sqrt32 = size * Math.sqrt(3) * 0.5,
258             s05 = size * 0.5,
259             stroke05 = parseFloat(el.visProp.strokewidth) / 2.0,
260             context = this.context;
261 
262         if (size <= 0 || !el.visProp.visible) {
263             return;
264         }
265 
266         switch (f) {
267             case 'cross':  // x
268             case 'x':
269                 context.beginPath();
270                 context.moveTo(scr[1] - size, scr[2] - size);
271                 context.lineTo(scr[1] + size, scr[2] + size);
272                 context.moveTo(scr[1] + size, scr[2] - size);
273                 context.lineTo(scr[1] - size, scr[2] + size);
274                 context.closePath();
275                 this._stroke(el);
276                 break;
277             case 'circle': // dot
278             case 'o':
279                 context.beginPath();
280                 context.arc(scr[1], scr[2], size + 1 + stroke05, 0, 2 * Math.PI, false);
281                 context.closePath();
282                 this._fill(el);
283                 this._stroke(el);
284                 break;
285             case 'square':  // rectangle
286             case '[]':
287                 if (size <= 0) {
288                     break;
289                 }
290 
291                 context.save();
292                 if (this._setColor(el, 'stroke', 'fill')) {
293                     context.fillRect(scr[1] - size - stroke05, scr[2] - size - stroke05, size * 2 + 3 * stroke05, size * 2 + 3 * stroke05);
294                 }
295                 context.restore();
296                 context.save();
297                 this._setColor(el, 'fill');
298                 context.fillRect(scr[1] - size + stroke05, scr[2] - size + stroke05, size * 2 - stroke05, size * 2 - stroke05);
299                 context.restore();
300                 break;
301             case 'plus':  // +
302             case '+':
303                 context.beginPath();
304                 context.moveTo(scr[1] - size, scr[2]);
305                 context.lineTo(scr[1] + size, scr[2]);
306                 context.moveTo(scr[1], scr[2] - size);
307                 context.lineTo(scr[1], scr[2] + size);
308                 context.closePath();
309                 this._stroke(el);
310                 break;
311             case 'diamond':   // <>
312             case '<>':
313                 context.beginPath();
314                 context.moveTo(scr[1] - size, scr[2]);
315                 context.lineTo(scr[1], scr[2] + size);
316                 context.lineTo(scr[1] + size, scr[2]);
317                 context.lineTo(scr[1], scr[2] - size);
318                 context.closePath();
319                 this._fill(el);
320                 this._stroke(el);
321                 break;
322             case 'triangleup':
323             case 'a':
324             case '^':
325                 context.beginPath();
326                 context.moveTo(scr[1], scr[2] - size);
327                 context.lineTo(scr[1] - sqrt32, scr[2] + s05);
328                 context.lineTo(scr[1] + sqrt32, scr[2] + s05);
329                 context.closePath();
330                 this._fill(el);
331                 this._stroke(el);
332                 break;
333             case 'triangledown':
334             case 'v':
335                 context.beginPath();
336                 context.moveTo(scr[1], scr[2] + size);
337                 context.lineTo(scr[1] - sqrt32, scr[2] - s05);
338                 context.lineTo(scr[1] + sqrt32, scr[2] - s05);
339                 context.closePath();
340                 this._fill(el);
341                 this._stroke(el);
342                 break;
343             case 'triangleleft':
344             case '<':
345                 context.beginPath();
346                 context.moveTo(scr[1] - size, scr[2]);
347                 context.lineTo(scr[1] + s05, scr[2] - sqrt32);
348                 context.lineTo(scr[1] + s05, scr[2] + sqrt32);
349                 context.closePath();
350                 this.fill(el);
351                 this._stroke(el);
352                 break;
353             case 'triangleright':
354             case '>':
355                 context.beginPath();
356                 context.moveTo(scr[1] + size, scr[2]);
357                 context.lineTo(scr[1] - s05, scr[2] - sqrt32);
358                 context.lineTo(scr[1] - s05, scr[2] + sqrt32);
359                 context.closePath();
360                 this._fill(el);
361                 this._stroke(el);
362                 break;
363         }
364     },
365 
366     // documented in AbstractRenderer
367     updatePoint: function (el) {
368         this.drawPoint(el);
369     },
370 
371     /* ******************************** *
372      *           Lines                  *
373      * ******************************** */
374 
375     // documented in AbstractRenderer
376     drawLine: function (el) {
377         var scr1 = new JXG.Coords(JXG.COORDS_BY_USER, el.point1.coords.usrCoords, el.board),
378             scr2 = new JXG.Coords(JXG.COORDS_BY_USER, el.point2.coords.usrCoords, el.board);
379 
380         JXG.Math.Geometry.calcStraight(el, scr1, scr2);
381 
382         this.context.beginPath();
383         this.context.moveTo(scr1.scrCoords[1], scr1.scrCoords[2]);
384         this.context.lineTo(scr2.scrCoords[1], scr2.scrCoords[2]);
385         this._stroke(el);
386 
387         this.makeArrows(el, scr1, scr2);
388     },
389 
390     // documented in AbstractRenderer
391     updateLine: function (el) {
392         this.drawLine(el);
393     },
394 
395     // documented in AbstractRenderer
396     drawTicks: function () {
397         // this function is supposed to initialize the svg/vml nodes in the SVG/VMLRenderer.
398         // but in canvas there are no such nodes, hence we just do nothing and wait until
399         // updateTicks is called.
400     },
401 
402     // documented in AbstractRenderer
403     updateTicks: function (axis, dxMaj, dyMaj, dxMin, dyMin) {
404         var i, c,
405             len = axis.ticks.length,
406             context = this.context;
407 
408         context.beginPath();
409         for (i = 0; i < len; i++) {
410             c = axis.ticks[i];
411             x = c[0];
412             y = c[1];
413             context.moveTo(x[0], y[0]);
414             context.lineTo(x[1], y[1]);
415         }
416         // Labels
417         for (i = 0; i < len; i++) {
418             c = axis.ticks[i].scrCoords;
419             if (axis.ticks[i].major 
420                 && (axis.board.needsFullUpdate || axis.needsRegularUpdate) 
421                 && axis.labels[i] 
422                 && axis.labels[i].visProp.visible) {
423                     this.updateText(axis.labels[i]);
424             } 
425         }
426         this._stroke(axis);
427     },
428 
429     /* **************************
430      *    Curves
431      * **************************/
432 
433     // documented in AbstractRenderer
434     drawCurve: function (el) {
435         if (el.visProp.handdrawing) {
436             this.updatePathStringBezierPrim(el);
437         } else {		
438             this.updatePathStringPrim(el);
439         }
440     },
441 
442     // documented in AbstractRenderer
443     updateCurve: function (el) {
444         this.drawCurve(el);
445     },
446 
447     /* **************************
448      *    Circle related stuff
449      * **************************/
450 
451     // documented in AbstractRenderer
452     drawEllipse: function (el) {
453         var m1 = el.center.coords.scrCoords[1],
454             m2 = el.center.coords.scrCoords[2],
455             sX = el.board.unitX,
456             sY = el.board.unitY,
457             rX = 2 * el.Radius(),
458             rY = 2 * el.Radius(),
459             aWidth = rX * sX,
460             aHeight = rY * sY,
461             aX = m1 - aWidth / 2,
462             aY = m2 - aHeight / 2,
463             hB = (aWidth / 2) * 0.5522848,
464             vB = (aHeight / 2) * 0.5522848,
465             eX = aX + aWidth,
466             eY = aY + aHeight,
467             mX = aX + aWidth / 2,
468             mY = aY + aHeight / 2,
469             context = this.context;
470 
471         if (rX > 0.0 && rY > 0.0 && !isNaN(m1 + m2)) {
472             context.beginPath();
473             context.moveTo(aX, mY);
474             context.bezierCurveTo(aX, mY - vB, mX - hB, aY, mX, aY);
475             context.bezierCurveTo(mX + hB, aY, eX, mY - vB, eX, mY);
476             context.bezierCurveTo(eX, mY + vB, mX + hB, eY, mX, eY);
477             context.bezierCurveTo(mX - hB, eY, aX, mY + vB, aX, mY);
478             context.closePath();
479             this._fill(el);
480             this._stroke(el);
481         }
482     },
483 
484     // documented in AbstractRenderer
485     updateEllipse: function (el) {
486         return this.drawEllipse(el);
487     },
488 
489     /* **************************
490      *    Polygon
491      * **************************/
492 
493     // nothing here, using AbstractRenderer implementations
494 
495     /* **************************
496      *    Text related stuff
497      * **************************/
498 
499     // already documented in JXG.AbstractRenderer
500     displayCopyright: function (str, fontSize) {
501         var context = this.context;
502 
503         // this should be called on EVERY update, otherwise it won't be shown after the first update
504         context.save();
505         context.font = fontSize + 'px Arial';
506         context.fillStyle = '#aaa';
507         context.lineWidth = 0.5;
508         context.fillText(str, 10, 2 + fontSize);
509         context.restore();
510     },
511 
512     // already documented in JXG.AbstractRenderer
513     drawInternalText: function (el) {
514         var fs, context = this.context;
515 
516         context.save();
517         // el.rendNode.setAttributeNS(null, "class", el.visProp.cssclass);
518         if (this._setColor(el, 'stroke', 'fill') && !isNaN(el.coords.scrCoords[1]+el.coords.scrCoords[2]) ) {
519             if (el.visProp.fontsize) {
520                 if (typeof el.visProp.fontsize === 'function') {
521                     fs = el.visProp.fontsize();
522                     context.font = (fs > 0 ? fs : 0) + 'px Arial';
523                 } else {
524                     context.font = (el.visProp.fontsize) + 'px Arial';
525                 }
526             }
527 
528             this.transformImage(el, el.transformations);
529             context.fillText(el.plaintext, el.coords.scrCoords[1], el.coords.scrCoords[2]);
530         }
531         context.restore();
532 
533         return null;
534     },
535 
536     // already documented in JXG.AbstractRenderer
537     updateInternalText: function (element) {
538         this.drawInternalText(element);
539     },
540 
541     // documented in JXG.AbstractRenderer
542     // Only necessary for texts
543     setObjectStrokeColor: function (el, color, opacity) {
544         var rgba = JXG.evaluate(color), c, rgbo,
545             o = JXG.evaluate(opacity), oo,
546             node;
547 
548         o = (o > 0) ? o : 0;
549 
550         if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
551             return;
552         }
553 
554         if (JXG.exists(rgba) && rgba !== false) {
555             if (rgba.length!=9) {          // RGB, not RGBA
556                 c = rgba;
557                 oo = o;
558             } else {                       // True RGBA, not RGB
559                 rgbo = JXG.rgba2rgbo(rgba);
560                 c = rgbo[0];
561                 oo = o*rgbo[1];
562             }
563             node = el.rendNode;
564             if (el.type === JXG.OBJECT_TYPE_TEXT && el.visProp.display === 'html') {
565                 node.style.color = c;     
566                 node.style.opacity = oo;
567             }
568         }
569 
570         el.visPropOld.strokecolor = rgba;
571         el.visPropOld.strokeopacity = o;
572     },
573     
574     // already documented in JXG.AbstractRenderer
575 /*    
576     updateTextStyle: function (element) { 
577         var fs = JXG.evaluate(element.visProp.fontsize);
578 
579         if (element.visProp.display === 'html') {
580             element.rendNode.style.fontSize = fs + 'px';
581         }
582     },
583 */
584     /* **************************
585      *    Image related stuff
586      * **************************/
587 
588     // already documented in JXG.AbstractRenderer
589     drawImage: function (el) {
590         el.rendNode = new Image();
591         // Store the file name of the image.
592         // Before, this was done in el.rendNode.src
593         // But there, the file name is expanded to
594         // the full url. This may be different from
595         // the url computed in updateImageURL().
596         el._src = '';
597         this.updateImage(el);
598     },
599 
600     // already documented in JXG.AbstractRenderer
601     updateImage: function (el) {
602         var context = this.context,
603             o = JXG.evaluate(el.visProp.fillopacity),
604             paintImg = JXG.bind(function () {
605                 el.imgIsLoaded = true;
606                 if (el.size[0] <= 0 || el.size[1] <= 0) {
607                     return;
608                 }
609                 context.save();
610                 context.globalAlpha = o;
611                 // If det(el.transformations)=0, FireFox 3.6. breaks down.
612                 // This is tested in transformImage
613                 this.transformImage(el, el.transformations);
614                 context.drawImage(el.rendNode,
615                     el.coords.scrCoords[1],
616                     el.coords.scrCoords[2] - el.size[1],
617                     el.size[0],
618                     el.size[1]);
619                 context.restore();
620             }, this);
621 
622         if (this.updateImageURL(el)) {
623             el.rendNode.onload = paintImg;
624         } else {
625             if (el.imgIsLoaded) {
626                 paintImg();
627             }
628         }
629     },
630 
631     // already documented in JXG.AbstractRenderer
632     transformImage: function (el, t) {
633         var m, len = t.length,
634             ctx = this.context;
635 
636         if (len > 0) {
637             m = this.joinTransforms(el, t);
638             if (Math.abs(JXG.Math.Numerics.det(m)) >= JXG.Math.eps) {
639                 ctx.transform(m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]);
640             }
641         }
642     },
643 
644     // already documented in JXG.AbstractRenderer
645     updateImageURL: function (el) {
646         var url;
647 
648         url = JXG.evaluate(el.url);
649         if (el._src !== url) {
650             el.imgIsLoaded = false;
651             el.rendNode.src = url;
652             el._src = url;
653             return true;
654         }
655 
656         return false;
657     },
658 
659     /* **************************
660      * Render primitive objects
661      * **************************/
662 
663     // documented in AbstractRenderer
664     remove: function (shape) {
665         // sounds odd for a pixel based renderer but we need this for html texts
666         if (JXG.exists(shape) && JXG.exists(shape.parentNode)) {
667             shape.parentNode.removeChild(shape);
668         }
669     },
670 
671     // documented in AbstractRenderer
672     makeArrows: function (el, scr1, scr2) {
673         // not done yet for curves and arcs.
674         var arrowHead = [
675             [ 2, 0 ],
676             [ -10, -4 ],
677             [ -10, 4]
678         ],
679             arrowTail = [
680                 [ -2, 0 ],
681                 [ 10, -4 ],
682                 [ 10, 4]
683             ],
684             x1, y1, x2, y2, ang,
685             context = this.context;
686 
687         if (el.visProp.strokecolor !== 'none' && (el.visProp.lastarrow || el.visProp.firstarrow)) {
688             if (el.elementClass === JXG.OBJECT_CLASS_LINE) {
689                 x1 = scr1.scrCoords[1];
690                 y1 = scr1.scrCoords[2];
691                 x2 = scr2.scrCoords[1];
692                 y2 = scr2.scrCoords[2];
693             } else {
694                 return;
695             }
696 
697             context.save();
698             if (this._setColor(el, 'stroke', 'fill')) {
699                 ang = Math.atan2(y2 - y1, x2 - x1);
700                 if (el.visProp.lastarrow) {
701                     this._drawFilledPolygon(this._translateShape(this._rotateShape(arrowHead, ang), x2, y2));
702                 }
703 
704                 if (el.visProp.firstarrow) {
705                     this._drawFilledPolygon(this._translateShape(this._rotateShape(arrowTail, ang), x1, y1));
706                 }
707             }
708             context.restore();
709         }
710     },
711 
712     // documented in AbstractRenderer
713     updatePathStringPrim: function (el) {
714         var symbm = 'M',
715             symbl = 'L',
716             nextSymb = symbm,
717             maxSize = 5000.0,
718             i, scr,
719             isNotPlot = (el.visProp.curvetype !== 'plot'),
720             len,
721             context = this.context;
722 
723         if (el.numberPoints <= 0) {
724             return;
725         }
726 
727         if (isNotPlot && el.board.options.curve.RDPsmoothing) {
728             el.points = JXG.Math.Numerics.RamerDouglasPeuker(el.points, 0.5);
729         }
730         len = Math.min(el.points.length, el.numberPoints);
731 
732         context.beginPath();
733         for (i = 0; i < len; i++) {
734             scr = el.points[i].scrCoords;
735 
736             if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
737                 nextSymb = symbm;
738             } else {
739                 // Chrome has problems with values  being too far away.
740                 if (scr[1] > maxSize) {
741                     scr[1] = maxSize;
742                 } else if (scr[1] < -maxSize) {
743                     scr[1] = -maxSize;
744                 }
745 
746                 if (scr[2] > maxSize) {
747                     scr[2] = maxSize;
748                 } else if (scr[2] < -maxSize) {
749                     scr[2] = -maxSize;
750                 }
751 
752                 if (nextSymb === symbm) {
753                     context.moveTo(scr[1], scr[2]);
754                 } else {
755                     context.lineTo(scr[1], scr[2]);
756                 }
757                 nextSymb = symbl;
758             }
759         }
760         this._fill(el);
761         this._stroke(el);
762     },
763 
764     // already documented in JXG.AbstractRenderer
765     updatePathStringBezierPrim: function (el) {
766         var symbm = 'M',
767             symbl = 'C',
768             nextSymb = symbm,
769             maxSize = 5000.0,
770             i, j, scr,
771             lx, ly, f = el.visProp.strokewidth, 
772             isNoPlot = (el.visProp.curvetype !== 'plot'),
773             len,
774             context = this.context;
775 
776         if (el.numberPoints <= 0) {
777             return;
778         }
779 
780         if (isNoPlot && el.board.options.curve.RDPsmoothing) {
781             el.points = JXG.Math.Numerics.RamerDouglasPeuker(el.points, 0.5);
782         }
783         len = Math.min(el.points.length, el.numberPoints);
784 
785         context.beginPath();
786         for (j=1; j<3; j++) {
787             nextSymb = symbm;
788             for (i = 0; i < len; i++) {
789                 scr = el.points[i].scrCoords;
790 
791                 if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
792                     nextSymb = symbm;
793                 } else {
794                     // Chrome has problems with values  being too far away.
795                     if (scr[1] > maxSize) {
796                         scr[1] = maxSize;
797                     } else if (scr[1] < -maxSize) {
798                         scr[1] = -maxSize;
799                     }
800 
801                     if (scr[2] > maxSize) {
802                         scr[2] = maxSize;
803                     } else if (scr[2] < -maxSize) {
804                         scr[2] = -maxSize;
805                     }
806 
807                     if (nextSymb == symbm) {
808                         context.moveTo(scr[1]+0*f*(2*j*Math.random()-j), 
809                                        scr[2]+0*f*(2*j*Math.random()-j));
810                     } else {
811                         context.bezierCurveTo(
812                             (lx + (scr[1]-lx)*0.333 + f*(2*j*Math.random()-j)),
813                             (ly + (scr[2]-ly)*0.333 + f*(2*j*Math.random()-j)),
814                             (lx + 2*(scr[1]-lx)*0.333 + f*(2*j*Math.random()-j)),
815                             (ly + 2*(scr[2]-ly)*0.333 + f*(2*j*Math.random()-j)),
816                             scr[1], scr[2]);
817                     }
818                     nextSymb = symbl;
819                     lx = scr[1];
820                     ly = scr[2];
821                 }
822             }
823         }
824         this._fill(el);
825         this._stroke(el);
826     },
827     
828     // documented in AbstractRenderer
829     updatePolygonPrim: function (node, el) {
830         var scrCoords, i,
831             len = el.vertices.length,
832             context = this.context;
833 
834         if (len <= 0) {
835             return;
836         }
837 
838         context.beginPath();
839         scrCoords = el.vertices[0].coords.scrCoords;
840         context.moveTo(scrCoords[1], scrCoords[2]);
841         for (i = 1; i < len; i++) {
842             scrCoords = el.vertices[i].coords.scrCoords;
843             context.lineTo(scrCoords[1], scrCoords[2]);
844         }
845         context.closePath();
846 
847         this._fill(el);    // The edges of a polygon are displayed separately (as segments).
848     },
849 
850     /* **************************
851      *  Set Attributes
852      * **************************/
853 
854     // documented in AbstractRenderer
855     show: function (el) {
856         // sounds odd for a pixel based renderer but we need this for html texts
857         if (JXG.exists(el.rendNode)) {
858             el.rendNode.style.visibility = "inherit";
859         }
860     },
861 
862     // documented in AbstractRenderer
863     hide: function (el) {
864         // sounds odd for a pixel based renderer but we need this for html texts
865         if (JXG.exists(el.rendNode)) {
866             el.rendNode.style.visibility = "hidden";
867         }
868     },
869 
870     // documented in AbstractRenderer
871     setGradient: function (el) {
872         var col, op;
873 
874         op = JXG.evaluate(el.visProp.fillopacity);
875         op = (op > 0) ? op : 0;
876 
877         col = JXG.evaluate(el.visProp.fillcolor);
878     },
879 
880     // documented in AbstractRenderer
881     setShadow: function (el) {
882         if (el.visPropOld.shadow === el.visProp.shadow) {
883             return;
884         }
885 
886         // not implemented yet
887         // we simply have to redraw the element
888         // probably the best way to do so would be to call el.updateRenderer(), i think.
889 
890         el.visPropOld.shadow = el.visProp.shadow;
891     },
892 
893     // documented in AbstractRenderer
894     highlight: function (obj) {
895         obj.board.prepareUpdate();
896         obj.board.renderer.suspendRedraw(obj.board);
897         obj.board.updateRenderer();
898         obj.board.renderer.unsuspendRedraw();
899         return this;
900     },
901 
902     // documented in AbstractRenderer
903     noHighlight: function (obj) {
904         obj.board.prepareUpdate();
905         obj.board.renderer.suspendRedraw(obj.board);
906         obj.board.updateRenderer();
907         obj.board.renderer.unsuspendRedraw();
908         return this;
909     },
910 
911     /* **************************
912      * renderer control
913      * **************************/
914 
915     // documented in AbstractRenderer
916     suspendRedraw: function (board) {
917         this.context.save();
918         this.context.clearRect(0, 0, this.canvasRoot.width, this.canvasRoot.height);
919 
920         if (board && board.showCopyright) {
921             this.displayCopyright(JXG.JSXGraph.licenseText, 12);
922         }
923     },
924 
925     // documented in AbstractRenderer
926     unsuspendRedraw: function () {
927         this.context.restore();
928     },
929 
930     // document in AbstractRenderer
931     resize: function (w, h) {
932         this.canvasRoot.style.width = parseFloat(w) + 'px';
933         this.canvasRoot.style.height = parseFloat(h) + 'px';
934 
935         this.canvasRoot.setAttribute('width', parseFloat(w) + 'px');
936         this.canvasRoot.setAttribute('height', parseFloat(h) + 'px');
937     }
938 
939 });
940