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: try
103: {
104: final boolean isJava14 = ObjectUtilities.isJDK14();
105:
106: final String configRotatedStringWorkaround =
107: BaseBoot.getInstance().getGlobalConfig().getConfigProperty(
108: "org.jfree.text.UseDrawRotatedStringWorkaround", "auto");
109: if (configRotatedStringWorkaround.equals("auto")) {
110: useDrawRotatedStringWorkaround = (isJava14 == false);
111: }
112: else {
113: useDrawRotatedStringWorkaround
114: = configRotatedStringWorkaround.equals("true");
115: }
116:
117: final String configFontMetricsStringBounds
118: = BaseBoot.getInstance().getGlobalConfig().getConfigProperty(
119: "org.jfree.text.UseFontMetricsGetStringBounds", "auto");
120: if (configFontMetricsStringBounds.equals("auto")) {
121: useFontMetricsGetStringBounds = (isJava14 == true);
122: }
123: else {
124: useFontMetricsGetStringBounds
125: = configFontMetricsStringBounds.equals("true");
126: }
127: }
128: catch (Exception e)
129: {
130:
131: useDrawRotatedStringWorkaround = true;
132: useFontMetricsGetStringBounds = true;
133: }
134: }
135:
136:
139: private TextUtilities() {
140: }
141:
142:
152: public static TextBlock createTextBlock(final String text, final Font font,
153: final Paint paint) {
154: if (text == null) {
155: throw new IllegalArgumentException("Null 'text' argument.");
156: }
157: final TextBlock result = new TextBlock();
158: String input = text;
159: boolean moreInputToProcess = (text.length() > 0);
160: final int start = 0;
161: while (moreInputToProcess) {
162: final int index = input.indexOf("\n");
163: if (index > start) {
164: final String line = input.substring(start, index);
165: if (index < input.length() - 1) {
166: result.addLine(line, font, paint);
167: input = input.substring(index + 1);
168: }
169: else {
170: moreInputToProcess = false;
171: }
172: }
173: else if (index == start) {
174: if (index < input.length() - 1) {
175: input = input.substring(index + 1);
176: }
177: else {
178: moreInputToProcess = false;
179: }
180: }
181: else {
182: result.addLine(input, font, paint);
183: moreInputToProcess = false;
184: }
185: }
186: return result;
187: }
188:
189:
202: public static TextBlock createTextBlock(final String text, final Font font,
203: final Paint paint, final float maxWidth,
204: final TextMeasurer measurer) {
205:
206: return createTextBlock(text, font, paint, maxWidth, Integer.MAX_VALUE,
207: measurer);
208: }
209:
210:
224: public static TextBlock createTextBlock(final String text, final Font font,
225: final Paint paint, final float maxWidth, final int maxLines,
226: final TextMeasurer measurer) {
227:
228: final TextBlock result = new TextBlock();
229: final BreakIterator iterator = BreakIterator.getLineInstance();
230: iterator.setText(text);
231: int current = 0;
232: int lines = 0;
233: final int length = text.length();
234: while (current < length && lines < maxLines) {
235: final int next = nextLineBreak(text, current, maxWidth, iterator,
236: measurer);
237: if (next == BreakIterator.DONE) {
238: result.addLine(text.substring(current), font, paint);
239: return result;
240: }
241: result.addLine(text.substring(current, next), font, paint);
242: lines++;
243: current = next;
244: while (current < text.length()&& text.charAt(current) == '\n') {
245: current++;
246: }
247: }
248: if (current < length) {
249: final TextLine lastLine = result.getLastLine();
250: final TextFragment lastFragment = lastLine.getLastTextFragment();
251: final String oldStr = lastFragment.getText();
252: String newStr = "...";
253: if (oldStr.length() > 3) {
254: newStr = oldStr.substring(0, oldStr.length() - 3) + "...";
255: }
256:
257: lastLine.removeFragment(lastFragment);
258: final TextFragment newFragment = new TextFragment(newStr,
259: lastFragment.getFont(), lastFragment.getPaint());
260: lastLine.addFragment(newFragment);
261: }
262: return result;
263: }
264:
265:
276: private static int nextLineBreak(final String text, final int start,
277: final float width, final BreakIterator iterator,
278: final TextMeasurer measurer) {
279:
280:
281:
282: int current = start;
283: int end;
284: float x = 0.0f;
285: boolean firstWord = true;
286: int newline = text.indexOf('\n', start);
287: if (newline < 0) {
288: newline = Integer.MAX_VALUE;
289: }
290: while (((end = iterator.next()) != BreakIterator.DONE)) {
291: if (end > newline) {
292: return newline;
293: }
294: x += measurer.getStringWidth(text, current, end);
295: if (x > width) {
296: if (firstWord) {
297: while (measurer.getStringWidth(text, start, end) > width) {
298: end--;
299: if (end <= start) {
300: return end;
301: }
302: }
303: return end;
304: }
305: else {
306: end = iterator.previous();
307: return end;
308: }
309: }
310:
311: firstWord = false;
312: current = end;
313: }
314: return BreakIterator.DONE;
315: }
316:
317:
327: public static Rectangle2D getTextBounds(final String text,
328: final Graphics2D g2, final FontMetrics fm) {
329:
330: final Rectangle2D bounds;
331: if (TextUtilities.useFontMetricsGetStringBounds) {
332: bounds = fm.getStringBounds(text, g2);
333:
334:
335:
336: LineMetrics lm = fm.getFont().getLineMetrics(text,
337: g2.getFontRenderContext());
338: bounds.setRect(bounds.getX(), bounds.getY(), bounds.getWidth(),
339: lm.getHeight());
340: }
341: else {
342: final double width = fm.stringWidth(text);
343: final double height = fm.getHeight();
344: if (logger.isDebugEnabled()) {
345: logger.debug("Height = " + height);
346: }
347: bounds = new Rectangle2D.Double(0.0, -fm.getAscent(), width,
348: height);
349: }
350: return bounds;
351: }
352:
353:
365: public static Rectangle2D drawAlignedString(final String text,
366: final Graphics2D g2, final float x, final float y,
367: final TextAnchor anchor) {
368:
369: final Rectangle2D textBounds = new Rectangle2D.Double();
370: final float[] adjust = deriveTextBoundsAnchorOffsets(g2, text, anchor,
371: textBounds);
372:
373: textBounds.setRect(x + adjust[0], y + adjust[1] + adjust[2],
374: textBounds.getWidth(), textBounds.getHeight());
375: g2.drawString(text, x + adjust[0], y + adjust[1]);
376: return textBounds;
377: }
378:
379:
395: private static float[] deriveTextBoundsAnchorOffsets(final Graphics2D g2,
396: final String text, final TextAnchor anchor,
397: final Rectangle2D textBounds) {
398:
399: final float[] result = new float[3];
400: final FontRenderContext frc = g2.getFontRenderContext();
401: final Font f = g2.getFont();
402: final FontMetrics fm = g2.getFontMetrics(f);
403: final Rectangle2D bounds = TextUtilities.getTextBounds(text, g2, fm);
404: final LineMetrics metrics = f.getLineMetrics(text, frc);
405: final float ascent = metrics.getAscent();
406: result[2] = -ascent;
407: final float halfAscent = ascent / 2.0f;
408: final float descent = metrics.getDescent();
409: final float leading = metrics.getLeading();
410: float xAdj = 0.0f;
411: float yAdj = 0.0f;
412:
413: if (anchor == TextAnchor.TOP_CENTER
414: || anchor == TextAnchor.CENTER
415: || anchor == TextAnchor.BOTTOM_CENTER
416: || anchor == TextAnchor.BASELINE_CENTER
417: || anchor == TextAnchor.HALF_ASCENT_CENTER) {
418:
419: xAdj = (float) -bounds.getWidth() / 2.0f;
420:
421: }
422: else if (anchor == TextAnchor.TOP_RIGHT
423: || anchor == TextAnchor.CENTER_RIGHT
424: || anchor == TextAnchor.BOTTOM_RIGHT
425: || anchor == TextAnchor.BASELINE_RIGHT
426: || anchor == TextAnchor.HALF_ASCENT_RIGHT) {
427:
428: xAdj = (float) -bounds.getWidth();
429:
430: }
431:
432: if (anchor == TextAnchor.TOP_LEFT
433: || anchor == TextAnchor.TOP_CENTER
434: || anchor == TextAnchor.TOP_RIGHT) {
435:
436: yAdj = -descent - leading + (float) bounds.getHeight();
437:
438: }
439: else if (anchor == TextAnchor.HALF_ASCENT_LEFT
440: || anchor == TextAnchor.HALF_ASCENT_CENTER
441: || anchor == TextAnchor.HALF_ASCENT_RIGHT) {
442:
443: yAdj = halfAscent;
444:
445: }
446: else if (anchor == TextAnchor.CENTER_LEFT
447: || anchor == TextAnchor.CENTER
448: || anchor == TextAnchor.CENTER_RIGHT) {
449:
450: yAdj = -descent - leading + (float) (bounds.getHeight() / 2.0);
451:
452: }
453: else if (anchor == TextAnchor.BASELINE_LEFT
454: || anchor == TextAnchor.BASELINE_CENTER
455: || anchor == TextAnchor.BASELINE_RIGHT) {
456:
457: yAdj = 0.0f;
458:
459: }
460: else if (anchor == TextAnchor.BOTTOM_LEFT
461: || anchor == TextAnchor.BOTTOM_CENTER
462: || anchor == TextAnchor.BOTTOM_RIGHT) {
463:
464: yAdj = -metrics.getDescent() - metrics.getLeading();
465:
466: }
467: if (textBounds != null) {
468: textBounds.setRect(bounds);
469: }
470: result[0] = xAdj;
471: result[1] = yAdj;
472: return result;
473:
474: }
475:
476:
485: public static void setUseDrawRotatedStringWorkaround(final boolean use) {
486: useDrawRotatedStringWorkaround = use;
487: }
488:
489:
501: public static void drawRotatedString(final String text, final Graphics2D g2,
502: final double angle, final float x, final float y) {
503: drawRotatedString(text, g2, x, y, angle, x, y);
504: }
505:
506:
520: public static void drawRotatedString(final String text, final Graphics2D g2,
521: final float textX, final float textY, final double angle,
522: final float rotateX, final float rotateY) {
523:
524: if ((text == null) || (text.equals(""))) {
525: return;
526: }
527:
528: final AffineTransform saved = g2.getTransform();
529:
530:
531: final AffineTransform rotate = AffineTransform.getRotateInstance(
532: angle, rotateX, rotateY);
533: g2.transform(rotate);
534:
535: if (useDrawRotatedStringWorkaround) {
536:
537: final TextLayout tl = new TextLayout(text, g2.getFont(),
538: g2.getFontRenderContext());
539: tl.draw(g2, textX, textY);
540: }
541: else {
542:
543: g2.drawString(text, textX, textY);
544: }
545: g2.setTransform(saved);
546:
547: }
548:
549:
562: public static void drawRotatedString(final String text,
563: final Graphics2D g2, final float x, final float y,
564: final TextAnchor textAnchor, final double angle,
565: final float rotationX, final float rotationY) {
566:
567: if (text == null || text.equals("")) {
568: return;
569: }
570: final float[] textAdj = deriveTextBoundsAnchorOffsets(g2, text,
571: textAnchor);
572: drawRotatedString(text, g2, x + textAdj[0], y + textAdj[1], angle,
573: rotationX, rotationY);
574: }
575:
576:
588: public static void drawRotatedString(final String text, final Graphics2D g2,
589: final float x, final float y, final TextAnchor textAnchor,
590: final double angle, final TextAnchor rotationAnchor) {
591:
592: if (text == null || text.equals("")) {
593: return;
594: }
595: final float[] textAdj = deriveTextBoundsAnchorOffsets(g2, text,
596: textAnchor);
597: final float[] rotateAdj = deriveRotationAnchorOffsets(g2, text,
598: rotationAnchor);
599: drawRotatedString(text, g2, x + textAdj[0], y + textAdj[1],
600: angle, x + textAdj[0] + rotateAdj[0],
601: y + textAdj[1] + rotateAdj[1]);
602:
603: }
604:
605:
619: public static Shape calculateRotatedStringBounds(final String text,
620: final Graphics2D g2, final float x, final float y,
621: final TextAnchor textAnchor, final double angle,
622: final TextAnchor rotationAnchor) {
623:
624: if (text == null || text.equals("")) {
625: return null;
626: }
627: final float[] textAdj = deriveTextBoundsAnchorOffsets(g2, text,
628: textAnchor);
629: if (logger.isDebugEnabled()) {
630: logger.debug("TextBoundsAnchorOffsets = " + textAdj[0] + ", "
631: + textAdj[1]);
632: }
633: final float[] rotateAdj = deriveRotationAnchorOffsets(g2, text,
634: rotationAnchor);
635: if (logger.isDebugEnabled()) {
636: logger.debug("RotationAnchorOffsets = " + rotateAdj[0] + ", "
637: + rotateAdj[1]);
638: }
639: final Shape result = calculateRotatedStringBounds(text, g2,
640: x + textAdj[0], y + textAdj[1], angle,
641: x + textAdj[0] + rotateAdj[0], y + textAdj[1] + rotateAdj[1]);
642: return result;
643:
644: }
645:
646:
659: private static float[] deriveTextBoundsAnchorOffsets(final Graphics2D g2,
660: final String text, final TextAnchor anchor) {
661:
662: final float[] result = new float[2];
663: final FontRenderContext frc = g2.getFontRenderContext();
664: final Font f = g2.getFont();
665: final FontMetrics fm = g2.getFontMetrics(f);
666: final Rectangle2D bounds = TextUtilities.getTextBounds(text, g2, fm);
667: final LineMetrics metrics = f.getLineMetrics(text, frc);
668: final float ascent = metrics.getAscent();
669: final float halfAscent = ascent / 2.0f;
670: final float descent = metrics.getDescent();
671: final float leading = metrics.getLeading();
672: float xAdj = 0.0f;
673: float yAdj = 0.0f;
674:
675: if (anchor == TextAnchor.TOP_CENTER
676: || anchor == TextAnchor.CENTER
677: || anchor == TextAnchor.BOTTOM_CENTER
678: || anchor == TextAnchor.BASELINE_CENTER
679: || anchor == TextAnchor.HALF_ASCENT_CENTER) {
680:
681: xAdj = (float) -bounds.getWidth() / 2.0f;
682:
683: }
684: else if (anchor == TextAnchor.TOP_RIGHT
685: || anchor == TextAnchor.CENTER_RIGHT
686: || anchor == TextAnchor.BOTTOM_RIGHT
687: || anchor == TextAnchor.BASELINE_RIGHT
688: || anchor == TextAnchor.HALF_ASCENT_RIGHT) {
689:
690: xAdj = (float) -bounds.getWidth();
691:
692: }
693:
694: if (anchor == TextAnchor.TOP_LEFT
695: || anchor == TextAnchor.TOP_CENTER
696: || anchor == TextAnchor.TOP_RIGHT) {
697:
698: yAdj = -descent - leading + (float) bounds.getHeight();
699:
700: }
701: else if (anchor == TextAnchor.HALF_ASCENT_LEFT
702: || anchor == TextAnchor.HALF_ASCENT_CENTER
703: || anchor == TextAnchor.HALF_ASCENT_RIGHT) {
704:
705: yAdj = halfAscent;
706:
707: }
708: else if (anchor == TextAnchor.CENTER_LEFT
709: || anchor == TextAnchor.CENTER
710: || anchor == TextAnchor.CENTER_RIGHT) {
711:
712: yAdj = -descent - leading + (float) (bounds.getHeight() / 2.0);
713:
714: }
715: else if (anchor == TextAnchor.BASELINE_LEFT
716: || anchor == TextAnchor.BASELINE_CENTER
717: || anchor == TextAnchor.BASELINE_RIGHT) {
718:
719: yAdj = 0.0f;
720:
721: }
722: else if (anchor == TextAnchor.BOTTOM_LEFT
723: || anchor == TextAnchor.BOTTOM_CENTER
724: || anchor == TextAnchor.BOTTOM_RIGHT) {
725:
726: yAdj = -metrics.getDescent() - metrics.getLeading();
727:
728: }
729: result[0] = xAdj;
730: result[1] = yAdj;
731: return result;
732:
733: }
734:
735:
746: private static float[] deriveRotationAnchorOffsets(final Graphics2D g2,
747: final String text, final TextAnchor anchor) {
748:
749: final float[] result = new float[2];
750: final FontRenderContext frc = g2.getFontRenderContext();
751: final LineMetrics metrics = g2.getFont().getLineMetrics(text, frc);
752: final FontMetrics fm = g2.getFontMetrics();
753: final Rectangle2D bounds = TextUtilities.getTextBounds(text, g2, fm);
754: final float ascent = metrics.getAscent();
755: final float halfAscent = ascent / 2.0f;
756: final float descent = metrics.getDescent();
757: final float leading = metrics.getLeading();
758: float xAdj = 0.0f;
759: float yAdj = 0.0f;
760:
761: if (anchor == TextAnchor.TOP_LEFT
762: || anchor == TextAnchor.CENTER_LEFT
763: || anchor == TextAnchor.BOTTOM_LEFT
764: || anchor == TextAnchor.BASELINE_LEFT
765: || anchor == TextAnchor.HALF_ASCENT_LEFT) {
766:
767: xAdj = 0.0f;
768:
769: }
770: else if (anchor == TextAnchor.TOP_CENTER
771: || anchor == TextAnchor.CENTER
772: || anchor == TextAnchor.BOTTOM_CENTER
773: || anchor == TextAnchor.BASELINE_CENTER
774: || anchor == TextAnchor.HALF_ASCENT_CENTER) {
775:
776: xAdj = (float) bounds.getWidth() / 2.0f;
777:
778: }
779: else if (anchor == TextAnchor.TOP_RIGHT
780: || anchor == TextAnchor.CENTER_RIGHT
781: || anchor == TextAnchor.BOTTOM_RIGHT
782: || anchor == TextAnchor.BASELINE_RIGHT
783: || anchor == TextAnchor.HALF_ASCENT_RIGHT) {
784:
785: xAdj = (float) bounds.getWidth();
786:
787: }
788:
789: if (anchor == TextAnchor.TOP_LEFT
790: || anchor == TextAnchor.TOP_CENTER
791: || anchor == TextAnchor.TOP_RIGHT) {
792:
793: yAdj = descent + leading - (float) bounds.getHeight();
794:
795: }
796: else if (anchor == TextAnchor.CENTER_LEFT
797: || anchor == TextAnchor.CENTER
798: || anchor == TextAnchor.CENTER_RIGHT) {
799:
800: yAdj = descent + leading - (float) (bounds.getHeight() / 2.0);
801:
802: }
803: else if (anchor == TextAnchor.HALF_ASCENT_LEFT
804: || anchor == TextAnchor.HALF_ASCENT_CENTER
805: || anchor == TextAnchor.HALF_ASCENT_RIGHT) {
806:
807: yAdj = -halfAscent;
808:
809: }
810: else if (anchor == TextAnchor.BASELINE_LEFT
811: || anchor == TextAnchor.BASELINE_CENTER
812: || anchor == TextAnchor.BASELINE_RIGHT) {
813:
814: yAdj = 0.0f;
815:
816: }
817: else if (anchor == TextAnchor.BOTTOM_LEFT
818: || anchor == TextAnchor.BOTTOM_CENTER
819: || anchor == TextAnchor.BOTTOM_RIGHT) {
820:
821: yAdj = metrics.getDescent() + metrics.getLeading();
822:
823: }
824: result[0] = xAdj;
825: result[1] = yAdj;
826: return result;
827:
828: }
829:
830:
845: public static Shape calculateRotatedStringBounds(final String text,
846: final Graphics2D g2, final float textX, final float textY,
847: final double angle, final float rotateX, final float rotateY) {
848:
849: if ((text == null) || (text.equals(""))) {
850: return null;
851: }
852: final FontMetrics fm = g2.getFontMetrics();
853: final Rectangle2D bounds = TextUtilities.getTextBounds(text, g2, fm);
854: final AffineTransform translate = AffineTransform.getTranslateInstance(
855: textX, textY);
856: final Shape translatedBounds = translate.createTransformedShape(bounds);
857: final AffineTransform rotate = AffineTransform.getRotateInstance(
858: angle, rotateX, rotateY);
859: final Shape result = rotate.createTransformedShape(translatedBounds);
860: return result;
861:
862: }
863:
864:
871: public static boolean getUseFontMetricsGetStringBounds() {
872: return useFontMetricsGetStringBounds;
873: }
874:
875:
882: public static void setUseFontMetricsGetStringBounds(final boolean use) {
883: useFontMetricsGetStringBounds = use;
884: }
885:
886:
892: public static boolean isUseDrawRotatedStringWorkaround() {
893: return useDrawRotatedStringWorkaround;
894: }
895: }