1 /*
  2     Copyright 2008-2022
  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 
 33 /*global JXG: true, define: true, AMprocessNode: true, MathJax: true, document: true, window: true */
 34 
 35 /*
 36     nomen:    Allow underscores to indicate private class members. Might be replaced by local variables.
 37     plusplus: Only allowed in for-loops
 38     newcap:   AsciiMathMl exposes non-constructor functions beginning with upper case letters
 39 */
 40 /*jslint nomen: true, plusplus: true, newcap: true, unparam: true*/
 41 /*eslint no-unused-vars: "off"*/
 42 
 43 /* depends:
 44  jxg
 45  options
 46  base/coords
 47  base/constants
 48  math/math
 49  math/geometry
 50  utils/type
 51  utils/env
 52 */
 53 
 54 /**
 55  * @fileoverview JSXGraph can use various technologies to render the contents of a construction, e.g.
 56  * SVG, VML, and HTML5 Canvas. To accomplish this, The rendering and the logic and control mechanisms
 57  * are completely separated from each other. Every rendering technology has it's own class, called
 58  * Renderer, e.g. SVGRenderer for SVG, the same for VML and Canvas. The common base for all available
 59  * renderers is the class AbstractRenderer defined in this file.
 60  */
 61 
 62 define([
 63     'jxg', 'options', 'base/coords', 'base/constants', 'math/math', 'math/geometry', 'utils/type', 'utils/env'
 64 ], function (JXG, Options, Coords, Const, Mat, Geometry, Type, Env) {
 65 
 66     "use strict";
 67 
 68     /**
 69      * <p>This class defines the interface to the graphics part of JSXGraph. This class is an abstract class, it
 70      * actually does not render anything. This is up to the {@link JXG.SVGRenderer}, {@link JXG.VMLRenderer},
 71      * and {@link JXG.CanvasRenderer} classes. We strongly discourage you from using the methods in these classes
 72      * directly. Only the methods which are defined in this class and are not marked as private are guaranteed
 73      * to exist in any renderer instance you can access via {@link JXG.Board#renderer}. But not all methods may
 74      * work as expected.</p>
 75      * <p>The methods of this renderer can be divided into different categories:
 76      * <dl>
 77      *     <dt>Draw basic elements</dt>
 78      *     <dd>In this category we find methods to draw basic elements like {@link JXG.Point}, {@link JXG.Line},
 79      *     and {@link JXG.Curve} as well as assisting methods tightly bound to these basic painters. You do not
 80      *     need to implement these methods in a descendant renderer but instead implement the primitive drawing
 81      *     methods described below. This approach is encouraged when you're using a XML based rendering engine
 82      *     like VML and SVG. If you want to use a bitmap based rendering technique you are supposed to override
 83      *     these methods instead of the primitive drawing methods.</dd>
 84      *     <dt>Draw primitives</dt>
 85      *     <dd>This category summarizes methods to handle primitive nodes. As creation and management of these nodes
 86      *     is different among different the rendering techniques most of these methods are purely virtual and need
 87      *     proper implementation if you choose to not overwrite the basic element drawing methods.</dd>
 88      *     <dt>Attribute manipulation</dt>
 89      *     <dd>In XML based renders you have to manipulate XML nodes and their attributes to change the graphics.
 90      *     For that purpose attribute manipulation methods are defined to set the color, opacity, and other things.
 91      *     Please note that some of these methods are required in bitmap based renderers, too, because some elements
 92      *     like {@link JXG.Text} can be HTML nodes floating over the construction.</dd>
 93      *     <dt>Renderer control</dt>
 94      *     <dd>Methods to clear the drawing board or to stop and to resume the rendering engine.</dd>
 95      * </dl></p>
 96      * @class JXG.AbstractRenderer
 97      * @constructor
 98      * @see JXG.SVGRenderer
 99      * @see JXG.VMLRenderer
100      * @see JXG.CanvasRenderer
101      */
102     JXG.AbstractRenderer = function () {
103 
104         // WHY THIS IS A CLASS INSTEAD OF A SINGLETON OBJECT:
105         //
106         // The renderers need to keep track of some stuff which is not always the same on different boards,
107         // like enhancedRendering, reference to the container object, and resolution in VML. Sure, those
108         // things could be stored in board. But they are rendering related and JXG.Board is already very
109         // very big.
110         //
111         // And we can't save the rendering related data in {SVG,VML,Canvas}Renderer and make only the
112         // JXG.AbstractRenderer a singleton because of that:
113         //
114         // Given an object o with property a set to true
115         //     var o = {a: true};
116         // and a class c doing nothing
117         //     c = function() {};
118         // Set c's prototype to o
119         //     c.prototype = o;
120         // and create an instance of c we get i.a to be true
121         //     i = new c();
122         //     i.a;
123         //     > true
124         // But we can overwrite this property via
125         //     c.prototype.a = false;
126         //     i.a;
127         //     > false
128 
129         /**
130          * The vertical offset for {@link Text} elements. Every {@link Text} element will
131          * be placed this amount of pixels below the user given coordinates.
132          * @type Number
133          * @default 0
134          */
135         this.vOffsetText = 0;
136 
137         /**
138          * If this property is set to <tt>true</tt> the visual properties of the elements are updated
139          * on every update. Visual properties means: All the stuff stored in the
140          * {@link JXG.GeometryElement#visProp} property won't be set if enhancedRendering is <tt>false</tt>
141          * @type Boolean
142          * @default true
143          */
144         this.enhancedRendering = true;
145 
146         /**
147          * The HTML element that stores the JSXGraph board in it.
148          * @type Node
149          */
150         this.container = null;
151 
152         /**
153          * This is used to easily determine which renderer we are using
154          * @example if (board.renderer.type === 'vml') {
155           *     // do something
156          * }
157          * @type String
158          */
159         this.type = '';
160 
161         /**
162          * True if the browsers' SVG engine supports foreignObject.
163          * Not supported browsers are IE 9 - 11.
164          * All other browsers return ture, since it is tested with
165          * document.implementation.hasFeature() which is deprecated.
166          *
167          * @type Boolean
168          * @private
169          */
170         this.supportsForeignObject = false;
171 
172     };
173 
174     JXG.extend(JXG.AbstractRenderer.prototype, /** @lends JXG.AbstractRenderer.prototype */ {
175 
176         /* ******************************** *
177          *    private methods               *
178          *    should not be called from     *
179          *    outside AbstractRenderer      *
180          * ******************************** */
181 
182         /**
183          * Update visual properties, but only if {@link JXG.AbstractRenderer#enhancedRendering} or <tt>enhanced</tt> is set to true.
184          * @param {JXG.GeometryElement} el The element to update
185          * @param {Object} [not={}] Select properties you don't want to be updated: <tt>{fill: true, dash: true}</tt> updates
186          * everything except for fill and dash. Possible values are <tt>stroke, fill, dash, shadow, gradient</tt>.
187          * @param {Boolean} [enhanced=false] If true, {@link JXG.AbstractRenderer#enhancedRendering} is assumed to be true.
188          * @private
189          */
190         _updateVisual: function (el, not, enhanced) {
191             if (enhanced || this.enhancedRendering) {
192                 not = not || {};
193 
194                 this.setObjectTransition(el);
195                 if (!Type.evaluate(el.visProp.draft)) {
196                     if (!not.stroke) {
197                         if (el.highlighted) {
198                             this.setObjectStrokeColor(el,
199                                 el.visProp.highlightstrokecolor,
200                                 el.visProp.highlightstrokeopacity);
201                             this.setObjectStrokeWidth(el, el.visProp.highlightstrokewidth);
202                         } else {
203                             this.setObjectStrokeColor(el,
204                                 el.visProp.strokecolor,
205                                 el.visProp.strokeopacity);
206                             this.setObjectStrokeWidth(el, el.visProp.strokewidth);
207                         }
208                     }
209 
210                     if (!not.fill) {
211                         if (el.highlighted) {
212                             this.setObjectFillColor(el,
213                                 el.visProp.highlightfillcolor,
214                                 el.visProp.highlightfillopacity);
215                         } else {
216                             this.setObjectFillColor(el,
217                                 el.visProp.fillcolor,
218                                 el.visProp.fillopacity);
219                         }
220                     }
221 
222                     if (!not.dash) {
223                         this.setDashStyle(el, el.visProp);
224                     }
225 
226                     if (!not.shadow) {
227                         this.setShadow(el);
228                     }
229 
230                     if (!not.gradient) {
231                         this.setShadow(el);
232                     }
233 
234                     if (!not.tabindex) {
235                         this.setTabindex(el);
236                     }
237                 } else {
238                     this.setDraft(el);
239                 }
240             }
241         },
242 
243         /**
244          * Get information if element is highlighted.
245          * @param {JXG.GeometryElement} el The element which is tested for being highlighted.
246          * @returns {String} 'highlight' if highlighted, otherwise the ampty string '' is returned.
247          * @private
248          */
249         _getHighlighted: function(el) {
250             var isTrace = false,
251                 hl;
252 
253             if (!Type.exists(el.board) || !Type.exists(el.board.highlightedObjects)) {
254                 // This case handles trace elements.
255                 // To make them work, we simply neglect highlighting.
256                 isTrace = true;
257             }
258 
259             if (!isTrace && Type.exists(el.board.highlightedObjects[el.id])) {
260                 hl = 'highlight';
261             } else {
262                 hl = '';
263             }
264             return hl;
265         },
266 
267         /* ******************************** *
268          *    Point drawing and updating    *
269          * ******************************** */
270 
271         /**
272          * Draws a point on the {@link JXG.Board}.
273          * @param {JXG.Point} el Reference to a {@link JXG.Point} object that has to be drawn.
274          * @see Point
275          * @see JXG.Point
276          * @see JXG.AbstractRenderer#updatePoint
277          * @see JXG.AbstractRenderer#changePointStyle
278          */
279         drawPoint: function (el) {
280             var prim,
281                 // sometimes el is not a real point and lacks the methods of a JXG.Point instance,
282                 // in these cases to not use el directly.
283                 face = Options.normalizePointFace(Type.evaluate(el.visProp.face));
284 
285             // determine how the point looks like
286             if (face === 'o') {
287                 prim = 'ellipse';
288             } else if (face === '[]') {
289                 prim = 'rect';
290             } else {
291                 // cross/x, diamond/<>, triangleup/a/^, triangledown/v, triangleleft/<,
292                 // triangleright/>, plus/+,
293                 prim = 'path';
294             }
295 
296             el.rendNode = this.appendChildPrim(this.createPrim(prim, el.id), Type.evaluate(el.visProp.layer));
297             this.appendNodesToElement(el, prim);
298 
299             // adjust visual propertys
300             this._updateVisual(el, {dash: true, shadow: true}, true);
301 
302             // By now we only created the xml nodes and set some styles, in updatePoint
303             // the attributes are filled with data.
304             this.updatePoint(el);
305         },
306 
307         /**
308          * Updates visual appearance of the renderer element assigned to the given {@link JXG.Point}.
309          * @param {JXG.Point} el Reference to a {@link JXG.Point} object, that has to be updated.
310          * @see Point
311          * @see JXG.Point
312          * @see JXG.AbstractRenderer#drawPoint
313          * @see JXG.AbstractRenderer#changePointStyle
314          */
315         updatePoint: function (el) {
316             var size = Type.evaluate(el.visProp.size),
317                 // sometimes el is not a real point and lacks the methods of a JXG.Point instance,
318                 // in these cases to not use el directly.
319                 face = Options.normalizePointFace(Type.evaluate(el.visProp.face)),
320                 unit = Type.evaluate(el.visProp.sizeunit),
321                 zoom = Type.evaluate(el.visProp.zoom),
322                 s1;
323 
324             if (!isNaN(el.coords.scrCoords[2] + el.coords.scrCoords[1])) {
325                 if (unit === 'user') {
326                     size *= Math.sqrt(el.board.unitX * el.board.unitY);
327                 }
328                 size *= ((!el.board || !zoom) ?
329                     1.0 : Math.sqrt(el.board.zoomX * el.board.zoomY));
330                 s1 = (size === 0) ? 0 : size + 1;
331 
332                 if (face === 'o') { // circle
333                     this.updateEllipsePrim(el.rendNode, el.coords.scrCoords[1],
334                          el.coords.scrCoords[2], s1, s1);
335                 } else if (face === '[]') { // rectangle
336                     this.updateRectPrim(el.rendNode, el.coords.scrCoords[1] - size,
337                          el.coords.scrCoords[2] - size, size * 2, size * 2);
338                 } else { // x, +, <>, ^, v, <, >
339                     this.updatePathPrim(el.rendNode,
340                         this.updatePathStringPoint(el, size, face), el.board);
341                 }
342                 this._updateVisual(el, {dash: false, shadow: false});
343                 this.setShadow(el);
344             }
345         },
346 
347         /**
348          * Changes the style of a {@link JXG.Point}. This is required because the point styles differ in what
349          * elements have to be drawn, e.g. if the point is marked by a "x" or a "+" two lines are drawn, if
350          * it's marked by spot a circle is drawn. This method removes the old renderer element(s) and creates
351          * the new one(s).
352          * @param {JXG.Point} el Reference to a {@link JXG.Point} object, that's style is changed.
353          * @see Point
354          * @see JXG.Point
355          * @see JXG.AbstractRenderer#updatePoint
356          * @see JXG.AbstractRenderer#drawPoint
357          */
358         changePointStyle: function (el) {
359             var node = this.getElementById(el.id);
360 
361             // remove the existing point rendering node
362             if (Type.exists(node)) {
363                 this.remove(node);
364             }
365 
366             // and make a new one
367             this.drawPoint(el);
368             Type.clearVisPropOld(el);
369 
370             if (!el.visPropCalc.visible) {
371                 this.display(el, false);
372             }
373 
374             if (Type.evaluate(el.visProp.draft)) {
375                 this.setDraft(el);
376             }
377         },
378 
379         /* ******************************** *
380          *           Lines                  *
381          * ******************************** */
382 
383         /**
384          * Draws a line on the {@link JXG.Board}.
385          * @param {JXG.Line} el Reference to a line object, that has to be drawn.
386          * @see Line
387          * @see JXG.Line
388          * @see JXG.AbstractRenderer#updateLine
389          */
390         drawLine: function (el) {
391             el.rendNode = this.appendChildPrim(this.createPrim('line', el.id),
392                                     Type.evaluate(el.visProp.layer));
393             this.appendNodesToElement(el, 'lines');
394             this.updateLine(el);
395         },
396 
397         /**
398          * Updates visual appearance of the renderer element assigned to the given {@link JXG.Line}.
399          * @param {JXG.Line} el Reference to the {@link JXG.Line} object that has to be updated.
400          * @see Line
401          * @see JXG.Line
402          * @see JXG.AbstractRenderer#drawLine
403          */
404         updateLine: function (el) {
405             this._updateVisual(el);
406             this.updatePathWithArrowHeads(el);  // Calls the renderer primitive
407             this.setLineCap(el);
408         },
409 
410         /* **************************
411          *    Curves
412          * **************************/
413 
414         /**
415          * Draws a {@link JXG.Curve} on the {@link JXG.Board}.
416          * @param {JXG.Curve} el Reference to a graph object, that has to be plotted.
417          * @see Curve
418          * @see JXG.Curve
419          * @see JXG.AbstractRenderer#updateCurve
420          */
421         drawCurve: function (el) {
422             el.rendNode = this.appendChildPrim(this.createPrim('path', el.id), Type.evaluate(el.visProp.layer));
423             this.appendNodesToElement(el, 'path');
424             this.updateCurve(el);
425         },
426 
427         /**
428          * Updates visual appearance of the renderer element assigned to the given {@link JXG.Curve}.
429          * @param {JXG.Curve} el Reference to a {@link JXG.Curve} object, that has to be updated.
430          * @see Curve
431          * @see JXG.Curve
432          * @see JXG.AbstractRenderer#drawCurve
433          */
434         updateCurve: function (el) {
435             this._updateVisual(el);
436             this.updatePathWithArrowHeads(el); // Calls the renderer primitive
437             this.setLineCap(el);
438         },
439 
440         /* **************************
441          *    Arrow heads and related stuff
442          * **************************/
443 
444         /**
445          * Handles arrow heads of a line or curve element and calls the renderer primitive.
446          *
447          * @param {JXG.GeometryElement} el Reference to a line or curve object that has to be drawn.
448          * @param {Boolean} doHighlight
449          *
450          * @private
451          * @see Line
452          * @see JXG.Line
453          * @see Curve
454          * @see JXG.Curve
455          * @see JXG.AbstractRenderer#updateLine
456          * @see JXG.AbstractRenderer#updateCurve
457          * @see JXG.AbstractRenderer#makeArrows
458          * @see JXG.AbstractRenderer#getArrowHeadData
459          */
460         updatePathWithArrowHeads: function(el, doHighlight) {
461             var ev = el.visProp,
462                 hl = doHighlight ? 'highlight' : '',
463                 w,
464                 arrowData;
465 
466             if (doHighlight && ev.highlightstrokewidth) {
467                 w = Math.max(Type.evaluate(ev.highlightstrokewidth), Type.evaluate(ev.strokewidth));
468             } else {
469                 w = Type.evaluate(ev.strokewidth);
470             }
471 
472             // Get information if there are arrow heads and how large they are.
473             arrowData = this.getArrowHeadData(el, w, hl);
474 
475             // Create the SVG nodes if neccessary
476             this.makeArrows(el, arrowData);
477 
478             // Draw the paths with arrow heads
479             if (el.elementClass === Const.OBJECT_CLASS_LINE) {
480                 this.updateLineWithEndings(el, arrowData);
481             } else if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
482                 this.updatePath(el);
483             }
484 
485             this.setArrowSize(el, arrowData);
486         },
487 
488         /**
489          * This method determines some data about the line endings of this element.
490          * If there are arrow heads, the offset is determined so that no parts of the line stroke
491          * lap over the arrow head.
492          * <p>
493          * The returned object also contains the types of the arrow heads.
494          *
495          * @param {JXG.GeometryElement} el JSXGraph line or curve element
496          * @param {Number} strokewidth strokewidth of the element
497          * @param {String} hl Ither 'highlight' or empty string
498          * @returns {Object} object containing the data
499          *
500          * @private
501          */
502         getArrowHeadData: function(el, strokewidth, hl) {
503             var minlen = Mat.eps,
504                 typeFirst, typeLast,
505                 offFirst = 0,
506                 offLast = 0,
507                 sizeFirst = 0,
508                 sizeLast = 0,
509                 ev_fa = Type.evaluate(el.visProp.firstarrow),
510                 ev_la = Type.evaluate(el.visProp.lastarrow),
511                 off, size;
512 
513             /*
514                Handle arrow heads.
515 
516                The default arrow head is an isosceles triangle with base length 10 units and height 10 units.
517                These 10 units are scaled to strokeWidth * arrowSize pixels pixels.
518             */
519             if (ev_fa || ev_la) {
520 
521                 if (Type.exists(ev_fa.type)) {
522                     typeFirst = Type.evaluate(ev_fa.type);
523                 } else {
524                     if (el.elementClass === Const.OBJECT_CLASS_LINE) {
525                         typeFirst = 1;
526                     } else {
527                         typeFirst = 7;
528                     }
529                 }
530                 if (Type.exists(ev_la.type)) {
531                     typeLast = Type.evaluate(ev_la.type);
532                 } else {
533                     if (el.elementClass === Const.OBJECT_CLASS_LINE) {
534                         typeLast = 1;
535                     } else {
536                         typeLast = 7;
537                     }
538                 }
539 
540                 if (ev_fa) {
541                     size = 6;
542                     if (Type.exists(ev_fa.size)) {
543                         size = Type.evaluate(ev_fa.size);
544                     }
545                     if (hl !== '' && Type.exists(ev_fa[hl + 'size'])) {
546                         size = Type.evaluate(ev_fa[hl + 'size']);
547                     }
548 
549                     off = strokewidth * size;
550                     if (typeFirst === 2) {
551                         off *= 0.5;
552                         minlen += strokewidth * size;
553                     } else if (typeFirst === 3) {
554                         off = strokewidth * size / 3;
555                         minlen += strokewidth;
556                     } else if (typeFirst === 4 || typeFirst === 5 || typeFirst === 6) {
557                         off = strokewidth * size / 1.5;
558                         minlen += strokewidth * size;
559                     } else if (typeFirst === 7) {
560                         off = 0;
561                         size = 10;
562                         minlen += strokewidth;
563                     } else {
564                         minlen += strokewidth * size;
565                     }
566                     offFirst += off;
567                     sizeFirst = size;
568                 }
569 
570                 if (ev_la) {
571                     size = 6;
572                     if (Type.exists(ev_la.size)) {
573                         size = Type.evaluate(ev_la.size);
574                     }
575                     if (hl !== '' && Type.exists(ev_la[hl + 'size'])) {
576                         size = Type.evaluate(ev_la[hl + 'size']);
577                     }
578                     off = strokewidth * size;
579                     if (typeLast === 2) {
580                         off *= 0.5;
581                         minlen += strokewidth * size;
582                     } else if (typeLast === 3) {
583                         off = strokewidth * size / 3;
584                         minlen += strokewidth;
585                     } else if (typeLast === 4 || typeLast === 5 || typeLast === 6) {
586                         off = strokewidth * size / 1.5;
587                         minlen += strokewidth * size;
588                     } else if (typeLast === 7) {
589                         off = 0;
590                         size = 10;
591                         minlen += strokewidth;
592                     } else {
593                         minlen += strokewidth * size;
594                     }
595                     offLast += off;
596                     sizeLast = size;
597                 }
598             }
599             el.visPropCalc.typeFirst = typeFirst;
600             el.visPropCalc.typeLast = typeLast;
601 
602             return {
603                 evFirst: ev_fa,
604                 evLast: ev_la,
605                 typeFirst: typeFirst,
606                 typeLast: typeLast,
607                 offFirst: offFirst,
608                 offLast: offLast,
609                 sizeFirst: sizeFirst,
610                 sizeLast: sizeLast,
611                 showFirst: 1, // Show arrow head. 0 if the distance is too small
612                 showLast: 1,  // Show arrow head. 0 if the distance is too small
613                 minLen: minlen,
614                 strokeWidth: strokewidth
615             };
616         },
617 
618         /**
619          * Corrects the line length if there are arrow heads, such that
620          * the arrow ends exactly at the intended position.
621          * Calls the renderer method to draw the line.
622          *
623          * @param {JXG.Line} el Reference to a line object, that has to be drawn
624          * @param {Object} arrowData Data concerning possible arrow heads
625          *
626          * @returns {JXG.AbstractRenderer} Reference to the renderer
627          *
628          * @private
629          * @see Line
630          * @see JXG.Line
631          * @see JXG.AbstractRenderer#updateLine
632          * @see JXG.AbstractRenderer#getPositionArrowHead
633          *
634          */
635         updateLineWithEndings: function(el, arrowData) {
636             var c1, c2,
637                 // useTotalLength = true,
638                 margin = null;
639 
640             c1 = new Coords(Const.COORDS_BY_USER, el.point1.coords.usrCoords, el.board);
641             c2 = new Coords(Const.COORDS_BY_USER, el.point2.coords.usrCoords, el.board);
642             margin = Type.evaluate(el.visProp.margin);
643             Geometry.calcStraight(el, c1, c2, margin);
644 
645             this.handleTouchpoints(el, c1, c2, arrowData);
646             this.getPositionArrowHead(el, c1, c2, arrowData);
647 
648             this.updateLinePrim(el.rendNode,
649                 c1.scrCoords[1], c1.scrCoords[2],
650                 c2.scrCoords[1], c2.scrCoords[2], el.board);
651 
652             return this;
653         },
654 
655         /**
656          *
657          * Calls the renderer method to draw a curve.
658          *
659          * @param {JXG.GeometryElement} el Reference to a line object, that has to be drawn.
660          * @returns {JXG.AbstractRenderer} Reference to the renderer
661          *
662          * @private
663          * @see Curve
664          * @see JXG.Curve
665          * @see JXG.AbstractRenderer#updateCurve
666          *
667          */
668         updatePath: function(el) {
669             if (Type.evaluate(el.visProp.handdrawing)) {
670                 this.updatePathPrim(el.rendNode, this.updatePathStringBezierPrim(el), el.board);
671             } else {
672                 this.updatePathPrim(el.rendNode, this.updatePathStringPrim(el), el.board);
673             }
674 
675             return this;
676         },
677 
678         /**
679          * Shorten the length of a line element such that the arrow head touches
680          * the start or end point and such that the arrow head ends exactly
681          * at the start / end position of the line.
682          *
683          * @param  {JXG.Line} el Reference to the line object that gets arrow heads.
684          * @param  {JXG.Coords} c1  Coords of the first point of the line (after {@link JXG.Math.Geometry#calcStraight}).
685          * @param  {JXG.Coords} c2  Coords of the second point of the line (after {@link JXG.Math.Geometry#calcStraight}).
686          * @param  {Object}  a
687          * @return {object} Object containing how much the line has to be shortened.
688          * Data structure: {c1, c2, d1x, d1y, d2x, d2y, sFirst, sLast}. sFirst and sLast is the length by which
689          * firstArrow and lastArrow have to shifted such that there is no gap between arrow head and line.
690          * Additionally, if one of these values is zero, the arrow is not displayed. This is the case, if the
691          * line length is very short.
692          */
693          getPositionArrowHead: function(el, c1, c2, a) {
694             var d, d1x, d1y, d2x, d2y;
695 
696             /*
697                Handle arrow heads.
698 
699                The default arrow head (type==1) is an isosceles triangle with base length 10 units and height 10 units.
700                These 10 units are scaled to strokeWidth * arrowSize pixels pixels.
701             */
702             if (a.evFirst || a.evLast) {
703                 // Correct the position of the arrow heads
704                 d1x = d1y = d2x = d2y = 0.0;
705                 d = c1.distance(Const.COORDS_BY_SCREEN, c2);
706 
707                 if (a.evFirst &&
708                     el.board.renderer.type !== 'vml') {
709                     if (d >= a.minLen) {
710                         d1x = (c2.scrCoords[1] - c1.scrCoords[1]) * a.offFirst / d;
711                         d1y = (c2.scrCoords[2] - c1.scrCoords[2]) * a.offFirst / d;
712                     } else {
713                         a.showFirst = 0;
714                     }
715                 }
716 
717                 if (a.evLast &&
718                     el.board.renderer.type !== 'vml') {
719                     if (d >= a.minLen) {
720                         d2x = (c2.scrCoords[1] - c1.scrCoords[1]) * a.offLast / d;
721                         d2y = (c2.scrCoords[2] - c1.scrCoords[2]) * a.offLast / d;
722                     } else {
723                         a.showLast = 0;
724                     }
725                 }
726                 c1.setCoordinates(Const.COORDS_BY_SCREEN, [c1.scrCoords[1] + d1x, c1.scrCoords[2] + d1y], false, true);
727                 c2.setCoordinates(Const.COORDS_BY_SCREEN, [c2.scrCoords[1] - d2x, c2.scrCoords[2] - d2y], false, true);
728             }
729 
730             return this;
731         },
732 
733         /**
734          * Handle touchlastpoint / touchfirstpoint
735          *
736          * @param {JXG.GeometryElement} el
737          * @param {JXG.Coords} c1 Coordinates of the start of the line. The coordinates are changed in place.
738          * @param {JXG.Coords} c2 Coordinates of the end of the line. The coordinates are changed in place.
739          * @param {Object} a
740          */
741         handleTouchpoints: function(el, c1, c2, a) {
742             var s1, s2, d,
743                 d1x, d1y, d2x, d2y;
744 
745             if (a.evFirst || a.evLast) {
746                 d = d1x = d1y = d2x = d2y = 0.0;
747 
748                 s1 = Type.evaluate(el.point1.visProp.size) + Type.evaluate(el.point1.visProp.strokewidth);
749                 s2 = Type.evaluate(el.point2.visProp.size) + Type.evaluate(el.point2.visProp.strokewidth);
750 
751                 // Handle touchlastpoint /touchfirstpoint
752                 if (a.evFirst && Type.evaluate(el.visProp.touchfirstpoint)) {
753                     d = c1.distance(Const.COORDS_BY_SCREEN, c2);
754                     //if (d > s) {
755                         d1x = (c2.scrCoords[1] - c1.scrCoords[1]) * s1 / d;
756                         d1y = (c2.scrCoords[2] - c1.scrCoords[2]) * s1 / d;
757                     //}
758                 }
759                 if (a.evLast && Type.evaluate(el.visProp.touchlastpoint)) {
760                     d = c1.distance(Const.COORDS_BY_SCREEN, c2);
761                     //if (d > s) {
762                         d2x = (c2.scrCoords[1] - c1.scrCoords[1]) * s2 / d;
763                         d2y = (c2.scrCoords[2] - c1.scrCoords[2]) * s2 / d;
764                     //}
765                 }
766                 c1.setCoordinates(Const.COORDS_BY_SCREEN, [c1.scrCoords[1] + d1x, c1.scrCoords[2] + d1y], false, true);
767                 c2.setCoordinates(Const.COORDS_BY_SCREEN, [c2.scrCoords[1] - d2x, c2.scrCoords[2] - d2y], false, true);
768             }
769 
770             return this;
771         },
772 
773         /**
774          * Set the arrow head size.
775          *
776          * @param {JXG.GeometryElement} el Reference to a line or curve object that has to be drawn.
777          * @param {Object} arrowData Data concerning possible arrow heads
778          * @returns {JXG.AbstractRenderer} Reference to the renderer
779          *
780          * @private
781          * @see Line
782          * @see JXG.Line
783          * @see Curve
784          * @see JXG.Curve
785          * @see JXG.AbstractRenderer#updatePathWithArrowHeads
786          * @see JXG.AbstractRenderer#getArrowHeadData
787          */
788         setArrowSize: function(el, a) {
789             if (a.evFirst) {
790                 this._setArrowWidth(el.rendNodeTriangleStart, a.showFirst * a.strokeWidth, el.rendNode, a.sizeFirst);
791             }
792             if (a.evLast) {
793                 this._setArrowWidth(el.rendNodeTriangleEnd, a.showLast * a.strokeWidth, el.rendNode, a.sizeLast);
794             }
795             return this;
796         },
797 
798         /**
799          * Update the line endings (linecap) of a straight line from its attribute
800          * 'linecap'.
801          * Possible values for the attribute 'linecap' are: 'butt', 'round', 'square'.
802          * The default value is 'butt'. Not available for VML renderer.
803          *
804          * @param {JXG.Line} element A arbitrary line.
805          * @see Line
806          * @see JXG.Line
807          * @see JXG.AbstractRenderer#updateLine
808          */
809         setLineCap: function(el) { /* stub */ },
810 
811         /* **************************
812          *    Ticks related stuff
813          * **************************/
814 
815         /**
816          * Creates a rendering node for ticks added to a line.
817          * @param {JXG.Line} el A arbitrary line.
818          * @see Line
819          * @see Ticks
820          * @see JXG.Line
821          * @see JXG.Ticks
822          * @see JXG.AbstractRenderer#updateTicks
823          */
824         drawTicks: function (el) {
825             el.rendNode = this.appendChildPrim(this.createPrim('path', el.id), Type.evaluate(el.visProp.layer));
826             this.appendNodesToElement(el, 'path');
827         },
828 
829         /**
830          * Update {@link Ticks} on a {@link JXG.Line}. This method is only a stub and has to be implemented
831          * in any descendant renderer class.
832          * @param {JXG.Ticks} element Reference of a ticks object that has to be updated.
833          * @see Line
834          * @see Ticks
835          * @see JXG.Line
836          * @see JXG.Ticks
837          * @see JXG.AbstractRenderer#drawTicks
838          */
839         updateTicks: function (element) { /* stub */ },
840 
841         /* **************************
842          *    Circle related stuff
843          * **************************/
844 
845         /**
846          * Draws a {@link JXG.Circle}
847          * @param {JXG.Circle} el Reference to a {@link JXG.Circle} object that has to be drawn.
848          * @see Circle
849          * @see JXG.Circle
850          * @see JXG.AbstractRenderer#updateEllipse
851          */
852         drawEllipse: function (el) {
853             el.rendNode = this.appendChildPrim(this.createPrim('ellipse', el.id),
854                                     Type.evaluate(el.visProp.layer));
855             this.appendNodesToElement(el, 'ellipse');
856             this.updateEllipse(el);
857         },
858 
859         /**
860          * Updates visual appearance of a given {@link JXG.Circle} on the {@link JXG.Board}.
861          * @param {JXG.Circle} el Reference to a {@link JXG.Circle} object, that has to be updated.
862          * @see Circle
863          * @see JXG.Circle
864          * @see JXG.AbstractRenderer#drawEllipse
865          */
866         updateEllipse: function (el) {
867             this._updateVisual(el);
868 
869             var radius = el.Radius();
870 
871             if (radius > 0.0 &&
872                     Math.abs(el.center.coords.usrCoords[0]) > Mat.eps &&
873                     !isNaN(radius + el.center.coords.scrCoords[1] + el.center.coords.scrCoords[2]) &&
874                     radius * el.board.unitX < 2000000) {
875                 this.updateEllipsePrim(el.rendNode, el.center.coords.scrCoords[1],
876                     el.center.coords.scrCoords[2],
877                     (radius * el.board.unitX),
878                     (radius * el.board.unitY));
879             }
880         },
881 
882         /* **************************
883          *   Polygon related stuff
884          * **************************/
885 
886         /**
887          * Draws a {@link JXG.Polygon} on the {@link JXG.Board}.
888          * @param {JXG.Polygon} el Reference to a Polygon object, that is to be drawn.
889          * @see Polygon
890          * @see JXG.Polygon
891          * @see JXG.AbstractRenderer#updatePolygon
892          */
893         drawPolygon: function (el) {
894             el.rendNode = this.appendChildPrim(this.createPrim('polygon', el.id),
895                                         Type.evaluate(el.visProp.layer));
896             this.appendNodesToElement(el, 'polygon');
897             this.updatePolygon(el);
898         },
899 
900         /**
901          * Updates properties of a {@link JXG.Polygon}'s rendering node.
902          * @param {JXG.Polygon} el Reference to a {@link JXG.Polygon} object, that has to be updated.
903          * @see Polygon
904          * @see JXG.Polygon
905          * @see JXG.AbstractRenderer#drawPolygon
906          */
907         updatePolygon: function (el) {
908             // Here originally strokecolor wasn't updated but strokewidth was.
909             // But if there's no strokecolor i don't see why we should update strokewidth.
910             this._updateVisual(el, {stroke: true, dash: true});
911             this.updatePolygonPrim(el.rendNode, el);
912         },
913 
914         /* **************************
915          *    Text related stuff
916          * **************************/
917 
918         /**
919          * Shows a small copyright notice in the top left corner of the board.
920          * @param {String} str The copyright notice itself
921          * @param {Number} fontsize Size of the font the copyright notice is written in
922          */
923         displayCopyright: function (str, fontsize) { /* stub */ },
924 
925         /**
926          * An internal text is a {@link JXG.Text} element which is drawn using only
927          * the given renderer but no HTML. This method is only a stub, the drawing
928          * is done in the special renderers.
929          * @param {JXG.Text} element Reference to a {@link JXG.Text} object
930          * @see Text
931          * @see JXG.Text
932          * @see JXG.AbstractRenderer#updateInternalText
933          * @see JXG.AbstractRenderer#drawText
934          * @see JXG.AbstractRenderer#updateText
935          * @see JXG.AbstractRenderer#updateTextStyle
936          */
937         drawInternalText: function (element) { /* stub */ },
938 
939         /**
940          * Updates visual properties of an already existing {@link JXG.Text} element.
941          * @param {JXG.Text} element Reference to an {@link JXG.Text} object, that has to be updated.
942          * @see Text
943          * @see JXG.Text
944          * @see JXG.AbstractRenderer#drawInternalText
945          * @see JXG.AbstractRenderer#drawText
946          * @see JXG.AbstractRenderer#updateText
947          * @see JXG.AbstractRenderer#updateTextStyle
948          */
949         updateInternalText: function (element) { /* stub */ },
950 
951         /**
952          * Displays a {@link JXG.Text} on the {@link JXG.Board} by putting a HTML div over it.
953          * @param {JXG.Text} el Reference to an {@link JXG.Text} object, that has to be displayed
954          * @see Text
955          * @see JXG.Text
956          * @see JXG.AbstractRenderer#drawInternalText
957          * @see JXG.AbstractRenderer#updateText
958          * @see JXG.AbstractRenderer#updateInternalText
959          * @see JXG.AbstractRenderer#updateTextStyle
960          */
961         drawText: function (el) {
962             var node, z, level,
963                 ev_visible;
964 
965             if (Type.evaluate(el.visProp.display) === 'html' && Env.isBrowser && this.type !== 'no') {
966                 node = this.container.ownerDocument.createElement('div');
967                 //node = this.container.ownerDocument.createElementNS('http://www.w3.org/1999/xhtml', 'div'); //
968                 node.style.position = 'absolute';
969                 node.className = Type.evaluate(el.visProp.cssclass);
970 
971                 level = Type.evaluate(el.visProp.layer);
972                 if (!Type.exists(level)) { // trace nodes have level not set
973                     level = 0;
974                 }
975 
976                 if (this.container.style.zIndex === '') {
977                     z = 0;
978                 } else {
979                     z = parseInt(this.container.style.zIndex, 10);
980                 }
981 
982                 node.style.zIndex = z + level;
983                 this.container.appendChild(node);
984 
985                 node.setAttribute('id', this.container.id + '_' + el.id);
986             } else {
987                 node = this.drawInternalText(el);
988             }
989 
990             el.rendNode = node;
991             el.htmlStr = '';
992 
993             // Set el.visPropCalc.visible
994             if (el.visProp.islabel && Type.exists(el.visProp.anchor)) {
995                 ev_visible = Type.evaluate(el.visProp.anchor.visProp.visible);
996                 el.prepareUpdate().updateVisibility(ev_visible);
997             } else {
998                 el.prepareUpdate().updateVisibility();
999             }
1000             this.updateText(el);
1001         },
1002 
1003         /**
1004          * Updates visual properties of an already existing {@link JXG.Text} element.
1005          * @param {JXG.Text} el Reference to an {@link JXG.Text} object, that has to be updated.
1006          * @see Text
1007          * @see JXG.Text
1008          * @see JXG.AbstractRenderer#drawText
1009          * @see JXG.AbstractRenderer#drawInternalText
1010          * @see JXG.AbstractRenderer#updateInternalText
1011          * @see JXG.AbstractRenderer#updateTextStyle
1012          */
1013         updateText: function (el) {
1014             var content = el.plaintext,
1015                 v, c,
1016                 parentNode,
1017                 scale, vshift, id, wrap_id,
1018                 ax, ay;
1019 
1020             if (el.visPropCalc.visible) {
1021                 this.updateTextStyle(el, false);
1022 
1023                 if (Type.evaluate(el.visProp.display) === 'html' && this.type !== 'no') {
1024                     // Set the position
1025                     if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) {
1026 
1027                         // Horizontal
1028                         c = el.coords.scrCoords[1];
1029                         // webkit seems to fail for extremely large values for c.
1030                         c = Math.abs(c) < 1000000 ? c : 1000000;
1031                         ax = el.getAnchorX();
1032 
1033                         if (ax === 'right') {
1034                             // v = Math.floor(el.board.canvasWidth - c);
1035                             v = el.board.canvasWidth - c;
1036                         } else if (ax === 'middle') {
1037                             // v = Math.floor(c - 0.5 * el.size[0]);
1038                             v = c - 0.5 * el.size[0];
1039                         } else { // 'left'
1040                             // v = Math.floor(c);
1041                             v = c;
1042                         }
1043 
1044                         // This may be useful for foreignObj.
1045                         //if (window.devicePixelRatio !== undefined) {
1046                         //v *= window.devicePixelRatio;
1047                         //}
1048 
1049                         if (el.visPropOld.left !== (ax + v)) {
1050                             if (ax === 'right') {
1051                                 el.rendNode.style.right = v + 'px';
1052                                 el.rendNode.style.left = 'auto';
1053                             } else {
1054                                 el.rendNode.style.left = v + 'px';
1055                                 el.rendNode.style.right = 'auto';
1056                             }
1057                             el.visPropOld.left = ax + v;
1058                         }
1059 
1060                         // Vertical
1061                         c = el.coords.scrCoords[2] + this.vOffsetText;
1062                         c = Math.abs(c) < 1000000 ? c : 1000000;
1063                         ay = el.getAnchorY();
1064 
1065                         if (ay === 'bottom') {
1066                             // v = Math.floor(el.board.canvasHeight - c);
1067                             v = el.board.canvasHeight - c;
1068                         } else if (ay === 'middle') {
1069                             // v = Math.floor(c - 0.5 * el.size[1]);
1070                             v = c - 0.5 * el.size[1];
1071                         } else { // top
1072                             // v = Math.floor(c);
1073                             v = c;
1074                         }
1075 
1076                         // This may be useful for foreignObj.
1077                         //if (window.devicePixelRatio !== undefined) {
1078                         //v *= window.devicePixelRatio;
1079                         //}
1080 
1081                         if (el.visPropOld.top !== (ay + v)) {
1082                             if (ay === 'bottom') {
1083                                 el.rendNode.style.top = 'auto';
1084                                 el.rendNode.style.bottom = v + 'px';
1085                             } else {
1086                                 el.rendNode.style.bottom = 'auto';
1087                                 el.rendNode.style.top = v + 'px';
1088                             }
1089                             el.visPropOld.top = ay + v;
1090                         }
1091                     }
1092 
1093                     // Set the content
1094                     if (el.htmlStr !== content) {
1095                         try {
1096                             if (el.type === Type.OBJECT_TYPE_BUTTON) {
1097                                 el.rendNodeButton.innerHTML = content;
1098                             } else if (el.type === Type.OBJECT_TYPE_CHECKBOX ||
1099                                 el.type === Type.OBJECT_TYPE_INPUT) {
1100                                 el.rendNodeLabel.innerHTML = content;
1101                             } else {
1102                                 el.rendNode.innerHTML = content;
1103                             }
1104                         } catch (e) {
1105                             // Setting innerHTML sometimes fails in IE8.
1106                             // A workaround is to take the node off the DOM, assign innerHTML,
1107                             // then append back.
1108                             // Works for text elements as they are absolutely positioned.
1109                             parentNode = el.rendNode.parentNode;
1110                             el.rendNode.parentNode.removeChild(el.rendNode);
1111                             el.rendNode.innerHTML = content;
1112                             parentNode.appendChild(el.rendNode);
1113                         }
1114                         el.htmlStr = content;
1115 
1116                         if (Type.evaluate(el.visProp.usemathjax)) {
1117                             // Typesetting directly might not work because mathjax was not loaded completely
1118                             // see http://www.mathjax.org/docs/1.1/typeset.html
1119                             try {
1120                                 if (MathJax.typeset) {
1121                                     // Version 3
1122                                     MathJax.typeset([el.rendNode]);
1123                                 } else {
1124                                     // Version 2
1125                                     MathJax.Hub.Queue(['Typeset', MathJax.Hub, el.rendNode]);
1126                                 }
1127 
1128                                 // Restore the transformation necessary for fullscreen mode
1129                                 // MathJax removes it when handling dynamic content
1130                                 id = el.board.container;
1131                                 wrap_id = 'fullscreenwrap_' + id;
1132                                 if (document.getElementById(wrap_id)) {
1133                                     scale = el.board.containerObj._cssFullscreenStore.scale;
1134                                     vshift = el.board.containerObj._cssFullscreenStore.vshift;
1135                                     Env.scaleJSXGraphDiv('#' + wrap_id, '#' + id, scale, vshift);
1136                                 }
1137 
1138                             } catch (e) {
1139                                 JXG.debug('MathJax (not yet) loaded');
1140                             }
1141                         } else if (Type.evaluate(el.visProp.usekatex)) {
1142                             try {
1143                                 /* eslint-disable no-undef */
1144                                 katex.render(content, el.rendNode, {
1145                                     throwOnError: false
1146                                 });
1147                                 /* eslint-enable no-undef */
1148                             } catch (e) {
1149                                 JXG.debug('KaTeX (not yet) loaded');
1150                             }
1151                     } else if (Type.evaluate(el.visProp.useasciimathml)) {
1152                             // This is not a constructor.
1153                             // See http://www1.chapman.edu/~jipsen/mathml/asciimath.html for more information
1154                             // about AsciiMathML and the project's source code.
1155                             try {
1156                                 AMprocessNode(el.rendNode, false);
1157                             } catch (e) {
1158                                 JXG.debug('AsciiMathML (not yet) loaded');
1159                             }
1160                         }
1161                     }
1162                     this.transformImage(el, el.transformations);
1163                 } else {
1164                     this.updateInternalText(el);
1165                 }
1166             }
1167         },
1168 
1169         /**
1170          * Converts string containing CSS properties into
1171          * array with key-value pair objects.
1172          *
1173          * @example
1174          * "color:blue; background-color:yellow" is converted to
1175          * [{'color': 'blue'}, {'backgroundColor': 'yellow'}]
1176          *
1177          * @param  {String} cssString String containing CSS properties
1178          * @return {Array}           Array of CSS key-value pairs
1179          */
1180         _css2js: function(cssString) {
1181             var pairs = [],
1182                 i, len, key, val, s,
1183                 list = Type.trim(cssString).replace(/;$/, '').split(";");
1184 
1185             len = list.length;
1186             for (i = 0; i < len; ++i) {
1187                 if (Type.trim(list[i]) !== '') {
1188                     s = list[i].split(':');
1189                     key = Type.trim(s[0].replace(/-([a-z])/gi, function(match, char) { return char.toUpperCase(); }));
1190                     val = Type.trim(s[1]);
1191                     pairs.push({'key': key, 'val': val});
1192                 }
1193             }
1194             return pairs;
1195 
1196         },
1197 
1198         /**
1199          * Updates font-size, color and opacity propertiey and CSS style properties of a {@link JXG.Text} node.
1200          * This function is also called by highlight() and nohighlight().
1201          * @param {JXG.Text} el Reference to the {@link JXG.Text} object, that has to be updated.
1202          * @param {Boolean} doHighlight
1203          * @see Text
1204          * @see JXG.Text
1205          * @see JXG.AbstractRenderer#drawText
1206          * @see JXG.AbstractRenderer#drawInternalText
1207          * @see JXG.AbstractRenderer#updateText
1208          * @see JXG.AbstractRenderer#updateInternalText
1209          * @see JXG.AbstractRenderer#updateInternalTextStyle
1210          */
1211         updateTextStyle: function (el, doHighlight) {
1212             var fs, so, sc, css, node,
1213                 ev = el.visProp,
1214                 display = Env.isBrowser ? ev.display : 'internal',
1215                 nodeList = ['rendNode', 'rendNodeTag', 'rendNodeLabel'],
1216                 lenN = nodeList.length,
1217                 fontUnit = Type.evaluate(ev.fontunit),
1218                 cssList, prop, style, cssString,
1219                 styleList = ['cssdefaultstyle', 'cssstyle'],
1220                 lenS = styleList.length;
1221 
1222             if (doHighlight) {
1223                 sc = ev.highlightstrokecolor;
1224                 so = ev.highlightstrokeopacity;
1225                 css = ev.highlightcssclass;
1226             } else {
1227                 sc = ev.strokecolor;
1228                 so = ev.strokeopacity;
1229                 css = ev.cssclass;
1230             }
1231 
1232             // This part is executed for all text elements except internal texts in canvas.
1233             // HTML-texts or internal texts in SVG or VML.
1234             //            HTML    internal
1235             //  SVG        +         +
1236             //  VML        +         +
1237             //  canvas     +         -
1238             //  no         -         -
1239             if ((this.type !== 'no') &&
1240                 (display === 'html' || this.type !== 'canvas')
1241                ) {
1242                 for (style = 0; style < lenS; style++) {
1243                     // First set cssString to
1244                     // ev.cssdefaultstyle of ev.highlightcssdefaultstyle,
1245                     // then to
1246                     // ev.cssstyle of ev.highlightcssstyle
1247                     cssString = Type.evaluate(ev[(doHighlight ? 'highlight' : '') + styleList[style]]);
1248                     if (cssString !== '' &&
1249                         el.visPropOld[styleList[style]] !== cssString) {
1250                         cssList = this._css2js(cssString);
1251                         for (node = 0; node < lenN; node++) {
1252                             if (Type.exists(el[nodeList[node]])) {
1253                                 for (prop in cssList) {
1254                                     if (cssList.hasOwnProperty(prop)) {
1255                                         el[nodeList[node]].style[cssList[prop].key] = cssList[prop].val;
1256                                     }
1257                                 }
1258                             }
1259                         }
1260                         el.visPropOld[styleList[style]] = cssString;
1261                     }
1262                 }
1263 
1264                 fs = Type.evaluate(ev.fontsize);
1265                 if (el.visPropOld.fontsize !== fs) {
1266                     el.needsSizeUpdate = true;
1267                     try {
1268                         for (node = 0; node < lenN; node++) {
1269                             if (Type.exists(el[nodeList[node]])) {
1270                                 el[nodeList[node]].style.fontSize = fs + fontUnit;
1271                             }
1272                         }
1273                     } catch (e) {
1274                         // IE needs special treatment.
1275                         for (node = 0; node < lenN; node++) {
1276                             if (Type.exists(el[nodeList[node]])) {
1277                                 el[nodeList[node]].style.fontSize = fs;
1278                             }
1279                         }
1280                     }
1281                     el.visPropOld.fontsize = fs;
1282                 }
1283             }
1284 
1285             this.setObjectTransition(el);
1286             if (display === 'html' && this.type !== 'no') {
1287                 // Set new CSS class
1288                 if (el.visPropOld.cssclass !== css) {
1289                     el.rendNode.className = css;
1290                     el.visPropOld.cssclass = css;
1291                     el.needsSizeUpdate = true;
1292                 }
1293                 this.setObjectStrokeColor(el, sc, so);
1294             } else {
1295                 this.updateInternalTextStyle(el, sc, so);
1296             }
1297 
1298             return this;
1299         },
1300 
1301         /**
1302          * Set color and opacity of internal texts.
1303          * This method is used for Canvas and VML.
1304          * SVG needs its own version.
1305          * @private
1306          * @see JXG.AbstractRenderer#updateTextStyle
1307          * @see JXG.SVGRenderer#updateInternalTextStyle
1308          */
1309         updateInternalTextStyle: function (el, strokeColor, strokeOpacity) {
1310             this.setObjectStrokeColor(el, strokeColor, strokeOpacity);
1311         },
1312 
1313         /* **************************
1314          *    Image related stuff
1315          * **************************/
1316 
1317         /**
1318          * Draws an {@link JXG.Image} on a board; This is just a template that has to be implemented by special
1319          * renderers.
1320          * @param {JXG.Image} element Reference to the image object that is to be drawn
1321          * @see Image
1322          * @see JXG.Image
1323          * @see JXG.AbstractRenderer#updateImage
1324          */
1325         drawImage: function (element) { /* stub */ },
1326 
1327         /**
1328          * Updates the properties of an {@link JXG.Image} element.
1329          * @param {JXG.Image} el Reference to an {@link JXG.Image} object, that has to be updated.
1330          * @see Image
1331          * @see JXG.Image
1332          * @see JXG.AbstractRenderer#drawImage
1333          */
1334         updateImage: function (el) {
1335             this.updateRectPrim(el.rendNode, el.coords.scrCoords[1],
1336                 el.coords.scrCoords[2] - el.size[1], el.size[0], el.size[1]);
1337 
1338             this.updateImageURL(el);
1339             this.transformImage(el, el.transformations);
1340             this._updateVisual(el, {stroke: true, dash: true}, true);
1341         },
1342 
1343         /**
1344          * Multiplication of transformations without updating. That means, at that point it is expected that the
1345          * matrices contain numbers only. First, the origin in user coords is translated to <tt>(0,0)</tt> in screen
1346          * coords. Then, the stretch factors are divided out. After the transformations in user coords, the stretch
1347          * factors are multiplied in again, and the origin in user coords is translated back to its position. This
1348          * method does not have to be implemented in a new renderer.
1349          * @param {JXG.GeometryElement} el A JSXGraph element. We only need its board property.
1350          * @param {Array} transformations An array of JXG.Transformations.
1351          * @returns {Array} A matrix represented by a two dimensional array of numbers.
1352          * @see JXG.AbstractRenderer#transformImage
1353          */
1354         joinTransforms: function (el, transformations) {
1355             var i,
1356                 ox = el.board.origin.scrCoords[1],
1357                 oy = el.board.origin.scrCoords[2],
1358                 ux = el.board.unitX,
1359                 uy = el.board.unitY,
1360                 // Translate to 0,0 in screen coords
1361                 /*
1362                 m = [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
1363                 mpre1 =  [[1,   0, 0],
1364                     [-ox, 1, 0],
1365                     [-oy, 0, 1]],
1366                 // Scale
1367                 mpre2 =  [[1, 0,     0],
1368                     [0, 1 / ux,  0],
1369                     [0, 0, -1 / uy]],
1370                 // Scale back
1371                 mpost2 = [[1, 0,   0],
1372                     [0, ux,  0],
1373                     [0, 0, -uy]],
1374                 // Translate back
1375                 mpost1 = [[1,  0, 0],
1376                     [ox, 1, 0],
1377                     [oy, 0, 1]],
1378                 */
1379                 len = transformations.length,
1380                 // Translate to 0,0 in screen coords and then scale
1381                 m = [[1,        0,       0],
1382                      [-ox / ux, 1 / ux,  0],
1383                      [ oy / uy, 0, -1 / uy]];
1384 
1385             for (i = 0; i < len; i++) {
1386                 //m = Mat.matMatMult(mpre1, m);
1387                 //m = Mat.matMatMult(mpre2, m);
1388                 m = Mat.matMatMult(transformations[i].matrix, m);
1389                 //m = Mat.matMatMult(mpost2, m);
1390                 //m = Mat.matMatMult(mpost1, m);
1391             }
1392             // Scale back and then translate back
1393             m = Mat.matMatMult([[1,   0, 0],
1394                                 [ox, ux, 0],
1395                                 [oy,  0, -uy]], m);
1396             return m;
1397         },
1398 
1399         /**
1400          * Applies transformations on images and text elements. This method is just a stub and has to be implemented in
1401          * all descendant classes where text and image transformations are to be supported.
1402          * @param {JXG.Image|JXG.Text} element A {@link JXG.Image} or {@link JXG.Text} object.
1403          * @param {Array} transformations An array of {@link JXG.Transformation} objects. This is usually the
1404          * transformations property of the given element <tt>el</tt>.
1405          */
1406         transformImage: function (element, transformations) { /* stub */ },
1407 
1408         /**
1409          * If the URL of the image is provided by a function the URL has to be updated during updateImage()
1410          * @param {JXG.Image} element Reference to an image object.
1411          * @see JXG.AbstractRenderer#updateImage
1412          */
1413         updateImageURL: function (element) { /* stub */ },
1414 
1415         /**
1416          * Updates CSS style properties of a {@link JXG.Image} node.
1417          * In SVGRenderer opacity is the only available style element.
1418          * This function is called by highlight() and nohighlight().
1419          * This function works for VML.
1420          * It does not work for Canvas.
1421          * SVGRenderer overwrites this method.
1422          * @param {JXG.Text} el Reference to the {@link JXG.Image} object, that has to be updated.
1423          * @param {Boolean} doHighlight
1424          * @see Image
1425          * @see JXG.Image
1426          * @see JXG.AbstractRenderer#highlight
1427          * @see JXG.AbstractRenderer#noHighlight
1428          */
1429         updateImageStyle: function (el, doHighlight) {
1430             el.rendNode.className = Type.evaluate(doHighlight ? el.visProp.highlightcssclass : el.visProp.cssclass);
1431         },
1432 
1433         drawForeignObject: function (el) { /* stub */ },
1434 
1435         updateForeignObject: function(el) { /* stub */ },
1436 
1437         /* **************************
1438          * Render primitive objects
1439          * **************************/
1440 
1441         /**
1442          * Appends a node to a specific layer level. This is just an abstract method and has to be implemented
1443          * in all renderers that want to use the <tt>createPrim</tt> model to draw.
1444          * @param {Node} node A DOM tree node.
1445          * @param {Number} level The layer the node is attached to. This is the index of the layer in
1446          * {@link JXG.SVGRenderer#layer} or the <tt>z-index</tt> style property of the node in VMLRenderer.
1447          */
1448         appendChildPrim: function (node, level) { /* stub */ },
1449 
1450         /**
1451          * Stores the rendering nodes. This is an abstract method which has to be implemented in all renderers that use
1452          * the <tt>createPrim</tt> method.
1453          * @param {JXG.GeometryElement} element A JSXGraph element.
1454          * @param {String} type The XML node name. Only used in VMLRenderer.
1455          */
1456         appendNodesToElement: function (element, type) { /* stub */ },
1457 
1458         /**
1459          * Creates a node of a given type with a given id.
1460          * @param {String} type The type of the node to create.
1461          * @param {String} id Set the id attribute to this.
1462          * @returns {Node} Reference to the created node.
1463          */
1464         createPrim: function (type, id) {
1465             /* stub */
1466             return null;
1467         },
1468 
1469         /**
1470          * Removes an element node. Just a stub.
1471          * @param {Node} node The node to remove.
1472          */
1473         remove: function (node) { /* stub */ },
1474 
1475         /**
1476          * Can be used to create the nodes to display arrows. This is an abstract method which has to be implemented
1477          * in any descendant renderer.
1478          * @param {JXG.GeometryElement} element The element the arrows are to be attached to.
1479          * @param {Object} arrowData Data concerning possible arrow heads
1480          *
1481          */
1482         makeArrows: function (element, arrowData) { /* stub */ },
1483 
1484         /**
1485          * Updates width of an arrow DOM node. Used in
1486          * @param {Node} node The arrow node.
1487          * @param {Number} width
1488          * @param {Node} parentNode Used in IE only
1489          */
1490         _setArrowWidth: function(node, width, parentNode) { /* stub */},
1491 
1492         /**
1493          * Updates an ellipse node primitive. This is an abstract method which has to be implemented in all renderers
1494          * that use the <tt>createPrim</tt> method.
1495          * @param {Node} node Reference to the node.
1496          * @param {Number} x Centre X coordinate
1497          * @param {Number} y Centre Y coordinate
1498          * @param {Number} rx The x-axis radius.
1499          * @param {Number} ry The y-axis radius.
1500          */
1501         updateEllipsePrim: function (node, x, y, rx, ry) { /* stub */ },
1502 
1503         /**
1504          * Refreshes a line node. This is an abstract method which has to be implemented in all renderers that use
1505          * the <tt>createPrim</tt> method.
1506          * @param {Node} node The node to be refreshed.
1507          * @param {Number} p1x The first point's x coordinate.
1508          * @param {Number} p1y The first point's y coordinate.
1509          * @param {Number} p2x The second point's x coordinate.
1510          * @param {Number} p2y The second point's y coordinate.
1511          * @param {JXG.Board} board
1512          */
1513         updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) { /* stub */ },
1514 
1515         /**
1516          * Updates a path element. This is an abstract method which has to be implemented in all renderers that use
1517          * the <tt>createPrim</tt> method.
1518          * @param {Node} node The path node.
1519          * @param {String} pathString A string formatted like e.g. <em>'M 1,2 L 3,1 L5,5'</em>. The format of the string
1520          * depends on the rendering engine.
1521          * @param {JXG.Board} board Reference to the element's board.
1522          */
1523         updatePathPrim: function (node, pathString, board) { /* stub */ },
1524 
1525         /**
1526          * Builds a path data string to draw a point with a face other than <em>rect</em> and <em>circle</em>. Since
1527          * the format of such a string usually depends on the renderer this method
1528          * is only an abstract method. Therefore, it has to be implemented in the descendant renderer itself unless
1529          * the renderer does not use the createPrim interface but the draw* interfaces to paint.
1530          * @param {JXG.Point} element The point element
1531          * @param {Number} size A positive number describing the size. Usually the half of the width and height of
1532          * the drawn point.
1533          * @param {String} type A string describing the point's face. This method only accepts the shortcut version of
1534          * each possible face: <tt>x, +, <>, ^, v, >, < </tt>
1535          */
1536         updatePathStringPoint: function (element, size, type) { /* stub */ },
1537 
1538         /**
1539          * Builds a path data string from a {@link JXG.Curve} element. Since the path data strings heavily depend on the
1540          * underlying rendering technique this method is just a stub. Although such a path string is of no use for the
1541          * CanvasRenderer, this method is used there to draw a path directly.
1542          * @param element
1543          */
1544         updatePathStringPrim: function (element) { /* stub */ },
1545 
1546         /**
1547          * Builds a path data string from a {@link JXG.Curve} element such that the curve looks like hand drawn. Since
1548          * the path data strings heavily depend on the underlying rendering technique this method is just a stub.
1549          * Although such a path string is of no use for the CanvasRenderer, this method is used there to draw a path
1550          * directly.
1551          * @param element
1552          */
1553         updatePathStringBezierPrim: function (element) { /* stub */ },
1554 
1555 
1556         /**
1557          * Update a polygon primitive.
1558          * @param {Node} node
1559          * @param {JXG.Polygon} element A JSXGraph element of type {@link JXG.Polygon}
1560          */
1561         updatePolygonPrim: function (node, element) { /* stub */ },
1562 
1563         /**
1564          * Update a rectangle primitive. This is used only for points with face of type 'rect'.
1565          * @param {Node} node The node yearning to be updated.
1566          * @param {Number} x x coordinate of the top left vertex.
1567          * @param {Number} y y coordinate of the top left vertex.
1568          * @param {Number} w Width of the rectangle.
1569          * @param {Number} h The rectangle's height.
1570          */
1571         updateRectPrim: function (node, x, y, w, h) { /* stub */ },
1572 
1573         /* **************************
1574          *  Set Attributes
1575          * **************************/
1576 
1577         /**
1578          * Sets a node's attribute.
1579          * @param {Node} node The node that is to be updated.
1580          * @param {String} key Name of the attribute.
1581          * @param {String} val New value for the attribute.
1582          */
1583         setPropertyPrim: function (node, key, val) { /* stub */ },
1584 
1585         setTabindex: function(element) {
1586             var val;
1587             if (element.board.attr.keyboard.enabled && Type.exists(element.rendNode)) {
1588                 val = Type.evaluate(element.visProp.tabindex);
1589                 if (!element.visPropCalc.visible || Type.evaluate(element.visProp.fixed)) {
1590                     val = null;
1591                 }
1592                 if (val !== element.visPropOld.tabindex) {
1593                     element.rendNode.setAttribute('tabindex', val);
1594                     element.visPropOld.tabindex = val;
1595                 }
1596             }
1597         },
1598 
1599         /**
1600          * Shows or hides an element on the canvas; Only a stub, requires implementation in the derived renderer.
1601          * @param {JXG.GeometryElement} element Reference to the object that has to appear.
1602          * @param {Boolean} value true to show the element, false to hide the element.
1603          */
1604         display: function (element, value) {
1605             if (element) {
1606                 element.visPropOld.visible = value;
1607             }
1608         },
1609 
1610         /**
1611          * Shows a hidden element on the canvas; Only a stub, requires implementation in the derived renderer.
1612          *
1613          * Please use JXG.AbstractRenderer#display instead
1614          * @param {JXG.GeometryElement} element Reference to the object that has to appear.
1615          * @see JXG.AbstractRenderer#hide
1616          * @deprecated
1617          */
1618         show: function (element) { /* stub */ },
1619 
1620         /**
1621          * Hides an element on the canvas; Only a stub, requires implementation in the derived renderer.
1622          *
1623          * Please use JXG.AbstractRenderer#display instead
1624          * @param {JXG.GeometryElement} element Reference to the geometry element that has to disappear.
1625          * @see JXG.AbstractRenderer#show
1626          * @deprecated
1627          */
1628         hide: function (element) { /* stub */ },
1629 
1630         /**
1631          * Sets the buffering as recommended by SVGWG. Until now only Opera supports this and will be ignored by other
1632          * browsers. Although this feature is only supported by SVG we have this method in {@link JXG.AbstractRenderer}
1633          * because it is called from outside the renderer.
1634          * @param {Node} node The SVG DOM Node which buffering type to update.
1635          * @param {String} type Either 'auto', 'dynamic', or 'static'. For an explanation see
1636          *   {@link http://www.w3.org/TR/SVGTiny12/painting.html#BufferedRenderingProperty}.
1637          */
1638         setBuffering: function (node, type) { /* stub */ },
1639 
1640         /**
1641          * Sets an element's dash style.
1642          * @param {JXG.GeometryElement} element An JSXGraph element.
1643          */
1644         setDashStyle: function (element) { /* stub */ },
1645 
1646         /**
1647          * Puts an object into draft mode, i.e. it's visual appearance will be changed. For GEONE<sub>x</sub>T backwards
1648          * compatibility.
1649          * @param {JXG.GeometryElement} el Reference of the object that is in draft mode.
1650          */
1651         setDraft: function (el) {
1652             if (!Type.evaluate(el.visProp.draft)) {
1653                 return;
1654             }
1655             var draftColor = el.board.options.elements.draft.color,
1656                 draftOpacity = el.board.options.elements.draft.opacity;
1657 
1658             this.setObjectTransition(el);
1659             if (el.type === Const.OBJECT_TYPE_POLYGON) {
1660                 this.setObjectFillColor(el, draftColor, draftOpacity);
1661             } else {
1662                 if (el.elementClass === Const.OBJECT_CLASS_POINT) {
1663                     this.setObjectFillColor(el, draftColor, draftOpacity);
1664                 } else {
1665                     this.setObjectFillColor(el, 'none', 0);
1666                 }
1667                 this.setObjectStrokeColor(el, draftColor, draftOpacity);
1668                 this.setObjectStrokeWidth(el, el.board.options.elements.draft.strokeWidth);
1669             }
1670         },
1671 
1672         /**
1673          * Puts an object from draft mode back into normal mode.
1674          * @param {JXG.GeometryElement} el Reference of the object that no longer is in draft mode.
1675          */
1676         removeDraft: function (el) {
1677             this.setObjectTransition(el);
1678             if (el.type === Const.OBJECT_TYPE_POLYGON) {
1679                 this.setObjectFillColor(el,
1680                     el.visProp.fillcolor,
1681                     el.visProp.fillopacity);
1682             } else {
1683                 if (el.type === Const.OBJECT_CLASS_POINT) {
1684                     this.setObjectFillColor(el,
1685                         el.visProp.fillcolor,
1686                         el.visProp.fillopacity);
1687                 }
1688                 this.setObjectStrokeColor(el, el.visProp.strokecolor, el.visProp.strokeopacity);
1689                 this.setObjectStrokeWidth(el, el.visProp.strokewidth);
1690             }
1691         },
1692 
1693         /**
1694          * Sets up nodes for rendering a gradient fill.
1695          * @param element
1696          */
1697         setGradient: function (element) { /* stub */ },
1698 
1699         /**
1700          * Updates the gradient fill.
1701          * @param {JXG.GeometryElement} element An JSXGraph element with an area that can be filled.
1702          */
1703         updateGradient: function (element) { /* stub */ },
1704 
1705         /**
1706          * Sets the transition duration (in milliseconds) for fill color and stroke
1707          * color and opacity.
1708          * @param {JXG.GeometryElement} element Reference of the object that wants a
1709          *         new transition duration.
1710          * @param {Number} duration (Optional) duration in milliseconds. If not given,
1711          *        element.visProp.transitionDuration is taken. This is the default.
1712          */
1713         setObjectTransition: function (element, duration) { /* stub */ },
1714 
1715         /**
1716          * Sets an objects fill color.
1717          * @param {JXG.GeometryElement} element Reference of the object that wants a new fill color.
1718          * @param {String} color Color in a HTML/CSS compatible format. If you don't want any fill color at all, choose
1719          * 'none'.
1720          * @param {Number} opacity Opacity of the fill color. Must be between 0 and 1.
1721          */
1722         setObjectFillColor: function (element, color, opacity) { /* stub */ },
1723 
1724         /**
1725          * Changes an objects stroke color to the given color.
1726          * @param {JXG.GeometryElement} element Reference of the {@link JXG.GeometryElement} that gets a new stroke
1727          * color.
1728          * @param {String} color Color value in a HTML compatible format, e.g. <strong>#00ff00</strong> or
1729          * <strong>green</strong> for green.
1730          * @param {Number} opacity Opacity of the fill color. Must be between 0 and 1.
1731          */
1732         setObjectStrokeColor: function (element, color, opacity) { /* stub */ },
1733 
1734         /**
1735          * Sets an element's stroke width.
1736          * @param {JXG.GeometryElement} element Reference to the geometry element.
1737          * @param {Number} width The new stroke width to be assigned to the element.
1738          */
1739         setObjectStrokeWidth: function (element, width) { /* stub */ },
1740 
1741         /**
1742          * Sets the shadow properties to a geometry element. This method is only a stub, it is implemented in the actual
1743          * renderers.
1744          * @param {JXG.GeometryElement} element Reference to a geometry object, that should get a shadow
1745          */
1746         setShadow: function (element) { /* stub */ },
1747 
1748         /**
1749          * Highlights an object, i.e. changes the current colors of the object to its highlighting colors
1750          * and highlighting stroke width.
1751          * @param {JXG.GeometryElement} el Reference of the object that will be highlighted.
1752          * @returns {JXG.AbstractRenderer} Reference to the renderer
1753          * @see JXG.AbstractRenderer#updateTextStyle
1754          */
1755         highlight: function (el) {
1756             var i, ev = el.visProp, sw;
1757 
1758             this.setObjectTransition(el);
1759             if (!ev.draft) {
1760                 if (el.type === Const.OBJECT_TYPE_POLYGON) {
1761                     this.setObjectFillColor(el,
1762                         ev.highlightfillcolor,
1763                         ev.highlightfillopacity);
1764                     for (i = 0; i < el.borders.length; i++) {
1765                         this.setObjectStrokeColor(el.borders[i],
1766                             el.borders[i].visProp.highlightstrokecolor,
1767                             el.borders[i].visProp.highlightstrokeopacity);
1768                     }
1769                 } else {
1770                     if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
1771                         this.updateTextStyle(el, true);
1772                     } else if (el.type === Const.OBJECT_TYPE_IMAGE) {
1773                         this.updateImageStyle(el, true);
1774                         this.setObjectFillColor(el,
1775                             ev.highlightfillcolor,
1776                             ev.highlightfillopacity);
1777                     } else {
1778                         this.setObjectStrokeColor(el, ev.highlightstrokecolor, ev.highlightstrokeopacity);
1779                         this.setObjectFillColor(el,
1780                             ev.highlightfillcolor,
1781                             ev.highlightfillopacity);
1782                     }
1783                 }
1784                 if (ev.highlightstrokewidth) {
1785                     sw = Math.max(Type.evaluate(ev.highlightstrokewidth), Type.evaluate(ev.strokewidth));
1786                     this.setObjectStrokeWidth(el, sw);
1787                     if (el.elementClass === Const.OBJECT_CLASS_LINE || el.elementClass === Const.OBJECT_CLASS_CURVE) {
1788                         this.updatePathWithArrowHeads(el, true);
1789                     }
1790                 }
1791             }
1792 
1793             return this;
1794         },
1795 
1796         /**
1797          * Uses the normal colors of an object, i.e. the opposite of {@link JXG.AbstractRenderer#highlight}.
1798          * @param {JXG.GeometryElement} el Reference of the object that will get its normal colors.
1799          * @returns {JXG.AbstractRenderer} Reference to the renderer
1800          * @see JXG.AbstractRenderer#updateTextStyle
1801          */
1802         noHighlight: function (el) {
1803             var i, ev = el.visProp, sw;
1804 
1805             this.setObjectTransition(el);
1806             if (!Type.evaluate(el.visProp.draft)) {
1807                 if (el.type === Const.OBJECT_TYPE_POLYGON) {
1808                     this.setObjectFillColor(el,
1809                         ev.fillcolor,
1810                         ev.fillopacity);
1811                     for (i = 0; i < el.borders.length; i++) {
1812                         this.setObjectStrokeColor(el.borders[i],
1813                             el.borders[i].visProp.strokecolor,
1814                             el.borders[i].visProp.strokeopacity);
1815                     }
1816                 } else {
1817                     if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
1818                         this.updateTextStyle(el, false);
1819                     } else if (el.type === Const.OBJECT_TYPE_IMAGE) {
1820                         this.updateImageStyle(el, false);
1821                         this.setObjectFillColor(el,
1822                             ev.fillcolor,
1823                             ev.fillopacity);
1824                     } else {
1825                         this.setObjectStrokeColor(el,
1826                             ev.strokecolor,
1827                             ev.strokeopacity);
1828                         this.setObjectFillColor(el,
1829                             ev.fillcolor,
1830                             ev.fillopacity);
1831                     }
1832                 }
1833 
1834                 sw = Type.evaluate(ev.strokewidth);
1835                 this.setObjectStrokeWidth(el, sw);
1836                 if (el.elementClass === Const.OBJECT_CLASS_LINE || el.elementClass === Const.OBJECT_CLASS_CURVE) {
1837                     this.updatePathWithArrowHeads(el, false);
1838                 }
1839 
1840             }
1841 
1842             return this;
1843         },
1844 
1845         /* **************************
1846          * renderer control
1847          * **************************/
1848 
1849         /**
1850          * Stop redraw. This method is called before every update, so a non-vector-graphics based renderer can use this
1851          * method to delete the contents of the drawing panel. This is an abstract method every descendant renderer
1852          * should implement, if appropriate.
1853          * @see JXG.AbstractRenderer#unsuspendRedraw
1854          */
1855         suspendRedraw: function () { /* stub */ },
1856 
1857         /**
1858          * Restart redraw. This method is called after updating all the rendering node attributes.
1859          * @see JXG.AbstractRenderer#suspendRedraw
1860          */
1861         unsuspendRedraw: function () { /* stub */ },
1862 
1863         /**
1864          * The tiny zoom bar shown on the bottom of a board (if showNavigation on board creation is true).
1865          * It is a div element and gets the CSS class "JXG_navigation" and the id {board id}_navigationbar.
1866          *
1867          * The buttons get the CSS class "JXG_navigation_button" and the id {board_id}_name where name is
1868          * one of [top, down, left, right, out, 100, in, fullscreen, screenshot, reload, cleartraces].
1869          *
1870          * The symbols are hard-coded.
1871          *
1872          * @param {JXG.Board} board Reference to a JSXGraph board.
1873          * @param {Object} attr Attributes of the navigation bar
1874          *
1875          */
1876         drawZoomBar: function (board, attr) {
1877             var doc,
1878                 node,
1879                 cancelbubble = function (e) {
1880                     if (!e) {
1881                         e = window.event;
1882                     }
1883 
1884                     if (e.stopPropagation) {
1885                         // Non IE<=8
1886                         e.stopPropagation();
1887                     } else {
1888                         e.cancelBubble = true;
1889                     }
1890                 },
1891                 createButton = function (label, handler, id) {
1892                     var button;
1893 
1894                     id = id || '';
1895 
1896                     button = doc.createElement('span');
1897                     button.innerHTML = label;  // button.appendChild(doc.createTextNode(label));
1898 
1899                     // Style settings are superseded by adding the CSS class below
1900                     button.style.paddingLeft = '7px';
1901                     button.style.paddingRight = '7px';
1902 
1903                     if (button.classList !== undefined) { // classList not available in IE 9
1904                         button.classList.add('JXG_navigation_button');
1905                     }
1906                     // button.setAttribute('tabindex', 0);
1907 
1908                     button.setAttribute('id', id);
1909                     node.appendChild(button);
1910 
1911                     // Highlighting is now done with CSS
1912                     // Env.addEvent(button, 'mouseover', function () {
1913                     //     this.style.backgroundColor = attr.highlightfillcolor;
1914                     // }, button);
1915                     // Env.addEvent(button, 'mouseover', function () {
1916                     //     this.style.backgroundColor = attr.highlightfillcolor;
1917                     // }, button);
1918                     // Env.addEvent(button, 'mouseout', function () {
1919                     //     this.style.backgroundColor = attr.fillcolor;
1920                     // }, button);
1921 
1922                     Env.addEvent(button, 'click', function(e) { (Type.bind(handler, board))(); return false; }, board);
1923                     // prevent the click from bubbling down to the board
1924                     Env.addEvent(button, 'mouseup', cancelbubble, board);
1925                     Env.addEvent(button, 'mousedown', cancelbubble, board);
1926                     Env.addEvent(button, 'touchend', cancelbubble, board);
1927                     Env.addEvent(button, 'touchstart', cancelbubble, board);
1928                 };
1929 
1930             if (Env.isBrowser && this.type !== 'no') {
1931                 doc = board.containerObj.ownerDocument;
1932                 node = doc.createElement('div');
1933 
1934                 node.setAttribute('id', board.container + '_navigationbar');
1935 
1936                 // Style settings are superseded by adding the CSS class below
1937                 node.style.color = attr.strokecolor;
1938                 node.style.backgroundColor = attr.fillcolor;
1939                 node.style.padding = attr.padding;
1940                 node.style.position = attr.position;
1941                 node.style.fontSize = attr.fontsize;
1942                 node.style.cursor = attr.cursor;
1943                 node.style.zIndex = attr.zindex;
1944                 board.containerObj.appendChild(node);
1945                 node.style.right = attr.right;
1946                 node.style.bottom = attr.bottom;
1947 
1948                 if (node.classList !== undefined) { // classList not available in IE 9
1949                     node.classList.add('JXG_navigation');
1950                 }
1951                 // For XHTML we need unicode instead of HTML entities
1952 
1953                 if (board.attr.showfullscreen) {
1954                     createButton(board.attr.fullscreen.symbol, function () {
1955                         board.toFullscreen(board.attr.fullscreen.id);
1956                     }, board.container + '_navigation_fullscreen');
1957                 }
1958 
1959                 if (board.attr.showscreenshot) {
1960                     createButton(board.attr.screenshot.symbol, function () {
1961                         window.setTimeout(function() {
1962                             board.renderer.screenshot(board, '', false);
1963                         }, 330);
1964                     }, board.container + '_navigation_screenshot');
1965                 }
1966 
1967                 if (board.attr.showreload) {
1968                     // full reload circle: \u27F2
1969                     // the board.reload() method does not exist during the creation
1970                     // of this button. That's why this anonymous function wrapper is required.
1971                     createButton('\u21BB', function () {
1972                         board.reload();
1973                     }, board.container + '_navigation_reload');
1974                 }
1975 
1976                 if (board.attr.showcleartraces) {
1977                     // clear traces symbol (otimes): \u27F2
1978                     createButton('\u2297', function () {
1979                         board.clearTraces();
1980                     }, board.container + '_navigation_cleartraces');
1981                 }
1982 
1983                 if (board.attr.shownavigation) {
1984                     if (board.attr.showzoom) {
1985                         createButton('\u2013', board.zoomOut, board.container + '_navigation_out');
1986                         createButton('o', board.zoom100, board.container + '_navigation_100');
1987                         createButton('+', board.zoomIn, board.container + '_navigation_in');
1988                     }
1989                     createButton('\u2190', board.clickLeftArrow, board.container + '_navigation_left');
1990                     createButton('\u2193', board.clickUpArrow, board.container + '_navigation_up');
1991                     createButton('\u2191', board.clickDownArrow, board.container + '_navigation_down');
1992                     createButton('\u2192', board.clickRightArrow, board.container + '_navigation_right');
1993                 }
1994             }
1995         },
1996 
1997         /**
1998          * Wrapper for getElementById for maybe other renderers which elements are not directly accessible by DOM
1999          * methods like document.getElementById().
2000          * @param {String} id Unique identifier for element.
2001          * @returns {Object} Reference to a JavaScript object. In case of SVG/VMLRenderer it's a reference to a SVG/VML
2002          * node.
2003          */
2004         getElementById: function (id) {
2005             if (Type.exists(this.container)) {
2006                 return this.container.ownerDocument.getElementById(this.container.id + '_' + id);
2007             }
2008             return '';
2009         },
2010 
2011         /**
2012          * Remove an element and provide a function that inserts it into its original position. This method
2013          * is taken from this article {@link https://developers.google.com/speed/articles/javascript-dom}.
2014          * @author KeeKim Heng, Google Web Developer
2015          * @param {Element} el The element to be temporarily removed
2016          * @returns {Function} A function that inserts the element into its original position
2017          */
2018         removeToInsertLater: function (el) {
2019             var parentNode = el.parentNode,
2020                 nextSibling = el.nextSibling;
2021 
2022             if (parentNode === null) {
2023                 return;
2024             }
2025             parentNode.removeChild(el);
2026 
2027             return function () {
2028                 if (nextSibling) {
2029                     parentNode.insertBefore(el, nextSibling);
2030                 } else {
2031                     parentNode.appendChild(el);
2032                 }
2033             };
2034         },
2035 
2036         /**
2037          * Resizes the rendering element
2038          * @param {Number} w New width
2039          * @param {Number} h New height
2040          */
2041         resize: function (w, h) { /* stub */},
2042 
2043         /**
2044          * Create crosshair elements (Fadenkreuz) for presentations.
2045          * @param {Number} n Number of crosshairs.
2046          */
2047         createTouchpoints: function (n) {},
2048 
2049         /**
2050          * Show a specific crosshair.
2051          * @param {Number} i Number of the crosshair to show
2052          */
2053         showTouchpoint: function (i) {},
2054 
2055         /**
2056          * Hide a specific crosshair.
2057          * @param {Number} i Number of the crosshair to show
2058          */
2059         hideTouchpoint: function (i) {},
2060 
2061         /**
2062          * Move a specific crosshair.
2063          * @param {Number} i Number of the crosshair to show
2064          * @param {Array} pos New positon in screen coordinates
2065          */
2066         updateTouchpoint: function (i, pos) {},
2067 
2068         /**
2069          * Convert SVG construction to base64 encoded SVG data URL.
2070          * Only available on SVGRenderer.
2071          *
2072          * @see JXG.SVGRenderer#dumpToDataURI
2073          */
2074         dumpToDataURI: function (_ignoreTexts) {},
2075 
2076         /**
2077          * Convert SVG construction to canvas.
2078          * Only available on SVGRenderer.
2079          *
2080          * @see JXG.SVGRenderer#dumpToCanvas
2081          */
2082         dumpToCanvas: function (canvasId, w, h, _ignoreTexts) {},
2083 
2084         /**
2085          * Display SVG image in html img-tag which enables
2086          * easy download for the user.
2087          *
2088          * See JXG.SVGRenderer#screenshot
2089          */
2090         screenshot: function (board) {},
2091 
2092         /**
2093          * Move element into new layer. This is trivial for canvas, but needs more effort in SVG.
2094          * Does not work dynamically, i.e. if level is a function.
2095          *
2096          * @param {JXG.GeometryElement} el Element which is put into different layer
2097          * @param {Number} value Layer number
2098          * @private
2099          */
2100         setLayer: function(el, level) {}
2101 
2102     });
2103 
2104     return JXG.AbstractRenderer;
2105 });
2106