Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / docs / source / Vml.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5   <title>The source code</title>
6   <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
7   <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
8   <style type="text/css">
9     .highlight { display: block; background-color: #ddd; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
17 <body onload="prettyPrint(); highlight();">
18   <pre class="prettyprint lang-js"><span id='Ext-draw-engine-Vml'>/**
19 </span> * @class Ext.draw.engine.Vml
20  * @extends Ext.draw.Surface
21  * Provides specific methods to draw with VML.
22  */
23
24 Ext.define('Ext.draw.engine.Vml', {
25
26     /* Begin Definitions */
27
28     extend: 'Ext.draw.Surface',
29
30     requires: ['Ext.draw.Draw', 'Ext.draw.Color', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.Element'],
31
32     /* End Definitions */
33
34     engine: 'Vml',
35
36     map: {M: &quot;m&quot;, L: &quot;l&quot;, C: &quot;c&quot;, Z: &quot;x&quot;, m: &quot;t&quot;, l: &quot;r&quot;, c: &quot;v&quot;, z: &quot;x&quot;},
37     bitesRe: /([clmz]),?([^clmz]*)/gi,
38     valRe: /-?[^,\s-]+/g,
39     fillUrlRe: /^url\(\s*['&quot;]?([^\)]+?)['&quot;]?\s*\)$/i,
40     pathlike: /^(path|rect)$/,
41     NonVmlPathRe: /[ahqstv]/ig, // Non-VML Pathing ops
42     partialPathRe: /[clmz]/g,
43     fontFamilyRe: /^['&quot;]+|['&quot;]+$/g,
44     baseVmlCls: Ext.baseCSSPrefix + 'vml-base',
45     vmlGroupCls: Ext.baseCSSPrefix + 'vml-group',
46     spriteCls: Ext.baseCSSPrefix + 'vml-sprite',
47     measureSpanCls: Ext.baseCSSPrefix + 'vml-measure-span',
48     zoom: 21600,
49     coordsize: 1000,
50     coordorigin: '0 0',
51
52     // VML uses CSS z-index and therefore doesn't need sprites to be kept in zIndex order
53     orderSpritesByZIndex: false,
54
55     // @private
56     // Convert an SVG standard path into a VML path
57     path2vml: function (path) {
58         var me = this,
59             nonVML =  me.NonVmlPathRe,
60             map = me.map,
61             val = me.valRe,
62             zoom = me.zoom,
63             bites = me.bitesRe,
64             command = Ext.Function.bind(Ext.draw.Draw.pathToAbsolute, Ext.draw.Draw),
65             res, pa, p, r, i, ii, j, jj;
66         if (String(path).match(nonVML)) {
67             command = Ext.Function.bind(Ext.draw.Draw.path2curve, Ext.draw.Draw);
68         } else if (!String(path).match(me.partialPathRe)) {
69             res = String(path).replace(bites, function (all, command, args) {
70                 var vals = [],
71                     isMove = command.toLowerCase() == &quot;m&quot;,
72                     res = map[command];
73                 args.replace(val, function (value) {
74                     if (isMove &amp;&amp; vals[length] == 2) {
75                         res += vals + map[command == &quot;m&quot; ? &quot;l&quot; : &quot;L&quot;];
76                         vals = [];
77                     }
78                     vals.push(Math.round(value * zoom));
79                 });
80                 return res + vals;
81             });
82             return res;
83         }
84         pa = command(path);
85         res = [];
86         for (i = 0, ii = pa.length; i &lt; ii; i++) {
87             p = pa[i];
88             r = pa[i][0].toLowerCase();
89             if (r == &quot;z&quot;) {
90                 r = &quot;x&quot;;
91             }
92             for (j = 1, jj = p.length; j &lt; jj; j++) {
93                 r += Math.round(p[j] * me.zoom) + (j != jj - 1 ? &quot;,&quot; : &quot;&quot;);
94             }
95             res.push(r);
96         }
97         return res.join(&quot; &quot;);
98     },
99
100     // @private - set of attributes which need to be translated from the sprite API to the native browser API
101     translateAttrs: {
102         radius: &quot;r&quot;,
103         radiusX: &quot;rx&quot;,
104         radiusY: &quot;ry&quot;,
105         lineWidth: &quot;stroke-width&quot;,
106         fillOpacity: &quot;fill-opacity&quot;,
107         strokeOpacity: &quot;stroke-opacity&quot;,
108         strokeLinejoin: &quot;stroke-linejoin&quot;
109     },
110
111     // @private - Minimun set of defaults for different types of sprites.
112     minDefaults: {
113         circle: {
114             fill: &quot;none&quot;,
115             stroke: null,
116             &quot;stroke-width&quot;: null,
117             opacity: null,
118             &quot;fill-opacity&quot;: null,
119             &quot;stroke-opacity&quot;: null
120         },
121         ellipse: {
122             cx: 0,
123             cy: 0,
124             rx: 0,
125             ry: 0,
126             fill: &quot;none&quot;,
127             stroke: null,
128             &quot;stroke-width&quot;: null,
129             opacity: null,
130             &quot;fill-opacity&quot;: null,
131             &quot;stroke-opacity&quot;: null
132         },
133         rect: {
134             x: 0,
135             y: 0,
136             width: 0,
137             height: 0,
138             rx: 0,
139             ry: 0,
140             fill: &quot;none&quot;,
141             stroke: null,
142             &quot;stroke-width&quot;: null,
143             opacity: null,
144             &quot;fill-opacity&quot;: null,
145             &quot;stroke-opacity&quot;: null
146         },
147         text: {
148             x: 0,
149             y: 0,
150             &quot;text-anchor&quot;: &quot;start&quot;,
151             font: '10px &quot;Arial&quot;',
152             fill: &quot;#000&quot;,
153             stroke: null,
154             &quot;stroke-width&quot;: null,
155             opacity: null,
156             &quot;fill-opacity&quot;: null,
157             &quot;stroke-opacity&quot;: null
158         },
159         path: {
160             d: &quot;M0,0&quot;,
161             fill: &quot;none&quot;,
162             stroke: null,
163             &quot;stroke-width&quot;: null,
164             opacity: null,
165             &quot;fill-opacity&quot;: null,
166             &quot;stroke-opacity&quot;: null
167         },
168         image: {
169             x: 0,
170             y: 0,
171             width: 0,
172             height: 0,
173             preserveAspectRatio: &quot;none&quot;,
174             opacity: null
175         }
176     },
177
178     // private
179     onMouseEnter: function(e) {
180         this.fireEvent(&quot;mouseenter&quot;, e);
181     },
182
183     // private
184     onMouseLeave: function(e) {
185         this.fireEvent(&quot;mouseleave&quot;, e);
186     },
187
188     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
189     processEvent: function(name, e) {
190         var target = e.getTarget(),
191             surface = this.surface,
192             sprite;
193         this.fireEvent(name, e);
194         sprite = this.items.get(target.id);
195         if (sprite) {
196             sprite.fireEvent(name, sprite, e);
197         }
198     },
199
200     // Create the VML element/elements and append them to the DOM
201     createSpriteElement: function(sprite) {
202         var me = this,
203             attr = sprite.attr,
204             type = sprite.type,
205             zoom = me.zoom,
206             vml = sprite.vml || (sprite.vml = {}),
207             round = Math.round,
208             el = me.createNode('shape'),
209             path, skew, textPath;
210
211         el.coordsize = zoom + ' ' + zoom;
212         el.coordorigin = attr.coordorigin || &quot;0 0&quot;;
213         Ext.get(el).addCls(me.spriteCls);
214         if (type == &quot;text&quot;) {
215             vml.path = path = me.createNode(&quot;path&quot;);
216             path.textpathok = true;
217             vml.textpath = textPath = me.createNode(&quot;textpath&quot;);
218             textPath.on = true;
219             el.appendChild(textPath);
220             el.appendChild(path);
221         }
222         el.id = sprite.id;
223         sprite.el = Ext.get(el);
224         me.el.appendChild(el);
225         if (type !== 'image') {
226             skew = me.createNode(&quot;skew&quot;);
227             skew.on = true;
228             el.appendChild(skew);
229             sprite.skew = skew;
230         }
231         sprite.matrix = Ext.create('Ext.draw.Matrix');
232         sprite.bbox = {
233             plain: null,
234             transform: null
235         };
236         sprite.fireEvent(&quot;render&quot;, sprite);
237         return sprite.el;
238     },
239
240     // @private - Get bounding box for the sprite.  The Sprite itself has the public method.
241     getBBox: function (sprite, isWithoutTransform) {
242         var realPath = this[&quot;getPath&quot; + sprite.type](sprite);
243         if (isWithoutTransform) {
244             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
245             return sprite.bbox.plain;
246         }
247         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
248         return sprite.bbox.transform;
249     },
250
251     getBBoxText: function (sprite) {
252         var vml = sprite.vml;
253         return {
254             x: vml.X + (vml.bbx || 0) - vml.W / 2,
255             y: vml.Y - vml.H / 2,
256             width: vml.W,
257             height: vml.H
258         };
259     },
260
261     applyAttrs: function (sprite) {
262         var me = this,
263             vml = sprite.vml,
264             group = sprite.group,
265             spriteAttr = sprite.attr,
266             el = sprite.el,
267             dom = el.dom,
268             style, name, groups, i, ln, scrubbedAttrs, font, key, bbox;
269
270         if (group) {
271             groups = [].concat(group);
272             ln = groups.length;
273             for (i = 0; i &lt; ln; i++) {
274                 group = groups[i];
275                 me.getGroup(group).add(sprite);
276             }
277             delete sprite.group;
278         }
279         scrubbedAttrs = me.scrubAttrs(sprite) || {};
280
281         if (sprite.zIndexDirty) {
282             me.setZIndex(sprite);
283         }
284
285         // Apply minimum default attributes
286         Ext.applyIf(scrubbedAttrs, me.minDefaults[sprite.type]);
287
288         if (dom.href) {
289             dom.href = scrubbedAttrs.href;
290         }
291         if (dom.title) {
292             dom.title = scrubbedAttrs.title;
293         }
294         if (dom.target) {
295             dom.target = scrubbedAttrs.target;
296         }
297         if (dom.cursor) {
298             dom.cursor = scrubbedAttrs.cursor;
299         }
300
301         // Change visibility
302         if (sprite.dirtyHidden) {
303             (scrubbedAttrs.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
304             sprite.dirtyHidden = false;
305         }
306
307         // Update path
308         if (sprite.dirtyPath) {
309             if (sprite.type == &quot;circle&quot; || sprite.type == &quot;ellipse&quot;) {
310                 var cx = scrubbedAttrs.x,
311                     cy = scrubbedAttrs.y,
312                     rx = scrubbedAttrs.rx || scrubbedAttrs.r || 0,
313                     ry = scrubbedAttrs.ry || scrubbedAttrs.r || 0;
314                 dom.path = Ext.String.format(&quot;ar{0},{1},{2},{3},{4},{1},{4},{1}&quot;,
315                             Math.round((cx - rx) * me.zoom),
316                             Math.round((cy - ry) * me.zoom),
317                             Math.round((cx + rx) * me.zoom),
318                             Math.round((cy + ry) * me.zoom),
319                             Math.round(cx * me.zoom));
320                 sprite.dirtyPath = false;
321             }
322             else if (sprite.type !== &quot;text&quot;) {
323                 sprite.attr.path = scrubbedAttrs.path = me.setPaths(sprite, scrubbedAttrs) || scrubbedAttrs.path;
324                 dom.path = me.path2vml(scrubbedAttrs.path);
325                 sprite.dirtyPath = false;
326             }
327         }
328
329         // Apply clipping
330         if (&quot;clip-rect&quot; in scrubbedAttrs) {
331             me.setClip(sprite, scrubbedAttrs);
332         }
333
334         // Handle text (special handling required)
335         if (sprite.type == &quot;text&quot;) {
336             me.setTextAttributes(sprite, scrubbedAttrs);
337         }
338
339         // Handle fill and opacity
340         if (sprite.type == 'image' || scrubbedAttrs.opacity  || scrubbedAttrs['fill-opacity'] || scrubbedAttrs.fill) {
341             me.setFill(sprite, scrubbedAttrs);
342         }
343
344         // Handle stroke (all fills require a stroke element)
345         if (scrubbedAttrs.stroke || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
346             me.setStroke(sprite, scrubbedAttrs);
347         }
348         
349         //set styles
350         style = spriteAttr.style;
351         if (style) {
352             el.setStyle(style);
353         }
354
355         sprite.dirty = false;
356     },
357
358     setZIndex: function(sprite) {
359         if (sprite.el) {
360             if (sprite.attr.zIndex != undefined) {
361                 sprite.el.setStyle('zIndex', sprite.attr.zIndex);
362             }
363             sprite.zIndexDirty = false;
364         }
365     },
366
367     // Normalize all virtualized types into paths.
368     setPaths: function(sprite, params) {
369         var spriteAttr = sprite.attr;
370         // Clear bbox cache
371         sprite.bbox.plain = null;
372         sprite.bbox.transform = null;
373         if (sprite.type == 'circle') {
374             spriteAttr.rx = spriteAttr.ry = params.r;
375             return Ext.draw.Draw.ellipsePath(sprite);
376         }
377         else if (sprite.type == 'ellipse') {
378             spriteAttr.rx = params.rx;
379             spriteAttr.ry = params.ry;
380             return Ext.draw.Draw.ellipsePath(sprite);
381         }
382         else if (sprite.type == 'rect' || sprite.type == 'image') {
383             spriteAttr.rx = spriteAttr.ry = params.r;
384             return Ext.draw.Draw.rectPath(sprite);
385         }
386         else if (sprite.type == 'path' &amp;&amp; spriteAttr.path) {
387             return Ext.draw.Draw.pathToAbsolute(spriteAttr.path);
388         }
389         return false;
390     },
391
392     setFill: function(sprite, params) {
393         var me = this,
394             el = sprite.el,
395             dom = el.dom,
396             fillEl = dom.getElementsByTagName('fill')[0],
397             opacity, gradient, fillUrl, rotation, angle;
398
399         if (fillEl) {
400             dom.removeChild(fillEl);
401         } else {
402             fillEl = me.createNode('fill');
403         }
404         if (Ext.isArray(params.fill)) {
405             params.fill = params.fill[0];
406         }
407         if (sprite.type == 'image') {
408             fillEl.on = true;
409             fillEl.src = params.src;
410             fillEl.type = &quot;tile&quot;;
411             fillEl.rotate = true;
412         } else if (params.fill == &quot;none&quot;) {
413             fillEl.on = false;
414         } else {
415             if (typeof params.opacity == &quot;number&quot;) {
416                 fillEl.opacity = params.opacity;
417             }
418             if (typeof params[&quot;fill-opacity&quot;] == &quot;number&quot;) {
419                 fillEl.opacity = params[&quot;fill-opacity&quot;];
420             }
421             fillEl.on = true;
422             if (typeof params.fill == &quot;string&quot;) {
423                 fillUrl = params.fill.match(me.fillUrlRe);
424                 if (fillUrl) {
425                     fillUrl = fillUrl[1];
426                     // If the URL matches one of the registered gradients, render that gradient
427                     if (fillUrl.charAt(0) == &quot;#&quot;) {
428                         gradient = me.gradientsColl.getByKey(fillUrl.substring(1));
429                     }
430                     if (gradient) {
431                         // VML angle is offset and inverted from standard, and must be adjusted to match rotation transform
432                         rotation = params.rotation;
433                         angle = -(gradient.angle + 270 + (rotation ? rotation.degrees : 0)) % 360;
434                         // IE will flip the angle at 0 degrees...
435                         if (angle === 0) {
436                             angle = 180;
437                         }
438                         fillEl.angle = angle;
439                         fillEl.type = &quot;gradient&quot;;
440                         fillEl.method = &quot;sigma&quot;;
441                         fillEl.colors = gradient.colors;
442                     }
443                     // Otherwise treat it as an image
444                     else {
445                         fillEl.src = fillUrl;
446                         fillEl.type = &quot;tile&quot;;
447                         fillEl.rotate = true;
448                     }
449                 }
450                 else {
451                     fillEl.color = Ext.draw.Color.toHex(params.fill) || params.fill;
452                     fillEl.src = &quot;&quot;;
453                     fillEl.type = &quot;solid&quot;;
454                 }
455             }
456         }
457         dom.appendChild(fillEl);
458     },
459
460     setStroke: function(sprite, params) {
461         var me = this,
462             el = sprite.el.dom,
463             strokeEl = sprite.strokeEl,
464             newStroke = false,
465             width, opacity;
466
467         if (!strokeEl) {
468             strokeEl = sprite.strokeEl = me.createNode(&quot;stroke&quot;);
469             newStroke = true;
470         }
471         if (Ext.isArray(params.stroke)) {
472             params.stroke = params.stroke[0];
473         }
474         if (!params.stroke || params.stroke == &quot;none&quot; || params.stroke == 0 || params[&quot;stroke-width&quot;] == 0) {
475             strokeEl.on = false;
476         }
477         else {
478             strokeEl.on = true;
479             if (params.stroke &amp;&amp; !params.stroke.match(me.fillUrlRe)) {
480                 // VML does NOT support a gradient stroke :(
481                 strokeEl.color = Ext.draw.Color.toHex(params.stroke);
482             }
483             strokeEl.joinstyle = params[&quot;stroke-linejoin&quot;];
484             strokeEl.endcap = params[&quot;stroke-linecap&quot;] || &quot;round&quot;;
485             strokeEl.miterlimit = params[&quot;stroke-miterlimit&quot;] || 8;
486             width = parseFloat(params[&quot;stroke-width&quot;] || 1) * 0.75;
487             opacity = params[&quot;stroke-opacity&quot;] || 1;
488             // VML Does not support stroke widths under 1, so we're going to fiddle with stroke-opacity instead.
489             if (Ext.isNumber(width) &amp;&amp; width &lt; 1) {
490                 strokeEl.weight = 1;
491                 strokeEl.opacity = opacity * width;
492             }
493             else {
494                 strokeEl.weight = width;
495                 strokeEl.opacity = opacity;
496             }
497         }
498         if (newStroke) {
499             el.appendChild(strokeEl);
500         }
501     },
502
503     setClip: function(sprite, params) {
504         var me = this,
505             el = sprite.el,
506             clipEl = sprite.clipEl,
507             rect = String(params[&quot;clip-rect&quot;]).split(me.separatorRe);
508         if (!clipEl) {
509             clipEl = sprite.clipEl = me.el.insertFirst(Ext.getDoc().dom.createElement(&quot;div&quot;));
510             clipEl.addCls(Ext.baseCSSPrefix + 'vml-sprite');
511         }
512         if (rect.length == 4) {
513             rect[2] = +rect[2] + (+rect[0]);
514             rect[3] = +rect[3] + (+rect[1]);
515             clipEl.setStyle(&quot;clip&quot;, Ext.String.format(&quot;rect({1}px {2}px {3}px {0}px)&quot;, rect[0], rect[1], rect[2], rect[3]));
516             clipEl.setSize(me.el.width, me.el.height);
517         }
518         else {
519             clipEl.setStyle(&quot;clip&quot;, &quot;&quot;);
520         }
521     },
522
523     setTextAttributes: function(sprite, params) {
524         var me = this,
525             vml = sprite.vml,
526             textStyle = vml.textpath.style,
527             spanCacheStyle = me.span.style,
528             zoom = me.zoom,
529             round = Math.round,
530             fontObj = {
531                 fontSize: &quot;font-size&quot;,
532                 fontWeight: &quot;font-weight&quot;,
533                 fontStyle: &quot;font-style&quot;
534             },
535             fontProp,
536             paramProp;
537         if (sprite.dirtyFont) {
538             if (params.font) {
539                 textStyle.font = spanCacheStyle.font = params.font;
540             }
541             if (params[&quot;font-family&quot;]) {
542                 textStyle.fontFamily = '&quot;' + params[&quot;font-family&quot;].split(&quot;,&quot;)[0].replace(me.fontFamilyRe, &quot;&quot;) + '&quot;';
543                 spanCacheStyle.fontFamily = params[&quot;font-family&quot;];
544             }
545
546             for (fontProp in fontObj) {
547                 paramProp = params[fontObj[fontProp]];
548                 if (paramProp) {
549                     textStyle[fontProp] = spanCacheStyle[fontProp] = paramProp;
550                 }
551             }
552
553             me.setText(sprite, params.text);
554             
555             if (vml.textpath.string) {
556                 me.span.innerHTML = String(vml.textpath.string).replace(/&lt;/g, &quot;&amp;#60;&quot;).replace(/&amp;/g, &quot;&amp;#38;&quot;).replace(/\n/g, &quot;&lt;br&gt;&quot;);
557             }
558             vml.W = me.span.offsetWidth;
559             vml.H = me.span.offsetHeight + 2; // TODO handle baseline differences and offset in VML Textpath
560
561             // text-anchor emulation
562             if (params[&quot;text-anchor&quot;] == &quot;middle&quot;) {
563                 textStyle[&quot;v-text-align&quot;] = &quot;center&quot;;
564             }
565             else if (params[&quot;text-anchor&quot;] == &quot;end&quot;) {
566                 textStyle[&quot;v-text-align&quot;] = &quot;right&quot;;
567                 vml.bbx = -Math.round(vml.W / 2);
568             }
569             else {
570                 textStyle[&quot;v-text-align&quot;] = &quot;left&quot;;
571                 vml.bbx = Math.round(vml.W / 2);
572             }
573         }
574         vml.X = params.x;
575         vml.Y = params.y;
576         vml.path.v = Ext.String.format(&quot;m{0},{1}l{2},{1}&quot;, Math.round(vml.X * zoom), Math.round(vml.Y * zoom), Math.round(vml.X * zoom) + 1);
577         // Clear bbox cache
578         sprite.bbox.plain = null;
579         sprite.bbox.transform = null;
580         sprite.dirtyFont = false;
581     },
582     
583     setText: function(sprite, text) {
584         sprite.vml.textpath.string = Ext.htmlDecode(text);
585     },
586
587     hide: function() {
588         this.el.hide();
589     },
590
591     show: function() {
592         this.el.show();
593     },
594
595     hidePrim: function(sprite) {
596         sprite.el.addCls(Ext.baseCSSPrefix + 'hide-visibility');
597     },
598
599     showPrim: function(sprite) {
600         sprite.el.removeCls(Ext.baseCSSPrefix + 'hide-visibility');
601     },
602
603     setSize: function(width, height) {
604         var me = this;
605         width = width || me.width;
606         height = height || me.height;
607         me.width = width;
608         me.height = height;
609
610         if (me.el) {
611             // Size outer div
612             if (width != undefined) {
613                 me.el.setWidth(width);
614             }
615             if (height != undefined) {
616                 me.el.setHeight(height);
617             }
618
619             // Handle viewBox sizing
620             me.applyViewBox();
621
622             me.callParent(arguments);
623         }
624     },
625
626     setViewBox: function(x, y, width, height) {
627         this.callParent(arguments);
628         this.viewBox = {
629             x: x,
630             y: y,
631             width: width,
632             height: height
633         };
634         this.applyViewBox();
635     },
636
637 <span id='Ext-draw-engine-Vml-method-applyViewBox'>    /**
638 </span>     * @private Using the current viewBox property and the surface's width and height, calculate the
639      * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
640      */
641     applyViewBox: function() {
642         var me = this,
643             viewBox = me.viewBox,
644             width = me.width,
645             height = me.height,
646             viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
647             relativeHeight, relativeWidth, size;
648
649         if (viewBox &amp;&amp; (width || height)) {
650             viewBoxX = viewBox.x;
651             viewBoxY = viewBox.y;
652             viewBoxWidth = viewBox.width;
653             viewBoxHeight = viewBox.height;
654             relativeHeight = height / viewBoxHeight;
655             relativeWidth = width / viewBoxWidth;
656
657             if (viewBoxWidth * relativeHeight &lt; width) {
658                 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
659             }
660             if (viewBoxHeight * relativeWidth &lt; height) {
661                 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
662             }
663
664             size = 1 / Math.max(viewBoxWidth / width, viewBoxHeight / height);
665
666             me.viewBoxShift = {
667                 dx: -viewBoxX,
668                 dy: -viewBoxY,
669                 scale: size
670             };
671             me.items.each(function(item) {
672                 me.transform(item);
673             });
674         }
675     },
676
677     onAdd: function(item) {
678         this.callParent(arguments);
679         if (this.el) {
680             this.renderItem(item);
681         }
682     },
683
684     onRemove: function(sprite) {
685         if (sprite.el) {
686             sprite.el.remove();
687             delete sprite.el;
688         }
689         this.callParent(arguments);
690     },
691
692     // VML Node factory method (createNode)
693     createNode : (function () {
694         try {
695             var doc = Ext.getDoc().dom;
696             if (!doc.namespaces.rvml) {
697                 doc.namespaces.add(&quot;rvml&quot;, &quot;urn:schemas-microsoft-com:vml&quot;);
698             }
699             return function (tagName) {
700                 return doc.createElement(&quot;&lt;rvml:&quot; + tagName + ' class=&quot;rvml&quot;&gt;');
701             };
702         } catch (e) {
703             return function (tagName) {
704                 return doc.createElement(&quot;&lt;&quot; + tagName + ' xmlns=&quot;urn:schemas-microsoft.com:vml&quot; class=&quot;rvml&quot;&gt;');
705             };
706         }
707     })(),
708
709     render: function (container) {
710         var me = this,
711             doc = Ext.getDoc().dom;
712
713         if (!me.el) {
714             var el = doc.createElement(&quot;div&quot;);
715             me.el = Ext.get(el);
716             me.el.addCls(me.baseVmlCls);
717
718             // Measuring span (offscrren)
719             me.span = doc.createElement(&quot;span&quot;);
720             Ext.get(me.span).addCls(me.measureSpanCls);
721             el.appendChild(me.span);
722             me.el.setSize(me.width || 10, me.height || 10);
723             container.appendChild(el);
724             me.el.on({
725                 scope: me,
726                 mouseup: me.onMouseUp,
727                 mousedown: me.onMouseDown,
728                 mouseover: me.onMouseOver,
729                 mouseout: me.onMouseOut,
730                 mousemove: me.onMouseMove,
731                 mouseenter: me.onMouseEnter,
732                 mouseleave: me.onMouseLeave,
733                 click: me.onClick
734             });
735         }
736         me.renderAll();
737     },
738
739     renderAll: function() {
740         this.items.each(this.renderItem, this);
741     },
742
743     redraw: function(sprite) {
744         sprite.dirty = true;
745         this.renderItem(sprite);
746     },
747
748     renderItem: function (sprite) {
749         // Does the surface element exist?
750         if (!this.el) {
751             return;
752         }
753
754         // Create sprite element if necessary
755         if (!sprite.el) {
756             this.createSpriteElement(sprite);
757         }
758
759         if (sprite.dirty) {
760             this.applyAttrs(sprite);
761             if (sprite.dirtyTransform) {
762                 this.applyTransformations(sprite);
763             }
764         }
765     },
766
767     rotationCompensation: function (deg, dx, dy) {
768         var matrix = Ext.create('Ext.draw.Matrix');
769         matrix.rotate(-deg, 0.5, 0.5);
770         return {
771             x: matrix.x(dx, dy),
772             y: matrix.y(dx, dy)
773         };
774     },
775
776     extractTransform: function (sprite) {
777         var me = this,
778             matrix = Ext.create('Ext.draw.Matrix'), scale,
779             transformstions, tranformationsLength,
780             transform, i = 0,
781             shift = me.viewBoxShift;
782
783         for(transformstions = sprite.transformations, tranformationsLength = transformstions.length;
784             i &lt; tranformationsLength; i ++) {
785             transform = transformstions[i];
786             switch (transform.type) {
787                 case 'translate' :
788                     matrix.translate(transform.x, transform.y);
789                     break;
790                 case 'rotate':
791                     matrix.rotate(transform.degrees, transform.x, transform.y);
792                     break;
793                 case 'scale':
794                     matrix.scale(transform.x || transform.scale, transform.y || transform.scale, transform.centerX, transform.centerY);
795                     break;
796             }
797         }
798
799         if (shift) {
800             matrix.add(1, 0, 0, 1, shift.dx, shift.dy);
801             matrix.prepend(shift.scale, 0, 0, shift.scale, 0, 0);
802         }
803         
804         return sprite.matrix = matrix;
805     },
806
807     setSimpleCoords: function(sprite, sx, sy, dx, dy, rotate) {
808         var me = this,
809             matrix = sprite.matrix,
810             dom = sprite.el.dom,
811             style = dom.style,
812             yFlipper = 1,
813             flip = &quot;&quot;,
814             fill = dom.getElementsByTagName('fill')[0],
815             kx = me.zoom / sx,
816             ky = me.zoom / sy,
817             rotationCompensation;
818         if (!sx || !sy) {
819             return;
820         }
821         dom.coordsize = Math.abs(kx) + ' ' + Math.abs(ky);
822         style.rotation = rotate * (sx * sy &lt; 0 ? -1 : 1);
823         if (rotate) {
824             rotationCompensation = me.rotationCompensation(rotate, dx, dy);
825             dx = rotationCompensation.x;
826             dy = rotationCompensation.y;
827         }
828         if (sx &lt; 0) {
829             flip += &quot;x&quot;
830         }
831         if (sy &lt; 0) {
832             flip += &quot; y&quot;;
833             yFlipper = -1;
834         }
835         style.flip = flip;
836         dom.coordorigin = (dx * -kx) + ' ' + (dy * -ky);
837         if (fill) {
838             dom.removeChild(fill);
839             rotationCompensation = me.rotationCompensation(rotate, matrix.x(sprite.x, sprite.y), matrix.y(sprite.x, sprite.y));
840             fill.position = rotationCompensation.x * yFlipper + ' ' + rotationCompensation.y * yFlipper;
841             fill.size = sprite.width * Math.abs(sx) + ' ' + sprite.height * Math.abs(sy);
842             dom.appendChild(fill);
843         }
844     },
845
846     transform : function (sprite) {
847         var me = this,
848             el = sprite.el,
849             skew = sprite.skew,
850             dom = el.dom,
851             domStyle = dom.style,
852             matrix = me.extractTransform(sprite).clone(),
853             split, zoom = me.zoom,
854             fill = dom.getElementsByTagName('fill')[0],
855             isPatt = !String(sprite.fill).indexOf(&quot;url(&quot;),
856             offset, c;
857
858
859         // Hide element while we transform
860
861         if (sprite.type != &quot;image&quot; &amp;&amp; skew &amp;&amp; !isPatt) {
862             // matrix transform via VML skew
863             skew.matrix = matrix.toString();
864             // skew.offset = '32767,1' OK
865             // skew.offset = '32768,1' Crash
866             // M$, R U kidding??
867             offset = matrix.offset();
868             if (offset[0] &gt; 32767) {
869                 offset[0] = 32767;
870             } else if (offset[0] &lt; -32768) {
871                 offset[0] = -32768
872             }
873             if (offset[1] &gt; 32767) {
874                 offset[1] = 32767;
875             } else if (offset[1] &lt; -32768) {
876                 offset[1] = -32768
877             }
878             skew.offset = offset;
879         } else {
880             if (skew) {
881                 skew.matrix = &quot;1 0 0 1&quot;;
882                 skew.offset = &quot;0 0&quot;;
883             }
884             split = matrix.split();
885             if (split.isSimple) {
886                 domStyle.filter = '';
887                 me.setSimpleCoords(sprite, split.scaleX, split.scaleY, split.translateX, split.translateY, split.rotate / Math.PI * 180);
888             } else {
889                 domStyle.filter = matrix.toFilter();
890                 var bb = me.getBBox(sprite),
891                     dx = bb.x - sprite.x,
892                     dy = bb.y - sprite.y;
893                 dom.coordorigin = (dx * -zoom) + ' ' + (dy * -zoom);
894                 if (fill) {
895                     dom.removeChild(fill);
896                     fill.position = dx + ' ' + dy;
897                     fill.size = sprite.width * sprite.scale.x + ' ' + sprite.height * 1.1;
898                     dom.appendChild(fill);
899                 }
900             }
901         }
902     },
903
904     createItem: function (config) {
905         return Ext.create('Ext.draw.Sprite', config);
906     },
907
908     getRegion: function() {
909         return this.el.getRegion();
910     },
911
912     addCls: function(sprite, className) {
913         if (sprite &amp;&amp; sprite.el) {
914             sprite.el.addCls(className);
915         }
916     },
917
918     removeCls: function(sprite, className) {
919         if (sprite &amp;&amp; sprite.el) {
920             sprite.el.removeCls(className);
921         }
922     },
923
924 <span id='Ext-draw-engine-Vml-method-addGradient'>    /**
925 </span>     * Adds a definition to this Surface for a linear gradient. We convert the gradient definition
926      * to its corresponding VML attributes and store it for later use by individual sprites.
927      * @param {Object} gradient
928      */
929     addGradient: function(gradient) {
930         var gradients = this.gradientsColl || (this.gradientsColl = Ext.create('Ext.util.MixedCollection')),
931             colors = [],
932             stops = Ext.create('Ext.util.MixedCollection');
933
934         // Build colors string
935         stops.addAll(gradient.stops);
936         stops.sortByKey(&quot;ASC&quot;, function(a, b) {
937             a = parseInt(a, 10);
938             b = parseInt(b, 10);
939             return a &gt; b ? 1 : (a &lt; b ? -1 : 0);
940         });
941         stops.eachKey(function(k, v) {
942             colors.push(k + &quot;% &quot; + v.color);
943         });
944
945         gradients.add(gradient.id, {
946             colors: colors.join(&quot;,&quot;),
947             angle: gradient.angle
948         });
949     },
950
951     destroy: function() {
952         var me = this;
953         
954         me.callParent(arguments);
955         if (me.el) {
956             me.el.remove();
957         }
958         delete me.el;
959     }
960 });
961 </pre>
962 </body>
963 </html>