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 }