Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Split the monolithic PIE.htc into separate source files per class/com…

…ponent. Introduce simple ant-based build to concatenate and compress all the individual files into the single htc file.
  • Loading branch information...
commit 3e71920cda8b497dbbf7c4e809f849f39b2850b5 1 parent 808847e
Jason Johnston authored
Showing with 2,377 additions and 2,379 deletions.
  1. +1 −1  .gitignore
  2. +0 −2,335 PIE.htc
  3. +56 −0 build.xml
  4. +24 −0 sources/Angle.js
  5. +582 −0 sources/BackgroundAndBorderRenderer.js
  6. +248 −0 sources/BackgroundStyleInfo.js
  7. +94 −0 sources/BgPosition.js
  8. +129 −0 sources/BorderImageRenderer.js
  9. +55 −0 sources/BorderImageStyleInfo.js
  10. +60 −0 sources/BorderRadiusStyleInfo.js
  11. +76 −0 sources/BorderStyleInfo.js
  12. +133 −0 sources/BoxShadowRenderer.js
  13. +50 −0 sources/BoxShadowStyleInfo.js
  14. +34 −0 sources/Color.js
  15. +113 −0 sources/Length.js
  16. +2 −0  sources/PIE_close.js
  17. +16 −0 sources/PIE_open.js
  18. +138 −0 sources/RendererBase.js
  19. +88 −0 sources/RootRenderer.js
  20. +32 −0 sources/StyleBase.js
  21. +197 −0 sources/Tokenizer.js
  22. +56 −0 sources/Util.js
  23. +138 −0 sources/event_handlers.js
  24. +3 −0  sources/htc_close.txt
  25. +9 −0 sources/htc_open.txt
  26. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-001.htm
  27. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-002.htm
  28. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-003.htm
  29. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-004.htm
  30. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-005.htm
  31. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-006.htm
  32. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-007.htm
  33. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-008.htm
  34. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-009.htm
  35. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-010.htm
  36. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-011.htm
  37. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-012.htm
  38. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-013.htm
  39. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-014.htm
  40. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-015.htm
  41. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-016.htm
  42. +1 −1  tests/IE9 border-radius tests/border-radius-applies-to-017.htm
  43. +1 −1  tests/IE9 border-radius tests/border-radius-different-width-001.htm
  44. +1 −1  tests/IE9 border-radius tests/border-radius-initial-value-001.htm
  45. +2 −2 tests/IE9 border-radius tests/border-radius-not-inherited-001.htm
  46. +4 −4 tests/IE9 border-radius tests/border-radius-shorthand-001.htm
  47. +1 −1  tests/IE9 border-radius tests/border-radius-style-001.htm
  48. +1 −1  tests/IE9 border-radius tests/border-radius-style-002.htm
  49. +1 −1  tests/IE9 border-radius tests/border-radius-style-003.htm
  50. +1 −1  tests/IE9 border-radius tests/border-radius-style-004.htm
  51. +1 −1  tests/IE9 border-radius tests/border-radius-style-005.htm
  52. +1 −1  tests/IE9 border-radius tests/border-radius-sum-of-radii-001.htm
  53. +1 −1  tests/IE9 border-radius tests/border-radius-sum-of-radii-002.htm
  54. +1 −1  tests/IE9 border-radius tests/border-radius-with-three-values-001.htm
  55. +1 −1  tests/IE9 border-radius tests/border-radius-with-two-values-001.htm
  56. +1 −1  tests/IE9 border-radius tests/border-top-left-radius-values-001.htm
  57. +2 −2 tests/IE9 border-radius tests/border-top-left-radius-values-002.htm
  58. +1 −1  tests/IE9 border-radius tests/border-top-left-radius-values-003.htm
  59. +1 −1  tests/border-image-test.html
  60. +1 −1  tests/gradient-tests.html
  61. +1 −1  tests/multiple-bg-tests.html
  62. +1 −1  tests/stress-test.html
  63. +1 −1  tests/z-index-tests.html
