1 /*
  2  *  Copyright 2010 Scriptoid s.r.l
  3  *
  4  * TODO: different methods for image patterns, either whole bg, with the figure as a mask, or shrunk to size
  5  * if shrunk to size, keep ratios?
  6  *  A small library of drawing primitives.
  7  *  All primitives should have the following methods implemented:
  8  *  Like a Shape interface:
  9  *  -paint:void
 10  *  -tranform(matrix):void
 11  *  -contains(x, y):boolean
 12  *  -equals(object):boolean
 13  *  -toString():String
 14  *  -clone():object  - this is a deep clone
 15  *  -getBounds():[number, number, number, number]
 16  *  -near(distance):boolean - This should be used to test if a click is close to a primitive/figure
 17  *  -getPoints():Array - returns an array of points, so that a figure can implement contains
 18  *  -getStyleInfo():Style - returns the different styles that can be used by any shape
 19  */
 20 
 21 
 22 
 23 
 24 
 25 /**
 26   * Creates an instance of Point
 27   *
 28   *
 29   * @constructor
 30   * @this {Point}
 31   * @param {Number} x The x coordinate of point.
 32   * @param {Number} y The y coordinate of point.
 33   * @author Alex Gheorghiu <alex@scriptoid.com>
 34   * Note: Even if it is named Point this class should be named Dot as Dot is closer
 35   * then Point from math perspective.
 36   **/
 37 function Point(x, y){
 38     /**The x coordinate of point*/
 39     this.x = x;
 40     
 41     /**The y coordinate of point*/
 42     this.y = y;
 43     
 44     /**The {@link Style} of the Point*/
 45     this.style = new Style();
 46     
 47     /**Serialization type*/
 48     this.oType = 'Point'; //object type used for JSON deserialization
 49 }
 50 
 51 /**Creates a {Point} out of JSON parsed object
 52  *@param {JSONObject} o - the JSON parsed object
 53  *@return {Point} a newly constructed Point
 54  *@author Alex Gheorghiu <alex@scriptoid.com>
 55  **/
 56 Point.load = function(o){
 57     var newPoint = new Point();
 58     newPoint.x = o.x;
 59     newPoint.y = o.y;
 60     newPoint.style = Style.load(o.style);
 61     return newPoint;
 62 }
 63 
 64 
 65 /**Creates an array of points from an array of {JSONObject}s
 66  *@param {Array} v - the array of JSONObjects
 67  *@return an {Array} of {Point}s
 68  **/
 69 Point.loadArray = function(v){
 70     var newPoints = [];
 71     for(var i=0; i< v.length; i++){
 72         newPoints.push(Point.load(v[i]));
 73     }
 74     return newPoints;
 75 }
 76 
 77 
 78 /**Clones an array of points
 79  *@param {Array} v - the array of {Point}s
 80  *@return an {Array} of {Point}s
 81  **/
 82 Point.cloneArray = function(v){
 83     var newPoints = [];
 84     for(var i=0; i< v.length; i++){
 85         newPoints.push(v[i].clone());
 86     }
 87     return newPoints;
 88 }
 89 
 90 Point.prototype = {
 91     /*@param matrix is a 3x3 matrix*/
 92     transform:function(matrix){
 93         if(this.style!=null){
 94             this.style.transform(matrix);
 95         }
 96         var oldX = this.x;
 97         var oldY = this.y;
 98         this.x = matrix[0][0] * oldX + matrix[0][1] * oldY + matrix[0][2];
 99         this.y = matrix[1][0] * oldX + matrix[1][1] * oldY + matrix[1][2];
100     },
101 
102     /**Paint current {Point} withing a context
103      *If you want to use a different style then the default one change the style
104      **/
105     paint:function(context){
106         if(this.style != null){
107             this.style.setupContext(context);
108         }
109         if(this.style.strokeStyle != ""){
110             context.fillStyle = this.style.strokeStyle;
111             context.beginPath();
112             var width = 1;
113             if(this.style.lineWidth != null){
114                 width = parseInt(this.style.lineWidth);
115             }
116             context.arc(this.x, this.y, width, 0,Math.PI/180*360,false);
117             context.fill();
118         }
119     },
120 
121 
122     /**Tests if this point is similar to other point
123      *@param {Point} anotherPoint - the other point
124      **/
125     equals:function(anotherPoint){
126         if(! (anotherPoint instanceof Point) ){
127             return false;
128         }
129         return (this.x == anotherPoint.x)
130         && (this.y == anotherPoint.y)
131         && this.style.equals(anotherPoint.style);
132     },
133 
134     /**Clone current Point
135      **/
136     clone: function(){
137         var newPoint = new Point(this.x, this.y);
138         newPoint.style = this.style.clone();
139         return newPoint;
140     },
141 
142     /**Tests to see if a point (x, y) is within a range of current Point
143      *@param {Numeric} x - the x coordinate of tested point
144      *@param {Numeric} y - the x coordinate of tested point
145      *@param {Numeric} radius - the radius of the vicinity
146      *@author Alex Gheorghiu <alex@scriptoid.com>
147      **/
148     near:function(x, y, radius){
149         var distance = Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2));
150 
151         return (distance <= radius);
152     },
153 
154     contains: function(x,y){
155         return this.x == x && this.y == y;
156     },
157 
158     toString:function(){
159         return 'point(' + this.x + ',' + this.y + ')';
160     },
161 
162     getPoints:function(){
163         return [this];
164     },
165 
166     getBounds:function(){
167         return Util.getBounds(this.getPoints());
168     },
169 
170     /**
171      *We will draw a point a circle. The "visual" color and thicknes of the point will
172      *be created by the SVG's element style
173      *
174      *@see <a href="http://tutorials.jenkov.com/svg/circle-element.html">http://tutorials.jenkov.com/svg/circle-element.html</a>
175      *
176      *Example:
177      *<circle cx="40" cy="40" r="1" style="stroke:#006600; fill:#00cc00"/>
178      **/
179     toSVG: function(){
180         var r = '';
181 
182         r += '<circle cx="' + this.x + '" cy="' + this.y + '" r="' + 1 + '"' ;
183         r += this.style.toSVG();
184         r += '/>'
185 
186         return r;
187     }
188 
189 
190 }
191 
192 
193 
194 
195 /**
196   * Creates an instance of a Line
197   *
198   * @constructor
199   * @this {Line}
200   * @param {Point} startPoint - starting point of the line
201   * @param {Point} endPoint - the ending point of the line
202   * @author Alex Gheorghiu <alex@scriptoid.com>
203   **/
204 function Line(startPoint, endPoint){
205     /**Starting {@link Point} of the line*/
206     this.startPoint = startPoint;
207     
208     /**Ending {@link Point} of the line*/
209     this.endPoint = endPoint;
210     
211     /**The {@link Style} of the line*/
212     this.style = new Style();
213     
214     /**Serialization type*/
215     this.oType = 'Line'; //object type used for JSON deserialization
216 }
217 
218 /**Creates a {Line} out of JSON parsed object
219  *@param {JSONObject} o - the JSON parsed object
220  *@return {Line} a newly constructed Line
221  *@author Alex Gheorghiu <alex@scriptoid.com>
222  **/
223 Line.load = function(o){
224     var newLine = new Line();
225     newLine.startPoint = Point.load(o.startPoint);
226     newLine.endPoint = Point.load(o.endPoint);
227     newLine.style = Style.load(o.style);
228     return newLine;
229 }
230 
231 Line.prototype = {
232     transform:function(matrix){
233         this.startPoint.transform(matrix);
234         this.endPoint.transform(matrix);
235         if(this.style!=null){
236             this.style.transform(matrix);
237         }
238 
239     },
240 
241     paint:function(context){
242         if(this.style != null){
243             this.style.setupContext(context);
244         }
245         context.moveTo(this.startPoint.x, this.startPoint.y);
246         if(this.style.dashLength==0){
247             context.lineTo(this.endPoint.x, this.endPoint.y);
248         }
249         else{
250 
251             //get the length of the line
252             var lineLength=Math.sqrt(Math.pow(this.startPoint.x-this.endPoint.x,2)+Math.pow(this.startPoint.y-this.endPoint.y,2));
253 
254             //get the angle
255             var angle=Util.getAngle(this.startPoint,this.endPoint);
256 
257             //draw a dotted line
258             var move=false;
259             for(var i=0; i<lineLength; i+=(this.style.dashLength)){
260                 var p=this.startPoint.clone();
261 
262                 //translate to origin of start
263                 p.transform(Matrix.translationMatrix(-this.startPoint.x,-this.startPoint.y))
264 
265                 //move it north by incremental dashlengths
266                 p.transform(Matrix.translationMatrix(0, -i));
267 
268                 //rotate to correct location
269                 p.transform(Matrix.rotationMatrix(angle));
270 
271                 //translate back
272                 p.transform(Matrix.translationMatrix(this.startPoint.x,this.startPoint.y))
273 
274                 if (move==false){
275                     context.lineTo(p.x,p.y);
276                     move=true;
277                 }
278                 else{
279                     context.moveTo(p.x,p.y);
280                     move=false;
281                 }
282             }
283         }
284 
285         if(this.style.strokeStyle != null && this.style.strokeStyle != ""){
286             context.stroke();
287         }
288     },
289 
290     clone:function(){
291         var ret = new Line(this.startPoint.clone(), this.endPoint.clone());
292         ret.style = this.style.clone();
293         return ret;
294     },
295 
296     equals:function(anotherLine){
297         if(!anotherLine instanceof Line){
298             return false;
299         }
300         return this.startPoint.equals(anotherLine.startPoint)
301         && this.endPoint.equals(anotherLine.endPoint)
302         && this.style.equals(anotherLine.style);
303     },
304 
305     /** Tests to see if a point belongs to this line (not as infinite line but more like a segment)
306      * Algorithm: Compute line's equation and see if (x, y) verifies it.
307      * @param {Number} x - the X coordinates
308      * @param {Number} y - the Y coordinates
309      * @author Alex Gheorghiu <alex@scriptoid.com>
310      **/
311     contains: function(x, y){
312         // if the point is inside rectangle bounds of the segment
313         if (Math.min(this.startPoint.x, this.endPoint.x) <= x
314             && x <= Math.max(this.startPoint.x, this.endPoint.x)
315             && Math.min(this.startPoint.y, this.endPoint.y) <= y
316             && y <= Math.max(this.startPoint.y, this.endPoint.y)) {
317 
318             // check for vertical line
319             if (this.startPoint.x == this.endPoint.x) {
320                 return x == this.startPoint.x;
321             } else { // usual (not vertical) line can be represented as y = a * x + b
322                 var a = (this.endPoint.y - this.startPoint.y) /
323                 (this.endPoint.x - this.startPoint.x);
324                 var b = this.startPoint.y - a * this.startPoint.x;
325                 return y == a * x + b;
326             }
327         } else {
328             return false;
329         }
330     },
331 
332     /*
333      *See if we are near a {Line} by a certain radius
334      *@param {Number} x - the x coordinates
335      *@param {Number} y - the y coordinates
336      *@param {Number} radius - the radius to search for
337      *@author Zack Newsham <zack_newsham@yahoo.co.uk>
338      *@see <a href="http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html">http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html</a>
339      *@see <a href="http://www.worsleyschool.net/science/files/linepoint/distance.html">http://www.worsleyschool.net/science/files/linepoint/distance.html </a> (see method 5)
340      *TODO: review not made
341      **/
342     near:function(x,y,radius){
343         with(this){
344             //get the slope of the line
345             var m;
346             if(endPoint.x==startPoint.x){
347                 return ( (startPoint.y-radius<=y && endPoint.y+radius>=y) || (endPoint.y-radius<=y && startPoint.y+radius>=y))
348                 && x>startPoint.x-radius && x<startPoint.x+radius ;
349             }
350             if(startPoint.y==endPoint.y){
351                 return ( (startPoint.x-radius<=x && endPoint.x+radius>=x) || (endPoint.x-radius<=x && startPoint.x+radius>=x))
352                 && y>startPoint.y-radius && y<startPoint.y+radius ;
353             }
354 
355 
356             startX = Math.min(endPoint.x,startPoint.x);
357             startY = Math.min(endPoint.y,startPoint.y);
358             endX = Math.max(endPoint.x,startPoint.x);
359             endY = Math.max(endPoint.y,startPoint.y);
360 
361             m = (endPoint.y-startPoint.y)/(endPoint.x-startPoint.x);
362             b = -1;
363             //get the intercept
364             var c = startPoint.y-m*startPoint.x;
365 
366             //get the radius
367             var d = (m*x+(b*y)+c)/Math.sqrt(Math.pow(m,2)+Math.pow(b,2));
368             if(d < 0){
369                 d = 0 - d;
370             }
371             return (d<=radius && endX>=x && x>=startX && endY>=y && y>=startY)
372             || startPoint.near(x,y,radius) || endPoint.near(x,y,radius);
373             }
374 
375     },
376 
377     /**we need to create a new array each time, or we will affect the actual shape*/
378     getPoints:function(){
379         points=[];
380         points.push(this.startPoint);
381         points.push(this.endPoint);
382         return points;
383     },
384 
385     /**
386      *Get bounds for this line
387      *@author Alex Gheorghiu <alex@scriptoid.com>
388      **/
389     getBounds:function(){
390         return Util.getBounds(this.getPoints());
391     },
392 
393     /**String representation*/
394     toString:function(){
395         return 'line(' + this.startPoint + ',' + this.endPoint + ')';
396     },
397 
398     /**Render the SVG fragment for this primitive*/
399     toSVG:function(){
400         //<line x1="0" y1="0" x2="300" y2="300" style="stroke:rgb(99,99,99);stroke-width:2"/>
401         var result = '<line x1="' + this.startPoint.x + '" y1="' + this.startPoint.y + '" x2="' + this.endPoint.x  + '" y2="' + this.endPoint.y + '"';
402         result += this.style.toSVG();
403         result += " />"
404         return  result;
405     }
406 }
407 
408 
409 
410 /**
411   * Creates an instance of a Polyline
412   *
413   * @constructor
414   * @this {Polyline}
415   * @author Alex Gheorghiu <alex@scriptoid.com>
416   **/
417 function Polyline(){
418     /**An {Array} of {@link Point}s*/
419     this.points = []
420     
421     /**The {@link Style} of the polyline*/
422     this.style = new Style();
423     
424     /**The starting {@link Point}. 
425      * Required for path, we could use getPoints(), but this existed first.
426      * Also its a lot simpler. Each other element used in path already has a startPoint
427      **/
428     this.startPoint = null;
429     
430     /**Serialization type*/
431     this.oType = 'Polyline'; //object type used for JSON deserialization
432 }
433 
434 /**Creates a {Polyline} out of JSON parsed object
435  *@param {JSONObject} o - the JSON parsed object
436  *@return {Polyline} a newly constructed Polyline
437  *@author Alex Gheorghiu <alex@scriptoid.com>
438  **/
439 Polyline.load = function(o){
440     var newPolyline = new Polyline();
441     newPolyline.points = Point.loadArray(o.points);
442     newPolyline.style = Style.load(o.style);
443     newPolyline.startPoint = Point.load(o.startPoint);
444     return newPolyline;
445 }
446 
447 Polyline.prototype = {
448     addPoint:function(point){
449         if(this.points.length==0){
450             this.startPoint=point;
451         }
452         this.points.push(point);
453     },
454     transform:function(matrix){
455         with(this){
456             if(style!=null){
457                 style.transform(matrix);
458             }
459             for(var i=0; i<points.length; i++){
460                 points[i].transform(matrix);
461             }
462             }
463     },
464     getPoints:function(){
465         var p=[];
466         for (var i=0; i<this.points.length; i++){
467             p.push(this.points[i]);
468         }
469         return p;
470     },
471 
472     getBounds:function(){
473         return Util.getBounds(this.getPoints());
474     },
475 
476     clone:function(){
477         var ret=new Polyline();
478         with(this){
479             for(var i=0; i<points.length; i++){
480                 ret.addPoint(points[i].clone());
481             }
482             }
483         ret.style=this.style.clone();
484         return ret;
485     },
486 
487 
488     equals:function(anotherPolyline){
489         if(!anotherPolyline instanceof Polyline){
490             return false;
491         }
492         if(anotherPolyline.points.length == this.points.length){
493             for(var i=0; i<this.points.length; i++){
494                 if(!this.points[i].equals(anotherPolyline.points[i])){
495                     return false;
496                 }
497             }
498         }
499         else{
500             return false;
501         }
502 
503         if(!this.style.equals(anotherPolyline.style)){
504             return false;
505         }
506 
507         if(!this.startPoint.equals(anotherPolyline.startPoint)){
508             return false;
509         }
510 
511 
512 
513         return true;
514     },
515     paint:function(context){
516         with(this){
517             if(style!=null){
518                 style.setupContext(context);
519             }
520             context.moveTo(points[0].x,points[0].y);
521             for(var i=1; i<points.length; i++){
522                 context.lineTo(points[i].x,points[i].y);
523             }
524             if(style.strokeStyle!=null && this.style.strokeStyle!=""){
525                 context.stroke();
526             }
527             if(style.fillStyle!=null && this.style.fillStyle!=""){
528                 context.fill();
529             }
530             }
531     },
532 
533     contains:function(x, y){
534         return Util.isPointInside(new Point(x, y), this.getPoints())
535     },
536 
537     near:function(x,y,radius){
538         with(this){
539             for(var i=0; i< points.length-1; i++){
540                 var l=new Line(points[i],points[i+1]);
541                 if(l.near(x,y)){
542                     return true;
543                 }
544             }
545             return false;
546             }
547 
548     },
549 
550     toString:function(){
551         var result = 'polyline(';
552         with(this){
553             for(var i=0; i < points.length; i++){
554                 result += points[i].toString() + ' ';
555             }
556             }
557         result += ')';
558         return result;
559     },
560 
561     /**Render the SVG fragment for this primitive*/
562     toSVG:function(){
563         //<polyline points="0,0 0,20 20,20 20,40 40,40 40,60" style="fill:white;stroke:red;stroke-width:2"/>
564         var result = '<polyline points="';
565         for(var i=0; i < this.points.length; i++){
566             result += this.points[i].x + ',' + this.points[i].y + ' ';
567         }
568         result += '"';
569         result += this.style.toSVG();
570         result += '/>';
571 
572         return result;
573     }
574 }
575 
576 
577 /**
578   * Creates an instance of a Polygon
579   *
580   * @constructor
581   * @this {Polygon}
582   * @author Alex Gheorghiu <alex@scriptoid.com>
583   **/
584 function Polygon(){
585     /**An {Array} of {@link Point}s*/
586     this.points = []
587     
588     /**The {@link Style} of the polygon*/
589     this.style = new Style();
590     
591     /**Serialization type*/
592     this.oType = 'Polygon'; //object type used for JSON deserialization
593 }
594 
595 /**Creates a {Polygon} out of JSON parsed object
596  *@param {JSONObject} o - the JSON parsed object
597  *@return {Polygon} a newly constructed Polygon
598  *@author Alex Gheorghiu <alex@scriptoid.com>
599  **/
600 Polygon.load = function(o){
601     var newPolygon = new Polygon();
602     newPolygon.points = Point.loadArray(o.points);
603     newPolygon.style = Style.load(o.style);
604     return newPolygon;
605 }
606 
607 
608 Polygon.prototype = {
609     addPoint:function(point){
610         this.points.push(point);
611     },
612 
613 
614     getPosition:function(){
615         return [this.points[0].x,[this.points[0].y]];
616     },
617 
618 
619     paint:function(context){
620         with(context){
621             beginPath();
622             if(this.style!=null){
623                 this.style.setupContext(context);
624             }
625             if(this.points.length > 1){
626                 moveTo(this.points[0].x,this.points[0].y);
627                 for(var i=1; i<this.points.length; i++){
628                     lineTo(this.points[i].x, this.points[i].y)
629                 }
630             }
631             closePath();
632 
633             //first fill
634             if(this.style.fillStyle!=null && this.style.fillStyle!=""){
635                 fill();
636             }
637 
638             //then stroke
639             if(this.style.strokeStyle!=null && this.style.strokeStyle!=""){
640                 stroke();
641             }
642             }
643     },
644 
645     getPoints:function(){
646         return this.points;
647     },
648 
649     /**
650      *@return {Array<Number>} - returns [minX, minY, maxX, maxY] - bounds, where
651      *  all points are in the bounds.*/
652     getBounds:function(){
653         return Util.getBounds(this.getPoints());
654     },
655 
656     fill:function(context,color){
657         with(this){
658             context.fillStyle=color;
659             context.beginPath();
660             context.moveTo(points[0].x,points[0].y);
661             for(var i=1; i<points.length; i++){
662                 context.lineTo(points[i].x,points[i].y);
663             }
664             context.lineTo(points[0].x,points[0].y);
665             context.closePath();
666             context.fill();
667             }
668     },
669 
670     near:function(x,y,radius){
671         with(this){
672             var i=0;
673             for(i=0; i< points.length-1; i++){
674                 var l=new Line(points[i],points[i+1]);
675                 if(l.near(x,y,radius)){
676                     return true;
677                 }
678             }
679             l=new Line(points[i],points[0]);
680             if(l.near(x,y,radius)){
681                 return true;
682             }
683             return false;
684             }
685     },
686     equals:function(anotherPolygon){
687         if(!anotherPolygon instanceof Polygon){
688             return false;
689         }
690         with(this){
691             if(anotherPolygon.points.length == points.length){
692                 for(var i=0; i<points.length; i++){
693                     if(!points[i].equals(anotherPolygon.points[i])){
694                         return false;
695                     }
696                 }
697             }
698             //TODO: test for all Polygon members
699             }
700         return true;
701     },
702 
703     clone:function(){
704         var ret=new Polygon();
705         with(this){
706             for(var i=0; i<points.length; i++){
707                 ret.addPoint(points[i].clone());
708             }
709             }
710         ret.style=this.style.clone();
711         return ret;
712     },
713 
714     contains:function(x, y){
715         var inPath = false;
716         return Util.isPointInside(new Point(x,y),this.points);
717     },
718 
719     transform:function(matrix){
720         with(this){
721             if(style!=null){
722                 style.transform(matrix);
723             }
724             for(var i=0; i < points.length; i++){
725                 points[i].transform(matrix);
726             }
727             }
728     },
729 
730     toString:function(){
731         var result = 'polygon(';
732         with(this){
733             for(var i=0; i < points.length; i++){
734                 result += points[i].toString() + ' ';
735             }
736             }
737         result += ')';
738         return result;
739     },
740 
741     /**Render the SVG fragment for this primitive*/
742     toSVG:function(){
743         //<polygon points="220,100 300,210 170,250" style="fill:#cccccc; stroke:#000000;stroke-width:1"/>
744         var result = '<polygon points="';
745         for(var i=0; i < this.points.length; i++){
746             result += this.points[i].x + ',' + this.points[i].y + ' ';
747         }
748         result += '" '
749         //+  'style="fill:#cccccc;stroke:#000000;stroke-width:1"'
750         +  this.style.toSVG()
751         +  ' />';
752         return result;
753     }
754 }
755 
756 
757 /**
758   * Creates an instance of a quad curve.
759   * A curved line determined by 2 normal points (startPoint and endPoint) and 1 control point (controlPoint)
760   *
761   * @constructor
762   * @this {QuadCurve}
763   * @param {Point} startPoint - starting point of the line
764   * @param {Point} controlPoint - the control point of the line
765   * @param {Point} endPoint - the ending point of the line
766   * @see <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Quadratic_B.C3.A9zier_curves">http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Quadratic_B.C3.A9zier_curves</a>
767  **/
768 function QuadCurve(startPoint, controlPoint, endPoint){
769     /**The start {@link Point}*/
770     this.startPoint = startPoint;
771     
772     /**The controll {@link Point}*/
773     this.controlPoint = controlPoint;
774     
775     /**The end {@link Point}*/
776     this.endPoint = endPoint;
777     
778     /**The {@link Style} of the quad*/
779     this.style = new Style();
780     
781     /**Serialization type*/
782     this.oType = 'QuadCurve'; //object type used for JSON deserialization
783 }
784 
785 /**Creates a {QuadCurve} out of JSON parsed object
786  *@param {JSONObject} o - the JSON parsed object
787  *@return {QuadCurve} a newly constructed QuadCurve
788  *@author Alex Gheorghiu <alex@scriptoid.com>
789  **/
790 QuadCurve.load = function(o){
791     var newQuad = new QuadCurve();
792     newQuad.startPoint = Point.load(o.startPoint);
793     newQuad.controlPoint = Point.load(o.controlPoint);
794     newQuad.endPoint = Point.load(o.endPoint);
795     newQuad.style = Style.load(o.style);
796     return newQuad;
797 }
798 
799 /**Creates an {Array} of {QuadCurve} out of JSON parsed object
800  *@param {JSONObject} v - the JSON parsed object (actually an {Array} of {JSONObject}s
801  *@return {Array} of {QuadCurve}s
802  *@author Alex Gheorghiu <alex@scriptoid.com>
803  **/
804 QuadCurve.loadArray = function(v){
805     var quads = [];
806 
807     for(var i=0; i<v.length; i++){
808         quads.push(QuadCurve.load(v[i]));
809     }
810 
811     return quads;
812 }
813 
814 QuadCurve.prototype = {
815     transform:function(matrix){
816         if(this.style!=null){
817             this.style.transform(matrix);
818         }
819         this.startPoint.transform(matrix);
820         this.controlPoint.transform(matrix);
821         this.endPoint.transform(matrix);
822     },
823 
824     getPoints:function(){
825         //should this return a number of points along the line?
826         return [this.startPoint,this.controlPoint,this.endPoint];
827     },
828 
829     /*We could use an interpolation algorightm t=0,1 and pick 10 points to iterate on ...but for now it's fine
830      **/
831     getBounds:function(){
832         return Util.getBounds(this.getPoints());
833     },
834 
835 
836     paint:function(context){
837         with(context){
838             if(this.style!=null){
839                 this.style.setupContext(context);
840             }
841             with(this){
842                 moveTo(startPoint.x,startPoint.y);
843                 quadraticCurveTo(controlPoint.x, controlPoint.y, endPoint.x, endPoint.y);
844                 //first fill
845                 if(style.fillStyle!=null && this.style.fillStyle!=""){
846                     context.fill();
847                 }
848 
849                 //then stroke
850                 if(style.strokeStyle!=null && this.style.strokeStyle!=""){
851                     stroke();
852                 }
853 
854                 }
855             }
856     },
857 
858     /*
859      *TODO: algorithm not clear and maybe we can find the math formula to determine if we have an intersection
860      *@see <a href="http://rosettacode.org/wiki/Bitmap/B%C3%A9zier_curves/Quadratic">http://rosettacode.org/wiki/Bitmap/B%C3%A9zier_curves/Quadratic</a>
861      */
862     near:function(x,y,radius){
863         with(this){
864             var polls=100;
865             if(!Util.isPointInside(new Point(x,y), [startPoint,controlPoint,endPoint]) && !startPoint.near(x,y,radius) && ! endPoint.near(x,y,radius)){
866                 return false;//not inside the control points, so can't be near the line
867             }
868             var low=0;
869             var high=polls;
870             var i=(high-low)/2;
871             while(i >= low && i <= high && high-low>0.01){//high-low indicates>0.01 stops us from taking increasingly tiny steps
872                 i=low+(high-low)/2 //we want the mid point
873 
874                 //dont fully understand this
875                 var t = i / polls;
876                 var fromEnd = Math.pow((1.0 - t), 2); //get how far from end we are and square it
877                 var a = 2.0 * t * (1.0 - t);
878                 var fromStart = Math.pow(t, 2); //get how far from start we are and square it
879                 var newX = fromEnd * startPoint.x + a * controlPoint.x + fromStart * endPoint.x;//?
880                 var newY = fromEnd * startPoint.y + a * controlPoint.y + fromStart * endPoint.y;//?
881                 p=new Point(newX,newY);
882                 if(p.near(x,y,radius)){
883                     return true;
884                 }
885 
886                 //get distance between start and the point we are looking for, and the current point on line
887                 pToStart=Math.sqrt(Math.pow(startPoint.x-p.x,2)+Math.pow(startPoint.y-p.y,2));
888                 myToStart=Math.sqrt(Math.pow(startPoint.x-x,2)+Math.pow(startPoint.y-y,2));
889 
890                 //if our point is closer to start, we know that our cursor must be between start and where we are
891                 if(myToStart<pToStart){
892                     high=i;
893                 }
894                 else if(myToStart!=pToStart){
895                     low=i;
896                 }
897                 else{
898                     return false;//their distance is the same but the point is not near, return false.
899                 }
900                 return startPoint.near(x,y,radius)|| endPoint.near(x,y,radius);
901             }
902             }
903     },
904 
905     clone:function(){
906         ret=new QuadCurve(this.startPoint.clone(),this.controlPoint.clone(),this.endPoint.clone());
907         ret.style=this.style.clone();
908         return ret;
909     },
910 
911     equals:function(anotherQuadCurve){
912         if(!anotherQuadCurve instanceof QuadCurve){
913             return false;
914         }
915 
916         return this.startPoint.equals(anotherQuadCurve.startPoint)
917         && this.controlPoint.equals(anotherQuadCurve.controlPoint)
918         && this.endPoint.equals(anotherQuadCurve.endPoint)
919         && this.style.equals(anotherQuadCurve.style);
920     },
921 
922     /**
923      *@deprecated
924      **/
925     deprecated_contains:function(x, y){
926         return this.near(x,y,3);
927         points=[this.startPoint,this.controlPoint,this.endPoint];
928         return Util.isPointInside(new Point(x,y),points);
929     },
930 
931     /**
932      * @see sources for <a href="http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/awt/geom/QuadCurve2D.java">java.awt.geom.QuadCurve2D</a>
933      * @author (just converted to JavaScript) alex@scriptoid.com
934      */
935     contains:function(x,y) {
936 
937         var x1 = this.startPoint.x;
938         var y1 = this.startPoint.y;
939         var xc = this.controlPoint.x;
940         var yc = this.controlPoint.y;
941         var x2 = this.endPoint.x;
942         var y2 = this.endPoint.y;
943 
944         /*
945 	 * We have a convex shape bounded by quad curve Pc(t)
946 	 * and ine Pl(t).
947 	 *
948 	 *     P1 = (x1, y1) - start point of curve
949 	 *     P2 = (x2, y2) - end point of curve
950 	 *     Pc = (xc, yc) - control point
951 	 *
952 	 *     Pq(t) = P1*(1 - t)^2 + 2*Pc*t*(1 - t) + P2*t^2 =
953 	 *           = (P1 - 2*Pc + P2)*t^2 + 2*(Pc - P1)*t + P1
954 	 *     Pl(t) = P1*(1 - t) + P2*t
955 	 *     t = [0:1]
956 	 *
957 	 *     P = (x, y) - point of interest
958 	 *
959 	 * Let's look at second derivative of quad curve equation:
960 	 *
961 	 *     Pq''(t) = 2 * (P1 - 2 * Pc + P2) = Pq''
962 	 *     It's constant vector.
963 	 *
964 	 * Let's draw a line through P to be parallel to this
965 	 * vector and find the intersection of the quad curve
966 	 * and the line.
967 	 *
968 	 * Pq(t) is point of intersection if system of equations
969 	 * below has the solution.
970 	 *
971 	 *     L(s) = P + Pq''*s == Pq(t)
972 	 *     Pq''*s + (P - Pq(t)) == 0
973 	 *
974 	 *     | xq''*s + (x - xq(t)) == 0
975 	 *     | yq''*s + (y - yq(t)) == 0
976 	 *
977 	 * This system has the solution if rank of its matrix equals to 1.
978 	 * That is, determinant of the matrix should be zero.
979 	 *
980 	 *     (y - yq(t))*xq'' == (x - xq(t))*yq''
981 	 *
982 	 * Let's solve this equation with 't' variable.
983 	 * Also let kx = x1 - 2*xc + x2
984 	 *          ky = y1 - 2*yc + y2
985 	 *
986 	 *     t0q = (1/2)*((x - x1)*ky - (y - y1)*kx) /
987 	 *                 ((xc - x1)*ky - (yc - y1)*kx)
988 	 *
989 	 * Let's do the same for our line Pl(t):
990 	 *
991 	 *     t0l = ((x - x1)*ky - (y - y1)*kx) /
992 	 *           ((x2 - x1)*ky - (y2 - y1)*kx)
993 	 *
994 	 * It's easy to check that t0q == t0l. This fact means
995 	 * we can compute t0 only one time.
996 	 *
997 	 * In case t0 < 0 or t0 > 1, we have an intersections outside
998 	 * of shape bounds. So, P is definitely out of shape.
999 	 *
1000 	 * In case t0 is inside [0:1], we should calculate Pq(t0)
1001 	 * and Pl(t0). We have three points for now, and all of them
1002 	 * lie on one line. So, we just need to detect, is our point
1003 	 * of interest between points of intersections or not.
1004 	 *
1005 	 * If the denominator in the t0q and t0l equations is
1006 	 * zero, then the points must be collinear and so the
1007 	 * curve is degenerate and encloses no area.  Thus the
1008 	 * result is false.
1009 	 */
1010         var kx = x1 - 2 * xc + x2;
1011         var ky = y1 - 2 * yc + y2;
1012         var dx = x - x1;
1013         var dy = y - y1;
1014         var dxl = x2 - x1;
1015         var dyl = y2 - y1;
1016 
1017         var t0 = (dx * ky - dy * kx) / (dxl * ky - dyl * kx);
1018         if (t0 < 0 || t0 > 1 || t0 != t0) {
1019             return false;
1020         }
1021 
1022         var xb = kx * t0 * t0 + 2 * (xc - x1) * t0 + x1;
1023         var yb = ky * t0 * t0 + 2 * (yc - y1) * t0 + y1;
1024         var xl = dxl * t0 + x1;
1025         var yl = dyl * t0 + y1;
1026 
1027         return (x >= xb && x < xl) ||
1028         (x >= xl && x < xb) ||
1029         (y >= yb && y < yl) ||
1030         (y >= yl && y < yb);
1031     },
1032 
1033     toString:function(){
1034         return 'quad(' + this.startPoint + ',' + this.controlPoint + ',' + this.endPoint + ')';
1035     },
1036 
1037     /**Render the SVG fragment for this primitive
1038      *@see <a href="http://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands">http://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands</a>
1039      **/
1040     toSVG:function(){
1041         //<path d="M200,300 Q400,50 600,300 T1000,300" fill="none" stroke="red" stroke-width="5"  />
1042 
1043         var result = '<path d="M';
1044         result += this.startPoint.x + ',' + this.endPoint.y;
1045         result += ' Q' + this.controlPoint.x + ',' + this.controlPoint.y;
1046         result += ' ' + this.endPoint.x + ',' + this.endPoint.y;
1047 
1048         result += '" '
1049         +  this.style.toSVG()
1050         +  ' />';
1051 
1052         return result;
1053     }
1054 }
1055 
1056 
1057 /**
1058   * Creates an instance of a cubic curve.
1059   * A curved line determined by 2 normal points (startPoint and endPoint) and 2 control points (controlPoint1, controlPoint2)
1060   *
1061   * @constructor
1062   * @this {CubicCurve}
1063   * @param {Point} startPoint - starting point of the line
1064   * @param {Point} controlPoint1 - 1st control point of the line
1065   * @param {Point} controlPoint2 - 2nd control point of the line
1066   * @param {Point} endPoint - the ending point of the line
1067   * @see <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves">http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves</a>
1068  **/
1069 function CubicCurve(startPoint, controlPoint1, controlPoint2, endPoint){
1070     /**The start {@link Point}*/
1071     this.startPoint = startPoint;
1072     
1073     /**The first controll {@link Point}*/
1074     this.controlPoint1 = controlPoint1;
1075     
1076     /**The second controll {@link Point}*/
1077     this.controlPoint2 = controlPoint2;
1078     
1079     /**The end {@link Point}*/
1080     this.endPoint = endPoint;
1081     
1082     /**The {@link Style} of the quad*/
1083     this.style = new Style();
1084     
1085     /**Object type used for JSON deserialization*/
1086     this.oType = 'CubicCurve';
1087 }
1088 
1089 /**Creates a {CubicCurve} out of JSON parsed object
1090  *@param {JSONObject} o - the JSON parsed object
1091  *@return {CubicCurve} a newly constructed CubicCurve
1092  *@author Alex Gheorghiu <alex@scriptoid.com>
1093  **/
1094 CubicCurve.load = function(o){
1095     var newCubic = new CubicCurve();
1096 
1097     newCubic.startPoint = Point.load(o.startPoint);
1098     newCubic.controlPoint1 = Point.load(o.controlPoint1);
1099     newCubic.controlPoint2 = Point.load(o.controlPoint2);
1100     newCubic.endPoint = Point.load(o.endPoint);
1101 
1102     newCubic.style = Style.load(o.style);
1103     return newCubic;
1104 }
1105 
1106 
1107 CubicCurve.prototype = {
1108     transform:function(matrix){
1109         if(this.style!=null){
1110             this.style.transform(matrix);
1111         }
1112         this.startPoint.transform(matrix);
1113         this.controlPoint1.transform(matrix);
1114         this.controlPoint2.transform(matrix);
1115         this.endPoint.transform(matrix);
1116     },
1117     paint:function(context){
1118         with(this){
1119             if(style!=null){
1120                 style.setupContext(context);
1121             }
1122             context.moveTo(startPoint.x,startPoint.y);
1123             context.bezierCurveTo(controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPoint.x, endPoint.y)
1124 
1125 
1126             if(style.fillStyle!=null && this.style.fillStyle!=""){
1127                 context.fill();
1128             }
1129 
1130             if(style.strokeStyle!=null && this.style.strokeStyle!=""){
1131                 context.stroke();
1132             }
1133             }
1134     },
1135 
1136     clone:function(){
1137         ret = new CubicCurve(this.startPoint.clone(),this.controlPoint1.clone(), this.controlPoint2.clone(),this.endPoint.clone());
1138         ret.style=this.style.clone();
1139         return ret;
1140     },
1141 
1142     equals:function(anotherCubicCurve){
1143         if(!anotherCubicCurve instanceof CubicCurve){
1144             return false;
1145         }
1146         return this.startPoint.equals(anotherCubicCurve.startPoint)
1147         && this.controlPoint1.equals(anotherCubicCurve.controlPoint1)
1148         && this.controlPoint2.equals(anotherCubicCurve.controlPoint2)
1149         && this.endPoint.equals(anotherCubicCurve.endPoint);
1150     },
1151 
1152     /**@deprecated*/
1153     deprecated_contains:function(x,y){
1154         with(this){
1155             var q1=new QuadCurve(startPoint,controlPoint1,controlPoint2);
1156             var q2=new QuadCurve(controlPoint1,controlPoint2,endPoint);
1157             return q1.contains(x,y) || q2.contains(x,y);
1158             //restore();
1159             }
1160 
1161     },
1162 
1163     /**
1164      * Inspired by java.awt.geom.CubicCurve2D
1165      */
1166     contains:function(x, y) {
1167         if (!(x * 0.0 + y * 0.0 == 0.0)) {
1168             /* Either x or y was infinite or NaN.
1169              * A NaN always produces a negative response to any test
1170              * and Infinity values cannot be "inside" any path so
1171              * they should return false as well.
1172              */
1173             return false;
1174         }
1175         // We count the "Y" crossings to determine if the point is
1176         // inside the curve bounded by its closing line.
1177         var x1 = this.startPoint.x//getX1();
1178         var y1 = this.startPoint.y//getY1();
1179         var x2 = this.endPoint.x//getX2();
1180         var y2 = this.endPoint.y//getY2();
1181         var crossings =
1182         (Util.pointCrossingsForLine(x, y, x1, y1, x2, y2) +
1183             Util.pointCrossingsForCubic(x, y,
1184                 x1, y1,
1185                 this.controlPoint1.x, this.controlPoint1.y,
1186                 this.controlPoint2.x, this.controlPoint2.y,
1187                 x2, y2, 0));
1188         return ((crossings & 1) == 1);
1189     },
1190 
1191 
1192     /**
1193      *TODO: algorithm not clear and maybe we can find the math formula to determine if we have an intersection
1194      *@see <a href="http://rosettacode.org/wiki/Bitmap/B%C3%A9zier_curves/Cubic">http://rosettacode.org/wiki/Bitmap/B%C3%A9zier_curves/Cubic</a>
1195      */
1196     near:function(x,y,radius){
1197         var polls=100;
1198         with(this){
1199             for(i=0; i<=polls; ++i){
1200                 var t=i/polls;
1201                 var fromEnd=Math.pow((1 - t), 3);
1202                 var fromStart=Math.pow(t, 3);
1203                 var b = 3 * t * Math.pow((1 - t), 2);
1204                 var c = 3 * Math.pow(t, 2) * (1 - t);
1205                 var newX = fromEnd * startPoint.x + b * controlPoint1.x + c * controlPoint2.x + fromStart * endPoint.x;
1206                 var newY = fromEnd * startPoint.y + b * controlPoint1.y + c * controlPoint2.y + fromStart * endPoint.y;
1207                 var p=new Point(newX,newY);
1208                 if(p.near(x,y,radius)){
1209                     return true;
1210                 }
1211             }
1212             }
1213         return false
1214     },
1215 
1216     getPoints:function(){
1217         return [this.startPoint,this.controlPoint1,this.controlPoint2,this.endPoint];
1218     },
1219 
1220     getBounds:function(){
1221         return Util.getBounds(this.getPoints());
1222     },
1223 
1224     toString:function(){
1225         return 'quad(' + this.startPoint + ',' + this.controlPoint1 + ',' + this.controlPoint2 + ',' + this.endPoint + ')';
1226     },
1227 
1228     /**Render the SVG fragment for this primitive
1229      *@see <a href="http://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands">http://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands</a>
1230      **/
1231     toSVG:function(){
1232         //<path d="M100,200 C100,100 250,100 250,200" />
1233 
1234 
1235         var result = '<path d="M';
1236         result += this.startPoint.x + ',' + this.endPoint.y;
1237         result += ' C' + this.controlPoint1.x + ',' + this.controlPoint1.y;
1238         result += ' ' + this.controlPoint2.x + ',' + this.controlPoint2.y;
1239         result += ' ' + this.endPoint.x + ',' + this.endPoint.y;
1240 
1241         result += '" style="' + this.style.toSVG() +  '"  />';
1242         return result;
1243     }
1244 }
1245 
1246 
1247 /**
1248  * Draws an arc.
1249  * To allow easy transformations of the arc we will simulate the arc by a series of curves
1250  *
1251  * @constructor
1252  * @this {Arc}
1253  * @param {Number} x - the X coordinates of the "imaginary" circle of which the arc is part of
1254  * @param {Number} y - the Y coordinates of the "imaginary" circle of which the arc is part of
1255  * @param {Number} radius - the radius of the arc
1256  * @param {Number} startAngle - the degrees (not radians as in Canvas'specs) of the starting angle for this arc
1257  * @param {Number} endAngle - the degrees (not radians as in Canvas'specs) of the starting angle for this arc
1258  * @param {Number} direction - the direction of drawing. For now it's from startAngle to endAngle (anticlockwise). Not really used
1259  * @param {Number} styleFlag (optional) -
1260  * 1: close path between start of arc and end,
1261  * 2: draw pie slice, line to center point, line to start point
1262  * default: empty/0/anything else: just draw the arc
1263  * TODO: make it a class constant
1264  * @see <a href="http://stackoverflow.com/questions/2688808/drawing-quadratic-bezier-circles-with-a-given-radius-how-to-determine-control-po">http://stackoverflow.com/questions/2688808/drawing-quadratic-bezier-circles-with-a-given-radius-how-to-determine-control-po</a>
1265  **/
1266 
1267 function Arc(x, y, radius, startAngle, endAngle, direction, styleFlag){
1268     /**End angle. Required for dashedArc*/
1269     this.endAngle = endAngle;
1270     
1271     /**Start angle. required for dashedArc*/
1272     this.startAngle = startAngle;
1273     
1274     /**The center {@link Point} of the circle*/
1275     this.middle = new Point(x,y); 
1276     
1277     /**The radius of the circle*/
1278     this.radius = radius 
1279     
1280     /**An {Array} of {@link QuadCurve}s used to draw the arc*/
1281     this.curves = [];
1282     
1283     /**Accuracy. It tells the story of how many QuadCurves we will use*/
1284     var numControlPoints = 8;
1285     
1286     /**The start {@link Point}*/
1287     this.startPoint = null;
1288     
1289     /**The end {@link Point}*/
1290     this.endPoint = null;
1291     
1292     /**The start angle, in radians*/
1293     this.startAngleRadians = 0;
1294     
1295     /**The end angle, in radians*/
1296     this.endAngleRadians = 0;
1297 
1298     //code shamelessly stollen from the above site.
1299     var start = Math.PI/180 * startAngle; //convert the angles back to radians
1300     this.startAngleRadians = start;
1301     this.endAngleRadians = Math.PI/180 * endAngle;
1302     var arcLength = (Math.PI/180*(endAngle-startAngle))/ numControlPoints;
1303     for (var i = 0; i < numControlPoints; i++) {
1304         if (i < 1)
1305         {
1306             this.startPoint = new Point(x + radius * Math.cos(arcLength * i),y + radius * Math.sin(arcLength * i))
1307         }
1308         var startPoint=new Point(x + radius * Math.cos(arcLength * i),y + radius * Math.sin(arcLength * i))
1309 
1310         //control radius formula
1311         //where does it come from, why does it work?
1312         var controlRadius = radius / Math.cos(arcLength * .5);
1313 
1314         //the control point is plotted halfway between the arcLength and uses the control radius
1315         var controlPoint=new Point(x + controlRadius * Math.cos(arcLength * (i + 1) - arcLength * .5),y + controlRadius * Math.sin(arcLength * (i + 1) - arcLength * .5))
1316         if (i == (numControlPoints - 1))
1317         {
1318             this.endPoint = new Point(x + radius * Math.cos(arcLength * (i + 1)),y + radius * Math.sin(arcLength * (i + 1)));
1319         }
1320         var endPoint=new Point(x + radius * Math.cos(arcLength * (i + 1)),y + radius * Math.sin(arcLength * (i + 1)));
1321 
1322 
1323         //if we arent starting at 0, rotate it to where it needs to be
1324 
1325         //move to origin (O)
1326         startPoint.transform(Matrix.translationMatrix(-x,-y));
1327         controlPoint.transform(Matrix.translationMatrix(-x,-y));
1328         endPoint.transform(Matrix.translationMatrix(-x,-y));
1329 
1330         //rotate by angle (start)
1331         startPoint.transform(Matrix.rotationMatrix(start));
1332         controlPoint.transform(Matrix.rotationMatrix(start));
1333         endPoint.transform(Matrix.rotationMatrix(start));
1334 
1335         //move it back to where it was
1336         startPoint.transform(Matrix.translationMatrix(x,y));
1337         controlPoint.transform(Matrix.translationMatrix(x,y));
1338         endPoint.transform(Matrix.translationMatrix(x,y));
1339 
1340         this.curves.push(new QuadCurve(startPoint,controlPoint,endPoint));
1341     }
1342 
1343     /**The style flag - see  contructor's arguments*/
1344     this.styleFlag = styleFlag;
1345     
1346     /**The {@link Style} of the arc*/
1347     this.style = new Style();
1348     
1349     /**Adding a reference to the end point makes the transform code hugely cleaner*/
1350     this.direction = direction;
1351     
1352     /**Object type used for JSON deserialization*/
1353     this.oType = 'Arc';
1354 }
1355 
1356 
1357 /**Creates a {Arc} out of JSON parsed object
1358  *@param {JSONObject} o - the JSON parsed object
1359  *@return {Arc} a newly constructed Arc
1360  *@author Alex Gheorghiu <alex@scriptoid.com>
1361  **/
1362 Arc.load = function(o){
1363     var newArc = new Arc();
1364 
1365     newArc.endAngle = o.endAngle;
1366     newArc.startAngle = o.startAngle;
1367     newArc.middle = Point.load(o.middle);
1368     newArc.radius = o.radius
1369     newArc.curves = QuadCurve.loadArray(o.curves);
1370 
1371     /*we need to load these 'computed' values as they are computed only in constructor :(
1372      *TODO: maybe make a new function setUp() that deal with this*/
1373     newArc.startPoint = Point.load(o.startPoint);
1374     newArc.endPoint = Point.load(o.endPoint);
1375     newArc.startAngleRadians = o.startAngleRadians;
1376     newArc.endAngleRadians = o.endAngleRadians;
1377 
1378     newArc.styleFlag = o.styleFlag;
1379     newArc.style = Style.load(o.style);
1380 
1381     return newArc;
1382 }
1383 
1384 /**Creates a {Arc} out of an Array of JSON parsed object
1385  *@param {Array} v - an {Array} of JSON parsed objects
1386  *@return {Array}of newly constructed Arcs
1387  *@author Alex Gheorghiu <alex@scriptoid.com>
1388  **/
1389 Arc.loadArray = function(v){
1390     var newArcs = [];
1391 
1392     for(var i=0; i<v.length; i++){
1393         newArcs.push(Arc.load(v[i]));
1394     }
1395 
1396     return newArcs;
1397 }
1398 
1399 
1400 
1401 Arc.prototype = {
1402     transform:function(matrix){
1403         /* rotations - ok
1404          * translations - ok
1405          * scale - ok
1406          * skew - NOT ok (i do not know how to preserve points, angles...etc- maybe a Cola :)
1407          **/
1408         with(this){
1409             //transform the style
1410             if(style!=null){
1411                 style.transform(matrix);
1412             }
1413 
1414             //transform the center of the circle
1415             middle.transform(matrix);
1416 
1417             //transform each curve
1418             for(var i=0; i<curves.length; i++){
1419                 curves[i].transform(matrix);
1420             }
1421             }
1422     },
1423 
1424 
1425     paint:function(context){
1426         with(this){
1427             if(style!=null){
1428                 style.setupContext(context);
1429             }
1430             context.lineWidth = style.lineWidth;
1431             //context.arc(x,y,radius,(Math.PI/180)*startAngle,(Math.PI/180)*endAngle,direction);
1432             context.moveTo(curves[0].startPoint.x,curves[0].startPoint.y);
1433             for(var i=0; i<curves.length; i++){
1434                 context.quadraticCurveTo(curves[i].controlPoint.x,curves[i].controlPoint.y,curves[i].endPoint.x,curves[i].endPoint.y)
1435             //curves[i].paint(context);
1436             }
1437 
1438             if(styleFlag == 1){
1439                 context.closePath();
1440             }
1441             else if(styleFlag == 2){
1442                 context.lineTo(this.middle.x,this.middle.y);
1443                 context.closePath();
1444             }
1445 
1446             //first fill
1447             if(style.fillStyle!=null && this.style.fillStyle!=""){
1448                 context.fill();
1449             }
1450 
1451             //then stroke
1452             if(style.strokeStyle!=null && this.style.strokeStyle!=""){
1453                 context.stroke();
1454             }
1455 
1456             }
1457     },
1458 
1459     clone:function(){
1460         with(this){
1461             var ret = new Arc(middle.x,middle.y,radius,startAngle,endAngle,direction,styleFlag);
1462             for (var i=0; i< this.curves.length; i++){
1463                 ret.curves[i]=this.curves[i].clone();
1464             }
1465             ret.style=this.style.clone();
1466             return ret;
1467             }
1468     },
1469 
1470     equals:function(anotherArc){
1471         if(!anotherArc instanceof Arc){
1472             return false;
1473         }
1474 
1475         //check curves
1476         for(var i = 0 ; i < this.curves.lenght; i++){
1477             if(!this.curves[i].equals(anotherArc.curves[i])){
1478                 return false;
1479             }
1480         }
1481 
1482         return this.startAngle == anotherArc.startAngle
1483         && this.endAngle == anotherArc.endAngle
1484         && this.middle.equals(anotherArc.middle)
1485         && this.radius == anotherArc.radius
1486         && this.numControlPoints == anotherArc.numControlPoints
1487         && this.startPoint.equals(anotherArc.startPoint)
1488         && this.endPoint.equals(anotherArc.endPoint)
1489         && this.startAngleRadians == anotherArc.startAngleRadians
1490         && this.endAngleRadians == anotherArc.endAngleRadians
1491     ;
1492     },
1493 
1494     near:function(thex,they,theradius){
1495         with(this){
1496             for(var i=0; i<curves.length; i++){
1497                 if(curves[i].near(thex,they,radius)){
1498                     return true;
1499                 }
1500             }
1501             //return (distance && angle) || finishLine || startLine || new Point(x,y).near(thex,they,theradius);
1502             }
1503 
1504         return false;
1505     },
1506 
1507     contains: function(thex,they){
1508         with(this){
1509             var p = getPoints();
1510             return Util.isPointInside((new Point(thex,they)), p);
1511             }
1512 
1513     },
1514 
1515     /**Get the points of the Arc
1516      *@return {Array} of {Point}s
1517      *@author Zack
1518      **/
1519     getPoints:function(){
1520         var p = [];
1521         if(this.styleFlag ==2){
1522             p.push(this.middle);
1523         }
1524         for(var i=0; i<this.curves.length; i++){
1525             var c = this.curves[i].getPoints();
1526             for(var a=0; a<c.length; a++){
1527                 p.push(c[a]);
1528             }
1529         }
1530         return p;
1531     },
1532 
1533     getBounds:function(){
1534         return Util.getBounds(this.getPoints());
1535     },
1536 
1537     toString:function(){
1538         return 'arc(' + new Point(this.x,this.y) + ','  + this.radius + ',' + this.startAngle + ',' + this.endAngle + ',' + this.direction + ')';
1539     },
1540 
1541 
1542     /**
1543      *As we simulate an arc by {QuadCurve}s so we will collect all of them
1544      *and add it to a <path/> element.
1545      *Note: We might use the SVG's arc command but what if the arc got distorted by a transformation?
1546      *@see <a href="http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands">http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands</a>
1547      *@see <a href="http://tutorials.jenkov.com/svg/path-element.html">http://tutorials.jenkov.com/svg/path-element.html</a>
1548      *@author Alex <alex@scriptoid.com>
1549      **/
1550     toSVG: function(){
1551         var r = '<path d="';
1552         r += ' M' + this.curves[0].startPoint.x  + ',' + this.curves[0].startPoint.y
1553         for(var i=0; i<this.curves.length; i++){
1554             r += ' Q' + this.curves[i].controlPoint.x  + ',' + this.curves[i].controlPoint.y
1555             + ' ' + this.curves[i].endPoint.x + ',' + this.curves[i].endPoint.y;
1556         }
1557         r += '" ';
1558         r += this.style.toSVG();
1559         r += '/>';
1560         return r;
1561     }
1562 }
1563 
1564 
1565 
1566 /**
1567  * Approximate an ellipse through 4 bezier curves, one for each quadrant
1568  * 
1569  * @constructor
1570  * @this {Ellipse}
1571  * @param {Point} centerPoint - the center point of the ellipse
1572  * @param {Number} width - the width of the ellipse
1573  * @param {Number} height - the height of the ellipse
1574 
1575  * @see <a href="http://www.codeguru.com/cpp/g-m/gdi/article.php/c131">http://www.codeguru.com/cpp/g-m/gdi/article.php/c131</a>
1576  * @see <a href="http://www.tinaja.com/glib/ellipse4.pdf">http://www.tinaja.com/glib/ellipse4.pdf</a>
1577  * @author Zack Newsham <zack_newsham@yahoo.co.uk>
1578  **/
1579 function Ellipse(centerPoint, width, height) {
1580     /**"THE" constant*/
1581     var EToBConst = 0.2761423749154;
1582 
1583     /**Width offset*/
1584     var offsetWidth = width * 2 * EToBConst;
1585     
1586     /**Height offset*/    
1587     var offsetHeight = height * 2 * EToBConst;
1588     
1589     /**The center {@link Point}*/
1590     this.centerPoint = centerPoint;
1591     
1592     /**Top left {@link CubicCurve}*/
1593     this.topLeftCurve = new CubicCurve(new Point(centerPoint.x-width,centerPoint.y),new Point(centerPoint.x-width,centerPoint.y-offsetHeight),new Point(centerPoint.x-offsetWidth,centerPoint.y-height),new Point(centerPoint.x,centerPoint.y-height));
1594     
1595     /**Top right {@link CubicCurve}*/
1596     this.topRightCurve = new CubicCurve(new Point(centerPoint.x,centerPoint.y-height),new Point(centerPoint.x+offsetWidth,centerPoint.y-height),new Point(centerPoint.x+width,centerPoint.y-offsetHeight),new Point(centerPoint.x+width,centerPoint.y));
1597     
1598     /**Bottom right {@link CubicCurve}*/
1599     this.bottomRightCurve = new CubicCurve(new Point(centerPoint.x+width,centerPoint.y),new Point(centerPoint.x+width,centerPoint.y+offsetHeight),new Point(centerPoint.x+offsetWidth,centerPoint.y+height),new Point(centerPoint.x,centerPoint.y+height));
1600     
1601     /**Bottom left {@link CubicCurve}*/
1602     this.bottomLeftCurve = new CubicCurve(new Point(centerPoint.x,centerPoint.y+height),new Point(centerPoint.x-offsetWidth,centerPoint.y+height),new Point(centerPoint.x-width,centerPoint.y+offsetHeight),new Point(centerPoint.x-width,centerPoint.y));
1603     
1604     /**The matrix array*/
1605     this.matrix = null; //TODO: do we really need this?
1606     
1607     /**The {@link Style} used*/
1608     this.style = new Style();
1609     
1610     /**Oject type used for JSON deserialization*/
1611     this.oType = 'Ellipse'; 
1612 }
1613 
1614 /**Creates a new {Ellipse} out of JSON parsed object
1615  *@param {JSONObject} o - the JSON parsed object
1616  *@return {Ellipse} a newly constructed Ellipse
1617  *@author Alex Gheorghiu <alex@scriptoid.com>
1618  **/
1619 Ellipse.load = function(o){
1620     var newEllipse= new Ellipse(new Point(0,0), 0, 0); //fake ellipse (if we use a null centerPoint we got errors)
1621 
1622     newEllipse.offsetWidth = o.offsetWidth;
1623     newEllipse.offsetHeight = o.offsetHeight;
1624     newEllipse.centerPoint = Point.load(o.centerPoint);
1625     newEllipse.topLeftCurve = CubicCurve.load(o.topLeftCurve);
1626     newEllipse.topRightCurve = CubicCurve.load(o.topRightCurve);
1627     newEllipse.bottomRightCurve = CubicCurve.load(o.bottomRightCurve);
1628     newEllipse.bottomLeftCurve = CubicCurve.load(o.bottomLeftCurve);
1629     this.matrix = Matrix.clone(o.matrix);
1630     newEllipse.style = Style.load(o.style);
1631 
1632     return newEllipse;
1633 }
1634 
1635 
1636 Ellipse.prototype = {
1637     transform:function(matrix){
1638         this.topLeftCurve.transform(matrix);
1639         this.topRightCurve.transform(matrix);
1640         this.bottomLeftCurve.transform(matrix);
1641         this.bottomRightCurve.transform(matrix);
1642         this.centerPoint.transform(matrix);
1643         if(this.style){
1644             this.style.transform(matrix);
1645         }
1646     },
1647 
1648     paint:function(context){
1649         with(context){
1650             with(this){
1651                 if(style!=null){
1652                     style.setupContext(context);
1653                 }
1654                 beginPath();
1655                 moveTo(topLeftCurve.startPoint.x,topLeftCurve.startPoint.y);
1656                 bezierCurveTo(topLeftCurve.controlPoint1.x,topLeftCurve.controlPoint1.y,topLeftCurve.controlPoint2.x,topLeftCurve.controlPoint2.y,topLeftCurve.endPoint.x,topLeftCurve.endPoint.y);
1657                 bezierCurveTo(topRightCurve.controlPoint1.x,topRightCurve.controlPoint1.y,topRightCurve.controlPoint2.x,topRightCurve.controlPoint2.y,topRightCurve.endPoint.x,topRightCurve.endPoint.y);
1658                 bezierCurveTo(bottomRightCurve.controlPoint1.x,bottomRightCurve.controlPoint1.y,bottomRightCurve.controlPoint2.x,bottomRightCurve.controlPoint2.y,bottomRightCurve.endPoint.x,bottomRightCurve.endPoint.y);
1659                 bezierCurveTo(bottomLeftCurve.controlPoint1.x,bottomLeftCurve.controlPoint1.y,bottomLeftCurve.controlPoint2.x,bottomLeftCurve.controlPoint2.y,bottomLeftCurve.endPoint.x,bottomLeftCurve.endPoint.y);
1660                 //first fill
1661                 if(style.fillStyle!=null && this.style.fillStyle!=""){
1662                     fill();
1663                 }
1664 
1665                 //then stroke
1666                 if(style.strokeStyle!=null && this.style.strokeStyle!=""){
1667                     stroke();
1668                 }
1669 
1670                 }
1671             }
1672     },
1673 
1674     contains:function(x,y){
1675         with(this){
1676             var points = topLeftCurve.getPoints();
1677             curves = [topRightCurve, bottomRightCurve, bottomLeftCurve];
1678             for(var i=0; i<curves.length; i++){
1679                 curPoints = curves[i].getPoints();
1680 
1681                 for(var a=0; a<curPoints.length; a++){
1682                     points.push(curPoints[a]);
1683                 }
1684             }
1685             return Util.isPointInside(new Point(x,y), points);
1686             }
1687 
1688         return false;
1689     },
1690 
1691     near:function(x,y,radius){
1692         return this.topLeftCurve.near(x,y,radius) || this.topRightCurve.near(x,y,radius) || this.bottomLeftCurve.near(x,y,radius) || this.bottomRightCurve.near(x,y,radius);
1693     },
1694 
1695     equals:function(anotherEllipse){
1696         if(!anotherEllipse instanceof Ellipse){
1697             return false;
1698         }
1699 
1700         return this.offsetWidth == anotherEllipse.offsetWidth
1701         && this.offsetHeight == anotherEllipse.offsetHeight
1702         && this.centerPoint.equals(anotherEllipse.centerPoint)
1703         && this.topLeftCurve.equals(anotherEllipse.topLeftCurve)
1704         && this.topRightCurve.equals(anotherEllipse.topRightCurve)
1705         && this.bottomRightCurve.equals(anotherEllipse.bottomRightCurve)
1706         && this.bottomLeftCurve.equals(anotherEllipse.bottomLeftCurve);
1707     //TODO: add this && this.matrix.equals(anotherEllipse.bottomLeftCurve)
1708     //TODO: add this && this.style.equals(anotherEllipse.bottomLeftCurve)
1709     },
1710 
1711     clone:function(){
1712         var ret=new Ellipse(this.centerPoint.clone(),10,10);
1713         ret.topLeftCurve=this.topLeftCurve.clone();
1714         ret.topRightCurve=this.topRightCurve.clone();
1715         ret.bottomLeftCurve=this.bottomLeftCurve.clone();
1716         ret.bottomRightCurve=this.bottomRightCurve.clone();
1717         ret.style=this.style.clone();
1718         return ret;
1719     },
1720 
1721     toString:function(){
1722         return 'ellipse('+this.centerPoint+","+this.xRadius+","+this.yRadius+")";
1723     },
1724 
1725     /**
1726      *@see <a href="http://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands">http://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands</a>
1727      *@author Alex Gheorghiu <scriptoid.com>
1728      **/
1729     toSVG: function(){
1730         var result = '<path d="M';
1731         result += this.topLeftCurve.startPoint.x + ',' + this.topLeftCurve.startPoint.y;
1732 
1733         //top left curve
1734         result += ' C' + this.topLeftCurve.controlPoint1.x + ',' + this.topLeftCurve.controlPoint1.y;
1735         result += ' ' + this.topLeftCurve.controlPoint2.x + ',' + this.topLeftCurve.controlPoint2.y;
1736         result += ' ' + this.topLeftCurve.endPoint.x + ',' + this.topLeftCurve.endPoint.y;
1737 
1738         //top right curve
1739         result += ' C' + this.topRightCurve.controlPoint1.x + ',' + this.topRightCurve.controlPoint1.y;
1740         result += ' ' + this.topRightCurve.controlPoint2.x + ',' + this.topRightCurve.controlPoint2.y;
1741         result += ' ' + this.topRightCurve.endPoint.x + ',' + this.topRightCurve.endPoint.y;
1742 
1743         //bottom right curve
1744         result += ' C' + this.bottomRightCurve.controlPoint1.x + ',' + this.bottomRightCurve.controlPoint1.y;
1745         result += ' ' + this.bottomRightCurve.controlPoint2.x + ',' + this.bottomRightCurve.controlPoint2.y;
1746         result += ' ' + this.bottomRightCurve.endPoint.x + ',' + this.bottomRightCurve.endPoint.y;
1747 
1748         //bottom left curve
1749         result += ' C' + this.bottomLeftCurve.controlPoint1.x + ',' + this.bottomLeftCurve.controlPoint1.y;
1750         result += ' ' + this.bottomLeftCurve.controlPoint2.x + ',' + this.bottomLeftCurve.controlPoint2.y;
1751         result += ' ' + this.bottomLeftCurve.endPoint.x + ',' + this.bottomLeftCurve.endPoint.y;
1752 
1753         result += '" ' + this.style.toSVG() +  '  />';
1754         return result;
1755 
1756     },
1757 
1758     getPoints:function(){
1759         var points = [];
1760         var curves = [this.topLeftCurve, this.topRightCurve,this.bottomRightCurve,this.bottomLeftCurve];
1761 
1762         for(var i=0; i<curves.length; i++){
1763             var curPoints = curves[i].getPoints();
1764             for(var a=0; a<curPoints.length; a++){
1765                 points.push(curPoints[a]);
1766             }
1767         }
1768         return points;
1769     },
1770 
1771 
1772     getBounds:function(){
1773         return Util.getBounds(this.getPoints());
1774     }
1775 }
1776 
1777 
1778 
1779 /**
1780  * Approximate an ellipse through 4 bezier curves, one for each quadrant
1781  * 
1782  * @constructor
1783  * @this {DashedArc}
1784  * @param {Number} x - x coodinated of the center of the "invisible" circle
1785  * @param {Number} y - y coodinated of the center of the "invisible" circle
1786  * @param {Number} radius - the radius of the "invisible" circle
1787  * @param {Number} startAngle - the angle the arc will start from
1788  * @param {Number} endAngle - the angle the arc will end into
1789  * @param {Number} direction - direction of drawing (clockwise or anti-clock wise)
1790  * @param {Style} styleFlag - the style of the arc
1791  * @param {Number} dashGap - how big the gap between the lines will be
1792 
1793  * @see <a href="http://www.codeguru.com/cpp/g-m/gdi/article.php/c131">http://www.codeguru.com/cpp/g-m/gdi/article.php/c131</a>
1794  * @see <a href="http://www.tinaja.com/glib/ellipse4.pdf">http://www.tinaja.com/glib/ellipse4.pdf</a>
1795  * @author Zack Newsham <zack_newsham@yahoo.co.uk>
1796  **/
1797 function DashedArc(x, y, radius, startAngle, endAngle, direction, styleFlag, dashGap){
1798     /**The "under the hood" {@link Arc}*/
1799     this.arc = new Arc(x, y, radius, startAngle, endAngle, direction, styleFlag);
1800     
1801     /*The {@link Style} used**/
1802     this.style = this.arc.style;
1803     
1804     /**The gap between dashes*/
1805     this.dashWidth = dashGap;
1806     
1807     /**An {Array} or {@link Arc}s*/
1808     this.lines = []; //an {Array} of {Arc}s
1809 
1810     //init the parts
1811     for(var i=0; i<100; i += this.dashWidth){
1812         var a = new Arc(x, y, radius+this.style.lineWidth/2, (endAngle-startAngle)/100*i, (endAngle-startAngle)/100*(i+1), false);
1813         a.style.strokeStyle = this.style.strokeStyle;
1814         this.lines.push(a);
1815     }
1816 
1817     /**Object type used for JSON deserialization*/
1818     this.oType = 'DashedArc'; 
1819 }
1820 
1821 /**Creates a new {Ellipse} out of JSON parsed object
1822  *@param {JSONObject} o - the JSON parsed object
1823  *@return {DashedArc} a newly constructed DashedArc
1824  *@author Alex Gheorghiu <alex@scriptoid.com>
1825  **/
1826 DashedArc.load = function(o){
1827     var newDashedArc = new DashedArc(100,100,30,0,360,false,0,6); //fake dashed (if we do not use it we got errors - endless loop)
1828     newDashedArc.style.fillStyle="#ffffff"
1829 
1830     newDashedArc.arc = Arc.load(o.arc);
1831     newDashedArc.style = newDashedArc.arc.style; //strange but...
1832     newDashedArc.dashWidth = o.dashWidth;
1833     newDashedArc.lines = Arc.loadArray(o.lines);
1834 
1835 
1836     return newDashedArc;
1837 }
1838 
1839 
1840 DashedArc.prototype = {
1841     transform:function(matrix){
1842         this.arc.transform(matrix);
1843         for (var i=0; i<this.lines.length; i++){
1844             this.lines[i].transform(matrix);
1845         }
1846     },
1847 
1848     getBounds:function(){
1849         return this.arc.getBounds();
1850     },
1851 
1852     getPoints:function(){
1853         return this.arc.getPoints();
1854     },
1855 
1856     contains:function(x,y){
1857         return this.arc.contains(x,y);
1858     },
1859 
1860     near:function(x,y,radius){
1861         return this.arc.near(x,y,radius);
1862     },
1863 
1864 
1865     toString:function(){
1866         return this.arc.toString();
1867     },
1868 
1869 
1870     toSVG: function(){
1871         throw 'Arc:toSVG() - not implemented';
1872     },
1873 
1874     /***/
1875     equals:function(anotherDashedArc){
1876         if(!anotherDashedArc instanceof DashedArc){
1877             return false;
1878         }
1879 
1880 
1881         if(this.lines.length != anotherDashedArc.lines.length){
1882             return false;
1883         }
1884         else{
1885             for(var i in this.lines){
1886                 if(!this.lines[i].equals(anotherDashedArc.lines[i])){
1887                     return false;
1888                 }
1889             }
1890         }
1891 
1892         return this.arc.equals(anotherDashedArc.arc)
1893         && this.style.equals(anotherDashedArc.style)
1894         && this.dashWidth == anotherDashedArc.dashWidth;
1895     },
1896 
1897     clone:function(){
1898         return this.arc.clone();
1899     },
1900 
1901     paint:function(context){
1902         this.style.setupContext(context);
1903         context.lineCap="round"//this.style.lineCap;
1904         for(var i=0; i<this.lines.length; i++){
1905             context.beginPath();
1906             this.lines[i].paint(context);
1907             context.stroke();
1908         }
1909         this.style.strokeStyle=null;
1910         this.arc.paint(context)
1911     }
1912 
1913 }
1914 
1915 
1916 /**
1917  * A path contains a number of elements (like shape) but they are drawn as one, i.e.
1918  * 
1919  * @example
1920  * begin path
1921  *  loop to draw shapes
1922  *      draw shape
1923  *  close loop
1924  * close path
1925  * 
1926  * 
1927  * @constructor
1928  * @this {Path}
1929  **/
1930 function Path() {
1931     /**An {Array} that will store all the basic primitives: {@link Point}s, {@link Line}s, {@link CubicCurve}s, etc that make the path*/
1932     this.primitives = [];
1933     
1934     /**The {@link Style} used for drawing*/
1935     this.style = new Style();
1936     
1937     /**Object type used for JSON deserialization*/
1938     this.oType = 'Path'; 
1939 }
1940 
1941 /**Creates a new {Path} out of JSON parsed object
1942  *@param {JSONObject} o - the JSON parsed object
1943  *@return {Path} a newly constructed {Path}
1944  *@author Alex Gheorghiu <alex@scriptoid.com>
1945  **/
1946 Path.load = function(o){
1947     var newPath = new Path(); //fake path
1948 
1949     newPath.style = Style.load(o.style);
1950 
1951     for(var i=0; i< o.primitives.length; i++){
1952         /**We can not use instanceof Point construction as
1953          *the JSON objects are typeless... so JSONObject are simply objects */
1954         if(o.primitives[i].oType == 'Point'){
1955             newPath.primitives.push(Point.load(o.primitives[i]))
1956         }
1957         else if(o.primitives[i].oType == 'Line'){
1958             newPath.primitives.push(Line.load(o.primitives[i]))
1959         }
1960         else if(o.primitives[i].oType == 'Polyline'){
1961             newPath.primitives.push(Polyline.load(o.primitives[i]))
1962         }
1963         else if(o.primitives[i].oType == 'Polygon'){
1964             newPath.primitives.push(Polygon.load(o.primitives[i]))
1965         }
1966         else if(o.primitives[i].oType == 'QuadCurve'){
1967             newPath.primitives.push(QuadCurve.load(o.primitives[i]))
1968         }
1969         else if(o.primitives[i].oType == 'CubicCurve'){
1970             newPath.primitives.push(CubicCurve.load(o.primitives[i]))
1971         }
1972         else if(o.primitives[i].oType == 'Arc'){
1973             newPath.primitives.push(Arc.load(o.primitives[i]))
1974         }
1975         else if(o.primitives[i].oType == 'Ellipse'){
1976             newPath.primitives.push(Ellipse.load(o.primitives[i]))
1977         }
1978         else if(o.primitives[i].oType == 'DashedArc'){
1979             newPath.primitives.push(DashedArc.load(o.primitives[i]))
1980         }
1981         else if(o.primitives[i].oType == 'Path'){
1982             newPath.primitives.push(Path.load(o.primitives[i]))
1983         }
1984 
1985     }
1986 
1987     return newPath;
1988 }
1989 
1990 
1991 Path.prototype = {
1992     transform:function(matrix){
1993         for(var i = 0; i<this.primitives.length; i++ ){
1994             this.primitives[i].transform(matrix);
1995         }
1996     },
1997 
1998     addPrimitive:function(primitive){
1999         this.primitives.push(primitive);
2000     },
2001 
2002     contains:function(x,y){
2003         with(this){
2004             var points=[];
2005             for(var i=0; i<primitives.length; i++){
2006                 if(primitives[i].contains(x,y)){
2007                     return true;
2008                 }
2009                 var curPoints=primitives[i].getPoints();
2010                 for(var a=0; a<curPoints.length; a++){
2011                     points.push(curPoints[a]);
2012                 }
2013             }
2014             }
2015         return Util.isPointInside(new Point(x,y),points);
2016     },
2017 
2018     near: function(x,y,radius){
2019         with(this){
2020             var points=[];
2021             for(var i=0; i<primitives.length; i++){
2022                 if(primitives[i].near(x,y,radius)){
2023                     return true;
2024                 }
2025             }
2026             }
2027         return false;
2028     },
2029 
2030     getPoints:function(){
2031         var points = [];
2032         for (var i=0; i<this.primitives.length; i++){
2033             points = points.concat(this.primitives[i].getPoints());
2034         }
2035         return points;
2036     },
2037     getBounds:function(){
2038         var points = [];
2039         for (var i in this.primitives) {
2040             var bounds = this.primitives[i].getBounds();
2041             points.push(new Point(bounds[0], bounds[1]));
2042             points.push(new Point(bounds[2], bounds[3]));
2043         }
2044         return Util.getBounds(points);
2045     },
2046 
2047     clone:function(){
2048         var ret = new Path();
2049         for (var i=0; i<this.primitives.length; i++){
2050             ret.addPrimitive(this.primitives[i].clone());
2051             if(this.primitives[i].parentFigure){
2052                 ret.primitives[i].parentFigure=ret;
2053             }
2054         }
2055         ret.style=this.style
2056         return ret;
2057     },
2058 
2059     /**@author: Alex Gheorghiu <alex@scriptoid.com>*/
2060     equals : function(anotherPath){
2061         if(!anotherPath instanceof Path){
2062             return false;
2063         }
2064 
2065         for(var i=0; i<this.primitives.length; i++){
2066             if(!this.primitives[i].equals(anotherPath.primitives[i])){
2067                 return  false;
2068             }
2069         }
2070         return true;
2071     },
2072 
2073     paint:function(context){
2074         context.save();
2075 
2076         if(this.style != null){
2077             this.style.setupContext(context);
2078         }
2079 
2080         //PAINT FILL
2081         //this loop is the one for the fill, we keep the reference.
2082         //if you try to put these two together, you will get a line that is the same colour,
2083         //even if you define different colours for each part of the line (i.e. fig 19)
2084         //not allowing multiple colours in a single path will clean this code up hugely.
2085         //
2086         if(this.style.fillStyle != null && this.style.fillStyle != "" ){
2087             context.beginPath();
2088             context.moveTo(this.primitives[0].startPoint.x,this.primitives[0].startPoint.y);
2089             for(var i = 0; i<this.primitives.length; i++ ){
2090                 var primitive  = this.primitives[i];
2091                 if(primitive instanceof Line){
2092                     context.lineTo(primitive.endPoint.x,primitive.endPoint.y);
2093                 }
2094                 else if(primitive instanceof Polyline){
2095                     for(var a=0; a<primitive.points.length; a++){
2096                         context.lineTo(primitive.points[a].x,primitive.points[a].y);
2097                     }
2098                 }
2099                 else if(primitive instanceof QuadCurve){
2100                     context.quadraticCurveTo(primitive.controlPoint.x, primitive.controlPoint.y, primitive.endPoint.x, primitive.endPoint.y);
2101                 }
2102                 else if(primitive instanceof CubicCurve){
2103                     context.bezierCurveTo(primitive.controlPoint1.x, primitive.controlPoint1.y, primitive.controlPoint2.x, primitive.controlPoint2.y, primitive.endPoint.x, primitive.endPoint.y)
2104                 }
2105             }
2106             context.fill();
2107         }
2108 
2109         //PAINT STROKE
2110         //This loop draws the lines of each individual shape. Each part might have a different strokeStyle !
2111         if(this.style.strokeStyle != null && this.style.strokeStyle != "" ){
2112             for(var i = 0; i<this.primitives.length; i++ ){
2113                 var primitive  = this.primitives[i];
2114 
2115                 context.save();
2116                 context.beginPath();
2117 
2118                 //TODO: what if a primitive does not have a start point?
2119                 context.moveTo(primitive.startPoint.x,primitive.startPoint.y);
2120 
2121                 if(primitive instanceof Line){
2122                     context.lineTo(primitive.endPoint.x,primitive.endPoint.y);
2123                 //Log.info("line");
2124                 }
2125                 else if(primitive instanceof Polyline){
2126                     for(var a=0; a<primitive.points.length; a++){
2127                         context.lineTo(primitive.points[a].x,primitive.points[a].y);
2128                     //Log.info("polyline");
2129                     }
2130                 }
2131                 else if(primitive instanceof QuadCurve){
2132                     context.quadraticCurveTo(primitive.controlPoint.x, primitive.controlPoint.y, primitive.endPoint.x, primitive.endPoint.y);
2133                 //Log.info("quadcurve");
2134                 }
2135                 else if(primitive instanceof CubicCurve){
2136                     context.bezierCurveTo(primitive.controlPoint1.x, primitive.controlPoint1.y, primitive.controlPoint2.x, primitive.controlPoint2.y, primitive.endPoint.x, primitive.endPoint.y)
2137                 //Log.info("cubiccurve");
2138                 }
2139                 else if(primitive instanceof Arc){
2140                     context.arc(primitive.startPoint.x, primitive.startPoint.y, primitive.radius, primitive.startAngleRadians, primitive.endAngleRadians, true)
2141                 //Log.info("arc" + primitive.startPoint.x + " " + primitive.startPoint.y);
2142                 }
2143                 else
2144                 {
2145                 //Log.info("unknown primitive");
2146                 }
2147 
2148                 //save primitive's old style
2149                 var oldStyle = primitive.style.clone();
2150 
2151                 //update primitive's style
2152                 if(primitive.style == null){
2153                     primitive.style = this.style;
2154                 }
2155                 else{
2156                     primitive.style.merge(this.style);
2157                 }
2158 
2159                 //use primitive's style
2160                 primitive.style.setupContext(context);
2161 
2162                 //stroke it
2163                 context.stroke();
2164 
2165                 //change primitive' style back to original one
2166                 primitive.style = oldStyle;
2167 
2168                 context.restore();
2169             }
2170         }
2171 
2172         context.restore();
2173 
2174     },
2175 
2176 
2177     /**
2178      *Export this path to SVG
2179      *@see <a href="http://tutorials.jenkov.com/svg/path-element.html">http://tutorials.jenkov.com/svg/path-element.html</a>
2180      *@example 
2181      * <path d="M50,50
2182      *        A30,30 0 0,1 35,20
2183      *        L100,100
2184      *        M110,110
2185      *        L100,0"
2186      *     style="stroke:#660000; fill:none;"/>
2187      */
2188     toSVG: function(){
2189 
2190         var result = '<path d="';
2191         var previousPrimitive = null;
2192         for(var i=0; i<this.primitives.length; i++){
2193 
2194             var primitive = this.primitives[i];
2195 
2196             if(primitive instanceof Point){
2197                 //TODO: implement me. Should we allow points?
2198                 /**Here is a big problem as if we implement the point as a line with 1px width
2199                  *upon scaling it will became obvious it's a line.
2200                  *Alternative solutions:
2201                  *1 - draw it as a SVG arc
2202                  *2 - draw it as an independent SVG circle*/
2203                 throw 'Path:toSVG()->Point - not implemented';
2204             }
2205             if(primitive instanceof Line){
2206                 /*If you want the Path to be a continuous contour we check if
2207                  *the M is unecessary - maybe we are already comming from that spot*/
2208                 if(previousPrimitive == null  || previousPrimitive.endPoint.x != primitive.startPoint.x || previousPrimitive.endPoint.y != primitive.startPoint.y){
2209                     result += ' M' + primitive.startPoint.x + ',' + primitive.startPoint.y;
2210                 }
2211                 result += ' L' + primitive.endPoint.x + ',' + primitive.endPoint.y;
2212             }
2213             else if(primitive instanceof Polyline){
2214                 for(var a=0; a<primitive.points.length; a++){
2215                     result += ' L' + primitive.points[a].x + ',' + primitive.points[a].y;
2216                 //Log.info("polyline");
2217                 }
2218             }
2219             else if(primitive instanceof QuadCurve){
2220                 result += ' Q' + primitive.controlPoint.x + ',' + primitive.controlPoint.y + ',' + primitive.endPoint.x + ',' + primitive.endPoint.y ;
2221             }
2222             else if(primitive instanceof CubicCurve){
2223                 result += ' C' + primitive.controlPoint1.x + ',' + primitive.controlPoint1.y + ',' + primitive.controlPoint2.x + ',' + primitive.controlPoint2.y + ',' + primitive.endPoint.x + ',' + primitive.endPoint.y;
2224             }
2225             else if(primitive instanceof Arc){
2226                 //TODO: implement me
2227                 //<path d="M100,100 A25 25 0 0 0 150 100" stroke="lightgreen" stroke-width="4" fill="none" />
2228                 throw 'Path:toSVG()->Arc - not implemented';
2229             }
2230             else if(primitive instanceof Polyline){
2231                 //TODO: implement me
2232                 throw 'Path:toSVGPolylineArc - not implemented';
2233             }
2234             else{
2235                 throw 'Path:toSVG()->unknown primitive rendering not implemented';
2236             }
2237 
2238             previousPrimitive = primitive;
2239         }//end for
2240         result += '" '; //end of primitive shapes
2241         //        result += ' fill="none" stroke="#0F0F00" /> '; //end of path
2242         result += this.style.toSVG(); //end of path
2243         result += ' />'; //end of path
2244 
2245         return result;
2246     }
2247 
2248 }
2249 
2250 
2251 
2252 /**A figure is simply a collection of basic primitives: Points, Lines, Arcs, Curves and Paths
2253  * A figure should not care about grouping primitives into Paths, each shape should draw itself.
2254  * The Figure only delegate the painting to the composing shape.
2255  *
2256  * @constructor
2257  * @this {DashedArc}
2258  * @param {String} name - the name of the figure
2259  *
2260  **/
2261 function Figure(name) {
2262     /**Each Figure will have an unique Id on canvas*/
2263     this.id = stack.generateId();
2264     
2265     /**Figure's name*/
2266     this.name = name;
2267     
2268     /**An {Array} of primitives that make the figure*/
2269     this.primitives = [];
2270 
2271     /**the Group'id to which this figure belongs to*/
2272     this.groupId = -1;
2273 
2274     /*Keeps track of all the handles for a figure*/
2275     //this.handles = [];
2276     //this.handleSelectedIndex=-1;
2277     //this.builder = new Builder(this.id);
2278 
2279     /**An {Array} of {@link BuilderProperty} objects*/
2280     this.properties = []; 
2281     
2282     /**The {@link Style} use to draw tis figure*/
2283     this.style = new Style();
2284 
2285     /**We keep the figure position by having different points
2286      *[central point of the figure, the middle of upper edge]
2287      * An {Array} or {@link Point}s
2288      **/
2289     this.rotationCoords = [];
2290 
2291     /**Object type used for JSON deserialization*/
2292     this.oType = 'Figure'; 
2293 }
2294 
2295 /**Creates a new {Figure} out of JSON parsed object
2296  *@param {JSONObject} o - the JSON parsed object
2297  *@return {Figure} a newly constructed Figure
2298  *@author Alex Gheorghiu <alex@scriptoid.com>
2299  **/
2300 Figure.load = function(o){
2301     var newFigure = new Figure(); //fake dashed (if we do not use it we got errors - endless loop)
2302 
2303     newFigure.id = o.id;
2304     newFigure.name = o.name;
2305     for(var i=0; i< o.primitives.length; i++){
2306         /**We can not use instanceof Point construction as
2307          *the JSON objects are typeless... so JSONObject are simply objects */
2308         if(o.primitives[i].oType == 'Point'){
2309             newFigure.primitives.push(Point.load(o.primitives[i]))
2310         }
2311         else if(o.primitives[i].oType == 'Line'){
2312             newFigure.primitives.push(Line.load(o.primitives[i]))
2313         }
2314         else if(o.primitives[i].oType == 'Polyline'){
2315             newFigure.primitives.push(Polyline.load(o.primitives[i]))
2316         }
2317         else if(o.primitives[i].oType == 'Polygon'){
2318             newFigure.primitives.push(Polygon.load(o.primitives[i]))
2319         }
2320         else if(o.primitives[i].oType == 'QuadCurve'){
2321             newFigure.primitives.push(QuadCurve.load(o.primitives[i]))
2322         }
2323         else if(o.primitives[i].oType == 'CubicCurve'){
2324             newFigure.primitives.push(CubicCurve.load(o.primitives[i]))
2325         }
2326         else if(o.primitives[i].oType == 'Arc'){
2327             newFigure.primitives.push(Arc.load(o.primitives[i]))
2328         }
2329         else if(o.primitives[i].oType == 'Ellipse'){
2330             newFigure.primitives.push(Ellipse.load(o.primitives[i]))
2331         }
2332         else if(o.primitives[i].oType == 'DashedArc'){
2333             newFigure.primitives.push(DashedArc.load(o.primitives[i]))
2334         }
2335         else if(o.primitives[i].oType == 'Text'){
2336             newFigure.primitives.push(Text.load(o.primitives[i]))
2337         }
2338         else if(o.primitives[i].oType == 'Path'){
2339             newFigure.primitives.push(Path.load(o.primitives[i]))
2340         }
2341         else if(o.primitives[i].oType == 'Figure'){
2342             newFigure.primitives.push(Figure.load(o.primitives[i])) //kinda recursevly
2343         }
2344         else if(o.primitives[i].oType == 'RasterImage'){
2345             newFigure.primitives.push(RasterImage.load(o.primitives[i])) //kinda recursevly
2346         }
2347     }//end for
2348 
2349     newFigure.groupId = o.groupId;
2350     newFigure.properties = BuilderProperty.loadArray(o.properties);
2351     newFigure.style = Style.load(o.style);
2352     newFigure.rotationCoords = Point.loadArray(o.rotationCoords);
2353 
2354 
2355     return newFigure ;
2356 }
2357 
2358 /**Creates a new {Array} of {Figure}s out of JSON parsed object
2359  *@param {JSONObject} v - the JSON parsed object
2360  *@return {Array} of newly constructed {Figure}s
2361  *@author Alex Gheorghiu <alex@scriptoid.com>
2362  **/
2363 Figure.loadArray = function(v){
2364     var newFigures = [];
2365 
2366     for(var i=0; i<v.length; i++){
2367         newFigures.push(Figure.load(v[i]));
2368     }
2369 
2370     return newFigures;
2371 }
2372 
2373 Figure.prototype = {
2374     /* used by the edit panel
2375      * @return {Text} the text item
2376      */
2377     getText:function(){
2378         for(var i=0; i<this.primitives.length; i++){
2379             if(this.primitives[i] instanceof Text){
2380                 return this.primitives[i];
2381             }
2382         }
2383 
2384         return '';
2385     },
2386 
2387     /*set the text from edit panel
2388      *@param{Text} text - text object
2389      */
2390     setText:function(text){
2391         for(var i=0; i<this.primitives.length; i++){
2392             if(this.primitives[i] instanceof Text){
2393                 this.primitives[i]=text;
2394             }
2395         }
2396     },
2397 
2398     //@param{bool} transformConnector - should we transform the connector? Used when we transform a figure,
2399     //without redrawing it.
2400     transform:function(matrix, transformConnector){
2401         if(transformConnector == "undefined" || transformConnector == undefined){
2402             transformConnector = true;
2403         }
2404         //transform all composing primitives
2405         for(var i = 0; i<this.primitives.length; i++ ){
2406             this.primitives[i].transform(matrix);
2407         }
2408 
2409         //transform the style
2410         this.style.transform(matrix);
2411 
2412         //cascade transform to the connection point
2413         //Log.info('Figure: transform()');
2414         if(transformConnector){
2415             CONNECTOR_MANAGER.connectionPointTransform(this.id,matrix);
2416         }
2417 
2418         //some figures dont have rotation coords, i.e. those that arent "real" figures, such as the highlight rectangle
2419         if(this.rotationCoords.length!=0){
2420             this.rotationCoords[0].transform(matrix);
2421             this.rotationCoords[1].transform(matrix);
2422         }
2423     },
2424 
2425     getPoints:function(){
2426         var points = [];
2427         for (var i=0; i<this.primitives.length; i++){
2428             points = points.concat(this.primitives[i].getPoints()); //add all primitive's points in a single pass
2429         }
2430         return points;
2431     },
2432 
2433     addPrimitive:function(primitive){
2434         this.primitives.push(primitive);
2435     },
2436 
2437     //no more points to add, so create the handles and selectRect
2438     finalise:function(){
2439         var bounds = this.getBounds();
2440 
2441         //central point of the figure
2442         this.rotationCoords[0] = new Point(bounds[0] + (bounds[2] - bounds[0]) / 2,
2443             bounds[1] + (bounds[3] - bounds[1]) / 2);
2444 
2445         //the middle of upper edge
2446         this.rotationCoords[1] = new Point(this.rotationCoords[0].x, bounds[1]);
2447     },
2448 
2449     /**TODO: this clone is not gonna be equal with the original
2450      *as it will have a different id
2451      **/
2452     clone:function(){
2453         var ret = new Figure();
2454         for (var i=0; i<this.primitives.length; i++){
2455             ret.addPrimitive(this.primitives[i].clone());
2456         }
2457         ret.style = this.style
2458         ret.rotationCoords[0]=this.rotationCoords[0].clone();
2459         ret.rotationCoords[1]=this.rotationCoords[1].clone();
2460         return ret;
2461     },
2462 
2463 
2464     contains:function(x,y){
2465         var points=[];
2466         for(var i=0; i<this.primitives.length; i++){
2467             if(this.primitives[i].contains(x,y)){
2468                 return true;
2469             }
2470             points = points.concat(this.primitives[i].getPoints());
2471         }
2472         return Util.isPointInside(new Point(x,y),points);
2473     },
2474 
2475     getBounds: function(){
2476         var points = [];
2477         for (var i = 0; i < this.primitives.length; i++) {
2478             var bounds = this.primitives[i].getBounds();
2479             points.push(new Point(bounds[0], bounds[1]));
2480             points.push(new Point(bounds[2], bounds[3]));
2481         }
2482         return Util.getBounds(points);
2483     },
2484 
2485 
2486     paint:function(context){
2487         if(this.style){
2488             this.style.setupContext(context);
2489         }
2490         for(var i = 0; i<this.primitives.length; i++ ){
2491             var primitive  = this.primitives[i];
2492             context.save();
2493 
2494             var oldStyle = null;
2495             if(primitive.style){ //save primitive's style
2496                 oldStyle = primitive.style.clone();
2497             }
2498 
2499             if(primitive.style == null){ //if primitive does not have a style use Figure's one
2500                 primitive.style = this.style;
2501             }
2502             else{ //if primitive has a style merge it
2503                 primitive.style.merge(this.style);
2504             }
2505 
2506             context.beginPath();
2507             primitive.paint(context);
2508             primitive.style = oldStyle;
2509             if(this.style.image != null){ //TODO: should a figure has a Style cann't just delegate all to primitives?
2510                 //clip required for background images, there were two methods, this was the second I tried
2511                 //neither work in IE
2512                 context.clip();
2513                 context.save();
2514                 if(this.rotationCoords.length != 0){
2515                     var angle=Util.getAngle(this.rotationCoords[0], this.rotationCoords[1]);
2516                     if(IE && angle==0){
2517                         angle=0.00000001;//stupid excanves, without this it puts all images down and right of the correct location
2518                     //and by an amount relative to the distane from the top left corner
2519                     }
2520 
2521                     //if we perform a rotation on the actual rotationCoords[0] (centerPoint), when we try to translate it back,
2522                     //rotationCoords[0] will = 0,0, so we create a clone that does not get changed
2523                     var rotPoint = this.rotationCoords[0].clone();
2524 
2525                     //move to origin, make a rotation, move back in place
2526                     this.transform(Matrix.translationMatrix(-rotPoint.x, -rotPoint.y))
2527                     this.transform(Matrix.rotationMatrix(-angle));
2528                     this.transform(Matrix.translationMatrix(rotPoint.x, rotPoint.y))
2529 
2530                     //TODO: these are not used...so why the whole acrobatics ?
2531                     //this was the second method that is also not supported by IE, get the image, place it in
2532                     //the correct place, then shrink it, so its still an 'image mask' but it is only a small image
2533                     //context.scale below is also part of this
2534                     //var shrinkBounds = this.getBounds();
2535 
2536                     //move back to origin, 'undo' the rotation, move back in place
2537                     this.transform(Matrix.translationMatrix(-rotPoint.x, -rotPoint.y))
2538                     this.transform(Matrix.rotationMatrix(angle));
2539                     this.transform(Matrix.translationMatrix(rotPoint.x, rotPoint.y))
2540 
2541                     //rotate current canvas to prepare it to draw the image (you can not roate the image...:D)
2542                     context.translate(rotPoint.x,rotPoint.y);
2543                     context.rotate(angle);
2544                     //context.scale(0.01,0.01)//1/getCanvas().width*shrinkBounds[0]+(shrinkBounds[2]-shrinkBounds[0])/2,1/getCanvas().width*shrinkBounds[1]+(shrinkBounds[3]-shrinkBounds[1])/2)
2545                     context.translate(-rotPoint.x,-rotPoint.y);
2546                 }
2547                 //draw image
2548                 /*context.fill();
2549                 context.beginPath();
2550                 context.globalCompositeOperation = "source-atop"
2551                  clip works best,but this works too, neither will work in IE*/
2552                 //context.fill();
2553                 context.drawImage(this.style.image,this.rotationCoords[0].x-this.style.image.width/2,this.rotationCoords[0].y-this.style.image.height/2,this.style.image.width,this.style.image.height)
2554 
2555                 context.restore();
2556             }
2557             else if (this.style.image!=null){
2558                 context.fill();
2559             }
2560 
2561             context.restore();
2562         }
2563     },
2564 
2565     equals:function(anotherFigure){
2566         if(!anotherFigure instanceof Figure){
2567             Log.info("Figure:equals() 0");
2568             return false;
2569         }
2570 
2571 
2572         if(this.primitives.length == anotherFigure.primitives.length){
2573             for(var i=0; i<this.primitives.length; i++){
2574                 if(!this.primitives[i].equals(anotherFigure.primitives[i])){
2575                     Log.info("Figure:equals() 1");
2576                     return false;
2577                 }
2578             }
2579         }
2580         else{
2581             Log.info("Figure:equals() 2");
2582             return false;
2583         }
2584         //test group
2585         if(this.groupId != anotherFigure.groupId){
2586             return false;
2587         }
2588 
2589         //test rotation coords
2590         if(this.rotationCoords.length == anotherFigure.rotationCoords.length){
2591             for(var i in this.rotationCoords){
2592                 if(!this.rotationCoords[i].equals(anotherFigure.rotationCoords[i])){
2593                     return false;
2594                 }
2595             }
2596         }
2597         else{
2598             return false;
2599         }
2600 
2601         //test style
2602         if(!this.style.equals(anotherFigure.style)){
2603             return false;
2604         }
2605 
2606         return true;
2607     },
2608 
2609     near:function(x,y,radius){
2610         for(var i=0; i<this.primitives.length; i++){
2611             if(this.primitives[i].near(x,y,radius)){
2612                 return true;
2613             }
2614         }
2615         return false;
2616     },
2617     toString:function(){
2618         var result = this.name + ' [id: ' + this.id + '] (';
2619         for(var i = 0; i<this.primitives.length; i++ ){
2620             result += this.primitives[i].toString();
2621         }
2622         result += ')';
2623         return result;
2624     },
2625 
2626 
2627     toSVG: function(){
2628         var tempSVG = '';
2629         for(var i = 0; i<this.primitives.length; i++ ){
2630             var primitive  = this.primitives[i];
2631 
2632             var oldStyle = null;
2633             if(primitive.style){ //save primitive's style
2634                 oldStyle = primitive.style.clone();
2635             }
2636 
2637             if(primitive.style == null){ //if primitive does not have a style use Figure's one
2638                 primitive.style = this.style;
2639             }
2640             else{ //if primitive has a style merge it
2641                 primitive.style.merge(this.style);
2642             }
2643 
2644             tempSVG += this.primitives[i].toSVG();
2645 
2646             //restore primitives style
2647             primitive.style = oldStyle;
2648         }
2649 
2650         return tempSVG;
2651     }
2652 }
2653 
2654 
2655 
2656 
2657 /**
2658  * A predefined matrix of a 90 degree clockwise rotation 
2659  *
2660  *@see <a href="http://en.wikipedia.org/wiki/Rotation_matrix">http://en.wikipedia.org/wiki/Rotation_matrix</a>
2661  */ 
2662 R90 = [
2663     [Math.cos(0.0872664626),-Math.sin(0.0872664626), 0],
2664     [Math.sin(0.0872664626),  Math.cos(0.0872664626), 0],
2665     [0,  0, 1]
2666     ];
2667     
2668 /**
2669  * A predefined matrix of a 90 degree anti-clockwise rotation 
2670  *
2671  *@see <a href="http://en.wikipedia.org/wiki/Rotation_matrix">http://en.wikipedia.org/wiki/Rotation_matrix</a>
2672  */     
2673 R90A = [
2674     [Math.cos(0.0872664626), Math.sin(0.0872664626), 0],
2675     [-Math.sin(0.0872664626),  Math.cos(0.0872664626), 0],
2676     [0,  0, 1]
2677     ];
2678 
2679 /**
2680  * The identity matrix
2681  */      
2682 
2683 IDENTITY=[[1,0,1],[0,1,0],[0,0,1]];
2684 
2685 
2686 if(typeof(document) == 'undefined'){ //test only from console
2687     print("\n--==Point==--\n");
2688     p = new Point(10, 10);
2689     print(p);
2690     print("\n");
2691     p.transform(R90);
2692     print(p)
2693 
2694     print("\n--==Line==--\n");
2695     l = new Line(new Point(10, 23), new Point(34, 50));
2696     print(l);
2697     print("\n");
2698 
2699 
2700     print("\n--==Polyline==--\n");
2701     polyline = new Polyline();
2702     for(var i=0;i<5; i++){
2703         polyline.addPoint(new Point(i, i*i));
2704     }
2705     print(polyline);
2706     print("\n");
2707 
2708 
2709 
2710 
2711     print("\n--==Quad curve==--\n");
2712     q = new QuadCurve(new Point(75,25), new Point(25,25), new Point(25,62))
2713     print(q)
2714 
2715     print("\n");
2716     q.transform(R90);
2717     print(q)
2718 
2719     print("\n--==Cubic curve==--\n");
2720     q = new CubicCurve(new Point(75,40), new Point(75,37), new Point(70,25), new Point(50,25))
2721     print(q)
2722 
2723     print("\n");
2724     q.transform(R90);
2725     print(q)
2726 
2727     print("\n--==Figure==--\n");
2728     f = new Figure();
2729     f.addPrimitive(p);
2730     f.addPrimitive(q);
2731     print(f);
2732 
2733     f.transform(R90);
2734     print("\n");
2735     print(f);
2736     print("\n");
2737 //f.draw();
2738 }
2739