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