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