1 /*
  2     Copyright 2008-2016
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
 29     and <http://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 /*global JXG: true, define: true, AMprocessNode: true, MathJax: true, document: true */
 33 /*jslint nomen: true, plusplus: true, newcap:true*/
 34 
 35 /* depends:
 36  jxg
 37  options
 38  renderer/abstract
 39  base/constants
 40  utils/type
 41  utils/env
 42  utils/color
 43  math/numerics
 44 */
 45 
 46 define([
 47     'jxg', 'options', 'renderer/abstract', 'base/constants', 'utils/type', 'utils/env', 'utils/color', 'utils/base64', 'math/numerics'
 48 ], function (JXG, Options, AbstractRenderer, Const, Type, Env, Color, Base64, Numerics) {
 49 
 50     "use strict";
 51 
 52     /**
 53      * Uses SVG to implement the rendering methods defined in {@link JXG.AbstractRenderer}.
 54      * @class JXG.AbstractRenderer
 55      * @augments JXG.AbstractRenderer
 56      * @param {Node} container Reference to a DOM node containing the board.
 57      * @param {Object} dim The dimensions of the board
 58      * @param {Number} dim.width
 59      * @param {Number} dim.height
 60      * @see JXG.AbstractRenderer
 61      */
 62     JXG.SVGRenderer = function (container, dim) {
 63         var i;
 64 
 65         // docstring in AbstractRenderer
 66         this.type = 'svg';
 67 
 68         this.isIE = navigator.appVersion.indexOf("MSIE") !== -1 || navigator.userAgent.match(/Trident\//);
 69 
 70         /**
 71          * SVG root node
 72          * @type Node
 73          */
 74         this.svgRoot = null;
 75 
 76         /**
 77          * The SVG Namespace used in JSXGraph.
 78          * @see http://www.w3.org/TR/SVG/
 79          * @type String
 80          * @default http://www.w3.org/2000/svg
 81          */
 82         this.svgNamespace = 'http://www.w3.org/2000/svg';
 83 
 84         /**
 85          * The xlink namespace. This is used for images.
 86          * @see http://www.w3.org/TR/xlink/
 87          * @type String
 88          * @default http://www.w3.org/1999/xlink
 89          */
 90         this.xlinkNamespace = 'http://www.w3.org/1999/xlink';
 91 
 92         // container is documented in AbstractRenderer
 93         this.container = container;
 94 
 95         // prepare the div container and the svg root node for use with JSXGraph
 96         this.container.style.MozUserSelect = 'none';
 97 
 98         this.container.style.overflow = 'hidden';
 99         if (this.container.style.position === '') {
100             this.container.style.position = 'relative';
101         }
102 
103         this.svgRoot = this.container.ownerDocument.createElementNS(this.svgNamespace, "svg");
104         this.svgRoot.style.overflow = 'hidden';
105 
106         this.resize(dim.width, dim.height);
107 
108         //this.svgRoot.setAttributeNS(null, 'shape-rendering', 'crispEdge'); //'optimizeQuality'); //geometricPrecision');
109 
110         this.container.appendChild(this.svgRoot);
111 
112         /**
113          * The <tt>defs</tt> element is a container element to reference reusable SVG elements.
114          * @type Node
115          * @see http://www.w3.org/TR/SVG/struct.html#DefsElement
116          */
117         this.defs = this.container.ownerDocument.createElementNS(this.svgNamespace, 'defs');
118         this.svgRoot.appendChild(this.defs);
119 
120         /**
121          * Filters are used to apply shadows.
122          * @type Node
123          * @see http://www.w3.org/TR/SVG/filters.html#FilterElement
124          */
125         this.filter = this.container.ownerDocument.createElementNS(this.svgNamespace, 'filter');
126         this.filter.setAttributeNS(null, 'id', this.container.id + '_' + 'f1');
127         /*
128         this.filter.setAttributeNS(null, 'x', '-100%');
129         this.filter.setAttributeNS(null, 'y', '-100%');
130         this.filter.setAttributeNS(null, 'width', '400%');
131         this.filter.setAttributeNS(null, 'height', '400%');
132         //this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse');
133         */
134         this.filter.setAttributeNS(null, 'width', '300%');
135         this.filter.setAttributeNS(null, 'height', '300%');
136         this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse');
137 
138         this.feOffset = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feOffset');
139         this.feOffset.setAttributeNS(null, 'result', 'offOut');
140         this.feOffset.setAttributeNS(null, 'in', 'SourceAlpha');
141         this.feOffset.setAttributeNS(null, 'dx', '5');
142         this.feOffset.setAttributeNS(null, 'dy', '5');
143         this.filter.appendChild(this.feOffset);
144 
145         this.feGaussianBlur = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feGaussianBlur');
146         this.feGaussianBlur.setAttributeNS(null, 'result', 'blurOut');
147         this.feGaussianBlur.setAttributeNS(null, 'in', 'offOut');
148         this.feGaussianBlur.setAttributeNS(null, 'stdDeviation', '3');
149         this.filter.appendChild(this.feGaussianBlur);
150 
151         this.feBlend = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feBlend');
152         this.feBlend.setAttributeNS(null, 'in', 'SourceGraphic');
153         this.feBlend.setAttributeNS(null, 'in2', 'blurOut');
154         this.feBlend.setAttributeNS(null, 'mode', 'normal');
155         this.filter.appendChild(this.feBlend);
156 
157         this.defs.appendChild(this.filter);
158 
159         /**
160          * JSXGraph uses a layer system to sort the elements on the board. This puts certain types of elements in front
161          * of other types of elements. For the order used see {@link JXG.Options.layer}. The number of layers is documented
162          * there, too. The higher the number, the "more on top" are the elements on this layer.
163          * @type Array
164          */
165         this.layer = [];
166         for (i = 0; i < Options.layer.numlayers; i++) {
167             this.layer[i] = this.container.ownerDocument.createElementNS(this.svgNamespace, 'g');
168             this.svgRoot.appendChild(this.layer[i]);
169         }
170 
171         // already documented in JXG.AbstractRenderer
172         this.supportsForeignObject = document.implementation.hasFeature("www.http://w3.org/TR/SVG11/feature#Extensibility", "1.1");
173         if (this.supportsForeignObject) {
174             this.foreignObjLayer = [];
175             for (i = 0; i < Options.layer.numlayers; i++) {
176                 if (i === Options.layer.text || i === 0) {    // 0 is for traces
177                     this.foreignObjLayer[i] = this.container.ownerDocument.createElementNS(this.svgNamespace, 'foreignObject');
178 
179                     this.foreignObjLayer[i].setAttribute("x",0);
180                     this.foreignObjLayer[i].setAttribute("y",0);
181                     this.foreignObjLayer[i].setAttribute("width","100%");
182                     this.foreignObjLayer[i].setAttribute("height","100%");
183                     this.layer[i].appendChild(this.foreignObjLayer[i]);
184                 }
185             }
186         }
187 
188         /**
189          * Defines dash patterns. Defined styles are: <ol>
190          * <li value="-1"> 2px dash, 2px space</li>
191          * <li> 5px dash, 5px space</li>
192          * <li> 10px dash, 10px space</li>
193          * <li> 20px dash, 20px space</li>
194          * <li> 20px dash, 10px space, 10px dash, 10px dash</li>
195          * <li> 20px dash, 5px space, 10px dash, 5px space</li></ol>
196          * @type Array
197          * @default ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5']
198          * @see http://www.w3.org/TR/SVG/painting.html#StrokeProperties
199          */
200         this.dashArray = ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5'];
201     };
202 
203     JXG.SVGRenderer.prototype = new AbstractRenderer();
204 
205     JXG.extend(JXG.SVGRenderer.prototype, /** @lends JXG.SVGRenderer.prototype */ {
206 
207         /**
208          * Creates an arrow DOM node. Arrows are displayed in SVG with a <em>marker</em> tag.
209          * @private
210          * @param {JXG.GeometryElement} element A JSXGraph element, preferably one that can have an arrow attached.
211          * @param {String} [idAppendix=''] A string that is added to the node's id.
212          * @returns {Node} Reference to the node added to the DOM.
213          */
214         _createArrowHead: function (element, idAppendix) {
215             var node2, node3,
216                 id = element.id + 'Triangle',
217                 s, d;
218 
219             if (Type.exists(idAppendix)) {
220                 id += idAppendix;
221             }
222             node2 = this.createPrim('marker', id);
223 
224             node2.setAttributeNS(null, 'stroke', Type.evaluate(element.visProp.strokecolor));
225             node2.setAttributeNS(null, 'stroke-opacity', Type.evaluate(element.visProp.strokeopacity));
226             node2.setAttributeNS(null, 'fill', Type.evaluate(element.visProp.strokecolor));
227             node2.setAttributeNS(null, 'fill-opacity', Type.evaluate(element.visProp.strokeopacity));
228             node2.setAttributeNS(null, 'stroke-width', 0);  // this is the stroke-width of the arrow head.
229                                                             // Should be zero to make the positioning easy
230 
231             node2.setAttributeNS(null, 'orient', 'auto');
232             node2.setAttributeNS(null, 'markerUnits', 'strokeWidth'); // 'strokeWidth' 'userSpaceOnUse');
233 
234             /*
235             * Changes here are also necessary in _setArrowWidth()
236             */
237             s = parseInt(element.visProp.strokewidth, 10);
238             //node2.setAttributeNS(null, 'viewBox', (-s) + ' ' + (-s) + ' ' + s * 12 + ' ' + s * 12);
239             node2.setAttributeNS(null, 'viewBox', (-s) + ' ' + (-s) + ' ' + s * 10 + ' ' + s * 10);
240 
241             /*
242                The arrow head is an equilateral triangle with base length 10 and height 10.
243                This 10 units are scaled to strokeWidth*3 pixels or minimum 10 pixels.
244                See also abstractRenderer.updateLine() where the line path is shortened accordingly.
245             */
246             d = Math.max(s * 3, 10);
247             node2.setAttributeNS(null, 'markerHeight', d);
248             node2.setAttributeNS(null, 'markerWidth', d);
249 
250             node3 = this.container.ownerDocument.createElementNS(this.svgNamespace, 'path');
251 
252             if (idAppendix === 'End') {     // First arrow
253                 node2.setAttributeNS(null, 'refY', 5);
254                 node2.setAttributeNS(null, 'refX', 10);
255                 node3.setAttributeNS(null, 'd', 'M 10 0 L 0 5 L 10 10 z');
256             } else {                        // Last arrow
257                 node2.setAttributeNS(null, 'refY', 5);
258                 node2.setAttributeNS(null, 'refX', 0);
259                 node3.setAttributeNS(null, 'd', 'M 0 0 L 10 5 L 0 10 z');
260             }
261 
262             node2.appendChild(node3);
263             return node2;
264         },
265 
266         /**
267          * Updates color of an arrow DOM node.
268          * @param {Node} node The arrow node.
269          * @param {String} color Color value in a HTML compatible format, e.g. <tt>#00ff00</tt> or <tt>green</tt> for green.
270          * @param {Number} opacity
271          */
272         _setArrowColor: function (node, color, opacity, parentNode) {
273             var s, d;
274 
275             if (node) {
276                 if (Type.isString(color)) {
277                     node.setAttributeNS(null, 'stroke', color);
278                     node.setAttributeNS(null, 'fill', color);
279                 }
280                 node.setAttributeNS(null, 'stroke-opacity', opacity);
281                 node.setAttributeNS(null, 'fill-opacity', opacity);
282 
283                 if (this.isIE) {
284                     parentNode.parentNode.insertBefore(parentNode, parentNode);
285                 }
286             }
287 
288         },
289 
290         /**
291          * Updates width of an arrow DOM node.
292          * @param {Node} node The arrow node.
293          * @param {Number} width
294          */
295         _setArrowWidth: function (node, width, parentNode) {
296             var s, d;
297 
298             if (node) {
299                 // This is the stroke-width of the arrow head.
300                 // Should be zero to make the positioning easy
301                 node.setAttributeNS(null, 'stroke-width', 0);
302 
303                 // The next lines are important if the strokeWidth of the line is changed.
304                 s = width;
305                 node.setAttributeNS(null, 'viewBox', (-s) + ' ' + (-s) + ' ' + s * 10 + ' ' + s * 10);
306                 d = Math.max(s * 3, 10);
307 
308                 node.setAttributeNS(null, 'markerHeight', d);
309                 node.setAttributeNS(null, 'markerWidth', d);
310 
311                 if (this.isIE) {
312                     parentNode.parentNode.insertBefore(parentNode, parentNode);
313                 }
314             }
315 
316         },
317 
318         /* ******************************** *
319          *  This renderer does not need to
320          *  override draw/update* methods
321          *  since it provides draw/update*Prim
322          *  methods except for some cases like
323          *  internal texts or images.
324          * ******************************** */
325 
326         /* **************************
327          *    Lines
328          * **************************/
329 
330         // documented in AbstractRenderer
331         updateTicks: function (ticks) {
332             var i, c, node, x, y,
333                 tickStr = '',
334                 len = ticks.ticks.length;
335 
336             for (i = 0; i < len; i++) {
337                 c = ticks.ticks[i];
338                 x = c[0];
339                 y = c[1];
340 
341                 if (Type.isNumber(x[0]) && Type.isNumber(x[1])) {
342                     tickStr += "M " + (x[0]) + " " + (y[0]) + " L " + (x[1]) + " " + (y[1]) + " ";
343                 }
344             }
345 
346             node = ticks.rendNode;
347 
348             if (!Type.exists(node)) {
349                 node = this.createPrim('path', ticks.id);
350                 this.appendChildPrim(node, ticks.visProp.layer);
351                 ticks.rendNode = node;
352             }
353 
354             node.setAttributeNS(null, 'stroke', ticks.visProp.strokecolor);
355             node.setAttributeNS(null, 'stroke-opacity', ticks.visProp.strokeopacity);
356             node.setAttributeNS(null, 'stroke-width', ticks.visProp.strokewidth);
357             this.updatePathPrim(node, tickStr, ticks.board);
358         },
359 
360         /* **************************
361          *    Text related stuff
362          * **************************/
363 
364         // already documented in JXG.AbstractRenderer
365         displayCopyright: function (str, fontsize) {
366             var node = this.createPrim('text', 'licenseText'),
367                 t;
368             node.setAttributeNS(null, 'x', '20px');
369             node.setAttributeNS(null, 'y', (2 + fontsize) + 'px');
370             node.setAttributeNS(null, "style", "font-family:Arial,Helvetica,sans-serif; font-size:" + fontsize + "px; fill:#356AA0;  opacity:0.3;");
371             t = this.container.ownerDocument.createTextNode(str);
372             node.appendChild(t);
373             this.appendChildPrim(node, 0);
374         },
375 
376         // already documented in JXG.AbstractRenderer
377         drawInternalText: function (el) {
378             var node = this.createPrim('text', el.id);
379 
380             node.setAttributeNS(null, "class", el.visProp.cssclass);
381             //node.setAttributeNS(null, "style", "alignment-baseline:middle"); // Not yet supported by Firefox
382 
383             // Preserve spaces
384             node.setAttributeNS("http://www.w3.org/XML/1998/namespace", "space", "preserve");
385 
386             el.rendNodeText = this.container.ownerDocument.createTextNode('');
387             node.appendChild(el.rendNodeText);
388             this.appendChildPrim(node,  el.visProp.layer);
389 
390             return node;
391         },
392 
393         // already documented in JXG.AbstractRenderer
394         updateInternalText: function (el) {
395             var content = el.plaintext, v;
396 
397             // el.rendNode.setAttributeNS(null, "class", el.visProp.cssclass);
398             if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) {
399 
400                 // Horizontal
401                 v = el.coords.scrCoords[1];
402                 if (el.visPropOld.left !== (el.visProp.anchorx + v)) {
403                     el.rendNode.setAttributeNS(null, 'x', v + 'px');
404 
405                     if (el.visProp.anchorx === 'left') {
406                         el.rendNode.setAttributeNS(null, 'text-anchor', 'start');
407                     } else if (el.visProp.anchorx === 'right') {
408                         el.rendNode.setAttributeNS(null, 'text-anchor', 'end');
409                     } else if (el.visProp.anchorx === 'middle') {
410                         el.rendNode.setAttributeNS(null, 'text-anchor', 'middle');
411                     }
412                     el.visPropOld.left = el.visProp.anchorx + v;
413                 }
414 
415                 // Vertical
416                 v = el.coords.scrCoords[2];
417                 if (el.visPropOld.top !== (el.visProp.anchory + v)) {
418                     el.rendNode.setAttributeNS(null, 'y', (v + this.vOffsetText * 0.5) + 'px');
419 
420                     if (el.visProp.anchory === 'bottom') {
421                         el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-after-edge');
422                     } else if (el.visProp.anchory === 'top') {
423                         el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-before-edge');
424                     } else if (el.visProp.anchory === 'middle') {
425                         el.rendNode.setAttributeNS(null, 'dominant-baseline', 'middle');
426                     }
427                     el.visPropOld.top = el.visProp.anchory + v;
428                 }
429             }
430             if (el.htmlStr !== content) {
431                 el.rendNodeText.data = content;
432                 el.htmlStr = content;
433             }
434             this.transformImage(el, el.transformations);
435         },
436 
437         /**
438          * Set color and opacity of internal texts.
439          * SVG needs its own version.
440          * @private
441          * @see JXG.AbstractRenderer#updateTextStyle
442          * @see JXG.AbstractRenderer#updateInternalTextStyle
443          */
444         updateInternalTextStyle: function (element, strokeColor, strokeOpacity) {
445             this.setObjectFillColor(element, strokeColor, strokeOpacity);
446         },
447 
448         /* **************************
449          *    Image related stuff
450          * **************************/
451 
452         // already documented in JXG.AbstractRenderer
453         drawImage: function (el) {
454             var node = this.createPrim('image', el.id);
455 
456             node.setAttributeNS(null, 'preserveAspectRatio', 'none');
457             this.appendChildPrim(node, el.visProp.layer);
458             el.rendNode = node;
459 
460             this.updateImage(el);
461         },
462 
463         // already documented in JXG.AbstractRenderer
464         transformImage: function (el, t) {
465             var s, m,
466                 node = el.rendNode,
467                 str = "",
468                 len = t.length;
469 
470             if (len > 0) {
471                 m = this.joinTransforms(el, t);
472                 s = [m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]].join(',');
473                 str += ' matrix(' + s + ') ';
474                 node.setAttributeNS(null, 'transform', str);
475             }
476         },
477 
478         // already documented in JXG.AbstractRenderer
479         updateImageURL: function (el) {
480             var url = Type.evaluate(el.url);
481 
482             el.rendNode.setAttributeNS(this.xlinkNamespace, 'xlink:href', url);
483         },
484 
485         // already documented in JXG.AbstractRenderer
486         updateImageStyle: function (el, doHighlight) {
487             var css = doHighlight ? el.visProp.highlightcssclass : el.visProp.cssclass;
488 
489             el.rendNode.setAttributeNS(null, 'class', css);
490         },
491 
492         /* **************************
493          * Render primitive objects
494          * **************************/
495 
496         // already documented in JXG.AbstractRenderer
497         appendChildPrim: function (node, level) {
498             if (!Type.exists(level)) { // trace nodes have level not set
499                 level = 0;
500             } else if (level >= Options.layer.numlayers) {
501                 level = Options.layer.numlayers - 1;
502             }
503 
504             this.layer[level].appendChild(node);
505 
506             return node;
507         },
508 
509         // already documented in JXG.AbstractRenderer
510         createPrim: function (type, id) {
511             var node = this.container.ownerDocument.createElementNS(this.svgNamespace, type);
512             node.setAttributeNS(null, 'id', this.container.id + '_' + id);
513             node.style.position = 'absolute';
514             if (type === 'path') {
515                 node.setAttributeNS(null, 'stroke-linecap', 'round');
516                 node.setAttributeNS(null, 'stroke-linejoin', 'round');
517             }
518             return node;
519         },
520 
521         // already documented in JXG.AbstractRenderer
522         remove: function (shape) {
523             if (Type.exists(shape) && Type.exists(shape.parentNode)) {
524                 shape.parentNode.removeChild(shape);
525             }
526         },
527 
528         // already documented in JXG.AbstractRenderer
529         makeArrows: function (el) {
530             var node2;
531 
532             if (el.visPropOld.firstarrow === el.visProp.firstarrow && el.visPropOld.lastarrow === el.visProp.lastarrow) {
533                 if (this.isIE && el.visProp.visible && (el.visProp.firstarrow || el.visProp.lastarrow)) {
534                     el.rendNode.parentNode.insertBefore(el.rendNode, el.rendNode);
535                 }
536                 return;
537             }
538 
539             if (el.visProp.firstarrow) {
540                 node2 = el.rendNodeTriangleStart;
541                 if (!Type.exists(node2)) {
542                     node2 = this._createArrowHead(el, 'End');
543                     this.defs.appendChild(node2);
544                     el.rendNodeTriangleStart = node2;
545                     el.rendNode.setAttributeNS(null, 'marker-start', 'url(#' + this.container.id + '_' + el.id + 'TriangleEnd)');
546                 } else {
547                     this.defs.appendChild(node2);
548                 }
549             } else {
550                 node2 = el.rendNodeTriangleStart;
551                 if (Type.exists(node2)) {
552                     this.remove(node2);
553                 }
554             }
555             if (el.visProp.lastarrow) {
556                 node2 = el.rendNodeTriangleEnd;
557                 if (!Type.exists(node2)) {
558                     node2 = this._createArrowHead(el, 'Start');
559                     this.defs.appendChild(node2);
560                     el.rendNodeTriangleEnd = node2;
561                     el.rendNode.setAttributeNS(null, 'marker-end', 'url(#' + this.container.id + '_' + el.id + 'TriangleStart)');
562                 } else {
563                     this.defs.appendChild(node2);
564                 }
565             } else {
566                 node2 = el.rendNodeTriangleEnd;
567                 if (Type.exists(node2)) {
568                     this.remove(node2);
569                 }
570             }
571             el.visPropOld.firstarrow = el.visProp.firstarrow;
572             el.visPropOld.lastarrow = el.visProp.lastarrow;
573         },
574 
575         // already documented in JXG.AbstractRenderer
576         updateEllipsePrim: function (node, x, y, rx, ry) {
577             var huge = 1000000;
578 
579             huge = 200000; // IE
580             // webkit does not like huge values if the object is dashed
581             // iE doesn't like huge values above 216000
582             x = Math.abs(x) < huge ? x : huge * x / Math.abs(x);
583             y = Math.abs(y) < huge ? y : huge * y / Math.abs(y);
584             rx = Math.abs(rx) < huge ? rx : huge * rx / Math.abs(rx);
585             ry = Math.abs(ry) < huge ? ry : huge * ry / Math.abs(ry);
586 
587             node.setAttributeNS(null, 'cx', x);
588             node.setAttributeNS(null, 'cy', y);
589             node.setAttributeNS(null, 'rx', Math.abs(rx));
590             node.setAttributeNS(null, 'ry', Math.abs(ry));
591         },
592 
593         // already documented in JXG.AbstractRenderer
594         updateLinePrim: function (node, p1x, p1y, p2x, p2y) {
595             var huge = 1000000;
596 
597             huge = 200000; //IE
598             if (!isNaN(p1x + p1y + p2x + p2y)) {
599                 // webkit does not like huge values if the object is dashed
600                 // IE doesn't like huge values above 216000
601                 p1x = Math.abs(p1x) < huge ? p1x : huge * p1x / Math.abs(p1x);
602                 p1y = Math.abs(p1y) < huge ? p1y : huge * p1y / Math.abs(p1y);
603                 p2x = Math.abs(p2x) < huge ? p2x : huge * p2x / Math.abs(p2x);
604                 p2y = Math.abs(p2y) < huge ? p2y : huge * p2y / Math.abs(p2y);
605 
606                 node.setAttributeNS(null, 'x1', p1x);
607                 node.setAttributeNS(null, 'y1', p1y);
608                 node.setAttributeNS(null, 'x2', p2x);
609                 node.setAttributeNS(null, 'y2', p2y);
610             }
611         },
612 
613         // already documented in JXG.AbstractRenderer
614         updatePathPrim: function (node, pointString) {
615             if (pointString === '') {
616                 pointString = 'M 0 0';
617             }
618             node.setAttributeNS(null, 'd', pointString);
619         },
620 
621         // already documented in JXG.AbstractRenderer
622         updatePathStringPoint: function (el, size, type) {
623             var s = '',
624                 scr = el.coords.scrCoords,
625                 sqrt32 = size * Math.sqrt(3) * 0.5,
626                 s05 = size * 0.5;
627 
628             if (type === 'x') {
629                 s = ' M ' + (scr[1] - size) + ' ' + (scr[2] - size) +
630                     ' L ' + (scr[1] + size) + ' ' + (scr[2] + size) +
631                     ' M ' + (scr[1] + size) + ' ' + (scr[2] - size) +
632                     ' L ' + (scr[1] - size) + ' ' + (scr[2] + size);
633             } else if (type === '+') {
634                 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) +
635                     ' L ' + (scr[1] + size) + ' ' + (scr[2]) +
636                     ' M ' + (scr[1])        + ' ' + (scr[2] - size) +
637                     ' L ' + (scr[1])        + ' ' + (scr[2] + size);
638             } else if (type === '<>') {
639                 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) +
640                     ' L ' + (scr[1])        + ' ' + (scr[2] + size) +
641                     ' L ' + (scr[1] + size) + ' ' + (scr[2]) +
642                     ' L ' + (scr[1])        + ' ' + (scr[2] - size) + ' Z ';
643             } else if (type === '^') {
644                 s = ' M ' + (scr[1])          + ' ' + (scr[2] - size) +
645                     ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] + s05) +
646                     ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] + s05) +
647                     ' Z ';  // close path
648             } else if (type === 'v') {
649                 s = ' M ' + (scr[1])          + ' ' + (scr[2] + size) +
650                     ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] - s05) +
651                     ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] - s05) +
652                     ' Z ';
653             } else if (type === '>') {
654                 s = ' M ' + (scr[1] + size) + ' ' + (scr[2]) +
655                     ' L ' + (scr[1] - s05)  + ' ' + (scr[2] - sqrt32) +
656                     ' L ' + (scr[1] - s05)  + ' ' + (scr[2] + sqrt32) +
657                     ' Z ';
658             } else if (type === '<') {
659                 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) +
660                     ' L ' + (scr[1] + s05)  + ' ' + (scr[2] - sqrt32) +
661                     ' L ' + (scr[1] + s05)  + ' ' + (scr[2] + sqrt32) +
662                     ' Z ';
663             }
664             return s;
665         },
666 
667         // already documented in JXG.AbstractRenderer
668         updatePathStringPrim: function (el) {
669             var i, scr, len,
670                 symbm = ' M ',
671                 symbl = ' L ',
672                 symbc = ' C ',
673                 nextSymb = symbm,
674                 maxSize = 5000.0,
675                 pStr = '';
676                 // isNotPlot = (el.visProp.curvetype !== 'plot');
677 
678             if (el.numberPoints <= 0) {
679                 return '';
680             }
681 
682             len = Math.min(el.points.length, el.numberPoints);
683 
684             if (el.bezierDegree === 1) {
685                 /*
686                 if (isNotPlot && el.visProp.rdpsmoothing) {
687                     el.points = Numerics.RamerDouglasPeucker(el.points, 0.5);
688                     el.numberPoints = el.points.length;
689                 }
690                 */
691 
692                 for (i = 0; i < len; i++) {
693                     scr = el.points[i].scrCoords;
694                     if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
695                         nextSymb = symbm;
696                     } else {
697                         // Chrome has problems with values being too far away.
698                         scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize);
699                         scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize);
700 
701                         // Attention: first coordinate may be inaccurate if far way
702                         //pStr += [nextSymb, scr[1], ' ', scr[2]].join('');
703                         pStr += nextSymb + scr[1] + ' ' + scr[2];   // Seems to be faster now (webkit and firefox)
704                         nextSymb = symbl;
705                     }
706                 }
707             } else if (el.bezierDegree === 3) {
708                 i = 0;
709                 while (i < len) {
710                     scr = el.points[i].scrCoords;
711                     if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
712                         nextSymb = symbm;
713                     } else {
714                         pStr += nextSymb + scr[1] + ' ' + scr[2];
715                         if (nextSymb === symbc) {
716                             i += 1;
717                             scr = el.points[i].scrCoords;
718                             pStr += ' ' + scr[1] + ' ' + scr[2];
719                             i += 1;
720                             scr = el.points[i].scrCoords;
721                             pStr += ' ' + scr[1] + ' ' + scr[2];
722                         }
723                         nextSymb = symbc;
724                     }
725                     i += 1;
726                 }
727             }
728             return pStr;
729         },
730 
731         // already documented in JXG.AbstractRenderer
732         updatePathStringBezierPrim: function (el) {
733             var i, j, k, scr, lx, ly, len,
734                 symbm = ' M ',
735                 symbl = ' C ',
736                 nextSymb = symbm,
737                 maxSize = 5000.0,
738                 pStr = '',
739                 f = el.visProp.strokewidth,
740                 isNoPlot = (el.visProp.curvetype !== 'plot');
741 
742             if (el.numberPoints <= 0) {
743                 return '';
744             }
745 
746             if (isNoPlot && el.board.options.curve.RDPsmoothing) {
747                 el.points = Numerics.RamerDouglasPeucker(el.points, 0.5);
748             }
749 
750             len = Math.min(el.points.length, el.numberPoints);
751             for (j = 1; j < 3; j++) {
752                 nextSymb = symbm;
753                 for (i = 0; i < len; i++) {
754                     scr = el.points[i].scrCoords;
755 
756                     if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
757                         nextSymb = symbm;
758                     } else {
759                         // Chrome has problems with values being too far away.
760                         scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize);
761                         scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize);
762 
763                         // Attention: first coordinate may be inaccurate if far way
764                         if (nextSymb === symbm) {
765                             //pStr += [nextSymb, scr[1], ' ', scr[2]].join('');
766                             pStr += nextSymb + scr[1] + ' ' + scr[2];   // Seems to be faster now (webkit and firefox)
767                         } else {
768                             k = 2 * j;
769                             pStr += [nextSymb,
770                                 (lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j)), ' ',
771                                 (ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j)), ' ',
772                                 (lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j)), ' ',
773                                 (ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j)), ' ',
774                                 scr[1], ' ', scr[2]].join('');
775                         }
776 
777                         nextSymb = symbl;
778                         lx = scr[1];
779                         ly = scr[2];
780                     }
781                 }
782             }
783             return pStr;
784         },
785 
786         // already documented in JXG.AbstractRenderer
787         updatePolygonPrim: function (node, el) {
788             var i,
789                 pStr = '',
790                 scrCoords,
791                 len = el.vertices.length;
792 
793             node.setAttributeNS(null, 'stroke', 'none');
794 
795             for (i = 0; i < len - 1; i++) {
796                 if (el.vertices[i].isReal) {
797                     scrCoords = el.vertices[i].coords.scrCoords;
798                     pStr = pStr + scrCoords[1] + "," + scrCoords[2];
799                 } else {
800                     node.setAttributeNS(null, 'points', '');
801                     return;
802                 }
803 
804                 if (i < len - 2) {
805                     pStr += " ";
806                 }
807             }
808             if (pStr.indexOf('NaN') === -1) {
809                 node.setAttributeNS(null, 'points', pStr);
810             }
811         },
812 
813         // already documented in JXG.AbstractRenderer
814         updateRectPrim: function (node, x, y, w, h) {
815             node.setAttributeNS(null, 'x', x);
816             node.setAttributeNS(null, 'y', y);
817             node.setAttributeNS(null, 'width', w);
818             node.setAttributeNS(null, 'height', h);
819         },
820 
821         /* **************************
822          *  Set Attributes
823          * **************************/
824 
825         // documented in JXG.AbstractRenderer
826         setPropertyPrim: function (node, key, val) {
827             if (key === 'stroked') {
828                 return;
829             }
830             node.setAttributeNS(null, key, val);
831         },
832 
833         // documented in JXG.AbstractRenderer
834         show: function (el) {
835             var node;
836 
837             if (el && el.rendNode) {
838                 node = el.rendNode;
839                 node.setAttributeNS(null, 'display', 'inline');
840                 node.style.visibility = "inherit";
841             }
842         },
843 
844         // documented in JXG.AbstractRenderer
845         hide: function (el) {
846             var node;
847 
848             if (el && el.rendNode) {
849                 node = el.rendNode;
850                 node.setAttributeNS(null, 'display', 'none');
851                 node.style.visibility = "hidden";
852             }
853         },
854 
855         // documented in JXG.AbstractRenderer
856         setBuffering: function (el, type) {
857             el.rendNode.setAttribute('buffered-rendering', type);
858         },
859 
860         // documented in JXG.AbstractRenderer
861         setDashStyle: function (el) {
862             var dashStyle = el.visProp.dash, node = el.rendNode;
863 
864             if (el.visProp.dash > 0) {
865                 node.setAttributeNS(null, 'stroke-dasharray', this.dashArray[dashStyle - 1]);
866             } else {
867                 if (node.hasAttributeNS(null, 'stroke-dasharray')) {
868                     node.removeAttributeNS(null, 'stroke-dasharray');
869                 }
870             }
871         },
872 
873         // documented in JXG.AbstractRenderer
874         setGradient: function (el) {
875             var fillNode = el.rendNode, col, op,
876                 node, node2, node3, x1, x2, y1, y2;
877 
878             op = Type.evaluate(el.visProp.fillopacity);
879             op = (op > 0) ? op : 0;
880 
881             col = Type.evaluate(el.visProp.fillcolor);
882 
883             if (el.visProp.gradient === 'linear') {
884                 node = this.createPrim('linearGradient', el.id + '_gradient');
885                 x1 = '0%';
886                 x2 = '100%';
887                 y1 = '0%';
888                 y2 = '0%';
889 
890                 node.setAttributeNS(null, 'x1', x1);
891                 node.setAttributeNS(null, 'x2', x2);
892                 node.setAttributeNS(null, 'y1', y1);
893                 node.setAttributeNS(null, 'y2', y2);
894                 node2 = this.createPrim('stop', el.id + '_gradient1');
895                 node2.setAttributeNS(null, 'offset', '0%');
896                 node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
897                 node3 = this.createPrim('stop', el.id + '_gradient2');
898                 node3.setAttributeNS(null, 'offset', '100%');
899                 node3.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity);
900                 node.appendChild(node2);
901                 node.appendChild(node3);
902                 this.defs.appendChild(node);
903                 fillNode.setAttributeNS(null, 'style', 'fill:url(#' + this.container.id + '_' + el.id + '_gradient)');
904                 el.gradNode1 = node2;
905                 el.gradNode2 = node3;
906             } else if (el.visProp.gradient === 'radial') {
907                 node = this.createPrim('radialGradient', el.id + '_gradient');
908 
909                 node.setAttributeNS(null, 'cx', '50%');
910                 node.setAttributeNS(null, 'cy', '50%');
911                 node.setAttributeNS(null, 'r', '50%');
912                 node.setAttributeNS(null, 'fx', el.visProp.gradientpositionx * 100 + '%');
913                 node.setAttributeNS(null, 'fy', el.visProp.gradientpositiony * 100 + '%');
914 
915                 node2 = this.createPrim('stop', el.id + '_gradient1');
916                 node2.setAttributeNS(null, 'offset', '0%');
917                 node2.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity);
918                 node3 = this.createPrim('stop', el.id + '_gradient2');
919                 node3.setAttributeNS(null, 'offset', '100%');
920                 node3.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
921 
922                 node.appendChild(node2);
923                 node.appendChild(node3);
924                 this.defs.appendChild(node);
925                 fillNode.setAttributeNS(null, 'style', 'fill:url(#' + this.container.id + '_' + el.id + '_gradient)');
926                 el.gradNode1 = node2;
927                 el.gradNode2 = node3;
928             } else {
929                 fillNode.removeAttributeNS(null, 'style');
930             }
931         },
932 
933         // documented in JXG.AbstractRenderer
934         updateGradient: function (el) {
935             var col, op,
936                 node2 = el.gradNode1,
937                 node3 = el.gradNode2;
938 
939             if (!Type.exists(node2) || !Type.exists(node3)) {
940                 return;
941             }
942 
943             op = Type.evaluate(el.visProp.fillopacity);
944             op = (op > 0) ? op : 0;
945 
946             col = Type.evaluate(el.visProp.fillcolor);
947 
948             if (el.visProp.gradient === 'linear') {
949                 node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
950                 node3.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity);
951             } else if (el.visProp.gradient === 'radial') {
952                 node2.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity);
953                 node3.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
954             }
955         },
956 
957         // documented in JXG.AbstractRenderer
958         setObjectFillColor: function (el, color, opacity, rendNode) {
959             var node, c, rgbo, oo,
960                 rgba = Type.evaluate(color),
961                 o = Type.evaluate(opacity);
962 
963             o = (o > 0) ? o : 0;
964 
965             if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) {
966                 return;
967             }
968 
969             if (Type.exists(rgba) && rgba !== false) {
970                 if (rgba.length !== 9) {          // RGB, not RGBA
971                     c = rgba;
972                     oo = o;
973                 } else {                       // True RGBA, not RGB
974                     rgbo = Color.rgba2rgbo(rgba);
975                     c = rgbo[0];
976                     oo = o * rgbo[1];
977                 }
978 
979                 if (rendNode === undefined) {
980                     node = el.rendNode;
981                 } else {
982                     node = rendNode;
983                 }
984 
985                 if (c !== 'none') {
986                     node.setAttributeNS(null, 'fill', c);
987                 }
988 
989                 if (el.type === JXG.OBJECT_TYPE_IMAGE) {
990                     node.setAttributeNS(null, 'opacity', oo);
991                     //node.style['opacity'] = oo;  // This would overwrite values set by CSS class.
992                 } else {
993                     if (c === 'none') {  // This is don only for non-images
994                                          // because images have no fill color.
995                         oo = 0;
996                     }
997                     node.setAttributeNS(null, 'fill-opacity', oo);
998                 }
999 
1000                 if (Type.exists(el.visProp.gradient)) {
1001                     this.updateGradient(el);
1002                 }
1003             }
1004             el.visPropOld.fillcolor = rgba;
1005             el.visPropOld.fillopacity = o;
1006         },
1007 
1008         // documented in JXG.AbstractRenderer
1009         setObjectStrokeColor: function (el, color, opacity) {
1010             var rgba = Type.evaluate(color), c, rgbo,
1011                 o = Type.evaluate(opacity), oo,
1012                 node;
1013 
1014             o = (o > 0) ? o : 0;
1015 
1016             if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
1017                 return;
1018             }
1019 
1020             if (Type.exists(rgba) && rgba !== false) {
1021                 if (rgba.length !== 9) {          // RGB, not RGBA
1022                     c = rgba;
1023                     oo = o;
1024                 } else {                       // True RGBA, not RGB
1025                     rgbo = Color.rgba2rgbo(rgba);
1026                     c = rgbo[0];
1027                     oo = o * rgbo[1];
1028                 }
1029 
1030                 node = el.rendNode;
1031 
1032                 if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
1033                     if (el.visProp.display === 'html') {
1034                         node.style.color = c;
1035                         node.style.opacity = oo;
1036                     } else {
1037                         node.setAttributeNS(null, "style", "fill:" + c);
1038                         node.setAttributeNS(null, "style", "fill-opacity:" + oo);
1039                     }
1040                 } else {
1041                     node.setAttributeNS(null, 'stroke', c);
1042                     node.setAttributeNS(null, 'stroke-opacity', oo);
1043                 }
1044 
1045                 if (el.type === Const.OBJECT_TYPE_ARROW) {
1046                     this._setArrowColor(el.rendNodeTriangle, c, oo, el.rendNode);
1047                 } else if (el.elementClass === Const.OBJECT_CLASS_CURVE ||
1048                             el.elementClass === Const.OBJECT_CLASS_LINE) {
1049                     if (el.visProp.firstarrow) {
1050                         this._setArrowColor(el.rendNodeTriangleStart, c, oo, el.rendNode);
1051                     }
1052 
1053                     if (el.visProp.lastarrow) {
1054                         this._setArrowColor(el.rendNodeTriangleEnd, c, oo, el.rendNode);
1055                     }
1056                 }
1057             }
1058 
1059             el.visPropOld.strokecolor = rgba;
1060             el.visPropOld.strokeopacity = o;
1061         },
1062 
1063         // documented in JXG.AbstractRenderer
1064         setObjectStrokeWidth: function (el, width) {
1065             var node,
1066                 w = Type.evaluate(width),
1067                 rgba, c, rgbo, o, oo;
1068 
1069             if (isNaN(w) || el.visPropOld.strokewidth === w) {
1070                 return;
1071             }
1072 
1073             node = el.rendNode;
1074             this.setPropertyPrim(node, 'stroked', 'true');
1075             if (Type.exists(w)) {
1076                 this.setPropertyPrim(node, 'stroke-width', w + 'px');
1077 
1078                 if (el.type === Const.OBJECT_TYPE_ARROW) {
1079                     this._setArrowWidth(el.rendNodeTriangle, w, el.rendNode);
1080                 } else if (el.elementClass === Const.OBJECT_CLASS_CURVE ||
1081                             el.elementClass === Const.OBJECT_CLASS_LINE) {
1082                     if (el.visProp.firstarrow) {
1083                         this._setArrowWidth(el.rendNodeTriangleStart, w, el.rendNode);
1084                     }
1085 
1086                     if (el.visProp.lastarrow) {
1087                         this._setArrowWidth(el.rendNodeTriangleEnd, w, el.rendNode);
1088                     }
1089                 }
1090              }
1091             el.visPropOld.strokewidth = w;
1092         },
1093 
1094         // documented in JXG.AbstractRenderer
1095         setShadow: function (el) {
1096             if (el.visPropOld.shadow === el.visProp.shadow) {
1097                 return;
1098             }
1099 
1100             if (Type.exists(el.rendNode)) {
1101                 if (el.visProp.shadow) {
1102                     el.rendNode.setAttributeNS(null, 'filter', 'url(#' + this.container.id + '_' + 'f1)');
1103                 } else {
1104                     el.rendNode.removeAttributeNS(null, 'filter');
1105                 }
1106             }
1107             el.visPropOld.shadow = el.visProp.shadow;
1108         },
1109 
1110         /* **************************
1111          * renderer control
1112          * **************************/
1113 
1114         // documented in JXG.AbstractRenderer
1115         suspendRedraw: function () {
1116             // It seems to be important for the Linux version of firefox
1117             //this.suspendHandle = this.svgRoot.suspendRedraw(10000);
1118         },
1119 
1120         // documented in JXG.AbstractRenderer
1121         unsuspendRedraw: function () {
1122             //this.svgRoot.unsuspendRedraw(this.suspendHandle);
1123             //this.svgRoot.unsuspendRedrawAll();
1124             //this.svgRoot.forceRedraw();
1125         },
1126 
1127         // documented in AbstractRenderer
1128         resize: function (w, h) {
1129             this.svgRoot.style.width = parseFloat(w) + 'px';
1130             this.svgRoot.style.height = parseFloat(h) + 'px';
1131             this.svgRoot.setAttribute("width", parseFloat(w));
1132             this.svgRoot.setAttribute("height", parseFloat(h));
1133         },
1134 
1135         // documented in JXG.AbstractRenderer
1136         createTouchpoints: function (n) {
1137             var i, na1, na2, node;
1138             this.touchpoints = [];
1139             for (i = 0; i < n; i++) {
1140                 na1 = 'touchpoint1_' + i;
1141                 node = this.createPrim('path', na1);
1142                 this.appendChildPrim(node, 19);
1143                 node.setAttributeNS(null, 'd', 'M 0 0');
1144                 this.touchpoints.push(node);
1145 
1146                 this.setPropertyPrim(node, 'stroked', 'true');
1147                 this.setPropertyPrim(node, 'stroke-width', '1px');
1148                 node.setAttributeNS(null, 'stroke', '#000000');
1149                 node.setAttributeNS(null, 'stroke-opacity', 1.0);
1150                 node.setAttributeNS(null, 'display', 'none');
1151 
1152                 na2 = 'touchpoint2_' + i;
1153                 node = this.createPrim('ellipse', na2);
1154                 this.appendChildPrim(node, 19);
1155                 this.updateEllipsePrim(node, 0, 0, 0, 0);
1156                 this.touchpoints.push(node);
1157 
1158                 this.setPropertyPrim(node, 'stroked', 'true');
1159                 this.setPropertyPrim(node, 'stroke-width', '1px');
1160                 node.setAttributeNS(null, 'stroke', '#000000');
1161                 node.setAttributeNS(null, 'stroke-opacity', 1.0);
1162                 node.setAttributeNS(null, 'fill', '#ffffff');
1163                 node.setAttributeNS(null, 'fill-opacity', 0.0);
1164 
1165                 node.setAttributeNS(null, 'display', 'none');
1166             }
1167         },
1168 
1169         // documented in JXG.AbstractRenderer
1170         showTouchpoint: function (i) {
1171             if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) {
1172                 this.touchpoints[2 * i].setAttributeNS(null, 'display', 'inline');
1173                 this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'inline');
1174             }
1175         },
1176 
1177         // documented in JXG.AbstractRenderer
1178         hideTouchpoint: function (i) {
1179             if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) {
1180                 this.touchpoints[2 * i].setAttributeNS(null, 'display', 'none');
1181                 this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'none');
1182             }
1183         },
1184 
1185         // documented in JXG.AbstractRenderer
1186         updateTouchpoint: function (i, pos) {
1187             var x, y,
1188                 d = 37;
1189 
1190             if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) {
1191                 x = pos[0];
1192                 y = pos[1];
1193 
1194                 this.touchpoints[2 * i].setAttributeNS(null, 'd', 'M ' + (x - d) + ' ' + y + ' ' +
1195                     'L ' + (x + d) + ' ' + y + ' ' +
1196                     'M ' + x + ' ' + (y - d) + ' ' +
1197                     'L ' + x + ' ' + (y + d));
1198                 this.updateEllipsePrim(this.touchpoints[2 * i + 1], pos[0], pos[1], 25, 25);
1199             }
1200         },
1201 
1202         /**
1203          * Convert the SVG construction into an HTML canvas image.
1204          * This works for all SVG supporting browsers.
1205          * For IE it works from version 9.
1206          * But HTML texts are ignored on IE. The drawing is done with a delay of
1207          * 200 ms. Otherwise there are problems with IE.
1208          *
1209          * @param  {String} canvasId Id of an HTML canvas element
1210          * @returns {Object}          the svg renderer object.
1211          *
1212          * @example
1213          * 	board.renderer.dumpToCanvas('canvas');
1214          */
1215         dumpToCanvas: function(canvasId) {
1216             var svgRoot = this.svgRoot,
1217                 btoa = window.btoa || Base64.encode,
1218                 svg, tmpImg, cv, ctx;
1219 
1220             svgRoot.setAttribute("xmlns", "http://www.w3.org/2000/svg");
1221             svg = new XMLSerializer().serializeToString(svgRoot);
1222 
1223             // In IE we have to remove the namespace again.
1224             if ((svg.match(/xmlns=\"http:\/\/www.w3.org\/2000\/svg\"/g) || []).length > 1) {
1225                 svg = svg.replace(/xmlns=\"http:\/\/www.w3.org\/2000\/svg\"/, '');
1226             }
1227 
1228             cv = document.getElementById(canvasId);
1229             ctx = cv.getContext("2d");
1230 
1231             tmpImg = new Image();
1232             tmpImg.onload = function () {
1233                 // IE needs a pause...
1234                 setTimeout(function(){
1235                     cv.width = cv.width;
1236                     ctx.drawImage(tmpImg, 0, 0);
1237                 }, 200);
1238             };
1239             tmpImg.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)));
1240 
1241             return this;
1242         }
1243 
1244     });
1245 
1246     return JXG.SVGRenderer;
1247 });
1248