mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-05-22 13:15:31 +00:00
1006 lines
35 KiB
Java
1006 lines
35 KiB
Java
/*
|
|
* $Id: BoundingBox.java,v 1.2 2007/08/26 18:56:35 gil1 Exp $
|
|
*
|
|
* $Date: 2007/08/26 18:56:35 $
|
|
*
|
|
* Copyright (c) Eric Z. Beard, ericzbeard@hotmail.com
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
package gnu.jpdf;
|
|
|
|
import java.awt.*;
|
|
import java.util.*;
|
|
|
|
/**
|
|
* <p>
|
|
* This class simplifies the placement of Strings within a canvas area where the
|
|
* placement of objects is absolute</p>
|
|
*
|
|
* <p>
|
|
* A <code>BoundingBox</code> is just a Rectangle that knows how to find the
|
|
* coordinates for a String based on the desired alignment and
|
|
* <code>FontMetrics</code>. For each new String, a new child
|
|
* <code>BoundingBox</code> is made that can be subtracted from the original box
|
|
* so new Strings can be added</p>
|
|
*
|
|
* <p>
|
|
* One of the more helpful features of this class is the string wrap feature of
|
|
* <code>getStringBounds</code>. The box returned by that method will contain an
|
|
* array of strings that have been broken down to fit the box. The box's
|
|
* coordinates and size will reflect the size of the entire group of strings if
|
|
* it is laid out as expected. Using the returned box and iterating through the
|
|
* array of strings from top to bottom, getting new bounding boxes for each one
|
|
* (with upper left alignment and no padding) will result in the correct string
|
|
* wrap.</p>
|
|
*
|
|
* <p>
|
|
* Note that you will need to have Xvfb running on a Unix server to use this
|
|
* class</p>
|
|
*
|
|
* @author Eric Z. Beard, ericzbeard@hotmail.com
|
|
* @version $Revision: 1.2 $, $Date: 2007/08/26 18:56:35 $
|
|
*/
|
|
public class BoundingBox extends Rectangle {
|
|
|
|
/**
|
|
* Percent f line height to space lines
|
|
*/
|
|
public static final int LINE_SPACING_PERCENTAGE = 20;
|
|
|
|
/**
|
|
* Used to a align a String centered vertically
|
|
*/
|
|
public static final int VERT_ALIGN_CENTER = 0;
|
|
|
|
/**
|
|
* Used to align a String at the top of the box
|
|
*/
|
|
public static final int VERT_ALIGN_TOP = 1;
|
|
|
|
/**
|
|
* Used to align a String at the bottom of the box
|
|
*/
|
|
public static final int VERT_ALIGN_BOTTOM = 2;
|
|
|
|
/**
|
|
* Used to align a String horizontally in the center of the box
|
|
*/
|
|
public static final int HORIZ_ALIGN_CENTER = 3;
|
|
|
|
/**
|
|
* Used to align a String to the left in the box
|
|
*/
|
|
public static final int HORIZ_ALIGN_LEFT = 4;
|
|
|
|
/**
|
|
* Used to align a String to the right in a box
|
|
*/
|
|
public static final int HORIZ_ALIGN_RIGHT = 5;
|
|
|
|
/**
|
|
* Used to subtract a child from a box, *leaving* the top portion
|
|
*/
|
|
public static final int SUBTRACT_FROM_TOP = 6;
|
|
|
|
/**
|
|
* Used to subtract a child from a box, *leaving* the bottom portion
|
|
*/
|
|
public static final int SUBTRACT_FROM_BOTTOM = 7;
|
|
|
|
/**
|
|
* Used to subtract a child from a box, *leaving* the left portion
|
|
*/
|
|
public static final int SUBTRACT_FROM_LEFT = 8;
|
|
|
|
/**
|
|
* Used to subtract a child from a box, *leaving" the right portion
|
|
*/
|
|
public static final int SUBTRACT_FROM_RIGHT = 9;
|
|
|
|
private static final int[] VERT_ALIGNS = {VERT_ALIGN_CENTER,
|
|
VERT_ALIGN_TOP,
|
|
VERT_ALIGN_BOTTOM};
|
|
|
|
private static final int[] HORIZ_ALIGNS = {HORIZ_ALIGN_CENTER,
|
|
HORIZ_ALIGN_LEFT,
|
|
HORIZ_ALIGN_RIGHT};
|
|
|
|
private static final int[] SUBTRACTS = {SUBTRACT_FROM_TOP,
|
|
SUBTRACT_FROM_BOTTOM,
|
|
SUBTRACT_FROM_LEFT,
|
|
SUBTRACT_FROM_RIGHT};
|
|
|
|
/**
|
|
* The point to use for Graphics.drawString()
|
|
*/
|
|
private Point drawingPoint;
|
|
|
|
/**
|
|
* The absolute, world location of the box
|
|
*/
|
|
private Point absoluteLocation;
|
|
|
|
/**
|
|
* Link to parent box
|
|
*/
|
|
private BoundingBox parent;
|
|
|
|
/**
|
|
* If this box was the result of a getStringBounds call, this array will
|
|
* hold the broken strings
|
|
*/
|
|
private String[] stringArray;
|
|
|
|
/**
|
|
* The string specified in getStringBounds
|
|
*/
|
|
private String fullString;
|
|
|
|
/**
|
|
* Creates a new <code>BoundingBox</code> instance.
|
|
*
|
|
* @param p a <code>Point</code>, upper left coords
|
|
* @param d a <code>Dimension</code>, used to determine height and width
|
|
*/
|
|
public BoundingBox(Point p, Dimension d) {
|
|
super(p, d);
|
|
this.drawingPoint = this.getLocation();
|
|
this.absoluteLocation = this.getLocation();
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Returns true if this box has a parent. The 'world', or enclosing canvas
|
|
* is not considered a parent</p>
|
|
*
|
|
* @return a <code>boolean</code> value
|
|
*/
|
|
public boolean hasParent() {
|
|
return parent != null;
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Get this box's parent box</p>
|
|
*
|
|
* @return a <code>BoundingBox</code> value
|
|
*/
|
|
public BoundingBox getParent() {
|
|
return parent;
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Make the specified box this box's child. Equivalent to
|
|
* <code>child.setParent(parent)</code> where the specified 'parent' is this
|
|
* instance</p>
|
|
*
|
|
* @param child a <code>BoundingBox</code>, any box that can fit inside this
|
|
* one. The results of calling <code>getAbsoluteLocation()</code> on the
|
|
* child will be altered after this to take into account the child's new
|
|
* location in the 'world'
|
|
*
|
|
*/
|
|
public void add(BoundingBox child) {
|
|
child.setParent(this);
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Make the specified box this box's parent</p>
|
|
*
|
|
* @param parent a <code>BoundingBox</code> value
|
|
*/
|
|
public void setParent(BoundingBox parent) {
|
|
// Prevent infinite recursion
|
|
if (this == parent) {
|
|
return;
|
|
}
|
|
this.parent = parent;
|
|
|
|
// If this box was created empty, without a String inside,
|
|
// determine its absolute location
|
|
if (this.getLocation().equals(this.getAbsoluteLocation())) {
|
|
int ancestorTranslateX = 0;
|
|
int ancestorTranslateY = 0;
|
|
|
|
BoundingBox ancestor = this;
|
|
while (ancestor.hasParent()) {
|
|
BoundingBox oldRef = ancestor;
|
|
ancestor = ancestor.getParent();
|
|
// Prevent infinite recursion
|
|
if (ancestor == oldRef) {
|
|
break;
|
|
}
|
|
ancestorTranslateX += (int) ancestor.getLocation().getX();
|
|
ancestorTranslateY += (int) ancestor.getLocation().getY();
|
|
}
|
|
|
|
this.getAbsoluteLocation().translate(ancestorTranslateX,
|
|
ancestorTranslateY);
|
|
} // end if
|
|
} // end setParent
|
|
|
|
/**
|
|
* <p>
|
|
* Get the wrapped strings if this box was from a call to getStringBounds,
|
|
* otherwise this method returns null</p>
|
|
*
|
|
* @return a <code>String[]</code> array of strings, top to bottom in layout
|
|
*/
|
|
public String[] getStringArray() {
|
|
return stringArray;
|
|
} // end getStringArray
|
|
|
|
/**
|
|
* <p>
|
|
* Set the value of the string array</p>
|
|
*
|
|
* @param strArray a <code>String</code> array
|
|
*
|
|
*/
|
|
public void setStringArray(String[] strArray) {
|
|
this.stringArray = strArray;
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Set the absolute upper left world location point for this box</p>
|
|
*
|
|
* @param point a <code>Point</code> value
|
|
*/
|
|
public void setAbsoluteLocation(Point point) {
|
|
this.absoluteLocation = point;
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Returns false if for any reason this box has negative dimensions</p>
|
|
*
|
|
* @return true if the box has positive height and width, false otherwise.
|
|
*/
|
|
public boolean boxExists() {
|
|
return (this.getHeight() > 0 && this.getWidth() > 0);
|
|
} // end boxExists
|
|
|
|
/**
|
|
* <p>
|
|
* Get the absolute upper left location point for this box</p>
|
|
*
|
|
* @return a <code>Point</code> value
|
|
*/
|
|
public Point getAbsoluteLocation() {
|
|
return absoluteLocation;
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Returns the full string associated with a call to
|
|
* <code>getStringBounds</code></p>
|
|
*
|
|
* @return the full string.
|
|
*/
|
|
public String getFullString() {
|
|
return fullString;
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Sets the full string associated with <code>getStringBounds</code></p>
|
|
*
|
|
* @param string a <code>String</code>
|
|
*/
|
|
public void setFullString(String string) {
|
|
this.fullString = string;
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Gets the location of a String after it is adjusted for alignment within
|
|
* this box. The point's coordinates are either within this box or within
|
|
* the enclosing area.</p>
|
|
*
|
|
* @param string a <code>String</code>, the String to be placed
|
|
* @param hAlign an <code>int</code>, HORIZ_ALIGN_CENTER, HORIZ_ALIGN_LEFT,
|
|
* HORIX_ALIGN_RIGHT
|
|
* @param vAlign an <code>int</code>, VERT_ALIGN_CENTER, VERT_ALIGN_TOP,
|
|
* VERT_ALIGN_BOTTOM
|
|
* @param fm a <code>FontMetrics</code> object for this String
|
|
* @param padding an <code>int</code>, the padding around the String
|
|
* @param enforce a <code>boolean</code>, if true the method will throw an
|
|
* exception when the string is too big, if not true it will break the
|
|
* string down and overrun the bottom of the box. If the box is too small
|
|
* for even one word, the exception will be thrown
|
|
* @return a <code>Point</code>, the coords to use in drawString()
|
|
* @see #HORIZ_ALIGN_LEFT
|
|
* @see #HORIZ_ALIGN_CENTER
|
|
* @see #HORIZ_ALIGN_RIGHT
|
|
* @see #VERT_ALIGN_TOP
|
|
* @see #VERT_ALIGN_CENTER
|
|
* @see #VERT_ALIGN_BOTTOM
|
|
* @throws IllegalArgumentException if the args are invalid
|
|
* @throws StringTooLongException if the string won't fit and enforce is set
|
|
* to true. The exception can still be thrown if enforce is false, but only
|
|
* in cases such as the box having no height or width
|
|
*/
|
|
public BoundingBox getStringBounds(String string,
|
|
int hAlign,
|
|
int vAlign,
|
|
FontMetrics fm,
|
|
int padding,
|
|
boolean enforce)
|
|
throws IllegalArgumentException, StringTooLongException {
|
|
// Check to make sure the values passed in are valid
|
|
if (!checkHAlign(hAlign)) {
|
|
throw new IllegalArgumentException("BoundingBox.getStringBounds, "
|
|
+ "hAlign invalid : " + hAlign);
|
|
}
|
|
if (!checkVAlign(vAlign)) {
|
|
throw new IllegalArgumentException("BoundingBox.getStringBounds, "
|
|
+ "vAlign invalid : " + hAlign);
|
|
}
|
|
if (fm == null) {
|
|
throw new IllegalArgumentException("BoundingBox.getStringBounds, "
|
|
+ "FontMetrics null");
|
|
}
|
|
if (string == null) {
|
|
throw new IllegalArgumentException("BoundingBox.getStringBounds, "
|
|
+ "String null");
|
|
}
|
|
|
|
// NOTE: For this portion of the method, parent refers
|
|
// to this object and child refers to the object about
|
|
// to be created. When the absolute point for drawing the
|
|
// String is determined, this object's ancestors are checked.
|
|
Dimension parentSize = this.getSize();
|
|
|
|
Point childLocation;
|
|
Dimension childSize;
|
|
|
|
// String ascent, width, height, parent, child width, height
|
|
int sa, sw, sh, pw, ph, cw, ch;
|
|
|
|
// Child, parent x, y coords for upper left
|
|
int cx, cy, px, py;
|
|
|
|
sa = fm.getMaxAscent();
|
|
sw = fm.stringWidth(string);
|
|
sh = sa + fm.getMaxDescent();
|
|
pw = (int) parentSize.getWidth();
|
|
ph = (int) parentSize.getHeight();
|
|
if (pw < 0) {
|
|
throw new StringTooLongException("The parent box has a negative width "
|
|
+ " (" + pw + ")");
|
|
}
|
|
if (ph < 0) {
|
|
throw new StringTooLongException("The parent box has a negative height"
|
|
+ " (" + ph + ")");
|
|
}
|
|
cw = sw + padding * 2;
|
|
ch = sh + padding * 2;
|
|
px = (int) this.getX();
|
|
py = (int) this.getY();
|
|
|
|
String[] childStrArray = null;
|
|
|
|
if ((cw > pw) || (string.indexOf("\n") != -1)) {
|
|
cw = pw - (padding * 2);
|
|
childStrArray = createStringArray(string, fm, padding, pw);
|
|
ch = getWrappedHeight(childStrArray, fm, padding);
|
|
if (ch > ph) {
|
|
// If enforce is not true, it means we want the box to
|
|
// be returned anyway (along with the strings in the array)
|
|
// so we can chop them manually and try again
|
|
if (enforce) {
|
|
throw new StringTooLongException("The wrapped strings do not "
|
|
+ "fit into the parent box, pw=" + pw
|
|
+ ", ph=" + ph + ", ch=" + ch + ", cw=" + cw
|
|
+ ", string: " + string);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Need to have child width and height, and string array set
|
|
// Child location is relative to this (parent) box, not the world
|
|
if (vAlign == VERT_ALIGN_TOP) {
|
|
cy = 0;
|
|
} else if (vAlign == VERT_ALIGN_CENTER) {
|
|
cy = (ph / 2) - (ch / 2);
|
|
} else {
|
|
cy = ph - ch;
|
|
}
|
|
|
|
if (hAlign == HORIZ_ALIGN_LEFT) {
|
|
cx = 0;
|
|
} else if (hAlign == HORIZ_ALIGN_CENTER) {
|
|
cx = (pw / 2) - (cw / 2);
|
|
} else {
|
|
cx = pw - cw;
|
|
}
|
|
|
|
childLocation = new Point(cx, cy);
|
|
childSize = new Dimension(cw, ch);
|
|
|
|
// Drawing location is based on the baseline of the String, and
|
|
// relative to the world, not this box. The drawing point differs
|
|
// from the absolute box location because of padding and ascent
|
|
int dpx, dpy, abx, aby;
|
|
|
|
// If this object also has a parent (maybe grandparents), iterate
|
|
// through them and find the absolute 'world' location
|
|
int ancestorTranslateX = 0;
|
|
int ancestorTranslateY = 0;
|
|
|
|
BoundingBox ancestor = this;
|
|
while (ancestor.hasParent()) {
|
|
BoundingBox oldRef = ancestor;
|
|
ancestor = ancestor.getParent();
|
|
// Prevent infinite recursion
|
|
if (ancestor == oldRef) {
|
|
break;
|
|
}
|
|
ancestorTranslateX += (int) ancestor.getLocation().getX();
|
|
ancestorTranslateY += (int) ancestor.getLocation().getY();
|
|
}
|
|
|
|
// Determine the absolute location for the box
|
|
abx = px + cx + ancestorTranslateX;
|
|
aby = py + cy + ancestorTranslateY;
|
|
|
|
// Determine the absolute drawing point for the String
|
|
dpx = abx + padding;
|
|
dpy = aby + padding + sa;
|
|
|
|
Point drawingPoint = new Point(dpx, dpy);
|
|
BoundingBox returnChild = new BoundingBox(childLocation,
|
|
childSize,
|
|
drawingPoint,
|
|
new Point(abx, aby));
|
|
this.add(returnChild);
|
|
returnChild.setFullString(string);
|
|
returnChild.setStringArray(childStrArray);
|
|
return returnChild;
|
|
|
|
} // end getStringBounds
|
|
|
|
/**
|
|
* <p>
|
|
* Gets the location of a String after it is adjusted for alignment within
|
|
* this box. The point's coordinates are either within this box or within
|
|
* the enclosing area.</p>
|
|
*
|
|
* <p>
|
|
* By default, this method enforces string length and throws the exception
|
|
* if it is too long</p>
|
|
*
|
|
* @param string a <code>String</code>, the String to be placed
|
|
* @param hAlign an <code>int</code>, HORIZ_ALIGN_CENTER, HORIZ_ALIGN_LEFT,
|
|
* HORIX_ALIGN_RIGHT
|
|
* @param vAlign an <code>int</code>, VERT_ALIGN_CENTER, VERT_ALIGN_TOP,
|
|
* VERT_ALIGN_BOTTOM
|
|
* @param fm a <code>FontMetrics</code> object for this String
|
|
* @param padding an <code>int</code>, the padding around the String
|
|
* @return a <code>Point</code>, the coords to use in drawString()
|
|
* @throws IllegalArgumentException if the args are invalid
|
|
* @throws StringTooLongException if the string won't fit
|
|
*/
|
|
public BoundingBox getStringBounds(String string,
|
|
int hAlign,
|
|
int vAlign,
|
|
FontMetrics fm,
|
|
int padding)
|
|
throws StringTooLongException, IllegalArgumentException {
|
|
return getStringBounds(string, hAlign, vAlign, fm, padding, true);
|
|
} // end getStringBounds (enforce true by default)
|
|
|
|
/**
|
|
* <p>
|
|
* This method is called after getting the box by calling
|
|
* <code>getStringBounds</code> on the parent. Wraps the string at word
|
|
* boundaries and draws it to the specified <code>Graphics</code> context.
|
|
* Make sure padding is the same as specified for the
|
|
* <code>getStringBounds</code> call, or you may get an unexpected
|
|
* {@link gnu.jpdf.StringTooLongException}</p>
|
|
*
|
|
* @param g the <code>Graphics</code> object
|
|
* @param fm the <code>FontMetrics</code> to use for sizing
|
|
* @param padding an int, the padding around the strings
|
|
* @param hAlign the <code>int</code> horizontal alignment
|
|
* @throws IllegalArgumentException if the args are invalid
|
|
* @throws StringTooLongException if the string won't fit this will only
|
|
* happen if the fm or padding has been changed since getStringBounds was
|
|
* called successfully
|
|
*/
|
|
public void drawWrappedString(Graphics g,
|
|
FontMetrics fm,
|
|
int padding,
|
|
int hAlign)
|
|
throws IllegalArgumentException, StringTooLongException {
|
|
if (getStringArray() == null) {
|
|
Point p = getDrawingPoint();
|
|
int xx = (int) p.getX();
|
|
int yy = (int) p.getY();
|
|
g.drawString(getFullString(), xx, yy);
|
|
} else {
|
|
int len = stringArray.length;
|
|
for (int i = 0; i < len; i++) {
|
|
BoundingBox wrappedBox = null;
|
|
wrappedBox = getStringBounds(stringArray[i],
|
|
hAlign,
|
|
BoundingBox.VERT_ALIGN_TOP,
|
|
fm,
|
|
0);
|
|
Point pp = wrappedBox.getDrawingPoint();
|
|
int xx = (int) pp.getX();
|
|
if (hAlign == BoundingBox.HORIZ_ALIGN_RIGHT) {
|
|
xx -= padding;
|
|
}
|
|
if (hAlign == BoundingBox.HORIZ_ALIGN_LEFT) {
|
|
xx += padding;
|
|
}
|
|
int yy = (int) pp.getY() + padding;
|
|
g.drawString(stringArray[i], xx, yy);
|
|
subtract(wrappedBox, BoundingBox.SUBTRACT_FROM_BOTTOM);
|
|
}
|
|
}
|
|
} // end drawWrappedString
|
|
|
|
/**
|
|
* <p>
|
|
* Draws lines from the wrapped string until there is no more room and then
|
|
* stops. If there is no string or the box is too small for anything to be
|
|
* drawn, does nothing</p>
|
|
*
|
|
* @param g the <code>Graphics</code> object to draw to
|
|
* @param fm the <code>FontMetrics</code> object to use for string sizing
|
|
* @param padding the <code>int</code> amount of padding around the string
|
|
* @param hAlign the <code>int</code> horizontal alignment
|
|
*
|
|
*/
|
|
public void drawWrappedStringTruncate(Graphics g,
|
|
FontMetrics fm,
|
|
int padding,
|
|
int hAlign) {
|
|
|
|
if (getStringArray() == null) {
|
|
Point p = getDrawingPoint();
|
|
int xx = (int) p.getX();
|
|
int yy = (int) p.getY();
|
|
if (getFullString() != null) {
|
|
g.drawString(getFullString(), xx, yy);
|
|
} else {
|
|
System.err.println("getStringArray and getFullString are null");
|
|
}
|
|
} else {
|
|
int totalHeight = 0;
|
|
int len = stringArray.length;
|
|
for (int i = 0; i < len; i++) {
|
|
BoundingBox wrappedBox = null;
|
|
try {
|
|
wrappedBox = getStringBounds(stringArray[i],
|
|
hAlign,
|
|
BoundingBox.VERT_ALIGN_TOP,
|
|
fm,
|
|
0,
|
|
false);
|
|
totalHeight += (int) wrappedBox.getHeight();
|
|
if (getParent() != null) {
|
|
if (totalHeight > (int) (getParent().getHeight())) {
|
|
return;
|
|
}
|
|
}
|
|
} catch (StringTooLongException stle) {
|
|
stle.printStackTrace();
|
|
return;
|
|
}
|
|
wrappedBox.drawChoppedString(g, fm, padding, hAlign);
|
|
subtract(wrappedBox, BoundingBox.SUBTRACT_FROM_BOTTOM);
|
|
}
|
|
}
|
|
} // end drawWrappedStringTruncate
|
|
|
|
/**
|
|
* <p>
|
|
* Take the first line of the string (if it is wrapped, otherwise just take
|
|
* the whole string) and chop the end of it off to make it fit in the box.
|
|
* If the box is smaller than one letter, draw nothing</p>
|
|
*
|
|
* @param g the <code>Graphics</code> object to draw to
|
|
* @param fm the <code>FontMetrics</code> object to use for string sizing
|
|
* @param padding the <code>int</code> amount of padding around the string
|
|
* @param hAlign the <code>int</code> horizontal alignment
|
|
*/
|
|
public void drawChoppedString(Graphics g,
|
|
FontMetrics fm,
|
|
int padding,
|
|
int hAlign) {
|
|
|
|
String string = "";
|
|
if (getStringArray() != null) {
|
|
string = new String(getStringArray()[0]);
|
|
} else {
|
|
string = new String(getFullString());
|
|
}
|
|
BoundingBox choppedBox = null;
|
|
try {
|
|
choppedBox = getStringBounds(string,
|
|
hAlign,
|
|
VERT_ALIGN_TOP,
|
|
fm,
|
|
padding);
|
|
Point p = choppedBox.getDrawingPoint();
|
|
int x = (int) p.getX();
|
|
int y = (int) p.getY();
|
|
g.drawString(string, x, y);
|
|
} catch (StringTooLongException stle) {
|
|
// Doesn't fit - start cutting from the end until it does
|
|
StringBuffer buf = new StringBuffer().append(string);
|
|
if (buf.length() == 0) {
|
|
System.out.println("BoundingBox.drawChoppedString, buf len 0 ??");
|
|
//return;
|
|
throw new RuntimeException();
|
|
}
|
|
buf.deleteCharAt(buf.length() - 1);
|
|
while ((fm.stringWidth(buf.toString()) > (int) getWidth())
|
|
&& (buf.length() > 0)) {
|
|
buf.deleteCharAt(buf.length() - 1);
|
|
}
|
|
|
|
try {
|
|
choppedBox = getStringBounds(buf.toString(),
|
|
hAlign,
|
|
VERT_ALIGN_TOP,
|
|
fm,
|
|
padding);
|
|
Point pp = choppedBox.getDrawingPoint();
|
|
int xx = (int) pp.getX();
|
|
int yy = (int) pp.getY();
|
|
g.drawString(string, xx, yy);
|
|
} catch (StringTooLongException sstle) {
|
|
// Must be a really small box!
|
|
sstle.printStackTrace();
|
|
}
|
|
}
|
|
} // end drawChoppedString
|
|
|
|
/**
|
|
* <p>
|
|
* Get the total height of the box needed to contain the strings in the
|
|
* specified array</p>
|
|
*/
|
|
private int getWrappedHeight(String[] strings, FontMetrics fm, int padding) {
|
|
int ma = fm.getMaxAscent();
|
|
int md = fm.getMaxDescent();
|
|
int sh = ma + md;
|
|
int hPad = sh / LINE_SPACING_PERCENTAGE;
|
|
sh += hPad;
|
|
int total = sh * strings.length;
|
|
|
|
return total + (padding * 2);
|
|
} // end getWrappedHeight
|
|
|
|
/**
|
|
*
|
|
* <p>
|
|
* Make a string array from a string, wrapped to fit the box</p>
|
|
*
|
|
* <p>
|
|
* If the line width is too short, the array is just a tokenized version of
|
|
* the string</p>
|
|
*
|
|
* @param string - the <code>String</code> to convert to an array
|
|
* @param
|
|
*/
|
|
private String[] createStringArray(String string,
|
|
FontMetrics fm,
|
|
int padding,
|
|
int pw) {
|
|
if (string == null) {
|
|
System.err.println("Tried createStringArray with null String");
|
|
return null;
|
|
}
|
|
if (fm == null) {
|
|
System.err.println("Tried createStringArray with null FontMetrics");
|
|
}
|
|
|
|
int lw = pw - (padding * 2);
|
|
|
|
Vector<String> returnVector = new Vector<String>();
|
|
// Return delimiters as tokens
|
|
StringTokenizer st = new StringTokenizer(string, " \t\n\r\f", true);
|
|
StringBuffer tempBuffer = new StringBuffer();
|
|
StringBuffer finalBuffer = new StringBuffer();
|
|
|
|
while (st.hasMoreTokens()) {
|
|
// Get the next word and add a space after it
|
|
String tempString = st.nextToken();
|
|
tempBuffer.append(tempString);
|
|
|
|
// If we haven't reached the width with our current
|
|
// line, keep adding tokens. Also, check for hard returns
|
|
if ((fm.stringWidth(tempBuffer.toString()) < lw)
|
|
&& (tempBuffer.toString()
|
|
.charAt(tempBuffer.toString().length() - 1) != '\n')
|
|
&& (tempBuffer.toString()
|
|
.charAt(tempBuffer.toString().length() - 1) != '\r')) {
|
|
finalBuffer.append(tempString);
|
|
continue;
|
|
}
|
|
returnVector.addElement(finalBuffer.toString());
|
|
finalBuffer.delete(0, finalBuffer.length());
|
|
tempBuffer.delete(0, tempBuffer.length());
|
|
if ((tempString.charAt(0) != '\n')
|
|
&& (tempString.charAt(0) != '\r')) {
|
|
tempBuffer.append(tempString);
|
|
finalBuffer.append(tempString);
|
|
}
|
|
continue;
|
|
|
|
} // end while
|
|
returnVector.addElement(finalBuffer.toString());
|
|
|
|
int len = returnVector.size();
|
|
// Init the class member field stringArray
|
|
String[] childStrArray = new String[len];
|
|
for (int i = 0; i < len; i++) {
|
|
String curStr = (String) returnVector.get(i);
|
|
childStrArray[i] = curStr;
|
|
}
|
|
|
|
return childStrArray;
|
|
|
|
} // end createStringArray
|
|
|
|
/**
|
|
* <p>
|
|
* Removes the child box from this parent box. The child must have this
|
|
* object as its parent or the method does nothing. The BoundingBox returned
|
|
* will be cut by an area equal to the child area plus the horizontal or
|
|
* vertical strip in which it sits, depending on the 'subtractFrom' value
|
|
* passed in</p>
|
|
*
|
|
* @param child a <code>BoundingBox</code> value
|
|
* @param subtractFrom an <code>int</code>, SUBTRACT_FROM_LEFT,
|
|
* SUBTRACT_FROM_RIGHT, SUBTRACT_FROM_TOP, SUBTRACT_FROM_BOTTOM
|
|
* @return a <code>BoundingBox</code> value
|
|
* @see #SUBTRACT_FROM_LEFT
|
|
* @see #SUBTRACT_FROM_RIGHT
|
|
* @see #SUBTRACT_FROM_TOP
|
|
* @see #SUBTRACT_FROM_BOTTOM
|
|
*/
|
|
public BoundingBox subtract(BoundingBox child, int subtractFrom) {
|
|
// First, check to see if the params are valid
|
|
if (child == null) {
|
|
throw new IllegalArgumentException("BoundingBox.subtract, "
|
|
+ "BoundingBox child is null");
|
|
}
|
|
if (!child.hasParent()) {
|
|
throw new IllegalArgumentException("BoundingBox.subtract, "
|
|
+ "BoundingBox child has no parent");
|
|
}
|
|
if (!(child.getParent() == this)) {
|
|
throw new IllegalArgumentException("BoundingBox.subtract, "
|
|
+ "this is not BoundingBox child's parent");
|
|
}
|
|
// Now that we know the child is this object's child, we continue
|
|
// and check the subtractFrom param
|
|
int len = SUBTRACTS.length;
|
|
boolean valid = false;
|
|
for (int i = 0; i < len; i++) {
|
|
if (subtractFrom == SUBTRACTS[i]) {
|
|
valid = true;
|
|
}
|
|
}
|
|
if (!valid) {
|
|
throw new IllegalArgumentException("BoundingBox.subtract, "
|
|
+ "subtractFrom invalid: " + subtractFrom);
|
|
}
|
|
|
|
// Now we know the child is valid, and if the subtractFrom
|
|
// preference was invalid, we subtract from the bottom
|
|
// The child should no longer be used, since the parent
|
|
// reference will be invalid
|
|
child.setParent(null);
|
|
|
|
int cx = (int) child.getLocation().getX();
|
|
int cy = (int) child.getLocation().getY();
|
|
int cw = (int) child.getSize().getWidth();
|
|
int ch = (int) child.getSize().getHeight();
|
|
int px = (int) this.getLocation().getX();
|
|
int py = (int) this.getLocation().getY();
|
|
int pw = (int) this.getSize().getWidth();
|
|
int ph = (int) this.getSize().getHeight();
|
|
|
|
switch (subtractFrom) {
|
|
case SUBTRACT_FROM_LEFT:
|
|
// This will be useful for right-justified Strings in tables
|
|
pw = cx;
|
|
this.setSize(new Dimension(pw, ph));
|
|
return this;
|
|
|
|
case SUBTRACT_FROM_RIGHT:
|
|
// This will be useful for left justified Strings in tables
|
|
px = px + cw + cx;
|
|
pw = pw - cw - cx;
|
|
this.setLocation(new Point(px, py));
|
|
this.setSize(new Dimension(pw, ph));
|
|
return this;
|
|
|
|
case SUBTRACT_FROM_BOTTOM:
|
|
py = py + ch + cy;
|
|
ph = ph - ch - cy;
|
|
this.setLocation(new Point(px, py));
|
|
this.setSize(new Dimension(pw, ph));
|
|
return this;
|
|
|
|
case SUBTRACT_FROM_TOP:
|
|
ph = cy;
|
|
this.setSize(new Dimension(pw, ph));
|
|
return this;
|
|
|
|
default: // Should never happen
|
|
break;
|
|
} // end switch
|
|
return this;
|
|
} // end subtract
|
|
|
|
/**
|
|
* <p>
|
|
* Gets the drawing point to use in Graphics drawing methods. After getting
|
|
* a new BoundingBox with getStringBounds(), calling this method will give
|
|
* you an absolute point, accounting for alignment and padding, etc, from
|
|
* which to start drawing the String
|
|
* </p>
|
|
*
|
|
* <p>
|
|
* If getStringBounds was not called (this is a parent box), the upper left
|
|
* coordinates will be returned (this.getLocation())
|
|
* </p>
|
|
*
|
|
* @return a <code>Point</code>
|
|
*/
|
|
public Point getDrawingPoint() {
|
|
return drawingPoint;
|
|
}
|
|
|
|
// main method is for testing /////////////////
|
|
/**
|
|
* For testing
|
|
*
|
|
* @param args a <code>String[]</code> value
|
|
*/
|
|
public static void main(String[] args) {
|
|
Point upperLeft = new Point(5, 5);
|
|
Dimension bounds = new Dimension(100, 100);
|
|
BoundingBox parent = new BoundingBox(upperLeft, bounds);
|
|
String string = "Hello World!";
|
|
Font font = new Font("SansSerif", Font.PLAIN, 12);
|
|
Frame frame = new Frame();
|
|
frame.addNotify();
|
|
try {
|
|
Image image = frame.createImage(100, 100);
|
|
if (image == null) {
|
|
System.err.println("image is null");
|
|
}
|
|
Graphics graphics = image.getGraphics();
|
|
FontMetrics fm = graphics.getFontMetrics(font);
|
|
BoundingBox child = parent
|
|
.getStringBounds(string,
|
|
BoundingBox.HORIZ_ALIGN_LEFT,
|
|
BoundingBox.VERT_ALIGN_TOP,
|
|
fm,
|
|
5);
|
|
System.out.println("Drawing Point: "
|
|
+ child.getDrawingPoint().toString());
|
|
System.out.println("Now testing subtract() method...");
|
|
|
|
parent = new BoundingBox(new Point(10, 10), new Dimension(300, 300));
|
|
System.out.println("parent: " + parent.toString());
|
|
child = new BoundingBox(new Point(90, 110), new Dimension(100, 100));
|
|
parent.add(child);
|
|
System.out.println("child: " + child.toString());
|
|
System.out.println();
|
|
System.out.println("subtracting the child from the parent");
|
|
System.out.println("SUBTRACT_FROM_TOP: ");
|
|
parent = parent.subtract(child, SUBTRACT_FROM_TOP);
|
|
System.out.println("new parent: " + parent.toString());
|
|
System.out.println();
|
|
System.out.println("Resetting parent");
|
|
parent = new BoundingBox(new Point(10, 10), new Dimension(300, 300));
|
|
parent.add(child);
|
|
System.out.println("SUBTRACT_FROM_BOTTOM");
|
|
parent.subtract(child, SUBTRACT_FROM_BOTTOM);
|
|
System.out.println("new parent: " + parent.toString());
|
|
System.out.println();
|
|
System.out.println("Resetting parent");
|
|
parent = new BoundingBox(new Point(10, 10), new Dimension(300, 300));
|
|
parent.add(child);
|
|
System.out.println("SUBTRACT_FROM_LEFT");
|
|
parent.subtract(child, SUBTRACT_FROM_LEFT);
|
|
System.out.println("new parent: " + parent.toString());
|
|
System.out.println();
|
|
System.out.println("Resetting parent");
|
|
parent = new BoundingBox(new Point(10, 10), new Dimension(300, 300));
|
|
parent.add(child);
|
|
System.out.println("SUBTRACT_FROM_RIGHT");
|
|
parent.subtract(child, SUBTRACT_FROM_RIGHT);
|
|
System.out.println("new parent: " + parent.toString());
|
|
System.out.println();
|
|
|
|
System.exit(0);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
System.exit(1);
|
|
}
|
|
}
|
|
|
|
// Private methods /////////////////////////////
|
|
/**
|
|
* Creates a new <code>BoundingBox</code> instance.
|
|
*
|
|
* @param p a <code>Point</code> value
|
|
* @param d a <code>Dimension</code> value
|
|
* @param drawingPoint a <code>Point</code> value
|
|
*/
|
|
private BoundingBox(Point p,
|
|
Dimension d,
|
|
Point drawingPoint,
|
|
Point absolute) {
|
|
super(p, d);
|
|
this.drawingPoint = drawingPoint;
|
|
this.absoluteLocation = absolute;
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Checks the horizontal alignment passed into a method to make sure it is
|
|
* one of the valid values</p>
|
|
*
|
|
* @param hAlign an <code>int</code> value
|
|
* @return a <code>boolean</code> value
|
|
*/
|
|
private boolean checkHAlign(int hAlign) {
|
|
int len = HORIZ_ALIGNS.length;
|
|
for (int i = 0; i < len; i++) {
|
|
if (hAlign == HORIZ_ALIGNS[i]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Checks the vertical alignment passed into a method to make sure it is one
|
|
* of the valid values</p>
|
|
*
|
|
* @param vAlign an <code>int</code> value
|
|
* @return a <code>boolean</code> value
|
|
*/
|
|
private boolean checkVAlign(int vAlign) {
|
|
int len = VERT_ALIGNS.length;
|
|
for (int i = 0; i < len; i++) {
|
|
if (vAlign == VERT_ALIGNS[i]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // end class BoundingBox
|
|
|