~cm-t/ubuntu-fr-tour/ubuntu-fr-tour

« back to all changes in this revision

Viewing changes to 13.04/12.10/pie/PIE_uncompressed.js

  • Committer: cm-t arudy
  • Date: 2013-10-22 01:24:09 UTC
  • Revision ID: arudy@ubuntu-fr.org-20131022012409-3dmo4i9u4ufohe5f
First Fr push to 13.10
Fixed many icons (updated to new version or fixed graphic)
Added Cloud indicator
Added Keyboard uindicator
Fixed many layout to fit Fr string

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
PIE: CSS3 rendering for IE
3
 
Version 1.0beta5
4
 
http://css3pie.com
5
 
Dual-licensed for use under the Apache License Version 2.0 or the General Public License (GPL) Version 2.
6
 
*/
7
 
(function(){
8
 
var doc = document;var PIE = window['PIE'];
9
 
 
10
 
if( !PIE ) {
11
 
    PIE = window['PIE'] = {
12
 
        CSS_PREFIX: '-pie-',
13
 
        STYLE_PREFIX: 'Pie',
14
 
        CLASS_PREFIX: 'pie_',
15
 
        tableCellTags: {
16
 
            'TD': 1,
17
 
            'TH': 1
18
 
        },
19
 
 
20
 
        /**
21
 
         * Lookup table of elements which cannot take custom children.
22
 
         */
23
 
        childlessElements: {
24
 
            'TABLE':1,
25
 
            'THEAD':1,
26
 
            'TBODY':1,
27
 
            'TFOOT':1,
28
 
            'TR':1,
29
 
            'INPUT':1,
30
 
            'TEXTAREA':1,
31
 
            'SELECT':1,
32
 
            'OPTION':1,
33
 
            'IMG':1,
34
 
            'HR':1
35
 
        },
36
 
 
37
 
        /**
38
 
         * Elements that can receive user focus
39
 
         */
40
 
        focusableElements: {
41
 
            'A':1,
42
 
            'INPUT':1,
43
 
            'TEXTAREA':1,
44
 
            'SELECT':1,
45
 
            'BUTTON':1
46
 
        },
47
 
 
48
 
        /**
49
 
         * Values of the type attribute for input elements displayed as buttons
50
 
         */
51
 
        inputButtonTypes: {
52
 
            'submit':1,
53
 
            'button':1,
54
 
            'reset':1
55
 
        },
56
 
 
57
 
        emptyFn: function() {}
58
 
    };
59
 
 
60
 
    // Force the background cache to be used. No reason it shouldn't be.
61
 
    try {
62
 
        doc.execCommand( 'BackgroundImageCache', false, true );
63
 
    } catch(e) {}
64
 
 
65
 
    (function() {
66
 
        /*
67
 
         * IE version detection approach by James Padolsey, with modifications -- from
68
 
         * http://james.padolsey.com/javascript/detect-ie-in-js-using-conditional-comments/
69
 
         */
70
 
        var ieVersion = 4,
71
 
            div = doc.createElement('div'),
72
 
            all = div.getElementsByTagName('i'),
73
 
            shape;
74
 
        while (
75
 
            div.innerHTML = '<!--[if gt IE ' + (++ieVersion) + ']><i></i><![endif]-->',
76
 
            all[0]
77
 
        ) {}
78
 
        PIE.ieVersion = ieVersion;
79
 
 
80
 
        // Detect IE6
81
 
        if( ieVersion === 6 ) {
82
 
            // IE6 can't access properties with leading dash, but can without it.
83
 
            PIE.CSS_PREFIX = PIE.CSS_PREFIX.replace( /^-/, '' );
84
 
        }
85
 
 
86
 
        PIE.ieDocMode = doc.documentMode || PIE.ieVersion;
87
 
 
88
 
        // Detect VML support (a small number of IE installs don't have a working VML engine)
89
 
        div.innerHTML = '<v:shape adj="1"/>';
90
 
        shape = div.firstChild;
91
 
        shape.style['behavior'] = 'url(#default#VML)';
92
 
        PIE.supportsVML = (typeof shape['adj'] === "object");
93
 
    }());
94
 
/**
95
 
 * Utility functions
96
 
 */
97
 
(function() {
98
 
    var vmlCreatorDoc,
99
 
        idNum = 0,
100
 
        imageSizes = {};
101
 
 
102
 
 
103
 
    PIE.Util = {
104
 
 
105
 
        /**
106
 
         * To create a VML element, it must be created by a Document which has the VML
107
 
         * namespace set. Unfortunately, if you try to add the namespace programatically
108
 
         * into the main document, you will get an "Unspecified error" when trying to
109
 
         * access document.namespaces before the document is finished loading. To get
110
 
         * around this, we create a DocumentFragment, which in IE land is apparently a
111
 
         * full-fledged Document. It allows adding namespaces immediately, so we add the
112
 
         * namespace there and then have it create the VML element.
113
 
         * @param {string} tag The tag name for the VML element
114
 
         * @return {Element} The new VML element
115
 
         */
116
 
        createVmlElement: function( tag ) {
117
 
            var vmlPrefix = 'css3vml';
118
 
            if( !vmlCreatorDoc ) {
119
 
                vmlCreatorDoc = doc.createDocumentFragment();
120
 
                vmlCreatorDoc.namespaces.add( vmlPrefix, 'urn:schemas-microsoft-com:vml' );
121
 
            }
122
 
            return vmlCreatorDoc.createElement( vmlPrefix + ':' + tag );
123
 
        },
124
 
 
125
 
 
126
 
        /**
127
 
         * Generate and return a unique ID for a given object. The generated ID is stored
128
 
         * as a property of the object for future reuse.
129
 
         * @param {Object} obj
130
 
         */
131
 
        getUID: function( obj ) {
132
 
            return obj && obj[ '_pieId' ] || ( obj[ '_pieId' ] = '_' + ++idNum );
133
 
        },
134
 
 
135
 
 
136
 
        /**
137
 
         * Simple utility for merging objects
138
 
         * @param {Object} obj1 The main object into which all others will be merged
139
 
         * @param {...Object} var_args Other objects which will be merged into the first, in order
140
 
         */
141
 
        merge: function( obj1 ) {
142
 
            var i, len, p, objN, args = arguments;
143
 
            for( i = 1, len = args.length; i < len; i++ ) {
144
 
                objN = args[i];
145
 
                for( p in objN ) {
146
 
                    if( objN.hasOwnProperty( p ) ) {
147
 
                        obj1[ p ] = objN[ p ];
148
 
                    }
149
 
                }
150
 
            }
151
 
            return obj1;
152
 
        },
153
 
 
154
 
 
155
 
        /**
156
 
         * Execute a callback function, passing it the dimensions of a given image once
157
 
         * they are known.
158
 
         * @param {string} src The source URL of the image
159
 
         * @param {function({w:number, h:number})} func The callback function to be called once the image dimensions are known
160
 
         * @param {Object} ctx A context object which will be used as the 'this' value within the executed callback function
161
 
         */
162
 
        withImageSize: function( src, func, ctx ) {
163
 
            var size = imageSizes[ src ], img, queue;
164
 
            if( size ) {
165
 
                // If we have a queue, add to it
166
 
                if( Object.prototype.toString.call( size ) === '[object Array]' ) {
167
 
                    size.push( [ func, ctx ] );
168
 
                }
169
 
                // Already have the size cached, call func right away
170
 
                else {
171
 
                    func.call( ctx, size );
172
 
                }
173
 
            } else {
174
 
                queue = imageSizes[ src ] = [ [ func, ctx ] ]; //create queue
175
 
                img = new Image();
176
 
                img.onload = function() {
177
 
                    size = imageSizes[ src ] = { w: img.width, h: img.height };
178
 
                    for( var i = 0, len = queue.length; i < len; i++ ) {
179
 
                        queue[ i ][ 0 ].call( queue[ i ][ 1 ], size );
180
 
                    }
181
 
                    img.onload = null;
182
 
                };
183
 
                img.src = src;
184
 
            }
185
 
        }
186
 
    };
187
 
})();/**
188
 
 * Utility functions for handling gradients
189
 
 */
190
 
PIE.GradientUtil = {
191
 
 
192
 
    getGradientMetrics: function( el, width, height, gradientInfo ) {
193
 
        var angle = gradientInfo.angle,
194
 
            startPos = gradientInfo.gradientStart,
195
 
            startX, startY,
196
 
            endX, endY,
197
 
            startCornerX, startCornerY,
198
 
            endCornerX, endCornerY,
199
 
            deltaX, deltaY,
200
 
            p, UNDEF;
201
 
 
202
 
        // Find the "start" and "end" corners; these are the corners furthest along the gradient line.
203
 
        // This is used below to find the start/end positions of the CSS3 gradient-line, and also in finding
204
 
        // the total length of the VML rendered gradient-line corner to corner.
205
 
        function findCorners() {
206
 
            startCornerX = ( angle >= 90 && angle < 270 ) ? width : 0;
207
 
            startCornerY = angle < 180 ? height : 0;
208
 
            endCornerX = width - startCornerX;
209
 
            endCornerY = height - startCornerY;
210
 
        }
211
 
 
212
 
        // Normalize the angle to a value between [0, 360)
213
 
        function normalizeAngle() {
214
 
            while( angle < 0 ) {
215
 
                angle += 360;
216
 
            }
217
 
            angle = angle % 360;
218
 
        }
219
 
 
220
 
        // Find the start and end points of the gradient
221
 
        if( startPos ) {
222
 
            startPos = startPos.coords( el, width, height );
223
 
            startX = startPos.x;
224
 
            startY = startPos.y;
225
 
        }
226
 
        if( angle ) {
227
 
            angle = angle.degrees();
228
 
 
229
 
            normalizeAngle();
230
 
            findCorners();
231
 
 
232
 
            // If no start position was specified, then choose a corner as the starting point.
233
 
            if( !startPos ) {
234
 
                startX = startCornerX;
235
 
                startY = startCornerY;
236
 
            }
237
 
 
238
 
            // Find the end position by extending a perpendicular line from the gradient-line which
239
 
            // intersects the corner opposite from the starting corner.
240
 
            p = PIE.GradientUtil.perpendicularIntersect( startX, startY, angle, endCornerX, endCornerY );
241
 
            endX = p[0];
242
 
            endY = p[1];
243
 
        }
244
 
        else if( startPos ) {
245
 
            // Start position but no angle specified: find the end point by rotating 180deg around the center
246
 
            endX = width - startX;
247
 
            endY = height - startY;
248
 
        }
249
 
        else {
250
 
            // Neither position nor angle specified; create vertical gradient from top to bottom
251
 
            startX = startY = endX = 0;
252
 
            endY = height;
253
 
        }
254
 
        deltaX = endX - startX;
255
 
        deltaY = endY - startY;
256
 
 
257
 
        if( angle === UNDEF ) {
258
 
            // Get the angle based on the change in x/y from start to end point. Checks first for horizontal
259
 
            // or vertical angles so they get exact whole numbers rather than what atan2 gives.
260
 
            angle = ( !deltaX ? ( deltaY < 0 ? 90 : 270 ) :
261
 
                        ( !deltaY ? ( deltaX < 0 ? 180 : 0 ) :
262
 
                            -Math.atan2( deltaY, deltaX ) / Math.PI * 180
263
 
                        )
264
 
                    );
265
 
            normalizeAngle();
266
 
            findCorners();
267
 
        }
268
 
 
269
 
        return {
270
 
            angle: angle,
271
 
            startX: startX,
272
 
            startY: startY,
273
 
            endX: endX,
274
 
            endY: endY,
275
 
            startCornerX: startCornerX,
276
 
            startCornerY: startCornerY,
277
 
            endCornerX: endCornerX,
278
 
            endCornerY: endCornerY,
279
 
            deltaX: deltaX,
280
 
            deltaY: deltaY,
281
 
            lineLength: PIE.GradientUtil.distance( startX, startY, endX, endY )
282
 
        }
283
 
    },
284
 
 
285
 
    /**
286
 
     * Find the point along a given line (defined by a starting point and an angle), at which
287
 
     * that line is intersected by a perpendicular line extending through another point.
288
 
     * @param x1 - x coord of the starting point
289
 
     * @param y1 - y coord of the starting point
290
 
     * @param angle - angle of the line extending from the starting point (in degrees)
291
 
     * @param x2 - x coord of point along the perpendicular line
292
 
     * @param y2 - y coord of point along the perpendicular line
293
 
     * @return [ x, y ]
294
 
     */
295
 
    perpendicularIntersect: function( x1, y1, angle, x2, y2 ) {
296
 
        // Handle straight vertical and horizontal angles, for performance and to avoid
297
 
        // divide-by-zero errors.
298
 
        if( angle === 0 || angle === 180 ) {
299
 
            return [ x2, y1 ];
300
 
        }
301
 
        else if( angle === 90 || angle === 270 ) {
302
 
            return [ x1, y2 ];
303
 
        }
304
 
        else {
305
 
            // General approach: determine the Ax+By=C formula for each line (the slope of the second
306
 
            // line is the negative inverse of the first) and then solve for where both formulas have
307
 
            // the same x/y values.
308
 
            var a1 = Math.tan( -angle * Math.PI / 180 ),
309
 
                c1 = a1 * x1 - y1,
310
 
                a2 = -1 / a1,
311
 
                c2 = a2 * x2 - y2,
312
 
                d = a2 - a1,
313
 
                endX = ( c2 - c1 ) / d,
314
 
                endY = ( a1 * c2 - a2 * c1 ) / d;
315
 
            return [ endX, endY ];
316
 
        }
317
 
    },
318
 
 
319
 
    /**
320
 
     * Find the distance between two points
321
 
     * @param {Number} p1x
322
 
     * @param {Number} p1y
323
 
     * @param {Number} p2x
324
 
     * @param {Number} p2y
325
 
     * @return {Number} the distance
326
 
     */
327
 
    distance: function( p1x, p1y, p2x, p2y ) {
328
 
        var dx = p2x - p1x,
329
 
            dy = p2y - p1y;
330
 
        return Math.abs(
331
 
            dx === 0 ? dy :
332
 
            dy === 0 ? dx :
333
 
            Math.sqrt( dx * dx + dy * dy )
334
 
        );
335
 
    }
336
 
 
337
 
};/**
338
 
 * 
339
 
 */
340
 
PIE.Observable = function() {
341
 
    /**
342
 
     * List of registered observer functions
343
 
     */
344
 
    this.observers = [];
345
 
 
346
 
    /**
347
 
     * Hash of function ids to their position in the observers list, for fast lookup
348
 
     */
349
 
    this.indexes = {};
350
 
};
351
 
PIE.Observable.prototype = {
352
 
 
353
 
    observe: function( fn ) {
354
 
        var id = PIE.Util.getUID( fn ),
355
 
            indexes = this.indexes,
356
 
            observers = this.observers;
357
 
        if( !( id in indexes ) ) {
358
 
            indexes[ id ] = observers.length;
359
 
            observers.push( fn );
360
 
        }
361
 
    },
362
 
 
363
 
    unobserve: function( fn ) {
364
 
        var id = PIE.Util.getUID( fn ),
365
 
            indexes = this.indexes;
366
 
        if( id && id in indexes ) {
367
 
            delete this.observers[ indexes[ id ] ];
368
 
            delete indexes[ id ];
369
 
        }
370
 
    },
371
 
 
372
 
    fire: function() {
373
 
        var o = this.observers,
374
 
            i = o.length;
375
 
        while( i-- ) {
376
 
            o[ i ] && o[ i ]();
377
 
        }
378
 
    }
379
 
 
380
 
};/*
381
 
 * Simple heartbeat timer - this is a brute-force workaround for syncing issues caused by IE not
382
 
 * always firing the onmove and onresize events when elements are moved or resized. We check a few
383
 
 * times every second to make sure the elements have the correct position and size. See Element.js
384
 
 * which adds heartbeat listeners based on the custom -pie-poll flag, which defaults to true in IE8
385
 
 * and false elsewhere.
386
 
 */
387
 
 
388
 
PIE.Heartbeat = new PIE.Observable();
389
 
PIE.Heartbeat.run = function() {
390
 
    var me = this;
391
 
    if( !me.running ) {
392
 
        setInterval( function() { me.fire() }, 250 );
393
 
        me.running = 1;
394
 
    }
395
 
};
396
 
/**
397
 
 * Create an observable listener for the onunload event
398
 
 */
399
 
(function() {
400
 
    PIE.OnUnload = new PIE.Observable();
401
 
 
402
 
    function handleUnload() {
403
 
        PIE.OnUnload.fire();
404
 
        window.detachEvent( 'onunload', handleUnload );
405
 
        window[ 'PIE' ] = null;
406
 
    }
407
 
 
408
 
    window.attachEvent( 'onunload', handleUnload );
409
 
 
410
 
    /**
411
 
     * Attach an event which automatically gets detached onunload
412
 
     */
413
 
    PIE.OnUnload.attachManagedEvent = function( target, name, handler ) {
414
 
        target.attachEvent( name, handler );
415
 
        this.observe( function() {
416
 
            target.detachEvent( name, handler );
417
 
        } );
418
 
    };
419
 
})()/**
420
 
 * Create a single observable listener for window resize events.
421
 
 */
422
 
PIE.OnResize = new PIE.Observable();
423
 
 
424
 
PIE.OnUnload.attachManagedEvent( window, 'onresize', function() { PIE.OnResize.fire(); } );
425
 
/**
426
 
 * Create a single observable listener for scroll events. Used for lazy loading based
427
 
 * on the viewport, and for fixed position backgrounds.
428
 
 */
429
 
(function() {
430
 
    PIE.OnScroll = new PIE.Observable();
431
 
 
432
 
    function scrolled() {
433
 
        PIE.OnScroll.fire();
434
 
    }
435
 
 
436
 
    PIE.OnUnload.attachManagedEvent( window, 'onscroll', scrolled );
437
 
 
438
 
    PIE.OnResize.observe( scrolled );
439
 
})();
440
 
/**
441
 
 * Listen for printing events, destroy all active PIE instances when printing, and
442
 
 * restore them afterward.
443
 
 */
444
 
(function() {
445
 
 
446
 
    var elements;
447
 
 
448
 
    function beforePrint() {
449
 
        elements = PIE.Element.destroyAll();
450
 
    }
451
 
 
452
 
    function afterPrint() {
453
 
        if( elements ) {
454
 
            for( var i = 0, len = elements.length; i < len; i++ ) {
455
 
                PIE[ 'attach' ]( elements[i] );
456
 
            }
457
 
            elements = 0;
458
 
        }
459
 
    }
460
 
 
461
 
    PIE.OnUnload.attachManagedEvent( window, 'onbeforeprint', beforePrint );
462
 
    PIE.OnUnload.attachManagedEvent( window, 'onafterprint', afterPrint );
463
 
 
464
 
})();/**
465
 
 * Create a single observable listener for document mouseup events.
466
 
 */
467
 
PIE.OnMouseup = new PIE.Observable();
468
 
 
469
 
PIE.OnUnload.attachManagedEvent( doc, 'onmouseup', function() { PIE.OnMouseup.fire(); } );
470
 
/**
471
 
 * Wrapper for length and percentage style values. The value is immutable. A singleton instance per unique
472
 
 * value is returned from PIE.getLength() - always use that instead of instantiating directly.
473
 
 * @constructor
474
 
 * @param {string} val The CSS string representing the length. It is assumed that this will already have
475
 
 *                 been validated as a valid length or percentage syntax.
476
 
 */
477
 
PIE.Length = (function() {
478
 
    var lengthCalcEl = doc.createElement( 'length-calc' ),
479
 
        parent = doc.documentElement,
480
 
        s = lengthCalcEl.style,
481
 
        conversions = {},
482
 
        units = [ 'mm', 'cm', 'in', 'pt', 'pc' ],
483
 
        i = units.length,
484
 
        instances = {};
485
 
 
486
 
    s.position = 'absolute';
487
 
    s.top = s.left = '-9999px';
488
 
 
489
 
    parent.appendChild( lengthCalcEl );
490
 
    while( i-- ) {
491
 
        lengthCalcEl.style.width = '100' + units[i];
492
 
        conversions[ units[i] ] = lengthCalcEl.offsetWidth / 100;
493
 
    }
494
 
    parent.removeChild( lengthCalcEl );
495
 
 
496
 
    // All calcs from here on will use 1em
497
 
    lengthCalcEl.style.width = '1em';
498
 
 
499
 
 
500
 
    function Length( val ) {
501
 
        this.val = val;
502
 
    }
503
 
    Length.prototype = {
504
 
        /**
505
 
         * Regular expression for matching the length unit
506
 
         * @private
507
 
         */
508
 
        unitRE: /(px|em|ex|mm|cm|in|pt|pc|%)$/,
509
 
 
510
 
        /**
511
 
         * Get the numeric value of the length
512
 
         * @return {number} The value
513
 
         */
514
 
        getNumber: function() {
515
 
            var num = this.num,
516
 
                UNDEF;
517
 
            if( num === UNDEF ) {
518
 
                num = this.num = parseFloat( this.val );
519
 
            }
520
 
            return num;
521
 
        },
522
 
 
523
 
        /**
524
 
         * Get the unit of the length
525
 
         * @return {string} The unit
526
 
         */
527
 
        getUnit: function() {
528
 
            var unit = this.unit,
529
 
                m;
530
 
            if( !unit ) {
531
 
                m = this.val.match( this.unitRE );
532
 
                unit = this.unit = ( m && m[0] ) || 'px';
533
 
            }
534
 
            return unit;
535
 
        },
536
 
 
537
 
        /**
538
 
         * Determine whether this is a percentage length value
539
 
         * @return {boolean}
540
 
         */
541
 
        isPercentage: function() {
542
 
            return this.getUnit() === '%';
543
 
        },
544
 
 
545
 
        /**
546
 
         * Resolve this length into a number of pixels.
547
 
         * @param {Element} el - the context element, used to resolve font-relative values
548
 
         * @param {(function():number|number)=} pct100 - the number of pixels that equal a 100% percentage. This can be either a number or a
549
 
         *                  function which will be called to return the number.
550
 
         */
551
 
        pixels: function( el, pct100 ) {
552
 
            var num = this.getNumber(),
553
 
                unit = this.getUnit();
554
 
            switch( unit ) {
555
 
                case "px":
556
 
                    return num;
557
 
                case "%":
558
 
                    return num * ( typeof pct100 === 'function' ? pct100() : pct100 ) / 100;
559
 
                case "em":
560
 
                    return num * this.getEmPixels( el );
561
 
                case "ex":
562
 
                    return num * this.getEmPixels( el ) / 2;
563
 
                default:
564
 
                    return num * conversions[ unit ];
565
 
            }
566
 
        },
567
 
 
568
 
        /**
569
 
         * The em and ex units are relative to the font-size of the current element,
570
 
         * however if the font-size is set using non-pixel units then we get that value
571
 
         * rather than a pixel conversion. To get around this, we keep a floating element
572
 
         * with width:1em which we insert into the target element and then read its offsetWidth.
573
 
         * For elements that won't accept a child we insert into the parent node and perform
574
 
         * additional calculation. If the font-size *is* specified in pixels, then we use that
575
 
         * directly to avoid the expensive DOM manipulation.
576
 
         * @param {Element} el
577
 
         * @return {number}
578
 
         */
579
 
        getEmPixels: function( el ) {
580
 
            var fs = el.currentStyle.fontSize,
581
 
                px, parent, me;
582
 
 
583
 
            if( fs.indexOf( 'px' ) > 0 ) {
584
 
                return parseFloat( fs );
585
 
            }
586
 
            else if( el.tagName in PIE.childlessElements ) {
587
 
                me = this;
588
 
                parent = el.parentNode;
589
 
                return PIE.getLength( fs ).pixels( parent, function() {
590
 
                    return me.getEmPixels( parent );
591
 
                } );
592
 
            }
593
 
            else {
594
 
                el.appendChild( lengthCalcEl );
595
 
                px = lengthCalcEl.offsetWidth;
596
 
                if( lengthCalcEl.parentNode === el ) { //not sure how this could be false but it sometimes is
597
 
                    el.removeChild( lengthCalcEl );
598
 
                }
599
 
                return px;
600
 
            }
601
 
        }
602
 
    };
603
 
 
604
 
 
605
 
    /**
606
 
     * Retrieve a PIE.Length instance for the given value. A shared singleton instance is returned for each unique value.
607
 
     * @param {string} val The CSS string representing the length. It is assumed that this will already have
608
 
     *                 been validated as a valid length or percentage syntax.
609
 
     */
610
 
    PIE.getLength = function( val ) {
611
 
        return instances[ val ] || ( instances[ val ] = new Length( val ) );
612
 
    };
613
 
 
614
 
    return Length;
615
 
})();
616
 
/**
617
 
 * Wrapper for a CSS3 bg-position value. Takes up to 2 position keywords and 2 lengths/percentages.
618
 
 * @constructor
619
 
 * @param {Array.<PIE.Tokenizer.Token>} tokens The tokens making up the background position value.
620
 
 */
621
 
PIE.BgPosition = (function() {
622
 
 
623
 
    var length_fifty = PIE.getLength( '50%' ),
624
 
        vert_idents = { 'top': 1, 'center': 1, 'bottom': 1 },
625
 
        horiz_idents = { 'left': 1, 'center': 1, 'right': 1 };
626
 
 
627
 
 
628
 
    function BgPosition( tokens ) {
629
 
        this.tokens = tokens;
630
 
    }
631
 
    BgPosition.prototype = {
632
 
        /**
633
 
         * Normalize the values into the form:
634
 
         * [ xOffsetSide, xOffsetLength, yOffsetSide, yOffsetLength ]
635
 
         * where: xOffsetSide is either 'left' or 'right',
636
 
         *        yOffsetSide is either 'top' or 'bottom',
637
 
         *        and x/yOffsetLength are both PIE.Length objects.
638
 
         * @return {Array}
639
 
         */
640
 
        getValues: function() {
641
 
            if( !this._values ) {
642
 
                var tokens = this.tokens,
643
 
                    len = tokens.length,
644
 
                    Tokenizer = PIE.Tokenizer,
645
 
                    identType = Tokenizer.Type,
646
 
                    length_zero = PIE.getLength( '0' ),
647
 
                    type_ident = identType.IDENT,
648
 
                    type_length = identType.LENGTH,
649
 
                    type_percent = identType.PERCENT,
650
 
                    type, value,
651
 
                    vals = [ 'left', length_zero, 'top', length_zero ];
652
 
 
653
 
                // If only one value, the second is assumed to be 'center'
654
 
                if( len === 1 ) {
655
 
                    tokens.push( new Tokenizer.Token( type_ident, 'center' ) );
656
 
                    len++;
657
 
                }
658
 
 
659
 
                // Two values - CSS2
660
 
                if( len === 2 ) {
661
 
                    // If both idents, they can appear in either order, so switch them if needed
662
 
                    if( type_ident & ( tokens[0].tokenType | tokens[1].tokenType ) &&
663
 
                        tokens[0].tokenValue in vert_idents && tokens[1].tokenValue in horiz_idents ) {
664
 
                        tokens.push( tokens.shift() );
665
 
                    }
666
 
                    if( tokens[0].tokenType & type_ident ) {
667
 
                        if( tokens[0].tokenValue === 'center' ) {
668
 
                            vals[1] = length_fifty;
669
 
                        } else {
670
 
                            vals[0] = tokens[0].tokenValue;
671
 
                        }
672
 
                    }
673
 
                    else if( tokens[0].isLengthOrPercent() ) {
674
 
                        vals[1] = PIE.getLength( tokens[0].tokenValue );
675
 
                    }
676
 
                    if( tokens[1].tokenType & type_ident ) {
677
 
                        if( tokens[1].tokenValue === 'center' ) {
678
 
                            vals[3] = length_fifty;
679
 
                        } else {
680
 
                            vals[2] = tokens[1].tokenValue;
681
 
                        }
682
 
                    }
683
 
                    else if( tokens[1].isLengthOrPercent() ) {
684
 
                        vals[3] = PIE.getLength( tokens[1].tokenValue );
685
 
                    }
686
 
                }
687
 
 
688
 
                // Three or four values - CSS3
689
 
                else {
690
 
                    // TODO
691
 
                }
692
 
 
693
 
                this._values = vals;
694
 
            }
695
 
            return this._values;
696
 
        },
697
 
 
698
 
        /**
699
 
         * Find the coordinates of the background image from the upper-left corner of the background area.
700
 
         * Note that these coordinate values are not rounded.
701
 
         * @param {Element} el
702
 
         * @param {number} width - the width for percentages (background area width minus image width)
703
 
         * @param {number} height - the height for percentages (background area height minus image height)
704
 
         * @return {Object} { x: Number, y: Number }
705
 
         */
706
 
        coords: function( el, width, height ) {
707
 
            var vals = this.getValues(),
708
 
                pxX = vals[1].pixels( el, width ),
709
 
                pxY = vals[3].pixels( el, height );
710
 
 
711
 
            return {
712
 
                x: vals[0] === 'right' ? width - pxX : pxX,
713
 
                y: vals[2] === 'bottom' ? height - pxY : pxY
714
 
            };
715
 
        }
716
 
    };
717
 
 
718
 
    return BgPosition;
719
 
})();
720
 
/**
721
 
 * Wrapper for a CSS3 background-size value.
722
 
 * @constructor
723
 
 * @param {String|PIE.Length} w The width parameter
724
 
 * @param {String|PIE.Length} h The height parameter, if any
725
 
 */
726
 
PIE.BgSize = (function() {
727
 
 
728
 
    var CONTAIN = 'contain',
729
 
        COVER = 'cover',
730
 
        AUTO = 'auto';
731
 
 
732
 
 
733
 
    function BgSize( w, h ) {
734
 
        this.w = w;
735
 
        this.h = h;
736
 
    }
737
 
    BgSize.prototype = {
738
 
 
739
 
        pixels: function( el, areaW, areaH, imgW, imgH ) {
740
 
            var me = this,
741
 
                w = me.w,
742
 
                h = me.h,
743
 
                areaRatio = areaW / areaH,
744
 
                imgRatio = imgW / imgH;
745
 
 
746
 
            if ( w === CONTAIN ) {
747
 
                w = imgRatio > areaRatio ? areaW : areaH * imgRatio;
748
 
                h = imgRatio > areaRatio ? areaW / imgRatio : areaH;
749
 
            }
750
 
            else if ( w === COVER ) {
751
 
                w = imgRatio < areaRatio ? areaW : areaH * imgRatio;
752
 
                h = imgRatio < areaRatio ? areaW / imgRatio : areaH;
753
 
            }
754
 
            else if ( w === AUTO ) {
755
 
                h = ( h === AUTO ? imgH : h.pixels( el, areaH ) );
756
 
                w = h * imgRatio;
757
 
            }
758
 
            else {
759
 
                w = w.pixels( el, areaW );
760
 
                h = ( h === AUTO ? w / imgRatio : h.pixels( el, areaH ) );
761
 
            }
762
 
 
763
 
            return { w: w, h: h };
764
 
        }
765
 
 
766
 
    };
767
 
 
768
 
    BgSize.DEFAULT = new BgSize( AUTO, AUTO );
769
 
 
770
 
    return BgSize;
771
 
})();
772
 
/**
773
 
 * Wrapper for angle values; handles conversion to degrees from all allowed angle units
774
 
 * @constructor
775
 
 * @param {string} val The raw CSS value for the angle. It is assumed it has been pre-validated.
776
 
 */
777
 
PIE.Angle = (function() {
778
 
    function Angle( val ) {
779
 
        this.val = val;
780
 
    }
781
 
    Angle.prototype = {
782
 
        unitRE: /[a-z]+$/i,
783
 
 
784
 
        /**
785
 
         * @return {string} The unit of the angle value
786
 
         */
787
 
        getUnit: function() {
788
 
            return this._unit || ( this._unit = this.val.match( this.unitRE )[0].toLowerCase() );
789
 
        },
790
 
 
791
 
        /**
792
 
         * Get the numeric value of the angle in degrees.
793
 
         * @return {number} The degrees value
794
 
         */
795
 
        degrees: function() {
796
 
            var deg = this._deg, u, n;
797
 
            if( deg === undefined ) {
798
 
                u = this.getUnit();
799
 
                n = parseFloat( this.val, 10 );
800
 
                deg = this._deg = ( u === 'deg' ? n : u === 'rad' ? n / Math.PI * 180 : u === 'grad' ? n / 400 * 360 : u === 'turn' ? n * 360 : 0 );
801
 
            }
802
 
            return deg;
803
 
        }
804
 
    };
805
 
 
806
 
    return Angle;
807
 
})();/**
808
 
 * Abstraction for colors values. Allows detection of rgba values. A singleton instance per unique
809
 
 * value is returned from PIE.getColor() - always use that instead of instantiating directly.
810
 
 * @constructor
811
 
 * @param {string} val The raw CSS string value for the color
812
 
 */
813
 
PIE.Color = (function() {
814
 
    var instances = {};
815
 
 
816
 
    function Color( val ) {
817
 
        this.val = val;
818
 
    }
819
 
 
820
 
    /**
821
 
     * Regular expression for matching rgba colors and extracting their components
822
 
     * @type {RegExp}
823
 
     */
824
 
    Color.rgbaRE = /\s*rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d+|\d*\.\d+)\s*\)\s*/;
825
 
 
826
 
    Color.names = {
827
 
        "aliceblue":"F0F8FF", "antiquewhite":"FAEBD7", "aqua":"0FF",
828
 
        "aquamarine":"7FFFD4", "azure":"F0FFFF", "beige":"F5F5DC",
829
 
        "bisque":"FFE4C4", "black":"000", "blanchedalmond":"FFEBCD",
830
 
        "blue":"00F", "blueviolet":"8A2BE2", "brown":"A52A2A",
831
 
        "burlywood":"DEB887", "cadetblue":"5F9EA0", "chartreuse":"7FFF00",
832
 
        "chocolate":"D2691E", "coral":"FF7F50", "cornflowerblue":"6495ED",
833
 
        "cornsilk":"FFF8DC", "crimson":"DC143C", "cyan":"0FF",
834
 
        "darkblue":"00008B", "darkcyan":"008B8B", "darkgoldenrod":"B8860B",
835
 
        "darkgray":"A9A9A9", "darkgreen":"006400", "darkkhaki":"BDB76B",
836
 
        "darkmagenta":"8B008B", "darkolivegreen":"556B2F", "darkorange":"FF8C00",
837
 
        "darkorchid":"9932CC", "darkred":"8B0000", "darksalmon":"E9967A",
838
 
        "darkseagreen":"8FBC8F", "darkslateblue":"483D8B", "darkslategray":"2F4F4F",
839
 
        "darkturquoise":"00CED1", "darkviolet":"9400D3", "deeppink":"FF1493",
840
 
        "deepskyblue":"00BFFF", "dimgray":"696969", "dodgerblue":"1E90FF",
841
 
        "firebrick":"B22222", "floralwhite":"FFFAF0", "forestgreen":"228B22",
842
 
        "fuchsia":"F0F", "gainsboro":"DCDCDC", "ghostwhite":"F8F8FF",
843
 
        "gold":"FFD700", "goldenrod":"DAA520", "gray":"808080",
844
 
        "green":"008000", "greenyellow":"ADFF2F", "honeydew":"F0FFF0",
845
 
        "hotpink":"FF69B4", "indianred":"CD5C5C", "indigo":"4B0082",
846
 
        "ivory":"FFFFF0", "khaki":"F0E68C", "lavender":"E6E6FA",
847
 
        "lavenderblush":"FFF0F5", "lawngreen":"7CFC00", "lemonchiffon":"FFFACD",
848
 
        "lightblue":"ADD8E6", "lightcoral":"F08080", "lightcyan":"E0FFFF",
849
 
        "lightgoldenrodyellow":"FAFAD2", "lightgreen":"90EE90", "lightgrey":"D3D3D3",
850
 
        "lightpink":"FFB6C1", "lightsalmon":"FFA07A", "lightseagreen":"20B2AA",
851
 
        "lightskyblue":"87CEFA", "lightslategray":"789", "lightsteelblue":"B0C4DE",
852
 
        "lightyellow":"FFFFE0", "lime":"0F0", "limegreen":"32CD32",
853
 
        "linen":"FAF0E6", "magenta":"F0F", "maroon":"800000",
854
 
        "mediumauqamarine":"66CDAA", "mediumblue":"0000CD", "mediumorchid":"BA55D3",
855
 
        "mediumpurple":"9370D8", "mediumseagreen":"3CB371", "mediumslateblue":"7B68EE",
856
 
        "mediumspringgreen":"00FA9A", "mediumturquoise":"48D1CC", "mediumvioletred":"C71585",
857
 
        "midnightblue":"191970", "mintcream":"F5FFFA", "mistyrose":"FFE4E1",
858
 
        "moccasin":"FFE4B5", "navajowhite":"FFDEAD", "navy":"000080",
859
 
        "oldlace":"FDF5E6", "olive":"808000", "olivedrab":"688E23",
860
 
        "orange":"FFA500", "orangered":"FF4500", "orchid":"DA70D6",
861
 
        "palegoldenrod":"EEE8AA", "palegreen":"98FB98", "paleturquoise":"AFEEEE",
862
 
        "palevioletred":"D87093", "papayawhip":"FFEFD5", "peachpuff":"FFDAB9",
863
 
        "peru":"CD853F", "pink":"FFC0CB", "plum":"DDA0DD",
864
 
        "powderblue":"B0E0E6", "purple":"800080", "red":"F00",
865
 
        "rosybrown":"BC8F8F", "royalblue":"4169E1", "saddlebrown":"8B4513",
866
 
        "salmon":"FA8072", "sandybrown":"F4A460", "seagreen":"2E8B57",
867
 
        "seashell":"FFF5EE", "sienna":"A0522D", "silver":"C0C0C0",
868
 
        "skyblue":"87CEEB", "slateblue":"6A5ACD", "slategray":"708090",
869
 
        "snow":"FFFAFA", "springgreen":"00FF7F", "steelblue":"4682B4",
870
 
        "tan":"D2B48C", "teal":"008080", "thistle":"D8BFD8",
871
 
        "tomato":"FF6347", "turquoise":"40E0D0", "violet":"EE82EE",
872
 
        "wheat":"F5DEB3", "white":"FFF", "whitesmoke":"F5F5F5",
873
 
        "yellow":"FF0", "yellowgreen":"9ACD32"
874
 
    };
875
 
 
876
 
    Color.prototype = {
877
 
        /**
878
 
         * @private
879
 
         */
880
 
        parse: function() {
881
 
            if( !this._color ) {
882
 
                var me = this,
883
 
                    v = me.val,
884
 
                    vLower,
885
 
                    m = v.match( Color.rgbaRE );
886
 
                if( m ) {
887
 
                    me._color = 'rgb(' + m[1] + ',' + m[2] + ',' + m[3] + ')';
888
 
                    me._alpha = parseFloat( m[4] );
889
 
                }
890
 
                else {
891
 
                    if( ( vLower = v.toLowerCase() ) in Color.names ) {
892
 
                        v = '#' + Color.names[vLower];
893
 
                    }
894
 
                    me._color = v;
895
 
                    me._alpha = ( v === 'transparent' ? 0 : 1 );
896
 
                }
897
 
            }
898
 
        },
899
 
 
900
 
        /**
901
 
         * Retrieve the value of the color in a format usable by IE natively. This will be the same as
902
 
         * the raw input value, except for rgba values which will be converted to an rgb value.
903
 
         * @param {Element} el The context element, used to get 'currentColor' keyword value.
904
 
         * @return {string} Color value
905
 
         */
906
 
        colorValue: function( el ) {
907
 
            this.parse();
908
 
            return this._color === 'currentColor' ? el.currentStyle.color : this._color;
909
 
        },
910
 
 
911
 
        /**
912
 
         * Retrieve the alpha value of the color. Will be 1 for all values except for rgba values
913
 
         * with an alpha component.
914
 
         * @return {number} The alpha value, from 0 to 1.
915
 
         */
916
 
        alpha: function() {
917
 
            this.parse();
918
 
            return this._alpha;
919
 
        }
920
 
    };
921
 
 
922
 
 
923
 
    /**
924
 
     * Retrieve a PIE.Color instance for the given value. A shared singleton instance is returned for each unique value.
925
 
     * @param {string} val The CSS string representing the color. It is assumed that this will already have
926
 
     *                 been validated as a valid color syntax.
927
 
     */
928
 
    PIE.getColor = function(val) {
929
 
        return instances[ val ] || ( instances[ val ] = new Color( val ) );
930
 
    };
931
 
 
932
 
    return Color;
933
 
})();/**
934
 
 * A tokenizer for CSS value strings.
935
 
 * @constructor
936
 
 * @param {string} css The CSS value string
937
 
 */
938
 
PIE.Tokenizer = (function() {
939
 
    function Tokenizer( css ) {
940
 
        this.css = css;
941
 
        this.ch = 0;
942
 
        this.tokens = [];
943
 
        this.tokenIndex = 0;
944
 
    }
945
 
 
946
 
    /**
947
 
     * Enumeration of token type constants.
948
 
     * @enum {number}
949
 
     */
950
 
    var Type = Tokenizer.Type = {
951
 
        ANGLE: 1,
952
 
        CHARACTER: 2,
953
 
        COLOR: 4,
954
 
        DIMEN: 8,
955
 
        FUNCTION: 16,
956
 
        IDENT: 32,
957
 
        LENGTH: 64,
958
 
        NUMBER: 128,
959
 
        OPERATOR: 256,
960
 
        PERCENT: 512,
961
 
        STRING: 1024,
962
 
        URL: 2048
963
 
    };
964
 
 
965
 
    /**
966
 
     * A single token
967
 
     * @constructor
968
 
     * @param {number} type The type of the token - see PIE.Tokenizer.Type
969
 
     * @param {string} value The value of the token
970
 
     */
971
 
    Tokenizer.Token = function( type, value ) {
972
 
        this.tokenType = type;
973
 
        this.tokenValue = value;
974
 
    };
975
 
    Tokenizer.Token.prototype = {
976
 
        isLength: function() {
977
 
            return this.tokenType & Type.LENGTH || ( this.tokenType & Type.NUMBER && this.tokenValue === '0' );
978
 
        },
979
 
        isLengthOrPercent: function() {
980
 
            return this.isLength() || this.tokenType & Type.PERCENT;
981
 
        }
982
 
    };
983
 
 
984
 
    Tokenizer.prototype = {
985
 
        whitespace: /\s/,
986
 
        number: /^[\+\-]?(\d*\.)?\d+/,
987
 
        url: /^url\(\s*("([^"]*)"|'([^']*)'|([!#$%&*-~]*))\s*\)/i,
988
 
        ident: /^\-?[_a-z][\w-]*/i,
989
 
        string: /^("([^"]*)"|'([^']*)')/,
990
 
        operator: /^[\/,]/,
991
 
        hash: /^#[\w]+/,
992
 
        hashColor: /^#([\da-f]{6}|[\da-f]{3})/i,
993
 
 
994
 
        unitTypes: {
995
 
            'px': Type.LENGTH, 'em': Type.LENGTH, 'ex': Type.LENGTH,
996
 
            'mm': Type.LENGTH, 'cm': Type.LENGTH, 'in': Type.LENGTH,
997
 
            'pt': Type.LENGTH, 'pc': Type.LENGTH,
998
 
            'deg': Type.ANGLE, 'rad': Type.ANGLE, 'grad': Type.ANGLE
999
 
        },
1000
 
 
1001
 
        colorFunctions: {
1002
 
            'rgb': 1, 'rgba': 1, 'hsl': 1, 'hsla': 1
1003
 
        },
1004
 
 
1005
 
 
1006
 
        /**
1007
 
         * Advance to and return the next token in the CSS string. If the end of the CSS string has
1008
 
         * been reached, null will be returned.
1009
 
         * @param {boolean} forget - if true, the token will not be stored for the purposes of backtracking with prev().
1010
 
         * @return {PIE.Tokenizer.Token}
1011
 
         */
1012
 
        next: function( forget ) {
1013
 
            var css, ch, firstChar, match, val,
1014
 
                me = this;
1015
 
 
1016
 
            function newToken( type, value ) {
1017
 
                var tok = new Tokenizer.Token( type, value );
1018
 
                if( !forget ) {
1019
 
                    me.tokens.push( tok );
1020
 
                    me.tokenIndex++;
1021
 
                }
1022
 
                return tok;
1023
 
            }
1024
 
            function failure() {
1025
 
                me.tokenIndex++;
1026
 
                return null;
1027
 
            }
1028
 
 
1029
 
            // In case we previously backed up, return the stored token in the next slot
1030
 
            if( this.tokenIndex < this.tokens.length ) {
1031
 
                return this.tokens[ this.tokenIndex++ ];
1032
 
            }
1033
 
 
1034
 
            // Move past leading whitespace characters
1035
 
            while( this.whitespace.test( this.css.charAt( this.ch ) ) ) {
1036
 
                this.ch++;
1037
 
            }
1038
 
            if( this.ch >= this.css.length ) {
1039
 
                return failure();
1040
 
            }
1041
 
 
1042
 
            ch = this.ch;
1043
 
            css = this.css.substring( this.ch );
1044
 
            firstChar = css.charAt( 0 );
1045
 
            switch( firstChar ) {
1046
 
                case '#':
1047
 
                    if( match = css.match( this.hashColor ) ) {
1048
 
                        this.ch += match[0].length;
1049
 
                        return newToken( Type.COLOR, match[0] );
1050
 
                    }
1051
 
                    break;
1052
 
 
1053
 
                case '"':
1054
 
                case "'":
1055
 
                    if( match = css.match( this.string ) ) {
1056
 
                        this.ch += match[0].length;
1057
 
                        return newToken( Type.STRING, match[2] || match[3] || '' );
1058
 
                    }
1059
 
                    break;
1060
 
 
1061
 
                case "/":
1062
 
                case ",":
1063
 
                    this.ch++;
1064
 
                    return newToken( Type.OPERATOR, firstChar );
1065
 
 
1066
 
                case 'u':
1067
 
                    if( match = css.match( this.url ) ) {
1068
 
                        this.ch += match[0].length;
1069
 
                        return newToken( Type.URL, match[2] || match[3] || match[4] || '' );
1070
 
                    }
1071
 
            }
1072
 
 
1073
 
            // Numbers and values starting with numbers
1074
 
            if( match = css.match( this.number ) ) {
1075
 
                val = match[0];
1076
 
                this.ch += val.length;
1077
 
 
1078
 
                // Check if it is followed by a unit
1079
 
                if( css.charAt( val.length ) === '%' ) {
1080
 
                    this.ch++;
1081
 
                    return newToken( Type.PERCENT, val + '%' );
1082
 
                }
1083
 
                if( match = css.substring( val.length ).match( this.ident ) ) {
1084
 
                    val += match[0];
1085
 
                    this.ch += match[0].length;
1086
 
                    return newToken( this.unitTypes[ match[0].toLowerCase() ] || Type.DIMEN, val );
1087
 
                }
1088
 
 
1089
 
                // Plain ol' number
1090
 
                return newToken( Type.NUMBER, val );
1091
 
            }
1092
 
 
1093
 
            // Identifiers
1094
 
            if( match = css.match( this.ident ) ) {
1095
 
                val = match[0];
1096
 
                this.ch += val.length;
1097
 
 
1098
 
                // Named colors
1099
 
                if( val.toLowerCase() in PIE.Color.names || val === 'currentColor' || val === 'transparent' ) {
1100
 
                    return newToken( Type.COLOR, val );
1101
 
                }
1102
 
 
1103
 
                // Functions
1104
 
                if( css.charAt( val.length ) === '(' ) {
1105
 
                    this.ch++;
1106
 
 
1107
 
                    // Color values in function format: rgb, rgba, hsl, hsla
1108
 
                    if( val.toLowerCase() in this.colorFunctions ) {
1109
 
                        function isNum( tok ) {
1110
 
                            return tok && tok.tokenType & Type.NUMBER;
1111
 
                        }
1112
 
                        function isNumOrPct( tok ) {
1113
 
                            return tok && ( tok.tokenType & ( Type.NUMBER | Type.PERCENT ) );
1114
 
                        }
1115
 
                        function isValue( tok, val ) {
1116
 
                            return tok && tok.tokenValue === val;
1117
 
                        }
1118
 
                        function next() {
1119
 
                            return me.next( 1 );
1120
 
                        }
1121
 
 
1122
 
                        if( ( val.charAt(0) === 'r' ? isNumOrPct( next() ) : isNum( next() ) ) &&
1123
 
                            isValue( next(), ',' ) &&
1124
 
                            isNumOrPct( next() ) &&
1125
 
                            isValue( next(), ',' ) &&
1126
 
                            isNumOrPct( next() ) &&
1127
 
                            ( val === 'rgb' || val === 'hsa' || (
1128
 
                                isValue( next(), ',' ) &&
1129
 
                                isNum( next() )
1130
 
                            ) ) &&
1131
 
                            isValue( next(), ')' ) ) {
1132
 
                            return newToken( Type.COLOR, this.css.substring( ch, this.ch ) );
1133
 
                        }
1134
 
                        return failure();
1135
 
                    }
1136
 
 
1137
 
                    return newToken( Type.FUNCTION, val );
1138
 
                }
1139
 
 
1140
 
                // Other identifier
1141
 
                return newToken( Type.IDENT, val );
1142
 
            }
1143
 
 
1144
 
            // Standalone character
1145
 
            this.ch++;
1146
 
            return newToken( Type.CHARACTER, firstChar );
1147
 
        },
1148
 
 
1149
 
        /**
1150
 
         * Determine whether there is another token
1151
 
         * @return {boolean}
1152
 
         */
1153
 
        hasNext: function() {
1154
 
            var next = this.next();
1155
 
            this.prev();
1156
 
            return !!next;
1157
 
        },
1158
 
 
1159
 
        /**
1160
 
         * Back up and return the previous token
1161
 
         * @return {PIE.Tokenizer.Token}
1162
 
         */
1163
 
        prev: function() {
1164
 
            return this.tokens[ this.tokenIndex-- - 2 ];
1165
 
        },
1166
 
 
1167
 
        /**
1168
 
         * Retrieve all the tokens in the CSS string
1169
 
         * @return {Array.<PIE.Tokenizer.Token>}
1170
 
         */
1171
 
        all: function() {
1172
 
            while( this.next() ) {}
1173
 
            return this.tokens;
1174
 
        },
1175
 
 
1176
 
        /**
1177
 
         * Return a list of tokens from the current position until the given function returns
1178
 
         * true. The final token will not be included in the list.
1179
 
         * @param {function():boolean} func - test function
1180
 
         * @param {boolean} require - if true, then if the end of the CSS string is reached
1181
 
         *        before the test function returns true, null will be returned instead of the
1182
 
         *        tokens that have been found so far.
1183
 
         * @return {Array.<PIE.Tokenizer.Token>}
1184
 
         */
1185
 
        until: function( func, require ) {
1186
 
            var list = [], t, hit;
1187
 
            while( t = this.next() ) {
1188
 
                if( func( t ) ) {
1189
 
                    hit = true;
1190
 
                    this.prev();
1191
 
                    break;
1192
 
                }
1193
 
                list.push( t );
1194
 
            }
1195
 
            return require && !hit ? null : list;
1196
 
        }
1197
 
    };
1198
 
 
1199
 
    return Tokenizer;
1200
 
})();/**
1201
 
 * Handles calculating, caching, and detecting changes to size and position of the element.
1202
 
 * @constructor
1203
 
 * @param {Element} el the target element
1204
 
 */
