Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
This file is part of the iText (R) project.
Copyright (c) 1998-2024 Apryse Group NV
Authors: Apryse Software.
This program is offered under a commercial and under the AGPL license.
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
AGPL licensing:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
package com.itextpdf.kernel.geom;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
import com.itextpdf.kernel.pdf.PdfArray;
import com.itextpdf.kernel.pdf.PdfPage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Class that represent rectangle object.
*/
public class Rectangle implements Cloneable {
static float EPS = 1e-4f;
protected float x;
protected float y;
protected float width;
protected float height;
/**
* Creates new instance.
*
* @param x the x coordinate of lower left point
* @param y the y coordinate of lower left point
* @param width the width value
* @param height the height value
*/
public Rectangle(float x, float y, float width, float height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
/**
* Creates new instance of rectangle with (0, 0) as the lower left point.
*
* @param width the width value
* @param height the height value
*/
public Rectangle(float width, float height) {
this(0, 0, width, height);
}
/**
* Creates the copy of given {@link Rectangle}
*
* @param rect the copied {@link Rectangle}
*/
public Rectangle(Rectangle rect) {
this(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
}
/**
* Calculates the common rectangle which includes all the input rectangles.
*
* @param rectangles list of input rectangles.
* @return common rectangle.
*/
public static Rectangle getCommonRectangle(Rectangle... rectangles) {
float ury = -Float.MAX_VALUE;
float llx = Float.MAX_VALUE;
float lly = Float.MAX_VALUE;
float urx = -Float.MAX_VALUE;
for (Rectangle rectangle : rectangles) {
if (rectangle == null)
continue;
Rectangle rec = rectangle.clone();
if (rec.getY() < lly)
lly = rec.getY();
if (rec.getX() < llx)
llx = rec.getX();
if (rec.getY() + rec.getHeight() > ury)
ury = rec.getY() + rec.getHeight();
if (rec.getX() + rec.getWidth() > urx)
urx = rec.getX() + rec.getWidth();
}
return new Rectangle(llx, lly, urx - llx, ury - lly);
}
/**
* Gets the rectangle as it looks on the rotated page
* and returns the rectangle in coordinates relevant to the true page origin.
* This rectangle can be used to add annotations, fields, and other objects
* to the rotated page.
*
* @param rect the rectangle as it looks on the rotated page.
* @param page the page on which one want to process the rectangle.
* @return the newly created rectangle with translated coordinates.
*/
public static Rectangle getRectangleOnRotatedPage(Rectangle rect, PdfPage page) {
Rectangle resultRect = rect;
int rotation = page.getRotation();
if (0 != rotation) {
Rectangle pageSize = page.getPageSize();
switch ((rotation / 90) % 4) {
case 1: // 90 degrees
resultRect = new Rectangle(pageSize.getWidth() - resultRect.getTop(), resultRect.getLeft(), resultRect.getHeight(), resultRect.getWidth());
break;
case 2: // 180 degrees
resultRect = new Rectangle(pageSize.getWidth() - resultRect.getRight(), pageSize.getHeight() - resultRect.getTop(), resultRect.getWidth(), resultRect.getHeight());
break;
case 3: // 270 degrees
resultRect = new Rectangle(resultRect.getLeft(), pageSize.getHeight() - resultRect.getRight(), resultRect.getHeight(), resultRect.getWidth());
break;
case 4: // 0 degrees
default:
break;
}
}
return resultRect;
}
/**
* Calculates the bounding box of passed points.
*
* @param points the points which appear inside the area
*
* @return the bounding box of passed points.
*/
public static Rectangle calculateBBox(List points) {
List xs = new ArrayList<>();
List ys = new ArrayList<>();
for (Point point : points) {
xs.add(point.getX());
ys.add(point.getY());
}
double left = Collections.min(xs);
double bottom = Collections.min(ys);
double right = Collections.max(xs);
double top = Collections.max(ys);
return new Rectangle((float) left, (float) bottom, (float) (right - left), (float) (top - bottom));
}
/**
* Convert rectangle to an array of points
*
* @return array of four extreme points of rectangle
*/
public Point[] toPointsArray() {
return new Point[] {new Point(x, y), new Point(x + width, y),
new Point(x + width, y + height), new Point(x, y + height)};
}
/**
* Get the rectangle representation of the intersection between this rectangle and the passed rectangle
*
* @param rect the rectangle to find the intersection with
* @return the intersection rectangle if the passed rectangles intersects with this rectangle,
* a rectangle representing a line if the intersection is along an edge or
* a rectangle representing a point if the intersection is a single point,
* null otherwise
*/
public Rectangle getIntersection(Rectangle rect) {
Rectangle result = null;
//Calculate possible lower-left corner and upper-right corner
float llx = Math.max(x, rect.x);
float lly = Math.max(y, rect.y);
float urx = Math.min(getRight(), rect.getRight());
float ury = Math.min(getTop(), rect.getTop());
//If width or height is non-negative, there is overlap and we can construct the intersection rectangle
float width = urx - llx;
if (Math.abs(width) < EPS) {
width = 0;
}
float height = ury - lly;
if (Math.abs(height) < EPS) {
height = 0;
}
if (Float.compare(width, 0) >= 0
&& Float.compare(height, 0) >= 0) {
if (Float.compare(width, 0) < 0) width = 0;
if (Float.compare(height, 0) < 0) height = 0;
result = new Rectangle(llx, lly, width, height);
}
return result;
}
/**
* Check if this rectangle contains the passed rectangle.
* A rectangle will envelop itself, meaning that for any rectangle {@code rect}
* the expression {@code rect.contains(rect)} always returns true.
*
* @param rect a rectangle which is to be checked if it is fully contained inside this rectangle.
* @return true if this rectangle contains the passed rectangle, false otherwise.
*/
public boolean contains(Rectangle rect) {
float llx = this.getX();
float lly = this.getY();
float urx = llx + this.getWidth();
float ury = lly + this.getHeight();
float rllx = rect.getX();
float rlly = rect.getY();
float rurx = rllx + rect.getWidth();
float rury = rlly + rect.getHeight();
return llx - EPS <= rllx && lly - EPS <= rlly
&& rurx <= urx + EPS && rury <= ury + EPS;
}
/**
* Check if this rectangle and the passed rectangle overlap
*
* @param rect a rectangle which is to be checked if it overlaps the passed rectangle
* @return true if there is overlap of some kind
*/
public boolean overlaps(final Rectangle rect) {
return overlaps(rect, -EPS);
}
/**
* Check if this rectangle and the passed rectangle overlap
*
* @param rect a rectangle which is to be checked if it overlaps the passed rectangle
* @param epsilon if greater than zero, then this is the maximum distance that one rectangle can go to another, but
* they will not overlap, if less than zero, then this is the minimum required distance between the
* rectangles so that they do not overlap
* @return true if there is overlap of some kind
*/
public boolean overlaps(final Rectangle rect, final float epsilon) {
// Two rectangles do not overlap if any of the following holds
// The first rectangle lies to the left of the second rectangle or touches very slightly
if ((this.getX() + this.getWidth()) < (rect.getX() + epsilon)) {
return false;
}
// The first rectangle lies to the right of the second rectangle or touches very slightly
if ((this.getX() + epsilon) > (rect.getX() + rect.getWidth())) {
return false;
}
// The first rectangle lies to the bottom of the second rectangle or touches very slightly
if ((this.getY() + this.getHeight()) < (rect.getY() + epsilon)) {
return false;
}
// The first rectangle lies to the top of the second rectangle or touches very slightly
if ((this.getY() + epsilon) > (rect.getY() + rect.getHeight())) {
return false;
}
return true;
}
/**
* Sets the rectangle by the coordinates, specifying its lower left and upper right points. May be used in chain.
*
*
* Note: this method will normalize coordinates, so the rectangle will have non negative width and height,
* and its x and y coordinates specified lower left point.
*
* @param llx the X coordinate of lower left point
* @param lly the Y coordinate of lower left point
* @param urx the X coordinate of upper right point
* @param ury the Y coordinate of upper right point
* @return this {@link Rectangle} instance.
*/
public Rectangle setBbox(float llx, float lly, float urx, float ury) {
// If llx is greater than urx, swap them (normalize)
if (llx > urx) {
float temp = llx;
llx = urx;
urx = temp;
}
// If lly is greater than ury, swap them (normalize)
if (lly > ury) {
float temp = lly;
lly = ury;
ury = temp;
}
x = llx;
y = lly;
width = urx - llx;
height = ury - lly;
return this;
}
/**
* Gets the X coordinate of lower left point.
*
* @return the X coordinate of lower left point.
*/
public float getX() {
return x;
}
/**
* Sets the X coordinate of lower left point. May be used in chain.
*
* @param x the X coordinate of lower left point to be set.
* @return this {@link Rectangle} instance.
*/
public Rectangle setX(float x) {
this.x = x;
return this;
}
/**
* Gets the Y coordinate of lower left point.
*
* @return the Y coordinate of lower left point.
*/
public float getY() {
return y;
}
/**
* Sets the Y coordinate of lower left point. May be used in chain.
*
* @param y the Y coordinate of lower left point to be set.
* @return this {@link Rectangle} instance.
*/
public Rectangle setY(float y) {
this.y = y;
return this;
}
/**
* Gets the width of rectangle.
*
* @return the width of rectangle.
*/
public float getWidth() {
return width;
}
/**
* Sets the width of rectangle. May be used in chain.
*
* @param width the the width of rectangle to be set.
* @return this {@link Rectangle} instance.
*/
public Rectangle setWidth(float width) {
this.width = width;
return this;
}
/**
* Gets the height of rectangle.
*
* @return the height of rectangle.
*/
public float getHeight() {
return height;
}
/**
* Sets the height of rectangle. May be used in chain.
*
* @param height the the width of rectangle to be set.
* @return this {@link Rectangle} instance.
*/
public Rectangle setHeight(float height) {
this.height = height;
return this;
}
/**
* Increases the height of rectangle by the given value. May be used in chain.
*
* @param extra the value of the extra height to be added.
* @return this {@link Rectangle} instance.
*/
public Rectangle increaseHeight(float extra) {
this.height += extra;
return this;
}
/**
* Decreases the height of rectangle by the given value. May be used in chain.
*
* @param extra the value of the extra height to be subtracted.
* @return this {@link Rectangle} instance.
*/
public Rectangle decreaseHeight(float extra) {
this.height -= extra;
return this;
}
/**
* Increases the width of rectangle by the given value. May be used in chain.
*
* @param extra the value of the extra wudth to be added.
* @return this {@link Rectangle} instance.
*/
public Rectangle increaseWidth(float extra) {
this.width += extra;
return this;
}
/**
* Decreases the width of rectangle by the given value. May be used in chain.
*
* @param extra the value of the extra width to be subtracted.
* @return this {@link Rectangle} instance.
*/
public Rectangle decreaseWidth(float extra) {
this.width -= extra;
return this;
}
/**
* Gets the X coordinate of the left edge of the rectangle. Same as: {@code getX()}.
*
* @return the X coordinate of the left edge of the rectangle.
*/
public float getLeft() {
return x;
}
/**
* Gets the X coordinate of the right edge of the rectangle. Same as: {@code getX() + getWidth()}.
*
* @return the X coordinate of the right edge of the rectangle.
*/
public float getRight() {
return x + width;
}
/**
* Gets the Y coordinate of the upper edge of the rectangle. Same as: {@code getY() + getHeight()}.
*
* @return the Y coordinate of the upper edge of the rectangle.
*/
public float getTop() {
return y + height;
}
/**
* Gets the Y coordinate of the lower edge of the rectangle. Same as: {@code getY()}.
*
* @return the Y coordinate of the lower edge of the rectangle.
*/
public float getBottom() {
return y;
}
/**
* Decreases the y coordinate.
*
* @param move the value on which the position will be changed.
* @return this {@link Rectangle} instance.
*/
public Rectangle moveDown(float move) {
y -= move;
return this;
}
/**
* Increases the y coordinate.
*
* @param move the value on which the position will be changed.
* @return this {@link Rectangle} instance.
*/
public Rectangle moveUp(float move) {
y += move;
return this;
}
/**
* Increases the x coordinate.
*
* @param move the value on which the position will be changed.
* @return this {@link Rectangle} instance.
*/
public Rectangle moveRight(float move) {
x += move;
return this;
}
/**
* Decreases the x coordinate.
*
* @param move the value on which the position will be changed.
* @return this {@link Rectangle} instance.
*/
public Rectangle moveLeft(float move) {
x -= move;
return this;
}
/**
* Change the rectangle according the specified margins.
*
* @param topIndent the value on which the top y coordinate will change.
* @param rightIndent the value on which the right x coordinate will change.
* @param bottomIndent the value on which the bottom y coordinate will change.
* @param leftIndent the value on which the left x coordinate will change.
* @param reverse if {@code true} the rectangle will expand, otherwise it will shrink
* @return the rectangle with applied margins
*/
public Rectangle applyMargins(float topIndent, float rightIndent, float bottomIndent, float leftIndent, boolean reverse) {
x += leftIndent * (reverse ? -1 : 1);
width -= (leftIndent + rightIndent) * (reverse ? -1 : 1);
y += bottomIndent * (reverse ? -1 : 1);
height -= (topIndent + bottomIndent) * (reverse ? -1 : 1);
return this;
}
/**
* Checks if rectangle have common points with line, specified by two points.
*
* @param x1 the x coordinate of first line's point.
* @param y1 the y coordinate of first line's point.
* @param x2 the x coordinate of second line's point.
* @param y2 the y coordinate of second line's point.
* @return {@code true} if rectangle have common points with line and {@code false} otherwise.
*/
public boolean intersectsLine(float x1, float y1, float x2, float y2) {
double rx1 = getX();
double ry1 = getY();
double rx2 = rx1 + getWidth();
double ry2 = ry1 + getHeight();
return
(rx1 <= x1 && x1 <= rx2 && ry1 <= y1 && y1 <= ry2) ||
(rx1 <= x2 && x2 <= rx2 && ry1 <= y2 && y2 <= ry2) ||
linesIntersect(rx1, ry1, rx2, ry2, x1, y1, x2, y2) ||
linesIntersect(rx2, ry1, rx1, ry2, x1, y1, x2, y2);
}
/**
* Gets the string representation of rectangle.
*
* @return the string representation of rectangle.
*/
@Override
public String toString() {
return "Rectangle: " + getWidth() +
'x' +
getHeight();
}
/**
* Creates a "deep copy" of this rectangle, meaning the object returned by this method will be independent
* of the object being cloned.
*
* @return the copied rectangle.
*/
@Override
public Rectangle clone() {
try {
// super.clone is safe to return since all of the Rectangle's fields are primitive.
return (Rectangle) super.clone();
} catch (CloneNotSupportedException e) {
// should never happen since Cloneable is implemented
return null;
}
}
/**
* Compares instance of this rectangle with given deviation equals to 0.0001
*
* @param that the {@link Rectangle} to compare with.
* @return {@code true} if the difference between corresponding rectangle values is less than deviation and {@code false} otherwise.
*/
public boolean equalsWithEpsilon(Rectangle that) {
return equalsWithEpsilon(that, EPS);
}
/**
* Compares instance of this rectangle with given deviation.
*
* @param that the {@link Rectangle} to compare with.
* @param eps the deviation value.
* @return {@code true} if the difference between corresponding rectangle values is less than deviation and {@code false} otherwise.
*/
public boolean equalsWithEpsilon(Rectangle that, float eps) {
float dx = Math.abs(x - that.x);
float dy = Math.abs(y - that.y);
float dw = Math.abs(width - that.width);
float dh = Math.abs(height - that.height);
return dx < eps && dy < eps && dw < eps && dh < eps;
}
private static boolean linesIntersect(double x1, double y1, double x2,
double y2, double x3, double y3, double x4, double y4) {
/*
* A = (x2-x1, y2-y1) B = (x3-x1, y3-y1) C = (x4-x1, y4-y1) D = (x4-x3,
* y4-y3) = C-B E = (x1-x3, y1-y3) = -B F = (x2-x3, y2-y3) = A-B
*
* Result is ((AxB) * (AxC) <=0) and ((DxE) * (DxF) <= 0)
*
* DxE = (C-B)x(-B) = BxB-CxB = BxC DxF = (C-B)x(A-B) = CxA-CxB-BxA+BxB =
* AxB+BxC-AxC
*/
// A
x2 -= x1;
y2 -= y1;
// B
x3 -= x1;
y3 -= y1;
// C
x4 -= x1;
y4 -= y1;
double AvB = x2 * y3 - x3 * y2;
double AvC = x2 * y4 - x4 * y2;
// Online
if (AvB == 0.0 && AvC == 0.0) {
if (x2 != 0.0) {
return
(x4 * x3 <= 0.0) ||
((x3 * x2 >= 0.0) &&
(x2 > 0.0 ? x3 <= x2 || x4 <= x2 : x3 >= x2 || x4 >= x2));
}
if (y2 != 0.0) {
return
(y4 * y3 <= 0.0) ||
((y3 * y2 >= 0.0) &&
(y2 > 0.0 ? y3 <= y2 || y4 <= y2 : y3 >= y2 || y4 >= y2));
}
return false;
}
double BvC = x3 * y4 - x4 * y3;
return (AvB * AvC <= 0.0) && (BvC * (AvB + BvC - AvC) <= 0.0);
}
/**
* Create a list of bounding rectangles from an 8 x n array of Quadpoints.
* @param quadPoints 8xn array of numbers representing 4 points
* @return a list of bounding rectangles for the passed quadpoints
* @throws PdfException if the passed array's size is not a multiple of 8.
*/
public static List createBoundingRectanglesFromQuadPoint(PdfArray quadPoints) throws PdfException {
List boundingRectangles = new ArrayList<>();
if (quadPoints.size() % 8 != 0) {
throw new PdfException(KernelExceptionMessageConstant.QUAD_POINT_ARRAY_LENGTH_IS_NOT_A_MULTIPLE_OF_EIGHT);
}
for (int i = 0; i < quadPoints.size(); i += 8) {
float[] quadPointEntry = Arrays.copyOfRange(quadPoints.toFloatArray(),i,i+8);
PdfArray quadPointEntryFA = new PdfArray(quadPointEntry);
boundingRectangles.add(createBoundingRectangleFromQuadPoint(quadPointEntryFA));
}
return boundingRectangles;
}
/**
* Create the bounding rectangle for the given array of quadpoints.
* @param quadPoints an array containing 8 numbers that correspond to 4 points.
* @return The smallest orthogonal rectangle containing the quadpoints.
* @throws PdfException if the passed array's size is not a multiple of 8.
*/
public static Rectangle createBoundingRectangleFromQuadPoint(PdfArray quadPoints) throws PdfException {
//Check if array length is a multiple of 8
if (quadPoints.size() % 8 != 0) {
throw new PdfException(KernelExceptionMessageConstant.QUAD_POINT_ARRAY_LENGTH_IS_NOT_A_MULTIPLE_OF_EIGHT);
}
float llx = Float.MAX_VALUE;
float lly = Float.MAX_VALUE;
float urx = -Float.MAX_VALUE;
float ury = -Float.MAX_VALUE;
// QuadPoints in redact annotations have "Z" order, in spec they're specified
for (int j = 0; j < 8; j += 2) {
float x = quadPoints.getAsNumber(j).floatValue();
float y = quadPoints.getAsNumber(j + 1).floatValue();
if (x < llx) llx = x;
if (x > urx) urx = x;
if (y < lly) lly = y;
if (y > ury) ury = y;
}
return (new Rectangle(llx,
lly,
urx - llx,
ury - lly));
}
}