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