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