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