001: package com.xoetrope.swing;
002:
003: import java.awt.Font;
004: import java.awt.FontMetrics;
005: import java.awt.Graphics2D;
006: import java.awt.font.FontRenderContext;
007: import java.awt.font.LineBreakMeasurer;
008: import java.awt.font.TextLayout;
009: import java.awt.geom.AffineTransform;
010: import java.awt.geom.Area;
011: import java.awt.geom.Point2D;
012: import java.awt.geom.Rectangle2D;
013: import java.text.AttributedString;
014: import java.text.BreakIterator;
015: import net.xoetrope.xui.XAttributedComponent;
016:
017: /**
018: * Display text at a user defined angle
019: *
020: * <p> Copyright (c) Xoetrope Ltd., 2001-2006, This software is licensed under
021: * the GNU Public License (GPL), please see license.txt for more details. If
022: * you make commercial use of this software you must purchase a commercial
023: * license from Xoetrope.</p>
024: * <p> $Revision: 1.14 $</p>
025: */
026: public class XRotatedText extends XFlowedTextComponent implements
027: XAttributedComponent {
028: protected double angle;
029:
030: /**
031: * Create a new instance
032: */
033: public XRotatedText() {
034: angle = 45;
035: }
036:
037: /**
038: * Sets the angle of the control's text.
039: * The range is 0 to 3600.
040: * @param value the new angle
041: */
042: public void setAngle(int value) {
043: angle = value;
044:
045: repaint();
046: }
047:
048: /**
049: * Gets the angle of the control's text.
050: * The range is 0 to 3600.
051: * @return the new angle
052: */
053: public int getAngle() {
054: return (int) angle;
055: }
056:
057: /**
058: * Set one or more attributes of the component. Currently this handles the
059: * attributes
060: * <OL>
061: * <LI>angle, value=the angle of the text rotation in tenths of degrees</LI>
062: * </OL>
063: * @param attribName the attribute name
064: * @param attribValue the attribute value
065: * @return 0 for success, non zero otherwise
066: */
067: public int setAttribute(String attribName, Object attribValue) {
068: if (attribName.equals("angle"))
069: angle = new Integer((String) attribValue).intValue();
070:
071: super .setAttribute(attribName, attribValue);
072:
073: return 0;
074: }
075:
076: /**
077: * Render a text that wraps within the client area
078: * @param g2 the graphics context
079: * @param localCopy the string to render
080: * @param currentPos the current starting y position
081: * @param y the y offset into the client area
082: * @param columns the column areas
083: * @return the latest y position
084: */
085: protected double wrapString(Graphics2D g2, String localCopy,
086: double currentPos, double y, Area[] columns) {
087: if (localCopy.length() <= 0)
088: return y;
089:
090: AffineTransform oldTransform = g2.getTransform();
091: TextLayout layout;
092: Point2D.Double pen = new Point2D.Double(0, currentPos);
093: double radians = angle * Math.PI * 2.0 / 360.0;
094: AffineTransform atr = AffineTransform
095: .getRotateInstance(-radians);
096: AffineTransform at = AffineTransform.getTranslateInstance(-oX,
097: -oY);
098: Font font = getFont().deriveFont(atr);
099: FontMetrics fm = g2.getFontMetrics(font);
100: FontRenderContext frc = g2.getFontRenderContext();
101: Object[] cachedRefs = (Object[]) attributedStrings
102: .get(localCopy);
103:
104: AttributedString as;
105: int[] lengths;
106: if (cachedRefs == null) {
107: Object[] refs = new Object[2];
108: lengths = new int[1];
109: refs[0] = as = getAttributedString(localCopy, font, lengths);
110: refs[1] = lengths;
111: attributedStrings.put(localCopy, refs);
112: } else {
113: as = (AttributedString) cachedRefs[0];
114: lengths = (int[]) cachedRefs[1];
115: }
116: g2.setTransform(at);
117:
118: double cos = Math.cos(radians);
119: double sin = Math.sin(radians);
120: LineBreakMeasurer measurer = new LineBreakMeasurer(as
121: .getIterator(), BreakIterator.getWordInstance(), frc);
122: int ii = 0;
123: int len = lengths[0];
124: try {
125: for (int columnIdx = currentColumn; columnIdx < columns.length; columnIdx++) {
126: double colX = columns[columnIdx].getBounds().getX();
127: double prevWidth = oW / sin;
128: if ((columnIdx > 0) && (measurer.getPosition() < len))
129: pen.y = currentPos;
130:
131: while ((measurer.getPosition() < len)
132: && (pen.y < (oH - ascent))) {
133: ii++;
134: // Take a big horizontal stripe to allow for the rotation and translation
135: Rectangle2D.Double rLine = new Rectangle2D.Double(
136: -1000.0, oY + pen.y, 2000.0 + oW / sin, 1);//ascent );
137: Area aArea = new Area(rLine);
138: aArea.transform(AffineTransform
139: .getRotateInstance(-radians));
140: aArea.transform(AffineTransform
141: .getTranslateInstance(-sin * pen.y, pen.y
142: * sin));
143: aArea.intersect(columns[columnIdx]);
144:
145: Rectangle2D.Double rLine2 = new Rectangle2D.Double(
146: -1000.0, oY + pen.y + ascent - 1, 2000.0
147: + oW / sin, 1);//ascent );
148: Area aArea2 = new Area(rLine2);
149: aArea2.transform(AffineTransform
150: .getRotateInstance(-radians));
151: aArea2
152: .transform(AffineTransform
153: .getTranslateInstance(-sin
154: * (pen.y + ascent - 1),
155: pen.y * sin));
156: aArea2.intersect(columns[columnIdx]);
157:
158: Rectangle2D r = aArea.getBounds2D();
159: Rectangle2D r2 = aArea2.getBounds2D();
160: double w = Math.min(r.getWidth() - 1 / sin, r2
161: .getWidth()
162: - 1 / sin);
163: double layoutW = Math.max(0.0, w);
164: if (layoutW > 0.0)
165: layout = measurer.nextLayout((float) (layoutW));
166: else
167: layout = null;
168: pen.y += ascent;
169: prevWidth = layoutW;//Math.max( r.getWidth(), r2.getWidth() );
170:
171: //g2.draw( aArea );
172: if (layout == null)
173: continue;
174: else {
175: double deltaX = 0;
176: if (r.getX() > colX)
177: deltaX = ascent;
178:
179: layout.draw(g2, (float) (r2.getX() + sin
180: * ascent), (float) (r2.getY()
181: + r2.getHeight() - fm.getDescent()));
182: }
183:
184: // Only increment the current column index when something has actually
185: // been drawn in the column. If we get this far then something has
186: // been drawn
187: currentColumn = columnIdx;
188: }
189: currentPos = y;
190: }
191: } catch (Exception ex1) {
192: ex1.printStackTrace();
193: }
194: g2.setTransform(oldTransform);
195: return pen.y;
196: }
197: }
|