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.Sink;
42 import org.apache.maven.doxia.sink.SinkEventAttributes;
43 import org.apache.maven.doxia.sink.impl.AbstractTextSink;
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 StringBuilder 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 , double height )
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 default:
640 }
641
642 Paragraph p = new Paragraph( stl, size );
643 p.style = styleNumber( sectionLevel );
644
645 beginParagraph( p );
646 }
647
648
649 public void sectionTitle_()
650 {
651 endParagraph();
652 }
653
654 private int styleNumber( int level )
655 {
656 return level;
657 }
658
659
660 public void list()
661 {
662 indentation.add( LIST_INDENT );
663 space.set( space.get() / 2 );
664 }
665
666
667 public void list_()
668 {
669 indentation.restore();
670 space.restore();
671 }
672
673
674 public void listItem()
675 {
676 Paragraph p = new Paragraph();
677 p.leftIndent = indentation.get() + listItemIndent;
678 p.firstLineIndent = ( -listItemIndent );
679 beginParagraph( p );
680
681 beginStyle( STYLE_BOLD );
682 writer.println( LIST_ITEM_HEADER );
683 endStyle();
684
685 indentation.add( listItemIndent );
686 space.set( space.get() / 2 );
687 }
688
689
690 public void listItem_()
691 {
692 endParagraph();
693
694 indentation.restore();
695 space.restore();
696 }
697
698
699 public void numberedList( int numbering )
700 {
701 this.numbering.addElement( Integer.valueOf( numbering ) );
702 itemNumber.addElement( new Counter( 0 ) );
703
704 indentation.add( LIST_INDENT );
705 space.set( space.get() / 2 );
706 }
707
708
709 public void numberedList_()
710 {
711 numbering.removeElementAt( numbering.size() - 1 );
712 itemNumber.removeElementAt( itemNumber.size() - 1 );
713
714 indentation.restore();
715 space.restore();
716 }
717
718
719 public void numberedListItem()
720 {
721 ( (Counter) itemNumber.lastElement() ).increment();
722
723 int indent = 0;
724 String header = getItemHeader();
725 Font font = getFont( STYLE_TYPEWRITER, fontSize );
726 if ( font != null )
727 {
728 indent = textWidth( header, font );
729 }
730
731 Paragraph p = new Paragraph();
732 p.leftIndent = indentation.get() + indent;
733 p.firstLineIndent = ( -indent );
734 beginParagraph( p );
735
736 beginStyle( STYLE_TYPEWRITER );
737 writer.println( header );
738 endStyle();
739
740 indentation.add( indent );
741 space.set( space.get() / 2 );
742 }
743
744
745 public void numberedListItem_()
746 {
747 endParagraph();
748
749 indentation.restore();
750 space.restore();
751 }
752
753 private String getItemHeader()
754 {
755 int nmb = ( (Integer) this.numbering.lastElement() ).intValue();
756 int iNmb = ( (Counter) this.itemNumber.lastElement() ).get();
757 StringBuilder buf = new StringBuilder();
758
759 switch ( nmb )
760 {
761 case Sink.NUMBERING_DECIMAL:
762 default:
763 buf.append( iNmb );
764 buf.append( ". " );
765 while ( buf.length() < 4 )
766 {
767 buf.append( ' ' );
768 }
769 break;
770
771 case Sink.NUMBERING_LOWER_ALPHA:
772 buf.append( AlphaNumerals.toString( iNmb, true ) );
773 buf.append( ") " );
774 break;
775
776 case Sink.NUMBERING_UPPER_ALPHA:
777 buf.append( AlphaNumerals.toString( iNmb, false ) );
778 buf.append( ". " );
779 break;
780
781 case Sink.NUMBERING_LOWER_ROMAN:
782 buf.append( RomanNumerals.toString( iNmb, true ) );
783 buf.append( ") " );
784 while ( buf.length() < 6 )
785 {
786 buf.append( ' ' );
787 }
788 break;
789
790 case Sink.NUMBERING_UPPER_ROMAN:
791 buf.append( RomanNumerals.toString( iNmb, false ) );
792 buf.append( ". " );
793 while ( buf.length() < 6 )
794 {
795 buf.append( ' ' );
796 }
797 break;
798 }
799
800 return buf.toString();
801 }
802
803
804 public void definitionList()
805 {
806 int next = space.getNext();
807
808 indentation.add( LIST_INDENT );
809 space.set( space.get() / 2 );
810 space.setNext( next );
811 }
812
813
814 public void definitionList_()
815 {
816 indentation.restore();
817 space.restore();
818 }
819
820
821 public void definitionListItem()
822 {
823 int next = space.getNext();
824 space.set( space.get() / 2 );
825 space.setNext( next );
826 }
827
828
829 public void definitionListItem_()
830 {
831 space.restore();
832 }
833
834
835 public void definedTerm()
836 {
837
838 }
839
840
841 public void definedTerm_()
842 {
843 endParagraph();
844 }
845
846
847 public void definition()
848 {
849 int next = space.getNext();
850
851 indentation.add( DEFINITION_INDENT );
852 space.set( space.get() / 2 );
853 space.setNext( next );
854 }
855
856
857 public void definition_()
858 {
859 endParagraph();
860
861 indentation.restore();
862 space.restore();
863 }
864
865
866 public void table()
867 {
868
869 }
870
871
872 public void table_()
873 {
874
875 }
876
877
878 public void tableRows( int[] justification, boolean grid )
879
880 {
881 table = new Table( justification, grid );
882 context.set( CONTEXT_TABLE );
883 }
884
885
886 public void tableRows_()
887 {
888 boolean bb = false;
889 boolean br = false;
890
891 int offset = ( pageWidth() - ( table.width() + indentation.get() ) ) / 2;
892 int x0 = indentation.get() + offset;
893
894 space.skip();
895
896 for ( int i = 0; i < table.rows.size(); ++i )
897 {
898 Row r = (Row) table.rows.elementAt( i );
899
900 writer.print( "\\trowd" );
901 writer.print( "\\trleft" + x0 );
902 writer.print( "\\trgaph" + CELL_HORIZONTAL_PAD );
903 writer.println( "\\trrh" + r.height() );
904
905 if ( table.grid )
906 {
907 if ( i == ( table.rows.size() - 1 ) )
908 {
909 bb = true;
910 }
911 br = false;
912 }
913
914 for ( int j = 0, x = x0; j < table.numColumns; ++j )
915 {
916 if ( table.grid )
917 {
918 if ( j == ( table.numColumns - 1 ) )
919 {
920 br = true;
921 }
922 setBorder( true, bb, true, br );
923 x += BORDER_WIDTH;
924 }
925 x += table.columnWidths[j];
926 writer.println( "\\clvertalc\\cellx" + x );
927 }
928
929 for ( int j = 0; j < table.numColumns; ++j )
930 {
931 if ( j >= r.cells.size() )
932 {
933 break;
934 }
935 Cell c = (Cell) r.cells.elementAt( j );
936
937 writer.print( "\\pard\\intbl" );
938 setJustification( table.justification[j] );
939 writer.println( "\\plain\\f0\\fs" + ( 2 * fontSize ) );
940
941 for ( int k = 0; k < c.lines.size(); ++k )
942 {
943 if ( k > 0 )
944 {
945 writer.println( "\\line" );
946 }
947 Line l = (Line) c.lines.elementAt( k );
948
949 for ( int n = 0; n < l.items.size(); ++n )
950 {
951 Item item = (Item) l.items.elementAt( n );
952 writer.print( "{" );
953 setStyle( item.style );
954 writer.println( escape( item.text ) );
955 writer.println( "}" );
956 }
957 }
958
959 writer.println( "\\cell" );
960 }
961
962 writer.println( "\\row" );
963 }
964
965 context.restore();
966 }
967
968 private int pageWidth()
969 {
970 double width = paperWidth - ( leftMargin + rightMargin );
971 return toTwips( width, UNIT_CENTIMETER );
972 }
973
974 private void setBorder( boolean bt, boolean bb, boolean bl, boolean br )
975 {
976 if ( bt )
977 {
978 writer.println( "\\clbrdrt\\brdrs\\brdrw" + BORDER_WIDTH );
979 }
980 if ( bb )
981 {
982 writer.println( "\\clbrdrb\\brdrs\\brdrw" + BORDER_WIDTH );
983 }
984 if ( bl )
985 {
986 writer.println( "\\clbrdrl\\brdrs\\brdrw" + BORDER_WIDTH );
987 }
988 if ( br )
989 {
990 writer.println( "\\clbrdrr\\brdrs\\brdrw" + BORDER_WIDTH );
991 }
992 }
993
994 private void setJustification( int justification )
995 {
996 switch ( justification )
997 {
998 case Sink.JUSTIFY_LEFT:
999 default:
1000 writer.println( "\\ql" );
1001 break;
1002 case Sink.JUSTIFY_CENTER:
1003 writer.println( "\\qc" );
1004 break;
1005 case Sink.JUSTIFY_RIGHT:
1006 writer.println( "\\qr" );
1007 break;
1008 }
1009 }
1010
1011 private void setStyle( int style )
1012 {
1013 switch ( style )
1014 {
1015 case STYLE_ITALIC:
1016 writer.println( "\\i" );
1017 break;
1018 case STYLE_BOLD:
1019 writer.println( "\\b" );
1020 break;
1021 case STYLE_TYPEWRITER:
1022 writer.println( "\\f1" );
1023 break;
1024 default:
1025 break;
1026 }
1027 }
1028
1029
1030 public void tableRow()
1031 {
1032 row = new Row();
1033 }
1034
1035
1036 public void tableRow_()
1037 {
1038 table.add( row );
1039 }
1040
1041
1042 public void tableHeaderCell()
1043 {
1044 tableCell();
1045 }
1046
1047
1048 public void tableHeaderCell_()
1049 {
1050 tableCell_();
1051 }
1052
1053
1054 public void tableCell()
1055 {
1056 cell = new Cell();
1057 line = new Line();
1058 }
1059
1060
1061 public void tableCell_()
1062 {
1063 cell.add( line );
1064 row.add( cell );
1065 }
1066
1067
1068 public void tableCaption()
1069 {
1070 Paragraph p = new Paragraph();
1071 p.justification = Sink.JUSTIFY_CENTER;
1072 p.spaceBefore /= 2;
1073 beginParagraph( p );
1074 }
1075
1076
1077 public void tableCaption_()
1078 {
1079 endParagraph();
1080 }
1081
1082
1083 public void paragraph()
1084 {
1085 if ( paragraph == null )
1086 {
1087 beginParagraph( new Paragraph() );
1088 }
1089 }
1090
1091
1092 public void paragraph_()
1093 {
1094 endParagraph();
1095 }
1096
1097 private void beginParagraph( Paragraph p )
1098 {
1099 p.begin();
1100 this.paragraph = p;
1101 if ( style != STYLE_ROMAN )
1102 {
1103 beginStyle( style );
1104 }
1105 }
1106
1107 private void endParagraph()
1108 {
1109 if ( paragraph != null )
1110 {
1111 if ( style != STYLE_ROMAN )
1112 {
1113 endStyle();
1114 }
1115 paragraph.end();
1116 paragraph = null;
1117 }
1118 }
1119
1120
1121 public void verbatim( boolean boxed )
1122 {
1123 verbatim = new StringBuilder();
1124 frame = boxed;
1125
1126 context.set( CONTEXT_VERBATIM );
1127 }
1128
1129
1130 public void verbatim_()
1131 {
1132 String text = verbatim.toString();
1133
1134 Paragraph p = new Paragraph();
1135 p.fontStyle = STYLE_TYPEWRITER;
1136 p.frame = frame;
1137
1138 beginParagraph( p );
1139
1140 StringTokenizer t = new StringTokenizer( text, EOL, true );
1141 while ( t.hasMoreTokens() )
1142 {
1143 String s = t.nextToken();
1144 if ( s.equals( EOL ) && t.hasMoreTokens() )
1145 {
1146 writer.println( "\\line" );
1147 }
1148 else
1149 {
1150 writer.println( escape( s ) );
1151 }
1152 }
1153
1154 endParagraph();
1155
1156 context.restore();
1157 }
1158
1159
1160 public void figure()
1161 {
1162
1163 }
1164
1165
1166 public void figure_()
1167 {
1168
1169 }
1170
1171
1172 public void figureGraphics( String name )
1173 {
1174 Paragraph p = new Paragraph();
1175 p.justification = Sink.JUSTIFY_CENTER;
1176 beginParagraph( p );
1177
1178 try
1179 {
1180 writeImage( name );
1181 }
1182 catch ( Exception e )
1183 {
1184 getLog().error( e.getMessage(), e );
1185 }
1186
1187 endParagraph();
1188 }
1189
1190 private void writeImage( String source )
1191 throws Exception
1192 {
1193 if ( !source.toLowerCase().endsWith( ".ppm" ) )
1194 {
1195
1196 String msg =
1197 "Unsupported image type for image file: '" + source + "'. Only PPM image type is "
1198 + "currently supported.";
1199 logMessage( "unsupportedImage", msg );
1200
1201 return;
1202 }
1203
1204 int bytesPerLine;
1205 PBMReader ppm = new PBMReader( source );
1206 WMFWriter.Dib dib = new WMFWriter.Dib();
1207 WMFWriter wmf = new WMFWriter();
1208
1209 int srcWidth = ppm.width();
1210 int srcHeight = ppm.height();
1211
1212 dib.biWidth = srcWidth;
1213 dib.biHeight = srcHeight;
1214 dib.biXPelsPerMeter = (int) ( resolution * 100. / 2.54 );
1215 dib.biYPelsPerMeter = dib.biXPelsPerMeter;
1216
1217 if ( imageType.equals( IMG_TYPE_RGB ) )
1218 {
1219 dib.biBitCount = 24;
1220 dib.biCompression = WMFWriter.Dib.BI_RGB;
1221
1222 bytesPerLine = 4 * ( ( 3 * srcWidth + 3 ) / 4 );
1223 dib.bitmap = new byte[srcHeight * bytesPerLine];
1224
1225 byte[] l = new byte[3 * srcWidth];
1226 for ( int i = ( srcHeight - 1 ); i >= 0; --i )
1227 {
1228 ppm.read( l, 0, l.length );
1229 for ( int j = 0, k = ( i * bytesPerLine ); j < l.length; j += 3 )
1230 {
1231
1232 dib.bitmap[k++] = l[j + 2];
1233 dib.bitmap[k++] = l[j + 1];
1234 dib.bitmap[k++] = l[j];
1235 }
1236 }
1237 }
1238 else
1239 {
1240 dib.biBitCount = 8;
1241
1242 bytesPerLine = 4 * ( ( srcWidth + 3 ) / 4 );
1243 byte[] bitmap = new byte[srcHeight * bytesPerLine];
1244
1245 Vector colors = new Vector( 256 );
1246 colors.addElement( Color.white );
1247 colors.addElement( Color.black );
1248
1249 byte[] l = new byte[3 * srcWidth];
1250 for ( int i = ( srcHeight - 1 ); i >= 0; --i )
1251 {
1252 ppm.read( l, 0, l.length );
1253 for ( int j = 0, k = ( i * bytesPerLine ); j < l.length; )
1254 {
1255 int r = (int) l[j++] & 0xff;
1256 int g = (int) l[j++] & 0xff;
1257 int b = (int) l[j++] & 0xff;
1258 Color color = new Color( r, g, b );
1259 int index = colors.indexOf( color );
1260 if ( index < 0 )
1261 {
1262 if ( colors.size() < colors.capacity() )
1263 {
1264 colors.addElement( color );
1265 index = colors.size() - 1;
1266 }
1267 else
1268 {
1269 index = 1;
1270 }
1271 }
1272 bitmap[k++] = (byte) index;
1273 }
1274 }
1275
1276 dib.biClrUsed = colors.size();
1277 dib.biClrImportant = dib.biClrUsed;
1278 dib.palette = new byte[4 * dib.biClrUsed];
1279 for ( int i = 0, j = 0; i < dib.biClrUsed; ++i, ++j )
1280 {
1281 Color color = (Color) colors.elementAt( i );
1282 dib.palette[j++] = (byte) color.getBlue();
1283 dib.palette[j++] = (byte) color.getGreen();
1284 dib.palette[j++] = (byte) color.getRed();
1285 }
1286
1287 if ( imageCompression )
1288 {
1289 dib.biCompression = WMFWriter.Dib.BI_RLE8;
1290 dib.bitmap = new byte[bitmap.length + ( 2 * ( bitmap.length / 255 + 1 ) )];
1291 dib.biSizeImage = WMFWriter.Dib.rlEncode8( bitmap, 0, bitmap.length, dib.bitmap, 0 );
1292 }
1293 else
1294 {
1295 dib.biCompression = WMFWriter.Dib.BI_RGB;
1296 dib.bitmap = bitmap;
1297 }
1298 }
1299
1300 if ( imageFormat.equals( IMG_FORMAT_WMF ) )
1301 {
1302 int[] parameters;
1303 WMFWriter.Record record;
1304
1305
1306
1307
1308
1309
1310
1311
1312 parameters = new int[1];
1313 parameters[0] = 1;
1314 record = new WMFWriter.Record( 0x0103, parameters );
1315 wmf.add( record );
1316
1317
1318 parameters = new int[2];
1319 record = new WMFWriter.Record( 0x020b, parameters );
1320 wmf.add( record );
1321 parameters = new int[2];
1322 parameters[0] = srcHeight;
1323 parameters[1] = srcWidth;
1324 record = new WMFWriter.Record( 0x020c, parameters );
1325 wmf.add( record );
1326
1327 parameters = new int[WMFWriter.DibBitBltRecord.P_COUNT];
1328
1329 parameters[WMFWriter.DibBitBltRecord.P_ROP_H] = 0x00cc;
1330 parameters[WMFWriter.DibBitBltRecord.P_ROP_L] = 0x0020;
1331 parameters[WMFWriter.DibBitBltRecord.P_WIDTH] = srcWidth;
1332 parameters[WMFWriter.DibBitBltRecord.P_HEIGHT] = srcHeight;
1333 record = new WMFWriter.DibBitBltRecord( parameters, dib );
1334 wmf.add( record );
1335 }
1336
1337 if ( imageFormat.equals( IMG_FORMAT_WMF ) )
1338 {
1339 writer.print( "{\\pict\\wmetafile1" );
1340 writer.println( "\\picbmp\\picbpp" + dib.biBitCount );
1341 }
1342 else
1343 {
1344 writer.print( "{\\pict\\dibitmap0\\wbmplanes1" );
1345 writer.print( "\\wbmbitspixel" + dib.biBitCount );
1346 writer.println( "\\wbmwidthbytes" + bytesPerLine );
1347 }
1348
1349 writer.print( "\\picw" + srcWidth );
1350 writer.print( "\\pich" + srcHeight );
1351 writer.print( "\\picwgoal" + toTwips( srcWidth, UNIT_PIXEL ) );
1352 writer.println( "\\pichgoal" + toTwips( srcHeight, UNIT_PIXEL ) );
1353
1354 if ( imageFormat.equals( IMG_FORMAT_WMF ) )
1355 {
1356 if ( imageDataFormat.equals( IMG_DATA_RAW ) )
1357 {
1358 writer.print( "\\bin" + ( 2 * wmf.size() ) + " " );
1359 writer.flush();
1360 wmf.write( stream );
1361 stream.flush();
1362 }
1363 else
1364 {
1365 wmf.print( writer );
1366 }
1367 }
1368 else
1369 {
1370 if ( imageDataFormat.equals( IMG_DATA_RAW ) )
1371 {
1372 writer.print( "\\bin" + ( 2 * dib.size() ) + " " );
1373 writer.flush();
1374 dib.write( stream );
1375 stream.flush();
1376 }
1377 else
1378 {
1379 dib.print( writer );
1380 }
1381 }
1382
1383 writer.println( "}" );
1384 }
1385
1386
1387 public void figureCaption()
1388 {
1389 Paragraph p = new Paragraph();
1390 p.justification = Sink.JUSTIFY_CENTER;
1391 p.spaceBefore /= 2;
1392 beginParagraph( p );
1393 }
1394
1395
1396 public void figureCaption_()
1397 {
1398 endParagraph();
1399 }
1400
1401
1402 public void horizontalRule()
1403 {
1404 writer.print( "\\pard\\li" + indentation.get() );
1405
1406 int skip = space.getNext();
1407 if ( skip > 0 )
1408 {
1409 writer.print( "\\sb" + skip );
1410 }
1411 space.setNext( skip );
1412
1413 writer.print( "\\brdrb\\brdrs\\brdrw" + BORDER_WIDTH );
1414 writer.println( "\\plain\\fs1\\par" );
1415 }
1416
1417
1418 public void pageBreak()
1419 {
1420 writer.println( "\\page" );
1421 }
1422
1423
1424 public void anchor( String name )
1425 {
1426
1427 }
1428
1429
1430 public void anchor_()
1431 {
1432
1433 }
1434
1435
1436 public void link( String name )
1437 {
1438
1439 }
1440
1441
1442 public void link_()
1443 {
1444
1445 }
1446
1447
1448 public void italic()
1449 {
1450 beginStyle( STYLE_ITALIC );
1451 }
1452
1453
1454 public void italic_()
1455 {
1456 endStyle();
1457 }
1458
1459
1460 public void bold()
1461 {
1462 beginStyle( STYLE_BOLD );
1463 }
1464
1465
1466 public void bold_()
1467 {
1468 endStyle();
1469 }
1470
1471
1472 public void monospaced()
1473 {
1474 beginStyle( STYLE_TYPEWRITER );
1475 }
1476
1477
1478 public void monospaced_()
1479 {
1480 endStyle();
1481 }
1482
1483 private void beginStyle( int style )
1484 {
1485 this.style = style;
1486
1487 switch ( context.get() )
1488 {
1489 case CONTEXT_TABLE:
1490 break;
1491 default:
1492 if ( paragraph != null )
1493 {
1494 switch ( style )
1495 {
1496 case STYLE_ITALIC:
1497 writer.println( "{\\i" );
1498 break;
1499 case STYLE_BOLD:
1500 writer.println( "{\\b" );
1501 break;
1502 case STYLE_TYPEWRITER:
1503 writer.println( "{\\f1" );
1504 break;
1505 default:
1506 writer.println( "{" );
1507 break;
1508 }
1509 }
1510 break;
1511 }
1512 }
1513
1514 private void endStyle()
1515 {
1516 style = STYLE_ROMAN;
1517
1518 switch ( context.get() )
1519 {
1520 case CONTEXT_TABLE:
1521 break;
1522 default:
1523 if ( paragraph != null )
1524 {
1525 writer.println( "}" );
1526 }
1527 break;
1528 }
1529 }
1530
1531
1532 public void lineBreak()
1533 {
1534 switch ( context.get() )
1535 {
1536 case CONTEXT_TABLE:
1537 cell.add( line );
1538 line = new Line();
1539 break;
1540 default:
1541 writer.println( "\\line" );
1542 break;
1543 }
1544 }
1545
1546
1547 public void nonBreakingSpace()
1548 {
1549 switch ( context.get() )
1550 {
1551 case CONTEXT_TABLE:
1552 line.add( new Item( style, " " ) );
1553 break;
1554 default:
1555 writer.println( "\\~" );
1556 break;
1557 }
1558 }
1559
1560
1561 public void text( String text )
1562 {
1563 switch ( context.get() )
1564 {
1565 case CONTEXT_VERBATIM:
1566 verbatim.append( text );
1567 break;
1568
1569 case CONTEXT_TABLE:
1570 StringTokenizer t = new StringTokenizer( text, EOL, true );
1571 while ( t.hasMoreTokens() )
1572 {
1573 String token = t.nextToken();
1574 if ( token.equals( EOL ) )
1575 {
1576 cell.add( line );
1577 line = new Line();
1578 }
1579 else
1580 {
1581 line.add( new Item( style, normalize( token ) ) );
1582 }
1583 }
1584 break;
1585
1586 default:
1587 if ( paragraph == null )
1588 {
1589 beginParagraph( new Paragraph() );
1590 }
1591 writer.println( escape( normalize( text ) ) );
1592 }
1593 }
1594
1595
1596
1597
1598
1599
1600
1601 public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
1602 {
1603 String msg = "Unknown Sink event: '" + name + "', ignoring!";
1604 logMessage( "unknownEvent", msg );
1605 }
1606
1607 private static String normalize( String s )
1608 {
1609 int length = s.length();
1610 StringBuilder buffer = new StringBuilder( length );
1611
1612 for ( int i = 0; i < length; ++i )
1613 {
1614 char c = s.charAt( i );
1615
1616 if ( Character.isWhitespace( c ) )
1617 {
1618 if ( buffer.length() == 0 || buffer.charAt( buffer.length() - 1 ) != ' ' )
1619 {
1620 buffer.append( ' ' );
1621 }
1622 }
1623
1624 else
1625 {
1626 buffer.append( c );
1627 }
1628 }
1629
1630 return buffer.toString();
1631 }
1632
1633 private static String escape( String s )
1634 {
1635 int length = s.length();
1636 StringBuilder buffer = new StringBuilder( length );
1637
1638 for ( int i = 0; i < length; ++i )
1639 {
1640 char c = s.charAt( i );
1641 switch ( c )
1642 {
1643 case '\\':
1644 buffer.append( "\\\\" );
1645 break;
1646 case '{':
1647 buffer.append( "\\{" );
1648 break;
1649 case '}':
1650 buffer.append( "\\}" );
1651 break;
1652 default:
1653 buffer.append( c );
1654 }
1655 }
1656
1657 return buffer.toString();
1658 }
1659
1660
1661
1662
1663
1664
1665
1666
1667 protected Font getFont( int style, int size )
1668 {
1669 Font font = null;
1670
1671 StringBuilder buf = new StringBuilder();
1672 buf.append( style );
1673 buf.append( size );
1674 String key = buf.toString();
1675
1676 Object object = fontTable.get( key );
1677 if ( object == null )
1678 {
1679 try
1680 {
1681 font = new Font( style, size );
1682 fontTable.put( key, font );
1683 }
1684 catch ( Exception ignored )
1685 {
1686 if ( getLog().isDebugEnabled() )
1687 {
1688 getLog().debug( ignored.getMessage(), ignored );
1689 }
1690 }
1691 }
1692 else
1693 {
1694 font = (Font) object;
1695 }
1696
1697 return font;
1698 }
1699
1700 private static int textWidth( String text, Font font )
1701 {
1702 int width = 0;
1703 StringTokenizer t = new StringTokenizer( text, EOL );
1704
1705 while ( t.hasMoreTokens() )
1706 {
1707 int w = font.textExtents( t.nextToken() ).width;
1708 if ( w > width )
1709 {
1710 width = w;
1711 }
1712 }
1713
1714 return width;
1715 }
1716
1717
1718
1719 public void flush()
1720 {
1721 writer.flush();
1722 }
1723
1724
1725 public void close()
1726 {
1727 writer.close();
1728
1729 if ( getLog().isWarnEnabled() && this.warnMessages != null )
1730 {
1731 for ( Iterator it = this.warnMessages.entrySet().iterator(); it.hasNext(); )
1732 {
1733 Map.Entry entry = (Map.Entry) it.next();
1734
1735 Set set = (Set) entry.getValue();
1736
1737 for ( Iterator it2 = set.iterator(); it2.hasNext(); )
1738 {
1739 String msg = (String) it2.next();
1740
1741 getLog().warn( msg );
1742 }
1743 }
1744
1745 this.warnMessages = null;
1746 }
1747
1748 init();
1749 }
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759 private void logMessage( String key, String msg )
1760 {
1761 msg = "[RTF Sink] " + msg;
1762 if ( getLog().isDebugEnabled() )
1763 {
1764 getLog().debug( msg );
1765
1766 return;
1767 }
1768
1769 if ( warnMessages == null )
1770 {
1771 warnMessages = new HashMap();
1772 }
1773
1774 Set set = (Set) warnMessages.get( key );
1775 if ( set == null )
1776 {
1777 set = new TreeSet();
1778 }
1779 set.add( msg );
1780 warnMessages.put( key, set );
1781 }
1782
1783
1784 protected void init()
1785 {
1786 super.init();
1787
1788 this.fontTable.clear();
1789 this.context = new Context();
1790 this.paragraph = null;
1791 this.indentation = new Indentation( 0 );
1792 this.space = new Space( 20 * DEFAULT_SPACING );
1793 Font font = getFont( STYLE_BOLD, fontSize );
1794 if ( font != null )
1795 {
1796 this.listItemIndent = textWidth( LIST_ITEM_HEADER, font );
1797 }
1798 this.numbering.clear();
1799 this.itemNumber.clear();
1800 this.style = STYLE_ROMAN;
1801 this.sectionLevel = 0;
1802 this.emptyHeader = false;
1803 this.verbatim = null;
1804 this.frame = false;
1805 this.table = null;
1806 this.row = null;
1807 this.cell = null;
1808 this.line = null;
1809 this.warnMessages = null;
1810 }
1811
1812
1813
1814 static class Counter
1815 {
1816 private int value;
1817
1818 Counter( int value )
1819 {
1820 set( value );
1821 }
1822
1823 void set( int value )
1824 {
1825 this.value = value;
1826 }
1827
1828 int get()
1829 {
1830 return value;
1831 }
1832
1833 void increment()
1834 {
1835 increment( 1 );
1836 }
1837
1838 void increment( int value )
1839 {
1840 this.value += value;
1841 }
1842 }
1843
1844 static class Context
1845 {
1846 private int context = CONTEXT_UNDEFINED;
1847
1848 private Vector stack = new Vector();
1849
1850 void set( int context )
1851 {
1852 stack.addElement( Integer.valueOf( this.context ) );
1853 this.context = context;
1854 }
1855
1856 void restore()
1857 {
1858 if ( !stack.isEmpty() )
1859 {
1860 context = ( (Integer) stack.lastElement() ).intValue();
1861 stack.removeElementAt( stack.size() - 1 );
1862 }
1863 }
1864
1865 int get()
1866 {
1867 return context;
1868 }
1869 }
1870
1871 class Paragraph
1872 {
1873 int style = 0;
1874
1875 int justification = Sink.JUSTIFY_LEFT;
1876
1877 int leftIndent = indentation.get();
1878
1879 int rightIndent = 0;
1880
1881 int firstLineIndent = 0;
1882
1883 int spaceBefore = space.getNext();
1884
1885 int spaceAfter = 0;
1886
1887 boolean frame = false;
1888
1889 int fontStyle = STYLE_ROMAN;
1890
1891 int fontSize = RtfSink.this.fontSize;
1892
1893 Paragraph()
1894 {
1895
1896 }
1897
1898 Paragraph( int style, int size )
1899 {
1900 fontStyle = style;
1901 fontSize = size;
1902 }
1903
1904 void begin()
1905 {
1906 writer.print( "\\pard" );
1907 if ( style > 0 )
1908 {
1909 writer.print( "\\s" + style );
1910 }
1911 switch ( justification )
1912 {
1913 case Sink.JUSTIFY_LEFT:
1914 default:
1915 break;
1916 case Sink.JUSTIFY_CENTER:
1917 writer.print( "\\qc" );
1918 break;
1919 case Sink.JUSTIFY_RIGHT:
1920 writer.print( "\\qr" );
1921 break;
1922 }
1923 if ( leftIndent != 0 )
1924 {
1925 writer.print( "\\li" + leftIndent );
1926 }
1927 if ( rightIndent != 0 )
1928 {
1929 writer.print( "\\ri" + rightIndent );
1930 }
1931 if ( firstLineIndent != 0 )
1932 {
1933 writer.print( "\\fi" + firstLineIndent );
1934 }
1935 if ( spaceBefore != 0 )
1936 {
1937 writer.print( "\\sb" + spaceBefore );
1938 }
1939 if ( spaceAfter != 0 )
1940 {
1941 writer.print( "\\sa" + spaceAfter );
1942 }
1943
1944 if ( frame )
1945 {
1946 writer.print( "\\box\\brdrs\\brdrw" + BORDER_WIDTH );
1947 }
1948
1949 writer.print( "\\plain" );
1950 switch ( fontStyle )
1951 {
1952 case STYLE_ROMAN:
1953 default:
1954 writer.print( "\\f0" );
1955 break;
1956 case STYLE_ITALIC:
1957 writer.print( "\\f0\\i" );
1958 break;
1959 case STYLE_BOLD:
1960 writer.print( "\\f0\\b" );
1961 break;
1962 case STYLE_TYPEWRITER:
1963 writer.print( "\\f1" );
1964 break;
1965 }
1966 writer.println( "\\fs" + ( 2 * fontSize ) );
1967 }
1968
1969 void end()
1970 {
1971 writer.println( "\\par" );
1972 }
1973 }
1974
1975 class Space
1976 {
1977 private int space;
1978
1979 private int next;
1980
1981 private Vector stack = new Vector();
1982
1983 Space( int space )
1984 {
1985 this.space = space;
1986 next = space;
1987 }
1988
1989 void set( int space )
1990 {
1991 stack.addElement( Integer.valueOf( this.space ) );
1992 this.space = space;
1993 next = space;
1994 }
1995
1996 int get()
1997 {
1998 return space;
1999 }
2000
2001 void restore()
2002 {
2003 if ( !stack.isEmpty() )
2004 {
2005 space = ( (Integer) stack.lastElement() ).intValue();
2006 stack.removeElementAt( stack.size() - 1 );
2007 next = space;
2008 }
2009 }
2010
2011 void setNext( int space )
2012 {
2013 next = space;
2014 }
2015
2016 int getNext()
2017 {
2018 int nxt = this.next;
2019 this.next = space;
2020 return nxt;
2021 }
2022
2023 void skip()
2024 {
2025 skip( getNext() );
2026 }
2027
2028 void skip( int space )
2029 {
2030 writer.print( "\\pard" );
2031 if ( ( space -= 10 ) > 0 )
2032 {
2033 writer.print( "\\sb" + space );
2034 }
2035 writer.println( "\\plain\\fs1\\par" );
2036 }
2037 }
2038
2039 static class Indentation
2040 {
2041 private int indent;
2042
2043 private Vector stack = new Vector();
2044
2045 Indentation( int indent )
2046 {
2047 this.indent = indent;
2048 }
2049
2050 void set( int indent )
2051 {
2052 stack.addElement( Integer.valueOf( this.indent ) );
2053 this.indent = indent;
2054 }
2055
2056 int get()
2057 {
2058 return indent;
2059 }
2060
2061 void restore()
2062 {
2063 if ( !stack.isEmpty() )
2064 {
2065 indent = ( (Integer) stack.lastElement() ).intValue();
2066 stack.removeElementAt( stack.size() - 1 );
2067 }
2068 }
2069
2070 void add( int indent )
2071 {
2072 set( this.indent + indent );
2073 }
2074 }
2075
2076 static class Table
2077 {
2078 int numColumns;
2079
2080 int[] columnWidths;
2081
2082 int[] justification;
2083
2084 boolean grid;
2085
2086 Vector rows;
2087
2088 Table( int[] justification, boolean grid )
2089 {
2090 numColumns = justification.length;
2091 columnWidths = new int[numColumns];
2092 this.justification = justification;
2093 this.grid = grid;
2094 rows = new Vector();
2095 }
2096
2097 void add( Row row )
2098 {
2099 rows.addElement( row );
2100
2101 for ( int i = 0; i < numColumns; ++i )
2102 {
2103 if ( i >= row.cells.size() )
2104 {
2105 break;
2106 }
2107 Cell cell = (Cell) row.cells.elementAt( i );
2108 int width = cell.boundingBox().width;
2109 if ( width > columnWidths[i] )
2110 {
2111 columnWidths[i] = width;
2112 }
2113 }
2114 }
2115
2116 int width()
2117 {
2118 int width = 0;
2119 for ( int i = 0; i < numColumns; ++i )
2120 {
2121 width += columnWidths[i];
2122 }
2123 if ( grid )
2124 {
2125 width += ( numColumns + 1 ) * BORDER_WIDTH;
2126 }
2127 return width;
2128 }
2129 }
2130
2131 static class Row
2132 {
2133 Vector cells = new Vector();
2134
2135 void add( Cell cell )
2136 {
2137 cells.addElement( cell );
2138 }
2139
2140 int height()
2141 {
2142 int height = 0;
2143 int numCells = cells.size();
2144 for ( int i = 0; i < numCells; ++i )
2145 {
2146 Cell cell = (Cell) cells.elementAt( i );
2147 Box box = cell.boundingBox();
2148 if ( box.height > height )
2149 {
2150 height = box.height;
2151 }
2152 }
2153 return height;
2154 }
2155 }
2156
2157 class Cell
2158 {
2159 Vector lines = new Vector();
2160
2161 void add( Line line )
2162 {
2163 lines.addElement( line );
2164 }
2165
2166 Box boundingBox()
2167 {
2168 int width = 0;
2169 int height = 0;
2170
2171 for ( int i = 0; i < lines.size(); ++i )
2172 {
2173 int w = 0;
2174 int h = 0;
2175 Line line = (Line) lines.elementAt( i );
2176
2177 for ( int j = 0; j < line.items.size(); ++j )
2178 {
2179 Item item = (Item) line.items.elementAt( j );
2180 Font font = getFont( item.style, fontSize );
2181 if ( font == null )
2182 {
2183 continue;
2184 }
2185 Font.TextExtents x = font.textExtents( item.text );
2186 w += x.width;
2187 if ( x.height > h )
2188 {
2189 h = x.height;
2190 }
2191 }
2192
2193 if ( w > width )
2194 {
2195 width = w;
2196 }
2197 height += h;
2198 }
2199
2200 width += ( 2 * CELL_HORIZONTAL_PAD );
2201 height += ( 2 * CELL_VERTICAL_PAD );
2202
2203
2204 width += toTwips( 1., UNIT_PIXEL );
2205
2206 return new Box( width, height );
2207 }
2208 }
2209
2210 static class Line
2211 {
2212 Vector items = new Vector();
2213
2214 void add( Item item )
2215 {
2216 items.addElement( item );
2217 }
2218 }
2219
2220 static class Item
2221 {
2222 int style;
2223
2224 String text;
2225
2226 Item( int style, String text )
2227 {
2228 this.style = style;
2229 this.text = text;
2230 }
2231 }
2232
2233 static class Box
2234 {
2235 int width;
2236
2237 int height;
2238
2239 Box( int width, int height )
2240 {
2241 this.width = width;
2242 this.height = height;
2243 }
2244 }
2245 }