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