1:
57:
58: package ;
59:
60: import ;
61: import ;
62: import ;
63: import ;
64: import ;
65: import ;
66: import ;
67: import ;
68: import ;
69: import ;
70: import ;
71:
72: import ;
73: import ;
74: import ;
75: import ;
76: import ;
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:
272:
273: 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:
302: 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:
325:
326:
327: 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:
364: 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:
522: final AffineTransform rotate = AffineTransform.getRotateInstance(
523: angle, rotateX, rotateY);
524: g2.transform(rotate);
525:
526: if (useDrawRotatedStringWorkaround) {
527:
528: final TextLayout tl = new TextLayout(text, g2.getFont(),
529: g2.getFontRenderContext());
530: tl.draw(g2, textX, textY);
531: }
532: else {
533:
534: 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: }