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