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