1205
 
PIE.BoundsInfo = function( el ) {
1206
 
    this.targetElement = el;
1207
 
};
1208
 
PIE.BoundsInfo.prototype = {
1209
 
 
1210
 
    _locked: 0,
1211
 
 
1212
 
    positionChanged: function() {
1213
 
        var last = this._lastBounds,
1214
 
            bounds;
1215
 
        return !last || ( ( bounds = this.getBounds() ) && ( last.x !== bounds.x || last.y !== bounds.y ) );
1216
 
    },
1217
 
 
1218
 
    sizeChanged: function() {
1219
 
        var last = this._lastBounds,
1220
 
            bounds;
1221
 
        return !last || ( ( bounds = this.getBounds() ) && ( last.w !== bounds.w || last.h !== bounds.h ) );
1222
 
    },
1223
 
 
1224
 
    getLiveBounds: function() {
1225
 
        var el = this.targetElement,
1226
 
            rect = el.getBoundingClientRect(),
1227
 
            isIE9 = PIE.ieDocMode === 9;
1228
 
        return {
1229
 
            x: rect.left,
1230
 
            y: rect.top,
1231
 
            // In some cases scrolling the page will cause IE9 to report incorrect dimensions
1232
 
            // in the rect returned by getBoundingClientRect, so we must query offsetWidth/Height instead
1233
 
            w: isIE9 ? el.offsetWidth : rect.right - rect.left,
1234
 
            h: isIE9 ? el.offsetHeight : rect.bottom - rect.top
1235
 
        };
1236
 
    },
1237
 
 
1238
 
    getBounds: function() {
1239
 
        return this._locked ? 
1240
 
                ( this._lockedBounds || ( this._lockedBounds = this.getLiveBounds() ) ) :
1241
 
                this.getLiveBounds();
1242
 
    },
1243
 
 
1244
 
    hasBeenQueried: function() {
1245
 
        return !!this._lastBounds;
1246
 
    },
1247
 
 
1248
 
    lock: function() {
1249
 
        ++this._locked;
1250
 
    },
1251
 
 
1252
 
    unlock: function() {
1253
 
        if( !--this._locked ) {
1254
 
            if( this._lockedBounds ) this._lastBounds = this._lockedBounds;
1255
 
            this._lockedBounds = null;
1256
 
        }
1257
 
    }
1258
 
 
1259
 
};
1260
 
