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