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