VerticalTextIcon.java
001 package gate.swing;
002 
003 import java.awt.*;
004 import java.beans.PropertyChangeEvent;
005 import java.beans.PropertyChangeListener;
006 
007 import javax.swing.Icon;
008 
009 /**
010  * VTextIcon is an Icon implementation which draws a short string vertically.
011  * It's useful for JTabbedPanes with LEFT or RIGHT tabs but can be used in any
012  * component which supports Icons, such as JLabel or JButton
013  * You can provide a hint to indicate whether to rotate the string
014  * to the left or right, or not at all, and it checks to make sure
015  * that the rotation is legal for the given string
016  * (for example, Chinese/Japanese/Korean scripts have special rules when
017  * drawn vertically and should never be rotated)
018  <hr>
019  * NOTE: This class was taken from <br>
020  * <a href="http://www.macdevcenter.com/pub/a/mac/2002/03/22/vertical_text.html">
021  * this location</a>.
022  <br>
023  * The author specifies:<br>
024  * &quot;Anybody can use the code for any purpose; I don't want any
025  * compensation, nor do I accept any liability.&quot;
026  *<hr>
027  @author Lee Ann Rucker &lt;LRucker@mac.com&gt;
028  *
029  */
030 public class VerticalTextIcon implements Icon, PropertyChangeListener {
031   String fLabel;
032   String[] fCharStrings; // for efficiency, break the fLabel into one-char strings to be passed to drawString
033   int[] fCharWidths; // Roman characters should be centered when not rotated (Japanese fonts are monospaced)
034   int[] fPosition; // Japanese half-height characters need to be shifted when drawn vertically
035   int fWidth, fHeight, fCharHeight, fDescent; // Cached for speed
036   int fRotation;
037   Component fComponent;
038 
039   static final int POSITION_NORMAL = 0;
040   static final int POSITION_TOP_RIGHT = 1;
041   static final int POSITION_FAR_TOP_RIGHT = 2;
042 
043   public static final int ROTATE_DEFAULT = 0x00;
044   public static final int ROTATE_NONE = 0x01;
045   public static final int ROTATE_LEFT = 0x02;
046   public static final int ROTATE_RIGHT = 0x04;
047 
048   /**
049    * Creates a <code>VTextIcon</code> for the specified <code>component</code>
050    * with the specified <code>label</code>.
051    * It sets the orientation to the default for the string
052    @see #verifyRotation
053    */
054   public VerticalTextIcon(Component component, String label) {
055     this(component, label, ROTATE_DEFAULT);
056   }
057 
058   /**
059    * Creates a <code>VTextIcon</code> for the specified <code>component</code>
060    * with the specified <code>label</code>.
061    * It sets the orientation to the provided value if it's legal for the string
062    @see #verifyRotation
063    */
064   public VerticalTextIcon(Component component, String label, int rotateHint) {
065     fComponent = component;
066     fLabel = label;
067     fRotation = verifyRotation(label, rotateHint);
068     calcDimensions();
069     fComponent.addPropertyChangeListener(this);
070   }
071 
072   /**
073    * sets the label to the given string, updating the orientation as needed
074    * and invalidating the layout if the size changes
075    @see #verifyRotation
076    */
077   public void setLabel(String label) {
078     fLabel = label;
079     fRotation = verifyRotation(label, fRotation)// Make sure the current rotation is still legal
080     recalcDimensions();
081   }
082 
083   /**
084    * Checks for changes to the font on the fComponent
085    * so that it can invalidate the layout if the size changes
086    */
087     @Override
088     public void propertyChange(PropertyChangeEvent e) {
089     String prop = e.getPropertyName();
090     if("font".equals(prop)) {
091       recalcDimensions();
092     }
093   }
094 
095   /**
096    * Calculates the dimensions.  If they've changed,
097    * invalidates the component
098    */
099   void recalcDimensions() {
100     int wOld = getIconWidth();
101     int hOld = getIconHeight();
102     calcDimensions();
103     if (wOld != getIconWidth() || hOld != getIconHeight())
104       fComponent.invalidate();
105   }
106 
107     void calcDimensions() {
108     FontMetrics fm = fComponent.getFontMetrics(fComponent.getFont());
109     fCharHeight = fm.getAscent() + fm.getDescent();
110     fDescent = fm.getDescent();
111     if (fRotation == ROTATE_NONE) {
112       int len = fLabel.length();
113       char data[] new char[len];
114       fLabel.getChars(0, len, data, 0);
115       // if not rotated, width is that of the widest char in the string
116       fWidth = 0;
117       // we need an array of one-char strings for drawString
118       fCharStrings = new String[len];
119       fCharWidths = new int[len];
120       fPosition = new int[len];
121       char ch;
122       for (int i = 0; i < len; i++) {
123         ch = data[i];
124         fCharWidths[i= fm.charWidth(ch);
125         if (fCharWidths[i> fWidth)
126           fWidth = fCharWidths[i];
127         fCharStrings[inew String(data, i, 1);
128         // small kana and punctuation
129         if (sDrawsInTopRight.indexOf(ch>= 0// if ch is in sDrawsInTopRight
130           fPosition[i= POSITION_TOP_RIGHT;
131         else if (sDrawsInFarTopRight.indexOf(ch>= 0)
132           fPosition[i= POSITION_FAR_TOP_RIGHT;
133         else
134           fPosition[i= POSITION_NORMAL;
135       }
136       // and height is the font height * the char count, + one extra leading at the bottom
137       fHeight = fCharHeight * len + fDescent;
138     }
139     else {
140       // if rotated, width is the height of the string
141       fWidth = fCharHeight;
142       // and height is the width, plus some buffer space
143       fHeight = fm.stringWidth(fLabel2*kBufferSpace;
144     }
145   }
146 
147    /**
148      * Draw the icon at the specified location.  Icon implementations
149      * may use the Component argument to get properties useful for
150      * painting, e.g. the foreground or background color.
151      */
152     @Override
153     public void paintIcon(Component c, Graphics g, int x, int y) {
154     // We don't insist that it be on the same Component
155     g.setColor(c.getForeground());
156     g.setFont(c.getFont());
157     if (fRotation == ROTATE_NONE) {
158       int yPos = y + fCharHeight;
159       for (int i = 0; i < fCharStrings.length; i++) {
160         // Special rules for Japanese - "half-height" characters (like ya, yu, yo in combinations)
161         // should draw in the top-right quadrant when drawn vertically
162         // - they draw in the bottom-left normally
163         int tweak;
164         switch (fPosition[i]) {
165           case POSITION_NORMAL:
166             // Roman fonts should be centered. Japanese fonts are always monospaced.
167             g.drawString(fCharStrings[i], x+((fWidth-fCharWidths[i])/2), yPos);
168             break;
169           case POSITION_TOP_RIGHT:
170             tweak = fCharHeight/3// Should be 2, but they aren't actually half-height
171             g.drawString(fCharStrings[i], x+(tweak/2), yPos-tweak);
172             break;
173           case POSITION_FAR_TOP_RIGHT:
174             tweak = fCharHeight - fCharHeight/3;
175             g.drawString(fCharStrings[i], x+(tweak/2), yPos-tweak);
176             break;
177         }
178         yPos += fCharHeight;
179       }
180     }
181     else if (fRotation == ROTATE_LEFT) {
182       g.translate(x+fWidth,y+fHeight);
183       ((Graphics2D)g).rotate(-NINETY_DEGREES);
184       g.drawString(fLabel, kBufferSpace, -fDescent);
185       ((Graphics2D)g).rotate(NINETY_DEGREES);
186       g.translate(-(x+fWidth),-(y+fHeight));
187     }
188     else if (fRotation == ROTATE_RIGHT) {
189       g.translate(x,y);
190       ((Graphics2D)g).rotate(NINETY_DEGREES);
191       g.drawString(fLabel, kBufferSpace, -fDescent);
192       ((Graphics2D)g).rotate(-NINETY_DEGREES);
193       g.translate(-x,-y);
194     }
195 
196   }
197 
198     /**
199      * Returns the icon's width.
200      *
201      @return an int specifying the fixed width of the icon.
202      */
203     @Override
204     public int getIconWidth() {
205     return fWidth;
206   }
207 
208     /**
209      * Returns the icon's height.
210      *
211      @return an int specifying the fixed height of the icon.
212      */
213     @Override
214     public int getIconHeight() {
215     return fHeight;
216   }
217 
218   /**
219      verifyRotation
220 
221     returns the best rotation for the string (ROTATE_NONE, ROTATE_LEFT, ROTATE_RIGHT)
222 
223     This is public static so you can use it to test a string without creating a VTextIcon
224 
225    from http://www.unicode.org/unicode/reports/tr9/tr9-3.html
226    When setting text using the Arabic script in vertical lines,
227    it is more common to employ a horizontal baseline that
228    is rotated by 90� counterclockwise so that the characters
229    are ordered from top to bottom. Latin text and numbers
230    may be rotated 90� clockwise so that the characters
231    are also ordered from top to bottom.
232 
233     Rotation rules
234     - Roman can rotate left, right, or none - default right (counterclockwise)
235     - CJK can't rotate
236     - Arabic must rotate - default left (clockwise)
237 
238    from the online edition of _The Unicode Standard, Version 3.0_, file ch10.pdf page 4
239    Ideographs are found in three blocks of the Unicode Standard...
240    U+4E00-U+9FFF, U+3400-U+4DFF, U+F900-U+FAFF
241 
242    Hiragana is U+3040-U+309F, katakana is U+30A0-U+30FF
243 
244    from http://www.unicode.org/unicode/faq/writingdirections.html
245    East Asian scripts are frequently written in vertical lines
246    which run from top-to-bottom and are arrange columns either
247    from left-to-right (Mongolian) or right-to-left (other scripts).
248    Most characters use the same shape and orientation when displayed
249    horizontally or vertically, but many punctuation characters
250    will change their shape when displayed vertically.
251 
252    Letters and words from other scripts are generally rotated through
253    ninety degree angles so that they, too, will read from top to bottom.
254    That is, letters from left-to-right scripts will be rotated clockwise
255    and letters from right-to-left scripts counterclockwise, both
256    through ninety degree angles.
257 
258   Unlike the bidirectional case, the choice of vertical layout
259   is usually treated as a formatting style; therefore,
260   the Unicode Standard does not define default rendering behavior
261   for vertical text nor provide directionality controls designed to override such behavior
262 
263    */
264   public static int verifyRotation(String label, int rotateHint) {
265     boolean hasCJK = false;
266     boolean hasMustRotate = false// Arabic, etc
267 
268     int len = label.length();
269     char data[] new char[len];
270     char ch;
271     label.getChars(0, len, data, 0);
272     for (int i = 0; i < len; i++) {
273       ch = data[i];
274       if ((ch >= '\u4E00' && ch <= '\u9FFF'||
275         (ch >= '\u3400' && ch <= '\u4DFF'||
276         (ch >= '\uF900' && ch <= '\uFAFF'||
277         (ch >= '\u3040' && ch <= '\u309F'||
278         (ch >= '\u30A0' && ch <= '\u30FF') )
279          hasCJK = true;
280       if ((ch >= '\u0590' && ch <= '\u05FF'|| // Hebrew
281         (ch >= '\u0600' && ch <= '\u06FF'|| // Arabic
282         (ch >= '\u0700' && ch <= '\u074F') ) // Syriac
283          hasMustRotate = true;
284     }
285     // If you mix Arabic with Chinese, you're on your own
286     if (hasCJK)
287       return DEFAULT_CJK;
288 
289     int legal = hasMustRotate ? LEGAL_MUST_ROTATE : LEGAL_ROMAN;
290     if ((rotateHint & legal0)
291       return rotateHint;
292 
293     // The hint wasn't legal, or it was zero
294     return hasMustRotate ? DEFAULT_MUST_ROTATE : DEFAULT_ROMAN;
295   }
296 
297   // The small kana characters and Japanese punctuation that draw in the top right quadrant:
298   // small a, i, u, e, o, tsu, ya, yu, yo, wa  (katakana only) ka ke
299   static final String sDrawsInTopRight =
300     "\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E" // hiragana
301     "\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6"// katakana
302   static final String sDrawsInFarTopRight = "\u3001\u3002"// comma, full stop
303 
304   static final int DEFAULT_CJK = ROTATE_NONE;
305   static final int LEGAL_ROMAN = ROTATE_NONE | ROTATE_LEFT | ROTATE_RIGHT;
306   static final int DEFAULT_ROMAN = ROTATE_RIGHT;
307   static final int LEGAL_MUST_ROTATE = ROTATE_LEFT | ROTATE_RIGHT;
308   static final int DEFAULT_MUST_ROTATE = ROTATE_LEFT;
309 
310   static final double NINETY_DEGREES = Math.toRadians(90.0);
311   static final int kBufferSpace = 5;
312 }