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