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    }