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