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