    001    /* ===========================================================
    002     * JFreeChart : a free chart 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/jfreechart/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     * TextTitle.java
    029     * --------------
    030     * (C) Copyright 2000-2005, by David Berry and Contributors.
    031     *
    032     * Original Author:  David Berry;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *                   Nicolas Brodu;
    035     *
    036     * $Id: TextTitle.java,v 1.16.2.5 2005/10/25 20:58:34 mungady Exp $
    037     *
    038     * Changes (from 18-Sep-2001)
    039     * --------------------------
    040     * 18-Sep-2001 : Added standard header (DG);
    041     * 07-Nov-2001 : Separated the JCommon Class Library classes, JFreeChart now
    042     *               requires jcommon.jar (DG);
    043     * 09-Jan-2002 : Updated Javadoc comments (DG);
    044     * 07-Feb-2002 : Changed Insets --> Spacer in AbstractTitle.java (DG);
    045     * 06-Mar-2002 : Updated import statements (DG);
    046     * 25-Jun-2002 : Removed redundant imports (DG);
    047     * 18-Sep-2002 : Fixed errors reported by Checkstyle (DG);
    048     * 28-Oct-2002 : Small modifications while changing JFreeChart class (DG);
    049     * 13-Mar-2003 : Changed width used for relative spacing to fix bug 703050 (DG);
    050     * 26-Mar-2003 : Implemented Serializable (DG);
    051     * 15-Jul-2003 : Fixed null pointer exception (DG);
    052     * 11-Sep-2003 : Implemented Cloneable (NB)
    053     * 22-Sep-2003 : Added checks for null values and throw nullpointer
    054     *               exceptions (TM);
    055     *               Background paint was not serialized.
    056     * 07-Oct-2003 : Added fix for exception caused by empty string in title (DG);
    057     * 29-Oct-2003 : Added workaround for text alignment in PDF output (DG);
    058     * 03-Feb-2004 : Fixed bug in getPreferredWidth() method (DG);
    059     * 17-Feb-2004 : Added clone() method and fixed bug in equals() method (DG);
    060     * 01-Apr-2004 : Changed java.awt.geom.Dimension2D to org.jfree.ui.Size2D
    061     *               because of JDK bug 4976448 which persists on JDK 1.3.1.  Also
    062     *               fixed bug in getPreferredHeight() method (DG);
    063     * 29-Apr-2004 : Fixed bug in getPreferredWidth() method - see bug id
    064     *               944173 (DG);
    065     * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
    066     *               release (DG);
    067     * 08-Feb-2005 : Updated for changes in RectangleConstraint class (DG);
    068     * 11-Feb-2005 : Implemented PublicCloneable (DG);
    069     * 20-Apr-2005 : Added support for tooltips (DG);
    070     * 26-Apr-2005 : Removed LOGGER (DG);
    071     * 06-Jun-2005 : Modified equals() to handle GradientPaint (DG);
    072     * 06-Jul-2005 : Added flag to control whether or not the title expands to
    073     *               fit the available space (DG);
    074     * 07-Oct-2005 : Added textAlignment attribute (DG);
    075     *
    076     */
    077
    078    package org.jfree.chart.title;
    079
    080    import java.awt.Color;
    081    import java.awt.Font;
    082    import java.awt.Graphics2D;
    083    import java.awt.Paint;
    084    import java.awt.geom.Rectangle2D;
    085    import java.io.IOException;
    086    import java.io.ObjectInputStream;
    087    import java.io.ObjectOutputStream;
    088    import java.io.Serializable;
    089
    090    import org.jfree.chart.block.BlockResult;
    091    import org.jfree.chart.block.EntityBlockParams;
    092    import org.jfree.chart.block.LengthConstraintType;
    093    import org.jfree.chart.block.RectangleConstraint;
    094    import org.jfree.chart.entity.ChartEntity;
    095    import org.jfree.chart.entity.EntityCollection;
    096    import org.jfree.chart.entity.StandardEntityCollection;
    097    import org.jfree.chart.event.TitleChangeEvent;
    098    import org.jfree.data.Range;
    099    import org.jfree.io.SerialUtilities;
    100    import org.jfree.text.G2TextMeasurer;
    101    import org.jfree.text.TextBlock;
    102    import org.jfree.text.TextBlockAnchor;
    103    import org.jfree.text.TextUtilities;
    104    import org.jfree.ui.HorizontalAlignment;
    105    import org.jfree.ui.RectangleEdge;
    106    import org.jfree.ui.RectangleInsets;
    107    import org.jfree.ui.Size2D;
    108    import org.jfree.ui.VerticalAlignment;
    109    import org.jfree.util.ObjectUtilities;
    110    import org.jfree.util.PaintUtilities;
    111    import org.jfree.util.PublicCloneable;
    112
    113    /**
    114     * A chart title that displays a text string with automatic wrapping as
    115     * required.
    116     */
    117    public class TextTitle extends Title
    118                           implements Serializable, Cloneable, PublicCloneable {
    119
    120        /** For serialization. */
    121        private static final long serialVersionUID = 8372008692127477443L;
    122
    123        /** The default font. */
    124        public static final Font DEFAULT_FONT
    125            = new Font("SansSerif", Font.BOLD, 12);
    126
    127        /** The default text color. */
    128        public static final Paint DEFAULT_TEXT_PAINT = Color.black;
    129
    130        /** The title text. */
    131        private String text;
    132
    133        /** The font used to display the title. */
    134        private Font font;
    135
    136        /** The text alignment. */
    137        private HorizontalAlignment textAlignment;
    138
    139        /** The paint used to display the title text. */
    140        private transient Paint paint;
    141
    142        /** The background paint. */
    143        private transient Paint backgroundPaint;
    144
    145        /** The tool tip text (can be <code>null</code>). */
    146        private String toolTipText;
    147
    148        /** The URL text (can be <code>null</code>). */
    149        private String urlText;
    150
    151        /** The content. */
    152        private TextBlock content;
    153
    154        /**
    155         * A flag that controls whether the title expands to fit the available
    156         * space..
    157         */
    158        private boolean expandToFitSpace = false;
    159
    160        /**
    161         * Creates a new title, using default attributes where necessary.
    162         */
    163        public TextTitle() {
    164            this("");
    165        }
    166
    167        /**
    168         * Creates a new title, using default attributes where necessary.
    169         *
    170         * @param text  the title text (<code>null</code> not permitted).
    171         */
    172        public TextTitle(String text) {
    173            this(
    174                text,
    175                TextTitle.DEFAULT_FONT,
    176                TextTitle.DEFAULT_TEXT_PAINT,
    177                Title.DEFAULT_POSITION,
    178                Title.DEFAULT_HORIZONTAL_ALIGNMENT,
    179                Title.DEFAULT_VERTICAL_ALIGNMENT,
    180                Title.DEFAULT_PADDING
    181            );
    182        }
    183
    184        /**
    185         * Creates a new title, using default attributes where necessary.
    186         *
    187         * @param text  the title text (<code>null</code> not permitted).
    188         * @param font  the title font (<code>null</code> not permitted).
    189         */
    190        public TextTitle(String text, Font font) {
    191
    192            this(
    193                text, font,
    194                TextTitle.DEFAULT_TEXT_PAINT,
    195                Title.DEFAULT_POSITION,
    196                Title.DEFAULT_HORIZONTAL_ALIGNMENT,
    197                Title.DEFAULT_VERTICAL_ALIGNMENT,
    198                Title.DEFAULT_PADDING
    199            );
    200
    201        }
    202
    203        /**
    204         * Creates a new title.
    205         *
    206         * @param text  the text for the title (<code>null</code> not permitted).
    207         * @param font  the title font (<code>null</code> not permitted).
    208         * @param paint  the title paint (<code>null</code> not permitted).
    209         * @param position  the title position (<code>null</code> not permitted).
    210         * @param horizontalAlignment  the horizontal alignment (<code>null</code>
    211         *                             not permitted).
    212         * @param verticalAlignment  the vertical alignment (<code>null</code> not
    213         *                           permitted).
    214         * @param padding  the space to leave around the outside of the title.
    215         */
    216        public TextTitle(String text,
    217                         Font font,
    218                         Paint paint,
    219                         RectangleEdge position,
    220                         HorizontalAlignment horizontalAlignment,
    221                         VerticalAlignment verticalAlignment,
    222                         RectangleInsets padding) {
    223
    224            super(position, horizontalAlignment, verticalAlignment, padding);
    225
    226            if (text == null) {
    227                throw new NullPointerException("Null 'text' argument.");
    228            }
    229            if (font == null) {
    230                throw new NullPointerException("Null 'font' argument.");
    231            }
    232            if (paint == null) {
    233                throw new NullPointerException("Null 'paint' argument.");
    234            }
    235            this.text = text;
    236            this.font = font;
    237            this.paint = paint;
    238            // the textAlignment and the horizontalAlignment are separate things,
    239            // but it makes sense for the default textAlignment to match the
    240            // title's horizontal alignment...
    241            this.textAlignment = horizontalAlignment;
    242            this.backgroundPaint = null;
    243            this.content = null;
    244            this.toolTipText = null;
    245            this.urlText = null;
    246
    247        }
    248
    249        /**
    250         * Returns the title text.
    251         *
    252         * @return The text (never <code>null</code>).
    253         */
    254        public String getText() {
    255            return this.text;
    256        }
    257
    258        /**
    259         * Sets the title to the specified text and sends a
    260         * {@link TitleChangeEvent} to all registered listeners.
    261         *
    262         * @param text  the text (<code>null</code> not permitted).
    263         */
    264        public void setText(String text) {
    265            if (text == null) {
    266                throw new NullPointerException("Null 'text' argument.");
    267            }
    268            if (!this.text.equals(text)) {
    269                this.text = text;
    270                notifyListeners(new TitleChangeEvent(this));
    271            }
    272        }
    273
    274        /**
    275         * Returns the text alignment.  This controls how the text is aligned
    276         * within the title's bounds, whereas the title's horizontal alignment
    277         * controls how the title's bounding rectangle is aligned within the
    278         * drawing space.
    279         *
    280         * @return The text alignment.
    281         */
    282        public HorizontalAlignment getTextAlignment() {
    283            return this.textAlignment;
    284        }
    285
    286        /**
    287         * Sets the text alignment.
    288         *
    289         * @param alignment  the alignment (<code>null</code> not permitted).
    290         */
    291        public void setTextAlignment(HorizontalAlignment alignment) {
    292            if (alignment == null) {
    293                throw new IllegalArgumentException("Null 'alignment' argument.");
    294            }
    295            this.textAlignment = alignment;
    296            notifyListeners(new TitleChangeEvent(this));
    297        }
    298
    299        /**
    300         * Returns the font used to display the title string.
    301         *
    302         * @return The font (never <code>null</code>).
    303         */
    304        public Font getFont() {
    305            return this.font;
    306        }
    307
    308        /**
    309         * Sets the font used to display the title string.  Registered listeners
    310         * are notified that the title has been modified.
    311         *
    312         * @param font  the new font (<code>null</code> not permitted).
    313         */
    314        public void setFont(Font font) {
    315            if (font == null) {
    316                throw new IllegalArgumentException("Null 'font' argument.");
    317            }
    318            if (!this.font.equals(font)) {
    319                this.font = font;
    320                notifyListeners(new TitleChangeEvent(this));
    321            }
    322        }
    323
    324        /**
    325         * Returns the paint used to display the title string.
    326         *
    327         * @return The paint (never <code>null</code>).
    328         */
    329        public Paint getPaint() {
    330            return this.paint;
    331        }
    332
    333        /**
    334         * Sets the paint used to display the title string.  Registered listeners
    335         * are notified that the title has been modified.
    336         *
    337         * @param paint  the new paint (<code>null</code> not permitted).
    338         */
    339        public void setPaint(Paint paint) {
    340            if (paint == null) {
    341                throw new IllegalArgumentException("Null 'paint' argument.");
    342            }
    343            if (!this.paint.equals(paint)) {
    344                this.paint = paint;
    345                notifyListeners(new TitleChangeEvent(this));
    346            }
    347        }
    348
    349        /**
    350         * Returns the background paint.
    351         *
    352         * @return The paint (possibly <code>null</code>).
    353         */
    354        public Paint getBackgroundPaint() {
    355            return this.backgroundPaint;
    356        }
    357
    358        /**
    359         * Sets the background paint and sends a {@link TitleChangeEvent} to all
    360         * registered listeners.  If you set this attribute to <code>null</code>,
    361         * no background is painted (which makes the title background transparent).
    362         *
    363         * @param paint  the background paint (<code>null</code> permitted).
    364         */
    365        public void setBackgroundPaint(Paint paint) {
    366            this.backgroundPaint = paint;
    367            notifyListeners(new TitleChangeEvent(this));
    368        }
    369
    370        /**
    371         * Returns the tool tip text.
    372         *
    373         * @return The tool tip text (possibly <code>null</code>).
    374         */
    375        public String getToolTipText() {
    376            return this.toolTipText;
    377        }
    378
    379        /**
    380         * Sets the tool tip text to the specified text and sends a
    381         * {@link TitleChangeEvent} to all registered listeners.
    382         *
    383         * @param text  the text (<code>null</code> permitted).
    384         */
    385        public void setToolTipText(String text) {
    386            this.toolTipText = text;
    387            notifyListeners(new TitleChangeEvent(this));
    388        }
    389
    390        /**
    391         * Returns the URL text.
    392         *
    393         * @return The URL text (possibly <code>null</code>).
    394         */
    395        public String getURLText() {
    396            return this.urlText;
    397        }
    398
    399        /**
    400         * Sets the URL text to the specified text and sends a
    401         * {@link TitleChangeEvent} to all registered listeners.
    402         *
    403         * @param text  the text (<code>null</code> permitted).
    404         */
    405        public void setURLText(String text) {
    406            this.urlText = text;
    407            notifyListeners(new TitleChangeEvent(this));
    408        }
    409
    410        /**
    411         * Returns the flag that controls whether or not the title expands to fit
    412         * the available space.
    413         *
    414         * @return The flag.
    415         */
    416        public boolean getExpandToFitSpace() {
    417            return this.expandToFitSpace;
    418        }
    419
    420        /**
    421         * Sets the flag that controls whether the title expands to fit the
    422         * available space, and sends a {@link TitleChangeEvent} to all registered
    423         * listeners.
    424         *
    425         * @param expand  the flag.
    426         */
    427        public void setExpandToFitSpace(boolean expand) {
    428            this.expandToFitSpace = expand;
    429            notifyListeners(new TitleChangeEvent(this));
    430        }
    431
    432        /**
    433         * Arranges the contents of the block, within the given constraints, and
    434         * returns the block size.
    435         *
    436         * @param g2  the graphics device.
    437         * @param constraint  the constraint (<code>null</code> not permitted).
    438         *
    439         * @return The block size (in Java2D units, never <code>null</code>).
    440         */
    441        public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
    442            RectangleConstraint cc = toContentConstraint(constraint);
    443            LengthConstraintType w = cc.getWidthConstraintType();
    444            LengthConstraintType h = cc.getHeightConstraintType();
    445            Size2D contentSize = null;
    446            if (w == LengthConstraintType.NONE) {
    447                if (h == LengthConstraintType.NONE) {
    448                    throw new RuntimeException("Not yet implemented.");
    449                }
    450                else if (h == LengthConstraintType.RANGE) {
    451                    throw new RuntimeException("Not yet implemented.");
    452                }
    453                else if (h == LengthConstraintType.FIXED) {
    454                    throw new RuntimeException("Not yet implemented.");
    455                }
    456            }
    457            else if (w == LengthConstraintType.RANGE) {
    458                if (h == LengthConstraintType.NONE) {
    459                    throw new RuntimeException("Not yet implemented.");
    460                }
    461                else if (h == LengthConstraintType.RANGE) {
    462                    contentSize = arrangeRR(g2, cc.getWidthRange(),
    463                            cc.getHeightRange());
    464                }
    465                else if (h == LengthConstraintType.FIXED) {
    466                    throw new RuntimeException("Not yet implemented.");
    467                }
    468            }
    469            else if (w == LengthConstraintType.FIXED) {
    470                if (h == LengthConstraintType.NONE) {
    471                    throw new RuntimeException("Not yet implemented.");
    472                }
    473                else if (h == LengthConstraintType.RANGE) {
    474                    throw new RuntimeException("Not yet implemented.");
    475                }
    476                else if (h == LengthConstraintType.FIXED) {
    477                    throw new RuntimeException("Not yet implemented.");
    478                }
    479            }
    480            return new Size2D(
    481                calculateTotalWidth(contentSize.getWidth()),
    482                calculateTotalHeight(contentSize.getHeight())
    483            );
    484        }
    485
    486        /**
    487         * Returns the content size for the title.
    488         *
    489         * @param g2  the graphics device.
    490         * @param widthRange  the width range.
    491         * @param heightRange  the height range.
    492         *
    493         * @return The content size.
    494         */
    495        protected Size2D arrangeRR(Graphics2D g2, Range widthRange,
    496                Range heightRange) {
    497            float maxWidth = (float) widthRange.getUpperBound();
    498            g2.setFont(this.font);
    499            this.content = TextUtilities.createTextBlock(
    500                this.text, this.font, this.paint, maxWidth, new G2TextMeasurer(g2));
    501            this.content.setLineAlignment(this.textAlignment);
    502            Size2D contentSize = this.content.calculateDimensions(g2);
    503            if (this.expandToFitSpace) {
    504                return new Size2D(maxWidth, contentSize.getHeight());
    505            }
    506            else {
    507                return contentSize;
    508            }
    509        }
    510
    511        /**
    512         * Draws the title on a Java 2D graphics device (such as the screen or a
    513         * printer).
    514         *
    515         * @param g2  the graphics device.
    516         * @param area  the area allocated for the title.
    517         */
    518        public void draw(Graphics2D g2, Rectangle2D area) {
    519            draw(g2, area, null);
    520        }
    521
    522        /**
    523         * Draws the block within the specified area.
    524         *
    525         * @param g2  the graphics device.
    526         * @param area  the area.
    527         * @param params  if this is an instance of {@link EntityBlockParams} it
    528         *                is used to determine whether or not an
    529         *                {@link EntityCollection} is returned by this method.
    530         *
    531         * @return An {@link EntityCollection} containing a chart entity for the
    532         *         title, or <code>null</code>.
    533         */
    534        public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
    535            if (this.content == null) {
    536                return null;
    537            }
    538            area = trimMargin(area);
    539            drawBorder(g2, area);
    540            if (this.text.equals("")) {
    541                return null;
    542            }
    543            ChartEntity entity = null;
    544            if (params instanceof EntityBlockParams) {
    545                EntityBlockParams p = (EntityBlockParams) params;
    546                if (p.getGenerateEntities()) {
    547                    entity = new ChartEntity(area, this.toolTipText, this.urlText);
    548                }
    549            }
    550            area = trimBorder(area);
    551            if (this.backgroundPaint != null) {
    552                g2.setPaint(this.backgroundPaint);
    553                g2.fill(area);
    554            }
    555            area = trimPadding(area);
    556            RectangleEdge position = getPosition();
    557            if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
    558                drawHorizontal(g2, area);
    559            }
    560            else if (position == RectangleEdge.LEFT
    561                     || position == RectangleEdge.RIGHT) {
    562                drawVertical(g2, area);
    563            }
    564            BlockResult result = new BlockResult();
    565            if (entity != null) {
    566                StandardEntityCollection sec = new StandardEntityCollection();
    567                sec.add(entity);
    568                result.setEntityCollection(sec);
    569            }
    570            return result;
    571        }
    572
    573        /**
    574         * Draws a the title horizontally within the specified area.  This method
    575         * will be called from the {@link #draw(Graphics2D, Rectangle2D) draw}
    576         * method.
    577         *
    578         * @param g2  the graphics device.
    579         * @param area  the area for the title.
    580         */
    581        protected void drawHorizontal(Graphics2D g2, Rectangle2D area) {
    582            Rectangle2D titleArea = (Rectangle2D) area.clone();
    583            g2.setFont(this.font);
    584            g2.setPaint(this.paint);
    585            TextBlockAnchor anchor = null;
    586            float x = 0.0f;
    587            HorizontalAlignment horizontalAlignment = getHorizontalAlignment();
    588            if (horizontalAlignment == HorizontalAlignment.LEFT) {
    589                x = (float) titleArea.getX();
    590                anchor = TextBlockAnchor.TOP_LEFT;
    591            }
    592            else if (horizontalAlignment == HorizontalAlignment.RIGHT) {
    593                x = (float) titleArea.getMaxX();
    594                anchor = TextBlockAnchor.TOP_RIGHT;
    595            }
    596            else if (horizontalAlignment == HorizontalAlignment.CENTER) {
    597                x = (float) titleArea.getCenterX();
    598                anchor = TextBlockAnchor.TOP_CENTER;
    599            }
    600            float y = 0.0f;
    601            RectangleEdge position = getPosition();
    602            if (position == RectangleEdge.TOP) {
    603                y = (float) titleArea.getY();
    604            }
    605            else if (position == RectangleEdge.BOTTOM) {
    606                y = (float) titleArea.getMaxY();
    607                if (horizontalAlignment == HorizontalAlignment.LEFT) {
    608                    anchor = TextBlockAnchor.BOTTOM_LEFT;
    609                }
    610                else if (horizontalAlignment == HorizontalAlignment.CENTER) {
    611                    anchor = TextBlockAnchor.BOTTOM_CENTER;
    612                }
    613                else if (horizontalAlignment == HorizontalAlignment.RIGHT) {
    614                    anchor = TextBlockAnchor.BOTTOM_RIGHT;
    615                }
    616            }
    617            this.content.draw(g2, x, y, anchor);
    618        }
    619
    620        /**
    621         * Draws a the title vertically within the specified area.  This method
    622         * will be called from the {@link #draw(Graphics2D, Rectangle2D) draw}
    623         * method.
    624         *
    625         * @param g2  the graphics device.
    626         * @param area  the area for the title.
    627         */
    628        protected void drawVertical(Graphics2D g2, Rectangle2D area) {
    629            Rectangle2D titleArea = (Rectangle2D) area.clone();
    630            g2.setFont(this.font);
    631            g2.setPaint(this.paint);
    632            TextBlockAnchor anchor = null;
    633            float y = 0.0f;
    634            VerticalAlignment verticalAlignment = getVerticalAlignment();
    635            if (verticalAlignment == VerticalAlignment.TOP) {
    636                y = (float) titleArea.getY();
    637                anchor = TextBlockAnchor.TOP_RIGHT;
    638            }
    639            else if (verticalAlignment == VerticalAlignment.BOTTOM) {
    640                y = (float) titleArea.getMaxY();
    641                anchor = TextBlockAnchor.TOP_LEFT;
    642            }
    643            else if (verticalAlignment == VerticalAlignment.CENTER) {
    644                y = (float) titleArea.getCenterY();
    645                anchor = TextBlockAnchor.TOP_CENTER;
    646            }
    647            float x = 0.0f;
    648            RectangleEdge position = getPosition();
    649            if (position == RectangleEdge.LEFT) {
    650                x = (float) titleArea.getX();
    651            }
    652            else if (position == RectangleEdge.RIGHT) {
    653                x = (float) titleArea.getMaxX();
    654                if (verticalAlignment == VerticalAlignment.TOP) {
    655                    anchor = TextBlockAnchor.BOTTOM_RIGHT;
    656                }
    657                else if (verticalAlignment == VerticalAlignment.CENTER) {
    658                    anchor = TextBlockAnchor.BOTTOM_CENTER;
    659                }
    660                else if (verticalAlignment == VerticalAlignment.BOTTOM) {
    661                    anchor = TextBlockAnchor.BOTTOM_LEFT;
    662                }
    663            }
    664            this.content.draw(g2, x, y, anchor, x, y, -Math.PI / 2.0);
    665        }
    666
    667        /**
    668         * Tests this title for equality with another object.
    669         *
    670         * @param obj  the object (<code>null</code> permitted).
    671         *
    672         * @return <code>true</code> or <code>false</code>.
    673         */
    674        public boolean equals(Object obj) {
    675            if (obj == this) {
    676                return true;
    677            }
    678            if (!(obj instanceof TextTitle)) {
    679                return false;
    680            }
    681            if (!super.equals(obj)) {
    682                return false;
    683            }
    684            TextTitle that = (TextTitle) obj;
    685            if (!ObjectUtilities.equal(this.text, that.text)) {
    686                return false;
    687            }
    688            if (!ObjectUtilities.equal(this.font, that.font)) {
    689                return false;
    690            }
    691            if (!PaintUtilities.equal(this.paint, that.paint)) {
    692                return false;
    693            }
    694            if (this.textAlignment != that.textAlignment) {
    695                return false;
    696            }
    697            if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
    698                return false;
    699            }
    700            return true;
    701        }
    702
    703        /**
    704         * Returns a hash code.
    705         *
    706         * @return A hash code.
    707         */
    708        public int hashCode() {
    709            int result = super.hashCode();
    710            result = 29 * result + (this.text != null ? this.text.hashCode() : 0);
    711            result = 29 * result + (this.font != null ? this.font.hashCode() : 0);
    712            result = 29 * result + (this.paint != null ? this.paint.hashCode() : 0);
    713            result = 29 * result + (
    714                this.backgroundPaint != null ? this.backgroundPaint.hashCode() : 0
    715            );
    716            return result;
    717        }
    718
    719        /**
    720         * Returns a clone of this object.
    721         *
    722         * @return A clone.
    723         *
    724         * @throws CloneNotSupportedException never.
    725         */
    726        public Object clone() throws CloneNotSupportedException {
    727            return super.clone();
    728        }
    729
    730        /**
    731         * Provides serialization support.
    732         *
    733         * @param stream  the output stream.
    734         *
    735         * @throws IOException  if there is an I/O error.
    736         */
    737        private void writeObject(ObjectOutputStream stream) throws IOException {
    738            stream.defaultWriteObject();
    739            SerialUtilities.writePaint(this.paint, stream);
    740            SerialUtilities.writePaint(this.backgroundPaint, stream);
    741        }
    742
    743        /**
    744         * Provides serialization support.
    745         *
    746         * @param stream  the input stream.
    747         *
    748         * @throws IOException  if there is an I/O error.
    749         * @throws ClassNotFoundException  if there is a classpath problem.
    750         */
    751        private void readObject(ObjectInputStream stream)
    752            throws IOException, ClassNotFoundException
    753        {
    754            stream.defaultReadObject();
    755            this.paint = SerialUtilities.readPaint(stream);
    756            this.backgroundPaint = SerialUtilities.readPaint(stream);
    757        }
    758
    759    }
    760