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