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