001package org.apache.maven.doxia.module.rtf;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.awt.Color;
023import java.io.BufferedOutputStream;
024import java.io.BufferedWriter;
025import java.io.IOException;
026import java.io.OutputStream;
027import java.io.OutputStreamWriter;
028import java.io.PrintWriter;
029import java.io.Writer;
030import java.util.ArrayList;
031import java.util.HashMap;
032import java.util.Hashtable;
033import java.util.List;
034import java.util.Map;
035import java.util.Set;
036import java.util.Stack;
037import java.util.StringTokenizer;
038import java.util.TreeSet;
039import java.util.Vector;
040
041import org.apache.maven.doxia.sink.Sink;
042import org.apache.maven.doxia.sink.SinkEventAttributes;
043import org.apache.maven.doxia.sink.impl.AbstractTextSink;
044import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
045
046/**
047 * <a href="http://en.wikipedia.org/wiki/Rich_Text_Format">RTF</a> Sink implementation.
048 *
049 * @since 1.0
050 */
051public class RtfSink
052    extends AbstractTextSink
053{
054    /** Paper width, 21 cm */
055    public static final double DEFAULT_PAPER_WIDTH = 21.;   /*cm*/
056
057    /** Paper height, 29.7 cm */
058    public static final double DEFAULT_PAPER_HEIGHT = 29.7; /*cm*/
059
060    /** Paper top margin, 2 cm */
061    public static final double DEFAULT_TOP_MARGIN = 2.;    /*cm*/
062
063    /** Paper bottom margin, 2 cm */
064    public static final double DEFAULT_BOTTOM_MARGIN = 2.; /*cm*/
065
066    /** Paper left margin, 2 cm */
067    public static final double DEFAULT_LEFT_MARGIN = 2.;   /*cm*/
068
069    /** Paper right margin, 2 cm */
070    public static final double DEFAULT_RIGHT_MARGIN = 2.;  /*cm*/
071
072    /** Font size, 10 pts */
073    public static final int DEFAULT_FONT_SIZE = 10; /*pts*/
074
075    /** Spacing, 10 pts */
076    public static final int DEFAULT_SPACING = 10;   /*pts*/
077
078    /** Resolution, 72 dpi */
079    public static final int DEFAULT_RESOLUTION = 72; /*dpi*/
080
081    /** Image format, bmp */
082    public static final String DEFAULT_IMAGE_FORMAT = "bmp";
083
084    /** Image type, palette */
085    public static final String DEFAULT_IMAGE_TYPE = "palette";
086
087    /** Data format, ascii */
088    public static final String DEFAULT_DATA_FORMAT = "ascii";
089
090    /** Codepage, 1252 */
091    public static final int DEFAULT_CODE_PAGE = 1252;
092
093    /** Constant <code>DEFAULT_CHAR_SET=0</code> */
094    public static final int DEFAULT_CHAR_SET = 0;
095
096    /** Constant <code>IMG_FORMAT_BMP="bmp"</code> */
097    public static final String IMG_FORMAT_BMP = "bmp";
098
099    /** 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}