(function() {
1261
 
 
1262
 
function cacheWhenLocked( fn ) {
1263
 
    var uid = PIE.Util.getUID( fn );
1264
 
    return function() {
1265
 
        if( this._locked ) {
1266
 
            var cache = this._lockedValues || ( this._lockedValues = {} );
1267
 
            return ( uid in cache ) ? cache[ uid ] : ( cache[ uid ] = fn.call( this ) );
1268
 
        } else {
1269
 
            return fn.call( this );
1270
 
        }
1271
 
    }
1272
 
}
1273
 
 
1274
 
 
1275
 
PIE.StyleInfoBase = {
1276
 
 
1277
 
    _locked: 0,
1278
 
 
1279
 
    /**
1280
 
     * Create a new StyleInfo class, with the standard constructor, and augmented by
1281
 
     * the StyleInfoBase's members.
1282
 
     * @param proto
1283
 
     */
1284
 
    newStyleInfo: function( proto ) {
1285
 
        function StyleInfo( el ) {
1286
 
            this.targetElement = el;
1287
 
            this._lastCss = this.getCss();
1288
 
        }
1289
 
        PIE.Util.merge( StyleInfo.prototype, PIE.StyleInfoBase, proto );
1290
 
        StyleInfo._propsCache = {};
1291
 
        return StyleInfo;
1292
 
    },
1293
 
 
1294
 
    /**
1295
 
     * Get an object representation of the target CSS style, caching it for each unique
1296
 
     * CSS value string.
1297
 
     * @return {Object}
1298
 
     */
1299
 
    getProps: function() {
1300
 
        var css = this.getCss(),
1301
 
            cache = this.constructor._propsCache;
1302
 
        return css ? ( css in cache ? cache[ css ] : ( cache[ css ] = this.parseCss( css ) ) ) : null;
1303
 
    },
1304
 
 
1305
 
    /**
1306
 
     * Get the raw CSS value for the target style
1307
 
     * @return {string}
1308
 
     */
1309
 
    getCss: cacheWhenLocked( function() {
1310
 
        var el = this.targetElement,
1311
 
            ctor = this.constructor,
1312
 
            s = el.style,
1313
 
            cs = el.currentStyle,
1314
 
            cssProp = this.cssProperty,
1315
 
            styleProp = this.styleProperty,
1316
 
            prefixedCssProp = ctor._prefixedCssProp || ( ctor._prefixedCssProp = PIE.CSS_PREFIX + cssProp ),
1317
 
            prefixedStyleProp = ctor._prefixedStyleProp || ( ctor._prefixedStyleProp = PIE.STYLE_PREFIX + styleProp.charAt(0).toUpperCase() + styleProp.substring(1) );
1318
 
        return s[ prefixedStyleProp ] || cs.getAttribute( prefixedCssProp ) || s[ styleProp ] || cs.getAttribute( cssProp );
1319
 
    } ),
1320
 
 
1321
 
    /**
1322
 
     * Determine whether the target CSS style is active.
1323
 
     * @return {boolean}
1324
 
     */
1325
 
    isActive: cacheWhenLocked( function() {
1326
 
        return !!this.getProps();
1327
 
    } ),
1328
 
 
1329
 
    /**
1330
 
     * Determine whether the target CSS style has changed since the last time it was used.
1331
 
     * @return {boolean}
1332
 
     */
1333
 
    changed: cacheWhenLocked( function() {
1334
 
        var currentCss = this.getCss(),
1335
 
            changed = currentCss !== this._lastCss;
1336
 
        this._lastCss = currentCss;
1337
 
        return changed;
1338
 
    } ),
1339
 
 
1340
 
    cacheWhenLocked: cacheWhenLocked,
1341
 
 
1342
 
    lock: function() {
1343
 
        ++this._locked;
1344
 
    },
1345
 
 
1346
 
    unlock: function() {
1347
 
        if( !--this._locked ) {
1348
 
            delete this._lockedValues;
1349
 
        }
1350
 
    }
1351
 
};
1352
 
 
1353
 
})();/**
1354
 
 * Handles parsing, caching, and detecting changes to background (and -pie-background) CSS
1355
 
 * @constructor
1356
 
 * @param {Element} el the target element
1357
 
 */
1358
 
PIE.BackgroundStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
1359
 
 
1360
 
    cssProperty: PIE.CSS_PREFIX + 'background',
1361
 
    styleProperty: PIE.STYLE_PREFIX + 'Background',
1362
 
 
1363
 
    attachIdents: { 'scroll':1, 'fixed':1, 'local':1 },
1364
 
    repeatIdents: { 'repeat-x':1, 'repeat-y':1, 'repeat':1, 'no-repeat':1 },
1365
 
    originAndClipIdents: { 'padding-box':1, 'border-box':1, 'content-box':1 },
1366
 
    positionIdents: { 'top':1, 'right':1, 'bottom':1, 'left':1, 'center':1 },
1367
 
    sizeIdents: { 'contain':1, 'cover':1 },
1368
 
    propertyNames: {
1369
 
        CLIP: 'backgroundClip',
1370
 
        COLOR: 'backgroundColor',
1371
 
        IMAGE: 'backgroundImage',
1372
 
        ORIGIN: 'backgroundOrigin',
1373
 
        POSITION: 'backgroundPosition',
1374
 
        REPEAT: 'backgroundRepeat',
1375
 
        SIZE: 'backgroundSize'
1376
 
    },
1377
 
 
1378
 
    /**
1379
 
     * For background styles, we support the -pie-background property but fall back to the standard
1380
 
     * backround* properties.  The reason we have to use the prefixed version is that IE natively
1381
 
     * parses the standard properties and if it sees something it doesn't know how to parse, for example
1382
 
     * multiple values or gradient definitions, it will throw that away and not make it available through
1383
 
     * currentStyle.
1384
 
     *
1385
 
     * Format of return object:
1386
 
     * {
1387
 
     *     color: <PIE.Color>,
1388
 
     *     bgImages: [
1389
 
     *         {
1390
 
     *             imgType: 'image',
1391
 
     *             imgUrl: 'image.png',
1392
 
     *             imgRepeat: <'no-repeat' | 'repeat-x' | 'repeat-y' | 'repeat'>,
1393
 
     *             bgPosition: <PIE.BgPosition>,
1394
 
     *             bgAttachment: <'scroll' | 'fixed' | 'local'>,
1395
 
     *             bgOrigin: <'border-box' | 'padding-box' | 'content-box'>,
1396
 
     *             bgClip: <'border-box' | 'padding-box'>,
1397
 
     *             bgSize: <PIE.BgSize>,
1398
 
     *             origString: 'url(img.png) no-repeat top left'
1399
 
     *         },
1400
 
     *         {
1401
 
     *             imgType: 'linear-gradient',
1402
 
     *             gradientStart: <PIE.BgPosition>,
1403
 
     *             angle: <PIE.Angle>,
1404
 
     *             stops: [
1405
 
     *                 { color: <PIE.Color>, offset: <PIE.Length> },
1406
 
     *                 { color: <PIE.Color>, offset: <PIE.Length> }, ...
1407
 
     *             ]
1408
 
     *         }
1409
 
     *     ]
1410
 
     * }
1411
 
     * @param {String} css
1412
 
     * @override
1413
 
     */
1414
 
    parseCss: function( css ) {
1415
 
        var el = this.targetElement,
1416
 
            cs = el.currentStyle,
1417
 
            tokenizer, token, image,
1418
 
            tok_type = PIE.Tokenizer.Type,
1419
 
            type_operator = tok_type.OPERATOR,
1420
 
            type_ident = tok_type.IDENT,
1421
 
            type_color = tok_type.COLOR,
1422
 
            tokType, tokVal,
1423
 
            beginCharIndex = 0,
1424
 
            positionIdents = this.positionIdents,
1425
 
            gradient, stop, width, height,
1426
 
            props = { bgImages: [] };
1427
 
 
1428
 
        function isBgPosToken( token ) {
1429
 
            return token && token.isLengthOrPercent() || ( token.tokenType & type_ident && token.tokenValue in positionIdents );
1430
 
        }
1431
 
 
1432
 
        function sizeToken( token ) {
1433
 
            return token && ( ( token.isLengthOrPercent() && PIE.getLength( token.tokenValue ) ) || ( token.tokenValue === 'auto' && 'auto' ) );
1434
 
        }
1435
 
 
1436
 
        // If the CSS3-specific -pie-background property is present, parse it
1437
 
        if( this.getCss3() ) {
1438
 
            tokenizer = new PIE.Tokenizer( css );
1439
 
            image = {};
1440
 
 
1441
 
            while( token = tokenizer.next() ) {
1442
 
                tokType = token.tokenType;
1443
 
                tokVal = token.tokenValue;
1444
 
 
1445
 
                if( !image.imgType && tokType & tok_type.FUNCTION && tokVal === 'linear-gradient' ) {
1446
 
                    gradient = { stops: [], imgType: tokVal };
1447
 
                    stop = {};
1448
 
                    while( token = tokenizer.next() ) {
1449
 
                        tokType = token.tokenType;
1450
 
                        tokVal = token.tokenValue;
1451
 
 
1452
 
                        // If we reached the end of the function and had at least 2 stops, flush the info
1453
 
                        if( tokType & tok_type.CHARACTER && tokVal === ')' ) {
1454
 
                            if( stop.color ) {
1455
 
                                gradient.stops.push( stop );
1456
 
                            }
1457
 
                            if( gradient.stops.length > 1 ) {
1458
 
                                PIE.Util.merge( image, gradient );
1459
 
                            }
1460
 
                            break;
1461
 
                        }
1462
 
 
1463
 
                        // Color stop - must start with color
1464
 
                        if( tokType & type_color ) {
1465
 
                            // if we already have an angle/position, make sure that the previous token was a comma
1466
 
                            if( gradient.angle || gradient.gradientStart ) {
1467
 
                                token = tokenizer.prev();
1468
 
                                if( token.tokenType !== type_operator ) {
1469
 
                                    break; //fail
1470
 
                                }
1471
 
                                tokenizer.next();
1472
 
                            }
1473
 
 
1474
 
                            stop = {
1475
 
                                color: PIE.getColor( tokVal )
1476
 
                            };
1477
 
                            // check for offset following color
1478
 
                            token = tokenizer.next();
1479
 
                            if( token.isLengthOrPercent() ) {
1480
 
                                stop.offset = PIE.getLength( token.tokenValue );
1481
 
                            } else {
1482
 
                                tokenizer.prev();
1483
 
                            }
1484
 
                        }
1485
 
                        // Angle - can only appear in first spot
1486
 
                        else if( tokType & tok_type.ANGLE && !gradient.angle && !stop.color && !gradient.stops.length ) {
1487
 
                            gradient.angle = new PIE.Angle( token.tokenValue );
1488
 
                        }
1489
 
                        else if( isBgPosToken( token ) && !gradient.gradientStart && !stop.color && !gradient.stops.length ) {
1490
 
                            tokenizer.prev();
1491
 
                            gradient.gradientStart = new PIE.BgPosition(
1492
 
                                tokenizer.until( function( t ) {
1493
 
                                    return !isBgPosToken( t );
1494
 
                                }, false )
1495
 
                            );
1496
 
                        }
1497
 
                        else if( tokType & type_operator && tokVal === ',' ) {
1498
 
                            if( stop.color ) {
1499
 
                                gradient.stops.push( stop );
1500
 
                                stop = {};
1501
 
                            }
1502
 
                        }
1503
 
                        else {
1504
 
                            // Found something we didn't recognize; fail without adding image
1505
 
                            break;
1506
 
                        }
1507
 
                    }
1508
 
                }
1509
 
                else if( !image.imgType && tokType & tok_type.URL ) {
1510
 
                    image.imgUrl = tokVal;
1511
 
                    image.imgType = 'image';
1512
 
                }
1513
 
                else if( isBgPosToken( token ) && !image.bgPosition ) {
1514
 
                    tokenizer.prev();
1515
 
                    image.bgPosition = new PIE.BgPosition(
1516
 
                        tokenizer.until( function( t ) {
1517
 
                            return !isBgPosToken( t );
1518
 
                        }, false )
1519
 
                    );
1520
 
                }
1521
 
                else if( tokType & type_ident ) {
1522
 
                    if( tokVal in this.repeatIdents && !image.imgRepeat ) {
1523
 
                        image.imgRepeat = tokVal;
1524
 
                    }
1525
 
                    else if( tokVal in this.originAndClipIdents && !image.bgOrigin ) {
1526
 
                        image.bgOrigin = tokVal;
1527
 
                        if( ( token = tokenizer.next() ) && ( token.tokenType & type_ident ) &&
1528
 
                            token.tokenValue in this.originAndClipIdents ) {
1529
 
                            image.bgClip = token.tokenValue;
1530
 
                        } else {
1531
 
                            image.bgClip = tokVal;
1532
 
                            tokenizer.prev();
1533
 
                        }
1534
 
                    }
1535
 
                    else if( tokVal in this.attachIdents && !image.bgAttachment ) {
1536
 
                        image.bgAttachment = tokVal;
1537
 
                    }
1538
 
                    else {
1539
 
                        return null;
1540
 
                    }
1541
 
                }
1542
 
                else if( tokType & type_color && !props.color ) {
1543
 
                    props.color = PIE.getColor( tokVal );
1544
 
                }
1545
 
                else if( tokType & type_operator && tokVal === '/' && !image.bgSize && image.bgPosition ) {
1546
 
                    // background size
1547
 
                    token = tokenizer.next();
1548
 
                    if( token.tokenType & type_ident && token.tokenValue in this.sizeIdents ) {
1549
 
                        image.bgSize = new PIE.BgSize( token.tokenValue );
1550
 
                    }
1551
 
                    else if( width = sizeToken( token ) ) {
1552
 
                        height = sizeToken( tokenizer.next() );
1553
 
                        if ( !height ) {
1554
 
                            height = width;
1555
 
                            tokenizer.prev();
1556
 
                        }
1557
 
                        image.bgSize = new PIE.BgSize( width, height );
1558
 
                    }
1559
 
                    else {
1560
 
                        return null;
1561
 
                    }
1562
 
                }
1563
 
                // new layer
1564
 
                else if( tokType & type_operator && tokVal === ',' && image.imgType ) {
1565
 
                    image.origString = css.substring( beginCharIndex, tokenizer.ch - 1 );
1566
 
                    beginCharIndex = tokenizer.ch;
1567
 
                    props.bgImages.push( image );
1568
 
                    image = {};
1569
 
                }
1570
 
                else {
1571
 
                    // Found something unrecognized; chuck everything
1572
 
                    return null;
1573
 
                }
1574
 
            }
1575
 
 
1576
 
            // leftovers
1577
 
            if( image.imgType ) {
1578
 
                image.origString = css.substring( beginCharIndex );
1579
 
                props.bgImages.push( image );
1580
 
            }
1581
 
        }
1582
 
 
1583
 
        // Otherwise, use the standard background properties; let IE give us the values rather than parsing them
1584
 
        else {
1585
 
            this.withActualBg( PIE.ieDocMode < 9 ?
1586
 
                function() {
1587
 
                    var propNames = this.propertyNames,
1588
 
                        posX = cs[propNames.POSITION + 'X'],
1589
 
                        posY = cs[propNames.POSITION + 'Y'],
1590
 
                        img = cs[propNames.IMAGE],
1591
 
                        color = cs[propNames.COLOR];
1592
 
 
1593
 
                    if( color !== 'transparent' ) {
1594
 
                        props.color = PIE.getColor( color )
1595
 
                    }
1596
 
                    if( img !== 'none' ) {
1597
 
                        props.bgImages = [ {
1598
 
                            imgType: 'image',
1599
 
                            imgUrl: new PIE.Tokenizer( img ).next().tokenValue,
1600
 
                            imgRepeat: cs[propNames.REPEAT],
1601
 
                            bgPosition: new PIE.BgPosition( new PIE.Tokenizer( posX + ' ' + posY ).all() )
1602
 
                        } ];
1603
 
                    }
1604
 
                } :
1605
 
                function() {
1606
 
                    var propNames = this.propertyNames,
1607
 
                        splitter = /\s*,\s*/,
1608
 
                        images = cs[propNames.IMAGE].split( splitter ),
1609
 
                        color = cs[propNames.COLOR],
1610
 
                        repeats, positions, origins, clips, sizes, i, len, image, sizeParts;
1611
 
 
1612
 
                    if( color !== 'transparent' ) {
1613
 
                        props.color = PIE.getColor( color )
1614
 
                    }
1615
 
 
1616
 
                    len = images.length;
1617
 
                    if( len && images[0] !== 'none' ) {
1618
 
                        repeats = cs[propNames.REPEAT].split( splitter );
1619
 
                        positions = cs[propNames.POSITION].split( splitter );
1620
 
                        origins = cs[propNames.ORIGIN].split( splitter );
1621
 
                        clips = cs[propNames.CLIP].split( splitter );
1622
 
                        sizes = cs[propNames.SIZE].split( splitter );
1623
 
 
1624
 
                        props.bgImages = [];
1625
 
                        for( i = 0; i < len; i++ ) {
1626
 
                            image = images[ i ];
1627
 
                            if( image && image !== 'none' ) {
1628
 
                                sizeParts = sizes[i].split( ' ' );
1629
 
                                props.bgImages.push( {
1630
 
                                    origString: image + ' ' + repeats[ i ] + ' ' + positions[ i ] + ' / ' + sizes[ i ] + ' ' +
1631
 
                                                origins[ i ] + ' ' + clips[ i ],
1632
 
                                    imgType: 'image',
1633
 
                                    imgUrl: new PIE.Tokenizer( image ).next().tokenValue,
1634
 
                                    imgRepeat: repeats[ i ],
1635
 
                                    bgPosition: new PIE.BgPosition( new PIE.Tokenizer( positions[ i ] ).all() ),
1636
 
                                    bgOrigin: origins[ i ],
1637
 
                                    bgClip: clips[ i ],
1638
 
                                    bgSize: new PIE.BgSize( sizeParts[ 0 ], sizeParts[ 1 ] )
1639
 
                                } );
1640
 
                            }
1641
 
                        }
1642
 
                    }
1643
 
                }
1644
 
            );
1645
 
        }
1646
 
 
1647
 
        return ( props.color || props.bgImages[0] ) ? props : null;
1648
 
    },
1649
 
 
1650
 
    /**
1651
 
     * Execute a function with the actual background styles (not overridden with runtimeStyle
1652
 
     * properties set by the renderers) available via currentStyle.
1653
 
     * @param fn
1654
 
     */
1655
 
    withActualBg: function( fn ) {
1656
 
        var isIE9 = PIE.ieDocMode > 8,
1657
 
            propNames = this.propertyNames,
1658
 
            rs = this.targetElement.runtimeStyle,
1659
 
            rsImage = rs[propNames.IMAGE],
1660
 
            rsColor = rs[propNames.COLOR],
1661
 
            rsRepeat = rs[propNames.REPEAT],
1662
 
            rsClip, rsOrigin, rsSize, rsPosition, ret;
1663
 
 
1664
 
        if( rsImage ) rs[propNames.IMAGE] = '';
1665
 
        if( rsColor ) rs[propNames.COLOR] = '';
1666
 
        if( rsRepeat ) rs[propNames.REPEAT] = '';
1667
 
        if( isIE9 ) {
1668
 
            rsClip = rs[propNames.CLIP];
1669
 
            rsOrigin = rs[propNames.ORIGIN];
1670
 
            rsPosition = rs[propNames.POSITION];
1671
 
            rsSize = rs[propNames.SIZE];
1672
 
            if( rsClip ) rs[propNames.CLIP] = '';
1673
 
            if( rsOrigin ) rs[propNames.ORIGIN] = '';
1674
 
            if( rsPosition ) rs[propNames.POSITION] = '';
1675
 
            if( rsSize ) rs[propNames.SIZE] = '';
1676
 
        }
1677
 
 
1678
 
        ret = fn.call( this );
1679
 
 
1680
 
        if( rsImage ) rs[propNames.IMAGE] = rsImage;
1681
 
        if( rsColor ) rs[propNames.COLOR] = rsColor;
1682
 
        if( rsRepeat ) rs[propNames.REPEAT] = rsRepeat;
1683
 
        if( isIE9 ) {
1684
 
            if( rsClip ) rs[propNames.CLIP] = rsClip;
1685
 
            if( rsOrigin ) rs[propNames.ORIGIN] = rsOrigin;
1686
 
            if( rsPosition ) rs[propNames.POSITION] = rsPosition;
1687
 
            if( rsSize ) rs[propNames.SIZE] = rsSize;
1688
 
        }
1689
 
 
1690
 
        return ret;
1691
 
    },
1692
 
 
1693
 
    getCss: PIE.StyleInfoBase.cacheWhenLocked( function() {
1694
 
        return this.getCss3() ||
1695
 
               this.withActualBg( function() {
1696
 
                   var cs = this.targetElement.currentStyle,
1697
 
                       propNames = this.propertyNames;
1698
 
                   return cs[propNames.COLOR] + ' ' + cs[propNames.IMAGE] + ' ' + cs[propNames.REPEAT] + ' ' +
1699
 
                   cs[propNames.POSITION + 'X'] + ' ' + cs[propNames.POSITION + 'Y'];
1700
 
               } );
1701
 
    } ),
1702
 
 
1703
 
    getCss3: PIE.StyleInfoBase.cacheWhenLocked( function() {
1704
 
        var el = this.targetElement;
1705
 
        return el.style[ this.styleProperty ] || el.currentStyle.getAttribute( this.cssProperty );
1706
 
    } ),
1707
 
 
1708
 
    /**
1709
 
     * Tests if style.PiePngFix or the -pie-png-fix property is set to true in IE6.
1710
 
     */
1711
 
    isPngFix: function() {
1712
 
        var val = 0, el;
1713
 
        if( PIE.ieVersion < 7 ) {
1714
 
            el = this.targetElement;
1715
 
            val = ( '' + ( el.style[ PIE.STYLE_PREFIX + 'PngFix' ] || el.currentStyle.getAttribute( PIE.CSS_PREFIX + 'png-fix' ) ) === 'true' );
1716
 
        }
1717
 
        return val;
1718
 
    },
1719
 
    
1720
 
    /**
1721
 
     * The isActive logic is slightly different, because getProps() always returns an object
1722
 
     * even if it is just falling back to the native background properties.  But we only want
1723
 
     * to report is as being "active" if either the -pie-background override property is present
1724
 
     * and parses successfully or '-pie-png-fix' is set to true in IE6.
1725
 
     */
1726
 
    isActive: PIE.StyleInfoBase.cacheWhenLocked( function() {
1727
 
        return (this.getCss3() || this.isPngFix()) && !!this.getProps();
1728
 
    } )
