    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     * TextBlock.java
    029     * --------------
    030     * (C) Copyright 2003, 2004, by Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * $Id: TextBlock.java,v 1.13 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     * 09-Jan-2004 : Added an extra draw() method for no rotation case (DG);
    042     * 25-Feb-2004 : Added getLines() method (DG);
    043     * 22-Mar-2004 : Added equals() method and implemented Serializable (DG);
    044     * 24-Mar-2004 : Added 'paint' argument to addLine() method (DG);
    045     * 01-Apr-2004 : Changed java.awt.geom.Dimension2D to org.jfree.ui.Size2D
    046     *               because of JDK bug 4976448 which persists on JDK 1.3.1 (DG);
    047     * 04-Oct-2004 : Renamed ShapeUtils --> ShapeUtilities (DG);
    048     *
    049     */
    050
    051    package org.jfree.text;
    052
    053    import java.awt.Font;
    054    import java.awt.Graphics2D;
    055    import java.awt.Paint;
    056    import java.awt.Shape;
    057    import java.awt.geom.Rectangle2D;
    058    import java.io.Serializable;
    059    import java.util.Collections;
    060    import java.util.Iterator;
    061    import java.util.List;
    062
    063    import org.jfree.ui.HorizontalAlignment;
    064    import org.jfree.ui.Size2D;
    065    import org.jfree.ui.TextAnchor;
    066    import org.jfree.util.Log;
    067    import org.jfree.util.LogContext;
    068    import org.jfree.util.ShapeUtilities;
    069
    070    /**
    071     * A list of {@link TextLine} objects that form a block of text.
    072     *
    073     * @see TextUtilities#createTextBlock(String, Font, Paint)
    074     *
    075     * @author David Gilbert
    076     */
    077    public class TextBlock implements Serializable {
    078
    079        /** For serialization. */
    080        private static final long serialVersionUID = -4333175719424385526L;
    081
    082        /** Storage for the lines of text. */
    083        private List lines;
    084
    085        /** The alignment of the lines. */
    086        private HorizontalAlignment lineAlignment;
    087
    088        /** Access to logging facilities. */
    089        protected static final LogContext logger
    090            = Log.createContext(TextBlock.class);
    091
    092        /**
    093         * Creates a new empty text block.
    094         */
    095        public TextBlock() {
    096            this.lines = new java.util.ArrayList();
    097            this.lineAlignment = HorizontalAlignment.CENTER;
    098        }
    099
    100        /**
    101         * Returns the alignment of the lines of text within the block.
    102         *
    103         * @return The alignment (never <code>null</code>).
    104         */
    105        public HorizontalAlignment getLineAlignment() {
    106            return this.lineAlignment;
    107        }
    108
    109        /**
    110         * Sets the alignment of the lines of text within the block.
    111         *
    112         * @param alignment  the alignment (<code>null</code> not permitted).
    113         */
    114        public void setLineAlignment(HorizontalAlignment alignment) {
    115            if (alignment == null) {
    116                throw new IllegalArgumentException("Null 'alignment' argument.");
    117            }
    118            this.lineAlignment = alignment;
    119        }
    120
    121        /**
    122         * Adds a line of text that will be displayed using the specified font.
    123         *
    124         * @param text  the text.
    125         * @param font  the font.
    126         * @param paint  the paint.
    127         */
    128        public void addLine(final String text, final Font font, final Paint paint) {
    129            addLine(new TextLine(text, font, paint));
    130        }
    131
    132        /**
    133         * Adds a {@link TextLine} to the block.
    134         *
    135         * @param line  the line.
    136         */
    137        public void addLine(final TextLine line) {
    138            this.lines.add(line);
    139        }
    140
    141        /**
    142         * Returns the last line in the block.
    143         *
    144         * @return The last line in the block.
    145         */
    146        public TextLine getLastLine() {
    147            TextLine last = null;
    148            final int index = this.lines.size() - 1;
    149            if (index >= 0) {
    150                last = (TextLine) this.lines.get(index);
    151            }
    152            return last;
    153        }
    154
    155        /**
    156         * Returns an unmodifiable list containing the lines for the text block.
    157         *
    158         * @return A list of {@link TextLine} objects.
    159         */
    160        public List getLines() {
    161            return Collections.unmodifiableList(this.lines);
    162        }
    163
    164        /**
    165         * Returns the width and height of the text block.
    166         *
    167         * @param g2  the graphics device.
    168         *
    169         * @return The width and height.
    170         */
    171        public Size2D calculateDimensions(final Graphics2D g2) {
    172            double width = 0.0;
    173            double height = 0.0;
    174            final Iterator iterator = this.lines.iterator();
    175            while (iterator.hasNext()) {
    176                final TextLine line = (TextLine) iterator.next();
    177                final Size2D dimension = line.calculateDimensions(g2);
    178                width = Math.max(width, dimension.getWidth());
    179                height = height + dimension.getHeight();
    180            }
    181            if (logger.isDebugEnabled()) {
    182                logger.debug("width = " + width + ", height = " + height);
    183            }
    184            return new Size2D(width, height);
    185        }
    186
    187        /**
    188         * Returns the bounds of the text block.
    189         *
    190         * @param g2  the graphics device (<code>null</code> not permitted).
    191         * @param anchorX  the x-coordinate for the anchor point.
    192         * @param anchorY  the y-coordinate for the anchor point.
    193         * @param anchor  the text block anchor (<code>null</code> not permitted).
    194         * @param rotateX  the x-coordinate for the rotation point.
    195         * @param rotateY  the y-coordinate for the rotation point.
    196         * @param angle  the rotation angle.
    197         *
    198         * @return The bounds.
    199         */
    200        public Shape calculateBounds(final Graphics2D g2,
    201                                     final float anchorX, final float anchorY,
    202                                     final TextBlockAnchor anchor,
    203                                     final float rotateX, final float rotateY,
    204                                     final double angle) {
    205
    206            final Size2D d = calculateDimensions(g2);
    207            final float[] offsets = calculateOffsets(
    208                anchor, d.getWidth(), d.getHeight()
    209            );
    210            final Rectangle2D bounds = new Rectangle2D.Double(
    211                anchorX + offsets[0], anchorY + offsets[1],
    212                d.getWidth(), d.getHeight()
    213            );
    214            final Shape rotatedBounds = ShapeUtilities.rotateShape(
    215                bounds, angle, rotateX, rotateY
    216            );
    217            return rotatedBounds;
    218
    219        }
    220
    221        /**
    222         * Draws the text block at a specific location.
    223         *
    224         * @param g2  the graphics device.
    225         * @param x  the x-coordinate for the anchor point.
    226         * @param y  the y-coordinate for the anchor point.
    227         * @param anchor  the anchor point.
    228         */
    229        public void draw(final Graphics2D g2, final float x, final float y,
    230                         final TextBlockAnchor anchor) {
    231            draw(g2, x, y, anchor, 0.0f, 0.0f, 0.0);
    232        }
    233
    234        /**
    235         * Draws the text block, aligning it with the specified anchor point and
    236         * rotating it about the specified rotation point.
    237         *
    238         * @param g2  the graphics device.
    239         * @param anchorX  the x-coordinate for the anchor point.
    240         * @param anchorY  the y-coordinate for the anchor point.
    241         * @param anchor  the point on the text block that is aligned to the
    242         *                anchor point.
    243         * @param rotateX  the x-coordinate for the rotation point.
    244         * @param rotateY  the x-coordinate for the rotation point.
    245         * @param angle  the rotation (in radians).
    246         */
    247        public void draw(final Graphics2D g2,
    248                         final float anchorX, final float anchorY,
    249                         final TextBlockAnchor anchor,
    250                         final float rotateX, final float rotateY,
    251                         final double angle) {
    252
    253            final Size2D d = calculateDimensions(g2);
    254            final float[] offsets = calculateOffsets(anchor, d.getWidth(),
    255                    d.getHeight());
    256            final Iterator iterator = this.lines.iterator();
    257            float yCursor = 0.0f;
    258            while (iterator.hasNext()) {
    259                TextLine line = (TextLine) iterator.next();
    260                Size2D dimension = line.calculateDimensions(g2);
    261                float lineOffset = 0.0f;
    262                if (this.lineAlignment == HorizontalAlignment.CENTER) {
    263                    lineOffset = (float) (d.getWidth() - dimension.getWidth())
    264                        / 2.0f;
    265                }
    266                else if (this.lineAlignment == HorizontalAlignment.RIGHT) {
    267                    lineOffset = (float) (d.getWidth() - dimension.getWidth());
    268                }
    269                line.draw(
    270                    g2, anchorX + offsets[0] + lineOffset, anchorY + offsets[1] + yCursor,
    271                    TextAnchor.TOP_LEFT, rotateX, rotateY, angle
    272                );
    273                yCursor = yCursor + (float) dimension.getHeight();
    274            }
    275
    276        }
    277
    278        /**
    279         * Calculates the x and y offsets required to align the text block with the
    280         * specified anchor point.  This assumes that the top left of the text
    281         * block is at (0.0, 0.0).
    282         *
    283         * @param anchor  the anchor position.
    284         * @param width  the width of the text block.
    285         * @param height  the height of the text block.
    286         *
    287         * @return The offsets (float[0] = x offset, float[1] = y offset).
    288         */
    289        private float[] calculateOffsets(final TextBlockAnchor anchor,
    290                                         final double width, final double height) {
    291            final float[] result = new float[2];
    292            float xAdj = 0.0f;
    293            float yAdj = 0.0f;
    294
    295            if (anchor == TextBlockAnchor.TOP_CENTER
    296                    || anchor == TextBlockAnchor.CENTER
    297                    || anchor == TextBlockAnchor.BOTTOM_CENTER) {
    298
    299                xAdj = (float) -width / 2.0f;
    300
    301            }
    302            else if (anchor == TextBlockAnchor.TOP_RIGHT
    303                    || anchor == TextBlockAnchor.CENTER_RIGHT
    304                    || anchor == TextBlockAnchor.BOTTOM_RIGHT) {
    305
    306                xAdj = (float) -width;
    307
    308            }
    309
    310            if (anchor == TextBlockAnchor.TOP_LEFT
    311                    || anchor == TextBlockAnchor.TOP_CENTER
    312                    || anchor == TextBlockAnchor.TOP_RIGHT) {
    313
    314                yAdj = 0.0f;
    315
    316            }
    317            else if (anchor == TextBlockAnchor.CENTER_LEFT
    318                    || anchor == TextBlockAnchor.CENTER
    319                    || anchor == TextBlockAnchor.CENTER_RIGHT) {
    320
    321                yAdj = (float) -height / 2.0f;
    322
    323            }
    324            else if (anchor == TextBlockAnchor.BOTTOM_LEFT
    325                    || anchor == TextBlockAnchor.BOTTOM_CENTER
    326                    || anchor == TextBlockAnchor.BOTTOM_RIGHT) {
    327
    328                yAdj = (float) -height;
    329
    330            }
    331            result[0] = xAdj;
    332            result[1] = yAdj;
    333            return result;
    334        }
    335
    336        /**
    337         * Tests this object for equality with an arbitrary object.
    338         *
    339         * @param obj  the object to test against (<code>null</code> permitted).
    340         *
    341         * @return A boolean.
    342         */
    343        public boolean equals(final Object obj) {
    344            if (obj == this) {
    345                return true;
    346            }
    347            if (obj instanceof TextBlock) {
    348                final TextBlock block = (TextBlock) obj;
    349                return this.lines.equals(block.lines);
    350            }
    351            return false;
    352        }
    353
    354        /**
    355         * Returns a hash code for this object.
    356         *
    357         * @return A hash code.
    358         */
    359        public int hashCode() {
    360            return (this.lines != null ? this.lines.hashCode() : 0);
    361        }
    362    }