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.15 2007/11/02 17:50:35 taqua 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 
051package org.jfree.text;
052
053import java.awt.Font;
054import java.awt.Graphics2D;
055import java.awt.Paint;
056import java.awt.Shape;
057import java.awt.geom.Rectangle2D;
058import java.io.Serializable;
059import java.util.Collections;
060import java.util.Iterator;
061import java.util.List;
062
063import org.jfree.ui.HorizontalAlignment;
064import org.jfree.ui.Size2D;
065import org.jfree.ui.TextAnchor;
066import org.jfree.util.ShapeUtilities;
067
068/**
069 * A list of {@link TextLine} objects that form a block of text.
070 * 
071 * @see TextUtilities#createTextBlock(String, Font, Paint)
072 *
073 * @author David Gilbert
074 */
075public class TextBlock implements Serializable {
076
077    /** For serialization. */
078    private static final long serialVersionUID = -4333175719424385526L;
079    
080    /** Storage for the lines of text. */
081    private List lines;
082    
083    /** The alignment of the lines. */
084    private HorizontalAlignment lineAlignment;
085
086    /**
087     * Creates a new empty text block.
088     */
089    public TextBlock() {
090        this.lines = new java.util.ArrayList();
091        this.lineAlignment = HorizontalAlignment.CENTER;
092    }
093    
094    /**
095     * Returns the alignment of the lines of text within the block.
096     * 
097     * @return The alignment (never <code>null</code>).
098     */
099    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}