KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openide > awt > HtmlRenderer


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.openide.awt;
21
22 import java.awt.Color JavaDoc;
23 import java.awt.Font JavaDoc;
24 import java.awt.FontMetrics JavaDoc;
25 import java.awt.Graphics JavaDoc;
26 import java.awt.Rectangle JavaDoc;
27 import java.awt.Shape JavaDoc;
28 import java.awt.font.LineMetrics JavaDoc;
29 import java.awt.geom.Area JavaDoc;
30 import java.awt.geom.Rectangle2D JavaDoc;
31 import java.util.Arrays JavaDoc;
32 import java.util.HashSet JavaDoc;
33 import java.util.Set JavaDoc;
34 import java.util.Stack JavaDoc;
35 import java.util.StringTokenizer JavaDoc;
36 import java.util.logging.Level JavaDoc;
37 import java.util.logging.Logger JavaDoc;
38 import javax.swing.Icon JavaDoc;
39 import javax.swing.JLabel JavaDoc;
40 import javax.swing.ListCellRenderer JavaDoc;
41 import javax.swing.SwingUtilities JavaDoc;
42 import javax.swing.UIManager JavaDoc;
43 import javax.swing.table.TableCellRenderer JavaDoc;
44 import javax.swing.tree.TreeCellRenderer JavaDoc;
45 import org.openide.util.Utilities;
46
47 /**
48  * A lightweight HTML renderer supporting a minimal subset of HTML used for
49  * markup purposes only: basic font styles and some colors.
50  * <p>
51  * Provides a generic cell renderer implementation which can be used for trees, tables,
52  * lists, combo boxes, etc.
53  * <p>
54  * If you only need to paint some HTML quickly, use the static methods for
55  * painting. These methods behave as follows:
56  * <ul>
57  * <li>{@link #renderString renderString} will check the string for opening HTML tags
58  * (upper or lower but not mixed case) and call either {@link #renderPlainString renderPlainString}
59  * or {@link #renderHTML renderHTML} as appropriate. Note this method does not tolerate
60  * whitespace in opening html tags - it expects exactly 6 characters to make up
61  * the opening tag if present.</li>
62  * <li>{@link #renderPlainString renderPlainString} simply renders a string to the graphics context,
63  * takes the same agruments as {@link #renderHTML renderHTML}, but will also honor
64  * {@link #STYLE_TRUNCATE}, so strings can be rendered with trailing
65  * elipsis if there is not enough space</li>
66  * <li>{@link #renderHTML renderHTML} renders whatever is passed to it as HTML, regardless
67  * of whether it has opening HTML tags or not. It can be used to render plain
68  * strings, but {@link #renderPlainString renderPlainString} is faster for that. It is useful
69  * if you want to render a string you <strong>know</strong> to be compliant
70  * HTML markup, but which does not have opening and closing HTML tags (though
71  * they are harmless if present). </li>
72  * </ul>
73  * <p>
74  * This parser is designed entirely for performance; there are no separate parsing
75  * and rendering loops. In order to achieve its performance, some trade-offs
76  * are required.
77  * <strong>This is not a forgiving HTML parser - the HTML supplied
78  * must follow the guidelines documented here!</strong>
79  * <p>
80  * The following tags are supported, in upper or lower (but not mixed) case:
81  * </p>
82  * <table border="1">
83  * <tr>
84  * <td><code>&lt;b&gt;</code></td>
85  * <td>Boldface text</td>
86  * </tr>
87  * <tr>
88  * <td><code>&lt;s&gt;</code></td>
89  * <td>Strikethrough text</td>
90  * </tr>
91  * <tr>
92  * <td><code>&lt;u&gt;</code></td>
93  * <td>Underline text</td>
94  * </tr>
95  * <tr>
96  * <td><code>&lt;i&gt;</code></td>
97  * <td>Italic text</td>
98  * </tr>
99  * <tr>
100  * <td><code>&lt;em&gt;</code></td>
101  * <td>Emphasized text (same as italic)</td>
102  * </tr>
103  * <tr>
104  * <td><code>&lt;strong&gt;</code></td>
105  * <td>Strong text (same as bold)</td>
106  * </tr>
107  * <tr>
108  * <td><code>&lt;font&gt;</code></td>
109  * <td>Font color - font attributes other than color are not supported. Colors
110  * may be specified as hexidecimal strings, such as #FF0000 or as logical colors
111  * defined in the current look and feel by specifying a ! character as the first
112  * character of the color name. Logical colors are colors available from the
113  * current look and feel's UIManager. For example,
114  * <code>&lt;font&nbsp;color="!Tree.background"&gt;</code> will set the font color to the
115  * result of {@link UIManager#getColor(Object) UIManager.getColor("Tree.background")}.
116  * <strong>Font size tags are not supported.</strong>
117  * </td>
118  * </tr>
119  * </table>
120  * <p>
121  * The lightweight HTML renderer supports the following named SGML character
122  * entities: <code>quot</code>, <code>lt</code>, <code>amp</code>, <code>lsquo</code>,
123  * <code>rsquo</code>, <code>ldquo</code>, <code>rdquo</code>, <code>ndash</code>,
124  * <code>mdash</code>, <code>ne</code>, <code>le</code>, <code>ge</code>,
125  * <code>copy</code>, <code>reg</code>, <code>trade</code>, and <code>nbsp</code>.
126  * It also supports numeric entities
127  * (e.g. <code>&amp;8822;</code>).
128  * <p><b>Why not use the JDK's HTML support?</b> The JDK's HTML support works
129  * well for stable components, but suffers from performance problems in the
130  * case of cell renderers - each call to set the text (which happens once per
131  * cell, per paint) causes a document tree to be created in memory. For small,
132  * markup-only strings, this is overkill. For rendering short strings
133  * (for example, in a tree or table cell renderer)
134  * with limited HTML, this method is approximately 10x faster than standard
135  * Swing HTML rendering.
136  *
137  * <P><B><U>Specifying logical colors</U></B><BR>
138  * Hardcoded text colors are undesirable, as they can be incompatible (even
139  * invisible) on some look and feels or themes, depending on the background
140  * color.
141  * The lightweight HTML renderer supports a non-standard syntax for specifying
142  * font colors via a key for a color in the UI defaults for the current look
143  * and feel. This is accomplished by prefixing the key name with a <code>!</code>
144  * character. For example: <code>&lt;font color='!controlShadow'&gt;</code>.
145  *
146  * <P><B><U>Modes of operation</U></B><BR>
147  * This method supports two modes of operation:
148  * <OL>
149  * <LI>{@link #STYLE_CLIP} - as much text as will fit in the pixel width passed
150  * to the method should be painted, and the text should be cut off at the maximum
151  * width or clip rectangle maximum X boundary for the graphics object, whichever is
152  * smaller.</LI>
153  * <LI>{@link #STYLE_TRUNCATE} - paint as much text as will fit in the pixel
154  * width passed to the method, but paint the last three characters as .'s, in the
155  * same manner as a JLabel truncates its text when the available space is too
156  * small.</LI>
157  * <!-- XXX and #STYLE_WORDWRAP? -->
158  * </OL>
159  * <P>
160  * The paint methods can also be used in non-painting mode to establish the space
161  * necessary to paint a string. This is accomplished by passing the value of the
162  * <code>paint</code> argument as false. The return value will be the required
163  * width in pixels
164  * to display the text. Note that in order to retrieve an
165  * accurate value, the argument for available width should be passed
166  * as {@link Integer#MAX_VALUE} or an appropriate maximum size - otherwise
167  * the return value will either be the passed maximum width or the required
168  * width, whichever is smaller. Also, the clip shape for the passed graphics
169  * object should be null or a value larger than the maximum possible render size,
170  * or text size measurement will stop at the clip bounds.
171  * <!-- XXX what does the following mean? <code>getGraphics</code>
172  * will always return non-null and non-clipped, and is suitable to pass in such a
173  * situation. -->
174  * <P>
175  *
176  * <P>
177  * <B>Example usages:</B><BR>
178  * <a HREF="@org-openide-nodes@/org/openide/nodes/Node.html#getHtmlDisplayName()">org.openide.nodes.Node.getHtmlDisplayName</a><BR>
179  * <a HREF="@org-openide-filesystems@/org/openide/filesystems/FileSystem.HtmlStatus.html">org.openide.filesystems.FileSystem.HtmlStatus</a>
180  * </P>
181  *
182  * @since 4.30
183  * @author Tim Boudreau
184  */