1729
 
 
1730
 
} );/**
1731
 
 * Handles parsing, caching, and detecting changes to border CSS
1732
 
 * @constructor
1733
 
 * @param {Element} el the target element
1734
 
 */
1735
 
PIE.BorderStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
1736
 
 
1737
 
    sides: [ 'Top', 'Right', 'Bottom', 'Left' ],
1738
 
    namedWidths: {
1739
 
        'thin': '1px',
1740
 
        'medium': '3px',
1741
 
        'thick': '5px'
1742
 
    },
1743
 
 
1744
 
    parseCss: function( css ) {
1745
 
        var w = {},
1746
 
            s = {},
1747
 
            c = {},
1748
 
            active = false,
1749
 
            colorsSame = true,
1750
 
            stylesSame = true,
1751
 
            widthsSame = true;
1752
 
 
1753
 
        this.withActualBorder( function() {
1754
 
            var el = this.targetElement,
1755
 
                cs = el.currentStyle,
1756
 
                i = 0,
1757
 
                style, color, width, lastStyle, lastColor, lastWidth, side, ltr;
1758
 
            for( ; i < 4; i++ ) {
1759
 
                side = this.sides[ i ];
1760
 
 
1761
 
                ltr = side.charAt(0).toLowerCase();
1762
 
                style = s[ ltr ] = cs[ 'border' + side + 'Style' ];
1763
 
                color = cs[ 'border' + side + 'Color' ];
1764
 
                width = cs[ 'border' + side + 'Width' ];
1765
 
 
1766
 
                if( i > 0 ) {
1767
 
                    if( style !== lastStyle ) { stylesSame = false; }
1768
 
                    if( color !== lastColor ) { colorsSame = false; }
1769
 
                    if( width !== lastWidth ) { widthsSame = false; }
1770
 
                }
1771
 
                lastStyle = style;
1772
 
                lastColor = color;
1773
 
                lastWidth = width;
1774
 
 
1775
 
                c[ ltr ] = PIE.getColor( color );
1776
 
 
1777
 
                width = w[ ltr ] = PIE.getLength( s[ ltr ] === 'none' ? '0' : ( this.namedWidths[ width ] || width ) );
1778
 
                if( width.pixels( this.targetElement ) > 0 ) {
1779
 
                    active = true;
1780
 
                }
1781
 
            }
1782
 
        } );
1783
 
 
1784
 
        return active ? {
1785
 
            widths: w,
1786
 
            styles: s,
1787
 
            colors: c,
1788
 
            widthsSame: widthsSame,
1789
 
            colorsSame: colorsSame,
1790
 
            stylesSame: stylesSame
1791
 
        } : null;
1792
 
    },
1793
 
 
1794
 
    getCss: PIE.StyleInfoBase.cacheWhenLocked( function() {
1795
 
        var el = this.targetElement,
1796
 
            cs = el.currentStyle,
1797
 
            css;
1798
 
 
1799
 
        // Don't redraw or hide borders for cells in border-collapse:collapse tables
1800
 
        if( !( el.tagName in PIE.tableCellTags && el.offsetParent.currentStyle.borderCollapse === 'collapse' ) ) {
1801
 
            this.withActualBorder( function() {
1802
 
                css = cs.borderWidth + '|' + cs.borderStyle + '|' + cs.borderColor;
1803
 
            } );
1804
 
        }
1805
 
        return css;
1806
 
    } ),
1807
 
 
1808
 
    /**
1809
 
     * Execute a function with the actual border styles (not overridden with runtimeStyle
1810
 
     * properties set by the renderers) available via currentStyle.
1811
 
     * @param fn
1812
 
     */
1813
 
    withActualBorder: function( fn ) {
1814
 
        var rs = this.targetElement.runtimeStyle,
1815
 
            rsWidth = rs.borderWidth,
1816
 
            rsColor = rs.borderColor,
1817
 
            ret;
1818
 
 
1819
 
        if( rsWidth ) rs.borderWidth = '';
1820
 
        if( rsColor ) rs.borderColor = '';
1821
 
 
1822
 
        ret = fn.call( this );
1823
 
 
1824
 
        if( rsWidth ) rs.borderWidth = rsWidth;
1825
 
        if( rsColor ) rs.borderColor = rsColor;
1826
 
 
1827
 
        return ret;
1828
 
    }
1829
 
 
1830
 
} );
1831
 
/**
1832
 
 * Handles parsing, caching, and detecting changes to border-radius CSS
1833
 
 * @constructor
1834
 
 * @param {Element} el the target element
1835
 
 */
1836
 
(function() {
1837
 
 
1838
 
PIE.BorderRadiusStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
1839
 
 
1840
 
    cssProperty: 'border-radius',
1841
 
    styleProperty: 'borderRadius',
1842
 
 
1843
 
    parseCss: function( css ) {
1844
 
        var p = null, x, y,
1845
 
            tokenizer, token, length,
1846
 
            hasNonZero = false;
1847
 
 
1848
 
        if( css ) {
1849
 
            tokenizer = new PIE.Tokenizer( css );
1850
 
 
1851
 
            function collectLengths() {
1852
 
                var arr = [], num;
1853
 
                while( ( token = tokenizer.next() ) && token.isLengthOrPercent() ) {
1854
 
                    length = PIE.getLength( token.tokenValue );
1855
 
                    num = length.getNumber();
1856
 
                    if( num < 0 ) {
1857
 
                        return null;
1858
 
                    }
1859
 
                    if( num > 0 ) {
1860
 
                        hasNonZero = true;
1861
 
                    }
1862
 
                    arr.push( length );
1863
 
                }
1864
 
                return arr.length > 0 && arr.length < 5 ? {
1865
 
                        'tl': arr[0],
1866
 
                        'tr': arr[1] || arr[0],
1867
 
                        'br': arr[2] || arr[0],
1868
 
                        'bl': arr[3] || arr[1] || arr[0]
1869
 
                    } : null;
1870
 
            }
1871
 
 
1872
 
            // Grab the initial sequence of lengths
1873
 
            if( x = collectLengths() ) {
1874
 
                // See if there is a slash followed by more lengths, for the y-axis radii
1875
 
                if( token ) {
1876
 
                    if( token.tokenType & PIE.Tokenizer.Type.OPERATOR && token.tokenValue === '/' ) {
1877
 
                        y = collectLengths();
1878
 
                    }
1879
 
                } else {
1880
 
                    y = x;
1881
 
                }
1882
 
 
1883
 
                // Treat all-zero values the same as no value
1884
 
                if( hasNonZero && x && y ) {
1885
 
                    p = { x: x, y : y };
1886
 
                }
1887
 
            }
1888
 
        }
1889
 
 
1890
 
        return p;
1891
 
    }
1892
 
} );
1893
 
 
1894
 
var zero = PIE.getLength( '0' ),
1895
 
    zeros = { 'tl': zero, 'tr': zero, 'br': zero, 'bl': zero };
1896
 
PIE.BorderRadiusStyleInfo.ALL_ZERO = { x: zeros, y: zeros };
1897
 
 
1898
 
})();/**
1899
 
 * Handles parsing, caching, and detecting changes to border-image CSS
1900
 
 * @constructor
1901
 
 * @param {Element} el the target element
1902
 
 */
1903
 
PIE.BorderImageStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
1904
 
 
1905
 
    cssProperty: 'border-image',
1906
 
    styleProperty: 'borderImage',
1907
 
 
1908
 
    repeatIdents: { 'stretch':1, 'round':1, 'repeat':1, 'space':1 },
1909
 
 
1910
 
    parseCss: function( css ) {
1911
 
        var p = null, tokenizer, token, type, value,
1912
 
            slices, widths, outsets,
1913
 
            slashCount = 0,
1914
 
            Type = PIE.Tokenizer.Type,
1915
 
            IDENT = Type.IDENT,
1916
 
            NUMBER = Type.NUMBER,
1917
 
            PERCENT = Type.PERCENT;
1918
 
 
1919
 
        if( css ) {
1920
 
            tokenizer = new PIE.Tokenizer( css );
1921
 
            p = {};
1922
 
 
1923
 
            function isSlash( token ) {
1924
 
                return token && ( token.tokenType & Type.OPERATOR ) && ( token.tokenValue === '/' );
1925
 
            }
1926
 
 
1927
 
            function isFillIdent( token ) {
1928
 
                return token && ( token.tokenType & IDENT ) && ( token.tokenValue === 'fill' );
1929
 
            }
1930
 
 
1931
 
            function collectSlicesEtc() {
1932
 
                slices = tokenizer.until( function( tok ) {
1933
 
                    return !( tok.tokenType & ( NUMBER | PERCENT ) );
1934
 
                } );
1935
 
 
1936
 
                if( isFillIdent( tokenizer.next() ) && !p.fill ) {
1937
 
                    p.fill = true;
1938
 
                } else {
1939
 
                    tokenizer.prev();
1940
 
                }
1941
 
 
1942
 
                if( isSlash( tokenizer.next() ) ) {
1943
 
                    slashCount++;
1944
 
                    widths = tokenizer.until( function( token ) {
1945
 
                        return !token.isLengthOrPercent() && !( ( token.tokenType & IDENT ) && token.tokenValue === 'auto' );
1946
 
                    } );
1947
 
 
1948
 
                    if( isSlash( tokenizer.next() ) ) {
1949
 
                        slashCount++;
1950
 
                        outsets = tokenizer.until( function( token ) {
1951
 
                            return !token.isLength();
1952
 
                        } );
1953
 
                    }
1954
 
                } else {
1955
 
                    tokenizer.prev();
1956
 
                }
1957
 
            }
1958
 
 
1959
 
            while( token = tokenizer.next() ) {
1960
 
                type = token.tokenType;
1961
 
                value = token.tokenValue;
1962
 
 
1963
 
                // Numbers and/or 'fill' keyword: slice values. May be followed optionally by width values, followed optionally by outset values
1964
 
                if( type & ( NUMBER | PERCENT ) && !slices ) {
1965
 
                    tokenizer.prev();
1966
 
                    collectSlicesEtc();
1967
 
                }
1968
 
                else if( isFillIdent( token ) && !p.fill ) {
1969
 
                    p.fill = true;
1970
 
                    collectSlicesEtc();
1971
 
                }
1972
 
 
1973
 
                // Idents: one or values for 'repeat'
1974
 
                else if( ( type & IDENT ) && this.repeatIdents[value] && !p.repeat ) {
1975
 
                    p.repeat = { h: value };
1976
 
                    if( token = tokenizer.next() ) {
1977
 
                        if( ( token.tokenType & IDENT ) && this.repeatIdents[token.tokenValue] ) {
1978
 
                            p.repeat.v = token.tokenValue;
1979
 
                        } else {
1980
 
                            tokenizer.prev();
1981
 
                        }
1982
 
                    }
1983
 
                }
1984
 
 
1985
 
                // URL of the image
1986
 
                else if( ( type & Type.URL ) && !p.src ) {
1987
 
                    p.src =  value;
1988
 
                }
1989
 
 
1990
 
                // Found something unrecognized; exit.
1991
 
                else {
1992
 
                    return null;
1993
 
                }
1994
 
            }
1995
 
 
1996
 
            // Validate what we collected
1997
 
            if( !p.src || !slices || slices.length < 1 || slices.length > 4 ||
1998
 
                ( widths && widths.length > 4 ) || ( slashCount === 1 && widths.length < 1 ) ||
1999
 
                ( outsets && outsets.length > 4 ) || ( slashCount === 2 && outsets.length < 1 ) ) {
2000
 
                return null;
2001
 
            }
2002
 
 
2003
 
            // Fill in missing values
2004
 
            if( !p.repeat ) {
2005
 
                p.repeat = { h: 'stretch' };
2006
 
            }
2007
 
            if( !p.repeat.v ) {
2008
 
                p.repeat.v = p.repeat.h;
2009
 
            }
2010
 
 
2011
 
            function distributeSides( tokens, convertFn ) {
2012
 
                return {
2013
 
                    't': convertFn( tokens[0] ),
2014
 
                    'r': convertFn( tokens[1] || tokens[0] ),
2015
 
                    'b': convertFn( tokens[2] || tokens[0] ),
2016
 
                    'l': convertFn( tokens[3] || tokens[1] || tokens[0] )
2017
 
                };
2018
 
            }
2019
 
 
2020
 
            p.slice = distributeSides( slices, function( tok ) {
2021
 
                return PIE.getLength( ( tok.tokenType & NUMBER ) ? tok.tokenValue + 'px' : tok.tokenValue );
2022
 
            } );
2023
 
 
2024
 
            if( widths && widths[0] ) {
2025
 
                p.widths = distributeSides( widths, function( tok ) {
2026
 
                    return tok.isLengthOrPercent() ? PIE.getLength( tok.tokenValue ) : tok.tokenValue;
2027
 
                } );
2028
 
            }
2029
 
 
2030
 
            if( outsets && outsets[0] ) {
2031
 
                p.outset = distributeSides( outsets, function( tok ) {
2032
 
                    return tok.isLength() ? PIE.getLength( tok.tokenValue ) : tok.tokenValue;
2033
 
                } );
2034
 
            }
2035
 
        }
2036
 
 
2037
 
        return p;
2038
 
    }
2039
 
 
2040
 
} );/**
2041
 
 * Handles parsing, caching, and detecting changes to box-shadow CSS
2042
 
 * @constructor
2043
 
 * @param {Element} el the target element
2044
 
 */
2045
 
PIE.BoxShadowStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
2046
 
 
2047
 
    cssProperty: 'box-shadow',
2048
 
    styleProperty: 'boxShadow',
2049
 
 
2050
 
    parseCss: function( css ) {
2051
 
        var props,
2052
 
            getLength = PIE.getLength,
2053
 
            Type = PIE.Tokenizer.Type,
2054
 
            tokenizer;
2055
 
 
2056
 
        if( css ) {
2057
 
            tokenizer = new PIE.Tokenizer( css );
2058
 
            props = { outset: [], inset: [] };
2059
 
 
2060
 
            function parseItem() {
2061
 
                var token, type, value, color, lengths, inset, len;
2062
 
 
2063
 
                while( token = tokenizer.next() ) {
2064
 
                    value = token.tokenValue;
2065
 
                    type = token.tokenType;
2066
 
 
2067
 
                    if( type & Type.OPERATOR && value === ',' ) {
2068
 
                        break;
2069
 
                    }
2070
 
                    else if( token.isLength() && !lengths ) {
2071
 
                        tokenizer.prev();
2072
 
                        lengths = tokenizer.until( function( token ) {
2073
 
                            return !token.isLength();
2074
 
                        } );
2075
 
                    }
2076
 
                    else if( type & Type.COLOR && !color ) {
2077
 
                        color = value;
2078
 
                    }
2079
 
                    else if( type & Type.IDENT && value === 'inset' && !inset ) {
2080
 
                        inset = true;
2081
 
                    }
2082
 
                    else { //encountered an unrecognized token; fail.
2083
 
                        return false;
2084
 
                    }
2085
 
                }
2086
 
 
2087
 
                len = lengths && lengths.length;
2088
 
                if( len > 1 && len < 5 ) {
2089
 
                    ( inset ? props.inset : props.outset ).push( {
2090
 
                        xOffset: getLength( lengths[0].tokenValue ),
2091
 
                        yOffset: getLength( lengths[1].tokenValue ),
2092
 
                        blur: getLength( lengths[2] ? lengths[2].tokenValue : '0' ),
2093
 
                        spread: getLength( lengths[3] ? lengths[3].tokenValue : '0' ),
2094
 
                        color: PIE.getColor( color || 'currentColor' )
2095
 
                    } );
2096
 
                    return true;
2097
 
                }
2098
 
                return false;
2099
 
            }
2100
 
 
2101
 
            while( parseItem() ) {}
2102
 
        }
2103
 
 
2104
 
        return props && ( props.inset.length || props.outset.length ) ? props : null;
2105
 
    }
2106
 
} );
2107
 
/**
2108
 
 * Retrieves the state of the element's visibility and display
2109
 
 * @constructor
2110
 
 * @param {Element} el the target element
2111
 
 */
2112
 
PIE.VisibilityStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
2113
 
 
2114
 
    getCss: PIE.StyleInfoBase.cacheWhenLocked( function() {
2115
 
        var cs = this.targetElement.currentStyle;
2116
 
        return cs.visibility + '|' + cs.display;
2117
 
    } ),
2118
 
 
2119
 
    parseCss: function() {
2120
 
        var el = this.targetElement,
2121
 
            rs = el.runtimeStyle,
2122
 
            cs = el.currentStyle,
2123
 
            rsVis = rs.visibility,
2124
 
            csVis;
2125
 
 
2126
 
        rs.visibility = '';
2127
 
        csVis = cs.visibility;
2128
 
        rs.visibility = rsVis;
2129
 
 
2130
 
        return {
2131
 
            visible: csVis !== 'hidden',
2132
 
            displayed: cs.display !== 'none'
2133
 
        }
2134
 
    },
2135
 
 
2136
 
    /**
2137
 
     * Always return false for isActive, since this property alone will not trigger
2138
 
     * a renderer to do anything.
2139
 
     */
2140
 
    isActive: function() {
2141
 
        return false;
2142
 
    }
2143
 
 
2144
 
} );
2145
 
PIE.RendererBase = {
2146
 
 
2147
 
    /**
2148
 
     * Create a new Renderer class, with the standard constructor, and augmented by
2149
 
     * the RendererBase's members.
2150
 
     * @param proto
2151
 
     */
2152
 
    newRenderer: function( proto ) {
2153
 
        function Renderer( el, boundsInfo, styleInfos, parent ) {
2154
 
            this.targetElement = el;
2155
 
            this.boundsInfo = boundsInfo;
2156
 
            this.styleInfos = styleInfos;
2157
 
            this.parent = parent;
2158
 
        }
2159
 
        PIE.Util.merge( Renderer.prototype, PIE.RendererBase, proto );
2160
 
        return Renderer;
2161
 
    },
2162
 
 
2163
 
    /**
2164
 
     * Flag indicating the element has already been positioned at least once.
2165
 
     * @type {boolean}
2166
 
     */
2167
 
    isPositioned: false,
2168
 
 
2169
 
    /**
2170
 
     * Determine if the renderer needs to be updated
2171
 
     * @return {boolean}
2172
 
     */
2173
 
    needsUpdate: function() {
2174
 
        return false;
2175
 
    },
2176
 
 
2177
 
    /**
2178
 
     * Run any preparation logic that would affect the main update logic of this
2179
 
     * renderer or any of the other renderers, e.g. things that might affect the
2180
 
     * element's size or style properties.
2181
 
     */
2182
 
    prepareUpdate: PIE.emptyFn,
2183
 
 
2184
 
    /**
2185
 
     * Tell the renderer to update based on modified properties
2186
 
     */
2187
 
    updateProps: function() {
2188
 
        this.destroy();
2189
 
        if( this.isActive() ) {
2190
 
            this.draw();
2191
 
        }
2192
 
    },
2193
 
 
2194
 
    /**
2195
 
     * Tell the renderer to update based on modified element position
2196
 
     */
2197
 
    updatePos: function() {
2198
 
        this.isPositioned = true;
2199
 
    },
2200
 
 
2201
 
    /**
2202
 
     * Tell the renderer to update based on modified element dimensions
2203
 
     */
2204
 
    updateSize: function() {
2205
 
        if( this.isActive() ) {
2206
 
            this.draw();
2207
 
        } else {
2208
 
            this.destroy();
2209
 
        }
2210
 
    },
2211
 
 
2212
 
 
2213
 
    /**
2214
 
     * Add a layer element, with the given z-order index, to the renderer's main box element. We can't use
2215
 
     * z-index because that breaks when the root rendering box's z-index is 'auto' in IE8+ standards mode.
2216
 
     * So instead we make sure they are inserted into the DOM in the correct order.
2217
 
     * @param {number} index
2218
 
     * @param {Element} el
2219
 
     */
2220
 
    addLayer: function( index, el ) {
2221
 
        this.removeLayer( index );
2222
 
        for( var layers = this._layers || ( this._layers = [] ), i = index + 1, len = layers.length, layer; i < len; i++ ) {
2223
 
            layer = layers[i];
2224
 
            if( layer ) {
2225
 
                break;
2226
 
            }
2227
 
        }
2228
 
        layers[index] = el;
2229
 
        this.getBox().insertBefore( el, layer || null );
2230
 
    },
2231
 
 
2232
 
    /**
2233
 
     * Retrieve a layer element by its index, or null if not present
2234
 
     * @param {number} index
2235
 
     * @return {Element}
2236
 
     */
2237
 
    getLayer: function( index ) {
2238
 
        var layers = this._layers;
2239
 
        return layers && layers[index] || null;
2240
 
    },
2241
 
 
2242
 
    /**
2243
 
     * Remove a layer element by its index
2244
 
     * @param {number} index
2245
 
     */
2246
 
    removeLayer: function( index ) {
2247
 
        var layer = this.getLayer( index ),
2248
 
            box = this._box;
2249
 
        if( layer && box ) {
2250
 
            box.removeChild( layer );
2251
 
            this._layers[index] = null;
2252
 
        }
2253
 
    },
2254
 
 
2255
 
 
2256
 
    /**
2257
 
     * Get a VML shape by name, creating it if necessary.
2258
 
     * @param {string} name A name identifying the element
2259
 
     * @param {string=} subElName If specified a subelement of the shape will be created with this tag name
2260
 
     * @param {Element} parent The parent element for the shape; will be ignored if 'group' is specified
2261
 
     * @param {number=} group If specified, an ordinal group for the shape. 1 or greater. Groups are rendered
2262
 
     *                  using container elements in the correct order, to get correct z stacking without z-index.
2263
 
     */
2264
 
    getShape: function( name, subElName, parent, group ) {
2265
 
        var shapes = this._shapes || ( this._shapes = {} ),
2266
 
            shape = shapes[ name ],
2267
 
            s;
2268
 
 
2269
 
        if( !shape ) {
2270
 
            shape = shapes[ name ] = PIE.Util.createVmlElement( 'shape' );
2271
 
            if( subElName ) {
2272
 
                shape.appendChild( shape[ subElName ] = PIE.Util.createVmlElement( subElName ) );
2273
 
            }
2274
 
 
2275
 
            if( group ) {
2276
 
                parent = this.getLayer( group );
2277
 
                if( !parent ) {
2278
 
                    this.addLayer( group, doc.createElement( 'group' + group ) );
2279
 
                    parent = this.getLayer( group );
2280
 
                }
2281
 
            }
2282
 
 
2283
 
            parent.appendChild( shape );
2284
 
 
2285
 
            s = shape.style;
2286
 
            s.position = 'absolute';
2287
 
            s.left = s.top = 0;
2288
 
            s['behavior'] = 'url(#default#VML)';
2289
 
        }
2290
 
        return shape;
2291
 
    },
2292
 
 
2293
 
    /**
2294
 
     * Delete a named shape which was created by getShape(). Returns true if a shape with the
2295
 
     * given name was found and deleted, or false if there was no shape of that name.
2296
 
     * @param {string} name
2297
 
     * @return {boolean}
2298
 
     */
2299
 
    deleteShape: function( name ) {
2300
 
        var shapes = this._shapes,
2301
 
            shape = shapes && shapes[ name ];
2302
 
        if( shape ) {
2303
 
            shape.parentNode.removeChild( shape );
2304
 
            delete shapes[ name ];
2305
 
        }
2306
 
        return !!shape;
2307
 
    },
2308
 
 
2309
 
 
2310
 
    /**
2311
 
     * For a given set of border radius length/percentage values, convert them to concrete pixel
2312
 
     * values based on the current size of the target element.
2313
 
     * @param {Object} radii
2314
 
     * @return {Object}
2315
 
     */
2316
 
    getRadiiPixels: function( radii ) {
2317
 
        var el = this.targetElement,
2318
 
            bounds = this.boundsInfo.getBounds(),
2319
 
            w = bounds.w,
2320
 
            h = bounds.h,
2321
 
            tlX, tlY, trX, trY, brX, brY, blX, blY, f;
2322
 
 
2323
 
        tlX = radii.x['tl'].pixels( el, w );
2324
 
        tlY = radii.y['tl'].pixels( el, h );
2325
 
        trX = radii.x['tr'].pixels( el, w );
2326
 
        trY = radii.y['tr'].pixels( el, h );
2327
 
        brX = radii.x['br'].pixels( el, w );
2328
 
        brY = radii.y['br'].pixels( el, h );
2329
 
        blX = radii.x['bl'].pixels( el, w );
2330
 
        blY = radii.y['bl'].pixels( el, h );
2331
 
 
2332
 
        // If any corner ellipses overlap, reduce them all by the appropriate factor. This formula
2333
 
        // is taken straight from the CSS3 Backgrounds and Borders spec.
2334
 
        f = Math.min(
2335
 
            w / ( tlX + trX ),
2336
 
            h / ( trY + brY ),
2337
 
            w / ( blX + brX ),
2338
 
            h / ( tlY + blY )
2339
 
        );
2340
 
        if( f < 1 ) {
2341
 
            tlX *= f;
2342
 
            tlY *= f;
2343
 
            trX *= f;
2344
 
            trY *= f;
2345
 
            brX *= f;
2346
 
            brY *= f;
2347
 
            blX *= f;
2348
 
            blY *= f;
2349
 
        }
2350
 
 
2351
 
        return {
2352
 
            x: {
2353
 
                'tl': tlX,
2354
 
                'tr': trX,
2355
 
                'br': brX,
2356
 
                'bl': blX
2357
 
            },
2358
 
            y: {
2359
 
                'tl': tlY,
2360
 
                'tr': trY,
2361
 
                'br': brY,
2362
 
                'bl': blY
2363
 
            }
2364
 
        }
2365
 
    },
2366
 
 
2367
 
    /**
2368
 
     * Return the VML path string for the element's background box, with corners rounded.
2369
 
     * @param {Object.<{t:number, r:number, b:number, l:number}>} shrink - if present, specifies number of
2370
 
     *        pixels to shrink the box path inward from the element's four sides.
2371
 
     * @param {number=} mult If specified, all coordinates will be multiplied by this number
2372
 
     * @param {Object=} radii If specified, this will be used for the corner radii instead of the properties
2373
 
     *        from this renderer's borderRadiusInfo object.
2374
 
     * @return {string} the VML path
2375
 
     */
2376
 
    getBoxPath: function( shrink, mult, radii ) {
2377
 
        mult = mult || 1;
2378
 
 
2379
 
        var r, str,
2380
 
            bounds = this.boundsInfo.getBounds(),
2381
 
            w = bounds.w * mult,
2382
 
            h = bounds.h * mult,
2383
 
            radInfo = this.styleInfos.borderRadiusInfo,
2384
 
            floor = Math.floor, ceil = Math.ceil,
2385
 
            shrinkT = shrink ? shrink.t * mult : 0,
2386
 
            shrinkR = shrink ? shrink.r * mult : 0,
2387
 
            shrinkB = shrink ? shrink.b * mult : 0,
2388
 
            shrinkL = shrink ? shrink.l * mult : 0,
2389
 
            tlX, tlY, trX, trY, brX, brY, blX, blY;
2390
 
 
2391
 
        if( radii || radInfo.isActive() ) {
2392
 
            r = this.getRadiiPixels( radii || radInfo.getProps() );
2393
 
 
2394
 
            tlX = r.x['tl'] * mult;
2395
 
            tlY = r.y['tl'] * mult;
2396
 
            trX = r.x['tr'] * mult;
2397
 
            trY = r.y['tr'] * mult;
2398
 
            brX = r.x['br'] * mult;
2399
 
            brY = r.y['br'] * mult;
2400
 
            blX = r.x['bl'] * mult;
2401
 
            blY = r.y['bl'] * mult;
2402
 
 
2403
 
            str = 'm' + floor( shrinkL ) + ',' + floor( tlY ) +
2404
 
                'qy' + floor( tlX ) + ',' + floor( shrinkT ) +
2405
 
                'l' + ceil( w - trX ) + ',' + floor( shrinkT ) +
2406
 
                'qx' + ceil( w - shrinkR ) + ',' + floor( trY ) +
2407
 
                'l' + ceil( w - shrinkR ) + ',' + ceil( h - brY ) +
2408
 
                'qy' + ceil( w - brX ) + ',' + ceil( h - shrinkB ) +
2409
 
                'l' + floor( blX ) + ',' + ceil( h - shrinkB ) +
2410
 
                'qx' + floor( shrinkL ) + ',' + ceil( h - blY ) + ' x e';
2411
 
        } else {
2412
 
            // simplified path for non-rounded box
2413
 
            str = 'm' + floor( shrinkL ) + ',' + floor( shrinkT ) +
2414
 
                  'l' + ceil( w - shrinkR ) + ',' + floor( shrinkT ) +
2415
 
                  'l' + ceil( w - shrinkR ) + ',' + ceil( h - shrinkB ) +
2416
 
                  'l' + floor( shrinkL ) + ',' + ceil( h - shrinkB ) +
2417
 
                  'xe';
2418
 
        }
2419
 
        return str;
2420
 
    },
2421
 
 
2422
 
 
2423
 
    /**
2424
 
     * Get the container element for the shapes, creating it if necessary.
2425
 
     */
2426
 
    getBox: function() {
2427
 
        var box = this.parent.getLayer( this.boxZIndex ), s;
2428
 
 
2429
 
        if( !box ) {
2430
 
            box = doc.createElement( this.boxName );
2431
 
            s = box.style;
2432
 
            s.position = 'absolute';
2433
 
            s.top = s.left = 0;
2434
 
            this.parent.addLayer( this.boxZIndex, box );
2435
 
        }
2436
 
 
2437
 
        return box;
2438
 
    },
2439
 
 
2440
 
 
2441
 
    /**
2442
 
     * Hide the actual border of the element. In IE7 and up we can just set its color to transparent;
2443
 
     * however IE6 does not support transparent borders so we have to get tricky with it. Also, some elements
2444
 
     * like form buttons require removing the border width altogether, so for those we increase the padding
2445
 
     * by the border size.
2446
 
     */
2447
 
    hideBorder: function() {
2448
 
        var el = this.targetElement,
2449
 
            cs = el.currentStyle,
2450
 
            rs = el.runtimeStyle,
2451
 
            tag = el.tagName,
2452
 
            isIE6 = PIE.ieVersion === 6,
2453
 
            sides, side, i;
2454
 
 
2455
 
        if( ( isIE6 && ( tag in PIE.childlessElements || tag === 'FIELDSET' ) ) ||
2456
 
                tag === 'BUTTON' || ( tag === 'INPUT' && el.type in PIE.inputButtonTypes ) ) {
2457
 
            rs.borderWidth = '';
2458
 
            sides = this.styleInfos.borderInfo.sides;
2459
 
            for( i = sides.length; i--; ) {
2460
 
                side = sides[ i ];
2461
 
                rs[ 'padding' + side ] = '';
2462
 
                rs[ 'padding' + side ] = ( PIE.getLength( cs[ 'padding' + side ] ) ).pixels( el ) +
2463
 
                                         ( PIE.getLength( cs[ 'border' + side + 'Width' ] ) ).pixels( el ) +
2464
 
                                         ( PIE.ieVersion !== 8 && i % 2 ? 1 : 0 ); //needs an extra horizontal pixel to counteract the extra "inner border" going away
2465
 
            }
2466
 
            rs.borderWidth = 0;
2467
 
        }
2468
 
        else if( isIE6 ) {
2469
 
            // Wrap all the element's children in a custom element, set the element to visiblity:hidden,
2470
 
            // and set the wrapper element to visiblity:visible. This hides the outer element's decorations
2471
 
            // (background and border) but displays all the contents.
2472
 
            // TODO find a better way to do this that doesn't mess up the DOM parent-child relationship,
2473
 
            // as this can interfere with other author scripts which add/modify/delete children. Also, this
2474
 
            // won't work for elements which cannot take children, e.g. input/button/textarea/img/etc. Look into
2475
 
            // using a compositor filter or some other filter which masks the border.
2476
 
            if( el.childNodes.length !== 1 || el.firstChild.tagName !== 'ie6-mask' ) {
2477
 
                var cont = doc.createElement( 'ie6-mask' ),
2478
 
                    s = cont.style, child;
2479
 
                s.visibility = 'visible';
2480
 
                s.zoom = 1;
2481
 
                while( child = el.firstChild ) {
2482
 
                    cont.appendChild( child );
2483
 
                }
2484
 
                el.appendChild( cont );
2485
 
                rs.visibility = 'hidden';
2486
 
            }
2487
 
        }
2488
 
        else {
2489
 
            rs.borderColor = 'transparent';
2490
 
        }
2491
 
    },
2492
 
 
2493
 
    unhideBorder: function() {
2494
 
        
2495
 
    },
2496
 
 
2497
 
 
2498
 
    /**
2499
 
     * Destroy the rendered objects. This is a base implementation which handles common renderer
2500
 
     * structures, but individual renderers may override as necessary.
2501
 
     */
2502
 
    destroy: function() {
2503
 
        this.parent.removeLayer( this.boxZIndex );
2504
 
        delete this._shapes;
2505
 
        delete this._layers;
2506
 
    }
2507
 
};
2508
 
