Repository: flex-asjs Updated Branches: refs/heads/develop 5f6058379 -> 3f556bcda
http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/fe4b40d3/frameworks/as/projects/FlexJSUI/src/org/apache/flex/core/graphics/utils/MatrixUtil.as ---------------------------------------------------------------------- diff --git a/frameworks/as/projects/FlexJSUI/src/org/apache/flex/core/graphics/utils/MatrixUtil.as b/frameworks/as/projects/FlexJSUI/src/org/apache/flex/core/graphics/utils/MatrixUtil.as new file mode 100644 index 0000000..04c2c13 --- /dev/null +++ b/frameworks/as/projects/FlexJSUI/src/org/apache/flex/core/graphics/utils/MatrixUtil.as @@ -0,0 +1,1605 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package org.apache.flex.core.graphics.utils +{ + + import flash.display.DisplayObject; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.PerspectiveProjection; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.geom.Utils3D; + import flash.geom.Vector3D; + import flash.system.ApplicationDomain; + + /** + * @private + * The MatrixUtil class is for internal use only. + * Class for matrix and geometric related math routines. + */ + public final class MatrixUtil + { + + private static const RADIANS_PER_DEGREES:Number = Math.PI / 180; + private static var SOLUTION_TOLERANCE:Number = 0.1; + private static var MIN_MAX_TOLERANCE:Number = 0.1; + + private static var staticPoint:Point = new Point(); + + // For use in getConcatenatedMatrix function + private static var fakeDollarParent:QName; + private static var uiComponentClass:Class; + private static var uiMovieClipClass:Class; + private static var usesMarshalling:Object; + private static var lastModuleFactory:Object; + private static var computedMatrixProperty:QName; + private static var $transformProperty:QName; + + //-------------------------------------------------------------------------- + // + // Class methods + // + //-------------------------------------------------------------------------- + + /** + * Returns rotation value clamped between -180 and 180 degreeds. + * This mimicks the Flash player behavior. + */ + public static function clampRotation(value:Number):Number + { + // Flash player doesn't handle values larger than 2^15 - 1 (FP-749). + if (value > 180 || value < -180) + { + value = value % 360; + + if (value > 180) + value = value - 360; + else if (value < -180) + value = value + 360; + } + return value; + } + + /** + * Returns a static Point object with the result. + * If matrix is null, point is untransformed. + */ + public static function transformPoint(x:Number, y:Number, m:Matrix):Point + { + if (!m) + { + staticPoint.x = x; + staticPoint.y = y; + return staticPoint; + } + + staticPoint.x = m.a * x + m.c * y + m.tx; + staticPoint.y = m.b * x + m.d * y + m.ty; + return staticPoint; + } + + public static function composeMatrix(x:Number = 0, + y:Number = 0, + scaleX:Number = 1, + scaleY:Number = 1, + rotation:Number = 0, + transformX:Number = 0, + transformY:Number = 0):Matrix + { + var m:Matrix = new Matrix(); + m.translate(-transformX, -transformY); + m.scale(scaleX, scaleY); + if (rotation != 0) + m.rotate(rotation / 180 * Math.PI); + m.translate(transformX + x, transformY + y); + return m; + } + + /** + * Decompose a matrix into its component scale, rotation, and translation parts. + * The Vector of Numbers passed in the components parameter will be + * populated by this function with the component parts. + * + * @param components Vector which holds the component scale, rotation + * and translation values. + * x = components[0] + * y = components[1] + * rotation = components[2] + * scaleX = components[3] + * scaleY = components[4] + * + * @param matrix The matrix to decompose + * @param transformX The x value of the transform center + * @param transformY The y value of the transform center + */ + public static function decomposeMatrix(components:Vector.<Number>, + matrix:Matrix, + transformX:Number = 0, + transformY:Number = 0):void + { + // else decompose matrix. Don't use MatrixDecompose(), it can return erronous values + // when negative scales (and therefore skews) are in use. + var Ux:Number; + var Uy:Number; + var Vx:Number; + var Vy:Number; + + Ux = matrix.a; + Uy = matrix.b; + components[3] = Math.sqrt(Ux*Ux + Uy*Uy); + + Vx = matrix.c; + Vy = matrix.d; + components[4] = Math.sqrt(Vx*Vx + Vy*Vy ); + + // sign of the matrix determinant will tell us if the space is inverted by a 180 degree skew or not. + var determinant:Number = Ux*Vy - Uy*Vx; + if (determinant < 0) // if so, choose y-axis scale as the skewed one. Unfortunately, its impossible to tell if it originally was the y or x axis that had the negative scale/skew. + { + components[4] = -(components[4]); + Vx = -Vx; + Vy = -Vy; + } + + components[2] = Math.atan2( Uy, Ux ) / RADIANS_PER_DEGREES; + + if (transformX != 0 || transformY != 0) + { + var postTransformCenter:Point = matrix.transformPoint(new Point(transformX,transformY)); + components[0] = postTransformCenter.x - transformX; + components[1] = postTransformCenter.y - transformY; + } + else + { + components[0] = matrix.tx; + components[1] = matrix.ty; + } + } + + /** + * @return Returns the union of <code>rect</code> and + * <code>Rectangle(left, top, right - left, bottom - top)</code>. + * Note that if rect is non-null, it will be updated to reflect the return value. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public static function rectUnion(left:Number, top:Number, right:Number, bottom:Number, + rect:Rectangle):Rectangle + { + if (!rect) + return new Rectangle(left, top, right - left, bottom - top); + + var minX:Number = Math.min(rect.left, left); + var minY:Number = Math.min(rect.top, top); + var maxX:Number = Math.max(rect.right, right); + var maxY:Number = Math.max(rect.bottom, bottom); + + rect.x = minX; + rect.y = minY; + rect.width = maxX - minX; + rect.height = maxY - minY; + return rect; + } + + /** + * Calculates the bounding box of a post-transformed ellipse. + * + * @param cx The x coordinate of the ellipse's center + * @param cy The y coordinate of the ellipse's center + * @param rx The horizontal radius of the ellipse + * @param ry The vertical radius of the ellipse + * @param matrix The transformation matrix. + * @param rect If non-null, rect will be updated to the union of rect and + * the segment bounding box. + * @return Returns the union of the passed in rect with the + * bounding box of the the post-transformed ellipse. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public static function getEllipseBoundingBox(cx:Number, cy:Number, + rx:Number, ry:Number, + matrix:Matrix, + rect:Rectangle = null):Rectangle + { + var a:Number = matrix.a; + var b:Number = matrix.b; + var c:Number = matrix.c; + var d:Number = matrix.d; + + // Ellipse can be represented by the following parametric equations: + // + // (1) x = cx + rx * cos(t) + // (2) y = cy + ry * sin(t) + // + // After applying transformation with matrix m(a, c, b, d) we get: + // + // (3) x = a * cx + a * cos(t) * rx + c * cy + c * sin(t) * ry + m.tx + // (4) y = b * cx + b * cos(t) * rx + d * cy + d * sin(t) * ry + m.ty + // + // In (3) and (4) x and y are functions of a parameter t. To find the extremums we need + // to find where dx/dt and dy/dt reach zero: + // + // (5) dx/dt = - a * sin(t) * rx + c * cos(t) * ry + // (6) dy/dt = - b * sin(t) * rx + d * cos(t) * ry + // (7) dx/dt = 0 <=> sin(t) / cos(t) = (c * ry) / (a * rx); + // (8) dy/dt = 0 <=> sin(t) / cos(t) = (d * ry) / (b * rx); + + if (rx == 0 && ry == 0) + { + var pt:Point = new Point(cx, cy); + pt = matrix.transformPoint(pt); + return rectUnion(pt.x, pt.y, pt.x, pt.y, rect); + } + + var t:Number; + var t1:Number; + + if (a * rx == 0) + t = Math.PI / 2; + else + t = Math.atan((c * ry) / (a * rx)); + + if (b * rx == 0) + t1 = Math.PI / 2; + else + t1 = Math.atan((d * ry) / (b * rx)); + + var x1:Number = a * Math.cos(t) * rx + c * Math.sin(t) * ry; + var x2:Number = -x1; + x1 += a * cx + c * cy + matrix.tx; + x2 += a * cx + c * cy + matrix.tx; + + var y1:Number = b * Math.cos(t1) * rx + d * Math.sin(t1) * ry; + var y2:Number = -y1; + y1 += b * cx + d * cy + matrix.ty; + y2 += b * cx + d * cy + matrix.ty; + + return rectUnion(Math.min(x1, x2), Math.min(y1, y2), Math.max(x1, x2), Math.max(y1, y2), rect); + } + + /** + * @param x0 x coordinate of the first control point + * @param y0 y coordinate of the first control point + * @param x1 x coordinate of the second control point + * @param y1 y coordinate of the second control point + * @param x2 x coordinate of the third control point + * @param y2 y coordinate of the third control point + * @param sx The pre-transform scale factor for x coordinates. + * @param sy The pre-transform scale factor for y coordinates. + * @param matrix The transformation matrix. Can be null for identity transformation. + * @param rect If non-null, rect will be updated to the union of rect and + * the segment bounding box. + * @return Returns the union of the post-transformed quadratic + * bezier segment's axis aligned bounding box and the passed in rect. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + static public function getQBezierSegmentBBox(x0:Number, y0:Number, + x1:Number, y1:Number, + x2:Number, y2:Number, + sx:Number, sy:Number, + matrix:Matrix, + rect:Rectangle):Rectangle + { + var pt:Point; + pt = MatrixUtil.transformPoint(x0 * sx, y0 * sy, matrix); + x0 = pt.x; + y0 = pt.y; + + pt = MatrixUtil.transformPoint(x1 * sx, y1 * sy, matrix); + x1 = pt.x; + y1 = pt.y; + + pt = MatrixUtil.transformPoint(x2 * sx, y2 * sy, matrix); + x2 = pt.x; + y2 = pt.y; + + var minX:Number = Math.min(x0, x2); + var maxX:Number = Math.max(x0, x2); + + var minY:Number = Math.min(y0, y2); + var maxY:Number = Math.max(y0, y2); + + var txDiv:Number = x0 - 2 * x1 + x2; + if (txDiv != 0) + { + var tx:Number = (x0 - x1) / txDiv; + if (0 <= tx && tx <= 1) + { + var x:Number = (1 - tx) * (1 - tx) * x0 + 2 * tx * (1 - tx) * x1 + tx * tx * x2; + minX = Math.min(x, minX); + maxX = Math.max(x, maxX); + } + } + + var tyDiv:Number = y0 - 2 * y1 + y2; + if (tyDiv != 0) + { + var ty:Number = (y0 - y1) / tyDiv; + if (0 <= ty && ty <= 1) + { + var y:Number = (1 - ty) * (1 - ty) * y0 + 2 * ty * (1 - ty) * y1 + ty * ty * y2; + minY = Math.min(y, minY); + maxY = Math.max(y, maxY); + } + } + + return rectUnion(minX, minY, maxX, maxY, rect); + } + + /** + * @param width The width of the bounds to be transformed. + * @param height The height of the bounds to be transformed. + * @param matrix The transfomration matrix. + * + * @param vec If vec is non-null it will be set to the vector from the + * transformed bounds top left to the untransformed bounds top left + * in the coordinate space defined by <code>matrix</code>. + * This is useful if you want to align the transformed bounds to x,y + * by modifying the object's position. Moving the object by + * <code>x + vec.x</code> and <code>y + vec.y</code> respectively + * will offset the transformed bounds top left corner by x,y. + * + * @return Returns the transformed bounds. Note that the Point object returned will be reused + * by other MatrixUtil methods. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public static function transformSize(width:Number, height:Number, matrix:Matrix):Point + { + const a:Number = matrix.a; + const b:Number = matrix.b; + const c:Number = matrix.c; + const d:Number = matrix.d; + + // transform point (0,0) + var x1:Number = 0; + var y1:Number = 0; + + // transform point (width, 0) + var x2:Number = width * a; + var y2:Number = width * b; + + // transform point (0, height) + var x3:Number = height * c; + var y3:Number = height * d; + + // transform point (width, height) + var x4:Number = x2 + x3; + var y4:Number = y2 + y3; + + var minX:Number = Math.min(Math.min(x1, x2), Math.min(x3, x4)); + var maxX:Number = Math.max(Math.max(x1, x2), Math.max(x3, x4)); + var minY:Number = Math.min(Math.min(y1, y2), Math.min(y3, y4)); + var maxY:Number = Math.max(Math.max(y1, y2), Math.max(y3, y4)); + + staticPoint.x = maxX - minX; + staticPoint.y = maxY - minY; + return staticPoint; + } + + /** + * @param width The width of the bounds to be transformed. + * @param height The height of the bounds to be transformed. + * @param matrix The transfomration matrix. + * + * @param topleft If topLeft is non-null it will be used as the origin of the bounds + * rectangle to be transformed. On return, it will be set to the top left of the rectangle + * after transformation. + * + * @return Returns the transformed width and height. Note that the Point object returned will be reused + * by other MatrixUtil methods. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public static function transformBounds(width:Number, height:Number, matrix:Matrix, topLeft:Point = null):Point + { + const a:Number = matrix.a; + const b:Number = matrix.b; + const c:Number = matrix.c; + const d:Number = matrix.d; + + // transform point (0,0) + var x1:Number = 0; + var y1:Number = 0; + + // transform point (width, 0) + var x2:Number = width * a; + var y2:Number = width * b; + + // transform point (0, height) + var x3:Number = height * c; + var y3:Number = height * d; + + // transform point (width, height) + var x4:Number = x2 + x3; + var y4:Number = y2 + y3; + + var minX:Number = Math.min(Math.min(x1, x2), Math.min(x3, x4)); + var maxX:Number = Math.max(Math.max(x1, x2), Math.max(x3, x4)); + var minY:Number = Math.min(Math.min(y1, y2), Math.min(y3, y4)); + var maxY:Number = Math.max(Math.max(y1, y2), Math.max(y3, y4)); + + staticPoint.x = maxX - minX; + staticPoint.y = maxY - minY; + + if (topLeft) + { + const tx:Number = matrix.tx; + const ty:Number = matrix.ty; + const x:Number = topLeft.x; + const y:Number = topLeft.y; + + topLeft.x = minX + a * x + b * y + tx; + topLeft.y = minY + c * x + d * y + ty; + } + return staticPoint; + } + + /** + * Returns the axis aligned bounding box <code>bounds</code> transformed + * with <code>matrix</code> and then projected with <code>projection</code>. + * + * @param bounds The bounds, in child coordinates, to be transformed and projected. + * @param matrix <p>The transformation matrix. Note that the method will clobber the + * original matrix values.</p> + * @param projection The projection. + * @return Returns the <code>bounds</code> parameter that has been updated with the + * transformed and projected bounds. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public static function projectBounds(bounds:Rectangle, + matrix:Matrix3D, + projection:PerspectiveProjection):Rectangle + { + // Setup the matrix + var centerX:Number = projection.projectionCenter.x; + var centerY:Number = projection.projectionCenter.y; + matrix.appendTranslation(-centerX, -centerY, projection.focalLength); + matrix.append(projection.toMatrix3D()); + + // Project the corner points + var pt1:Vector3D = new Vector3D(bounds.left, bounds.top, 0); + var pt2:Vector3D = new Vector3D(bounds.right, bounds.top, 0) + var pt3:Vector3D = new Vector3D(bounds.left, bounds.bottom, 0); + var pt4:Vector3D = new Vector3D(bounds.right, bounds.bottom, 0); + pt1 = Utils3D.projectVector(matrix, pt1); + pt2 = Utils3D.projectVector(matrix, pt2); + pt3 = Utils3D.projectVector(matrix, pt3); + pt4 = Utils3D.projectVector(matrix, pt4); + + // Find the bounding box in 2D + var maxX:Number = Math.max(Math.max(pt1.x, pt2.x), Math.max(pt3.x, pt4.x)); + var minX:Number = Math.min(Math.min(pt1.x, pt2.x), Math.min(pt3.x, pt4.x)); + var maxY:Number = Math.max(Math.max(pt1.y, pt2.y), Math.max(pt3.y, pt4.y)); + var minY:Number = Math.min(Math.min(pt1.y, pt2.y), Math.min(pt3.y, pt4.y)); + + // Add back the projection center + bounds.x = minX + centerX; + bounds.y = minY + centerY; + bounds.width = maxX - minX; + bounds.height = maxY - minY; + return bounds; + } + + /** + * @param matrix + * @return Returns true when <code>pt == matrix.DeltaTransformPoint(pt)</code> + * for any <code>pt:Point</code> (<code>matrix</code> is identity matrix, + * when disregarding the translation part). + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public static function isDeltaIdentity(matrix:Matrix):Boolean + { + return (matrix.a == 1 && matrix.d == 1 && + matrix.b == 0 && matrix.c == 0); + } + + /** + * <code>fitBounds</code> Calculates a size (x,y) for a bounding box (0,0,x,y) + * such that the bounding box transformed with <code>matrix</code> will fit + * into (0,0,width,height). + * + * @param width This is the width of the bounding box that calculated size + * needs to fit in. + * + * @param height This is the height of the bounding box that the calculated + * size needs to fit in. + * + * @param matrix This defines the transformations that the function will take + * into account when calculating the size. The bounding box (0,0,x,y) of the + * calculated size (x,y) transformed with <code>matrix</code> will fit in the + * specified <code>width</code> and <code>height</code>. + * + * @param explicitWidth Explicit width for the calculated size. The function + * will first try to find a solution using this width. + * + * @param explicitHeight Preferred height for the calculated size. The function + * will first try to find a solution using this height. + * + * @param preferredWidth Preferred width for the calculated size. If possible + * the function will set the calculated size width to this value. + * + * @param preferredHeight Preferred height for the calculated size. If possible + * the function will set the calculated size height to this value. + * + * @param minWidth The minimum allowed value for the calculated size width. + * + * @param minHeight The minimum allowed value for the calculated size height. + * + * @param maxWidth The maximum allowed value for the calculated size width. + * + * @param maxHeight The maximum allowed value for the calculated size height. + * + * @return Returns the size (x,y) such that the bounding box (0,0,x,y) will + * fit into (0,0,width,height) after transformation with <code>matrix</code>. + * Returns null if there is no possible solution. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public static function fitBounds(width:Number, height:Number, matrix:Matrix, + explicitWidth:Number, explicitHeight:Number, + preferredWidth:Number, preferredHeight:Number, + minWidth:Number, minHeight:Number, + maxWidth:Number, maxHeight:Number):Point + { + if (isNaN(width) && isNaN(height)) + return new Point(preferredWidth, preferredHeight); + + // Allow for precision errors by including tolerance for certain values. + const newMinWidth:Number = (minWidth < MIN_MAX_TOLERANCE) ? 0 : minWidth - MIN_MAX_TOLERANCE; + const newMinHeight:Number = (minHeight < MIN_MAX_TOLERANCE) ? 0 : minHeight - MIN_MAX_TOLERANCE; + const newMaxWidth:Number = maxWidth + MIN_MAX_TOLERANCE; + const newMaxHeight:Number = maxHeight + MIN_MAX_TOLERANCE; + + var actualSize:Point; + + if (!isNaN(width) && !isNaN(height)) + { + actualSize = calcUBoundsToFitTBounds(width, height, matrix, + newMinWidth, newMinHeight, + newMaxWidth, newMaxHeight); + + // If we couldn't fit in both dimensions, try to fit only one and + // don't stick out of the other + if (!actualSize) + { + var actualSize1:Point; + actualSize1 = fitTBoundsWidth(width, matrix, + explicitWidth, explicitHeight, + preferredWidth, preferredHeight, + newMinWidth, newMinHeight, + newMaxWidth, newMaxHeight); + + // If we fit the width, but not the height. + if (actualSize1) + { + var fitHeight:Number = transformSize(actualSize1.x, actualSize1.y, matrix).y; + if (fitHeight - SOLUTION_TOLERANCE > height) + actualSize1 = null; + } + + var actualSize2:Point + actualSize2 = fitTBoundsHeight(height, matrix, + explicitWidth, explicitHeight, + preferredWidth, preferredHeight, + newMinWidth, newMinHeight, + newMaxWidth, newMaxHeight); + + // If we fit the height, but not the width + if (actualSize2) + { + var fitWidth:Number = transformSize(actualSize2.x, actualSize2.y, matrix).x; + if (fitWidth - SOLUTION_TOLERANCE > width) + actualSize2 = null; + } + + if (actualSize1 && actualSize2) + { + // Pick a solution + actualSize = ((actualSize1.x * actualSize1.y) > (actualSize2.x * actualSize2.y)) ? actualSize1 : actualSize2; + } + else if (actualSize1) + { + actualSize = actualSize1; + } + else + { + actualSize = actualSize2; + } + } + return actualSize; + } + else if (!isNaN(width)) + { + return fitTBoundsWidth(width, matrix, + explicitWidth, explicitHeight, + preferredWidth, preferredHeight, + newMinWidth, newMinHeight, + newMaxWidth, newMaxHeight); + } + else + { + return fitTBoundsHeight(height, matrix, + explicitWidth, explicitHeight, + preferredWidth, preferredHeight, + newMinWidth, newMinHeight, + newMaxWidth, newMaxHeight); + } + } + + /** + * @private + * + * <code>fitTBoundsWidth</code> Calculates a size (x,y) for a bounding box (0,0,x,y) + * such that the bounding box transformed with <code>matrix</code> will fit + * into the specified width. + * + * @param width This is the width of the bounding box that calculated size + * needs to fit in. + * + * @param matrix This defines the transformations that the function will take + * into account when calculating the size. The bounding box (0,0,x,y) of the + * calculated size (x,y) transformed with <code>matrix</code> will fit in the + * specified <code>width</code> and <code>height</code>. + * + * @param explicitWidth Explicit width for the calculated size. The function + * will first try to find a solution using this width. + * + * @param explicitHeight Preferred height for the calculated size. The function + * will first try to find a solution using this height. + * + * @param preferredWidth Preferred width for the calculated size. If possible + * the function will set the calculated size width to this value. + * + * @param preferredHeight Preferred height for the calculated size. If possible + * the function will set the calculated size height to this value. + * + * @param minWidth The minimum allowed value for the calculated size width. + * + * @param minHeight The minimum allowed value for the calculated size height. + * + * @param maxWidth The maximum allowed value for the calculated size width. + * + * @param maxHeight The maximum allowed value for the calculated size height. + * + * @return Returns the size (x,y) such that the bounding box (0,0,x,y) will + * fit into (0,0,width,height) after transformation with <code>matrix</code>. + * Returns null if there is no possible solution. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + private static function fitTBoundsWidth(width:Number, matrix:Matrix, + explicitWidth:Number, explicitHeight:Number, + preferredWidth:Number, preferredHeight:Number, + minWidth:Number, minHeight:Number, + maxWidth:Number, maxHeight:Number):Point + { + var actualSize:Point; + + // cases 1 and 2: only explicit width or explicit height is specified, + // so we try to find a solution with that hard constraint. + if (!isNaN(explicitWidth) && isNaN(explicitHeight)) + { + actualSize = calcUBoundsToFitTBoundsWidth(width, matrix, + explicitWidth, preferredHeight, + explicitWidth, minHeight, + explicitWidth, maxHeight); + + if (actualSize) + return actualSize; + } + else if (isNaN(explicitWidth) && !isNaN(explicitHeight)) + { + actualSize = calcUBoundsToFitTBoundsWidth(width, matrix, + preferredWidth, explicitHeight, + minWidth, explicitHeight, + maxWidth, explicitHeight); + if (actualSize) + return actualSize; + } + + // case 3: default case. When explicitWidth, explicitHeight are both set + // or not set, we use the preferred size since calcUBoundsToFitTBoundsWidth + // will just pick one. + actualSize = calcUBoundsToFitTBoundsWidth(width, matrix, + preferredWidth, preferredHeight, + minWidth, minHeight, + maxWidth, maxHeight); + + return actualSize; + } + + /** + * @private + * + * <code>fitTBoundsWidth</code> Calculates a size (x,y) for a bounding box (0,0,x,y) + * such that the bounding box transformed with <code>matrix</code> will fit + * into the specified height. + * + * @param height This is the height of the bounding box that the calculated + * size needs to fit in. + * + * @param matrix This defines the transformations that the function will take + * into account when calculating the size. The bounding box (0,0,x,y) of the + * calculated size (x,y) transformed with <code>matrix</code> will fit in the + * specified <code>width</code> and <code>height</code>. + * + * @param explicitWidth Explicit width for the calculated size. The function + * will first try to find a solution using this width. + * + * @param explicitHeight Preferred height for the calculated size. The function + * will first try to find a solution using this height. + * + * @param preferredWidth Preferred width for the calculated size. If possible + * the function will set the calculated size width to this value. + * + * @param preferredHeight Preferred height for the calculated size. If possible + * the function will set the calculated size height to this value. + * + * @param minWidth The minimum allowed value for the calculated size width. + * + * @param minHeight The minimum allowed value for the calculated size height. + * + * @param maxWidth The maximum allowed value for the calculated size width. + * + * @param maxHeight The maximum allowed value for the calculated size height. + * + * @return Returns the size (x,y) such that the bounding box (0,0,x,y) will + * fit into (0,0,width,height) after transformation with <code>matrix</code>. + * Returns null if there is no possible solution. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + private static function fitTBoundsHeight(height:Number, matrix:Matrix, + explicitWidth:Number, explicitHeight:Number, + preferredWidth:Number, preferredHeight:Number, + minWidth:Number, minHeight:Number, + maxWidth:Number, maxHeight:Number):Point + { + var actualSize:Point; + + // cases 1 and 2: only explicit width or explicit height is specified, + // so we try to find a solution with that hard constraint. + if (!isNaN(explicitWidth) && isNaN(explicitHeight)) + { + actualSize = calcUBoundsToFitTBoundsHeight(height, matrix, + explicitWidth, preferredHeight, + explicitWidth, minHeight, + explicitWidth, maxHeight); + + if (actualSize) + return actualSize; + } + else if (isNaN(explicitWidth) && !isNaN(explicitHeight)) + { + actualSize = calcUBoundsToFitTBoundsHeight(height, matrix, + preferredWidth, explicitHeight, + minWidth, explicitHeight, + maxWidth, explicitHeight); + if (actualSize) + return actualSize; + } + + // case 3: default case. When explicitWidth, explicitHeight are both set + // or not set, we use the preferred size since calcUBoundsToFitTBoundsWidth + // will just pick one. + actualSize = calcUBoundsToFitTBoundsHeight(height, matrix, + preferredWidth, preferredHeight, + minWidth, minHeight, + maxWidth, maxHeight); + + return actualSize; + } + + /** + * Calculates (x,y) such that the bounding box (0,0,x,y) transformed + * with <code>matrix</code> will have bounding box with + * height equal to <code>h</code>. + * x and y are restricted by <code>minX</code>, <code>maxX</code> and + * <code>minY</code>, <code>maxY</code>. + * + * If possible x will be set to <code>preferredX</code> or + * y will be set to <code>preferredY</code>. + * + * When there are multiple solutions, the function picks the one that + * minimizes the bounding box area of transformed (0,0,x,y). + * + * The functon assumes <code>minX >= 0</code> and <code>minY >= 0</code> + * (solution components x and y are non-negative). + * + * @return Returns Point(x,y) or null if no solution exists. + * + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + static public function calcUBoundsToFitTBoundsHeight(h:Number, + matrix:Matrix, + preferredX:Number, + preferredY:Number, + minX:Number, + minY:Number, + maxX:Number, + maxY:Number):Point + { + // Untransformed bounds size is (x,y). The corners of the untransformed + // bounding box are p1(0,0) p2(x,0) p3(0,y) p4(x,y). + // Matrix is | a c tx | + // | b d ty | + // + // After transfomation with the matrix those four points are: + // t1 = (0, 0) = matrix.deltaTransformPoint(p1) + // t2 = (ax, bx) = matrix.deltaTransformPoint(p2) + // t3 = (cy, dy) = matrix.deltaTransformPoint(p3) + // t4 = (ax + cy, cx + dy) = matrix.deltaTransformPoint(p4) + // + // The transformed bounds bounding box dimensions are (w,h): + // (1) w = max( t1.x, t2.x, t3.x, t4.x ) - min( t1.x, t2.x, t3.x, t4.x) + // (2) h = max( t1.y, t2.y, t3.y, t4.y ) - min( t1.y, t2.y, t3.y, t4.y) + // + // Looking at all the possible cases for min and max functions above, + // we can construct and solve simple linear systems for x and y. + // For example in the case of + // t1.x <= t2.x <= t3.x <= t4.x + // our first equation is + // (1) w = t4.x - t1.x <==> w = ax + cy + // + // To minimize the cases we're looking at we can take advantage of + // the limits we have: x >= 0, y >= 0; + // Taking into account these limits we deduce that: + // a*c >= 0 gives us (1) w = abs( t4.x - t1.x ) = abs( ax + cy ) + // a*c < 0 gives us (1) w = abs( t2.x - t3.x ) = abs( ax - cy ) + // b*d >= 0 gives us (2) h = abs( t4.y - t1.y ) = abs( bx + dy ) + // b*d < 0 gives us (2) h = abs( t2.y - t3.y ) = abs( bx - dy ) + // + // If we do a substitution such that + // c1 = a*c >= 0 ? c : -c + // d1 = b*d >= 0 ? d : -d + // we get the following linear system: + // (1) w = abs( ax + c1y ) + // (2) h = abs( bx + d1y ) + // + // Since we're matching height we only care about (2) + + var b:Number = matrix.b; + var d:Number = matrix.d; + + // If components are very close to zero, zero them out to handle the special cases + if (-1.0e-9 < b && b < +1.0e-9) + b = 0; + if (-1.0e-9 < d && d < +1.0e-9) + d = 0; + + if (b == 0 && d == 0) + return null; // No solution + + // Handle special cases first + if (b == 0 && d == 0) + return null; // No solution + + if (b == 0) + return new Point( preferredX, h / Math.abs(d) ); + else if (d == 0) + return new Point( h / Math.abs(b), preferredY ); + + const d1:Number = (b*d >= 0) ? d : -d; + // Now we have the following linear sytesm: + // (1) x = preferredX or y = preferredY + // (2) h = abs( bx + d1y ) + + var s:Point; + var x:Number; + var y:Number; + + if (d1 != 0 && preferredX > 0) + { + const invD1:Number = 1 / d1; + preferredX = Math.max(minX, Math.min(maxX, preferredX)); + x = preferredX; + + // Case1: + // bx + d1y >= 0 + // x = preferredX + y = (h - b * x) * invD1; + if (minY <= y && y <= maxY && + b * x + d1 * y >= 0 ) // Satisfy Case1 + { + s = new Point(x, y); + } + + // Case2: + // bx + d1y < 0 + // x = preferredX + y = (-h - b * x) * invD1; + if (minY <= y && y <= maxY && + b * x + d1 * y < 0 ) // Satisfy Case2 + { + // If there is no solution, or the new solution yields smaller value, pick the new solution. + if (!s || transformSize(s.x, s.y, matrix).x > transformSize(x, y, matrix).x) + s = new Point(x, y); + } + } + + if (b != 0 && preferredY > 0) + { + const invB:Number = 1 / b; + preferredY = Math.max(minY, Math.min(maxY, preferredY)); + y = preferredY; + + // Case3: + // bx + d1y >= 0 + // y = preferredY + x = ( h - d1 * y ) * invB; + if (minX <= x && x <= maxX && + b * x + d1 * y >= 0) // Satisfy Case3 + { + // If there is no solution, or the new solution yields smaller value, pick the new solution. + if (!s || transformSize(s.x, s.y, matrix).x > transformSize(x, y, matrix).x) + s = new Point(x, y); + } + + // Case4: + // bx + d1y < 0 + // y = preferredY + x = ( -h - d1 * y ) * invB; + if (minX <= x && x <= maxX && + b * x + d1 * y < 0) // Satisfy Case4 + { + // If there is no solution, or the new solution yields smaller value, pick the new solution. + if (!s || transformSize(s.x, s.y, matrix).x > transformSize(x, y, matrix).x) + s = new Point(x, y); + } + } + + // If there's already a solution that matches preferred dimention, return + if (s) + return s; + + // Find a solution that matches the width and minimizes the height: + const a:Number = matrix.a; + const c:Number = matrix.c; + const c1:Number = ( a*c >= 0 ) ? c : -c; + return solveEquation(b, d1, h, minX, minY, maxX, maxY, a, c1); + } + + /** + * Calculates (x,y) such that the bounding box (0,0,x,y) transformed + * with <code>matrix</code> will have bounding box with + * width equal to <code>w</code>. + * x and y are restricted by <code>minX</code>, <code>maxX</code> and + * <code>minY</code>, <code>maxY</code>. + * + * If possible x will be set to <code>preferredX</code> or + * y will be set to <code>preferredY</code>. + * + * When there are multiple solutions, the function picks the one that + * minimizes the bounding box area of transformed (0,0,x,y). + * + * The functon assumes <code>minX >= 0</code> and <code>minY >= 0</code> + * (solution components x and y are non-negative). + * + * @return Returns Point(x,y) or null if no solution exists. + * + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + static public function calcUBoundsToFitTBoundsWidth(w:Number, + matrix:Matrix, + preferredX:Number, + preferredY:Number, + minX:Number, + minY:Number, + maxX:Number, + maxY:Number):Point + { + // Untransformed bounds size is (x,y). The corners of the untransformed + // bounding box are p1(0,0) p2(x,0) p3(0,y) p4(x,y). + // Matrix is | a c tx | + // | b d ty | + // + // After transfomation with the matrix those four points are: + // t1 = (0, 0) = matrix.deltaTransformPoint(p1) + // t2 = (ax, bx) = matrix.deltaTransformPoint(p2) + // t3 = (cy, dy) = matrix.deltaTransformPoint(p3) + // t4 = (ax + cy, cx + dy) = matrix.deltaTransformPoint(p4) + // + // The transformed bounds bounding box dimensions are (w,h): + // (1) w = max( t1.x, t2.x, t3.x, t4.x ) - min( t1.x, t2.x, t3.x, t4.x) + // (2) h = max( t1.y, t2.y, t3.y, t4.y ) - min( t1.y, t2.y, t3.y, t4.y) + // + // Looking at all the possible cases for min and max functions above, + // we can construct and solve simple linear systems for x and y. + // For example in the case of + // t1.x <= t2.x <= t3.x <= t4.x + // our first equation is + // (1) w = t4.x - t1.x <==> w = ax + cy + // + // To minimize the cases we're looking at we can take advantage of + // the limits we have: x >= 0, y >= 0; + // Taking into account these limits we deduce that: + // a*c >= 0 gives us (1) w = abs( t4.x - t1.x ) = abs( ax + cy ) + // a*c < 0 gives us (1) w = abs( t2.x - t3.x ) = abs( ax - cy ) + // b*d >= 0 gives us (2) h = abs( t4.y - t1.y ) = abs( bx + dy ) + // b*d < 0 gives us (2) h = abs( t2.y - t3.y ) = abs( bx - dy ) + // + // If we do a substitution such that + // c1 = a*c >= 0 ? c : -c + // d1 = b*d >= 0 ? d : -d + // we get the following linear system: + // (1) w = abs( ax + c1y ) + // (2) h = abs( bx + d1y ) + // + // Since we're matching width we only care about (1) + + var a:Number = matrix.a; + var c:Number = matrix.c; + + // If components are very close to zero, zero them out to handle the special cases + if (-1.0e-9 < a && a < +1.0e-9) + a = 0; + if (-1.0e-9 < c && c < +1.0e-9) + c = 0; + + // Handle special cases first + if (a == 0 && c == 0) + return null; // No solution + + if (a == 0) + return new Point( preferredX, w / Math.abs(c) ); + else if (c == 0) + return new Point( w / Math.abs(a), preferredY ); + + const c1:Number = ( a*c >= 0 ) ? c : -c; + // Now we have the following linear sytesm: + // (1) w = abs( ax + c1y ) + // (2) x = preferredX or y = preferredY + + var s:Point; + var x:Number; + var y:Number; + + if (c1 != 0 && preferredX > 0) + { + const invC1:Number = 1 / c1; + preferredX = Math.max(minX, Math.min(maxX, preferredX)); + x = preferredX; + + // Case1: + // a * x + c1 * y >= 0 + // x = preferredX + y = (w - a * x) * invC1; + if (minY <= y && y <= maxY && + a * x + c1 * y >= 0 ) // Satisfy Case1 + { + s = new Point(x, y); + } + + // Case2: + // a * x + c1 * y < 0 + // x = preferredX + y = (-w - a * x) * invC1; + if (minY <= y && y <= maxY && + a * x + c1 * y < 0 ) // Satisfy Case2 + { + // If there is no solution, or the new solution yields smaller value, pick the new solution. + if (!s || transformSize(s.x, s.y, matrix).y > transformSize(x, y, matrix).y) + s = new Point(x, y); + } + } + + if (a != 0 && preferredY > 0) + { + const invA:Number = 1 / a; + preferredY = Math.max(minY, Math.min(maxY, preferredY)); + y = preferredY; + + // Case3: + // a * x + c1 * y >= 0 + // y = preferredY + x = (w - c1 * y ) * invA; + if (minX <= x && x <= maxX && + a * x + c1 * y >= 0) // Satisfy Case3 + { + // If there is no solution, or the new solution yields smaller value, pick the new solution. + if (!s || transformSize(s.x, s.y, matrix).y > transformSize(x, y, matrix).y) + s = new Point(x, y); + } + + // Case4: + // a * x + c1 * y < 0 + // y = preferredY + x = (-w - c1 * y ) * invA; + if (minX <= x && x <= maxX && + a * x + c1 * y < 0) // Satisfy Case4 + { + // If there is no solution, or the new solution yields smaller value, pick the new solution. + if (!s || transformSize(s.x, s.y, matrix).y > transformSize(x, y, matrix).y) + s = new Point(x, y); + } + } + + // If there's already a solution that matches preferred dimention, return + if (s) + return s; + + // Find a solution that matches the width and minimizes the height: + const b:Number = matrix.b; + const d:Number = matrix.d; + const d1:Number = (b*d >= 0) ? d : -d; + return solveEquation(a, c1, w, minX, minY, maxX, maxY, b, d1); + } + + /** + * Finds a solution (x,y) for the equation abs(a*x + c*y) = w such that + * abs(b*x +d*y) is minimized. + * If there is infinite number of solutions, x and y are picked to be + * as close as possible. + * + * Doesn't handle cases where <code>a</code> or <code>c</code> are zero. + * + * @return Returns Point(x,y) + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + static private function solveEquation(a:Number, + c:Number, + w:Number, + minX:Number, + minY:Number, + maxX:Number, + maxY:Number, + b:Number, + d:Number):Point + { + if (a == 0 || c == 0) + return null; // x and y are not co-dependent + + // (1) w = abs( ax + cy ) + // Find the range of solutsion for y and pick: + var x:Number; + var y:Number; + var s:Point; + + // Case1: ax + cy >= 0, from (1) above we get: + // (1) x = (w - cy) / a + // + // Lets find the possible range of values for y: + // We know that + // (3) minX <= x <= maxX + // + // Substitute x with (w - cy)/a in (3): + // (3) minX - w/a <= -cy/a <= maxX - w/a + // (3) min( A, B ) <= y <= max( A, B ), where + // A = (minX - w/a) * (-a/c) + // B = (maxX - w/a) * (-a/c) + + var A:Number = (w - minX * a) / c; + var B:Number = (w - maxX * a) / c; + var rangeMinY:Number = Math.max(minY, Math.min(A, B)); + var rangeMaxY:Number = Math.min(maxY, Math.max(A, B)); + const det:Number = (b * c - a * d); + + // We have a possible solution for Case1 if the range for y is valid + if (rangeMinY <= rangeMaxY) + { + // Now that we have a valid range for y, we need to pick a value within + // that range. + // + // We calculate the value based on a custom condition. + // + // The custom condition that we use could be anything that defines + // another equation for x and y. Some examples are: + // "make x and y as close as possible": y = w / ( a + c ); + // "minimize abs(bx + dy)": y = b * w / det + // "preserve aspect ratio": y = w / ( a * preferredX / preferredY + c ); + if (Math.abs(det) < 1.0e-9) + { + // There is infinite number of solutions, lets pick x == y + y = w / ( a + c ); + } + else + { + // Minimize abs(bx + dy) - we need to solve: + // abs( b * ( w - c * y ) / a + d * y ) = 0 + // which gives us: + y = b * w / det; + } + + // Now that we have the y value calculated from the custom condition, + // we clamp with the range. This gives us a solution with + // values as close as possible to satisfy our custom condition when + // the condition is a linear function of x and y (in our case it is). + y = Math.max(rangeMinY, Math.min(y, rangeMaxY)); + + x = (w - c * y) / a; + return new Point(x, y); + } + + // Case2: ax + cy < 0, from (1) above we get: + // (1) x = (-w - cy) / a + // + // Lets find the possible range of values for y: + // We know that + // (3) minX <= x <= maxX + // + // Substitute x with (-w - cy)/a in (3): + // (3) minX + w/a <= -cy/a <= maxX + w/a + // (3) min( A, B ) <= y <= max( A, B ), where + // A = (minX + w/a) * (-a/c) + // B = (maxX + w/a) * (-a/c) + + A = -(minX * a + w) / c; + B = -(maxX * a + w) / c; + rangeMinY = Math.max(minY, Math.min(A, B)); + rangeMaxY = Math.min(maxY, Math.max(A, B)); + + // We have a possible solution for Case2 if the range for y is valid + if (rangeMinY <= rangeMaxY) + { + // Now that we have a valid range for y, we need to pick a value within + // that range. + // + // We calculate the value based on a custom condition. + // + // The custom condition that we use could be anything that defines + // another equation for x and y. Some examples are: + // "make x and y as close as possible": y = -w / ( a + c ); + // "minimize abs(bx + dy)": y = -b * w / det + // "preserve aspect ratio": y = w / ( a * preferredX / preferredY + c ); + if (Math.abs(det) < 1.0e-9) + { + // There is infinite number of solutions, lets pick x == y + y = -w / ( a + c ); + } + else + { + // Minimize abs(bx + dy) - we need to solve: + // abs( b * ( -w - c * y ) / a + d * y ) = 0 + // which gives us: + y = -b * w / det; + } + + // Now that we have the y value calculated from the custom condition, + // we clamp with the range. This gives us a solution with + // values as close as possible to satisfy our custom condition when + // the condition is a linear function of x and y (in our case it is). + y = Math.max(rangeMinY, Math.min(y, rangeMaxY)); + x = (-w - c * y) / a; + return new Point(x, y); + + } + return null; // No solution + } + + /** + * Calculates (x,y) such that the bounding box (0,0,x,y) transformed + * with <code>matrix</code> will have bounding box (0,0,w,h). + * x and y are restricted by <code>minX</code>, <code>maxX</code> and + * <code>minY</code>, <code>maxY</code>. + * + * When there is infinite number of solutions, the function will + * calculate x and y to be as close as possible. + * + * The functon assumes <code>minX >= 0</code> and <code>minY >= 0</code> + * (solution components x and y are non-negative). + * + * @return Point(x,y) or null if no solution exists. + * + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + static public function calcUBoundsToFitTBounds(w:Number, + h:Number, + matrix:Matrix, + minX:Number, + minY:Number, + maxX:Number, + maxY:Number):Point + { + // Untransformed bounds size is (x,y). The corners of the untransformed + // bounding box are p1(0,0) p2(x,0) p3(0,y) p4(x,y). + // Matrix is | a c tx | + // | b d ty | + // + // After transfomation with the matrix those four points are: + // t1 = (0, 0) = matrix.deltaTransformPoint(p1) + // t2 = (ax, bx) = matrix.deltaTransformPoint(p2) + // t3 = (cy, dy) = matrix.deltaTransformPoint(p3) + // t4 = (ax + cy, cx + dy) = matrix.deltaTransformPoint(p4) + // + // The transformed bounds bounding box dimensions are (w,h): + // (1) w = max( t1.x, t2.x, t3.x, t4.x ) - min( t1.x, t2.x, t3.x, t4.x) + // (2) h = max( t1.y, t2.y, t3.y, t4.y ) - min( t1.y, t2.y, t3.y, t4.y) + // + // Looking at all the possible cases for min and max functions above, + // we can construct and solve simple linear systems for x and y. + // For example in the case of + // t1.x <= t2.x <= t3.x <= t4.x + // our first equation is + // (1) w = t4.x - t1.x <==> w = ax + cy + // + // To minimize the cases we're looking at we can take advantage of + // the limits we have: x >= 0, y >= 0; + // Taking into account these limits we deduce that: + // a*c >= 0 gives us (1) w = abs( t4.x - t1.x ) = abs( ax + cy ) + // a*c < 0 gives us (1) w = abs( t2.x - t3.x ) = abs( ax - cy ) + // b*d >= 0 gives us (2) h = abs( t4.y - t1.y ) = abs( bx + dy ) + // b*d < 0 gives us (2) h = abs( t2.y - t3.y ) = abs( bx - dy ) + // + // If we do a substitution such that + // c1 = a*c >= 0 ? c : -c + // d1 = b*d >= 0 ? d : -d + // we get the following linear system: + // (1) w = abs( ax + c1y ) + // (2) h = abs( bx + d1y ) + // + + var a:Number = matrix.a; + var b:Number = matrix.b; + var c:Number = matrix.c; + var d:Number = matrix.d; + + // If components are very close to zero, zero them out to handle the special cases + if (-1.0e-9 < a && a < +1.0e-9) + a = 0; + if (-1.0e-9 < b && b < +1.0e-9) + b = 0; + if (-1.0e-9 < c && c < +1.0e-9) + c = 0; + if (-1.0e-9 < d && d < +1.0e-9) + d = 0; + + // Handle special cases. + if (b == 0 && c == 0) + { + // No solution in the following cases since the matrix collapses + // all points into a line. + if (a == 0 || d == 0) + return null; + + // (1) w = abs( ax + cy ) <=> w = abs( ax ) <=> w = abs(a)x + // (2) h = abs( bx + dy ) <=> h = abs( dy ) <=> h = abs(d)y + return new Point(w / Math.abs(a), h / Math.abs(d)); + } + + if (a == 0 && d == 0) + { + // No solution in the following cases since the matrix collapses + // all points into a line. + if (b == 0 || c == 0) + return null; + + // (1) w = abs( ax + cy ) <=> w = abs( cy ) <=> w = abs(c)y + // (2) h = abs( bx + dy ) <=> h = abs( bx ) <=> h = abs(b)x + return new Point(h / Math.abs(b), w / Math.abs(c)); + } + + // Handle general cases. + const c1:Number = ( a*c >= 0 ) ? c : -c; + const d1:Number = ( b*d >= 0 ) ? d : -d; + // we get the following linear system: + // (1) w = abs( ax + c1y ) + // (2) h = abs( bx + d1y ) + + // Calculate the determinant of the system + const det:Number = a * d1 - b * c1; + if (Math.abs(det) < 1.0e-9) + { + // No solution in these cases since the matrix + // collapses all points into a line. + if (c1 == 0 || a == 0 || a == -c1) + return null; + + if (Math.abs(a * h - b * w) > 1.0e-9) + return null; // No solution in this case + + // Determinant is zero, the equations (1) & (2) are equivalent and + // we have only one equation: + // (1) w = abs( ax + c1y ) + // + // Solve it finding x and y as close as possible: + return solveEquation(a, c1, w, minX, minX, maxX, maxY, b, d1); + } + + // Pre-multiply w & h by the inverse dteterminant + const invDet:Number = 1 / det; + w *= invDet; + h *= invDet; + + // Case 1: + // a * x + c1 * y >= 0 + // b * x + d1 * y >= 0 + var s:Point; + s = solveSystem(a, c1, b, d1, w, h); + if (s && + minX <= s.x && s.x <= maxX && minY <= s.y && s.y <= maxY && + a * s.x + c1 * s.x >= 0 && + b * s.x + d1 * s.y >= 0) + return s; + + // Case 2: + // a * x + c1 * y >= 0 + // b * x + d1 * y < 0 + s = solveSystem( a, c1, b, d1, w, -h); + if (s && + minX <= s.x && s.x <= maxX && minY <= s.y && s.y <= maxY && + a * s.x + c1 * s.x >= 0 && + b * s.x + d1 * s.y < 0) + return s; + + // Case 3: + // a * x + c1 * y < 0 + // b * x + d1 * y >= 0 + s = solveSystem( a, c1, b, d1, -w, h); + if (s && + minX <= s.x && s.x <= maxX && minY <= s.y && s.y <= maxY && + a * s.x + c1 * s.x < 0 && + b * s.x + d1 * s.y >= 0) + return s; + + // Case 4: + // a * x + c1 * y < 0 + // b * x + d1 * y < 0 + s = solveSystem( a, c1, b, d1, -w, -h); + if (s && + minX <= s.x && s.x <= maxX && minY <= s.y && s.y <= maxY && + a * s.x + c1 * s.x < 0 && + b * s.x + d1 * s.y < 0) + return s; + + return null; // No solution. + } + + /** + * Determine if two Matrix instances are equal. + * + * @return true if the matrices are equal. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public static function isEqual(m1:Matrix, m2:Matrix):Boolean + { + return ((m1 && m2 && + m1.a == m2.a && + m1.b == m2.b && + m1.c == m2.c && + m1.d == m2.d && + m1.tx == m2.tx && + m1.ty == m2.ty) || + (!m1 && !m2)); + } + + /** + * Determine if two Matrix3D instances are equal. + * + * @return true if the matrices are equal. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public static function isEqual3D(m1:Matrix3D, m2:Matrix3D):Boolean + { + if (m1 && m2 && m1.rawData.length == m2.rawData.length) + { + var r1:Vector.<Number> = m1.rawData; + var r2:Vector.<Number> = m2.rawData; + + return (r1[0] == r2[0] && + r1[1] == r2[1] && + r1[2] == r2[2] && + r1[3] == r2[3] && + r1[4] == r2[4] && + r1[5] == r2[5] && + r1[6] == r2[6] && + r1[7] == r2[7] && + r1[8] == r2[8] && + r1[9] == r2[9] && + r1[10] == r2[10] && + r1[11] == r2[11] && + r1[12] == r2[12] && + r1[13] == r2[13] && + r1[14] == r2[14] && + r1[15] == r2[15]); + } + + return (!m1 && !m2); + } + + /** + * Calculates (x,y) such as to satisfy the linear system: + * | a * x + c * y = m + * | b * x + d * y = n + * + * @param mOverDet <code>mOverDet must be equal to m / (a*d - b*c)</code> + * @param nOverDet <code>mOverDet must be equal to n / (a*d - b*c)</code> + * + * @return returns Point(x,y) + * + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + static private function solveSystem(a:Number, + c:Number, + b:Number, + d:Number, + mOverDet:Number, + nOverDet:Number):Point + { + return new Point(d * mOverDet - c * nOverDet, + a * nOverDet - b * mOverDet); + } + + } + +} + http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/fe4b40d3/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Circle.js ---------------------------------------------------------------------- diff --git a/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Circle.js b/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Circle.js index b044c92..a9208d5 100644 --- a/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Circle.js +++ b/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Circle.js @@ -43,7 +43,7 @@ org.apache.flex.core.graphics.Circle.prototype.FLEXJS_CLASS_INFO = /** * @expose * @param {number} x The x location of the center of the circle - * @param {number} y The x location of the center of the circle + * @param {number} y The y location of the center of the circle * @param {number} radius The radius of the circle. */ org.apache.flex.core.graphics.Circle.prototype.drawCircle = function(x, y, radius) { @@ -55,5 +55,6 @@ org.apache.flex.core.graphics.Circle.prototype.drawCircle = function(x, y, radiu circle.setAttribute('rx', String(radius)); circle.setAttribute('ry', String(radius)); this.element.appendChild(circle); - this.resize(x-radius,y-radius,radius*2+this.get_stroke().get_weight()*2,radius*2+this.get_stroke().get_weight()*2); +// this.resize(x-radius,y-radius,radius*2+this.get_stroke().get_weight()*2,radius*2+this.get_stroke().get_weight()*2); + this.setPosition(x-radius+this.get_stroke().get_weight(),y-radius+this.get_stroke().get_weight()); }; http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/fe4b40d3/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Ellipse.js ---------------------------------------------------------------------- diff --git a/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Ellipse.js b/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Ellipse.js index a2033df..cca3c47 100644 --- a/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Ellipse.js +++ b/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Ellipse.js @@ -56,5 +56,5 @@ org.apache.flex.core.graphics.Ellipse.prototype.drawEllipse = function(x, y, wid ellipse.setAttribute('rx', String(width/2)); ellipse.setAttribute('ry', String(height/2)); this.element.appendChild(ellipse); - this.resize(x,y,width+this.get_stroke().get_weight()*2,height+this.get_stroke().get_weight()*2); + this.setPosition(x+this.get_stroke().get_weight(),y+this.get_stroke().get_weight()); }; http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/fe4b40d3/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/GraphicShape.js ---------------------------------------------------------------------- diff --git a/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/GraphicShape.js b/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/GraphicShape.js index e796691..6f5cb13 100644 --- a/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/GraphicShape.js +++ b/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/GraphicShape.js @@ -36,8 +36,21 @@ org.apache.flex.core.graphics.GraphicShape = function() { */ this.stroke_ = null; + /** + * @private + * @type {number} + */ + this.x_ = 0; + + /** + * @private + * @type {number} + */ + this.y_ = 0; + /** * @expose + * @type {SVGElement} */ this.element = document.createElementNS("http://www.w3.org/2000/svg","svg"); @@ -87,7 +100,8 @@ org.apache.flex.core.graphics.GraphicShape.prototype.set_stroke = function(value }; org.apache.flex.core.graphics.GraphicShape.prototype.addedToParent = function() { - //Don't do anything + var bbox = this.element.getBBox(); + this.resize(this.x_,this.y_,bbox.width+this.x_*2,bbox.height+this.y_*2); }; @@ -119,3 +133,13 @@ org.apache.flex.core.graphics.GraphicShape.prototype.resize = function(x,y,w,h) this.element.setAttribute("style", "position:absolute; left:" + String(x) + "px; top:" + String(y) + "px;"); }; +/** + * @expose + * @param {number} x X position + * @param {number} y Y position + */ +org.apache.flex.core.graphics.GraphicShape.prototype.setPosition = function(x,y) { + this.x_ = x; + this.y_ = y; +}; + http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/fe4b40d3/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Path.js ---------------------------------------------------------------------- diff --git a/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Path.js b/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Path.js new file mode 100644 index 0000000..f488ec2 --- /dev/null +++ b/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Path.js @@ -0,0 +1,62 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +goog.provide('org.apache.flex.core.graphics.Path'); + +goog.require('org.apache.flex.core.graphics.GraphicShape'); + + + +/** + * @constructor + * @extends {org.apache.flex.core.graphics.GraphicShape} + */ +org.apache.flex.core.graphics.Path = function() { + org.apache.flex.core.graphics.Path.base(this, 'constructor'); + +}; +goog.inherits(org.apache.flex.core.graphics.Path, + org.apache.flex.core.graphics.GraphicShape); + + +/** + * Metadata + * + * @type {Object.<string, Array.<Object>>} + */ +org.apache.flex.core.graphics.Path.prototype.FLEXJS_CLASS_INFO = + { names: [{ name: 'Path', + qName: 'org.apache.flex.core.graphics.Path' }] }; + + +/** + * @expose + * @param {number} x The x location of the center of the Path + * @param {number} y The y location of the center of the Path + * @param {string} data A string containing a compact represention of the path segments. + * The value is a space-delimited string describing each path segment. Each + * segment entry has a single character which denotes the segment type and + * two or more segment parameters. + * + * If the segment command is upper-case, the parameters are absolute values. + * If the segment command is lower-case, the parameters are relative values. + */ +org.apache.flex.core.graphics.Path.prototype.drawPath = function(x, y, data) { + var style = this.getStyleStr(); + var path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + path.setAttribute('style', style); + path.setAttribute('d', data); + this.element.appendChild(path); + this.setPosition(x+this.get_stroke().get_weight(),y+this.get_stroke().get_weight()); +}; http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/fe4b40d3/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Rect.js ---------------------------------------------------------------------- diff --git a/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Rect.js b/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Rect.js index 35ebda8..18d6467 100644 --- a/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Rect.js +++ b/frameworks/js/FlexJS/src/org/apache/flex/core/graphics/Rect.js @@ -51,10 +51,10 @@ org.apache.flex.core.graphics.Rect.prototype.drawRect = function(x, y, width, he var style = this.getStyleStr(); var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); rect.setAttribute('style', style); - rect.setAttribute('x', String(this.get_stroke().get_weight())); - rect.setAttribute('y', String(this.get_stroke().get_weight())); + rect.setAttribute('x', String(this.get_stroke().get_weight()/2)); + rect.setAttribute('y', String(this.get_stroke().get_weight()/2)); rect.setAttribute('width', String(width)); rect.setAttribute('height', String(height)); this.element.appendChild(rect); - this.resize(x,y,width+this.get_stroke().get_weight()*2,height+this.get_stroke().get_weight()*2); + this.setPosition(x+this.get_stroke().get_weight(),y+this.get_stroke().get_weight()); };