185 public final class HtmlRenderer {
186     private static HtmlRendererImpl LABEL = null;
187
188     /** Stack object used during HTML rendering to hold previous colors in
189      * the case of nested color entries. */

190     private static Stack JavaDoc<Color JavaDoc> colorStack = new Stack JavaDoc<Color JavaDoc>();
191
192     /**
193      * Constant used by {@link #renderString renderString}, {@link #renderPlainString renderPlainString},
194      * {@link #renderHTML renderHTML}, and {@link Renderer#setRenderStyle}
195      * if painting should simply be cut off at the boundary of the cooordinates passed.
196      */

197     public static final int STYLE_CLIP = 0;
198
199     /**
200      * Constant used by {@link #renderString renderString}, {@link #renderPlainString renderPlainString},
201      * {@link #renderHTML renderHTML}, and {@link Renderer#setRenderStyle} if
202      * painting should produce an ellipsis (...)
203      * if the text would overlap the boundary of the coordinates passed.
204      */

205     public static final int STYLE_TRUNCATE = 1;
206
207     /**
208      * Constant used by {@link #renderString renderString}, {@link #renderPlainString renderPlainString},
209      * {@link #renderHTML renderHTML}, and {@link Renderer#setRenderStyle}
210      * if painting should word wrap the text. In
211      * this case, the return value of any of the above methods will be the
212      * height, rather than width painted.
213      */

214     private static final int STYLE_WORDWRAP = 2;
215
216     /** System property to cause exceptions to be thrown when unparsable
217      * html is encountered */

218     private static final boolean STRICT_HTML = Boolean.getBoolean("netbeans.lwhtml.strict"); //NOI18N
219

220     /** Cache for strings which have produced errors, so we don't post an
221      * error message more than once */

222     private static Set JavaDoc<String JavaDoc> badStrings = null;
223
224     private static Logger JavaDoc LOG = Logger.getLogger(HtmlRenderer.class.getName());
225
226     /** Definitions for a limited subset of SGML character entities */
227     private static final Object JavaDoc[] entities = new Object JavaDoc[] {
228             new char[] { 'g', 't' }, new char[] { 'l', 't' }, //NOI18N
229
new char[] { 'q', 'u', 'o', 't' }, new char[] { 'a', 'm', 'p' }, //NOI18N
230
new char[] { 'l', 's', 'q', 'u', 'o' }, //NOI18N
231
new char[] { 'r', 's', 'q', 'u', 'o' }, //NOI18N
232
new char[] { 'l', 'd', 'q', 'u', 'o' }, //NOI18N
233
new char[] { 'r', 'd', 'q', 'u', 'o' }, //NOI18N
234
new char[] { 'n', 'd', 'a', 's', 'h' }, //NOI18N
235
new char[] { 'm', 'd', 'a', 's', 'h' }, //NOI18N
236
new char[] { 'n', 'e' }, //NOI18N
237
new char[] { 'l', 'e' }, //NOI18N
238
new char[] { 'g', 'e' }, //NOI18N
239
new char[] { 'c', 'o', 'p', 'y' }, //NOI18N
240
new char[] { 'r', 'e', 'g' }, //NOI18N
241
new char[] { 't', 'r', 'a', 'd', 'e' }, //NOI18N
242
new char[] { 'n', 'b', 's', 'p' //NOI18N
243
}
244         }; //NOI18N
245

246     /** Mappings for the array of SGML character entities to characters */
247     private static final char[] entitySubstitutions = new char[] {
248             '>', '<', '"', '&', 8216, 8217, 8220, 8221, 8211, 8212, 8800, 8804, 8805, //NOI18N
249
169, 174, 8482, ' '
250         };
251     private HtmlRenderer() {
252         //do nothing
253
}
254
255     /**
256      * Returns an instance of Renderer which may be used as a table/tree/list cell renderer.
257      * This method must be called on the AWT event thread. If you <strong>know</strong> you will
258      * be passing it legal HTML (legal as documented here), call {@link Renderer#setHtml setHtml(true)} on the
259      * result of this call <strong>after calling getNNNCellRenderer</strong> to provide this hint.
260      *
261      * @return A cell renderer that can render HTML.
262      */

263     public static final Renderer JavaDoc createRenderer() {
264         return new HtmlRendererImpl();
265     }
266
267     /**
268      * For HTML rendering jobs outside of trees/lists/tables, returns a JLabel which will paint its text using
269      * the lightweight HTML renderer. The result of this call will implement {@link Renderer}.
270      * <strong>Do not add the result of this call to the AWT hierarchy</strong>. It is not a general purpose <code>JLabel</code>, and
271      * will not behave correctly. Use the result of this call to paint or to measure text. Example:
272      * <pre>
273      * private final JLabel label = HtmlRenderer.createLabel();
274      *
275      * public void paint(Graphics g) {
276      * // background/whatever painting code here...
277      * label.setText(someHtmlText);
278      * label.paint(g);
279      * }
280      * </pre>
281      *
282      *
283      * @return a label which can render a subset of HTML very quickly
284      */

285     public static final JLabel JavaDoc createLabel() {
286         return new HtmlRendererImpl();
287     }
288
289     /**
290      * Render a string to a graphics instance, using the same API as {@link #renderHTML renderHTML}.
291      * Can render a string using JLabel-style ellipsis (...) in the case that
292      * it will not fit in the passed rectangle, if the style parameter is
293      * {@link #STYLE_CLIP}. Returns the width in pixels successfully painted.
294      * <strong>This method is not thread-safe and should not be called off
295      * the AWT thread.</strong>
296      *
297      * @see #renderHTML
298      */

299     public static double renderPlainString(
300         String JavaDoc s, Graphics JavaDoc g, int x, int y, int w, int h, Font JavaDoc f, Color JavaDoc defaultColor, int style, boolean paint
301     ) {
302         //per Jarda's request, keep the word wrapping code but don't expose it.
303
if ((style < 0) || (style > 1)) {
304             throw new IllegalArgumentException JavaDoc("Unknown rendering mode: " + style); //NOI18N
305
}
306
307         return _renderPlainString(s, g, x, y, w, h, f, defaultColor, style, paint);
308     }
309
310     private static double _renderPlainString(
311         String JavaDoc s, Graphics JavaDoc g, int x, int y, int w, int h, Font JavaDoc f, Color JavaDoc foreground, int style, boolean paint
312     ) {
313         if (f == null) {
314             f = UIManager.getFont("controlFont"); //NOI18N
315

316             if (f == null) {
317                 int fs = 11;
318                 Object JavaDoc cfs = UIManager.get("customFontSize"); //NOI18N
319

320                 if (cfs instanceof Integer JavaDoc) {
321                     fs = ((Integer JavaDoc) cfs).intValue();
322                 }
323
324                 f = new Font JavaDoc("Dialog", Font.PLAIN, fs); //NOI18N
325
}
326         }
327
328         FontMetrics JavaDoc fm = g.getFontMetrics(f);
329 // Rectangle2D r = fm.getStringBounds(s, g);
330
int wid;
331         if (Utilities.isMac()) {
332             // #54257 - on macosx + chinese/japanese fonts, the getStringBounds() method returns bad value
333
wid = fm.stringWidth(s);
334         } else {
335             wid = (int)fm.getStringBounds(s, g).getWidth();
336         }
337
338         if (paint) {
339             g.setColor(foreground);
340             g.setFont(f);
341
342             if ((wid <= w) || (style == STYLE_CLIP)) {
343                 g.drawString(s, x, y);
344             } else {
345                 char[] chars = s.toCharArray();
346
347                 if (chars.length == 0) {
348                     return 0;
349                 }
350
351                 double chWidth = wid / chars.length;
352                 int estCharsToPaint = new Double JavaDoc(w / chWidth).intValue();
353                 if( estCharsToPaint > chars.length )
354                     estCharsToPaint = chars.length;
355                 //let's correct the estimate now
356
while( estCharsToPaint > 3 ) {
357                     if( estCharsToPaint < chars.length )
358                         Arrays.fill(chars, estCharsToPaint - 3, estCharsToPaint, '.'); //NOI18N
359
int newWidth;
360                     if (Utilities.isMac()) {
361                         // #54257 - on macosx + chinese/japanese fonts, the getStringBounds() method returns bad value
362
newWidth = fm.stringWidth(new String JavaDoc(chars, 0, estCharsToPaint));
363                     } else {
364                         newWidth = (int)fm.getStringBounds(chars, 0, estCharsToPaint, g).getWidth();
365                     }
366                     if( newWidth <= w )
367                         break;
368                     estCharsToPaint--;
369                 }
370
371                 if (style == STYLE_TRUNCATE) {
372                     int length = estCharsToPaint;
373
374                     if (length <= 0) {
375                         return 0;
376                     }
377
378                     if (paint) {
379                         if (length > 3) {
380                             g.drawChars(chars, 0, length, x, y);
381                         } else {
382                             Shape JavaDoc shape = g.getClip();
383
384                             if (s != null) {
385                                 Area JavaDoc area = new Area JavaDoc(shape);
386                                 area.intersect(new Area JavaDoc(new Rectangle JavaDoc(x, y, w, h)));
387                                 g.setClip(area);
388                             } else {
389                                 g.setClip(new Rectangle JavaDoc(x, y, w, h));
390                             }
391
392                             g.drawString("...", x, y);
393                             g.setClip(shape);
394                         }
395                     }
396                 } else {
397                     //TODO implement plaintext word wrap if we want to support it at some point
398
}
399             }
400         }
401
402         return wid;
403     }
404
405     /**
406      * Render a string to a graphics context, using HTML markup if the string
407      * begins with <code>&lt;html&gt;</code>. Delegates to {@link #renderPlainString renderPlainString}
408      * or {@link #renderHTML renderHTML} as appropriate. See the class documentation for
409      * for details of the subset of HTML that is
410      * supported.
411      * @param s The string to render
412      * @param g A graphics object into which the string should be drawn, or which should be
413      * used for calculating the appropriate size
414      * @param x The x coordinate to paint at.
415      * @param y The y position at which to paint. Note that this method does not calculate font
416      * height/descent - this value should be the baseline for the line of text, not
417      * the upper corner of the rectangle to paint in.
418      * @param w The maximum width within which to paint.
419      * @param h The maximum height within which to paint.
420      * @param f The base font to be used for painting or calculating string width/height.
421      * @param defaultColor The base color to use if no font color is specified as html tags
422      * @param style The wrapping style to use, either {@link #STYLE_CLIP},
423      * or {@link #STYLE_TRUNCATE}
424      * @param paint True if actual painting should occur. If false, this method will not actually
425      * paint anything, only return a value representing the width/height needed to
426      * paint the passed string.
427      * @return The width in pixels required
428      * to paint the complete string, or the passed parameter <code>w</code> if it is
429      * smaller than the required width.
430      */

431     public static double renderString(
432         String JavaDoc s, Graphics JavaDoc g, int x, int y, int w, int h, Font JavaDoc f, Color JavaDoc defaultColor, int style, boolean paint
433     ) {
434         switch (style) {
435         case STYLE_CLIP:
436         case STYLE_TRUNCATE:
437             break;
438
439         default:
440             throw new IllegalArgumentException JavaDoc("Unknown rendering mode: " + style); //NOI18N
441
}
442
443         // System.err.println ("rps: " + y + " " + s);
444
if (s.startsWith("<html") || s.startsWith("<HTML")) { //NOI18N
445

446             return _renderHTML(s, 6, g, x, y, w, h, f, defaultColor, style, paint, null);
447         } else {
448             return renderPlainString(s, g, x, y, w, h, f, defaultColor, style, paint);
449         }
450     }
451
452     /**
453      * Render a string as HTML using a fast, lightweight renderer supporting a limited
454      * subset of HTML. See class Javadoc for details.
455      *
456      * <P>
457      * This method can also be used in non-painting mode to establish the space
458      * necessary to paint a string. This is accomplished by passing the value of the
459      * <code>paint</code> argument as false. The return value will be the required
460      * width in pixels
461      * to display the text. Note that in order to retrieve an
462      * accurate value, the argument for available width should be passed
463      * as {@link Integer#MAX_VALUE} or an appropriate maximum size - otherwise
464      * the return value will either be the passed maximum width or the required
465      * width, whichever is smaller. Also, the clip shape for the passed graphics
466      * object should be null or a value larger than the maximum possible render size.
467      * <P>
468      * This method will log a warning if it encounters HTML markup it cannot
469      * render. To aid diagnostics, if NetBeans is run with the argument
470      * <code>-J-Dnetbeans.lwhtml.strict=true</code> an exception will be thrown
471      * when an attempt is made to render unsupported HTML.
472      * @param s The string to render
473      * @param g A graphics object into which the string should be drawn, or which should be
474      * used for calculating the appropriate size
475      * @param x The x coordinate to paint at.
476      * @param y The y position at which to paint. Note that this method does not calculate font
477      * height/descent - this value should be the baseline for the line of text, not
478      * the upper corner of the rectangle to paint in.
479      * @param w The maximum width within which to paint.
480      * @param h The maximum height within which to paint.
481      * @param f The base font to be used for painting or calculating string width/height.
482      * @param defaultColor The base color to use if no font color is specified as html tags
483      * @param style The wrapping style to use, either {@link #STYLE_CLIP},
484      * or {@link #STYLE_TRUNCATE}
485      * @param paint True if actual painting should occur. If false, this method will not actually
486      * paint anything, only return a value representing the width/height needed to
487      * paint the passed string.
488      * @return The width in pixels required
489      * to paint the complete string, or the passed parameter <code>w</code> if it is
490      * smaller than the required width.
491      */

492     public static double renderHTML(
493         String JavaDoc s, Graphics JavaDoc g, int x, int y, int w, int h, Font JavaDoc f, Color JavaDoc defaultColor, int style, boolean paint
494     ) {
495         //per Jarda's request, keep the word wrapping code but don't expose it.
496
if ((style < 0) || (style > 1)) {
497             throw new IllegalArgumentException JavaDoc("Unknown rendering mode: " + style); //NOI18N
498
}
499
500         return _renderHTML(s, 0, g, x, y, w, h, f, defaultColor, style, paint, null);
501     }
502
503     /** Implementation of HTML rendering */
504     static double _renderHTML(
505         String JavaDoc s, int pos, Graphics JavaDoc g, int x, int y, int w, int h, Font JavaDoc f, Color JavaDoc defaultColor, int style, boolean paint,
506         Color JavaDoc background
507     ) {
508         // System.err.println ("rhs: " + y + " " + s);
509
if (f == null) {
510             f = UIManager.getFont("controlFont"); //NOI18N
511

512             if (f == null) {
513                 int fs = 11;
514                 Object JavaDoc cfs = UIManager.get("customFontSize"); //NOI18N
515

516                 if (cfs instanceof Integer JavaDoc) {
517                     fs = ((Integer JavaDoc) cfs).intValue();
518                 }
519
520                 f = new Font JavaDoc("Dialog", Font.PLAIN, fs); //NOI18N
521
}
522         }
523
524         //Thread safety - avoid allocating memory for the common case
525
Stack JavaDoc<Color JavaDoc> colorStack = SwingUtilities.isEventDispatchThread() ? HtmlRenderer.colorStack : new Stack JavaDoc<Color JavaDoc>();
526
527         g.setColor(defaultColor);
528         g.setFont(f);
529
530         char[] chars = s.toCharArray();
531         int origX = x;
532         boolean done = false; //flag if rendering completed, either by finishing the string or running out of space
533
boolean inTag = false; //flag if the current position is inside a tag, and the tag should be processed rather than rendering
534
boolean inClosingTag = false; //flag if the current position is inside a closing tag
535
boolean strikethrough = false; //flag if a strikethrough line should be painted
536
boolean underline = false; //flag if an underline should be painted
537
boolean bold = false; //flag if text is currently bold
538
boolean italic = false; //flag if text is currently italic
539
boolean truncated = false; //flag if the last possible character has been painted, and the next loop should paint "..." and return
540
double widthPainted = 0; //the total width painted, for calculating needed space
541
double heightPainted = 0; //the total height painted, for calculating needed space
542
boolean lastWasWhitespace = false; //flag to skip additional whitespace if one whitespace char already painted
543
double lastHeight = 0; //the last line height, for calculating total required height
544

545         double dotWidth = 0;
546
547         //Calculate the width of a . character if we may need to truncate
548
if (style == STYLE_TRUNCATE) {
549             dotWidth = g.getFontMetrics().charWidth('.'); //NOI18N
550
}
551
552         /* How this all works, for anyone maintaining this code (hopefully it will
553           never need it):
554           1. The string is converted to a char array
555           2. Loop over the characters. Variable pos is the current point.
556             2a. See if we're in a tag by or'ing inTag with currChar == '<'
557               If WE ARE IN A TAG:
558                2a1: is it an opening tag?
559                  If YES:
560                    - Identify the tag, Configure the Graphics object with
561                      the appropriate font, color, etc. Set pos = the first
562                      character after the tag
563                  If NO (it's a closing tag)
564                    - Identify the tag. Reconfigure the Graphics object
565                      with the state it should be in outside the tag
566                      (reset the font if italic, pop a color off the stack, etc.)
567             2b. If WE ARE NOT IN A TAG
568                - Locate the next < or & character or the end of the string
569                - Paint the characters using the Graphics object
570                - Check underline and strikethrough tags, and paint line if
571                  needed
572             See if we're out of space, and do the right thing for the style
573             (paint ..., give up or skip to the next line)
574          */

575         //Clear any junk left behind from a previous rendering loop
576
colorStack.clear();
577
578         //Enter the painting loop
579
while (!done) {
580             if (pos == s.length()) {
581                 return widthPainted;
582             }
583
584             //see if we're in a tag
585
try {
586                 inTag |= (chars[pos] == '<');
587             } catch (ArrayIndexOutOfBoundsException JavaDoc e) {
588                 //Should there be any problem, give a meaningful enough
589
//message to reproduce the problem
590
ArrayIndexOutOfBoundsException JavaDoc aib = new ArrayIndexOutOfBoundsException JavaDoc(
591                         "HTML rendering failed at position " + pos + " in String \"" //NOI18N
592
+s + "\". Please report this at http://www.netbeans.org"
593                     ); //NOI18N
594

595                 if (STRICT_HTML) {
596                     throw aib;
597                 } else {
598                     Logger.getLogger(HtmlRenderer.class.getName()).log(Level.WARNING, null, aib);
599
600                     return renderPlainString(s, g, x, y, w, h, f, defaultColor, style, paint);
601                 }
602             }
603
604             inClosingTag = inTag && ((pos + 1) < chars.length) && (chars[pos + 1] == '/'); //NOI18N
605

606             if (truncated) {
607                 //Then we've almost run out of space, time to print ... and quit
608
g.setColor(defaultColor);
609                 g.setFont(f);
610
611                 if (paint) {
612                     g.drawString("...", x, y); //NOI18N
613
}
614
615                 done = true;
616             } else if (inTag) {
617                 //If we're in a tag, don't paint, process it
618
pos++;
619
620                 int tagEnd = pos;
621
622                 //#54237 - if done and end of string -> wrong html
623
done = tagEnd >= (chars.length - 1);
624
625                 while (!done && (chars[tagEnd] != '>')) {
626                     done = tagEnd == (chars.length - 1);
627                     tagEnd++;
628                 }
629
630                 if (done) {
631                     throw new IllegalArgumentException JavaDoc("HTML rendering failed on string \"" + s + "\"");
632                 }
633
634                 if (inClosingTag) {
635                     //Handle closing tags by resetting the Graphics object (font, etc.)
636
pos++;
637
638                     switch (chars[pos]) {
639                     case 'P': //NOI18N
640
case 'p': //NOI18N
641
case 'H': //NOI18N
642
case 'h':
643                         break; //ignore html opening/closing tags
644

645                     case 'B': //NOI18N
646
case 'b': //NOI18N
647

648                         if ((chars[pos + 1] == 'r') || (chars[pos + 1] == 'R')) {
649                             break;
650                         }
651
652                         if (!bold) {
653                             throwBadHTML("Closing bold tag w/o " + //NOI18N
654
"opening bold tag", pos, chars
655                             ); //NOI18N
656
}
657
658                         if (italic) {
659                             g.setFont(deriveFont(f, Font.ITALIC));
660                         } else {
661                             g.setFont(deriveFont(f, Font.PLAIN));
662                         }
663
664                         bold = false;
665
666                         break;
667
668                     case 'E': //NOI18N
669
case 'e': //em tag
670
case 'I': //NOI18N
671
case 'i': //NOI18N
672

673                         if (bold) {
674                             g.setFont(deriveFont(f, Font.BOLD));
675                         } else {
676                             g.setFont(deriveFont(f, Font.PLAIN));
677                         }
678
679                         if (!italic) {
680                             throwBadHTML("Closing italics tag w/o" //NOI18N
681
+"opening italics tag", pos, chars
682                             ); //NOI18N
683
}
684
685                         italic = false;
686
687                         break;
688
689                     case 'S': //NOI18N
690
case 's': //NOI18N
691

692                         switch (chars[pos + 1]) {
693                         case 'T': //NOI18N
694
case 't':
695
696                             if (italic) { //NOI18N
697
g.setFont(deriveFont(f, Font.ITALIC));
698                             } else {
699                                 g.setFont(deriveFont(f, Font.PLAIN));
700                             }
701
702                             bold = false;
703
704                             break;
705
706                         case '>': //NOI18N
707
strikethrough = false;
708
709                             break;
710                         }
711
712                         break;
713
714                     case 'U': //NOI18N
715
case 'u':
716                         underline = false; //NOI18N
717

718                         break;
719
720                     case 'F': //NOI18N
721
case 'f': //NOI18N
722

723                         if (colorStack.isEmpty()) {
724                             g.setColor(defaultColor);
725                         } else {
726                             g.setColor(colorStack.pop());
727                         }
728
729                         break;
730
731                     default:
732                         throwBadHTML("Malformed or unsupported HTML", //NOI18N
733
pos, chars
734                         );
735                     }
736                 } else {
737                     //Okay, we're in an opening tag. See which one and configure the Graphics object
738
switch (chars[pos]) {
739                     case 'B': //NOI18N
740
case 'b': //NOI18N
741

742                         switch (chars[pos + 1]) {
743                         case 'R': //NOI18N
744
case 'r': //NOI18N
745

746                             if (style == STYLE_WORDWRAP) {
747                                 x = origX;
748
749                                 int lineHeight = g.getFontMetrics().getHeight();
750                                 y += lineHeight;
751                                 heightPainted += lineHeight;
752                                 widthPainted = 0;
753                             }
754
755                             break;
756
757                         case '>':
758                             bold = true;
759
760                             if (italic) {
761                                 g.setFont(deriveFont(f, Font.BOLD | Font.ITALIC));
762                             } else {
763                                 g.setFont(deriveFont(f, Font.BOLD));
764                             }
765
766                             break;
767                         }
768
769                         break;
770
771                     case 'e': //NOI18N //em tag
772
case 'E': //NOI18N
773
case 'I': //NOI18N
774
case 'i': //NOI18N
775
italic = true;
776
777                         if (bold) {
778                             g.setFont(deriveFont(f, Font.ITALIC | Font.BOLD));
779                         } else {
780                             g.setFont(deriveFont(f, Font.ITALIC));
781                         }
782
783                         break;
784
785                     case 'S': //NOI18N
786
case 's': //NOI18N
787

788                         switch (chars[pos + 1]) {
789                         case '>':
790                             strikethrough = true;
791
792                             break;
793
794                         case 'T':
795                         case 't':
796                             bold = true;
797
798                             if (italic) {
799                                 g.setFont(deriveFont(f, Font.BOLD | Font.ITALIC));
800                             } else {
801                                 g.setFont(deriveFont(f, Font.BOLD));
802                             }
803
804                             break;
805                         }
806
807                         break;
808
809                     case 'U': //NOI18N
810
case 'u': //NOI18N
811
underline = true;
812
813                         break;
814
815                     case 'f': //NOI18N
816
case 'F': //NOI18N
817

818                         Color JavaDoc c = findColor(chars, pos, tagEnd);
819                         colorStack.push(g.getColor());
820
821                         if (background != null) {
822                             c = HtmlLabelUI.ensureContrastingColor(c, background);
823                         }
824
825                         g.setColor(c);
826
827                         break;
828
829                     case 'P': //NOI18N
830
case 'p': //NOI18N
831

832                         if (style == STYLE_WORDWRAP) {
833                             x = origX;
834
835                             int lineHeight = g.getFontMetrics().getHeight();
836                             y += (lineHeight + (lineHeight / 2));
837                             heightPainted = y + lineHeight;
838                             widthPainted = 0;
839                         }
840
841                         break;
842
843                     case 'H':
844                     case 'h': //Just an opening HTML tag
845

846                         if (pos == 1) {
847                             break;
848                         }
849
850                     default:
851                         throwBadHTML("Malformed or unsupported HTML", pos, chars); //NOI18N
852
}
853                 }
854
855                 pos = tagEnd + (done ? 0 : 1);
856                 inTag = false;
857             } else {
858                 //Okay, we're not in a tag, we need to paint
859
if (lastWasWhitespace) {
860                     //Skip multiple whitespace characters
861
while ((pos < (s.length() - 1)) && Character.isWhitespace(chars[pos])) {
862                         pos++;
863                     }
864
865                     //Check strings terminating with multiple whitespace -
866
//otherwise could get an AIOOBE here
867
if (pos == (chars.length - 1)) {
868                         return (style != STYLE_WORDWRAP) ? widthPainted : heightPainted;
869                     }
870                 }
871
872                 //Flag to indicate if an ampersand entity was processed,
873
//so the resulting & doesn't get treated as the beginning of
874
//another entity (and loop endlessly)
875
boolean isAmp = false;
876
877                 //Flag to indicate the next found < character really should
878
//be painted (it came from an entity), it is not the beginning
879
//of a tag
880
boolean nextLtIsEntity = false;
881                 int nextTag = chars.length - 1;
882
883                 if ((chars[pos] == '&')) { //NOI18N
884

885                     boolean inEntity = pos != (chars.length - 1);
886
887                     if (inEntity) {
888                         int newPos = substEntity(chars, pos + 1);
889                         inEntity = newPos != -1;
890
891                         if (inEntity) {
892                             pos = newPos;
893                             isAmp = chars[pos] == '&'; //NOI18N
894

895                             nextLtIsEntity = chars[pos] == '<';
896                         } else {
897                             nextLtIsEntity = false;
898                             isAmp = true;
899                         }
900                     }
901                 } else {
902                     nextLtIsEntity = false;
903                 }
904
905                 for (int i = pos; i < chars.length; i++) {
906                     if ((chars[i] == '<' && !nextLtIsEntity) || (chars[i] == '&' && !isAmp && i != chars.length - 1)) {
907                         nextTag = i - 1;
908
909                         break;
910                     }
911
912                     //Reset these flags so we don't skip all & or < chars for the rest of the string
913
isAmp = false;
914                     nextLtIsEntity = false;
915                 }
916
917                 FontMetrics JavaDoc fm = g.getFontMetrics();
918
919                 //Get the bounds of the substring we'll paint
920
Rectangle2D JavaDoc r = fm.getStringBounds(chars, pos, nextTag + 1, g);
921                 if (Utilities.isMac()) {
922                     // #54257 - on macosx + chinese/japanese fonts, the getStringBounds() method returns bad value
923
r.setRect(r.getX(), r.getY(), (double)fm.stringWidth(new String JavaDoc(chars, pos, nextTag - pos + 1)), r.getHeight());
924                 }
925
926                 //Store the height, so we can add it if we're in word wrap mode,
927
//to return the height painted
928
lastHeight = r.getHeight();
929
930                 //Work out the length of this tag
931
int length = (nextTag + 1) - pos;
932
933                 //Flag to be set to true if we run out of space
934
boolean goToNextRow = false;
935
936                 //Flag that the current line is longer than the available width,
937
//and should be wrapped without finding a word boundary
938
boolean brutalWrap = false;
939
940                 //Work out the per-character avg width of the string, for estimating
941
//when we'll be out of space and should start the ... in truncate
942
//mode
943
double chWidth;
944
945                 if (truncated) {
946                     //if we're truncating, use the width of one dot from an
947
//ellipsis to get an accurate result for truncation
948
chWidth = dotWidth;
949                 } else {
950                     //calculate an average character width
951
chWidth = r.getWidth() / (nextTag+1 - pos);
952
953                     //can return this sometimes, so handle it
954
if ((chWidth == Double.POSITIVE_INFINITY) || (chWidth == Double.NEGATIVE_INFINITY)) {
955                         chWidth = fm.getMaxAdvance();
956                     }
957                 }
958
959                 if (
960                     ((style != STYLE_CLIP) &&
961                         ((style == STYLE_TRUNCATE) && ((widthPainted + r.getWidth()) > (w /*- (chWidth * 3)*/)))) ||
962                         /** mkleint - commented out the "- (chWidth *3) because it makes no sense to strip the text and add dots when it fits exactly
963                          * into the rendering rectangle.. with this condition we stripped even strings that came close to the limit..
964                          **/

965                         ((style == STYLE_WORDWRAP) && ((widthPainted + r.getWidth()) > w))
966                 ) {
967                     if (chWidth > 3) {
968                         double pixelsOff = (widthPainted + (r.getWidth() + 5)) - w;
969
970                         double estCharsOver = pixelsOff / chWidth;
971
972                         if (style == STYLE_TRUNCATE) {
973                             int charsToPaint = Math.round(Math.round(Math.ceil((w - widthPainted) / chWidth)));
974
975                             /* System.err.println("estCharsOver = " + estCharsOver);
976                                                         System.err.println("Chars to paint " + charsToPaint + " chwidth = " + chWidth + " widthPainted " + widthPainted);
977                                                         System.err.println("Width painted + width of tag: " + (widthPainted + r.getWidth()) + " available: " + w);
978                              */

979                             int startPeriodsPos = (pos + charsToPaint) - 3;
980
981                             if (startPeriodsPos >= chars.length) {
982                                 startPeriodsPos = chars.length - 4;
983                             }
984
985                             length = (startPeriodsPos - pos);
986
987                             if (length < 0) {
988                                 length = 0;
989                             }
990
991                             r = fm.getStringBounds(chars, pos, pos + length, g);
992                             if (Utilities.isMac()) {
993                                 // #54257 - on macosx + chinese/japanese fonts, the getStringBounds() method returns bad value
994
r.setRect(r.getX(), r.getY(), (double)fm.stringWidth(new String JavaDoc(chars, pos, length)), r.getHeight());
995                             }
996
997                             // System.err.println("Truncated set to true at " + pos + " (" + chars[pos] + ")");
998
truncated = true;
999                         } else {
1000                            //Word wrap mode
1001
goToNextRow = true;
1002
1003                            int lastChar = new Double JavaDoc(nextTag - estCharsOver).intValue();
1004
1005                            //Unlike Swing's word wrap, which does not wrap on tag boundaries correctly, if we're out of space,
1006
//we're out of space
1007
brutalWrap = x == 0;
1008
1009                            for (int i = lastChar; i > pos; i--) {
1010                                lastChar--;
1011
1012                                if (Character.isWhitespace(chars[i])) {
1013                                    length = (lastChar - pos) + 1;
1014                                    brutalWrap = false;
1015
1016                                    break;
1017                                }
1018                            }
1019
1020                            if ((lastChar <= pos) && (length > estCharsOver) && !brutalWrap) {
1021                                x = origX;
1022                                y += r.getHeight();
1023                                heightPainted += r.getHeight();
1024
1025                                boolean boundsChanged = false;
1026
1027                                while (!done && Character.isWhitespace(chars[pos]) && (pos < nextTag)) {
1028                                    pos++;
1029                                    boundsChanged = true;
1030                                    done = pos == (chars.length - 1);
1031                                }
1032
1033                                if (pos == nextTag) {
1034                                    lastWasWhitespace = true;
1035                                }
1036
1037                                if (boundsChanged) {
1038                                    //recalculate the width we will add
1039
r = fm.getStringBounds(chars, pos, nextTag + 1, g);
1040                                    if (Utilities.isMac()) {
1041                                        // #54257 - on macosx + chinese/japanese fonts, the getStringBounds() method returns bad value
1042
r.setRect(r.getX(), r.getY(), (double)fm.stringWidth(new String JavaDoc(chars, pos, nextTag - pos + 1)), r.getHeight());
1043                                    }
1044                                }
1045
1046                                goToNextRow = false;
1047                                widthPainted = 0;
1048
1049                                if (chars[pos - 1 + length] == '<') {
1050                                    length--;
1051                                }
1052                            } else if (brutalWrap) {
1053                                //wrap without checking word boundaries
1054
length = (new Double JavaDoc((w - widthPainted) / chWidth)).intValue();
1055
1056                                if ((pos + length) > nextTag) {
1057                                    length = (nextTag - pos);
1058                                }
1059
1060                                goToNextRow = true;
1061                            }
1062                        }
1063                    }
1064                }
1065
1066                if (!done) {
1067                    if (paint) {
1068                        g.drawChars(chars, pos, length, x, y);
1069                    }
1070
1071                    if (strikethrough || underline) {
1072                        LineMetrics JavaDoc lm = fm.getLineMetrics(chars, pos, length - 1, g);
1073                        int lineWidth = new Double JavaDoc(x + r.getWidth()).intValue();
1074
1075                        if (paint) {
1076                            if (strikethrough) {
1077                                int stPos = Math.round(lm.getStrikethroughOffset()) +
1078                                    g.getFont().getBaselineFor(chars[pos]) + 1;
1079
1080                                //PENDING - worth supporting with g.setStroke()? A one pixel line is most likely
1081
//good enough
1082
//int stThick = Math.round (lm.getStrikethroughThickness());
1083
g.drawLine(x, y + stPos, lineWidth, y + stPos);
1084                            }
1085
1086                            if (underline) {
1087                                int stPos = Math.round(lm.getUnderlineOffset()) +
1088                                    g.getFont().getBaselineFor(chars[pos]) + 1;
1089
1090                                //PENDING - worth supporting with g.setStroke()? A one pixel line is most likely
1091
//good enough
1092
//int stThick = new Float (lm.getUnderlineThickness()).intValue();
1093
g.drawLine(x, y + stPos, lineWidth, y + stPos);
1094                            }
1095                        }
1096                    }
1097
1098                    if (goToNextRow) {
1099                        //if we're in word wrap mode and need to go to the next
1100
//line, reconfigure the x and y coordinates
1101
x = origX;
1102                        y += r.getHeight();
1103                        heightPainted += r.getHeight();
1104                        widthPainted = 0;
1105                        pos += (length);
1106
1107                        //skip any leading whitespace
1108
while ((pos < chars.length) && (Character.isWhitespace(chars[pos])) && (chars[pos] != '<')) {
1109                            pos++;
1110                        }
1111
1112                        lastWasWhitespace = true;
1113                        done |= (pos >= chars.length);
1114                    } else {
1115                        x += r.getWidth();
1116                        widthPainted += r.getWidth();
1117                        lastWasWhitespace = Character.isWhitespace(chars[nextTag]);
1118                        pos = nextTag + 1;
1119                    }
1120
1121                    done |= (nextTag == chars.length);
1122                }
1123            }
1124        }
1125
1126        if (style != STYLE_WORDWRAP) {
1127            return widthPainted;
1128        } else {
1129            return heightPainted + lastHeight;
1130        }
1131    }
1132
1133    /** Parse a font color tag and return an appopriate java.awt.Color instance */
1134    private static Color JavaDoc findColor(final char[] ch, final int pos, final int tagEnd) {
1135        int colorPos = pos;
1136        boolean useUIManager = false;
1137
1138        for (int i = pos; i < tagEnd; i++) {
1139            if (ch[i] == 'c') {
1140                colorPos = i + 6;
1141
1142                if ((ch[colorPos] == '\'') || (ch[colorPos] == '"')) {
1143                    colorPos++;
1144                }
1145
1146                //skip the leading # character
1147
if (ch[colorPos] == '#') {
1148                    colorPos++;
1149                } else if (ch[colorPos] == '!') {
1150                    useUIManager = true;
1151                    colorPos++;
1152                }
1153
1154                break;
1155            }
1156        }
1157
1158        if (colorPos == pos) {
1159            String JavaDoc out = "Could not find color identifier in font declaration"; //NOI18N
1160
throwBadHTML(out, pos, ch);
1161        }
1162
1163        //Okay, we're now on the first character of the hex color definition
1164
String JavaDoc s;
1165
1166        if (useUIManager) {
1167            int end = ch.length - 1;
1168
1169            for (int i = colorPos; i < ch.length; i++) {
1170                if ((ch[i] == '"') || (ch[i] == '\'')) { //NOI18N
1171
end = i;
1172
1173                    break;
1174                }
1175            }
1176
1177            s = new String JavaDoc(ch, colorPos, end - colorPos);
1178        } else {
1179            s = new String JavaDoc(ch, colorPos, 6);
1180        }
1181
1182        Color JavaDoc result = null;
1183
1184        if (useUIManager) {
1185            result = UIManager.getColor(s);
1186
1187            //Not all look and feels will provide standard colors; handle it gracefully
1188
if (result == null) {
1189                throwBadHTML("Could not resolve logical font declared in HTML: " + s, //NOI18N
1190
pos, ch
1191                );
1192                result = UIManager.getColor("textText"); //NOI18N
1193

1194                //Avoid NPE in headless situation?
1195
if (result == null) {
1196                    result = Color.BLACK;
1197                }
1198            }
1199        } else {
1200            try {
1201                int rgb = Integer.parseInt(s, 16);
1202                result = new Color JavaDoc(rgb);
1203            } catch (NumberFormatException JavaDoc nfe) {
1204                throwBadHTML("Illegal hexadecimal color text: " + s + //NOI18N
1205
" in HTML string", colorPos, ch
1206                ); //NOI18N
1207
}
1208        }
1209
1210        if (result == null) {
1211            throwBadHTML("Unresolvable html color: " + s //NOI18N
1212
+" in HTML string \n ", pos, ch
1213            ); //NOI18N
1214
}
1215
1216        return result;
1217    }
1218
1219    /**
1220     * Workaround for Apple bug 3644261 - after using form editor, all boldface
1221     * fonts start showing up with incorrect metrics, such that all boldface
1222     * fonts in the entire IDE are displayed 12px below where they should be.
1223     * Embarrassing and awful.
1224     */

1225    private static final Font JavaDoc deriveFont(Font JavaDoc f, int style) {
1226        // return f.deriveFont(style);
1227
// see #49973 for details.
1228
Font JavaDoc result = Utilities.isMac() ? new Font JavaDoc(f.getName(), style, f.getSize()) : f.deriveFont(style);
1229
1230        return result;
1231    }
1232
1233    /** Find an entity at the passed character position in the passed array.
1234     * If an entity is found, the trailing ; character will be substituted
1235     * with the resulting character, and the position of that character
1236     * in the array will be returned as the new position to render from,
1237     * causing the renderer to skip the intervening characters */

1238    private static final int substEntity(char[] ch, int pos) {
1239        //There are no 1 character entities, abort
1240
if (pos >= (ch.length - 2)) {
1241            return -1;
1242        }
1243
1244        //if it's numeric, parse out the number
1245
if (ch[pos] == '#') { //NOI18N
1246

1247            return substNumericEntity(ch, pos + 1);
1248        }
1249
1250        //Okay, we've potentially got a named character entity. Try to find it.
1251
boolean match;
1252
1253        for (int i = 0; i < entities.length; i++) {
1254            char[] c = (char[]) entities[i];
1255            match = true;
1256
1257            if (c.length < (ch.length - pos)) {
1258                for (int j = 0; j < c.length; j++) {
1259                    match &= (c[j] == ch[j + pos]);
1260                }
1261            } else {
1262                match = false;
1263            }
1264
1265            if (match) {
1266                //if it's a match, we still need the trailing ;
1267
if (ch[pos + c.length] == ';') { //NOI18N
1268

1269                    //substitute the character referenced by the entity
1270
ch[pos + c.length] = entitySubstitutions[i];
1271
1272                    return pos + c.length;
1273                }
1274            }
1275        }
1276
1277        return -1;
1278    }
1279
1280    /** Finds a character defined as a numeric entity (e.g. &amp;#8222;)
1281     * and replaces the trailing ; with the referenced character, returning
1282     * the position of it so the renderer can continue from there.
1283     */

1284    private static final int substNumericEntity(char[] ch, int pos) {
1285        for (int i = pos; i < ch.length; i++) {
1286            if (ch[i] == ';') {
1287                try {
1288                    ch[i] = (char) Integer.parseInt(new String JavaDoc(ch, pos, i - pos));
1289
1290                    return i;
1291                } catch (NumberFormatException JavaDoc nfe) {
1292                    throwBadHTML("Unparsable numeric entity: " + //NOI18N
1293
new String JavaDoc(ch, pos, i - pos), pos, ch
1294                    ); //NOI18N
1295
}
1296            }
1297        }
1298
1299        return -1;
1300    }
1301
1302    /** Throw an exception for unsupported or bad html, indicating where the problem is
1303     * in the message */

1304    private static void throwBadHTML(String JavaDoc msg, int pos, char[] chars) {
1305        char[] chh = new char[pos];
1306        Arrays.fill(chh, ' '); //NOI18N
1307
chh[pos - 1] = '^'; //NOI18N
1308

1309        String JavaDoc out = msg + "\n " + new String JavaDoc(chars) + "\n " + new String JavaDoc(chh) + "\n Full HTML string:" +
1310            new String JavaDoc(chars); //NOI18N
1311

1312        if (!STRICT_HTML) {
1313            if (LOG.isLoggable(Level.WARNING)) {
1314                if (badStrings == null) {
1315                    badStrings = new HashSet JavaDoc<String JavaDoc>();
1316                }
1317
1318                if (!badStrings.contains(msg)) {
1319                    // bug, issue 38372 - log messages containing
1320
//newlines are truncated - so for now we iterate the
1321
//string we've just constructed
1322
StringTokenizer JavaDoc tk = new StringTokenizer JavaDoc(out, "\n", false);
1323
1324                    while (tk.hasMoreTokens()) {
1325                        LOG.warning(tk.nextToken());
1326                    }
1327
1328                    badStrings.add(msg.intern()); // NOPMD
1329
}
1330            }
1331        } else {
1332            throw new IllegalArgumentException JavaDoc(out);
1333        }
1334    }
1335
1336    /**
1337     * Interface aggregating table, tree, and list cell renderers.
1338     * @see #createRenderer
1339     * @see #createLabel
1340     */

1341    public interface Renderer extends TableCellRenderer JavaDoc, TreeCellRenderer JavaDoc, ListCellRenderer JavaDoc {
1342        /** Indicate that the component being rendered has keyboard focus. NetBeans requires that a different
1343         * selection color be used depending on whether the view has focus.
1344         *
1345         * @param parentFocused Whether or not the focused selection color should be used
1346         */

1347        void setParentFocused(boolean parentFocused);
1348
1349        /**
1350         * Indicate that the text should be painted centered below the icon. This is primarily used
1351         * by org.openide.explorer.view.IconView
1352         *
1353         * @param centered Whether or not centered painting should be used.
1354         */

1355        void setCentered(boolean centered);
1356
1357        /**
1358         * Set a number of pixels the icon and text should be indented. Used by ChoiceView and ListView to
1359         * fake tree-style nesting. This value has no effect if {@link #setCentered setCentered(true)} has been called.
1360         *
1361         * @param pixels The number of pixels to indent
1362         */

1363        void setIndent(int pixels);
1364
1365        /**
1366         * Explicitly tell the renderer it is going to receive HTML markup, or it is not. If the renderer should
1367         * check the string for opening HTML tags to determine this, don't call this method. If you <strong>know</strong>
1368         * the string will be compliant HTML, it is preferable to call this method with true; if you want to intentionally
1369         * render HTML markup literally, call this method with false.
1370         *
1371         * @param val
1372         */

1373        void setHtml(boolean val);
1374
1375        /**
1376         * Set the rendering style - this can be JLabel-style truncated-with-elipsis (...) text, or clipped text.
1377         * The default is {@link #STYLE_CLIP}.
1378         *
1379         * @param style The text style
1380         */

1381        void setRenderStyle(int style);
1382
1383        /** Set the icon to be used for painting
1384         *
1385         * @param icon An icon or null
1386         */

1387        void setIcon(Icon JavaDoc icon);
1388
1389        /** Clear any stale data from previous use by other components,
1390         * clearing the icon, text, disabled state and other customizations, returning the component
1391         * to its initialized state. This is done automatically when get*CellRenderer() is called,
1392         * and to the shared instance when {@link #createLabel} is called.<p>
1393         * Users of the static {@link #createLabel} method may want to call this method if they
1394         * use the returned instance more than once without again calling {@link #createLabel}.
1395         */

1396        void reset();
1397
1398        /** Set the text to be displayed. Use this if the object being rendered's toString() does not
1399         * return a real user-displayable string, after calling get**CellRenderer(). Typically after calling
1400         * this one calls {@link #setHtml} if the text is known to either be or not be HTML markup.
1401         *
1402         * @param txt The text that should be displayed
1403         */

1404        void setText(String JavaDoc txt);
1405
1406        /**
1407         * Convenience method to set the gap between the icon and text.
1408         *
1409         * @param gap an integer number of pixels
1410         */

1411        void setIconTextGap(int gap);
1412    }
1413    
1414}
1415
Popular Tags