Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / docs / source / Surface.html
1 <!DOCTYPE html><html><head><title>Sencha Documentation Project</title><link rel="stylesheet" href="../reset.css" type="text/css"><link rel="stylesheet" href="../prettify.css" type="text/css"><link rel="stylesheet" href="../prettify_sa.css" type="text/css"><script type="text/javascript" src="../prettify.js"></script></head><body onload="prettyPrint()"><pre class="prettyprint"><pre><span id='Ext-draw.Surface'>/**
2 </span> * @class Ext.draw.Surface
3  * @extends Object
4  *
5  * A Surface is an interface to render methods inside a draw {@link Ext.draw.Component}.
6  * A Surface contains methods to render sprites, get bounding boxes of sprites, add
7  * sprites to the canvas, initialize other graphic components, etc. One of the most used
8  * methods for this class is the `add` method, to add Sprites to the surface.
9  *
10  * Most of the Surface methods are abstract and they have a concrete implementation
11  * in VML or SVG engines.
12  *
13  * A Surface instance can be accessed as a property of a draw component. For example:
14  *
15  *     drawComponent.surface.add({
16  *         type: 'circle',
17  *         fill: '#ffc',
18  *         radius: 100,
19  *         x: 100,
20  *         y: 100
21  *     });
22  *
23  * The configuration object passed in the `add` method is the same as described in the {@link Ext.draw.Sprite}
24  * class documentation.
25  *
26  * ### Listeners
27  *
28  * You can also add event listeners to the surface using the `Observable` listener syntax. Supported events are:
29  *
30  * - mousedown
31  * - mouseup
32  * - mouseover
33  * - mouseout
34  * - mousemove
35  * - mouseenter
36  * - mouseleave
37  * - click
38  *
39  * For example:
40  *
41         drawComponent.surface.on({
42            'mousemove': function() {
43                 console.log('moving the mouse over the surface');   
44             }
45         });
46  */
47 Ext.define('Ext.draw.Surface', {
48
49     /* Begin Definitions */
50
51     mixins: {
52         observable: 'Ext.util.Observable'
53     },
54
55     requires: ['Ext.draw.CompositeSprite'],
56     uses: ['Ext.draw.engine.Svg', 'Ext.draw.engine.Vml'],
57
58     separatorRe: /[, ]+/,
59
60     statics: {
61 <span id='Ext-draw.Surface-method-create'>        /**
62 </span>         * Create and return a new concrete Surface instance appropriate for the current environment.
63          * @param {Object} config Initial configuration for the Surface instance
64          * @param {Array} enginePriority Optional order of implementations to use; the first one that is
65          *                available in the current environment will be used. Defaults to
66          *                &lt;code&gt;['Svg', 'Vml']&lt;/code&gt;.
67          */
68         create: function(config, enginePriority) {
69             enginePriority = enginePriority || ['Svg', 'Vml'];
70
71             var i = 0,
72                 len = enginePriority.length,
73                 surfaceClass;
74
75             for (; i &lt; len; i++) {
76                 if (Ext.supports[enginePriority[i]]) {
77                     return Ext.create('Ext.draw.engine.' + enginePriority[i], config);
78                 }
79             }
80             return false;
81         }
82     },
83
84     /* End Definitions */
85
86     // @private
87     availableAttrs: {
88         blur: 0,
89         &quot;clip-rect&quot;: &quot;0 0 1e9 1e9&quot;,
90         cursor: &quot;default&quot;,
91         cx: 0,
92         cy: 0,
93         'dominant-baseline': 'auto',
94         fill: &quot;none&quot;,
95         &quot;fill-opacity&quot;: 1,
96         font: '10px &quot;Arial&quot;',
97         &quot;font-family&quot;: '&quot;Arial&quot;',
98         &quot;font-size&quot;: &quot;10&quot;,
99         &quot;font-style&quot;: &quot;normal&quot;,
100         &quot;font-weight&quot;: 400,
101         gradient: &quot;&quot;,
102         height: 0,
103         hidden: false,
104         href: &quot;http://sencha.com/&quot;,
105         opacity: 1,
106         path: &quot;M0,0&quot;,
107         radius: 0,
108         rx: 0,
109         ry: 0,
110         scale: &quot;1 1&quot;,
111         src: &quot;&quot;,
112         stroke: &quot;#000&quot;,
113         &quot;stroke-dasharray&quot;: &quot;&quot;,
114         &quot;stroke-linecap&quot;: &quot;butt&quot;,
115         &quot;stroke-linejoin&quot;: &quot;butt&quot;,
116         &quot;stroke-miterlimit&quot;: 0,
117         &quot;stroke-opacity&quot;: 1,
118         &quot;stroke-width&quot;: 1,
119         target: &quot;_blank&quot;,
120         text: &quot;&quot;,
121         &quot;text-anchor&quot;: &quot;middle&quot;,
122         title: &quot;Ext Draw&quot;,
123         width: 0,
124         x: 0,
125         y: 0,
126         zIndex: 0
127     },
128
129 <span id='Ext-draw.Surface-cfg-height'> /**
130 </span>  * @cfg {Number} height
131   * The height of this component in pixels (defaults to auto).
132   * &lt;b&gt;Note&lt;/b&gt; to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
133   */
134 <span id='Ext-draw.Surface-cfg-width'> /**
135 </span>  * @cfg {Number} width
136   * The width of this component in pixels (defaults to auto).
137   * &lt;b&gt;Note&lt;/b&gt; to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
138   */
139     container: undefined,
140     height: 352,
141     width: 512,
142     x: 0,
143     y: 0,
144
145     constructor: function(config) {
146         var me = this;
147         config = config || {};
148         Ext.apply(me, config);
149
150         me.domRef = Ext.getDoc().dom;
151         
152         me.customAttributes = {};
153
154         me.addEvents(
155             'mousedown',
156             'mouseup',
157             'mouseover',
158             'mouseout',
159             'mousemove',
160             'mouseenter',
161             'mouseleave',
162             'click'
163         );
164
165         me.mixins.observable.constructor.call(me);
166
167         me.getId();
168         me.initGradients();
169         me.initItems();
170         if (me.renderTo) {
171             me.render(me.renderTo);
172             delete me.renderTo;
173         }
174         me.initBackground(config.background);
175     },
176
177     // @private called to initialize components in the surface
178     // this is dependent on the underlying implementation.
179     initSurface: Ext.emptyFn,
180
181     // @private called to setup the surface to render an item
182     //this is dependent on the underlying implementation.
183     renderItem: Ext.emptyFn,
184
185     // @private
186     renderItems: Ext.emptyFn,
187
188     // @private
189     setViewBox: Ext.emptyFn,
190
191 <span id='Ext-draw.Surface-property-addCls'>    /**
192 </span>     * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
193      *
194      * For example:
195      *
196      *          drawComponent.surface.addCls(sprite, 'x-visible');
197      *      
198      * @param {Object} sprite The sprite to add the class to.
199      * @param {String/Array} className The CSS class to add, or an array of classes
200      */
201     addCls: Ext.emptyFn,
202
203 <span id='Ext-draw.Surface-property-removeCls'>    /**
204 </span>     * Removes one or more CSS classes from the element.
205      *
206      * For example:
207      *
208      *      drawComponent.surface.removeCls(sprite, 'x-visible');
209      *      
210      * @param {Object} sprite The sprite to remove the class from.
211      * @param {String/Array} className The CSS class to remove, or an array of classes
212      */
213     removeCls: Ext.emptyFn,
214
215 <span id='Ext-draw.Surface-property-setStyle'>    /**
216 </span>     * Sets CSS style attributes to an element.
217      *
218      * For example:
219      *
220      *      drawComponent.surface.setStyle(sprite, {
221      *          'cursor': 'pointer'
222      *      });
223      *      
224      * @param {Object} sprite The sprite to add, or an array of classes to
225      * @param {Object} styles An Object with CSS styles.
226      */
227     setStyle: Ext.emptyFn,
228
229     // @private
230     initGradients: function() {
231         var gradients = this.gradients;
232         if (gradients) {
233             Ext.each(gradients, this.addGradient, this);
234         }
235     },
236
237     // @private
238     initItems: function() {
239         var items = this.items;
240         this.items = Ext.create('Ext.draw.CompositeSprite');
241         this.groups = Ext.create('Ext.draw.CompositeSprite');
242         if (items) {
243             this.add(items);
244         }
245     },
246     
247     // @private
248     initBackground: function(config) {
249         var gradientId, 
250             gradient,
251             backgroundSprite,
252             width = this.width,
253             height = this.height;
254         if (config) {
255             if (config.gradient) {
256                 gradient = config.gradient;
257                 gradientId = gradient.id;
258                 this.addGradient(gradient);
259                 this.background = this.add({
260                     type: 'rect',
261                     x: 0,
262                     y: 0,
263                     width: width,
264                     height: height,
265                     fill: 'url(#' + gradientId + ')'
266                 });
267             } else if (config.fill) {
268                 this.background = this.add({
269                     type: 'rect',
270                     x: 0,
271                     y: 0,
272                     width: width,
273                     height: height,
274                     fill: config.fill
275                 });
276             } else if (config.image) {
277                 this.background = this.add({
278                     type: 'image',
279                     x: 0,
280                     y: 0,
281                     width: width,
282                     height: height,
283                     src: config.image
284                 });
285             }
286         }
287     },
288     
289 <span id='Ext-draw.Surface-method-setSize'>    /**
290 </span>     * Sets the size of the surface. Accomodates the background (if any) to fit the new size too.
291      *
292      * For example:
293      *
294      *      drawComponent.surface.setSize(500, 500);
295      *
296      * This method is generally called when also setting the size of the draw Component.
297      * 
298      * @param {Number} w The new width of the canvas.
299      * @param {Number} h The new height of the canvas.
300      */
301     setSize: function(w, h) {
302         if (this.background) {
303             this.background.setAttributes({
304                 width: w,
305                 height: h,
306                 hidden: false
307             }, true);
308         }
309     },
310
311     // @private
312     scrubAttrs: function(sprite) {
313         var i,
314             attrs = {},
315             exclude = {},
316             sattr = sprite.attr;
317         for (i in sattr) {    
318             // Narrow down attributes to the main set
319             if (this.translateAttrs.hasOwnProperty(i)) {
320                 // Translated attr
321                 attrs[this.translateAttrs[i]] = sattr[i];
322                 exclude[this.translateAttrs[i]] = true;
323             }
324             else if (this.availableAttrs.hasOwnProperty(i) &amp;&amp; !exclude[i]) {
325                 // Passtrhough attr
326                 attrs[i] = sattr[i];
327             }
328         }
329         return attrs;
330     },
331
332     // @private
333     onClick: function(e) {
334         this.processEvent('click', e);
335     },
336
337     // @private
338     onMouseUp: function(e) {
339         this.processEvent('mouseup', e);
340     },
341
342     // @private
343     onMouseDown: function(e) {
344         this.processEvent('mousedown', e);
345     },
346
347     // @private
348     onMouseOver: function(e) {
349         this.processEvent('mouseover', e);
350     },
351
352     // @private
353     onMouseOut: function(e) {
354         this.processEvent('mouseout', e);
355     },
356
357     // @private
358     onMouseMove: function(e) {
359         this.fireEvent('mousemove', e);
360     },
361
362     // @private
363     onMouseEnter: Ext.emptyFn,
364
365     // @private
366     onMouseLeave: Ext.emptyFn,
367
368 <span id='Ext-draw.Surface-property-addGradient'>    /**
369 </span>     * Add a gradient definition to the Surface. Note that in some surface engines, adding
370      * a gradient via this method will not take effect if the surface has already been rendered.
371      * Therefore, it is preferred to pass the gradients as an item to the surface config, rather
372      * than calling this method, especially if the surface is rendered immediately (e.g. due to
373      * 'renderTo' in its config). For more information on how to create gradients in the Chart
374      * configuration object please refer to {@link Ext.chart.Chart}.
375      *
376      * The gradient object to be passed into this method is composed by:
377      * 
378      * 
379      *  - **id** - string - The unique name of the gradient.
380      *  - **angle** - number, optional - The angle of the gradient in degrees.
381      *  - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values.
382      * 
383      *
384      For example:
385                 drawComponent.surface.addGradient({
386                     id: 'gradientId',
387                     angle: 45,
388                     stops: {
389                         0: {
390                             color: '#555'
391                         },
392                         100: {
393                             color: '#ddd'
394                         }
395                     }
396                 });
397      */
398     addGradient: Ext.emptyFn,
399
400 <span id='Ext-draw.Surface-method-add'>    /**
401 </span>     * Add a Sprite to the surface. See {@link Ext.draw.Sprite} for the configuration object to be passed into this method.
402      *
403      * For example:
404      *
405      *     drawComponent.surface.add({
406      *         type: 'circle',
407      *         fill: '#ffc',
408      *         radius: 100,
409      *         x: 100,
410      *         y: 100
411      *     });
412      *
413     */
414     add: function() {
415         var args = Array.prototype.slice.call(arguments),
416             sprite,
417             index;
418
419         var hasMultipleArgs = args.length &gt; 1;
420         if (hasMultipleArgs || Ext.isArray(args[0])) {
421             var items = hasMultipleArgs ? args : args[0],
422                 results = [],
423                 i, ln, item;
424
425             for (i = 0, ln = items.length; i &lt; ln; i++) {
426                 item = items[i];
427                 item = this.add(item);
428                 results.push(item);
429             }
430
431             return results;
432         }
433         sprite = this.prepareItems(args[0], true)[0];
434         this.normalizeSpriteCollection(sprite);
435         this.onAdd(sprite);
436         return sprite;
437     },
438
439 <span id='Ext-draw.Surface-method-normalizeSpriteCollection'>    /**
440 </span>     * @private
441      * Insert or move a given sprite into the correct position in the items
442      * MixedCollection, according to its zIndex. Will be inserted at the end of
443      * an existing series of sprites with the same or lower zIndex. If the sprite
444      * is already positioned within an appropriate zIndex group, it will not be moved.
445      * This ordering can be used by subclasses to assist in rendering the sprites in
446      * the correct order for proper z-index stacking.
447      * @param {Ext.draw.Sprite} sprite
448      * @return {Number} the sprite's new index in the list
449      */
450     normalizeSpriteCollection: function(sprite) {
451         var items = this.items,
452             zIndex = sprite.attr.zIndex,
453             idx = items.indexOf(sprite);
454
455         if (idx &lt; 0 || (idx &gt; 0 &amp;&amp; items.getAt(idx - 1).attr.zIndex &gt; zIndex) ||
456                 (idx &lt; items.length - 1 &amp;&amp; items.getAt(idx + 1).attr.zIndex &lt; zIndex)) {
457             items.removeAt(idx);
458             idx = items.findIndexBy(function(otherSprite) {
459                 return otherSprite.attr.zIndex &gt; zIndex;
460             });
461             if (idx &lt; 0) {
462                 idx = items.length;
463             }
464             items.insert(idx, sprite);
465         }
466         return idx;
467     },
468
469     onAdd: function(sprite) {
470         var group = sprite.group,
471             draggable = sprite.draggable,
472             groups, ln, i;
473         if (group) {
474             groups = [].concat(group);
475             ln = groups.length;
476             for (i = 0; i &lt; ln; i++) {
477                 group = groups[i];
478                 this.getGroup(group).add(sprite);
479             }
480             delete sprite.group;
481         }
482         if (draggable) {
483             sprite.initDraggable();
484         }
485     },
486
487 <span id='Ext-draw.Surface-method-remove'>    /**
488 </span>     * Remove a given sprite from the surface, optionally destroying the sprite in the process.
489      * You can also call the sprite own `remove` method.
490      *
491      * For example:
492      *
493      *      drawComponent.surface.remove(sprite);
494      *      //or...
495      *      sprite.remove();
496      *      
497      * @param {Ext.draw.Sprite} sprite
498      * @param {Boolean} destroySprite
499      * @return {Number} the sprite's new index in the list
500      */
501     remove: function(sprite, destroySprite) {
502         if (sprite) {
503             this.items.remove(sprite);
504             this.groups.each(function(item) {
505                 item.remove(sprite);
506             });
507             sprite.onRemove();
508             if (destroySprite === true) {
509                 sprite.destroy();
510             }
511         }
512     },
513
514 <span id='Ext-draw.Surface-method-removeAll'>    /**
515 </span>     * Remove all sprites from the surface, optionally destroying the sprites in the process.
516      *
517      * For example:
518      *
519      *      drawComponent.surface.removeAll();
520      *      
521      * @param {Boolean} destroySprites Whether to destroy all sprites when removing them.
522      * @return {Number} The sprite's new index in the list.
523      */
524     removeAll: function(destroySprites) {
525         var items = this.items.items,
526             ln = items.length,
527             i;
528         for (i = ln - 1; i &gt; -1; i--) {
529             this.remove(items[i], destroySprites);
530         }
531     },
532
533     onRemove: Ext.emptyFn,
534
535     onDestroy: Ext.emptyFn,
536
537     // @private
538     applyTransformations: function(sprite) {
539             sprite.bbox.transform = 0;
540             this.transform(sprite);
541
542         var me = this,
543             dirty = false,
544             attr = sprite.attr;
545
546         if (attr.translation.x != null || attr.translation.y != null) {
547             me.translate(sprite);
548             dirty = true;
549         }
550         if (attr.scaling.x != null || attr.scaling.y != null) {
551             me.scale(sprite);
552             dirty = true;
553         }
554         if (attr.rotation.degrees != null) {
555             me.rotate(sprite);
556             dirty = true;
557         }
558         if (dirty) {
559             sprite.bbox.transform = 0;
560             this.transform(sprite);
561             sprite.transformations = [];
562         }
563     },
564
565     // @private
566     rotate: function (sprite) {
567         var bbox,
568             deg = sprite.attr.rotation.degrees,
569             centerX = sprite.attr.rotation.x,
570             centerY = sprite.attr.rotation.y;
571         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
572             bbox = this.getBBox(sprite);
573             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
574             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
575         }
576         sprite.transformations.push({
577             type: &quot;rotate&quot;,
578             degrees: deg,
579             x: centerX,
580             y: centerY
581         });
582     },
583
584     // @private
585     translate: function(sprite) {
586         var x = sprite.attr.translation.x || 0,
587             y = sprite.attr.translation.y || 0;
588         sprite.transformations.push({
589             type: &quot;translate&quot;,
590             x: x,
591             y: y
592         });
593     },
594
595     // @private
596     scale: function(sprite) {
597         var bbox,
598             x = sprite.attr.scaling.x || 1,
599             y = sprite.attr.scaling.y || 1,
600             centerX = sprite.attr.scaling.centerX,
601             centerY = sprite.attr.scaling.centerY;
602
603         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
604             bbox = this.getBBox(sprite);
605             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
606             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
607         }
608         sprite.transformations.push({
609             type: &quot;scale&quot;,
610             x: x,
611             y: y,
612             centerX: centerX,
613             centerY: centerY
614         });
615     },
616
617     // @private
618     rectPath: function (x, y, w, h, r) {
619         if (r) {
620             return [[&quot;M&quot;, x + r, y], [&quot;l&quot;, w - r * 2, 0], [&quot;a&quot;, r, r, 0, 0, 1, r, r], [&quot;l&quot;, 0, h - r * 2], [&quot;a&quot;, r, r, 0, 0, 1, -r, r], [&quot;l&quot;, r * 2 - w, 0], [&quot;a&quot;, r, r, 0, 0, 1, -r, -r], [&quot;l&quot;, 0, r * 2 - h], [&quot;a&quot;, r, r, 0, 0, 1, r, -r], [&quot;z&quot;]];
621         }
622         return [[&quot;M&quot;, x, y], [&quot;l&quot;, w, 0], [&quot;l&quot;, 0, h], [&quot;l&quot;, -w, 0], [&quot;z&quot;]];
623     },
624
625     // @private
626     ellipsePath: function (x, y, rx, ry) {
627         if (ry == null) {
628             ry = rx;
629         }
630         return [[&quot;M&quot;, x, y], [&quot;m&quot;, 0, -ry], [&quot;a&quot;, rx, ry, 0, 1, 1, 0, 2 * ry], [&quot;a&quot;, rx, ry, 0, 1, 1, 0, -2 * ry], [&quot;z&quot;]];
631     },
632
633     // @private
634     getPathpath: function (el) {
635         return el.attr.path;
636     },
637
638     // @private
639     getPathcircle: function (el) {
640         var a = el.attr;
641         return this.ellipsePath(a.x, a.y, a.radius, a.radius);
642     },
643
644     // @private
645     getPathellipse: function (el) {
646         var a = el.attr;
647         return this.ellipsePath(a.x, a.y, a.radiusX, a.radiusY);
648     },
649
650     // @private
651     getPathrect: function (el) {
652         var a = el.attr;
653         return this.rectPath(a.x, a.y, a.width, a.height, a.r);
654     },
655
656     // @private
657     getPathimage: function (el) {
658         var a = el.attr;
659         return this.rectPath(a.x || 0, a.y || 0, a.width, a.height);
660     },
661
662     // @private
663     getPathtext: function (el) {
664         var bbox = this.getBBoxText(el);
665         return this.rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
666     },
667
668     createGroup: function(id) {
669         var group = this.groups.get(id);
670         if (!group) {
671             group = Ext.create('Ext.draw.CompositeSprite', {
672                 surface: this
673             });
674             group.id = id || Ext.id(null, 'ext-surface-group-');
675             this.groups.add(group);
676         }
677         return group;
678     },
679
680 <span id='Ext-draw.Surface-method-getGroup'>    /**
681 </span>     * Returns a new group or an existent group associated with the current surface.
682      * The group returned is a {@link Ext.draw.CompositeSprite} group.
683      *
684      * For example:
685      *
686      *      var spriteGroup = drawComponent.surface.getGroup('someGroupId');
687      *      
688      * @param {String} id The unique identifier of the group.
689      * @return {Object} The {@link Ext.draw.CompositeSprite}.
690      */
691     getGroup: function(id) {
692         if (typeof id == &quot;string&quot;) {
693             var group = this.groups.get(id);
694             if (!group) {
695                 group = this.createGroup(id);
696             }
697         } else {
698             group = id;
699         }
700         return group;
701     },
702
703     // @private
704     prepareItems: function(items, applyDefaults) {
705         items = [].concat(items);
706         // Make sure defaults are applied and item is initialized
707         var item, i, ln;
708         for (i = 0, ln = items.length; i &lt; ln; i++) {
709             item = items[i];
710             if (!(item instanceof Ext.draw.Sprite)) {
711                 // Temporary, just take in configs...
712                 item.surface = this;
713                 items[i] = this.createItem(item);
714             } else {
715                 item.surface = this;
716             }
717         }
718         return items;
719     },
720     
721 <span id='Ext-draw.Surface-property-setText'>    /**
722 </span>     * Changes the text in the sprite element. The sprite must be a `text` sprite.
723      * This method can also be called from {@link Ext.draw.Sprite}.
724      *
725      * For example:
726      *
727      *      var spriteGroup = drawComponent.surface.setText(sprite, 'my new text');
728      *      
729      * @param {Object} sprite The Sprite to change the text.
730      * @param {String} text The new text to be set.
731      */
732     setText: Ext.emptyFn,
733     
734     //@private Creates an item and appends it to the surface. Called
735     //as an internal method when calling `add`.
736     createItem: Ext.emptyFn,
737
738 <span id='Ext-draw.Surface-method-getId'>    /**
739 </span>     * Retrieves the id of this component.
740      * Will autogenerate an id if one has not already been set.
741      */
742     getId: function() {
743         return this.id || (this.id = Ext.id(null, 'ext-surface-'));
744     },
745
746 <span id='Ext-draw.Surface-method-destroy'>    /**
747 </span>     * Destroys the surface. This is done by removing all components from it and
748      * also removing its reference to a DOM element.
749      *
750      * For example:
751      *
752      *      drawComponent.surface.destroy();
753      */
754     destroy: function() {
755         delete this.domRef;
756         this.removeAll();
757     }
758 });</pre></pre></body></html>