Source for org.jfree.text.TextBlock

   1: /* ========================================================================
   2:  * JCommon : a free general purpose class library for the Java(tm) platform
   3:  * ========================================================================
   4:  *
   5:  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
   6:  * 
   7:  * Project Info:  http://www.jfree.org/jcommon/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  * 
  27:  * --------------
  28:  * TextBlock.java
  29:  * --------------
  30:  * (C) Copyright 2003, 2004, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * $Id: TextBlock.java,v 1.15 2007/11/02 17:50:35 taqua Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 07-Nov-2003 : Version 1 (DG);
  40:  * 22-Dec-2003 : Added workaround for Java bug 4245442 (DG);
  41:  * 09-Jan-2004 : Added an extra draw() method for no rotation case (DG);
  42:  * 25-Feb-2004 : Added getLines() method (DG);
  43:  * 22-Mar-2004 : Added equals() method and implemented Serializable (DG);
  44:  * 24-Mar-2004 : Added 'paint' argument to addLine() method (DG);
  45:  * 01-Apr-2004 : Changed java.awt.geom.Dimension2D to org.jfree.ui.Size2D 
  46:  *               because of JDK bug 4976448 which persists on JDK 1.3.1 (DG);
  47:  * 04-Oct-2004 : Renamed ShapeUtils --> ShapeUtilities (DG);
  48:  *
  49:  */
  50:  
  51: package org.jfree.text;
  52: 
  53: import java.awt.Font;
  54: import java.awt.Graphics2D;
  55: import java.awt.Paint;
  56: import java.awt.Shape;
  57: import java.awt.geom.Rectangle2D;
  58: import java.io.Serializable;
  59: import java.util.Collections;
  60: import java.util.Iterator;
  61: import java.util.List;
  62: 
  63: import org.jfree.ui.HorizontalAlignment;
  64: import org.jfree.ui.Size2D;
  65: import org.jfree.ui.TextAnchor;
  66: import org.jfree.util.ShapeUtilities;
  67: 
  68: /**
  69:  * A list of {@link TextLine} objects that form a block of text.
  70:  * 
  71:  * @see TextUtilities#createTextBlock(String, Font, Paint)
  72:  *
  73:  * @author David Gilbert
  74:  */
  75: public class TextBlock implements Serializable {
  76: 
  77:     /** For serialization. */
  78:     private static final long serialVersionUID = -4333175719424385526L;
  79:     
  80:     /** Storage for the lines of text. */
  81:     private List lines;
  82:     
  83:     /** The alignment of the lines. */
  84:     private HorizontalAlignment lineAlignment;
  85: 
  86:     /**
  87:      * Creates a new empty text block.
  88:      */
  89:     public TextBlock() {
  90:         this.lines = new java.util.ArrayList();
  91:         this.lineAlignment = HorizontalAlignment.CENTER;
  92:     }
  93:     
  94:     /**
  95:      * Returns the alignment of the lines of text within the block.
  96:      * 
  97:      * @return The alignment (never <code>null</code>).
  98:      */
  99:     public HorizontalAlignment getLineAlignment() {
 100:         return this.lineAlignment;   
 101:     }
 102:     
 103:     /**
 104:      * Sets the alignment of the lines of text within the block.
 105:      * 
 106:      * @param alignment  the alignment (<code>null</code> not permitted).
 107:      */
 108:     public void setLineAlignment(HorizontalAlignment alignment) {
 109:         if (alignment == null) {
 110:             throw new IllegalArgumentException("Null 'alignment' argument.");
 111:         }
 112:         this.lineAlignment = alignment;   
 113:     }
 114:     
 115:     /**
 116:      * Adds a line of text that will be displayed using the specified font.
 117:      * 
 118:      * @param text  the text.
 119:      * @param font  the font.
 120:      * @param paint  the paint.
 121:      */
 122:     public void addLine(final String text, final Font font, final Paint paint) {
 123:         addLine(new TextLine(text, font, paint));
 124:     }
 125:     
 126:     /**
 127:      * Adds a {@link TextLine} to the block.
 128:      * 
 129:      * @param line  the line.
 130:      */
 131:     public void addLine(final TextLine line) {
 132:         this.lines.add(line);    
 133:     }
 134:     
 135:     /**
 136:      * Returns the last line in the block.
 137:      * 
 138:      * @return The last line in the block.
 139:      */
 140:     public TextLine getLastLine() {
 141:         TextLine last = null;
 142:         final int index = this.lines.size() - 1;
 143:         if (index >= 0) {
 144:             last = (TextLine) this.lines.get(index);
 145:         }
 146:         return last;
 147:     }
 148:     
 149:     /**
 150:      * Returns an unmodifiable list containing the lines for the text block.
 151:      *
 152:      * @return A list of {@link TextLine} objects.
 153:      */
 154:     public List getLines() {
 155:         return Collections.unmodifiableList(this.lines);
 156:     }
 157:     
 158:     /**
 159:      * Returns the width and height of the text block.
 160:      * 
 161:      * @param g2  the graphics device.
 162:      * 
 163:      * @return The width and height.
 164:      */
 165:     public Size2D calculateDimensions(final Graphics2D g2) {
 166:         double width = 0.0;
 167:         double height = 0.0;
 168:         final Iterator iterator = this.lines.iterator();
 169:         while (iterator.hasNext()) {
 170:             final TextLine line = (TextLine) iterator.next();
 171:             final Size2D dimension = line.calculateDimensions(g2);
 172:             width = Math.max(width, dimension.getWidth());
 173:             height = height + dimension.getHeight();
 174:         }
 175:         return new Size2D(width, height);
 176:     }
 177:     
 178:     /**
 179:      * Returns the bounds of the text block.
 180:      * 
 181:      * @param g2  the graphics device (<code>null</code> not permitted).
 182:      * @param anchorX  the x-coordinate for the anchor point.
 183:      * @param anchorY  the y-coordinate for the anchor point.
 184:      * @param anchor  the text block anchor (<code>null</code> not permitted).
 185:      * @param rotateX  the x-coordinate for the rotation point.
 186:      * @param rotateY  the y-coordinate for the rotation point.
 187:      * @param angle  the rotation angle.
 188:      * 
 189:      * @return The bounds.
 190:      */
 191:     public Shape calculateBounds(final Graphics2D g2,
 192:                                  final float anchorX, final float anchorY, 
 193:                                  final TextBlockAnchor anchor,
 194:                                  final float rotateX, final float rotateY, 
 195:                                  final double angle) {
 196:         
 197:         final Size2D d = calculateDimensions(g2);
 198:         final float[] offsets = calculateOffsets(
 199:             anchor, d.getWidth(), d.getHeight()
 200:         );
 201:         final Rectangle2D bounds = new Rectangle2D.Double(
 202:             anchorX + offsets[0], anchorY + offsets[1], 
 203:             d.getWidth(), d.getHeight()
 204:         );
 205:         final Shape rotatedBounds = ShapeUtilities.rotateShape(
 206:             bounds, angle, rotateX, rotateY
 207:         );
 208:         return rotatedBounds;   
 209:         
 210:     }
 211:     
 212:     /**
 213:      * Draws the text block at a specific location.
 214:      * 
 215:      * @param g2  the graphics device.
 216:      * @param x  the x-coordinate for the anchor point.
 217:      * @param y  the y-coordinate for the anchor point.
 218:      * @param anchor  the anchor point.
 219:      */
 220:     public void draw(final Graphics2D g2, final float x, final float y, 
 221:                      final TextBlockAnchor anchor) {
 222:         draw(g2, x, y, anchor, 0.0f, 0.0f, 0.0);
 223:     }
 224:     
 225:     /**
 226:      * Draws the text block, aligning it with the specified anchor point and 
 227:      * rotating it about the specified rotation point.
 228:      * 
 229:      * @param g2  the graphics device.
 230:      * @param anchorX  the x-coordinate for the anchor point.
 231:      * @param anchorY  the y-coordinate for the anchor point.
 232:      * @param anchor  the point on the text block that is aligned to the 
 233:      *                anchor point.
 234:      * @param rotateX  the x-coordinate for the rotation point.
 235:      * @param rotateY  the x-coordinate for the rotation point.
 236:      * @param angle  the rotation (in radians).
 237:      */
 238:     public void draw(final Graphics2D g2,
 239:                      final float anchorX, final float anchorY, 
 240:                      final TextBlockAnchor anchor,
 241:                      final float rotateX, final float rotateY, 
 242:                      final double angle) {
 243:     
 244:         final Size2D d = calculateDimensions(g2);
 245:         final float[] offsets = calculateOffsets(anchor, d.getWidth(), 
 246:                 d.getHeight());
 247:         final Iterator iterator = this.lines.iterator();
 248:         float yCursor = 0.0f;
 249:         while (iterator.hasNext()) {
 250:             TextLine line = (TextLine) iterator.next();
 251:             Size2D dimension = line.calculateDimensions(g2);
 252:             float lineOffset = 0.0f;
 253:             if (this.lineAlignment == HorizontalAlignment.CENTER) {
 254:                 lineOffset = (float) (d.getWidth() - dimension.getWidth()) 
 255:                     / 2.0f;   
 256:             }
 257:             else if (this.lineAlignment == HorizontalAlignment.RIGHT) {
 258:                 lineOffset = (float) (d.getWidth() - dimension.getWidth());   
 259:             }
 260:             line.draw(
 261:                 g2, anchorX + offsets[0] + lineOffset, anchorY + offsets[1] + yCursor,
 262:                 TextAnchor.TOP_LEFT, rotateX, rotateY, angle
 263:             );
 264:             yCursor = yCursor + (float) dimension.getHeight();
 265:         }
 266:         
 267:     }
 268:  
 269:     /**
 270:      * Calculates the x and y offsets required to align the text block with the
 271:      * specified anchor point.  This assumes that the top left of the text 
 272:      * block is at (0.0, 0.0).
 273:      * 
 274:      * @param anchor  the anchor position.
 275:      * @param width  the width of the text block.
 276:      * @param height  the height of the text block.
 277:      * 
 278:      * @return The offsets (float[0] = x offset, float[1] = y offset).
 279:      */
 280:     private float[] calculateOffsets(final TextBlockAnchor anchor, 
 281:                                      final double width, final double height) {
 282:         final float[] result = new float[2];
 283:         float xAdj = 0.0f;
 284:         float yAdj = 0.0f;
 285: 
 286:         if (anchor == TextBlockAnchor.TOP_CENTER
 287:                 || anchor == TextBlockAnchor.CENTER
 288:                 || anchor == TextBlockAnchor.BOTTOM_CENTER) {
 289:                     
 290:             xAdj = (float) -width / 2.0f;
 291:             
 292:         }
 293:         else if (anchor == TextBlockAnchor.TOP_RIGHT
 294:                 || anchor == TextBlockAnchor.CENTER_RIGHT
 295:                 || anchor == TextBlockAnchor.BOTTOM_RIGHT) {
 296:                     
 297:             xAdj = (float) -width;
 298:             
 299:         }
 300: 
 301:         if (anchor == TextBlockAnchor.TOP_LEFT
 302:                 || anchor == TextBlockAnchor.TOP_CENTER
 303:                 || anchor == TextBlockAnchor.TOP_RIGHT) {
 304:                     
 305:             yAdj = 0.0f;
 306:             
 307:         }
 308:         else if (anchor == TextBlockAnchor.CENTER_LEFT
 309:                 || anchor == TextBlockAnchor.CENTER
 310:                 || anchor == TextBlockAnchor.CENTER_RIGHT) {
 311:                     
 312:             yAdj = (float) -height / 2.0f;
 313:             
 314:         }
 315:         else if (anchor == TextBlockAnchor.BOTTOM_LEFT
 316:                 || anchor == TextBlockAnchor.BOTTOM_CENTER
 317:                 || anchor == TextBlockAnchor.BOTTOM_RIGHT) {
 318:                     
 319:             yAdj = (float) -height;
 320:             
 321:         }
 322:         result[0] = xAdj;
 323:         result[1] = yAdj;
 324:         return result;
 325:     }   
 326:     
 327:     /**
 328:      * Tests this object for equality with an arbitrary object.
 329:      * 
 330:      * @param obj  the object to test against (<code>null</code> permitted).
 331:      * 
 332:      * @return A boolean.
 333:      */
 334:     public boolean equals(final Object obj) {
 335:         if (obj == this) {
 336:             return true;   
 337:         }
 338:         if (obj instanceof TextBlock) {
 339:             final TextBlock block = (TextBlock) obj;
 340:             return this.lines.equals(block.lines);
 341:         }
 342:         return false;
 343:     }
 344: 
 345:     /**
 346:      * Returns a hash code for this object.
 347:      * 
 348:      * @return A hash code.
 349:      */
 350:     public int hashCode() {
 351:         return (this.lines != null ? this.lines.hashCode() : 0);
 352:     }
 353: }