]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/GeometryUtils.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / utils / GeometryUtils.java
index e329c47c0acea3155243505e8029dbfddf71395a..a3f4ec8bee57b770491b4a7da99a606928cf5fc7 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.scenegraph.utils;\r
-\r
-import java.awt.BasicStroke;\r
-import java.awt.Stroke;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Point2D;\r
-import java.awt.geom.Rectangle2D;\r
-\r
-import org.simantics.utils.page.MarginUtils.Margins;\r
-\r
-/**\r
- * Basic utilities for geometric calculations and testing.\r
- * \r
- * @author J-P Laine\r
- * @author Tuukka Lehtonen\r
- */\r
-public final class GeometryUtils {\r
-\r
-    /**\r
-     * @param p the point of distance measure\r
-     * @param p1 other end of line\r
-     * @param p2 other end of line\r
-     * @return distance of p from the line defined by p1 and p2\r
-     */\r
-    public static double distanceFromLine(Point2D p, Point2D p1, Point2D p2) {\r
-        return distanceFromLine(p1.getX(), p1.getY(), p2.getX(), p2.getY(), p.getX(), p.getY());\r
-    }\r
-\r
-    /**\r
-     * @param x1 line segment end 1 x\r
-     * @param y1 line segment end 1 y\r
-     * @param x2 line segment end 2 x\r
-     * @param y2 line segment end 2 y\r
-     * @param px point x\r
-     * @param py point y\r
-     * @return\r
-     */\r
-    public static double distanceFromLine(double x1, double y1, double x2, double y2, double px, double py) {\r
-        // Adjust vectors relative to x1,y1\r
-        // x2,y2 becomes relative vector from x1,y1 to end of segment\r
-        x2 -= x1;\r
-        y2 -= y1;\r
-        // px,py becomes relative vector from x1,y1 to test point\r
-        px -= x1;\r
-        py -= y1;\r
-        double dotprod = px * x2 + py * y2;\r
-        // dotprod is the length of the px,py vector\r
-        // projected on the x1,y1=>x2,y2 vector times the\r
-        // length of the x1,y1=>x2,y2 vector\r
-        double lineLenSq = x2 * x2 + y2 * y2;\r
-        double lineLen = Math.sqrt(lineLenSq);\r
-        double projLen = dotprod / lineLen;\r
-\r
-        // Check whether the projection of (px,py) is outside the specified line.\r
-        if (projLen < 0) {\r
-            return Math.sqrt(px * px + py * py);\r
-        } else if (projLen > lineLen) {\r
-            double dx = px - x2;\r
-            double dy = py - y2;\r
-            return Math.sqrt(dx * dx + dy * dy);\r
-        }\r
-        return Math.sqrt(px * px + py * py - projLen * projLen);\r
-    }\r
-\r
-    /**\r
-     * @param x1 line segment end 1 x\r
-     * @param y1 line segment end 1 y\r
-     * @param x2 line segment end 2 x\r
-     * @param y2 line segment end 2 y\r
-     * @param px point x\r
-     * @param py point y\r
-     * @return\r
-     */\r
-    public static Point2D intersectionToLine(double x1, double y1, double x2, double y2, double px, double py) {\r
-        double xx2 = x2;\r
-        double yy2 = y2;\r
-        // Adjust vectors relative to x1,y1\r
-        // x2,y2 becomes relative vector from x1,y1 to end of segment\r
-        x2 -= x1;\r
-        y2 -= y1;\r
-        // px,py becomes relative vector from x1,y1 to test point\r
-        px -= x1;\r
-        py -= y1;\r
-        double dotprod = px * x2 + py * y2;\r
-        // dotprod is the length of the px,py vector\r
-        // projected on the x1,y1=>x2,y2 vector times the\r
-        // length of the x1,y1=>x2,y2 vector\r
-        double lineLenSq = x2 * x2 + y2 * y2;\r
-        double lineLen = Math.sqrt(lineLenSq);\r
-        if (lineLen <= Double.MIN_VALUE)\r
-            return new Point2D.Double(x1, y1);\r
-\r
-        double projLen = dotprod / lineLen;\r
-\r
-        // Check whether the projection of (px,py) is outside the specified line.\r
-        if (projLen < 0) {\r
-            return new Point2D.Double(x1, y1);\r
-        } else if (projLen > lineLen) {\r
-            return new Point2D.Double(xx2, yy2);\r
-        }\r
-        return new Point2D.Double(x1 + x2/lineLen*projLen, y1 + y2/lineLen*projLen);\r
-    }\r
-\r
-    /**\r
-     * Expands margins to a rectangle\r
-     * @param rect\r
-     * @param top\r
-     * @param bottom\r
-     * @param left\r
-     * @param right\r
-     */\r
-    public static Rectangle2D expandRectangle(Rectangle2D rect, double top, double bottom, double left, double right)\r
-    {\r
-        if (rect==null) throw new IllegalArgumentException("null arg");\r
-        rect.setRect(\r
-                rect.getX() - left,\r
-                rect.getY() - top,\r
-                rect.getWidth() + left + right,\r
-                rect.getHeight() + top + bottom);\r
-        return rect;\r
-    }\r
-\r
-    public static Rectangle2D expandRectangle(Rectangle2D rect, double horizontalExpand, double verticalExpand) {\r
-        return expandRectangle(rect, verticalExpand, verticalExpand, horizontalExpand, horizontalExpand);\r
-    }\r
-\r
-    public static Rectangle2D expandRectangle(Rectangle2D rect, double evenExpand) {\r
-        return expandRectangle(rect, evenExpand, evenExpand, evenExpand, evenExpand);\r
-    }\r
-\r
-    public static BasicStroke scaleStroke(Stroke stroke, float factor)\r
-    {\r
-        return scaleAndOffsetStroke(stroke, factor, 0.0f);\r
-    }\r
-\r
-    public static BasicStroke offsetStroke(Stroke stroke, float offset)\r
-    {\r
-        BasicStroke s = (BasicStroke) stroke;\r
-        float[] dash = s.getDashArray();\r
-        if (dash == null)\r
-            return s;\r
-\r
-        return new BasicStroke(\r
-                s.getLineWidth(),\r
-                s.getEndCap(),\r
-                s.getLineJoin(),\r
-                s.getMiterLimit(),\r
-                dash,\r
-                s.getDashPhase() + offset\r
-        );\r
-    }\r
-\r
-    public static BasicStroke scaleAndOffsetStroke(Stroke stroke, float factor, float offset)\r
-    {\r
-        BasicStroke s = (BasicStroke) stroke;\r
-        float[] dash = s.getDashArray();\r
-        if (dash!=null) {\r
-            assert(factor!=0);\r
-            dash = scaleArray(factor, dash, new float[dash.length]);\r
-        }\r
-        if (dash==null)\r
-            return new BasicStroke(\r
-                    s.getLineWidth() * factor,\r
-                    s.getEndCap(),\r
-                    s.getLineJoin(),\r
-                    s.getMiterLimit()\r
-            );\r
-        return new BasicStroke(\r
-                s.getLineWidth() * factor,\r
-                s.getEndCap(),\r
-                s.getLineJoin(),\r
-                s.getMiterLimit(),\r
-                dash,\r
-                s.getDashPhase() * factor + offset\r
-        );\r
-    }\r
-\r
-    public static BasicStroke scaleStrokeWidth(Stroke stroke, float factor)\r
-    {\r
-        BasicStroke s = (BasicStroke) stroke;\r
-        return new BasicStroke(\r
-                s.getLineWidth() * factor,\r
-                s.getEndCap(),\r
-                s.getLineJoin(),\r
-                s.getMiterLimit(),\r
-                s.getDashArray(),\r
-                s.getDashPhase()\r
-        );\r
-    }\r
-\r
-    public static BasicStroke scaleAndOffsetStrokeWidth(Stroke stroke, float factor, float widthOffset)\r
-    {\r
-        BasicStroke s = (BasicStroke) stroke;\r
-        return new BasicStroke(\r
-                s.getLineWidth() * factor + widthOffset,\r
-                s.getEndCap(),\r
-                s.getLineJoin(),\r
-                s.getMiterLimit(),\r
-                s.getDashArray(),\r
-                s.getDashPhase()\r
-        );\r
-    }\r
-\r
-    /**\r
-     * Scales every element in array\r
-     * @param array\r
-     * @param factor\r
-     * @return new scaled array\r
-     */\r
-    public static float[] scaleArray(float factor, float [] array, float [] targetArray)\r
-    {\r
-        assert(array!=null);\r
-        if (targetArray==null)\r
-            targetArray = new float[array.length];\r
-        for (int i=0; i<array.length; i++)\r
-        {\r
-            targetArray[i] = array[i] * factor;\r
-        }\r
-        return targetArray;\r
-    }\r
-\r
-    public static double getScale(AffineTransform at)\r
-    {\r
-        double m00 = at.getScaleX();\r
-        double m11 = at.getScaleY();\r
-        double m10 = at.getShearY();\r
-        double m01 = at.getShearX();\r
-\r
-        return Math.sqrt(Math.abs(m00*m11 - m10*m01));\r
-    }\r
-\r
-    public static Point2D getScale2D(AffineTransform at)\r
-    {\r
-        double m00 = at.getScaleX();\r
-        double m11 = at.getScaleY();\r
-        double m10 = at.getShearY();\r
-        double m01 = at.getShearX();\r
-        double sx = Math.sqrt(m00 * m00 + m10 * m10);\r
-        double sy = Math.sqrt(m01 * m01 + m11 * m11);\r
-        return new Point2D.Double(sx, sy);\r
-    }\r
-\r
-    /**\r
-     * Computes the greatest absolute value of the eigenvalues\r
-     * of the matrix.\r
-     */\r
-    public static double getMaxScale(AffineTransform at)\r
-    {\r
-        double m00 = at.getScaleX();\r
-        double m11 = at.getScaleY();\r
-        double m10 = at.getShearY();\r
-        double m01 = at.getShearX();\r
-\r
-        /*\r
-         * If a and b are the eigenvalues of the matrix,\r
-         * then\r
-         *   trace       = a + b\r
-         *   determinant = a*b\r
-         */\r
-        double trace = m00 + m11;\r
-        double determinant = m00*m11 - m10*m01;\r
-\r
-        double dd = trace*trace*0.25 - determinant;\r
-        if(dd >= 0.0) {\r
-            /*\r
-             * trace/2 +- sqrt(trace^2 / 4 - determinant)\r
-             * = (a+b)/2 +- sqrt((a+b)^2 / 4 - a b)\r
-             * = (a+b)/2 +- sqrt(a^2 / 4 + a b / 2 + b^2 / 4 - a b)\r
-             * = (a+b)/2 +- sqrt(a^2 / 4 - a b / 2 + b^2 / 4)\r
-             * = (a+b)/2 +- (a-b)/2\r
-             * = a or b\r
-             * \r
-             * Thus the formula below calculates the greatest\r
-             * absolute value of the eigenvalues max(abs(a), abs(b))\r
-             */\r
-            return Math.abs(trace*0.5) + Math.sqrt(dd);\r
-        }\r
-        else {\r
-            /*\r
-             * If dd < 0, then the eigenvalues a and b are not real.\r
-             * Because both trace and determinant are real, a and b\r
-             * have form:\r
-             *   a = x + i y\r
-             *   b = x - i y\r
-             * \r
-             * Then\r
-             *    sqrt(determinant)\r
-             *    = sqrt(a b)\r
-             *    = sqrt(x^2 + y^2),\r
-             * which is the absolute value of the eigenvalues.\r
-             */\r
-            return Math.sqrt(determinant);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Get a transform that makes canvas area fully visible.\r
-     * @param controlArea\r
-     * @param diagramArea\r
-     * @param margins margins\r
-     * @return transform\r
-     */\r
-    public static AffineTransform fitArea(Rectangle2D controlArea, Rectangle2D diagramArea)\r
-    {\r
-        double controlAspectRatio = controlArea.getWidth() / controlArea.getHeight();\r
-        double canvasAspectRatio  = diagramArea.getWidth() / diagramArea.getHeight();\r
-        // Control is really wide => center canvas horizontally, match vertically\r
-        double scale = 1.0;\r
-        double tx = 0.0;\r
-        double ty = 0.0;\r
-        if (controlAspectRatio>canvasAspectRatio)\r
-        {\r
-            scale = controlArea.getHeight() / diagramArea.getHeight();\r
-            tx = ( controlArea.getWidth() - diagramArea.getWidth() * scale ) / 2;\r
-        } else\r
-            // Control is really tall => center canvas vertically, match horizontally\r
-        {\r
-            scale = controlArea.getWidth() / diagramArea.getWidth();\r
-            ty = ( controlArea.getHeight() - diagramArea.getHeight() * scale ) / 2;\r
-        }\r
-        AffineTransform at = new AffineTransform();\r
-        at.translate(tx, ty);\r
-        at.translate(controlArea.getMinX(), controlArea.getMinY());\r
-        at.scale(scale, scale);\r
-        at.translate(-diagramArea.getMinX(), -diagramArea.getMinY());\r
-        //System.out.println("FIT TRANSFORM: " + at);\r
-        return at;\r
-    }\r
-\r
-    /**\r
-     * Rotates rectangle. Note, general rotation is not supported.\r
-     * @param transform\r
-     * @param rect\r
-     * @return transformed rectangle\r
-     */\r
-    public static Rectangle2D transformRectangle(AffineTransform transform, Rectangle2D rect) {\r
-        int type = transform.getType();\r
-        if (type == AffineTransform.TYPE_IDENTITY)\r
-            return new Rectangle2D.Double(rect.getMinX(), rect.getMinY(), rect.getWidth(), rect.getHeight());\r
-        if ((type & (AffineTransform.TYPE_GENERAL_ROTATION|AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0) {\r
-            double x0 = rect.getMinX();\r
-            double y0 = rect.getMinY();\r
-            double x1 = rect.getMaxX();\r
-            double y1 = rect.getMaxY();\r
-            double m00 = transform.getScaleX();\r
-            double m10 = transform.getShearY();\r
-            double m01 = transform.getShearX();\r
-            double m11 = transform.getScaleY();\r
-            double X0, Y0, X1, Y1;\r
-            if(m00 > 0.0) {\r
-                X0 = m00 * x0;\r
-                X1 = m00 * x1;\r
-            }\r
-            else {\r
-                X1 = m00 * x0;\r
-                X0 = m00 * x1;\r
-            }\r
-            if(m01 > 0.0) {\r
-                X0 += m01 * y0;\r
-                X1 += m01 * y1;\r
-            }\r
-            else {\r
-                X1 += m01 * y0;\r
-                X0 += m01 * y1;\r
-            }\r
-            if(m10 > 0.0) {\r
-                Y0 = m10 * x0;\r
-                Y1 = m10 * x1;\r
-            }\r
-            else {\r
-                Y1 = m10 * x0;\r
-                Y0 = m10 * x1;\r
-            }\r
-            if(m11 > 0.0) {\r
-                Y0 += m11 * y0;\r
-                Y1 += m11 * y1;\r
-            }\r
-            else {\r
-                Y1 += m11 * y0;\r
-                Y0 += m11 * y1;\r
-            }\r
-            return new Rectangle2D.Double(X0+transform.getTranslateX(),\r
-                    Y0+transform.getTranslateY(), X1-X0, Y1-Y0);\r
-        }\r
-    \r
-        // Generic but much slower.\r
-        return transform.createTransformedShape(rect).getBounds2D();\r
-    }\r
-\r
-    public static Rectangle2D transformRectangleInv(AffineTransform transform, Rectangle2D rect) {\r
-        assert( (transform.getType() & (AffineTransform.TYPE_GENERAL_ROTATION|AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0);\r
-        double[] mx = new double[6];\r
-        transform.getMatrix(mx);\r
-        double x0 = rect.getMinX() - mx[4];\r
-        double y0 = rect.getMinY() - mx[5];\r
-        double x1 = rect.getMaxX() - mx[4];\r
-        double y1 = rect.getMaxY() - mx[5];\r
-        double det = mx[0]*mx[3] - mx[1]*mx[2];\r
-        double m00 = mx[3] / det;\r
-        double m10 = -mx[1] / det;\r
-        double m01 = -mx[2] / det;\r
-        double m11 = mx[0] / det;\r
-        double X0, Y0, X1, Y1;\r
-        if(m00 > 0.0) {\r
-            X0 = m00 * x0;\r
-            X1 = m00 * x1;\r
-        }\r
-        else {\r
-            X1 = m00 * x0;\r
-            X0 = m00 * x1;\r
-        }\r
-        if(m01 > 0.0) {\r
-            X0 += m01 * y0;\r
-            X1 += m01 * y1;\r
-        }\r
-        else {\r
-            X1 += m01 * y0;\r
-            X0 += m01 * y1;\r
-        }\r
-        if(m10 > 0.0) {\r
-            Y0 = m10 * x0;\r
-            Y1 = m10 * x1;\r
-        }\r
-        else {\r
-            Y1 = m10 * x0;\r
-            Y0 = m10 * x1;\r
-        }\r
-        if(m11 > 0.0) {\r
-            Y0 += m11 * y0;\r
-            Y1 += m11 * y1;\r
-        }\r
-        else {\r
-            Y1 += m11 * y0;\r
-            Y0 += m11 * y1;\r
-        }\r
-        return new Rectangle2D.Double(X0, Y0, X1-X0, Y1-Y0);\r
-    }\r
-\r
-    /**\r
-     * @param points\r
-     * @return millimeters\r
-     */\r
-    public static float pointToMillimeter(float points) {\r
-        return points * 25.4f / 72.0f;\r
-    }\r
-\r
-    /**\r
-     * @param points\r
-     * @return millimeters\r
-     */\r
-    public static double pointToMillimeter(double points) {\r
-        return points * 25.4 / 72.0;\r
-    }\r
-\r
-    /**\r
-     * Get a transform that makes canvas area fully visible.\r
-     * @param controlArea\r
-     * @param diagramArea\r
-     * @param margins margins\r
-     * @return transform\r
-     */\r
-    public static AffineTransform fitArea(Rectangle2D controlArea, Rectangle2D diagramArea, Margins margins)\r
-    {\r
-        diagramArea = expandRectangle(diagramArea,\r
-                margins.top.diagramAbsolute,\r
-                margins.bottom.diagramAbsolute,\r
-                margins.left.diagramAbsolute,\r
-                margins.right.diagramAbsolute);\r
-        controlArea = expandRectangle(controlArea,\r
-                -margins.top.controlAbsolute   - margins.top.controlRelative * controlArea.getHeight(),\r
-                -margins.bottom.controlAbsolute        - margins.bottom.controlRelative * controlArea.getHeight(),\r
-                -margins.left.controlAbsolute  - margins.left.controlRelative * controlArea.getWidth(),\r
-                -margins.right.controlAbsolute - margins.right.controlRelative * controlArea.getWidth());\r
-\r
-        double controlAspectRatio = controlArea.getWidth() / controlArea.getHeight();\r
-        double canvasAspectRatio  = diagramArea.getWidth() / diagramArea.getHeight();\r
-        // Control is really wide => center canvas horizontally, match vertically\r
-        double scale = 1.0;\r
-        double tx = 0.0;\r
-        double ty = 0.0;\r
-        if (controlAspectRatio>canvasAspectRatio)\r
-        {\r
-            scale = controlArea.getHeight() / diagramArea.getHeight();\r
-            tx = ( controlArea.getWidth() - diagramArea.getWidth() * scale ) / 2;\r
-        } else\r
-            // Control is really tall => center canvas vertically, match horizontally\r
-        {\r
-            scale = controlArea.getWidth() / diagramArea.getWidth();\r
-            ty = ( controlArea.getHeight() - diagramArea.getHeight() * scale ) / 2;\r
-        }\r
-        AffineTransform at = new AffineTransform();\r
-        at.translate(tx, ty);\r
-        at.translate(controlArea.getMinX(), controlArea.getMinY());\r
-        at.scale(scale, scale);\r
-        at.translate(-diagramArea.getMinX(), -diagramArea.getMinY());\r
-        //System.out.println("FIT TRANSFORM: " + at);\r
-        return at;\r
-    }\r
-    \r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.scenegraph.utils;
+
+import java.awt.BasicStroke;
+import java.awt.Stroke;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+import org.simantics.utils.page.MarginUtils.Margins;
+
+/**
+ * Basic utilities for geometric calculations and testing.
+ * 
+ * @author J-P Laine
+ * @author Tuukka Lehtonen
+ */
+public final class GeometryUtils {
+
+    /**
+     * @param p the point of distance measure
+     * @param p1 other end of line
+     * @param p2 other end of line
+     * @return distance of p from the line defined by p1 and p2
+     */
+    public static double distanceFromLine(Point2D p, Point2D p1, Point2D p2) {
+        return distanceFromLine(p1.getX(), p1.getY(), p2.getX(), p2.getY(), p.getX(), p.getY());
+    }
+
+    /**
+     * @param x1 line segment end 1 x
+     * @param y1 line segment end 1 y
+     * @param x2 line segment end 2 x
+     * @param y2 line segment end 2 y
+     * @param px point x
+     * @param py point y
+     * @return
+     */
+    public static double distanceFromLine(double x1, double y1, double x2, double y2, double px, double py) {
+        // Adjust vectors relative to x1,y1
+        // x2,y2 becomes relative vector from x1,y1 to end of segment
+        x2 -= x1;
+        y2 -= y1;
+        // px,py becomes relative vector from x1,y1 to test point
+        px -= x1;
+        py -= y1;
+        double dotprod = px * x2 + py * y2;
+        // dotprod is the length of the px,py vector
+        // projected on the x1,y1=>x2,y2 vector times the
+        // length of the x1,y1=>x2,y2 vector
+        double lineLenSq = x2 * x2 + y2 * y2;
+        double lineLen = Math.sqrt(lineLenSq);
+        double projLen = dotprod / lineLen;
+
+        // Check whether the projection of (px,py) is outside the specified line.
+        if (projLen < 0) {
+            return Math.sqrt(px * px + py * py);
+        } else if (projLen > lineLen) {
+            double dx = px - x2;
+            double dy = py - y2;
+            return Math.sqrt(dx * dx + dy * dy);
+        }
+        return Math.sqrt(px * px + py * py - projLen * projLen);
+    }
+
+    /**
+     * @param x1 line segment end 1 x
+     * @param y1 line segment end 1 y
+     * @param x2 line segment end 2 x
+     * @param y2 line segment end 2 y
+     * @param px point x
+     * @param py point y
+     * @return
+     */
+    public static Point2D intersectionToLine(double x1, double y1, double x2, double y2, double px, double py) {
+        double xx2 = x2;
+        double yy2 = y2;
+        // Adjust vectors relative to x1,y1
+        // x2,y2 becomes relative vector from x1,y1 to end of segment
+        x2 -= x1;
+        y2 -= y1;
+        // px,py becomes relative vector from x1,y1 to test point
+        px -= x1;
+        py -= y1;
+        double dotprod = px * x2 + py * y2;
+        // dotprod is the length of the px,py vector
+        // projected on the x1,y1=>x2,y2 vector times the
+        // length of the x1,y1=>x2,y2 vector
+        double lineLenSq = x2 * x2 + y2 * y2;
+        double lineLen = Math.sqrt(lineLenSq);
+        if (lineLen <= Double.MIN_VALUE)
+            return new Point2D.Double(x1, y1);
+
+        double projLen = dotprod / lineLen;
+
+        // Check whether the projection of (px,py) is outside the specified line.
+        if (projLen < 0) {
+            return new Point2D.Double(x1, y1);
+        } else if (projLen > lineLen) {
+            return new Point2D.Double(xx2, yy2);
+        }
+        return new Point2D.Double(x1 + x2/lineLen*projLen, y1 + y2/lineLen*projLen);
+    }
+
+    /**
+     * Expands margins to a rectangle
+     * @param rect
+     * @param top
+     * @param bottom
+     * @param left
+     * @param right
+     */
+    public static Rectangle2D expandRectangle(Rectangle2D rect, double top, double bottom, double left, double right)
+    {
+        if (rect==null) throw new IllegalArgumentException("null arg");
+        rect.setRect(
+                rect.getX() - left,
+                rect.getY() - top,
+                rect.getWidth() + left + right,
+                rect.getHeight() + top + bottom);
+        return rect;
+    }
+
+    public static Rectangle2D expandRectangle(Rectangle2D rect, double horizontalExpand, double verticalExpand) {
+        return expandRectangle(rect, verticalExpand, verticalExpand, horizontalExpand, horizontalExpand);
+    }
+
+    public static Rectangle2D expandRectangle(Rectangle2D rect, double evenExpand) {
+        return expandRectangle(rect, evenExpand, evenExpand, evenExpand, evenExpand);
+    }
+
+    public static BasicStroke scaleStroke(Stroke stroke, float factor)
+    {
+        return scaleAndOffsetStroke(stroke, factor, 0.0f);
+    }
+
+    public static BasicStroke offsetStroke(Stroke stroke, float offset)
+    {
+        BasicStroke s = (BasicStroke) stroke;
+        float[] dash = s.getDashArray();
+        if (dash == null)
+            return s;
+
+        return new BasicStroke(
+                s.getLineWidth(),
+                s.getEndCap(),
+                s.getLineJoin(),
+                s.getMiterLimit(),
+                dash,
+                s.getDashPhase() + offset
+        );
+    }
+
+    public static BasicStroke scaleAndOffsetStroke(Stroke stroke, float factor, float offset)
+    {
+        BasicStroke s = (BasicStroke) stroke;
+        float[] dash = s.getDashArray();
+        if (dash!=null) {
+            assert(factor!=0);
+            dash = scaleArray(factor, dash, new float[dash.length]);
+        }
+        if (dash==null)
+            return new BasicStroke(
+                    s.getLineWidth() * factor,
+                    s.getEndCap(),
+                    s.getLineJoin(),
+                    s.getMiterLimit()
+            );
+        return new BasicStroke(
+                s.getLineWidth() * factor,
+                s.getEndCap(),
+                s.getLineJoin(),
+                s.getMiterLimit(),
+                dash,
+                s.getDashPhase() * factor + offset
+        );
+    }
+
+    public static BasicStroke scaleStrokeWidth(Stroke stroke, float factor)
+    {
+        BasicStroke s = (BasicStroke) stroke;
+        return new BasicStroke(
+                s.getLineWidth() * factor,
+                s.getEndCap(),
+                s.getLineJoin(),
+                s.getMiterLimit(),
+                s.getDashArray(),
+                s.getDashPhase()
+        );
+    }
+
+    public static BasicStroke scaleAndOffsetStrokeWidth(Stroke stroke, float factor, float widthOffset)
+    {
+        BasicStroke s = (BasicStroke) stroke;
+        return new BasicStroke(
+                s.getLineWidth() * factor + widthOffset,
+                s.getEndCap(),
+                s.getLineJoin(),
+                s.getMiterLimit(),
+                s.getDashArray(),
+                s.getDashPhase()
+        );
+    }
+
+    /**
+     * Scales every element in array
+     * @param array
+     * @param factor
+     * @return new scaled array
+     */
+    public static float[] scaleArray(float factor, float [] array, float [] targetArray)
+    {
+        assert(array!=null);
+        if (targetArray==null)
+            targetArray = new float[array.length];
+        for (int i=0; i<array.length; i++)
+        {
+            targetArray[i] = array[i] * factor;
+        }
+        return targetArray;
+    }
+
+    public static double getScale(AffineTransform at)
+    {
+        double m00 = at.getScaleX();
+        double m11 = at.getScaleY();
+        double m10 = at.getShearY();
+        double m01 = at.getShearX();
+
+        return Math.sqrt(Math.abs(m00*m11 - m10*m01));
+    }
+
+    public static Point2D getScale2D(AffineTransform at)
+    {
+        double m00 = at.getScaleX();
+        double m11 = at.getScaleY();
+        double m10 = at.getShearY();
+        double m01 = at.getShearX();
+        double sx = Math.sqrt(m00 * m00 + m10 * m10);
+        double sy = Math.sqrt(m01 * m01 + m11 * m11);
+        return new Point2D.Double(sx, sy);
+    }
+
+    /**
+     * Computes the greatest absolute value of the eigenvalues
+     * of the matrix.
+     */
+    public static double getMaxScale(AffineTransform at)
+    {
+        double m00 = at.getScaleX();
+        double m11 = at.getScaleY();
+        double m10 = at.getShearY();
+        double m01 = at.getShearX();
+
+        /*
+         * If a and b are the eigenvalues of the matrix,
+         * then
+         *   trace       = a + b
+         *   determinant = a*b
+         */
+        double trace = m00 + m11;
+        double determinant = m00*m11 - m10*m01;
+
+        double dd = trace*trace*0.25 - determinant;
+        if(dd >= 0.0) {
+            /*
+             * trace/2 +- sqrt(trace^2 / 4 - determinant)
+             * = (a+b)/2 +- sqrt((a+b)^2 / 4 - a b)
+             * = (a+b)/2 +- sqrt(a^2 / 4 + a b / 2 + b^2 / 4 - a b)
+             * = (a+b)/2 +- sqrt(a^2 / 4 - a b / 2 + b^2 / 4)
+             * = (a+b)/2 +- (a-b)/2
+             * = a or b
+             * 
+             * Thus the formula below calculates the greatest
+             * absolute value of the eigenvalues max(abs(a), abs(b))
+             */
+            return Math.abs(trace*0.5) + Math.sqrt(dd);
+        }
+        else {
+            /*
+             * If dd < 0, then the eigenvalues a and b are not real.
+             * Because both trace and determinant are real, a and b
+             * have form:
+             *   a = x + i y
+             *   b = x - i y
+             * 
+             * Then
+             *    sqrt(determinant)
+             *    = sqrt(a b)
+             *    = sqrt(x^2 + y^2),
+             * which is the absolute value of the eigenvalues.
+             */
+            return Math.sqrt(determinant);
+        }
+    }
+
+    /**
+     * Get a transform that makes canvas area fully visible.
+     * @param controlArea
+     * @param diagramArea
+     * @param margins margins
+     * @return transform
+     */
+    public static AffineTransform fitArea(Rectangle2D controlArea, Rectangle2D diagramArea)
+    {
+        double controlAspectRatio = controlArea.getWidth() / controlArea.getHeight();
+        double canvasAspectRatio  = diagramArea.getWidth() / diagramArea.getHeight();
+        // Control is really wide => center canvas horizontally, match vertically
+        double scale = 1.0;
+        double tx = 0.0;
+        double ty = 0.0;
+        if (controlAspectRatio>canvasAspectRatio)
+        {
+            scale = controlArea.getHeight() / diagramArea.getHeight();
+            tx = ( controlArea.getWidth() - diagramArea.getWidth() * scale ) / 2;
+        } else
+            // Control is really tall => center canvas vertically, match horizontally
+        {
+            scale = controlArea.getWidth() / diagramArea.getWidth();
+            ty = ( controlArea.getHeight() - diagramArea.getHeight() * scale ) / 2;
+        }
+        AffineTransform at = new AffineTransform();
+        at.translate(tx, ty);
+        at.translate(controlArea.getMinX(), controlArea.getMinY());
+        at.scale(scale, scale);
+        at.translate(-diagramArea.getMinX(), -diagramArea.getMinY());
+        //System.out.println("FIT TRANSFORM: " + at);
+        return at;
+    }
+
+    /**
+     * Rotates rectangle. Note, general rotation is not supported.
+     * @param transform
+     * @param rect
+     * @return transformed rectangle
+     */
+    public static Rectangle2D transformRectangle(AffineTransform transform, Rectangle2D rect) {
+        int type = transform.getType();
+        if (type == AffineTransform.TYPE_IDENTITY)
+            return new Rectangle2D.Double(rect.getMinX(), rect.getMinY(), rect.getWidth(), rect.getHeight());
+        if ((type & (AffineTransform.TYPE_GENERAL_ROTATION|AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0) {
+            double x0 = rect.getMinX();
+            double y0 = rect.getMinY();
+            double x1 = rect.getMaxX();
+            double y1 = rect.getMaxY();
+            double m00 = transform.getScaleX();
+            double m10 = transform.getShearY();
+            double m01 = transform.getShearX();
+            double m11 = transform.getScaleY();
+            double X0, Y0, X1, Y1;
+            if(m00 > 0.0) {
+                X0 = m00 * x0;
+                X1 = m00 * x1;
+            }
+            else {
+                X1 = m00 * x0;
+                X0 = m00 * x1;
+            }
+            if(m01 > 0.0) {
+                X0 += m01 * y0;
+                X1 += m01 * y1;
+            }
+            else {
+                X1 += m01 * y0;
+                X0 += m01 * y1;
+            }
+            if(m10 > 0.0) {
+                Y0 = m10 * x0;
+                Y1 = m10 * x1;
+            }
+            else {
+                Y1 = m10 * x0;
+                Y0 = m10 * x1;
+            }
+            if(m11 > 0.0) {
+                Y0 += m11 * y0;
+                Y1 += m11 * y1;
+            }
+            else {
+                Y1 += m11 * y0;
+                Y0 += m11 * y1;
+            }
+            return new Rectangle2D.Double(X0+transform.getTranslateX(),
+                    Y0+transform.getTranslateY(), X1-X0, Y1-Y0);
+        }
+    
+        // Generic but much slower.
+        return transform.createTransformedShape(rect).getBounds2D();
+    }
+
+    public static Rectangle2D transformRectangleInv(AffineTransform transform, Rectangle2D rect) {
+        assert( (transform.getType() & (AffineTransform.TYPE_GENERAL_ROTATION|AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0);
+        double[] mx = new double[6];
+        transform.getMatrix(mx);
+        double x0 = rect.getMinX() - mx[4];
+        double y0 = rect.getMinY() - mx[5];
+        double x1 = rect.getMaxX() - mx[4];
+        double y1 = rect.getMaxY() - mx[5];
+        double det = mx[0]*mx[3] - mx[1]*mx[2];
+        double m00 = mx[3] / det;
+        double m10 = -mx[1] / det;
+        double m01 = -mx[2] / det;
+        double m11 = mx[0] / det;
+        double X0, Y0, X1, Y1;
+        if(m00 > 0.0) {
+            X0 = m00 * x0;
+            X1 = m00 * x1;
+        }
+        else {
+            X1 = m00 * x0;
+            X0 = m00 * x1;
+        }
+        if(m01 > 0.0) {
+            X0 += m01 * y0;
+            X1 += m01 * y1;
+        }
+        else {
+            X1 += m01 * y0;
+            X0 += m01 * y1;
+        }
+        if(m10 > 0.0) {
+            Y0 = m10 * x0;
+            Y1 = m10 * x1;
+        }
+        else {
+            Y1 = m10 * x0;
+            Y0 = m10 * x1;
+        }
+        if(m11 > 0.0) {
+            Y0 += m11 * y0;
+            Y1 += m11 * y1;
+        }
+        else {
+            Y1 += m11 * y0;
+            Y0 += m11 * y1;
+        }
+        return new Rectangle2D.Double(X0, Y0, X1-X0, Y1-Y0);
+    }
+
+    /**
+     * @param points
+     * @return millimeters
+     */
+    public static float pointToMillimeter(float points) {
+        return points * 25.4f / 72.0f;
+    }
+
+    /**
+     * @param points
+     * @return millimeters
+     */
+    public static double pointToMillimeter(double points) {
+        return points * 25.4 / 72.0;
+    }
+
+    /**
+     * Get a transform that makes canvas area fully visible.
+     * @param controlArea
+     * @param diagramArea
+     * @param margins margins
+     * @return transform
+     */
+    public static AffineTransform fitArea(Rectangle2D controlArea, Rectangle2D diagramArea, Margins margins)
+    {
+        diagramArea = expandRectangle(diagramArea,
+                margins.top.diagramAbsolute,
+                margins.bottom.diagramAbsolute,
+                margins.left.diagramAbsolute,
+                margins.right.diagramAbsolute);
+        controlArea = expandRectangle(controlArea,
+                -margins.top.controlAbsolute   - margins.top.controlRelative * controlArea.getHeight(),
+                -margins.bottom.controlAbsolute        - margins.bottom.controlRelative * controlArea.getHeight(),
+                -margins.left.controlAbsolute  - margins.left.controlRelative * controlArea.getWidth(),
+                -margins.right.controlAbsolute - margins.right.controlRelative * controlArea.getWidth());
+
+        double controlAspectRatio = controlArea.getWidth() / controlArea.getHeight();
+        double canvasAspectRatio  = diagramArea.getWidth() / diagramArea.getHeight();
+        // Control is really wide => center canvas horizontally, match vertically
+        double scale = 1.0;
+        double tx = 0.0;
+        double ty = 0.0;
+        if (controlAspectRatio>canvasAspectRatio)
+        {
+            scale = controlArea.getHeight() / diagramArea.getHeight();
+            tx = ( controlArea.getWidth() - diagramArea.getWidth() * scale ) / 2;
+        } else
+            // Control is really tall => center canvas vertically, match horizontally
+        {
+            scale = controlArea.getWidth() / diagramArea.getWidth();
+            ty = ( controlArea.getHeight() - diagramArea.getHeight() * scale ) / 2;
+        }
+        AffineTransform at = new AffineTransform();
+        at.translate(tx, ty);
+        at.translate(controlArea.getMinX(), controlArea.getMinY());
+        at.scale(scale, scale);
+        at.translate(-diagramArea.getMinX(), -diagramArea.getMinY());
+        //System.out.println("FIT TRANSFORM: " + at);
+        return at;
+    }
+    
+}