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