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 }