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 }