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