1 /*
  2     Copyright 2008-2017
  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         this.container.style.userSelect = 'none';
 98 
 99         this.container.style.overflow = 'hidden';
100         if (this.container.style.position === '') {
101             this.container.style.position = 'relative';
102         }
103 
104         this.svgRoot = this.container.ownerDocument.createElementNS(this.svgNamespace, "svg");
105         this.svgRoot.style.overflow = 'hidden';
106 
107         this.resize(dim.width, dim.height);
108 
109         //this.svgRoot.setAttributeNS(null, 'shape-rendering', 'crispEdge'); //'optimizeQuality'); //geometricPrecision');
110 
111         this.container.appendChild(this.svgRoot);
112 
113         /**
114          * The <tt>defs</tt> element is a container element to reference reusable SVG elements.
115          * @type Node
116          * @see http://www.w3.org/TR/SVG/struct.html#DefsElement
117          */
118         this.defs = this.container.ownerDocument.createElementNS(this.svgNamespace, 'defs');
119         this.svgRoot.appendChild(this.defs);
120 
121         /**
122          * Filters are used to apply shadows.
123          * @type Node
124          * @see http://www.w3.org/TR/SVG/filters.html#FilterElement
125          */
126         this.filter = this.container.ownerDocument.createElementNS(this.svgNamespace, 'filter');
127         this.filter.setAttributeNS(null, 'id', this.container.id + '_' + 'f1');
128         /*
129         this.filter.setAttributeNS(null, 'x', '-100%');
130         this.filter.setAttributeNS(null, 'y', '-100%');
131         this.filter.setAttributeNS(null, 'width', '400%');
132         this.filter.setAttributeNS(null, 'height', '400%');
133         //this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse');
134         */
135         this.filter.setAttributeNS(null, 'width', '300%');
136         this.filter.setAttributeNS(null, 'height', '300%');
137         this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse');
138 
139         this.feOffset = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feOffset');
140         this.feOffset.setAttributeNS(null, 'result', 'offOut');
141         this.feOffset.setAttributeNS(null, 'in', 'SourceAlpha');
142         this.feOffset.setAttributeNS(null, 'dx', '5');
143         this.feOffset.setAttributeNS(null, 'dy', '5');
144         this.filter.appendChild(this.feOffset);
145 
146         this.feGaussianBlur = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feGaussianBlur');
147         this.feGaussianBlur.setAttributeNS(null, 'result', 'blurOut');
148         this.feGaussianBlur.setAttributeNS(null, 'in', 'offOut');
149         this.feGaussianBlur.setAttributeNS(null, 'stdDeviation', '3');
150         this.filter.appendChild(this.feGaussianBlur);
151 
152         this.feBlend = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feBlend');
153         this.feBlend.setAttributeNS(null, 'in', 'SourceGraphic');
154         this.feBlend.setAttributeNS(null, 'in2', 'blurOut');
155         this.feBlend.setAttributeNS(null, 'mode', 'normal');
156         this.filter.appendChild(this.feBlend);
157 
158         this.defs.appendChild(this.filter);
159 
160         /**
161          * JSXGraph uses a layer system to sort the elements on the board. This puts certain types of elements in front
162          * of other types of elements. For the order used see {@link JXG.Options.layer}. The number of layers is documented
163          * there, too. The higher the number, the "more on top" are the elements on this layer.
164          * @type Array
165          */
166         this.layer = [];
167         for (i = 0; i < Options.layer.numlayers; i++) {
168             this.layer[i] = this.container.ownerDocument.createElementNS(this.svgNamespace, 'g');
169             this.svgRoot.appendChild(this.layer[i]);
170         }
171 
172         // already documented in JXG.AbstractRenderer
173         this.supportsForeignObject = document.implementation.hasFeature("www.http://w3.org/TR/SVG11/feature#Extensibility", "1.1");
174 
175         if (this.supportsForeignObject) {
176             this.foreignObjLayer = this.container.ownerDocument.createElementNS(this.svgNamespace, 'foreignObject');
177             this.foreignObjLayer.setAttribute("x",0);
178             this.foreignObjLayer.setAttribute("y",0);
179             this.foreignObjLayer.setAttribute("width","100%");
180             this.foreignObjLayer.setAttribute("height","100%");
181             this.foreignObjLayer.setAttribute('id', this.container.id + '_foreignObj');
182             this.svgRoot.appendChild(this.foreignObjLayer);
183         }
184 
185         /**
186          * Defines dash patterns. Defined styles are: <ol>
187          * <li value="-1"> 2px dash, 2px space</li>
188          * <li> 5px dash, 5px space</li>
189          * <li> 10px dash, 10px space</li>
190          * <li> 20px dash, 20px space</li>
191          * <li> 20px dash, 10px space, 10px dash, 10px dash</li>
192          * <li> 20px dash, 5px space, 10px dash, 5px space</li></ol>
193          * @type Array
194          * @default ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5']
195          * @see http://www.w3.org/TR/SVG/painting.html#StrokeProperties
196          */
197         this.dashArray = ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5'];
198     };
199 
200     JXG.SVGRenderer.prototype = new AbstractRenderer();
201 
202     JXG.extend(JXG.SVGRenderer.prototype, /** @lends JXG.SVGRenderer.prototype */ {
203 
204         /**
205          * Creates an arrow DOM node. Arrows are displayed in SVG with a <em>marker</em> tag.
206          * @private
207          * @param {JXG.GeometryElement} el A JSXGraph element, preferably one that can have an arrow attached.
208          * @param {String} [idAppendix=''] A string that is added to the node's id.
209          * @returns {Node} Reference to the node added to the DOM.
210          */
211         _createArrowHead: function (el, idAppendix) {
212             var node2, node3,
213                 id = el.id + 'Triangle',
214                 type = null,
215                 w, s,
216                 ev_fa = Type.evaluate(el.visProp.firstarrow),
217                 ev_la = Type.evaluate(el.visProp.lastarrow);
218 
219             if (Type.exists(idAppendix)) {
220                 id += idAppendix;
221             }
222             node2 = this.createPrim('marker', id);
223 
224             node2.setAttributeNS(null, 'stroke', Type.evaluate(el.visProp.strokecolor));
225             node2.setAttributeNS(null, 'stroke-opacity', Type.evaluate(el.visProp.strokeopacity));
226             node2.setAttributeNS(null, 'fill', Type.evaluate(el.visProp.strokecolor));
227             node2.setAttributeNS(null, 'fill-opacity', Type.evaluate(el.visProp.strokeopacity));
228             node2.setAttributeNS(null, 'stroke-width', 0);  // this is the stroke-width of the arrow head.
229                                                             // Should be zero to simplify the calculations
230 
231             node2.setAttributeNS(null, 'orient', 'auto');
232             node2.setAttributeNS(null, 'markerUnits', 'strokeWidth'); // 'strokeWidth' 'userSpaceOnUse');
233 
234             /*
235                The arrow head is an isosceles triangle with base length 10 and height 10.
236                This 10 units are scaled to strokeWidth * arrowSize pixels, see
237                this._setArrowWidth().
238 
239                See also abstractRenderer.updateLine() where the line path is shortened accordingly.
240 
241                Changes here are also necessary in setArrowWidth().
242             */
243             node3 = this.container.ownerDocument.createElementNS(this.svgNamespace, 'path');
244             if (idAppendix === 'End') {
245                 // First arrow
246                 if (JXG.exists(ev_fa.type)) {
247                     type = Type.evaluate(ev_fa.type);
248                 }
249 
250                 node2.setAttributeNS(null, 'refY', 5);
251                 if (type === 2) {
252                     node2.setAttributeNS(null, 'refX', 4.9);
253                     node3.setAttributeNS(null, 'd', 'M 10,0 L 0,5 L 10,10 L 5,5 z');
254                 } else if (type === 3) {
255                         node2.setAttributeNS(null, 'refX', 3.33);
256                         node3.setAttributeNS(null, 'd', 'M 0,0 L 3.33,0 L 3.33,10 L 0,10 z');
257                 } else {
258                     node2.setAttributeNS(null, 'refX', 9.9);
259                     node3.setAttributeNS(null, 'd', 'M 10,0 L 0,5 L 10,10 z');
260                 }
261             } else {
262                 // Last arrow
263                 if (JXG.exists(ev_la.type)) {
264                     type = Type.evaluate(ev_la.type);
265                 }
266 
267                 node2.setAttributeNS(null, 'refY', 5);
268                 if (type === 2) {
269                     node2.setAttributeNS(null, 'refX', 5.1);
270                     node3.setAttributeNS(null, 'd', 'M 0,0 L 10,5 L 0,10 L 5,5 z');
271                 } else if (type === 3) {
272                     node2.setAttributeNS(null, 'refX', 0.1);
273                     node3.setAttributeNS(null, 'd', 'M 0,0 L 3.33,0 L 3.33,10 L 0,10 z');
274                 } else {
275                     node2.setAttributeNS(null, 'refX', 0.1);
276                     node3.setAttributeNS(null, 'd', 'M 0,0 L 10,5 L 0,10 z');
277                 }
278             }
279 
280             node2.appendChild(node3);
281             return node2;
282         },
283 
284         /**
285          * Updates color of an arrow DOM node.
286          * @param {Node} node The arrow node.
287          * @param {String} color Color value in a HTML compatible format, e.g. <tt>#00ff00</tt> or <tt>green</tt> for green.
288          * @param {Number} opacity
289          * @param {JXG.GeometryElement} el The element the arrows are to be attached to
290          */
291         _setArrowColor: function (node, color, opacity, el) {
292             var s, d;
293 
294             if (node) {
295                 if (Type.isString(color)) {
296                     this._setAttribute(function() {
297                         node.setAttributeNS(null, 'stroke', color);
298                         node.setAttributeNS(null, 'fill', color);
299                         node.setAttributeNS(null, 'stroke-opacity', opacity);
300                         node.setAttributeNS(null, 'fill-opacity', opacity);
301                     }, el.visPropOld.fillcolor);
302                 }
303 
304                 if (this.isIE) {
305                     el.rendNode.parentNode.insertBefore(el.rendNode, el.rendNode);
306                 }
307             }
308 
309         },
310 
311         // already documented in JXG.AbstractRenderer
312         _setArrowWidth: function (node, width, parentNode, size) {
313             var s, d;
314 
315             if (node) {
316                 if (width === 0) {
317                     node.setAttributeNS(null, 'display', 'none');
318                 } else {
319                     s = width;
320                     d = s * size;
321                     node.setAttributeNS(null, 'viewBox', (0) + ' ' + (0) + ' ' + (s * 10) + ' ' + (s * 10));
322                     node.setAttributeNS(null, 'markerHeight', d);
323                     node.setAttributeNS(null, 'markerWidth', d);
324                     node.setAttributeNS(null, 'display', 'inherit');
325                 }
326 
327                 if (this.isIE) {
328                     parentNode.parentNode.insertBefore(parentNode, parentNode);
329                 }
330             }
331         },
332 
333         /* ******************************** *
334          *  This renderer does not need to
335          *  override draw/update* methods
336          *  since it provides draw/update*Prim
337          *  methods except for some cases like
338          *  internal texts or images.
339          * ******************************** */
340 
341         /* **************************
342          *    Lines
343          * **************************/
344 
345         // documented in AbstractRenderer
346         updateTicks: function (ticks) {
347             var i, c, node, x, y,
348                 tickStr = '',
349                 len = ticks.ticks.length;
350 
351             for (i = 0; i < len; i++) {
352                 c = ticks.ticks[i];
353                 x = c[0];
354                 y = c[1];
355 
356                 if (Type.isNumber(x[0]) && Type.isNumber(x[1])) {
357                     tickStr += "M " + (x[0]) + " " + (y[0]) + " L " + (x[1]) + " " + (y[1]) + " ";
358                 }
359             }
360 
361             node = ticks.rendNode;
362 
363             if (!Type.exists(node)) {
364                 node = this.createPrim('path', ticks.id);
365                 this.appendChildPrim(node, Type.evaluate(ticks.visProp.layer));
366                 ticks.rendNode = node;
367             }
368 
369             node.setAttributeNS(null, 'stroke', Type.evaluate(ticks.visProp.strokecolor));
370             node.setAttributeNS(null, 'stroke-opacity', Type.evaluate(ticks.visProp.strokeopacity));
371             node.setAttributeNS(null, 'stroke-width', Type.evaluate(ticks.visProp.strokewidth));
372             this.updatePathPrim(node, tickStr, ticks.board);
373         },
374 
375         /* **************************
376          *    Text related stuff
377          * **************************/
378 
379         // already documented in JXG.AbstractRenderer
380         displayCopyright: function (str, fontsize) {
381             var node = this.createPrim('text', 'licenseText'),
382                 t;
383             node.setAttributeNS(null, 'x', '20px');
384             node.setAttributeNS(null, 'y', (2 + fontsize) + 'px');
385             node.setAttributeNS(null, "style", "font-family:Arial,Helvetica,sans-serif; font-size:" + fontsize + "px; fill:#356AA0;  opacity:0.3;");
386             t = this.container.ownerDocument.createTextNode(str);
387             node.appendChild(t);
388             this.appendChildPrim(node, 0);
389         },
390 
391         // already documented in JXG.AbstractRenderer
392         drawInternalText: function (el) {
393             var node = this.createPrim('text', el.id);
394 
395             //node.setAttributeNS(null, "style", "alignment-baseline:middle"); // Not yet supported by Firefox
396             // Preserve spaces
397             //node.setAttributeNS("http://www.w3.org/XML/1998/namespace", "space", "preserve");
398             node.style.whiteSpace = 'nowrap';
399 
400             el.rendNodeText = this.container.ownerDocument.createTextNode('');
401             node.appendChild(el.rendNodeText);
402             this.appendChildPrim(node,  Type.evaluate(el.visProp.layer));
403 
404             return node;
405         },
406 
407         // already documented in JXG.AbstractRenderer
408         updateInternalText: function (el) {
409             var content = el.plaintext, v,
410                 ev_ax = Type.evaluate(el.visProp.anchorx),
411                 ev_ay = Type.evaluate(el.visProp.anchory);
412 
413             if (el.rendNode.getAttributeNS(null, "class") !== el.visProp.cssclass) {
414                 el.rendNode.setAttributeNS(null, "class", Type.evaluate(el.visProp.cssclass));
415                 el.needsSizeUpdate = true;
416             }
417 
418             if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) {
419                 // Horizontal
420                 v = el.coords.scrCoords[1];
421                 if (el.visPropOld.left !== (ev_ax + v)) {
422                     el.rendNode.setAttributeNS(null, 'x', v + 'px');
423 
424                     if (ev_ax === 'left') {
425                         el.rendNode.setAttributeNS(null, 'text-anchor', 'start');
426                     } else if (ev_ax === 'right') {
427                         el.rendNode.setAttributeNS(null, 'text-anchor', 'end');
428                     } else if (ev_ax === 'middle') {
429                         el.rendNode.setAttributeNS(null, 'text-anchor', 'middle');
430                     }
431                     el.visPropOld.left = ev_ax + v;
432                 }
433 
434                 // Vertical
435                 v = el.coords.scrCoords[2];
436                 if (el.visPropOld.top !== (ev_ay + v)) {
437                     el.rendNode.setAttributeNS(null, 'y', (v + this.vOffsetText * 0.5) + 'px');
438 
439                     if (ev_ay === 'bottom') {
440                         el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-after-edge');
441                     } else if (ev_ay === 'top') {
442                         el.rendNode.setAttributeNS(null, 'dy', '1.6ex');
443                         //el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-before-edge'); // Not supported by IE, edge
444                     } else if (ev_ay === 'middle') {
445                         //el.rendNode.setAttributeNS(null, 'dominant-baseline', 'middle');
446                         el.rendNode.setAttributeNS(null, 'dy', '0.6ex');
447                     }
448                     el.visPropOld.top = ev_ay + v;
449                 }
450             }
451             if (el.htmlStr !== content) {
452                 el.rendNodeText.data = content;
453                 el.htmlStr = content;
454             }
455             this.transformImage(el, el.transformations);
456         },
457 
458         /**
459          * Set color and opacity of internal texts.
460          * SVG needs its own version.
461          * @private
462          * @see JXG.AbstractRenderer#updateTextStyle
463          * @see JXG.AbstractRenderer#updateInternalTextStyle
464          */
465         updateInternalTextStyle: function (el, strokeColor, strokeOpacity, duration) {
466             this.setObjectFillColor(el, strokeColor, strokeOpacity);
467         },
468 
469         /* **************************
470          *    Image related stuff
471          * **************************/
472 
473         // already documented in JXG.AbstractRenderer
474         drawImage: function (el) {
475             var node = this.createPrim('image', el.id);
476 
477             node.setAttributeNS(null, 'preserveAspectRatio', 'none');
478             this.appendChildPrim(node, Type.evaluate(el.visProp.layer));
479             el.rendNode = node;
480 
481             this.updateImage(el);
482         },
483 
484         // already documented in JXG.AbstractRenderer
485         transformImage: function (el, t) {
486             var s, m,
487                 node = el.rendNode,
488                 str = "",
489                 len = t.length;
490 
491             if (len > 0) {
492                 m = this.joinTransforms(el, t);
493                 s = [m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]].join(',');
494                 str += ' matrix(' + s + ') ';
495                 node.setAttributeNS(null, 'transform', str);
496             }
497         },
498 
499         // already documented in JXG.AbstractRenderer
500         updateImageURL: function (el) {
501             var url = Type.evaluate(el.url);
502 
503             el.rendNode.setAttributeNS(this.xlinkNamespace, 'xlink:href', url);
504         },
505 
506         // already documented in JXG.AbstractRenderer
507         updateImageStyle: function (el, doHighlight) {
508             var css = Type.evaluate(doHighlight ? el.visProp.highlightcssclass : el.visProp.cssclass);
509 
510             el.rendNode.setAttributeNS(null, 'class', css);
511         },
512 
513         /* **************************
514          * Render primitive objects
515          * **************************/
516 
517         // already documented in JXG.AbstractRenderer
518         appendChildPrim: function (node, level) {
519             if (!Type.exists(level)) { // trace nodes have level not set
520                 level = 0;
521             } else if (level >= Options.layer.numlayers) {
522                 level = Options.layer.numlayers - 1;
523             }
524 
525             this.layer[level].appendChild(node);
526 
527             return node;
528         },
529 
530         // already documented in JXG.AbstractRenderer
531         createPrim: function (type, id) {
532             var node = this.container.ownerDocument.createElementNS(this.svgNamespace, type);
533             node.setAttributeNS(null, 'id', this.container.id + '_' + id);
534             node.style.position = 'absolute';
535             if (type === 'path') {
536                 node.setAttributeNS(null, 'stroke-linecap', 'round');
537                 node.setAttributeNS(null, 'stroke-linejoin', 'round');
538             }
539             return node;
540         },
541 
542         // already documented in JXG.AbstractRenderer
543         remove: function (shape) {
544             if (Type.exists(shape) && Type.exists(shape.parentNode)) {
545                 shape.parentNode.removeChild(shape);
546             }
547         },
548 
549         // already documented in JXG.AbstractRenderer
550         makeArrows: function (el) {
551             var node2,
552                 ev_fa = Type.evaluate(el.visProp.firstarrow),
553                 ev_la = Type.evaluate(el.visProp.lastarrow);
554 
555             if (el.visPropOld.firstarrow === ev_fa &&
556                 el.visPropOld.lastarrow === ev_la) {
557                 if (this.isIE && el.visPropCalc.visible &&
558                     (ev_fa || ev_la)) {
559                     el.rendNode.parentNode.insertBefore(el.rendNode, el.rendNode);
560                 }
561                 return;
562             }
563 
564             if (ev_fa) {
565                 node2 = el.rendNodeTriangleStart;
566                 if (!Type.exists(node2)) {
567                     node2 = this._createArrowHead(el, 'End');
568                     this.defs.appendChild(node2);
569                     el.rendNodeTriangleStart = node2;
570                     el.rendNode.setAttributeNS(null, 'marker-start', 'url(#' + this.container.id + '_' + el.id + 'TriangleEnd)');
571                 } else {
572                     this.defs.appendChild(node2);
573                 }
574             } else {
575                 node2 = el.rendNodeTriangleStart;
576                 if (Type.exists(node2)) {
577                     this.remove(node2);
578                 }
579             }
580             if (ev_la) {
581                 node2 = el.rendNodeTriangleEnd;
582                 if (!Type.exists(node2)) {
583                     node2 = this._createArrowHead(el, 'Start');
584                     this.defs.appendChild(node2);
585                     el.rendNodeTriangleEnd = node2;
586                     el.rendNode.setAttributeNS(null, 'marker-end', 'url(#' + this.container.id + '_' + el.id + 'TriangleStart)');
587                 } else {
588                     this.defs.appendChild(node2);
589                 }
590             } else {
591                 node2 = el.rendNodeTriangleEnd;
592                 if (Type.exists(node2)) {
593                     this.remove(node2);
594                 }
595             }
596             el.visPropOld.firstarrow = ev_fa;
597             el.visPropOld.lastarrow = ev_la;
598         },
599 
600         // already documented in JXG.AbstractRenderer
601         updateEllipsePrim: function (node, x, y, rx, ry) {
602             var huge = 1000000;
603 
604             huge = 200000; // IE
605             // webkit does not like huge values if the object is dashed
606             // iE doesn't like huge values above 216000
607             x = Math.abs(x) < huge ? x : huge * x / Math.abs(x);
608             y = Math.abs(y) < huge ? y : huge * y / Math.abs(y);
609             rx = Math.abs(rx) < huge ? rx : huge * rx / Math.abs(rx);
610             ry = Math.abs(ry) < huge ? ry : huge * ry / Math.abs(ry);
611 
612             node.setAttributeNS(null, 'cx', x);
613             node.setAttributeNS(null, 'cy', y);
614             node.setAttributeNS(null, 'rx', Math.abs(rx));
615             node.setAttributeNS(null, 'ry', Math.abs(ry));
616         },
617 
618         // already documented in JXG.AbstractRenderer
619         updateLinePrim: function (node, p1x, p1y, p2x, p2y) {
620             var huge = 1000000;
621 
622             huge = 200000; //IE
623             if (!isNaN(p1x + p1y + p2x + p2y)) {
624                 // webkit does not like huge values if the object is dashed
625                 // IE doesn't like huge values above 216000
626                 p1x = Math.abs(p1x) < huge ? p1x : huge * p1x / Math.abs(p1x);
627                 p1y = Math.abs(p1y) < huge ? p1y : huge * p1y / Math.abs(p1y);
628                 p2x = Math.abs(p2x) < huge ? p2x : huge * p2x / Math.abs(p2x);
629                 p2y = Math.abs(p2y) < huge ? p2y : huge * p2y / Math.abs(p2y);
630 
631                 node.setAttributeNS(null, 'x1', p1x);
632                 node.setAttributeNS(null, 'y1', p1y);
633                 node.setAttributeNS(null, 'x2', p2x);
634                 node.setAttributeNS(null, 'y2', p2y);
635             }
636         },
637 
638         // already documented in JXG.AbstractRenderer
639         updatePathPrim: function (node, pointString) {
640             if (pointString === '') {
641                 pointString = 'M 0 0';
642             }
643             node.setAttributeNS(null, 'd', pointString);
644         },
645 
646         // already documented in JXG.AbstractRenderer
647         updatePathStringPoint: function (el, size, type) {
648             var s = '',
649                 scr = el.coords.scrCoords,
650                 sqrt32 = size * Math.sqrt(3) * 0.5,
651                 s05 = size * 0.5;
652 
653             if (type === 'x') {
654                 s = ' M ' + (scr[1] - size) + ' ' + (scr[2] - size) +
655                     ' L ' + (scr[1] + size) + ' ' + (scr[2] + size) +
656                     ' M ' + (scr[1] + size) + ' ' + (scr[2] - size) +
657                     ' L ' + (scr[1] - size) + ' ' + (scr[2] + size);
658             } else if (type === '+') {
659                 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) +
660                     ' L ' + (scr[1] + size) + ' ' + (scr[2]) +
661                     ' M ' + (scr[1])        + ' ' + (scr[2] - size) +
662                     ' L ' + (scr[1])        + ' ' + (scr[2] + size);
663             } else if (type === '<>') {
664                 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) +
665                     ' L ' + (scr[1])        + ' ' + (scr[2] + size) +
666                     ' L ' + (scr[1] + size) + ' ' + (scr[2]) +
667                     ' L ' + (scr[1])        + ' ' + (scr[2] - size) + ' Z ';
668             } else if (type === '^') {
669                 s = ' M ' + (scr[1])          + ' ' + (scr[2] - size) +
670                     ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] + s05) +
671                     ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] + s05) +
672                     ' Z ';  // close path
673             } else if (type === 'v') {
674                 s = ' M ' + (scr[1])          + ' ' + (scr[2] + size) +
675                     ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] - s05) +
676                     ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] - s05) +
677                     ' Z ';
678             } else if (type === '>') {
679                 s = ' M ' + (scr[1] + size) + ' ' + (scr[2]) +
680                     ' L ' + (scr[1] - s05)  + ' ' + (scr[2] - sqrt32) +
681                     ' L ' + (scr[1] - s05)  + ' ' + (scr[2] + sqrt32) +
682                     ' Z ';
683             } else if (type === '<') {
684                 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) +
685                     ' L ' + (scr[1] + s05)  + ' ' + (scr[2] - sqrt32) +
686                     ' L ' + (scr[1] + s05)  + ' ' + (scr[2] + sqrt32) +
687                     ' Z ';
688             }
689             return s;
690         },
691 
692         // already documented in JXG.AbstractRenderer
693         updatePathStringPrim: function (el) {
694             var i, scr, len,
695                 symbm = ' M ',
696                 symbl = ' L ',
697                 symbc = ' C ',
698                 nextSymb = symbm,
699                 maxSize = 5000.0,
700                 pStr = '';
701 
702             if (el.numberPoints <= 0) {
703                 return '';
704             }
705 
706             len = Math.min(el.points.length, el.numberPoints);
707 
708             if (el.bezierDegree === 1) {
709                 for (i = 0; i < len; i++) {
710                     scr = el.points[i].scrCoords;
711                     if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
712                         nextSymb = symbm;
713                     } else {
714                         // Chrome has problems with values being too far away.
715                         scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize);
716                         scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize);
717 
718                         // Attention: first coordinate may be inaccurate if far way
719                         //pStr += [nextSymb, scr[1], ' ', scr[2]].join('');
720                         pStr += nextSymb + scr[1] + ' ' + scr[2];   // Seems to be faster now (webkit and firefox)
721                         nextSymb = symbl;
722                     }
723                 }
724             } else if (el.bezierDegree === 3) {
725                 i = 0;
726                 while (i < len) {
727                     scr = el.points[i].scrCoords;
728                     if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
729                         nextSymb = symbm;
730                     } else {
731                         pStr += nextSymb + scr[1] + ' ' + scr[2];
732                         if (nextSymb === symbc) {
733                             i += 1;
734                             scr = el.points[i].scrCoords;
735                             pStr += ' ' + scr[1] + ' ' + scr[2];
736                             i += 1;
737                             scr = el.points[i].scrCoords;
738                             pStr += ' ' + scr[1] + ' ' + scr[2];
739                         }
740                         nextSymb = symbc;
741                     }
742                     i += 1;
743                 }
744             }
745             return pStr;
746         },
747 
748         // already documented in JXG.AbstractRenderer
749         updatePathStringBezierPrim: function (el) {
750             var i, j, k, scr, lx, ly, len,
751                 symbm = ' M ',
752                 symbl = ' C ',
753                 nextSymb = symbm,
754                 maxSize = 5000.0,
755                 pStr = '',
756                 f = Type.evaluate(el.visProp.strokewidth),
757                 isNoPlot = (Type.evaluate(el.visProp.curvetype) !== 'plot');
758 
759             if (el.numberPoints <= 0) {
760                 return '';
761             }
762 
763             if (isNoPlot && el.board.options.curve.RDPsmoothing) {
764                 el.points = Numerics.RamerDouglasPeucker(el.points, 0.5);
765             }
766 
767             len = Math.min(el.points.length, el.numberPoints);
768             for (j = 1; j < 3; j++) {
769                 nextSymb = symbm;
770                 for (i = 0; i < len; i++) {
771                     scr = el.points[i].scrCoords;
772 
773                     if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
774                         nextSymb = symbm;
775                     } else {
776                         // Chrome has problems with values being too far away.
777                         scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize);
778                         scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize);
779 
780                         // Attention: first coordinate may be inaccurate if far way
781                         if (nextSymb === symbm) {
782                             //pStr += [nextSymb, scr[1], ' ', scr[2]].join('');
783                             pStr += nextSymb + scr[1] + ' ' + scr[2];   // Seems to be faster now (webkit and firefox)
784                         } else {
785                             k = 2 * j;
786                             pStr += [nextSymb,
787                                 (lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j)), ' ',
788                                 (ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j)), ' ',
789                                 (lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j)), ' ',
790                                 (ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j)), ' ',
791                                 scr[1], ' ', scr[2]].join('');
792                         }
793 
794                         nextSymb = symbl;
795                         lx = scr[1];
796                         ly = scr[2];
797                     }
798                 }
799             }
800             return pStr;
801         },
802 
803         // already documented in JXG.AbstractRenderer
804         updatePolygonPrim: function (node, el) {
805             var i,
806                 pStr = '',
807                 scrCoords,
808                 len = el.vertices.length;
809 
810             node.setAttributeNS(null, 'stroke', 'none');
811 
812             for (i = 0; i < len - 1; i++) {
813                 if (el.vertices[i].isReal) {
814                     scrCoords = el.vertices[i].coords.scrCoords;
815                     pStr = pStr + scrCoords[1] + "," + scrCoords[2];
816                 } else {
817                     node.setAttributeNS(null, 'points', '');
818                     return;
819                 }
820 
821                 if (i < len - 2) {
822                     pStr += " ";
823                 }
824             }
825             if (pStr.indexOf('NaN') === -1) {
826                 node.setAttributeNS(null, 'points', pStr);
827             }
828         },
829 
830         // already documented in JXG.AbstractRenderer
831         updateRectPrim: function (node, x, y, w, h) {
832             node.setAttributeNS(null, 'x', x);
833             node.setAttributeNS(null, 'y', y);
834             node.setAttributeNS(null, 'width', w);
835             node.setAttributeNS(null, 'height', h);
836         },
837 
838         /* **************************
839          *  Set Attributes
840          * **************************/
841 
842         // documented in JXG.AbstractRenderer
843         setPropertyPrim: function (node, key, val) {
844             if (key === 'stroked') {
845                 return;
846             }
847             node.setAttributeNS(null, key, val);
848         },
849 
850         display: function(el, val) {
851             var node;
852 
853             if (el && el.rendNode) {
854                 el.visPropOld.visible = val;
855                 node = el.rendNode;
856                 if (val) {
857                     node.setAttributeNS(null, 'display', 'inline');
858                     node.style.visibility = "inherit";
859                 } else {
860                     node.setAttributeNS(null, 'display', 'none');
861                     node.style.visibility = "hidden";
862                 }
863             }
864         },
865 
866         // documented in JXG.AbstractRenderer
867         show: function (el) {
868             JXG.deprecated('Board.renderer.show()', 'Board.renderer.display()');
869             this.display(el, true);
870             // var node;
871             //
872             // if (el && el.rendNode) {
873             //     node = el.rendNode;
874             //     node.setAttributeNS(null, 'display', 'inline');
875             //     node.style.visibility = "inherit";
876             // }
877         },
878 
879         // documented in JXG.AbstractRenderer
880         hide: function (el) {
881             JXG.deprecated('Board.renderer.hide()', 'Board.renderer.display()');
882             this.display(el, false);
883             // var node;
884             //
885             // if (el && el.rendNode) {
886             //     node = el.rendNode;
887             //     node.setAttributeNS(null, 'display', 'none');
888             //     node.style.visibility = "hidden";
889             // }
890         },
891 
892         // documented in JXG.AbstractRenderer
893         setBuffering: function (el, type) {
894             el.rendNode.setAttribute('buffered-rendering', type);
895         },
896 
897         // documented in JXG.AbstractRenderer
898         setDashStyle: function (el) {
899             var dashStyle = Type.evaluate(el.visProp.dash),
900                 node = el.rendNode;
901 
902             if (dashStyle > 0) {
903                 node.setAttributeNS(null, 'stroke-dasharray', this.dashArray[dashStyle - 1]);
904             } else {
905                 if (node.hasAttributeNS(null, 'stroke-dasharray')) {
906                     node.removeAttributeNS(null, 'stroke-dasharray');
907                 }
908             }
909         },
910 
911         // documented in JXG.AbstractRenderer
912         setGradient: function (el) {
913             var fillNode = el.rendNode, col, op,
914                 node, node2, node3, x1, x2, y1, y2,
915                 ev_g = Type.evaluate(el.visProp.gradient);
916 
917             op = Type.evaluate(el.visProp.fillopacity);
918             op = (op > 0) ? op : 0;
919             col = Type.evaluate(el.visProp.fillcolor);
920 
921             if (ev_g === 'linear') {
922                 node = this.createPrim('linearGradient', el.id + '_gradient');
923                 x1 = '0%';
924                 x2 = '100%';
925                 y1 = '0%';
926                 y2 = '0%';
927 
928                 node.setAttributeNS(null, 'x1', x1);
929                 node.setAttributeNS(null, 'x2', x2);
930                 node.setAttributeNS(null, 'y1', y1);
931                 node.setAttributeNS(null, 'y2', y2);
932                 node2 = this.createPrim('stop', el.id + '_gradient1');
933                 node2.setAttributeNS(null, 'offset', '0%');
934                 node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
935                 node3 = this.createPrim('stop', el.id + '_gradient2');
936                 node3.setAttributeNS(null, 'offset', '100%');
937                 node3.setAttributeNS(null, 'style', 'stop-color:' + Type.evaluate(el.visProp.gradientsecondcolor) +
938                             ';stop-opacity:' + Type.evaluate(el.visProp.gradientsecondopacity));
939                 node.appendChild(node2);
940                 node.appendChild(node3);
941                 this.defs.appendChild(node);
942                 fillNode.setAttributeNS(null, 'style', 'fill:url(#' + this.container.id + '_' + el.id + '_gradient)');
943                 el.gradNode1 = node2;
944                 el.gradNode2 = node3;
945             } else if (ev_g === 'radial') {
946                 node = this.createPrim('radialGradient', el.id + '_gradient');
947 
948                 node.setAttributeNS(null, 'cx', '50%');
949                 node.setAttributeNS(null, 'cy', '50%');
950                 node.setAttributeNS(null, 'r', '50%');
951                 node.setAttributeNS(null, 'fx', Type.evaluate(el.visProp.gradientpositionx) * 100 + '%');
952                 node.setAttributeNS(null, 'fy', Type.evaluate(el.visProp.gradientpositiony) * 100 + '%');
953 
954                 node2 = this.createPrim('stop', el.id + '_gradient1');
955                 node2.setAttributeNS(null, 'offset', '0%');
956                 node2.setAttributeNS(null, 'style', 'stop-color:' + Type.evaluate(el.visProp.gradientsecondcolor) +
957                                 ';stop-opacity:' + Type.evaluate(el.visProp.gradientsecondopacity));
958                 node3 = this.createPrim('stop', el.id + '_gradient2');
959                 node3.setAttributeNS(null, 'offset', '100%');
960                 node3.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
961 
962                 node.appendChild(node2);
963                 node.appendChild(node3);
964                 this.defs.appendChild(node);
965                 fillNode.setAttributeNS(null, 'style', 'fill:url(#' + this.container.id + '_' + el.id + '_gradient)');
966                 el.gradNode1 = node2;
967                 el.gradNode2 = node3;
968             } else {
969                 fillNode.removeAttributeNS(null, 'style');
970             }
971         },
972 
973         // documented in JXG.AbstractRenderer
974         updateGradient: function (el) {
975             var col, op,
976                 node2 = el.gradNode1,
977                 node3 = el.gradNode2,
978                 ev_g = Type.evaluate(el.visProp.gradient);
979 
980             if (!Type.exists(node2) || !Type.exists(node3)) {
981                 return;
982             }
983 
984             op = Type.evaluate(el.visProp.fillopacity);
985             op = (op > 0) ? op : 0;
986             col = Type.evaluate(el.visProp.fillcolor);
987 
988             if (ev_g === 'linear') {
989                 node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
990                 node3.setAttributeNS(null, 'style', 'stop-color:' + Type.evaluate(el.visProp.gradientsecondcolor) +
991                         ';stop-opacity:' + Type.evaluate(el.visProp.gradientsecondopacity));
992             } else if (ev_g === 'radial') {
993                 node2.setAttributeNS(null, 'style', 'stop-color:' + Type.evaluate(el.visProp.gradientsecondcolor) +
994                         ';stop-opacity:' + Type.evaluate(el.visProp.gradientsecondopacity));
995                 node3.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
996             }
997         },
998 
999         // documented in JXG.AbstractRenderer
1000         setObjectTransition: function (el, duration) {
1001             var node, transitionStr,
1002                 i, len,
1003                 nodes = ['rendNode',
1004                          'rendNodeTriangleStart',
1005                          'rendNodeTriangleEnd'];
1006 
1007             if (duration === undefined) {
1008                 duration = Type.evaluate(el.visProp.transitionduration);
1009             }
1010 
1011             if (duration === el.visPropOld.transitionduration) {
1012                 return;
1013             }
1014 
1015             if (el.elementClass === Const.OBJECT_CLASS_TEXT &&
1016                 Type.evaluate(el.visProp.display) === 'html') {
1017                 transitionStr = ' color ' + duration + 'ms,' +
1018                             ' opacity ' + duration + 'ms';
1019             } else {
1020                 transitionStr = ' fill ' + duration + 'ms,' +
1021                             ' fill-opacity ' + duration + 'ms,' +
1022                             ' stroke ' + duration + 'ms,' +
1023                             ' stroke-opacity ' + duration + 'ms';
1024             }
1025 
1026             len = nodes.length;
1027             for (i = 0; i < len; ++i) if (el[nodes[i]]) {
1028                 node = el[nodes[i]];
1029                 node.style.transition = transitionStr;
1030             }
1031 
1032             el.visPropOld.transitionduration = duration;
1033         },
1034 
1035         /**
1036          * Call user-defined function to set visual attributes.
1037          * If "testAttribute" is the empty string, the function
1038          * is called immediately, otherwise it is called in a timeOut.
1039          *
1040          * This is necessary to realize smooth transitions buit avoid transistions
1041          * when first creating the objects.
1042          *
1043          * Usually, the string in testAttribute is the visPropOld attribute
1044          * of the values which are set.
1045          *
1046          * @param {Function} setFunc       Some function which usually sets some attributes
1047          * @param {String} testAttribute If this string is the empty string  the function is called immediately,
1048          *                               otherwise it is called in a setImeout.
1049          * @see JXG.SVGRenderer#setObjectFillColor
1050          * @see JXG.SVGRenderer#setObjectStrokeColor
1051          * @see JXG.SVGRenderer#_setArrowColor
1052          * @private
1053          */
1054         _setAttribute: function(setFunc, testAttribute) {
1055             if (testAttribute === '') {
1056                 setFunc();
1057             } else {
1058                 setTimeout(setFunc, 1);
1059             }
1060         },
1061 
1062         // documented in JXG.AbstractRenderer
1063         setObjectFillColor: function (el, color, opacity, rendNode) {
1064             var node, c, rgbo, oo, t,
1065                 rgba = Type.evaluate(color),
1066                 o = Type.evaluate(opacity);
1067 
1068             o = (o > 0) ? o : 0;
1069 
1070             if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) {
1071                 return;
1072             }
1073 
1074             if (Type.exists(rgba) && rgba !== false) {
1075                 if (rgba.length !== 9) {          // RGB, not RGBA
1076                     c = rgba;
1077                     oo = o;
1078                 } else {                       // True RGBA, not RGB
1079                     rgbo = Color.rgba2rgbo(rgba);
1080                     c = rgbo[0];
1081                     oo = o * rgbo[1];
1082                 }
1083 
1084                 if (rendNode === undefined) {
1085                     node = el.rendNode;
1086                 } else {
1087                     node = rendNode;
1088                 }
1089 
1090                 if (c !== 'none') {
1091                     this._setAttribute(function() {
1092                             node.setAttributeNS(null, 'fill', c);
1093                         }, el.visPropOld.fillcolor);
1094                 }
1095 
1096                 if (el.type === JXG.OBJECT_TYPE_IMAGE) {
1097                     this._setAttribute(function() {
1098                             node.setAttributeNS(null, 'opacity', oo);
1099                         }, el.visPropOld.fillopacity);
1100                     //node.style['opacity'] = oo;  // This would overwrite values set by CSS class.
1101                 } else {
1102                     if (c === 'none') {  // This is done only for non-images
1103                                          // because images have no fill color.
1104                         oo = 0;
1105                     }
1106                     this._setAttribute(function() {
1107                             node.setAttributeNS(null, 'fill-opacity', oo);
1108                         }, el.visPropOld.fillopacity);
1109                 }
1110 
1111                 if (Type.exists(el.visProp.gradient)) {
1112                     this.updateGradient(el);
1113                 }
1114             }
1115             el.visPropOld.fillcolor = rgba;
1116             el.visPropOld.fillopacity = o;
1117         },
1118 
1119         // documented in JXG.AbstractRenderer
1120         setObjectStrokeColor: function (el, color, opacity) {
1121             var rgba = Type.evaluate(color), c, rgbo,
1122                 o = Type.evaluate(opacity), oo,
1123                 node;
1124 
1125             o = (o > 0) ? o : 0;
1126 
1127             if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
1128                 return;
1129             }
1130 
1131             if (Type.exists(rgba) && rgba !== false) {
1132                 if (rgba.length !== 9) {          // RGB, not RGBA
1133                     c = rgba;
1134                     oo = o;
1135                 } else {                       // True RGBA, not RGB
1136                     rgbo = Color.rgba2rgbo(rgba);
1137                     c = rgbo[0];
1138                     oo = o * rgbo[1];
1139                 }
1140 
1141                 node = el.rendNode;
1142 
1143                 if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
1144                     if (Type.evaluate(el.visProp.display) === 'html') {
1145                         this._setAttribute(function() {
1146                                 node.style.color = c;
1147                                 node.style.opacity = oo;
1148                             }, el.visPropOld.strokecolor);
1149 
1150                     } else {
1151                         this._setAttribute(function() {
1152                                 node.setAttributeNS(null, "style", "fill:" + c);
1153                                 node.setAttributeNS(null, "style", "fill-opacity:" + oo);
1154                             }, el.visPropOld.strokecolor);
1155                     }
1156                 } else {
1157                     this._setAttribute(function() {
1158                             node.setAttributeNS(null, 'stroke', c);
1159                             node.setAttributeNS(null, 'stroke-opacity', oo);
1160                         }, el.visPropOld.strokecolor);
1161                 }
1162 
1163                 if (el.elementClass === Const.OBJECT_CLASS_CURVE ||
1164                     el.elementClass === Const.OBJECT_CLASS_LINE) {
1165                     if (Type.evaluate(el.visProp.firstarrow)) {
1166                         this._setArrowColor(el.rendNodeTriangleStart, c, oo, el);
1167                     }
1168 
1169                     if (Type.evaluate(el.visProp.lastarrow)) {
1170                         this._setArrowColor(el.rendNodeTriangleEnd, c, oo, el);
1171                     }
1172                 }
1173             }
1174 
1175             el.visPropOld.strokecolor = rgba;
1176             el.visPropOld.strokeopacity = o;
1177         },
1178 
1179         // documented in JXG.AbstractRenderer
1180         setObjectStrokeWidth: function (el, width) {
1181             var node,
1182                 w = Type.evaluate(width),
1183                 rgba, c, rgbo, o, oo;
1184 
1185             if (isNaN(w) || el.visPropOld.strokewidth === w) {
1186                 return;
1187             }
1188 
1189             node = el.rendNode;
1190             this.setPropertyPrim(node, 'stroked', 'true');
1191             if (Type.exists(w)) {
1192                 this.setPropertyPrim(node, 'stroke-width', w + 'px');
1193 
1194                 // if (el.elementClass === Const.OBJECT_CLASS_CURVE ||
1195                 // el.elementClass === Const.OBJECT_CLASS_LINE) {
1196                 //     if (Type.evaluate(el.visProp.firstarrow)) {
1197                 //         this._setArrowWidth(el.rendNodeTriangleStart, w, el.rendNode);
1198                 //     }
1199                 //
1200                 //     if (Type.evaluate(el.visProp.lastarrow)) {
1201                 //         this._setArrowWidth(el.rendNodeTriangleEnd, w, el.rendNode);
1202                 //     }
1203                 // }
1204              }
1205             el.visPropOld.strokewidth = w;
1206         },
1207 
1208         // documented in JXG.AbstractRenderer
1209         setLineCap: function (el) {
1210             var capStyle = Type.evaluate(el.visProp.linecap);
1211 
1212             if (capStyle === undefined || capStyle === '' || el.visPropOld.linecap === capStyle ||
1213                 !Type.exists(el.rendNode)) {
1214                 return;
1215             }
1216 
1217             this.setPropertyPrim(el.rendNode, 'stroke-linecap', capStyle);
1218             el.visPropOld.linecap = capStyle;
1219 
1220         },
1221 
1222         // documented in JXG.AbstractRenderer
1223         setShadow: function (el) {
1224             var ev_s = Type.evaluate(el.visProp.shadow);
1225             if (el.visPropOld.shadow === ev_s) {
1226                 return;
1227             }
1228 
1229             if (Type.exists(el.rendNode)) {
1230                 if (ev_s) {
1231                     el.rendNode.setAttributeNS(null, 'filter', 'url(#' + this.container.id + '_' + 'f1)');
1232                 } else {
1233                     el.rendNode.removeAttributeNS(null, 'filter');
1234                 }
1235             }
1236             el.visPropOld.shadow = ev_s;
1237         },
1238 
1239         /* **************************
1240          * renderer control
1241          * **************************/
1242 
1243         // documented in JXG.AbstractRenderer
1244         suspendRedraw: function () {
1245             // It seems to be important for the Linux version of firefox
1246             //this.suspendHandle = this.svgRoot.suspendRedraw(10000);
1247         },
1248 
1249         // documented in JXG.AbstractRenderer
1250         unsuspendRedraw: function () {
1251             //this.svgRoot.unsuspendRedraw(this.suspendHandle);
1252             //this.svgRoot.unsuspendRedrawAll();
1253             //this.svgRoot.forceRedraw();
1254         },
1255 
1256         // documented in AbstractRenderer
1257         resize: function (w, h) {
1258             this.svgRoot.style.width = parseFloat(w) + 'px';
1259             this.svgRoot.style.height = parseFloat(h) + 'px';
1260             this.svgRoot.setAttribute("width", parseFloat(w));
1261             this.svgRoot.setAttribute("height", parseFloat(h));
1262         },
1263 
1264         // documented in JXG.AbstractRenderer
1265         createTouchpoints: function (n) {
1266             var i, na1, na2, node;
1267             this.touchpoints = [];
1268             for (i = 0; i < n; i++) {
1269                 na1 = 'touchpoint1_' + i;
1270                 node = this.createPrim('path', na1);
1271                 this.appendChildPrim(node, 19);
1272                 node.setAttributeNS(null, 'd', 'M 0 0');
1273                 this.touchpoints.push(node);
1274 
1275                 this.setPropertyPrim(node, 'stroked', 'true');
1276                 this.setPropertyPrim(node, 'stroke-width', '1px');
1277                 node.setAttributeNS(null, 'stroke', '#000000');
1278                 node.setAttributeNS(null, 'stroke-opacity', 1.0);
1279                 node.setAttributeNS(null, 'display', 'none');
1280 
1281                 na2 = 'touchpoint2_' + i;
1282                 node = this.createPrim('ellipse', na2);
1283                 this.appendChildPrim(node, 19);
1284                 this.updateEllipsePrim(node, 0, 0, 0, 0);
1285                 this.touchpoints.push(node);
1286 
1287                 this.setPropertyPrim(node, 'stroked', 'true');
1288                 this.setPropertyPrim(node, 'stroke-width', '1px');
1289                 node.setAttributeNS(null, 'stroke', '#000000');
1290                 node.setAttributeNS(null, 'stroke-opacity', 1.0);
1291                 node.setAttributeNS(null, 'fill', '#ffffff');
1292                 node.setAttributeNS(null, 'fill-opacity', 0.0);
1293 
1294                 node.setAttributeNS(null, 'display', 'none');
1295             }
1296         },
1297 
1298         // documented in JXG.AbstractRenderer
1299         showTouchpoint: function (i) {
1300             if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) {
1301                 this.touchpoints[2 * i].setAttributeNS(null, 'display', 'inline');
1302                 this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'inline');
1303             }
1304         },
1305 
1306         // documented in JXG.AbstractRenderer
1307         hideTouchpoint: function (i) {
1308             if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) {
1309                 this.touchpoints[2 * i].setAttributeNS(null, 'display', 'none');
1310                 this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'none');
1311             }
1312         },
1313 
1314         // documented in JXG.AbstractRenderer
1315         updateTouchpoint: function (i, pos) {
1316             var x, y,
1317                 d = 37;
1318 
1319             if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) {
1320                 x = pos[0];
1321                 y = pos[1];
1322 
1323                 this.touchpoints[2 * i].setAttributeNS(null, 'd', 'M ' + (x - d) + ' ' + y + ' ' +
1324                     'L ' + (x + d) + ' ' + y + ' ' +
1325                     'M ' + x + ' ' + (y - d) + ' ' +
1326                     'L ' + x + ' ' + (y + d));
1327                 this.updateEllipsePrim(this.touchpoints[2 * i + 1], pos[0], pos[1], 25, 25);
1328             }
1329         },
1330 
1331         /**
1332          * Convert the SVG construction into an HTML canvas image.
1333          * This works for all SVG supporting browsers.
1334          * For IE it works from version 9.
1335          * But HTML texts are ignored on IE. The drawing is done with a delay of
1336          * 200 ms. Otherwise there are problems with IE.
1337          *
1338          * @param {String} canvasId Id of an HTML canvas element
1339          * @param {Number} w Width in pixel of the dumped image, i.e. of the canvas tag.
1340          * @param {Number} h Height in pixel of the dumped image, i.e. of the canvas tag.
1341          * @returns {Object}          the svg renderer object.
1342          *
1343          * @example
1344          * 	board.renderer.dumpToCanvas('canvas');
1345          */
1346         dumpToCanvas: function(canvasId, w, h) {
1347             var svgRoot = this.svgRoot,
1348                 btoa = window.btoa || Base64.encode,
1349                 svg, tmpImg, cv, ctx,
1350                 wOrg, hOrg,
1351                 uriPayload;
1352 
1353             // Move all HTML tags (beside the SVG root) of the container
1354             // to the foreignObject element inside of the svgRoot node
1355             if (this.container.hasChildNodes() && Type.exists(this.foreignObjLayer)) {
1356                 while (svgRoot.nextSibling) {
1357                     this.foreignObjLayer.appendChild(svgRoot.nextSibling);
1358                 }
1359             }
1360 
1361             svgRoot.setAttribute("xmlns", "http://www.w3.org/2000/svg");
1362             wOrg = svgRoot.getAttribute('width');
1363             hOrg = svgRoot.getAttribute('height');
1364 
1365             svg = new XMLSerializer().serializeToString(svgRoot);
1366 
1367             // In IE we have to remove the namespace again.
1368             if ((svg.match(/xmlns=\"http:\/\/www.w3.org\/2000\/svg\"/g) || []).length > 1) {
1369                 svg = svg.replace(/xmlns=\"http:\/\/www.w3.org\/2000\/svg\"/g, '');
1370             }
1371 
1372             // Safari fails if the svg string contains a " "
1373             svg = svg.replace(/ /g, ' ');
1374 
1375             cv = document.getElementById(canvasId);
1376             // Clear the canvas
1377             cv.width = cv.width;
1378 
1379             ctx = cv.getContext("2d");
1380             if (w !== undefined && h !== undefined) {
1381                 // Scale twice the CSS size to make the image crisp
1382                 cv.style.width = parseFloat(w) + 'px';
1383                 cv.style.height = parseFloat(h) + 'px';
1384                 cv.setAttribute('width',  2 * parseFloat(wOrg));
1385                 cv.setAttribute('height', 2 * parseFloat(hOrg));
1386                 ctx.scale(2 * wOrg / w, 2 * hOrg / h);
1387             }
1388 
1389             tmpImg = new Image();
1390             if (true) {
1391                 tmpImg.onload = function () {
1392                     // IE needs a pause...
1393                     setTimeout(function(){
1394                         ctx.drawImage(tmpImg, 0, 0, w, h);
1395                     }, 200);
1396                 };
1397 
1398                 tmpImg.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)));
1399                 // uriPayload = encodeURIComponent(svg.replace(/\n+/g, '')) // remove newlines // encode URL-unsafe characters
1400                 //             .replace(/%20/g, ' ') // put spaces back in
1401                 //             .replace(/%3D/g, '=') // ditto equals signs
1402                 //             .replace(/%3A/g, ':') // ditto colons
1403                 //             .replace(/%2F/g, '/') // ditto slashes
1404                 //             .replace(/%22/g, "'");
1405                 // tmpImg.src = 'data:image/svg+xml,' + uriPayload;
1406             } else {
1407                 // Alternative version
1408                 var DOMURL = window.URL || window.webkitURL || window;
1409                 var svgBlob = new Blob([svg], {type: 'image/svg+xml'});
1410                 var url = DOMURL.createObjectURL(svgBlob);
1411                 tmpImg.src = url;
1412 
1413                 tmpImg.onload = function () {
1414                     // IE needs a pause...
1415                     setTimeout(function(){
1416                         ctx.drawImage(tmpImg, 0, 0, w, h);
1417                     }, 200);
1418                     DOMURL.revokeObjectURL(url);
1419                 };
1420             }
1421 
1422             // Move all HTML tags back from
1423             // the foreignObject element to the container
1424             if (Type.exists(this.foreignObjLayer) && this.foreignObjLayer.hasChildNodes()) {
1425                 while (this.foreignObjLayer.firstChild) {
1426                     this.container.appendChild(this.foreignObjLayer.firstChild);
1427                 }
1428             }
1429 
1430             return this;
1431         },
1432 
1433         screenshot: function(board) {
1434             var node, doc, cPos,
1435                 canvas, id,
1436                 img,
1437                 button, buttonText,
1438                 w, h,
1439                 bas = board.attr.screenshot,
1440                 zbar, zbarDisplay,
1441                 cssTxt;
1442 
1443             if (this.type === 'no') {
1444                 return;
1445             }
1446 
1447             w = bas.scale * parseFloat(board.containerObj.style.width);
1448             h = bas.scale * parseFloat(board.containerObj.style.height);
1449 
1450             // Create div which contains canvas element and close button
1451             doc = board.containerObj.ownerDocument;
1452             node = doc.createElement('div');
1453             node.style.cssText = bas.css;
1454             node.style.width = (w) + 'px';
1455             node.style.height = (h) + 'px';
1456             node.style.zIndex = board.containerObj.style.zIndex + 120;
1457 
1458             // Position the div exactly over the JSXGraph board
1459             cPos = board.getCoordsTopLeftCorner();
1460             node.style.position= 'absolute';
1461             node.style.left = (500+ cPos[0]) + 'px';
1462             node.style.top = (cPos[1]) + 'px';
1463 
1464             // Create canvas element
1465             canvas = doc.createElement('canvas');
1466             id = Math.random().toString(36).substr(2, 5);
1467             canvas.setAttribute('id', id);
1468             canvas.setAttribute('width', w);
1469             canvas.setAttribute('height', h);
1470             canvas.style.width = w + 'px';
1471             canvas.style.height = w + 'px';
1472             canvas.style.display = 'none';
1473 
1474             img = new Image(); //doc.createElement('img');
1475             img.style.width = w + 'px';
1476             img.style.height = h + 'px';
1477             //img.crossOrigin = 'anonymous';
1478 
1479             // Create close button
1480             button = doc.createElement('span');
1481             buttonText = doc.createTextNode('\u2716');
1482             button.style.cssText = bas.cssButton;
1483             button.appendChild(buttonText);
1484             button.onclick = function() {
1485                 node.parentNode.removeChild(node);
1486             };
1487 
1488             // Add all nodes
1489             node.appendChild(canvas);
1490             node.appendChild(img);
1491             node.appendChild(button);
1492             board.containerObj.parentNode.appendChild(node);
1493 
1494             // Hide navigation bar in board
1495             zbar = document.getElementById(board.containerObj.id + '_navigationbar');
1496             if (Type.exists(zbar)) {
1497                 zbarDisplay = zbar.style.display;
1498                 zbar.style.display = 'none';
1499             }
1500 
1501             // Create screenshot in canvas
1502             this.dumpToCanvas(id, w, h);
1503             // Show image in img tag
1504             setTimeout(function() {
1505                 img.src = canvas.toDataURL('image/png');
1506             }, 400);
1507 
1508             // Show navigation bar in board
1509             if (Type.exists(zbar)) {
1510                 zbar.style.display = zbarDisplay;
1511             }
1512         }
1513 
1514     });
1515 
1516     return JXG.SVGRenderer;
1517 });
1518