25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 
 
 

3608 satır
128 KiB

  1. <!--
  2. PIE: CSS3 rendering for IE
  3. Version 1.0beta3
  4. http://css3pie.com
  5. Dual-licensed for use under the Apache License Version 2.0 or the General Public License (GPL) Version 2.
  6. -->
  7. <PUBLIC:COMPONENT lightWeight="true">
  8. <PUBLIC:ATTACH EVENT="oncontentready" FOR="element" ONEVENT="init()" />
  9. <PUBLIC:ATTACH EVENT="ondocumentready" FOR="element" ONEVENT="init()" />
  10. <PUBLIC:ATTACH EVENT="ondetach" FOR="element" ONEVENT="cleanup()" />
  11. <script type="text/javascript">
  12. var doc = element.document;var PIE = window['PIE'];
  13. if( !PIE ) {
  14. PIE = window['PIE'] = {
  15. CSS_PREFIX: '-pie-',
  16. STYLE_PREFIX: 'Pie',
  17. CLASS_PREFIX: 'pie_',
  18. tableCellTags: {
  19. 'TD': 1,
  20. 'TH': 1
  21. }
  22. };
  23. // Force the background cache to be used. No reason it shouldn't be.
  24. try {
  25. doc.execCommand( 'BackgroundImageCache', false, true );
  26. } catch(e) {}
  27. /*
  28. * IE version detection approach by James Padolsey, with modifications -- from
  29. * http://james.padolsey.com/javascript/detect-ie-in-js-using-conditional-comments/
  30. */
  31. PIE.ieVersion = function(){
  32. var v = 4,
  33. div = doc.createElement('div'),
  34. all = div.getElementsByTagName('i');
  35. while (
  36. div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
  37. all[0]
  38. ) {}
  39. return v;
  40. }();
  41. // Detect IE6
  42. if( PIE.ieVersion === 6 ) {
  43. // IE6 can't access properties with leading dash, but can without it.
  44. PIE.CSS_PREFIX = PIE.CSS_PREFIX.replace( /^-/, '' );
  45. }
  46. // Detect IE8
  47. PIE.ie8DocMode = PIE.ieVersion === 8 && doc.documentMode;
  48. /**
  49. * Utility functions
  50. */
  51. PIE.Util = {
  52. /**
  53. * To create a VML element, it must be created by a Document which has the VML
  54. * namespace set. Unfortunately, if you try to add the namespace programatically
  55. * into the main document, you will get an "Unspecified error" when trying to
  56. * access document.namespaces before the document is finished loading. To get
  57. * around this, we create a DocumentFragment, which in IE land is apparently a
  58. * full-fledged Document. It allows adding namespaces immediately, so we add the
  59. * namespace there and then have it create the VML element.
  60. * @param {string} tag The tag name for the VML element
  61. * @return {Element} The new VML element
  62. */
  63. createVmlElement: function( tag ) {
  64. var vmlPrefix = 'css3vml',
  65. vmlDoc = PIE._vmlCreatorDoc;
  66. if( !vmlDoc ) {
  67. vmlDoc = PIE._vmlCreatorDoc = doc.createDocumentFragment();
  68. vmlDoc.namespaces.add( vmlPrefix, 'urn:schemas-microsoft-com:vml' );
  69. }
  70. return vmlDoc.createElement( vmlPrefix + ':' + tag );
  71. },
  72. /**
  73. * Generate and return a unique ID for a given object. The generated ID is stored
  74. * as a property of the object for future reuse.
  75. * @param {Object} obj
  76. */
  77. getUID: function( obj ) {
  78. return obj && obj[ '_pieId' ] || ( obj[ '_pieId' ] = +new Date() + Math.random() );
  79. },
  80. /**
  81. * Simple utility for merging objects
  82. * @param {Object} obj1 The main object into which all others will be merged
  83. * @param {...Object} var_args Other objects which will be merged into the first, in order
  84. */
  85. merge: function( obj1 ) {
  86. var i, len, p, objN, args = arguments;
  87. for( i = 1, len = args.length; i < len; i++ ) {
  88. objN = args[i];
  89. for( p in objN ) {
  90. if( objN.hasOwnProperty( p ) ) {
  91. obj1[ p ] = objN[ p ];
  92. }
  93. }
  94. }
  95. return obj1;
  96. },
  97. /**
  98. * Execute a callback function, passing it the dimensions of a given image once
  99. * they are known.
  100. * @param {string} src The source URL of the image
  101. * @param {function({w:number, h:number})} func The callback function to be called once the image dimensions are known
  102. * @param {Object} ctx A context object which will be used as the 'this' value within the executed callback function
  103. */
  104. withImageSize: function( src, func, ctx ) {
  105. var sizes = PIE._imgSizes || ( PIE._imgSizes = {} ),
  106. size = sizes[ src ], img;
  107. if( size ) {
  108. func.call( ctx, size );
  109. } else {
  110. img = new Image();
  111. img.onload = function() {
  112. size = sizes[ src ] = { w: img.width, h: img.height };
  113. func.call( ctx, size );
  114. img.onload = null;
  115. };
  116. img.src = src;
  117. }
  118. }
  119. };/**
  120. *
  121. */
  122. PIE.Observable = function() {
  123. /**
  124. * List of registered observer functions
  125. */
  126. this.observers = [];
  127. /**
  128. * Hash of function ids to their position in the observers list, for fast lookup
  129. */
  130. this.indexes = {};
  131. };
  132. PIE.Observable.prototype = {
  133. observe: function( fn ) {
  134. var id = PIE.Util.getUID( fn ),
  135. indexes = this.indexes,
  136. observers = this.observers;
  137. if( !( id in indexes ) ) {
  138. indexes[ id ] = observers.length;
  139. observers.push( fn );
  140. }
  141. },
  142. unobserve: function( fn ) {
  143. var id = PIE.Util.getUID( fn ),
  144. indexes = this.indexes;
  145. if( id && id in indexes ) {
  146. delete this.observers[ indexes[ id ] ];
  147. delete indexes[ id ];
  148. }
  149. },
  150. fire: function() {
  151. var o = this.observers,
  152. i = o.length;
  153. while( i-- ) {
  154. o[ i ] && o[ i ]();
  155. }
  156. }
  157. };/*
  158. * Set up polling for IE8 - this is a brute-force workaround for syncing issues caused by IE8 not
  159. * always firing the onmove and onresize events when elements are moved or resized. We check a few
  160. * times every second to make sure the elements have the correct position and size.
  161. */
  162. if( PIE.ie8DocMode === 8 ) {
  163. PIE.Heartbeat = new PIE.Observable();
  164. setInterval( function() { PIE.Heartbeat.fire() }, 250 );
  165. }
  166. /**
  167. * Create an observable listener for the onbeforeunload event
  168. */
  169. PIE.OnBeforeUnload = new PIE.Observable();
  170. window.attachEvent( 'onbeforeunload', function() { PIE.OnBeforeUnload.fire(); } );
  171. /**
  172. * Attach an event which automatically gets detached onbeforeunload
  173. */
  174. PIE.OnBeforeUnload.attachManagedEvent = function( target, name, handler ) {
  175. target.attachEvent( name, handler );
  176. this.observe( function() {
  177. target.detachEvent( name, handler );
  178. } );
  179. };/**
  180. * Create a single observable listener for window resize events.
  181. */
  182. (function() {
  183. PIE.OnResize = new PIE.Observable();
  184. function resized() {
  185. PIE.OnResize.fire();
  186. }
  187. PIE.OnBeforeUnload.attachManagedEvent( window, 'onresize', resized );
  188. })();
  189. /**
  190. * Create a single observable listener for scroll events. Used for lazy loading based
  191. * on the viewport, and for fixed position backgrounds.
  192. */
  193. (function() {
  194. PIE.OnScroll = new PIE.Observable();
  195. function scrolled() {
  196. PIE.OnScroll.fire();
  197. }
  198. PIE.OnBeforeUnload.attachManagedEvent( window, 'onscroll', scrolled );
  199. PIE.OnResize.observe( scrolled );
  200. })();
  201. /**
  202. * Listen for printing events, destroy all active PIE instances when printing, and
  203. * restore them afterward.
  204. */
  205. (function() {
  206. var elements;
  207. function beforePrint() {
  208. elements = PIE.Element.destroyAll();
  209. }
  210. function afterPrint() {
  211. if( elements ) {
  212. for( var i = 0, len = elements.length; i < len; i++ ) {
  213. PIE[ 'attach' ]( elements[i] );
  214. }
  215. elements = 0;
  216. }
  217. }
  218. PIE.OnBeforeUnload.attachManagedEvent( window, 'onbeforeprint', beforePrint );
  219. PIE.OnBeforeUnload.attachManagedEvent( window, 'onafterprint', afterPrint );
  220. })();/**
  221. * Wrapper for length and percentage style values
  222. * @constructor
  223. * @param {string} val The CSS string representing the length. It is assumed that this will already have
  224. * been validated as a valid length or percentage syntax.
  225. */
  226. PIE.Length = (function() {
  227. var lengthCalcEl = doc.createElement( 'length-calc' ),
  228. s = lengthCalcEl.style,
  229. numCache = {},
  230. unitCache = {};
  231. s.position = 'absolute';
  232. s.top = s.left = -9999;
  233. function Length( val ) {
  234. this.val = val;
  235. }
  236. Length.prototype = {
  237. /**
  238. * Regular expression for matching the length unit
  239. * @private
  240. */
  241. unitRE: /(px|em|ex|mm|cm|in|pt|pc|%)$/,
  242. /**
  243. * Get the numeric value of the length
  244. * @return {number} The value
  245. */
  246. getNumber: function() {
  247. var num = numCache[ this.val ],
  248. UNDEF;
  249. if( num === UNDEF ) {
  250. num = numCache[ this.val ] = parseFloat( this.val );
  251. }
  252. return num;
  253. },
  254. /**
  255. * Get the unit of the length
  256. * @return {string} The unit
  257. */
  258. getUnit: function() {
  259. var unit = unitCache[ this.val ], m;
  260. if( !unit ) {
  261. m = this.val.match( this.unitRE );
  262. unit = unitCache[ this.val ] = ( m && m[0] ) || 'px';
  263. }
  264. return unit;
  265. },
  266. /**
  267. * Determine whether this is a percentage length value
  268. * @return {boolean}
  269. */
  270. isPercentage: function() {
  271. return this.getUnit() === '%';
  272. },
  273. /**
  274. * Resolve this length into a number of pixels.
  275. * @param {Element} el - the context element, used to resolve font-relative values
  276. * @param {(function():number|number)=} pct100 - the number of pixels that equal a 100% percentage. This can be either a number or a
  277. * function which will be called to return the number.
  278. */
  279. pixels: function( el, pct100 ) {
  280. var num = this.getNumber(),
  281. unit = this.getUnit();
  282. switch( unit ) {
  283. case "px":
  284. return num;
  285. case "%":
  286. return num * ( typeof pct100 === 'function' ? pct100() : pct100 ) / 100;
  287. case "em":
  288. return num * this.getEmPixels( el );
  289. case "ex":
  290. return num * this.getEmPixels( el ) / 2;
  291. default:
  292. return num * Length.conversions[ unit ];
  293. }
  294. },
  295. /**
  296. * The em and ex units are relative to the font-size of the current element,
  297. * however if the font-size is set using non-pixel units then we get that value
  298. * rather than a pixel conversion. To get around this, we keep a floating element
  299. * with width:1em which we insert into the target element and then read its offsetWidth.
  300. * But if the font-size *is* specified in pixels, then we use that directly to avoid
  301. * the expensive DOM manipulation.
  302. * @param el
  303. */
  304. getEmPixels: function( el ) {
  305. var fs = el.currentStyle.fontSize,
  306. px;
  307. if( fs.indexOf( 'px' ) > 0 ) {
  308. return parseFloat( fs );
  309. } else {
  310. lengthCalcEl.style.width = '1em';
  311. el.appendChild( lengthCalcEl );
  312. px = lengthCalcEl.offsetWidth;
  313. if( lengthCalcEl.parentNode !== el ) { //not sure how this happens but it does
  314. el.removeChild( lengthCalcEl );
  315. }
  316. return px;
  317. }
  318. }
  319. };
  320. Length.conversions = (function() {
  321. var units = [ 'mm', 'cm', 'in', 'pt', 'pc' ],
  322. vals = {},
  323. parent = doc.documentElement,
  324. i = units.length, unit;
  325. parent.appendChild( lengthCalcEl );
  326. while( i-- ) {
  327. unit = units[i];
  328. lengthCalcEl.style.width = '100' + unit;
  329. vals[ unit ] = lengthCalcEl.offsetWidth / 100;
  330. }
  331. parent.removeChild( lengthCalcEl );
  332. return vals;
  333. })();
  334. Length.ZERO = new Length( '0' );
  335. return Length;
  336. })();
  337. /**
  338. * Wrapper for a CSS3 bg-position value. Takes up to 2 position keywords and 2 lengths/percentages.
  339. * @constructor
  340. * @param {Array.<PIE.Tokenizer.Token>} tokens The tokens making up the background position value.
  341. */
  342. PIE.BgPosition = (function() {
  343. var length_fifty = new PIE.Length( '50%' ),
  344. vert_idents = { 'top': 1, 'center': 1, 'bottom': 1 },
  345. horiz_idents = { 'left': 1, 'center': 1, 'right': 1 };
  346. function BgPosition( tokens ) {
  347. this.tokens = tokens;
  348. }
  349. BgPosition.prototype = {
  350. /**
  351. * Normalize the values into the form:
  352. * [ xOffsetSide, xOffsetLength, yOffsetSide, yOffsetLength ]
  353. * where: xOffsetSide is either 'left' or 'right',
  354. * yOffsetSide is either 'top' or 'bottom',
  355. * and x/yOffsetLength are both PIE.Length objects.
  356. * @return {Array}
  357. */
  358. getValues: function() {
  359. if( !this._values ) {
  360. var tokens = this.tokens,
  361. len = tokens.length,
  362. identType = PIE.Tokenizer.Type,
  363. length_zero = PIE.Length.ZERO,
  364. type_ident = identType.IDENT,
  365. type_length = identType.LENGTH,
  366. type_percent = identType.PERCENT,
  367. type, value,
  368. vals = [ 'left', length_zero, 'top', length_zero ];
  369. // If only one value, the second is assumed to be 'center'
  370. if( len === 1 ) {
  371. tokens.push( { type: type_ident, value: 'center' } );
  372. len++;
  373. }
  374. // Two values - CSS2
  375. if( len === 2 ) {
  376. // If both idents, they can appear in either order, so switch them if needed
  377. if( type_ident & ( tokens[0].type | tokens[1].type ) &&
  378. tokens[0].value in vert_idents && tokens[1].value in horiz_idents ) {
  379. tokens.push( tokens.shift() );
  380. }
  381. if( tokens[0].type & type_ident ) {
  382. if( tokens[0].value === 'center' ) {
  383. vals[1] = length_fifty;
  384. } else {
  385. vals[0] = tokens[0].value;
  386. }
  387. }
  388. else if( tokens[0].isLengthOrPercent() ) {
  389. vals[1] = new PIE.Length( tokens[0].value );
  390. }
  391. if( tokens[1].type & type_ident ) {
  392. if( tokens[1].value === 'center' ) {
  393. vals[3] = length_fifty;
  394. } else {
  395. vals[2] = tokens[1].value;
  396. }
  397. }
  398. else if( tokens[1].isLengthOrPercent() ) {
  399. vals[3] = new PIE.Length( tokens[1].value );
  400. }
  401. }
  402. // Three or four values - CSS3
  403. else {
  404. // TODO
  405. }
  406. this._values = vals;
  407. }
  408. return this._values;
  409. },
  410. /**
  411. * Find the coordinates of the background image from the upper-left corner of the background area.
  412. * Note that these coordinate values are not rounded.
  413. * @param {Element} el
  414. * @param {number} width - the width for percentages (background area width minus image width)
  415. * @param {number} height - the height for percentages (background area height minus image height)
  416. * @return {Object} { x: Number, y: Number }
  417. */
  418. coords: function( el, width, height ) {
  419. var vals = this.getValues(),
  420. pxX = vals[1].pixels( el, width ),
  421. pxY = vals[3].pixels( el, height );
  422. return {
  423. x: vals[0] === 'right' ? width - pxX : pxX,
  424. y: vals[2] === 'bottom' ? height - pxY : pxY
  425. };
  426. }
  427. };
  428. return BgPosition;
  429. })();
  430. /**
  431. * Wrapper for angle values; handles conversion to degrees from all allowed angle units
  432. * @constructor
  433. * @param {string} val The raw CSS value for the angle. It is assumed it has been pre-validated.
  434. */
  435. PIE.Angle = (function() {
  436. function Angle( val ) {
  437. this.val = val;
  438. }
  439. Angle.prototype = {
  440. unitRE: /[a-z]+$/i,
  441. /**
  442. * @return {string} The unit of the angle value
  443. */
  444. getUnit: function() {
  445. return this._unit || ( this._unit = this.val.match( this.unitRE )[0].toLowerCase() );
  446. },
  447. /**
  448. * Get the numeric value of the angle in degrees.
  449. * @return {number} The degrees value
  450. */
  451. degrees: function() {
  452. var deg = this._deg, u, n;
  453. if( deg === undefined ) {
  454. u = this.getUnit();
  455. n = parseFloat( this.val, 10 );
  456. deg = this._deg = ( u === 'deg' ? n : u === 'rad' ? n / Math.PI * 180 : u === 'grad' ? n / 400 * 360 : u === 'turn' ? n * 360 : 0 );
  457. }
  458. return deg;
  459. }
  460. };
  461. return Angle;
  462. })();/**
  463. * Abstraction for colors values. Allows detection of rgba values.
  464. * @constructor
  465. * @param {string} val The raw CSS string value for the color
  466. */
  467. PIE.Color = (function() {
  468. function Color( val ) {
  469. this.val = val;
  470. }
  471. /**
  472. * Regular expression for matching rgba colors and extracting their components
  473. * @type {RegExp}
  474. */
  475. 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*/;
  476. Color.prototype = {
  477. /**
  478. * @private
  479. */
  480. parse: function() {
  481. if( !this._color ) {
  482. var v = this.val,
  483. m = v.match( Color.rgbaRE );
  484. if( m ) {
  485. this._color = 'rgb(' + m[1] + ',' + m[2] + ',' + m[3] + ')';
  486. this._alpha = parseFloat( m[4] );
  487. } else {
  488. this._color = v;
  489. this._alpha = ( v === 'transparent' ? 0 : 1 );
  490. }
  491. }
  492. },
  493. /**
  494. * Retrieve the value of the color in a format usable by IE natively. This will be the same as
  495. * the raw input value, except for rgba values which will be converted to an rgb value.
  496. * @param {Element} el The context element, used to get 'currentColor' keyword value.
  497. * @return {string} Color value
  498. */
  499. value: function( el ) {
  500. this.parse();
  501. return this._color === 'currentColor' ? el.currentStyle.color : this._color;
  502. },
  503. /**
  504. * Retrieve the alpha value of the color. Will be 1 for all values except for rgba values
  505. * with an alpha component.
  506. * @return {number} The alpha value, from 0 to 1.
  507. */
  508. alpha: function() {
  509. this.parse();
  510. return this._alpha;
  511. }
  512. };
  513. return Color;
  514. })();/**
  515. * A tokenizer for CSS value strings.
  516. * @constructor
  517. * @param {string} css The CSS value string
  518. */
  519. PIE.Tokenizer = (function() {
  520. function Tokenizer( css ) {
  521. this.css = css;
  522. this.ch = 0;
  523. this.tokens = [];
  524. this.tokenIndex = 0;
  525. }
  526. /**
  527. * Enumeration of token type constants.
  528. * @enum {number}
  529. */
  530. var Type = Tokenizer.Type = {
  531. ANGLE: 1,
  532. CHARACTER: 2,
  533. COLOR: 4,
  534. DIMEN: 8,
  535. FUNCTION: 16,
  536. IDENT: 32,
  537. LENGTH: 64,
  538. NUMBER: 128,
  539. OPERATOR: 256,
  540. PERCENT: 512,
  541. STRING: 1024,
  542. URL: 2048
  543. };
  544. /**
  545. * A single token
  546. * @constructor
  547. * @param {number} type The type of the token - see PIE.Tokenizer.Type
  548. * @param {string} value The value of the token
  549. */
  550. Tokenizer.Token = function( type, value ) {
  551. this.type = type;
  552. this.value = value;
  553. };
  554. Tokenizer.Token.prototype = {
  555. isLength: function() {
  556. return this.type & Type.LENGTH || ( this.type & Type.NUMBER && this.value === '0' );
  557. },
  558. isLengthOrPercent: function() {
  559. return this.isLength() || this.type & Type.PERCENT;
  560. }
  561. };
  562. Tokenizer.prototype = {
  563. whitespace: /\s/,
  564. number: /^[\+\-]?(\d*\.)?\d+/,
  565. url: /^url\(\s*("([^"]*)"|'([^']*)'|([!#$%&*-~]*))\s*\)/i,
  566. ident: /^\-?[_a-z][\w-]*/i,
  567. string: /^("([^"]*)"|'([^']*)')/,
  568. operator: /^[\/,]/,
  569. hash: /^#[\w]+/,
  570. hashColor: /^#([\da-f]{6}|[\da-f]{3})/i,
  571. unitTypes: {
  572. 'px': Type.LENGTH, 'em': Type.LENGTH, 'ex': Type.LENGTH,
  573. 'mm': Type.LENGTH, 'cm': Type.LENGTH, 'in': Type.LENGTH,
  574. 'pt': Type.LENGTH, 'pc': Type.LENGTH,
  575. 'deg': Type.ANGLE, 'rad': Type.ANGLE, 'grad': Type.ANGLE
  576. },
  577. colorNames: {
  578. 'aqua':1, 'black':1, 'blue':1, 'fuchsia':1, 'gray':1, 'green':1, 'lime':1, 'maroon':1,
  579. 'navy':1, 'olive':1, 'purple':1, 'red':1, 'silver':1, 'teal':1, 'white':1, 'yellow': 1,
  580. 'currentColor': 1
  581. },
  582. colorFunctions: {
  583. 'rgb': 1, 'rgba': 1, 'hsl': 1, 'hsla': 1
  584. },
  585. /**
  586. * Advance to and return the next token in the CSS string. If the end of the CSS string has
  587. * been reached, null will be returned.
  588. * @param {boolean} forget - if true, the token will not be stored for the purposes of backtracking with prev().
  589. * @return {PIE.Tokenizer.Token}
  590. */
  591. next: function( forget ) {
  592. var css, ch, firstChar, match, type, val,
  593. me = this;
  594. function newToken( type, value ) {
  595. var tok = new Tokenizer.Token( type, value );
  596. if( !forget ) {
  597. me.tokens.push( tok );
  598. me.tokenIndex++;
  599. }
  600. return tok;
  601. }
  602. function failure() {
  603. me.tokenIndex++;
  604. return null;
  605. }
  606. // In case we previously backed up, return the stored token in the next slot
  607. if( this.tokenIndex < this.tokens.length ) {
  608. return this.tokens[ this.tokenIndex++ ];
  609. }
  610. // Move past leading whitespace characters
  611. while( this.whitespace.test( this.css.charAt( this.ch ) ) ) {
  612. this.ch++;
  613. }
  614. if( this.ch >= this.css.length ) {
  615. return failure();
  616. }
  617. ch = this.ch;
  618. css = this.css.substring( this.ch );
  619. firstChar = css.charAt( 0 );
  620. switch( firstChar ) {
  621. case '#':
  622. if( match = css.match( this.hashColor ) ) {
  623. this.ch += match[0].length;
  624. return newToken( Type.COLOR, match[0] );
  625. }
  626. break;
  627. case '"':
  628. case "'":
  629. if( match = css.match( this.string ) ) {
  630. this.ch += match[0].length;
  631. return newToken( Type.STRING, match[2] || match[3] || '' );
  632. }
  633. break;
  634. case "/":
  635. case ",":
  636. this.ch++;
  637. return newToken( Type.OPERATOR, firstChar );
  638. case 'u':
  639. if( match = css.match( this.url ) ) {
  640. this.ch += match[0].length;
  641. return newToken( Type.URL, match[2] || match[3] || match[4] || '' );
  642. }
  643. }
  644. // Numbers and values starting with numbers
  645. if( match = css.match( this.number ) ) {
  646. val = match[0];
  647. this.ch += val.length;
  648. // Check if it is followed by a unit
  649. if( css.charAt( val.length ) === '%' ) {
  650. this.ch++;
  651. return newToken( Type.PERCENT, val + '%' );
  652. }
  653. if( match = css.substring( val.length ).match( this.ident ) ) {
  654. val += match[0];
  655. this.ch += match[0].length;
  656. return newToken( this.unitTypes[ match[0].toLowerCase() ] || Type.DIMEN, val );
  657. }
  658. // Plain ol' number
  659. return newToken( Type.NUMBER, val );
  660. }
  661. // Identifiers
  662. if( match = css.match( this.ident ) ) {
  663. val = match[0];
  664. this.ch += val.length;
  665. // Named colors
  666. if( val.toLowerCase() in this.colorNames ) {
  667. return newToken( Type.COLOR, val );
  668. }
  669. // Functions
  670. if( css.charAt( val.length ) === '(' ) {
  671. this.ch++;
  672. // Color values in function format: rgb, rgba, hsl, hsla
  673. if( val.toLowerCase() in this.colorFunctions ) {
  674. function isNum( tok ) {
  675. return tok && tok.type & Type.NUMBER;
  676. }
  677. function isNumOrPct( tok ) {
  678. return tok && ( tok.type & ( Type.NUMBER | Type.PERCENT ) );
  679. }
  680. function isValue( tok, val ) {
  681. return tok && tok.value === val;
  682. }
  683. function next() {
  684. return me.next( 1 );
  685. }
  686. if( ( val.charAt(0) === 'r' ? isNumOrPct( next() ) : isNum( next() ) ) &&
  687. isValue( next(), ',' ) &&
  688. isNumOrPct( next() ) &&
  689. isValue( next(), ',' ) &&
  690. isNumOrPct( next() ) &&
  691. ( val === 'rgb' || val === 'hsa' || (
  692. isValue( next(), ',' ) &&
  693. isNum( next() )
  694. ) ) &&
  695. isValue( next(), ')' ) ) {
  696. return newToken( Type.COLOR, this.css.substring( ch, this.ch ) );
  697. }
  698. return failure();
  699. }
  700. return newToken( Type.FUNCTION, val + '(' );
  701. }
  702. // Other identifier
  703. return newToken( Type.IDENT, val );
  704. }
  705. // Standalone character
  706. this.ch++;
  707. return newToken( Type.CHARACTER, firstChar );
  708. },
  709. /**
  710. * Determine whether there is another token
  711. * @return {boolean}
  712. */
  713. hasNext: function() {
  714. var next = this.next();
  715. this.prev();
  716. return !!next;
  717. },
  718. /**
  719. * Back up and return the previous token
  720. * @return {PIE.Tokenizer.Token}
  721. */
  722. prev: function() {
  723. return this.tokens[ this.tokenIndex-- - 2 ];
  724. },
  725. /**
  726. * Retrieve all the tokens in the CSS string
  727. * @return {Array.<PIE.Tokenizer.Token>}
  728. */
  729. all: function() {
  730. while( this.next() ) {}
  731. return this.tokens;
  732. },
  733. /**
  734. * Return a list of tokens from the current position until the given function returns
  735. * true. The final token will not be included in the list.
  736. * @param {function():boolean} func - test function
  737. * @param {boolean} require - if true, then if the end of the CSS string is reached
  738. * before the test function returns true, null will be returned instead of the
  739. * tokens that have been found so far.
  740. * @return {Array.<PIE.Tokenizer.Token>}
  741. */
  742. until: function( func, require ) {
  743. var list = [], t, hit;
  744. while( t = this.next() ) {
  745. if( func( t ) ) {
  746. hit = true;
  747. this.prev();
  748. break;
  749. }
  750. list.push( t );
  751. }
  752. return require && !hit ? null : list;
  753. }
  754. };
  755. return Tokenizer;
  756. })();/**
  757. * Handles calculating, caching, and detecting changes to size and position of the element.
  758. * @constructor
  759. * @param {Element} el the target element
  760. */
  761. PIE.BoundsInfo = function( el ) {
  762. this.targetElement = el;
  763. };
  764. PIE.BoundsInfo.prototype = {
  765. _locked: 0,
  766. positionChanged: function() {
  767. var last = this._lastBounds,
  768. bounds;
  769. return !last || ( ( bounds = this.getBounds() ) && ( last.x !== bounds.x || last.y !== bounds.y ) );
  770. },
  771. sizeChanged: function() {
  772. var last = this._lastBounds,
  773. bounds;
  774. return !last || ( ( bounds = this.getBounds() ) && ( last.w !== bounds.w || last.h !== bounds.h ) );
  775. },
  776. getLiveBounds: function() {
  777. var rect = this.targetElement.getBoundingClientRect();
  778. return {
  779. x: rect.left,
  780. y: rect.top,
  781. w: rect.right - rect.left,
  782. h: rect.bottom - rect.top
  783. };
  784. },
  785. getBounds: function() {
  786. return this._locked ?
  787. ( this._lockedBounds || ( this._lockedBounds = this.getLiveBounds() ) ) :
  788. this.getLiveBounds();
  789. },
  790. hasBeenQueried: function() {
  791. return !!this._lastBounds;
  792. },
  793. lock: function() {
  794. ++this._locked;
  795. },
  796. unlock: function() {
  797. if( !--this._locked ) {
  798. if( this._lockedBounds ) this._lastBounds = this._lockedBounds;
  799. this._lockedBounds = null;
  800. }
  801. }
  802. };
  803. (function() {
  804. function cacheWhenLocked( fn ) {
  805. var uid = PIE.Util.getUID( fn );
  806. return function() {
  807. if( this._locked ) {
  808. var cache = this._lockedValues || ( this._lockedValues = {} );
  809. return ( uid in cache ) ? cache[ uid ] : ( cache[ uid ] = fn.call( this ) );
  810. } else {
  811. return fn.call( this );
  812. }
  813. }
  814. }
  815. PIE.StyleInfoBase = {
  816. _locked: 0,
  817. /**
  818. * Create a new StyleInfo class, with the standard constructor, and augmented by
  819. * the StyleInfoBase's members.
  820. * @param proto
  821. */
  822. newStyleInfo: function( proto ) {
  823. function StyleInfo( el ) {
  824. this.targetElement = el;
  825. }
  826. PIE.Util.merge( StyleInfo.prototype, PIE.StyleInfoBase, proto );
  827. StyleInfo._propsCache = {};
  828. return StyleInfo;
  829. },
  830. /**
  831. * Get an object representation of the target CSS style, caching it for each unique
  832. * CSS value string.
  833. * @return {Object}
  834. */
  835. getProps: function() {
  836. var css = this.getCss(),
  837. cache = this.constructor._propsCache;
  838. return css ? ( css in cache ? cache[ css ] : ( cache[ css ] = this.parseCss( css ) ) ) : null;
  839. },
  840. /**
  841. * Get the raw CSS value for the target style
  842. * @return {string}
  843. */
  844. getCss: cacheWhenLocked( function() {
  845. var el = this.targetElement,
  846. ctor = this.constructor,
  847. s = el.style,
  848. cs = el.currentStyle,
  849. cssProp = this.cssProperty,
  850. styleProp = this.styleProperty,
  851. prefixedCssProp = ctor._prefixedCssProp || ( ctor._prefixedCssProp = PIE.CSS_PREFIX + cssProp ),
  852. prefixedStyleProp = ctor._prefixedStyleProp || ( ctor._prefixedStyleProp = PIE.STYLE_PREFIX + styleProp.charAt(0).toUpperCase() + styleProp.substring(1) );
  853. return s[ prefixedStyleProp ] || cs.getAttribute( prefixedCssProp ) || s[ styleProp ] || cs.getAttribute( cssProp );
  854. } ),
  855. /**
  856. * Determine whether the target CSS style is active.
  857. * @return {boolean}
  858. */
  859. isActive: cacheWhenLocked( function() {
  860. return !!this.getProps();
  861. } ),
  862. /**
  863. * Determine whether the target CSS style has changed since the last time it was used.
  864. * @return {boolean}
  865. */
  866. changed: cacheWhenLocked( function() {
  867. var currentCss = this.getCss(),
  868. changed = currentCss !== this._lastCss;
  869. this._lastCss = currentCss;
  870. return changed;
  871. } ),
  872. cacheWhenLocked: cacheWhenLocked,
  873. lock: function() {
  874. ++this._locked;
  875. },
  876. unlock: function() {
  877. if( !--this._locked ) {
  878. delete this._lockedValues;
  879. }
  880. }
  881. };
  882. })();/**
  883. * Handles parsing, caching, and detecting changes to background (and -pie-background) CSS
  884. * @constructor
  885. * @param {Element} el the target element
  886. */
  887. PIE.BackgroundStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
  888. cssProperty: PIE.CSS_PREFIX + 'background',
  889. styleProperty: PIE.STYLE_PREFIX + 'Background',
  890. attachIdents: { 'scroll':1, 'fixed':1, 'local':1 },
  891. repeatIdents: { 'repeat-x':1, 'repeat-y':1, 'repeat':1, 'no-repeat':1 },
  892. originIdents: { 'padding-box':1, 'border-box':1, 'content-box':1 },
  893. clipIdents: { 'padding-box':1, 'border-box':1 },
  894. positionIdents: { 'top':1, 'right':1, 'bottom':1, 'left':1, 'center':1 },
  895. sizeIdents: { 'contain':1, 'cover':1 },
  896. /**
  897. * For background styles, we support the -pie-background property but fall back to the standard
  898. * backround* properties. The reason we have to use the prefixed version is that IE natively
  899. * parses the standard properties and if it sees something it doesn't know how to parse, for example
  900. * multiple values or gradient definitions, it will throw that away and not make it available through
  901. * currentStyle.
  902. *
  903. * Format of return object:
  904. * {
  905. * color: <PIE.Color>,
  906. * images: [
  907. * {
  908. * type: 'image',
  909. * url: 'image.png',
  910. * repeat: <'no-repeat' | 'repeat-x' | 'repeat-y' | 'repeat'>,
  911. * position: <PIE.BgPosition>,
  912. * attachment: <'scroll' | 'fixed' | 'local'>,
  913. * origin: <'border-box' | 'padding-box' | 'content-box'>,
  914. * clip: <'border-box' | 'padding-box'>,
  915. * size: <'contain' | 'cover' | { w: <'auto' | PIE.Length>, h: <'auto' | PIE.Length> }>
  916. * },
  917. * {
  918. * type: 'linear-gradient',
  919. * gradientStart: <PIE.BgPosition>,
  920. * angle: <PIE.Angle>,
  921. * stops: [
  922. * { color: <PIE.Color>, offset: <PIE.Length> },
  923. * { color: <PIE.Color>, offset: <PIE.Length> }, ...
  924. * ]
  925. * }
  926. * ]
  927. * }
  928. * @param {String} css
  929. * @override
  930. */
  931. parseCss: function( css ) {
  932. var el = this.targetElement,
  933. cs = el.currentStyle,
  934. tokenizer, token, image,
  935. tok_type = PIE.Tokenizer.Type,
  936. type_operator = tok_type.OPERATOR,
  937. type_ident = tok_type.IDENT,
  938. type_color = tok_type.COLOR,
  939. tokType, tokVal,
  940. positionIdents = this.positionIdents,
  941. gradient, stop,
  942. props = null;
  943. function isBgPosToken( token ) {
  944. return token.isLengthOrPercent() || ( token.type & type_ident && token.value in positionIdents );
  945. }
  946. function sizeToken( token ) {
  947. return ( token.isLengthOrPercent() && new PIE.Length( token.value ) ) || ( token.value === 'auto' && 'auto' );
  948. }
  949. // If the CSS3-specific -pie-background property is present, parse it
  950. if( this.getCss3() ) {
  951. tokenizer = new PIE.Tokenizer( css );
  952. props = { images: [] };
  953. image = {};
  954. while( token = tokenizer.next() ) {
  955. tokType = token.type;
  956. tokVal = token.value;
  957. if( !image.type && tokType & tok_type.FUNCTION && tokVal === 'linear-gradient(' ) {
  958. gradient = { stops: [], type: 'linear-gradient' };
  959. stop = {};
  960. while( token = tokenizer.next() ) {
  961. tokType = token.type;
  962. tokVal = token.value;
  963. // If we reached the end of the function and had at least 2 stops, flush the info
  964. if( tokType & tok_type.CHARACTER && tokVal === ')' ) {
  965. if( stop.color ) {
  966. gradient.stops.push( stop );
  967. }
  968. if( gradient.stops.length > 1 ) {
  969. PIE.Util.merge( image, gradient );
  970. }
  971. break;
  972. }
  973. // Color stop - must start with color
  974. if( tokType & type_color ) {
  975. // if we already have an angle/position, make sure that the previous token was a comma
  976. if( gradient.angle || gradient.gradientStart ) {
  977. token = tokenizer.prev();
  978. if( token.type !== type_operator ) {
  979. break; //fail
  980. }
  981. tokenizer.next();
  982. }
  983. stop = {
  984. color: new PIE.Color( tokVal )
  985. };
  986. // check for offset following color
  987. token = tokenizer.next();
  988. if( token.isLengthOrPercent() ) {
  989. stop.offset = new PIE.Length( token.value );
  990. } else {
  991. tokenizer.prev();
  992. }
  993. }
  994. // Angle - can only appear in first spot
  995. else if( tokType & tok_type.ANGLE && !gradient.angle && !stop.color && !gradient.stops.length ) {
  996. gradient.angle = new PIE.Angle( token.value );
  997. }
  998. else if( isBgPosToken( token ) && !gradient.gradientStart && !stop.color && !gradient.stops.length ) {
  999. tokenizer.prev();
  1000. gradient.gradientStart = new PIE.BgPosition(
  1001. tokenizer.until( function( t ) {
  1002. return !isBgPosToken( t );
  1003. }, false )
  1004. );
  1005. }
  1006. else if( tokType & type_operator && tokVal === ',' ) {
  1007. if( stop.color ) {
  1008. gradient.stops.push( stop );
  1009. stop = {};
  1010. }
  1011. }
  1012. else {
  1013. // Found something we didn't recognize; fail without adding image
  1014. break;
  1015. }
  1016. }
  1017. }
  1018. else if( !image.type && tokType & tok_type.URL ) {
  1019. image.url = tokVal;
  1020. image.type = 'image';
  1021. }
  1022. else if( isBgPosToken( token ) && !image.size ) {
  1023. tokenizer.prev();
  1024. image.position = new PIE.BgPosition(
  1025. tokenizer.until( function( t ) {
  1026. return !isBgPosToken( t );
  1027. }, false )
  1028. );
  1029. }
  1030. else if( tokType & type_ident ) {
  1031. if( tokVal in this.repeatIdents ) {
  1032. image.repeat = tokVal;
  1033. }
  1034. else if( tokVal in this.originIdents ) {
  1035. image.origin = tokVal;
  1036. if( tokVal in this.clipIdents ) {
  1037. image.clip = tokVal;
  1038. }
  1039. }
  1040. else if( tokVal in this.attachIdents ) {
  1041. image.attachment = tokVal;
  1042. }
  1043. }
  1044. else if( tokType & type_color && !props.color ) {
  1045. props.color = new PIE.Color( tokVal );
  1046. }
  1047. else if( tokType & type_operator ) {
  1048. // background size
  1049. if( tokVal === '/' ) {
  1050. token = tokenizer.next();
  1051. tokType = token.type;
  1052. tokVal = token.value;
  1053. if( tokType & type_ident && tokVal in this.sizeIdents ) {
  1054. image.size = tokVal;
  1055. }
  1056. else if( tokVal = sizeToken( token ) ) {
  1057. image.size = {
  1058. w: tokVal,
  1059. h: sizeToken( tokenizer.next() ) || ( tokenizer.prev() && tokVal )
  1060. };
  1061. }
  1062. }
  1063. // new layer
  1064. else if( tokVal === ',' && image.type ) {
  1065. props.images.push( image );
  1066. image = {};
  1067. }
  1068. }
  1069. else {
  1070. // Found something unrecognized; chuck everything
  1071. return null;
  1072. }
  1073. }
  1074. // leftovers
  1075. if( image.type ) {
  1076. props.images.push( image );
  1077. }
  1078. }
  1079. // Otherwise, use the standard background properties; let IE give us the values rather than parsing them
  1080. else {
  1081. this.withActualBg( function() {
  1082. var posX = cs.backgroundPositionX,
  1083. posY = cs.backgroundPositionY,
  1084. img = cs.backgroundImage,
  1085. color = cs.backgroundColor;
  1086. props = {};
  1087. if( color !== 'transparent' ) {
  1088. props.color = new PIE.Color( color )
  1089. }
  1090. if( img !== 'none' ) {
  1091. props.images = [ {
  1092. type: 'image',
  1093. url: new PIE.Tokenizer( img ).next().value,
  1094. repeat: cs.backgroundRepeat,
  1095. position: new PIE.BgPosition( new PIE.Tokenizer( posX + ' ' + posY ).all() )
  1096. } ];
  1097. }
  1098. } );
  1099. }
  1100. return ( props && ( props.color || ( props.images && props.images[0] ) ) ) ? props : null;
  1101. },
  1102. /**
  1103. * Execute a function with the actual background styles (not overridden with runtimeStyle
  1104. * properties set by the renderers) available via currentStyle.
  1105. * @param fn
  1106. */
  1107. withActualBg: function( fn ) {
  1108. var rs = this.targetElement.runtimeStyle,
  1109. rsImage = rs.backgroundImage,
  1110. rsColor = rs.backgroundColor,
  1111. ret;
  1112. if( rsImage ) rs.backgroundImage = '';
  1113. if( rsColor ) rs.backgroundColor = '';
  1114. ret = fn.call( this );
  1115. if( rsImage ) rs.backgroundImage = rsImage;
  1116. if( rsColor ) rs.backgroundColor = rsColor;
  1117. return ret;
  1118. },
  1119. getCss: PIE.StyleInfoBase.cacheWhenLocked( function() {
  1120. return this.getCss3() ||
  1121. this.withActualBg( function() {
  1122. var cs = this.targetElement.currentStyle;
  1123. return cs.backgroundColor + ' ' + cs.backgroundImage + ' ' + cs.backgroundRepeat + ' ' +
  1124. cs.backgroundPositionX + ' ' + cs.backgroundPositionY;
  1125. } );
  1126. } ),
  1127. getCss3: PIE.StyleInfoBase.cacheWhenLocked( function() {
  1128. var el = this.targetElement;
  1129. return el.style[ this.styleProperty ] || el.currentStyle.getAttribute( this.cssProperty );
  1130. } ),
  1131. /**
  1132. * Tests if style.PiePngFix or the -pie-png-fix property is set to true in IE6.
  1133. */
  1134. isPngFix: function() {
  1135. var val = 0, el;
  1136. if( PIE.ieVersion < 7 ) {
  1137. el = this.targetElement;
  1138. val = ( '' + ( el.style[ PIE.STYLE_PREFIX + 'PngFix' ] || el.currentStyle.getAttribute( PIE.CSS_PREFIX + 'png-fix' ) ) === 'true' );
  1139. }
  1140. return val;
  1141. },
  1142. /**
  1143. * The isActive logic is slightly different, because getProps() always returns an object
  1144. * even if it is just falling back to the native background properties. But we only want
  1145. * to report is as being "active" if either the -pie-background override property is present
  1146. * and parses successfully or '-pie-png-fix' is set to true in IE6.
  1147. */
  1148. isActive: PIE.StyleInfoBase.cacheWhenLocked( function() {
  1149. return (this.getCss3() || this.isPngFix()) && !!this.getProps();
  1150. } )
  1151. } );/**
  1152. * Handles parsing, caching, and detecting changes to border CSS
  1153. * @constructor
  1154. * @param {Element} el the target element
  1155. */
  1156. PIE.BorderStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
  1157. sides: [ 'Top', 'Right', 'Bottom', 'Left' ],
  1158. namedWidths: {
  1159. thin: '1px',
  1160. medium: '3px',
  1161. thick: '5px'
  1162. },
  1163. parseCss: function( css ) {
  1164. var w = {},
  1165. s = {},
  1166. c = {},
  1167. active = false,
  1168. colorsSame = true,
  1169. stylesSame = true,
  1170. widthsSame = true;
  1171. this.withActualBorder( function() {
  1172. var el = this.targetElement,
  1173. cs = el.currentStyle,
  1174. i = 0,
  1175. style, color, width, lastStyle, lastColor, lastWidth, side, ltr;
  1176. for( ; i < 4; i++ ) {
  1177. side = this.sides[ i ];
  1178. ltr = side.charAt(0).toLowerCase();
  1179. style = s[ ltr ] = cs[ 'border' + side + 'Style' ];
  1180. color = cs[ 'border' + side + 'Color' ];
  1181. width = cs[ 'border' + side + 'Width' ];
  1182. if( i > 0 ) {
  1183. if( style !== lastStyle ) { stylesSame = false; }
  1184. if( color !== lastColor ) { colorsSame = false; }
  1185. if( width !== lastWidth ) { widthsSame = false; }
  1186. }
  1187. lastStyle = style;
  1188. lastColor = color;
  1189. lastWidth = width;
  1190. c[ ltr ] = new PIE.Color( color );
  1191. width = w[ ltr ] = new PIE.Length( s[ ltr ] === 'none' ? '0' : ( this.namedWidths[ width ] || width ) );
  1192. if( width.pixels( this.targetElement ) > 0 ) {
  1193. active = true;
  1194. }
  1195. }
  1196. } );
  1197. return active ? {
  1198. widths: w,
  1199. styles: s,
  1200. colors: c,
  1201. widthsSame: widthsSame,
  1202. colorsSame: colorsSame,
  1203. stylesSame: stylesSame
  1204. } : null;
  1205. },
  1206. getCss: PIE.StyleInfoBase.cacheWhenLocked( function() {
  1207. var el = this.targetElement,
  1208. cs = el.currentStyle,
  1209. css;
  1210. // Don't redraw or hide borders for cells in border-collapse:collapse tables
  1211. if( !( el.tagName in PIE.tableCellTags && el.offsetParent.currentStyle.borderCollapse === 'collapse' ) ) {
  1212. this.withActualBorder( function() {
  1213. css = cs.borderWidth + '|' + cs.borderStyle + '|' + cs.borderColor;
  1214. } );
  1215. }
  1216. return css;
  1217. } ),
  1218. /**
  1219. * Execute a function with the actual border styles (not overridden with runtimeStyle
  1220. * properties set by the renderers) available via currentStyle.
  1221. * @param fn
  1222. */
  1223. withActualBorder: function( fn ) {
  1224. var rs = this.targetElement.runtimeStyle,
  1225. rsWidth = rs.borderWidth,
  1226. rsColor = rs.borderColor,
  1227. ret;
  1228. if( rsWidth ) rs.borderWidth = '';
  1229. if( rsColor ) rs.borderColor = '';
  1230. ret = fn.call( this );
  1231. if( rsWidth ) rs.borderWidth = rsWidth;
  1232. if( rsColor ) rs.borderColor = rsColor;
  1233. return ret;
  1234. }
  1235. } );
  1236. /**
  1237. * Handles parsing, caching, and detecting changes to border-radius CSS
  1238. * @constructor
  1239. * @param {Element} el the target element
  1240. */
  1241. (function() {
  1242. PIE.BorderRadiusStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
  1243. cssProperty: 'border-radius',
  1244. styleProperty: 'borderRadius',
  1245. parseCss: function( css ) {
  1246. var p = null, x, y,
  1247. tokenizer, token, length,
  1248. hasNonZero = false;
  1249. function newLength( v ) {
  1250. return new PIE.Length( v );
  1251. }
  1252. if( css ) {
  1253. tokenizer = new PIE.Tokenizer( css );
  1254. function collectLengths() {
  1255. var arr = [], num;
  1256. while( ( token = tokenizer.next() ) && token.isLengthOrPercent() ) {
  1257. length = newLength( token.value );
  1258. num = length.getNumber();
  1259. if( num < 0 ) {
  1260. return null;
  1261. }
  1262. if( num > 0 ) {
  1263. hasNonZero = true;
  1264. }
  1265. arr.push( length );
  1266. }
  1267. return arr.length > 0 && arr.length < 5 ? {
  1268. 'tl': arr[0],
  1269. 'tr': arr[1] || arr[0],
  1270. 'br': arr[2] || arr[0],
  1271. 'bl': arr[3] || arr[1] || arr[0]
  1272. } : null;
  1273. }
  1274. // Grab the initial sequence of lengths
  1275. if( x = collectLengths() ) {
  1276. // See if there is a slash followed by more lengths, for the y-axis radii
  1277. if( token ) {
  1278. if( token.type & PIE.Tokenizer.Type.OPERATOR && token.value === '/' ) {
  1279. y = collectLengths();
  1280. }
  1281. } else {
  1282. y = x;
  1283. }
  1284. // Treat all-zero values the same as no value
  1285. if( hasNonZero && x && y ) {
  1286. p = { x: x, y : y };
  1287. }
  1288. }
  1289. }
  1290. return p;
  1291. }
  1292. } );
  1293. var ZERO = PIE.Length.ZERO,
  1294. zeros = { 'tl': ZERO, 'tr': ZERO, 'br': ZERO, 'bl': ZERO };
  1295. PIE.BorderRadiusStyleInfo.ALL_ZERO = { x: zeros, y: zeros };
  1296. })();/**
  1297. * Handles parsing, caching, and detecting changes to border-image CSS
  1298. * @constructor
  1299. * @param {Element} el the target element
  1300. */
  1301. PIE.BorderImageStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
  1302. cssProperty: 'border-image',
  1303. styleProperty: 'borderImage',
  1304. repeatIdents: { 'stretch':1, 'round':1, 'repeat':1, 'space':1 },
  1305. parseCss: function( css ) {
  1306. var p = null, tokenizer, token, type, value,
  1307. slices, widths, outsets,
  1308. slashCount = 0, cs,
  1309. Type = PIE.Tokenizer.Type,
  1310. IDENT = Type.IDENT,
  1311. NUMBER = Type.NUMBER,
  1312. LENGTH = Type.LENGTH,
  1313. PERCENT = Type.PERCENT;
  1314. if( css ) {
  1315. tokenizer = new PIE.Tokenizer( css );
  1316. p = {};
  1317. function isSlash( token ) {
  1318. return token && ( token.type & Type.OPERATOR ) && ( token.value === '/' );
  1319. }
  1320. function isFillIdent( token ) {
  1321. return token && ( token.type & IDENT ) && ( token.value === 'fill' );
  1322. }
  1323. function collectSlicesEtc() {
  1324. slices = tokenizer.until( function( tok ) {
  1325. return !( tok.type & ( NUMBER | PERCENT ) );
  1326. } );
  1327. if( isFillIdent( tokenizer.next() ) && !p.fill ) {
  1328. p.fill = true;
  1329. } else {
  1330. tokenizer.prev();
  1331. }
  1332. if( isSlash( tokenizer.next() ) ) {
  1333. slashCount++;
  1334. widths = tokenizer.until( function( tok ) {
  1335. return !( token.type & ( NUMBER | PERCENT | LENGTH ) ) && !( ( token.type & IDENT ) && token.value === 'auto' );
  1336. } );
  1337. if( isSlash( tokenizer.next() ) ) {
  1338. slashCount++;
  1339. outsets = tokenizer.until( function( tok ) {
  1340. return !( token.type & ( NUMBER | LENGTH ) );
  1341. } );
  1342. }
  1343. } else {
  1344. tokenizer.prev();
  1345. }
  1346. }
  1347. while( token = tokenizer.next() ) {
  1348. type = token.type;
  1349. value = token.value;
  1350. // Numbers and/or 'fill' keyword: slice values. May be followed optionally by width values, followed optionally by outset values
  1351. if( type & ( NUMBER | PERCENT ) && !slices ) {
  1352. tokenizer.prev();
  1353. collectSlicesEtc();
  1354. }
  1355. else if( isFillIdent( token ) && !p.fill ) {
  1356. p.fill = true;
  1357. collectSlicesEtc();
  1358. }
  1359. // Idents: one or values for 'repeat'
  1360. else if( ( type & IDENT ) && this.repeatIdents[value] && !p.repeat ) {
  1361. p.repeat = { h: value };
  1362. if( token = tokenizer.next() ) {
  1363. if( ( token.type & IDENT ) && this.repeatIdents[token.value] ) {
  1364. p.repeat.v = token.value;
  1365. } else {
  1366. tokenizer.prev();
  1367. }
  1368. }
  1369. }
  1370. // URL of the image
  1371. else if( ( type & Type.URL ) && !p.src ) {
  1372. p.src = value;
  1373. }
  1374. // Found something unrecognized; exit.
  1375. else {
  1376. return null;
  1377. }
  1378. }
  1379. // Validate what we collected
  1380. if( !p.src || !slices || slices.length < 1 || slices.length > 4 ||
  1381. ( widths && widths.length > 4 ) || ( slashCount === 1 && widths.length < 1 ) ||
  1382. ( outsets && outsets.length > 4 ) || ( slashCount === 2 && outsets.length < 1 ) ) {
  1383. return null;
  1384. }
  1385. // Fill in missing values
  1386. if( !p.repeat ) {
  1387. p.repeat = { h: 'stretch' };
  1388. }
  1389. if( !p.repeat.v ) {
  1390. p.repeat.v = p.repeat.h;
  1391. }
  1392. function distributeSides( tokens, convertFn ) {
  1393. return {
  1394. t: convertFn( tokens[0] ),
  1395. r: convertFn( tokens[1] || tokens[0] ),
  1396. b: convertFn( tokens[2] || tokens[0] ),
  1397. l: convertFn( tokens[3] || tokens[1] || tokens[0] )
  1398. };
  1399. }
  1400. p.slice = distributeSides( slices, function( tok ) {
  1401. return new PIE.Length( ( tok.type & NUMBER ) ? tok.value + 'px' : tok.value );
  1402. } );
  1403. p.width = widths && widths.length > 0 ?
  1404. distributeSides( widths, function( tok ) {
  1405. return tok.type & ( LENGTH | PERCENT ) ? new PIE.Length( tok.value ) : tok.value;
  1406. } ) :
  1407. ( cs = this.targetElement.currentStyle ) && {
  1408. t: new PIE.Length( cs.borderTopWidth ),
  1409. r: new PIE.Length( cs.borderRightWidth ),
  1410. b: new PIE.Length( cs.borderBottomWidth ),
  1411. l: new PIE.Length( cs.borderLeftWidth )
  1412. };
  1413. p.outset = distributeSides( outsets || [ 0 ], function( tok ) {
  1414. return tok.type & LENGTH ? new PIE.Length( tok.value ) : tok.value;
  1415. } );
  1416. }
  1417. return p;
  1418. }
  1419. } );/**
  1420. * Handles parsing, caching, and detecting changes to box-shadow CSS
  1421. * @constructor
  1422. * @param {Element} el the target element
  1423. */
  1424. PIE.BoxShadowStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
  1425. cssProperty: 'box-shadow',
  1426. styleProperty: 'boxShadow',
  1427. parseCss: function( css ) {
  1428. var props,
  1429. Length = PIE.Length,
  1430. Type = PIE.Tokenizer.Type,
  1431. tokenizer;
  1432. if( css ) {
  1433. tokenizer = new PIE.Tokenizer( css );
  1434. props = { outset: [], inset: [] };
  1435. function parseItem() {
  1436. var token, type, value, color, lengths, inset, len;
  1437. while( token = tokenizer.next() ) {
  1438. value = token.value;
  1439. type = token.type;
  1440. if( type & Type.OPERATOR && value === ',' ) {
  1441. break;
  1442. }
  1443. else if( token.isLength() && !lengths ) {
  1444. tokenizer.prev();
  1445. lengths = tokenizer.until( function( token ) {
  1446. return !token.isLength();
  1447. } );
  1448. }
  1449. else if( type & Type.COLOR && !color ) {
  1450. color = value;
  1451. }
  1452. else if( type & Type.IDENT && value === 'inset' && !inset ) {
  1453. inset = true;
  1454. }
  1455. else { //encountered an unrecognized token; fail.
  1456. return false;
  1457. }
  1458. }
  1459. len = lengths && lengths.length;
  1460. if( len > 1 && len < 5 ) {
  1461. ( inset ? props.inset : props.outset ).push( {
  1462. xOffset: new Length( lengths[0].value ),
  1463. yOffset: new Length( lengths[1].value ),
  1464. blur: new Length( lengths[2] ? lengths[2].value : '0' ),
  1465. spread: new Length( lengths[3] ? lengths[3].value : '0' ),
  1466. color: new PIE.Color( color || 'currentColor' )
  1467. } );
  1468. return true;
  1469. }
  1470. return false;
  1471. }
  1472. while( parseItem() ) {}
  1473. }
  1474. return props && ( props.inset.length || props.outset.length ) ? props : null;
  1475. }
  1476. } );
  1477. /**
  1478. * Retrieves the state of the element's visibility and display
  1479. * @constructor
  1480. * @param {Element} el the target element
  1481. */
  1482. PIE.VisibilityStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
  1483. getCss: PIE.StyleInfoBase.cacheWhenLocked( function() {
  1484. var cs = this.targetElement.currentStyle;
  1485. return cs.visibility + '|' + cs.display;
  1486. } ),
  1487. parseCss: function() {
  1488. var el = this.targetElement,
  1489. rs = el.runtimeStyle,
  1490. cs = el.currentStyle,
  1491. rsVis = rs.visibility,
  1492. csVis;
  1493. rs.visibility = '';
  1494. csVis = cs.visibility;
  1495. rs.visibility = rsVis;
  1496. return {
  1497. visible: csVis !== 'hidden',
  1498. displayed: cs.display !== 'none'
  1499. }
  1500. },
  1501. /**
  1502. * Always return false for isActive, since this property alone will not trigger
  1503. * a renderer to do anything.
  1504. */
  1505. isActive: function() {
  1506. return false;
  1507. }
  1508. } );
  1509. PIE.RendererBase = {
  1510. /**
  1511. * Create a new Renderer class, with the standard constructor, and augmented by
  1512. * the RendererBase's members.
  1513. * @param proto
  1514. */
  1515. newRenderer: function( proto ) {
  1516. function Renderer( el, boundsInfo, styleInfos, parent ) {
  1517. this.targetElement = el;
  1518. this.boundsInfo = boundsInfo;
  1519. this.styleInfos = styleInfos;
  1520. this.parent = parent;
  1521. }
  1522. PIE.Util.merge( Renderer.prototype, PIE.RendererBase, proto );
  1523. return Renderer;
  1524. },
  1525. /**
  1526. * Flag indicating the element has already been positioned at least once.
  1527. * @type {boolean}
  1528. */
  1529. isPositioned: false,
  1530. /**
  1531. * Determine if the renderer needs to be updated
  1532. * @return {boolean}
  1533. */
  1534. needsUpdate: function() {
  1535. return false;
  1536. },
  1537. /**
  1538. * Tell the renderer to update based on modified properties
  1539. */
  1540. updateProps: function() {
  1541. this.destroy();
  1542. if( this.isActive() ) {
  1543. this.draw();
  1544. }
  1545. },
  1546. /**
  1547. * Tell the renderer to update based on modified element position
  1548. */
  1549. updatePos: function() {
  1550. this.isPositioned = true;
  1551. },
  1552. /**
  1553. * Tell the renderer to update based on modified element dimensions
  1554. */
  1555. updateSize: function() {
  1556. if( this.isActive() ) {
  1557. this.draw();
  1558. } else {
  1559. this.destroy();
  1560. }
  1561. },
  1562. /**
  1563. * Add a layer element, with the given z-order index, to the renderer's main box element. We can't use
  1564. * z-index because that breaks when the root rendering box's z-index is 'auto' in IE8+ standards mode.
  1565. * So instead we make sure they are inserted into the DOM in the correct order.
  1566. * @param {number} index
  1567. * @param {Element} el
  1568. */
  1569. addLayer: function( index, el ) {
  1570. this.removeLayer( index );
  1571. for( var layers = this._layers || ( this._layers = [] ), i = index + 1, len = layers.length, layer; i < len; i++ ) {
  1572. layer = layers[i];
  1573. if( layer ) {
  1574. break;
  1575. }
  1576. }
  1577. layers[index] = el;
  1578. this.getBox().insertBefore( el, layer || null );
  1579. },
  1580. /**
  1581. * Retrieve a layer element by its index, or null if not present
  1582. * @param {number} index
  1583. * @return {Element}
  1584. */
  1585. getLayer: function( index ) {
  1586. var layers = this._layers;
  1587. return layers && layers[index] || null;
  1588. },
  1589. /**
  1590. * Remove a layer element by its index
  1591. * @param {number} index
  1592. */
  1593. removeLayer: function( index ) {
  1594. var layer = this.getLayer( index ),
  1595. box = this._box;
  1596. if( layer && box ) {
  1597. box.removeChild( layer );
  1598. this._layers[index] = null;
  1599. }
  1600. },
  1601. /**
  1602. * Get a VML shape by name, creating it if necessary.
  1603. * @param {string} name A name identifying the element
  1604. * @param {string=} subElName If specified a subelement of the shape will be created with this tag name
  1605. * @param {Element} parent The parent element for the shape; will be ignored if 'group' is specified
  1606. * @param {number=} group If specified, an ordinal group for the shape. 1 or greater. Groups are rendered
  1607. * using container elements in the correct order, to get correct z stacking without z-index.
  1608. */
  1609. getShape: function( name, subElName, parent, group ) {
  1610. var shapes = this._shapes || ( this._shapes = {} ),
  1611. shape = shapes[ name ],
  1612. s;
  1613. if( !shape ) {
  1614. shape = shapes[ name ] = PIE.Util.createVmlElement( 'shape' );
  1615. if( subElName ) {
  1616. shape.appendChild( shape[ subElName ] = PIE.Util.createVmlElement( subElName ) );
  1617. }
  1618. if( group ) {
  1619. parent = this.getLayer( group );
  1620. if( !parent ) {
  1621. this.addLayer( group, doc.createElement( 'group' + group ) );
  1622. parent = this.getLayer( group );
  1623. }
  1624. }
  1625. parent.appendChild( shape );
  1626. s = shape.style;
  1627. s.position = 'absolute';
  1628. s.left = s.top = 0;
  1629. s['behavior'] = 'url(#default#VML)';
  1630. }
  1631. return shape;
  1632. },
  1633. /**
  1634. * Delete a named shape which was created by getShape(). Returns true if a shape with the
  1635. * given name was found and deleted, or false if there was no shape of that name.
  1636. * @param {string} name
  1637. * @return {boolean}
  1638. */
  1639. deleteShape: function( name ) {
  1640. var shapes = this._shapes,
  1641. shape = shapes && shapes[ name ];
  1642. if( shape ) {
  1643. shape.parentNode.removeChild( shape );
  1644. delete shapes[ name ];
  1645. }
  1646. return !!shape;
  1647. },
  1648. /**
  1649. * For a given set of border radius length/percentage values, convert them to concrete pixel
  1650. * values based on the current size of the target element.
  1651. * @param {Object} radii
  1652. * @return {Object}
  1653. */
  1654. getRadiiPixels: function( radii ) {
  1655. var el = this.targetElement,
  1656. bounds = this.boundsInfo.getBounds(),
  1657. w = bounds.w,
  1658. h = bounds.h,
  1659. tlX, tlY, trX, trY, brX, brY, blX, blY, f;
  1660. tlX = radii.x['tl'].pixels( el, w );
  1661. tlY = radii.y['tl'].pixels( el, h );
  1662. trX = radii.x['tr'].pixels( el, w );
  1663. trY = radii.y['tr'].pixels( el, h );
  1664. brX = radii.x['br'].pixels( el, w );
  1665. brY = radii.y['br'].pixels( el, h );
  1666. blX = radii.x['bl'].pixels( el, w );
  1667. blY = radii.y['bl'].pixels( el, h );
  1668. // If any corner ellipses overlap, reduce them all by the appropriate factor. This formula
  1669. // is taken straight from the CSS3 Backgrounds and Borders spec.
  1670. f = Math.min(
  1671. w / ( tlX + trX ),
  1672. h / ( trY + brY ),
  1673. w / ( blX + brX ),
  1674. h / ( tlY + blY )
  1675. );
  1676. if( f < 1 ) {
  1677. tlX *= f;
  1678. tlY *= f;
  1679. trX *= f;
  1680. trY *= f;
  1681. brX *= f;
  1682. brY *= f;
  1683. blX *= f;
  1684. blY *= f;
  1685. }
  1686. return {
  1687. x: {
  1688. 'tl': tlX,
  1689. 'tr': trX,
  1690. 'br': brX,
  1691. 'bl': blX
  1692. },
  1693. y: {
  1694. 'tl': tlY,
  1695. 'tr': trY,
  1696. 'br': brY,
  1697. 'bl': blY
  1698. }
  1699. }
  1700. },
  1701. /**
  1702. * Return the VML path string for the element's background box, with corners rounded.
  1703. * @param {Object.<{t:number, r:number, b:number, l:number}>} shrink - if present, specifies number of
  1704. * pixels to shrink the box path inward from the element's four sides.
  1705. * @param {number=} mult If specified, all coordinates will be multiplied by this number
  1706. * @param {Object=} radii If specified, this will be used for the corner radii instead of the properties
  1707. * from this renderer's borderRadiusInfo object.
  1708. * @return {string} the VML path
  1709. */
  1710. getBoxPath: function( shrink, mult, radii ) {
  1711. mult = mult || 1;
  1712. var r, str,
  1713. bounds = this.boundsInfo.getBounds(),
  1714. w = bounds.w * mult,
  1715. h = bounds.h * mult,
  1716. radInfo = this.styleInfos.borderRadiusInfo,
  1717. floor = Math.floor, ceil = Math.ceil,
  1718. shrinkT = shrink ? shrink.t * mult : 0,
  1719. shrinkR = shrink ? shrink.r * mult : 0,
  1720. shrinkB = shrink ? shrink.b * mult : 0,
  1721. shrinkL = shrink ? shrink.l * mult : 0,
  1722. tlX, tlY, trX, trY, brX, brY, blX, blY;
  1723. if( radii || radInfo.isActive() ) {
  1724. r = this.getRadiiPixels( radii || radInfo.getProps() );
  1725. tlX = r.x['tl'] * mult;
  1726. tlY = r.y['tl'] * mult;
  1727. trX = r.x['tr'] * mult;
  1728. trY = r.y['tr'] * mult;
  1729. brX = r.x['br'] * mult;
  1730. brY = r.y['br'] * mult;
  1731. blX = r.x['bl'] * mult;
  1732. blY = r.y['bl'] * mult;
  1733. str = 'm' + floor( shrinkL ) + ',' + floor( tlY ) +
  1734. 'qy' + floor( tlX ) + ',' + floor( shrinkT ) +
  1735. 'l' + ceil( w - trX ) + ',' + floor( shrinkT ) +
  1736. 'qx' + ceil( w - shrinkR ) + ',' + floor( trY ) +
  1737. 'l' + ceil( w - shrinkR ) + ',' + ceil( h - brY ) +
  1738. 'qy' + ceil( w - brX ) + ',' + ceil( h - shrinkB ) +
  1739. 'l' + floor( blX ) + ',' + ceil( h - shrinkB ) +
  1740. 'qx' + floor( shrinkL ) + ',' + ceil( h - blY ) + ' x e';
  1741. } else {
  1742. // simplified path for non-rounded box
  1743. str = 'm' + floor( shrinkL ) + ',' + floor( shrinkT ) +
  1744. 'l' + ceil( w - shrinkR ) + ',' + floor( shrinkT ) +
  1745. 'l' + ceil( w - shrinkR ) + ',' + ceil( h - shrinkB ) +
  1746. 'l' + floor( shrinkL ) + ',' + ceil( h - shrinkB ) +
  1747. 'xe';
  1748. }
  1749. return str;
  1750. },
  1751. /**
  1752. * Get the container element for the shapes, creating it if necessary.
  1753. */
  1754. getBox: function() {
  1755. var box = this.parent.getLayer( this.boxZIndex ), s;
  1756. if( !box ) {
  1757. box = doc.createElement( this.boxName );
  1758. s = box.style;
  1759. s.position = 'absolute';
  1760. s.top = s.left = 0;
  1761. this.parent.addLayer( this.boxZIndex, box );
  1762. }
  1763. return box;
  1764. },
  1765. /**
  1766. * Destroy the rendered objects. This is a base implementation which handles common renderer
  1767. * structures, but individual renderers may override as necessary.
  1768. */
  1769. destroy: function() {
  1770. this.parent.removeLayer( this.boxZIndex );
  1771. delete this._shapes;
  1772. delete this._layers;
  1773. }
  1774. };
  1775. /**
  1776. * Root renderer; creates the outermost container element and handles keeping it aligned
  1777. * with the target element's size and position.
  1778. * @param {Element} el The target element
  1779. * @param {Object} styleInfos The StyleInfo objects
  1780. */
  1781. PIE.RootRenderer = PIE.RendererBase.newRenderer( {
  1782. isActive: function() {
  1783. var children = this.childRenderers;
  1784. for( var i in children ) {
  1785. if( children.hasOwnProperty( i ) && children[ i ].isActive() ) {
  1786. return true;
  1787. }
  1788. }
  1789. return false;
  1790. },
  1791. needsUpdate: function() {
  1792. return this.styleInfos.visibilityInfo.changed();
  1793. },
  1794. updatePos: function() {
  1795. if( this.isActive() ) {
  1796. var el = this.getPositioningElement(),
  1797. par = el,
  1798. docEl,
  1799. parRect,
  1800. tgtCS = el.currentStyle,
  1801. tgtPos = tgtCS.position,
  1802. boxPos,
  1803. s = this.getBox().style, cs,
  1804. x = 0, y = 0,
  1805. elBounds = this.boundsInfo.getBounds();
  1806. if( tgtPos === 'fixed' && PIE.ieVersion > 6 ) {
  1807. x = elBounds.x;
  1808. y = elBounds.y;
  1809. boxPos = tgtPos;
  1810. } else {
  1811. // Get the element's offsets from its nearest positioned ancestor. Uses
  1812. // getBoundingClientRect for accuracy and speed.
  1813. do {
  1814. par = par.offsetParent;
  1815. } while( par && ( par.currentStyle.position === 'static' ) );
  1816. if( par ) {
  1817. parRect = par.getBoundingClientRect();
  1818. cs = par.currentStyle;
  1819. x = elBounds.x - parRect.left - ( parseFloat(cs.borderLeftWidth) || 0 );
  1820. y = elBounds.y - parRect.top - ( parseFloat(cs.borderTopWidth) || 0 );
  1821. } else {
  1822. docEl = doc.documentElement;
  1823. x = elBounds.x + docEl.scrollLeft - docEl.clientLeft;
  1824. y = elBounds.y + docEl.scrollTop - docEl.clientTop;
  1825. }
  1826. boxPos = 'absolute';
  1827. }
  1828. s.position = boxPos;
  1829. s.left = x;
  1830. s.top = y;
  1831. s.zIndex = tgtPos === 'static' ? -1 : tgtCS.zIndex;
  1832. this.isPositioned = true;
  1833. }
  1834. },
  1835. updateSize: function() {
  1836. // NO-OP
  1837. },
  1838. updateVisibility: function() {
  1839. var vis = this.styleInfos.visibilityInfo.getProps();
  1840. this.getBox().style.display = ( vis.visible && vis.displayed ) ? '' : 'none';
  1841. },
  1842. updateProps: function() {
  1843. if( this.isActive() ) {
  1844. this.updateVisibility();
  1845. } else {
  1846. this.destroy();
  1847. }
  1848. },
  1849. getPositioningElement: function() {
  1850. var el = this.targetElement;
  1851. return el.tagName in PIE.tableCellTags ? el.offsetParent : el;
  1852. },
  1853. getBox: function() {
  1854. var box = this._box, el;
  1855. if( !box ) {
  1856. el = this.getPositioningElement();
  1857. box = this._box = doc.createElement( 'css3-container' );
  1858. this.updateVisibility();
  1859. el.parentNode.insertBefore( box, el );
  1860. }
  1861. return box;
  1862. },
  1863. destroy: function() {
  1864. var box = this._box, par;
  1865. if( box && ( par = box.parentNode ) ) {
  1866. par.removeChild( box );
  1867. }
  1868. delete this._box;
  1869. delete this._layers;
  1870. }
  1871. } );
  1872. /**
  1873. * Renderer for element backgrounds.
  1874. * @constructor
  1875. * @param {Element} el The target element
  1876. * @param {Object} styleInfos The StyleInfo objects
  1877. * @param {PIE.RootRenderer} parent
  1878. */
  1879. PIE.BackgroundRenderer = PIE.RendererBase.newRenderer( {
  1880. boxZIndex: 2,
  1881. boxName: 'background',
  1882. needsUpdate: function() {
  1883. var si = this.styleInfos;
  1884. return si.backgroundInfo.changed() || si.borderRadiusInfo.changed();
  1885. },
  1886. isActive: function() {
  1887. var si = this.styleInfos;
  1888. return si.borderImageInfo.isActive() ||
  1889. si.borderRadiusInfo.isActive() ||
  1890. si.backgroundInfo.isActive() ||
  1891. ( si.boxShadowInfo.isActive() && si.boxShadowInfo.getProps().inset );
  1892. },
  1893. /**
  1894. * Draw the shapes
  1895. */
  1896. draw: function() {
  1897. var bounds = this.boundsInfo.getBounds();
  1898. if( bounds.w && bounds.h ) {
  1899. this.drawBgColor();
  1900. this.drawBgImages();
  1901. }
  1902. },
  1903. /**
  1904. * Draw the background color shape
  1905. */
  1906. drawBgColor: function() {
  1907. var props = this.styleInfos.backgroundInfo.getProps(),
  1908. bounds = this.boundsInfo.getBounds(),
  1909. el = this.targetElement,
  1910. color = props && props.color,
  1911. shape, w, h, s, alpha;
  1912. if( color && color.alpha() > 0 ) {
  1913. this.hideBackground();
  1914. shape = this.getShape( 'bgColor', 'fill', this.getBox(), 1 );
  1915. w = bounds.w;
  1916. h = bounds.h;
  1917. shape.stroked = false;
  1918. shape.coordsize = w * 2 + ',' + h * 2;
  1919. shape.coordorigin = '1,1';
  1920. shape.path = this.getBoxPath( null, 2 );
  1921. s = shape.style;
  1922. s.width = w;
  1923. s.height = h;
  1924. shape.fill.color = color.value( el );
  1925. alpha = color.alpha();
  1926. if( alpha < 1 ) {
  1927. shape.fill.opacity = alpha;
  1928. }
  1929. } else {
  1930. this.deleteShape( 'bgColor' );
  1931. }
  1932. },
  1933. /**
  1934. * Draw all the background image layers
  1935. */
  1936. drawBgImages: function() {
  1937. var props = this.styleInfos.backgroundInfo.getProps(),
  1938. bounds = this.boundsInfo.getBounds(),
  1939. images = props && props.images,
  1940. img, shape, w, h, s, i;
  1941. if( images ) {
  1942. this.hideBackground();
  1943. w = bounds.w;
  1944. h = bounds.h;
  1945. i = images.length;
  1946. while( i-- ) {
  1947. img = images[i];
  1948. shape = this.getShape( 'bgImage' + i, 'fill', this.getBox(), 2 );
  1949. shape.stroked = false;
  1950. shape.fill.type = 'tile';
  1951. shape.fillcolor = 'none';
  1952. shape.coordsize = w * 2 + ',' + h * 2;
  1953. shape.coordorigin = '1,1';
  1954. shape.path = this.getBoxPath( 0, 2 );
  1955. s = shape.style;
  1956. s.width = w;
  1957. s.height = h;
  1958. if( img.type === 'linear-gradient' ) {
  1959. this.addLinearGradient( shape, img );
  1960. }
  1961. else {
  1962. shape.fill.src = img.url;
  1963. this.positionBgImage( shape, i );
  1964. }
  1965. }
  1966. }
  1967. // Delete any bgImage shapes previously created which weren't used above
  1968. i = images ? images.length : 0;
  1969. while( this.deleteShape( 'bgImage' + i++ ) ) {}
  1970. },
  1971. /**
  1972. * Set the position and clipping of the background image for a layer
  1973. * @param {Element} shape
  1974. * @param {number} index
  1975. */
  1976. positionBgImage: function( shape, index ) {
  1977. PIE.Util.withImageSize( shape.fill.src, function( size ) {
  1978. var fill = shape.fill,
  1979. el = this.targetElement,
  1980. bounds = this.boundsInfo.getBounds(),
  1981. elW = bounds.w,
  1982. elH = bounds.h,
  1983. si = this.styleInfos,
  1984. border = si.borderInfo.getProps(),
  1985. bw = border && border.widths,
  1986. bwT = bw ? bw['t'].pixels( el ) : 0,
  1987. bwR = bw ? bw['r'].pixels( el ) : 0,
  1988. bwB = bw ? bw['b'].pixels( el ) : 0,
  1989. bwL = bw ? bw['l'].pixels( el ) : 0,
  1990. bg = si.backgroundInfo.getProps().images[ index ],
  1991. bgPos = bg.position ? bg.position.coords( el, elW - size.w - bwL - bwR, elH - size.h - bwT - bwB ) : { x:0, y:0 },
  1992. repeat = bg.repeat,
  1993. pxX, pxY,
  1994. clipT = 0, clipL = 0,
  1995. clipR = elW + 1, clipB = elH + 1, //make sure the default clip region is not inside the box (by a subpixel)
  1996. clipAdjust = PIE.ieVersion === 8 ? 0 : 1; //prior to IE8 requires 1 extra pixel in the image clip region
  1997. // Positioning - find the pixel offset from the top/left and convert to a ratio
  1998. // The position is shifted by half a pixel, to adjust for the half-pixel coordorigin shift which is
  1999. // needed to fix antialiasing but makes the bg image fuzzy.
  2000. pxX = Math.round( bgPos.x ) + bwL + 0.5;
  2001. pxY = Math.round( bgPos.y ) + bwT + 0.5;
  2002. fill.position = ( pxX / elW ) + ',' + ( pxY / elH );
  2003. // Repeating - clip the image shape
  2004. if( repeat && repeat !== 'repeat' ) {
  2005. if( repeat === 'repeat-x' || repeat === 'no-repeat' ) {
  2006. clipT = pxY + 1;
  2007. clipB = pxY + size.h + clipAdjust;
  2008. }
  2009. if( repeat === 'repeat-y' || repeat === 'no-repeat' ) {
  2010. clipL = pxX + 1;
  2011. clipR = pxX + size.w + clipAdjust;
  2012. }
  2013. shape.style.clip = 'rect(' + clipT + 'px,' + clipR + 'px,' + clipB + 'px,' + clipL + 'px)';
  2014. }
  2015. }, this );
  2016. },
  2017. /**
  2018. * Draw the linear gradient for a gradient layer
  2019. * @param {Element} shape
  2020. * @param {Object} info The object holding the information about the gradient
  2021. */
  2022. addLinearGradient: function( shape, info ) {
  2023. var el = this.targetElement,
  2024. bounds = this.boundsInfo.getBounds(),
  2025. w = bounds.w,
  2026. h = bounds.h,
  2027. fill = shape.fill,
  2028. angle = info.angle,
  2029. startPos = info.gradientStart,
  2030. stops = info.stops,
  2031. stopCount = stops.length,
  2032. PI = Math.PI,
  2033. UNDEF,
  2034. startX, startY,
  2035. endX, endY,
  2036. startCornerX, startCornerY,
  2037. endCornerX, endCornerY,
  2038. vmlAngle, vmlGradientLength, vmlColors,
  2039. deltaX, deltaY, lineLength,
  2040. stopPx, vmlOffsetPct,
  2041. p, i, j, before, after;
  2042. /**
  2043. * Find the point along a given line (defined by a starting point and an angle), at which
  2044. * that line is intersected by a perpendicular line extending through another point.
  2045. * @param x1 - x coord of the starting point
  2046. * @param y1 - y coord of the starting point
  2047. * @param angle - angle of the line extending from the starting point (in degrees)
  2048. * @param x2 - x coord of point along the perpendicular line
  2049. * @param y2 - y coord of point along the perpendicular line
  2050. * @return [ x, y ]
  2051. */
  2052. function perpendicularIntersect( x1, y1, angle, x2, y2 ) {
  2053. // Handle straight vertical and horizontal angles, for performance and to avoid
  2054. // divide-by-zero errors.
  2055. if( angle === 0 || angle === 180 ) {
  2056. return [ x2, y1 ];
  2057. }
  2058. else if( angle === 90 || angle === 270 ) {
  2059. return [ x1, y2 ];
  2060. }
  2061. else {
  2062. // General approach: determine the Ax+By=C formula for each line (the slope of the second
  2063. // line is the negative inverse of the first) and then solve for where both formulas have
  2064. // the same x/y values.
  2065. var a1 = Math.tan( -angle * PI / 180 ),
  2066. c1 = a1 * x1 - y1,
  2067. a2 = -1 / a1,
  2068. c2 = a2 * x2 - y2,
  2069. d = a2 - a1,
  2070. endX = ( c2 - c1 ) / d,
  2071. endY = ( a1 * c2 - a2 * c1 ) / d;
  2072. return [ endX, endY ];
  2073. }
  2074. }
  2075. // Find the "start" and "end" corners; these are the corners furthest along the gradient line.
  2076. // This is used below to find the start/end positions of the CSS3 gradient-line, and also in finding
  2077. // the total length of the VML rendered gradient-line corner to corner.
  2078. function findCorners() {
  2079. startCornerX = ( angle >= 90 && angle < 270 ) ? w : 0;
  2080. startCornerY = angle < 180 ? h : 0;
  2081. endCornerX = w - startCornerX;
  2082. endCornerY = h - startCornerY;
  2083. }
  2084. // Normalize the angle to a value between [0, 360)
  2085. function normalizeAngle() {
  2086. while( angle < 0 ) {
  2087. angle += 360;
  2088. }
  2089. angle = angle % 360;
  2090. }
  2091. // Find the distance between two points
  2092. function distance( p1, p2 ) {
  2093. var dx = p2[0] - p1[0],
  2094. dy = p2[1] - p1[1];
  2095. return Math.abs(
  2096. dx === 0 ? dy :
  2097. dy === 0 ? dx :
  2098. Math.sqrt( dx * dx + dy * dy )
  2099. );
  2100. }
  2101. // Find the start and end points of the gradient
  2102. if( startPos ) {
  2103. startPos = startPos.coords( el, w, h );
  2104. startX = startPos.x;
  2105. startY = startPos.y;
  2106. }
  2107. if( angle ) {
  2108. angle = angle.degrees();
  2109. normalizeAngle();
  2110. findCorners();
  2111. // If no start position was specified, then choose a corner as the starting point.
  2112. if( !startPos ) {
  2113. startX = startCornerX;
  2114. startY = startCornerY;
  2115. }
  2116. // Find the end position by extending a perpendicular line from the gradient-line which
  2117. // intersects the corner opposite from the starting corner.
  2118. p = perpendicularIntersect( startX, startY, angle, endCornerX, endCornerY );
  2119. endX = p[0];
  2120. endY = p[1];
  2121. }
  2122. else if( startPos ) {
  2123. // Start position but no angle specified: find the end point by rotating 180deg around the center
  2124. endX = w - startX;
  2125. endY = h - startY;
  2126. }
  2127. else {
  2128. // Neither position nor angle specified; create vertical gradient from top to bottom
  2129. startX = startY = endX = 0;
  2130. endY = h;
  2131. }
  2132. deltaX = endX - startX;
  2133. deltaY = endY - startY;
  2134. if( angle === UNDEF ) {
  2135. // Get the angle based on the change in x/y from start to end point. Checks first for horizontal
  2136. // or vertical angles so they get exact whole numbers rather than what atan2 gives.
  2137. angle = ( !deltaX ? ( deltaY < 0 ? 90 : 270 ) :
  2138. ( !deltaY ? ( deltaX < 0 ? 180 : 0 ) :
  2139. -Math.atan2( deltaY, deltaX ) / PI * 180
  2140. )
  2141. );
  2142. normalizeAngle();
  2143. findCorners();
  2144. }
  2145. // In VML land, the angle of the rendered gradient depends on the aspect ratio of the shape's
  2146. // bounding box; for example specifying a 45 deg angle actually results in a gradient
  2147. // drawn diagonally from one corner to its opposite corner, which will only appear to the
  2148. // viewer as 45 degrees if the shape is equilateral. We adjust for this by taking the x/y deltas
  2149. // between the start and end points, multiply one of them by the shape's aspect ratio,
  2150. // and get their arctangent, resulting in an appropriate VML angle. If the angle is perfectly
  2151. // horizontal or vertical then we don't need to do this conversion.
  2152. vmlAngle = ( angle % 90 ) ? Math.atan2( deltaX * w / h, deltaY ) / PI * 180 : ( angle + 90 );
  2153. // VML angles are 180 degrees offset from CSS angles
  2154. vmlAngle += 180;
  2155. vmlAngle = vmlAngle % 360;
  2156. // Add all the stops to the VML 'colors' list, including the first and last stops.
  2157. // For each, we find its pixel offset along the gradient-line; if the offset of a stop is less
  2158. // than that of its predecessor we increase it to be equal. We then map that pixel offset to a
  2159. // percentage along the VML gradient-line, which runs from shape corner to corner.
  2160. lineLength = distance( [ startX, startY ], [ endX, endY ] );
  2161. vmlGradientLength = distance( [ startCornerX, startCornerY ], perpendicularIntersect( startCornerX, startCornerY, angle, endCornerX, endCornerY ) );
  2162. vmlColors = [];
  2163. vmlOffsetPct = distance( [ startX, startY ], perpendicularIntersect( startX, startY, angle, startCornerX, startCornerY ) ) / vmlGradientLength * 100;
  2164. // Find the pixel offsets along the CSS3 gradient-line for each stop.
  2165. stopPx = [];
  2166. for( i = 0; i < stopCount; i++ ) {
  2167. stopPx.push( stops[i].offset ? stops[i].offset.pixels( el, lineLength ) :
  2168. i === 0 ? 0 : i === stopCount - 1 ? lineLength : null );
  2169. }
  2170. // Fill in gaps with evenly-spaced offsets
  2171. for( i = 1; i < stopCount; i++ ) {
  2172. if( stopPx[ i ] === null ) {
  2173. before = stopPx[ i - 1 ];
  2174. j = i;
  2175. do {
  2176. after = stopPx[ ++j ];
  2177. } while( after === null );
  2178. stopPx[ i ] = before + ( after - before ) / ( j - i + 1 );
  2179. }
  2180. // Make sure each stop's offset is no less than the one before it
  2181. stopPx[ i ] = Math.max( stopPx[ i ], stopPx[ i - 1 ] );
  2182. }
  2183. // Convert to percentage along the VML gradient line and add to the VML 'colors' value
  2184. for( i = 0; i < stopCount; i++ ) {
  2185. vmlColors.push(
  2186. ( vmlOffsetPct + ( stopPx[ i ] / vmlGradientLength * 100 ) ) + '% ' + stops[i].color.value( el )
  2187. );
  2188. }
  2189. // Now, finally, we're ready to render the gradient fill. Set the start and end colors to
  2190. // the first and last stop colors; this just sets outer bounds for the gradient.
  2191. fill['angle'] = vmlAngle;
  2192. fill['type'] = 'gradient';
  2193. fill['method'] = 'sigma';
  2194. fill['color'] = stops[0].color.value( el );
  2195. fill['color2'] = stops[stopCount - 1].color.value( el );
  2196. fill['colors'].value = vmlColors.join( ',' );
  2197. },
  2198. /**
  2199. * Hide the actual background image and color of the element.
  2200. */
  2201. hideBackground: function() {
  2202. var rs = this.targetElement.runtimeStyle;
  2203. rs.backgroundImage = 'url(about:blank)'; //ensures the background area reacts to mouse events
  2204. rs.backgroundColor = 'transparent';
  2205. },
  2206. destroy: function() {
  2207. PIE.RendererBase.destroy.call( this );
  2208. var rs = this.targetElement.runtimeStyle;
  2209. rs.backgroundImage = rs.backgroundColor = '';
  2210. }
  2211. } );
  2212. /**
  2213. * Renderer for element borders.
  2214. * @constructor
  2215. * @param {Element} el The target element
  2216. * @param {Object} styleInfos The StyleInfo objects
  2217. * @param {PIE.RootRenderer} parent
  2218. */
  2219. PIE.BorderRenderer = PIE.RendererBase.newRenderer( {
  2220. boxZIndex: 4,
  2221. boxName: 'border',
  2222. /**
  2223. * Lookup table of elements which cannot take custom children.
  2224. */
  2225. childlessElements: {
  2226. 'TABLE':1, //can obviously have children but not custom ones
  2227. 'INPUT':1,
  2228. 'TEXTAREA':1,
  2229. 'SELECT':1,
  2230. 'OPTION':1,
  2231. 'IMG':1,
  2232. 'HR':1,
  2233. 'FIELDSET':1 //can take children but wrapping its children messes up its <legend>
  2234. },
  2235. /**
  2236. * Values of the type attribute for input elements displayed as buttons
  2237. */
  2238. inputButtonTypes: {
  2239. 'submit':1,
  2240. 'button':1,
  2241. 'reset':1
  2242. },
  2243. needsUpdate: function() {
  2244. var si = this.styleInfos;
  2245. return si.borderInfo.changed() || si.borderRadiusInfo.changed();
  2246. },
  2247. isActive: function() {
  2248. var si = this.styleInfos;
  2249. return ( si.borderImageInfo.isActive() ||
  2250. si.borderRadiusInfo.isActive() ||
  2251. si.backgroundInfo.isActive() ) &&
  2252. si.borderInfo.isActive(); //check BorderStyleInfo last because it's the most expensive
  2253. },
  2254. /**
  2255. * Draw the border shape(s)
  2256. */
  2257. draw: function() {
  2258. var el = this.targetElement,
  2259. cs = el.currentStyle,
  2260. props = this.styleInfos.borderInfo.getProps(),
  2261. bounds = this.boundsInfo.getBounds(),
  2262. w = bounds.w,
  2263. h = bounds.h,
  2264. side, shape, stroke, s,
  2265. segments, seg, i, len;
  2266. if( props ) {
  2267. this.hideBorder();
  2268. segments = this.getBorderSegments( 2 );
  2269. for( i = 0, len = segments.length; i < len; i++) {
  2270. seg = segments[i];
  2271. shape = this.getShape( 'borderPiece' + i, seg.stroke ? 'stroke' : 'fill', this.getBox() );
  2272. shape.coordsize = w * 2 + ',' + h * 2;
  2273. shape.coordorigin = '1,1';
  2274. shape.path = seg.path;
  2275. s = shape.style;
  2276. s.width = w;
  2277. s.height = h;
  2278. shape.filled = !!seg.fill;
  2279. shape.stroked = !!seg.stroke;
  2280. if( seg.stroke ) {
  2281. stroke = shape.stroke;
  2282. stroke['weight'] = seg.weight + 'px';
  2283. stroke.color = seg.color.value( el );
  2284. stroke['dashstyle'] = seg.stroke === 'dashed' ? '2 2' : seg.stroke === 'dotted' ? '1 1' : 'solid';
  2285. stroke['linestyle'] = seg.stroke === 'double' && seg.weight > 2 ? 'ThinThin' : 'Single';
  2286. } else {
  2287. shape.fill.color = seg.fill.value( el );
  2288. }
  2289. }
  2290. // remove any previously-created border shapes which didn't get used above
  2291. while( this.deleteShape( 'borderPiece' + i++ ) ) {}
  2292. }
  2293. },
  2294. /**
  2295. * Hide the actual border of the element. In IE7 and up we can just set its color to transparent;
  2296. * however IE6 does not support transparent borders so we have to get tricky with it. Also, some elements
  2297. * like form buttons require removing the border width altogether, so for those we increase the padding
  2298. * by the border size.
  2299. */
  2300. hideBorder: function() {
  2301. var el = this.targetElement,
  2302. cs = el.currentStyle,
  2303. rs = el.runtimeStyle,
  2304. tag = el.tagName,
  2305. isIE6 = PIE.ieVersion === 6,
  2306. sides, side, i;
  2307. if( ( isIE6 && tag in this.childlessElements ) || tag === 'BUTTON' ||
  2308. ( tag === 'INPUT' && el.type in this.inputButtonTypes ) ) {
  2309. rs.borderWidth = '';
  2310. sides = this.styleInfos.borderInfo.sides;
  2311. for( i = sides.length; i--; ) {
  2312. side = sides[ i ];
  2313. rs[ 'padding' + side ] = '';
  2314. rs[ 'padding' + side ] = ( new PIE.Length( cs[ 'padding' + side ] ) ).pixels( el ) +
  2315. ( new PIE.Length( cs[ 'border' + side + 'Width' ] ) ).pixels( el ) +
  2316. ( !PIE.ieVersion === 8 && i % 2 ? 1 : 0 ); //needs an extra horizontal pixel to counteract the extra "inner border" going away
  2317. }
  2318. rs.borderWidth = 0;
  2319. }
  2320. else if( isIE6 ) {
  2321. // Wrap all the element's children in a custom element, set the element to visiblity:hidden,
  2322. // and set the wrapper element to visiblity:visible. This hides the outer element's decorations
  2323. // (background and border) but displays all the contents.
  2324. // TODO find a better way to do this that doesn't mess up the DOM parent-child relationship,
  2325. // as this can interfere with other author scripts which add/modify/delete children. Also, this
  2326. // won't work for elements which cannot take children, e.g. input/button/textarea/img/etc. Look into
  2327. // using a compositor filter or some other filter which masks the border.
  2328. if( el.childNodes.length !== 1 || el.firstChild.tagName !== 'ie6-mask' ) {
  2329. var cont = doc.createElement( 'ie6-mask' ),
  2330. s = cont.style, child;
  2331. s.visibility = 'visible';
  2332. s.zoom = 1;
  2333. while( child = el.firstChild ) {
  2334. cont.appendChild( child );
  2335. }
  2336. el.appendChild( cont );
  2337. rs.visibility = 'hidden';
  2338. }
  2339. }
  2340. else {
  2341. rs.borderColor = 'transparent';
  2342. }
  2343. },
  2344. /**
  2345. * Get the VML path definitions for the border segment(s).
  2346. * @param {number=} mult If specified, all coordinates will be multiplied by this number
  2347. * @return {Array.<string>}
  2348. */
  2349. getBorderSegments: function( mult ) {
  2350. var el = this.targetElement,
  2351. bounds, elW, elH,
  2352. borderInfo = this.styleInfos.borderInfo,
  2353. segments = [],
  2354. floor, ceil, wT, wR, wB, wL,
  2355. round = Math.round,
  2356. borderProps, radiusInfo, radii, widths, styles, colors;
  2357. if( borderInfo.isActive() ) {
  2358. borderProps = borderInfo.getProps();
  2359. widths = borderProps.widths;
  2360. styles = borderProps.styles;
  2361. colors = borderProps.colors;
  2362. if( borderProps.widthsSame && borderProps.stylesSame && borderProps.colorsSame ) {
  2363. if( colors['t'].alpha() > 0 ) {
  2364. // shortcut for identical border on all sides - only need 1 stroked shape
  2365. wT = widths['t'].pixels( el ); //thickness
  2366. wR = wT / 2; //shrink
  2367. segments.push( {
  2368. path: this.getBoxPath( { t: wR, r: wR, b: wR, l: wR }, mult ),
  2369. stroke: styles['t'],
  2370. color: colors['t'],
  2371. weight: wT
  2372. } );
  2373. }
  2374. }
  2375. else {
  2376. mult = mult || 1;
  2377. bounds = this.boundsInfo.getBounds();
  2378. elW = bounds.w;
  2379. elH = bounds.h;
  2380. wT = round( widths['t'].pixels( el ) );
  2381. wR = round( widths['r'].pixels( el ) );
  2382. wB = round( widths['b'].pixels( el ) );
  2383. wL = round( widths['l'].pixels( el ) );
  2384. var pxWidths = {
  2385. 't': wT,
  2386. 'r': wR,
  2387. 'b': wB,
  2388. 'l': wL
  2389. };
  2390. radiusInfo = this.styleInfos.borderRadiusInfo;
  2391. if( radiusInfo.isActive() ) {
  2392. radii = this.getRadiiPixels( radiusInfo.getProps() );
  2393. }
  2394. floor = Math.floor;
  2395. ceil = Math.ceil;
  2396. function radius( xy, corner ) {
  2397. return radii ? radii[ xy ][ corner ] : 0;
  2398. }
  2399. function curve( corner, shrinkX, shrinkY, startAngle, ccw, doMove ) {
  2400. var rx = radius( 'x', corner),
  2401. ry = radius( 'y', corner),
  2402. deg = 65535,
  2403. isRight = corner.charAt( 1 ) === 'r',
  2404. isBottom = corner.charAt( 0 ) === 'b';
  2405. return ( rx > 0 && ry > 0 ) ?
  2406. ( doMove ? 'al' : 'ae' ) +
  2407. ( isRight ? ceil( elW - rx ) : floor( rx ) ) * mult + ',' + // center x
  2408. ( isBottom ? ceil( elH - ry ) : floor( ry ) ) * mult + ',' + // center y
  2409. ( floor( rx ) - shrinkX ) * mult + ',' + // width
  2410. ( floor( ry ) - shrinkY ) * mult + ',' + // height
  2411. ( startAngle * deg ) + ',' + // start angle
  2412. ( 45 * deg * ( ccw ? 1 : -1 ) // angle change
  2413. ) : (
  2414. ( doMove ? 'm' : 'l' ) +
  2415. ( isRight ? elW - shrinkX : shrinkX ) * mult + ',' +
  2416. ( isBottom ? elH - shrinkY : shrinkY ) * mult
  2417. );
  2418. }
  2419. function line( side, shrink, ccw, doMove ) {
  2420. var
  2421. start = (
  2422. side === 't' ?
  2423. floor( radius( 'x', 'tl') ) * mult + ',' + ceil( shrink ) * mult :
  2424. side === 'r' ?
  2425. ceil( elW - shrink ) * mult + ',' + floor( radius( 'y', 'tr') ) * mult :
  2426. side === 'b' ?
  2427. ceil( elW - radius( 'x', 'br') ) * mult + ',' + floor( elH - shrink ) * mult :
  2428. // side === 'l' ?
  2429. floor( shrink ) * mult + ',' + ceil( elH - radius( 'y', 'bl') ) * mult
  2430. ),
  2431. end = (
  2432. side === 't' ?
  2433. ceil( elW - radius( 'x', 'tr') ) * mult + ',' + ceil( shrink ) * mult :
  2434. side === 'r' ?
  2435. ceil( elW - shrink ) * mult + ',' + ceil( elH - radius( 'y', 'br') ) * mult :
  2436. side === 'b' ?
  2437. floor( radius( 'x', 'bl') ) * mult + ',' + floor( elH - shrink ) * mult :
  2438. // side === 'l' ?
  2439. floor( shrink ) * mult + ',' + floor( radius( 'y', 'tl') ) * mult
  2440. );
  2441. return ccw ? ( doMove ? 'm' + end : '' ) + 'l' + start :
  2442. ( doMove ? 'm' + start : '' ) + 'l' + end;
  2443. }
  2444. function addSide( side, sideBefore, sideAfter, cornerBefore, cornerAfter, baseAngle ) {
  2445. var vert = side === 'l' || side === 'r',
  2446. sideW = pxWidths[ side ],
  2447. beforeX, beforeY, afterX, afterY;
  2448. if( sideW > 0 && styles[ side ] !== 'none' && colors[ side ].alpha() > 0 ) {
  2449. beforeX = pxWidths[ vert ? side : sideBefore ];
  2450. beforeY = pxWidths[ vert ? sideBefore : side ];
  2451. afterX = pxWidths[ vert ? side : sideAfter ];
  2452. afterY = pxWidths[ vert ? sideAfter : side ];
  2453. if( styles[ side ] === 'dashed' || styles[ side ] === 'dotted' ) {
  2454. segments.push( {
  2455. path: curve( cornerBefore, beforeX, beforeY, baseAngle + 45, 0, 1 ) +
  2456. curve( cornerBefore, 0, 0, baseAngle, 1, 0 ),
  2457. fill: colors[ side ]
  2458. } );
  2459. segments.push( {
  2460. path: line( side, sideW / 2, 0, 1 ),
  2461. stroke: styles[ side ],
  2462. weight: sideW,
  2463. color: colors[ side ]
  2464. } );
  2465. segments.push( {
  2466. path: curve( cornerAfter, afterX, afterY, baseAngle, 0, 1 ) +
  2467. curve( cornerAfter, 0, 0, baseAngle - 45, 1, 0 ),
  2468. fill: colors[ side ]
  2469. } );
  2470. }
  2471. else {
  2472. segments.push( {
  2473. path: curve( cornerBefore, beforeX, beforeY, baseAngle + 45, 0, 1 ) +
  2474. line( side, sideW, 0, 0 ) +
  2475. curve( cornerAfter, afterX, afterY, baseAngle, 0, 0 ) +
  2476. ( styles[ side ] === 'double' && sideW > 2 ?
  2477. curve( cornerAfter, afterX - floor( afterX / 3 ), afterY - floor( afterY / 3 ), baseAngle - 45, 1, 0 ) +
  2478. line( side, ceil( sideW / 3 * 2 ), 1, 0 ) +
  2479. curve( cornerBefore, beforeX - floor( beforeX / 3 ), beforeY - floor( beforeY / 3 ), baseAngle, 1, 0 ) +
  2480. 'x ' +
  2481. curve( cornerBefore, floor( beforeX / 3 ), floor( beforeY / 3 ), baseAngle + 45, 0, 1 ) +
  2482. line( side, floor( sideW / 3 ), 1, 0 ) +
  2483. curve( cornerAfter, floor( afterX / 3 ), floor( afterY / 3 ), baseAngle, 0, 0 )
  2484. : '' ) +
  2485. curve( cornerAfter, 0, 0, baseAngle - 45, 1, 0 ) +
  2486. line( side, 0, 1, 0 ) +
  2487. curve( cornerBefore, 0, 0, baseAngle, 1, 0 ),
  2488. fill: colors[ side ]
  2489. } );
  2490. }
  2491. }
  2492. }
  2493. addSide( 't', 'l', 'r', 'tl', 'tr', 90 );
  2494. addSide( 'r', 't', 'b', 'tr', 'br', 0 );
  2495. addSide( 'b', 'r', 'l', 'br', 'bl', -90 );
  2496. addSide( 'l', 'b', 't', 'bl', 'tl', -180 );
  2497. }
  2498. }
  2499. return segments;
  2500. },
  2501. destroy: function() {
  2502. PIE.RendererBase.destroy.call( this );
  2503. this.targetElement.runtimeStyle.borderColor = '';
  2504. }
  2505. } );
  2506. /**
  2507. * Renderer for border-image
  2508. * @constructor
  2509. * @param {Element} el The target element
  2510. * @param {Object} styleInfos The StyleInfo objects
  2511. * @param {PIE.RootRenderer} parent
  2512. */
  2513. PIE.BorderImageRenderer = PIE.RendererBase.newRenderer( {
  2514. boxZIndex: 5,
  2515. pieceNames: [ 't', 'tr', 'r', 'br', 'b', 'bl', 'l', 'tl', 'c' ],
  2516. needsUpdate: function() {
  2517. return this.styleInfos.borderImageInfo.changed();
  2518. },
  2519. isActive: function() {
  2520. return this.styleInfos.borderImageInfo.isActive();
  2521. },
  2522. draw: function() {
  2523. var props = this.styleInfos.borderImageInfo.getProps(),
  2524. bounds = this.boundsInfo.getBounds(),
  2525. box = this.getBox(), //make sure pieces are created
  2526. el = this.targetElement,
  2527. pieces = this.pieces;
  2528. PIE.Util.withImageSize( props.src, function( imgSize ) {
  2529. var elW = bounds.w,
  2530. elH = bounds.h,
  2531. widths = props.width,
  2532. widthT = widths.t.pixels( el ),
  2533. widthR = widths.r.pixels( el ),
  2534. widthB = widths.b.pixels( el ),
  2535. widthL = widths.l.pixels( el ),
  2536. slices = props.slice,
  2537. sliceT = slices.t.pixels( el ),
  2538. sliceR = slices.r.pixels( el ),
  2539. sliceB = slices.b.pixels( el ),
  2540. sliceL = slices.l.pixels( el );
  2541. // Piece positions and sizes
  2542. function setSizeAndPos( piece, w, h, x, y ) {
  2543. var s = pieces[piece].style;
  2544. s.width = w;
  2545. s.height = h;
  2546. s.left = x;
  2547. s.top = y;
  2548. }
  2549. setSizeAndPos( 'tl', widthL, widthT, 0, 0 );
  2550. setSizeAndPos( 't', elW - widthL - widthR, widthT, widthL, 0 );
  2551. setSizeAndPos( 'tr', widthR, widthT, elW - widthR, 0 );
  2552. setSizeAndPos( 'r', widthR, elH - widthT - widthB, elW - widthR, widthT );
  2553. setSizeAndPos( 'br', widthR, widthB, elW - widthR, elH - widthB );
  2554. setSizeAndPos( 'b', elW - widthL - widthR, widthB, widthL, elH - widthB );
  2555. setSizeAndPos( 'bl', widthL, widthB, 0, elH - widthB );
  2556. setSizeAndPos( 'l', widthL, elH - widthT - widthB, 0, widthT );
  2557. setSizeAndPos( 'c', elW - widthL - widthR, elH - widthT - widthB, widthL, widthT );
  2558. // image croppings
  2559. function setCrops( sides, crop, val ) {
  2560. for( var i=0, len=sides.length; i < len; i++ ) {
  2561. pieces[ sides[i] ]['imagedata'][ crop ] = val;
  2562. }
  2563. }
  2564. // corners
  2565. setCrops( [ 'tl', 't', 'tr' ], 'cropBottom', ( imgSize.h - sliceT ) / imgSize.h );
  2566. setCrops( [ 'tl', 'l', 'bl' ], 'cropRight', ( imgSize.w - sliceL ) / imgSize.w );
  2567. setCrops( [ 'bl', 'b', 'br' ], 'cropTop', ( imgSize.h - sliceB ) / imgSize.h );
  2568. setCrops( [ 'tr', 'r', 'br' ], 'cropLeft', ( imgSize.w - sliceR ) / imgSize.w );
  2569. // edges and center
  2570. if( props.repeat.v === 'stretch' ) {
  2571. setCrops( [ 'l', 'r', 'c' ], 'cropTop', sliceT / imgSize.h );
  2572. setCrops( [ 'l', 'r', 'c' ], 'cropBottom', sliceB / imgSize.h );
  2573. }
  2574. if( props.repeat.h === 'stretch' ) {
  2575. setCrops( [ 't', 'b', 'c' ], 'cropLeft', sliceL / imgSize.w );
  2576. setCrops( [ 't', 'b', 'c' ], 'cropRight', sliceR / imgSize.w );
  2577. }
  2578. // center fill
  2579. pieces['c'].style.display = props.fill ? '' : 'none';
  2580. }, this );
  2581. },
  2582. getBox: function() {
  2583. var box = this.parent.getLayer( this.boxZIndex ),
  2584. s, piece, i,
  2585. pieceNames = this.pieceNames,
  2586. len = pieceNames.length;
  2587. if( !box ) {
  2588. box = doc.createElement( 'border-image' );
  2589. s = box.style;
  2590. s.position = 'absolute';
  2591. this.pieces = {};
  2592. for( i = 0; i < len; i++ ) {
  2593. piece = this.pieces[ pieceNames[i] ] = PIE.Util.createVmlElement( 'rect' );
  2594. piece.appendChild( PIE.Util.createVmlElement( 'imagedata' ) );
  2595. s = piece.style;
  2596. s['behavior'] = 'url(#default#VML)';
  2597. s.position = "absolute";
  2598. s.top = s.left = 0;
  2599. piece['imagedata'].src = this.styleInfos.borderImageInfo.getProps().src;
  2600. piece.stroked = false;
  2601. piece.filled = false;
  2602. box.appendChild( piece );
  2603. }
  2604. this.parent.addLayer( this.boxZIndex, box );
  2605. }
  2606. return box;
  2607. }
  2608. } );
  2609. /**
  2610. * Renderer for outset box-shadows
  2611. * @constructor
  2612. * @param {Element} el The target element
  2613. * @param {Object} styleInfos The StyleInfo objects
  2614. * @param {PIE.RootRenderer} parent
  2615. */
  2616. PIE.BoxShadowOutsetRenderer = PIE.RendererBase.newRenderer( {
  2617. boxZIndex: 1,
  2618. boxName: 'outset-box-shadow',
  2619. needsUpdate: function() {
  2620. var si = this.styleInfos;
  2621. return si.boxShadowInfo.changed() || si.borderRadiusInfo.changed();
  2622. },
  2623. isActive: function() {
  2624. var boxShadowInfo = this.styleInfos.boxShadowInfo;
  2625. return boxShadowInfo.isActive() && boxShadowInfo.getProps().outset[0];
  2626. },
  2627. draw: function() {
  2628. var me = this,
  2629. el = this.targetElement,
  2630. box = this.getBox(),
  2631. styleInfos = this.styleInfos,
  2632. shadowInfos = styleInfos.boxShadowInfo.getProps().outset,
  2633. radii = styleInfos.borderRadiusInfo.getProps(),
  2634. len = shadowInfos.length,
  2635. i = len, j,
  2636. bounds = this.boundsInfo.getBounds(),
  2637. w = bounds.w,
  2638. h = bounds.h,
  2639. clipAdjust = PIE.ieVersion === 8 ? 1 : 0, //workaround for IE8 bug where VML leaks out top/left of clip region by 1px
  2640. corners = [ 'tl', 'tr', 'br', 'bl' ], corner,
  2641. shadowInfo, shape, fill, ss, xOff, yOff, spread, blur, shrink, color, alpha, path,
  2642. totalW, totalH, focusX, focusY, isBottom, isRight;
  2643. function getShadowShape( index, corner, xOff, yOff, color, blur, path ) {
  2644. var shape = me.getShape( 'shadow' + index + corner, 'fill', box, len - index ),
  2645. fill = shape.fill;
  2646. // Position and size
  2647. shape['coordsize'] = w * 2 + ',' + h * 2;
  2648. shape['coordorigin'] = '1,1';
  2649. // Color and opacity
  2650. shape['stroked'] = false;
  2651. shape['filled'] = true;
  2652. fill.color = color.value( el );
  2653. if( blur ) {
  2654. fill['type'] = 'gradienttitle'; //makes the VML gradient follow the shape's outline - hooray for undocumented features?!?!
  2655. fill['color2'] = fill.color;
  2656. fill['opacity'] = 0;
  2657. }
  2658. // Path
  2659. shape.path = path;
  2660. // This needs to go last for some reason, to prevent rendering at incorrect size
  2661. ss = shape.style;
  2662. ss.left = xOff;
  2663. ss.top = yOff;
  2664. ss.width = w;
  2665. ss.height = h;
  2666. return shape;
  2667. }
  2668. while( i-- ) {
  2669. shadowInfo = shadowInfos[ i ];
  2670. xOff = shadowInfo.xOffset.pixels( el );
  2671. yOff = shadowInfo.yOffset.pixels( el );
  2672. spread = shadowInfo.spread.pixels( el ),
  2673. blur = shadowInfo.blur.pixels( el );
  2674. color = shadowInfo.color;
  2675. // Shape path
  2676. shrink = -spread - blur;
  2677. if( !radii && blur ) {
  2678. // If blurring, use a non-null border radius info object so that getBoxPath will
  2679. // round the corners of the expanded shadow shape rather than squaring them off.
  2680. radii = PIE.BorderRadiusStyleInfo.ALL_ZERO;
  2681. }
  2682. path = this.getBoxPath( { t: shrink, r: shrink, b: shrink, l: shrink }, 2, radii );
  2683. if( blur ) {
  2684. totalW = ( spread + blur ) * 2 + w;
  2685. totalH = ( spread + blur ) * 2 + h;
  2686. focusX = blur * 2 / totalW;
  2687. focusY = blur * 2 / totalH;
  2688. if( blur - spread > w / 2 || blur - spread > h / 2 ) {
  2689. // If the blur is larger than half the element's narrowest dimension, we cannot do
  2690. // this with a single shape gradient, because its focussize would have to be less than
  2691. // zero which results in ugly artifacts. Instead we create four shapes, each with its
  2692. // gradient focus past center, and then clip them so each only shows the quadrant
  2693. // opposite the focus.
  2694. for( j = 4; j--; ) {
  2695. corner = corners[j];
  2696. isBottom = corner.charAt( 0 ) === 'b';
  2697. isRight = corner.charAt( 1 ) === 'r';
  2698. shape = getShadowShape( i, corner, xOff, yOff, color, blur, path );
  2699. fill = shape.fill;
  2700. fill['focusposition'] = ( isRight ? 1 - focusX : focusX ) + ',' +
  2701. ( isBottom ? 1 - focusY : focusY );
  2702. fill['focussize'] = '0,0';
  2703. // Clip to show only the appropriate quadrant. Add 1px to the top/left clip values
  2704. // in IE8 to prevent a bug where IE8 displays one pixel outside the clip region.
  2705. shape.style.clip = 'rect(' + ( ( isBottom ? totalH / 2 : 0 ) + clipAdjust ) + 'px,' +
  2706. ( isRight ? totalW : totalW / 2 ) + 'px,' +
  2707. ( isBottom ? totalH : totalH / 2 ) + 'px,' +
  2708. ( ( isRight ? totalW / 2 : 0 ) + clipAdjust ) + 'px)';
  2709. }
  2710. } else {
  2711. // TODO delete old quadrant shapes if resizing expands past the barrier
  2712. shape = getShadowShape( i, '', xOff, yOff, color, blur, path );
  2713. fill = shape.fill;
  2714. fill['focusposition'] = focusX + ',' + focusY;
  2715. fill['focussize'] = ( 1 - focusX * 2 ) + ',' + ( 1 - focusY * 2 );
  2716. }
  2717. } else {
  2718. shape = getShadowShape( i, '', xOff, yOff, color, blur, path );
  2719. alpha = color.alpha();
  2720. if( alpha < 1 ) {
  2721. // shape.style.filter = 'alpha(opacity=' + ( alpha * 100 ) + ')';
  2722. // ss.filter = 'progid:DXImageTransform.Microsoft.BasicImage(opacity=' + ( alpha ) + ')';
  2723. shape.fill.opacity = alpha;
  2724. }
  2725. }
  2726. }
  2727. }
  2728. } );
  2729. /**
  2730. * Renderer for re-rendering img elements using VML. Kicks in if the img has
  2731. * a border-radius applied, or if the -pie-png-fix flag is set.
  2732. * @constructor
  2733. * @param {Element} el The target element
  2734. * @param {Object} styleInfos The StyleInfo objects
  2735. * @param {PIE.RootRenderer} parent
  2736. */
  2737. PIE.ImgRenderer = PIE.RendererBase.newRenderer( {
  2738. boxZIndex: 6,
  2739. boxName: 'imgEl',
  2740. needsUpdate: function() {
  2741. var si = this.styleInfos;
  2742. return this.targetElement.src !== this._lastSrc || si.borderRadiusInfo.changed();
  2743. },
  2744. isActive: function() {
  2745. var si = this.styleInfos;
  2746. return si.borderRadiusInfo.isActive() || si.backgroundInfo.isPngFix();
  2747. },
  2748. draw: function() {
  2749. this.hideActualImg();
  2750. var shape = this.getShape( 'img', 'fill', this.getBox() ),
  2751. fill = shape.fill,
  2752. bounds = this.boundsInfo.getBounds(),
  2753. w = bounds.w,
  2754. h = bounds.h,
  2755. borderProps = this.styleInfos.borderInfo.getProps(),
  2756. borderWidths = borderProps && borderProps.widths,
  2757. el = this.targetElement,
  2758. src = el.src,
  2759. round = Math.round,
  2760. s;
  2761. shape.stroked = false;
  2762. fill.type = 'frame';
  2763. fill.src = src;
  2764. fill.position = (0.5 / w) + ',' + (0.5 / h);
  2765. shape.coordsize = w * 2 + ',' + h * 2;
  2766. shape.coordorigin = '1,1';
  2767. shape.path = this.getBoxPath( borderWidths ? {
  2768. t: round( borderWidths['t'].pixels( el ) ),
  2769. r: round( borderWidths['r'].pixels( el ) ),
  2770. b: round( borderWidths['b'].pixels( el ) ),
  2771. l: round( borderWidths['l'].pixels( el ) )
  2772. } : 0, 2 );
  2773. s = shape.style;
  2774. s.width = w;
  2775. s.height = h;
  2776. this._lastSrc = src;
  2777. },
  2778. hideActualImg: function() {
  2779. this.targetElement.runtimeStyle.filter = 'alpha(opacity=0)';
  2780. },
  2781. destroy: function() {
  2782. PIE.RendererBase.destroy.call( this );
  2783. this.targetElement.runtimeStyle.filter = '';
  2784. }
  2785. } );
  2786. PIE.Element = (function() {
  2787. var wrappers = {},
  2788. lazyInitCssProp = PIE.CSS_PREFIX + 'lazy-init',
  2789. hoverClass = ' ' + PIE.CLASS_PREFIX + 'hover',
  2790. hoverClassRE = new RegExp( '\\b' + PIE.CLASS_PREFIX + 'hover\\b', 'g' ),
  2791. ignorePropertyNames = { 'background':1, 'bgColor':1, 'display': 1 };
  2792. function addListener( el, type, handler ) {
  2793. el.attachEvent( type, handler );
  2794. }
  2795. function removeListener( el, type, handler ) {
  2796. el.detachEvent( type, handler );
  2797. }
  2798. function Element( el ) {
  2799. var renderers,
  2800. boundsInfo = new PIE.BoundsInfo( el ),
  2801. styleInfos,
  2802. styleInfosArr,
  2803. ancestors,
  2804. initializing,
  2805. initialized,
  2806. eventsAttached,
  2807. delayed,
  2808. destroyed;
  2809. /**
  2810. * Initialize PIE for this element.
  2811. */
  2812. function init() {
  2813. if( !initialized ) {
  2814. var docEl,
  2815. bounds,
  2816. lazy = el.currentStyle.getAttribute( lazyInitCssProp ) === 'true',
  2817. rootRenderer;
  2818. // Force layout so move/resize events will fire. Set this as soon as possible to avoid layout changes
  2819. // after load, but make sure it only gets called the first time through to avoid recursive calls to init().
  2820. if( !initializing ) {
  2821. initializing = 1;
  2822. el.runtimeStyle.zoom = 1;
  2823. initFirstChildPseudoClass();
  2824. }
  2825. boundsInfo.lock();
  2826. // If the -pie-lazy-init:true flag is set, check if the element is outside the viewport and if so, delay initialization
  2827. if( lazy && ( bounds = boundsInfo.getBounds() ) && ( docEl = doc.documentElement || doc.body ) &&
  2828. ( bounds.y > docEl.clientHeight || bounds.x > docEl.clientWidth || bounds.y + bounds.h < 0 || bounds.x + bounds.w < 0 ) ) {
  2829. if( !delayed ) {
  2830. delayed = 1;
  2831. PIE.OnScroll.observe( init );
  2832. }
  2833. } else {
  2834. initialized = 1;
  2835. delayed = initializing = 0;
  2836. PIE.OnScroll.unobserve( init );
  2837. // Create the style infos and renderers
  2838. styleInfos = {
  2839. backgroundInfo: new PIE.BackgroundStyleInfo( el ),
  2840. borderInfo: new PIE.BorderStyleInfo( el ),
  2841. borderImageInfo: new PIE.BorderImageStyleInfo( el ),
  2842. borderRadiusInfo: new PIE.BorderRadiusStyleInfo( el ),
  2843. boxShadowInfo: new PIE.BoxShadowStyleInfo( el ),
  2844. visibilityInfo: new PIE.VisibilityStyleInfo( el )
  2845. };
  2846. styleInfosArr = [
  2847. styleInfos.backgroundInfo,
  2848. styleInfos.borderInfo,
  2849. styleInfos.borderImageInfo,
  2850. styleInfos.borderRadiusInfo,
  2851. styleInfos.boxShadowInfo,
  2852. styleInfos.visibilityInfo
  2853. ];
  2854. rootRenderer = new PIE.RootRenderer( el, boundsInfo, styleInfos );
  2855. var childRenderers = [
  2856. new PIE.BoxShadowOutsetRenderer( el, boundsInfo, styleInfos, rootRenderer ),
  2857. new PIE.BackgroundRenderer( el, boundsInfo, styleInfos, rootRenderer ),
  2858. //new PIE.BoxShadowInsetRenderer( el, boundsInfo, styleInfos, rootRenderer ),
  2859. new PIE.BorderRenderer( el, boundsInfo, styleInfos, rootRenderer ),
  2860. new PIE.BorderImageRenderer( el, boundsInfo, styleInfos, rootRenderer )
  2861. ];
  2862. if( el.tagName === 'IMG' ) {
  2863. childRenderers.push( new PIE.ImgRenderer( el, boundsInfo, styleInfos, rootRenderer ) );
  2864. }
  2865. rootRenderer.childRenderers = childRenderers; // circular reference, can't pass in constructor; TODO is there a cleaner way?
  2866. renderers = [ rootRenderer ].concat( childRenderers );
  2867. // Add property change listeners to ancestors if requested
  2868. initAncestorPropChangeListeners();
  2869. // Add to list of polled elements in IE8
  2870. if( PIE.ie8DocMode === 8 ) {
  2871. PIE.Heartbeat.observe( update );
  2872. }
  2873. // Trigger rendering
  2874. update( 1 );
  2875. }
  2876. if( !eventsAttached ) {
  2877. eventsAttached = 1;
  2878. addListener( el, 'onmove', handleMoveOrResize );
  2879. addListener( el, 'onresize', handleMoveOrResize );
  2880. addListener( el, 'onpropertychange', propChanged );
  2881. addListener( el, 'onmouseenter', mouseEntered );
  2882. addListener( el, 'onmouseleave', mouseLeft );
  2883. PIE.OnResize.observe( handleMoveOrResize );
  2884. PIE.OnBeforeUnload.observe( removeEventListeners );
  2885. }
  2886. boundsInfo.unlock();
  2887. }
  2888. }
  2889. /**
  2890. * Event handler for onmove and onresize events. Invokes update() only if the element's
  2891. * bounds have previously been calculated, to prevent multiple runs during page load when
  2892. * the element has no initial CSS3 properties.
  2893. */
  2894. function handleMoveOrResize() {
  2895. if( boundsInfo && boundsInfo.hasBeenQueried() ) {
  2896. update();
  2897. }
  2898. }
  2899. /**
  2900. * Update position and/or size as necessary. Both move and resize events call
  2901. * this rather than the updatePos/Size functions because sometimes, particularly
  2902. * during page load, one will fire but the other won't.
  2903. */
  2904. function update( force ) {
  2905. if( !destroyed ) {
  2906. if( initialized ) {
  2907. var i, len;
  2908. lockAll();
  2909. if( force || boundsInfo.positionChanged() ) {
  2910. /* TODO just using getBoundingClientRect (used internally by BoundsInfo) for detecting
  2911. position changes may not always be accurate; it's possible that
  2912. an element will actually move relative to its positioning parent, but its position
  2913. relative to the viewport will stay the same. Need to come up with a better way to
  2914. track movement. The most accurate would be the same logic used in RootRenderer.updatePos()
  2915. but that is a more expensive operation since it does some DOM walking, and we want this
  2916. check to be as fast as possible. */
  2917. for( i = 0, len = renderers.length; i < len; i++ ) {
  2918. renderers[i].updatePos();
  2919. }
  2920. }
  2921. if( force || boundsInfo.sizeChanged() ) {
  2922. for( i = 0, len = renderers.length; i < len; i++ ) {
  2923. renderers[i].updateSize();
  2924. }
  2925. }
  2926. unlockAll();
  2927. }
  2928. else if( !initializing ) {
  2929. init();
  2930. }
  2931. }
  2932. }
  2933. /**
  2934. * Handle property changes to trigger update when appropriate.
  2935. */
  2936. function propChanged() {
  2937. var i, len, renderer,
  2938. e = event;
  2939. // Some elements like <table> fire onpropertychange events for old-school background properties
  2940. // ('background', 'bgColor') when runtimeStyle background properties are changed, which
  2941. // results in an infinite loop; therefore we filter out those property names. Also, 'display'
  2942. // is ignored because size calculations don't work correctly immediately when its onpropertychange
  2943. // event fires, and because it will trigger an onresize event anyway.
  2944. if( !destroyed && !( e && e.propertyName in ignorePropertyNames ) ) {
  2945. if( initialized ) {
  2946. lockAll();
  2947. for( i = 0, len = renderers.length; i < len; i++ ) {
  2948. renderer = renderers[i];
  2949. // Make sure position is synced if the element hasn't already been rendered.
  2950. // TODO this feels sloppy - look into merging propChanged and update functions
  2951. if( !renderer.isPositioned ) {
  2952. renderer.updatePos();
  2953. }
  2954. if( renderer.needsUpdate() ) {
  2955. renderer.updateProps();
  2956. }
  2957. }
  2958. unlockAll();
  2959. }
  2960. else if( !initializing ) {
  2961. init();
  2962. }
  2963. }
  2964. }
  2965. function addHoverClass() {
  2966. el.className += hoverClass;
  2967. }
  2968. function removeHoverClass() {
  2969. el.className = el.className.replace( hoverClassRE, '' );
  2970. }
  2971. /**
  2972. * Handle mouseenter events. Adds a custom class to the element to allow IE6 to add
  2973. * hover styles to non-link elements, and to trigger a propertychange update.
  2974. */
  2975. function mouseEntered() {
  2976. //must delay this because the mouseenter event fires before the :hover styles are added.
  2977. setTimeout( addHoverClass, 0 );
  2978. }
  2979. /**
  2980. * Handle mouseleave events
  2981. */
  2982. function mouseLeft() {
  2983. //must delay this because the mouseleave event fires before the :hover styles are removed.
  2984. setTimeout( removeHoverClass, 0 );
  2985. }
  2986. /**
  2987. * Handle property changes on ancestors of the element; see initAncestorPropChangeListeners()
  2988. * which adds these listeners as requested with the -pie-watch-ancestors CSS property.
  2989. */
  2990. function ancestorPropChanged() {
  2991. var name = event.propertyName;
  2992. if( name === 'className' || name === 'id' ) {
  2993. propChanged();
  2994. }
  2995. }
  2996. function lockAll() {
  2997. boundsInfo.lock();
  2998. for( var i = styleInfosArr.length; i--; ) {
  2999. styleInfosArr[i].lock();
  3000. }
  3001. }
  3002. function unlockAll() {
  3003. for( var i = styleInfosArr.length; i--; ) {
  3004. styleInfosArr[i].unlock();
  3005. }
  3006. boundsInfo.unlock();
  3007. }
  3008. /**
  3009. * Remove all event listeners from the element and any monitoried ancestors.
  3010. */
  3011. function removeEventListeners() {
  3012. if (eventsAttached) {
  3013. if( ancestors ) {
  3014. for( var i = 0, len = ancestors.length, a; i < len; i++ ) {
  3015. a = ancestors[i];
  3016. removeListener( a, 'onpropertychange', ancestorPropChanged );
  3017. removeListener( a, 'onmouseenter', mouseEntered );
  3018. removeListener( a, 'onmouseleave', mouseLeft );
  3019. }
  3020. }
  3021. // Remove event listeners
  3022. removeListener( el, 'onmove', update );
  3023. removeListener( el, 'onresize', update );
  3024. removeListener( el, 'onpropertychange', propChanged );
  3025. removeListener( el, 'onmouseenter', mouseEntered );
  3026. removeListener( el, 'onmouseleave', mouseLeft );
  3027. PIE.OnBeforeUnload.unobserve( removeEventListeners );
  3028. eventsAttached = 0;
  3029. }
  3030. }
  3031. /**
  3032. * Clean everything up when the behavior is removed from the element, or the element
  3033. * is manually destroyed.
  3034. */
  3035. function destroy() {
  3036. if( !destroyed ) {
  3037. var i, len;
  3038. removeEventListeners();
  3039. destroyed = 1;
  3040. // destroy any active renderers
  3041. if( renderers ) {
  3042. for( i = 0, len = renderers.length; i < len; i++ ) {
  3043. renderers[i].destroy();
  3044. }
  3045. }
  3046. // Remove from list of polled elements in IE8
  3047. if( PIE.ie8DocMode === 8 ) {
  3048. PIE.Heartbeat.unobserve( update );
  3049. }
  3050. // Stop onresize listening
  3051. PIE.OnResize.unobserve( update );
  3052. // Kill references
  3053. renderers = boundsInfo = styleInfos = styleInfosArr = ancestors = el = null;
  3054. }
  3055. }
  3056. /**
  3057. * If requested via the custom -pie-watch-ancestors CSS property, add onpropertychange listeners
  3058. * to ancestor(s) of the element so we can pick up style changes based on CSS rules using
  3059. * descendant selectors.
  3060. */
  3061. function initAncestorPropChangeListeners() {
  3062. var watch = el.currentStyle.getAttribute( PIE.CSS_PREFIX + 'watch-ancestors' ),
  3063. i, a;
  3064. if( watch ) {
  3065. ancestors = [];
  3066. watch = parseInt( watch, 10 );
  3067. i = 0;
  3068. a = el.parentNode;
  3069. while( a && ( watch === 'NaN' || i++ < watch ) ) {
  3070. ancestors.push( a );
  3071. addListener( a, 'onpropertychange', ancestorPropChanged );
  3072. addListener( a, 'onmouseenter', mouseEntered );
  3073. addListener( a, 'onmouseleave', mouseLeft );
  3074. a = a.parentNode;
  3075. }
  3076. }
  3077. }
  3078. /**
  3079. * If the target element is a first child, add a pie_first-child class to it. This allows using
  3080. * the added class as a workaround for the fact that PIE's rendering element breaks the :first-child
  3081. * pseudo-class selector.
  3082. */
  3083. function initFirstChildPseudoClass() {
  3084. var tmpEl = el,
  3085. isFirst = 1;
  3086. while( tmpEl = tmpEl.previousSibling ) {
  3087. if( tmpEl.nodeType === 1 ) {
  3088. isFirst = 0;
  3089. break;
  3090. }
  3091. }
  3092. if( isFirst ) {
  3093. el.className += ' ' + PIE.CLASS_PREFIX + 'first-child';
  3094. }
  3095. }
  3096. // These methods are all already bound to this instance so there's no need to wrap them
  3097. // in a closure to maintain the 'this' scope object when calling them.
  3098. this.init = init;
  3099. this.update = update;
  3100. this.destroy = destroy;
  3101. this.el = el;
  3102. }
  3103. Element.getInstance = function( el ) {
  3104. var id = PIE.Util.getUID( el );
  3105. return wrappers[ id ] || ( wrappers[ id ] = new Element( el ) );
  3106. };
  3107. Element.destroy = function( el ) {
  3108. var id = PIE.Util.getUID( el ),
  3109. wrapper = wrappers[ id ];
  3110. if( wrapper ) {
  3111. wrapper.destroy();
  3112. delete wrappers[ id ];
  3113. }
  3114. };
  3115. Element.destroyAll = function() {
  3116. var els = [], wrapper;
  3117. if( wrappers ) {
  3118. for( var w in wrappers ) {
  3119. if( wrappers.hasOwnProperty( w ) ) {
  3120. wrapper = wrappers[ w ];
  3121. els.push( wrapper.el );
  3122. wrapper.destroy();
  3123. }
  3124. }
  3125. wrappers = {};
  3126. }
  3127. return els;
  3128. };
  3129. return Element;
  3130. })();
  3131. /*
  3132. * This file exposes the public API for invoking PIE.
  3133. */
  3134. /**
  3135. * Programatically attach PIE to a single element.
  3136. * @param {Element} el
  3137. */
  3138. PIE[ 'attach' ] = function( el ) {
  3139. if (PIE.ieVersion < 9) {
  3140. PIE.Element.getInstance( el ).init();
  3141. }
  3142. };
  3143. /**
  3144. * Programatically detach PIE from a single element.
  3145. * @param {Element} el
  3146. */
  3147. PIE[ 'detach' ] = function( el ) {
  3148. PIE.Element.destroy( el );
  3149. };
  3150. } // if( !PIE )
  3151. var p = window['PIE'],
  3152. el = element;
  3153. function init() {
  3154. if( doc.media !== 'print' ) { // IE strangely attaches a second copy of the behavior to elements when printing
  3155. p['attach']( el );
  3156. }
  3157. }
  3158. function cleanup() {
  3159. p['detach']( el );
  3160. p = el = 0;
  3161. }
  3162. if( el.readyState === 'complete' ) {
  3163. init();
  3164. }
  3165. </script>
  3166. </PUBLIC:COMPONENT>