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