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