1/* 2PIE: CSS3 rendering for IE 3Version 1.0beta5 4http://css3pie.com 5Dual-licensed for use under the Apache License Version 2.0 or the General Public License (GPL) Version 2. 6*/ 7(function(){ 8var doc = document;var PIE = window['PIE']; 9 10if( !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 */ 190PIE.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 */ 340PIE.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}; 351PIE.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 388PIE.Heartbeat = new PIE.Observable(); 389PIE.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 */ 422PIE.OnResize = new PIE.Observable(); 423 424PIE.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 */ 467PIE.OnMouseup = new PIE.Observable(); 468 469PIE.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 */ 477PIE.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 */ 621PIE.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 */ 726PIE.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 */ 777PIE.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 */ 813PIE.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 */ 938PIE.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 */ 1205PIE.BoundsInfo = function( el ) { 1206 this.targetElement = el; 1207}; 1208PIE.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 1262function 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 1275PIE.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 */ 1358PIE.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 */ 1735PIE.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 1838PIE.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 1894var zero = PIE.getLength( '0' ), 1895 zeros = { 'tl': zero, 'tr': zero, 'br': zero, 'bl': zero }; 1896PIE.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 */ 1903PIE.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 */ 2045PIE.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 */ 2112PIE.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} ); 2145PIE.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 */ 2514PIE.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 */ 2627PIE.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 */ 2912PIE.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 */ 3167PIE.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 */ 3327PIE.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 */ 3464PIE.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 */ 3537PIE.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 */ 3576PIE.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 */ 3729PIE.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 3900PIE.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 */ 4419PIE[ 'supportsVML' ] = PIE.supportsVML; 4420 4421 4422/** 4423 * Programatically attach PIE to a single element. 4424 * @param {Element} el 4425 */ 4426PIE[ '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 */ 4437PIE[ 'detach' ] = function( el ) { 4438 PIE.Element.destroy( el ); 4439}; 4440 4441 4442} // if( !PIE ) 4443})();