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