001package org.apache.maven.doxia.module.itext;
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 com.lowagie.text.BadElementException;
023import com.lowagie.text.ElementTags;
024import com.lowagie.text.Image;
025
026import java.awt.Color;
027import java.io.File;
028import java.io.IOException;
029import java.io.LineNumberReader;
030import java.io.StringReader;
031import java.io.StringWriter;
032import java.io.Writer;
033import java.net.MalformedURLException;
034import java.net.URL;
035import java.util.ArrayList;
036import java.util.HashMap;
037import java.util.List;
038import java.util.Locale;
039import java.util.Map;
040import java.util.Set;
041import java.util.Stack;
042import java.util.TreeSet;
043
044import org.apache.maven.doxia.sink.Sink;
045import org.apache.maven.doxia.sink.SinkEventAttributes;
046import org.apache.maven.doxia.sink.impl.AbstractXmlSink;
047import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
048import org.apache.maven.doxia.util.DoxiaUtils;
049import org.apache.maven.doxia.util.HtmlTools;
050
051import org.codehaus.plexus.util.IOUtil;
052import org.codehaus.plexus.util.StringUtils;
053import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
054import org.codehaus.plexus.util.xml.XMLWriter;
055
056/**
057 * <p>A doxia Sink which produces an XML Front End document for <code>iText</code> framework.</p>
058 * Known limitations:
059 * <ul>
060 * <li>Roman lists are not supported.</li>
061 * <li>Horizontal rule is not supported with 1.3.
062 * See <a href="http://www.mail-archive.com/itext-questions@lists.sourceforge.net/msg10323.html">
063 * http://www.mail-archive.com/itext-questions@lists.sourceforge.net/msg10323.html</a></li>
064 * <li>iText has some problems with <code>ElementTags.TABLE</code> and <code>ElementTags.TABLEFITSPAGE</code>.
065 * See <a href="http://sourceforge.net/tracker/index.php?func=detail&aid=786427&group_id=15255&atid=115255">
066 * SourceForce Tracker</a>.</li>
067 * <li>Images could be on another page and next text on the last one.</li>
068 * </ul>
069 *
070 * @see <a href="http://www.lowagie.com/iText/tutorial/ch07.html">http://www.lowagie.com/iText/tutorial/ch07.html</a>
071 * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
072 */
073public class ITextSink
074    extends AbstractXmlSink
075{
076    /** This is the place where the iText DTD is located. IMPORTANT: this DTD is not uptodate! */
077    public static final String DTD = "http://itext.sourceforge.net/itext.dtd";
078
079    /** This is the reference to the DTD. */
080    public static final String DOCTYPE = "ITEXT SYSTEM \"" + DTD + "\"";
081
082    /** This is the default leading for chapter title */
083    public static final String DEFAULT_CHAPTER_TITLE_LEADING = "36.0";
084
085    /** This is the default leading for section title */
086    public static final String DEFAULT_SECTION_TITLE_LEADING = "24.0";
087
088    /** The ClassLoader used */
089    private ClassLoader currentClassLoader;
090
091    /** The action context */
092    private SinkActionContext actionContext;
093
094    /** The Writer used */
095    private Writer writer;
096
097    /** The XML Writer used */
098    private final XMLWriter xmlWriter;
099
100    private boolean writeStart;
101
102    /** The Header object */
103    private ITextHeader header;
104
105    /** The font object */
106    private ITextFont font;
107
108    private int numberDepth = 1;
109
110    private int depth = 0;
111
112    private StringWriter tableCaptionWriter = null;
113
114    private XMLWriter tableCaptionXMLWriter = null;
115
116    /** Flag to know if an anchor is defined or not. Used as workaround for iText which needs a defined local
117     * destination. */
118    private boolean anchorDefined = false;
119
120    /** Flag to know if an figure event is called. */
121    private boolean figureDefined = false;
122
123    /** Keep track of the closing tags for inline events. */
124    protected Stack<List<String>> inlineStack = new Stack<>();
125
126    /** Map of warn messages with a String as key to describe the error type and a Set as value.
127     * Using to reduce warn messages. */
128    private Map<String, Set<String>> warnMessages;
129
130    /**
131     * <p>Constructor for ITextSink.</p>
132     *
133     * @param writer the writer.
134     */
135    protected ITextSink( Writer writer )
136    {
137        this( writer, "UTF-8" );
138    }
139
140    /**
141     * <p>Constructor for ITextSink.</p>
142     *
143     * @param writer the writer.
144     * @param encoding the encoding.
145     * @since 1.1
146     */
147    protected ITextSink( Writer writer, String encoding )
148    {
149        // No doctype since itext doctype is not up to date!
150        this( new PrettyPrintXMLWriter( writer, encoding, null ) );
151
152        this.writer = writer;
153        this.writeStart = true;
154    }
155
156    /**
157     * <p>Constructor for ITextSink.</p>
158     *
159     * @param xmlWriter a pretty-printing xml writer.
160     */
161    protected ITextSink( PrettyPrintXMLWriter xmlWriter )
162    {
163        this.xmlWriter = xmlWriter;
164
165        this.writeStart = false;
166
167        init();
168    }
169
170    /**
171     * Get the current classLoader
172     *
173     * @return the current class loader
174     */
175    public ClassLoader getClassLoader()
176    {
177        return currentClassLoader;
178    }
179
180    /**
181     * Set a new class loader
182     *
183     * @param cl the class loader.
184     */
185    public void setClassLoader( ClassLoader cl )
186    {
187        currentClassLoader = cl;
188    }
189
190    // ----------------------------------------------------------------------
191    // Document
192    // ----------------------------------------------------------------------
193
194    /**
195     * {@inheritDoc}
196     */
197    public void close()
198    {
199        IOUtil.close( writer );
200
201        init();
202    }
203
204    /**
205     * {@inheritDoc}
206     */
207    public void flush()
208    {
209        if ( getLog().isWarnEnabled() && this.warnMessages != null )
210        {
211            for ( Map.Entry<String, Set<String>> entry : this.warnMessages.entrySet() )
212            {
213                for ( String msg : entry.getValue() )
214                {
215                    getLog().warn( msg );
216                }
217            }
218        }
219
220        this.warnMessages = null;
221    }
222
223    // ----------------------------------------------------------------------
224    // Header
225    // ----------------------------------------------------------------------
226
227    /**
228     * {@inheritDoc}
229     */
230    public void head_()
231    {
232        actionContext.release();
233    }
234
235    /**
236     * {@inheritDoc}
237     */
238    public void head()
239    {
240        //init(); // why? this causes DOXIA-413
241
242        actionContext.setAction( SinkActionContext.HEAD );
243    }
244
245    /**
246     * {@inheritDoc}
247     */
248    public void author_()
249    {
250        actionContext.release();
251    }
252
253    /**
254     * {@inheritDoc}
255     */
256    public void author()
257    {
258        actionContext.setAction( SinkActionContext.AUTHOR );
259    }
260
261    /**
262     * {@inheritDoc}
263     */
264    public void date_()
265    {
266        actionContext.release();
267    }
268
269    /**
270     * {@inheritDoc}
271     */
272    public void date()
273    {
274        actionContext.setAction( SinkActionContext.DATE );
275    }
276
277    /**
278     * {@inheritDoc}
279     */
280    public void title_()
281    {
282        actionContext.release();
283    }
284
285    /**
286     * {@inheritDoc}
287     */
288    public void title()
289    {
290        actionContext.setAction( SinkActionContext.TITLE );
291    }
292
293    // ----------------------------------------------------------------------
294    // Body
295    // ----------------------------------------------------------------------
296
297    /**
298     * {@inheritDoc}
299     */
300    public void body_()
301    {
302        if ( writeStart )
303        {
304            writeEndElement(); // ElementTags.CHAPTER
305
306            writeEndElement(); // ElementTags.ITEXT
307        }
308
309        actionContext.release();
310    }
311
312    /**
313     * {@inheritDoc}
314     */
315    public void body()
316    {
317        if ( writeStart )
318        {
319            writeStartElement( ElementTags.ITEXT );
320            writeAddAttribute( ElementTags.TITLE, header.getTitle() );
321            writeAddAttribute( ElementTags.AUTHOR, header.getAuthors() );
322            writeAddAttribute( ElementTags.CREATIONDATE, header.getDate() );
323            writeAddAttribute( ElementTags.SUBJECT, header.getTitle() );
324            writeAddAttribute( ElementTags.KEYWORDS, "" );
325            writeAddAttribute( ElementTags.PRODUCER, "Generated with Doxia by " + System.getProperty( "user.name" ) );
326            writeAddAttribute( ElementTags.PAGE_SIZE, ITextUtil.getPageSize( ITextUtil.getDefaultPageSize() ) );
327
328            writeStartElement( ElementTags.CHAPTER );
329            writeAddAttribute( ElementTags.NUMBERDEPTH, numberDepth );
330            writeAddAttribute( ElementTags.DEPTH, depth );
331            writeAddAttribute( ElementTags.INDENT, "0.0" );
332
333            writeStartElement( ElementTags.TITLE );
334            writeAddAttribute( ElementTags.LEADING, DEFAULT_CHAPTER_TITLE_LEADING );
335            writeAddAttribute( ElementTags.FONT, ITextFont.DEFAULT_FONT_NAME );
336            writeAddAttribute( ElementTags.SIZE, ITextFont.getSectionFontSize( 0 ) );
337            writeAddAttribute( ElementTags.STYLE, ITextFont.BOLD );
338            writeAddAttribute( ElementTags.BLUE, ITextFont.DEFAULT_FONT_COLOR_BLUE );
339            writeAddAttribute( ElementTags.GREEN, ITextFont.DEFAULT_FONT_COLOR_GREEN );
340            writeAddAttribute( ElementTags.RED, ITextFont.DEFAULT_FONT_COLOR_RED );
341            writeAddAttribute( ElementTags.ALIGN, ElementTags.ALIGN_CENTER );
342
343//            startChunk( ITextFont.DEFAULT_FONT_NAME, ITextFont.getSectionFontSize( 0 ),
344//                    ITextFont.BOLD, ITextFont.DEFAULT_FONT_COLOR_BLUE, ITextFont.DEFAULT_FONT_COLOR_GREEN,
345//                    ITextFont.DEFAULT_FONT_COLOR_RED, "top" );
346
347            writeStartElement( ElementTags.CHUNK );
348            writeAddAttribute( ElementTags.FONT, ITextFont.DEFAULT_FONT_NAME );
349            writeAddAttribute( ElementTags.SIZE, ITextFont.getSectionFontSize( 0 ) );
350            writeAddAttribute( ElementTags.STYLE, ITextFont.BOLD );
351            writeAddAttribute( ElementTags.BLUE, ITextFont.DEFAULT_FONT_COLOR_BLUE );
352            writeAddAttribute( ElementTags.GREEN, ITextFont.DEFAULT_FONT_COLOR_GREEN );
353            writeAddAttribute( ElementTags.RED, ITextFont.DEFAULT_FONT_COLOR_RED );
354//            writeAddAttribute( ElementTags.LOCALDESTINATION, "top" );
355
356            write( header.getTitle() );
357
358            writeEndElement(); // ElementTags.CHUNK
359
360            writeEndElement(); // ElementTags.TITLE
361        }
362
363        actionContext.setAction( SinkActionContext.BODY );
364    }
365
366    // ----------------------------------------------------------------------
367    // Sections
368    // ----------------------------------------------------------------------
369
370    /**
371     * {@inheritDoc}
372     */
373    public void sectionTitle()
374    {
375        actionContext.release();
376    }
377
378    /**
379     * {@inheritDoc}
380     */
381    public void sectionTitle_()
382    {
383        actionContext.setAction( SinkActionContext.SECTION_TITLE );
384    }
385
386    /**
387     * {@inheritDoc}
388     */
389    public void section1_()
390    {
391        writeEndElement(); // ElementTags.SECTION
392
393        numberDepth--;
394        depth = 0;
395
396        actionContext.release();
397    }
398
399    /**
400     * {@inheritDoc}
401     */
402    public void section1()
403    {
404        numberDepth++;
405        depth = 1;
406
407        writeStartElement( ElementTags.SECTION );
408        writeAddAttribute( ElementTags.NUMBERDEPTH, numberDepth );
409        writeAddAttribute( ElementTags.DEPTH, depth );
410        writeAddAttribute( ElementTags.INDENT, "0.0" );
411
412        lineBreak();
413
414        actionContext.setAction( SinkActionContext.SECTION_1 );
415    }
416
417    /**
418     * {@inheritDoc}
419     */
420    public void sectionTitle1_()
421    {
422        writeEndElement(); // ElementTags.TITLE
423
424        font.setSize( ITextFont.DEFAULT_FONT_SIZE );
425        bold_();
426
427        actionContext.release();
428    }
429
430    /**
431     * {@inheritDoc}
432     */
433    public void sectionTitle1()
434    {
435        font.setSize( ITextFont.getSectionFontSize( 1 ) );
436        font.setColor( Color.BLACK );
437        bold();
438
439        writeStartElement( ElementTags.TITLE );
440        writeAddAttribute( ElementTags.LEADING, DEFAULT_SECTION_TITLE_LEADING );
441        writeAddAttribute( ElementTags.FONT, font.getFontName() );
442        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
443        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
444        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
445        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
446        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
447//        writeAddAttribute( ElementTags.LOCALDESTINATION, "top" ); // trygve
448
449        actionContext.setAction( SinkActionContext.SECTION_TITLE_1 );
450    }
451
452    /**
453     * {@inheritDoc}
454     */
455    public void section2_()
456    {
457        writeEndElement(); // ElementTags.SECTION
458
459        numberDepth--;
460        depth = 0;
461
462        actionContext.release();
463    }
464
465    /**
466     * {@inheritDoc}
467     */
468    public void section2()
469    {
470        numberDepth++;
471        depth = 1;
472
473        writeStartElement( ElementTags.SECTION );
474        writeAddAttribute( ElementTags.NUMBERDEPTH, numberDepth );
475        writeAddAttribute( ElementTags.DEPTH, depth );
476        writeAddAttribute( ElementTags.INDENT, "0.0" );
477
478        lineBreak();
479
480        actionContext.setAction( SinkActionContext.SECTION_2 );
481    }
482
483    /**
484     * {@inheritDoc}
485     */
486    public void sectionTitle2_()
487    {
488        writeEndElement(); // ElementTags.TITLE
489
490        font.setSize( ITextFont.DEFAULT_FONT_SIZE );
491        bold_();
492
493        actionContext.release();
494    }
495
496    /**
497     * {@inheritDoc}
498     */
499    public void sectionTitle2()
500    {
501        font.setSize( ITextFont.getSectionFontSize( 2 ) );
502        font.setColor( Color.BLACK );
503        bold();
504
505        writeStartElement( ElementTags.TITLE );
506        writeAddAttribute( ElementTags.LEADING, DEFAULT_SECTION_TITLE_LEADING );
507        writeAddAttribute( ElementTags.FONT, font.getFontName() );
508        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
509        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
510        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
511        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
512        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
513//        writeAddAttribute( ElementTags.LOCALDESTINATION, "top" ); // trygve
514
515        actionContext.setAction( SinkActionContext.SECTION_TITLE_2 );
516    }
517
518    /**
519     * {@inheritDoc}
520     */
521    public void section3_()
522    {
523        writeEndElement(); // ElementTags.SECTION
524
525        numberDepth--;
526        depth = 1;
527
528        actionContext.release();
529    }
530
531    /**
532     * {@inheritDoc}
533     */
534    public void section3()
535    {
536        numberDepth++;
537        depth = 1;
538
539        writeStartElement( ElementTags.SECTION );
540        writeAddAttribute( ElementTags.NUMBERDEPTH, numberDepth );
541        writeAddAttribute( ElementTags.DEPTH, depth );
542        writeAddAttribute( ElementTags.INDENT, "0.0" );
543
544        lineBreak();
545
546        actionContext.setAction( SinkActionContext.SECTION_3 );
547    }
548
549    /**
550     * {@inheritDoc}
551     */
552    public void sectionTitle3_()
553    {
554        writeEndElement(); // ElementTags.TITLE
555
556        font.setSize( ITextFont.DEFAULT_FONT_SIZE );
557        bold_();
558
559        actionContext.release();
560    }
561
562    /**
563     * {@inheritDoc}
564     */
565    public void sectionTitle3()
566    {
567        font.setSize( ITextFont.getSectionFontSize( 3 ) );
568        font.setColor( Color.BLACK );
569        bold();
570
571        writeStartElement( ElementTags.TITLE );
572        writeAddAttribute( ElementTags.LEADING, DEFAULT_SECTION_TITLE_LEADING );
573        writeAddAttribute( ElementTags.FONT, font.getFontName() );
574        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
575        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
576        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
577        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
578        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
579//        writeAddAttribute( ElementTags.LOCALDESTINATION, "top" ); // trygve
580
581        actionContext.setAction( SinkActionContext.SECTION_TITLE_3 );
582    }
583
584    /**
585     * {@inheritDoc}
586     */
587    public void section4_()
588    {
589        writeEndElement(); // ElementTags.SECTION
590
591        numberDepth--;
592        depth = 1;
593
594        actionContext.release();
595    }
596
597    /**
598     * {@inheritDoc}
599     */
600    public void section4()
601    {
602        numberDepth++;
603        depth = 1;
604
605        writeStartElement( ElementTags.SECTION );
606        writeAddAttribute( ElementTags.NUMBERDEPTH, numberDepth );
607        writeAddAttribute( ElementTags.DEPTH, depth );
608        writeAddAttribute( ElementTags.INDENT, "0.0" );
609
610        lineBreak();
611
612        actionContext.setAction( SinkActionContext.SECTION_4 );
613    }
614
615    /**
616     * {@inheritDoc}
617     */
618    public void sectionTitle4_()
619    {
620        writeEndElement(); // ElementTags.TITLE
621
622        font.setSize( ITextFont.DEFAULT_FONT_SIZE );
623        bold_();
624
625        actionContext.release();
626    }
627
628    /**
629     * {@inheritDoc}
630     */
631    public void sectionTitle4()
632    {
633        font.setSize( ITextFont.getSectionFontSize( 4 ) );
634        font.setColor( Color.BLACK );
635        bold();
636
637        writeStartElement( ElementTags.TITLE );
638        writeAddAttribute( ElementTags.LEADING, DEFAULT_SECTION_TITLE_LEADING );
639        writeAddAttribute( ElementTags.FONT, font.getFontName() );
640        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
641        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
642        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
643        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
644        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
645//        writeAddAttribute( ElementTags.LOCALDESTINATION, "top" ); // trygve
646
647        actionContext.setAction( SinkActionContext.SECTION_TITLE_4 );
648    }
649
650    /**
651     * {@inheritDoc}
652     */
653    public void section5_()
654    {
655        writeEndElement(); // ElementTags.SECTION
656
657        numberDepth--;
658        depth = 1;
659
660        actionContext.release();
661    }
662
663    /**
664     * {@inheritDoc}
665     */
666    public void section5()
667    {
668        numberDepth++;
669        depth = 1;
670
671        writeStartElement( ElementTags.SECTION );
672        writeAddAttribute( ElementTags.NUMBERDEPTH, numberDepth );
673        writeAddAttribute( ElementTags.DEPTH, depth );
674        writeAddAttribute( ElementTags.INDENT, "0.0" );
675
676        lineBreak();
677
678        actionContext.setAction( SinkActionContext.SECTION_5 );
679    }
680
681    /**
682     * {@inheritDoc}
683     */
684    public void sectionTitle5_()
685    {
686        writeEndElement(); // ElementTags.TITLE
687
688        font.setSize( ITextFont.DEFAULT_FONT_SIZE );
689        bold_();
690
691        actionContext.release();
692    }
693
694    /**
695     * {@inheritDoc}
696     */
697    public void sectionTitle5()
698    {
699        font.setSize( ITextFont.getSectionFontSize( 5 ) );
700        font.setColor( Color.BLACK );
701        bold();
702
703        writeStartElement( ElementTags.TITLE );
704        writeAddAttribute( ElementTags.LEADING, DEFAULT_SECTION_TITLE_LEADING );
705        writeAddAttribute( ElementTags.FONT, font.getFontName() );
706        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
707        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
708        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
709        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
710        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
711//        writeAddAttribute( ElementTags.LOCALDESTINATION, "top" ); // trygve
712
713        actionContext.setAction( SinkActionContext.SECTION_TITLE_5 );
714    }
715
716    // ----------------------------------------------------------------------
717    // Paragraph
718    // ----------------------------------------------------------------------
719
720    /**
721     * {@inheritDoc}
722     */
723    public void paragraph_()
724    {
725        // Special case
726        if ( ( actionContext.getCurrentAction() == SinkActionContext.LIST_ITEM )
727            || ( actionContext.getCurrentAction() == SinkActionContext.NUMBERED_LIST_ITEM )
728            || ( actionContext.getCurrentAction() == SinkActionContext.DEFINITION ) )
729        {
730            return;
731        }
732
733        writeEndElement(); // ElementTags.PARAGRAPH
734
735        actionContext.release();
736    }
737
738    /**
739     * {@inheritDoc}
740     */
741    public void paragraph()
742    {
743        // Special case
744        if ( ( actionContext.getCurrentAction() == SinkActionContext.LIST_ITEM )
745            || ( actionContext.getCurrentAction() == SinkActionContext.NUMBERED_LIST_ITEM )
746            || ( actionContext.getCurrentAction() == SinkActionContext.DEFINITION ) )
747        {
748            return;
749        }
750
751        writeStartElement( ElementTags.PARAGRAPH );
752        writeStartElement( ElementTags.NEWLINE );
753        writeEndElement();
754
755        actionContext.setAction( SinkActionContext.PARAGRAPH );
756    }
757
758    // ----------------------------------------------------------------------
759    // Lists
760    // ----------------------------------------------------------------------
761
762    /**
763     * {@inheritDoc}
764     */
765    public void list_()
766    {
767        writeEndElement(); // ElementTags.LIST
768
769        writeEndElement(); // ElementTags.CHUNK
770
771        actionContext.release();
772    }
773
774    /**
775     * {@inheritDoc}
776     */
777    public void list()
778    {
779        writeStartElement( ElementTags.CHUNK );
780        writeAddAttribute( ElementTags.FONT, font.getFontName() );
781        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
782        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
783        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
784        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
785        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
786
787        writeStartElement( ElementTags.LIST );
788        writeAddAttribute( ElementTags.NUMBERED, Boolean.FALSE.toString() );
789        writeAddAttribute( ElementTags.SYMBOLINDENT, "15" );
790
791        actionContext.setAction( SinkActionContext.LIST );
792    }
793
794    /**
795     * {@inheritDoc}
796     */
797    public void listItem_()
798    {
799        writeEndElement(); // ElementTags.LISTITEM
800
801        actionContext.release();
802    }
803
804    /**
805     * {@inheritDoc}
806     */
807    public void listItem()
808    {
809        writeStartElement( ElementTags.LISTITEM );
810        writeAddAttribute( ElementTags.INDENTATIONLEFT, "20.0" );
811
812        actionContext.setAction( SinkActionContext.LIST_ITEM );
813    }
814
815    /**
816     * {@inheritDoc}
817     */
818    public void numberedList_()
819    {
820        writeEndElement(); // ElementTags.LIST
821
822        writeEndElement(); // ElementTags.CHUNK
823
824        actionContext.release();
825    }
826
827    /** {@inheritDoc} */
828    public void numberedList( int numbering )
829    {
830        writeStartElement( ElementTags.CHUNK );
831        writeAddAttribute( ElementTags.FONT, font.getFontName() );
832        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
833        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
834        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
835        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
836        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
837
838        writeStartElement( ElementTags.LIST );
839        writeAddAttribute( ElementTags.NUMBERED, Boolean.TRUE.toString() );
840        writeAddAttribute( ElementTags.SYMBOLINDENT, "20" );
841
842        switch ( numbering )
843        {
844            case Sink.NUMBERING_UPPER_ALPHA:
845                writeAddAttribute( ElementTags.LETTERED, Boolean.TRUE.toString() );
846                writeAddAttribute( ElementTags.FIRST, 'A' );
847                break;
848
849            case Sink.NUMBERING_LOWER_ALPHA:
850                writeAddAttribute( ElementTags.LETTERED, Boolean.TRUE.toString() );
851                writeAddAttribute( ElementTags.FIRST, 'a' );
852                break;
853
854            // TODO Doesn't work
855            case Sink.NUMBERING_UPPER_ROMAN:
856                writeAddAttribute( ElementTags.LETTERED, Boolean.TRUE.toString() );
857                writeAddAttribute( ElementTags.FIRST, 'I' );
858                break;
859
860            case Sink.NUMBERING_LOWER_ROMAN:
861                writeAddAttribute( ElementTags.LETTERED, Boolean.TRUE.toString() );
862                writeAddAttribute( ElementTags.FIRST, 'i' );
863                break;
864
865            case Sink.NUMBERING_DECIMAL:
866            default:
867                writeAddAttribute( ElementTags.LETTERED, Boolean.FALSE.toString() );
868        }
869
870        actionContext.setAction( SinkActionContext.NUMBERED_LIST );
871    }
872
873    /**
874     * {@inheritDoc}
875     */
876    public void numberedListItem_()
877    {
878        writeEndElement(); // ElementTags.LISTITEM
879
880        actionContext.release();
881    }
882
883    /**
884     * {@inheritDoc}
885     */
886    public void numberedListItem()
887    {
888        writeStartElement( ElementTags.LISTITEM );
889        writeAddAttribute( ElementTags.INDENTATIONLEFT, "20" );
890
891        actionContext.setAction( SinkActionContext.NUMBERED_LIST_ITEM );
892    }
893
894    /**
895     * {@inheritDoc}
896     */
897    public void definitionList_()
898    {
899        actionContext.release();
900    }
901
902    /**
903     * {@inheritDoc}
904     */
905    public void definitionList()
906    {
907        lineBreak();
908
909        actionContext.setAction( SinkActionContext.DEFINITION_LIST );
910    }
911
912    /**
913     * {@inheritDoc}
914     */
915    public void definedTerm_()
916    {
917        font.setSize( ITextFont.DEFAULT_FONT_SIZE );
918        bold_();
919
920        writeEndElement(); // ElementTags.CHUNK
921
922        actionContext.release();
923
924        lineBreak();
925    }
926
927    /**
928     * {@inheritDoc}
929     */
930    public void definedTerm()
931    {
932        font.setSize( ITextFont.DEFAULT_FONT_SIZE + 2 );
933        bold();
934
935        writeStartElement( ElementTags.CHUNK );
936        writeAddAttribute( ElementTags.FONT, font.getFontName() );
937        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
938        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
939        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
940        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
941        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
942
943        actionContext.setAction( SinkActionContext.DEFINED_TERM );
944    }
945
946    /**
947     * {@inheritDoc}
948     */
949    public void definition_()
950    {
951        writeEndElement(); // ElementTags.CHUNK
952
953        actionContext.release();
954
955        lineBreak();
956    }
957
958    /**
959     * {@inheritDoc}
960     */
961    public void definition()
962    {
963        writeStartElement( ElementTags.CHUNK );
964        writeAddAttribute( ElementTags.FONT, font.getFontName() );
965        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
966        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
967        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
968        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
969        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
970
971
972        writeStartElement( ElementTags.CHUNK );
973        writeAddAttribute( ElementTags.FONT, font.getFontName() );
974        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
975        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
976        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
977        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
978        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
979
980        // We need to add a non break space first to display empty string
981        write( "\u00A0" + StringUtils.repeat( " ", 16 ), false, false );
982
983        writeEndElement(); // ElementTags.CHUNK
984
985        actionContext.setAction( SinkActionContext.DEFINITION );
986    }
987
988    /**
989     * {@inheritDoc}
990     */
991    public void definitionListItem_()
992    {
993        actionContext.release();
994    }
995
996    /**
997     * {@inheritDoc}
998     */
999    public void definitionListItem()
1000    {
1001        actionContext.setAction( SinkActionContext.DEFINITION_LIST_ITEM );
1002    }
1003
1004    // ----------------------------------------------------------------------
1005    //  Tables
1006    // ----------------------------------------------------------------------
1007
1008    /**
1009     * {@inheritDoc}
1010     */
1011    public void table_()
1012    {
1013        if ( tableCaptionXMLWriter != null )
1014        {
1015            tableCaptionXMLWriter = null;
1016
1017            writeEndElement(); // ElementTags.TABLE
1018
1019            writeEndElement(); // ElementTags.CHUNK
1020
1021            writeStartElement( ElementTags.PARAGRAPH );
1022            writeAddAttribute( ElementTags.ALIGN, ElementTags.ALIGN_CENTER );
1023
1024            write( tableCaptionWriter.toString(), true );
1025
1026            writeEndElement(); // ElementTags.PARAGRAPH
1027
1028            tableCaptionWriter = null;
1029        }
1030        else
1031        {
1032            writeEndElement(); // ElementTags.TABLE
1033
1034            writeEndElement(); // ElementTags.CHUNK
1035        }
1036        actionContext.release();
1037    }
1038
1039    /**
1040     * {@inheritDoc}
1041     */
1042    public void table()
1043    {
1044        writeStartElement( ElementTags.CHUNK );
1045        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1046        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1047        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1048        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1049        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1050        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1051
1052        writeStartElement( ElementTags.TABLE );
1053        writeAddAttribute( ElementTags.LEFT, Boolean.TRUE.toString() );
1054        writeAddAttribute( ElementTags.RIGHT, Boolean.TRUE.toString() );
1055        writeAddAttribute( ElementTags.TOP, Boolean.TRUE.toString() );
1056        writeAddAttribute( ElementTags.BOTTOM, Boolean.TRUE.toString() );
1057        writeAddAttribute( ElementTags.ALIGN, ElementTags.ALIGN_CENTER );
1058        writeAddAttribute( ElementTags.WIDTH, "100.0%" );
1059        writeAddAttribute( ElementTags.TABLEFITSPAGE, Boolean.TRUE.toString() );
1060        writeAddAttribute( ElementTags.CELLSFITPAGE, Boolean.TRUE.toString() );
1061        writeAddAttribute( ElementTags.CELLPADDING, "10" );
1062        //writeAddAttribute( ElementTags.COLUMNS, "2" );
1063
1064        actionContext.setAction( SinkActionContext.TABLE );
1065    }
1066
1067    /**
1068     * {@inheritDoc}
1069     */
1070    public void tableCaption_()
1071    {
1072        actionContext.release();
1073    }
1074
1075    /**
1076     * {@inheritDoc}
1077     */
1078    public void tableCaption()
1079    {
1080        tableCaptionWriter = new StringWriter();
1081        tableCaptionXMLWriter = new PrettyPrintXMLWriter( tableCaptionWriter );
1082        actionContext.setAction( SinkActionContext.TABLE_CAPTION );
1083    }
1084
1085    /**
1086     * {@inheritDoc}
1087     */
1088    public void tableCell_()
1089    {
1090        writeEndElement(); // ElementTags.CELL
1091
1092        actionContext.release();
1093    }
1094
1095    /**
1096     * {@inheritDoc}
1097     */
1098    public void tableCell()
1099    {
1100        writeStartElement( ElementTags.CELL );
1101        writeAddAttribute( ElementTags.LEFT, Boolean.TRUE.toString() );
1102        writeAddAttribute( ElementTags.RIGHT, Boolean.TRUE.toString() );
1103        writeAddAttribute( ElementTags.TOP, Boolean.TRUE.toString() );
1104        writeAddAttribute( ElementTags.BOTTOM, Boolean.TRUE.toString() );
1105        writeAddAttribute( ElementTags.HORIZONTALALIGN, ElementTags.ALIGN_LEFT );
1106
1107        actionContext.setAction( SinkActionContext.TABLE_CELL );
1108    }
1109
1110    /** {@inheritDoc} */
1111    public void tableCell( String width )
1112    {
1113        actionContext.setAction( SinkActionContext.TABLE_CELL );
1114    }
1115
1116    /**
1117     * {@inheritDoc}
1118     */
1119    public void tableHeaderCell_()
1120    {
1121        writeEndElement(); // ElementTags.CELL
1122
1123        actionContext.release();
1124    }
1125
1126    /**
1127     * {@inheritDoc}
1128     */
1129    public void tableHeaderCell()
1130    {
1131        writeStartElement( ElementTags.CELL );
1132        writeAddAttribute( ElementTags.LEFT, Boolean.TRUE.toString() );
1133        writeAddAttribute( ElementTags.RIGHT, Boolean.TRUE.toString() );
1134        writeAddAttribute( ElementTags.TOP, Boolean.TRUE.toString() );
1135        writeAddAttribute( ElementTags.BOTTOM, Boolean.TRUE.toString() );
1136        writeAddAttribute( ElementTags.HEADER, Boolean.TRUE.toString() );
1137        writeAddAttribute( ElementTags.BGRED, Color.GRAY.getRed() );
1138        writeAddAttribute( ElementTags.BGBLUE, Color.GRAY.getBlue() );
1139        writeAddAttribute( ElementTags.BGGREEN, Color.GRAY.getGreen() );
1140        writeAddAttribute( ElementTags.HORIZONTALALIGN, ElementTags.ALIGN_CENTER );
1141
1142        actionContext.setAction( SinkActionContext.TABLE_HEADER_CELL );
1143    }
1144
1145    /** {@inheritDoc} */
1146    public void tableHeaderCell( String width )
1147    {
1148        actionContext.setAction( SinkActionContext.TABLE_HEADER_CELL );
1149    }
1150
1151    /**
1152     * {@inheritDoc}
1153     */
1154    public void tableRow_()
1155    {
1156        writeEndElement(); // ElementTags.ROW
1157
1158        actionContext.release();
1159    }
1160
1161    /**
1162     * {@inheritDoc}
1163     */
1164    public void tableRow()
1165    {
1166        writeStartElement( ElementTags.ROW );
1167
1168        actionContext.setAction( SinkActionContext.TABLE_ROW );
1169    }
1170
1171    /**
1172     * {@inheritDoc}
1173     */
1174    public void tableRows_()
1175    {
1176        //writeEndElement(); // ElementTags.TABLE
1177
1178        actionContext.release();
1179    }
1180
1181    /** {@inheritDoc} */
1182    public void tableRows( int[] justification, boolean grid )
1183    {
1184        // ElementTags.TABLE
1185        writeAddAttribute( ElementTags.COLUMNS, justification.length );
1186
1187        actionContext.setAction( SinkActionContext.TABLE_ROWS );
1188    }
1189
1190    // ----------------------------------------------------------------------
1191    // Verbatim
1192    // ----------------------------------------------------------------------
1193
1194    /**
1195     * {@inheritDoc}
1196     */
1197    public void verbatim_()
1198    {
1199        writeEndElement(); // ElementTags.CELL
1200
1201        writeEndElement(); // ElementTags.ROW
1202
1203        writeEndElement(); // ElementTags.TABLE
1204
1205        writeEndElement(); // ElementTags.CHUNK
1206
1207        actionContext.release();
1208    }
1209
1210    /** {@inheritDoc} */
1211    public void verbatim( boolean boxed )
1212    {
1213        // Always boxed
1214        writeStartElement( ElementTags.CHUNK );
1215        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1216        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1217        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1218        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1219        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1220        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1221
1222        writeStartElement( ElementTags.TABLE );
1223        writeAddAttribute( ElementTags.COLUMNS, "1" );
1224        writeAddAttribute( ElementTags.LEFT, Boolean.TRUE.toString() );
1225        writeAddAttribute( ElementTags.RIGHT, Boolean.TRUE.toString() );
1226        writeAddAttribute( ElementTags.TOP, Boolean.TRUE.toString() );
1227        writeAddAttribute( ElementTags.BOTTOM, Boolean.TRUE.toString() );
1228        writeAddAttribute( ElementTags.ALIGN, ElementTags.ALIGN_CENTER );
1229        writeAddAttribute( ElementTags.TABLEFITSPAGE, Boolean.TRUE.toString() );
1230        writeAddAttribute( ElementTags.CELLSFITPAGE, Boolean.TRUE.toString() );
1231        writeAddAttribute( ElementTags.CELLPADDING, "10" );
1232        writeAddAttribute( ElementTags.WIDTH, "100.0%" );
1233
1234        writeStartElement( ElementTags.ROW );
1235
1236        writeStartElement( ElementTags.CELL );
1237        writeAddAttribute( ElementTags.LEFT, Boolean.TRUE.toString() );
1238        writeAddAttribute( ElementTags.RIGHT, Boolean.TRUE.toString() );
1239        writeAddAttribute( ElementTags.TOP, Boolean.TRUE.toString() );
1240        writeAddAttribute( ElementTags.BOTTOM, Boolean.TRUE.toString() );
1241
1242        actionContext.setAction( SinkActionContext.VERBATIM );
1243    }
1244
1245    // ----------------------------------------------------------------------
1246    // Figures
1247    // ----------------------------------------------------------------------
1248
1249    /**
1250     * {@inheritDoc}
1251     */
1252    public void figure_()
1253    {
1254        writeEndElement(); // ElementTags.IMAGE
1255
1256        writeEndElement(); // ElementTags.CHUNK
1257
1258        actionContext.release();
1259
1260        figureDefined = false;
1261    }
1262
1263    /**
1264     * {@inheritDoc}
1265     */
1266    public void figure()
1267    {
1268        figureDefined = true;
1269
1270        writeStartElement( ElementTags.CHUNK );
1271        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1272        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1273        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1274        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1275        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1276        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1277
1278        writeStartElement( ElementTags.IMAGE );
1279
1280        actionContext.setAction( SinkActionContext.FIGURE );
1281    }
1282
1283    /**
1284     * {@inheritDoc}
1285     */
1286    public void figureCaption_()
1287    {
1288        actionContext.release();
1289    }
1290
1291    /**
1292     * {@inheritDoc}
1293     */
1294    public void figureCaption()
1295    {
1296        actionContext.setAction( SinkActionContext.FIGURE_CAPTION );
1297    }
1298
1299    /**
1300     * If the <code>name</code> is a relative link, the internal link will used a System property
1301     * <code>itext.basedir</code>, or the class loader.
1302     * {@inheritDoc}
1303     */
1304    public void figureGraphics( String name )
1305    {
1306        String urlName = null;
1307        File nameFile = null;
1308        if ( ( name.toLowerCase( Locale.ENGLISH ).startsWith( "http://" ) )
1309            || ( name.toLowerCase( Locale.ENGLISH ).startsWith( "https://" ) ) )
1310        {
1311            urlName = name;
1312        }
1313        else
1314        {
1315            if ( System.getProperty( "itext.basedir" ) != null )
1316            {
1317                try
1318                {
1319                    nameFile = new File( System.getProperty( "itext.basedir" ), name );
1320                    urlName = nameFile.toURI().toURL().toString();
1321                }
1322                catch ( MalformedURLException e )
1323                {
1324                    getLog().error( "MalformedURLException: " + e.getMessage(), e );
1325                }
1326            }
1327            else
1328            {
1329                if ( getClassLoader() != null )
1330                {
1331                    if ( getClassLoader().getResource( name ) != null )
1332                    {
1333                        urlName = getClassLoader().getResource( name ).toString();
1334                    }
1335                }
1336                else
1337                {
1338                    if ( ITextSink.class.getClassLoader().getResource( name ) != null )
1339                    {
1340                        urlName = ITextSink.class.getClassLoader().getResource( name ).toString();
1341                    }
1342                }
1343            }
1344        }
1345
1346        if ( urlName == null )
1347        {
1348            String msg =
1349                "No image '" + name
1350                    + "' found in the class loader. Try to call setClassLoader(ClassLoader) before.";
1351            logMessage( "imageNotFound", msg );
1352
1353            return;
1354        }
1355
1356        if ( nameFile != null && !nameFile.exists() )
1357        {
1358            String msg = "No image '" + nameFile + "' found in your system, check the path.";
1359            logMessage( "imageNotFound", msg );
1360
1361            return;
1362        }
1363
1364        boolean figureCalled = figureDefined;
1365        if ( !figureCalled )
1366        {
1367            figure();
1368        }
1369
1370        float width = 0;
1371        float height = 0;
1372        try
1373        {
1374            Image image = Image.getInstance( new URL( urlName ) );
1375            image.scaleToFit( ITextUtil.getDefaultPageSize().width() / 2, ITextUtil.getDefaultPageSize().height() / 2 );
1376            width = image.plainWidth();
1377            height = image.plainHeight();
1378        }
1379        catch ( BadElementException e )
1380        {
1381            getLog().error( "BadElementException: " + e.getMessage(), e );
1382        }
1383        catch ( MalformedURLException e )
1384        {
1385            getLog().error( "MalformedURLException: " + e.getMessage(), e );
1386        }
1387        catch ( IOException e )
1388        {
1389            getLog().error( "IOException: " + e.getMessage(), e );
1390        }
1391
1392        writeAddAttribute( ElementTags.URL, urlName );
1393        writeAddAttribute( ElementTags.ALIGN, ElementTags.ALIGN_MIDDLE );
1394        writeAddAttribute( ElementTags.PLAINWIDTH, String.valueOf( width ) );
1395        writeAddAttribute( ElementTags.PLAINHEIGHT, String.valueOf( height ) );
1396
1397        actionContext.setAction( SinkActionContext.FIGURE_GRAPHICS );
1398
1399        if ( !figureCalled )
1400        {
1401            figure_();
1402        }
1403    }
1404
1405    // ----------------------------------------------------------------------
1406    // Fonts
1407    // ----------------------------------------------------------------------
1408
1409    /**
1410     * {@inheritDoc}
1411     */
1412    public void inline()
1413    {
1414        inline( null );
1415    }
1416
1417    /** {@inheritDoc} */
1418    public void inline( SinkEventAttributes attributes )
1419    {
1420        List<String> tags = new ArrayList<>();
1421
1422        if ( attributes != null )
1423        {
1424
1425            if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "italic" ) )
1426            {
1427                font.addItalic();
1428                tags.add( 0, "italic" );
1429            }
1430
1431            if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "bold" ) )
1432            {
1433                font.addBold();
1434                tags.add( 0, "bold" );
1435            }
1436
1437            if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "code" ) )
1438            {
1439                font.setMonoSpaced( true );
1440                tags.add( 0, "code" );
1441            }
1442
1443        }
1444
1445        inlineStack.push( tags );
1446    }
1447
1448    /**
1449     * {@inheritDoc}
1450     */
1451    public void inline_()
1452    {
1453        for ( String tag: inlineStack.pop() )
1454        {
1455            if ( "italic".equals( tag ) )
1456            {
1457                font.removeItalic();
1458            }
1459            else if ( "bold".equals( tag ) )
1460            {
1461                font.removeBold();
1462            }
1463            else if ( "code".equals( tag ) )
1464            {
1465                font.setMonoSpaced( false );
1466            }
1467        }
1468    }
1469
1470    /**
1471     * {@inheritDoc}
1472     */
1473    public void bold_()
1474    {
1475        inline_();
1476    }
1477
1478    /**
1479     * {@inheritDoc}
1480     */
1481    public void bold()
1482    {
1483        inline( SinkEventAttributeSet.Semantics.BOLD );
1484    }
1485
1486    /**
1487     * {@inheritDoc}
1488     */
1489    public void italic_()
1490    {
1491        inline_();
1492    }
1493
1494    /**
1495     * {@inheritDoc}
1496     */
1497    public void italic()
1498    {
1499        inline( SinkEventAttributeSet.Semantics.ITALIC );
1500    }
1501
1502    /**
1503     * {@inheritDoc}
1504     */
1505    public void monospaced_()
1506    {
1507        inline_();
1508    }
1509
1510    /**
1511     * {@inheritDoc}
1512     */
1513    public void monospaced()
1514    {
1515        inline( SinkEventAttributeSet.Semantics.CODE );
1516    }
1517
1518    // ----------------------------------------------------------------------
1519    // Links
1520    // ----------------------------------------------------------------------
1521
1522    /**
1523     * {@inheritDoc}
1524     */
1525    public void link_()
1526    {
1527        writeEndElement(); // ElementTags.ANCHOR
1528
1529        font.setColor( Color.BLACK );
1530        font.removeUnderlined();
1531
1532        actionContext.release();
1533    }
1534
1535    /** {@inheritDoc} */
1536    public void link( String name )
1537    {
1538        if ( name == null )
1539        {
1540            throw new NullPointerException( "Link name cannot be null!" );
1541        }
1542
1543        font.setColor( Color.BLUE );
1544        font.addUnderlined();
1545
1546        writeStartElement( ElementTags.ANCHOR );
1547        if ( StringUtils.isNotEmpty( name ) && name.startsWith( "#" ) && StringUtils.isNotEmpty( header.getTitle() ) )
1548        {
1549            name = "#" + DoxiaUtils.encodeId( header.getTitle(), true ) + "_" + name.substring( 1 );
1550        }
1551        writeAddAttribute( ElementTags.REFERENCE, HtmlTools.escapeHTML( name ) );
1552        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1553        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1554        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1555        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1556        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1557        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1558
1559        actionContext.setAction( SinkActionContext.LINK );
1560    }
1561
1562    /**
1563     * {@inheritDoc}
1564     */
1565    public void anchor_()
1566    {
1567        if ( !anchorDefined )
1568        {
1569            // itext needs a defined local destination, we put an invisible text
1570            writeAddAttribute( ElementTags.BLUE, "255" );
1571            writeAddAttribute( ElementTags.GREEN, "255" );
1572            writeAddAttribute( ElementTags.RED, "255" );
1573
1574            write( "_" );
1575        }
1576
1577        anchorDefined = false;
1578
1579        writeEndElement(); // ElementTags.ANCHOR
1580
1581        actionContext.release();
1582    }
1583
1584    /** {@inheritDoc} */
1585    public void anchor( String name )
1586    {
1587        if ( name == null )
1588        {
1589            throw new NullPointerException( "Anchor name cannot be null!" );
1590        }
1591
1592        if ( StringUtils.isNotEmpty( header.getTitle() ) )
1593        {
1594            name = header.getTitle() + "_" + name;
1595        }
1596        String id = name;
1597
1598        if ( !DoxiaUtils.isValidId( id ) )
1599        {
1600            id = DoxiaUtils.encodeId( name, true );
1601
1602            String msg = "Modified invalid link: '" + name + "' to '" + id + "'";
1603            logMessage( "modifiedLink", msg );
1604        }
1605
1606        writeStartElement( ElementTags.ANCHOR );
1607        writeAddAttribute( ElementTags.NAME, id );
1608        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1609        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1610        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1611
1612        actionContext.setAction( SinkActionContext.ANCHOR );
1613    }
1614
1615    // ----------------------------------------------------------------------
1616    // Misc
1617    // ----------------------------------------------------------------------
1618
1619    /**
1620     * {@inheritDoc}
1621     */
1622    public void lineBreak()
1623    {
1624        // Special case for the header
1625        if ( ( actionContext.getCurrentAction() == SinkActionContext.AUTHOR )
1626            || ( actionContext.getCurrentAction() == SinkActionContext.DATE )
1627            || ( actionContext.getCurrentAction() == SinkActionContext.TITLE ) )
1628        {
1629            return;
1630        }
1631
1632        writeStartElement( ElementTags.NEWLINE );
1633        writeEndElement();
1634    }
1635
1636    /**
1637     * {@inheritDoc}
1638     */
1639    public void nonBreakingSpace()
1640    {
1641        write( " " );
1642    }
1643
1644    /**
1645     * {@inheritDoc}
1646     */
1647    public void pageBreak()
1648    {
1649        writeStartElement( ElementTags.NEWPAGE );
1650        writeEndElement();
1651    }
1652
1653    /**
1654     * {@inheritDoc}
1655     */
1656    public void horizontalRule()
1657    {
1658        writeStartElement( ElementTags.PARAGRAPH );
1659        writeAddAttribute( ElementTags.BLUE, "255" );
1660        writeAddAttribute( ElementTags.GREEN, "255" );
1661        writeAddAttribute( ElementTags.RED, "255" );
1662        write( "_" );
1663        writeEndElement();
1664
1665        writeStartElement( ElementTags.PARAGRAPH );
1666        writeStartElement( ElementTags.HORIZONTALRULE );
1667        writeEndElement();
1668        writeEndElement();
1669    }
1670
1671    // ----------------------------------------------------------------------
1672    // Text
1673    // ----------------------------------------------------------------------
1674
1675    /** {@inheritDoc} */
1676    public void rawText( String text )
1677    {
1678        writeStartElement( ElementTags.CHUNK );
1679        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1680        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1681        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1682        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1683        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1684        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1685
1686        write( text, false );
1687
1688        writeEndElement(); // ElementTags.CHUNK
1689    }
1690
1691    /** {@inheritDoc} */
1692    public void text( String text )
1693    {
1694        if ( StringUtils.isEmpty( text ) )
1695        {
1696            return;
1697        }
1698
1699        switch ( actionContext.getCurrentAction() )
1700        {
1701            case SinkActionContext.AUTHOR:
1702                header.addAuthor( text );
1703                break;
1704
1705            case SinkActionContext.DATE:
1706                header.setDate( text );
1707                break;
1708
1709            case SinkActionContext.TITLE:
1710                header.setTitle( text );
1711                break;
1712
1713            case SinkActionContext.TABLE_CAPTION:
1714                this.tableCaptionXMLWriter.writeText( text );
1715                break;
1716
1717            case SinkActionContext.VERBATIM:
1718                // Used to preserve indentation and formating
1719                LineNumberReader lnr = new LineNumberReader( new StringReader( text ) );
1720                String line;
1721                try
1722                {
1723                    while ( ( line = lnr.readLine() ) != null )
1724                    {
1725                        writeStartElement( ElementTags.CHUNK );
1726                        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1727                        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1728                        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1729                        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1730                        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1731                        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1732
1733                        write( "<![CDATA[", true );
1734                        // Special case
1735                        line = StringUtils.replace( line, "<![CDATA[", "< ![CDATA[" );
1736                        line = StringUtils.replace( line, "]]>", "]] >" );
1737                        write( line, true, false );
1738                        write( "]]>", true );
1739
1740                        writeEndElement();
1741                        lineBreak();
1742                    }
1743                }
1744                catch ( IOException e )
1745                {
1746                    throw new RuntimeException( "IOException: ", e );
1747                }
1748                break;
1749
1750            case SinkActionContext.FIGURE_CAPTION:
1751                writeAddAttribute( ElementTags.ALT, text );
1752                break;
1753
1754            case SinkActionContext.SECTION_TITLE:
1755            case SinkActionContext.SECTION_1:
1756            case SinkActionContext.SECTION_2:
1757            case SinkActionContext.SECTION_3:
1758            case SinkActionContext.SECTION_4:
1759            case SinkActionContext.SECTION_5:
1760            case SinkActionContext.FIGURE:
1761            case SinkActionContext.FIGURE_GRAPHICS:
1762            case SinkActionContext.TABLE_ROW:
1763            case SinkActionContext.TABLE:
1764            case SinkActionContext.HEAD:
1765            case SinkActionContext.UNDEFINED:
1766                break;
1767
1768            case SinkActionContext.ANCHOR:
1769                anchorDefined = true;
1770            case SinkActionContext.PARAGRAPH:
1771            case SinkActionContext.LINK:
1772            case SinkActionContext.TABLE_CELL:
1773            case SinkActionContext.TABLE_HEADER_CELL:
1774            case SinkActionContext.DEFINITION:
1775            case SinkActionContext.DEFINED_TERM:
1776            case SinkActionContext.NUMBERED_LIST_ITEM:
1777            case SinkActionContext.LIST_ITEM:
1778            case SinkActionContext.SECTION_TITLE_5:
1779            case SinkActionContext.SECTION_TITLE_4:
1780            case SinkActionContext.SECTION_TITLE_3:
1781            case SinkActionContext.SECTION_TITLE_2:
1782            case SinkActionContext.SECTION_TITLE_1:
1783            default:
1784                writeStartElement( ElementTags.CHUNK );
1785                writeAddAttribute( ElementTags.FONT, font.getFontName() );
1786                writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1787                writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1788                writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1789                writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1790                writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1791
1792                write( text );
1793
1794                writeEndElement(); // ElementTags.CHUNK
1795        }
1796    }
1797
1798    /**
1799     * {@inheritDoc}
1800     *
1801     * Unkown events just log a warning message but are ignored otherwise.
1802     * @see org.apache.maven.doxia.sink.Sink#unknown(String,Object[],SinkEventAttributes)
1803     */
1804    public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
1805    {
1806        String msg = "Unknown Sink event: '" + name + "', ignoring!";
1807        logMessage( "unknownEvent", msg );
1808    }
1809
1810    /**
1811     * {@inheritDoc}
1812     */
1813    protected void init()
1814    {
1815        super.init();
1816
1817        this.actionContext = new SinkActionContext();
1818        this.font = new ITextFont();
1819        this.header = new ITextHeader();
1820
1821        this.numberDepth = 1;
1822        this.depth = 0;
1823        this.tableCaptionWriter = null;
1824        this.tableCaptionXMLWriter = null;
1825        this.anchorDefined = false;
1826        this.figureDefined = false;
1827        this.warnMessages = null;
1828    }
1829
1830    /**
1831     * Convenience method to write a starting element.
1832     *
1833     * @param tag the name of the tag
1834     */
1835    private void writeStartElement( String tag )
1836    {
1837        if ( tableCaptionXMLWriter == null )
1838        {
1839            xmlWriter.startElement( tag );
1840        }
1841        else
1842        {
1843            tableCaptionXMLWriter.startElement( tag );
1844        }
1845    }
1846
1847    /**
1848     * Convenience method to write a key-value pair.
1849     *
1850     * @param key the name of an attribute
1851     * @param value the value of an attribute
1852     */
1853    private void writeAddAttribute( String key, String value )
1854    {
1855        if ( tableCaptionXMLWriter == null )
1856        {
1857            xmlWriter.addAttribute( key, value );
1858        }
1859        else
1860        {
1861            tableCaptionXMLWriter.addAttribute( key, value );
1862        }
1863    }
1864
1865    /**
1866     * Convenience method to write a key-value pair.
1867     *
1868     * @param key the name of an attribute
1869     * @param value the value of an attribute
1870     */
1871    private void writeAddAttribute( String key, int value )
1872    {
1873        if ( tableCaptionXMLWriter == null )
1874        {
1875            xmlWriter.addAttribute( key, String.valueOf( value ) );
1876        }
1877        else
1878        {
1879            tableCaptionXMLWriter.addAttribute( key, String.valueOf( value ) );
1880        }
1881    }
1882
1883    /**
1884     * Convenience method to write an end element.
1885     */
1886    private void writeEndElement()
1887    {
1888        if ( tableCaptionXMLWriter == null )
1889        {
1890            xmlWriter.endElement();
1891        }
1892        else
1893        {
1894            tableCaptionXMLWriter.endElement();
1895        }
1896    }
1897
1898    /**
1899     * {@inheritDoc}
1900     *
1901     * Convenience method to write a String
1902     */
1903    protected void write( String aString )
1904    {
1905        write( aString, false );
1906    }
1907
1908    /**
1909     * Convenience method to write a String depending the escapeHtml flag
1910     *
1911     * @param aString
1912     * @param escapeHtml
1913     */
1914    private void write( String aString, boolean escapeHtml )
1915    {
1916        write( aString, escapeHtml, true );
1917    }
1918
1919    /**
1920     * Convenience method to write a String depending the escapeHtml flag
1921     *
1922     * @param aString
1923     * @param escapeHtml
1924     * @param trim
1925     */
1926    private void write( String aString, boolean escapeHtml, boolean trim )
1927    {
1928        if ( aString == null )
1929        {
1930            return;
1931        }
1932
1933        if ( trim )
1934        {
1935            aString = StringUtils.replace( aString, "\n", "" );
1936
1937            LineNumberReader lnr = new LineNumberReader( new StringReader( aString ) );
1938            StringBuilder sb = new StringBuilder();
1939            String line;
1940            try
1941            {
1942                while ( ( line = lnr.readLine() ) != null )
1943                {
1944                    sb.append( beautifyPhrase( line.trim() ) );
1945                    sb.append( " " );
1946                }
1947
1948                aString = sb.toString();
1949            }
1950            catch ( IOException e )
1951            {
1952                // nop
1953            }
1954            if ( aString.trim().length() == 0 )
1955            {
1956                return;
1957            }
1958        }
1959        if ( escapeHtml )
1960        {
1961            if ( tableCaptionXMLWriter == null )
1962            {
1963                xmlWriter.writeMarkup( aString );
1964            }
1965            else
1966            {
1967                tableCaptionXMLWriter.writeMarkup( aString );
1968            }
1969        }
1970        else
1971        {
1972            if ( tableCaptionXMLWriter == null )
1973            {
1974                xmlWriter.writeText( aString );
1975            }
1976            else
1977            {
1978                tableCaptionXMLWriter.writeText( aString );
1979            }
1980        }
1981    }
1982
1983    /**
1984     * Convenience method to return a beautify phrase, i.e. one space between words.
1985     *
1986     * @param aString
1987     * @return a String with only one space between words
1988     */
1989    private static String beautifyPhrase( String aString )
1990    {
1991        String[] strings = StringUtils.split( aString, " " );
1992        StringBuilder sb = new StringBuilder();
1993        for ( String string : strings )
1994        {
1995            if ( string.trim().length() != 0 )
1996            {
1997                sb.append( string.trim() );
1998                sb.append( " " );
1999            }
2000        }
2001
2002        return sb.toString().trim();
2003    }
2004
2005    private void startChunk( String fontName, int fontSize, String fontStyle, int fontColorBlue, int fontColorGreen,
2006                             int fontColorRed, String localDestination )
2007    {
2008        writeStartElement( ElementTags.CHUNK );
2009        writeAddAttribute( ElementTags.FONT, fontName );
2010        writeAddAttribute( ElementTags.SIZE, fontSize );
2011        writeAddAttribute( ElementTags.STYLE, fontStyle );
2012        writeAddAttribute( ElementTags.BLUE, fontColorBlue );
2013        writeAddAttribute( ElementTags.GREEN, fontColorGreen );
2014        writeAddAttribute( ElementTags.RED, fontColorRed );
2015//        writeAddAttribute( ElementTags.LOCALDESTINATION, localDestination );
2016    }
2017
2018    /**
2019     * If debug mode is enabled, log the <code>msg</code> as is, otherwise add unique msg in <code>warnMessages</code>.
2020     *
2021     * @param key not null
2022     * @param msg not null
2023     * @see #close()
2024     * @since 1.1.1
2025     */
2026    private void logMessage( String key, String msg )
2027    {
2028        msg = "[iText Sink] " + msg;
2029        if ( getLog().isDebugEnabled() )
2030        {
2031            getLog().debug( msg );
2032
2033            return;
2034        }
2035
2036        if ( warnMessages == null )
2037        {
2038            warnMessages = new HashMap<>();
2039        }
2040
2041        Set<String> set = warnMessages.get( key );
2042        if ( set == null )
2043        {
2044            set = new TreeSet<>();
2045        }
2046        set.add( msg );
2047        warnMessages.put( key, set );
2048    }
2049}