001 /* ======================================================================== 002 * JCommon : a free general purpose class library for the Java(tm) platform 003 * ======================================================================== 004 * 005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jcommon/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * -------------- 028 * TextBlock.java 029 * -------------- 030 * (C) Copyright 2003, 2004, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * $Id: TextBlock.java,v 1.13 2005/10/18 13:17:16 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 07-Nov-2003 : Version 1 (DG); 040 * 22-Dec-2003 : Added workaround for Java bug 4245442 (DG); 041 * 09-Jan-2004 : Added an extra draw() method for no rotation case (DG); 042 * 25-Feb-2004 : Added getLines() method (DG); 043 * 22-Mar-2004 : Added equals() method and implemented Serializable (DG); 044 * 24-Mar-2004 : Added 'paint' argument to addLine() method (DG); 045 * 01-Apr-2004 : Changed java.awt.geom.Dimension2D to org.jfree.ui.Size2D 046 * because of JDK bug 4976448 which persists on JDK 1.3.1 (DG); 047 * 04-Oct-2004 : Renamed ShapeUtils --> ShapeUtilities (DG); 048 * 049 */ 050 051 package org.jfree.text; 052 053 import java.awt.Font; 054 import java.awt.Graphics2D; 055 import java.awt.Paint; 056 import java.awt.Shape; 057 import java.awt.geom.Rectangle2D; 058 import java.io.Serializable; 059 import java.util.Collections; 060 import java.util.Iterator; 061 import java.util.List; 062 063 import org.jfree.ui.HorizontalAlignment; 064 import org.jfree.ui.Size2D; 065 import org.jfree.ui.TextAnchor; 066 import org.jfree.util.Log; 067 import org.jfree.util.LogContext; 068 import org.jfree.util.ShapeUtilities; 069 070 /** 071 * A list of {@link TextLine} objects that form a block of text. 072 * 073 * @see TextUtilities#createTextBlock(String, Font, Paint) 074 * 075 * @author David Gilbert 076 */ 077 public class TextBlock implements Serializable { 078 079 /** For serialization. */ 080 private static final long serialVersionUID = -4333175719424385526L; 081 082 /** Storage for the lines of text. */ 083 private List lines; 084 085 /** The alignment of the lines. */ 086 private HorizontalAlignment lineAlignment; 087 088 /** Access to logging facilities. */ 089 protected static final LogContext logger 090 = Log.createContext(TextBlock.class); 091 092 /** 093 * Creates a new empty text block. 094 */ 095 public TextBlock() { 096 this.lines = new java.util.ArrayList(); 097 this.lineAlignment = HorizontalAlignment.CENTER; 098 } 099 100 /** 101 * Returns the alignment of the lines of text within the block. 102 * 103 * @return The alignment (never <code>null</code>). 104 */ 105 public HorizontalAlignment getLineAlignment() { 106 return this.lineAlignment; 107 } 108 109 /** 110 * Sets the alignment of the lines of text within the block. 111 * 112 * @param alignment the alignment (<code>null</code> not permitted). 113 */ 114 public void setLineAlignment(HorizontalAlignment alignment) { 115 if (alignment == null) { 116 throw new IllegalArgumentException("Null 'alignment' argument."); 117 } 118 this.lineAlignment = alignment; 119 } 120 121 /** 122 * Adds a line of text that will be displayed using the specified font. 123 * 124 * @param text the text. 125 * @param font the font. 126 * @param paint the paint. 127 */ 128 public void addLine(final String text, final Font font, final Paint paint) { 129 addLine(new TextLine(text, font, paint)); 130 } 131 132 /** 133 * Adds a {@link TextLine} to the block. 134 * 135 * @param line the line. 136 */ 137 public void addLine(final TextLine line) { 138 this.lines.add(line); 139 } 140 141 /** 142 * Returns the last line in the block. 143 * 144 * @return The last line in the block. 145 */ 146 public TextLine getLastLine() { 147 TextLine last = null; 148 final int index = this.lines.size() - 1; 149 if (index >= 0) { 150 last = (TextLine) this.lines.get(index); 151 } 152 return last; 153 } 154 155 /** 156 * Returns an unmodifiable list containing the lines for the text block. 157 * 158 * @return A list of {@link TextLine} objects. 159 */ 160 public List getLines() { 161 return Collections.unmodifiableList(this.lines); 162 } 163 164 /** 165 * Returns the width and height of the text block. 166 * 167 * @param g2 the graphics device. 168 * 169 * @return The width and height. 170 */ 171 public Size2D calculateDimensions(final Graphics2D g2) { 172 double width = 0.0; 173 double height = 0.0; 174 final Iterator iterator = this.lines.iterator(); 175 while (iterator.hasNext()) { 176 final TextLine line = (TextLine) iterator.next(); 177 final Size2D dimension = line.calculateDimensions(g2); 178 width = Math.max(width, dimension.getWidth()); 179 height = height + dimension.getHeight(); 180 } 181 if (logger.isDebugEnabled()) { 182 logger.debug("width = " + width + ", height = " + height); 183 } 184 return new Size2D(width, height); 185 } 186 187 /** 188 * Returns the bounds of the text block. 189 * 190 * @param g2 the graphics device (<code>null</code> not permitted). 191 * @param anchorX the x-coordinate for the anchor point. 192 * @param anchorY the y-coordinate for the anchor point. 193 * @param anchor the text block anchor (<code>null</code> not permitted). 194 * @param rotateX the x-coordinate for the rotation point. 195 * @param rotateY the y-coordinate for the rotation point. 196 * @param angle the rotation angle. 197 * 198 * @return The bounds. 199 */ 200 public Shape calculateBounds(final Graphics2D g2, 201 final float anchorX, final float anchorY, 202 final TextBlockAnchor anchor, 203 final float rotateX, final float rotateY, 204 final double angle) { 205 206 final Size2D d = calculateDimensions(g2); 207 final float[] offsets = calculateOffsets( 208 anchor, d.getWidth(), d.getHeight() 209 ); 210 final Rectangle2D bounds = new Rectangle2D.Double( 211 anchorX + offsets[0], anchorY + offsets[1], 212 d.getWidth(), d.getHeight() 213 ); 214 final Shape rotatedBounds = ShapeUtilities.rotateShape( 215 bounds, angle, rotateX, rotateY 216 ); 217 return rotatedBounds; 218 219 } 220 221 /** 222 * Draws the text block at a specific location. 223 * 224 * @param g2 the graphics device. 225 * @param x the x-coordinate for the anchor point. 226 * @param y the y-coordinate for the anchor point. 227 * @param anchor the anchor point. 228 */ 229 public void draw(final Graphics2D g2, final float x, final float y, 230 final TextBlockAnchor anchor) { 231 draw(g2, x, y, anchor, 0.0f, 0.0f, 0.0); 232 } 233 234 /** 235 * Draws the text block, aligning it with the specified anchor point and 236 * rotating it about the specified rotation point. 237 * 238 * @param g2 the graphics device. 239 * @param anchorX the x-coordinate for the anchor point. 240 * @param anchorY the y-coordinate for the anchor point. 241 * @param anchor the point on the text block that is aligned to the 242 * anchor point. 243 * @param rotateX the x-coordinate for the rotation point. 244 * @param rotateY the x-coordinate for the rotation point. 245 * @param angle the rotation (in radians). 246 */ 247 public void draw(final Graphics2D g2, 248 final float anchorX, final float anchorY, 249 final TextBlockAnchor anchor, 250 final float rotateX, final float rotateY, 251 final double angle) { 252 253 final Size2D d = calculateDimensions(g2); 254 final float[] offsets = calculateOffsets(anchor, d.getWidth(), 255 d.getHeight()); 256 final Iterator iterator = this.lines.iterator(); 257 float yCursor = 0.0f; 258 while (iterator.hasNext()) { 259 TextLine line = (TextLine) iterator.next(); 260 Size2D dimension = line.calculateDimensions(g2); 261 float lineOffset = 0.0f; 262 if (this.lineAlignment == HorizontalAlignment.CENTER) { 263 lineOffset = (float) (d.getWidth() - dimension.getWidth()) 264 / 2.0f; 265 } 266 else if (this.lineAlignment == HorizontalAlignment.RIGHT) { 267 lineOffset = (float) (d.getWidth() - dimension.getWidth()); 268 } 269 line.draw( 270 g2, anchorX + offsets[0] + lineOffset, anchorY + offsets[1] + yCursor, 271 TextAnchor.TOP_LEFT, rotateX, rotateY, angle 272 ); 273 yCursor = yCursor + (float) dimension.getHeight(); 274 } 275 276 } 277 278 /** 279 * Calculates the x and y offsets required to align the text block with the 280 * specified anchor point. This assumes that the top left of the text 281 * block is at (0.0, 0.0). 282 * 283 * @param anchor the anchor position. 284 * @param width the width of the text block. 285 * @param height the height of the text block. 286 * 287 * @return The offsets (float[0] = x offset, float[1] = y offset). 288 */ 289 private float[] calculateOffsets(final TextBlockAnchor anchor, 290 final double width, final double height) { 291 final float[] result = new float[2]; 292 float xAdj = 0.0f; 293 float yAdj = 0.0f; 294 295 if (anchor == TextBlockAnchor.TOP_CENTER 296 || anchor == TextBlockAnchor.CENTER 297 || anchor == TextBlockAnchor.BOTTOM_CENTER) { 298 299 xAdj = (float) -width / 2.0f; 300 301 } 302 else if (anchor == TextBlockAnchor.TOP_RIGHT 303 || anchor == TextBlockAnchor.CENTER_RIGHT 304 || anchor == TextBlockAnchor.BOTTOM_RIGHT) { 305 306 xAdj = (float) -width; 307 308 } 309 310 if (anchor == TextBlockAnchor.TOP_LEFT 311 || anchor == TextBlockAnchor.TOP_CENTER 312 || anchor == TextBlockAnchor.TOP_RIGHT) { 313 314 yAdj = 0.0f; 315 316 } 317 else if (anchor == TextBlockAnchor.CENTER_LEFT 318 || anchor == TextBlockAnchor.CENTER 319 || anchor == TextBlockAnchor.CENTER_RIGHT) { 320 321 yAdj = (float) -height / 2.0f; 322 323 } 324 else if (anchor == TextBlockAnchor.BOTTOM_LEFT 325 || anchor == TextBlockAnchor.BOTTOM_CENTER 326 || anchor == TextBlockAnchor.BOTTOM_RIGHT) { 327 328 yAdj = (float) -height; 329 330 } 331 result[0] = xAdj; 332 result[1] = yAdj; 333 return result; 334 } 335 336 /** 337 * Tests this object for equality with an arbitrary object. 338 * 339 * @param obj the object to test against (<code>null</code> permitted). 340 * 341 * @return A boolean. 342 */ 343 public boolean equals(final Object obj) { 344 if (obj == this) { 345 return true; 346 } 347 if (obj instanceof TextBlock) { 348 final TextBlock block = (TextBlock) obj; 349 return this.lines.equals(block.lines); 350 } 351 return false; 352 } 353 354 /** 355 * Returns a hash code for this object. 356 * 357 * @return A hash code. 358 */ 359 public int hashCode() { 360 return (this.lines != null ? this.lines.hashCode() : 0); 361 } 362 }