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