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