]> git.somenet.org - irc/pjirc-ng.git/blob - src/main/java/irc/style/FormattedStringDrawer.java
Pjirc 2.2.1 as available on the net, reformatted and made it compile.
[irc/pjirc-ng.git] / src / main / java / irc / style / FormattedStringDrawer.java
1 /*****************************************************/
2 /*          This java file is a part of the          */
3 /*                                                   */
4 /*           -  Plouf's Java IRC Client  -           */
5 /*                                                   */
6 /*   Copyright (C)  2002 - 2004 Philippe Detournay   */
7 /*                                                   */
8 /*         All contacts : theplouf@yahoo.com         */
9 /*                                                   */
10 /*  PJIRC is free software; you can redistribute     */
11 /*  it and/or modify it under the terms of the GNU   */
12 /*  General Public License as published by the       */
13 /*  Free Software Foundation; version 2 or later of  */
14 /*  the License.                                     */
15 /*                                                   */
16 /*  PJIRC is distributed in the hope that it will    */
17 /*  be useful, but WITHOUT ANY WARRANTY; without     */
18 /*  even the implied warranty of MERCHANTABILITY or  */
19 /*  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU   */
20 /*  General Public License for more details.         */
21 /*                                                   */
22 /*  You should have received a copy of the GNU       */
23 /*  General Public License along with PJIRC; if      */
24 /*  not, write to the Free Software Foundation,      */
25 /*  Inc., 59 Temple Place, Suite 330, Boston,        */
26 /*  MA  02111-1307  USA                              */
27 /*                                                   */
28 /*****************************************************/
29
30 package irc.style;
31
32 import irc.EventDispatcher;
33 import irc.IRCConfiguration;
34 import irc.StyleContext;
35
36 import java.awt.Color;
37 import java.awt.Dimension;
38 import java.awt.Font;
39 import java.awt.FontMetrics;
40 import java.awt.Graphics;
41 import java.awt.Image;
42 import java.awt.Rectangle;
43 import java.awt.image.ImageObserver;
44 import java.util.Vector;
45
46 /**
47  * CharacterInfo.
48  */
49 class CharacterInfo {
50         /**
51          * Create a new CharacterInfo
52          */
53         public CharacterInfo() {
54                 frontColor = Color.black;
55                 backColor = Color.white;
56                 isBold = false;
57                 isUnderline = false;
58                 isReverse = false;
59                 isTransparent = true;
60         }
61
62         /**
63          * Create a new CharacterInfo
64          * 
65          * @param base
66          *          base info that will be copied in the newly created instance.
67          */
68         public CharacterInfo(CharacterInfo base) {
69                 frontColor = base.frontColor;
70                 backColor = base.backColor;
71                 isBold = base.isBold;
72                 isUnderline = base.isUnderline;
73                 isReverse = base.isReverse;
74                 isTransparent = base.isTransparent;
75         }
76
77         @Override
78         public boolean equals(Object o) {
79                 if (!(o instanceof CharacterInfo))
80                         return false;
81                 CharacterInfo c = (CharacterInfo) o;
82                 if (!frontColor.equals(c.frontColor))
83                         return false;
84                 if (!backColor.equals(c.backColor))
85                         return false;
86                 if (isBold != c.isBold)
87                         return false;
88                 if (isUnderline != c.isUnderline)
89                         return false;
90                 if (isTransparent != c.isTransparent)
91                         return false;
92                 return true;
93         }
94
95         @Override
96         public int hashCode() {
97                 int c = 0;
98                 if (isBold)
99                         c++;
100                 if (isUnderline)
101                         c++;
102                 if (isTransparent)
103                         c++;
104                 return c + frontColor.hashCode() + backColor.hashCode();
105         }
106
107         /**
108          * Front color.
109          */
110         public Color frontColor;
111         /**
112          * Back color.
113          */
114         public Color backColor;
115         /**
116          * True if bold.
117          */
118         public boolean isBold;
119         /**
120          * True if underline.
121          */
122         public boolean isUnderline;
123         /**
124          * True if reverse.
125          */
126         public boolean isReverse;
127         /**
128          * True if transparent.
129          */
130         public boolean isTransparent;
131 }
132
133 /**
134  * CharacterGroupItem.
135  */
136 class CharacterGroupItem {
137         /**
138          * String content.
139          */
140         public String s;
141         /**
142          * String style.
143          */
144         public CharacterInfo info;
145
146         /**
147          * Create a new CharacterGroupItem
148          * 
149          * @param nfo
150          *          style.
151          */
152         public CharacterGroupItem(CharacterInfo nfo) {
153                 info = nfo;
154                 s = "";
155         }
156 }
157
158 /**
159  * WordItem.
160  */
161 class WordItem {
162         /**
163          * Items.
164          */
165         public CharacterGroupItem[] items;
166         /**
167          * Original stripped word.
168          */
169         public String originalstrippedword;
170         /**
171          * Original word.
172          */
173         public String originalword;
174         /**
175          * Smiley-decoded word.
176          */
177         public String decodedword;
178         /**
179          * Last character style.
180          */
181         public CharacterInfo lastInfo;
182
183         /**
184          * Create a new WordItem
185          * 
186          * @param itm
187          *          items.
188          * @param lInfo
189          *          last character style.
190          */
191         public WordItem(CharacterGroupItem[] itm, CharacterInfo lInfo) {
192                 lastInfo = lInfo;
193                 items = itm;
194                 decodedword = "";
195                 for (int i = 0; i < items.length; i++)
196                         decodedword += items[i].s;
197                 originalword = decodedword;
198                 originalstrippedword = decodedword;
199         }
200 }
201
202 /**
203  * LineItem.
204  */
205 class LineItem {
206         /**
207          * First item.
208          */
209         public int first;
210         /**
211          * Number of items.
212          */
213         public int count;
214 }
215
216 /**
217  * DecodedLineInternal.
218  */
219 class DecodedLineInternal extends DecodedLine {
220         /**
221          * All items.
222          */
223         public WordItem[] words;
224 }
225
226 /**
227  * The formatted string drawer.
228  */
229 public class FormattedStringDrawer implements ImageObserver {
230         private Font _font;
231         private Font _fontPlain;
232         private Font _fontBold;
233         private Color[] _cols;
234         private CharactersDrawer _drawer;
235         private IRCConfiguration _config;
236         private Dimension _tmp;
237         private LineItem[] _lines;
238         private int _vdirection;
239         private int _hdirection;
240         private FormattedStringDrawerListener _listener;
241
242         /**
243          * Bottom.
244          */
245         public static final int BOTTOM = 0;
246         /**
247          * Top.
248          */
249         public static final int TOP = 1;
250         /**
251          * Left.
252          */
253         public static final int LEFT = 0;
254         /**
255          * Right.
256          */
257         public static final int RIGHT = 1;
258
259         /**
260          * Create a new FormattedStringDrawer.
261          * 
262          * @param config
263          *          the global configuration.
264          * @param context
265          *          the style context to use.
266          * @param listener
267          *          the listener to notify upon draw update.
268          */
269         public FormattedStringDrawer(IRCConfiguration config, StyleContext context, FormattedStringDrawerListener listener) {
270                 _listener = listener;
271                 _tmp = new Dimension();
272                 _lines = new LineItem[8];
273                 for (int i = 0; i < _lines.length; i++)
274                         _lines[i] = new LineItem();
275                 _config = config;
276                 setFont(config.getStyleFont(context));
277                 _drawer = new CharactersDrawer(_config);
278                 setStyleContext(context);
279                 _vdirection = BOTTOM;
280                 _hdirection = LEFT;
281                 if (config.getB("style:righttoleft"))
282                         setHorizontalDirection(RIGHT);
283
284         }
285
286         /**
287          * Create a new FormattedStringDrawer.
288          * 
289          * @param config
290          *          the global configuration.
291          * @param context
292          *          the style context to use.
293          */
294         public FormattedStringDrawer(IRCConfiguration config, StyleContext context) {
295                 this(config, context, null);
296         }
297
298         /**
299          * Set the vertical alignment direction.
300          * 
301          * @param dir
302          *          vertical direction.
303          */
304         public void setVerticalDirection(int dir) {
305                 _vdirection = dir;
306         }
307
308         /**
309          * Get the vertical alignment direction.
310          * 
311          * @return the vertical direction.
312          */
313         public int getVerticalDirection() {
314                 return _vdirection;
315         }
316
317         /**
318          * Set the horizontal alignment direction.
319          * 
320          * @param dir
321          *          horizontal direction.
322          */
323         public void setHorizontalDirection(int dir) {
324                 _hdirection = dir;
325         }
326
327         /**
328          * Get the horizontal alignment direction.
329          * 
330          * @return horizontal direction.
331          */
332         public int getHorizontalDirection() {
333                 return _hdirection;
334         }
335
336         /**
337          * Set the style context to use.
338          * 
339          * @param context
340          *          new color context.
341          */
342         public void setStyleContext(StyleContext context) {
343                 _cols = _config.getStyleColors(context);
344         }
345
346         /**
347          * Prepare a line for display.
348          * 
349          * @param str
350          *          line to prepare.
351          * @return prepared line, ready for display.
352          */
353         public DecodedLine decodeLine(String str) {
354                 DecodedLineInternal ans = new DecodedLineInternal();
355                 ans.original = str;
356                 str += (char) 15;
357                 String decoded = _drawer.decodeLine(str);
358                 ans.decoded = decoded;
359                 ans.decoded_stripped = getStripped(decoded);
360                 Vector v = doWords(str, decoded);
361                 ans.words = new WordItem[v.size()];
362                 for (int i = 0; i < ans.words.length; i++)
363                         ans.words[i] = (WordItem) v.elementAt(i);
364                 return ans;
365         }
366
367         private Vector doWords(String ostr, String dstr) {
368                 Vector words = new Vector();
369                 CharacterInfo info = new CharacterInfo();
370                 info.frontColor = _cols[1];
371                 info.backColor = _cols[0];
372                 info.isTransparent = true;
373
374                 while (dstr.length() > 0) {
375                         int opos = ostr.indexOf(' ');
376                         int dpos = dstr.indexOf(' ');
377                         WordItem word;
378                         if (dpos == -1) {
379                                 word = decodeWord(info, dstr + " ", _cols);
380                                 word.originalword = ostr + " ";
381                                 word.originalstrippedword = getStripped(ostr + " ");
382                                 dstr = "";
383                         } else {
384                                 String owrd = ostr.substring(0, opos);
385                                 String dwrd = dstr.substring(0, dpos);
386                                 word = decodeWord(info, dwrd + " ", _cols);
387                                 word.originalword = owrd + " ";
388                                 word.originalstrippedword = getStripped(owrd + " ");
389                                 ostr = ostr.substring(opos + 1);
390                                 dstr = dstr.substring(dpos + 1);
391                         }
392                         words.insertElementAt(word, words.size());
393                         info = word.lastInfo;
394                 }
395
396                 return words;
397         }
398
399         /**
400          * Get the given string width, in pixel.
401          * 
402          * @param str
403          *          the prepared line.
404          * @param fm
405          *          the FontMetrics that will be used on display.
406          * @return the string width, in pixel.
407          */
408         public int getHeight(DecodedLine str, FontMetrics fm) {
409                 return _drawer.getHeight(str.decoded_stripped, fm, this);
410         }
411
412         /**
413          * Get the given string height, in pixel.
414          * 
415          * @param str
416          *          the prepared line.
417          * @param fm
418          *          the FontMetrics that will be used on display.
419          * @return the string height, in pixel.
420          */
421         public int getWidth(DecodedLine str, FontMetrics fm) {
422                 return _drawer.getWidth(str.decoded_stripped, fm, this);
423         }
424
425         private Font deriveFont(Font fnt, int style) {
426                 return new Font(fnt.getName(), style, fnt.getSize());
427         }
428
429         /**
430          * Set the colors to use, overriding current colors from color context.
431          * 
432          * @param cols
433          *          colors to use.
434          */
435         public void setColors(Color[] cols) {
436                 _cols = cols;
437         }
438
439         /**
440          * Get the current color at index i.
441          * 
442          * @param i
443          *          color index.
444          * @return the color at index i.
445          */
446         public Color getColor(int i) {
447                 return _cols[i];
448         }
449
450         /**
451          * Set the font to use.
452          * 
453          * @param fnt
454          *          the font to be used.
455          */
456         public void setFont(Font fnt) {
457                 _font = fnt;
458                 _fontPlain = deriveFont(_font, Font.PLAIN);
459                 _fontBold = deriveFont(_font, Font.BOLD);
460         }
461
462         /**
463          * Get the used font.
464          * 
465          * @return the used font.
466          */
467         public Font getFont() {
468                 return _font;
469         }
470
471         private WordItem decodeWord(CharacterInfo base, String str, Color[] cols) {
472                 Vector v = new Vector();
473                 CharacterInfo current = new CharacterInfo(base);
474                 CharacterGroupItem currentItem = new CharacterGroupItem(new CharacterInfo(current));
475                 int size = str.length();
476                 for (int pos = 0; pos < size; pos++) {
477                         char c = str.charAt(pos);
478                         if (c < ' ') {
479                                 int code = c;
480                                 if (code == 15) {
481                                         current.isBold = false;
482                                         current.isUnderline = false;
483                                         current.isReverse = false;
484                                         current.frontColor = cols[1];
485                                         current.backColor = cols[0];
486                                         current.isTransparent = true;
487                                 } else if (code == 2) {
488                                         current.isBold = !current.isBold;
489                                 } else if (code == 31) {
490                                         current.isUnderline = !current.isUnderline;
491                                 } else if (code == 22) {
492                                         current.isReverse = !current.isReverse;
493                                         if (current.isReverse) {
494                                                 current.frontColor = cols[0];
495                                                 current.backColor = cols[1];
496                                                 current.isTransparent = false;
497                                         } else {
498                                                 current.frontColor = cols[1];
499                                                 current.backColor = cols[0];
500                                                 current.isTransparent = true;
501                                         }
502                                 } else if (code == 3) {
503                                         boolean front = true;
504                                         String frontC = "";
505                                         String backC = "";
506                                         pos++;
507                                         while (pos < size) {
508                                                 char d = str.charAt(pos);
509                                                 if ((d >= '0') && (d <= '9')) {
510                                                         if (front) {
511                                                                 if (frontC.length() == 2) {
512                                                                         pos--;
513                                                                         break;
514                                                                 }
515                                                                 frontC += d;
516                                                         } else {
517                                                                 if (backC.length() == 2) {
518                                                                         pos--;
519                                                                         break;
520                                                                 }
521                                                                 backC += d;
522                                                         }
523                                                         pos++;
524                                                 } else if (d == ',') {
525                                                         if (front) {
526                                                                 front = false;
527                                                                 pos++;
528                                                         } else {
529                                                                 pos--;
530                                                                 break;
531                                                         }
532                                                 } else {
533                                                         pos--;
534                                                         break;
535                                                 }
536                                         }
537                                         if (frontC.length() == 0)
538                                                 backC = "";
539                                         if (frontC.length() > 0) {
540                                                 int col = Integer.parseInt(frontC);
541                                                 col %= _cols.length;
542                                                 current.frontColor = cols[col];
543                                         }
544                                         if (backC.length() > 0) {
545                                                 int col = Integer.parseInt(backC);
546                                                 col %= _cols.length;
547                                                 current.backColor = cols[col];
548                                                 current.isTransparent = (col == 0);
549                                         }
550                                         if ((frontC.length() == 0) && (backC.length() == 0)) {
551                                                 current.frontColor = cols[1];
552                                                 current.backColor = cols[0];
553                                                 current.isTransparent = true;
554                                         }
555                                 }
556                                 if (!current.equals(currentItem.info)) {
557                                         v.insertElementAt(currentItem, v.size());
558                                         currentItem = new CharacterGroupItem(new CharacterInfo(current));
559                                 }
560                         } else {
561                                 currentItem.s += c;
562                         }
563                 }
564                 v.insertElementAt(currentItem, v.size());
565
566                 CharacterGroupItem[] ans = new CharacterGroupItem[v.size()];
567                 for (int i = 0; i < v.size(); i++) {
568                         ans[i] = (CharacterGroupItem) v.elementAt(i);
569                 }
570
571                 return new WordItem(ans, current);
572         }
573
574         private FontMetrics getFontMetrics(Graphics g, CharacterInfo nfo) {
575                 Font old = g.getFont();
576                 if (nfo.isBold)
577                         g.setFont(_fontBold);
578                 else
579                         g.setFont(_fontPlain);
580                 FontMetrics res = g.getFontMetrics();
581                 g.setFont(old);
582                 return res;
583         }
584
585         private int drawPart(Graphics g, CharacterInfo nfo, String str, int x, int y, FontMetrics plainMetrics, int clipxl,
586                         int clipxr, ImageObserver obs, Vector handles) {
587                 FontMetrics fm = plainMetrics;
588                 // int up=plainMetrics.getAscent();
589                 int down = plainMetrics.getDescent();
590
591                 if (nfo.isBold)
592                         g.setFont(_fontBold);
593
594                 fm = g.getFontMetrics();
595
596                 int width = _drawer.getWidth(str, fm, this);
597                 if ((x <= clipxr) && (x + width > clipxl)) {
598                         int height = _drawer.getHeight(str, fm, this);
599                         Rectangle originalClip = g.getClipBounds();
600                         int cx = clipxl;
601                         int cy = y - height;
602                         int cw = clipxr - clipxl + 1;
603                         int ch = height;
604
605                         g.clipRect(cx, cy, cw, ch);
606
607                         g.setColor(nfo.backColor);
608
609                         if (!nfo.isTransparent)
610                                 g.fillRect(x, y - height, width, height);
611
612                         y -= down;
613
614                         g.setColor(nfo.frontColor);
615                         _drawer.draw(str, g, fm, x, y, obs, handles);
616
617                         if (nfo.isUnderline)
618                                 g.drawLine(x, y + 1, x + width - 1, y + 1);
619                         if (originalClip != null)
620                                 g.setClip(originalClip.x, originalClip.y, originalClip.width, originalClip.height);
621                         else
622                                 g.setClip(null);
623                 }
624
625                 if (nfo.isBold)
626                         g.setFont(_fontPlain);
627                 return width;
628         }
629
630         private void drawWord(Graphics g, WordItem word, int x, int y, boolean last, FontMetrics plainMetrics, int clipxl,
631                         int clipxr, ImageObserver obs, Vector handles) {
632                 for (int pos = 0; pos < word.items.length; pos++) {
633                         CharacterGroupItem item = word.items[pos];
634                         x += drawPart(g, item.info, item.s, x, y, plainMetrics, clipxl, clipxr, obs, handles);
635                 }
636         }
637
638         /**
639          * Strip a line from all its color and special codes.
640          * 
641          * @param str
642          *          string to strip.
643          * @return stripped line.
644          */
645         public String getStripped(String str) {
646                 CharacterInfo info = new CharacterInfo();
647                 info.frontColor = _cols[1];
648                 info.backColor = _cols[0];
649                 info.isTransparent = true;
650                 String res = "";
651                 while (str.length() > 0) {
652                         int pos = str.indexOf(' ');
653                         WordItem word;
654                         if (pos == -1) {
655                                 word = decodeWord(info, str, _cols);
656                                 str = "";
657                         } else {
658                                 String wrd = str.substring(0, pos);
659                                 word = decodeWord(info, wrd + " ", _cols);
660                                 str = str.substring(pos + 1);
661                         }
662                         if (res.length() > 0)
663                                 res += " " + word.originalword;
664                         else
665                                 res += word.originalword;
666                 }
667                 return res;
668         }
669
670         private boolean isAlphaNum(char c) {
671                 if ((c == '(') || (c == ')'))
672                         return false;
673                 if ((c == '<') || (c == '>'))
674                         return false;
675                 if ((c == '"') || (c == '"'))
676                         return false;
677                 if ((c == '{') || (c == '}'))
678                         return false;
679                 if ((c == '.') || (c == ','))
680                         return false;
681                 if (c == ':')
682                         return false;
683                 // if(c=='-') return false;
684                 return true;
685         }
686
687         private String trimAlphaNum(String s) {
688                 int index = 0;
689                 while ((index < s.length()) && !isAlphaNum(s.charAt(index)))
690                         index++;
691                 if (index == s.length())
692                         return "";
693                 s = s.substring(index);
694                 index = s.length() - 1;
695                 while ((index >= 0) && !isAlphaNum(s.charAt(index)))
696                         index--;
697                 if (index == -1)
698                         return "";
699                 s = s.substring(0, index + 1);
700                 return s;
701         }
702
703         private void getWordItemWidthHeight(Graphics g, WordItem item, Dimension res) {
704                 int resx = 0;
705                 int resy = 0;
706                 for (int i = 0; i < item.items.length; i++) {
707                         FontMetrics fm = getFontMetrics(g, item.items[i].info);
708                         _drawer.getWidthHeight(item.items[i].s, fm, res, this);
709                         resx += res.width;
710                         int h = res.height;
711                         if (h > resy)
712                                 resy = h;
713                 }
714                 res.width = resx;
715                 res.height = resy;
716         }
717
718         private void expandLines() {
719                 LineItem[] n = new LineItem[_lines.length * 2];
720                 System.arraycopy(_lines, 0, n, 0, _lines.length);
721                 for (int i = _lines.length; i < n.length; i++)
722                         n[i] = new LineItem();
723                 _lines = n;
724         }
725
726         /**
727          * Get the height of the given decoded line.
728          * 
729          * @param str
730          *          decoded line.
731          * @param g
732          *          graphics where the string will be displayed.
733          * @param x
734          *          display position.
735          * @param wmax
736          *          maximum width.
737          * @param wrap
738          *          true if wrapping must occur.
739          * @return actual height.
740          */
741         public int getHeight(DecodedLine str, Graphics g, int x, int wmax, boolean wrap) {
742                 WordItem[] words = ((DecodedLineInternal) str).words;
743
744                 Font currFont = _fontPlain;
745                 g.setFont(currFont);
746                 FontMetrics plainFm = g.getFontMetrics();
747
748                 int currentLineLength = 0;
749                 int w = 0;
750                 int h = 0;
751
752                 int mh = 0;
753                 for (int i = 0; i < words.length; i++) {
754                         WordItem word = words[i];
755                         getWordItemWidthHeight(g, word, _tmp);
756                         int wordWidth = _tmp.width;
757                         if ((w + wordWidth > wmax) && (currentLineLength > 0) && wrap) {
758                                 w = _drawer.getWidth("  ", plainFm, this);
759                                 currentLineLength = 0;
760                                 h += mh;
761                                 mh = 0;
762                         }
763                         if (_tmp.height > mh)
764                                 mh = _tmp.height;
765                         currentLineLength++;
766                         w += wordWidth;
767                 }
768                 if (currentLineLength > 0)
769                         h += mh;
770                 return h;
771         }
772
773         /**
774          * Draw the given prepared line.
775          * 
776          * @param str
777          *          the prepared line to draw.
778          * @param g
779          *          the graphics where to draw.
780          * @param left
781          *          left margin.
782          * @param right
783          *          right margin.
784          * @param y
785          *          y position.
786          * @param clipxl
787          *          left clip position : drawing doesn't have to be complete left to
788          *          this position.
789          * @param clipxr
790          *          right clip position : drawing doens't have to be complete right to
791          *          this position.
792          * @param analyse
793          *          true if word per word analyse must be performed, false otherwise.
794          *          If not analyse is requested, the DrawResultItem array in res will
795          *          be zero-sized.
796          * @param wrap
797          *          true is wrapping must be done, false otherwise.
798          * @param res
799          *          analyse report destination for the drawed line.
800          */
801         public void draw(DecodedLine str, Graphics g, int left, int right, int y, int clipxl, int clipxr, boolean analyse,
802                         boolean wrap, DrawResult res) {
803                 WordItem[] words = ((DecodedLineInternal) str).words;
804                 if (res.updateHandles == null) {
805                         res.updateHandles = new Vector();
806                 } else {
807                         if (res.updateHandles.size() > 0)
808                                 res.updateHandles.removeAllElements();
809                 }
810
811                 // split lines
812                 Font currFont = _fontPlain;
813                 g.setFont(currFont);
814                 FontMetrics plainFm = g.getFontMetrics();
815
816                 int lineCount = 0;
817                 int firstWordInLine = 0;
818                 int currentLineLength = 0;
819                 int wmax = right - left + 1;
820                 if (wrap) {
821                         int w = 0;
822                         for (int i = 0; i < words.length; i++) {
823                                 WordItem word = words[i];
824                                 getWordItemWidthHeight(g, word, _tmp);
825                                 int wordWidth = _tmp.width;
826                                 if ((w + wordWidth > wmax) && (currentLineLength > 0)) {
827                                         w = _drawer.getWidth("  ", plainFm, this);
828                                         LineItem newLine = _lines[lineCount++];
829                                         if (lineCount == _lines.length)
830                                                 expandLines();
831                                         newLine.first = firstWordInLine;
832                                         newLine.count = currentLineLength;
833                                         firstWordInLine = i;
834                                         currentLineLength = 0;
835                                 }
836
837                                 currentLineLength++;
838                                 w += wordWidth;
839                         }
840                         if (currentLineLength != 0) {
841                                 LineItem newLine = _lines[lineCount++];
842                                 if (lineCount == _lines.length)
843                                         expandLines();
844                                 newLine.first = firstWordInLine;
845                                 newLine.count = currentLineLength;
846                         }
847                 } else {
848                         LineItem newLine = _lines[lineCount++];
849                         newLine.first = 0;
850                         newLine.count = words.length;
851                 }
852
853                 // display
854                 int s = 0;
855                 if (analyse)
856                         s = words.length;
857                 if ((res.items == null) || (res.items.length != s))
858                         res.items = new DrawResultItem[s];
859
860                 DrawResultItem[] dres = res.items;
861                 int minX = right;
862                 int maxX = left;
863                 int h = 0;
864                 int py = 0;
865                 int marginWidth = _drawer.getWidth("  ", plainFm, this);
866
867                 int hdir = 1;
868                 if (_hdirection == RIGHT)
869                         hdir = -1;
870
871                 if (_vdirection == BOTTOM) {
872                         for (int i = lineCount - 1; i >= 0; i--) {
873                                 int px = left;
874                                 if (hdir == -1)
875                                         px = right;
876                                 int maxHeight = 0;
877                                 if (i != 0)
878                                         px += hdir * marginWidth;
879                                 LineItem line = _lines[i];
880                                 for (int j = line.first; j < line.first + line.count; j++) {
881
882                                         getWordItemWidthHeight(g, words[j], _tmp);
883
884                                         int wordWidth = _tmp.width;
885                                         int wordHeight = _tmp.height;
886
887                                         if (hdir == -1)
888                                                 px -= wordWidth;
889
890                                         int trimmedWidth = wordWidth;
891                                         if (maxHeight < wordHeight)
892                                                 maxHeight = wordHeight;
893                                         if ((px + wordWidth > clipxl) && (px <= clipxr))
894                                                 drawWord(g, words[j], px, y, j == line.first + line.count - 1, plainFm, clipxl, clipxr, this,
895                                                                 res.updateHandles);
896
897                                         if (analyse) {
898                                                 // String wrd=words[j].decodedword;
899                                                 String owrd = words[j].originalword;
900                                                 String swrd = words[j].originalstrippedword;
901                                                 String twrd = trimAlphaNum(swrd.trim());
902                                                 if (dres[j] == null)
903                                                         dres[j] = new DrawResultItem();
904                                                 DrawResultItem ritem = dres[j];
905                                                 ritem.parent = res;
906                                                 ritem.item = twrd;
907                                                 ritem.originalword = owrd;
908                                                 ritem.originalstrippedword = swrd;
909                                                 ritem.rectangle = new StyledRectangle(px, py - wordHeight, trimmedWidth, wordHeight);
910                                         }
911                                         if (hdir == 1)
912                                                 px += wordWidth;
913                                         if (px > maxX)
914                                                 maxX = px;
915                                         if (px < minX)
916                                                 minX = px;
917                                 }
918                                 y -= maxHeight;
919                                 py -= maxHeight;
920                                 h += maxHeight;
921                         }
922
923                         if (analyse)
924                                 for (int i = 0; i < dres.length; i++)
925                                         dres[i].rectangle.y += h;
926                 } else if (_vdirection == TOP) {
927                         for (int i = 0; i < lineCount; i++) {
928                                 int px = left;
929                                 if (hdir == -1)
930                                         px = right;
931                                 int maxHeight = 0;
932                                 if (i != 0)
933                                         px += hdir * marginWidth;
934                                 LineItem line = _lines[i];
935                                 for (int j = line.first; j < line.first + line.count; j++) {
936                                         getWordItemWidthHeight(g, words[j], _tmp);
937
938                                         int wordWidth = _tmp.width;
939                                         int wordHeight = _tmp.height;
940                                         if (hdir == -1)
941                                                 px -= wordWidth;
942
943                                         int trimmedWidth = wordWidth;
944                                         if (maxHeight < wordHeight)
945                                                 maxHeight = wordHeight;
946
947                                         if ((px + wordWidth > clipxl) && (px <= clipxr))
948                                                 drawWord(g, words[j], px, y + wordHeight, j == line.first + line.count - 1, plainFm, clipxl, clipxr, this,
949                                                                 res.updateHandles);
950
951                                         if (analyse) {
952                                                 String wrd = words[j].decodedword;
953                                                 String owrd = words[j].originalword;
954                                                 String swrd = words[j].originalstrippedword;
955                                                 String twrd = trimAlphaNum(wrd.trim());
956                                                 if (dres[j] == null)
957                                                         dres[j] = new DrawResultItem();
958                                                 DrawResultItem ritem = dres[j];
959                                                 ritem.parent = res;
960                                                 ritem.item = twrd;
961                                                 ritem.originalword = owrd;
962                                                 ritem.originalstrippedword = swrd;
963                                                 ritem.rectangle = new StyledRectangle(px, py, trimmedWidth, wordHeight);
964                                         }
965                                         if (hdir == 1)
966                                                 px += wordWidth;
967                                         if (px > maxX)
968                                                 maxX = px;
969                                         if (px < minX)
970                                                 minX = px;
971                                 }
972                                 y += maxHeight;
973                                 py += maxHeight;
974                                 h += maxHeight;
975                         }
976                         y -= h;
977                 }
978
979                 int x1 = left;
980                 int x2 = maxX;
981                 if (hdir == -1) {
982                         x1 = minX;
983                         x2 = right;
984                 }
985
986                 if (analyse)
987                         for (int i = 0; i < res.items.length; i++)
988                                 res.items[i].rectangle.x -= x1;
989
990                 if (res.rectangle == null) {
991                         res.rectangle = new StyledRectangle(x1, y, x2 - x1 + 1, h);
992                 } else {
993                         res.rectangle.x = x1;
994                         res.rectangle.y = y;
995                         res.rectangle.width = x2 - x1 + 1;
996                         res.rectangle.height = h;
997                 }
998         }
999
1000         @Override
1001         public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
1002                 if ((infoflags & ImageObserver.ERROR) != 0)
1003                         return false;
1004                 if ((infoflags & ImageObserver.ABORT) != 0)
1005                         return false;
1006
1007                 // We don't care about properties, just go ahead...
1008                 if ((infoflags & ImageObserver.PROPERTIES) != 0)
1009                         return true;
1010
1011                 if (_listener != null) {
1012                         Boolean b;
1013                         int what = 0;
1014                         if ((infoflags & ImageObserver.WIDTH) != 0)
1015                                 what |= FormattedStringDrawerListener.SIZE;
1016                         if ((infoflags & ImageObserver.HEIGHT) != 0)
1017                                 what |= FormattedStringDrawerListener.SIZE;
1018                         if ((infoflags & ImageObserver.ALLBITS) != 0)
1019                                 what |= FormattedStringDrawerListener.DATA;
1020                         if ((infoflags & ImageObserver.FRAMEBITS) != 0)
1021                                 what |= FormattedStringDrawerListener.FRAME;
1022                         if ((infoflags & ImageObserver.SOMEBITS) != 0)
1023                                 what |= FormattedStringDrawerListener.DATA;
1024
1025                         try {
1026                                 b = (Boolean) EventDispatcher.dispatchEventAsyncAndWaitEx(_listener, "displayUpdated", new Object[] { img,
1027                                                 new Integer(what) });
1028                                 return b.booleanValue();
1029                         } catch (Throwable e) {
1030                                 e.printStackTrace();
1031                                 return false;
1032                         }
1033                 }
1034                 return true;
1035         }
1036 }