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