001/* ========================================================================
002 * JCommon : a free general purpose class library for the Java(tm) platform
003 * ========================================================================
004 *
005 * (C) Copyright 2000-2013, 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 * TextLine.java
029 * -------------
030 * (C) Copyright 2003-2013, by Object Refinery Limited and Contributors.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * $Id: TextLine.java,v 1.13 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 * 29-Jan-2004 : Added new constructor (DG);
042 * 22-Mar-2004 : Added equals() method and implemented Serializable (DG);
043 * 01-Apr-2004 : Changed java.awt.geom.Dimension2D to org.jfree.ui.Size2D 
044 *               because of JDK bug 4976448 which persists on JDK 1.3.1 (DG);
045 * 03-Sep-2004 : Added a method to remove a fragment (DG);
046 * 08-Jul-2005 : Fixed bug in calculateBaselineOffset() (DG);
047 * 01-Sep-2013 : Updated draw() method to take into account the textAnchor (DG);
048 *
049 */
050
051package org.jfree.text;
052
053import java.awt.Font;
054import java.awt.Graphics2D;
055import java.awt.Paint;
056import java.io.Serializable;
057import java.util.Iterator;
058import java.util.List;
059
060import org.jfree.ui.Size2D;
061import org.jfree.ui.TextAnchor;
062
063/**
064 * A sequence of {@link TextFragment} objects that together form a line of 
065 * text.  A sequence of text lines is managed by the {@link TextBlock} class.
066 *
067 * @author David Gilbert
068 */
069public class TextLine implements Serializable {
070
071    /** For serialization. */
072    private static final long serialVersionUID = 7100085690160465444L;
073    
074    /** Storage for the text fragments that make up the line. */
075    private List fragments;
076
077    /**
078     * Creates a new empty line.
079     */
080    public TextLine() {
081        this.fragments = new java.util.ArrayList();
082    }
083    
084    /**
085     * Creates a new text line using the default font.
086     * 
087     * @param text  the text (<code>null</code> not permitted).
088     */
089    public TextLine(final String text) {
090        this(text, TextFragment.DEFAULT_FONT);   
091    }
092    
093    /**
094     * Creates a new text line.
095     * 
096     * @param text  the text (<code>null</code> not permitted).
097     * @param font  the text font (<code>null</code> not permitted).
098     */
099    public TextLine(final String text, final Font font) {
100        this.fragments = new java.util.ArrayList();
101        final TextFragment fragment = new TextFragment(text, font);
102        this.fragments.add(fragment);
103    }
104    
105    /**
106     * Creates a new text line.
107     * 
108     * @param text  the text (<code>null</code> not permitted).
109     * @param font  the text font (<code>null</code> not permitted).
110     * @param paint  the text color (<code>null</code> not permitted).
111     */
112    public TextLine(final String text, final Font font, final Paint paint) {
113        if (text == null) {
114            throw new IllegalArgumentException("Null 'text' argument.");   
115        }
116        if (font == null) {
117            throw new IllegalArgumentException("Null 'font' argument.");   
118        }
119        if (paint == null) {
120            throw new IllegalArgumentException("Null 'paint' argument.");   
121        }
122        this.fragments = new java.util.ArrayList();
123        final TextFragment fragment = new TextFragment(text, font, paint);
124        this.fragments.add(fragment);
125    }
126    
127    /**
128     * Adds a text fragment to the text line.
129     * 
130     * @param fragment  the text fragment (<code>null</code> not permitted).
131     */
132    public void addFragment(final TextFragment fragment) {
133        this.fragments.add(fragment);        
134    }
135    
136    /**
137     * Removes a fragment from the line.
138     * 
139     * @param fragment  the fragment to remove.
140     */
141    public void removeFragment(final TextFragment fragment) {
142        this.fragments.remove(fragment);
143    }
144    
145    /**
146     * Draws the text line.
147     * 
148     * @param g2  the graphics device.
149     * @param anchorX  the x-coordinate for the anchor point.
150     * @param anchorY  the y-coordinate for the anchor point.
151     * @param anchor  the point on the text line that is aligned to the anchor 
152     *                point.
153     * @param rotateX  the x-coordinate for the rotation point.
154     * @param rotateY  the y-coordinate for the rotation point.
155     * @param angle  the rotation angle (in radians).
156     */
157    public void draw(Graphics2D g2, float anchorX, float anchorY, 
158            TextAnchor anchor, float rotateX,  float rotateY, double angle) {
159    
160        Size2D dim = calculateDimensions(g2);
161        float xAdj = 0.0f;
162        if (anchor.isHorizontalCenter()) {
163            xAdj = (float) -dim.getWidth() / 2.0f;
164        }
165        else if (anchor.isRight()) {
166            xAdj = (float) -dim.getWidth();
167        }
168        float x = anchorX + xAdj;
169        final float yOffset = calculateBaselineOffset(g2, anchor);
170        final Iterator iterator = this.fragments.iterator();
171        while (iterator.hasNext()) {
172            final TextFragment fragment = (TextFragment) iterator.next();
173            final Size2D d = fragment.calculateDimensions(g2);
174            fragment.draw(g2, x, anchorY + yOffset, TextAnchor.BASELINE_LEFT, 
175                    rotateX, rotateY, angle);
176            x = x + (float) d.getWidth();
177        }
178    
179    }
180    
181    /**
182     * Calculates the width and height of the text line.
183     * 
184     * @param g2  the graphics device.
185     * 
186     * @return The width and height.
187     */
188    public Size2D calculateDimensions(final Graphics2D g2) {
189        double width = 0.0;
190        double height = 0.0;
191        final Iterator iterator = this.fragments.iterator();
192        while (iterator.hasNext()) {
193            final TextFragment fragment = (TextFragment) iterator.next();
194            final Size2D dimension = fragment.calculateDimensions(g2);
195            width = width + dimension.getWidth();
196            height = Math.max(height, dimension.getHeight());
197        }
198        return new Size2D(width, height);
199    }
200    
201    /**
202     * Returns the first text fragment in the line.
203     * 
204     * @return The first text fragment in the line.
205     */
206    public TextFragment getFirstTextFragment() {
207        TextFragment result = null;
208        if (this.fragments.size() > 0) {
209            result = (TextFragment) this.fragments.get(0);
210        }    
211        return result;
212    }
213    
214    /**
215     * Returns the last text fragment in the line.
216     * 
217     * @return The last text fragment in the line.
218     */
219    public TextFragment getLastTextFragment() {
220        TextFragment result = null;
221        if (this.fragments.size() > 0) {
222            result = (TextFragment) this.fragments.get(this.fragments.size() 
223                    - 1);
224        }    
225        return result;
226    }
227    
228    /**
229     * Calculate the offsets required to translate from the specified anchor 
230     * position to the left baseline position.
231     * 
232     * @param g2  the graphics device.
233     * @param anchor  the anchor position.
234     * 
235     * @return The offsets.
236     */
237    private float calculateBaselineOffset(final Graphics2D g2, 
238                                          final TextAnchor anchor) {
239        float result = 0.0f;
240        Iterator iterator = this.fragments.iterator();
241        while (iterator.hasNext()) {
242            TextFragment fragment = (TextFragment) iterator.next();
243            result = Math.max(result, 
244                    fragment.calculateBaselineOffset(g2, anchor));
245        }
246        return result;
247    }
248    
249    /**
250     * Tests this object for equality with an arbitrary object.
251     * 
252     * @param obj  the object to test against (<code>null</code> permitted).
253     * 
254     * @return A boolean.
255     */
256    public boolean equals(final Object obj) {
257        if (obj == null) {
258            return false;
259        }
260        if (obj == this) {
261            return true;   
262        }
263        if (obj instanceof TextLine) {
264            final TextLine line = (TextLine) obj;
265            return this.fragments.equals(line.fragments);
266        }
267        return false;
268    }
269
270    /**
271     * Returns a hash code for this object.
272     * 
273     * @return A hash code.
274     */
275    public int hashCode() {
276        return (this.fragments != null ? this.fragments.hashCode() : 0);
277    }
278
279}