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 }