Upgrade to ExtJS 4.0.1 - Released 05/18/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             viewBox = me.viewBox,
615             scaleX, scaleY, items, i, len;
616         width = width || me.width;
617         height = height || me.height;
618         me.width = width;
619         me.height = height;
620
621         if (!me.el) {
622             return;
623         }
624
625         // Size outer div
626         if (width != undefined) {
627             me.el.setWidth(width);
628         }
629         if (height != undefined) {
630             me.el.setHeight(height);
631         }
632
633         // Handle viewBox sizing
634         if (viewBox &amp;&amp; (width || height)) {
635             var viewBoxX = viewBox.x,
636                 viewBoxY = viewBox.y,
637                 viewBoxWidth = viewBox.width,
638                 viewBoxHeight = viewBox.height,
639                 relativeHeight = height / viewBoxHeight,
640                 relativeWidth = width / viewBoxWidth,
641                 size;
642             if (viewBoxWidth * relativeHeight &lt; width) {
643                 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
644             }
645             if (viewBoxHeight * relativeWidth &lt; height) {
646                 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
647             }
648             size = 1 / Math.max(viewBoxWidth / width, viewBoxHeight / height);
649             // Scale and translate group
650             me.viewBoxShift = {
651                 dx: -viewBoxX,
652                 dy: -viewBoxY,
653                 scale: size
654             };
655             items = me.items.items;
656             for (i = 0, len = items.length; i &lt; len; i++) {
657                 me.transform(items[i]);
658             }
659         }
660         this.callParent(arguments);
661     },
662
663     setViewBox: function(x, y, width, height) {
664         this.callParent(arguments);
665         this.viewBox = {
666             x: x,
667             y: y,
668             width: width,
669             height: height
670         };
671     },
672
673     onAdd: function(item) {
674         this.callParent(arguments);
675         if (this.el) {
676             this.renderItem(item);
677         }
678     },
679
680     onRemove: function(sprite) {
681         if (sprite.el) {
682             sprite.el.remove();
683             delete sprite.el;
684         }
685         this.callParent(arguments);
686     },
687
688     render: function (container) {
689         var me = this,
690             doc = Ext.getDoc().dom;
691         // VML Node factory method (createNode)
692         if (!me.createNode) {
693             try {
694                 if (!doc.namespaces.rvml) {
695                     doc.namespaces.add(&quot;rvml&quot;, &quot;urn:schemas-microsoft-com:vml&quot;);
696                 }
697                 me.createNode = function (tagName) {
698                     return doc.createElement(&quot;&lt;rvml:&quot; + tagName + ' class=&quot;rvml&quot;&gt;');
699                 };
700             } catch (e) {
701                 me.createNode = function (tagName) {
702                     return doc.createElement(&quot;&lt;&quot; + tagName + ' xmlns=&quot;urn:schemas-microsoft.com:vml&quot; class=&quot;rvml&quot;&gt;');
703                 };
704             }
705         }
706
707         if (!me.el) {
708             var el = doc.createElement(&quot;div&quot;);
709             me.el = Ext.get(el);
710             me.el.addCls(me.baseVmlCls);
711
712             // Measuring span (offscrren)
713             me.span = doc.createElement(&quot;span&quot;);
714             Ext.get(me.span).addCls(me.measureSpanCls);
715             el.appendChild(me.span);
716             me.el.setSize(me.width || 10, me.height || 10);
717             container.appendChild(el);
718             me.el.on({
719                 scope: me,
720                 mouseup: me.onMouseUp,
721                 mousedown: me.onMouseDown,
722                 mouseover: me.onMouseOver,
723                 mouseout: me.onMouseOut,
724                 mousemove: me.onMouseMove,
725                 mouseenter: me.onMouseEnter,
726                 mouseleave: me.onMouseLeave,
727                 click: me.onClick
728             });
729         }
730         me.renderAll();
731     },
732
733     renderAll: function() {
734         this.items.each(this.renderItem, this);
735     },
736
737     redraw: function(sprite) {
738         sprite.dirty = true;
739         this.renderItem(sprite);
740     },
741
742     renderItem: function (sprite) {
743         // Does the surface element exist?
744         if (!this.el) {
745             return;
746         }
747
748         // Create sprite element if necessary
749         if (!sprite.el) {
750             this.createSpriteElement(sprite);
751         }
752
753         if (sprite.dirty) {
754             this.applyAttrs(sprite);
755             if (sprite.dirtyTransform) {
756                 this.applyTransformations(sprite);
757             }
758         }
759     },
760
761     rotationCompensation: function (deg, dx, dy) {
762         var matrix = Ext.create('Ext.draw.Matrix');
763         matrix.rotate(-deg, 0.5, 0.5);
764         return {
765             x: matrix.x(dx, dy),
766             y: matrix.y(dx, dy)
767         };
768     },
769
770     transform: function(sprite) {
771         var me = this,
772             matrix = Ext.create('Ext.draw.Matrix'),
773             transforms = sprite.transformations,
774             transformsLength = transforms.length,
775             i = 0,
776             deltaDegrees = 0,
777             deltaScaleX = 1,
778             deltaScaleY = 1,
779             flip = &quot;&quot;,
780             el = sprite.el,
781             dom = el.dom,
782             domStyle = dom.style,
783             zoom = me.zoom,
784             skew = sprite.skew,
785             deltaX, deltaY, transform, type, compensate, y, fill, newAngle,zoomScaleX, zoomScaleY, newOrigin;
786
787         for (; i &lt; transformsLength; i++) {
788             transform = transforms[i];
789             type = transform.type;
790             if (type == &quot;translate&quot;) {
791                 matrix.translate(transform.x, transform.y);
792             }
793             else if (type == &quot;rotate&quot;) {
794                 matrix.rotate(transform.degrees, transform.x, transform.y);
795                 deltaDegrees += transform.degrees;
796             }
797             else if (type == &quot;scale&quot;) {
798                 matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
799                 deltaScaleX *= transform.x;
800                 deltaScaleY *= transform.y;
801             }
802         }
803
804         if (me.viewBoxShift) {
805             matrix.scale(me.viewBoxShift.scale, me.viewBoxShift.scale, -1, -1);
806             matrix.add(1, 0, 0, 1, me.viewBoxShift.dx, me.viewBoxShift.dy);
807         }
808
809         sprite.matrix = matrix;
810
811
812         // Hide element while we transform
813
814         if (sprite.type != &quot;image&quot; &amp;&amp; skew) {
815             // matrix transform via VML skew
816             skew.matrix = matrix.toString();
817             skew.offset = matrix.offset();
818         }
819         else {
820             deltaX = matrix.matrix[0][2];
821             deltaY = matrix.matrix[1][2];
822             // Scale via coordsize property
823             zoomScaleX = zoom / deltaScaleX;
824             zoomScaleY = zoom / deltaScaleY;
825
826             dom.coordsize = Math.abs(zoomScaleX) + &quot; &quot; + Math.abs(zoomScaleY);
827
828             // Rotate via rotation property
829             newAngle = deltaDegrees * (deltaScaleX * ((deltaScaleY &lt; 0) ? -1 : 1));
830             if (newAngle != domStyle.rotation &amp;&amp; !(newAngle === 0 &amp;&amp; !domStyle.rotation)) {
831                 domStyle.rotation = newAngle;
832             }
833             if (deltaDegrees) {
834                 // Compensate x/y position due to rotation
835                 compensate = me.rotationCompensation(deltaDegrees, deltaX, deltaY);
836                 deltaX = compensate.x;
837                 deltaY = compensate.y;
838             }
839
840             // Handle negative scaling via flipping
841             if (deltaScaleX &lt; 0) {
842                 flip += &quot;x&quot;;
843             }
844             if (deltaScaleY &lt; 0) {
845                 flip += &quot; y&quot;;
846                 y = -1;
847             }
848             if (flip != &quot;&quot; &amp;&amp; !dom.style.flip) {
849                 domStyle.flip = flip;
850             }
851
852             // Translate via coordorigin property
853             newOrigin = (deltaX * -zoomScaleX) + &quot; &quot; + (deltaY * -zoomScaleY);
854             if (newOrigin != dom.coordorigin) {
855                 dom.coordorigin = (deltaX * -zoomScaleX) + &quot; &quot; + (deltaY * -zoomScaleY);
856             }
857         }
858     },
859
860     createItem: function (config) {
861         return Ext.create('Ext.draw.Sprite', config);
862     },
863
864     getRegion: function() {
865         return this.el.getRegion();
866     },
867
868     addCls: function(sprite, className) {
869         if (sprite &amp;&amp; sprite.el) {
870             sprite.el.addCls(className);
871         }
872     },
873
874     removeCls: function(sprite, className) {
875         if (sprite &amp;&amp; sprite.el) {
876             sprite.el.removeCls(className);
877         }
878     },
879
880 <span id='Ext-draw-engine-Vml-method-addGradient'>    /**
881 </span>     * Adds a definition to this Surface for a linear gradient. We convert the gradient definition
882      * to its corresponding VML attributes and store it for later use by individual sprites.
883      * @param {Object} gradient
884      */
885     addGradient: function(gradient) {
886         var gradients = this.gradientsColl || (this.gradientsColl = Ext.create('Ext.util.MixedCollection')),
887             colors = [],
888             stops = Ext.create('Ext.util.MixedCollection');
889
890         // Build colors string
891         stops.addAll(gradient.stops);
892         stops.sortByKey(&quot;ASC&quot;, function(a, b) {
893             a = parseInt(a, 10);
894             b = parseInt(b, 10);
895             return a &gt; b ? 1 : (a &lt; b ? -1 : 0);
896         });
897         stops.eachKey(function(k, v) {
898             colors.push(k + &quot;% &quot; + v.color);
899         });
900
901         gradients.add(gradient.id, {
902             colors: colors.join(&quot;,&quot;),
903             angle: gradient.angle
904         });
905     },
906
907     destroy: function() {
908         var me = this;
909         
910         me.callParent(arguments);
911         if (me.el) {
912             me.el.remove();
913         }
914         delete me.el;
915     }
916 });
917 </pre>
918 </body>
919 </html>