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