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