KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openlaszlo > compiler > TextCompiler


1 /*****************************************************************************
2  * TextCompiler.java
3  * ****************************************************************************/

4
5 /* J_LZ_COPYRIGHT_BEGIN *******************************************************
6 * Copyright 2001-2004 Laszlo Systems, Inc. All Rights Reserved. *
7 * Use is subject to license terms. *
8 * J_LZ_COPYRIGHT_END *********************************************************/

9
10 package org.openlaszlo.compiler;
11 import org.openlaszlo.utils.ChainedException;
12 import java.io.*;
13 import java.util.*;
14 import org.jdom.Attribute;
15 import org.jdom.Comment;
16 import org.jdom.CDATA;
17 import org.jdom.Document;
18 import org.jdom.Element;
19 import org.jdom.EntityRef;
20 import org.jdom.Text;
21 import org.jdom.output.XMLOutputter;
22 import java.util.Iterator JavaDoc;
23 import org.apache.log4j.*;
24 import org.openlaszlo.iv.flash.api.text.Font;
25 import java.awt.geom.Rectangle2D JavaDoc;
26
27 /** Utility functions for measuring HTML content, and translating it into Flash strings.
28  *
29  * @author <a HREF="mailto:hminsky@laszlosystems.com">Henry Minsky</a>
30  */

