1   package org.apache.maven.doxia.module.rtf;
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  import java.awt.Color;
23  
24  import java.io.BufferedOutputStream;
25  import java.io.BufferedWriter;
26  import java.io.IOException;
27  import java.io.OutputStream;
28  import java.io.OutputStreamWriter;
29  import java.io.PrintWriter;
30  import java.io.Writer;
31  
32  import java.util.HashMap;
33  import java.util.Hashtable;
34  import java.util.Iterator;
35  import java.util.Map;
36  import java.util.Set;
37  import java.util.StringTokenizer;
38  import java.util.TreeSet;
39  import java.util.Vector;
40  
41  import org.apache.maven.doxia.sink.AbstractTextSink;
42  import org.apache.maven.doxia.sink.Sink;
43  import org.apache.maven.doxia.sink.SinkEventAttributes;
44  
45  
46  
47  
48  
49  
50  
51  public class RtfSink
52      extends AbstractTextSink
53  {
54      
55      public static final double DEFAULT_PAPER_WIDTH = 21.;   
56  
57      
58      public static final double DEFAULT_PAPER_HEIGHT = 29.7; 
59  
60      
61      public static final double DEFAULT_TOP_MARGIN = 2.;    
62  
63      
64      public static final double DEFAULT_BOTTOM_MARGIN = 2.; 
65  
66      
67      public static final double DEFAULT_LEFT_MARGIN = 2.;   
68  
69      
70      public static final double DEFAULT_RIGHT_MARGIN = 2.;  
71  
72      
73      public static final int DEFAULT_FONT_SIZE = 10; 
74  
75      
76      public static final int DEFAULT_SPACING = 10;   
77  
78      
79      public static final int DEFAULT_RESOLUTION = 72; 
80  
81      
82      public static final String DEFAULT_IMAGE_FORMAT = "bmp";
83  
84      
85      public static final String DEFAULT_IMAGE_TYPE = "palette";
86  
87      
88      public static final String DEFAULT_DATA_FORMAT = "ascii";
89  
90      
91      public static final int DEFAULT_CODE_PAGE = 1252;
92  
93      
94      public static final int DEFAULT_CHAR_SET = 0;
95  
96      
97      public static final String IMG_FORMAT_BMP = "bmp";
98  
99      
100     public static final String IMG_FORMAT_WMF = "wmf";
101 
102     
103     public static final String IMG_TYPE_PALETTE = "palette";
104 
105     
106     public static final String IMG_TYPE_RGB = "rgb";
107 
108     
109     public static final String IMG_DATA_ASCII = "ascii";
110 
111     
112     public static final String IMG_DATA_RAW = "raw";
113 
114     
115     public static final int STYLE_ROMAN = 0;
116 
117     
118     public static final int STYLE_ITALIC = 1;
119 
120     
121     public static final int STYLE_BOLD = 2;
122 
123     
124     public static final int STYLE_TYPEWRITER = 3;
125 
126     private static final int CONTEXT_UNDEFINED = 0;
127 
128     private static final int CONTEXT_VERBATIM = 1;
129 
130     private static final int CONTEXT_TABLE = 2;
131 
132     private static final int UNIT_MILLIMETER = 1;
133 
134     private static final int UNIT_CENTIMETER = 2;
135 
136     private static final int UNIT_INCH = 3;
137 
138     private static final int UNIT_PIXEL = 4;
139 
140     private static final int LIST_INDENT = 300; 
141 
142     private static final String LIST_ITEM_HEADER = "-  ";
143 
144     private static final int DEFINITION_INDENT = 300; 
145 
146     private static final int CELL_HORIZONTAL_PAD = 60; 
147 
148     private static final int CELL_VERTICAL_PAD = 20;   
149 
150     private static final int BORDER_WIDTH = 15; 
151 
152     private double paperWidth = DEFAULT_PAPER_WIDTH;
153 
154     private double paperHeight = DEFAULT_PAPER_HEIGHT;
155 
156     private double topMargin = DEFAULT_TOP_MARGIN;
157 
158     private double bottomMargin = DEFAULT_BOTTOM_MARGIN;
159 
160     private double leftMargin = DEFAULT_LEFT_MARGIN;
161 
162     private double rightMargin = DEFAULT_RIGHT_MARGIN;
163 
164     protected int fontSize = DEFAULT_FONT_SIZE;
165 
166     private int resolution = DEFAULT_RESOLUTION;
167 
168     private String imageFormat = DEFAULT_IMAGE_FORMAT;
169 
170     private String imageType = DEFAULT_IMAGE_TYPE;
171 
172     private String imageDataFormat = DEFAULT_DATA_FORMAT;
173 
174     private boolean imageCompression = true;
175 
176     private int codePage = DEFAULT_CODE_PAGE;
177 
178     private int charSet = DEFAULT_CHAR_SET;
179 
180     private final Hashtable fontTable;
181 
182     private Context context;
183 
184     private Paragraph paragraph;
185 
186     protected Indentation indentation;
187 
188     protected Space space;
189 
190     private int listItemIndent;
191 
192     private final Vector numbering;
193 
194     private final Vector itemNumber;
195 
196     private int style = STYLE_ROMAN;
197 
198     private int sectionLevel;
199 
200     private boolean emptyHeader;
201 
202     private StringBuffer verbatim;
203 
204     private boolean frame;
205 
206     private Table table;
207 
208     private Row row;
209 
210     private Cell cell;
211 
212     private Line line;
213 
214     protected PrintWriter writer;
215 
216     protected OutputStream stream; 
217 
218     
219 
220     private Map warnMessages;
221 
222     
223 
224     
225 
226 
227 
228 
229     protected RtfSink()
230         throws IOException
231     {
232         this( System.out );
233     }
234 
235     
236 
237 
238 
239 
240 
241     protected RtfSink( OutputStream output )
242         throws IOException
243     {
244         this( output, null );
245     }
246 
247     
248 
249 
250 
251 
252 
253 
254     protected RtfSink( OutputStream output, String encoding )
255         throws IOException
256     {
257         this.fontTable = new Hashtable();
258         this.numbering = new Vector();
259         this.itemNumber = new Vector();
260 
261         Writer w;
262         this.stream = new BufferedOutputStream( output );
263         
264         if ( encoding != null )
265         {
266             w = new OutputStreamWriter( stream, encoding );
267         }
268         else
269         {
270             w = new OutputStreamWriter( stream );
271         }
272         this.writer = new PrintWriter( new BufferedWriter( w ) );
273 
274         init();
275     }
276 
277     
278 
279 
280 
281 
282 
283     public void setPaperSize( double width 
284     {
285         paperWidth = width;
286         paperHeight = height;
287     }
288 
289     
290 
291 
292 
293 
294     public void setTopMargin( double margin )
295     {
296         topMargin = margin;
297     }
298 
299     
300 
301 
302 
303 
304     public void setBottomMargin( double margin )
305     {
306         bottomMargin = margin;
307     }
308 
309     
310 
311 
312 
313 
314     public void setLeftMargin( double margin )
315     {
316         leftMargin = margin;
317     }
318 
319     
320 
321 
322 
323 
324     public void setRightMargin( double margin )
325     {
326         rightMargin = margin;
327     }
328 
329     
330 
331 
332 
333 
334     public void setFontSize( int size 
335     {
336         fontSize = size;
337     }
338 
339     
340 
341 
342 
343 
344     public void setSpacing( int spacing 
345     {
346         space.set( 20 * spacing );
347     }
348 
349     
350 
351 
352 
353 
354     public void setResolution( int resolution 
355     {
356         this.resolution = resolution;
357     }
358 
359     
360 
361 
362 
363 
364     public void setImageFormat( String format )
365     {
366         imageFormat = format;
367     }
368 
369     
370 
371 
372 
373 
374     public void setImageType( String type )
375     {
376         imageType = type;
377     }
378 
379     
380 
381 
382 
383 
384     public void setImageDataFormat( String format )
385     {
386         imageDataFormat = format;
387     }
388 
389     
390 
391 
392 
393 
394     public void setImageCompression( boolean compression )
395     {
396         imageCompression = compression;
397     }
398 
399     
400 
401 
402 
403 
404     public void setCodePage( int cp )
405     {
406         codePage = cp;
407     }
408 
409     
410 
411 
412 
413 
414     public void setCharSet( int cs )
415     {
416         charSet = cs;
417     }
418 
419     
420     public void head()
421     {
422         init();
423 
424         writer.println( "{\\rtf1\\ansi\\ansicpg" + codePage + "\\deff0" );
425 
426         writer.println( "{\\fonttbl" );
427         writer.println( "{\\f0\\froman\\fcharset" + charSet + " Times;}" );
428         writer.println( "{\\f1\\fmodern\\fcharset" + charSet + " Courier;}" );
429         writer.println( "}" );
430 
431         writer.println( "{\\stylesheet" );
432         for ( int level = 1; level <= 5; ++level )
433         {
434             writer.print( "{\\s" + styleNumber( level ) );
435             writer.print( "\\outlinelevel" + level );
436             writer.print( " Section Title " + level );
437             writer.println( ";}" );
438         }
439         writer.println( "}" );
440 
441         writer.println( "\\paperw" + toTwips( paperWidth, UNIT_CENTIMETER ) );
442         writer.println( "\\paperh" + toTwips( paperHeight, UNIT_CENTIMETER ) );
443         writer.println( "\\margl" + toTwips( leftMargin, UNIT_CENTIMETER ) );
444         writer.println( "\\margr" + toTwips( rightMargin, UNIT_CENTIMETER ) );
445         writer.println( "\\margt" + toTwips( topMargin, UNIT_CENTIMETER ) );
446         writer.println( "\\margb" + toTwips( bottomMargin, UNIT_CENTIMETER ) );
447 
448         space.set( space.get() / 2 );
449         space.setNext( 0 );
450 
451         emptyHeader = true;
452     }
453 
454     
455     public void head_()
456     {
457         space.restore();
458         if ( emptyHeader )
459         {
460             space.setNext( 0 );
461         }
462         else
463         {
464             space.setNext( 2 * space.get() );
465         }
466     }
467 
468     
469 
470 
471 
472 
473 
474 
475     protected int toTwips( double length, int unit )
476     {
477         double points;
478 
479         switch ( unit )
480         {
481             case UNIT_MILLIMETER:
482                 points = ( length / 25.4 ) * 72.;
483                 break;
484             case UNIT_CENTIMETER:
485                 points = ( length / 2.54 ) * 72.;
486                 break;
487             case UNIT_INCH:
488                 points = length * 72.;
489                 break;
490             case UNIT_PIXEL:
491             default:
492                 points = ( length / resolution ) * 72.;
493                 break;
494         }
495 
496         return (int) Math.rint( points * 20. );
497     }
498 
499     
500     public void title()
501     {
502         Paragraph p = new Paragraph( STYLE_BOLD, fontSize + 6 );
503         p.justification = Sink.JUSTIFY_CENTER;
504         beginParagraph( p );
505         emptyHeader = false;
506     }
507 
508     
509     public void title_()
510     {
511         endParagraph();
512     }
513 
514     
515     public void author()
516     {
517         Paragraph p = new Paragraph( STYLE_ROMAN, fontSize + 2 );
518         p.justification = Sink.JUSTIFY_CENTER;
519         beginParagraph( p );
520         emptyHeader = false;
521     }
522 
523     
524     public void author_()
525     {
526         endParagraph();
527     }
528 
529     
530     public void date()
531     {
532         Paragraph p = new Paragraph( STYLE_ROMAN, fontSize );
533         p.justification = Sink.JUSTIFY_CENTER;
534         beginParagraph( p );
535         emptyHeader = false;
536     }
537 
538     
539     public void date_()
540     {
541         endParagraph();
542     }
543 
544     
545     public void body()
546     {
547         
548     }
549 
550     
551     public void body_()
552     {
553         writer.println( "}" );
554         writer.flush();
555     }
556 
557     
558     public void section1()
559     {
560         sectionLevel = 1;
561     }
562 
563     
564     public void section1_()
565     {
566         
567     }
568 
569     
570     public void section2()
571     {
572         sectionLevel = 2;
573     }
574 
575     
576     public void section2_()
577     {
578         
579     }
580 
581     
582     public void section3()
583     {
584         sectionLevel = 3;
585     }
586 
587     
588     public void section3_()
589     {
590         
591     }
592 
593     
594     public void section4()
595     {
596         sectionLevel = 4;
597     }
598 
599     
600     public void section4_()
601     {
602         
603     }
604 
605     
606     public void section5()
607     {
608         sectionLevel = 5;
609     }
610 
611     
612     public void section5_()
613     {
614         
615     }
616 
617     
618     public void sectionTitle()
619     {
620         int stl = STYLE_BOLD;
621         int size = fontSize;
622 
623         switch ( sectionLevel )
624         {
625             case 1:
626                 size = fontSize + 6;
627                 break;
628             case 2:
629                 size = fontSize + 4;
630                 break;
631             case 3:
632                 size = fontSize + 2;
633                 break;
634             case 4:
635                 break;
636             case 5:
637                 stl = STYLE_ROMAN;
638                 break;
639         }
640 
641         Paragraph p = new Paragraph( stl, size );
642         p.style = styleNumber( sectionLevel );
643 
644         beginParagraph( p );
645     }
646 
647     
648     public void sectionTitle_()
649     {
650         endParagraph();
651     }
652 
653     private int styleNumber( int level )
654     {
655         return level;
656     }
657 
658     
659     public void list()
660     {
661         indentation.add( LIST_INDENT );
662         space.set( space.get() / 2 );
663     }
664 
665     
666     public void list_()
667     {
668         indentation.restore();
669         space.restore();
670     }
671 
672     
673     public void listItem()
674     {
675         Paragraph p = new Paragraph();
676         p.leftIndent = indentation.get() + listItemIndent;
677         p.firstLineIndent = ( -listItemIndent );
678         beginParagraph( p );
679 
680         beginStyle( STYLE_BOLD );
681         writer.println( LIST_ITEM_HEADER );
682         endStyle();
683 
684         indentation.add( listItemIndent );
685         space.set( space.get() / 2 );
686     }
687 
688     
689     public void listItem_()
690     {
691         endParagraph();
692 
693         indentation.restore();
694         space.restore();
695     }
696 
697     
698     public void numberedList( int numbering )
699     {
700         this.numbering.addElement( new Integer( numbering ) );
701         itemNumber.addElement( new Counter( 0 ) );
702 
703         indentation.add( LIST_INDENT );
704         space.set( space.get() / 2 );
705     }
706 
707     
708     public void numberedList_()
709     {
710         numbering.removeElementAt( numbering.size() - 1 );
711         itemNumber.removeElementAt( itemNumber.size() - 1 );
712 
713         indentation.restore();
714         space.restore();
715     }
716 
717     
718     public void numberedListItem()
719     {
720         ( (Counter) itemNumber.lastElement() ).increment();
721 
722         int indent = 0;
723         String header = getItemHeader();
724         Font font = getFont( STYLE_TYPEWRITER, fontSize );
725         if ( font != null )
726         {
727             indent = textWidth( header, font );
728         }
729 
730         Paragraph p = new Paragraph();
731         p.leftIndent = indentation.get() + indent;
732         p.firstLineIndent = ( -indent );
733         beginParagraph( p );
734 
735         beginStyle( STYLE_TYPEWRITER );
736         writer.println( header );
737         endStyle();
738 
739         indentation.add( indent );
740         space.set( space.get() / 2 );
741     }
742 
743     
744     public void numberedListItem_()
745     {
746         endParagraph();
747 
748         indentation.restore();
749         space.restore();
750     }
751 
752     private String getItemHeader()
753     {
754         int nmb = ( (Integer) this.numbering.lastElement() ).intValue();
755         int iNmb = ( (Counter) this.itemNumber.lastElement() ).get();
756         StringBuffer buf = new StringBuffer();
757 
758         switch ( nmb )
759         {
760             case Sink.NUMBERING_DECIMAL:
761             default:
762                 buf.append( iNmb );
763                 buf.append( ". " );
764                 while ( buf.length() < 4 )
765                 {
766                     buf.append( ' ' );
767                 }
768                 break;
769 
770             case Sink.NUMBERING_LOWER_ALPHA:
771                 buf.append( AlphaNumerals.toString( iNmb, true ) );
772                 buf.append( ") " );
773                 break;
774 
775             case Sink.NUMBERING_UPPER_ALPHA:
776                 buf.append( AlphaNumerals.toString( iNmb, false ) );
777                 buf.append( ". " );
778                 break;
779 
780             case Sink.NUMBERING_LOWER_ROMAN:
781                 buf.append( RomanNumerals.toString( iNmb, true ) );
782                 buf.append( ") " );
783                 while ( buf.length() < 6 )
784                 {
785                     buf.append( ' ' );
786                 }
787                 break;
788 
789             case Sink.NUMBERING_UPPER_ROMAN:
790                 buf.append( RomanNumerals.toString( iNmb, false ) );
791                 buf.append( ". " );
792                 while ( buf.length() < 6 )
793                 {
794                     buf.append( ' ' );
795                 }
796                 break;
797         }
798 
799         return buf.toString();
800     }
801 
802     
803     public void definitionList()
804     {
805         int next = space.getNext();
806 
807         indentation.add( LIST_INDENT );
808         space.set( space.get() / 2 );
809         space.setNext( next );
810     }
811 
812     
813     public void definitionList_()
814     {
815         indentation.restore();
816         space.restore();
817     }
818 
819     
820     public void definitionListItem()
821     {
822         int next = space.getNext();
823         space.set( space.get() / 2 );
824         space.setNext( next );
825     }
826 
827     
828     public void definitionListItem_()
829     {
830         space.restore();
831     }
832 
833     
834     public void definedTerm()
835     {
836         
837     }
838 
839     
840     public void definedTerm_()
841     {
842         endParagraph();
843     }
844 
845     
846     public void definition()
847     {
848         int next = space.getNext();
849 
850         indentation.add( DEFINITION_INDENT );
851         space.set( space.get() / 2 );
852         space.setNext( next );
853     }
854 
855     
856     public void definition_()
857     {
858         endParagraph();
859 
860         indentation.restore();
861         space.restore();
862     }
863 
864     
865     public void table()
866     {
867         
868     }
869 
870     
871     public void table_()
872     {
873         
874     }
875 
876     
877     public void tableRows( int[] justification, boolean grid )
878 
879     {
880         table = new Table( justification, grid );
881         context.set( CONTEXT_TABLE );
882     }
883 
884     
885     public void tableRows_()
886     {
887         boolean bb = false;
888         boolean br = false;
889 
890         int offset = ( pageWidth() - ( table.width() + indentation.get() ) ) / 2;
891         int x0 = indentation.get() + offset;
892 
893         space.skip();
894 
895         for ( int i = 0; i < table.rows.size(); ++i )
896         {
897             Row r = (Row) table.rows.elementAt( i );
898 
899             writer.print( "\\trowd" );
900             writer.print( "\\trleft" + x0 );
901             writer.print( "\\trgaph" + CELL_HORIZONTAL_PAD );
902             writer.println( "\\trrh" + r.height() );
903 
904             if ( table.grid )
905             {
906                 if ( i == ( table.rows.size() - 1 ) )
907                 {
908                     bb = true;
909                 }
910                 br = false;
911             }
912 
913             for ( int j = 0, x = x0; j < table.numColumns; ++j )
914             {
915                 if ( table.grid )
916                 {
917                     if ( j == ( table.numColumns - 1 ) )
918                     {
919                         br = true;
920                     }
921                     setBorder( true, bb, true, br );
922                     x += BORDER_WIDTH;
923                 }
924                 x += table.columnWidths[j];
925                 writer.println( "\\clvertalc\\cellx" + x );
926             }
927 
928             for ( int j = 0; j < table.numColumns; ++j )
929             {
930                 if ( j >= r.cells.size() )
931                 {
932                     break;
933                 }
934                 Cell c = (Cell) r.cells.elementAt( j );
935 
936                 writer.print( "\\pard\\intbl" );
937                 setJustification( table.justification[j] );
938                 writer.println( "\\plain\\f0\\fs" + ( 2 * fontSize ) );
939 
940                 for ( int k = 0; k < c.lines.size(); ++k )
941                 {
942                     if ( k > 0 )
943                     {
944                         writer.println( "\\line" );
945                     }
946                     Line l = (Line) c.lines.elementAt( k );
947 
948                     for ( int n = 0; n < l.items.size(); ++n )
949                     {
950                         Item item = (Item) l.items.elementAt( n );
951                         writer.print( "{" );
952                         setStyle( item.style );
953                         writer.println( escape( item.text ) );
954                         writer.println( "}" );
955                     }
956                 }
957 
958                 writer.println( "\\cell" );
959             }
960 
961             writer.println( "\\row" );
962         }
963 
964         context.restore();
965     }
966 
967     private int pageWidth()
968     {
969         double width = paperWidth - ( leftMargin + rightMargin );
970         return toTwips( width, UNIT_CENTIMETER );
971     }
972 
973     private void setBorder( boolean bt, boolean bb, boolean bl, boolean br )
974     {
975         if ( bt )
976         {
977             writer.println( "\\clbrdrt\\brdrs\\brdrw" + BORDER_WIDTH );
978         }
979         if ( bb )
980         {
981             writer.println( "\\clbrdrb\\brdrs\\brdrw" + BORDER_WIDTH );
982         }
983         if ( bl )
984         {
985             writer.println( "\\clbrdrl\\brdrs\\brdrw" + BORDER_WIDTH );
986         }
987         if ( br )
988         {
989             writer.println( "\\clbrdrr\\brdrs\\brdrw" + BORDER_WIDTH );
990         }
991     }
992 
993     private void setJustification( int justification )
994     {
995         switch ( justification )
996         {
997             case Sink.JUSTIFY_LEFT:
998             default:
999                 writer.println( "\\ql" );
1000                 break;
1001             case Sink.JUSTIFY_CENTER:
1002                 writer.println( "\\qc" );
1003                 break;
1004             case Sink.JUSTIFY_RIGHT:
1005                 writer.println( "\\qr" );
1006                 break;
1007         }
1008     }
1009 
1010     private void setStyle( int style )
1011     {
1012         switch ( style )
1013         {
1014             case STYLE_ITALIC:
1015                 writer.println( "\\i" );
1016                 break;
1017             case STYLE_BOLD:
1018                 writer.println( "\\b" );
1019                 break;
1020             case STYLE_TYPEWRITER:
1021                 writer.println( "\\f1" );
1022                 break;
1023             default:
1024                 break;
1025         }
1026     }
1027 
1028     
1029     public void tableRow()
1030     {
1031         row = new Row();
1032     }
1033 
1034     
1035     public void tableRow_()
1036     {
1037         table.add( row );
1038     }
1039 
1040     
1041     public void tableHeaderCell()
1042     {
1043         tableCell();
1044     }
1045 
1046     
1047     public void tableHeaderCell_()
1048     {
1049         tableCell_();
1050     }
1051 
1052     
1053     public void tableCell()
1054     {
1055         cell = new Cell();
1056         line = new Line();
1057     }
1058 
1059     
1060     public void tableCell_()
1061     {
1062         cell.add( line );
1063         row.add( cell );
1064     }
1065 
1066     
1067     public void tableCaption()
1068     {
1069         Paragraph p = new Paragraph();
1070         p.justification = Sink.JUSTIFY_CENTER;
1071         p.spaceBefore /= 2;
1072         beginParagraph( p );
1073     }
1074 
1075     
1076     public void tableCaption_()
1077     {
1078         endParagraph();
1079     }
1080 
1081     
1082     public void paragraph()
1083     {
1084         if ( paragraph == null )
1085         {
1086             beginParagraph( new Paragraph() );
1087         }
1088     }
1089 
1090     
1091     public void paragraph_()
1092     {
1093         endParagraph();
1094     }
1095 
1096     private void beginParagraph( Paragraph p )
1097     {
1098         p.begin();
1099         this.paragraph = p;
1100         if ( style != STYLE_ROMAN )
1101         {
1102             beginStyle( style );
1103         }
1104     }
1105 
1106     private void endParagraph()
1107     {
1108         if ( paragraph != null )
1109         {
1110             if ( style != STYLE_ROMAN )
1111             {
1112                 endStyle();
1113             }
1114             paragraph.end();
1115             paragraph = null;
1116         }
1117     }
1118 
1119     
1120     public void verbatim( boolean boxed )
1121     {
1122         verbatim = new StringBuffer();
1123         frame = boxed;
1124 
1125         context.set( CONTEXT_VERBATIM );
1126     }
1127 
1128     
1129     public void verbatim_()
1130     {
1131         String text = verbatim.toString();
1132 
1133         Paragraph p = new Paragraph();
1134         p.fontStyle = STYLE_TYPEWRITER;
1135         p.frame = frame;
1136 
1137         beginParagraph( p );
1138 
1139         StringTokenizer t = new StringTokenizer( text, EOL, true );
1140         while ( t.hasMoreTokens() )
1141         {
1142             String s = t.nextToken();
1143             if ( s.equals( EOL ) && t.hasMoreTokens() )
1144             {
1145                 writer.println( "\\line" );
1146             }
1147             else
1148             {
1149                 writer.println( escape( s ) );
1150             }
1151         }
1152 
1153         endParagraph();
1154 
1155         context.restore();
1156     }
1157 
1158     
1159     public void figure()
1160     {
1161         
1162     }
1163 
1164     
1165     public void figure_()
1166     {
1167         
1168     }
1169 
1170     
1171     public void figureGraphics( String name )
1172     {
1173         Paragraph p = new Paragraph();
1174         p.justification = Sink.JUSTIFY_CENTER;
1175         beginParagraph( p );
1176 
1177         try
1178         {
1179             writeImage( name );
1180         }
1181         catch ( Exception e )
1182         {
1183             getLog().error( e.getMessage(), e );
1184         }
1185 
1186         endParagraph();
1187     }
1188 
1189     private void writeImage( String source )
1190         throws Exception
1191     {
1192         if ( !source.toLowerCase().endsWith( ".ppm" ) )
1193         {
1194             
1195             String msg =
1196                 "Unsupported image type for image file: '" + source + "'. Only PPM image type is "
1197                     + "currently supported.";
1198             logMessage( "unsupportedImage", msg );
1199 
1200             return;
1201         }
1202 
1203         int bytesPerLine;
1204         PBMReader ppm = new PBMReader( source );
1205         WMFWriter.Dib dib = new WMFWriter.Dib();
1206         WMFWriter wmf = new WMFWriter();
1207 
1208         int srcWidth = ppm.width();
1209         int srcHeight = ppm.height();
1210 
1211         dib.biWidth = srcWidth;
1212         dib.biHeight = srcHeight;
1213         dib.biXPelsPerMeter = (int) ( resolution * 100. / 2.54 );
1214         dib.biYPelsPerMeter = dib.biXPelsPerMeter;
1215 
1216         if ( imageType.equals( IMG_TYPE_RGB ) )
1217         {
1218             dib.biBitCount = 24;
1219             dib.biCompression = WMFWriter.Dib.BI_RGB; 
1220 
1221             bytesPerLine = 4 * ( ( 3 * srcWidth + 3 ) / 4 );
1222             dib.bitmap = new byte[srcHeight * bytesPerLine];
1223 
1224             byte[] l = new byte[3 * srcWidth];
1225             for ( int i = ( srcHeight - 1 ); i >= 0; --i )
1226             {
1227                 ppm.read( l, 0, l.length );
1228                 for ( int j = 0, k = ( i * bytesPerLine ); j < l.length; j += 3 )
1229                 {
1230                     
1231                     dib.bitmap[k++] = l[j + 2];
1232                     dib.bitmap[k++] = l[j + 1];
1233                     dib.bitmap[k++] = l[j];
1234                 }
1235             }
1236         }
1237         else
1238         {
1239             dib.biBitCount = 8;
1240 
1241             bytesPerLine = 4 * ( ( srcWidth + 3 ) / 4 );
1242             byte[] bitmap = new byte[srcHeight * bytesPerLine];
1243 
1244             Vector colors = new Vector( 256 );
1245             colors.addElement( Color.white );
1246             colors.addElement( Color.black );
1247 
1248             byte[] l = new byte[3 * srcWidth];
1249             for ( int i = ( srcHeight - 1 ); i >= 0; --i )
1250             {
1251                 ppm.read( l, 0, l.length );
1252                 for ( int j = 0, k = ( i * bytesPerLine ); j < l.length; )
1253                 {
1254                     int r = (int) l[j++] & 0xff;
1255                     int g = (int) l[j++] & 0xff;
1256                     int b = (int) l[j++] & 0xff;
1257                     Color color = new Color( r, g, b );
1258                     int index = colors.indexOf( color );
1259                     if ( index < 0 )
1260                     {
1261                         if ( colors.size() < colors.capacity() )
1262                         {
1263                             colors.addElement( color );
1264                             index = colors.size() - 1;
1265                         }
1266                         else
1267                         {
1268                             index = 1;
1269                         }
1270                     }
1271                     bitmap[k++] = (byte) index;
1272                 }
1273             }
1274 
1275             dib.biClrUsed = colors.size();
1276             dib.biClrImportant = dib.biClrUsed;
1277             dib.palette = new byte[4 * dib.biClrUsed];
1278             for ( int i = 0, j = 0; i < dib.biClrUsed; ++i, ++j )
1279             {
1280                 Color color = (Color) colors.elementAt( i );
1281                 dib.palette[j++] = (byte) color.getBlue();
1282                 dib.palette[j++] = (byte) color.getGreen();
1283                 dib.palette[j++] = (byte) color.getRed();
1284             }
1285 
1286             if ( imageCompression )
1287             {
1288                 dib.biCompression = WMFWriter.Dib.BI_RLE8;
1289                 dib.bitmap = new byte[bitmap.length + ( 2 * ( bitmap.length / 255 + 1 ) )];
1290                 dib.biSizeImage = WMFWriter.Dib.rlEncode8( bitmap, 0, bitmap.length, dib.bitmap, 0 );
1291             }
1292             else
1293             {
1294                 dib.biCompression = WMFWriter.Dib.BI_RGB;
1295                 dib.bitmap = bitmap;
1296             }
1297         }
1298 
1299         if ( imageFormat.equals( IMG_FORMAT_WMF ) )
1300         {
1301             int[] parameters;
1302             WMFWriter.Record record;
1303 
1304             
1305 
1306 
1307 
1308 
1309 
1310             
1311             parameters = new int[1];
1312             parameters[0] = 1;
1313             record = new WMFWriter.Record( 0x0103, parameters );
1314             wmf.add( record );
1315 
1316             
1317             parameters = new int[2];
1318             record = new WMFWriter.Record( 0x020b, parameters );
1319             wmf.add( record );
1320             parameters = new int[2];
1321             parameters[0] = srcHeight;
1322             parameters[1] = srcWidth;
1323             record = new WMFWriter.Record( 0x020c, parameters );
1324             wmf.add( record );
1325 
1326             parameters = new int[WMFWriter.DibBitBltRecord.P_COUNT];
1327             
1328             parameters[WMFWriter.DibBitBltRecord.P_ROP_H] = 0x00cc;
1329             parameters[WMFWriter.DibBitBltRecord.P_ROP_L] = 0x0020;
1330             parameters[WMFWriter.DibBitBltRecord.P_WIDTH] = srcWidth;
1331             parameters[WMFWriter.DibBitBltRecord.P_HEIGHT] = srcHeight;
1332             record = new WMFWriter.DibBitBltRecord( parameters, dib );
1333             wmf.add( record );
1334         }
1335 
1336         if ( imageFormat.equals( IMG_FORMAT_WMF ) )
1337         {
1338             writer.print( "{\\pict\\wmetafile1" );
1339             writer.println( "\\picbmp\\picbpp" + dib.biBitCount );
1340         }
1341         else
1342         {
1343             writer.print( "{\\pict\\dibitmap0\\wbmplanes1" );
1344             writer.print( "\\wbmbitspixel" + dib.biBitCount );
1345             writer.println( "\\wbmwidthbytes" + bytesPerLine );
1346         }
1347 
1348         writer.print( "\\picw" + srcWidth );
1349         writer.print( "\\pich" + srcHeight );
1350         writer.print( "\\picwgoal" + toTwips( srcWidth, UNIT_PIXEL ) );
1351         writer.println( "\\pichgoal" + toTwips( srcHeight, UNIT_PIXEL ) );
1352 
1353         if ( imageFormat.equals( IMG_FORMAT_WMF ) )
1354         {
1355             if ( imageDataFormat.equals( IMG_DATA_RAW ) )
1356             {
1357                 writer.print( "\\bin" + ( 2 * wmf.size() ) + " " );
1358                 writer.flush();
1359                 wmf.write( stream );
1360                 stream.flush();
1361             }
1362             else
1363             {
1364                 wmf.print( writer );
1365             }
1366         }
1367         else
1368         {
1369             if ( imageDataFormat.equals( IMG_DATA_RAW ) )
1370             {
1371                 writer.print( "\\bin" + ( 2 * dib.size() ) + " " );
1372                 writer.flush();
1373                 dib.write( stream );
1374                 stream.flush();
1375             }
1376             else
1377             {
1378                 dib.print( writer );
1379             }
1380         }
1381 
1382         writer.println( "}" );
1383     }
1384 
1385     
1386     public void figureCaption()
1387     {
1388         Paragraph p = new Paragraph();
1389         p.justification = Sink.JUSTIFY_CENTER;
1390         p.spaceBefore /= 2;
1391         beginParagraph( p );
1392     }
1393 
1394     
1395     public void figureCaption_()
1396     {
1397         endParagraph();
1398     }
1399 
1400     
1401     public void horizontalRule()
1402     {
1403         writer.print( "\\pard\\li" + indentation.get() );
1404 
1405         int skip = space.getNext();
1406         if ( skip > 0 )
1407         {
1408             writer.print( "\\sb" + skip );
1409         }
1410         space.setNext( skip );
1411 
1412         writer.print( "\\brdrb\\brdrs\\brdrw" + BORDER_WIDTH );
1413         writer.println( "\\plain\\fs1\\par" );
1414     }
1415 
1416     
1417     public void pageBreak()
1418     {
1419         writer.println( "\\page" );
1420     }
1421 
1422     
1423     public void anchor( String name )
1424     {
1425         
1426     }
1427 
1428     
1429     public void anchor_()
1430     {
1431         
1432     }
1433 
1434     
1435     public void link( String name )
1436     {
1437         
1438     }
1439 
1440     
1441     public void link_()
1442     {
1443         
1444     }
1445 
1446     
1447     public void italic()
1448     {
1449         beginStyle( STYLE_ITALIC );
1450     }
1451 
1452     
1453     public void italic_()
1454     {
1455         endStyle();
1456     }
1457 
1458     
1459     public void bold()
1460     {
1461         beginStyle( STYLE_BOLD );
1462     }
1463 
1464     
1465     public void bold_()
1466     {
1467         endStyle();
1468     }
1469 
1470     
1471     public void monospaced()
1472     {
1473         beginStyle( STYLE_TYPEWRITER );
1474     }
1475 
1476     
1477     public void monospaced_()
1478     {
1479         endStyle();
1480     }
1481 
1482     private void beginStyle( int style )
1483     {
1484         this.style = style;
1485 
1486         switch ( context.get() )
1487         {
1488             case CONTEXT_TABLE:
1489                 break;
1490             default:
1491                 if ( paragraph != null )
1492                 {
1493                     switch ( style )
1494                     {
1495                         case STYLE_ITALIC:
1496                             writer.println( "{\\i" );
1497                             break;
1498                         case STYLE_BOLD:
1499                             writer.println( "{\\b" );
1500                             break;
1501                         case STYLE_TYPEWRITER:
1502                             writer.println( "{\\f1" );
1503                             break;
1504                         default:
1505                             writer.println( "{" );
1506                             break;
1507                     }
1508                 }
1509                 break;
1510         }
1511     }
1512 
1513     private void endStyle()
1514     {
1515         style = STYLE_ROMAN;
1516 
1517         switch ( context.get() )
1518         {
1519             case CONTEXT_TABLE:
1520                 break;
1521             default:
1522                 if ( paragraph != null )
1523                 {
1524                     writer.println( "}" );
1525                 }
1526                 break;
1527         }
1528     }
1529 
1530     
1531     public void lineBreak()
1532     {
1533         switch ( context.get() )
1534         {
1535             case CONTEXT_TABLE:
1536                 cell.add( line );
1537                 line = new Line();
1538                 break;
1539             default:
1540                 writer.println( "\\line" );
1541                 break;
1542         }
1543     }
1544 
1545     
1546     public void nonBreakingSpace()
1547     {
1548         switch ( context.get() )
1549         {
1550             case CONTEXT_TABLE:
1551                 line.add( new Item( style, " " ) );
1552                 break;
1553             default:
1554                 writer.println( "\\~" );
1555                 break;
1556         }
1557     }
1558 
1559     
1560     public void text( String text )
1561     {
1562         switch ( context.get() )
1563         {
1564             case CONTEXT_VERBATIM:
1565                 verbatim.append( text );
1566                 break;
1567 
1568             case CONTEXT_TABLE:
1569                 StringTokenizer t = new StringTokenizer( text, EOL, true );
1570                 while ( t.hasMoreTokens() )
1571                 {
1572                     String token = t.nextToken();
1573                     if ( token.equals( EOL ) )
1574                     {
1575                         cell.add( line );
1576                         line = new Line();
1577                     }
1578                     else
1579                     {
1580                         line.add( new Item( style, normalize( token ) ) );
1581                     }
1582                 }
1583                 break;
1584 
1585             default:
1586                 if ( paragraph == null )
1587                 {
1588                     beginParagraph( new Paragraph() );
1589                 }
1590                 writer.println( escape( normalize( text ) ) );
1591         }
1592     }
1593 
1594     
1595 
1596 
1597 
1598 
1599 
1600     public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
1601     {
1602         String msg = "Unknown Sink event: '" + name + "', ignoring!";
1603         logMessage( "unknownEvent", msg );
1604     }
1605 
1606     private static String normalize( String s )
1607     {
1608         int length = s.length();
1609         StringBuffer buffer = new StringBuffer( length );
1610 
1611         for ( int i = 0; i < length; ++i )
1612         {
1613             char c = s.charAt( i );
1614 
1615             if ( Character.isWhitespace( c ) )
1616             {
1617                 if ( buffer.length() == 0 || buffer.charAt( buffer.length() - 1 ) != ' ' )
1618                 {
1619                     buffer.append( ' ' );
1620                 }
1621             }
1622 
1623             else
1624             {
1625                 buffer.append( c );
1626             }
1627         }
1628 
1629         return buffer.toString();
1630     }
1631 
1632     private static String escape( String s )
1633     {
1634         int length = s.length();
1635         StringBuffer buffer = new StringBuffer( length );
1636 
1637         for ( int i = 0; i < length; ++i )
1638         {
1639             char c = s.charAt( i );
1640             switch ( c )
1641             {
1642                 case '\\':
1643                     buffer.append( "\\\\" );
1644                     break;
1645                 case '{':
1646                     buffer.append( "\\{" );
1647                     break;
1648                 case '}':
1649                     buffer.append( "\\}" );
1650                     break;
1651                 default:
1652                     buffer.append( c );
1653             }
1654         }
1655 
1656         return buffer.toString();
1657     }
1658 
1659     
1660 
1661 
1662 
1663 
1664 
1665 
1666     protected Font getFont( int style, int size )
1667     {
1668         Font font = null;
1669 
1670         StringBuffer buf = new StringBuffer();
1671         buf.append( style );
1672         buf.append( size );
1673         String key = buf.toString();
1674 
1675         Object object = fontTable.get( key );
1676         if ( object == null )
1677         {
1678             try
1679             {
1680                 font = new Font( style, size );
1681                 fontTable.put( key, font );
1682             }
1683             catch ( Exception ignored )
1684             {
1685                 if ( getLog().isDebugEnabled() )
1686                 {
1687                     getLog().debug( ignored.getMessage(), ignored );
1688                 }
1689             }
1690         }
1691         else
1692         {
1693             font = (Font) object;
1694         }
1695 
1696         return font;
1697     }
1698 
1699     private static int textWidth( String text, Font font )
1700     {
1701         int width = 0;
1702         StringTokenizer t = new StringTokenizer( text, EOL );
1703 
1704         while ( t.hasMoreTokens() )
1705         {
1706             int w = font.textExtents( t.nextToken() ).width;
1707             if ( w > width )
1708             {
1709                 width = w;
1710             }
1711         }
1712 
1713         return width;
1714     }
1715 
1716 
1717     
1718     public void flush()
1719     {
1720         writer.flush();
1721     }
1722 
1723     
1724     public void close()
1725     {
1726         writer.close();
1727 
1728         if ( getLog().isWarnEnabled() && this.warnMessages != null )
1729         {
1730             for ( Iterator it = this.warnMessages.entrySet().iterator(); it.hasNext(); )
1731             {
1732                 Map.Entry entry = (Map.Entry) it.next();
1733 
1734                 Set set = (Set) entry.getValue();
1735 
1736                 for ( Iterator it2 = set.iterator(); it2.hasNext(); )
1737                 {
1738                     String msg = (String) it2.next();
1739 
1740                     getLog().warn( msg );
1741                 }
1742             }
1743 
1744             this.warnMessages = null;
1745         }
1746 
1747         init();
1748     }
1749 
1750     
1751 
1752 
1753 
1754 
1755 
1756 
1757 
1758     private void logMessage( String key, String msg )
1759     {
1760         msg = "[RTF Sink] " + msg;
1761         if ( getLog().isDebugEnabled() )
1762         {
1763             getLog().debug( msg );
1764 
1765             return;
1766         }
1767 
1768         if ( warnMessages == null )
1769         {
1770             warnMessages = new HashMap();
1771         }
1772 
1773         Set set = (Set) warnMessages.get( key );
1774         if ( set == null )
1775         {
1776             set = new TreeSet();
1777         }
1778         set.add( msg );
1779         warnMessages.put( key, set );
1780     }
1781 
1782     
1783     protected void init()
1784     {
1785         super.init();
1786 
1787         this.fontTable.clear();
1788         this.context = new Context();
1789         this.paragraph = null;
1790         this.indentation = new Indentation( 0 );
1791         this.space = new Space( 20 * DEFAULT_SPACING );
1792         Font font = getFont( STYLE_BOLD, fontSize );
1793         if ( font != null )
1794         {
1795             this.listItemIndent = textWidth( LIST_ITEM_HEADER, font );
1796         }
1797         this.numbering.clear();
1798         this.itemNumber.clear();
1799         this.style = STYLE_ROMAN;
1800         this.sectionLevel = 0;
1801         this.emptyHeader = false;
1802         this.verbatim = null;
1803         this.frame = false;
1804         this.table = null;
1805         this.row = null;
1806         this.cell = null;
1807         this.line = null;
1808         this.warnMessages = null;
1809     }
1810 
1811     
1812 
1813     static class Counter
1814     {
1815         private int value;
1816 
1817         Counter( int value )
1818         {
1819             set( value );
1820         }
1821 
1822         void set( int value )
1823         {
1824             this.value = value;
1825         }
1826 
1827         int get()
1828         {
1829             return value;
1830         }
1831 
1832         void increment()
1833         {
1834             increment( 1 );
1835         }
1836 
1837         void increment( int value )
1838         {
1839             this.value += value;
1840         }
1841     }
1842 
1843     static class Context
1844     {
1845         private int context = CONTEXT_UNDEFINED;
1846 
1847         private Vector stack = new Vector();
1848 
1849         void set( int context )
1850         {
1851             stack.addElement( new Integer( this.context ) );
1852             this.context = context;
1853         }
1854 
1855         void restore()
1856         {
1857             if ( !stack.isEmpty() )
1858             {
1859                 context = ( (Integer) stack.lastElement() ).intValue();
1860                 stack.removeElementAt( stack.size() - 1 );
1861             }
1862         }
1863 
1864         int get()
1865         {
1866             return context;
1867         }
1868     }
1869 
1870     class Paragraph
1871     {
1872         int style = 0;
1873 
1874         int justification = Sink.JUSTIFY_LEFT;
1875 
1876         int leftIndent = indentation.get();
1877 
1878         int rightIndent = 0;
1879 
1880         int firstLineIndent = 0;
1881 
1882         int spaceBefore = space.getNext();
1883 
1884         int spaceAfter = 0;
1885 
1886         boolean frame = false;
1887 
1888         int fontStyle = STYLE_ROMAN;
1889 
1890         int fontSize = RtfSink.this.fontSize;
1891 
1892         Paragraph()
1893         {
1894             
1895         }
1896 
1897         Paragraph( int style, int size )
1898         {
1899             fontStyle = style;
1900             fontSize = size;
1901         }
1902 
1903         void begin()
1904         {
1905             writer.print( "\\pard" );
1906             if ( style > 0 )
1907             {
1908                 writer.print( "\\s" + style );
1909             }
1910             switch ( justification )
1911             {
1912                 case Sink.JUSTIFY_LEFT:
1913                 default:
1914                     break;
1915                 case Sink.JUSTIFY_CENTER:
1916                     writer.print( "\\qc" );
1917                     break;
1918                 case Sink.JUSTIFY_RIGHT:
1919                     writer.print( "\\qr" );
1920                     break;
1921             }
1922             if ( leftIndent != 0 )
1923             {
1924                 writer.print( "\\li" + leftIndent );
1925             }
1926             if ( rightIndent != 0 )
1927             {
1928                 writer.print( "\\ri" + rightIndent );
1929             }
1930             if ( firstLineIndent != 0 )
1931             {
1932                 writer.print( "\\fi" + firstLineIndent );
1933             }
1934             if ( spaceBefore != 0 )
1935             {
1936                 writer.print( "\\sb" + spaceBefore );
1937             }
1938             if ( spaceAfter != 0 )
1939             {
1940                 writer.print( "\\sa" + spaceAfter );
1941             }
1942 
1943             if ( frame )
1944             {
1945                 writer.print( "\\box\\brdrs\\brdrw" + BORDER_WIDTH );
1946             }
1947 
1948             writer.print( "\\plain" );
1949             switch ( fontStyle )
1950             {
1951                 case STYLE_ROMAN:
1952                 default:
1953                     writer.print( "\\f0" );
1954                     break;
1955                 case STYLE_ITALIC:
1956                     writer.print( "\\f0\\i" );
1957                     break;
1958                 case STYLE_BOLD:
1959                     writer.print( "\\f0\\b" );
1960                     break;
1961                 case STYLE_TYPEWRITER:
1962                     writer.print( "\\f1" );
1963                     break;
1964             }
1965             writer.println( "\\fs" + ( 2 * fontSize ) );
1966         }
1967 
1968         void end()
1969         {
1970             writer.println( "\\par" );
1971         }
1972     }
1973 
1974     class Space
1975     {
1976         private int space;
1977 
1978         private int next;
1979 
1980         private Vector stack = new Vector();
1981 
1982         Space( int space 
1983         {
1984             this.space = space;
1985             next = space;
1986         }
1987 
1988         void set( int space 
1989         {
1990             stack.addElement( new Integer( this.space ) );
1991             this.space = space;
1992             next = space;
1993         }
1994 
1995         int get()
1996         {
1997             return space;
1998         }
1999 
2000         void restore()
2001         {
2002             if ( !stack.isEmpty() )
2003             {
2004                 space = ( (Integer) stack.lastElement() ).intValue();
2005                 stack.removeElementAt( stack.size() - 1 );
2006                 next = space;
2007             }
2008         }
2009 
2010         void setNext( int space 
2011         {
2012             next = space;
2013         }
2014 
2015         int getNext()
2016         {
2017             int nxt = this.next;
2018             this.next = space;
2019             return nxt;
2020         }
2021 
2022         void skip()
2023         {
2024             skip( getNext() );
2025         }
2026 
2027         void skip( int space 
2028         {
2029             writer.print( "\\pard" );
2030             if ( ( space -= 10 ) > 0 )
2031             {
2032                 writer.print( "\\sb" + space );
2033             }
2034             writer.println( "\\plain\\fs1\\par" );
2035         }
2036     }
2037 
2038     static class Indentation
2039     {
2040         private int indent;
2041 
2042         private Vector stack = new Vector();
2043 
2044         Indentation( int indent 
2045         {
2046             this.indent = indent;
2047         }
2048 
2049         void set( int indent 
2050         {
2051             stack.addElement( new Integer( this.indent ) );
2052             this.indent = indent;
2053         }
2054 
2055         int get()
2056         {
2057             return indent;
2058         }
2059 
2060         void restore()
2061         {
2062             if ( !stack.isEmpty() )
2063             {
2064                 indent = ( (Integer) stack.lastElement() ).intValue();
2065                 stack.removeElementAt( stack.size() - 1 );
2066             }
2067         }
2068 
2069         void add( int indent 
2070         {
2071             set( this.indent + indent );
2072         }
2073     }
2074 
2075     static class Table
2076     {
2077         int numColumns;
2078 
2079         int[] columnWidths;
2080 
2081         int[] justification;
2082 
2083         boolean grid;
2084 
2085         Vector rows;
2086 
2087         Table( int[] justification, boolean grid )
2088         {
2089             numColumns = justification.length;
2090             columnWidths = new int[numColumns];
2091             this.justification = justification;
2092             this.grid = grid;
2093             rows = new Vector();
2094         }
2095 
2096         void add( Row row )
2097         {
2098             rows.addElement( row );
2099 
2100             for ( int i = 0; i < numColumns; ++i )
2101             {
2102                 if ( i >= row.cells.size() )
2103                 {
2104                     break;
2105                 }
2106                 Cell cell = (Cell) row.cells.elementAt( i );
2107                 int width = cell.boundingBox().width;
2108                 if ( width > columnWidths[i] )
2109                 {
2110                     columnWidths[i] = width;
2111                 }
2112             }
2113         }
2114 
2115         int width()
2116         {
2117             int width = 0;
2118             for ( int i = 0; i < numColumns; ++i )
2119             {
2120                 width += columnWidths[i];
2121             }
2122             if ( grid )
2123             {
2124                 width += ( numColumns + 1 ) * BORDER_WIDTH;
2125             }
2126             return width;
2127         }
2128     }
2129 
2130     static class Row
2131     {
2132         Vector cells = new Vector();
2133 
2134         void add( Cell cell )
2135         {
2136             cells.addElement( cell );
2137         }
2138 
2139         int height()
2140         {
2141             int height = 0;
2142             int numCells = cells.size();
2143             for ( int i = 0; i < numCells; ++i )
2144             {
2145                 Cell cell = (Cell) cells.elementAt( i );
2146                 Box box = cell.boundingBox();
2147                 if ( box.height > height )
2148                 {
2149                     height = box.height;
2150                 }
2151             }
2152             return height;
2153         }
2154     }
2155 
2156     class Cell
2157     {
2158         Vector lines = new Vector();
2159 
2160         void add( Line line )
2161         {
2162             lines.addElement( line );
2163         }
2164 
2165         Box boundingBox()
2166         {
2167             int width = 0;
2168             int height = 0;
2169 
2170             for ( int i = 0; i < lines.size(); ++i )
2171             {
2172                 int w = 0;
2173                 int h = 0;
2174                 Line line = (Line) lines.elementAt( i );
2175 
2176                 for ( int j = 0; j < line.items.size(); ++j )
2177                 {
2178                     Item item = (Item) line.items.elementAt( j );
2179                     Font font = getFont( item.style, fontSize );
2180                     if ( font == null )
2181                     {
2182                         continue;
2183                     }
2184                     Font.TextExtents x = font.textExtents( item.text );
2185                     w += x.width;
2186                     if ( x.height > h )
2187                     {
2188                         h = x.height;
2189                     }
2190                 }
2191 
2192                 if ( w > width )
2193                 {
2194                     width = w;
2195                 }
2196                 height += h;
2197             }
2198 
2199             width += ( 2 * CELL_HORIZONTAL_PAD );
2200             height += ( 2 * CELL_VERTICAL_PAD );
2201 
2202             
2203             width += toTwips( 1., UNIT_PIXEL );
2204 
2205             return new Box( width, height );
2206         }
2207     }
2208 
2209     static class Line
2210     {
2211         Vector items = new Vector();
2212 
2213         void add( Item item )
2214         {
2215             items.addElement( item );
2216         }
2217     }
2218 
2219     static class Item
2220     {
2221         int style;
2222 
2223         String text;
2224 
2225         Item( int style, String text )
2226         {
2227             this.style = style;
2228             this.text = text;
2229         }
2230     }
2231 
2232     static class Box
2233     {
2234         int width;
2235 
2236         int height;
2237 
2238         Box( int width, int height )
2239         {
2240             this.width = width;
2241             this.height = height;
2242         }
2243     }
2244 }