    001    /* ========================================================================
    002     * JCommon : a free general purpose class library for the Java(tm) platform
    003     * ========================================================================
    004     *
    005     * (C) Copyright 2000-2006, 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     * TextUtilities.java
    029     * ------------------
    030     * (C) Copyright 2004-2006, by Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * $Id: TextUtilities.java,v 1.23 2008/03/28 17:49:28 taqua Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 07-Jan-2004 : Version 1 (DG);
    040     * 24-Mar-2004 : Added 'paint' argument to createTextBlock() method (DG);
    041     * 07-Apr-2004 : Added getTextBounds() method and useFontMetricsGetStringBounds
    042     *               flag (DG);
    043     * 08-Apr-2004 : Changed word break iterator to line break iterator in the
    044     *               createTextBlock() method - see bug report 926074 (DG);
    045     * 03-Sep-2004 : Updated createTextBlock() method to add ellipses when limit
    046     *               is reached (DG);
    047     * 30-Sep-2004 : Modified bounds returned by drawAlignedString() method (DG);
    048     * 10-Nov-2004 : Added new createTextBlock() method that works with
    049     *               newlines (DG);
    050     * 19-Apr-2005 : Changed default value of useFontMetricsGetStringBounds (DG);
    051     * 17-May-2005 : createTextBlock() now recognises '\n' (DG);
    052     * 27-Jun-2005 : Added code to getTextBounds() method to work around Sun's bug
    053     *               parade item 6183356 (DG);
    054     * 06-Jan-2006 : Reformatted (DG);
    055     *
    056     */
    057
    058    package org.jfree.text;
    059
    060    import java.awt.Font;
    061    import java.awt.FontMetrics;
    062    import java.awt.Graphics2D;
    063    import java.awt.Paint;
    064    import java.awt.Shape;
    065    import java.awt.font.FontRenderContext;
    066    import java.awt.font.LineMetrics;
    067    import java.awt.font.TextLayout;
    068    import java.awt.geom.AffineTransform;
    069    import java.awt.geom.Rectangle2D;
    070    import java.text.BreakIterator;
    071
    072    import org.jfree.base.BaseBoot;
    073    import org.jfree.ui.TextAnchor;
    074    import org.jfree.util.Log;
    075    import org.jfree.util.LogContext;
    076    import org.jfree.util.ObjectUtilities;
    077
    078    /**
    079     * Some utility methods for working with text.
    080     *
    081     * @author David Gilbert
    082     */
    083    public class TextUtilities {
    084
    085        /** Access to logging facilities. */
    086        protected static final LogContext logger = Log.createContext(
    087                TextUtilities.class);
    088
    089        /**
    090         * A flag that controls whether or not the rotated string workaround is
    091         * used.
    092         */
    093        private static boolean useDrawRotatedStringWorkaround;
    094
    095        /**
    096         * A flag that controls whether the FontMetrics.getStringBounds() method
    097         * is used or a workaround is applied.
    098         */
    099        private static boolean useFontMetricsGetStringBounds;
    100
    101        static {
    102          try
    103          {
    104            final boolean isJava14 = ObjectUtilities.isJDK14();
    105
    106            final String configRotatedStringWorkaround =
    107                  BaseBoot.getInstance().getGlobalConfig().getConfigProperty(
    108                          "org.jfree.text.UseDrawRotatedStringWorkaround", "auto");
    109            if (configRotatedStringWorkaround.equals("auto")) {
    110               useDrawRotatedStringWorkaround = (isJava14 == false);
    111            }
    112            else {
    113                useDrawRotatedStringWorkaround
    114                        = configRotatedStringWorkaround.equals("true");
    115            }
    116
    117            final String configFontMetricsStringBounds
    118                    = BaseBoot.getInstance().getGlobalConfig().getConfigProperty(
    119                            "org.jfree.text.UseFontMetricsGetStringBounds", "auto");
    120            if (configFontMetricsStringBounds.equals("auto")) {
    121                useFontMetricsGetStringBounds = (isJava14 == true);
    122            }
    123            else {
    124                useFontMetricsGetStringBounds
    125                        = configFontMetricsStringBounds.equals("true");
    126            }
    127          }
    128          catch (Exception e)
    129          {
    130            // ignore everything.
    131            useDrawRotatedStringWorkaround = true;
    132            useFontMetricsGetStringBounds = true;
    133          }
    134        }
    135
    136        /**
    137         * Private constructor prevents object creation.
    138         */
    139        private TextUtilities() {
    140        }
    141
    142        /**
    143         * Creates a {@link TextBlock} from a <code>String</code>.  Line breaks
    144         * are added where the <code>String</code> contains '\n' characters.
    145         *
    146         * @param text  the text.
    147         * @param font  the font.
    148         * @param paint  the paint.
    149         *
    150         * @return A text block.
    151         */
    152        public static TextBlock createTextBlock(final String text, final Font font,
    153                                                final Paint paint) {
    154            if (text == null) {
    155                throw new IllegalArgumentException("Null 'text' argument.");
    156            }
    157            final TextBlock result = new TextBlock();
    158            String input = text;
    159            boolean moreInputToProcess = (text.length() > 0);
    160            final int start = 0;
    161            while (moreInputToProcess) {
    162                final int index = input.indexOf("\n");
    163                if (index > start) {
    164                    final String line = input.substring(start, index);
    165                    if (index < input.length() - 1) {
    166                        result.addLine(line, font, paint);
    167                        input = input.substring(index + 1);
    168                    }
    169                    else {
    170                        moreInputToProcess = false;
    171                    }
    172                }
    173                else if (index == start) {
    174                    if (index < input.length() - 1) {
    175                        input = input.substring(index + 1);
    176                    }
    177                    else {
    178                        moreInputToProcess = false;
    179                    }
    180                }
    181                else {
    182                    result.addLine(input, font, paint);
    183                    moreInputToProcess = false;
    184                }
    185            }
    186            return result;
    187        }
    188
    189        /**
    190         * Creates a new text block from the given string, breaking the
    191         * text into lines so that the <code>maxWidth</code> value is
    192         * respected.
    193         *
    194         * @param text  the text.
    195         * @param font  the font.
    196         * @param paint  the paint.
    197         * @param maxWidth  the maximum width for each line.
    198         * @param measurer  the text measurer.
    199         *
    200         * @return A text block.
    201         */
    202        public static TextBlock createTextBlock(final String text, final Font font,
    203                final Paint paint, final float maxWidth,
    204                final TextMeasurer measurer) {
    205
    206            return createTextBlock(text, font, paint, maxWidth, Integer.MAX_VALUE,
    207                    measurer);
    208        }
    209
    210        /**
    211         * Creates a new text block from the given string, breaking the
    212         * text into lines so that the <code>maxWidth</code> value is
    213         * respected.
    214         *
    215         * @param text  the text.
    216         * @param font  the font.
    217         * @param paint  the paint.
    218         * @param maxWidth  the maximum width for each line.
    219         * @param maxLines  the maximum number of lines.
    220         * @param measurer  the text measurer.
    221         *
    222         * @return A text block.
    223         */
    224        public static TextBlock createTextBlock(final String text, final Font font,
    225                final Paint paint, final float maxWidth, final int maxLines,
    226                final TextMeasurer measurer) {
    227
    228            final TextBlock result = new TextBlock();
    229            final BreakIterator iterator = BreakIterator.getLineInstance();
    230            iterator.setText(text);
    231            int current = 0;
    232            int lines = 0;
    233            final int length = text.length();
    234            while (current < length && lines < maxLines) {
    235                final int next = nextLineBreak(text, current, maxWidth, iterator,
    236                        measurer);
    237                if (next == BreakIterator.DONE) {
    238                    result.addLine(text.substring(current), font, paint);
    239                    return result;
    240                }
    241                result.addLine(text.substring(current, next), font, paint);
    242                lines++;
    243                current = next;
    244                while (current < text.length()&& text.charAt(current) == '\n') {
    245                    current++;
    246                }
    247            }
    248            if (current < length) {
    249                final TextLine lastLine = result.getLastLine();
    250                final TextFragment lastFragment = lastLine.getLastTextFragment();
    251                final String oldStr = lastFragment.getText();
    252                String newStr = "...";
    253                if (oldStr.length() > 3) {
    254                    newStr = oldStr.substring(0, oldStr.length() - 3) + "...";
    255                }
    256
    257                lastLine.removeFragment(lastFragment);
    258                final TextFragment newFragment = new TextFragment(newStr,
    259                        lastFragment.getFont(), lastFragment.getPaint());
    260                lastLine.addFragment(newFragment);
    261            }
    262            return result;
    263        }
    264
    265        /**
    266         * Returns the character index of the next line break.
    267         *
    268         * @param text  the text.
    269         * @param start  the start index.
    270         * @param width  the target display width.
    271         * @param iterator  the word break iterator.
    272         * @param measurer  the text measurer.
    273         *
    274         * @return The index of the next line break.
    275         */
    276        private static int nextLineBreak(final String text, final int start,
    277                final float width, final BreakIterator iterator,
    278                final TextMeasurer measurer) {
    279
    280            // this method is (loosely) based on code in JFreeReport's
    281            // TextParagraph class
    282            int current = start;
    283            int end;
    284            float x = 0.0f;
    285            boolean firstWord = true;
    286            int newline = text.indexOf('\n', start);
    287            if (newline < 0) {
    288                newline = Integer.MAX_VALUE;
    289            }
    290            while (((end = iterator.next()) != BreakIterator.DONE)) {
    291                if (end > newline) {
    292                    return newline;
    293                }
    294                x += measurer.getStringWidth(text, current, end);
    295                if (x > width) {
    296                    if (firstWord) {
    297                        while (measurer.getStringWidth(text, start, end) > width) {
    298                            end--;
    299                            if (end <= start) {
    300                                return end;
    301                            }
    302                        }
    303                        return end;
    304                    }
    305                    else {
    306                        end = iterator.previous();
    307                        return end;
    308                    }
    309                }
    310                // we found at least one word that fits ...
    311                firstWord = false;
    312                current = end;
    313            }
    314            return BreakIterator.DONE;
    315        }
    316
    317        /**
    318         * Returns the bounds for the specified text.
    319         *
    320         * @param text  the text (<code>null</code> permitted).
    321         * @param g2  the graphics context (not <code>null</code>).
    322         * @param fm  the font metrics (not <code>null</code>).
    323         *
    324         * @return The text bounds (<code>null</code> if the <code>text</code>
    325         *         argument is <code>null</code>).
    326         */
    327        public static Rectangle2D getTextBounds(final String text,
    328                final Graphics2D g2, final FontMetrics fm) {
    329
    330            final Rectangle2D bounds;
    331            if (TextUtilities.useFontMetricsGetStringBounds) {
    332                bounds = fm.getStringBounds(text, g2);
    333                // getStringBounds() can return incorrect height for some Unicode
    334                // characters...see bug parade 6183356, let's replace it with
    335                // something correct
    336                LineMetrics lm = fm.getFont().getLineMetrics(text,
    337                        g2.getFontRenderContext());
    338                bounds.setRect(bounds.getX(), bounds.getY(), bounds.getWidth(),
    339                        lm.getHeight());
    340            }
    341            else {
    342                final double width = fm.stringWidth(text);
    343                final double height = fm.getHeight();
    344                if (logger.isDebugEnabled()) {
    345                    logger.debug("Height = " + height);
    346                }
    347                bounds = new Rectangle2D.Double(0.0, -fm.getAscent(), width,
    348                        height);
    349            }
    350            return bounds;
    351        }
    352
    353        /**
    354         * Draws a string such that the specified anchor point is aligned to the
    355         * given (x, y) location.
    356         *
    357         * @param text  the text.
    358         * @param g2  the graphics device.
    359         * @param x  the x coordinate (Java 2D).
    360         * @param y  the y coordinate (Java 2D).
    361         * @param anchor  the anchor location.
    362         *
    363         * @return The text bounds (adjusted for the text position).
    364         */
    365        public static Rectangle2D drawAlignedString(final String text,
    366                final Graphics2D g2, final float x, final float y,
    367                final TextAnchor anchor) {
    368
    369            final Rectangle2D textBounds = new Rectangle2D.Double();
    370            final float[] adjust = deriveTextBoundsAnchorOffsets(g2, text, anchor,
    371                    textBounds);
    372            // adjust text bounds to match string position
    373            textBounds.setRect(x + adjust[0], y + adjust[1] + adjust[2],
    374                textBounds.getWidth(), textBounds.getHeight());
    375            g2.drawString(text, x + adjust[0], y + adjust[1]);
    376            return textBounds;
    377        }
    378
    379        /**
    380         * A utility method that calculates the anchor offsets for a string.
    381         * Normally, the (x, y) coordinate for drawing text is a point on the
    382         * baseline at the left of the text string.  If you add these offsets to
    383         * (x, y) and draw the string, then the anchor point should coincide with
    384         * the (x, y) point.
    385         *
    386         * @param g2  the graphics device (not <code>null</code>).
    387         * @param text  the text.
    388         * @param anchor  the anchor point.
    389         * @param textBounds  the text bounds (if not <code>null</code>, this
    390         *                    object will be updated by this method to match the
    391         *                    string bounds).
    392         *
    393         * @return  The offsets.
    394         */
    395        private static float[] deriveTextBoundsAnchorOffsets(final Graphics2D g2,
    396                final String text, final TextAnchor anchor,
    397                final Rectangle2D textBounds) {
    398
    399            final float[] result = new float[3];
    400            final FontRenderContext frc = g2.getFontRenderContext();
    401            final Font f = g2.getFont();
    402            final FontMetrics fm = g2.getFontMetrics(f);
    403            final Rectangle2D bounds = TextUtilities.getTextBounds(text, g2, fm);
    404            final LineMetrics metrics = f.getLineMetrics(text, frc);
    405            final float ascent = metrics.getAscent();
    406            result[2] = -ascent;
    407            final float halfAscent = ascent / 2.0f;
    408            final float descent = metrics.getDescent();
    409            final float leading = metrics.getLeading();
    410            float xAdj = 0.0f;
    411            float yAdj = 0.0f;
    412
    413            if (anchor == TextAnchor.TOP_CENTER
    414                    || anchor == TextAnchor.CENTER
    415                    || anchor == TextAnchor.BOTTOM_CENTER
    416                    || anchor == TextAnchor.BASELINE_CENTER
    417                    || anchor == TextAnchor.HALF_ASCENT_CENTER) {
    418
    419                xAdj = (float) -bounds.getWidth() / 2.0f;
    420
    421            }
    422            else if (anchor == TextAnchor.TOP_RIGHT
    423                    || anchor == TextAnchor.CENTER_RIGHT
    424                    || anchor == TextAnchor.BOTTOM_RIGHT
    425                    || anchor == TextAnchor.BASELINE_RIGHT
    426                    || anchor == TextAnchor.HALF_ASCENT_RIGHT) {
    427
    428                xAdj = (float) -bounds.getWidth();
    429
    430            }
    431
    432            if (anchor == TextAnchor.TOP_LEFT
    433                    || anchor == TextAnchor.TOP_CENTER
    434                    || anchor == TextAnchor.TOP_RIGHT) {
    435
    436                yAdj = -descent - leading + (float) bounds.getHeight();
    437
    438            }
    439            else if (anchor == TextAnchor.HALF_ASCENT_LEFT
    440                    || anchor == TextAnchor.HALF_ASCENT_CENTER
    441                    || anchor == TextAnchor.HALF_ASCENT_RIGHT) {
    442
    443                yAdj = halfAscent;
    444
    445            }
    446            else if (anchor == TextAnchor.CENTER_LEFT
    447                    || anchor == TextAnchor.CENTER
    448                    || anchor == TextAnchor.CENTER_RIGHT) {
    449
    450                yAdj = -descent - leading + (float) (bounds.getHeight() / 2.0);
    451
    452            }
    453            else if (anchor == TextAnchor.BASELINE_LEFT
    454                    || anchor == TextAnchor.BASELINE_CENTER
    455                    || anchor == TextAnchor.BASELINE_RIGHT) {
    456
    457                yAdj = 0.0f;
    458
    459            }
    460            else if (anchor == TextAnchor.BOTTOM_LEFT
    461                    || anchor == TextAnchor.BOTTOM_CENTER
    462                    || anchor == TextAnchor.BOTTOM_RIGHT) {
    463
    464                yAdj = -metrics.getDescent() - metrics.getLeading();
    465
    466            }
    467            if (textBounds != null) {
    468                textBounds.setRect(bounds);
    469            }
    470            result[0] = xAdj;
    471            result[1] = yAdj;
    472            return result;
    473
    474        }
    475
    476        /**
    477         * Sets the flag that controls whether or not a workaround is used for
    478         * drawing rotated strings.  The related bug is on Sun's bug parade
    479         * (id 4312117) and the workaround involves using a <code>TextLayout</code>
    480         * instance to draw the text instead of calling the
    481         * <code>drawString()</code> method in the <code>Graphics2D</code> class.
    482         *
    483         * @param use  the new flag value.
    484         */
    485        public static void setUseDrawRotatedStringWorkaround(final boolean use) {
    486            useDrawRotatedStringWorkaround = use;
    487        }
    488
    489        /**
    490         * A utility method for drawing rotated text.
    491         * <P>
    492         * A common rotation is -Math.PI/2 which draws text 'vertically' (with the
    493         * top of the characters on the left).
    494         *
    495         * @param text  the text.
    496         * @param g2  the graphics device.
    497         * @param angle  the angle of the (clockwise) rotation (in radians).
    498         * @param x  the x-coordinate.
    499         * @param y  the y-coordinate.
    500         */
    501        public static void drawRotatedString(final String text, final Graphics2D g2,
    502                final double angle, final float x, final float y) {
    503            drawRotatedString(text, g2, x, y, angle, x, y);
    504        }
    505
    506        /**
    507         * A utility method for drawing rotated text.
    508         * <P>
    509         * A common rotation is -Math.PI/2 which draws text 'vertically' (with the
    510         * top of the characters on the left).
    511         *
    512         * @param text  the text.
    513         * @param g2  the graphics device.
    514         * @param textX  the x-coordinate for the text (before rotation).
    515         * @param textY  the y-coordinate for the text (before rotation).
    516         * @param angle  the angle of the (clockwise) rotation (in radians).
    517         * @param rotateX  the point about which the text is rotated.
    518         * @param rotateY  the point about which the text is rotated.
    519         */
    520        public static void drawRotatedString(final String text, final Graphics2D g2,
    521                final float textX, final float textY, final double angle,
    522                final float rotateX, final float rotateY) {
    523
    524            if ((text == null) || (text.equals(""))) {
    525                return;
    526            }
    527
    528            final AffineTransform saved = g2.getTransform();
    529
    530            // apply the rotation...
    531            final AffineTransform rotate = AffineTransform.getRotateInstance(
    532                    angle, rotateX, rotateY);
    533            g2.transform(rotate);
    534
    535            if (useDrawRotatedStringWorkaround) {
    536                // workaround for JDC bug ID 4312117 and others...
    537                final TextLayout tl = new TextLayout(text, g2.getFont(),
    538                        g2.getFontRenderContext());
    539                tl.draw(g2, textX, textY);
    540            }
    541            else {
    542                // replaces this code...
    543                g2.drawString(text, textX, textY);
    544            }
    545            g2.setTransform(saved);
    546
    547        }
    548
    549        /**
    550         * Draws a string that is aligned by one anchor point and rotated about
    551         * another anchor point.
    552         *
    553         * @param text  the text.
    554         * @param g2  the graphics device.
    555         * @param x  the x-coordinate for positioning the text.
    556         * @param y  the y-coordinate for positioning the text.
    557         * @param textAnchor  the text anchor.
    558         * @param angle  the rotation angle.
    559         * @param rotationX  the x-coordinate for the rotation anchor point.
    560         * @param rotationY  the y-coordinate for the rotation anchor point.
    561         */
    562        public static void drawRotatedString(final String text,
    563                final Graphics2D g2, final float x, final float y,
    564                final TextAnchor textAnchor, final double angle,
    565                final float rotationX, final float rotationY) {
    566
    567            if (text == null || text.equals("")) {
    568                return;
    569            }
    570            final float[] textAdj = deriveTextBoundsAnchorOffsets(g2, text,
    571                    textAnchor);
    572            drawRotatedString(text, g2, x + textAdj[0], y + textAdj[1], angle,
    573                    rotationX, rotationY);
    574        }
    575
    576        /**
    577         * Draws a string that is aligned by one anchor point and rotated about
    578         * another anchor point.
    579         *
    580         * @param text  the text.
    581         * @param g2  the graphics device.
    582         * @param x  the x-coordinate for positioning the text.
    583         * @param y  the y-coordinate for positioning the text.
    584         * @param textAnchor  the text anchor.
    585         * @param angle  the rotation angle (in radians).
    586         * @param rotationAnchor  the rotation anchor.
    587         */
    588        public static void drawRotatedString(final String text, final Graphics2D g2,
    589                final float x, final float y, final TextAnchor textAnchor,
    590                final double angle, final TextAnchor rotationAnchor) {
    591
    592            if (text == null || text.equals("")) {
    593                return;
    594            }
    595            final float[] textAdj = deriveTextBoundsAnchorOffsets(g2, text,
    596                    textAnchor);
    597            final float[] rotateAdj = deriveRotationAnchorOffsets(g2, text,
    598                    rotationAnchor);
    599            drawRotatedString(text, g2, x + textAdj[0], y + textAdj[1],
    600                    angle, x + textAdj[0] + rotateAdj[0],
    601                    y + textAdj[1] + rotateAdj[1]);
    602
    603        }
    604
    605        /**
    606         * Returns a shape that represents the bounds of the string after the
    607         * specified rotation has been applied.
    608         *
    609         * @param text  the text (<code>null</code> permitted).
    610         * @param g2  the graphics device.
    611         * @param x  the x coordinate for the anchor point.
    612         * @param y  the y coordinate for the anchor point.
    613         * @param textAnchor  the text anchor.
    614         * @param angle  the angle.
    615         * @param rotationAnchor  the rotation anchor.
    616         *
    617         * @return The bounds (possibly <code>null</code>).
    618         */
    619        public static Shape calculateRotatedStringBounds(final String text,
    620                final Graphics2D g2, final float x, final float y,
    621                final TextAnchor textAnchor, final double angle,
    622                final TextAnchor rotationAnchor) {
    623
    624            if (text == null || text.equals("")) {
    625                return null;
    626            }
    627            final float[] textAdj = deriveTextBoundsAnchorOffsets(g2, text,
    628                    textAnchor);
    629            if (logger.isDebugEnabled()) {
    630                logger.debug("TextBoundsAnchorOffsets = " + textAdj[0] + ", "
    631                        + textAdj[1]);
    632            }
    633            final float[] rotateAdj = deriveRotationAnchorOffsets(g2, text,
    634                    rotationAnchor);
    635            if (logger.isDebugEnabled()) {
    636                logger.debug("RotationAnchorOffsets = " + rotateAdj[0] + ", "
    637                        + rotateAdj[1]);
    638            }
    639            final Shape result = calculateRotatedStringBounds(text, g2,
    640                    x + textAdj[0], y + textAdj[1], angle,
    641                    x + textAdj[0] + rotateAdj[0], y + textAdj[1] + rotateAdj[1]);
    642            return result;
    643
    644        }
    645
    646        /**
    647         * A utility method that calculates the anchor offsets for a string.
    648         * Normally, the (x, y) coordinate for drawing text is a point on the
    649         * baseline at the left of the text string.  If you add these offsets to
    650         * (x, y) and draw the string, then the anchor point should coincide with
    651         * the (x, y) point.
    652         *
    653         * @param g2  the graphics device (not <code>null</code>).
    654         * @param text  the text.
    655         * @param anchor  the anchor point.
    656         *
    657         * @return  The offsets.
    658         */
    659        private static float[] deriveTextBoundsAnchorOffsets(final Graphics2D g2,
    660                final String text, final TextAnchor anchor) {
    661
    662            final float[] result = new float[2];
    663            final FontRenderContext frc = g2.getFontRenderContext();
    664            final Font f = g2.getFont();
    665            final FontMetrics fm = g2.getFontMetrics(f);
    666            final Rectangle2D bounds = TextUtilities.getTextBounds(text, g2, fm);
    667            final LineMetrics metrics = f.getLineMetrics(text, frc);
    668            final float ascent = metrics.getAscent();
    669            final float halfAscent = ascent / 2.0f;
    670            final float descent = metrics.getDescent();
    671            final float leading = metrics.getLeading();
    672            float xAdj = 0.0f;
    673            float yAdj = 0.0f;
    674
    675            if (anchor == TextAnchor.TOP_CENTER
    676                    || anchor == TextAnchor.CENTER
    677                    || anchor == TextAnchor.BOTTOM_CENTER
    678                    || anchor == TextAnchor.BASELINE_CENTER
    679                    || anchor == TextAnchor.HALF_ASCENT_CENTER) {
    680
    681                xAdj = (float) -bounds.getWidth() / 2.0f;
    682
    683            }
    684            else if (anchor == TextAnchor.TOP_RIGHT
    685                    || anchor == TextAnchor.CENTER_RIGHT
    686                    || anchor == TextAnchor.BOTTOM_RIGHT
    687                    || anchor == TextAnchor.BASELINE_RIGHT
    688                    || anchor == TextAnchor.HALF_ASCENT_RIGHT) {
    689
    690                xAdj = (float) -bounds.getWidth();
    691
    692            }
    693
    694            if (anchor == TextAnchor.TOP_LEFT
    695                    || anchor == TextAnchor.TOP_CENTER
    696                    || anchor == TextAnchor.TOP_RIGHT) {
    697
    698                yAdj = -descent - leading + (float) bounds.getHeight();
    699
    700            }
    701            else if (anchor == TextAnchor.HALF_ASCENT_LEFT
    702                    || anchor == TextAnchor.HALF_ASCENT_CENTER
    703                    || anchor == TextAnchor.HALF_ASCENT_RIGHT) {
    704
    705                yAdj = halfAscent;
    706
    707            }
    708            else if (anchor == TextAnchor.CENTER_LEFT
    709                    || anchor == TextAnchor.CENTER
    710                    || anchor == TextAnchor.CENTER_RIGHT) {
    711
    712                yAdj = -descent - leading + (float) (bounds.getHeight() / 2.0);
    713
    714            }
    715            else if (anchor == TextAnchor.BASELINE_LEFT
    716                    || anchor == TextAnchor.BASELINE_CENTER
    717                    || anchor == TextAnchor.BASELINE_RIGHT) {
    718
    719                yAdj = 0.0f;
    720
    721            }
    722            else if (anchor == TextAnchor.BOTTOM_LEFT
    723                    || anchor == TextAnchor.BOTTOM_CENTER
    724                    || anchor == TextAnchor.BOTTOM_RIGHT) {
    725
    726                yAdj = -metrics.getDescent() - metrics.getLeading();
    727
    728            }
    729            result[0] = xAdj;
    730            result[1] = yAdj;
    731            return result;
    732
    733        }
    734
    735        /**
    736         * A utility method that calculates the rotation anchor offsets for a
    737         * string.  These offsets are relative to the text starting coordinate
    738         * (BASELINE_LEFT).
    739         *
    740         * @param g2  the graphics device.
    741         * @param text  the text.
    742         * @param anchor  the anchor point.
    743         *
    744         * @return  The offsets.
    745         */
    746        private static float[] deriveRotationAnchorOffsets(final Graphics2D g2,
    747                final String text, final TextAnchor anchor) {
    748
    749            final float[] result = new float[2];
    750            final FontRenderContext frc = g2.getFontRenderContext();
    751            final LineMetrics metrics = g2.getFont().getLineMetrics(text, frc);
    752            final FontMetrics fm = g2.getFontMetrics();
    753            final Rectangle2D bounds = TextUtilities.getTextBounds(text, g2, fm);
    754            final float ascent = metrics.getAscent();
    755            final float halfAscent = ascent / 2.0f;
    756            final float descent = metrics.getDescent();
    757            final float leading = metrics.getLeading();
    758            float xAdj = 0.0f;
    759            float yAdj = 0.0f;
    760
    761            if (anchor == TextAnchor.TOP_LEFT
    762                    || anchor == TextAnchor.CENTER_LEFT
    763                    || anchor == TextAnchor.BOTTOM_LEFT
    764                    || anchor == TextAnchor.BASELINE_LEFT
    765                    || anchor == TextAnchor.HALF_ASCENT_LEFT) {
    766
    767                xAdj = 0.0f;
    768
    769            }
    770            else if (anchor == TextAnchor.TOP_CENTER
    771                    || anchor == TextAnchor.CENTER
    772                    || anchor == TextAnchor.BOTTOM_CENTER
    773                    || anchor == TextAnchor.BASELINE_CENTER
    774                    || anchor == TextAnchor.HALF_ASCENT_CENTER) {
    775
    776                xAdj = (float) bounds.getWidth() / 2.0f;
    777
    778            }
    779            else if (anchor == TextAnchor.TOP_RIGHT
    780                    || anchor == TextAnchor.CENTER_RIGHT
    781                    || anchor == TextAnchor.BOTTOM_RIGHT
    782                    || anchor == TextAnchor.BASELINE_RIGHT
    783                    || anchor == TextAnchor.HALF_ASCENT_RIGHT) {
    784
    785                xAdj = (float) bounds.getWidth();
    786
    787            }
    788
    789            if (anchor == TextAnchor.TOP_LEFT
    790                    || anchor == TextAnchor.TOP_CENTER
    791                    || anchor == TextAnchor.TOP_RIGHT) {
    792
    793                yAdj = descent + leading - (float) bounds.getHeight();
    794
    795            }
    796            else if (anchor == TextAnchor.CENTER_LEFT
    797                    || anchor == TextAnchor.CENTER
    798                    || anchor == TextAnchor.CENTER_RIGHT) {
    799
    800                yAdj = descent + leading - (float) (bounds.getHeight() / 2.0);
    801
    802            }
    803            else if (anchor == TextAnchor.HALF_ASCENT_LEFT
    804                    || anchor == TextAnchor.HALF_ASCENT_CENTER
    805                    || anchor == TextAnchor.HALF_ASCENT_RIGHT) {
    806
    807                yAdj = -halfAscent;
    808
    809            }
    810            else if (anchor == TextAnchor.BASELINE_LEFT
    811                    || anchor == TextAnchor.BASELINE_CENTER
    812                    || anchor == TextAnchor.BASELINE_RIGHT) {
    813
    814                yAdj = 0.0f;
    815
    816            }
    817            else if (anchor == TextAnchor.BOTTOM_LEFT
    818                    || anchor == TextAnchor.BOTTOM_CENTER
    819                    || anchor == TextAnchor.BOTTOM_RIGHT) {
    820
    821                yAdj = metrics.getDescent() + metrics.getLeading();
    822
    823            }
    824            result[0] = xAdj;
    825            result[1] = yAdj;
    826            return result;
    827
    828        }
    829
    830        /**
    831         * Returns a shape that represents the bounds of the string after the
    832         * specified rotation has been applied.
    833         *
    834         * @param text  the text (<code>null</code> permitted).
    835         * @param g2  the graphics device.
    836         * @param textX  the x coordinate for the text.
    837         * @param textY  the y coordinate for the text.
    838         * @param angle  the angle.
    839         * @param rotateX  the x coordinate for the rotation point.
    840         * @param rotateY  the y coordinate for the rotation point.
    841         *
    842         * @return The bounds (<code>null</code> if <code>text</code> is
    843         *         </code>null</code> or has zero length).
    844         */
    845        public static Shape calculateRotatedStringBounds(final String text,
    846                final Graphics2D g2, final float textX, final float textY,
    847                final double angle, final float rotateX, final float rotateY) {
    848
    849            if ((text == null) || (text.equals(""))) {
    850                return null;
    851            }
    852            final FontMetrics fm = g2.getFontMetrics();
    853            final Rectangle2D bounds = TextUtilities.getTextBounds(text, g2, fm);
    854            final AffineTransform translate = AffineTransform.getTranslateInstance(
    855                    textX, textY);
    856            final Shape translatedBounds = translate.createTransformedShape(bounds);
    857            final AffineTransform rotate = AffineTransform.getRotateInstance(
    858                    angle, rotateX, rotateY);
    859            final Shape result = rotate.createTransformedShape(translatedBounds);
    860            return result;
    861
    862        }
    863
    864        /**
    865         * Returns the flag that controls whether the FontMetrics.getStringBounds()
    866         * method is used or not.  If you are having trouble with label alignment
    867         * or positioning, try changing the value of this flag.
    868         *
    869         * @return A boolean.
    870         */
    871        public static boolean getUseFontMetricsGetStringBounds() {
    872            return useFontMetricsGetStringBounds;
    873        }
    874
    875        /**
    876         * Sets the flag that controls whether the FontMetrics.getStringBounds()
    877         * method is used or not.  If you are having trouble with label alignment
    878         * or positioning, try changing the value of this flag.
    879         *
    880         * @param use  the flag.
    881         */
    882        public static void setUseFontMetricsGetStringBounds(final boolean use) {
    883            useFontMetricsGetStringBounds = use;
    884        }
    885
    886        public static boolean isUseDrawRotatedStringWorkaround() {
    887            return useDrawRotatedStringWorkaround;
    888        }
    889    }