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