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