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