/**
2509
 
 * Root renderer; creates the outermost container element and handles keeping it aligned
2510
 
 * with the target element's size and position.
2511
 
 * @param {Element} el The target element
2512
 
 * @param {Object} styleInfos The StyleInfo objects
2513
 
 */
2514
 
PIE.RootRenderer = PIE.RendererBase.newRenderer( {
2515
 
 
2516
 
    isActive: function() {
2517
 
        var children = this.childRenderers;
2518
 
        for( var i in children ) {
2519
 
            if( children.hasOwnProperty( i ) && children[ i ].isActive() ) {
2520
 
                return true;
2521
 
            }
2522
 
        }
2523
 
        return false;
2524
 
    },
2525
 
 
2526
 
    needsUpdate: function() {
2527
 
        return this.styleInfos.visibilityInfo.changed();
2528
 
    },
2529
 
 
2530
 
    updatePos: function() {
2531
 
        if( this.isActive() ) {
2532
 
            var el = this.getPositioningElement(),
2533
 
                par = el,
2534
 
                docEl,
2535
 
                parRect,
2536
 
                tgtCS = el.currentStyle,
2537
 
                tgtPos = tgtCS.position,
2538
 
                boxPos,
2539
 
                s = this.getBox().style, cs,
2540
 
                x = 0, y = 0,
2541
 
                elBounds = this.boundsInfo.getBounds();
2542
 
 
2543
 
            if( tgtPos === 'fixed' && PIE.ieVersion > 6 ) {
2544
 
                x = elBounds.x;
2545
 
                y = elBounds.y;
2546
 
                boxPos = tgtPos;
2547
 
            } else {
2548
 
                // Get the element's offsets from its nearest positioned ancestor. Uses
2549
 
                // getBoundingClientRect for accuracy and speed.
2550
 
                do {
2551
 
                    par = par.offsetParent;
2552
 
                } while( par && ( par.currentStyle.position === 'static' ) );
2553
 
                if( par ) {
2554
 
                    parRect = par.getBoundingClientRect();
2555
 
                    cs = par.currentStyle;
2556
 
                    x = elBounds.x - parRect.left - ( parseFloat(cs.borderLeftWidth) || 0 );
2557
 
                    y = elBounds.y - parRect.top - ( parseFloat(cs.borderTopWidth) || 0 );
2558
 
                } else {
2559
 
                    docEl = doc.documentElement;
2560
 
                    x = elBounds.x + docEl.scrollLeft - docEl.clientLeft;
2561
 
                    y = elBounds.y + docEl.scrollTop - docEl.clientTop;
2562
 
                }
2563
 
                boxPos = 'absolute';
2564
 
            }
2565
 
 
2566
 
            s.position = boxPos;
2567
 
            s.left = x;
2568
 
            s.top = y;
2569
 
            s.zIndex = tgtPos === 'static' ? -1 : tgtCS.zIndex;
2570
 
            this.isPositioned = true;
2571
 
        }
2572
 
    },
2573
 
 
2574
 
    updateSize: PIE.emptyFn,
2575
 
 
2576
 
    updateVisibility: function() {
2577
 
        var vis = this.styleInfos.visibilityInfo.getProps();
2578
 
        this.getBox().style.display = ( vis.visible && vis.displayed ) ? '' : 'none';
2579
 
    },
2580
 
 
2581
 
    updateProps: function() {
2582
 
        if( this.isActive() ) {
2583
 
            this.updateVisibility();
2584
 
        } else {
2585
 
            this.destroy();
2586
 
        }
2587
 
    },
2588
 
 
2589
 
    getPositioningElement: function() {
2590
 
        var el = this.targetElement;
2591
 
        return el.tagName in PIE.tableCellTags ? el.offsetParent : el;
2592
 
    },
2593
 
 
2594
 
    getBox: function() {
2595
 
        var box = this._box, el;
2596
 
        if( !box ) {
2597
 
            el = this.getPositioningElement();
2598
 
            box = this._box = doc.createElement( 'css3-container' );
2599
 
            box.style['direction'] = 'ltr'; //fix positioning bug in rtl environments
2600
 
 
2601
 
            this.updateVisibility();
2602
 
 
2603
 
            el.parentNode.insertBefore( box, el );
2604
 
        }
2605
 
        return box;
2606
 
    },
2607
 
 
2608
 
    finishUpdate: PIE.emptyFn,
2609
 
 
2610
 
    destroy: function() {
2611
 
        var box = this._box, par;
2612
 
        if( box && ( par = box.parentNode ) ) {
2613
 
            par.removeChild( box );
2614
 
        }
2615
 
        delete this._box;
2616
 
        delete this._layers;
2617
 
    }
2618
 
 
2619
 
} );
2620
 
/**
2621
 
 * Renderer for element backgrounds.
2622
 
 * @constructor
2623
 
 * @param {Element} el The target element
2624
 
 * @param {Object} styleInfos The StyleInfo objects
2625
 
 * @param {PIE.RootRenderer} parent
2626
 
 */
2627
 
PIE.BackgroundRenderer = PIE.RendererBase.newRenderer( {
2628
 
 
2629
 
    boxZIndex: 2,
2630
 
    boxName: 'background',
2631
 
 
2632
 
    needsUpdate: function() {
2633
 
        var si = this.styleInfos;
2634
 
        return si.backgroundInfo.changed() || si.borderRadiusInfo.changed();
2635
 
    },
2636
 
 
2637
 
    isActive: function() {
2638
 
        var si = this.styleInfos;
2639
 
        return si.borderImageInfo.isActive() ||
2640
 
               si.borderRadiusInfo.isActive() ||
2641
 
               si.backgroundInfo.isActive() ||
2642
 
               ( si.boxShadowInfo.isActive() && si.boxShadowInfo.getProps().inset );
2643
 
    },
2644
 
 
2645
 
    /**
2646
 
     * Draw the shapes
2647
 
     */
2648
 
    draw: function() {
2649
 
        var bounds = this.boundsInfo.getBounds();
2650
 
        if( bounds.w && bounds.h ) {
2651
 
            this.drawBgColor();
2652
 
            this.drawBgImages();
2653
 
        }
2654
 
    },
2655
 
 
2656
 
    /**
2657
 
     * Draw the background color shape
2658
 
     */
2659
 
    drawBgColor: function() {
2660
 
        var props = this.styleInfos.backgroundInfo.getProps(),
2661
 
            bounds = this.boundsInfo.getBounds(),
2662
 
            el = this.targetElement,
2663
 
            color = props && props.color,
2664
 
            shape, w, h, s, alpha;
2665
 
 
2666
 
        if( color && color.alpha() > 0 ) {
2667
 
            this.hideBackground();
2668
 
 
2669
 
            shape = this.getShape( 'bgColor', 'fill', this.getBox(), 1 );
2670
 
            w = bounds.w;
2671
 
            h = bounds.h;
2672
 
            shape.stroked = false;
2673
 
            shape.coordsize = w * 2 + ',' + h * 2;
2674
 
            shape.coordorigin = '1,1';
2675
 
            shape.path = this.getBoxPath( null, 2 );
2676
 
            s = shape.style;
2677
 
            s.width = w;
2678
 
            s.height = h;
2679
 
            shape.fill.color = color.colorValue( el );
2680
 
 
2681
 
            alpha = color.alpha();
2682
 
            if( alpha < 1 ) {
2683
 
                shape.fill.opacity = alpha;
2684
 
            }
2685
 
        } else {
2686
 
            this.deleteShape( 'bgColor' );
2687
 
        }
2688
 
    },
2689
 
 
2690
 
    /**
2691
 
     * Draw all the background image layers
2692
 
     */
2693
 
    drawBgImages: function() {
2694
 
        var props = this.styleInfos.backgroundInfo.getProps(),
2695
 
            bounds = this.boundsInfo.getBounds(),
2696
 
            images = props && props.bgImages,
2697
 
            img, shape, w, h, s, i;
2698
 
 
2699
 
        if( images ) {
2700
 
            this.hideBackground();
2701
 
 
2702
 
            w = bounds.w;
2703
 
            h = bounds.h;
2704
 
 
2705
 
            i = images.length;
2706
 
            while( i-- ) {
2707
 
                img = images[i];
2708
 
                shape = this.getShape( 'bgImage' + i, 'fill', this.getBox(), 2 );
2709
 
 
2710
 
                shape.stroked = false;
2711
 
                shape.fill.type = 'tile';
2712
 
                shape.fillcolor = 'none';
2713
 
                shape.coordsize = w * 2 + ',' + h * 2;
2714
 
                shape.coordorigin = '1,1';
2715
 
                shape.path = this.getBoxPath( 0, 2 );
2716
 
                s = shape.style;
2717
 
                s.width = w;
2718
 
                s.height = h;
2719
 
 
2720
 
                if( img.imgType === 'linear-gradient' ) {
2721
 
                    this.addLinearGradient( shape, img );
2722
 
                }
2723
 
                else {
2724
 
                    shape.fill.src = img.imgUrl;
2725
 
                    this.positionBgImage( shape, i );
2726
 
                }
2727
 
            }
2728
 
        }
2729
 
 
2730
 
        // Delete any bgImage shapes previously created which weren't used above
2731
 
        i = images ? images.length : 0;
2732
 
        while( this.deleteShape( 'bgImage' + i++ ) ) {}
2733
 
    },
2734
 
 
2735
 
 
2736
 
    /**
2737
 
     * Set the position and clipping of the background image for a layer
2738
 
     * @param {Element} shape
2739
 
     * @param {number} index
2740
 
     */
2741
 
    positionBgImage: function( shape, index ) {
2742
 
        var me = this;
2743
 
        PIE.Util.withImageSize( shape.fill.src, function( size ) {
2744
 
            var el = me.targetElement,
2745
 
                bounds = me.boundsInfo.getBounds(),
2746
 
                elW = bounds.w,
2747
 
                elH = bounds.h;
2748
 
 
2749
 
            // It's possible that the element dimensions are zero now but weren't when the original
2750
 
            // update executed, make sure that's not the case to avoid divide-by-zero error
2751
 
            if( elW && elH ) {
2752
 
                var fill = shape.fill,
2753
 
                    si = me.styleInfos,
2754
 
                    border = si.borderInfo.getProps(),
2755
 
                    bw = border && border.widths,
2756
 
                    bwT = bw ? bw['t'].pixels( el ) : 0,
2757
 
                    bwR = bw ? bw['r'].pixels( el ) : 0,
2758
 
                    bwB = bw ? bw['b'].pixels( el ) : 0,
2759
 
                    bwL = bw ? bw['l'].pixels( el ) : 0,
2760
 
                    bg = si.backgroundInfo.getProps().bgImages[ index ],
2761
 
                    bgPos = bg.bgPosition ? bg.bgPosition.coords( el, elW - size.w - bwL - bwR, elH - size.h - bwT - bwB ) : { x:0, y:0 },
2762
 
                    repeat = bg.imgRepeat,
2763
 
                    pxX, pxY,
2764
 
                    clipT = 0, clipL = 0,
2765
 
                    clipR = elW + 1, clipB = elH + 1, //make sure the default clip region is not inside the box (by a subpixel)
2766
 
                    clipAdjust = PIE.ieVersion === 8 ? 0 : 1; //prior to IE8 requires 1 extra pixel in the image clip region
2767
 
 
2768
 
                // Positioning - find the pixel offset from the top/left and convert to a ratio
2769
 
                // The position is shifted by half a pixel, to adjust for the half-pixel coordorigin shift which is
2770
 
                // needed to fix antialiasing but makes the bg image fuzzy.
2771
 
                pxX = Math.round( bgPos.x ) + bwL + 0.5;
2772
 
                pxY = Math.round( bgPos.y ) + bwT + 0.5;
2773
 
                fill.position = ( pxX / elW ) + ',' + ( pxY / elH );
2774
 
 
2775
 
                // Repeating - clip the image shape
2776
 
                if( repeat && repeat !== 'repeat' ) {
2777
 
                    if( repeat === 'repeat-x' || repeat === 'no-repeat' ) {
2778
 
                        clipT = pxY + 1;
2779
 
                        clipB = pxY + size.h + clipAdjust;
2780
 
                    }
2781
 
                    if( repeat === 'repeat-y' || repeat === 'no-repeat' ) {
2782
 
                        clipL = pxX + 1;
2783
 
                        clipR = pxX + size.w + clipAdjust;
2784
 
                    }
2785
 
                    shape.style.clip = 'rect(' + clipT + 'px,' + clipR + 'px,' + clipB + 'px,' + clipL + 'px)';
2786
 
                }
2787
 
            }
2788
 
        } );
2789
 
    },
2790
 
 
2791
 
 
2792
 
    /**
2793
 
     * Draw the linear gradient for a gradient layer
2794
 
     * @param {Element} shape
2795
 
     * @param {Object} info The object holding the information about the gradient
2796
 
     */
2797
 
    addLinearGradient: function( shape, info ) {
2798
 
        var el = this.targetElement,
2799
 
            bounds = this.boundsInfo.getBounds(),
2800
 
            w = bounds.w,
2801
 
            h = bounds.h,
2802
 
            fill = shape.fill,
2803
 
            stops = info.stops,
2804
 
            stopCount = stops.length,
2805
 
            PI = Math.PI,
2806
 
            GradientUtil = PIE.GradientUtil,
2807
 
            perpendicularIntersect = GradientUtil.perpendicularIntersect,
2808
 
            distance = GradientUtil.distance,
2809
 
            metrics = GradientUtil.getGradientMetrics( el, w, h, info ),
2810
 
            angle = metrics.angle,
2811
 
            startX = metrics.startX,
2812
 
            startY = metrics.startY,
2813
 
            startCornerX = metrics.startCornerX,
2814
 
            startCornerY = metrics.startCornerY,
2815
 
            endCornerX = metrics.endCornerX,
2816
 
            endCornerY = metrics.endCornerY,
2817
 
            deltaX = metrics.deltaX,
2818
 
            deltaY = metrics.deltaY,
2819
 
            lineLength = metrics.lineLength,
2820
 
            vmlAngle, vmlGradientLength, vmlColors,
2821
 
            stopPx, vmlOffsetPct,
2822
 
            p, i, j, before, after;
2823
 
 
2824
 
        // In VML land, the angle of the rendered gradient depends on the aspect ratio of the shape's
2825
 
        // bounding box; for example specifying a 45 deg angle actually results in a gradient
2826
 
        // drawn diagonally from one corner to its opposite corner, which will only appear to the
2827
 
        // viewer as 45 degrees if the shape is equilateral.  We adjust for this by taking the x/y deltas
2828
 
        // between the start and end points, multiply one of them by the shape's aspect ratio,
2829
 
        // and get their arctangent, resulting in an appropriate VML angle. If the angle is perfectly
2830
 
        // horizontal or vertical then we don't need to do this conversion.
2831
 
        vmlAngle = ( angle % 90 ) ? Math.atan2( deltaX * w / h, deltaY ) / PI * 180 : ( angle + 90 );
2832
 
 
2833
 
        // VML angles are 180 degrees offset from CSS angles
2834
 
        vmlAngle += 180;
2835
 
        vmlAngle = vmlAngle % 360;
2836
 
 
2837
 
        // Add all the stops to the VML 'colors' list, including the first and last stops.
2838
 
        // For each, we find its pixel offset along the gradient-line; if the offset of a stop is less
2839
 
        // than that of its predecessor we increase it to be equal. We then map that pixel offset to a
2840
 
        // percentage along the VML gradient-line, which runs from shape corner to corner.
2841
 
        p = perpendicularIntersect( startCornerX, startCornerY, angle, endCornerX, endCornerY );
2842
 
        vmlGradientLength = distance( startCornerX, startCornerY, p[0], p[1] );
2843
 
        vmlColors = [];
2844
 
        p = perpendicularIntersect( startX, startY, angle, startCornerX, startCornerY );
2845
 
        vmlOffsetPct = distance( startX, startY, p[0], p[1] ) / vmlGradientLength * 100;
2846
 
 
2847
 
        // Find the pixel offsets along the CSS3 gradient-line for each stop.
2848
 
        stopPx = [];
2849
 
        for( i = 0; i < stopCount; i++ ) {
2850
 
            stopPx.push( stops[i].offset ? stops[i].offset.pixels( el, lineLength ) :
2851
 
                         i === 0 ? 0 : i === stopCount - 1 ? lineLength : null );
2852
 
        }
2853
 
        // Fill in gaps with evenly-spaced offsets
2854
 
        for( i = 1; i < stopCount; i++ ) {
2855
 
            if( stopPx[ i ] === null ) {
2856
 
                before = stopPx[ i - 1 ];
2857
 
                j = i;
2858
 
                do {
2859
 
                    after = stopPx[ ++j ];
2860
 
                } while( after === null );
2861
 
                stopPx[ i ] = before + ( after - before ) / ( j - i + 1 );
2862
 
            }
2863
 
            // Make sure each stop's offset is no less than the one before it
2864
 
            stopPx[ i ] = Math.max( stopPx[ i ], stopPx[ i - 1 ] );
2865
 
        }
2866
 
 
2867
 
        // Convert to percentage along the VML gradient line and add to the VML 'colors' value
2868
 
        for( i = 0; i < stopCount; i++ ) {
2869
 
            vmlColors.push(
2870
 
                ( vmlOffsetPct + ( stopPx[ i ] / vmlGradientLength * 100 ) ) + '% ' + stops[i].color.colorValue( el )
2871
 
            );
2872
 
        }
2873
 
 
2874
 
        // Now, finally, we're ready to render the gradient fill. Set the start and end colors to
2875
 
        // the first and last stop colors; this just sets outer bounds for the gradient.
2876
 
        fill['angle'] = vmlAngle;
2877
 
        fill['type'] = 'gradient';
2878
 
        fill['method'] = 'sigma';
2879
 
        fill['color'] = stops[0].color.colorValue( el );
2880
 
        fill['color2'] = stops[stopCount - 1].color.colorValue( el );
2881
 
        if( fill['colors'] ) { //sometimes the colors object isn't initialized so we have to assign it directly (?)
2882
 
            fill['colors'].value = vmlColors.join( ',' );
2883
 
        } else {
2884
 
            fill['colors'] = vmlColors.join( ',' );
2885
 
        }
2886
 
    },
2887
 
 
2888
 
 
2889
 
    /**
2890
 
     * Hide the actual background image and color of the element.
2891
 
     */
2892
 
    hideBackground: function() {
2893
 
        var rs = this.targetElement.runtimeStyle;
2894
 
        rs.backgroundImage = 'url(about:blank)'; //ensures the background area reacts to mouse events
2895
 
        rs.backgroundColor = 'transparent';
2896
 
    },
2897
 
 
2898
 
    destroy: function() {
2899
 
        PIE.RendererBase.destroy.call( this );
2900
 
        var rs = this.targetElement.runtimeStyle;
2901
 
        rs.backgroundImage = rs.backgroundColor = '';
2902
 
    }
2903
 
 
2904
 
} );
2905
 
