001: /*
002: * $Id: BoundingBox.java,v 1.8 2002/02/05 19:25:35 ezb Exp $
003: *
004: * $Date: 2002/02/05 19:25:35 $
005: *
006: * Copyright (c) Eric Z. Beard, ericzbeard@hotmail.com
007: *
008: * This library is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Lesser General Public
010: * License as published by the Free Software Foundation; either
011: * version 2.1 of the License, or (at your option) any later version.
012: *
013: * This library is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * Lesser General Public License for more details.
017: *
018: * You should have received a copy of the GNU Lesser General Public
019: * License along with this library; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021: *
022: */
023: package gnu.jpdf;
024:
025: import java.awt.*;
026: import java.util.*;
027:
028: /**
029: * <p>This class simplifies the placement of Strings within
030: * a canvas area where the placement of objects is absolute</p>
031: *
032: * <p>A <code>BoundingBox</code> is just a Rectangle that knows how to
033: * find the coordinates for a String based on the desired alignment and
034: * <code>FontMetrics</code>. For each new String, a new child
035: * <code>BoundingBox</code> is made that can be subtracted from the
036: * original box so new Strings can be added</p>
037: *
038: * <p>One of the more helpful features of this class is the string wrap
039: * feature of <code>getStringBounds</code>. The box returned by that method
040: * will contain an array of strings that have been broken down to fit the
041: * box. The box's coordinates and size will reflect the size of the
042: * entire group of strings if it is laid out as expected. Using the
043: * returned box and iterating through the array of strings from top to
044: * bottom, getting new bounding boxes for each one (with upper left
045: * alignment and no padding) will result in the correct string wrap.</p>
046: *
047: * <p>Note that you will need to have Xvfb running on a Unix server to
048: * use this class</p>
049: *
050: * @author Eric Z. Beard, ericzbeard@hotmail.com
051: * @author $Author: ezb $
052: * @version $Revision: 1.8 $, $Date: 2002/02/05 19:25:35 $
053: */
054: public class BoundingBox extends Rectangle {
055: /** Percent f line height to space lines */
056: public static final int LINE_SPACING_PERCENTAGE = 20;
057:
058: /** Used to a align a String centered vertically */
059: public static final int VERT_ALIGN_CENTER = 0;
060:
061: /** Used to align a String at the top of the box */
062: public static final int VERT_ALIGN_TOP = 1;
063:
064: /** Used to align a String at the bottom of the box */
065: public static final int VERT_ALIGN_BOTTOM = 2;
066:
067: /** Used to align a String horizontally in the center of the box */
068: public static final int HORIZ_ALIGN_CENTER = 3;
069:
070: /** Used to align a String to the left in the box */
071: public static final int HORIZ_ALIGN_LEFT = 4;
072:
073: /** Used to aling a String to the right in a box */
074: public static final int HORIZ_ALIGN_RIGHT = 5;
075:
076: /** Used to subtract a child from a box, *leaving* the top portion */
077: public static final int SUBTRACT_FROM_TOP = 6;
078:
079: /** Used to subtract a child from a box, *leaving* the bottom portion */
080: public static final int SUBTRACT_FROM_BOTTOM = 7;
081:
082: /** Used to subtract a child from a box, *leaving* the left portion */
083: public static final int SUBTRACT_FROM_LEFT = 8;
084:
085: /** Used to subtract a child from a box, *leaving" the right portion */
086: public static final int SUBTRACT_FROM_RIGHT = 9;
087:
088: private static final int[] VERT_ALIGNS = { VERT_ALIGN_CENTER,
089: VERT_ALIGN_TOP, VERT_ALIGN_BOTTOM };
090:
091: private static final int[] HORIZ_ALIGNS = { HORIZ_ALIGN_CENTER,
092: HORIZ_ALIGN_LEFT, HORIZ_ALIGN_RIGHT };
093:
094: private static final int[] SUBTRACTS = { SUBTRACT_FROM_TOP,
095: SUBTRACT_FROM_BOTTOM, SUBTRACT_FROM_LEFT,
096: SUBTRACT_FROM_RIGHT };
097:
098: /** The point to use for Graphics.drawString() */
099: private Point drawingPoint;
100:
101: /** The absolute, world location of the box */
102: private Point absoluteLocation;
103:
104: /** Link to parent box */
105: private BoundingBox parent;
106:
107: /**
108: * If this box was the result of a getStringBounds call, this
109: * array will hold the broken strings
110: */
111: private String[] stringArray;
112:
113: /** The string specified in getStringBounds */
114: private String fullString;
115:
116: /**
117: * Creates a new <code>BoundingBox</code> instance.
118: *
119: * @param p a <code>Point</code>, upper left coords
120: * @param d a <code>Dimension</code>, used to determine height and width
121: */
122: public BoundingBox(Point p, Dimension d) {
123: super (p, d);
124: this .drawingPoint = this .getLocation();
125: this .absoluteLocation = this .getLocation();
126: }
127:
128: /**
129: * <p>Returns true if this box has a parent. The 'world', or
130: * enclosing canvas is not considered a parent</p>
131: *
132: * @return a <code>boolean</code> value
133: */
134: public boolean hasParent() {
135: if (parent == null) {
136: return false;
137: } else {
138: return true;
139: }
140: }
141:
142: /**
143: * <p>Get this box's parent box</p>
144: *
145: * @return a <code>BoundingBox</code> value
146: */
147: public BoundingBox getParent() {
148: return parent;
149: }
150:
151: /**
152: * <p>Make the specified box this box's child. Equivalent to
153: * <code>child.setParent(parent)</code> where the specified 'parent' is
154: * this instance</p>
155: *
156: * @param child a <code>BoundingBox</code>, any box that can fit inside
157: * this one. The results of calling
158: * <code>getAbsoluteLocation()</code> on the child will be
159: * altered after this to take into account the child's
160: * new location in the 'world'
161: *
162: */
163: public void add(BoundingBox child) {
164: child.setParent(this );
165: }
166:
167: /**
168: * <p>Make the specified box this box's parent</p>
169: *
170: * @param parent a <code>BoundingBox</code> value
171: */
172: public void setParent(BoundingBox parent) {
173: // Prevent infinite recursion
174: if (this == parent) {
175: return;
176: }
177: this .parent = parent;
178:
179: // If this box was created empty, without a String inside,
180: // determine its absolute location
181: if (this .getLocation().equals(this .getAbsoluteLocation())) {
182: int ancestorTranslateX = 0;
183: int ancestorTranslateY = 0;
184:
185: BoundingBox ancestor = this ;
186: while (ancestor.hasParent()) {
187: BoundingBox oldRef = ancestor;
188: ancestor = ancestor.getParent();
189: // Prevent infinite recursion
190: if (ancestor == oldRef) {
191: break;
192: }
193: ancestorTranslateX += (int) ancestor.getLocation()
194: .getX();
195: ancestorTranslateY += (int) ancestor.getLocation()
196: .getY();
197: }
198:
199: this .getAbsoluteLocation().translate(ancestorTranslateX,
200: ancestorTranslateY);
201: } // end if
202: } // end setParent
203:
204: /**
205: * <p>Get the wrapped strings if this box was from a call to getStringBounds,
206: * otherwise this method returns null</p>
207: *
208: * @return a <code>String[]</code> array of strings, top to bottom in layout
209: */
210: public String[] getStringArray() {
211: return stringArray;
212: } // end getStringArray
213:
214: /**
215: * <p>Set the value of the string array</p>
216: *
217: * @param strArray a <code>String</code> array
218: *
219: */
220: public void setStringArray(String[] strArray) {
221: this .stringArray = strArray;
222: }
223:
224: /**
225: * <p>Set the absolute upper left world location point for this box</p>
226: *
227: * @param point a <code>Point</code> value
228: */
229: public void setAbsoluteLocation(Point point) {
230: this .absoluteLocation = point;
231: }
232:
233: /**
234: * <p>Returns false if for any reason this box has negative dimensions</p>
235: */
236: public boolean boxExists() {
237: if ((this .getHeight() < 0) || (this .getWidth() < 0)) {
238: return false;
239: }
240: return true;
241: } // end boxExists
242:
243: /**
244: * <p>Get the absolute upper left location point for this box</p>
245: *
246: * @return a <code>Point</code> value
247: */
248: public Point getAbsoluteLocation() {
249: return absoluteLocation;
250: }
251:
252: /**
253: * <p>Returns the full string associated with a call to
254: * <code>getStringBounds</code></p>
255: */
256: public String getFullString() {
257: return fullString;
258: }
259:
260: /**
261: * <p>Sets the full string associated with <code>getStringBounds</code></p>
262: *
263: * @param string a <code>String</code>
264: */
265: public void setFullString(String string) {
266: this .fullString = string;
267: }
268:
269: /**
270: * <p>Gets the location of a String after it is adjusted for
271: * alignment within this box. The point's coordinates are
272: * either within this box or within the enclosing area.</p>
273: *
274: * @param string a <code>String</code>, the String to be placed
275: * @param hAlign an <code>int</code>, HORIZ_ALIGN_CENTER,
276: * HORIZ_ALIGN_LEFT, HORIX_ALIGN_RIGHT
277: * @param vAlign an <code>int</code>, VERT_ALIGN_CENTER,
278: * VERT_ALIGN_TOP, VERT_ALIGN_BOTTOM
279: * @param fm a <code>FontMetrics</code> object for this String
280: * @param padding an <code>int</code>, the padding around the String
281: * @param enforce a <code>boolean</code>, if true the method will throw
282: * an exception when the string is too big, if not true it will break
283: * the string down and overrun the bottom of the box. If the box
284: * is too small for even one word, the exception will be thrown
285: * @return a <code>Point</code>, the coords to use in drawString()
286: * @see #HORIZ_ALIGN_LEFT
287: * @see #HORIZ_ALIGN_CENTER
288: * @see #HORIZ_ALIGN_RIGHT
289: * @see #VERT_ALIGN_TOP
290: * @see #VERT_ALIGN_CENTER
291: * @see #VERT_ALIGN_BOTTOM
292: * @throws <code>IllegalArgumentException</code> if the args are invalid
293: * @throws <code>StringTooLongException</code> if the string won't fit
294: * and enforce is set to true. The exception can still be thrown
295: * if enforce is false, but only in cases such as the box having
296: * no height or width
297: */
298: public BoundingBox getStringBounds(String string, int hAlign,
299: int vAlign, FontMetrics fm, int padding, boolean enforce)
300: throws IllegalArgumentException, StringTooLongException {
301: // Check to make sure the values passed in are valid
302: if (!checkHAlign(hAlign)) {
303: throw new IllegalArgumentException(
304: "BoundingBox.getStringBounds, "
305: + "hAlign invalid : " + hAlign);
306: }
307: if (!checkVAlign(vAlign)) {
308: throw new IllegalArgumentException(
309: "BoundingBox.getStringBounds, "
310: + "vAlign invalid : " + hAlign);
311: }
312: if (fm == null) {
313: throw new IllegalArgumentException(
314: "BoundingBox.getStringBounds, "
315: + "FontMetrics null");
316: }
317: if (string == null) {
318: throw new IllegalArgumentException(
319: "BoundingBox.getStringBounds, " + "String null");
320: }
321:
322: // NOTE: For this portion of the method, parent refers
323: // to this object and child refers to the object about
324: // to be created. When the absolute point for drawing the
325: // String is determined, this object's ancestors are checked.
326: Point parentLocation = this .getLocation();
327: Dimension parentSize = this .getSize();
328:
329: Point childLocation;
330: Dimension childSize;
331:
332: // String ascent, width, height, parent, child width, height
333: int sa, sw, sh, pw, ph, cw, ch;
334:
335: // Child, parent x, y coords for upper left
336: int cx, cy, px, py;
337:
338: sa = fm.getMaxAscent();
339: sw = fm.stringWidth(string);
340: sh = sa + fm.getMaxDescent();
341: pw = (int) parentSize.getWidth();
342: ph = (int) parentSize.getHeight();
343: if (pw < 0) {
344: throw new StringTooLongException(
345: "The parent box has a negative width " + " (" + pw
346: + ")");
347: }
348: if (ph < 0) {
349: throw new StringTooLongException(
350: "The parent box has a negative height" + " (" + ph
351: + ")");
352: }
353: cw = sw + padding * 2;
354: ch = sh + padding * 2;
355: px = (int) this .getX();
356: py = (int) this .getY();
357:
358: String[] childStrArray = null;
359:
360: if ((cw > pw) || (string.indexOf("\n") != -1)) {
361: cw = pw - (padding * 2);
362: childStrArray = createStringArray(string, fm, padding, pw);
363: ch = getWrappedHeight(childStrArray, fm, padding);
364: if (ch > ph) {
365: // If enforce is not true, it means we want the box to
366: // be returned anyway (along with the strings in the array)
367: // so we can chop them manually and try again
368: if (enforce) {
369: throw new StringTooLongException(
370: "The wrapped strings do not "
371: + "fit into the parent box, pw="
372: + pw + ", ph=" + ph + ", ch=" + ch
373: + ", cw=" + cw + ", string: "
374: + string);
375: }
376: }
377: }
378:
379: // Need to have child width and height, and string array set
380:
381: // Child location is relative to this (parent) box, not the world
382: if (vAlign == VERT_ALIGN_TOP) {
383: cy = 0;
384: } else if (vAlign == VERT_ALIGN_CENTER) {
385: cy = (ph / 2) - (ch / 2);
386: } else {
387: cy = ph - ch;
388: }
389:
390: if (hAlign == HORIZ_ALIGN_LEFT) {
391: cx = 0;
392: } else if (hAlign == HORIZ_ALIGN_CENTER) {
393: cx = (pw / 2) - (cw / 2);
394: } else {
395: cx = pw - cw;
396: }
397:
398: childLocation = new Point(cx, cy);
399: childSize = new Dimension(cw, ch);
400:
401: // Drawing location is based on the baseline of the String, and
402: // relative to the world, not this box. The drawing point differs
403: // from the absolute box location because of padding and ascent
404: int dpx, dpy, abx, aby;
405:
406: // If this object also has a parent (maybe grandparents), iterate
407: // through them and find the absolute 'world' location
408: int ancestorTranslateX = 0;
409: int ancestorTranslateY = 0;
410:
411: BoundingBox ancestor = this ;
412: while (ancestor.hasParent()) {
413: BoundingBox oldRef = ancestor;
414: ancestor = ancestor.getParent();
415: // Prevent infinite recursion
416: if (ancestor == oldRef) {
417: break;
418: }
419: ancestorTranslateX += (int) ancestor.getLocation().getX();
420: ancestorTranslateY += (int) ancestor.getLocation().getY();
421: }
422:
423: // Determine the absolute location for the box
424: abx = px + cx + ancestorTranslateX;
425: aby = py + cy + ancestorTranslateY;
426:
427: // Determine the absolute drawing point for the String
428: dpx = abx + padding;
429: dpy = aby + padding + sa;
430:
431: Point drawingPoint = new Point(dpx, dpy);
432: BoundingBox returnChild = new BoundingBox(childLocation,
433: childSize, drawingPoint, new Point(abx, aby));
434: this .add(returnChild);
435: returnChild.setFullString(string);
436: returnChild.setStringArray(childStrArray);
437: return returnChild;
438:
439: } // end getStringBounds
440:
441: /**
442: * <p>Gets the location of a String after it is adjusted for
443: * alignment within this box. The point's coordinates are
444: * either within this box or within the enclosing area.</p>
445: *
446: * <p>By default, this method enforces string length and throws the
447: * exception if it is too long</p>
448: *
449: * @param string a <code>String</code>, the String to be placed
450: * @param hAlign an <code>int</code>, HORIZ_ALIGN_CENTER,
451: * HORIZ_ALIGN_LEFT, HORIX_ALIGN_RIGHT
452: * @param vAlign an <code>int</code>, VERT_ALIGN_CENTER,
453: * VERT_ALIGN_TOP, VERT_ALIGN_BOTTOM
454: * @param fm a <code>FontMetrics</code> object for this String
455: * @param padding an <code>int</code>, the padding around the String
456: * @return a <code>Point</code>, the coords to use in drawString()
457: * @throws <code>IllegalArgumentException</code> if the args are invalid
458: * @throws <code>StringTooLongException</code> if the string won't fit
459: */
460: public BoundingBox getStringBounds(String string, int hAlign,
461: int vAlign, FontMetrics fm, int padding)
462: throws StringTooLongException, IllegalArgumentException {
463: return getStringBounds(string, hAlign, vAlign, fm, padding,
464: true);
465: } // end getStringBounds (enforce true by default)
466:
467: /**
468: * <p>This method is called after getting the box by calling
469: * <code>getStringBounds</code> on the parent. Wraps the string at
470: * word boundaries and draws it to the specified <code>Graphics</code>
471: * context. Make sure padding is the same as specified for the
472: * <code>getStringBounds</code> call, or you may get an unexpected
473: * {@link gnu.jpdf.StringTooLongException}</p>
474: *
475: * @param g the <code>Graphics</code> object
476: * @param fm the <code>FontMetrics</code> to use for sizing
477: * @param padding an int, the padding around the strings
478: * @param hAlign the <code>int</code> horizontal alignment
479: * @throws <code>IllegalArgumentException</code> if the args are invalid
480: * @throws <code>StringTooLongException</code> if the string
481: * won't fit this will only happen if the fm or padding has
482: * been changed since getStringBounds was called succesfully
483: */
484: public void drawWrappedString(Graphics g, FontMetrics fm,
485: int padding, int hAlign) throws IllegalArgumentException,
486: StringTooLongException {
487: if (getStringArray() == null) {
488: Point p = getDrawingPoint();
489: int xx = (int) p.getX();
490: int yy = (int) p.getY();
491: g.drawString(getFullString(), xx, yy);
492: } else {
493: int len = stringArray.length;
494: for (int i = 0; i < len; i++) {
495: BoundingBox wrappedBox = null;
496: wrappedBox = getStringBounds(stringArray[i], hAlign,
497: BoundingBox.VERT_ALIGN_TOP, fm, 0);
498: Point pp = wrappedBox.getDrawingPoint();
499: int xx = (int) pp.getX();
500: if (hAlign == BoundingBox.HORIZ_ALIGN_RIGHT) {
501: xx -= padding;
502: }
503: if (hAlign == BoundingBox.HORIZ_ALIGN_LEFT) {
504: xx += padding;
505: }
506: int yy = (int) pp.getY() + padding;
507: g.drawString(stringArray[i], xx, yy);
508: subtract(wrappedBox, BoundingBox.SUBTRACT_FROM_BOTTOM);
509: }
510: }
511: } // end drawWrappedString
512:
513: /**
514: * <p>Draws lines from the wrapped string until there is no more room and
515: * then stops. If there is no string or the box is too small for
516: * anything to be drawn, does nothing</p>
517: *
518: * @param g the <code>Graphics</code> object to draw to
519: * @param fm the <code>FontMetrics</code> object to use for string sizing
520: * @param padding the <code>int</code> amount of padding around the string
521: * @param hAlign the <code>int</code> horizontal alignment
522: *
523: */
524: public void drawWrappedStringTruncate(Graphics g, FontMetrics fm,
525: int padding, int hAlign) {
526:
527: if (getStringArray() == null) {
528: Point p = getDrawingPoint();
529: int xx = (int) p.getX();
530: int yy = (int) p.getY();
531: if (getFullString() != null) {
532: g.drawString(getFullString(), xx, yy);
533: } else {
534: System.err
535: .println("getStringArray and getFullString are null");
536: }
537: } else {
538: int totalHeight = 0;
539: int len = stringArray.length;
540: for (int i = 0; i < len; i++) {
541: BoundingBox wrappedBox = null;
542: try {
543: wrappedBox = getStringBounds(stringArray[i],
544: hAlign, BoundingBox.VERT_ALIGN_TOP, fm, 0,
545: false);
546: totalHeight += (int) wrappedBox.getHeight();
547: if (getParent() != null) {
548: if (totalHeight > (int) (getParent()
549: .getHeight())) {
550: return;
551: }
552: }
553: } catch (StringTooLongException stle) {
554: stle.printStackTrace();
555: return;
556: }
557: wrappedBox.drawChoppedString(g, fm, padding, hAlign);
558: subtract(wrappedBox, BoundingBox.SUBTRACT_FROM_BOTTOM);
559: }
560: }
561: } // end drawWrappedStringTruncate
562:
563: /**
564: * <p>Take the first line of the string (if it is wrapped, otherwise just
565: * take the whole string) and chop the end of it off to make it fit in the
566: * box. If the box is smaller than one letter, draw nothing</p>
567: *
568: * @param g the <code>Graphics</code> object to draw to
569: * @param fm the <code>FontMetrics</code> object to use for string sizing
570: * @param padding the <code>int</code> amount of padding around the string
571: * @param hAlign the <code>int</code> horizontal alignment
572: */
573: public void drawChoppedString(Graphics g, FontMetrics fm,
574: int padding, int hAlign) {
575:
576: String string = "";
577: if (getStringArray() != null) {
578: string = new String(getStringArray()[0]);
579: } else {
580: string = new String(getFullString());
581: }
582: BoundingBox choppedBox = null;
583: try {
584: choppedBox = getStringBounds(string, hAlign,
585: VERT_ALIGN_TOP, fm, padding);
586: Point p = choppedBox.getDrawingPoint();
587: int x = (int) p.getX();
588: int y = (int) p.getY();
589: g.drawString(string, x, y);
590: } catch (StringTooLongException stle) {
591: // Doesn't fit - start cutting from the end until it does
592: StringBuffer buf = new StringBuffer().append(string);
593: if (buf.length() == 0) {
594: System.out
595: .println("BoundingBox.drawChoppedString, buf len 0 ??");
596: //return;
597: throw new RuntimeException();
598: }
599: buf.deleteCharAt(buf.length() - 1);
600: while ((fm.stringWidth(buf.toString()) > (int) getWidth())
601: && (buf.length() > 0)) {
602: buf.deleteCharAt(buf.length() - 1);
603: }
604:
605: try {
606: choppedBox = getStringBounds(buf.toString(), hAlign,
607: VERT_ALIGN_TOP, fm, padding);
608: Point pp = choppedBox.getDrawingPoint();
609: int xx = (int) pp.getX();
610: int yy = (int) pp.getY();
611: g.drawString(string, xx, yy);
612: } catch (StringTooLongException sstle) {
613: // Must be a really small box!
614: sstle.printStackTrace();
615: }
616: }
617: } // end drawChoppedString
618:
619: /**
620: * <p>Get the total height of the box needed to contain the strings in
621: * the specified array</p>
622: */
623: private int getWrappedHeight(String[] strings, FontMetrics fm,
624: int padding) {
625: int ma = fm.getMaxAscent();
626: int md = fm.getMaxDescent();
627: int sh = ma + md;
628: int hPad = sh / LINE_SPACING_PERCENTAGE;
629: sh += hPad;
630: int total = sh * strings.length;
631:
632: return total + (padding * 2);
633: } // end getWrappedHeight
634:
635: /**
636: *
637: * <p>Make a string array from a string, wrapped to fit the box</p>
638: *
639: * <p>If the line width is too short, the array is just a
640: * tokenized version of the string</p>
641: *
642: * @param string - the <code>String</code> to convert to an array
643: * @param
644: */
645: private String[] createStringArray(String string, FontMetrics fm,
646: int padding, int pw) {
647: if (string == null) {
648: System.err
649: .println("Tried createStringArray with null String");
650: return null;
651: }
652: if (fm == null) {
653: System.err
654: .println("Tried createStringArray with null FontMetrics");
655: }
656:
657: int sw = fm.stringWidth(string);
658: int lw = pw - (padding * 2);
659:
660: Vector returnVector = new Vector();
661: // Return delimiters as tokens
662: StringTokenizer st = new StringTokenizer(string, " \t\n\r\f",
663: true);
664: StringBuffer tempBuffer = new StringBuffer();
665: StringBuffer finalBuffer = new StringBuffer();
666:
667: while (st.hasMoreTokens()) {
668: // Get the next word and add a space after it
669: String tempString = st.nextToken();
670: tempBuffer.append(tempString);
671:
672: // If we haven't reached the width with our current
673: // line, keep adding tokens. Also, check for hard returns
674: if ((fm.stringWidth(tempBuffer.toString()) < lw)
675: && (tempBuffer.toString().charAt(
676: tempBuffer.toString().length() - 1) != '\n')
677: && (tempBuffer.toString().charAt(
678: tempBuffer.toString().length() - 1) != '\r')) {
679: finalBuffer.append(tempString);
680: continue;
681: } else {
682: returnVector.addElement(finalBuffer.toString());
683: finalBuffer.delete(0, finalBuffer.length());
684: tempBuffer.delete(0, tempBuffer.length());
685: if ((tempString.charAt(0) != '\n')
686: && (tempString.charAt(0) != '\r')) {
687: tempBuffer.append(tempString);
688: finalBuffer.append(tempString);
689: }
690: continue;
691: }
692:
693: } // end while
694: returnVector.addElement(finalBuffer.toString());
695:
696: int len = returnVector.size();
697: // Init the class member field stringArray
698: String[] childStrArray = new String[len];
699: for (int i = 0; i < len; i++) {
700: String curStr = (String) returnVector.get(i);
701: childStrArray[i] = curStr;
702: }
703:
704: return childStrArray;
705:
706: } // end createStringArray
707:
708: /**
709: * <p>Removes the child box from this parent box. The child must
710: * have this object as its parent or the method does nothing.
711: * The BoundingBox returned will be cut by an area equal to
712: * the child area plus the horizontal or vertical strip in
713: * which it sits, depending on the 'subtractFrom' value passed
714: * in</p>
715: *
716: * @param child a <code>BoundingBox</code> value
717: * @param int an <code>int</code>, SUBTRACT_FROM_LEFT,
718: SUBTRACT_FROM_RIGHT, SUBTRACT_FROM_TOP,
719: SUBTRACT_FROM_BOTTOM
720: * @return a <code>BoundingBox</code> value
721: * @see #SUBTRACT_FROM_LEFT
722: * @see #SUBTRACT_FROM_RIGHT
723: * @see #SUBTRACT_FROM_TOP
724: * @see #SUBTRACT_FROM_BOTTOM
725: */
726: public BoundingBox subtract(BoundingBox child, int subtractFrom) {
727: // First, check to see if the params are valid
728: if (child == null) {
729: throw new IllegalArgumentException("BoundingBox.subtract, "
730: + "BoundingBox child is null");
731: }
732: if (!child.hasParent()) {
733: throw new IllegalArgumentException("BoundingBox.subtract, "
734: + "BoundingBox child has no parent");
735: } else {
736: if (!(child.getParent() == this )) {
737: throw new IllegalArgumentException(
738: "BoundingBox.subtract, "
739: + "this is not BoundingBox child's parent");
740: } else {
741: // Now that we know the child is this object's child, we continue
742: // and check the subtractFrom param
743: int len = SUBTRACTS.length;
744: boolean valid = false;
745: for (int i = 0; i < len; i++) {
746: if (subtractFrom == SUBTRACTS[i]) {
747: valid = true;
748: }
749: }
750: if (!valid) {
751: throw new IllegalArgumentException(
752: "BoundingBox.subtract, "
753: + "subtractFrom invalid: "
754: + subtractFrom);
755: }
756:
757: // Now we know the child is valid, and if the subtractFrom
758: // preference was invalid, we subtract from the bottom
759:
760: // The child should no longer be used, since the parent
761: // reference will be invalid
762: child.setParent(null);
763:
764: int cx = (int) child.getLocation().getX();
765: int cy = (int) child.getLocation().getY();
766: int cw = (int) child.getSize().getWidth();
767: int ch = (int) child.getSize().getHeight();
768: int px = (int) this .getLocation().getX();
769: int py = (int) this .getLocation().getY();
770: int pw = (int) this .getSize().getWidth();
771: int ph = (int) this .getSize().getHeight();
772:
773: switch (subtractFrom) {
774: case SUBTRACT_FROM_LEFT:
775: // This will be useful for right-justified Strings in tables
776: pw = cx;
777: this .setSize(new Dimension(pw, ph));
778: return this ;
779:
780: case SUBTRACT_FROM_RIGHT:
781: // This will be useful for left justified Strings in tables
782: px = px + cw + cx;
783: pw = pw - cw - cx;
784: this .setLocation(new Point(px, py));
785: this .setSize(new Dimension(pw, ph));
786: return this ;
787:
788: case SUBTRACT_FROM_BOTTOM:
789: py = py + ch + cy;
790: ph = ph - ch - cy;
791: this .setLocation(new Point(px, py));
792: this .setSize(new Dimension(pw, ph));
793: return this ;
794:
795: case SUBTRACT_FROM_TOP:
796: ph = cy;
797: this .setSize(new Dimension(pw, ph));
798: return this ;
799:
800: default: // Should never happen
801: break;
802: } // end switch
803: }
804: }
805: return this ;
806: } // end subtract
807:
808: /**
809: * <p>Gets the drawing point to use in Graphics drawing
810: * methods. After getting a new BoundingBox with getStringBounds(),
811: * calling this method will give you an absolute point, accounting
812: * for alignment and padding, etc, from which to start drawing the
813: * String</p>
814: *
815: * <p>If getStringBounds was not called (this is a parent box), the
816: * upper left coordinates will be returned (this.getLocation())</p>
817: *
818: * @return a <code>Point</code>
819: */
820: public Point getDrawingPoint() {
821: return drawingPoint;
822: }
823:
824: // main method is for testing /////////////////
825:
826: /**
827: * For testing
828: *
829: * @param args a <code>String[]</code> value
830: */
831: public static void main(String[] args) {
832: Point upperLeft = new Point(5, 5);
833: Dimension bounds = new Dimension(100, 100);
834: BoundingBox parent = new BoundingBox(upperLeft, bounds);
835: String string = "Hello World!";
836: Font font = new Font("SansSerif", Font.PLAIN, 12);
837: Frame frame = new Frame();
838: frame.addNotify();
839: try {
840: Image image = frame.createImage(100, 100);
841: if (image == null) {
842: System.err.println("image is null");
843: }
844: Graphics graphics = image.getGraphics();
845: FontMetrics fm = graphics.getFontMetrics(font);
846: BoundingBox child = parent.getStringBounds(string,
847: BoundingBox.HORIZ_ALIGN_LEFT,
848: BoundingBox.VERT_ALIGN_TOP, fm, 5);
849: System.out.println("Drawing Point: "
850: + child.getDrawingPoint().toString());
851: System.out.println("Now testing subtract() method...");
852:
853: parent = new BoundingBox(new Point(10, 10), new Dimension(
854: 300, 300));
855: System.out.println("parent: " + parent.toString());
856: child = new BoundingBox(new Point(90, 110), new Dimension(
857: 100, 100));
858: parent.add(child);
859: System.out.println("child: " + child.toString());
860: System.out.println();
861: System.out.println("subtracting the child from the parent");
862: System.out.println("SUBTRACT_FROM_TOP: ");
863: parent = parent.subtract(child, SUBTRACT_FROM_TOP);
864: System.out.println("new parent: " + parent.toString());
865: System.out.println();
866: System.out.println("Resetting parent");
867: parent = new BoundingBox(new Point(10, 10), new Dimension(
868: 300, 300));
869: parent.add(child);
870: System.out.println("SUBTRACT_FROM_BOTTOM");
871: parent.subtract(child, SUBTRACT_FROM_BOTTOM);
872: System.out.println("new parent: " + parent.toString());
873: System.out.println();
874: System.out.println("Resetting parent");
875: parent = new BoundingBox(new Point(10, 10), new Dimension(
876: 300, 300));
877: parent.add(child);
878: System.out.println("SUBTRACT_FROM_LEFT");
879: parent.subtract(child, SUBTRACT_FROM_LEFT);
880: System.out.println("new parent: " + parent.toString());
881: System.out.println();
882: System.out.println("Resetting parent");
883: parent = new BoundingBox(new Point(10, 10), new Dimension(
884: 300, 300));
885: parent.add(child);
886: System.out.println("SUBTRACT_FROM_RIGHT");
887: parent.subtract(child, SUBTRACT_FROM_RIGHT);
888: System.out.println("new parent: " + parent.toString());
889: System.out.println();
890:
891: System.exit(0);
892: } catch (Exception e) {
893: e.printStackTrace();
894: System.exit(1);
895: }
896: }
897:
898: // Private methods /////////////////////////////
899:
900: /**
901: * Creates a new <code>BoundingBox</code> instance.
902: *
903: * @param p a <code>Point</code> value
904: * @param d a <code>Dimension</code> value
905: * @param drawingPoint a <code>Point</code> value
906: */
907: private BoundingBox(Point p, Dimension d, Point drawingPoint,
908: Point absolute) {
909: super (p, d);
910: this .drawingPoint = drawingPoint;
911: this .absoluteLocation = absolute;
912: }
913:
914: /**
915: * <p>Checks the horizontal alignment passed into a
916: * method to make sure it is one of the valid values</p>
917: *
918: * @param hAlign an <code>int</code> value
919: * @return a <code>boolean</code> value
920: */
921: private boolean checkHAlign(int hAlign) {
922: int len = HORIZ_ALIGNS.length;
923: for (int i = 0; i < len; i++) {
924: if (hAlign == HORIZ_ALIGNS[i]) {
925: return true;
926: }
927: }
928: return false;
929: }
930:
931: /**
932: * <p>Checks the vertical alignment passed into a
933: * method to make sure it is one of the valid values</p>
934: *
935: * @param vAlign an <code>int</code> value
936: * @return a <code>boolean</code> value
937: */
938: private boolean checkVAlign(int vAlign) {
939: int len = VERT_ALIGNS.length;
940: for (int i = 0; i < len; i++) {
941: if (vAlign == VERT_ALIGNS[i]) {
942: return true;
943: }
944: }
945: return false;
946: }
947:
948: } // end class BoundingBox
|