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