View Javadoc

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