1 57 58 package org.jfree.text; 59 60 import java.awt.Font ; 61 import java.awt.FontMetrics ; 62 import java.awt.Graphics2D ; 63 import java.awt.Paint ; 64 import java.awt.Shape ; 65 import java.awt.font.FontRenderContext ; 66 import java.awt.font.LineMetrics ; 67 import java.awt.font.TextLayout ; 68 import java.awt.geom.AffineTransform ; 69 import java.awt.geom.Rectangle2D ; 70 import java.text.BreakIterator ; 71 72 import org.jfree.ui.TextAnchor; 73 import org.jfree.util.Log; 74 import org.jfree.util.LogContext; 75 import org.jfree.util.ObjectUtilities; 76 import org.jfree.base.BaseBoot; 77 78 83 public class TextUtilities { 84 85 86 protected static final LogContext logger = Log.createContext( 87 TextUtilities.class); 88 89 93 private static boolean useDrawRotatedStringWorkaround; 94 95 99 private static boolean useFontMetricsGetStringBounds; 100 101 static { 102 final boolean isJava14 = ObjectUtilities.isJDK14(); 103 104 final String configRotatedStringWorkaround = 105 BaseBoot.getInstance().getGlobalConfig().getConfigProperty( 106 "org.jfree.text.UseDrawRotatedStringWorkaround", "auto"); 107 if (configRotatedStringWorkaround.equals("auto")) { 108 useDrawRotatedStringWorkaround = (isJava14 == false); 109 } 110 else { 111 useDrawRotatedStringWorkaround 112 = configRotatedStringWorkaround.equals("true"); 113 } 114 115 final String configFontMetricsStringBounds 116 = BaseBoot.getInstance().getGlobalConfig().getConfigProperty( 117 "org.jfree.text.UseFontMetricsGetStringBounds", "auto"); 118 if (configFontMetricsStringBounds.equals("auto")) { 119 useFontMetricsGetStringBounds = (isJava14 == true); 120 } 121 else { 122 useFontMetricsGetStringBounds 123 = configFontMetricsStringBounds.equals("true"); 124 } 125 } 126 127 130 private TextUtilities() { 131 } 132 133 143 public static TextBlock createTextBlock(final String text, final Font font, 144 final Paint paint) { 145 if (text == null) { 146 throw new IllegalArgumentException ("Null 'text' argument."); 147 } 148 final TextBlock result = new TextBlock(); 149 String input = text; 150 boolean moreInputToProcess = (text.length() > 0); 151 final int start = 0; 152 while (moreInputToProcess) { 153 final int index = input.indexOf("\n"); 154 if (index > start) { 155 final String line = input.substring(start, index); 156 if (index < input.length() - 1) { 157 result.addLine(line, font, paint); 158 input = input.substring(index + 1); 159 } 160 else { 161 moreInputToProcess = false; 162 } 163 } 164 else if (index == start) { 165 if (index < input.length() - 1) { 166 input = input.substring(index + 1); 167 } 168 else { 169 moreInputToProcess = false; 170 } 171 } 172 else { 173 result.addLine(input, font, paint); 174 moreInputToProcess = false; 175 } 176 } 177 return result; 178 } 179 180 193 public static TextBlock createTextBlock(final String text, final Font font, 194 final Paint paint, final float maxWidth, 195 final TextMeasurer measurer) { 196 197 return createTextBlock(text, font, paint, maxWidth, Integer.MAX_VALUE, 198 measurer); 199 } 200 201 215 public static TextBlock createTextBlock(final String text, final Font font, 216 final Paint paint, final float maxWidth, final int maxLines, 217 final TextMeasurer measurer) { 218 219 final TextBlock result = new TextBlock(); 220 final BreakIterator iterator = BreakIterator.getLineInstance(); 221 iterator.setText(text); 222 int current = 0; 223 int lines = 0; 224 final int length = text.length(); 225 while (current < length && lines < maxLines) { 226 final int next = nextLineBreak(text, current, maxWidth, iterator, 227 measurer); 228 if (next == BreakIterator.DONE) { 229 result.addLine(text.substring(current), font, paint); 230 return result; 231 } 232 result.addLine(text.substring(current, next), font, paint); 233 lines++; 234 current = next; 235 while (current < text.length()&& text.charAt(current) == '\n') { 236 current++; 237 } 238 } 239 if (current < length) { 240 final TextLine lastLine = result.getLastLine(); 241 final TextFragment lastFragment = lastLine.getLastTextFragment(); 242 final String oldStr = lastFragment.getText(); 243 String newStr = "..."; 244 if (oldStr.length() > 3) { 245 newStr = oldStr.substring(0, oldStr.length() - 3) + "..."; 246 } 247 248 lastLine.removeFragment(lastFragment); 249 final TextFragment newFragment = new TextFragment(newStr, 250 lastFragment.getFont(), lastFragment.getPaint()); 251 lastLine.addFragment(newFragment); 252 } 253 return result; 254 } 255 256 267 private static int nextLineBreak(final String text, final int start, 268 final float width, final BreakIterator iterator, 269 final TextMeasurer measurer) { 270 271 int current = start; 274 int end; 275 float x = 0.0f; 276 boolean firstWord = true; 277 int newline = text.indexOf('\n', start); 278 if (newline < 0) { 279 newline = Integer.MAX_VALUE; 280 } 281 while (((end = iterator.next()) != BreakIterator.DONE)) { 282 if (end > newline) { 283 return newline; 284 } 285 x += measurer.getStringWidth(text, current, end); 286 if (x > width) { 287 if (firstWord) { 288 while (measurer.getStringWidth(text, start, end) > width) { 289 end--; 290 if (end <= start) { 291 return end; 292 } 293 } 294 return end; 295 } 296 else { 297 end = iterator.previous(); 298 return end; 299 } 300 } 301 firstWord = false; 303 current = end; 304 } 305 return BreakIterator.DONE; 306 } 307 308 318 public static Rectangle2D getTextBounds(final String text, 319 final Graphics2D g2, final FontMetrics fm) { 320 321 final Rectangle2D bounds; 322 if (TextUtilities.useFontMetricsGetStringBounds) { 323 bounds = fm.getStringBounds(text, g2); 324 LineMetrics lm = fm.getFont().getLineMetrics(text, 328 g2.getFontRenderContext()); 329 bounds.setRect(bounds.getX(), bounds.getY(), bounds.getWidth(), 330 lm.getHeight()); 331 } 332 else { 333 final double width = fm.stringWidth(text); 334 final double height = fm.getHeight(); 335 if (logger.isDebugEnabled()) { 336 logger.debug("Height = " + height); 337 } 338 bounds = new Rectangle2D.Double (0.0, -fm.getAscent(), width, 339 height); 340 } 341 return bounds; 342 } 343 344 356 public static Rectangle2D drawAlignedString(final String text, 357 final Graphics2D g2, final float x, final float y, 358 final TextAnchor anchor) { 359 360 final Rectangle2D textBounds = new Rectangle2D.Double (); 361 final float[] adjust = deriveTextBoundsAnchorOffsets(g2, text, anchor, 362 textBounds); 363 textBounds.setRect(x + adjust[0], y + adjust[1] + adjust[2], 365 textBounds.getWidth(), textBounds.getHeight()); 366 g2.drawString(text, x + adjust[0], y + adjust[1]); 367 return textBounds; 368 } 369 370 386 private static float[] deriveTextBoundsAnchorOffsets(final Graphics2D g2, 387 final String text, final TextAnchor anchor, 388 final Rectangle2D textBounds) { 389 390 final float[] result = new float[3]; 391 final FontRenderContext frc = g2.getFontRenderContext(); 392 final Font f = g2.getFont(); 393 final FontMetrics fm = g2.getFontMetrics(f); 394 final Rectangle2D bounds = TextUtilities.getTextBounds(text, g2, fm); 395 final LineMetrics metrics = f.getLineMetrics(text, frc); 396 final float ascent = metrics.getAscent(); 397 result[2] = -ascent; 398 final float halfAscent = ascent / 2.0f; 399 final float descent = metrics.getDescent(); 400 final float leading = metrics.getLeading(); 401 float xAdj = 0.0f; 402 float yAdj = 0.0f; 403 404 if (anchor == TextAnchor.TOP_CENTER 405 || anchor == TextAnchor.CENTER 406 || anchor == TextAnchor.BOTTOM_CENTER 407 || anchor == TextAnchor.BASELINE_CENTER 408 || anchor == TextAnchor.HALF_ASCENT_CENTER) { 409 410 xAdj = (float) -bounds.getWidth() / 2.0f; 411 412 } 413 else if (anchor == TextAnchor.TOP_RIGHT 414 || anchor == TextAnchor.CENTER_RIGHT 415 || anchor == TextAnchor.BOTTOM_RIGHT 416 || anchor == TextAnchor.BASELINE_RIGHT 417 || anchor == TextAnchor.HALF_ASCENT_RIGHT) { 418 419 xAdj = (float) -bounds.getWidth(); 420 421 } 422 423 if (anchor == TextAnchor.TOP_LEFT 424 || anchor == TextAnchor.TOP_CENTER 425 || anchor == TextAnchor.TOP_RIGHT) { 426 427 yAdj = -descent - leading + (float) bounds.getHeight(); 428 429 } 430 else if (anchor == TextAnchor.HALF_ASCENT_LEFT 431 || anchor == TextAnchor.HALF_ASCENT_CENTER 432 || anchor == TextAnchor.HALF_ASCENT_RIGHT) { 433 434 yAdj = halfAscent; 435 436 } 437 else if (anchor == TextAnchor.CENTER_LEFT 438 || anchor == TextAnchor.CENTER 439 || anchor == TextAnchor.CENTER_RIGHT) { 440 441 yAdj = -descent - leading + (float) (bounds.getHeight() / 2.0); 442 443 } 444 else if (anchor == TextAnchor.BASELINE_LEFT 445 || anchor == TextAnchor.BASELINE_CENTER 446 || anchor == TextAnchor.BASELINE_RIGHT) { 447 448 yAdj = 0.0f; 449 450 } 451 else if (anchor == TextAnchor.BOTTOM_LEFT 452 || anchor == TextAnchor.BOTTOM_CENTER 453 || anchor == TextAnchor.BOTTOM_RIGHT) { 454 455 yAdj = -metrics.getDescent() - metrics.getLeading(); 456 457 } 458 if (textBounds != null) { 459 textBounds.setRect(bounds); 460 } 461 result[0] = xAdj; 462 result[1] = yAdj; 463 return result; 464 465 } 466 467 476 public static void setUseDrawRotatedStringWorkaround(final boolean use) { 477 useDrawRotatedStringWorkaround = use; 478 } 479 480 492 public static void drawRotatedString(final String text, final Graphics2D g2, 493 final double angle, final float x, final float y) { 494 drawRotatedString(text, g2, x, y, angle, x, y); 495 } 496 497 511 public static void drawRotatedString(final String text, final Graphics2D g2, 512 final float textX, final float textY, final double angle, 513 final float rotateX, final float rotateY) { 514 515 if ((text == null) || (text.equals(""))) { 516 return; 517 } 518 519 final AffineTransform saved = g2.getTransform(); 520 521 final AffineTransform rotate = AffineTransform.getRotateInstance( 523 angle, rotateX, rotateY); 524 g2.transform(rotate); 525 526 if (useDrawRotatedStringWorkaround) { 527 final TextLayout tl = new TextLayout (text, g2.getFont(), 529 g2.getFontRenderContext()); 530 tl.draw(g2, textX, textY); 531 } 532 else { 533 g2.drawString(text, textX, textY); 535 } 536 g2.setTransform(saved); 537 538 } 539 540 553 public static void drawRotatedString(final String text, 554 final Graphics2D g2, final float x, final float y, 555 final TextAnchor textAnchor, final double angle, 556 final float rotationX, final float rotationY) { 557 558 if (text == null || text.equals("")) { 559 return; 560 } 561 final float[] textAdj = deriveTextBoundsAnchorOffsets(g2, text, 562 textAnchor); 563 drawRotatedString(text, g2, x + textAdj[0], y + textAdj[1], angle, 564 rotationX, rotationY); 565 } 566 567 579 public static void drawRotatedString(final String text, final Graphics2D g2, 580 final float x, final float y, final TextAnchor textAnchor, 581 final double angle, final TextAnchor rotationAnchor) { 582 583 if (text == null || text.equals("")) { 584 return; 585 } 586 final float[] textAdj = deriveTextBoundsAnchorOffsets(g2, text, 587 textAnchor); 588 final float[] rotateAdj = deriveRotationAnchorOffsets(g2, text, 589 rotationAnchor); 590 drawRotatedString(text, g2, x + textAdj[0], y + textAdj[1], 591 angle, x + textAdj[0] + rotateAdj[0], 592 y + textAdj[1] + rotateAdj[1]); 593 594 } 595 596 610 public static Shape calculateRotatedStringBounds(final String text, 611 final Graphics2D g2, final float x, final float y, 612 final TextAnchor textAnchor, final double angle, 613 final TextAnchor rotationAnchor) { 614 615 if (text == null || text.equals("")) { 616 return null; 617 } 618 final float[] textAdj = deriveTextBoundsAnchorOffsets(g2, text, 619 textAnchor); 620 if (logger.isDebugEnabled()) { 621 logger.debug("TextBoundsAnchorOffsets = " + textAdj[0] + ", " 622 + textAdj[1]); 623 } 624 final float[] rotateAdj = deriveRotationAnchorOffsets(g2, text, 625 rotationAnchor); 626 if (logger.isDebugEnabled()) { 627 logger.debug("RotationAnchorOffsets = " + rotateAdj[0] + ", " 628 + rotateAdj[1]); 629 } 630 final Shape result = calculateRotatedStringBounds(text, g2, 631 x + textAdj[0], y + textAdj[1], angle, 632 x + textAdj[0] + rotateAdj[0], y + textAdj[1] + rotateAdj[1]); 633 return result; 634 635 } 636 637 650 private static float[] deriveTextBoundsAnchorOffsets(final Graphics2D g2, 651 final String text, final TextAnchor anchor) { 652 653 final float[] result = new float[2]; 654 final FontRenderContext frc = g2.getFontRenderContext(); 655 final Font f = g2.getFont(); 656 final FontMetrics fm = g2.getFontMetrics(f); 657 final Rectangle2D bounds = TextUtilities.getTextBounds(text, g2, fm); 658 final LineMetrics metrics = f.getLineMetrics(text, frc); 659 final float ascent = metrics.getAscent(); 660 final float halfAscent = ascent / 2.0f; 661 final float descent = metrics.getDescent(); 662 final float leading = metrics.getLeading(); 663 float xAdj = 0.0f; 664 float yAdj = 0.0f; 665 666 if (anchor == TextAnchor.TOP_CENTER 667 || anchor == TextAnchor.CENTER 668 || anchor == TextAnchor.BOTTOM_CENTER 669 || anchor == TextAnchor.BASELINE_CENTER 670 || anchor == TextAnchor.HALF_ASCENT_CENTER) { 671 672 xAdj = (float) -bounds.getWidth() / 2.0f; 673 674 } 675 else if (anchor == TextAnchor.TOP_RIGHT 676 || anchor == TextAnchor.CENTER_RIGHT 677 || anchor == TextAnchor.BOTTOM_RIGHT 678 || anchor == TextAnchor.BASELINE_RIGHT 679 || anchor == TextAnchor.HALF_ASCENT_RIGHT) { 680 681 xAdj = (float) -bounds.getWidth(); 682 683 } 684 685 if (anchor == TextAnchor.TOP_LEFT 686 || anchor == TextAnchor.TOP_CENTER 687 || anchor == TextAnchor.TOP_RIGHT) { 688 689 yAdj = -descent - leading + (float) bounds.getHeight(); 690 691 } 692 else if (anchor == TextAnchor.HALF_ASCENT_LEFT 693 || anchor == TextAnchor.HALF_ASCENT_CENTER 694 || anchor == TextAnchor.HALF_ASCENT_RIGHT) { 695 696 yAdj = halfAscent; 697 698 } 699 else if (anchor == TextAnchor.CENTER_LEFT 700 || anchor == TextAnchor.CENTER 701 || anchor == TextAnchor.CENTER_RIGHT) { 702 703 yAdj = -descent - leading + (float) (bounds.getHeight() / 2.0); 704 705 } 706 else if (anchor == TextAnchor.BASELINE_LEFT 707 || anchor == TextAnchor.BASELINE_CENTER 708 || anchor == TextAnchor.BASELINE_RIGHT) { 709 710 yAdj = 0.0f; 711 712 } 713 else if (anchor == TextAnchor.BOTTOM_LEFT 714 || anchor == TextAnchor.BOTTOM_CENTER 715 || anchor == TextAnchor.BOTTOM_RIGHT) { 716 717 yAdj = -metrics.getDescent() - metrics.getLeading(); 718 719 } 720 result[0] = xAdj; 721 result[1] = yAdj; 722 return result; 723 724 } 725 726 737 private static float[] deriveRotationAnchorOffsets(final Graphics2D g2, 738 final String text, final TextAnchor anchor) { 739 740 final float[] result = new float[2]; 741 final FontRenderContext frc = g2.getFontRenderContext(); 742 final LineMetrics metrics = g2.getFont().getLineMetrics(text, frc); 743 final FontMetrics fm = g2.getFontMetrics(); 744 final Rectangle2D bounds = TextUtilities.getTextBounds(text, g2, fm); 745 final float ascent = metrics.getAscent(); 746 final float halfAscent = ascent / 2.0f; 747 final float descent = metrics.getDescent(); 748 final float leading = metrics.getLeading(); 749 float xAdj = 0.0f; 750 float yAdj = 0.0f; 751 752 if (anchor == TextAnchor.TOP_LEFT 753 || anchor == TextAnchor.CENTER_LEFT 754 || anchor == TextAnchor.BOTTOM_LEFT 755 || anchor == TextAnchor.BASELINE_LEFT 756 || anchor == TextAnchor.HALF_ASCENT_LEFT) { 757 758 xAdj = 0.0f; 759 760 } 761 else if (anchor == TextAnchor.TOP_CENTER 762 || anchor == TextAnchor.CENTER 763 || anchor == TextAnchor.BOTTOM_CENTER 764 || anchor == TextAnchor.BASELINE_CENTER 765 || anchor == TextAnchor.HALF_ASCENT_CENTER) { 766 767 xAdj = (float) bounds.getWidth() / 2.0f; 768 769 } 770 else if (anchor == TextAnchor.TOP_RIGHT 771 || anchor == TextAnchor.CENTER_RIGHT 772 || anchor == TextAnchor.BOTTOM_RIGHT 773 || anchor == TextAnchor.BASELINE_RIGHT 774 || anchor == TextAnchor.HALF_ASCENT_RIGHT) { 775 776 xAdj = (float) bounds.getWidth(); 777 778 } 779 780 if (anchor == TextAnchor.TOP_LEFT 781 || anchor == TextAnchor.TOP_CENTER 782 || anchor == TextAnchor.TOP_RIGHT) { 783 784 yAdj = descent + leading - (float) bounds.getHeight(); 785 786 } 787 else if (anchor == TextAnchor.CENTER_LEFT 788 || anchor == TextAnchor.CENTER 789 || anchor == TextAnchor.CENTER_RIGHT) { 790 791 yAdj = descent + leading - (float) (bounds.getHeight() / 2.0); 792 793 } 794 else if (anchor == TextAnchor.HALF_ASCENT_LEFT 795 || anchor == TextAnchor.HALF_ASCENT_CENTER 796 || anchor == TextAnchor.HALF_ASCENT_RIGHT) { 797 798 yAdj = -halfAscent; 799 800 } 801 else if (anchor == TextAnchor.BASELINE_LEFT 802 || anchor == TextAnchor.BASELINE_CENTER 803 || anchor == TextAnchor.BASELINE_RIGHT) { 804 805 yAdj = 0.0f; 806 807 } 808 else if (anchor == TextAnchor.BOTTOM_LEFT 809 || anchor == TextAnchor.BOTTOM_CENTER 810 || anchor == TextAnchor.BOTTOM_RIGHT) { 811 812 yAdj = metrics.getDescent() + metrics.getLeading(); 813 814 } 815 result[0] = xAdj; 816 result[1] = yAdj; 817 return result; 818 819 } 820 821 836 public static Shape calculateRotatedStringBounds(final String text, 837 final Graphics2D g2, final float textX, final float textY, 838 final double angle, final float rotateX, final float rotateY) { 839 840 if ((text == null) || (text.equals(""))) { 841 return null; 842 } 843 final FontMetrics fm = g2.getFontMetrics(); 844 final Rectangle2D bounds = TextUtilities.getTextBounds(text, g2, fm); 845 final AffineTransform translate = AffineTransform.getTranslateInstance( 846 textX, textY); 847 final Shape translatedBounds = translate.createTransformedShape(bounds); 848 final AffineTransform rotate = AffineTransform.getRotateInstance( 849 angle, rotateX, rotateY); 850 final Shape result = rotate.createTransformedShape(translatedBounds); 851 return result; 852 853 } 854 855 862 public static boolean getUseFontMetricsGetStringBounds() { 863 return useFontMetricsGetStringBounds; 864 } 865 866 873 public static void setUseFontMetricsGetStringBounds(final boolean use) { 874 useFontMetricsGetStringBounds = use; 875 } 876 877 public static boolean isUseDrawRotatedStringWorkaround() { 878 return useDrawRotatedStringWorkaround; 879 } 880 } 881 | Popular Tags |