/**
2906
 
 * Renderer for element borders.
2907
 
 * @constructor
2908
 
 * @param {Element} el The target element
2909
 
 * @param {Object} styleInfos The StyleInfo objects
2910
 
 * @param {PIE.RootRenderer} parent
2911
 
 */
2912
 
PIE.BorderRenderer = PIE.RendererBase.newRenderer( {
2913
 
 
2914
 
    boxZIndex: 4,
2915
 
    boxName: 'border',
2916
 
 
2917
 
    needsUpdate: function() {
2918
 
        var si = this.styleInfos;
2919
 
        return si.borderInfo.changed() || si.borderRadiusInfo.changed();
2920
 
    },
2921
 
 
2922
 
    isActive: function() {
2923
 
        var si = this.styleInfos;
2924
 
        return ( si.borderRadiusInfo.isActive() ||
2925
 
                 si.backgroundInfo.isActive() ) &&
2926
 
               !si.borderImageInfo.isActive() &&
2927
 
               si.borderInfo.isActive(); //check BorderStyleInfo last because it's the most expensive
2928
 
    },
2929
 
 
2930
 
    /**
2931
 
     * Draw the border shape(s)
2932
 
     */
2933
 
    draw: function() {
2934
 
        var el = this.targetElement,
2935
 
            props = this.styleInfos.borderInfo.getProps(),
2936
 
            bounds = this.boundsInfo.getBounds(),
2937
 
            w = bounds.w,
2938
 
            h = bounds.h,
2939
 
            shape, stroke, s,
2940
 
            segments, seg, i, len;
2941
 
 
2942
 
        if( props ) {
2943
 
            this.hideBorder();
2944
 
 
2945
 
            segments = this.getBorderSegments( 2 );
2946
 
            for( i = 0, len = segments.length; i < len; i++) {
2947
 
                seg = segments[i];
2948
 
                shape = this.getShape( 'borderPiece' + i, seg.stroke ? 'stroke' : 'fill', this.getBox() );
2949
 
                shape.coordsize = w * 2 + ',' + h * 2;
2950
 
                shape.coordorigin = '1,1';
2951
 
                shape.path = seg.path;
2952
 
                s = shape.style;
2953
 
                s.width = w;
2954
 
                s.height = h;
2955
 
 
2956
 
                shape.filled = !!seg.fill;
2957
 
                shape.stroked = !!seg.stroke;
2958
 
                if( seg.stroke ) {
2959
 
                    stroke = shape.stroke;
2960
 
                    stroke['weight'] = seg.weight + 'px';
2961
 
                    stroke.color = seg.color.colorValue( el );
2962
 
                    stroke['dashstyle'] = seg.stroke === 'dashed' ? '2 2' : seg.stroke === 'dotted' ? '1 1' : 'solid';
2963
 
                    stroke['linestyle'] = seg.stroke === 'double' && seg.weight > 2 ? 'ThinThin' : 'Single';
2964
 
                } else {
2965
 
                    shape.fill.color = seg.fill.colorValue( el );
2966
 
                }
2967
 
            }
2968
 
 
2969
 
            // remove any previously-created border shapes which didn't get used above
2970
 
            while( this.deleteShape( 'borderPiece' + i++ ) ) {}
2971
 
        }
2972
 
    },
2973
 
 
2974
 
 
2975
 
    /**
2976
 
     * Get the VML path definitions for the border segment(s).
2977
 
     * @param {number=} mult If specified, all coordinates will be multiplied by this number
2978
 
     * @return {Array.<string>}
2979
 
     */
2980
 
    getBorderSegments: function( mult ) {
2981
 
        var el = this.targetElement,
2982
 
            bounds, elW, elH,
2983
 
            borderInfo = this.styleInfos.borderInfo,
2984
 
            segments = [],
2985
 
            floor, ceil, wT, wR, wB, wL,
2986
 
            round = Math.round,
2987
 
            borderProps, radiusInfo, radii, widths, styles, colors;
2988
 
 
2989
 
        if( borderInfo.isActive() ) {
2990
 
            borderProps = borderInfo.getProps();
2991
 
 
2992
 
            widths = borderProps.widths;
2993
 
            styles = borderProps.styles;
2994
 
            colors = borderProps.colors;
2995
 
 
2996
 
            if( borderProps.widthsSame && borderProps.stylesSame && borderProps.colorsSame ) {
2997
 
                if( colors['t'].alpha() > 0 ) {
2998
 
                    // shortcut for identical border on all sides - only need 1 stroked shape
2999
 
                    wT = widths['t'].pixels( el ); //thickness
3000
 
                    wR = wT / 2; //shrink
3001
 
                    segments.push( {
3002
 
                        path: this.getBoxPath( { t: wR, r: wR, b: wR, l: wR }, mult ),
3003
 
                        stroke: styles['t'],
3004
 
                        color: colors['t'],
3005
 
                        weight: wT
3006
 
                    } );
3007
 
                }
3008
 
            }
3009
 
            else {
3010
 
                mult = mult || 1;
3011
 
                bounds = this.boundsInfo.getBounds();
3012
 
                elW = bounds.w;
3013
 
                elH = bounds.h;
3014
 
 
3015
 
                wT = round( widths['t'].pixels( el ) );
3016
 
                wR = round( widths['r'].pixels( el ) );
3017
 
                wB = round( widths['b'].pixels( el ) );
3018
 
                wL = round( widths['l'].pixels( el ) );
3019
 
                var pxWidths = {
3020
 
                    't': wT,
3021
 
                    'r': wR,
3022
 
                    'b': wB,
3023
 
                    'l': wL
3024
 
                };
3025
 
 
3026
 
                radiusInfo = this.styleInfos.borderRadiusInfo;
3027
 
                if( radiusInfo.isActive() ) {
3028
 
                    radii = this.getRadiiPixels( radiusInfo.getProps() );
3029
 
                }
3030
 
 
3031
 
                floor = Math.floor;
3032
 
                ceil = Math.ceil;
3033
 
 
3034
 
                function radius( xy, corner ) {
3035
 
                    return radii ? radii[ xy ][ corner ] : 0;
3036
 
                }
3037
 
 
3038
 
                function curve( corner, shrinkX, shrinkY, startAngle, ccw, doMove ) {
3039
 
                    var rx = radius( 'x', corner),
3040
 
                        ry = radius( 'y', corner),
3041
 
                        deg = 65535,
3042
 
                        isRight = corner.charAt( 1 ) === 'r',
3043
 
                        isBottom = corner.charAt( 0 ) === 'b';
3044
 
                    return ( rx > 0 && ry > 0 ) ?
3045
 
                                ( doMove ? 'al' : 'ae' ) +
3046
 
                                ( isRight ? ceil( elW - rx ) : floor( rx ) ) * mult + ',' + // center x
3047
 
                                ( isBottom ? ceil( elH - ry ) : floor( ry ) ) * mult + ',' + // center y
3048
 
                                ( floor( rx ) - shrinkX ) * mult + ',' + // width
3049
 
                                ( floor( ry ) - shrinkY ) * mult + ',' + // height
3050
 
                                ( startAngle * deg ) + ',' + // start angle
3051
 
                                ( 45 * deg * ( ccw ? 1 : -1 ) // angle change
3052
 
                            ) : (
3053
 
                                ( doMove ? 'm' : 'l' ) +
3054
 
                                ( isRight ? elW - shrinkX : shrinkX ) * mult + ',' +
3055
 
                                ( isBottom ? elH - shrinkY : shrinkY ) * mult
3056
 
                            );
3057
 
                }
3058
 
 
3059
 
                function line( side, shrink, ccw, doMove ) {
3060
 
                    var
3061
 
                        start = (
3062
 
                            side === 't' ?
3063
 
                                floor( radius( 'x', 'tl') ) * mult + ',' + ceil( shrink ) * mult :
3064
 
                            side === 'r' ?
3065
 
                                ceil( elW - shrink ) * mult + ',' + floor( radius( 'y', 'tr') ) * mult :
3066
 
                            side === 'b' ?
3067
 
                                ceil( elW - radius( 'x', 'br') ) * mult + ',' + floor( elH - shrink ) * mult :
3068
 
                            // side === 'l' ?
3069
 
                                floor( shrink ) * mult + ',' + ceil( elH - radius( 'y', 'bl') ) * mult
3070
 
                        ),
3071
 
                        end = (
3072
 
                            side === 't' ?
3073
 
                                ceil( elW - radius( 'x', 'tr') ) * mult + ',' + ceil( shrink ) * mult :
3074
 
                            side === 'r' ?
3075
 
                                ceil( elW - shrink ) * mult + ',' + ceil( elH - radius( 'y', 'br') ) * mult :
3076
 
                            side === 'b' ?
3077
 
                                floor( radius( 'x', 'bl') ) * mult + ',' + floor( elH - shrink ) * mult :
3078
 
                            // side === 'l' ?
3079
 
                                floor( shrink ) * mult + ',' + floor( radius( 'y', 'tl') ) * mult
3080
 
                        );
3081
 
                    return ccw ? ( doMove ? 'm' + end : '' ) + 'l' + start :
3082
 
                                 ( doMove ? 'm' + start : '' ) + 'l' + end;
3083
 
                }
3084
 
 
3085
 
 
3086
 
                function addSide( side, sideBefore, sideAfter, cornerBefore, cornerAfter, baseAngle ) {
3087
 
                    var vert = side === 'l' || side === 'r',
3088
 
                        sideW = pxWidths[ side ],
3089
 
                        beforeX, beforeY, afterX, afterY;
3090
 
 
3091
 
                    if( sideW > 0 && styles[ side ] !== 'none' && colors[ side ].alpha() > 0 ) {
3092
 
                        beforeX = pxWidths[ vert ? side : sideBefore ];
3093
 
                        beforeY = pxWidths[ vert ? sideBefore : side ];
3094
 
                        afterX = pxWidths[ vert ? side : sideAfter ];
3095
 
                        afterY = pxWidths[ vert ? sideAfter : side ];
3096
 
 
3097
 
                        if( styles[ side ] === 'dashed' || styles[ side ] === 'dotted' ) {
3098
 
                            segments.push( {
3099
 
                                path: curve( cornerBefore, beforeX, beforeY, baseAngle + 45, 0, 1 ) +
3100
 
                                      curve( cornerBefore, 0, 0, baseAngle, 1, 0 ),
3101
 
                                fill: colors[ side ]
3102
 
                            } );
3103
 
                            segments.push( {
3104
 
                                path: line( side, sideW / 2, 0, 1 ),
3105
 
                                stroke: styles[ side ],
3106
 
                                weight: sideW,
3107
 
                                color: colors[ side ]
3108
 
                            } );
3109
 
                            segments.push( {
3110
 
                                path: curve( cornerAfter, afterX, afterY, baseAngle, 0, 1 ) +
3111
 
                                      curve( cornerAfter, 0, 0, baseAngle - 45, 1, 0 ),
3112
 
                                fill: colors[ side ]
3113
 
                            } );
3114
 
                        }
3115
 
                        else {
3116
 
                            segments.push( {
3117
 
                                path: curve( cornerBefore, beforeX, beforeY, baseAngle + 45, 0, 1 ) +
3118
 
                                      line( side, sideW, 0, 0 ) +
3119
 
                                      curve( cornerAfter, afterX, afterY, baseAngle, 0, 0 ) +
3120
 
 
3121
 
                                      ( styles[ side ] === 'double' && sideW > 2 ?
3122
 
                                              curve( cornerAfter, afterX - floor( afterX / 3 ), afterY - floor( afterY / 3 ), baseAngle - 45, 1, 0 ) +
3123
 
                                              line( side, ceil( sideW / 3 * 2 ), 1, 0 ) +
3124
 
                                              curve( cornerBefore, beforeX - floor( beforeX / 3 ), beforeY - floor( beforeY / 3 ), baseAngle, 1, 0 ) +
3125
 
                                              'x ' +
3126
 
                                              curve( cornerBefore, floor( beforeX / 3 ), floor( beforeY / 3 ), baseAngle + 45, 0, 1 ) +
3127
 
                                              line( side, floor( sideW / 3 ), 1, 0 ) +
3128
 
                                              curve( cornerAfter, floor( afterX / 3 ), floor( afterY / 3 ), baseAngle, 0, 0 )
3129
 
                                          : '' ) +
3130
 
 
3131
 
                                      curve( cornerAfter, 0, 0, baseAngle - 45, 1, 0 ) +
3132
 
                                      line( side, 0, 1, 0 ) +
3133
 
                                      curve( cornerBefore, 0, 0, baseAngle, 1, 0 ),
3134
 
                                fill: colors[ side ]
3135
 
                            } );
3136
 
                        }
3137
 
                    }
3138
 
                }
3139
 
 
3140
 
                addSide( 't', 'l', 'r', 'tl', 'tr', 90 );
3141
 
                addSide( 'r', 't', 'b', 'tr', 'br', 0 );
3142
 
                addSide( 'b', 'r', 'l', 'br', 'bl', -90 );
3143
 
                addSide( 'l', 'b', 't', 'bl', 'tl', -180 );
3144
 
            }
3145
 
        }
3146
 
 
3147
 
        return segments;
3148
 
    },
3149
 
 
3150
 
    destroy: function() {
3151
 
        var me = this;
3152
 
        if (me.finalized || !me.styleInfos.borderImageInfo.isActive()) {
3153
 
            me.targetElement.runtimeStyle.borderColor = '';
3154
 
        }
3155
 
        PIE.RendererBase.destroy.call( me );
3156
 
    }
3157
 
 
3158
 
 
3159
 
} );
3160
 
/**
3161
 
 * Renderer for border-image
3162
 
 * @constructor
3163
 
 * @param {Element} el The target element
3164
 
 * @param {Object} styleInfos The StyleInfo objects
3165
 
 * @param {PIE.RootRenderer} parent
3166
 
 */
3167
 
PIE.BorderImageRenderer = PIE.RendererBase.newRenderer( {
3168
 
 
3169
 
    boxZIndex: 5,
3170
 
    pieceNames: [ 't', 'tr', 'r', 'br', 'b', 'bl', 'l', 'tl', 'c' ],
3171
 
 
3172
 
    needsUpdate: function() {
3173
 
        return this.styleInfos.borderImageInfo.changed();
3174
 
    },
3175
 
 
3176
 
    isActive: function() {
3177
 
        return this.styleInfos.borderImageInfo.isActive();
3178
 
    },
3179
 
 
3180
 
    draw: function() {
3181
 
        this.getBox(); //make sure pieces are created
3182
 
 
3183
 
        var props = this.styleInfos.borderImageInfo.getProps(),
3184
 
            borderProps = this.styleInfos.borderInfo.getProps(),
3185
 
            bounds = this.boundsInfo.getBounds(),
3186
 
            el = this.targetElement,
3187
 
            pieces = this.pieces;
3188
 
 
3189
 
        PIE.Util.withImageSize( props.src, function( imgSize ) {
3190
 
            var elW = bounds.w,
3191
 
                elH = bounds.h,
3192
 
                zero = PIE.getLength( '0' ),
3193
 
                widths = props.widths || ( borderProps ? borderProps.widths : { 't': zero, 'r': zero, 'b': zero, 'l': zero } ),
3194
 
                widthT = widths['t'].pixels( el ),
3195
 
                widthR = widths['r'].pixels( el ),
3196
 
                widthB = widths['b'].pixels( el ),
3197
 
                widthL = widths['l'].pixels( el ),
3198
 
                slices = props.slice,
3199
 
                sliceT = slices['t'].pixels( el ),
3200
 
                sliceR = slices['r'].pixels( el ),
3201
 
                sliceB = slices['b'].pixels( el ),
3202
 
                sliceL = slices['l'].pixels( el );
3203
 
 
3204
 
            // Piece positions and sizes
3205
 
            function setSizeAndPos( piece, w, h, x, y ) {
3206
 
                var s = pieces[piece].style,
3207
 
                    max = Math.max;
3208
 
                s.width = max(w, 0);
3209
 
                s.height = max(h, 0);
3210
 
                s.left = x;
3211
 
                s.top = y;
3212
 
            }
3213
 
            setSizeAndPos( 'tl', widthL, widthT, 0, 0 );
3214
 
            setSizeAndPos( 't', elW - widthL - widthR, widthT, widthL, 0 );
3215
 
            setSizeAndPos( 'tr', widthR, widthT, elW - widthR, 0 );
3216
 
            setSizeAndPos( 'r', widthR, elH - widthT - widthB, elW - widthR, widthT );
3217
 
            setSizeAndPos( 'br', widthR, widthB, elW - widthR, elH - widthB );
3218
 
            setSizeAndPos( 'b', elW - widthL - widthR, widthB, widthL, elH - widthB );
3219
 
            setSizeAndPos( 'bl', widthL, widthB, 0, elH - widthB );
3220
 
            setSizeAndPos( 'l', widthL, elH - widthT - widthB, 0, widthT );
3221
 
            setSizeAndPos( 'c', elW - widthL - widthR, elH - widthT - widthB, widthL, widthT );
3222
 
 
3223
 
 
3224
 
            // image croppings
3225
 
            function setCrops( sides, crop, val ) {
3226
 
                for( var i=0, len=sides.length; i < len; i++ ) {
3227
 
                    pieces[ sides[i] ]['imagedata'][ crop ] = val;
3228
 
                }
3229
 
            }
3230
 
 
3231
 
            // corners
3232
 
            setCrops( [ 'tl', 't', 'tr' ], 'cropBottom', ( imgSize.h - sliceT ) / imgSize.h );
3233
 
            setCrops( [ 'tl', 'l', 'bl' ], 'cropRight', ( imgSize.w - sliceL ) / imgSize.w );
3234
 
            setCrops( [ 'bl', 'b', 'br' ], 'cropTop', ( imgSize.h - sliceB ) / imgSize.h );
3235
 
            setCrops( [ 'tr', 'r', 'br' ], 'cropLeft', ( imgSize.w - sliceR ) / imgSize.w );
3236
 
 
3237
 
            // edges and center
3238
 
            // TODO right now this treats everything like 'stretch', need to support other schemes
3239
 
            //if( props.repeat.v === 'stretch' ) {
3240
 
                setCrops( [ 'l', 'r', 'c' ], 'cropTop', sliceT / imgSize.h );
3241
 
                setCrops( [ 'l', 'r', 'c' ], 'cropBottom', sliceB / imgSize.h );
3242
 
            //}
3243
 
            //if( props.repeat.h === 'stretch' ) {
3244
 
                setCrops( [ 't', 'b', 'c' ], 'cropLeft', sliceL / imgSize.w );
3245
 
                setCrops( [ 't', 'b', 'c' ], 'cropRight', sliceR / imgSize.w );
3246
 
            //}
3247
 
 
3248
 
            // center fill
3249
 
            pieces['c'].style.display = props.fill ? '' : 'none';
3250
 
        }, this );
3251
 
    },
3252
 
 
3253
 
    getBox: function() {
3254
 
        var box = this.parent.getLayer( this.boxZIndex ),
3255
 
            s, piece, i,
3256
 
            pieceNames = this.pieceNames,
3257
 
            len = pieceNames.length;
3258
 
 
3259
 
        if( !box ) {
3260
 
            box = doc.createElement( 'border-image' );
3261
 
            s = box.style;
3262
 
            s.position = 'absolute';
3263
 
 
3264
 
            this.pieces = {};
3265
 
 
3266
 
            for( i = 0; i < len; i++ ) {
3267
 
                piece = this.pieces[ pieceNames[i] ] = PIE.Util.createVmlElement( 'rect' );
3268
 
                piece.appendChild( PIE.Util.createVmlElement( 'imagedata' ) );
3269
 
                s = piece.style;
3270
 
                s['behavior'] = 'url(#default#VML)';
3271
 
                s.position = "absolute";
3272
 
                s.top = s.left = 0;
3273
 
                piece['imagedata'].src = this.styleInfos.borderImageInfo.getProps().src;
3274
 
                piece.stroked = false;
3275
 
                piece.filled = false;
3276
 
                box.appendChild( piece );
3277
 
            }
3278
 
 
3279
 
            this.parent.addLayer( this.boxZIndex, box );
3280
 
        }
3281
 
 
3282
 
        return box;
3283
 
    },
3284
 
 
3285
 
    prepareUpdate: function() {
3286
 
        if (this.isActive()) {
3287
 
            var me = this,
3288
 
                el = me.targetElement,
3289
 
                rs = el.runtimeStyle,
3290
 
                widths = me.styleInfos.borderImageInfo.getProps().widths;
3291
 
 
3292
 
            // Force border-style to solid so it doesn't collapse
3293
 
            rs.borderStyle = 'solid';
3294
 
 
3295
 
            // If widths specified in border-image shorthand, override border-width
3296
 
            // NOTE px units needed here as this gets used by the IE9 renderer too
3297
 
            if ( widths ) {
3298
 
                rs.borderTopWidth = widths['t'].pixels( el ) + 'px';
3299
 
                rs.borderRightWidth = widths['r'].pixels( el ) + 'px';
3300
 
                rs.borderBottomWidth = widths['b'].pixels( el ) + 'px';
3301
 
                rs.borderLeftWidth = widths['l'].pixels( el ) + 'px';
3302
 
            }
3303
 
 
3304
 
            // Make the border transparent
3305
 
            me.hideBorder();
3306
 
        }
3307
 
    },
3308
 
 
3309
 
    destroy: function() {
3310
 
        var me = this,
3311
 
            rs = me.targetElement.runtimeStyle;
3312
 
        rs.borderStyle = '';
3313
 
        if (me.finalized || !me.styleInfos.borderInfo.isActive()) {
3314
 
            rs.borderColor = rs.borderWidth = '';
3315
 
        }
3316
 
        PIE.RendererBase.destroy.call( this );
3317
 
    }
3318
 
 
3319
 
} );
3320
 
/**
3321
 
 * Renderer for outset box-shadows
3322
 
 * @constructor
3323
 
 * @param {Element} el The target element
3324
 
 * @param {Object} styleInfos The StyleInfo objects
3325
 
 * @param {PIE.RootRenderer} parent
3326
 
 */
3327
 
