View Javadoc

1   // Copyright FreeHEP, 2000-2007
2   package org.freehep.graphics2d;
3   
4   import java.awt.Graphics2D;
5   import java.awt.font.TextAttribute;
6   import java.awt.font.TextLayout;
7   import java.awt.geom.AffineTransform;
8   import java.text.AttributedString;
9   import java.util.Hashtable;
10  import java.util.Stack;
11  import java.util.Vector;
12  
13  import org.freehep.graphics2d.font.FontUtilities;
14  
15  /**
16   * The class converts HTML tags like <u> in instances of {@link TextAttribute}.
17   *
18   * @author Mark Donszelmann
19   * @author Steffen Greiffenberg
20   * @version $Id: GenericTagHandler.java 12753 2007-06-12 22:32:31Z duns $
21   */
22  public class GenericTagHandler extends TagHandler {
23  
24      /**
25       * TextAttribute for overline, not a standard
26       */
27      public static Integer UNDERLINE_OVERLINE = new Integer(128);
28  
29      /**
30       * context to draw AttributedString
31       */
32      private Graphics2D graphics;
33  
34      /**
35       * text without any tags, e.g. "<sub>text</sub>" would become "text".
36       * filled by {@link #text(String)}
37       */
38      private StringBuffer clearedText;
39  
40      /**
41       * stores AttributeEntries created by {@link #closeTag(String)}
42       */
43      private Vector/*<AttributeEntry>*/ attributes;
44  
45      /**
46       * stores all open tags, e.g. "<sub>" an the position
47       * on which it placed in text. Filled by {@link #openTag(String)},
48       * emptied and translated into <code>attributes</code> by
49       * {@link #closeTag(String)}
50       */
51      private Hashtable tags;
52  
53      /**
54       * store the names of font families before they are changed by openTag()
55       */
56      private Stack/*<String>*/ fontFamilyStack;
57  
58      /**
59       * if we aplly TextAttribute.SUPERSCRIPT with correction of
60       * transformation the text is to high / to low by some points
61       */
62      private double superscriptCorrection;
63  
64      /**
65       * creates a tag handler for printing text and calculating its size
66       *
67       * @param graphics stores the font for calculations
68       */
69      public GenericTagHandler(Graphics2D graphics) {
70          super();
71          this.graphics = graphics;
72          this.clearedText = new StringBuffer();
73          this.tags = new Hashtable();
74      }
75  
76      /**
77       * prints the tagged string at x:y
78       *
79       * @param s string to print using the stored graphics
80       * @param x coordinate for drawing
81       * @param y coordinate for drawing
82       * @param superscriptCorrection correction for to high / to low text
83       */
84      public void print(TagString s, double x, double y, double superscriptCorrection) {
85  
86          fontFamilyStack = new Stack();
87  
88          this.clearedText = new StringBuffer();
89          this.attributes = new Vector();
90          this.superscriptCorrection = superscriptCorrection;
91  
92          parse(s);
93  
94          // close all open tags to ensure all
95          // open attributes are applied
96          while (tags.size() > 0) {
97              closeTag((String) tags.keys().nextElement());
98          }
99  
100         // create attributed string to print
101         // with current font settings
102         AttributedString attributedString = new AttributedString(
103             clearedText.toString(),
104             FontUtilities.getAttributes(graphics.getFont()));
105 
106         // aplly attributes
107         for (int i = 0; i < attributes.size(); i++) {
108             ((AttributeEntry)attributes.elementAt(i)).apply(attributedString);
109         }
110 
111         graphics.drawString(attributedString.getIterator(), (float)x, (float)y);
112     }
113 
114     /**
115      * calculates the string bounds using the current font of {@link #graphics}.
116      *
117      * @param s string to calculate
118      * @return bouding box after parsing s
119      */
120     public TextLayout createTextLayout(TagString s, double superscriptCorrection) {
121 
122         fontFamilyStack = new Stack();
123 
124         this.clearedText = new StringBuffer();
125         this.attributes = new Vector();
126         this.superscriptCorrection = superscriptCorrection;
127 
128         parse(s);
129 
130         // close all open tags to ensure all
131         // open attributes are applied
132         while (tags.size() > 0) {
133             closeTag((String) tags.keys().nextElement());
134         }
135 
136         // create attributed string to print
137         // with current font settings
138         AttributedString attributedString = new AttributedString(
139             clearedText.toString(),
140             FontUtilities.getAttributes(graphics.getFont()));
141 
142         // aplly attributes
143         for (int i = 0; i < attributes.size(); i++) {
144             ((AttributeEntry)attributes.elementAt(i)).apply(attributedString);
145         }
146 
147         // create the layout
148         return new TextLayout(
149             attributedString.getIterator(),
150             graphics.getFontRenderContext());
151     }
152 
153     /**
154      * handles bold <b>, italic <i>, superscript <sup>, subscript <sub>,
155      * vertical <v>, overline <over>, underline <u>, strikethrough <s>,
156      * underline dashed <udash>, underline dotted <udot> and typewriter <tt>
157      *
158      * @param tag one of the known tags, otherwise the overloaded methode is called
159      * @return empty string or the result of the overloaded method
160      */
161     // FIXME: check if we can support overline and vertical?
162     protected String openTag(String tag) {
163         // store position of parser for openening tag only if
164         // it the first openened, e.g. <b>text<b>text2</b> will draw
165         // text and text2 in bold weight
166         if (!tags.containsKey(tag)) {
167             tags.put(tag, new Integer(clearedText.length()));
168         }
169         return "";
170     }
171 
172     /**
173      * closes the given html tag. It doesn't matter, if that one was opened, so </udot>
174      * closes a <udash> too, because the use the same TextAttribute.UNDERLINE.
175      *
176      * @param tag to close
177      * @return empty string or the result of the overloaded method
178      */
179     protected String closeTag(String tag) {
180         // begin is stored in 'tags'
181         int begin;
182 
183         // do nothing if tag wasn't opened
184         if (!tags.containsKey(tag)) {
185             return super.closeTag(tag);
186         } else {
187             begin = ((Integer)tags.get(tag)).intValue();
188             tags.remove(tag);
189         }
190 
191         // change attributes
192         if (tag.equalsIgnoreCase("b")) {
193             this.attributes.add(new AttributeEntry(
194                 begin,
195                 clearedText.length(),
196                 TextAttribute.WEIGHT,
197                 TextAttribute.WEIGHT_BOLD));
198         } else if (tag.equalsIgnoreCase("i")) {
199             this.attributes.add(new AttributeEntry(
200                 begin,
201                 clearedText.length(),
202                 TextAttribute.POSTURE,
203                 TextAttribute.POSTURE_OBLIQUE));
204         } else if (tag.equalsIgnoreCase("s") || tag.equalsIgnoreCase("strike")) {
205             this.attributes.add(new AttributeEntry(
206                 begin,
207                 clearedText.length(),
208                 TextAttribute.STRIKETHROUGH,
209                 TextAttribute.STRIKETHROUGH_ON));
210         } else if (tag.equalsIgnoreCase("udash")) {
211             this.attributes.add(new AttributeEntry(
212                 begin,
213                 clearedText.length(),
214                 TextAttribute.UNDERLINE,
215                 TextAttribute.UNDERLINE_LOW_DASHED));
216         } else if (tag.equalsIgnoreCase("udot")) {
217             this.attributes.add(new AttributeEntry(
218                 begin,
219                 clearedText.length(),
220                 TextAttribute.UNDERLINE,
221                 TextAttribute.UNDERLINE_LOW_DOTTED));
222         } else if (tag.equalsIgnoreCase("u")) {
223             this.attributes.add(new AttributeEntry(
224                 begin,
225                 clearedText.length(),
226                 TextAttribute.UNDERLINE,
227                 TextAttribute.UNDERLINE_ON));
228         } else if (tag.equalsIgnoreCase("tt")) {
229             this.attributes.add(new AttributeEntry(
230                 begin,
231                 clearedText.length(),
232                 TextAttribute.FAMILY,
233                 fontFamilyStack.pop()));
234         } else if (tag.equalsIgnoreCase("v")) {
235             // vertical = false;
236         } else if (tag.equalsIgnoreCase("over")) {
237             this.attributes.add(new AttributeEntry(
238                 begin,
239                 clearedText.length(),
240                 TextAttribute.UNDERLINE,
241                 UNDERLINE_OVERLINE));
242         } else if (tag.equalsIgnoreCase("sup")) {
243 
244             // FIXME: not quite clear why this is necessary
245             this.attributes.add(new AttributeEntry(
246                 begin,
247                 clearedText.length(),
248                 TextAttribute.TRANSFORM,
249                 AffineTransform.getTranslateInstance(0, superscriptCorrection)));
250 
251             this.attributes.add(new AttributeEntry(
252                 begin,
253                 clearedText.length(),
254                 TextAttribute.SUPERSCRIPT,
255                 TextAttribute.SUPERSCRIPT_SUPER));
256 
257         } else if (tag.equalsIgnoreCase("sub")) {
258 
259             // FIXME: not quite clear why this is necessary
260             this.attributes.add(new AttributeEntry(
261                 begin,
262                 clearedText.length(),
263                 TextAttribute.TRANSFORM,
264                 AffineTransform.getTranslateInstance(0, -superscriptCorrection)));
265 
266             this.attributes.add(new AttributeEntry(
267                 begin,
268                 clearedText.length(),
269                 TextAttribute.SUPERSCRIPT,
270                 TextAttribute.SUPERSCRIPT_SUB));
271         } else {
272             return super.closeTag(tag);
273         }
274 
275         // set the font
276         return "";
277     }
278 
279     /**
280      * calculates miny und maxy for {@link #createTextLayout(TagString, double)}. If
281      * {@link #print} is set, text is drawed using
282      * {@link Graphics2D#drawString(String, float, float)} of
283      * {@link #graphics}
284      *
285      * @param text text to draw
286      * @return unmodified text parameter
287      */
288     protected String text(String text) {
289         // appand text as cleared
290         clearedText.append(text);
291 
292         return text;
293     }
294 
295     /**
296      * Helper to store an TextAttribute, its value and the range
297      * it should cover in <code>text</code>. Entries are created
298      * by {@link GenericTagHandler#closeTag(String)} and apllied
299      * in {@link GenericTagHandler#print(TagString, double, double, double)}
300      */
301     private class AttributeEntry {
302 
303         /**
304          * start offset in text
305          */
306         private int begin;
307 
308         /**
309          * end position for TextAttribute in text
310          */
311         private int end;
312 
313         /**
314          * the TextAttribute key to layout the text,
315          * e.g. TextAttribute.WEIGHT
316          */
317         private TextAttribute textAttribute;
318 
319         /**
320          * the TextAttribute key to layout the text,
321          * e.g. TextAttribute.WEIGHT_BOLD
322          */
323         private Object value;
324 
325         /**
326          * stores the given parameters
327          *
328          * @param begin
329          * @param end
330          * @param textAttribute
331          * @param value
332          */
333         protected AttributeEntry(int begin, int end, TextAttribute textAttribute, Object value) {
334             this.begin = begin;
335             this.end = end;
336             this.textAttribute = textAttribute;
337             this.value = value;
338         }
339 
340         /**
341          * apply the stored attributes to as
342          *
343          * @param as
344          */
345         protected void apply(AttributedString as) {
346             as.addAttribute(textAttribute,  value,  begin,  end);
347         }
348     }
349 }