2  .gitignore
View
@@ -1,2 +1,2 @@
.idea
-
+build
2,335 PIE.htc
View
0 additions, 2,335 deletions not shown
56 build.xml
View
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+
+<project name="PIE" default="package" basedir=".">
+
+ <property name="build_dir" value="./build" />
+ <property name="src_dir" value="./sources" />
+
+ <target name="package-uncompressed">
+ <mkdir dir="${build_dir}" />
+ <concat destfile="${build_dir}/script_uncompressed.js">
+ <fileset file="${src_dir}/PIE_open.js" />
+ <fileset file="${src_dir}/Util.js" />
+ <fileset file="${src_dir}/Length.js" />
+ <fileset file="${src_dir}/BgPosition.js" />
+ <fileset file="${src_dir}/Angle.js" />
+ <fileset file="${src_dir}/Color.js" />
+ <fileset file="${src_dir}/Tokenizer.js" />
+ <fileset file="${src_dir}/StyleBase.js" />
+ <fileset file="${src_dir}/BackgroundStyleInfo.js" />
+ <fileset file="${src_dir}/BorderStyleInfo.js" />
+ <fileset file="${src_dir}/BorderRadiusStyleInfo.js" />
+ <fileset file="${src_dir}/BorderImageStyleInfo.js" />
+ <fileset file="${src_dir}/BoxShadowStyleInfo.js" />
+ <fileset file="${src_dir}/RendererBase.js" />
+ <fileset file="${src_dir}/RootRenderer.js" />
+ <fileset file="${src_dir}/BackgroundAndBorderRenderer.js" />
+ <fileset file="${src_dir}/BorderImageRenderer.js" />
+ <fileset file="${src_dir}/BoxShadowRenderer.js" />
+ <fileset file="${src_dir}/PIE_close.js" />
+ <fileset file="${src_dir}/event_handlers.js" />
+ </concat>
+
+ <concat destfile="${build_dir}/PIE_uncompressed.htc">
+ <fileset file="${src_dir}/htc_open.txt" />
+ <fileset file="${build_dir}/script_uncompressed.js" />
+ <fileset file="${src_dir}/htc_close.txt" />
+ </concat>
+ </target>
+
+ <target name="package-compressed" depends="package-uncompressed">
+ <!--<copy file="${build_dir}/script_uncompressed.js" tofile="${build_dir}/script_compressed.js" overwrite="true" />-->
+
+ <exec executable="yuicompressor">
+ <arg line="${build_dir}/script_uncompressed.js -o ${build_dir}/script_compressed.js" />
+ </exec>
+
+ <concat destfile="${build_dir}/PIE.htc">
+ <fileset file="${src_dir}/htc_open.txt" />
+ <fileset file="${build_dir}/script_compressed.js" />
+ <fileset file="${src_dir}/htc_close.txt" />
+ </concat>
+ </target>
+
+ <target name="package" depends="package-uncompressed,package-compressed" />
+
+</project>
24 sources/Angle.js
View
@@ -0,0 +1,24 @@
+/**
+ * Wrapper for angle values; handles conversion to degrees from all allowed angle units
+ * @param val
+ */
+PIE.Angle = function( val ) {
+ this.val = val;
+};
+PIE.Angle.prototype = {
+ unitRE: /(deg|rad|grad|turn)$/,
+
+ getUnit: function() {
+ return this._unit || ( this._unit = this.val.match( this.unitRE )[1] );
+ },
+
+ degrees: function() {
+ var deg = this._deg, u, n;
+ if( !deg ) {
+ u = this.getUnit();
+ n = parseFloat( this.val, 10 );
+ deg = this._deg = ( u === 'deg' ? n : u === 'rad' ? n / Math.PI * 180 : u === 'grad' ? n / 400 * 360 : u === 'turn' ? n * 360 : 0 );
+ }
+ return deg;
+ }
+};
582 sources/BackgroundAndBorderRenderer.js
View
@@ -0,0 +1,582 @@
+PIE.BackgroundAndBorderRenderer = function( el, styleInfos, parent ) {
+ this.element = el;
+ this.styleInfos = styleInfos;
+ this.parent = parent;
+};
+PIE.Util.merge( PIE.BackgroundAndBorderRenderer.prototype, PIE.RendererBase, {
+
+ zIndex: 200,
+
+ needsUpdate: function() {
+ var si = this.styleInfos;
+ return si.border.changed() || si.background.changed();
+ },
+
+ isActive: function() {
+ var si = this.styleInfos;
+ return si.borderImage.isActive() ||
+ si.borderRadius.isActive() ||
+ si.background.isActive() ||
+ ( si.boxShadow.isActive() && si.boxShadow.getProps().inset );
+ },
+
+ updateSize: function() {
+ if( this.isActive() ) {
+ this.draw();
+ }
+ },
+
+ updateProps: function() {
+ this.destroy();
+ if( this.isActive() ) {
+ this.draw();
+ }
+ },
+
+ draw: function() {
+ this.drawBgColor();
+ this.drawBgImages();
+ this.drawBorder();
+ },
+
+ drawBgColor: function() {
+ var props = this.styleInfos.background.getProps(),
+ color = props && props.color && props.color.value(),
+ cont, el, shape, w, h, s, alpha;
+
+ if( color && color !== 'transparent' ) {
+ this.hideBackground();
+
+ cont = this.getBox();
+ el = this.element;
+ shape = this.getShape( 'bgColor', 'fill' );
+ w = el.offsetWidth;
+ h = el.offsetHeight;
+ shape.stroked = false;
+ shape.coordsize = w + ',' + h;
+ shape.path = this.getBoxPath();
+ s = shape.style;
+ s.width = w;
+ s.height = h;
+ s.zIndex = 1;
+ shape.fill.color = color;
+
+ alpha = props.color.alpha();
+ if( alpha < 1 ) {
+ shape.fill.opacity = alpha;
+ }
+ }
+ },
+
+ drawBgImages: function() {
+ var props = this.styleInfos.background.getProps(),
+ images = props && props.images,
+ img, cont, el, shape, w, h, s, i;
+
+ if( images ) {
+ this.hideBackground();
+
+ el = this.element;
+ w = el.offsetWidth,
+ h = el.offsetHeight,
+
+ i = images.length;
+ while( i-- ) {
+ img = images[i];
+ shape = this.getShape( 'bgImage' + i, 'fill' );
+
+ shape.stroked = false;
+ shape.fill.type = 'tile';
+ shape.fillcolor = 'none';
+ shape.coordsize = w + ',' + h;
+ shape.path = this.getBoxPath();
+ s = shape.style;
+ s.width = w;
+ s.height = h;
+ s.zIndex = 2;
+
+ if( img.type === 'linear-gradient' ) {
+ this.addLinearGradient( shape, img );
+ }
+ else {
+ shape.fill.src = img.url;
+ this.positionBgImage( shape, i );
+ }
+ }
+ }
+ },
+
+ positionBgImage: function( shape, index ) {
+ PIE.Util.withImageSize( shape.fill.src, function( size ) {
+ var fill = shape.fill,
+ el = this.element,
+ elW = el.offsetWidth,
+ elH = el.offsetHeight,
+ cs = el.currentStyle,
+ si = this.styleInfos,
+ border = si.border.getProps(),
+ bw = border && border.widths,
+ bwT = bw ? bw.t.pixels( el ) : 0,
+ bwR = bw ? bw.r.pixels( el ) : 0,
+ bwB = bw ? bw.b.pixels( el ) : 0,
+ bwL = bw ? bw.l.pixels( el ) : 0,
+ bg = si.background.getProps().images[ index ],
+ bgPos = bg.position.coords( el, elW - size.w - bwL - bwR, elH - size.h - bwT - bwB ),
+ repeat = bg.repeat,
+ pxX, pxY,
+ clipT = 0, clipR = elW, clipB = elH, clipL = 0;
+
+ // Positioning - find the pixel offset from the top/left and convert to a ratio
+ pxX = bgPos.x + bwL;
+ pxY = bgPos.y + bwT;
+ fill.position = ( pxX / elW ) + ',' + ( pxY / elH );
+
+ // Repeating - clip the image shape
+ if( repeat !== 'repeat' ) {
+ if( repeat === 'repeat-x' || repeat === 'no-repeat' ) {
+ clipT = pxY;
+ clipB = pxY + size.h;
+ }
+ if( repeat === 'repeat-y' || repeat === 'no-repeat' ) {
+ clipL = pxX;
+ clipR = pxX + size.w;
+ }
+ shape.style.clip = 'rect(' + clipT + 'px,' + clipR + 'px,' + clipB + 'px,' + clipL + 'px)';
+ }
+ }, this );
+ },
+
+ addLinearGradient: function( shape, info ) {
+ var el = this.element,
+ w = el.offsetWidth,
+ h = el.offsetHeight,
+ fill = shape.fill,
+ angle = info.angle,
+ startPos = info.gradientStart,
+ stops = info.stops,
+ stopCount = stops.length,
+ PI = Math.PI,
+ startX, startY,
+ endX, endY,
+ startCornerX, startCornerY,
+ endCornerX, endCornerY,
+ vmlAngle, vmlGradientLength, vmlColors,
+ deltaX, deltaY, lineLength,
+ stopPx, vmlOffsetPct,
+ p, i, j, before, after;
+
+ /**
+ * Find the point along a given line (defined by a starting point and an angle), at which
+ * that line is intersected by a perpendicular line extending through another point.
+ * @param x1 - x coord of the starting point
+ * @param y1 - y coord of the starting point
+ * @param angle - angle of the line extending from the starting point (in degrees)
+ * @param x2 - x coord of point along the perpendicular line
+ * @param y2 - y coord of point along the perpendicular line
+ * @return [ x, y ]
+ */
+ function perpendicularIntersect( x1, y1, angle, x2, y2 ) {
+ // Handle straight vertical and horizontal angles, for performance and to avoid
+ // divide-by-zero errors.
+ if( angle === 0 || angle === 180 ) {
+ return [ x2, y1 ];
+ }
+ else if( angle === 90 || angle === 270 ) {
+ return [ x1, y2 ];
+ }
+ else {
+ // General approach: determine the Ax+By=C formula for each line (the slope of the second
+ // line is the negative inverse of the first) and then solve for where both formulas have
+ // the same x/y values.
+ var a1 = Math.tan( -angle * PI / 180 ),
+ c1 = a1 * x1 - y1,
+ a2 = -1 / a1,
+ c2 = a2 * x2 - y2,
+ d = a2 - a1,
+ endX = ( c2 - c1 ) / d,
+ endY = ( a1 * c2 - a2 * c1 ) / d;
+ return [ endX, endY ];
+ }
+ }
+
+ // Find the "start" and "end" corners; these are the corners furthest along the gradient line.
+ // This is used below to find the start/end positions of the CSS3 gradient-line, and also in finding
+ // the total length of the VML rendered gradient-line corner to corner.
+ function findCorners() {
+ startCornerX = ( angle >= 90 && angle < 270 ) ? w : 0;
+ startCornerY = angle < 180 ? h : 0;
+ endCornerX = w - startCornerX;
+ endCornerY = h - startCornerY;
+ }
+
+ // Normalize the angle to a value between [0, 360)
+ function normalizeAngle() {
+ if( angle < 0 ) {
+ angle += 360;
+ }
+ angle = angle % 360;
+ }
+
+ // Find the distance between two points
+ function distance( p1, p2 ) {
+ var dx = p2[0] - p1[0],
+ dy = p2[1] - p1[1];
+ return Math.abs(
+ dx === 0 ? dy :
+ dy === 0 ? dx :
+ Math.sqrt( dx * dx + dy * dy )
+ );
+ }
+
+ // Find the start and end points of the gradient
+ if( startPos ) {
+ startPos = startPos.coords( el, w, h );
+ startX = startPos.x;
+ startY = startPos.y;
+ }
+ if( angle ) {
+ angle = angle.degrees();
+
+ normalizeAngle();
+ findCorners();
+
+ // If no start position was specified, then choose a corner as the starting point.
+ if( !startPos ) {
+ startX = startCornerX;
+ startY = startCornerY;
+ }
+
+ // Find the end position by extending a perpendicular line from the gradient-line which
+ // intersects the corner opposite from the starting corner.
+ p = perpendicularIntersect( startX, startY, angle, endCornerX, endCornerY );
+ endX = p[0];
+ endY = p[1];
+ }
+ else if( startPos ) {
+ // Start position but no angle specified: find the end point by rotating 180deg around the center
+ endX = w - startX;
+ endY = h - startY;
+ }
+ else {
+ // Neither position nor angle specified; create vertical gradient from top to bottom
+ startX = startY = endX = 0;
+ endY = h;
+ }
+ deltaX = endX - startX;
+ deltaY = endY - startY;
+
+ if( angle === undefined ) {
+ angle = -Math.atan2( deltaY, deltaX ) / PI * 180;
+ normalizeAngle();
+ findCorners();
+ }
+
+
+ // In VML land, the angle of the rendered gradient depends on the aspect ratio of the shape's
+ // bounding box; for example specifying a 45 deg angle actually results in a gradient
+ // drawn diagonally from one corner to its opposite corner, which will only appear to the
+ // viewer as 45 degrees if the shape is equilateral. We adjust for this by taking the x/y deltas
+ // between the start and end points, multiply one of them by the shape's aspect ratio,
+ // and get their arctangent, resulting in an appropriate VML angle.
+ vmlAngle = Math.atan2( deltaX * w / h, deltaY ) / PI * 180;
+
+ // VML angles are 180 degrees offset from CSS angles
+ vmlAngle += 180;
+ vmlAngle = vmlAngle % 360;
+
+ // Add all the stops to the VML 'colors' list, including the first and last stops.
+ // For each, we find its pixel offset along the gradient-line; if the offset of a stop is less
+ // than that of its predecessor we increase it to be equal. We then map that pixel offset to a
+ // percentage along the VML gradient-line, which runs from shape corner to corner.
+ lineLength = distance( [ startX, startY ], [ endX, endY ] );
+ /*vmlGradientLength = Math.abs(
+ Math.cos(
+ Math.atan2( endCornerX - startCornerX, endCornerY - startCornerY ) +
+ ( angle * PI / 180 )
+ ) *
+ Math.sqrt( w * w + h * h )
+ );*/
+ vmlGradientLength = distance( [ startCornerX, startCornerY ], perpendicularIntersect( startCornerX, startCornerY, angle, endCornerX, endCornerY ) );
+ vmlColors = [];
+ vmlOffsetPct = distance( [ startX, startY ], perpendicularIntersect( startX, startY, angle, startCornerX, startCornerY ) ) / vmlGradientLength * 100;
+
+ // Find the pixel offsets along the CSS3 gradient-line for each stop.
+ stopPx = [];
+ for( i = 0; i < stopCount; i++ ) {
+ stopPx.push( stops[i].offset ? stops[i].offset.pixels( el, lineLength ) :
+ i === 0 ? 0 : i === stopCount - 1 ? lineLength : null );
+ }
+ // Fill in gaps with evenly-spaced offsets
+ for( i = 1; i < stopCount; i++ ) {
+ if( stopPx[ i ] === null ) {
+ before = stopPx[ i - 1 ];
+ j = i;
+ do {
+ after = stopPx[ ++j ];
+ } while( after === null );
+ stopPx[ i ] = before + ( after - before ) / ( j - i + 1 );
+ }
+ // Make sure each stop's offset is no less than the one before it
+ stopPx[ i ] = Math.max( stopPx[ i ], stopPx[ i - 1 ] );
+ }
+
+ // Convert to percentage along the VML gradient line and add to the VML 'colors' value
+ for( i = 0; i < stopCount; i++ ) {
+ vmlColors.push(
+ ( vmlOffsetPct + ( stopPx[ i ] / vmlGradientLength * 100 ) ) + '% ' + stops[i].color.value()
+ );
+ }
+
+ // Now, finally, we're ready to render the gradient fill. Set the start and end colors to
+ // the first and last stop colors; this just sets outer bounds for the gradient.
+ fill.angle = vmlAngle;
+ fill.type = 'gradient';
+ fill.method = 'sigma';
+ fill.color = stops[0].color.value();
+ fill.color2 = stops[stopCount - 1].color.value();
+ fill.colors.value = vmlColors.join( ',' );
+ },
+
+ drawBorder: function() {
+ var cont = this.getBox(),
+ el = this.element,
+ cs = el.currentStyle,
+ w = el.offsetWidth,
+ h = el.offsetHeight,
+ props = this.styleInfos.border.getProps(),
+ styles, colors, widths,
+ side, shape, stroke, bColor, bWidth, bStyle, s;
+
+ if( props ) {
+ styles = props.styles;
+ colors = props.colors;
+ widths = props.widths;
+
+ this.hideBorder();
+
+ var segments = this.getBorderSegments();
+ for( var i=0; i<segments.length; i++) {
+ var seg = segments[i];
+ shape = this.getShape( 'borderPiece' + i, seg.stroke ? 'stroke' : 'fill' );
+ shape.coordsize = w + ',' + h;
+ shape.path = seg.path;
+ s = shape.style;
+ s.width = w;
+ s.height = h;
+ s.zIndex = 3;
+
+ shape.filled = !!seg.fill;
+ shape.stroked = !!seg.stroke;
+ if( seg.stroke ) {
+ stroke = shape.stroke;
+ stroke.weight = seg.weight + 'px';
+ stroke.color = seg.color.value();
+ stroke.dashstyle = seg.stroke === 'dashed' ? '2 2' : seg.stroke === 'dotted' ? '1 1' : 'solid';
+ stroke.linestyle = seg.stroke === 'double' && seg.weight > 2 ? 'ThinThin' : 'Single';
+ } else {
+ shape.fill.color = seg.fill.value();
+ }
+ }
+ }
+ },
+
+ /**
+ * Hide the actual background image and color of the element.
+ */
+ hideBackground: function() {
+ var rs = this.element.runtimeStyle;
+ rs.backgroundImage = 'none';
+ rs.backgroundColor = 'transparent';
+ },
+
+ /**
+ * Hide the actual border of the element. In IE7 and up we can just set its color to transparent;
+ * however IE6 does not support transparent borders so we have to get tricky with it.
+ */
+ hideBorder: function() {
+ var el = this.element;
+ if( PIE.isIE6 ) {
+ // Wrap all the element's children in a custom element, set the element to visiblity:hidden,
+ // and set the wrapper element to visiblity:visible. This hides the outer element's decorations
+ // (background and border) but displays all the contents.
+ // TODO find a better way to do this that doesn't mess up the DOM parent-child relationship,
+ // as this can interfere with other author scripts which add/modify/delete children. Look into
+ // using a compositor filter which masks the border.
+ if( el.childNodes.length !== 1 || el.firstChild.tagName !== 'ie6-mask' ) {
+ var cont = this.element.document.createElement( 'ie6-mask' );
+ cont.style.visibility = 'visible';
+ cont.style.zoom = 1;
+ while( el.firstChild ) {
+ cont.appendChild( el.firstChild );
+ }
+ el.appendChild( cont );
+ el.runtimeStyle.visibility = 'hidden';
+ }
+ } else {
+ el.runtimeStyle.borderColor = 'transparent';
+ }
+ },
+
+ getBorderSegments: function() {
+ var el = this.element,
+ elW, elH,
+ borderInfo = this.styleInfos.border,
+ segments = [],
+ deg = 65535,
+ floor, ceil, wT, wR, wB, wL,
+ borderProps, radiusInfo, radii, widths, styles, colors;
+
+ if( borderInfo.isActive() ) {
+ borderProps = borderInfo.getProps();
+
+ widths = borderProps.widths;
+ styles = borderProps.styles;
+ colors = borderProps.colors;
+
+ if( borderProps.widthsSame && borderProps.stylesSame && borderProps.colorsSame ) {
+ // shortcut for identical border on all sides - only need 1 stroked shape
+ wT = widths.t.pixels( el );
+ segments.push( {
+ path: this.getBoxPath( wT / 2 ),
+ stroke: styles.t,
+ color: colors.t,
+ weight: wT
+ } );
+ }
+ else {
+ elW = el.offsetWidth - 1;
+ elH = el.offsetHeight - 1;
+
+ wT = widths.t.pixels( el );
+ wR = widths.r.pixels( el );
+ wB = widths.b.pixels( el );
+ wL = widths.l.pixels( el );
+ var pxWidths = {
+ t: wT,
+ r: wR,
+ b: wB,
+ l: wL
+ };
+
+ radiusInfo = this.styleInfos.borderRadius;
+ if( radiusInfo.isActive() ) {
+ radii = this.getRadiiPixels( radiusInfo.getProps() );
+ }
+
+ floor = Math.floor;
+ ceil = Math.ceil;
+
+ function curve( corner, shrinkX, shrinkY, startAngle, ccw ) {
+ var rx = radii.x[ corner ],
+ ry = radii.y[ corner ],
+ deg = 65535,
+ isRight = corner.charAt( 1 ) === 'r',
+ isBottom = corner.charAt( 0 ) === 'b';
+ return ( rx > 0 && ry > 0 ) ?
+ ( isRight ? ceil( elW - rx ) : floor( rx ) ) + ',' + // center x
+ ( isBottom ? ceil( elH - ry ) : floor( ry ) ) + ',' + // center y
+ ( floor( rx ) - shrinkX ) + ',' + // width
+ ( floor( ry ) - shrinkY ) + ',' + // height
+ ( startAngle * deg ) + ',' + // start angle
+ ( 45 * deg * ( ccw ? 1 : -1 ) ) // angle change
+ : '';
+ }
+
+
+ function addSide( side, sideBefore, sideAfter, cornerBefore, cornerAfter, baseAngle ) {
+ var vert = side === 'l' || side === 'r',
+ beforeX, beforeY, afterX, afterY;
+
+ if( pxWidths[ side ] > 0 && styles[ side ] !== 'none' ) {
+ beforeX = pxWidths[ vert ? side : sideBefore ];
+ beforeY = pxWidths[ vert ? sideBefore : side ];
+ afterX = pxWidths[ vert ? side : sideAfter ];
+ afterY = pxWidths[ vert ? sideAfter : side ];
+
+ if( styles[ side ] === 'dashed' || styles[ side ] === 'dotted' ) {
+ segments.push( {
+ path: 'al' + curve( cornerBefore, beforeX, beforeY, baseAngle + 45, 0 ) +
+ 'ae' + curve( cornerBefore, 0, 0, baseAngle, 1 ),
+ fill: colors[ side ]
+ } );
+ segments.push( {
+ path: (
+ side === 't' ?
+ 'm' + floor( radii.x.tl ) + ',' + ceil( wT/2 ) +
+ 'l' + ceil( elW - radii.x.tr ) + ',' + ceil( wT/2 ) :
+ side === 'r' ?
+ 'm' + ceil( elW - wR/2 ) + ',' + floor( radii.y.tr ) +
+ 'l' + ceil( elW - wR/2 ) + ',' + ceil( elH - radii.y.br ) :
+ side === 'b' ?
+ 'm' + ceil( elW - radii.x.br ) + ',' + floor( elH - wB/2 ) +
+ 'l' + floor( radii.x.bl ) + ',' + floor( elH - wB/2 ) :
+ // side === 'l'
+ 'm' + floor( wL/2 ) + ',' + ceil( elH - radii.y.bl ) +
+ 'l' + floor( wL/2 ) + ',' + floor( radii.y.tl )
+ ),
+ stroke: styles[ side ],
+ weight: pxWidths[ side ],
+ color: colors[ side ]
+ } );
+ segments.push( {
+ path: 'al' + curve( cornerAfter, afterX, afterY, baseAngle, 0 ) +
+ 'ae' + curve( cornerAfter, 0, 0, baseAngle - 45, 1 ),
+ fill: colors[ side ]
+ } );
+ }
+ else {
+ segments.push( {
+ path: 'al' + curve( cornerBefore, beforeX, beforeY, baseAngle + 45, 0 ) +
+ 'ae' + curve( cornerAfter, afterX, afterY, baseAngle, 0 ) +
+
+ ( styles[ side ] === 'double' && pxWidths[ side ] > 2 ?
+ 'ae' + curve( cornerAfter, afterX - floor( afterX / 3 ), afterY - floor( afterY / 3 ), baseAngle - 45, 1 ) +
+ 'ae' + curve( cornerBefore, beforeX - floor( beforeX / 3 ), beforeY - floor( beforeY / 3 ), baseAngle, 1 ) +
+ 'x al' + curve( cornerBefore, floor( beforeX / 3 ), floor( beforeY / 3 ), baseAngle + 45, 0 ) +
+ 'ae' + curve( cornerAfter, floor( afterX / 3 ), floor( afterY / 3 ), baseAngle, 0 )
+ : '' ) +
+
+ 'ae' + curve( cornerAfter, 0, 0, baseAngle - 45, 1 ) +
+ 'ae' + curve( cornerBefore, 0, 0, baseAngle, 1 ),
+ fill: colors[ side ]
+ } );
+ }
+ }
+ }
+
+ addSide( 't', 'l', 'r', 'tl', 'tr', 90 );
+ addSide( 'r', 't', 'b', 'tr', 'br', 0 );
+ addSide( 'b', 'r', 'l', 'br', 'bl', -90 );
+ addSide( 'l', 'b', 't', 'bl', 'tl', -180 );
+ }
+ }
+
+ return segments;
+ },
+
+ getBox: function() {
+ var box = this._box,
+ infos = this.styleInfos,
+ s;
+
+ if( !box ) {
+ box = this._box = this.element.document.createElement( 'bg-and-border' );
+ s = box.style;
+ s.position = 'absolute';
+ s.zIndex = this.zIndex;
+ this.parent.getBox().appendChild( box );
+ }
+
+ return box;
+ },
+
+ destroy: function() {
+ var box = this._box;
+ if( box && box.parentNode ) {
+ box.parentNode.removeChild( box );
+ }
+ delete this._box;
+ delete this._shapes;
+ }
+
+} );
248 sources/BackgroundStyleInfo.js
View
@@ -0,0 +1,248 @@
+PIE.BackgroundStyleInfo = function( el ) {
+ this.element = el;
+};
+PIE.Util.merge( PIE.BackgroundStyleInfo.prototype, PIE.StyleBase, {
+
+ cssProperty: PIE.CSS_PREFIX + 'background',
+ styleProperty: PIE.STYLE_PREFIX + 'Background',
+
+ attachIdents: { scroll:1, fixed:1, local:1 },
+ repeatIdents: { 'repeat-x':1, 'repeat-y':1, 'repeat':1, 'no-repeat':1 },
+ originIdents: { 'padding-box':1, 'border-box':1, 'content-box':1 },
+ positionIdents: { top:1, right:1, bottom:1, left:1, center:1 },
+
+ /**
+ * For background styles, we support the -pie-background property but fall back to the standard
+ * backround* properties. The reason we have to use the prefixed version is that IE natively
+ * parses the standard properties and if it sees something it doesn't know how to parse, for example
+ * multiple values or gradient definitions, it will throw that away and not make it available through
+ * currentStyle.
+ *
+ * Format of return object:
+ * {
+ * color: new PIE.Color('red'),
+ * images: [
+ * {
+ * type: 'image',
+ * url: 'image.png',
+ * repeat: 'no-repeat',
+ * position: new PIE.BgPosition(...)
+ * },
+ * {
+ * type: 'linear-gradient',
+ * gradientStart: new PIE.BgPosition(...),
+ * angle: new PIE.Angle( '45deg' ),
+ * stops: [
+ * { color: new PIE.Color('blue'), offset: new Length( '0' ) },
+ * { color: new PIE.Color('red'), offset: new Length( '100%' ) }
+ * ]
+ * }
+ * ]
+ * }
+ * @param css
+ */
+ parseCss: function( css ) {
+ var el = this.element,
+ cs = el.currentStyle,
+ rs = el.runtimeStyle,
+ tokenizer, token, image,
+ tokType, tokVal,
+ positionIdents = this.positionIdents,
+ gradient, stop,
+ props = null;
+
+ function isBgPosToken( token ) {
+ return token.type === 'LENGTH' || token.type === 'PERCENT' || token.type === 'NUMBER' ||
+ ( token.type === 'IDENT' && token.value in positionIdents );
+ }
+
+ // If the CSS3-specific -pie-background property is present, parse it
+ if( this.getCss3() ) {
+ tokenizer = new PIE.Tokenizer( css );
+ props = { images: [] };
+ image = {};
+
+ while( token = tokenizer.next() ) {
+ tokType = token.type;
+ tokVal = token.value;
+
+ if( !image.type && tokType === 'FUNCTION' && tokVal === 'linear-gradient(' ) {
+ gradient = { stops: [], type: 'linear-gradient' };
+ stop = {};
+ while( token = tokenizer.next() ) {
+ tokType = token.type;
+ tokVal = token.value;
+
+ // If we reached the end of the function and had at least 2 stops, flush the info
+ if( tokType === 'CHAR' && tokVal === ')' ) {
+ if( stop.color ) {
+ gradient.stops.push( stop );
+ }
+ if( gradient.stops.length > 1 ) {
+ PIE.Util.merge( image, gradient );
+ }
+ break;
+ }
+
+ // Color stop - must start with color
+ if( tokType === 'COLOR' ) {
+ // if we already have an angle/position, make sure that the previous token was a comma
+ if( gradient.angle || gradient.gradientStart ) {
+ token = tokenizer.prev();
+ if( token.type !== 'OPERATOR' ) {
+ break; //fail
+ }
+ tokenizer.next();
+ }
+
+ stop = {
+ color: new PIE.Color( tokVal )
+ };
+ // check for offset following color
+ token = tokenizer.next();
+ if( token.type === 'LENGTH' || token.type === 'PERCENT' || token.type === 'NUMBER' ) {
+ stop.offset = new PIE.Length( token.value );
+ } else {
+ tokenizer.prev();
+ }
+ }
+ // Angle - can only appear in first spot
+ else if( tokType === 'ANGLE' && !gradient.angle && !stop.color && !gradient.stops.length ) {
+ gradient.angle = new PIE.Angle( token.value );
+ }
+ else if( isBgPosToken( token ) && !gradient.gradientStart && !stop.color && !gradient.stops.length ) {
+ tokenizer.prev();
+ gradient.gradientStart = new PIE.BgPosition(
+ tokenizer.until( function( t ) {
+ return !isBgPosToken( t );
+ }, false ).slice( 0, -1 )
+ );
+ tokenizer.prev();
+ }
+ else if( tokType === 'OPERATOR' && tokVal === ',' ) {
+ if( stop.color ) {
+ gradient.stops.push( stop );
+ stop = {};
+ }
+ }
+ else {
+ // Found something we didn't recognize; fail without adding image
+ break;
+ }
+ }
+ }
+ else if( !image.type && tokType === 'URL' ) {
+ image.url = tokVal;
+ image.type = 'image';
+ }
+ else if( isBgPosToken( token ) ) {
+ tokenizer.prev();
+ image.position = new PIE.BgPosition(
+ tokenizer.until( function( t ) {
+ return !isBgPosToken( t );
+ }, false ).slice( 0, -1 )
+ );
+ tokenizer.prev();
+ }
+ else if( tokType === 'IDENT' ) {
+ if( tokVal in this.repeatIdents ) {
+ image.repeat = tokVal;
+ }
+ // TODO attachment, origin
+ }
+ else if( tokType === 'COLOR' && !props.color ) {
+ props.color = new PIE.Color( tokVal );
+ }
+ else if( tokType === 'OPERATOR' ) {
+ /* TODO bg-size
+ if( tokVal === '/' ) {
+ }*/
+ if( tokVal === ',' && image.type ) {
+ props.images.push( image );
+ image = {};
+ }
+ }
+ else {
+ // Found something unrecognized; chuck everything
+ return null;
+ }
+ }
+
+ // leftovers
+ if( image.type ) {
+ props.images.push( image );
+ }
+ }
+
+ // Otherwise, use the standard background properties; let IE give us the values rather than parsing them
+ else {
+ this.withActualBg( function() {
+ var posX = cs.backgroundPositionX,
+ posY = cs.backgroundPositionY,
+ img = cs.backgroundImage,
+ color = cs.backgroundColor;
+
+ props = {};
+ if( color !== 'transparent' ) {
+ props.color = new PIE.Color( color )
+ }
+ if( img !== 'none' ) {
+ props.images = [ {
+ type: 'image',
+ url: img.replace( this.urlRE, "$1" ),
+ repeat: cs.backgroundRepeat,
+ position: new PIE.BgPosition( new PIE.Tokenizer( posX + ' ' + posY ).all() )
+ } ];
+ }
+ } );
+ }
+
+ return props;
+ },
+
+ /**
+ * Execute a function with the actual background styles (not overridden with runtimeStyle
+ * properties set by the renderers) available via currentStyle.
+ * @param fn
+ */
+ withActualBg: function( fn ) {
+ var rs = this.element.runtimeStyle,
+ rsImage = rs.backgroundImage,
+ rsColor = rs.backgroundColor,
+ ret;
+
+ rs.backgroundImage = rs.backgroundColor = '';
+
+ ret = fn.call( this );
+
+ rs.backgroundImage = rsImage;
+ rs.backgroundColor = rsColor;
+
+ return ret;
+ },
+
+ getCss: function() {
+ var cs = this.element.currentStyle;
+ return this.getCss3() ||
+ this.withActualBg( function() {
+ return cs.backgroundColor + ' ' + cs.backgroundImage + ' ' + cs.backgroundRepeat + ' ' +
+ cs.backgroundPositionX + ' ' + cs.backgroundPositionY;
+ } );
+ },
+
+ getCss3: function() {
+ var el = this.element;
+ return el.style[ this.styleProperty ] || el.currentStyle.getAttribute( this.cssProperty );
+ },
+
+ /**
+ * The isActive logic is slightly different, because getProps() always returns an object
+ * even if it is just falling back to the native background properties. But we only want
+ * to report is as being "active" if the -pie-background override property is present and
+ * parses successfully.
+ */
+ isActive: function() {
+ return this.getCss3() && !!this.getProps();
+ }
+
+} );
94 sources/BgPosition.js
View
@@ -0,0 +1,94 @@
+/**
+ * Wrapper for a CSS3 bg-position value. Takes up to 2 position keywords and 2 lengths/percentages.
+ * @param tokens
+ */
+PIE.BgPosition = function( tokens ) {
+ this.tokens = tokens;
+};
+PIE.BgPosition.prototype = {
+ /**
+ * Normalize the values into the form:
+ * [ xOffsetSide, xOffsetLength, yOffsetSide, yOffsetLength ]
+ * where: xOffsetSide is either 'left' or 'right',
+ * yOffsetSide is either 'top' or 'bottom',
+ * and x/yOffsetLength are both PIE.Length objects.
+ */
+ getValues: function() {
+ if( !this._values ) {
+ var tokens = this.tokens,
+ len = tokens.length,
+ ident_left = 'left',
+ ident_top = 'top',
+ ident_center = 'center',
+ ident_right = 'right',
+ ident_bottom = 'bottom',
+ length_zero = PIE.Length.ZERO,
+ length_fifty = new PIE.Length( '50%' ),
+ type, value,
+ vert_idents = { top: 1, center: 1, bottom: 1 },
+ horiz_idents = { left: 1, center: 1, right: 1 },
+ vals = [ ident_left, length_zero, ident_top, length_zero ];
+
+ // If only one value, the second is assumed to be 'center'
+ if( len === 1 ) {
+ tokens.push( { type: 'IDENT', value: 'center' } );
+ len++;
+ }
+
+ // Two values - CSS2
+ if( len === 2 ) {
+ // If both idents, they can appear in either order, so switch them if needed
+ if( tokens[0].type === 'IDENT' && tokens[1].type === 'IDENT' &&
+ tokens[0].value in vert_idents && tokens[1].value in horiz_idents ) {
+ tokens.push( tokens.shift() );
+ }
+ if( tokens[0].type === 'IDENT' ) {
+ if( tokens[0].value === 'center' ) {
+ vals[1] = length_fifty;
+ } else {
+ vals[0] = tokens[0].value;
+ }
+ }
+ else if( tokens[0].type === 'LENGTH' || tokens[0].type === 'PERCENT' ) {
+ vals[1] = new PIE.Length( tokens[0].value );
+ }
+ if( tokens[1].type === 'IDENT' ) {
+ if( tokens[1].value === 'center' ) {
+ vals[3] = length_fifty;
+ } else {
+ vals[2] = tokens[1].value;
+ }
+ }
+ else if( tokens[1].type === 'LENGTH' || tokens[1].type === 'PERCENT' ) {
+ vals[3] = new PIE.Length( tokens[1].value );
+ }
+ }
+
+ // Three or four values - CSS3
+ else {
+ // TODO
+ }
+
+ this._values = vals;
+ }
+ return this._values;
+ },
+
+ /**
+ * Find the coordinates of the background image from the upper-left corner of the background area
+ * @param {Element} el
+ * @param {Number} width - the width for percentages (background area width minus image width)
+ * @param {Number} height - the height for percentages (background area height minus image height)
+ * @return {Object} { x: Number, y: Number }
+ */
+ coords: function( el, width, height ) {
+ var vals = this.getValues(),
+ pxX = vals[1].pixels( el, width ),
+ pxY = vals[3].pixels( el, height );
+
+ return {
+ x: Math.round( vals[0] === 'right' ? width - pxX : pxX ),
+ y: Math.round( vals[2] === 'bottom' ? height - pxY : pxY )
+ };
+ }
+};
129 sources/BorderImageRenderer.js
View
@@ -0,0 +1,129 @@
+PIE.BorderImageRenderer = function( el, styleInfos, parent ) {
+ this.element = el;
+ this.styleInfos = styleInfos;
+ this.parent = parent;
+};
+PIE.Util.merge( PIE.BorderImageRenderer.prototype, PIE.RendererBase, {
+
+ zIndex: 400,
+ pieceNames: [ 't', 'tr', 'r', 'br', 'b', 'bl', 'l', 'tl', 'c' ],
+
+ needsUpdate: function() {
+ var si = this.styleInfos;
+ return si.borderImage.changed() || si.border.changed();
+ },
+
+ isActive: function() {
+ return this.styleInfos.borderImage.isActive();
+ },
+
+ updateSize: function() {
+ if( this.isActive() ) {
+ var props = this.styleInfos.borderImage.getProps(),
+ box = this.getBox(), //make sure pieces are created
+ el = this.element,
+ p = this.pieces;
+
+ PIE.Util.withImageSize( props.src, function( imgSize ) {
+ var w = el.offsetWidth,
+ h = el.offsetHeight,
+ z = el.currentStyle.zIndex,
+
+ t = p.t.style,
+ tr = p.tr.style,
+ r = p.r.style,
+ br = p.br.style,
+ b = p.b.style,
+ bl = p.bl.style,
+ l = p.l.style,
+ tl = p.tl.style,
+ c = p.c.style,
+
+ slices = props.slice,
+ widths = props.width,
+ widthT = widths.t.pixels( el ),
+ widthR = widths.r.pixels( el ),
+ widthB = widths.b.pixels( el ),
+ widthL = widths.l.pixels( el );
+
+ tl.height = t.height = tr.height = widthT;
+ tl.width = l.width = bl.width = widthL;
+ tr.left = r.left = br.left = w - widthR;
+ tr.width = r.width = br.width = widthR;
+ br.top = b.top = bl.top = h - widthB;
+ br.height = b.height = bl.height = widthB;
+ t.left = b.left = c.left = widthL;
+ t.width = b.width = c.width = w - widthL - widthR;
+ l.top = r.top = c.top = widthT;
+ l.height = r.height = c.height = h - widthT - widthB;
+
+
+ // image croppings
+
+ // corners
+ p.tl.imagedata.cropBottom = p.t.imagedata.cropBottom = p.tr.imagedata.cropBottom = ( imgSize.h - slices.t ) / imgSize.h;
+ p.tl.imagedata.cropRight = p.l.imagedata.cropRight = p.bl.imagedata.cropRight = ( imgSize.w - slices.l ) / imgSize.w;
+ p.bl.imagedata.cropTop = p.b.imagedata.cropTop = p.br.imagedata.cropTop = ( imgSize.h - slices.b ) / imgSize.h;
+ p.tr.imagedata.cropLeft = p.r.imagedata.cropLeft = p.br.imagedata.cropLeft = ( imgSize.w - slices.r ) / imgSize.w;
+
+ // edges and center
+ if( props.repeat.v === 'stretch' ) {
+ p.l.imagedata.cropTop = p.r.imagedata.cropTop = p.c.imagedata.cropTop = slices.t / imgSize.h;
+ p.l.imagedata.cropBottom = p.r.imagedata.cropBottom = p.c.imagedata.cropBottom = slices.b / imgSize.h;
+ }
+ if( props.repeat.h === 'stretch' ) {
+ p.t.imagedata.cropLeft = p.b.imagedata.cropLeft = p.c.imagedata.cropLeft = slices.l / imgSize.w;
+ p.t.imagedata.cropRight = p.b.imagedata.cropRight = p.c.imagedata.cropRight = slices.r / imgSize.w;
+ }
+ }, this );
+ }
+ },
+
+ updateProps: function() {
+ this.destroy();
+ if( this.isActive() ) {
+ this.updateSize();
+ }
+ },
+
+ getBox: function() {
+ var box = this._box, s, piece, i,
+ pieceNames = this.pieceNames,
+ len = pieceNames.length;
+
+ if( !box ) {
+ box = this._box = this.element.document.createElement( 'border-image' );
+ s = box.style;
+ s.position = 'absolute';
+ s.zIndex = this.zIndex;
+
+ this.pieces = {};
+
+ for( i = 0; i < len; i++ ) {
+ piece = this.pieces[ pieceNames[i] ] = PIE.Util.createVmlElement( 'rect' );
+ piece.appendChild( PIE.Util.createVmlElement( 'imagedata' ) );
+ s = piece.style;
+ s.behavior = 'url(#default#VML)';
+ s.position = "absolute";
+ s.top = s.left = 0;
+ piece.imagedata.src = this.styleInfos.borderImage.getProps().src;
+ piece.stroked = false;
+ piece.filled = false;
+ box.appendChild( piece );
+ }
+
+ this.parent.getBox().appendChild( box );
+ }
+
+ return box;
+ },
+
+ destroy: function() {
+ var box = this._box;
+ if( box && box.parentNode ) {
+ box.parentNode.removeChild( box );
+ }
+ delete this._box;
+ }
+
+} );
55 sources/BorderImageStyleInfo.js
View
@@ -0,0 +1,55 @@
+PIE.BorderImageStyleInfo = function( el ) {
+ this.element = el;
+};
+PIE.Util.merge( PIE.BorderImageStyleInfo.prototype, PIE.StyleBase, {
+
+ cssProperty: PIE.CSS_PREFIX + 'border-image',
+ styleProperty: PIE.STYLE_PREFIX + 'BorderImage',
+
+ //TODO this needs to be reworked to allow the components to appear in arbitrary order
+ parseRE: new RegExp(
+ '^\\s*url\\(\\s*([^\\s\\)]+)\\s*\\)\\s+N(\\s+N)?(\\s+N)?(\\s+N)?(\\s*\\/\\s*L(\\s+L)?(\\s+L)?(\\s+L)?)?RR\\s*$'
+ .replace( /N/g, '(\\d+|' + PIE.StyleBase.percentRE.source + ')' )
+ .replace( /L/g, PIE.StyleBase.lengthRE.source )
+ .replace( /R/g, '(\\s+(stretch|round|repeat))?' )
+ ),
+
+ parseCss: function( css ) {
+ var cs = this.element.currentStyle,
+ p = null,
+ Length = PIE.Length,
+ m = css && css.match( this.parseRE );
+
+ if( m ) {
+ p = {
+ src: m[1],
+
+ slice: {
+ t: parseInt( m[2], 10 ),
+ r: parseInt( m[4] || m[2], 10 ),
+ b: parseInt( m[6] || m[2], 10 ),
+ l: parseInt( m[8] || m[4] || m[2], 10 )
+ },
+
+ width: m[9] ? {
+ t: new Length( m[10] ),
+ r: new Length( m[12] || m[10] ),
+ b: new Length( m[14] || m[10] ),
+ l: new Length( m[16] || m[12] || m[10] )
+ } : {
+ t: new Length( cs.borderTopWidth ),
+ r: new Length( cs.borderRightWidth ),
+ b: new Length( cs.borderBottomWidth ),
+ l: new Length( cs.borderLeftWidth )
+ },
+
+ repeat: {
+ h: m[18] || 'stretch',
+ v: m[20] || m[18] || 'stretch'
+ }
+ };
+ }
+
+ return p;
+ }
+} );
60 sources/BorderRadiusStyleInfo.js
View
@@ -0,0 +1,60 @@
+PIE.BorderRadiusStyleInfo = function( el ) {
+ this.element = el;
+};
+PIE.Util.merge( PIE.BorderRadiusStyleInfo.prototype, PIE.StyleBase, {
+
+ cssProperty: 'border-radius',
+ styleProperty: 'borderRadius',
+
+ parseRE: new RegExp(
+ '^G(\\/G)?$'.replace( /G/g, '\\s*(L)(\\s+(L))?(\\s+(L))?(\\s+(L))?\\s*'.replace( /L/g, PIE.StyleBase.lengthRE.source + '|' + PIE.StyleBase.percentRE.source ) )
+ ),
+
+ parseCss: function( css ) {
+ var p = null, x, y, c,
+ hasNonZero = false,
+ m = css && css.match( this.parseRE );
+
+ function len( v ) {
+ return new PIE.Length( v );
+ }
+
+ if( m ) {
+ x = {
+ tl: len( m[1] ),
+ tr: len( m[4] || m[1] ),
+ br: len( m[7] || m[1] ),
+ bl: len( m[10] || m[4] || m[1] )
+ };
+ for( c in x ) {
+ if( x.hasOwnProperty( c ) && x[c].getNumber() !== 0 ) {
+ hasNonZero = true;
+ }
+ }
+
+ if( m[12] ) {
+ y = {
+ tl: len( m[13] ),
+ tr: len( m[16] || m[13] ),
+ br: len( m[19] || m[13] ),
+ bl: len( m[22] || m[16] || m[13] )
+ };
+ for( c in y ) {
+ if( y.hasOwnProperty( c ) && y[c].getNumber() !== 0 ) {
+ hasNonZero = true;
+ }
+ }
+ } else {
+ y = x;
+ }
+
+ // Treat all-zero values the same as no value
+ if( hasNonZero ) {
+ p = { x: x, y : y };
+ }
+ }
+
+ return p;
+ }
+} );
+
76 sources/BorderStyleInfo.js
View
@@ -0,0 +1,76 @@
+PIE.BorderStyleInfo = function( el ) {
+ this.element = el;
+};
+PIE.Util.merge( PIE.BorderStyleInfo.prototype, PIE.StyleBase, {
+
+ sides: [ 'Top', 'Right', 'Bottom', 'Left' ],
+ namedWidths: {
+ thin: '1px',
+ medium: '3px',
+ thick: '5px'
+ },
+
+ parseCss: function( css ) {
+ var w = {},
+ s = {},
+ c = {},
+ el = this.element,
+ cs = el.currentStyle,
+ rs = el.runtimeStyle,
+ rtColor = rs.borderColor,
+ i = 0,
+ active = false,
+ colorsSame = true, stylesSame = true, widthsSame = true,
+ style, color, width, lastStyle, lastColor, lastWidth, side, ltr;
+
+ rs.borderColor = '';
+ for( ; i < 4; i++ ) {
+ side = this.sides[ i ];
+ ltr = side.charAt(0).toLowerCase();
+ style = s[ ltr ] = cs[ 'border' + side + 'Style' ];
+ color = cs[ 'border' + side + 'Color' ];
+ width = cs[ 'border' + side + 'Width' ];
+
+ if( i > 0 ) {
+ if( style !== lastStyle ) { stylesSame = false; }
+ if( color !== lastColor ) { colorsSame = false; }
+ if( width !== lastWidth ) { widthsSame = false; }
+ }
+ lastStyle = style;
+ lastColor = color;
+ lastWidth = width;
+
+ c[ ltr ] = new PIE.Color( color );
+
+ width = w[ ltr ] = new PIE.Length( s[ ltr ] === 'none' ? '0' : ( this.namedWidths[ width ] || width ) );
+ if( s[ ltr ] !== 'none' || width.pixels( this.element ) > 0 ) {
+ active = true;
+ }
+ }
+ rs.borderColor = rtColor;
+
+ return active ? {
+ widths: w,
+ styles: s,
+ colors: c,
+ widthsSame: widthsSame,
+ colorsSame: colorsSame,
+ stylesSame: stylesSame
+ } : null;
+ },
+
+ getCss: function() {
+ var el = this.element,
+ cs = el.currentStyle,
+ rs = el.runtimeStyle,
+ rtColor = rs.borderColor,
+ css;
+
+ rs.borderColor = '';
+ css = cs.borderWidth + '|' + cs.borderStyle + '|' + cs.borderColor;
+ rs.borderColor = rtColor;
+ return css;
+ }
+
+} );
+
133 sources/BoxShadowRenderer.js
View
@@ -0,0 +1,133 @@
+PIE.BoxShadowRenderer = function( el, styleInfos, parent ) {
+ this.element = el;
+ this.styleInfos = styleInfos;
+ this.parent = parent;
+};
+PIE.Util.merge( PIE.BoxShadowRenderer.prototype, PIE.RendererBase, {
+
+ outsetZIndex: 100,
+ insetZIndex: 300,
+
+ needsUpdate: function() {
+ var si = this.styleInfos;
+ return si.boxShadow.changed() || si.borderRadius.changed();
+ },
+
+ isActive: function() {
+ return this.styleInfos.boxShadow.isActive();
+ },
+
+ updateSize: function() {
+ if( this.isActive() ) {
+ var box = this.getBox(),
+ shape = box.firstChild,
+ s,
+ el = this.element,
+ bs = this.styleInfos.boxShadow.getProps(),
+ spread = bs.spread.pixels( el ),
+ w = el.offsetWidth,
+ h = el.offsetHeight;
+
+ if( bs.inset ) {
+ // if inset, the width does not include any element border
+ s = el.currentStyle;
+ w -= ( parseInt( s.borderLeftWidth, 10 ) || 0 ) + ( parseInt( s.borderRightWidth, 10 ) || 0 );
+ h -= ( parseInt( s.borderTopWidth, 10 ) || 0 ) + ( parseInt( s.borderBottomWidth, 10 ) || 0 );
+
+ // update width of inner element
+ s = box.firstChild.style;
+ s.width = w - spread * 2;
+ s.height = h - spread * 2;
+ } else {
+ w += spread * 2;
+ h += spread * 2;
+ }
+
+ s = shape.style;
+ s.width = w;
+ s.height = h;
+
+ shape.coordsize = ( w + 1 ) + ',' + ( h + 1 ); //shrink the rendered shadow by 1 extra pixel
+ shape.path = this.getBoxPath();
+ }
+ },
+
+ updateProps: function() {
+ this.destroy();
+ if( this.isActive() ) {
+ this.updateSize();
+ }
+ },
+
+ getBox: function() {
+ var box = this._box, s, ss, cs, bs, xOff, yOff, spread, blur, halfBlur, shape, el, filter, alpha;
+ if( !box ) {
+ el = this.element;
+ box = this._box = el.document.createElement( 'box-shadow' );
+ bs = this.styleInfos.boxShadow.getProps();
+ xOff = bs.xOffset.pixels( el );
+ yOff = bs.yOffset.pixels( el );
+ blur = bs.blur.pixels( el );
+ spread = bs.spread.pixels( el );
+
+ // Adjust the blur value so it's always an even number
+ halfBlur = Math.ceil( blur / 2 );
+ blur = halfBlur * 2;
+
+ s = box.style;
+ s.position = 'absolute';
+
+ shape = this.getShape( 'shadow', 'fill' );
+ ss = shape.style;
+ shape.stroked = false;
+
+ if( bs.inset ) {
+ cs = this.element.currentStyle;
+ s.overflow = 'hidden';
+ s.left = parseInt( cs.borderLeftWidth, 10 ) || 0;
+ s.top = parseInt( cs.borderTopWidth, 10 ) || 0;
+ s.zIndex = this.insetZIndex;
+
+ s = shape.style;
+ s.position = 'absolute';
+
+ //TODO handle wider border if needed due to very large offsets or spread
+ s.left = xOff - 20 + spread - blur;
+ s.top = yOff - 20 + spread - blur;
+ s.border = '20px solid ' + bs.color.value();
+ } else {
+ s.left = xOff - blur - spread;
+ s.top = yOff - blur - spread;
+ s.zIndex = this.outsetZIndex;
+
+ shape.filled = true;
+ shape.fillcolor = bs.color.value();
+
+ alpha = bs.color.alpha();
+ if( alpha < 1 ) {
+ shape.fill.opacity = alpha;
+ }
+ }
+
+ // Apply blur filter to the outer or inner element. Applying the blur filter twice with
+ // half the pixel value gives a shadow nearly identical to other browsers.
+ if( blur > 0 ) {
+ filter = 'progid:DXImageTransform.Microsoft.blur(pixelRadius=' + halfBlur + ')';
+ ss.filter = filter + ' ' + filter;
+ }
+
+ this.parent.getBox().appendChild( box );
+ }
+ return box;
+ },
+
+ destroy: function() {
+ var box = this._box;
+ if( box && box.parentNode ) {
+ box.parentNode.removeChild( box );
+ }
+ delete this._box;
+ delete this._shapes;
+ }
+
+} );
50 sources/BoxShadowStyleInfo.js
View
@@ -0,0 +1,50 @@
+PIE.BoxShadowStyleInfo = function( el ) {
+ this.element = el;
+};
+PIE.Util.merge( PIE.BoxShadowStyleInfo.prototype, PIE.StyleBase, {
+
+ cssProperty: PIE.CSS_PREFIX + 'box-shadow',
+ styleProperty: PIE.STYLE_PREFIX + 'BoxShadow',
+
+ noneRE: /^\s*none\s*$/,
+ insetRE: /(inset)/,
+ lengthsRE: new RegExp( '\\s*(L)\\s+(L)(\\s+(L))?(\\s+(L))?\\s*'.replace( /L/g, PIE.StyleBase.lengthRE.source ) ),
+
+ parseCss: function( css ) {
+ var p = null, m,
+ Length = PIE.Length;
+
+ if( css && !this.noneRE.test( css ) ) {
+ p = {};
+
+ // check for inset keyword
+ if( this.insetRE.test( css ) ) {
+ css = css.replace( this.insetRE, '' );
+ p.inset = true;
+ }
+
+ // get the color
+ m = css.match( this.colorRE );
+ if( m ) {
+ css = css.replace( this.colorRE, '' );
+ p.color = new PIE.Color( m[0] );
+ } else {
+ p.color = new PIE.Color( this.element.currentStyle.color );
+ }
+
+ // all that's left should be lengths; map them to xOffset/yOffset/blurRadius/spreadRadius
+ m = css.match( this.lengthsRE );
+ if( m ) {
+ p.xOffset = new Length( m[1] );
+ p.yOffset = new Length( m[3] );
+ p.blur = new Length( m[6] || '0' );
+ p.spread = new Length( m[9] || '0' );
+ } else {
+ // Something unknown was present; give up.
+ p = null;
+ }
+ }
+
+ return p;
+ }
+} );
34 sources/Color.js
View
@@ -0,0 +1,34 @@
+/**
+ * Abstraction for colors values. Allows detection of rgba values.
+ * @param el
+ */
+PIE.Color = (function() {
+ function Color( val ) {
+ this.val = val;
+ }
+ 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*/;
+ Color.prototype = {
+ parse: function() {
+ if( !this._color ) {
+ var v = this.val,
+ m = v.match( Color.rgbaRE );
+ if( m ) {
+ this._color = 'rgb(' + m[1] + ',' + m[2] + ',' + m[3] + ')';
+ this._alpha = parseFloat( m[4] );
+ } else {
+ this._color = v;
+ this._alpha = 1;
+ }
+ }
+ },
+ value: function() {
+ this.parse();
+ return this._color;
+ },
+ alpha: function() {
+ this.parse();
+ return this._alpha;
+ }
+ };
+ return Color;
+})();
113 sources/Length.js
View
@@ -0,0 +1,113 @@
+/**
+ * Wrapper for length style values
+ * @param val - the CSS string representing the length. It is assumed that this will already have
+ * been validated as a valid length or percentage syntax.
+ */
+PIE.Length = ( function() {
+ function Length( val ) {
+ this.val = val;
+ }
+ Length.prototype = {
+ unitRE: /(px|em|ex|mm|cm|in|pt|pc|%)$/,
+
+ getNumber: function() {
+ var num = this._number;
+ if( num === undefined ) {
+ num = this._number = parseFloat( this.val );
+ }
+ return num;
+ },
+
+ getUnit: function() {
+ var unit = this._unit, m;
+ if( !unit ) {
+ m = this.val.match( this.unitRE );
+ unit = this._unit = ( m && m[0] ) || 'px';
+ }
+ return unit;
+ },
+
+ isPercentage: function() {
+ return this.getUnit() === '%';
+ },
+
+ /**
+ * Resolve this length into a number of pixels.
+ * @param el - the context element, used to resolve font-relative values
+ * @param pct100 - the number of pixels that equal a 100% percentage. This can be either a number or a
+ * function which will be called to return the number.
+ */
+ pixels: function( el, pct100 ) {
+ var num = this.getNumber(),
+ unit = this.getUnit();
+ switch( unit ) {
+ case "px":
+ return num;
+ case "%":
+ return num * ( typeof pct100 === 'function' ? pct100() : pct100 ) / 100;
+ case "em":
+ return num * this.getEmPixels( el );
+ case "ex":
+ return num * this.getEmPixels( el ) / 2;
+ default:
+ return num * Length.conversions[ unit ];
+ }
+ },
+
+ /**
+ * The em and ex units are relative to the font-size of the current element,
+ * however if the font-size is set using non-pixel units then we get that value
+ * rather than a pixel conversion. To get around this, we keep a floating element
+ * with width:1em which we insert into the target element and then read its offsetWidth.
+ * But if the font-size *is* specified in pixels, then we use that directly to avoid
+ * the expensive DOM manipulation.
+ * @param el
+ */
+ getEmPixels: function( el ) {
+ var fs = el.currentStyle.fontSize,
+ tester, s, px;
+
+ if( fs.indexOf( 'px' ) > 0 ) {
+ return parseFloat( fs );
+ } else {
+ tester = this._tester;
+ if( !tester ) {
+ tester = this._tester = el.document.createElement( 'length-calc' );
+ s = tester.style;
+ s.width = '1em';
+ s.position = 'absolute';
+ s.top = s.left = -9999;
+ s.visibility = 'hidden';
+ }
+ el.appendChild( tester );
+ px = tester.offsetWidth;
+ el.removeChild( tester );
+ return px;
+ }
+ }
+ };
+
+ Length.conversions = (function() {
+ var units = [ 'mm', 'cm', 'in', 'pt', 'pc' ],
+ vals = {},
+ parent = element.parentNode,
+ i = 0, len = units.length, unit, el, s;
+ for( ; i < len; i++ ) {
+ unit = units[i];
+ el = element.document.createElement( 'length-calc' );
+ s = el.style;
+ s.position = 'absolute';
+ s.top = s.left = -9999;
+ s.width = '100' + unit;
+ parent.appendChild( el );
+ vals[ unit ] = el.offsetWidth / 100;
+ parent.removeChild( el );
+ }
+ return vals;
+ })();
+
+ Length.ZERO = new Length( '0' );
+
+ return Length;
+} )();
+
2  sources/PIE_close.js
View
@@ -0,0 +1,2 @@
+
+} // if( !PIE )
16 sources/PIE_open.js
View
@@ -0,0 +1,16 @@
+var PIE = window.PIE;
+
+if( !PIE ) {
+ PIE = window.PIE = {
+ CSS_PREFIX: '-pie-',
+ STYLE_PREFIX: 'Pie'
+ };
+
+ // Detect IE6
+ if( !window.XMLHttpRequest ) {
+ PIE.isIE6 = true;
+
+ // IE6 can't access properties with leading dash, but can without it.
+ PIE.CSS_PREFIX = PIE.CSS_PREFIX.replace( /^-/, '' );
+ }
+
138 sources/RendererBase.js
View
@@ -0,0 +1,138 @@
+PIE.RendererBase = {
+
+ needsUpdate: function() {
+ return false;
+ },
+
+ updateProps: function() {
+ },
+
+ updatePos: function() {
+ },
+
+ updateSize: function() {
+ },
+
+ updateVis: function() {
+ },
+
+ getShape: function( name, subElName ) {
+ var shapes = this._shapes || ( this._shapes = {} ),
+ shape = shapes[ name ],
+ s;
+
+ if( !shape ) {
+ shape = shapes[ name ] = PIE.Util.createVmlElement( 'shape' );
+ if( subElName ) {
+ shape.appendChild( shape[ subElName ] = PIE.Util.createVmlElement( subElName ) );
+ }
+ this.getBox().appendChild( shape );
+ s = shape.style;
+ s.position = 'absolute';
+ s.left = s.top = 0;
+ s.behavior = 'url(#default#VML)';
+ }
+ return shape;
+ },
+
+ getRadiiPixels: function( radii ) {
+ var el = this.element,
+ w = el.offsetWidth,
+ h = el.offsetHeight,
+ tlX, tlY, trX, trY, brX, brY, blX, blY, f;
+
+ tlX = radii.x.tl.pixels( el, w );
+ tlY = radii.y.tl.pixels( el, h );
+ trX = radii.x.tr.pixels( el, w );
+ trY = radii.y.tr.pixels( el, h );
+ brX = radii.x.br.pixels( el, w );
+ brY = radii.y.br.pixels( el, h );
+ blX = radii.x.bl.pixels( el, w );
+ blY = radii.y.bl.pixels( el, h );
+
+ // If any corner ellipses overlap, reduce them all by the appropriate factor. This formula
+ // is taken straight from the CSS3 Backgrounds and Borders spec.
+ f = Math.min(
+ w / ( tlX + trX ),
+ h / ( trY + brY ),
+ w / ( blX + brX ),
+ h / ( tlY + blY )
+ );
+ if( f < 1 ) {
+ tlX *= f;
+ tlY *= f;
+ trX *= f;
+ trY *= f;
+ brX *= f;
+ brY *= f;
+ blX *= f;
+ blY *= f;
+ }
+
+ return {
+ x: {
+ tl: tlX,
+ tr: trX,
+ br: brX,
+ bl: blX
+ },
+ y: {
+ tl: tlY,
+ tr: trY,
+ br: brY,
+ bl: blY
+ }
+ }
+ },
+
+ getBoxPath: function( shrink ) {
+ var r, instructions, values, str = '', i,
+ el = this.element,
+ w = el.offsetWidth - 1,
+ h = el.offsetHeight - 1,
+ radInfo = this.styleInfos.borderRadius,
+ floor = Math.floor, ceil = Math.ceil,
+ deg = 65535,
+ tlX, tlY, trX, trY, brX, brY, blX, blY;
+
+ shrink = floor( shrink || 0 );
+
+ if( radInfo.isActive() ) {
+ r = this.getRadiiPixels( radInfo.getProps() );
+
+ tlX = r.x.tl;
+ tlY = r.y.tl;
+ trX = r.x.tr;
+ trY = r.y.tr;
+ brX = r.x.br;
+ brY = r.y.br;
+ blX = r.x.bl;
+ blY = r.y.bl;
+
+ instructions = [ 'm', 'qy', 'l', 'qx', 'l', 'qy', 'l', 'qx' ];
+ values = [
+ shrink + ',' + floor(tlY),
+ floor(tlX) + ',' + shrink,
+ ceil(w - trX) + ',' + shrink,
+ ( w - shrink ) + ',' + floor(trY),
+ ( w - shrink ) + ',' + ceil(h - brY),
+ ceil(w - brX) + ',' + ( h - shrink ),
+ floor(blX) + ',' + ( h - shrink ),
+ shrink + ',' + ceil(h - blY)
+ ];
+
+ for ( i = 0; i < 8; i++ ) {
+ str += instructions[i] + values[i];
+ }
+ str += ' x e';
+ } else {
+ // simplified path for non-rounded box
+ str = 'm' + shrink + ',' + shrink +
+ 'l' + ( w - shrink ) + ',' + shrink +
+ 'l' + ( w - shrink ) + ',' + ( h - shrink ) +
+ 'l' + shrink + ',' + ( h - shrink ) +
+ 'xe';
+ }
+ return str;
+ }
+};
88 sources/RootRenderer.js
View
@@ -0,0 +1,88 @@
+PIE.RootRenderer = function( el, styleInfos ) {
+ this.element = el;
+ this.styleInfos = styleInfos;
+};
+PIE.Util.merge( PIE.RootRenderer.prototype, PIE.RendererBase, {
+
+ isActive: function() {
+ var infos = this.styleInfos;
+ for( var i in infos ) {
+ if( infos.hasOwnProperty( i ) && infos[ i ].isActive() ) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ updateVis: function() {
+ if( this.isActive() ) {
+ var cs = this.element.currentStyle;
+ this.getBox().style.display = ( cs.visibility === 'hidden' || cs.display === 'none' ) ? 'none' : '';
+ }
+ },
+
+ updatePos: function() {
+ if( this.isActive() ) {
+ var el = this.element,
+ par = el,
+ docEl,
+ elRect, parRect,
+ s = this.getBox().style,
+ x = 0, y = 0;
+
+ // Get the element's offsets from its nearest positioned ancestor. Uses
+ // getBoundingClientRect for accuracy and speed.
+ do {
+ par = par.offsetParent;
+ } while( par && par.currentStyle.position === 'static' );
+ elRect = el.getBoundingClientRect();
+ if( par ) {
+ parRect = par.getBoundingClientRect();
+ x = elRect.left - parRect.left;
+ y = elRect.top - parRect.top;
+ } else {
+ docEl = el.document.documentElement;
+ x = elRect.left + docEl.scrollLeft - docEl.clientLeft;
+ y = elRect.top + docEl.scrollTop - docEl.clientTop;
+ }
+
+ s.left = x;
+ s.top = y;
+ s.zIndex = el.currentStyle.position === 'static' ? -1 : el.currentStyle.zIndex;
+ }
+ },
+
+ updateSize: function() {
+ if( this.isActive() ) {
+ var el = this.element,
+ s = this.getBox().style,
+ i, len;
+
+ s.width = el.offsetWidth;
+ s.height = el.offsetHeight;
+ }
+ },
+
+ getBox: function() {
+ var box = this._box, el, s;
+ if( !box ) {
+ el = this.element;
+ box = this._box = el.document.createElement( 'css3-container' );
+ s = box.style;
+
+ s.position = 'absolute';
+
+ el.parentNode.insertBefore( box, el );
+ }
+ return box;
+ },
+
+ destroy: function() {
+ var box = this._box;
+ if( box && box.parentNode ) {
+ box.parentNode.removeChild( box );
+ }
+ delete this._box;
+ }
+
+} );
32 sources/StyleBase.js
View
@@ -0,0 +1,32 @@
+PIE.StyleBase = {
+ colorRE: /(#[0-9A-Fa-f]{6}|#[0-9A-Fa-f]{3})|rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(\s*,\s*(\d+|\d*\.\d+))?\s*\)|aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|purple|red|silver|teal|white|yellow/,
+ lengthRE: /[\-\+]?\d*\.?\d*(px|em|ex|mm|cm|in|pt|pc)|[\-\+]?0/,
+ percentRE: /[\-\+]?\d+\.?\d*%|[\-\+]?0/,
+ angleRE: /[\-\+]?\d+(deg|rad|grad|turn)|[\-\+]?0/,
+ urlRE: /^url\(\s*['"]?([^\s\)"]*)['"]?\s*\)$/,
+ trimRE: /^\s*|\s*$/g,
+
+ getProps: function() {
+ if( this.changed() ) {
+ this._props = this.parseCss( this._css = this.getCss() );
+ }
+ return this._props;
+ },
+
+ getCss: function() {
+ return this.element.style[ this.styleProperty ] ||
+ this.element.currentStyle.getAttribute( this.cssProperty );
+ },
+
+ isActive: function() {
+ return !!this.getProps();
+ },
+
+ changed: function() {
+ return this._css !== this.getCss();
+ },
+
+ trim: function( str ) {
+ return str.replace( this.trimRE, '' );
+ }
+};
197 sources/Tokenizer.js
View
@@ -0,0 +1,197 @@
+PIE.Tokenizer = (function() {
+
+ var TYPE_IDENT = 'IDENT',
+ TYPE_NUMBER = 'NUMBER',
+ TYPE_LENGTH = 'LENGTH',
+ TYPE_ANGLE = 'ANGLE',
+ TYPE_PERCENT = 'PERCENT',
+ TYPE_DIMEN = 'DIMEN',
+ TYPE_COLOR = 'COLOR',
+ TYPE_STRING = 'STRING',
+ TYPE_URL = 'URL',
+ TYPE_FUNCTION = 'FUNCTION',
+ TYPE_OPERATOR = 'OPERATOR',
+ TYPE_CHARACTER = 'CHAR';
+
+
+ function Tokenizer( css ) {
+ this.css = css;
+ this.ch = 0;
+ this.tokens = [];
+ this.tokenIndex = 0;
+ }
+ Tokenizer.prototype = {
+ whitespace: /\s/,
+ number: /^[\+\-]?(\d*\.)?\d+/,
+ url: /^url\(\s*("([^"]*)"|'([^']*)'|([!#$%&*-~]*))\s*\)/i,
+ ident: /^\-?[_a-z][\w-]*/i,
+ string: /^("([^"]*)"|'([^']*)')/,
+ operator: /^[\/,]/,
+ hash: /^#[\w]+/,
+ hashColor: /^#([\da-f]{6}|[\da-f]{3})/i,
+
+ unitTypes: {
+ px: TYPE_LENGTH, em: TYPE_LENGTH, ex: TYPE_LENGTH, mm: TYPE_LENGTH,
+ cm: TYPE_LENGTH, 'in': TYPE_LENGTH, pt: TYPE_LENGTH, pc: TYPE_LENGTH,
+ deg: TYPE_ANGLE, rad: TYPE_ANGLE, grad: TYPE_ANGLE
+ },
+
+ colorNames: {
+ aqua:1, black:1, blue:1, fuchsia:1, gray:1, green:1, lime:1, maroon:1,
+ navy:1, olive:1, purple:1, red:1, silver:1, teal:1, white:1, yellow: 1,
+ currentColor: 1
+ },
+
+ colorFunctions: {
+ rgb: 1, rgba: 1, hsl: 1, hsla: 1
+ },
+
+ next: function() {
+ var css, ch, match, type, val, next,
+ me = this;
+
+ // In case we previously backed up, return the stored token in the next slot
+ if( this.tokenIndex < this.tokens.length ) {
+ return this.tokens[ this.tokenIndex++ ];
+ }
+
+ // Move past leading whitespace characters
+ while( this.whitespace.test( this.css.charAt( this.ch ) ) ) {
+ this.ch++;
+ }
+ if( this.ch >= this.css.length ) {
+ return null;
+ }
+
+ function newToken( type, value ) {
+ var tok = { type: type, value: value };
+ me.tokens.push( tok );
+ me.tokenIndex++;
+ return tok;
+ }
+
+ css = this.css.substring( this.ch );
+ ch = css.charAt( 0 );
+ switch( ch ) {
+ case '#':
+ if( match = css.match( this.hashColor ) ) {
+ this.ch += match[0].length;
+ return newToken( TYPE_COLOR, match[0] );
+ }
+ break;
+
+ case '"':
+ case "'":
+ if( match = css.match( this.string ) ) {
+ this.ch += match[0].length;
+ return newToken( TYPE_STRING, match[2] || match[3] || '' );
+ }
+ break;
+
+ case "/":
+ case ",":
+ this.ch++;
+ return newToken( TYPE_OPERATOR, ch );
+
+ case 'u':
+ if( match = css.match( this.url ) ) {
+ this.ch += match[0].length;
+ return newToken( TYPE_URL, match[2] || match[3] || match[4] || '' );
+ }
+ }
+
+ // Numbers and values starting with numbers
+ if( match = css.match( this.number ) ) {
+ val = match[0];
+ this.ch += val.length;
+
+ // Check if it is followed by a unit
+ if( css.charAt( val.length ) === '%' ) {
+ this.ch++;
+ return newToken( TYPE_PERCENT, val + '%' );
+ }
+ if( match = css.substring( val.length ).match( this.ident ) ) {
+ val += match[0];
+ this.ch += match[0].length;
+ return newToken( this.unitTypes[ match[0].toLowerCase() ] || TYPE_DIMEN, val );
+ }
+
+ // Plain ol' number
+ return newToken( TYPE_NUMBER, val );
+ }
+
+ // Identifiers
+ if( match = css.match( this.ident ) ) {
+ val = match[0];
+ this.ch += val.length;
+
+ // Named colors
+ if( val.toLowerCase() in this.colorNames ) {
+ return newToken( TYPE_COLOR, val );
+ }
+
+ // Functions
+ if( css.charAt( val.length ) === '(' ) {
+ this.ch++;
+
+ // Color values in function format: rgb, rgba, hsl, hsla
+ if( val.toLowerCase() in this.colorFunctions ) {
+ if( ( next = this.next() ) && ( next.type === TYPE_NUMBER || ( val.charAt(0) === 'r' && next.type === TYPE_PERCENT ) ) &&
+ ( next = this.next() ) && next.value === ',' &&
+ ( next = this.next() ) && ( next.type === TYPE_NUMBER || next.type === TYPE_PERCENT ) &&
+ ( next = this.next() ) && next.value === ',' &&
+ ( next = this.next() ) && ( next.type === TYPE_NUMBER || next.type === TYPE_PERCENT ) &&
+ ( val === 'rgb' || val === 'hsa' || (
+ ( next = this.next() ) && next.value === ',' &&
+ ( next = this.next() ) && next.type === TYPE_NUMBER )
+ ) &&
+ ( next = this.next() ) && next.value === ')' ) {
+ return newToken( TYPE_COLOR, css.substring( 0, this.ch ) );
+ }
+ return null;
+ }
+
+ return newToken( TYPE_FUNCTION, val + '(' );
+ }
+
+ // Other identifier
+ return newToken( TYPE_IDENT, val );
+ }
+
+ // Standalone character
+ this.ch++;
+ return newToken( TYPE_CHARACTER, ch );
+ },
+
+ prev: function() {
+ return this.tokens[ this.tokenIndex-- - 2 ];
+ },
+
+ all: function() {
+ while( this.next() ) {}
+ return this.tokens;
+ },
+
+ /**
+ * Return a list of tokens until the given function returns true. The final token
+ * will be included in the list.
+ * @param {Function} func
+ * @param {Boolean} require - if true, then if the end of the CSS string is reached
+ * before the test function returns true, null will be returned instead of the
+ * tokens that have been found so far.
+ */
+ until: function( func, require ) {
+ var list = [], t, hit;
+ while( t = this.next() ) {
+ list.push( t );
+ if( func( t ) ) {
+ hit = true;
+ break;
+ }
+ }
+ return require && !hit ? null : list;
+ }
+ };
+
+ return Tokenizer;
+})();
56 sources/Util.js
View
@@ -0,0 +1,56 @@
+PIE.Util = {
+
+ /**
+ * To create a VML element, it must be created by a Document which has the VML
+ * namespace set. Unfortunately, if you try to add the namespace programatically
+ * into the main document, you will get an "Unspecified error" when trying to
+ * access document.namespaces before the document is finished loading. To get
+ * around this, we create a DocumentFragment, which in IE land is apparently a
+ * full-fledged Document. It allows adding namespaces immediately, so we add the
+ * namespace there and then have it create the VML element.
+ */
+ createVmlElement: function( tag ) {
+ var vmlPrefix = 'css3vml',
+ vmlDoc = PIE._vmlCreatorDoc;
+ if( !vmlDoc ) {
+ vmlDoc = PIE._vmlCreatorDoc = element.document.createDocumentFragment();
+ vmlDoc.namespaces.add( vmlPrefix, 'urn:schemas-microsoft-com:vml' );
+ }
+ return vmlDoc.createElement( vmlPrefix + ':' + tag );
+ },
+
+
+ merge: function( obj1 ) {
+ var i, len, p, objN, args = arguments;
+ for( i = 1, len = args.length; i < len; i++ ) {
+ objN = args[i];
+ for( p in objN ) {
+ if( objN.hasOwnProperty( p ) ) {
+ obj1[ p ] = objN[ p ];
+ }
+ }
+ }
+ return obj1;
+ },
+
+
+ /**
+ * Execute a callback function, passing it the dimensions of a given image once
+ * they are known.
+ */
+ withImageSize: function( src, func, ctx ) {
+ var sizes = PIE._imgSizes || ( PIE._imgSizes = {} ),
+ size = sizes[ src ], img;
+ if( size ) {
+ func.call( ctx, size );
+ } else {
+ img = new Image();
+ img.onload = function() {
+ size = sizes[ src ] = { w: img.width, h: img.height };
+ func.call( ctx, size );
+ img.onload = null;
+ };
+ img.src = src;
+ }
+ }
+};
138 sources/event_handlers.js
View
@@ -0,0 +1,138 @@
+var lastW, lastH, lastX, lastY,
+ renderers,
+ styleInfos,
+ ancestors;
+
+/**
+ * Update position and/or size as necessary. Both move and resize events call
+ * this rather than the updatePos/Size functions because sometimes, particularly
+ * during page load, one will fire but the other won't.
+ */
+function update( force ) {
+ if( renderers ) {
+ var el = element,
+ x = el.offsetLeft,
+ y = el.offsetTop,
+ w = el.offsetWidth,
+ h = el.offsetHeight,
+ i, len;
+
+ if( force || x !== lastX || y !== lastY ) {
+ for( i = 0, len = renderers.length; i < len; i++ ) {
+ renderers[i].updatePos();
+ }
+ lastX = x;
+ lastY = y;
+ }
+ if( force || w !== lastW || h !== lastH ) {
+ for( i = 0, len = renderers.length; i < len; i++ ) {
+ renderers[i].updateSize();
+ }
+ lastW = w;
+ lastH = h;
+ }
+ }
+}
+
+/**
+ * Handle property changes to trigger update when appropriate.
+ */
+function propChanged() {
+ if( renderers ) {
+ var name = event.propertyName,
+ i, len, toUpdate;
+ if( name === 'style.display' || name === 'style.visibility' ) {
+ for( i = 0, len = renderers.length; i < len; i++ ) {
+ renderers[i].updateVis();
+ }
+ }
+ else { //if( event.propertyName === 'style.boxShadow' ) {
+ toUpdate = [];
+ for( i = 0, len = renderers.length; i < len; i++ ) {
+ toUpdate.push( renderers[i] );
+ }
+ for( i = 0, len = toUpdate.length; i < len; i++ ) {
+ toUpdate[i].updateProps();
+ }
+ }
+ }
+}
+
+
+function ancestorPropChanged() {
+ var name = event.propertyName;
+ if( name === 'className' || name === 'id' ) {
+ propChanged();
+ }
+}
+
+
+function cleanup() {
+ var i, len;
+
+ // destroy any active renderers
+ for( i = 0, len = renderers.length; i < len; i++ ) {
+ renderers[i].destroy();
+ }
+ renderers = null;
+ styleInfos = null;
+
+ // remove any ancestor propertychange listeners
+ if( ancestors ) {
+ for( i = 0, len = ancestors.length; i < len; i++ ) {
+ ancestors[i].detachEvent( 'onpropertychange', ancestorPropChanged );
+ }
+ ancestors = null;
+ }
+}
+
+
+
+function initAncestorPropChangeListeners() {
+ var watch = element.currentStyle.getAttribute( PIE.CSS_PREFIX + 'watch-ancestors' ),
+ i, a;
+ if( watch ) {
+ ancestors = [];
+ watch = parseInt( watch, 10 );
+ i = 0;
+ a = element.parentNode;
+ while( a && ( watch === 'NaN' || i++ < watch ) ) {
+ ancestors.push( a );
+ a.attachEvent( 'onpropertychange', ancestorPropChanged );
+ a = a.parentNode;
+ }
+ }
+}
+
+
+/**
+ * Initialize
+ */
+function init() {
+ var el = element;
+
+ // force layout so move/resize events will fire
+ el.runtimeStyle.zoom = 1;
+
+ // Create the style infos and renderers
+ styleInfos = {
+ background: new PIE.BackgroundStyleInfo( el ),
+ border: new PIE.BorderStyleInfo( el ),
+ borderImage: new PIE.BorderImageStyleInfo( el ),
+ borderRadius: new PIE.BorderRadiusStyleInfo( el ),
+ boxShadow: new PIE.BoxShadowStyleInfo( el )
+ };
+
+ var rootRenderer = new PIE.RootRenderer( el, styleInfos );
+ renderers = [
+ rootRenderer,
+ new PIE.BoxShadowRenderer( el, styleInfos, rootRenderer ),
+ new PIE.BackgroundAndBorderRenderer( el, styleInfos, rootRenderer ),
+ new PIE.BorderImageRenderer( el, styleInfos, rootRenderer )
+ ];
+
+ // Add property change listeners to ancestors if requested
+ initAncestorPropChangeListeners();
+
+ update();
+}
3  sources/htc_close.txt
View
@@ -0,0 +1,3 @@
+ </script>
+
+</PUBLIC:COMPONENT>
9 sources/htc_open.txt
View