1 /*
  2     Copyright 2011
  3         Emmanuel Ostenne
  4         Alfred Wassermann
  5 
  6     This file is part of JSXGraph.
  7 
  8     JSXGraph is free software: you can redistribute it and/or modify
  9     it under the terms of the GNU Lesser General Public License as published by
 10     the Free Software Foundation, either version 3 of the License, or
 11     (at your option) any later version.
 12 
 13     JSXGraph is distributed in the hope that it will be useful,
 14     but WITHOUT ANY WARRANTY; without even the implied warranty of
 15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16     GNU Lesser General Public License for more details.
 17 
 18     You should have received a copy of the GNU Lesser General Public License
 19     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
 20 */
 21 JXG.TracenpocheReader = new function() {
 22 
 23     this.tokenize = function(inputStr, prefix, suffix) {
 24         if (typeof prefix !== 'string') { prefix = '<>+-&'; }
 25         if (typeof suffix !== 'string') { suffix = '=>&:';  }
 26         var c;                      // The current character.
 27         var from;                   // The index of the start of the token.
 28         var i = 0;                  // The index of the current character.
 29         var length = inputStr.length;
 30         var n;                      // The number value.
 31         var q;                      // The quote character.
 32         var str;                    // The string value.
 33         var isSmallName;
 34 
 35         var result = [];            // An array to hold the results.
 36 
 37         // Make a token object.
 38         var make = function (type, value) {
 39             return {
 40                 type: type,
 41                 value: value,
 42                 from: from,
 43                 to: i
 44             };
 45         };
 46         
 47         var error = function(type, value, msg) {
 48             console.log('Tokenizer: problem with ' + type + ' ' + value + ': ' + msg);
 49         };
 50 
 51         if (!inputStr || inputStr=='') return;
 52 
 53         // Loop through this text, one character at a time.
 54 
 55         c = inputStr.charAt(i);
 56         while (c) {
 57             from = i;
 58             if (c <= ' ') {                                                 // Ignore whitespace
 59                 i++;
 60                 c = inputStr.charAt(i);
 61             } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {  // name
 62                 str = c;
 63                 i += 1;
 64                 // if the name starts with a small capital, anything may follow
 65                 // otherwise, if the next char is a capital letter like
 66                 // AB, the meaning is Dist(A,B)
 67                 isSmallName = (c >= 'a' && c <= 'z') ? true : false;
 68                     
 69                 for (;;) {
 70                     c = inputStr.charAt(i);
 71                     if (isSmallName) {
 72                         if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
 73                             (c >= '0' && c <= '9') || (c === "'") /*|| c === '_' */) {
 74                             str += c;
 75                             i++;
 76                         } else {
 77                             break;
 78                         }
 79                     } else { 
 80                         if ((c >= '0' && c <= '9') || (c === "'") ) {
 81                             str += c;
 82                             i++;
 83                         } else {
 84                             break;
 85                         }
 86                     }
 87                 }
 88                 if (result.length>0 && result[result.length-1].type=='name' 
 89                     && result[result.length-1].value!='var' 
 90                     && result[result.length-1].value!='for' 
 91                     ) {     
 92                                                                     // Here we have the situation AB -> A#B
 93                     result.push(make('operator', '#'));
 94                 }
 95                 result.push(make('name', str));
 96             } else if (c >= '0' && c <= '9') {                              // number
 97                 // A number cannot start with a decimal point. It must start with a digit,
 98                 // possibly '0'.
 99                 str = c;
100                 i++;
101                 for (;;) {                  // Look for more digits
102                     c = inputStr.charAt(i);
103                     if (c < '0' || c > '9') { break; }
104                     i++;
105                     str += c;
106                 }
107                 if (c === '.') {            // Look for a decimal fraction part
108                     i++;
109                     str += c;
110                     for (;;) {
111                         c = inputStr.charAt(i);
112                         if (c < '0' || c > '9') { break; }
113                         i++;
114                         str += c;
115                     }
116                 }
117                 if (c === 'e' || c === 'E') {   // Look for an exponent part.
118                     i++;
119                     str += c;
120                     c = inputStr.charAt(i);
121                     if (c === '-' || c === '+') {
122                         i++;
123                         str += c;
124                         c = inputStr.charAt(i);
125                     }
126                     if (c < '0' || c > '9') {
127                         error('number', str, "Bad exponent");
128                     }
129                     do {
130                         i++;
131                         str += c;
132                         c = inputStr.charAt(i);
133                     } while (c >= '0' && c <= '9');
134                 }
135                 if (c >= 'a' && c <= 'z') {        // Make sure the next character is not a letter
136                     i++;
137                     str += c;
138                     error('number', str, "Bad number");
139                 }
140                 n = +str;          // Convert the string value to a number. If it is finite, then it is a good token.
141                 if (isFinite(n)) {
142                     result.push(make('number', n));
143                 } else {
144                     error('number', str, "Bad number");
145                 }
146             } else if (c === '\'' || c === '"') {                               // string
147                 str = '';
148                 q = c;
149                 i++;
150                 for (;;) {
151                     c = inputStr.charAt(i);
152                     if (c < ' ') {
153                         error('string', str, c === '\n' || c === '\r' || c === '' ?
154                             "Unterminated string." :
155                             "Control character in string.", make('', str));
156                         break;
157                     }
158                     if (i >= length) {
159                         error('string', str, "Unterminated string"); break;
160                     }
161                     if (c === q) {   // Look for the closing quote.
162                         break;
163                     }
164                     if (c === '\\') {          // Look for escapement
165                         i++;
166                         if (i >= length) {
167                             error('string', str, "Unterminated string"); break;
168                         }
169                         c = inputStr.charAt(i);
170                         switch (c) {
171                         case 'b':
172                             c = '\b'; break;
173                         case 'f':
174                             c = '\f'; break;
175                         case 'n':
176                             c = '\n'; break;
177                         case 'r':
178                             c = '\r'; break;
179                         case 't':
180                             c = '\t'; break;
181                         case 'u':
182                             if (i >= length) {
183                                 error('string', str, "Unterminated string");
184                             }
185                             c = parseInt(inputStr.substr(i + 1, 4), 16);
186                             if (!isFinite(c) || c < 0) {
187                                 error('string', str, "Unterminated string");
188                             }
189                             c = String.fromCharCode(c);
190                             i += 4;
191                             break;
192                         }
193                     }
194                     str += c;
195                     i++;
196                 }
197                 i++;
198                 result.push(make('string', str));
199                 c = inputStr.charAt(i);
200             } else if (c === '/' && inputStr.charAt(i + 1) === '/') {   // comment
201                 i++;
202                 for (;;) {
203                     c = inputStr.charAt(i);
204                     if (c === '\n' || c === '\r' || c === '') { break; }
205                     i++;
206                 }
207             } else if (prefix.indexOf(c) >= 0) {                        // combining (multi-character operator)
208                 str = c;
209                 i++;
210                 while (true) {
211                     c = inputStr.charAt(i);
212                     if (i >= length || suffix.indexOf(c) < 0) {
213                         break;
214                     }
215                     i++;
216                     str += c;
217                 }
218                 result.push(make('operator', str));
219             } else {                                                    // single-character operator
220                 i++;
221                 result.push(make('operator', c));
222                 c = inputStr.charAt(i);
223             }
224         }
225         
226         return result;
227     }; 
228         
229     this.parseOptions = function(board) {
230         //var code, i, len = script.length;
231        
232        // Analyze this.data for "@options;"
233         // Just for testing.
234         board.setBoundingBox([-10,10,10,-10], true);
235         board.create('axis', [[0, 0], [1, 0]]);
236         board.create('axis', [[0, 0], [0, 1]]);
237         
238         /*
239         for (i=start+1; i<len; i++) {
240             code = script[i];
241             if (code=='') continue;
242 
243             if (code.match(/@/)) {   // Reached the end of the options section
244                 return i-1;
245             }
246             console.log("OPT>", code);
247             // Read options:
248         }
249         */
250     };
251 
252     this.parse = function(tokens, scopeObjName) { 
253         var scope;
254         var symbol_table = {};
255         var token;
256         var token_nr;
257         var i, arr;
258         
259         var error = function(tok, msg) {
260             throw new Error("TraceEnPocheReader: syntax error at char " + tok.from + ': ' + tok.value+ ' - ' + msg);
261         };
262 
263         var createObject = function (o) {
264             function F() {};
265             F.prototype = o;
266             return new F();
267         };
268         
269         var original_scope = {
270             define: function (n) {
271                 //console.log("Add scope var " + n.value);            
272                 this.def[n.value] = n.value;
273             },
274             find: function (n) {
275                 var e = this, o;
276                 while (true) {
277                     o = e.def[n];
278                     if (o) {
279                         return "$"+'["' + e.def[n].value + '"]';
280                     }
281                     e = e.parent;
282                     if (!e) {
283                         o = symbol_table[n];
284                         return o;
285                     }
286                 }
287             },
288             pop: function () {
289                 scope = this.parent;
290             }
291         };
292 
293         var new_scope = function () {
294             var s = scope;
295             scope = Object.create(original_scope);
296             scope.def = {};
297             scope.parent = s;
298             return scope;
299         };
300         
301         var advance = function (id) {
302             var a, o, t, v;
303             if (id && token.id !== id) {
304                 error(token, "Expected '" + id + "'.");
305             }
306             if (token_nr >= tokens.length) {
307                 token = symbol_table["(end)"];
308                 return;
309             }
310             t = tokens[token_nr];
311             token_nr++;
312             v = t.value;
313             a = t.type;
314             if (a === "name") {
315                 o = symbol_table[v];
316                 if (!o) {
317                     o = variable(v);
318                 }
319             } else if (a === "operator") {
320                 o = symbol_table[v];
321                 if (!o) {
322                     error(t, "Unknown operator.");
323                 }
324             } else if (a === "string" || a ===  "number") {
325                 o = symbol_table["(literal)"];
326                 // a = "literal";
327             } else {
328                 error(t, "Unexpected token.");
329             }
330             token = createObject(o);
331             token.from  = t.from;
332             token.to    = t.to;
333             token.value = v;
334             return token;
335         };
336 
337         var expression = function (rbp) {
338             var left, t = token;
339             advance();
340             left = t.nud();
341             while (rbp < token.lbp) {
342                 t = token;
343                 advance();
344                 left = t.led(left);
345             }
346             return left;
347         };
348         
349         var statement = function () {
350             var n = token, v;
351 
352             if (n.std) {
353                 advance();
354                 //scope.reserve(n);
355         
356                 return n.std();
357             }
358             v = expression(0) + ';';
359             /*
360             if (!v.assignment && v.id !== "(") {
361                 error(v, "Bad expression statement.");
362             }
363             */
364             advance(";");
365             return v;
366         };
367 
368         var statements = function () {
369             var a = [], s;
370             while (true) {
371                 if (token.id === "end" || token.id === "(end)") {
372                     break;
373                 }
374                 s = statement();
375                 if (s) {
376                     a.push(s);
377                 }
378             }
379             return a.length === 0 ? null : a;
380         };
381 
382         var original_symbol = {
383             nud: function () {
384                 error(this, "Undefined.");
385             },
386             led: function (left) {
387                 error(this, "Missing operator.");
388             }
389         };
390 
391         /*
392          * Shortcuts
393          */
394         var symbol = function (id, bp) {
395             var s = symbol_table[id];
396             bp = bp || 0;
397             if (s) {
398                 if (bp >= s.lbp) {
399                     s.lbp = bp;
400                 }
401             } else {
402                 s = createObject(original_symbol);
403                 s.id = s.value = id;
404                 s.lbp = bp;
405                 symbol_table[id] = s;
406             }
407             return s;
408         };
409 
410         var constant = function (s, v) {
411             var x = symbol(s);
412             x.nud = function () {
413                 this.value = symbol_table[this.id].value;
414                 return this.value;
415             };
416             x.value = v;
417             x.arity = 'name';
418             return x;
419         };
420 
421         var predefined = function (s, v) {
422             var x = symbol(s);
423             x.nud = function () {
424                 this.value = symbol_table[this.id].value;
425                 return this.value;
426             };
427             x.arity = "function";
428             x.value = v;
429             return x;
430         };
431 
432         var variable = function (s) {
433             var x = symbol(s), second;
434 //console.log("Define " + s);
435             scope.define(x);
436             
437             x.nud = function () {
438                 this.value = symbol_table[this.id].value;
439                 if (token.id === '[') {
440 //console.log("Proceed " + this.value);
441                     second = expression(11);
442                     return scopeObjName + '["' + this.value + '"+' + second + ']';
443                 }   
444                 return scopeObjName + '["' + this.value + '"]';
445             };
446             return x;
447         };
448         
449         var infix = function (id, bp, led) {
450             var s = symbol(id, bp);
451             s.led = led || function (left) {
452                 this.first = left;
453                 this.second = expression(bp);
454                 return '('+this.first + this.value + this.second+')';
455             };
456             return s;
457         };
458 
459         var infixr = function (id, bp, led) {
460             var s = symbol(id, bp);
461             s.led = led || function (left) {
462                 this.first = left;
463                 this.second = expression(bp - 1);
464                 return '('+this.first + this.value + this.second+')';
465             };
466             return s;
467         };
468         
469         var assignment = function (id) {
470             return infixr(id, 10, function (left) {
471                 this.first = left;
472                 if (token.id === '[') {
473                     this.first += expression(0);
474                     this.first = '$[' + this.first + ']';
475                 } 
476                 this.second = expression(9);
477                 this.assignment = true;
478                 return this.first + this.value + this.second;
479             });
480         };  
481         
482         var prefix = function (id, nud) {
483             var s = symbol(id);
484             s.nud = nud || function () {
485                 this.first = expression(70);
486                 return this.value + this.first;
487             };
488             return s;
489         };
490 
491         var stmt = function (s, f) {
492             var x = symbol(s);
493             x.std = f;
494             return x;
495         };
496         
497         /*
498          * Define the language
499          * 
500          */
501         symbol("(literal)").nud = function() { return (typeof this.value === "string")? "'" + this.value + "'" :this.value; };
502         symbol("(end)");
503         symbol("(name)");
504         symbol(":");
505         symbol(";");
506         symbol(")");
507         symbol("]");
508         symbol("}");
509         symbol(",");
510         symbol("do");
511         symbol("to");
512         symbol("end");
513 
514         constant("true", true);
515         constant("false", false);
516 
517         /*
518          * Predefined functions
519          */
520         for (i=0; i<this.tepElements.length; i++) {
521             predefined(this.tepElements[i], "that." + this.tepElements[i]);
522         }
523         
524         constant("x", "x");
525         predefined("pi", "Math.PI");
526         predefined("sin", "Math.sin");
527         predefined("cos", "Math.cos");
528         predefined("tan", "Math.tan");
529         predefined("abs", "Math.abs");
530         predefined("racine", "Math.sqrt");
531         predefined("carre", "JXG.Math.carre");
532 
533         assignment("="); 
534         
535         infixr("&&", 30);
536         infixr("||", 30);
537         
538         arr = ["==", "!=", "<", "<=", ">", ">="];
539         for (i=0; i<arr.length; i++) {
540             infixr(arr[i], 40);
541         }
542 
543         infix("#", 50, function (left) {
544                 this.first = left;
545                 this.second = expression(0);
546                 return 'function(){return '+ this.first + '.Dist(' + this.second+');}';
547         });
548 
549         infix("+", 50);
550         infix("-", 50);
551         infix("*", 60);
552         infix("/", 60);
553         infix("%", 50);
554 
555         infixr("^", 65, function (left) {
556                 this.first = left;
557                 this.second = expression(64);
558                 return 'Math.pow('+this.first + ',' + this.second+')';
559         });
560 
561         infix("(", 80, function (left) {
562             var a = [];
563             this.first = left;
564             this.second = a;
565             
566             // Parameters
567             if (token.id !== ")") {
568                 while (true) {
569                     a.push(expression(0));
570                     if (token.id !== ",") {
571                         break;
572                     }
573                     advance(",");
574                 }
575             }
576             advance(")");
577             
578             // Optional attributes
579             if (token.id === '{') {
580                 this.third = expression(0).first;
581             } else {
582                 this.third = [];
583             }
584             return this.first + '([' + this.second.join(',') + '],[' + this.third.join(',') + '])';
585         });
586 
587         prefix("-");
588         prefix("(", function () {
589             var e = expression(0);
590             advance(")");
591             return e;
592         });    
593 
594         prefix("fonction", function () {
595             advance("(");
596             var e = expression(0);
597             advance(")");
598             e = e.replace(/,\[\]/g,"").replace(/[\[\]]/g,"");
599             return "that.fonction([" + "'" + e + "'" + "],{})";
600         });    
601 
602         // Attributes
603         prefix("{", function () {
604             var a = [], n, v;
605             if (token.id !== "}") {
606                 while (true) {
607                     // Ignore
608                     n = token;
609                     
610                     //if (n.arity !== "name"/* && n.arity !== "literal"*/) {
611                     //    error(token, "Bad property name.");
612                     //}
613                     advance();
614                     a.push( "'" + n.value + "'");
615                     if (token.id !== ",") {
616                         break;
617                     }
618                     advance(",");
619                 }
620             }
621             advance("}");
622             this.first = a;
623             this.arity = "unary";
624             return this;
625         });
626         
627         prefix("for", function () {
628             var n = token, vname;                   // FIXME error message
629             
630             this.first = expression(0);
631             advance("to");
632             this.second = expression(0);
633             advance("do");
634             if (token.id === ';') advance(";");
635             this.third = statements().join("\n");
636             advance("end");
637             varname = scopeObjName + '["' + n.value + '"]';
638             return 'for (' + this.first + ';' + 
639                             varname + '<=' + this.second + ';' + 
640                             varname + '++){' + this.third + '}';
641         });
642         
643         stmt("var", function () {
644             var a, n, t;
645             //n = token;
646             // scope.define(n);
647             a = statement();
648             return /*"VAR " + */ a;
649         });    
650 
651         prefix("[", function () {
652             var a = [];
653             if (token.id !== "]") {
654                 while (true) {
655                     a.push(expression(0));
656                     if (token.id !== ",") {
657                         break;
658                     }
659                     advance(",");
660                 }
661             }
662             advance("]");
663             this.first = a;
664             this.arity = "unary";
665             return a.length==0 ? null : a.length==1 ? a[0] : a[0]+'?'+a[1]+':'+a[2];
666         });    
667         
668         /*
669          * Here starts the parsing part
670          * 
671          */
672         token_nr = 0;
673         new_scope();
674         advance();
675         var s = statements().join('\n');
676 //console.log(s);        
677         return s;
678     };
679     
680     this.parseData = function(board) {
681         this.parseOptions(board);
682         this.parseFigure(board);
683     };
684 
685     this.parseFigure = function(board) {
686         var i = this.data.indexOf('@figure;');
687         if (i<0) {
688             return;             // no figure found
689         }
690         
691         i += 8;                 // skip string "@figure;"
692         var i2 = this.data.indexOf('@',i+1);
693         if (i2<0) { i2 = this.data.length; }
694         
695         var tokens = this.tokenize(this.data.slice(i, i2), '=<>!+-*&|/%^#', '=<>&|');
696         this.board = board;
697         var s = this.parse(tokens, 'tep');
698         var tep = {};
699         
700         
701         // Set the default options
702         board.options.point.face = 'x';
703         board.options.point.strokeColor = '#0000ff';
704         board.options.point.strokeWidth = 1;
705         board.options.line.strokeWidth = 1;
706         
707 //console.log(s);        
708         var fun = new Function("that", "tep", s);
709         //console.log(fun.toString());
710         fun(this, tep);
711 //console.log(tep);
712         
713         // Set the correct labels and names 
714         var el;
715         for (el in tep) {
716             if (JXG.exists(tep[el].setProperty)) {
717                 tep[el].setProperty({name:el});
718                 if (JXG.exists(tep[el].label) && JXG.exists(tep[el].label.content)) {
719                     tep[el].label.content.setText(el);
720                 }
721             }
722         }
723     };
724 
725     // 
726     //--------------------------------------------------------------------- 
727     //
728     this.prepareString = function(fileStr) {
729         //fileStr = JXG.Util.utf8Decode(fileStr);
730         //fileStr = JXG.GeogebraReader.utf8replace(fileStr);
731         return fileStr;
732     };
733     
734     this.readTracenpoche = function(fileStr, board){
735         this.data = this.prepareString(fileStr);
736         board.suspendUpdate();
737         this.parseData(board);
738         board.unsuspendUpdate();
739         return this.data;
740     };
741     
742     // 
743     //--------------------------------------------------------------------- 
744     //
745     this.handleAtts = function(attsArr) {
746         var obj = {}, i, le = attsArr.length;
747         
748         obj["withLabel"] = true;
749         for (i=0; i<le; i++) {
750             switch (attsArr[i]) {
751                 case 'sansnom': obj["withLabel"] = false; break;
752             }
753         }
754         return obj;
755     };
756 
757 
758     JXG.Math.carre = function(x) {
759         return x*x;
760     };
761         
762     /*
763      * Now, the constructions of TeP elements follow
764      */
765     this.tepElements = [
766             // points
767             "point", "pointsur", "intersection", "projete", "barycentre", "image", "milieu",
768             // lines
769             "segment", "droite", "droiteEQR", "droiteEQ", "mediatrice", "parallele", "bissectrice", "perpendiculaire", "tangente",
770             "vecteur",
771             // circles
772             "cercle", "cerclerayon",
773             // polygons
774             "polygone",
775             // other
776             "texte", "reel", "entier", "fonction", 
777             // transformations
778             "homothetie", "reflexion", "rotation", "symetrie", "translation"
779             ];
780             
781     /*
782      * Points 
783      */
784     this.point = function(parents, attributes) {
785         if (parents.length==0) {
786             return this.board.create('point', [Math.random(),Math.random()], this.handleAtts(attributes));
787         } else {
788             return this.board.create('point', parents, this.handleAtts(attributes));
789         }
790     };
791 
792     this.pointsur = function(parents, attributes) {
793         var p1, p2, c, lambda, par3;
794 
795         par3 = parents[parents.length-1];
796         if (JXG.isNumber(par3)) {
797             lambda = function(){ return par3; };
798         } else {
799             lambda = function(){ return par3.Value(); };
800         }
801         
802         if (parents.length==3) {        // point between two points
803             p1 = parents[0];
804             p2 = parents[1];
805             return this.board.create('point', [
806                 function(){ return p1.X()+(p2.X()-p1.X())*lambda(); },
807                 function(){ return p1.Y()+(p2.Y()-p1.Y())*lambda(); }
808                 ],
809                 this.handleAtts(attributes)
810             );
811         } else if (parents.length==2) {   // point on segment
812             if (parents[0].elementClass==JXG.OBJECT_CLASS_LINE) {
813                 p1 = parents[0].point1;
814                 p2 = parents[0].point2;
815                 return this.board.create('point', [
816                     function(){ return p1.X()+(p2.X()-p1.X())*lambda(); },
817                     function(){ return p1.Y()+(p2.Y()-p1.Y())*lambda(); }
818                     ],
819                     this.handleAtts(attributes)
820                 );
821             } else {                      // point on circle
822                 c = parents[0];
823                 return this.board.create('point', [
824                     function(){ return c.center.X()+c.Radius()*Math.cos(lambda()); },
825                     function(){ return c.center.Y()+c.Radius()*Math.sin(lambda()); }
826                     ],
827                     this.handleAtts(attributes)
828                 );
829             }
830             
831         }
832     };
833 
834     this.intersection = function(parents, attributes) {
835         if (parents.length==2) {  // line line
836             return this.board.create('intersection', [parents[0],parents[1],0], this.handleAtts(attributes));
837         } else if (parents.length==3) {
838             if (JXG.isNumber(parents[2])) {  // line circle
839                 parents[2] -= 1;
840                 return this.board.create('intersection', parents, this.handleAtts(attributes));
841             } else {
842                 return this.board.create('otherintersection', parents, this.handleAtts(attributes));
843             }
844         }
845     }
846     
847     this.projete = function(parents, attributes) {
848         var lpar;
849         if (parents.length == 2) {          // orthogonal projection
850             return this.board.create('orthogonalprojection', parents, this.handleAtts(attributes));
851         } else {                             // parallel projection along parents[2]
852             lpar = this.board.create('parallel', [parents[2], parents[0]], {visible:false, withLabel:false});
853             return this.board.create('intersection', [parents[1], lpar, 0], this.handleAtts(attributes));
854         }
855     }
856 
857     this.barycentre = function(parents, attributes) {
858         return this.board.create('point', [
859             function() {
860                 var i, s = 0, le = parents.length, x = 0.0;
861                 for (i=0; i<le; i+=2) {
862                     x += parents[i].X()*parents[i+1];
863                     s += parents[i+1];
864                 }
865                 return x/s;
866             },
867             function() {
868                 var i, s = 0, le = parents.length, y = 0.0;
869                 for (i=0; i<le; i+=2) {
870                     y += parents[i].Y()*parents[i+1];
871                     s += parents[i+1];
872                 }
873                 return y/s;
874             }
875         ], this.handleAtts(attributes));
876     }
877     
878     this.image = function(parents, attributes) {
879         return this.board.create('point', [parents[1], parents[0]], this.handleAtts(attributes));
880     }
881     
882     this.milieu = function(parents, attributes) {
883         return this.board.create('midpoint', parents, this.handleAtts(attributes));
884     }
885 
886     /*
887      * Lines
888      */
889     this.segment = function(parents, attributes) {
890         return this.board.create('segment', parents, this.handleAtts(attributes));
891     };
892 
893     this.droite = function(parents, attributes) {
894         return this.board.create('line', parents, this.handleAtts(attributes));
895     };
896 
897     this.droiteEQR = function(parents, attributes) {
898         return this.board.create('line', [parents[2], parents[0], parents[1]], this.handleAtts(attributes));
899     };
900     
901     this.droiteEQ = function(parents, attributes) {
902         return this.board.create('line', [1.0, parents[0], parents[1]], this.handleAtts(attributes));
903     };
904 
905     this.parallele = function(parents, attributes) {
906         return this.board.create('parallel', [parents[1], parents[0]], this.handleAtts(attributes));
907     };
908 
909     this.mediatrice = function(parents, attributes) {
910         var m, li, el; 
911         if (parents.length==1) {
912             m = this.board.create('midpoint', [parents[0]], {visible:false, withLabel:false});
913             el = this.board.create('perpendicular', [parents[0], m], this.handleAtts(attributes));
914         } else {
915             li = this.board.create('line', parents, {visible:false, withLabel:false});
916             m = this.board.create('midpoint', parents, {visible:false, withLabel:false});
917             el = this.board.create('perpendicular', [li, m], this.handleAtts(attributes));
918         }
919         return el;
920     };
921 
922     this.perpendiculaire = function(parents, attributes) {
923         return this.board.create('perpendicular', [parents[1], parents[0]], this.handleAtts(attributes));
924     };
925 
926     this.bissectrice = function(parents, attributes) {
927         return this.board.create('bisector', parents, this.handleAtts(attributes));
928     };
929 
930     this.tangente = function(parents, attributes) {
931         var gli,
932             f = parents[0],
933             x = parents[1];
934             
935         if (JXG.isNumber(parents[1])) {
936             x = parents[1];
937             gli = this.board.create('glider', [x, f.Y(x), f], {fixed:true, visible:false, withLabel:false});
938             return this.board.create('tangent', [f, gli], this.handleAtts(attributes));
939         } else if (JXG.exists(parents[1].Value)) {
940             // Fake glider: it needs the properties "position" and "slideObject".
941             gli = this.board.create('point', 
942                 [function(){ this.position = x.Value(); return x.Value(); }, function(){ return f.Y(x.Value()); }], 
943                 {visible:false, withLabel:false});
944             gli.slideObject = f;
945             return this.board.create('tangent', [f, gli], this.handleAtts(attributes));
946         } else {
947             // Fake glider: it needs the properties "position" and "slideObject".
948             gli = this.board.create('point', 
949                 [function(){ this.position = x.X(); return x.X(); }, function(){ return f.Y(x.X()); }], 
950                 {visible:false, withLabel:false});
951             gli.slideObject = f;
952             return this.board.create('tangent', [f, gli], this.handleAtts(attributes));
953         }
954     };
955 
956     this.vecteur = function(parents, attributes) {
957         return this.board.create('arrow', parents, this.handleAtts(attributes));
958     };
959 
960     
961     /* 
962      * Circles
963      */
964     this.cercle = function(parents, attributes) {
965         return this.board.create('circle', parents, this.handleAtts(attributes));
966     };
967 
968     this.cerclerayon = function(parents, attributes) {
969         return this.board.create('circle', parents, this.handleAtts(attributes));
970     };
971     
972     /*
973      * Polygons
974      */
975     this.polygone = function(parents, attributes) {
976         return this.board.create('polygon', parents, this.handleAtts(attributes));
977     };
978 
979     /*
980      * Other
981      */
982     this.texte = function(parents, attributes) {
983         return this.board.create('text', parents, this.handleAtts(attributes));
984     };
985 
986     this.reel = function(parents, attributes) {
987         var atts = this.handleAtts(attributes);
988         atts["snapWidth"] = parents[3];
989         return this.board.create('slider', [[0,-2],[3,-2], [parents[1], parents[0], parents[2]]], atts);
990     };
991     
992     this.entier = function(parents, attributes) {
993         return this.reel(parents, attributes);
994     };
995  
996     this.fonction = function(parents, attributes) {
997         var f = new Function("x", "return " + parents[0]);
998         return this.board.create('functiongraph', [f], this.handleAtts(attributes));
999     };
1000     
1001     /*
1002      * Transformations
1003      */
1004     
1005     this.homothetie = function(parents, attributes) {
1006         var c = parents[0], a = parents[1];
1007         if (JXG.isNumber(a)) {
1008             return this.board.create('transform', 
1009                 [1, 0, 0, 
1010                  function(){ return (-a+1)*c.X(); }, a, 0,
1011                  function(){ return (-a+1)*c.Y(); }, 0, a], 
1012                  {type:'generic'});
1013         } else {  // Slider
1014             return this.board.create('transform', 
1015                 [1, 0, 0, 
1016                  function(){ return (-a.Value()+1)*c.X(); }, function(){ return a.Value();}, 0,
1017                  function(){ return (-a.Value()+1)*c.Y(); }, 0, function(){ return a.Value();}], 
1018                  {type:'generic'});
1019         }
1020     };
1021 
1022     this.symetrie = function(parents, attributes) {
1023         if (parents.length==1 && JXG.isPoint(parents[0])) {
1024             return this.board.create('transform', [Math.PI, parents[0]], {type:'rotate'});
1025         }
1026     };
1027 
1028     this.reflexion = function(parents, attributes) {
1029         return this.board.create('transform', [parents[0]], {type:'reflect'});
1030     };
1031 
1032     this.rotation = function(parents, attributes) {
1033         var a = parents[1];
1034         if (JXG.isNumber(a)) {  
1035             a = (Math.PI*a)/180.0;
1036             return this.board.create('transform', [a, parents[0]], {type:'rotate'});
1037         } else {  // slider
1038             return this.board.create('transform', [function(){ return (Math.PI*a.Value())/180.0;}, parents[0]], {type:'rotate'});
1039         }
1040     };
1041 
1042     this.translation = function(parents, attributes) {
1043         if (parents.length==1) {
1044             return this.board.create('transform', [
1045                 function(){ return parents[0].point2.X()-parents[0].point1.X(); }, 
1046                 function(){ return parents[0].point2.Y()-parents[0].point1.Y(); }
1047                 ], {type:'translate'});
1048         } else {
1049             return this.board.create('transform', [                
1050                 function(){ return parents[1].X()-parents[0].X(); }, 
1051                 function(){ return parents[1].Y()-parents[0].Y(); }
1052                 ], {type:'translate'});
1053         }
1054     };
1055 
1056 
1057 };
1058