1 /*
  2     Copyright 2010
  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 JXG.CanvasRenderer = function(container) {
 27     var i;
 28     this.constructor();
 29 
 30     /* 
 31         Enable easy test which renderer is used.
 32     */
 33     this.type = 'canvas';
 34     
 35     this.canvasRoot = null;
 36     this.suspendHandle = null;
 37     this.canvasId = JXG.Util.genUUID();
 38     
 39     this.canvasNamespace = null;
 40 
 41     this.container = container;
 42     this.container.style.MozUserSelect = 'none';
 43 
 44     this.container.style.overflow = 'hidden';
 45     if (this.container.style.position=='') {
 46         this.container.style.position = 'relative';
 47     }
 48     
 49     //this.canvasRoot = this.container.ownerDocument.createElementNS(this.canvasNamespace, "canvas");
 50     this.container.innerHTML = '<canvas id="'+this.canvasId+'" width="'+this.container.style.width+'" height="'+this.container.style.height+'"></canvas>';
 51     //this.canvasRoot.setAttribute('id', 'canvasel');
 52     //this.canvasRoot.style.overflow = 'hidden';
 53     //this.canvasRoot.style.width = this.container.style.width;
 54     //this.canvasRoot.style.height = this.container.style.height;
 55     //this.container.appendChild(this.canvasRoot);
 56     this.canvasRoot = document.getElementById(this.canvasId);
 57     this.context =  this.canvasRoot.getContext('2d');
 58 
 59     this.dashArray = [[2, 2], [5, 5], [10, 10], [20, 20], [20, 10, 10, 10], [20, 5, 10, 5]];
 60 };
 61 
 62 JXG.CanvasRenderer.prototype = JXG.AbstractRenderer();
 63 
 64 /*
 65  * I suggest to use this.fill() and this.stroke() instead of this.updateStencilBuffer() A.W.
 66  */
 67 JXG.CanvasRenderer.prototype.updateStencilBuffer = function(el) {
 68     var highlight;
 69 
 70     // this highlight thing doesn't work by now ... :/
 71     if(typeof el.board.highlightedObjects[el.id] != 'undefined' && el.board.highlightedObjects[el.id] != null) {
 72         if (el.visProp.strokeColor!='none') this.context.strokeStyle = el.visProp.highlightStrokeColor;
 73         if (el.visProp.fillColor!='none') this.context.fillStyle = el.visProp.highlightFillColor;
 74         this.context.lineWidth = parseFloat(el.visProp.strokeWidth);
 75 
 76         // we  can only set ONE globalAlpha value in here, so we set it to the elements fill-alpha.
 77         // but we can use the stroke alpha in the other methods by ourselves.
 78         this.context.globalAlpha = el.visProp.highlightFillOpacity;
 79         highlight = true;
 80     } else {
 81         if (el.visProp.strokeColor!='none') this.context.strokeStyle = el.visProp.strokeColor;
 82         if (el.visProp.fillColor!='none') this.context.fillStyle = el.visProp.fillColor;
 83         this.context.lineWidth = parseFloat(el.visProp.strokeWidth);
 84 
 85         // we  can only set ONE globalAlpha value in here, so we set it to the elements fill-alpha.
 86         // but we can use the stroke alpha in the other methods by ourselves.
 87         this.context.globalAlpha = el.visProp.fillOpacity;
 88         highlight = false;
 89     }
 90 
 91     return highlight;
 92 };
 93 
 94 /*
 95  * Sets color and opacity for filling and stroking
 96  */
 97 JXG.CanvasRenderer.prototype.setColor = function(el, type) {
 98     var hasColor = true, isTrace = false;
 99 	if (!JXG.exists(el.board)||!JXG.exists(el.board.highlightedObjects)) {
100 		// This case handles trace elements. 
101 		// To make them work, we simply neglect highlighting.
102 		isTrace = true;
103 	}
104     if (type=='fill') {
105         if(!isTrace && typeof el.board.highlightedObjects[el.id] != 'undefined' && el.board.highlightedObjects[el.id] != null) {
106             if (el.visProp.highlightFillColor!='none') {
107                 this.context.globalAlpha = el.visProp.highlightFillOpacity;
108                 this.context.fillStyle = el.visProp.highlightFillColor;
109             } else {
110                 hasColor = false;
111             }
112         } else {
113             if (el.visProp.fillColor!='none') {
114                 this.context.globalAlpha = el.visProp.fillOpacity;
115                 this.context.fillStyle = el.visProp.fillColor;
116             } else {
117                 hasColor = false;
118             }
119         }
120     } else {
121         if(!isTrace && typeof el.board.highlightedObjects[el.id] != 'undefined' && el.board.highlightedObjects[el.id] != null) {
122             if (el.visProp.highlightStrokeColor!='none') {
123                 this.context.globalAlpha = el.visProp.highlightStrokeOpacity;
124                 this.context.strokeStyle = el.visProp.highlightStrokeColor;
125             } else {
126                 hasColor = false;
127             }
128         } else {
129             if (el.visProp.strokeColor!='none') {
130                 this.context.globalAlpha = el.visProp.strokeOpacity;
131                 this.context.strokeStyle = el.visProp.strokeColor;
132             } else {
133                 hasColor = false;
134             }
135         }
136         this.context.lineWidth = parseFloat(el.visProp.strokeWidth);
137     }
138     return hasColor;
139 };
140 
141 /*
142  * Sets color and opacity for filling
143  * and does the filling
144  */
145 JXG.CanvasRenderer.prototype.fill = function(el) {
146     this.context.save();
147     if (this.setColor(el, 'fill')) this.context.fill();
148     this.context.restore();
149 };
150 
151 /*
152  * Sets color and opacity for drawing paths and lines
153  * and draws the paths and lines.
154  */
155 JXG.CanvasRenderer.prototype.stroke = function(el) {
156     this.context.save();
157     if(el.visProp['dash']>0) {
158 // doesnt work by now
159 //        this.context.lineDashArray = this.dashArray[el.visProp['dash']-1];
160     } else {
161         this.context.lineDashArray = [];
162     }
163     if (this.setColor(el, 'stroke')) this.context.stroke();
164     this.context.restore();
165 };
166 
167 JXG.CanvasRenderer.prototype.setShadow = function(el) {
168     if (el.visPropOld['shadow']==el.visProp['shadow']) {
169         return;
170     }
171 
172     // not implemented yet
173     // we simply have to redraw the element
174     // probably the best way to do so would be to call el.updateRenderer(), i think.
175 
176     el.visPropOld['shadow']=el.visProp['shadow'];
177 };
178 
179 JXG.CanvasRenderer.prototype.setGradient = function(el) {
180     var fillNode = el.rendNode, col, op,
181         node, node2, node3, x1, x2, y1, y2;
182     
183     if (typeof el.visProp['fillOpacity']=='function') {
184         op = el.visProp['fillOpacity']();
185     } else {
186         op = el.visProp['fillOpacity'];
187     }
188     op = (op>0)?op:0;
189     if (typeof el.visProp['fillColor']=='function') {
190         col = el.visProp['fillColor']();
191     } else {
192         col = el.visProp['fillColor'];
193     }
194 
195     // not implemented yet. this should be done in the draw methods for the
196     // elements and here we just call the updateRenderer of the given element,
197     // resp. the JXG.Board.update().
198 	/*
199     if(el.visProp['gradient'] == 'linear') {
200     }
201     else if (el.visProp['gradient'] == 'radial') {
202     }
203     else {
204     }
205 	*/
206 };
207 
208 JXG.CanvasRenderer.prototype.updateGradient = function(el) {
209     // see drawGradient
210 }; 
211 
212 JXG.CanvasRenderer.prototype.displayCopyright = function(str, fontSize) {
213     // this should be called on EVERY update, otherwise it won't be shown after the first update
214     this.context.save();
215     this.context.font = fontSize+'px Arial';
216     this.context.fillStyle = '#aaa';
217     this.context.lineWidth = 0.5;
218     this.context.fillText(str, 10, 2+fontSize);
219     this.context.restore();
220 };
221 
222 JXG.CanvasRenderer.prototype.drawInternalText = function(el) {
223     var fs, ctx = this.context;
224     //this.updateStencilBuffer(el);
225     
226     ctx.save();
227     if (this.setColor(el,'stroke')) {
228         if(typeof el.board.highlightedObjects[el.id] != 'undefined' && el.board.highlightedObjects[el.id] != null) {
229                 ctx.fillStyle = el.visProp.highlightStrokeColor;
230         } else {
231                 ctx.fillStyle = el.visProp.strokeColor;
232         }
233         if (el.visProp['fontSize']) {
234             if (typeof el.visProp['fontSize'] == 'function') {
235                 fs = el.visProp['fontSize']();
236                 ctx.font = (fs > 0 ? fs : 0)+'px Arial';
237             } else {
238                 ctx.font = (el.visProp['fontSize'])+'px Arial';
239             }
240         }
241         //ctx.font = el.board.options.text.fontSize+'px Arial';
242         this.transformImage(el,el.transformations,ctx); 
243         ctx.fillText(el.plaintextStr, el.coords.scrCoords[1], el.coords.scrCoords[2]);
244     }
245     ctx.restore();
246 
247     return null;
248 };
249 
250 JXG.CanvasRenderer.prototype.updateInternalText = function(/** JXG.Text */ el) {
251     this.drawInternalText(el);
252 };
253 
254 JXG.CanvasRenderer.prototype.updateTextStyle =  function(el) {
255 };
256 
257 JXG.CanvasRenderer.prototype.drawTicks = function(axis) {
258     // this function is supposed to initialize the svg/vml nodes in the SVG/VMLRenderer.
259     // but in canvas there are no such nodes, hence we just do nothing and wait until
260     // updateTicks is called.
261 };
262 
263 JXG.CanvasRenderer.prototype.updateTicks = function(axis,dxMaj,dyMaj,dxMin,dyMin) {
264     var i, c, len = axis.ticks.length;
265     //this.context.globalAlpha = axis.visProp[(this.updateStencilBuffer(axis) ? 'highlightS' : 's' ) + 'trokeOpacity'];
266 
267     this.context.beginPath();
268     for (i=0; i<len; i++) {
269         c = axis.ticks[i].scrCoords;
270         if (axis.ticks[i].major) {
271             if ((axis.board.needsFullUpdate||axis.needsRegularUpdate||axis.labels[i].display=='internal') 
272                 && axis.labels[i].visProp['visible']) {
273                 this.drawText(axis.labels[i]);
274             }
275             this.context.moveTo(c[1]+dxMaj, c[2]-dyMaj);
276             this.context.lineTo(c[1]-dxMaj, c[2]+dyMaj);
277         }
278         else {
279             this.context.moveTo(c[1]+dxMin, c[2]-dyMin);
280             this.context.lineTo(c[1]-dxMin, c[2]+dyMin);
281         }
282     }
283     //this.context.stroke();
284     this.stroke(axis);
285 };
286 
287 JXG.CanvasRenderer.prototype.drawImage = function(el) {
288     el.rendNode = new Image();
289     // Store the file name of the image.
290     // Before, this was done in el.rendNode.src
291     // But there, the file name is expanded to
292     // the full url. This may be different from 
293     // the url computed in updateImageURL().
294     el._src = '';
295     this.updateImage(el);
296 };
297 
298 JXG.CanvasRenderer.prototype.updateImageURL = function(el) {
299     var url;
300     if (JXG.isFunction(el.url)) {
301         url = el.url();
302     } else {
303         url = el.url;
304     }
305     if (el._src!=url) {
306         el.imgIsLoaded = false;
307         el.rendNode.src = url;  
308         el._src = url;
309         return true;
310     } else {
311         return false;
312     }
313 };
314 
315 JXG.CanvasRenderer.prototype.updateImage = function(/** Image */ el) { 
316     var ctx = this.context,
317         o = this.evaluate(el.visProp.fillOpacity),
318         paintImg = JXG.bind(function(){ 
319             el.imgIsLoaded = true;
320             if (el.size[0]<=0 || el.size[1]<=0) return;
321             ctx.save();
322             ctx.globalAlpha = o;
323             // If det(el.transformations)=0, FireFox 3.6. breaks down.
324             // This is tested in transformImage
325             this.transformImage(el,el.transformations,ctx); 
326             ctx.drawImage(el.rendNode, 
327                     el.coords.scrCoords[1],
328                     el.coords.scrCoords[2]-el.size[1],
329                     el.size[0],
330                     el.size[1]);
331             ctx.restore();
332         }, this);  
333     
334     if (this.updateImageURL(el)) {
335         el.rendNode.onload = paintImg;
336     } else {
337         if (el.imgIsLoaded) paintImg();
338     }
339 };
340 
341 JXG.CanvasRenderer.prototype.transformImage = function(el,t,ctx) {
342     var m, len = t.length;
343     if (len>0) {
344         m = this.joinTransforms(el,t);
345         if (Math.abs(JXG.Math.Numerics.det(m))>=JXG.Math.eps)
346             ctx.transform(m[1][1],m[2][1],m[1][2],m[2][2],m[1][0],m[2][0]);
347     }
348 };
349 
350 JXG.CanvasRenderer.prototype.setArrowAtts = function(node, c, o) {
351     // this isn't of any use in a canvas based renderer,
352     // because the arrows have to be redrawn on every update.
353 };
354 
355 JXG.CanvasRenderer.prototype.setObjectStrokeColor = function(el, color, opacity) {
356     // this is not required in a canvas based renderer
357     //if (JXG.exists(el.board))
358     //    el.board.updateRenderer();
359 };
360 
361 JXG.CanvasRenderer.prototype.setObjectFillColor = function(el, color, opacity) {
362     // useless
363     //el.board.updateRenderer();
364 };
365 
366 JXG.CanvasRenderer.prototype.setObjectStrokeWidth = function(el, width) {
367     // useless
368     //el.board.updateRenderer();
369 };
370 
371 JXG.CanvasRenderer.prototype.hide = function(el) {
372     // useless beside HTML texts
373 	if (JXG.exists(el.rendNode)) 
374 		el.rendNode.style.visibility = "hidden";
375     //el.board.updateRenderer();
376 };
377 
378 JXG.CanvasRenderer.prototype.show = function(el) {
379     // useless beside HTML texts
380 	if (JXG.exists(el.rendNode)) 
381 		el.rendNode.style.visibility = "inherit";
382     //el.board.updateRenderer();
383 };
384 
385 JXG.CanvasRenderer.prototype.remove = function(shape) {
386     // useless with the exception of HTML texts
387     if(shape!=null && shape.parentNode != null)
388         shape.parentNode.removeChild(shape);
389 };
390 
391 JXG.CanvasRenderer.prototype.suspendRedraw = function() {
392     this.context.save();
393     this.context.clearRect(0, 0, this.canvasRoot.width, this.canvasRoot.height);
394     this.displayCopyright(JXG.JSXGraph.licenseText, 12);
395 };
396 
397 JXG.CanvasRenderer.prototype.unsuspendRedraw = function() {
398     this.context.restore();
399 };
400 
401 JXG.CanvasRenderer.prototype.setDashStyle = function(el,visProp) {
402 };
403 
404 JXG.CanvasRenderer.prototype.setGridDash = function(id) {
405     // useless
406 };
407 
408 JXG.CanvasRenderer.prototype.createPrim = function(type,id) {
409     // <canvas> is not node-based like svg, hence this is useless
410 };
411 
412 JXG.CanvasRenderer.prototype.createArrowHead = function(el,idAppendix) {
413     // we have to draw arrow heads directly in the draw line methods
414 };
415 
416 /*
417 // seems to be unused
418 JXG.CanvasRenderer.prototype.makeArrow = function(node,el,idAppendix) {
419     // we have to draw arrow heads directly in the draw line methods
420 };
421 */
422 
423 /*
424  * _drawFilledPolygon, _translateShape, _rotateShape 
425  * are necessary for drawing arrow heads.
426  */
427 JXG.CanvasRenderer.prototype._drawFilledPolygon = function(shape) {
428     var i, len = shape.length;
429     if (len<=0) return;
430     this.context.beginPath();
431     this.context.moveTo(shape[0][0],shape[0][1]);
432     for(i=0;i<len;i++) {
433         if (i > 0) this.context.lineTo(shape[i][0],shape[i][1]);
434     }
435     this.context.lineTo(shape[0][0],shape[0][1]);
436     this.context.fill();
437 };
438 
439 JXG.CanvasRenderer.prototype._translateShape = function(shape,x,y) {
440     var i, rv = [], len = shape.length;
441     if (len<=0) return shape;
442     for(i=0;i<len;i++) {
443         rv.push([ shape[i][0] + x, shape[i][1] + y ]);
444     }
445     return rv;
446 };
447 
448 JXG.CanvasRenderer.prototype._rotateShape = function(shape,ang) {
449     var i, rv = [], len = shape.length;
450     if (len<=0) return shape;
451     
452     for(i=0;i<len;i++) {
453         rv.push(this._rotatePoint(ang,shape[i][0],shape[i][1]));
454     }
455     return rv;
456 };
457 
458 JXG.CanvasRenderer.prototype._rotatePoint = function(ang,x,y) {
459     return [
460         (x * Math.cos(ang)) - (y * Math.sin(ang)),
461         (x * Math.sin(ang)) + (y * Math.cos(ang))
462     ];
463 };
464 
465 JXG.CanvasRenderer.prototype.makeArrows = function(el, scr1, scr2) {
466     var ang;
467     
468     // not done yet for curves and arcs.
469     var arrowHead = [
470             [ 2, 0 ],
471             [ -10, -4 ],
472             [ -10, 4]
473             ],
474         arrowTail = [
475             [ -2, 0 ],
476             [ 10, -4 ],
477             [ 10, 4]
478             ],            
479         x1, y1, x2, y2, ang;
480     
481     if (el.visProp['strokeColor']!='none' && (el.visProp['lastArrow']||el.visProp['firstArrow'])) {
482         if (el.elementClass==JXG.OBJECT_CLASS_LINE) {
483             x1 = scr1.scrCoords[1];
484             y1 = scr1.scrCoords[2];
485             x2 = scr2.scrCoords[1];
486             y2 = scr2.scrCoords[2];
487         } else {
488             return;
489         }
490         
491         this.context.save();
492         if (this.setColor(el,'stroke')) {
493             if(typeof el.board.highlightedObjects[el.id] != 'undefined' && el.board.highlightedObjects[el.id] != null) {
494                     this.context.fillStyle = el.visProp.highlightStrokeColor;
495             } else {
496                     this.context.fillStyle = el.visProp.strokeColor;
497             }
498             var ang = Math.atan2(y2-y1,x2-x1);
499             if (el.visProp['lastArrow']) 
500                 this._drawFilledPolygon(this._translateShape(this._rotateShape(arrowHead,ang),x2,y2));
501             if (el.visProp['firstArrow']) 
502                 this._drawFilledPolygon(this._translateShape(this._rotateShape(arrowTail,ang),x1,y1));
503         }
504         this.context.restore();
505     }
506 };
507 
508 JXG.CanvasRenderer.prototype.updateLinePrim = function(node,p1x,p1y,p2x,p2y) {
509     // not required
510 };
511 
512 JXG.CanvasRenderer.prototype.updateCirclePrim = function(node,x,y,r) {
513     // not required
514 };
515 
516 JXG.CanvasRenderer.prototype.updateEllipsePrim = function(node,x,y,rx,ry) {
517     // not required
518 };
519 
520 JXG.CanvasRenderer.prototype.updateRectPrim = function(node,x,y,w,h) {
521     // not required
522 };
523 
524 JXG.CanvasRenderer.prototype.updatePathPrim = function(node, pointString, board) { 
525     // not required
526 };
527 
528 JXG.CanvasRenderer.prototype.updatePathStringPrim = function(el) {
529     var symbm = 'M',
530         symbl = 'L',
531         nextSymb = symbm,
532         maxSize = 5000.0,
533         pStr = '',
534         //h = 3*el.board.canvasHeight,
535         //w = 100*el.board.canvasWidth,
536         i, scr, 
537         isNoPlot = (el.curveType!='plot'),
538         //isFunctionGraph = (el.curveType=='functiongraph'),
539         len;
540 
541     if (el.numberPoints<=0) { return ''; }
542     
543     if (isNoPlot && el.board.options.curve.RDPsmoothing) {
544         el.points = this.RamenDouglasPeuker(el.points,0.5);
545     }
546     len = Math.min(el.points.length,el.numberPoints);
547 
548     this.context.beginPath();
549     for (i=0; i<len; i++) {
550         scr = el.points[i].scrCoords;
551         //if (isNaN(scr[1]) || isNaN(scr[2]) /*|| Math.abs(scr[1])>w || (isFunctionGraph && (scr[2]>h || scr[2]<-0.5*h))*/ ) {  // PenUp
552         if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
553             nextSymb = symbm;
554         } else {
555             // Chrome has problems with values  being too far away.
556             if (scr[1]>maxSize) { scr[1] = maxSize; }
557             else if (scr[1]<-maxSize) { scr[1] = -maxSize; }
558             if (scr[2]>maxSize) { scr[2] = maxSize; }
559             else if (scr[2]<-maxSize) { scr[2] = -maxSize; }
560             
561             if(nextSymb == 'M')
562                 this.context.moveTo(scr[1], scr[2]);
563             else
564                 this.context.lineTo(scr[1], scr[2]);
565             //pStr += [nextSymb,scr[1],' ',scr[2]].join(''); // Attention: first coordinate may be inaccurate if far way
566             nextSymb = symbl;
567         }
568     }
569     //this.context.closePath();
570     this.fill(el);
571     this.stroke(el);
572     return null;
573 };
574 
575 JXG.CanvasRenderer.prototype.appendChildPrim = function(node,level) {
576 
577 };
578 
579 JXG.CanvasRenderer.prototype.setPropertyPrim = function(node,key,val) {
580     if (key=='stroked') {
581     }
582     //node.setAttributeNS(null, key, val);
583 };
584 
585 JXG.CanvasRenderer.prototype.drawVerticalGrid = function(topLeft, bottomRight, gx, board) {
586     var node = this.createPrim('path', 'gridx'),
587         gridArr = '';
588         
589     while(topLeft.scrCoords[1] < bottomRight.scrCoords[1] + gx - 1) { 
590         gridArr += ' M ' + topLeft.scrCoords[1] + ' ' + 0 + ' L ' + topLeft.scrCoords[1] + ' ' + board.canvasHeight+' ';
591         topLeft.setCoordinates(JXG.COORDS_BY_SCREEN, [topLeft.scrCoords[1] + gx, topLeft.scrCoords[2]]);   
592     }
593     this.updatePathPrim(node, gridArr, board);
594     return node;
595 };
596 
597 JXG.CanvasRenderer.prototype.drawHorizontalGrid = function(topLeft, bottomRight, gy, board) {
598     var node = this.createPrim('path', 'gridy'),
599         gridArr = '';
600         
601     while(topLeft.scrCoords[2] <= bottomRight.scrCoords[2] + gy - 1) {
602         gridArr += ' M ' + 0 + ' ' + topLeft.scrCoords[2] + ' L ' + board.canvasWidth + ' ' + topLeft.scrCoords[2]+' ';
603         topLeft.setCoordinates(JXG.COORDS_BY_SCREEN, [topLeft.scrCoords[1], topLeft.scrCoords[2] + gy]);
604     }
605     this.updatePathPrim(node, gridArr, board);
606     return node;
607 };
608 
609 JXG.CanvasRenderer.prototype.appendNodesToElement = function(element, type) {
610     // not used,
611 };
612 
613 // we need to overwrite some AbstractRenderer methods which are only useful for vector based renderers
614 JXG.CanvasRenderer.prototype.drawPoint = function(/** Point */ el) {
615     var f = el.visProp['face'],
616         size = el.visProp['size'],
617         scr = el.coords.scrCoords,
618         sqrt32 = size*Math.sqrt(3)*0.5,
619         s05 = size*0.5,
620         stroke05 = parseFloat(el.visProp.strokeWidth)/2.0;
621 
622     if (size<=0) return;
623     // determine how the point looks like
624     switch(f) {
625         case 'cross':  // x
626         case 'x':
627             //this.context.globalAlpha = el.visProp[(highlight ? 'highlightS' : 's' ) + 'trokeOpacity'];
628             this.context.beginPath();
629             this.context.moveTo(scr[1]-size, scr[2]-size);
630             this.context.lineTo(scr[1]+size, scr[2]+size);
631             this.context.moveTo(scr[1]+size, scr[2]-size);
632             this.context.lineTo(scr[1]-size, scr[2]+size);
633             this.context.closePath();
634             this.stroke(el);
635         break;
636         case 'circle': // dot
637         case 'o':
638             this.context.beginPath();
639             this.context.arc(scr[1], scr[2], size+1+stroke05, 0, 2*Math.PI, false);
640             this.context.closePath();
641             //this.context.fill();
642             this.fill(el);
643             this.stroke(el);
644         break;
645         case 'square':  // rectangle
646         case '[]':
647             if (size<=0) break;
648             this.context.save();
649             if (this.setColor(el,'stroke')) {
650                 if(typeof el.board.highlightedObjects[el.id] != 'undefined' && el.board.highlightedObjects[el.id] != null) {
651                     this.context.fillStyle = el.visProp.highlightStrokeColor;
652                 } else {
653                     this.context.fillStyle = el.visProp.strokeColor;
654                 }
655                 this.context.fillRect(scr[1]-size-stroke05, scr[2]-size-stroke05, size*2+3*stroke05, size*2+3*stroke05);
656             }
657             this.context.restore();
658             this.context.save();
659             this.setColor(el,'fill');
660             this.context.fillRect(scr[1]-size+stroke05, scr[2]-size+stroke05, size*2-stroke05, size*2-stroke05);
661             this.context.restore();
662         break;
663         case 'plus':  // +
664         case '+':
665             //this.context.globalAlpha = el.visProp[(highlight ? 'highlightS' : 's' ) + 'trokeOpacity'];
666             this.context.beginPath();
667             this.context.moveTo(scr[1]-size, scr[2]);
668             this.context.lineTo(scr[1]+size, scr[2]);
669             this.context.moveTo(scr[1], scr[2]-size);
670             this.context.lineTo(scr[1], scr[2]+size);
671             this.context.closePath();
672             this.stroke(el);
673         break;
674         case 'diamond':   // <>
675         case '<>':
676             this.context.beginPath();
677             this.context.moveTo(scr[1]-size, scr[2]);
678             this.context.lineTo(scr[1], scr[2]+size);
679             this.context.lineTo(scr[1]+size, scr[2]);
680             this.context.lineTo(scr[1], scr[2]-size);
681             this.context.closePath();
682             this.fill(el);
683             this.stroke(el);
684         break;
685         case 'triangleup':
686         case 'a':
687         case '^':
688             this.context.beginPath();
689             this.context.moveTo(scr[1], scr[2]-size);
690             this.context.lineTo(scr[1]-sqrt32, scr[2]+s05);
691             this.context.lineTo(scr[1]+sqrt32, scr[2]+s05);
692             this.context.closePath();
693             this.fill(el);
694             this.stroke(el);
695         break;
696         case 'triangledown':
697         case 'v':
698             this.context.beginPath();
699             this.context.moveTo(scr[1], scr[2]+size);
700             this.context.lineTo(scr[1]-sqrt32, scr[2]-s05);
701             this.context.lineTo(scr[1]+sqrt32, scr[2]-s05);
702             this.context.closePath();
703             this.fill(el);
704             this.stroke(el);
705         break;
706         case 'triangleleft':
707         case '<':
708             this.context.beginPath();
709             this.context.moveTo(scr[1]-size, scr[2]);
710             this.context.lineTo(scr[1]+s05, scr[2]-sqrt32);
711             this.context.lineTo(scr[1]+s05, scr[2]+sqrt32);
712             this.context.closePath();
713             this.fill(el);
714             this.stroke(el);
715         break;
716         case 'triangleright':
717         case '>':
718             this.context.beginPath();
719             this.context.moveTo(scr[1]+size, scr[2]);
720             this.context.lineTo(scr[1]-s05, scr[2]-sqrt32);
721             this.context.lineTo(scr[1]-s05, scr[2]+sqrt32);
722             this.context.closePath();
723             this.fill(el);
724             this.stroke(el);
725         break;
726     }
727 };
728 
729 JXG.CanvasRenderer.prototype.updatePoint = function(el) {
730     this.drawPoint(el);
731 };
732 
733 JXG.CanvasRenderer.prototype.changePointStyle = function(/** Point */el) {
734     this.drawPoint(el);
735 };
736 
737 JXG.CanvasRenderer.prototype.drawText = function(/** Text */ el) {
738     var node;
739     if (el.display=='html') {
740         node = this.container.ownerDocument.createElement('div');
741         node.style.position = 'absolute';
742         node.style.color = el.visProp['strokeColor'];
743         node.className = 'JXGtext';
744         node.style.zIndex = '10';
745         this.container.appendChild(node);
746         node.setAttribute('id', this.container.id+'_'+el.id);
747         node.style.fontSize = el.board.options.text.fontSize + 'px';
748     } else {
749         node = this.drawInternalText(el);
750     }
751     el.rendNode = node;
752     el.htmlStr = '';
753     this.updateText(el);
754 };
755 
756 JXG.CanvasRenderer.prototype.updateText = function(/** JXG.Text */ el) {
757     // Update only objects that are visible.
758     if (el.visProp['visible'] === false) return;
759     if (isNaN(el.coords.scrCoords[1]+el.coords.scrCoords[2])) return;
760     this.updateTextStyle(el);
761     if (el.display=='html') {
762         el.rendNode.style.left = (el.coords.scrCoords[1])+'px';
763         el.rendNode.style.top = (el.coords.scrCoords[2] - this.vOffsetText)+'px';
764         el.updateText();
765         if (el.htmlStr!= el.plaintextStr) {
766             el.rendNode.innerHTML = el.plaintextStr;
767             if (el.board.options.text.useASCIIMathML) {
768                 AMprocessNode(el.rendNode,false);
769             }
770             el.htmlStr = el.plaintextStr;
771         }
772     } else {
773         this.updateInternalText(el);
774     }
775 };
776 
777 JXG.CanvasRenderer.prototype.drawLine = function(/** Line */ el) {
778     var scr1 = new JXG.Coords(JXG.COORDS_BY_USER, el.point1.coords.usrCoords, el.board),
779         scr2 = new JXG.Coords(JXG.COORDS_BY_USER, el.point2.coords.usrCoords, el.board),
780         ax, ay, bx, by, beta, sgn, x, y, m;
781     this.calcStraight(el,scr1,scr2);
782 
783     this.context.beginPath();
784     this.context.moveTo(scr1.scrCoords[1],scr1.scrCoords[2]);
785     this.context.lineTo(scr2.scrCoords[1],scr2.scrCoords[2]);
786     //this.context.stroke();
787     //this.context.closePath();
788     this.stroke(el);
789     // if this line has arrows attached, update them, too.
790     this.makeArrows(el,scr1,scr2);
791 };
792 
793 JXG.CanvasRenderer.prototype.updateLine = function(/** Line */ el) {
794     this.drawLine(el);
795 };
796 
797 JXG.CanvasRenderer.prototype.drawCurve = function(/** Curve */ el) {
798     this.updatePathStringPrim(el);
799 };
800 
801 JXG.CanvasRenderer.prototype.updateCurve = function(/** Curve */ el) {
802     this.drawCurve(el);
803 };
804 
805 JXG.CanvasRenderer.prototype.drawEllipse = function(el, m1, m2, sX, sY, rX, rY) {
806     var aWidth = rX*sX,
807         aHeight = rY*sY,
808         aX = m1 - aWidth/2,
809         aY = m2 - aHeight/2,
810         hB = (aWidth / 2) * .5522848,
811         vB = (aHeight / 2) * .5522848,
812         eX = aX + aWidth,
813         eY = aY + aHeight,
814         mX = aX + aWidth / 2,
815         mY = aY + aHeight / 2;
816 
817     //this.context.globalAlpha = el.visProp[(this.updateStencilBuffer(el) ? 'highlightS' : 's' ) + 'trokeOpacity'];
818     if (rX>0.0 && rY>0.0 && !isNaN(m1+m2) ) {
819         this.context.beginPath();
820         this.context.moveTo(aX, mY);
821         this.context.bezierCurveTo(aX, mY - vB, mX - hB, aY, mX, aY);
822         this.context.bezierCurveTo(mX + hB, aY, eX, mY - vB, eX, mY);
823         this.context.bezierCurveTo(eX, mY + vB, mX + hB, eY, mX, eY);
824         this.context.bezierCurveTo(mX - hB, eY, aX, mY + vB, aX, mY);
825         this.context.closePath();
826         this.fill(el);
827         this.stroke(el);
828     }
829 };
830 
831 JXG.CanvasRenderer.prototype.drawCircle = function(/** Circle */ el) {
832     this.drawEllipse(el, el.midpoint.coords.scrCoords[1], 
833                          el.midpoint.coords.scrCoords[2], 
834                          el.board.stretchX, el.board.stretchY, 
835                          2*el.Radius(), 2*el.Radius());
836 };
837 
838 JXG.CanvasRenderer.prototype.updateCircle = function(/** Circle */ el) {
839     this.drawCircle(el);
840 };
841 
842 JXG.CanvasRenderer.prototype.drawPolygon = function(/** Polygon */ el) { 
843 };
844 
845 JXG.CanvasRenderer.prototype.updatePolygonPrim = function(node, el) {
846     var pStr = '', 
847         scrCoords, i,
848         len = el.vertices.length;
849 
850     if (len<=0) return;
851     this.context.beginPath();
852     scrCoords = el.vertices[0].coords.scrCoords;
853     this.context.moveTo(scrCoords[1],scrCoords[2]);
854     for (i=1; i<len; i++) {
855             scrCoords = el.vertices[i].coords.scrCoords;
856             this.context.lineTo(scrCoords[1],scrCoords[2]);
857     }
858     this.context.closePath();
859     
860     this.fill(el);    // The edges of a polygon are displayed separately (as segments).
861 };
862 
863 /*
864  * Highlighting in CanvasRenderer means we have to render again
865  */
866 JXG.CanvasRenderer.prototype.highlight = function(/** JXG.GeometryElement */ obj) {
867     obj.board.prepareUpdate();
868     obj.board.renderer.suspendRedraw();
869     obj.board.updateRenderer();
870     obj.board.renderer.unsuspendRedraw();
871     return this;
872 };
873 
874 /*
875  * Dehighlighting in CanvasRenderer means we have to render again
876  */
877 JXG.CanvasRenderer.prototype.noHighlight = function(/** JXG.GeometryElement */ obj) {
878     obj.board.prepareUpdate();
879     obj.board.renderer.suspendRedraw();
880     obj.board.updateRenderer();
881     obj.board.renderer.unsuspendRedraw();
882     return this;
883 };
884