1 /*
  2     Copyright 2008-2011
  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: you can redistribute it and/or modify
 13     it under the terms of the GNU Lesser General Public License as published by
 14     the Free Software Foundation, either version 3 of the License, or
 15     (at your option) any later version.
 16 
 17     JSXGraph is distributed in the hope that it will be useful,
 18     but WITHOUT ANY WARRANTY; without even the implied warranty of
 19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 20     GNU Lesser General Public License for more details.
 21 
 22     You should have received a copy of the GNU Lesser General Public License
 23     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
 24 */
 25 
 26 /*jshint bitwise: false, curly: true, debug: false, eqeqeq: true, devel: false, evil: false,
 27   forin: false, immed: true, laxbreak: false, newcap: false, noarg: true, nonew: true, onevar: true,
 28    undef: true, white: false, sub: false*/
 29 /*global JXG: true, AMprocessNode: true, MathJax: true, document: true */
 30 
 31 /**
 32  * Uses VML to implement the rendering methods defined in {@link JXG.AbstractRenderer}.
 33  * @class JXG.AbstractRenderer
 34  * @augments JXG.AbstractRenderer
 35  * @param {Node} container Reference to a DOM node containing the board.
 36  * @see JXG.AbstractRenderer
 37  */
 38 JXG.VMLRenderer = function (container) {
 39     this.type = 'vml';
 40     
 41     this.container = container;
 42     this.container.style.overflow = 'hidden';
 43     this.container.onselectstart = function () {
 44         return false;
 45     };
 46     
 47     this.resolution = 10; // Paths are drawn with a a resolution of this.resolution/pixel.
 48   
 49     // Add VML includes and namespace
 50     // Original: IE <=7
 51     //container.ownerDocument.createStyleSheet().addRule("v\\:*", "behavior: url(#default#VML);");
 52     if (!JXG.exists(JXG.vmlStylesheet)) {
 53         container.ownerDocument.namespaces.add("jxgvml", "urn:schemas-microsoft-com:vml");
 54         JXG.vmlStylesheet = this.container.ownerDocument.createStyleSheet();
 55         JXG.vmlStylesheet.addRule(".jxgvml", "behavior:url(#default#VML)");
 56     }
 57 
 58     try {
 59         !container.ownerDocument.namespaces.jxgvml && container.ownerDocument.namespaces.add("jxgvml", "urn:schemas-microsoft-com:vml");
 60         this.createNode = function (tagName) {
 61             return container.ownerDocument.createElement('<jxgvml:' + tagName + ' class="jxgvml">');
 62         };
 63     } catch (e) {
 64         this.createNode = function (tagName) {
 65             return container.ownerDocument.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="jxgvml">');
 66         };
 67     }
 68 
 69     // dash styles
 70     this.dashArray = ['Solid', '1 1', 'ShortDash', 'Dash', 'LongDash', 'ShortDashDot', 'LongDashDot'];    
 71 };
 72 
 73 JXG.VMLRenderer.prototype = new JXG.AbstractRenderer();
 74 
 75 JXG.extend(JXG.VMLRenderer.prototype, /** @lends JXG.VMLRenderer */ {
 76 
 77     /**
 78      * Sets attribute <tt>key</tt> of node <tt>node</tt> to <tt>value</tt>.
 79      * @param {Node} node A DOM node.
 80      * @param {String} key Name of the attribute.
 81      * @param {String} val New value of the attribute.
 82      * @param {Boolean} [iFlag=false] If false, the attribute's name is case insensitive.
 83      */
 84     _setAttr: function (node, key, val, iFlag) {
 85         try {
 86             if (document.documentMode === 8) {
 87                 node[key] = val;
 88             } else {
 89                 node.setAttribute(key, val, iFlag);
 90             }
 91         } catch (e) {
 92             JXG.debug('_setAttr:'/*node.id*/ + ' ' + key + ' ' + val + '<br>\n');
 93         }
 94     },
 95 
 96     /* ******************************** *
 97      *  This renderer does not need to
 98      *  override draw/update* methods
 99      *  since it provides draw/update*Prim
100      *  methods.
101      * ******************************** */
102 
103     /* **************************
104      *    Lines
105      * **************************/
106 
107     // documented in AbstractRenderer
108     updateTicks: function (axis, dxMaj, dyMaj, dxMin, dyMin) {
109         var tickArr = [], i, len, c, ticks, r = this.resolution;
110 
111         len = axis.ticks.length;
112         for (i = 0; i < len; i++) {
113             c = axis.ticks[i];
114             x = c[0];
115             y = c[1];
116             if (typeof x[0] != 'undefined' && typeof x[1] != 'undefined') {
117                 tickArr.push(
118                     ' m ' + Math.round(r * x[0]) + ', ' + Math.round(r * y[0]) +
119                     ' l ' + Math.round(r * x[1]) + ', ' + Math.round(r * y[1]) + ' '
120                 );
121             }
122         }
123         // Labels
124         for (i = 0; i < len; i++) {
125             c = axis.ticks[i].scrCoords;
126             if (axis.ticks[i].major 
127                 && (axis.board.needsFullUpdate || axis.needsRegularUpdate) 
128                 && axis.labels[i] 
129                 && axis.labels[i].visProp.visible) {
130                     this.updateText(axis.labels[i]);
131             } 
132         }
133          
134         //ticks = this.getElementById(axis.id);
135         if (!JXG.exists(axis)) {
136             ticks = this.createPrim('path', axis.id);
137             this.appendChildPrim(ticks, axis.visProp.layer);
138             this.appendNodesToElement(axis, 'path');
139         }
140         this._setAttr(axis.rendNode, 'stroked', 'true');
141         this._setAttr(axis.rendNode, 'strokecolor', axis.visProp.strokecolor, 1);
142         this._setAttr(axis.rendNode, 'strokeweight', axis.visProp.strokewidth);
143         this._setAttr(axis.rendNodeStroke, 'opacity', (axis.visProp.strokeopacity * 100) + '%');
144         this.updatePathPrim(axis.rendNode, tickArr, axis.board);
145     },
146 
147     /* **************************
148      *    Text related stuff
149      * **************************/
150 
151     // already documented in JXG.AbstractRenderer
152     displayCopyright: function (str, fontsize) {
153         var node, t;
154 
155         node = this.createNode('textbox');
156         node.style.position = 'absolute';
157         this._setAttr(node, 'id', this.container.id + '_' + 'licenseText');
158 
159         node.style.left = 20;
160         node.style.top = 2;
161         node.style.fontSize = fontsize;
162         node.style.color = '#356AA0';
163         node.style.fontFamily = 'Arial,Helvetica,sans-serif';
164         this._setAttr(node, 'opacity', '30%');
165         node.style.filter = 'alpha(opacity = 30)';
166 
167         t = document.createTextNode(str);
168         node.appendChild(t);
169         this.appendChildPrim(node, 0);
170     },
171 
172     // documented in AbstractRenderer
173     drawInternalText: function (el) {
174         var node;
175         node = this.createNode('textbox');
176         node.style.position = 'absolute';
177         /*
178         if (document.documentMode === 8) {                 // IE 8
179             node.setAttribute('class', el.visProp.cssclass);
180         } else {
181             node.setAttribute(document.all ? 'className' : 'class', el.visProp.cssclass);
182         }
183         */
184         el.rendNodeText = document.createTextNode('');
185         node.appendChild(el.rendNodeText);
186         this.appendChildPrim(node, 9);
187         return node;
188     },
189 
190     // documented in AbstractRenderer
191     updateInternalText: function (el) {
192         var content = el.plaintext;
193         /*
194         if (document.documentMode === 8) {                 // IE 8
195             el.rendNode.setAttribute('class', el.visProp.cssclass);
196         } else {
197             el.rendNode.setAttribute(document.all ? 'className' : 'class', el.visProp.cssclass);
198         }
199         */
200         if (!isNaN(el.coords.scrCoords[1]+el.coords.scrCoords[2])) {
201             el.rendNode.style.left = parseInt(el.coords.scrCoords[1]) + 'px';
202             el.rendNode.style.top = parseInt(el.coords.scrCoords[2] - parseInt(el.visProp.fontsize) + this.vOffsetText) + 'px';
203         }
204         
205         if (el.htmlStr !== content) {
206             el.rendNodeText.data = content;
207             el.htmlStr = content;
208         }
209 
210         this.transformImage(el, el.transformations);
211     },
212 
213     /* **************************
214      *    Image related stuff
215      * **************************/
216 
217     // already documented in JXG.AbstractRenderer
218     drawImage: function (el) {
219         // IE 8: Bilder ueber data URIs werden bis 32kB unterstuetzt.
220         var node;
221 
222         node = this.container.ownerDocument.createElement('img');
223         node.style.position = 'absolute';
224         this._setAttr(node, 'id', this.container.id + '_' + el.id);
225 
226         this.container.appendChild(node);
227         this.appendChildPrim(node, el.visProp.layer);
228 
229         // Adding the rotation filter. This is always filter item 0:
230         // node.filters.item(0), see transformImage
231         //node.style.filter = node.style['-ms-filter'] = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand')";
232         node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand')";
233         el.rendNode = node;
234         this.updateImage(el);
235     },
236 
237     // already documented in JXG.AbstractRenderer
238     transformImage: function (el, t) {
239         var node = el.rendNode,
240             m, p = [], s, len = t.length,
241             maxX, maxY, minX, minY, i, nt;
242 
243         if (el.type === JXG.OBJECT_TYPE_TEXT) {
244             el.updateSize();
245         }
246         if (len > 0) {
247             nt = el.rendNode.style.filter.toString();
248             if (!nt.match(/DXImageTransform/)) {
249                 node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand') " + nt;
250             }
251 
252             m = this.joinTransforms(el, t);
253             p[0] = JXG.Math.matVecMult(m, el.coords.scrCoords);
254             p[0][1] /= p[0][0];
255             p[0][2] /= p[0][0];
256             p[1] = JXG.Math.matVecMult(m, [1, el.coords.scrCoords[1] + el.size[0], el.coords.scrCoords[2]]);
257             p[1][1] /= p[1][0];
258             p[1][2] /= p[1][0];
259             p[2] = JXG.Math.matVecMult(m, [1, el.coords.scrCoords[1] + el.size[0], el.coords.scrCoords[2] - el.size[1]]);
260             p[2][1] /= p[2][0];
261             p[2][2] /= p[2][0];
262             p[3] = JXG.Math.matVecMult(m, [1, el.coords.scrCoords[1], el.coords.scrCoords[2] - el.size[1]]);
263             p[3][1] /= p[3][0];
264             p[3][2] /= p[3][0];
265             maxX = p[0][1];
266             minX = p[0][1];
267             maxY = p[0][2];
268             minY = p[0][2];
269             for (i = 1; i < 4; i++) {
270                 maxX = Math.max(maxX, p[i][1]);
271                 minX = Math.min(minX, p[i][1]);
272                 maxY = Math.max(maxY, p[i][2]);
273                 minY = Math.min(minY, p[i][2]);
274             }
275             node.style.left = parseInt(minX) + 'px';
276             node.style.top  = parseInt(minY) + 'px';
277 
278             node.filters.item(0).M11 = m[1][1];
279             node.filters.item(0).M12 = m[1][2];
280             node.filters.item(0).M21 = m[2][1];
281             node.filters.item(0).M22 = m[2][2];
282         }
283     },
284 
285     // already documented in JXG.AbstractRenderer
286     updateImageURL: function (el) {
287         var url = JXG.evaluate(el.url);
288         this._setAttr(el.rendNode, 'src', url);
289     },
290 
291     /* **************************
292      * Render primitive objects
293      * **************************/
294 
295     // already documented in JXG.AbstractRenderer
296     appendChildPrim: function (node, level) {
297         if (!JXG.exists(level)) {  // For trace nodes
298             level = 0;
299         }
300         node.style.zIndex = level;
301         this.container.appendChild(node);
302     },
303 
304     // already documented in JXG.AbstractRenderer
305     appendNodesToElement: function (element, type) {
306         if (type === 'shape' || type === 'path' || type === 'polygon') {
307             element.rendNodePath = this.getElementById(element.id + '_path');
308         }
309         element.rendNodeFill = this.getElementById(element.id + '_fill');
310         element.rendNodeStroke = this.getElementById(element.id + '_stroke');
311         element.rendNodeShadow = this.getElementById(element.id + '_shadow');
312         element.rendNode = this.getElementById(element.id);
313     },
314 
315     // already documented in JXG.AbstractRenderer
316     createPrim: function (type, id) {
317         var node,
318             fillNode = this.createNode('fill'),
319             strokeNode = this.createNode('stroke'),
320             shadowNode = this.createNode('shadow'),
321             pathNode;
322 
323         this._setAttr(fillNode, 'id', this.container.id + '_' + id + '_fill');
324         this._setAttr(strokeNode, 'id', this.container.id + '_' + id + '_stroke');
325         this._setAttr(shadowNode, 'id', this.container.id + '_' + id + '_shadow');
326 
327         if (type === 'circle' || type === 'ellipse') {
328             node = this.createNode('oval');
329             node.appendChild(fillNode);
330             node.appendChild(strokeNode);
331             node.appendChild(shadowNode);
332         } else if (type === 'polygon' || type === 'path' || type === 'shape' || type === 'line') {
333             node = this.createNode('shape');
334             node.appendChild(fillNode);
335             node.appendChild(strokeNode);
336             node.appendChild(shadowNode);
337             pathNode = this.createNode('path');
338             this._setAttr(pathNode, 'id', this.container.id + '_' + id + '_path');
339             node.appendChild(pathNode);
340         } else {
341             node = this.createNode(type);
342             node.appendChild(fillNode);
343             node.appendChild(strokeNode);
344             node.appendChild(shadowNode);
345         }
346         node.style.position = 'absolute';
347         node.style.left = '0px';
348         node.style.top = '0px';
349         this._setAttr(node, 'id', this.container.id + '_' + id);
350 
351         return node;
352     },
353 
354     // already documented in JXG.AbstractRenderer
355     remove: function (node) {
356         if (JXG.exists(node)) {
357             node.removeNode(true);
358         }
359     },
360 
361     // already documented in JXG.AbstractRenderer
362     makeArrows: function (el) {
363         var nodeStroke;
364 
365         if (el.visPropOld.firstarrow === el.visProp.firstarrow && el.visPropOld.lastarrow === el.visProp.lastarrow) {
366             return;
367         }
368 
369         if (el.visProp.firstarrow) {
370             nodeStroke = el.rendNodeStroke;
371             this._setAttr(nodeStroke, 'startarrow', 'block');
372             this._setAttr(nodeStroke, 'startarrowlength', 'long');
373         } else {
374             nodeStroke = el.rendNodeStroke;
375             if (JXG.exists(nodeStroke)) {
376                 this._setAttr(nodeStroke, 'startarrow', 'none');
377             }
378         }
379 
380         if (el.visProp.lastarrow) {
381             nodeStroke = el.rendNodeStroke;
382             this._setAttr(nodeStroke, 'id', this.container.id + '_' + el.id + "stroke");
383             this._setAttr(nodeStroke, 'endarrow', 'block');
384             this._setAttr(nodeStroke, 'endarrowlength', 'long');
385         } else {
386             nodeStroke = el.rendNodeStroke;
387             if (JXG.exists(nodeStroke)) {
388                 this._setAttr(nodeStroke, 'endarrow', 'none');
389             }
390         }
391         el.visPropOld.firstarrow = el.visProp.firstarrow;
392         el.visPropOld.lastarrow = el.visProp.lastarrow;
393     },
394 
395     // already documented in JXG.AbstractRenderer
396     updateEllipsePrim: function (node, x, y, rx, ry) {
397         node.style.left = parseInt(x - rx) + 'px';
398         node.style.top =  parseInt(y - ry) + 'px';
399         node.style.width = parseInt(Math.abs(rx) * 2) + 'px';
400         node.style.height = parseInt(Math.abs(ry) * 2) + 'px';
401     },
402 
403     // already documented in JXG.AbstractRenderer
404     updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) {
405         var s, r = this.resolution;
406 
407         if (!isNaN(p1x+p1y+p2x+p2y)) {
408             s = ['m ', parseInt(r * p1x), ', ', parseInt(r * p1y), ' l ', parseInt(r * p2x), ', ', parseInt(r * p2y)];
409             this.updatePathPrim(node, s, board);
410         }
411     },
412 
413     // already documented in JXG.AbstractRenderer
414     updatePathPrim: function (node, pointString, board) {
415         var x = board.canvasWidth,
416             y = board.canvasHeight;
417         if (pointString.length <= 0) {
418             pointString = ['m 0,0'];
419         }
420         node.style.width = x;
421         node.style.height = y;
422         this._setAttr(node, 'coordsize', [parseInt(this.resolution * x), parseInt(this.resolution * y)].join(','));
423         this._setAttr(node, 'path', pointString.join(""));
424     },
425 
426     // already documented in JXG.AbstractRenderer
427     updatePathStringPoint: function (el, size, type) {
428         var s = [],
429             mround = Math.round,
430             scr = el.coords.scrCoords,
431             sqrt32 = size * Math.sqrt(3) * 0.5,
432             s05 = size * 0.5,
433             r = this.resolution;
434 
435         if (type === 'x') {
436             s.push([
437                 ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2] - size)),
438                 ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2] + size)),
439                 ' m ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2] - size)),
440                 ' l ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2] + size))
441             ].join(''));
442         }
443         else if (type === '+') {
444             s.push([
445                 ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])),
446                 ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])),
447                 ' m ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] - size)),
448                 ' l ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] + size))
449             ].join(''));
450         }
451         else if (type === '<>') {
452             
453             s.push([
454                 ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])),
455                 ' l ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] + size)),
456                 ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])),
457                 ' l ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] - size)),
458                 ' x e '
459             ].join(''));
460         }
461         else if (type === '^') {
462             s.push([
463                 ' m ', mround(r * (scr[1])),          ', ', mround(r * (scr[2] - size)),
464                 ' l ', mround(r * (scr[1] - sqrt32)), ', ', mround(r * (scr[2] + s05)),
465                 ' l ', mround(r * (scr[1] + sqrt32)), ', ', mround(r * (scr[2] + s05)),
466                 ' x e '
467             ].join(''));
468         }
469         else if (type === 'v') {
470             s.push([
471                 ' m ', mround(r * (scr[1])),          ', ', mround(r * (scr[2] + size)),
472                 ' l ', mround(r * (scr[1] - sqrt32)), ', ', mround(r * (scr[2] - s05)),
473                 ' l ', mround(r * (scr[1] + sqrt32)), ', ', mround(r * (scr[2] - s05)),
474                 ' x e '
475             ].join(''));
476         }
477         else if (type === '>') {
478             s.push([
479                 ' m ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])),
480                 ' l ', mround(r * (scr[1] - s05)),  ', ', mround(r * (scr[2] - sqrt32)),
481                 ' l ', mround(r * (scr[1] - s05)),  ', ', mround(r * (scr[2] + sqrt32)),
482                 ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2]))
483             ].join(''));
484         }
485         else if (type === '<') {
486             s.push([
487                 ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])),
488                 ' l ', mround(r * (scr[1] + s05)),  ', ', mround(r * (scr[2] - sqrt32)),
489                 ' l ', mround(r * (scr[1] + s05)),  ', ', mround(r * (scr[2] + sqrt32)),
490                 ' x e '
491             ].join(''));
492         }
493         return s;
494     },
495 
496     // already documented in JXG.AbstractRenderer
497     updatePathStringPrim: function (el) {
498         var pStr = [],
499             i, scr,
500             r = this.resolution,
501             mround = Math.round,
502             symbm = ' m ',
503             symbl = ' l ',
504             nextSymb = symbm,
505             isNotPlot = (el.visProp.curvetype !== 'plot'),
506             len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html
507 
508         if (el.numberPoints <= 0) {
509             return '';
510         }
511         if (isNotPlot && el.board.options.curve.RDPsmoothing) {
512             el.points = JXG.Math.Numerics.RamerDouglasPeuker(el.points, 1.0);
513         }
514         len = Math.min(len, el.points.length);
515 
516         for (i = 0; i < len; i++) {
517             scr = el.points[i].scrCoords;
518             if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
519                 nextSymb = symbm;
520             } else {
521                 // IE has problems with values  being too far away.
522                 if (scr[1] > 20000.0) {
523                     scr[1] = 20000.0;
524                 } else if (scr[1] < -20000.0) {
525                     scr[1] = -20000.0;
526                 }
527 
528                 if (scr[2] > 20000.0) {
529                     scr[2] = 20000.0;
530                 } else if (scr[2] < -20000.0) {
531                     scr[2] = -20000.0;
532                 }
533 
534                 pStr.push([nextSymb, mround(r * scr[1]), ', ', mround(r * scr[2])].join(''));
535                 nextSymb = symbl;
536             }
537         }
538         pStr.push(' e');
539         return pStr;
540     },
541 
542     // already documented in JXG.AbstractRenderer
543     updatePathStringBezierPrim: function (el) {
544         var pStr = [],
545             i, j, scr,
546             lx, ly, f = el.visProp.strokewidth, 
547             r = this.resolution,
548             mround = Math.round,
549             symbm = ' m ',
550             symbl = ' c ',
551             nextSymb = symbm,
552             isNoPlot = (el.visProp.curvetype !== 'plot'),
553             len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html
554 
555         if (el.numberPoints <= 0) {
556             return '';
557         }
558         if (isNoPlot && el.board.options.curve.RDPsmoothing) {
559             el.points = JXG.Math.Numerics.RamerDouglasPeuker(el.points, 1.0);
560         }
561         len = Math.min(len, el.points.length);
562 
563         for (j=1; j<3; j++) {
564             nextSymb = symbm;
565             for (i = 0; i < len; i++) {
566                 scr = el.points[i].scrCoords;
567                 if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
568                     nextSymb = symbm;
569                 } else {
570                     // IE has problems with values  being too far away.
571                     if (scr[1] > 20000.0) {
572                         scr[1] = 20000.0;
573                     } else if (scr[1] < -20000.0) {
574                         scr[1] = -20000.0;
575                     }
576     
577                     if (scr[2] > 20000.0) {
578                         scr[2] = 20000.0;
579                     } else if (scr[2] < -20000.0) {
580                         scr[2] = -20000.0;
581                     }
582     
583                     if (nextSymb == symbm) {
584                         pStr.push([nextSymb, 
585                             mround(r * (scr[1]+0*f*(2*j*Math.random()-j))), ' ', 
586                             mround(r * (scr[2]+0*f*(2*j*Math.random()-j)))].join(''));
587                     } else {
588                         pStr.push([nextSymb, 
589                             mround(r * (lx + (scr[1]-lx)*0.333 + f*(2*j*Math.random()-j))), ' ',
590                             mround(r * (ly + (scr[2]-ly)*0.333 + f*(2*j*Math.random()-j))), ' ',
591                             mround(r * (lx + 2*(scr[1]-lx)*0.333 + f*(2*j*Math.random()-j))), ' ',
592                             mround(r * (ly + 2*(scr[2]-ly)*0.333 + f*(2*j*Math.random()-j))), ' ',
593                             mround(r * scr[1]), ' ', 
594                             mround(r * scr[2])
595                             ].join(''));
596                     }
597                     nextSymb = symbl;
598                     lx = scr[1];
599                     ly = scr[2];
600                 }
601             }
602         }      
603         pStr.push(' e');
604         return pStr;
605     },
606     
607     // already documented in JXG.AbstractRenderer
608     updatePolygonPrim: function (node, el) {
609         var i,
610             len = el.vertices.length,
611             r = this.resolution,
612             scr,
613             pStr = [];
614 
615         this._setAttr(node, 'stroked', 'false');
616 
617         scr = el.vertices[0].coords.scrCoords;
618         if (isNaN(scr[1]+scr[2])) return;
619         pStr.push(["m ", parseInt(r * scr[1]), ",", parseInt(r * scr[2]), " l "].join(''));
620 
621         for (i = 1; i < len - 1; i++) {
622             if (el.vertices[i].isReal) {
623                 scr = el.vertices[i].coords.scrCoords;
624                 if (isNaN(scr[1]+scr[2])) return;
625                 pStr.push(parseInt(r * scr[1]) + "," + parseInt(r * scr[2]));
626             } else {
627                 this.updatePathPrim(node, '', el.board);
628                 return;
629             }
630             if (i < len - 2) {
631                     pStr.push(", ");
632             }
633         }
634         pStr.push(" x e");
635         this.updatePathPrim(node, pStr, el.board);
636     },
637 
638     // already documented in JXG.AbstractRenderer
639     updateRectPrim: function (node, x, y, w, h) {
640         node.style.left = parseInt(x) + 'px';
641         node.style.top = parseInt(y) + 'px';
642 
643         if (w >= 0) {
644             node.style.width = w + 'px';
645         }
646         
647         if (h >= 0) {
648             node.style.height = h + 'px';
649         }
650     },
651 
652     /* **************************
653      *  Set Attributes
654      * **************************/
655 
656     // already documented in JXG.AbstractRenderer
657     setPropertyPrim: function (node, key, val) {
658         var keyVml = '',
659             v;
660 
661         switch (key) {
662             case 'stroke':
663                 keyVml = 'strokecolor';
664                 break;
665             case 'stroke-width':
666                 keyVml = 'strokeweight';
667                 break;
668             case 'stroke-dasharray':
669                 keyVml = 'dashstyle';
670                 break;
671         }
672 
673         if (keyVml !== '') {
674             v = JXG.evaluate(val);
675             this._setAttr(node, keyVml, v);
676         }
677     },
678 
679     // already documented in JXG.AbstractRenderer
680     show: function (el) {
681         if (el && el.rendNode) {
682             el.rendNode.style.visibility = "inherit";
683         }
684     },
685 
686     // already documented in JXG.AbstractRenderer
687     hide: function (el) {
688         if (el && el.rendNode) {
689             el.rendNode.style.visibility = "hidden";
690         }
691     },
692 
693     // already documented in JXG.AbstractRenderer
694     setDashStyle: function (el, visProp) {
695         var node;
696         if (visProp.dash >= 0) {
697             node = el.rendNodeStroke;
698             this._setAttr(node, 'dashstyle', this.dashArray[visProp.dash]);
699         }
700     },
701 
702     // already documented in JXG.AbstractRenderer
703     setGradient: function (el) {
704         var nodeFill = el.rendNodeFill;
705         
706         if (el.visProp.gradient === 'linear') {
707             this._setAttr(nodeFill, 'type', 'gradient');
708             this._setAttr(nodeFill, 'color2', el.visProp.gradientsecondcolor);
709             this._setAttr(nodeFill, 'opacity2', el.visProp.gradientsecondopacity);
710             this._setAttr(nodeFill, 'angle', el.visProp.gradientangle);
711         } else if (el.visProp.gradient === 'radial') {
712             this._setAttr(nodeFill, 'type', 'gradientradial');
713             this._setAttr(nodeFill, 'color2', el.visProp.gradientsecondcolor);
714             this._setAttr(nodeFill, 'opacity2', el.visProp.gradientsecondopacity);
715             this._setAttr(nodeFill, 'focusposition', el.visProp.gradientpositionx * 100 + '%,' + el.visProp.gradientpositiony * 100 + '%');
716             this._setAttr(nodeFill, 'focussize', '0,0');
717         } else {
718             this._setAttr(nodeFill, 'type', 'solid');
719         }
720     },
721 
722     // already documented in JXG.AbstractRenderer
723     setObjectFillColor: function (el, color, opacity) {
724         var rgba = JXG.evaluate(color), c, rgbo,
725             o = JXG.evaluate(opacity), oo,
726             node = el.rendNode, 
727             t;
728 
729         o = (o > 0) ? o : 0;
730 
731         if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) {
732             return;
733         }
734 
735         if (JXG.exists(rgba) && rgba !== false) {
736             if (rgba.length!=9) {          // RGB, not RGBA
737                 c = rgba;
738                 oo = o;
739             } else {                       // True RGBA, not RGB
740                 rgbo = JXG.rgba2rgbo(rgba);
741                 c = rgbo[0];
742                 oo = o*rgbo[1];
743             }
744             if (c === 'none' || c === false) {
745                 this._setAttr(el.rendNode, 'filled', 'false');
746             } else {
747                 this._setAttr(el.rendNode, 'filled', 'true');
748                 this._setAttr(el.rendNode, 'fillcolor', c);
749 
750                 if (JXG.exists(oo) && el.rendNodeFill) {
751                     this._setAttr(el.rendNodeFill, 'opacity', (oo * 100) + '%');
752                 }
753             }
754             if (el.type === JXG.OBJECT_TYPE_IMAGE) {
755                 t = el.rendNode.style.filter.toString();
756                 if (t.match(/alpha/)) {
757                     el.rendNode.style.filter = t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + (oo * 100) + ')');
758                 } else {
759                     el.rendNode.style.filter += ' alpha(opacity = ' + (oo * 100) +')';
760                 }
761             }
762         }
763         el.visPropOld.fillcolor = rgba;
764         el.visPropOld.fillopacity = o;
765     },
766 
767     // already documented in JXG.AbstractRenderer
768     setObjectStrokeColor: function (el, color, opacity) {
769         var rgba = JXG.evaluate(color), c, rgbo,
770             o = JXG.evaluate(opacity), oo,
771             node = el.rendNode, nodeStroke;
772 
773         o = (o > 0) ? o : 0;
774 
775         if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
776             return;
777         }
778 
779         if (JXG.exists(rgba) && rgba !== false) {
780             if (rgba.length!=9) {          // RGB, not RGBA
781                 c = rgba;
782                 oo = o;
783             } else {                       // True RGBA, not RGB
784                 rgbo = JXG.rgba2rgbo(rgba);
785                 c = rgbo[0];
786                 oo = o*rgbo[1];
787             }
788             if (el.type === JXG.OBJECT_TYPE_TEXT) {
789                 oo = Math.round(oo*100);
790                 node.style.filter = ' alpha(opacity = ' + oo +')';
791                 //node.style.filter = node.style['-ms-filter'] = "progid:DXImageTransform.Microsoft.Alpha(Opacity="+oo+")";
792                 node.style.color = c;
793             } else {
794                 if (c !== false) {
795                     this._setAttr(node, 'stroked', 'true');
796                     this._setAttr(node, 'strokecolor', c);
797                 }
798 
799                 nodeStroke = el.rendNodeStroke;
800                 if (JXG.exists(oo) && el.type !== JXG.OBJECT_TYPE_IMAGE) {
801                     this._setAttr(nodeStroke, 'opacity', (oo * 100) + '%');
802                 }
803             }
804         }
805         el.visPropOld.strokecolor = rgba;
806         el.visPropOld.strokeopacity = o;
807     },
808 
809     // already documented in JXG.AbstractRenderer
810     setObjectStrokeWidth: function (el, width) {
811         var w = JXG.evaluate(width),
812             node;
813 
814         if (el.visPropOld.strokewidth === w) {
815             return;
816         }
817 
818         node = el.rendNode;
819         this.setPropertyPrim(node, 'stroked', 'true');
820         if (JXG.exists(w)) {
821             this.setPropertyPrim(node, 'stroke-width', w);
822         }
823         el.visPropOld.strokewidth = w;
824     },
825 
826     // already documented in JXG.AbstractRenderer
827     setShadow: function (el) {
828         var nodeShadow = el.rendNodeShadow;
829 
830         if (!nodeShadow || el.visPropOld.shadow === el.visProp.shadow) {
831             return;
832         }
833 
834         if (el.visProp.shadow) {
835             this._setAttr(nodeShadow, 'On', 'True');
836             this._setAttr(nodeShadow, 'Offset', '3pt,3pt');
837             this._setAttr(nodeShadow, 'Opacity', '60%');
838             this._setAttr(nodeShadow, 'Color', '#aaaaaa');
839         } else {
840             this._setAttr(nodeShadow, 'On', 'False');
841         }
842 
843         el.visPropOld.shadow = el.visProp.shadow;
844     },
845 
846     /* **************************
847      * renderer control
848      * **************************/
849 
850     // already documented in JXG.AbstractRenderer
851     suspendRedraw: function () {
852         this.container.style.display = 'none';
853     },
854 
855     // already documented in JXG.AbstractRenderer
856     unsuspendRedraw: function () {
857         this.container.style.display = '';
858     }
859 
860 });
861