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