/*
* Scriptographer
*
* This file is part of Scriptographer, a Scripting Plugin for Adobe Illustrator
* http://scriptographer.org/
*
* Copyright (c) 2002-2010, Juerg Lehni
* http://scratchdisk.com/
*
* All rights reserved. See LICENSE file for details.
*
* File created on 19.12.2004.
*/
package com.scriptographer.ai;
import java.awt.geom.Rectangle2D;
import com.scratchdisk.script.ArgumentReader;
import com.scratchdisk.script.ChangeEmitter;
import com.scratchdisk.script.ChangeReceiver;
import com.scriptographer.ScriptographerEngine;
/**
* A Rectangle specifies an area that is enclosed by it's top-left point (x, y),
* its width, and its height. It should not be confused with a rectangular path,
* it is not an item.
*
* @author lehni
*/
public class Rectangle implements ChangeEmitter, ChangeReceiver {
protected double x;
protected double y;
protected double width;
protected double height;
public Rectangle() {
x = y = width = height = 0;
}
/**
* Creates a rectangle object.
*
* @param x the left coordinate {@default 0}
* @param y the top coordinate if in the top-down coordinate system, bottom
* otherwise. See
* {@link com.scriptographer.sg.Script#getCoordinateSystem() } for
* more information. {@default 0}
* @param width {@default 0}
* @param height {@default 0}
*/
public Rectangle(double x, double y, double width, double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public Rectangle(float x, float y, float width, float height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public Rectangle(Point point, Size size) {
this(point.x, point.y, size.width, size.height);
}
/**
* Creates a rectangle object from the passed points. These do not
* necessarily need to be the top left and bottom right corners, the
* constructor figures out how to fit a rectangle between them.
*
* @param point1 The first point defining the rectangle
* @param point2 The second point defining the rectangle
*/
public Rectangle(Point point1, Point point2) {
this(point1.x, point1.y, point2.x - point1.x, point2.y - point1.y);
if (width < 0) {
x = point2.x;
width = -width;
}
if (height < 0) {
y = point2.y;
height = -height;
}
}
/**
* Creates a new rectangle object from the passed rectangle object.
* @param rt
*/
public Rectangle(Rectangle rt) {
this(rt.x, rt.y, rt.width, rt.height);
}
/**
* @jshide
*/
public Rectangle(Rectangle2D rt) {
this(rt.getX(), rt.getY(), rt.getWidth(), rt.getHeight());
}
/**
* @jshide
*/
public Rectangle(ArgumentReader reader) {
this(reader.readDouble("x", 0),
reader.readDouble("y", 0),
reader.readDouble("width", 0),
reader.readDouble("height", 0));
}
/**
* Changes the boundary properties of the rectangle.
*
* @jshide
*/
public void set(double x, double y, double width, double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
/**
* The x position of the rectangle.
*/
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
/**
* The y position of the rectangle. This is the top coordinate if in the
* top-down coordinate system, bottom otherwise. See
* {@link com.scriptographer.sg.Script#getCoordinateSystem() } for more
* information.
*/
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
/**
* The width of the rectangle.
*/
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
/**
* The height of the rectangle.
*/
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public Point getPoint() {
return new Point(x, y);
}
public void setPoint(Point point) {
this.x = point.x;
this.y = point.y;
}
/**
* @jshide
*/
public void setPoint(double x, double y) {
this.x = x;
this.y = y;
}
public Size getSize() {
return new Size(width, height);
}
public void setSize(Size size) {
this.width = size.width;
this.height = size.height;
}
/**
* @jshide
*/
public void setSize(double width, double height) {
this.width = width;
this.height = height;
}
/**
* {@grouptitle Side Positions}
*
* The position of the left hand side of the rectangle. Note that this
* doesn't move the whole rectangle; the right hand side stays where it was.
*/
public double getLeft() {
return x;
}
public void setLeft(double left) {
// right should not move
width -= left - x;
x = left;
}
/**
* The top coordinate of the rectangle. Note that this doesn't move the
* whole rectangle: the bottom won't move.
*/
public double getTop() {
if (ScriptographerEngine.topDownCoordinates) {
return y;
} else {
return y + height;
}
}
public void setTop(double top) {
if (ScriptographerEngine.topDownCoordinates) {
// bottom should not move
height -= top - y;
y = top;
} else {
height = top - y;
}
}
/**
* The position of the right hand side of the rectangle. Note that this
* doesn't move the whole rectangle; the left hand side stays where it was.
*/
public double getRight() {
return x + width;
}
public void setRight(double right) {
width = right - x;
}
/**
* The bottom coordinate of the rectangle. Note that this doesn't move the
* whole rectangle: the top won't move.
*/
public double getBottom() {
if (ScriptographerEngine.topDownCoordinates) {
return y + height;
} else {
return y;
}
}
public void setBottom(double bottom) {
if (ScriptographerEngine.topDownCoordinates) {
height = bottom - y;
} else {
// top should not move
height -= bottom - y;
y = bottom;
}
}
private double getCenterX() {
return x + width * 0.5f;
}
private double getCenterY() {
return y + height * 0.5f;
}
private void setCenterX(double x) {
this.x = x - width * 0.5f;
}
private void setCenterY(double y) {
this.y = y - height * 0.5f;
}
/**
* {@grouptitle Corner and Center Point Positions}
*
* The center point of the rectangle.
*/
public Point getCenter() {
return new Point(getCenterX(), getCenterY());
}
public void setCenter(Point center) {
setCenterX(center.x);
setCenterY(center.y);
}
/**
* The top left point of the rectangle.
*/
public Point getTopLeft() {
return new Point(getLeft(), getTop());
}
public void setTopLeft(Point pt) {
setLeft(pt.x);
setTop(pt.y);
}
/**
* The top right point of the rectangle.
*/
public Point getTopRight() {
return new Point(getRight(), getTop());
}
public void setTopRight(Point pt) {
setRight(pt.x);
setTop(pt.y);
}
/**
* The bottom left point of the rectangle.
*/
public Point getBottomLeft() {
return new Point(getLeft(), getBottom());
}
public void setBottomLeft(Point pt) {
setLeft(pt.x);
setBottom(pt.y);
}
/**
* The bottom right point of the rectangle.
*/
public Point getBottomRight() {
return new Point(getRight(), getBottom());
}
public void setBottomRight(Point pt) {
setRight(pt.x);
setBottom(pt.y);
}
public Point getLeftCenter() {
return new Point(getLeft(), getCenterY());
}
public void setLeftCenter(Point pt) {
setLeft(pt.x);
setCenterY(pt.y);
}
public Point getTopCenter() {
return new Point(getCenterX(), getTop());
}
public void setTopCenter(Point pt) {
setCenterX(pt.x);
setTop(pt.y);
}
public Point getRightCenter() {
return new Point(getRight(), getCenterY());
}
public void setRightCenter(Point pt) {
setRight(pt.x);
setCenterY(pt.y);
}
public Point getBottomCenter() {
return new Point(getCenterX(), getBottom());
}
public void setBottomCenter(Point pt) {
setCenterX(pt.x);
setBottom(pt.y);
}
/**
* Clones the rectangle.
*/
public Object clone() {
return new Rectangle(this);
}
public boolean equals(Object object) {
if (object instanceof Rectangle) {
Rectangle rt = (Rectangle) object;
return rt.x == x && rt.y == y &&
rt.width == width && rt.height == height;
}
// TODO: support other rect types?
return false;
}
/**
* Returns {@true if the rectangle is empty}
*/
public boolean isEmpty() {
return width == 0 || height == 0;
}
/**
* {@grouptitle Geometric Tests}
*
* Tests if the specified point is inside the boundary of the rectangle.
*
* @param point the specified point
* @return {@true if the point is inside the rectangle's
* boundary}
*/
public boolean contains(Point point) {
return contains(point.x, point.y);
}
/**
* Tests if specified coordinates are inside the boundary of the rectangle.
*
* @param x, y the coordinates to test
* @return {@true if the specified coordinates are inside the
* boundary of the rectangle}
*
* @jshide
*/
public boolean contains(double x, double y) {
return x >= this.x && y >= this.y
&& x <= this.x + width && y <= this.y + height;
}
/**
* Tests if the interior of the rectangle entirely contains the specified
* rectangle.
*
* @param rect The specified rectangle
* @return {@true if the rectangle entirely contains the
* specified rectangle}
*/
public boolean contains(Rectangle rect) {
return rect.x >= x && rect.y >= y
&& rect.x + rect.width <= x + width
&& rect.y + rect.height <= y + height;
}
/**
* Tests if the interior of this rectangle intersects the interior of
* another rectangle. Rectangles just touching each other are considered as
* non-intersecting.
*
* @param rect the specified rectangle
* @return {@true if the rectangle and the specified rectangle intersect
* each other}
*/
public boolean intersects(Rectangle rect) {
return rect.x + rect.width > this.x
&& rect.y + rect.height > this.y
&& rect.x < this.x + this.width
&& rect.y < this.y + this.height;
}
/**
* {@grouptitle Boolean Operations}
*
* Returns a new rectangle representing the intersection of this rectangle
* with the specified rectangle.
*
* @param rect The rectangle to be intersected with this rectangle
* @return The largest rectangle contained in both the specified rectangle
* and in this rectangle.
*/
public Rectangle intersect(Rectangle rect) {
double x1 = Math.max(x, rect.x);
double y1 = Math.max(y, rect.y);
double x2 = Math.min(x + width, rect.x + rect.width);
double y2 = Math.min(y + height, rect.y + rect.height);
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
}
/**
* Returns a new rectangle representing the union of this rectangle with the
* specified rectangle.
*
* @param rect the rectangle to be combined with this rectangle
* @return the smallest rectangle containing both the specified rectangle
* and this rectangle.
*/
public Rectangle unite(Rectangle rect) {
double x1 = Math.min(x, rect.x);
double y1 = Math.min(y, rect.y);
double x2 = Math.max(x + width, rect.x + rect.width);
double y2 = Math.max(y + height, rect.y + rect.height);
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
}
/**
* Adds a point, specified by the arguments {@code x}
* and {@code y}, to the rectangle. The resulting rectangle is the
* smallest rectangle that contains both the original rectangle and the
* specified point.
*
* After adding a point, a call to {@code contains} with the added
* point as an argument does not necessarily return {@code true}.
* The {@code contains} method does not return {@code true}
* for points on the right or bottom edges of a rectangle. Therefore, if the
* added point falls on the left or bottom edge of the enlarged rectangle,
* {@code contains} returns {@code false} for that point.
*
* @param px the x coordinate of the point
* @param py the y coordinate of the point
*
* @jshide
*/
public Rectangle include(double px, double py) {
double x1 = Math.min(x, px);
double y1 = Math.min(y, py);
double x2 = Math.max(x + width, px);
double y2 = Math.max(y + height, py);
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
}
/**
* Adds a point to this rectangle. The resulting rectangle is the
* smallest rectangle that contains both the original rectangle and the
* specified point.
*
* After adding a point, a call to {@code contains} with the added
* point as an argument does not necessarily return {@code true}.
* The {@code contains} method does not return {@code true}
* for points on the right or bottom edges of a rectangle. Therefore, if the
* added point falls on the left or bottom edge of the enlarged rectangle,
* {@code contains} returns {@code false} for that point.
*
* @param point
*/
public Rectangle include(Point point) {
return include(point.x, point.y);
}
/**
* @deprecated
*/
public Rectangle unite(double px, double py) {
return include(px, py);
}
/**
* @deprecated
*/
public Rectangle unite(Point point) {
return include(point);
}
protected Rectangle2D toRectangle2D() {
return new Rectangle2D.Double(x, y, width, height);
}
public String toString() {
StringBuffer buf = new StringBuffer(128);
buf.append("{ x: ").append(ScriptographerEngine.numberFormat.format(x));
buf.append(", y: ").append(ScriptographerEngine.numberFormat.format(y));
buf.append(", width: ").append(
ScriptographerEngine.numberFormat.format(width));
buf.append(", height: ").append(
ScriptographerEngine.numberFormat.format(height));
buf.append(" }");
return buf.toString();
}
}