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