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