PIE.BoxShadowOutsetRenderer = PIE.RendererBase.newRenderer( {
3328
 
 
3329
 
    boxZIndex: 1,
3330
 
    boxName: 'outset-box-shadow',
3331
 
 
3332
 
    needsUpdate: function() {
3333
 
        var si = this.styleInfos;
3334
 
        return si.boxShadowInfo.changed() || si.borderRadiusInfo.changed();
3335
 
    },
3336
 
 
3337
 
    isActive: function() {
3338
 
        var boxShadowInfo = this.styleInfos.boxShadowInfo;
3339
 
        return boxShadowInfo.isActive() && boxShadowInfo.getProps().outset[0];
3340
 
    },
3341
 
 
3342
 
    draw: function() {
3343
 
        var me = this,
3344
 
            el = this.targetElement,
3345
 
            box = this.getBox(),
3346
 
            styleInfos = this.styleInfos,
3347
 
            shadowInfos = styleInfos.boxShadowInfo.getProps().outset,
3348
 
            radii = styleInfos.borderRadiusInfo.getProps(),
3349
 
            len = shadowInfos.length,
3350
 
            i = len, j,
3351
 
            bounds = this.boundsInfo.getBounds(),
3352
 
            w = bounds.w,
3353
 
            h = bounds.h,
3354
 
            clipAdjust = PIE.ieVersion === 8 ? 1 : 0, //workaround for IE8 bug where VML leaks out top/left of clip region by 1px
3355
 
            corners = [ 'tl', 'tr', 'br', 'bl' ], corner,
3356
 
            shadowInfo, shape, fill, ss, xOff, yOff, spread, blur, shrink, color, alpha, path,
3357
 
            totalW, totalH, focusX, focusY, isBottom, isRight;
3358
 
 
3359
 
 
3360
 
        function getShadowShape( index, corner, xOff, yOff, color, blur, path ) {
3361
 
            var shape = me.getShape( 'shadow' + index + corner, 'fill', box, len - index ),
3362
 
                fill = shape.fill;
3363
 
 
3364
 
            // Position and size
3365
 
            shape['coordsize'] = w * 2 + ',' + h * 2;
3366
 
            shape['coordorigin'] = '1,1';
3367
 
 
3368
 
            // Color and opacity
3369
 
            shape['stroked'] = false;
3370
 
            shape['filled'] = true;
3371
 
            fill.color = color.colorValue( el );
3372
 
            if( blur ) {
3373
 
                fill['type'] = 'gradienttitle'; //makes the VML gradient follow the shape's outline - hooray for undocumented features?!?!
3374
 
                fill['color2'] = fill.color;
3375
 
                fill['opacity'] = 0;
3376
 
            }
3377
 
 
3378
 
            // Path
3379
 
            shape.path = path;
3380
 
 
3381
 
            // This needs to go last for some reason, to prevent rendering at incorrect size
3382
 
            ss = shape.style;
3383
 
            ss.left = xOff;
3384
 
            ss.top = yOff;
3385
 
            ss.width = w;
3386
 
            ss.height = h;
3387
 
 
3388
 
            return shape;
3389
 
        }
3390
 
 
3391
 
 
3392
 
        while( i-- ) {
3393
 
            shadowInfo = shadowInfos[ i ];
3394
 
            xOff = shadowInfo.xOffset.pixels( el );
3395
 
            yOff = shadowInfo.yOffset.pixels( el );
3396
 
            spread = shadowInfo.spread.pixels( el ),
3397
 
            blur = shadowInfo.blur.pixels( el );
3398
 
            color = shadowInfo.color;
3399
 
            // Shape path
3400
 
            shrink = -spread - blur;
3401
 
            if( !radii && blur ) {
3402
 
                // If blurring, use a non-null border radius info object so that getBoxPath will
3403
 
                // round the corners of the expanded shadow shape rather than squaring them off.
3404
 
                radii = PIE.BorderRadiusStyleInfo.ALL_ZERO;
3405
 
            }
3406
 
            path = this.getBoxPath( { t: shrink, r: shrink, b: shrink, l: shrink }, 2, radii );
3407
 
 
3408
 
            if( blur ) {
3409
 
                totalW = ( spread + blur ) * 2 + w;
3410
 
                totalH = ( spread + blur ) * 2 + h;
3411
 
                focusX = blur * 2 / totalW;
3412
 
                focusY = blur * 2 / totalH;
3413
 
                if( blur - spread > w / 2 || blur - spread > h / 2 ) {
3414
 
                    // If the blur is larger than half the element's narrowest dimension, we cannot do
3415
 
                    // this with a single shape gradient, because its focussize would have to be less than
3416
 
                    // zero which results in ugly artifacts. Instead we create four shapes, each with its
3417
 
                    // gradient focus past center, and then clip them so each only shows the quadrant
3418
 
                    // opposite the focus.
3419
 
                    for( j = 4; j--; ) {
3420
 
                        corner = corners[j];
3421
 
                        isBottom = corner.charAt( 0 ) === 'b';
3422
 
                        isRight = corner.charAt( 1 ) === 'r';
3423
 
                        shape = getShadowShape( i, corner, xOff, yOff, color, blur, path );
3424
 
                        fill = shape.fill;
3425
 
                        fill['focusposition'] = ( isRight ? 1 - focusX : focusX ) + ',' +
3426
 
                                                ( isBottom ? 1 - focusY : focusY );
3427
 
                        fill['focussize'] = '0,0';
3428
 
 
3429
 
                        // Clip to show only the appropriate quadrant. Add 1px to the top/left clip values
3430
 
                        // in IE8 to prevent a bug where IE8 displays one pixel outside the clip region.
3431
 
                        shape.style.clip = 'rect(' + ( ( isBottom ? totalH / 2 : 0 ) + clipAdjust ) + 'px,' +
3432
 
                                                     ( isRight ? totalW : totalW / 2 ) + 'px,' +
3433
 
                                                     ( isBottom ? totalH : totalH / 2 ) + 'px,' +
3434
 
                                                     ( ( isRight ? totalW / 2 : 0 ) + clipAdjust ) + 'px)';
3435
 
                    }
3436
 
                } else {
3437
 
                    // TODO delete old quadrant shapes if resizing expands past the barrier
3438
 
                    shape = getShadowShape( i, '', xOff, yOff, color, blur, path );
3439
 
                    fill = shape.fill;
3440
 
                    fill['focusposition'] = focusX + ',' + focusY;
3441
 
                    fill['focussize'] = ( 1 - focusX * 2 ) + ',' + ( 1 - focusY * 2 );
3442
 
                }
3443
 
            } else {
3444
 
                shape = getShadowShape( i, '', xOff, yOff, color, blur, path );
3445
 
                alpha = color.alpha();
3446
 
                if( alpha < 1 ) {
3447
 
                    // shape.style.filter = 'alpha(opacity=' + ( alpha * 100 ) + ')';
3448
 
                    // ss.filter = 'progid:DXImageTransform.Microsoft.BasicImage(opacity=' + ( alpha  ) + ')';
3449
 
                    shape.fill.opacity = alpha;
3450
 
                }
3451
 
            }
3452
 
        }
3453
 
    }
3454
 
 
3455
 
} );
3456
 
/**
3457
 
 * Renderer for re-rendering img elements using VML. Kicks in if the img has
3458
 
 * a border-radius applied, or if the -pie-png-fix flag is set.
3459
 
 * @constructor
3460
 
 * @param {Element} el The target element
3461
 
 * @param {Object} styleInfos The StyleInfo objects
3462
 
 * @param {PIE.RootRenderer} parent
3463
 
 */
3464
 
PIE.ImgRenderer = PIE.RendererBase.newRenderer( {
3465
 
 
3466
 
    boxZIndex: 6,
3467
 
    boxName: 'imgEl',
3468
 
 
3469
 
    needsUpdate: function() {
3470
 
        var si = this.styleInfos;
3471
 
        return this.targetElement.src !== this._lastSrc || si.borderRadiusInfo.changed();
3472
 
    },
3473
 
 
3474
 
    isActive: function() {
3475
 
        var si = this.styleInfos;
3476
 
        return si.borderRadiusInfo.isActive() || si.backgroundInfo.isPngFix();
3477
 
    },
3478
 
 
3479
 
    draw: function() {
3480
 
        this._lastSrc = src;
3481
 
        this.hideActualImg();
3482
 
 
3483
 
        var shape = this.getShape( 'img', 'fill', this.getBox() ),
3484
 
            fill = shape.fill,
3485
 
            bounds = this.boundsInfo.getBounds(),
3486
 
            w = bounds.w,
3487
 
            h = bounds.h,
3488
 
            borderProps = this.styleInfos.borderInfo.getProps(),
3489
 
            borderWidths = borderProps && borderProps.widths,
3490
 
            el = this.targetElement,
3491
 
            src = el.src,
3492
 
            round = Math.round,
3493
 
            cs = el.currentStyle,
3494
 
            getLength = PIE.getLength,
3495
 
            s, zero;
3496
 
 
3497
 
        // In IE6, the BorderRenderer will have hidden the border by moving the border-width to
3498
 
        // the padding; therefore we want to pretend the borders have no width so they aren't doubled
3499
 
        // when adding in the current padding value below.
3500
 
        if( !borderWidths || PIE.ieVersion < 7 ) {
3501
 
            zero = PIE.getLength( '0' );
3502
 
            borderWidths = { 't': zero, 'r': zero, 'b': zero, 'l': zero };
3503
 
        }
3504
 
 
3505
 
        shape.stroked = false;
3506
 
        fill.type = 'frame';
3507
 
        fill.src = src;
3508
 
        fill.position = (w ? 0.5 / w : 0) + ',' + (h ? 0.5 / h : 0);
3509
 
        shape.coordsize = w * 2 + ',' + h * 2;
3510
 
        shape.coordorigin = '1,1';
3511
 
        shape.path = this.getBoxPath( {
3512
 
            t: round( borderWidths['t'].pixels( el ) + getLength( cs.paddingTop ).pixels( el ) ),
3513
 
            r: round( borderWidths['r'].pixels( el ) + getLength( cs.paddingRight ).pixels( el ) ),
3514
 
            b: round( borderWidths['b'].pixels( el ) + getLength( cs.paddingBottom ).pixels( el ) ),
3515
 
            l: round( borderWidths['l'].pixels( el ) + getLength( cs.paddingLeft ).pixels( el ) )
3516
 
        }, 2 );
3517
 
        s = shape.style;
3518
 
        s.width = w;
3519
 
        s.height = h;
3520
 
    },
3521
 
 
3522
 
    hideActualImg: function() {
3523
 
        this.targetElement.runtimeStyle.filter = 'alpha(opacity=0)';
3524
 
    },
3525
 
 
3526
 
    destroy: function() {
3527
 
        PIE.RendererBase.destroy.call( this );
3528
 
        this.targetElement.runtimeStyle.filter = '';
3529
 
    }
3530
 
 
3531
 
} );
3532
 
/**
3533
 
 * Root renderer for IE9; manages the rendering layers in the element's background
3534
 
 * @param {Element} el The target element
3535
 
 * @param {Object} styleInfos The StyleInfo objects
3536
 
 */
3537
 
PIE.IE9RootRenderer = PIE.RendererBase.newRenderer( {
3538
 
 
3539
 
    updatePos: PIE.emptyFn,
3540
 
    updateSize: PIE.emptyFn,
3541
 
    updateVisibility: PIE.emptyFn,
3542
 
    updateProps: PIE.emptyFn,
3543
 
 
3544
 
    outerCommasRE: /^,+|,+$/g,
3545
 
    innerCommasRE: /,+/g,
3546
 
 
3547
 
    setBackgroundLayer: function(zIndex, bg) {
3548
 
        var me = this,
3549
 
            bgLayers = me._bgLayers || ( me._bgLayers = [] ),
3550
 
            undef;
3551
 
        bgLayers[zIndex] = bg || undef;
3552
 
    },
3553
 
 
3554
 
    finishUpdate: function() {
3555
 
        var me = this,
3556
 
            bgLayers = me._bgLayers,
3557
 
            bg;
3558
 
        if( bgLayers && ( bg = bgLayers.join( ',' ).replace( me.outerCommasRE, '' ).replace( me.innerCommasRE, ',' ) ) !== me._lastBg ) {
3559
 
            me._lastBg = me.targetElement.runtimeStyle.background = bg;
3560
 
        }
3561
 
    },
3562
 
 
3563
 
    destroy: function() {
3564
 
        this.targetElement.runtimeStyle.background = '';
3565
 
        delete this._bgLayers;
3566
 
    }
3567
 
 
3568
 
} );
3569
 
/**
3570
 
 * Renderer for element backgrounds, specific for IE9. Only handles translating CSS3 gradients
3571
 
 * to an equivalent SVG data URI.
3572
 
 * @constructor
3573
 
 * @param {Element} el The target element
3574
 
 * @param {Object} styleInfos The StyleInfo objects
3575
 
 */
3576
 
PIE.IE9BackgroundRenderer = PIE.RendererBase.newRenderer( {
3577
 
 
3578
 
    bgLayerZIndex: 1,
3579
 
 
3580
 
    needsUpdate: function() {
3581
 
        var si = this.styleInfos;
3582
 
        return si.backgroundInfo.changed();
3583
 
    },
3584
 
 
3585
 
    isActive: function() {
3586
 
        var si = this.styleInfos;
3587
 
        return si.backgroundInfo.isActive() || si.borderImageInfo.isActive();
3588
 
    },
3589
 
 
3590
 
    draw: function() {
3591
 
        var me = this,
3592
 
            props = me.styleInfos.backgroundInfo.getProps(),
3593
 
            bg, images, i = 0, img, bgAreaSize, bgSize;
3594
 
 
3595
 
        if ( props ) {
3596
 
            bg = [];
3597
 
 
3598
 
            images = props.bgImages;
3599
 
            if ( images ) {
3600
 
                while( img = images[ i++ ] ) {
3601
 
                    if (img.imgType === 'linear-gradient' ) {
3602
 
                        bgAreaSize = me.getBgAreaSize( img.bgOrigin );
3603
 
                        bgSize = ( img.bgSize || PIE.BgSize.DEFAULT ).pixels(
3604
 
                            me.targetElement, bgAreaSize.w, bgAreaSize.h, bgAreaSize.w, bgAreaSize.h
3605
 
                        ),
3606
 
                        bg.push(
3607
 
                            'url(data:image/svg+xml,' + escape( me.getGradientSvg( img, bgSize.w, bgSize.h ) ) + ') ' +
3608
 
                            me.bgPositionToString( img.bgPosition ) + ' / ' + bgSize.w + 'px ' + bgSize.h + 'px ' +
3609
 
                            ( img.bgAttachment || '' ) + ' ' + ( img.bgOrigin || '' ) + ' ' + ( img.bgClip || '' )
3610
 
                        );
3611
 
                    } else {
3612
 
                        bg.push( img.origString );
3613
 
                    }
3614
 
                }
3615
 
            }
3616
 
 
3617
 
            if ( props.color ) {
3618
 
                bg.push( props.color.val );
3619
 
            }
3620
 
 
3621
 
            me.parent.setBackgroundLayer(me.bgLayerZIndex, bg.join(','));
3622
 
        }
3623
 
    },
3624
 
 
3625
 
    bgPositionToString: function( bgPosition ) {
3626
 
        return bgPosition ? bgPosition.tokens.map(function(token) {
3627
 
            return token.tokenValue;
3628
 
        }).join(' ') : '0 0';
3629
 
    },
3630
 
 
3631
 
    getBgAreaSize: function( bgOrigin ) {
3632
 
        var me = this,
3633
 
            el = me.targetElement,
3634
 
            bounds = me.boundsInfo.getBounds(),
3635
 
            elW = bounds.w,
3636
 
            elH = bounds.h,
3637
 
            w = elW,
3638
 
            h = elH,
3639
 
            borders, getLength, cs;
3640
 
 
3641
 
        if( bgOrigin !== 'border-box' ) {
3642
 
            borders = me.styleInfos.borderInfo.getProps();
3643
 
            if( borders && ( borders = borders.widths ) ) {
3644
 
                w -= borders[ 'l' ].pixels( el ) + borders[ 'l' ].pixels( el );
3645
 
                h -= borders[ 't' ].pixels( el ) + borders[ 'b' ].pixels( el );
3646
 
            }
3647
 
        }
3648
 
 
3649
 
        if ( bgOrigin === 'content-box' ) {
3650
 
            getLength = PIE.getLength;
3651
 
            cs = el.currentStyle;
3652
 
            w -= getLength( cs.paddingLeft ).pixels( el ) + getLength( cs.paddingRight ).pixels( el );
3653
 
            h -= getLength( cs.paddingTop ).pixels( el ) + getLength( cs.paddingBottom ).pixels( el );
3654
 
        }
3655
 
 
3656
 
        return { w: w, h: h };
3657
 
    },
3658
 
 
3659
 
    getGradientSvg: function( info, bgWidth, bgHeight ) {
3660
 
        var el = this.targetElement,
3661
 
            stopsInfo = info.stops,
3662
 
            stopCount = stopsInfo.length,
3663
 
            metrics = PIE.GradientUtil.getGradientMetrics( el, bgWidth, bgHeight, info ),
3664
 
            startX = metrics.startX,
3665
 
            startY = metrics.startY,
3666
 
            endX = metrics.endX,
3667
 
            endY = metrics.endY,
3668
 
            lineLength = metrics.lineLength,
3669
 
            stopPx,
3670
 
            i, j, before, after,
3671
 
            svg;
3672
 
 
3673
 
        // Find the pixel offsets along the CSS3 gradient-line for each stop.
3674
 
        stopPx = [];
3675
 
        for( i = 0; i < stopCount; i++ ) {
3676
 
            stopPx.push( stopsInfo[i].offset ? stopsInfo[i].offset.pixels( el, lineLength ) :
3677
 
                         i === 0 ? 0 : i === stopCount - 1 ? lineLength : null );
3678
 
        }
3679
 
        // Fill in gaps with evenly-spaced offsets
3680
 
        for( i = 1; i < stopCount; i++ ) {
3681
 
            if( stopPx[ i ] === null ) {
3682
 
                before = stopPx[ i - 1 ];
3683
 
                j = i;
3684
 
                do {
3685
 
                    after = stopPx[ ++j ];
3686
 
                } while( after === null );
3687
 
                stopPx[ i ] = before + ( after - before ) / ( j - i + 1 );
3688
 
            }
3689
 
        }
3690
 
 
3691
 
        svg = [
3692
 
            '<svg width="' + bgWidth + '" height="' + bgHeight + '" xmlns="http://www.w3.org/2000/svg">' +
3693
 
                '<defs>' +
3694
 
                    '<linearGradient id="g" gradientUnits="userSpaceOnUse"' +
3695
 
                    ' x1="' + ( startX / bgWidth * 100 ) + '%" y1="' + ( startY / bgHeight * 100 ) + '%" x2="' + ( endX / bgWidth * 100 ) + '%" y2="' + ( endY / bgHeight * 100 ) + '%">'
3696
 
        ];
3697
 
 
3698
 
        // Convert to percentage along the SVG gradient line and add to the stops list
3699
 
        for( i = 0; i < stopCount; i++ ) {
3700
 
            svg.push(
3701
 
                '<stop offset="' + ( stopPx[ i ] / lineLength ) +
3702
 
                    '" stop-color="' + stopsInfo[i].color.colorValue( el ) +
3703
 
                    '" stop-opacity="' + stopsInfo[i].color.alpha() + '"/>'
3704
 
            );
3705
 
        }
3706
 
 
3707
 
        svg.push(
3708
 
                    '</linearGradient>' +
3709
 
                '</defs>' +
3710
 
                '<rect width="100%" height="100%" fill="url(#g)"/>' +
3711
 
            '</svg>'
3712
 
        );
3713
 
 
3714
 
        return svg.join( '' );
3715
 
    },
3716
 
 
3717
 
    destroy: function() {
3718
 
        this.parent.setBackgroundLayer( this.bgLayerZIndex );
3719
 
    }
3720
 
 
3721
 
} );
3722
 
/**
3723
 
 * Renderer for border-image
3724
 
 * @constructor
3725
 
 * @param {Element} el The target element
3726
 
 * @param {Object} styleInfos The StyleInfo objects
3727
 
 * @param {PIE.RootRenderer} parent
3728
 
 */
3729
 
PIE.IE9BorderImageRenderer = PIE.RendererBase.newRenderer( {
3730
 
 
3731
 
    REPEAT: 'repeat',
3732
 
    STRETCH: 'stretch',
3733
 
    ROUND: 'round',
3734
 
 
3735
 
    bgLayerZIndex: 0,
3736
 
 
3737
 
    needsUpdate: function() {
3738
 
        return this.styleInfos.borderImageInfo.changed();
3739
 
    },
3740
 
 
3741
 
    isActive: function() {
3742
 
        return this.styleInfos.borderImageInfo.isActive();
3743
 
    },
3744
 
 
3745
 
    draw: function() {
3746
 
        var me = this,
3747
 
            props = me.styleInfos.borderImageInfo.getProps(),
3748
 
            borderProps = me.styleInfos.borderInfo.getProps(),
3749
 
            bounds = me.boundsInfo.getBounds(),
3750
 
            repeat = props.repeat,
3751
 
            repeatH = repeat.h,
3752
 
            repeatV = repeat.v,
3753
 
            el = me.targetElement,
3754
 
            isAsync = 0;
3755
 
 
3756
 
        PIE.Util.withImageSize( props.src, function( imgSize ) {
3757
 
            var elW = bounds.w,
3758
 
                elH = bounds.h,
3759
 
                imgW = imgSize.w,
3760
 
                imgH = imgSize.h,
3761
 
 
3762
 
                // The image cannot be referenced as a URL directly in the SVG because IE9 throws a strange
3763
 
                // security exception (perhaps due to cross-origin policy within data URIs?) Therefore we
3764
 
                // work around this by converting the image data into a data URI itself using a transient
3765
 
                // canvas. This unfortunately requires the border-image src to be within the same domain,
3766
 
                // which isn't a limitation in true border-image, so we need to try and find a better fix.
3767
 
                imgSrc = me.imageToDataURI( props.src, imgW, imgH ),
3768
 
 
3769
 
                REPEAT = me.REPEAT,
3770
 
                STRETCH = me.STRETCH,
3771
 
                ROUND = me.ROUND,
3772
 
                ceil = Math.ceil,
3773
 
 
3774
 
                zero = PIE.getLength( '0' ),
3775
 
                widths = props.widths || ( borderProps ? borderProps.widths : { 't': zero, 'r': zero, 'b': zero, 'l': zero } ),
3776
 
                widthT = widths['t'].pixels( el ),
3777
 
                widthR = widths['r'].pixels( el ),
3778
 
                widthB = widths['b'].pixels( el ),
3779
 
                widthL = widths['l'].pixels( el ),
3780
 
                slices = props.slice,
3781
 
                sliceT = slices['t'].pixels( el ),
3782
 
                sliceR = slices['r'].pixels( el ),
3783
 
                sliceB = slices['b'].pixels( el ),
3784
 
                sliceL = slices['l'].pixels( el ),
3785
 
                centerW = elW - widthL - widthR,
3786
 
                middleH = elH - widthT - widthB,
3787
 
                imgCenterW = imgW - sliceL - sliceR,
3788
 
                imgMiddleH = imgH - sliceT - sliceB,
3789
 
 
3790
 
                // Determine the size of each tile - 'round' is handled below
3791
 
                tileSizeT = repeatH === STRETCH ? centerW : imgCenterW * widthT / sliceT,
3792
 
                tileSizeR = repeatV === STRETCH ? middleH : imgMiddleH * widthR / sliceR,
3793
 
                tileSizeB = repeatH === STRETCH ? centerW : imgCenterW * widthB / sliceB,
3794
 
                tileSizeL = repeatV === STRETCH ? middleH : imgMiddleH * widthL / sliceL,
3795
 
 
3796
 
                svg,
3797
 
                patterns = [],
3798
 
                rects = [],
3799
 
                i = 0;
3800
 
 
3801
 
            // For 'round', subtract from each tile's size enough so that they fill the space a whole number of times
3802
 
            if (repeatH === ROUND) {
3803
 
                tileSizeT -= (tileSizeT - (centerW % tileSizeT || tileSizeT)) / ceil(centerW / tileSizeT);
3804
 
                tileSizeB -= (tileSizeB - (centerW % tileSizeB || tileSizeB)) / ceil(centerW / tileSizeB);
3805
 
            }
3806
 
            if (repeatV === ROUND) {
3807
 
                tileSizeR -= (tileSizeR - (middleH % tileSizeR || tileSizeR)) / ceil(middleH / tileSizeR);
3808
 
                tileSizeL -= (tileSizeL - (middleH % tileSizeL || tileSizeL)) / ceil(middleH / tileSizeL);
3809
 
            }
3810
 
 
3811
 
 
3812
 
            // Build the SVG for the border-image rendering. Add each piece as a pattern, which is then stretched
3813
 
            // or repeated as the fill of a rect of appropriate size.
3814
 
            svg = [
3815
 
                '<svg width="' + elW + '" height="' + elH + '" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">'
3816
 
            ];
3817
 
 
3818
 
            function addImage( x, y, w, h, cropX, cropY, cropW, cropH, tileW, tileH ) {
3819
 
                patterns.push(
3820
 
                    '<pattern patternUnits="userSpaceOnUse" id="pattern' + i + '" ' +
3821
 
                            'x="' + (repeatH === REPEAT ? x + w / 2 - tileW / 2 : x) + '" ' +
3822
 
                            'y="' + (repeatV === REPEAT ? y + h / 2 - tileH / 2 : y) + '" ' +
3823
 
                            'width="' + tileW + '" height="' + tileH + '">' +
3824
 
                        '<svg width="' + tileW + '" height="' + tileH + '" viewBox="' + cropX + ' ' + cropY + ' ' + cropW + ' ' + cropH + '" preserveAspectRatio="none">' +
3825
 
                            '<image xlink:href="' + imgSrc + '" x="0" y="0" width="' + imgW + '" height="' + imgH + '" />' +
3826
 
                        '</svg>' +
3827
 
                    '</pattern>'
3828
 
                );
3829
 
                rects.push(
3830
 
                    '<rect x="' + x + '" y="' + y + '" width="' + w + '" height="' + h + '" fill="url(#pattern' + i + ')" />'
3831
 
                );
3832
 
                i++;
3833
 
            }
3834
 
            addImage( 0, 0, widthL, widthT, 0, 0, sliceL, sliceT, widthL, widthT ); // top left
3835
 
            addImage( widthL, 0, centerW, widthT, sliceL, 0, imgCenterW, sliceT, tileSizeT, widthT ); // top center
3836
 
            addImage( elW - widthR, 0, widthR, widthT, imgW - sliceR, 0, sliceR, sliceT, widthR, widthT ); // top right
3837
 
            addImage( 0, widthT, widthL, middleH, 0, sliceT, sliceL, imgMiddleH, widthL, tileSizeL ); // middle left
3838
 
            if ( props.fill ) { // center fill
3839
 
                addImage( widthL, widthT, centerW, middleH, sliceL, sliceT, imgCenterW, imgMiddleH, 
3840
 
                          tileSizeT || tileSizeB || imgCenterW, tileSizeL || tileSizeR || imgMiddleH );
3841
 
            }
3842
 
            addImage( elW - widthR, widthT, widthR, middleH, imgW - sliceR, sliceT, sliceR, imgMiddleH, widthR, tileSizeR ); // middle right
3843
 
            addImage( 0, elH - widthB, widthL, widthB, 0, imgH - sliceB, sliceL, sliceB, widthL, widthB ); // bottom left
3844
 
            addImage( widthL, elH - widthB, centerW, widthB, sliceL, imgH - sliceB, imgCenterW, sliceB, tileSizeB, widthB ); // bottom center
3845
 
            addImage( elW - widthR, elH - widthB, widthR, widthB, imgW - sliceR, imgH - sliceB, sliceR, sliceB, widthR, widthB ); // bottom right
3846
 
 
3847
 
            svg.push(
3848
 
                    '<defs>' +
3849
 
                        patterns.join('\n') +
3850
 
                    '</defs>' +
3851
 
                    rects.join('\n') +
3852
 
                '</svg>'
3853
 
            );
3854
 
 
3855
 
            me.parent.setBackgroundLayer( me.bgLayerZIndex, 'url(data:image/svg+xml,' + escape( svg.join( '' ) ) + ') no-repeat border-box border-box' );
3856
 
 
3857
 
            // If the border-image's src wasn't immediately available, the SVG for its background layer
3858
 
            // will have been created asynchronously after the main element's update has finished; we'll
3859
 
            // therefore need to force the root renderer to sync to the final background once finished.
3860
 
            if( isAsync ) {
3861
 
                me.parent.finishUpdate();
3862
 
            }
3863
 
        }, me );
3864
 
 
3865
 
        isAsync = 1;
3866
 
    },
3867
 
 
3868
 
    /**
3869
 
     * Convert a given image to a data URI
3870
 
     */
3871
 
    imageToDataURI: (function() {
3872
 
        var uris = {};
3873
 
        return function( src, width, height ) {
3874
 
            var uri = uris[ src ],
3875
 
                image, canvas;
3876
 
            if ( !uri ) {
3877
 
                image = new Image();
3878
 
                canvas = doc.createElement( 'canvas' );
3879
 
                image.src = src;
3880
 
                canvas.width = width;
3881
 
                canvas.height = height;
3882
 
                canvas.getContext( '2d' ).drawImage( image, 0, 0 );
3883
 
                uri = uris[ src ] = canvas.toDataURL();
3884
 
            }
3885
 
            return uri;
3886
 
        }
3887
 
    })(),
3888
 
 
3889
 
    prepareUpdate: PIE.BorderImageRenderer.prototype.prepareUpdate,
3890
 
 
3891
 
    destroy: function() {
3892
 
        var me = this,
3893
 
            rs = me.targetElement.runtimeStyle;
3894
 
        me.parent.setBackgroundLayer( me.bgLayerZIndex );
3895
 
        rs.borderColor = rs.borderStyle = rs.borderWidth = '';
3896
 
    }
3897
 
 
3898
 
} );
3899
 
 
3900
 
