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