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