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