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