31 abstract class TextCompiler {
32
33     private static Logger mLogger = Logger.getLogger(TextCompiler.class);
34     private static Logger mTextLogger = Logger.getLogger("lps.text");
35
36     public static double computeTextWidth(String JavaDoc text, FontInfo fontInfo, SWFWriter generator)
37       throws CompilationError {
38         LineMetrics lm = new LineMetrics();
39         return computeTextWidth(text, fontInfo, generator, lm);
40     }
41
42     /** Check if a specified font is known by the Font Manager
43      *
44      * @param manager a font manager
45      * @param fontInfo the font spec you want to check
46      *
47      * This will throw an informative CompilationError if the font does not exist.
48      */

49      public static void checkFontExists (SWFWriter generator, FontInfo fontInfo) {
50         String JavaDoc fontName = fontInfo.getName();
51         int size = fontInfo.getSize();
52         int style = fontInfo.styleBits;
53
54         if (!generator.checkFontExists(fontInfo)) {
55             throw new CompilationError(
56                 "Can't find font "
57                 + fontName
58                 + " of style "
59                 + fontInfo.getStyle());
60         }
61     }
62
63     /**
64      * Compute text width for a given font
65      *
66      * @param text text stringtext string
67      * @param fontInfo font info for this text
68      * @return text width in pixels
69      */

70     public static double computeTextWidth(String JavaDoc text, FontInfo fontInfo, SWFWriter generator, LineMetrics lm)
71         throws CompilationError {
72
73         boolean trace = false; //mProperties.getProperty("trace.fonts", "false") == "true";
74

75         String JavaDoc fontName = fontInfo.getName();
76         int size = fontInfo.getSize();
77         int style = fontInfo.styleBits;
78
79         mTextLogger.debug("computeTextWidth fontName " + fontName +
80           " (style: " + fontInfo.getStyle() +
81           ", size: " + fontInfo.getSize() +
82           ") text: " + text);
83
84         if (text.length() == 0) {
85             return 0;
86         }
87
88         generator.checkFontExists(fontInfo);
89
90         FontFamily family = generator.getFontManager().getFontFamily(fontName);
91         if (family == null) {
92             throw new CompilationError("Can't find font " + fontName);
93         }
94
95         Font font = family.getStyle(style);
96         if (font == null) {
97             throw new CompilationError(
98                                        "Can't measure text because font "
99                                        + FontInfo.styleBitsToString(style) + " "
100                                        + fontName
101                                        + " is missing.");
102         }
103
104         Rectangle2D JavaDoc[] bounds = family.getBounds(style);
105
106         if (bounds == null) {
107             throw new CompilationError(
108                                        "Can't measure text because font "
109                                        + FontInfo.styleBitsToString(style) + " "
110                                        + fontName
111                                        + " is missing its bounds array.");
112         }
113
114         double width = 0;
115         int length = text.length();
116         char c = text.charAt(0);
117         int idx = font.getIndex(c);
118         int nextIdx;
119
120         double last_charwidth = 0;
121
122         // Cope with \n \r and missing characters? XXX
123
for(int i = 0; i < length; i++) {
124             if (idx == -1) {
125                 mLogger.warn("Character \'" + c +
126                              "\' (" + (int)c + ") not available in font " + fontName +
127                              " (style " +
128                              fontInfo.getStyle() + ")");
129                 continue;
130             } else {
131                 double adv = font.getAdvanceValue(idx);
132
133                 if (i == length - 1) {
134                     double m = 0;
135                     try {
136                         m = bounds[idx].getMaxX();
137                     } catch (Exception JavaDoc e) {
138                     }
139                     if (m > adv) {
140                         adv = m;
141                     }
142                 }
143
144                 if (i == 0) {
145                     try {
146                         double m = bounds[idx].getMinX();
147                         if (m > 0) {
148                             adv += m;
149                         }
150                     } catch (Exception JavaDoc e) {
151                     }
152                 }
153
154                 last_charwidth = adv;
155                 width += adv;
156
157                 mLogger.debug("adv " + adv);
158             }
159
160             if (i != length - 1) {
161                 c = text.charAt(i+1);
162                 nextIdx = font.getIndex(c);
163                 if (nextIdx != -1) {
164                     double cw = font.getKerning(idx, nextIdx);
165                     width += cw;
166                 }
167                 idx = nextIdx;
168             }
169         }
170         // Width in pixels
171
double w = (double)(width * fontInfo.getSize()) / 1024.0;
172
173         // If the last character was a space, remember it's width, as we may need
174
// to trim the trailing space from the HTML formatted text
175
if (c == ' ') {
176             lm.last_spacewidth = (double)(last_charwidth * fontInfo.getSize()) / 1024.0;
177         }
178
179         mTextLogger.debug("computeTextWidth: " + text +
180           " (font: " + fontInfo.getName() +
181           ", size: " + fontInfo.getSize() +
182           ", style: " + fontInfo.getStyle() +
183           ") has textwidth: " + w);
184
185         // FIXME: [2003-09-26 bloch] handle empty string case? should it be w/out slop?
186
// Match this in LzNewText.as
187
//
188
final int SLOP = 2;
189
190         return w + SLOP;
191     }
192
193     /** Compute the text width of a string. If there are multiple
194      * lines, return the maximum line width.
195      *
196      * <p>
197      *
198      * The only multi-line strings we will ever see here will be
199      * non-normalized text such as inside &ltpre;&gt; verbatim
200      * regions, because in normal running HTML text, the normalization
201      * will have stripped out newlines.
202      *
203      * <p>
204      *
205      * The LineMetrics holds state from possibly a previous text run
206      * on the same line, telling us whether we need to prepend an
207      * extra whitespace.
208      */

209     static double getTextWidth(String JavaDoc str, FontInfo fontInfo, SWFWriter generator,
210                                LineMetrics lm) {
211
212         double maxw = 0;
213         int lastpos = 0;
214         int nextpos = str.indexOf('\n');
215         String JavaDoc substr;
216
217         if (nextpos < 0) {
218             return computeTextWidth(str, fontInfo, generator, lm);
219         }
220         while (nextpos >= 0) {
221             substr = str.substring(lastpos, nextpos);
222             maxw = Math.max(maxw, computeTextWidth(substr, fontInfo, generator, lm));
223             lastpos = nextpos+1;
224             nextpos = str.indexOf('\n', lastpos);
225             lm.nlines++;
226         }
227
228         substr = str.substring(lastpos);
229         maxw = Math.max(maxw, computeTextWidth(substr, fontInfo, generator, lm));
230         return maxw;
231     }
232
233     /** Measure the content text allowing for "HTML" markup.
234      *
235      * This uses rules similar to how you would measure browser HTML text:
236      *
237      * <ul>
238      * <li> All text is whitespace normalized, except that which occurs between &lt;pre&gt; tags
239      * <li> Linebreaks occur only when &lt;br/&gt; or &lt;p/&gt; elements occur, or when a newline
240      * is present inside of a &lt;pre&gt; region.
241      * <li> When multiple text lines are present, the length of the longest line is returned.
242      * </ul>
243      */

244
245     static LineMetrics getElementWidth(Element e, FontInfo fontInfo, SWFWriter generator) {
246         LineMetrics lm = new LineMetrics();
247         getElementWidth(e, fontInfo, generator, lm);
248         lm.endOfLine();
249         // cache the normalized HTML content
250
((ElementWithLocationInfo) e).setHTMLContent(lm.getText());
251         return lm;
252     }
253
254     /** Gets the text content, with HTML normalization rules applied */
255     static String JavaDoc getHTMLContent(Element e) {
256         // check if the normalized text is cached
257
if ((e instanceof ElementWithLocationInfo) &&
258             ((ElementWithLocationInfo) e).getHTMLContent() != null) {
259             return ((ElementWithLocationInfo) e).getHTMLContent();
260         }
261
262         LineMetrics lm = new LineMetrics();
263         // Just use a dummy font info, we only care about the HTML
264
// text, not string widths
265
FontInfo fontInfo = new FontInfo("default", "8", "");
266         getElementWidth(e, fontInfo, null, lm);
267         lm.endOfLine();
268         return lm.getText();
269     }
270
271     /** Return text suitable for passing to Laszlo inputtext component.
272      * This means currently no HTML tags are supported except PRE
273      */

274     static String JavaDoc getInputText (Element e) {
275         String JavaDoc text = "";
276         for (Iterator JavaDoc iter = e.getContent().iterator();
277              iter.hasNext();) {
278             Object JavaDoc node = iter.next();
279             if (node instanceof Element) {
280                 Element child = (Element) node;
281                 String JavaDoc tagName = child.getName();
282                 if (tagName.equals("p") || tagName.equals("br")) {
283                     text += "\n";
284                 } else if (tagName.equals("pre")) {
285                     text += child.getText();
286                 } else {
287                     // ignore everything else
288
}
289             } else if ((node instanceof Text) || (node instanceof CDATA)) {
290                 if (node instanceof Text) {
291                     text += ((Text) node).getTextNormalize();
292                 } else {
293                     text += ((CDATA) node).getTextNormalize();
294                 }
295             }
296         }
297         return text;
298     }
299
300
301     /**
302         Processes the text content of the element. The element
303         content may contain XHTML markup elements, which we will
304         interpret as we map over the content. Normally, whitespace
305         will be normalized away. However, preformat &lt;pre&gt; tags
306         will cause the enclosed text to be treated as verbatim,
307         meaning means that whitespace and linebreaks will be
308         preserved.
309     
310         Supported XHTML markup is currently:
311         <ul>
312         <li> P, BR cause linebreaks
313         <li> PRE sets verbatim (literal whitespace) mode
314         <li> font face control: B, I, FONT tags modify the font
315         <li> A [href] indicates a hyperlink
316         </ul>
317
318     */

319     static void getElementWidth(Element e, FontInfo fontInfo, SWFWriter generator,
320                                 LineMetrics lm) {
321         for (Iterator JavaDoc iter = e.getContent().iterator();
322              iter.hasNext();) {
323             Object JavaDoc node = iter.next();
324             if (node instanceof Element) {
325                 Element child = (Element) node;
326                 String JavaDoc tagName = child.getName();
327
328                 if (tagName.equals("br")) {
329                     lm.newline(); // explicit linebreak
330
getElementWidth(child, fontInfo, generator, lm);
331                     if (!child.getText().equals("")) {
332                         lm.newline();
333                     }
334                 } else if (tagName.equals("p")) {
335                     lm.paragraphBreak();
336                     getElementWidth(child, fontInfo, generator, lm);
337                     lm.paragraphBreak();
338                 } else if (tagName.equals("pre")) {
339                     boolean prev = lm.verbatim;
340                     lm.setVerbatim(true);
341                     getElementWidth(child, fontInfo, generator, lm);
342                     lm.setVerbatim(prev);
343                 } else if (ViewSchema.isHTMLElement(child)) {
344                     FontInfo newInfo = new FontInfo(fontInfo);
345                     if (tagName.equals("b")) {
346                         newInfo.styleBits |= FontInfo.BOLD;
347                     } else if (tagName.equals("i")) {
348                         newInfo.styleBits |= FontInfo.ITALIC;
349                     } else if (tagName.equals("font")) {
350                         ViewCompiler.setFontInfo(newInfo, child);
351                     }
352                     lm.addStartTag(tagName, newInfo, generator);
353                     // print font-related attributes:
354
// face, size, color
355
// supported Flash HTML tags: http://www.macromedia.com/support/flash/ts/documents/htmltext.htm
356
for (Iterator JavaDoc attrs = child.getAttributes().iterator(); attrs.hasNext(); ) {
357                         Attribute attr = (Attribute) attrs.next();
358                         String JavaDoc name = attr.getName();
359                         String JavaDoc value = child.getAttributeValue(name);
360                         // TBD: [hqm nov-15-2002] The value ought to be quoted in case it contains double quotes
361
// (but no values of currently supported HTML tags will contain double quotes)
362
lm.addFormat(" "+name+"=\""+value+"\"");
363                     }
364                     lm.endStartTag();
365                     getElementWidth(child, newInfo, generator, lm);
366                     lm.addEndTag(tagName);
367                 }
368             } else if ((node instanceof Text) || (node instanceof CDATA)) {
369                 String JavaDoc rawtext;
370                 if (node instanceof Text) {
371                     rawtext = ((Text) node).getText();
372                 } else {
373                     rawtext = ((CDATA) node).getText();
374                 }
375                 if (lm.verbatim) {
376                     lm.addSpan(rawtext, fontInfo, generator);
377                 } else {
378                     // Apply HTML normalization rules to the text content.
379
if (rawtext.length() > 0) {
380                         // getTextNormalize turns an all-whitespace string into an empty string
381
String JavaDoc normalized_text;
382                         if (node instanceof Text) {
383                             normalized_text = ((Text) node).getTextNormalize();
384                         } else {
385                             normalized_text = ((CDATA) node).getTextNormalize();
386                         }
387                         lm.addHTML (rawtext, normalized_text, fontInfo, generator);
388                     }
389                 }
390             } else if (node instanceof EntityRef) {
391                 // EntityRefs don't seem to occur in our JDOM, they were all resolved
392
// to strings by the parser already
393
throw new RuntimeException JavaDoc("encountered unexpected EntityRef node in getElementWidth()");
394             }
395         }
396     }
397 }
398
Popular Tags