001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-present, by David Gilbert and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jfreechart/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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 */
028
029package org.jfree.chart.text;
030
031import java.awt.Font;
032import java.awt.Graphics2D;
033import java.awt.Paint;
034import java.awt.Shape;
035import java.awt.geom.Rectangle2D;
036import java.io.Serializable;
037import java.util.Collections;
038import java.util.Iterator;
039import java.util.List;
040import org.jfree.chart.ui.HorizontalAlignment;
041import org.jfree.chart.ui.Size2D;
042import org.jfree.chart.ui.TextAnchor;
043import org.jfree.chart.util.ShapeUtils;
044
045/**
046 * A list of {@link TextLine} objects that form a block of text.
047 * 
048 * @see TextUtils#createTextBlock(String, Font, Paint)
049 */
050public class TextBlock implements Serializable {
051
052    /** For serialization. */
053    private static final long serialVersionUID = -4333175719424385526L;
054    
055    /** Storage for the lines of text. */
056    private List lines;
057    
058    /** The alignment of the lines. */
059    private HorizontalAlignment lineAlignment;
060
061    /**
062     * Creates a new empty text block.
063     */
064    public TextBlock() {
065        this.lines = new java.util.ArrayList();
066        this.lineAlignment = HorizontalAlignment.CENTER;
067    }
068    
069    /**
070     * Returns the alignment of the lines of text within the block.
071     * 
072     * @return The alignment (never {@code null}).
073     */
074    public HorizontalAlignment getLineAlignment() {
075        return this.lineAlignment;   
076    }
077    
078    /**
079     * Sets the alignment of the lines of text within the block.
080     * 
081     * @param alignment  the alignment ({@code null} not permitted).
082     */
083    public void setLineAlignment(HorizontalAlignment alignment) {
084        if (alignment == null) {
085            throw new IllegalArgumentException("Null 'alignment' argument.");
086        }
087        this.lineAlignment = alignment;   
088    }
089    
090    /**
091     * Adds a line of text that will be displayed using the specified font.
092     * 
093     * @param text  the text.
094     * @param font  the font.
095     * @param paint  the paint.
096     */
097    public void addLine(String text, Font font, Paint paint) {
098        addLine(new TextLine(text, font, paint));
099    }
100    
101    /**
102     * Adds a {@link TextLine} to the block.
103     * 
104     * @param line  the line.
105     */
106    public void addLine(TextLine line) {
107        this.lines.add(line);    
108    }
109    
110    /**
111     * Returns the last line in the block.
112     * 
113     * @return The last line in the block.
114     */
115    public TextLine getLastLine() {
116        TextLine last = null;
117        final int index = this.lines.size() - 1;
118        if (index >= 0) {
119            last = (TextLine) this.lines.get(index);
120        }
121        return last;
122    }
123    
124    /**
125     * Returns an unmodifiable list containing the lines for the text block.
126     *
127     * @return A list of {@link TextLine} objects.
128     */
129    public List getLines() {
130        return Collections.unmodifiableList(this.lines);
131    }
132    
133    /**
134     * Returns the width and height of the text block.
135     * 
136     * @param g2  the graphics device.
137     * 
138     * @return The width and height.
139     */
140    public Size2D calculateDimensions(Graphics2D g2) {
141        double width = 0.0;
142        double height = 0.0;
143        Iterator iterator = this.lines.iterator();
144        while (iterator.hasNext()) {
145            final TextLine line = (TextLine) iterator.next();
146            final Size2D dimension = line.calculateDimensions(g2);
147            width = Math.max(width, dimension.getWidth());
148            height = height + dimension.getHeight();
149        }
150        return new Size2D(width, height);
151    }
152    
153    /**
154     * Returns the bounds of the text block.
155     * 
156     * @param g2  the graphics device ({@code null} not permitted).
157     * @param anchorX  the x-coordinate for the anchor point.
158     * @param anchorY  the y-coordinate for the anchor point.
159     * @param anchor  the text block anchor ({@code null} not permitted).
160     * @param rotateX  the x-coordinate for the rotation point.
161     * @param rotateY  the y-coordinate for the rotation point.
162     * @param angle  the rotation angle.
163     * 
164     * @return The bounds.
165     */
166    public Shape calculateBounds(Graphics2D g2, float anchorX, float anchorY, 
167            TextBlockAnchor anchor, float rotateX, float rotateY, double angle) {
168        
169        Size2D d = calculateDimensions(g2);
170        float[] offsets = calculateOffsets(anchor, d.getWidth(), d.getHeight());
171        Rectangle2D bounds = new Rectangle2D.Double(anchorX + offsets[0], 
172                anchorY + offsets[1], d.getWidth(), d.getHeight());
173        Shape rotatedBounds = ShapeUtils.rotateShape(bounds, angle, rotateX, 
174                rotateY);
175        return rotatedBounds;   
176        
177    }
178    
179    /**
180     * Draws the text block at a specific location.
181     * 
182     * @param g2  the graphics device.
183     * @param x  the x-coordinate for the anchor point.
184     * @param y  the y-coordinate for the anchor point.
185     * @param anchor  the anchor point.
186     */
187    public void draw(Graphics2D g2, float x, float y, TextBlockAnchor anchor) {
188        draw(g2, x, y, anchor, 0.0f, 0.0f, 0.0);
189    }
190    
191    /**
192     * Draws the text block, aligning it with the specified anchor point and 
193     * rotating it about the specified rotation point.
194     * 
195     * @param g2  the graphics device.
196     * @param anchorX  the x-coordinate for the anchor point.
197     * @param anchorY  the y-coordinate for the anchor point.
198     * @param anchor  the point on the text block that is aligned to the 
199     *                anchor point.
200     * @param rotateX  the x-coordinate for the rotation point.
201     * @param rotateY  the x-coordinate for the rotation point.
202     * @param angle  the rotation (in radians).
203     */
204    public void draw(Graphics2D g2, float anchorX, float anchorY, 
205            TextBlockAnchor anchor, float rotateX, float rotateY, double angle) {
206    
207        Size2D d = calculateDimensions(g2);
208        float[] offsets = calculateOffsets(anchor, d.getWidth(), 
209                d.getHeight());
210        Iterator iterator = this.lines.iterator();
211        float yCursor = 0.0f;
212        while (iterator.hasNext()) {
213            TextLine line = (TextLine) iterator.next();
214            Size2D dimension = line.calculateDimensions(g2);
215            float lineOffset = 0.0f;
216            if (this.lineAlignment == HorizontalAlignment.CENTER) {
217                lineOffset = (float) (d.getWidth() - dimension.getWidth()) 
218                    / 2.0f;   
219            }
220            else if (this.lineAlignment == HorizontalAlignment.RIGHT) {
221                lineOffset = (float) (d.getWidth() - dimension.getWidth());   
222            }
223            line.draw(g2, anchorX + offsets[0] + lineOffset, 
224                    anchorY + offsets[1] + yCursor, TextAnchor.TOP_LEFT, 
225                    rotateX, rotateY, angle);
226            yCursor = yCursor + (float) dimension.getHeight();
227        }
228        
229    }
230 
231    /**
232     * Calculates the x and y offsets required to align the text block with the
233     * specified anchor point.  This assumes that the top left of the text 
234     * block is at (0.0, 0.0).
235     * 
236     * @param anchor  the anchor position.
237     * @param width  the width of the text block.
238     * @param height  the height of the text block.
239     * 
240     * @return The offsets (float[0] = x offset, float[1] = y offset).
241     */
242    private float[] calculateOffsets(TextBlockAnchor anchor, double width, 
243            double height) {
244        float[] result = new float[2];
245        float xAdj = 0.0f;
246        float yAdj = 0.0f;
247
248        if (anchor == TextBlockAnchor.TOP_CENTER
249                || anchor == TextBlockAnchor.CENTER
250                || anchor == TextBlockAnchor.BOTTOM_CENTER) {
251                    
252            xAdj = (float) -width / 2.0f;
253            
254        }
255        else if (anchor == TextBlockAnchor.TOP_RIGHT
256                || anchor == TextBlockAnchor.CENTER_RIGHT
257                || anchor == TextBlockAnchor.BOTTOM_RIGHT) {
258                    
259            xAdj = (float) -width;
260            
261        }
262
263        if (anchor == TextBlockAnchor.TOP_LEFT
264                || anchor == TextBlockAnchor.TOP_CENTER
265                || anchor == TextBlockAnchor.TOP_RIGHT) {
266                    
267            yAdj = 0.0f;
268            
269        }
270        else if (anchor == TextBlockAnchor.CENTER_LEFT
271                || anchor == TextBlockAnchor.CENTER
272                || anchor == TextBlockAnchor.CENTER_RIGHT) {
273                    
274            yAdj = (float) -height / 2.0f;
275            
276        }
277        else if (anchor == TextBlockAnchor.BOTTOM_LEFT
278                || anchor == TextBlockAnchor.BOTTOM_CENTER
279                || anchor == TextBlockAnchor.BOTTOM_RIGHT) {
280                    
281            yAdj = (float) -height;
282            
283        }
284        result[0] = xAdj;
285        result[1] = yAdj;
286        return result;
287    }   
288    
289    /**
290     * Tests this object for equality with an arbitrary object.
291     * 
292     * @param obj  the object to test against ({@code null} permitted).
293     * 
294     * @return A boolean.
295     */
296    @Override
297    public boolean equals(Object obj) {
298        if (obj == this) {
299            return true;   
300        }
301        if (obj instanceof TextBlock) {
302            TextBlock block = (TextBlock) obj;
303            return this.lines.equals(block.lines);
304        }
305        return false;
306    }
307
308    /**
309     * Returns a hash code for this object.
310     * 
311     * @return A hash code.
312     */
313    @Override
314    public int hashCode() {
315        return (this.lines != null ? this.lines.hashCode() : 0);
316    }
317}
318