Permalink
Browse files

Implement transform parsing, make getBoxPath accept x/y offsets to ma…

…ke transform origins of all shapes line up, add Matrix class that roughly implements CSSMatrix interface, add transforms test file
  • Loading branch information...
1 parent 4671d18 commit 92b9c78f6b570a182b5adfaa02fd4c63711cbd5c Jason Johnston committed Feb 6, 2011
Showing with 413 additions and 111 deletions.
  1. +7 −10 sources/BoxShadowOutsetRenderer.js
  2. +162 −0 sources/Matrix.js
  3. +18 −14 sources/RendererBase.js
  4. +113 −87 sources/TransformStyleInfo.js
  5. +113 −0 tests/2dtransforms.html
@@ -34,11 +34,11 @@ PIE.BoxShadowOutsetRenderer = PIE.RendererBase.newRenderer( {
h = bounds.h,
clipAdjust = PIE.ieVersion === 8 ? 1 : 0, //workaround for IE8 bug where VML leaks out top/left of clip region by 1px
corners = [ 'tl', 'tr', 'br', 'bl' ], corner,
- shadowInfo, shape, fill, ss, xOff, yOff, spread, blur, shrink, color, alpha, path,
+ shadowInfo, shape, fill, ss, spread, blur, shrink, color, alpha, path,
totalW, totalH, focusX, focusY, isBottom, isRight;
- function getShadowShape( index, corner, xOff, yOff, color, blur, path ) {
+ function getShadowShape( index, corner, color, blur, path ) {
var shape = me.getShape( 'shadow' + index + corner, 'fill', box, len - index ),
fill = shape.fill;
@@ -61,8 +61,6 @@ PIE.BoxShadowOutsetRenderer = PIE.RendererBase.newRenderer( {
// This needs to go last for some reason, to prevent rendering at incorrect size
ss = shape.style;
- ss.left = xOff;
- ss.top = yOff;
ss.width = w;
ss.height = h;
@@ -72,8 +70,6 @@ PIE.BoxShadowOutsetRenderer = PIE.RendererBase.newRenderer( {
while( i-- ) {
shadowInfo = shadowInfos[ i ];
- xOff = shadowInfo.xOffset.pixels( el );
- yOff = shadowInfo.yOffset.pixels( el );
spread = shadowInfo.spread.pixels( el ),
blur = shadowInfo.blur.pixels( el );
color = shadowInfo.color;
@@ -84,7 +80,8 @@ PIE.BoxShadowOutsetRenderer = PIE.RendererBase.newRenderer( {
// round the corners of the expanded shadow shape rather than squaring them off.
radii = PIE.BorderRadiusStyleInfo.ALL_ZERO;
}
- path = this.getBoxPath( { t: shrink, r: shrink, b: shrink, l: shrink }, 2, radii );
+ path = this.getBoxPath( { t: shrink, r: shrink, b: shrink, l: shrink }, 2, radii,
+ shadowInfo.xOffset.pixels( el ), shadowInfo.yOffset.pixels( el ) );
if( blur ) {
totalW = ( spread + blur ) * 2 + w;
@@ -101,7 +98,7 @@ PIE.BoxShadowOutsetRenderer = PIE.RendererBase.newRenderer( {
corner = corners[j];
isBottom = corner.charAt( 0 ) === 'b';
isRight = corner.charAt( 1 ) === 'r';
- shape = getShadowShape( i, corner, xOff, yOff, color, blur, path );
+ shape = getShadowShape( i, corner, color, blur, path );
fill = shape.fill;
fill['focusposition'] = ( isRight ? 1 - focusX : focusX ) + ',' +
( isBottom ? 1 - focusY : focusY );
@@ -116,13 +113,13 @@ PIE.BoxShadowOutsetRenderer = PIE.RendererBase.newRenderer( {
}
} else {
// TODO delete old quadrant shapes if resizing expands past the barrier
- shape = getShadowShape( i, '', xOff, yOff, color, blur, path );
+ shape = getShadowShape( i, '', color, blur, path );
fill = shape.fill;
fill['focusposition'] = focusX + ',' + focusY;
fill['focussize'] = ( 1 - focusX * 2 ) + ',' + ( 1 - focusY * 2 );
}
} else {
- shape = getShadowShape( i, '', xOff, yOff, color, blur, path );
+ shape = getShadowShape( i, '', color, blur, path );
alpha = color.alpha();
if( alpha < 1 ) {
// shape.style.filter = 'alpha(opacity=' + ( alpha * 100 ) + ')';
View
@@ -0,0 +1,162 @@
+PIE.Matrix = function( a, b, c, d, e, f ) {
+ var me = this;
+ me['a'] = a,
+ me['b'] = b,
+ me['c'] = c,
+ me['d'] = d,
+ me['e'] = e,
+ me['f'] = f;
+};
+
+PIE.Matrix.prototype = {
+
+ ////////// CSSMatrix interface methods: //////////
+
+ /**
+ * The setMatrixValue method replaces the existing matrix with one computed from parsing the passed
+ * string as though it had been assigned to the transform property in a CSS style rule.
+ * @param {String} cssString The string to parse.
+ * @throws {DOMException} SYNTAX_ERR Thrown when the provided string can not be parsed into a CSSMatrix.
+ */
+ setMatrixValue: function( cssString ) {
+ // TODO
+ // The problem here is how to handle relative/percent units in translate() functions. Those type
+ // of values mean nothing without the context of a particular element to resolve them to pixel
+ // numbers. They cannot be multiplied until that conversion has been performed.
+ },
+
+ /**
+ * The multiply method returns a new CSSMatrix which is the result of this matrix multiplied by the
+ * passed matrix, with the passed matrix to the right. This matrix is not modified.
+ * @param {CSSMatrix} secondMatrix The matrix to multiply.
+ * @return {CSSMatrix} The result matrix.
+ */
+ multiply: function( secondMatrix ) {
+ var matrix = this;
+ return new PIE.Matrix(
+ matrix['a'] * secondMatrix['a'] + matrix['c'] * secondMatrix['b'],
+ matrix['b'] * secondMatrix['a'] + matrix['d'] * secondMatrix['b'],
+ matrix['a'] * secondMatrix['c'] + matrix['c'] * secondMatrix['d'],
+ matrix['b'] * secondMatrix['c'] + matrix['d'] * secondMatrix['d'],
+ matrix['a'] * secondMatrix['e'] + matrix['c'] * secondMatrix['f'] + matrix['e'],
+ matrix['b'] * secondMatrix['e'] + matrix['d'] * secondMatrix['f'] + matrix['f']
+ )
+ },
+
+ /**
+ * The multiplyLeft method returns a new CSSMatrix which is the result of this matrix multiplied
+ * by the passed matrix, with the passed matrix to the left. This matrix is not modified.
+ * @param {CSSMatrix} secondMatrix The matrix to multiply.
+ * @return {CSSMatrix} The result matrix.
+ */
+ multiplyLeft: function( secondMatrix ) {
+ return secondMatrix.multiply( this );
+ },
+
+ /**
+ * The inverse method returns a new matrix which is the inverse of this matrix. This matrix is not modified.
+ * @return {CSSMatrix} The inverted matrix.
+ * @throws {DOMException} NOT_SUPPORTED_ERR Thrown when the CSSMatrix can not be inverted.
+ */
+ inverse: function() {
+ // IS THIS USEFUL?
+ },
+
+ /**
+ * The translate method returns a new matrix which is this matrix post multiplied by a translation
+ * matrix containing the passed values. This matrix is not modified.
+ * @param {Number} x The X component of the translation value.
+ * @param {Number} y The Y component of the translation value.
+ * @return {CSSMatrix} The result matrix.
+ */
+ translate: function( x, y ) {
+ return this.multiply( new PIE.Matrix( 1, 0, 0, 1, x, y ) );
+ },
+
+ /**
+ * The scale method returns a new matrix which is this matrix post multiplied by a scale matrix
+ * containing the passed values. If the y component is undefined, the x component value is used in its
+ * place. This matrix is not modified.
+ * @param {Number} x The X component of the scale value.
+ * @param {Number} y The (optional) Y component of the scale value.
+ * @return {CSSMatrix} The result matrix.
+ */
+ scale: function( x, y, undef ) {
+ return this.multiply( new PIE.Matrix( x, 0, 0, y === undef ? x : y, 0, 0 ) );
+ },
+
+ /**
+ * The rotate method returns a new matrix which is this matrix post multiplied by a rotation matrix.
+ * The rotation value is in degrees. This matrix is not modified.
+ * @param {Number} angle The angle of rotation (in degrees).
+ * @return {CSSMatrix} The result matrix.
+ */
+ rotate: function( angle ) {
+ var maths = Math,
+ radians = angle / 180 * maths.PI,
+ cosine = maths.cos( radians ),
+ sine = maths.sin( radians );
+ return this.multiply( new PIE.Matrix( cosine, -sine, sine, cosine, 0, 0 ) );
+ },
+
+ /**
+ * The skew method returns a new matrix which is this matrix post multiplied by a skew matrix. The
+ * rotation value is in degrees. This matrix is not modified.
+ * @param {Number} angleX The angle of skew along the X axis.
+ * @param {Number} angleY The angle of skew along the Y axis.
+ * @return {CSSMatrix} The result matrix.
+ */
+ skew: function( angleX, angleY ) {
+ var maths = Math,
+ pi = maths.PI;
+ return this.multiply( new PIE.Matrix( 1, maths.tan( angleX / 180 * pi ), maths.tan(angleY / 180 * pi ), 1, 0, 0 ) );
+ },
+
+ toString: function() {
+ var me = this;
+ return 'matrix(' + [me['a'], me['b'], me['c'], me['d'], me['e'], me['f']] + ')';
+ },
+
+
+
+ ////////// PIE private methods: //////////
+
+ /**
+ * Transform a given x/y point according to this matrix.
+ * @return {Object.<{x:number, y:number}>} The transformed x/y point
+ */
+ applyToPoint: function( x, y ) {
+ var matrix = this;
+ return {
+ x: matrix['a'] * x + matrix['c'] * y + matrix['e'],
+ y: matrix['b'] * x + matrix['d'] * y + matrix['f']
+ }
+ },
+
+ /**
+ * Transform a given bounding box according to this matrix and return the bounding box of the result
+ * @param {Object.<{x:number, y:number, w:number, h:number}>} bounds The bounding box to transform
+ * @return {Object.<{x:number, y:number, w:number, h:number}>} The resulting bounding box
+ */
+ getTransformedBounds: function( bounds ) {
+ var matrix = this,
+ tl = matrix.applyToPoint( 0, 0 ),
+ tr = matrix.applyToPoint( bounds.w, 0 ),
+ br = matrix.applyToPoint( bounds.w, bounds.h ),
+ bl = matrix.applyToPoint( 0, bounds.h ),
+ min = Math.min,
+ max = Math.max,
+ left = min( tl.x, tr.x, br.x, bl.x ),
+ top = min( tl.y, tr.y, br.y, bl.y );
+ return {
+ x: left,
+ y: top,
+ w: max( tl.x, tr.x, br.x, bl.x ) - left,
+ h: max( tl.y, tr.y, br.y, bl.y ) - top
+ }
+ }
+
+};
+
+// Alias to global CSSMatrix interface
+window.CSSMatrix = PIE.Matrix;
View
@@ -220,10 +220,14 @@ PIE.RendererBase = {
* @param {number=} mult If specified, all coordinates will be multiplied by this number
* @param {Object=} radii If specified, this will be used for the corner radii instead of the properties
* from this renderer's borderRadiusInfo object.
+ * @param {number=} xOffset If specified, all points will be shifted by this amount on the x-axis
+ * @param {number=} yOffset If specified, all points will be shifted by this amount on the y-axis
* @return {string} the VML path
*/
- getBoxPath: function( shrink, mult, radii ) {
- mult = mult || 1;
+ getBoxPath: function( shrink, mult, radii, xOffset, yOffset ) {
+ mult || ( mult = 1 );
+ xOffset = ( xOffset || 0 ) * mult;
+ yOffset = ( yOffset || 0 ) * mult;
var r, str,
bounds = this.boundsInfo.getBounds(),
@@ -249,20 +253,20 @@ PIE.RendererBase = {
blX = r.x['bl'] * mult;
blY = r.y['bl'] * mult;
- str = 'm' + floor( shrinkL ) + ',' + floor( tlY ) +
- 'qy' + floor( tlX ) + ',' + floor( shrinkT ) +
- 'l' + ceil( w - trX ) + ',' + floor( shrinkT ) +
- 'qx' + ceil( w - shrinkR ) + ',' + floor( trY ) +
- 'l' + ceil( w - shrinkR ) + ',' + ceil( h - brY ) +
- 'qy' + ceil( w - brX ) + ',' + ceil( h - shrinkB ) +
- 'l' + floor( blX ) + ',' + ceil( h - shrinkB ) +
- 'qx' + floor( shrinkL ) + ',' + ceil( h - blY ) + ' x e';
+ str = 'm' + ( floor( shrinkL ) + xOffset ) + ',' + ( floor( tlY ) + yOffset ) +
+ 'qy' + ( floor( tlX ) + xOffset ) + ',' + ( floor( shrinkT ) + yOffset ) +
+ 'l' + ( ceil( w - trX ) + xOffset ) + ',' + ( floor( shrinkT ) + yOffset ) +
+ 'qx' + ( ceil( w - shrinkR ) + xOffset ) + ',' + ( floor( trY ) + yOffset ) +
+ 'l' + ( ceil( w - shrinkR ) + xOffset ) + ',' + ( ceil( h - brY ) + yOffset ) +
+ 'qy' + ( ceil( w - brX ) + xOffset ) + ',' + ( ceil( h - shrinkB ) + yOffset ) +
+ 'l' + ( floor( blX ) + xOffset ) + ',' + ( ceil( h - shrinkB ) + yOffset ) +
+ 'qx' + ( floor( shrinkL ) + xOffset ) + ',' + ( ceil( h - blY ) + yOffset ) + ' x e';
} else {
// simplified path for non-rounded box
- str = 'm' + floor( shrinkL ) + ',' + floor( shrinkT ) +
- 'l' + ceil( w - shrinkR ) + ',' + floor( shrinkT ) +
- 'l' + ceil( w - shrinkR ) + ',' + ceil( h - shrinkB ) +
- 'l' + floor( shrinkL ) + ',' + ceil( h - shrinkB ) +
+ str = 'm' + ( floor( shrinkL ) + xOffset ) + ',' + ( floor( shrinkT ) + yOffset ) +
+ 'l' + ( ceil( w - shrinkR ) + xOffset ) + ',' + ( floor( shrinkT ) + yOffset ) +
+ 'l' + ( ceil( w - shrinkR ) + xOffset ) + ',' + ( ceil( h - shrinkB ) + yOffset ) +
+ 'l' + ( floor( shrinkL ) + xOffset ) + ',' + ( ceil( h - shrinkB ) + yOffset ) +
'xe';
}
return str;
Oops, something went wrong.

0 comments on commit 92b9c78

Please sign in to comment.