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