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