-/*******************************************************************************\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;
+ }
+
+}