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