PIE.Element = (function() {
3901
 
 
3902
 
    var wrappers = {},
3903
 
        lazyInitCssProp = PIE.CSS_PREFIX + 'lazy-init',
3904
 
        pollCssProp = PIE.CSS_PREFIX + 'poll',
3905
 
        hoverClass = PIE.CLASS_PREFIX + 'hover',
3906
 
        activeClass = PIE.CLASS_PREFIX + 'active',
3907
 
        focusClass = PIE.CLASS_PREFIX + 'focus',
3908
 
        firstChildClass = PIE.CLASS_PREFIX + 'first-child',
3909
 
        ignorePropertyNames = { 'background':1, 'bgColor':1, 'display': 1 },
3910
 
        classNameRegExes = {},
3911
 
        dummyArray = [];
3912
 
 
3913
 
 
3914
 
    function addClass( el, className ) {
3915
 
        el.className += ' ' + className;
3916
 
    }
3917
 
 
3918
 
    function removeClass( el, className ) {
3919
 
        var re = classNameRegExes[ className ] ||
3920
 
            ( classNameRegExes[ className ] = new RegExp( '\\b' + className + '\\b', 'g' ) );
3921
 
        el.className = el.className.replace( re, '' );
3922
 
    }
3923
 
 
3924
 
    function delayAddClass( el, className /*, className2*/ ) {
3925
 
        var classes = dummyArray.slice.call( arguments, 1 ),
3926
 
            i = classes.length;
3927
 
        setTimeout( function() {
3928
 
            while( i-- ) {
3929
 
                addClass( el, classes[ i ] );
3930
 
            }
3931
 
        }, 0 );
3932
 
    }
3933
 
 
3934
 
    function delayRemoveClass( el, className /*, className2*/ ) {
3935
 
        var classes = dummyArray.slice.call( arguments, 1 ),
3936
 
            i = classes.length;
3937
 
        setTimeout( function() {
3938
 
            while( i-- ) {
3939
 
                removeClass( el, classes[ i ] );
3940
 
            }
3941
 
        }, 0 );
3942
 
    }
3943
 
 
3944
 
 
3945
 
 
3946
 
    function Element( el ) {
3947
 
        var renderers,
3948
 
            rootRenderer,
3949
 
            boundsInfo = new PIE.BoundsInfo( el ),
3950
 
            styleInfos,
3951
 
            styleInfosArr,
3952
 
            initializing,
3953
 
            initialized,
3954
 
            eventsAttached,
3955
 
            eventListeners = [],
3956
 
            delayed,
3957
 
            destroyed,
3958
 
            poll;
3959
 
 
3960
 
        /**
3961
 
         * Initialize PIE for this element.
3962
 
         */
3963
 
        function init() {
3964
 
            if( !initialized ) {
3965
 
                var docEl,
3966
 
                    bounds,
3967
 
                    ieDocMode = PIE.ieDocMode,
3968
 
                    cs = el.currentStyle,
3969
 
                    lazy = cs.getAttribute( lazyInitCssProp ) === 'true',
3970
 
                    childRenderers;
3971
 
 
3972
 
                // Polling for size/position changes: default to on in IE8, off otherwise, overridable by -pie-poll
3973
 
                poll = cs.getAttribute( pollCssProp );
3974
 
                poll = ieDocMode > 7 ? poll !== 'false' : poll === 'true';
3975
 
 
3976
 
                // Force layout so move/resize events will fire. Set this as soon as possible to avoid layout changes
3977
 
                // after load, but make sure it only gets called the first time through to avoid recursive calls to init().
3978
 
                if( !initializing ) {
3979
 
                    initializing = 1;
3980
 
                    el.runtimeStyle.zoom = 1;
3981
 
                    initFirstChildPseudoClass();
3982
 
                }
3983
 
 
3984
 
                boundsInfo.lock();
3985
 
 
3986
 
                // If the -pie-lazy-init:true flag is set, check if the element is outside the viewport and if so, delay initialization
3987
 
                if( lazy && ( bounds = boundsInfo.getBounds() ) && ( docEl = doc.documentElement || doc.body ) &&
3988
 
                        ( bounds.y > docEl.clientHeight || bounds.x > docEl.clientWidth || bounds.y + bounds.h < 0 || bounds.x + bounds.w < 0 ) ) {
3989
 
                    if( !delayed ) {
3990
 
                        delayed = 1;
3991
 
                        PIE.OnScroll.observe( init );
3992
 
                    }
3993
 
                } else {
3994
 
                    initialized = 1;
3995
 
                    delayed = initializing = 0;
3996
 
                    PIE.OnScroll.unobserve( init );
3997
 
 
3998
 
                    // Create the style infos and renderers
3999
 
                    if ( ieDocMode === 9 ) {
4000
 
                        styleInfos = {
4001
 
                            backgroundInfo: new PIE.BackgroundStyleInfo( el ),
4002
 
                            borderImageInfo: new PIE.BorderImageStyleInfo( el ),
4003
 
                            borderInfo: new PIE.BorderStyleInfo( el )
4004
 
                        };
4005
 
                        styleInfosArr = [
4006
 
                            styleInfos.backgroundInfo,
4007
 
                            styleInfos.borderImageInfo
4008
 
                        ];
4009
 
                        rootRenderer = new PIE.IE9RootRenderer( el, boundsInfo, styleInfos );
4010
 
                        childRenderers = [
4011
 
                            new PIE.IE9BackgroundRenderer( el, boundsInfo, styleInfos, rootRenderer ),
4012
 
                            new PIE.IE9BorderImageRenderer( el, boundsInfo, styleInfos, rootRenderer )
4013
 
                        ];
4014
 
                    } else {
4015
 
 
4016
 
                        styleInfos = {
4017
 
                            backgroundInfo: new PIE.BackgroundStyleInfo( el ),
4018
 
                            borderInfo: new PIE.BorderStyleInfo( el ),
4019
 
                            borderImageInfo: new PIE.BorderImageStyleInfo( el ),
4020
 
                            borderRadiusInfo: new PIE.BorderRadiusStyleInfo( el ),
4021
 
                            boxShadowInfo: new PIE.BoxShadowStyleInfo( el ),
4022
 
                            visibilityInfo: new PIE.VisibilityStyleInfo( el )
4023
 
                        };
4024
 
                        styleInfosArr = [
4025
 
                            styleInfos.backgroundInfo,
4026
 
                            styleInfos.borderInfo,
4027
 
                            styleInfos.borderImageInfo,
4028
 
                            styleInfos.borderRadiusInfo,
4029
 
                            styleInfos.boxShadowInfo,
4030
 
                            styleInfos.visibilityInfo
4031
 
                        ];
4032
 
                        rootRenderer = new PIE.RootRenderer( el, boundsInfo, styleInfos );
4033
 
                        childRenderers = [
4034
 
                            new PIE.BoxShadowOutsetRenderer( el, boundsInfo, styleInfos, rootRenderer ),
4035
 
                            new PIE.BackgroundRenderer( el, boundsInfo, styleInfos, rootRenderer ),
4036
 
                            //new PIE.BoxShadowInsetRenderer( el, boundsInfo, styleInfos, rootRenderer ),
4037
 
                            new PIE.BorderRenderer( el, boundsInfo, styleInfos, rootRenderer ),
4038
 
                            new PIE.BorderImageRenderer( el, boundsInfo, styleInfos, rootRenderer )
4039
 
                        ];
4040
 
                        if( el.tagName === 'IMG' ) {
4041
 
                            childRenderers.push( new PIE.ImgRenderer( el, boundsInfo, styleInfos, rootRenderer ) );
4042
 
                        }
4043
 
                        rootRenderer.childRenderers = childRenderers; // circular reference, can't pass in constructor; TODO is there a cleaner way?
4044
 
                    }
4045
 
                    renderers = [ rootRenderer ].concat( childRenderers );
4046
 
 
4047
 
                    // Add property change listeners to ancestors if requested
4048
 
                    initAncestorEventListeners();
4049
 
 
4050
 
                    // Add to list of polled elements in IE8
4051
 
                    if( poll ) {
4052
 
                        PIE.Heartbeat.observe( update );
4053
 
                        PIE.Heartbeat.run();
4054
 
                    }
4055
 
 
4056
 
                    // Trigger rendering
4057
 
                    update( 1 );
4058
 
                }
4059
 
 
4060
 
                if( !eventsAttached ) {
4061
 
                    eventsAttached = 1;
4062
 
                    if( ieDocMode < 9 ) {
4063
 
                        addListener( el, 'onmove', handleMoveOrResize );
4064
 
                    }
4065
 
                    addListener( el, 'onresize', handleMoveOrResize );
4066
 
                    addListener( el, 'onpropertychange', propChanged );
4067
 
                    addListener( el, 'onmouseenter', mouseEntered );
4068
 
                    addListener( el, 'onmouseleave', mouseLeft );
4069
 
                    addListener( el, 'onmousedown', mousePressed );
4070
 
                    if( el.tagName in PIE.focusableElements ) {
4071
 
                        addListener( el, 'onfocus', focused );
4072
 
                        addListener( el, 'onblur', blurred );
4073
 
                    }
4074
 
                    PIE.OnResize.observe( handleMoveOrResize );
4075
 
 
4076
 
                    PIE.OnUnload.observe( removeEventListeners );
4077
 
                }
4078
 
 
4079
 
                boundsInfo.unlock();
4080
 
            }
4081
 
        }
4082
 
 
4083
 
 
4084
 
 
4085
 
 
4086
 
        /**
4087
 
         * Event handler for onmove and onresize events. Invokes update() only if the element's
4088
 
         * bounds have previously been calculated, to prevent multiple runs during page load when
4089
 
         * the element has no initial CSS3 properties.
4090
 
         */
4091
 
        function handleMoveOrResize() {
4092
 
            if( boundsInfo && boundsInfo.hasBeenQueried() ) {
4093
 
                update();
4094
 
            }
4095
 
        }
4096
 
 
4097
 
 
4098
 
        /**
4099
 
         * Update position and/or size as necessary. Both move and resize events call
4100
 
         * this rather than the updatePos/Size functions because sometimes, particularly
4101
 
         * during page load, one will fire but the other won't.
4102
 
         */
4103
 
        function update( force ) {
4104
 
            if( !destroyed ) {
4105
 
                if( initialized ) {
4106
 
                    var i, len = renderers.length;
4107
 
 
4108
 
                    lockAll();
4109
 
                    for( i = 0; i < len; i++ ) {
4110
 
                        renderers[i].prepareUpdate();
4111
 
                    }
4112
 
                    if( force || boundsInfo.positionChanged() ) {
4113
 
                        /* TODO just using getBoundingClientRect (used internally by BoundsInfo) for detecting
4114
 
                           position changes may not always be accurate; it's possible that
4115
 
                           an element will actually move relative to its positioning parent, but its position
4116
 
                           relative to the viewport will stay the same. Need to come up with a better way to
4117
 
                           track movement. The most accurate would be the same logic used in RootRenderer.updatePos()
4118
 
                           but that is a more expensive operation since it does some DOM walking, and we want this
4119
 
                           check to be as fast as possible. */
4120
 
                        for( i = 0; i < len; i++ ) {
4121
 
                            renderers[i].updatePos();
4122
 
                        }
4123
 
                    }
4124
 
                    if( force || boundsInfo.sizeChanged() ) {
4125
 
                        for( i = 0; i < len; i++ ) {
4126
 
                            renderers[i].updateSize();
4127
 
                        }
4128
 
                    }
4129
 
                    rootRenderer.finishUpdate();
4130
 
                    unlockAll();
4131
 
                }
4132
 
                else if( !initializing ) {
4133
 
                    init();
4134
 
                }
4135
 
            }
4136
 
        }
4137
 
 
4138
 
        /**
4139
 
         * Handle property changes to trigger update when appropriate.
4140
 
         */
4141
 
        function propChanged() {
4142
 
            var i, len = renderers.length,
4143
 
                renderer,
4144
 
                e = event;
4145
 
 
4146
 
            // Some elements like <table> fire onpropertychange events for old-school background properties
4147
 
            // ('background', 'bgColor') when runtimeStyle background properties are changed, which
4148
 
            // results in an infinite loop; therefore we filter out those property names. Also, 'display'
4149
 
            // is ignored because size calculations don't work correctly immediately when its onpropertychange
4150
 
            // event fires, and because it will trigger an onresize event anyway.
4151
 
            if( !destroyed && !( e && e.propertyName in ignorePropertyNames ) ) {
4152
 
                if( initialized ) {
4153
 
                    lockAll();
4154
 
                    for( i = 0; i < len; i++ ) {
4155
 
                        renderers[i].prepareUpdate();
4156
 
                    }
4157
 
                    for( i = 0; i < len; i++ ) {
4158
 
                        renderer = renderers[i];
4159
 
                        // Make sure position is synced if the element hasn't already been rendered.
4160
 
                        // TODO this feels sloppy - look into merging propChanged and update functions
4161
 
                        if( !renderer.isPositioned ) {
4162
 
                            renderer.updatePos();
4163
 
                        }
4164
 
                        if( renderer.needsUpdate() ) {
4165
 
                            renderer.updateProps();
4166
 
                        }
4167
 
                    }
4168
 
                    rootRenderer.finishUpdate();
4169
 
                    unlockAll();
4170
 
                }
4171
 
                else if( !initializing ) {
4172
 
                    init();
4173
 
                }
4174
 
            }
4175
 
        }
4176
 
 
4177
 
 
4178
 
        /**
4179
 
         * Handle mouseenter events. Adds a custom class to the element to allow IE6 to add
4180
 
         * hover styles to non-link elements, and to trigger a propertychange update.
4181
 
         */
4182
 
        function mouseEntered() {
4183
 
            //must delay this because the mouseenter event fires before the :hover styles are added.
4184
 
            delayAddClass( el, hoverClass );
4185
 
        }
4186
 
 
4187
 
        /**
4188
 
         * Handle mouseleave events
4189
 
         */
4190
 
        function mouseLeft() {
4191
 
            //must delay this because the mouseleave event fires before the :hover styles are removed.
4192
 
            delayRemoveClass( el, hoverClass, activeClass );
4193
 
        }
4194
 
 
4195
 
        /**
4196
 
         * Handle mousedown events. Adds a custom class to the element to allow IE6 to add
4197
 
         * active styles to non-link elements, and to trigger a propertychange update.
4198
 
         */
4199
 
        function mousePressed() {
4200
 
            //must delay this because the mousedown event fires before the :active styles are added.
4201
 
            delayAddClass( el, activeClass );
4202
 
 
4203
 
            // listen for mouseups on the document; can't just be on the element because the user might
4204
 
            // have dragged out of the element while the mouse button was held down
4205
 
            PIE.OnMouseup.observe( mouseReleased );
4206
 
        }
4207
 
 
4208
 
        /**
4209
 
         * Handle mouseup events
4210
 
         */
4211
 
        function mouseReleased() {
4212
 
            //must delay this because the mouseup event fires before the :active styles are removed.
4213
 
            delayRemoveClass( el, activeClass );
4214
 
 
4215
 
            PIE.OnMouseup.unobserve( mouseReleased );
4216
 
        }
4217
 
 
4218
 
        /**
4219
 
         * Handle focus events. Adds a custom class to the element to trigger a propertychange update.
4220
 
         */
4221
 
        function focused() {
4222
 
            //must delay this because the focus event fires before the :focus styles are added.
4223
 
            delayAddClass( el, focusClass );
4224
 
        }
4225
 
 
4226
 
        /**
4227
 
         * Handle blur events
4228
 
         */
4229
 
        function blurred() {
4230
 
            //must delay this because the blur event fires before the :focus styles are removed.
4231
 
            delayRemoveClass( el, focusClass );
4232
 
        }
4233
 
 
4234
 
 
4235
 
        /**
4236
 
         * Handle property changes on ancestors of the element; see initAncestorEventListeners()
4237
 
         * which adds these listeners as requested with the -pie-watch-ancestors CSS property.
4238
 
         */
4239
 
        function ancestorPropChanged() {
4240
 
            var name = event.propertyName;
4241
 
            if( name === 'className' || name === 'id' ) {
4242
 
                propChanged();
4243
 
            }
4244
 
        }
4245
 
 
4246
 
        function lockAll() {
4247
 
            boundsInfo.lock();
4248
 
            for( var i = styleInfosArr.length; i--; ) {
4249
 
                styleInfosArr[i].lock();
4250
 
            }
4251
 
        }
4252
 
 
4253
 
        function unlockAll() {
4254
 
            for( var i = styleInfosArr.length; i--; ) {
4255
 
                styleInfosArr[i].unlock();
4256
 
            }
4257
 
            boundsInfo.unlock();
4258
 
        }
4259
 
 
4260
 
 
4261
 
        function addListener( targetEl, type, handler ) {
4262
 
            targetEl.attachEvent( type, handler );
4263
 
            eventListeners.push( [ targetEl, type, handler ] );
4264
 
        }
4265
 
 
4266
 
        /**
4267
 
         * Remove all event listeners from the element and any monitored ancestors.
4268
 
         */
4269
 
        function removeEventListeners() {
4270
 
            if (eventsAttached) {
4271
 
                var i = eventListeners.length,
4272
 
                    listener;
4273
 
 
4274
 
                while( i-- ) {
4275
 
                    listener = eventListeners[ i ];
4276
 
                    listener[ 0 ].detachEvent( listener[ 1 ], listener[ 2 ] );
4277
 
                }
4278
 
 
4279
 
                PIE.OnUnload.unobserve( removeEventListeners );
4280
 
                eventsAttached = 0;
4281
 
                eventListeners = [];
4282
 
            }
4283
 
        }
4284
 
 
4285
 
 
4286
 
        /**
4287
 
         * Clean everything up when the behavior is removed from the element, or the element
4288
 
         * is manually destroyed.
4289
 
         */
4290
 
        function destroy() {
4291
 
            if( !destroyed ) {
4292
 
                var i, len;
4293
 
 
4294
 
                removeEventListeners();
4295
 
 
4296
 
                destroyed = 1;
4297
 
 
4298
 
                // destroy any active renderers
4299
 
                if( renderers ) {
4300
 
                    for( i = 0, len = renderers.length; i < len; i++ ) {
4301
 
                        renderers[i].finalized = 1;
4302
 
                        renderers[i].destroy();
4303
 
                    }
4304
 
                }
4305
 
 
4306
 
                // Remove from list of polled elements in IE8
4307
 
                if( poll ) {
4308
 
                    PIE.Heartbeat.unobserve( update );
4309
 
                }
4310
 
                // Stop onresize listening
4311
 
                PIE.OnResize.unobserve( update );
4312
 
 
4313
 
                // Kill references
4314
 
                renderers = boundsInfo = styleInfos = styleInfosArr = el = null;
4315
 
            }
4316
 
        }
4317
 
 
4318
 
 
4319
 
        /**
4320
 
         * If requested via the custom -pie-watch-ancestors CSS property, add onpropertychange and
4321
 
         * other event listeners to ancestor(s) of the element so we can pick up style changes
4322
 
         * based on CSS rules using descendant selectors.
4323
 
         */
4324
 
        function initAncestorEventListeners() {
4325
 
            var watch = el.currentStyle.getAttribute( PIE.CSS_PREFIX + 'watch-ancestors' ),
4326
 
                i, a;
4327
 
            if( watch ) {
4328
 
                watch = parseInt( watch, 10 );
4329
 
                i = 0;
4330
 
                a = el.parentNode;
4331
 
                while( a && ( watch === 'NaN' || i++ < watch ) ) {
4332
 
                    addListener( a, 'onpropertychange', ancestorPropChanged );
4333
 
                    addListener( a, 'onmouseenter', mouseEntered );
4334
 
                    addListener( a, 'onmouseleave', mouseLeft );
4335
 
                    addListener( a, 'onmousedown', mousePressed );
4336
 
                    if( a.tagName in PIE.focusableElements ) {
4337
 
                        addListener( a, 'onfocus', focused );
4338
 
                        addListener( a, 'onblur', blurred );
4339
 
                    }
4340
 
                    a = a.parentNode;
4341
 
                }
4342
 
            }
4343
 
        }
4344
 
 
4345
 
 
4346
 
        /**
4347
 
         * If the target element is a first child, add a pie_first-child class to it. This allows using
4348
 
         * the added class as a workaround for the fact that PIE's rendering element breaks the :first-child
4349
 
         * pseudo-class selector.
4350
 
         */
4351
 
        function initFirstChildPseudoClass() {
4352
 
            var tmpEl = el,
4353
 
                isFirst = 1;
4354
 
            while( tmpEl = tmpEl.previousSibling ) {
4355
 
                if( tmpEl.nodeType === 1 ) {
4356
 
                    isFirst = 0;
4357
 
                    break;
4358
 
                }
4359
 
            }
4360
 
            if( isFirst ) {
4361
 
                addClass( el, firstChildClass );
4362
 
            }
4363
 
        }
4364
 
 
4365
 
 
4366
 
        // These methods are all already bound to this instance so there's no need to wrap them
4367
 
        // in a closure to maintain the 'this' scope object when calling them.
4368
 
        this.init = init;
4369
 
        this.update = update;
4370
 
        this.destroy = destroy;
4371
 
        this.el = el;
4372
 
    }
4373
 
 
4374
 
    Element.getInstance = function( el ) {
4375
 
        var id = PIE.Util.getUID( el );
4376
 
        return wrappers[ id ] || ( wrappers[ id ] = new Element( el ) );
4377
 
    };
4378
 
 
4379
 
    Element.destroy = function( el ) {
4380
 
        var id = PIE.Util.getUID( el ),
4381
 
            wrapper = wrappers[ id ];
4382
 
        if( wrapper ) {
4383
 
            wrapper.destroy();
4384
 
            delete wrappers[ id ];
4385
 
        }
4386
 
    };
4387
 
 
4388
 
    Element.destroyAll = function() {
4389
 
        var els = [], wrapper;
4390
 
        if( wrappers ) {
4391
 
            for( var w in wrappers ) {
4392
 
                if( wrappers.hasOwnProperty( w ) ) {
4393
 
                    wrapper = wrappers[ w ];
4394
 
                    els.push( wrapper.el );
4395
 
                    wrapper.destroy();
4396
 
                }
4397
 
            }
4398
 
            wrappers = {};
4399
 
        }
4400
 
        return els;
4401
 
    };
4402
 
 
4403
 
    return Element;
4404
 
})();
4405
 
 
4406
 
/*
4407
 
 * This file exposes the public API for invoking PIE.
4408
 
 */
4409
 
 
4410
 
 
4411
 
/**
4412
 
 * @property supportsVML
4413
 
 * True if the current IE browser environment has a functioning VML engine. Should be true
4414
 
 * in most IEs, but in rare cases may be false. If false, PIE will exit immediately when
4415
 
 * attached to an element; this property may be used for debugging or by external scripts
4416
 
 * to perform some special action when VML support is absent.
4417
 
 * @type {boolean}
4418
 
 */
4419
 
PIE[ 'supportsVML' ] = PIE.supportsVML;
4420
 
 
4421
 
 
4422
 
/**
4423
 
 * Programatically attach PIE to a single element.
4424
 
 * @param {Element} el
4425
 
 */
4426
 
PIE[ 'attach' ] = function( el ) {
4427
 
    if (PIE.ieDocMode < 10 && PIE.supportsVML) {
4428
 
        PIE.Element.getInstance( el ).init();
4429
 
    }
4430
 
};
4431
 
 
4432
 
 
4433
 
/**
4434
 
 * Programatically detach PIE from a single element.
4435
 
 * @param {Element} el
4436
 
 */
4437
 
PIE[ 'detach' ] = function( el ) {
4438
 
    PIE.Element.destroy( el );
4439
 
};
4440
 
 
4441
 
 
4442
 
} // if( !PIE )
4443
 
})();
 
 
b'\\ No newline at end of file'