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     * TextFragment.java
029     * -----------------
030     * (C) Copyright 2003-2005, by Object Refinery Limited and Contributors.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * $Id: TextFragment.java,v 1.12 2005/10/18 13:17:16 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 07-Nov-2003 : Version 1 (DG);
040     * 25-Nov-2003 : Fixed bug in the dimension calculation (DG);
041     * 22-Dec-2003 : Added workaround for Java bug 4245442 (DG);
042     * 29-Jan-2004 : Added paint attribute (DG);
043     * 22-Mar-2004 : Added equals() method and implemented Serializable (DG);
044     * 01-Apr-2004 : Changed java.awt.geom.Dimension2D to org.jfree.ui.Size2D 
045     *               because of JDK bug 4976448 which persists on JDK 1.3.1 (DG);
046     * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities 
047     *               --> TextUtilities (DG);
048     *
049     */
050     
051    package org.jfree.text;
052    
053    import java.awt.Color;
054    import java.awt.Font;
055    import java.awt.FontMetrics;
056    import java.awt.Graphics2D;
057    import java.awt.Paint;
058    import java.awt.font.LineMetrics;
059    import java.awt.geom.Rectangle2D;
060    import java.io.IOException;
061    import java.io.ObjectInputStream;
062    import java.io.ObjectOutputStream;
063    import java.io.Serializable;
064    
065    import org.jfree.io.SerialUtilities;
066    import org.jfree.ui.Size2D;
067    import org.jfree.ui.TextAnchor;
068    import org.jfree.util.Log;
069    import org.jfree.util.LogContext;
070    
071    /**
072     * A text item, with an associated font, that fits on a single line (see 
073     * {@link TextLine}).  Instances of the class are immutable.
074     *
075     * @author David Gilbert
076     */
077    public class TextFragment implements Serializable {
078    
079        /** For serialization. */
080        private static final long serialVersionUID = 4465945952903143262L;
081        
082        /** The default font. */
083        public static final Font DEFAULT_FONT = new Font("Serif", Font.PLAIN, 12);
084        
085        /** The default text color. */
086        public static final Paint DEFAULT_PAINT = Color.black;
087        
088        /** The text. */
089        private String text;
090        
091        /** The font. */
092        private Font font;
093        
094        /** The text color. */
095        private Paint paint;
096        
097        /** 
098         * The baseline offset (can be used to simulate subscripts and 
099         * superscripts). 
100         */
101        private float baselineOffset;
102        
103        /** Access to logging facilities. */
104        protected static final LogContext logger 
105            = Log.createContext(TextFragment.class);
106        
107        /**
108         * Creates a new text fragment.
109         * 
110         * @param text  the text (<code>null</code> not permitted).
111         */
112        public TextFragment(final String text) {
113            this(text, DEFAULT_FONT, DEFAULT_PAINT);
114        }
115        
116        /**
117         * Creates a new text fragment.
118         * 
119         * @param text  the text (<code>null</code> not permitted).
120         * @param font  the font (<code>null</code> not permitted).
121         */
122        public TextFragment(final String text, final Font font) {
123            this(text, font, DEFAULT_PAINT);
124        }
125    
126        /**
127         * Creates a new text fragment.
128         * 
129         * @param text  the text (<code>null</code> not permitted).
130         * @param font  the font (<code>null</code> not permitted).
131         * @param paint  the text color (<code>null</code> not permitted).
132         */
133        public TextFragment(final String text, final Font font, final Paint paint) {
134            this(text, font, paint, 0.0f);
135        }
136    
137        /**
138         * Creates a new text fragment.
139         * 
140         * @param text  the text (<code>null</code> not permitted).
141         * @param font  the font (<code>null</code> not permitted).
142         * @param paint  the text color (<code>null</code> not permitted).
143         * @param baselineOffset  the baseline offset.
144         */
145        public TextFragment(final String text, final Font font, final Paint paint,
146                            final float baselineOffset) {
147            if (text == null) {
148                throw new IllegalArgumentException("Null 'text' argument.");  
149            }
150            if (font == null) {
151                throw new IllegalArgumentException("Null 'font' argument.");
152            }
153            if (paint == null) {
154                throw new IllegalArgumentException("Null 'paint' argument.");
155            }
156            this.text = text;
157            this.font = font;
158            this.paint = paint;
159            this.baselineOffset = baselineOffset;
160        }
161    
162        /**
163         * Returns the text.
164         * 
165         * @return The text (possibly <code>null</code>).
166         */
167        public String getText() {
168            return this.text;
169        }
170        
171        /**
172         * Returns the font.
173         * 
174         * @return The font (never <code>null</code>).
175         */
176        public Font getFont() {
177            return this.font;
178        }
179        
180        /**
181         * Returns the text paint.
182         * 
183         * @return The text paint (never <code>null</code>).
184         */
185        public Paint getPaint() {
186            return this.paint;
187        }
188        
189        public float getBaselineOffset() {
190            return this.baselineOffset;   
191        }
192        
193        /**
194         * Draws the text fragment.
195         * 
196         * @param g2  the graphics device.
197         * @param anchorX  the x-coordinate of the anchor point.
198         * @param anchorY  the y-coordinate of the anchor point.
199         * @param anchor  the location of the text that is aligned to the anchor 
200         *                point.
201         * @param rotateX  the x-coordinate of the rotation point.
202         * @param rotateY  the y-coordinate of the rotation point.
203         * @param angle  the angle.
204         */
205        public void draw(final Graphics2D g2, final float anchorX, 
206                         final float anchorY, final TextAnchor anchor,
207                         final float rotateX, final float rotateY, 
208                         final double angle) {
209        
210            g2.setFont(this.font);
211            g2.setPaint(this.paint);
212            TextUtilities.drawRotatedString(
213                this.text, g2, anchorX, anchorY + this.baselineOffset, anchor, 
214                angle, rotateX, rotateY
215            );
216        
217        }
218        
219        /**
220         * Calculates the dimensions of the text fragment.
221         * 
222         * @param g2  the graphics device.
223         * 
224         * @return The width and height of the text.
225         */
226        public Size2D calculateDimensions(final Graphics2D g2) {
227            final FontMetrics fm = g2.getFontMetrics(this.font);
228            final Rectangle2D bounds = TextUtilities.getTextBounds(this.text, g2, 
229                    fm);
230            final Size2D result = new Size2D(bounds.getWidth(), bounds.getHeight());
231            return result;
232        }
233        
234        /**
235         * Calculates the vertical offset between the baseline and the specified 
236         * text anchor.
237         * 
238         * @param g2  the graphics device.
239         * @param anchor  the anchor.
240         * 
241         * @return the offset.
242         */
243        public float calculateBaselineOffset(final Graphics2D g2, 
244                                             final TextAnchor anchor) {
245            float result = 0.0f;
246            final FontMetrics fm = g2.getFontMetrics(this.font);
247            final LineMetrics lm = fm.getLineMetrics("ABCxyz", g2);
248            if (anchor == TextAnchor.TOP_LEFT || anchor == TextAnchor.TOP_CENTER
249                                              || anchor == TextAnchor.TOP_RIGHT) {
250                result = lm.getAscent();
251            }
252            else if (anchor == TextAnchor.BOTTOM_LEFT 
253                    || anchor == TextAnchor.BOTTOM_CENTER
254                    || anchor == TextAnchor.BOTTOM_RIGHT) {
255                result = -lm.getDescent() - lm.getLeading();
256            }
257            return result;                                             
258        }
259        
260        /**
261         * Tests this instance for equality with an arbitrary object.
262         * 
263         * @param obj  the object to test against (<code>null</code> permitted).
264         * 
265         * @return A boolean.
266         */
267        public boolean equals(final Object obj) {
268            if (obj == null) {
269                return false;   
270            }
271            if (obj == this) {
272                return true;   
273            }
274            if (obj instanceof TextFragment) {
275                final TextFragment tf = (TextFragment) obj;
276                if (!this.text.equals(tf.text)) {
277                    return false;   
278                }
279                if (!this.font.equals(tf.font)) {
280                    return false;   
281                }
282                if (!this.paint.equals(tf.paint)) {
283                    return false;   
284                }
285                return true;
286            }
287            return false;
288        }
289    
290        /**
291         * Returns a hash code for this object.
292         * 
293         * @return A hash code.
294         */
295        public int hashCode() {
296            int result;
297            result = (this.text != null ? this.text.hashCode() : 0);
298            result = 29 * result + (this.font != null ? this.font.hashCode() : 0);
299            result = 29 * result + (this.paint != null ? this.paint.hashCode() : 0);
300            return result;
301        }
302    
303        /**
304         * Provides serialization support.
305         *
306         * @param stream  the output stream.
307         *
308         * @throws IOException  if there is an I/O error.
309         */
310        private void writeObject(final ObjectOutputStream stream) 
311            throws IOException {
312            stream.defaultWriteObject();
313            SerialUtilities.writePaint(this.paint, stream);
314        }
315    
316        /**
317         * Provides serialization support.
318         *
319         * @param stream  the input stream.
320         *
321         * @throws IOException  if there is an I/O error.
322         * @throws ClassNotFoundException  if there is a classpath problem.
323         */
324        private void readObject(final ObjectInputStream stream) 
325            throws IOException, ClassNotFoundException {
326            stream.defaultReadObject();
327            this.paint = SerialUtilities.readPaint(stream);
328        }
329       
330    }