fr.opensagres.poi.xwpf.converter.pdf.internal.PdfMapper.java Source code

Java tutorial

Introduction

Here is the source code for fr.opensagres.poi.xwpf.converter.pdf.internal.PdfMapper.java

Source

/**
 * Copyright (C) 2011-2015 The XDocReport Team <xdocreport@googlegroups.com>
 *
 * All rights reserved.
 *
 * Permission is hereby granted, free  of charge, to any person obtaining
 * a  copy  of this  software  and  associated  documentation files  (the
 * "Software"), to  deal in  the Software without  restriction, including
 * without limitation  the rights to  use, copy, modify,  merge, publish,
 * distribute,  sublicense, and/or sell  copies of  the Software,  and to
 * permit persons to whom the Software  is furnished to do so, subject to
 * the following conditions:
 *
 * The  above  copyright  notice  and  this permission  notice  shall  be
 * included in all copies or substantial portions of the Software.
 *
 * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
 * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
 * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package fr.opensagres.poi.xwpf.converter.pdf.internal;

import static fr.opensagres.poi.xwpf.converter.core.utils.DxaUtil.emu2points;

import java.io.OutputStream;
import java.math.BigInteger;
import java.util.List;
import java.util.logging.Logger;

import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
import org.openxmlformats.schemas.drawingml.x2006.picture.CTPicture;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STRelFromH;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STRelFromV;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STWrapText;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBookmark;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBorder;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHdrFtrRef;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTLvl;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPTab;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTabStop;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTabs;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTextDirection;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTabJc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTabTlc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTextDirection;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc.Enum;

import com.lowagie.text.Chunk;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.Image;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.draw.DottedLineSeparator;
import com.lowagie.text.pdf.draw.LineSeparator;
import com.lowagie.text.pdf.draw.VerticalPositionMark;

import fr.opensagres.poi.xwpf.converter.core.BorderSide;
import fr.opensagres.poi.xwpf.converter.core.Color;
import fr.opensagres.poi.xwpf.converter.core.ListItemContext;
import fr.opensagres.poi.xwpf.converter.core.ParagraphLineSpacing;
import fr.opensagres.poi.xwpf.converter.core.TableCellBorder;
import fr.opensagres.poi.xwpf.converter.core.TableHeight;
import fr.opensagres.poi.xwpf.converter.core.TableWidth;
import fr.opensagres.poi.xwpf.converter.core.XWPFDocumentVisitor;
import fr.opensagres.poi.xwpf.converter.core.styles.paragraph.ParagraphIndentationHangingValueProvider;
import fr.opensagres.poi.xwpf.converter.core.styles.paragraph.ParagraphIndentationLeftValueProvider;
import fr.opensagres.poi.xwpf.converter.core.utils.DxaUtil;
import fr.opensagres.poi.xwpf.converter.core.utils.StringUtils;
import fr.opensagres.poi.xwpf.converter.pdf.PdfOptions;
import fr.opensagres.poi.xwpf.converter.pdf.internal.elements.StylableAnchor;
import fr.opensagres.poi.xwpf.converter.pdf.internal.elements.StylableDocument;
import fr.opensagres.poi.xwpf.converter.pdf.internal.elements.StylableHeaderFooter;
import fr.opensagres.poi.xwpf.converter.pdf.internal.elements.StylableMasterPage;
import fr.opensagres.poi.xwpf.converter.pdf.internal.elements.StylableParagraph;
import fr.opensagres.poi.xwpf.converter.pdf.internal.elements.StylableTable;
import fr.opensagres.poi.xwpf.converter.pdf.internal.elements.StylableTableCell;
import fr.opensagres.xdocreport.itext.extension.ExtendedChunk;
import fr.opensagres.xdocreport.itext.extension.ExtendedImage;
import fr.opensagres.xdocreport.itext.extension.ExtendedParagraph;
import fr.opensagres.xdocreport.itext.extension.ExtendedPdfPCell;
import fr.opensagres.xdocreport.itext.extension.ExtendedPdfPTable;
import fr.opensagres.xdocreport.itext.extension.IITextContainer;
import fr.opensagres.xdocreport.itext.extension.font.FontGroup;

public class PdfMapper extends XWPFDocumentVisitor<IITextContainer, PdfOptions, StylableMasterPage> {

    private static final String TAB = "\t";

    /**
     * Logger for this class
     */
    private static final Logger LOGGER = Logger.getLogger(PdfMapper.class.getName());

    private final OutputStream out;

    // Instance of PDF document
    private StylableDocument pdfDocument;

    private Font currentRunFontAscii;

    private Font currentRunFontEastAsia;

    private Font currentRunFontHAnsi;

    private UnderlinePatterns currentRunUnderlinePatterns;

    private Color currentRunBackgroundColor;

    private Float currentRunX;

    private Float currentPageWidth;

    private StylableHeaderFooter pdfHeader;

    private StylableHeaderFooter pdfFooter;

    private Integer expectedPageCount;

    public PdfMapper(XWPFDocument document, OutputStream out, PdfOptions options, Integer expectedPageCount)
            throws Exception {
        super(document, options != null ? options : PdfOptions.getDefault());
        this.out = out;
        this.expectedPageCount = expectedPageCount;
    }

    // ------------------------- Document

    @Override
    protected IITextContainer startVisitDocument() throws Exception {
        // Create instance of PDF document
        this.pdfDocument = new StylableDocument(out, options.getConfiguration());
        this.pdfDocument.setMasterPageManager(getMasterPageManager());
        return pdfDocument;

    }

    @Override
    protected void endVisitDocument() throws Exception {
        pdfDocument.close();
        out.close();
    }

    @Override
    protected IITextContainer startVisitSDT(XWPFSDT contents, IITextContainer container) {
        return null;
    }

    @Override
    protected void endVisitSDT(XWPFSDT contents, IITextContainer container, IITextContainer sdtContainer) {

    }

    @Override
    protected void visitSDTBody(XWPFSDT contents, IITextContainer sdtContainer) throws Exception {
    }
    // ------------------------- Header/Footer

    @Override
    protected void visitHeader(XWPFHeader header, CTHdrFtrRef headerRef, CTSectPr sectPr,
            StylableMasterPage masterPage) throws Exception {
        BigInteger headerY = sectPr.getPgMar() != null ? sectPr.getPgMar().getHeader() : null;
        this.currentPageWidth = sectPr.getPgMar() != null ? DxaUtil.dxa2points(sectPr.getPgSz().getW()) : null;
        this.pdfHeader = new StylableHeaderFooter(pdfDocument, headerY, true);
        // List<IBodyElement> bodyElements = header.getBodyElements();
        List<IBodyElement> bodyElements = super.getBodyElements(header);
        StylableTableCell tableCell = getHeaderFooterTableCell(pdfHeader, bodyElements);
        visitBodyElements(bodyElements, tableCell);
        masterPage.setHeader(pdfHeader);
        this.currentPageWidth = null;
        this.pdfHeader = null;
    }

    @Override
    protected void visitFooter(XWPFFooter footer, CTHdrFtrRef footerRef, CTSectPr sectPr,
            StylableMasterPage masterPage) throws Exception {
        BigInteger footerY = sectPr.getPgMar() != null ? sectPr.getPgMar().getFooter() : null;
        this.currentPageWidth = sectPr.getPgMar() != null ? DxaUtil.dxa2points(sectPr.getPgSz().getW()) : null;
        this.pdfFooter = new StylableHeaderFooter(pdfDocument, footerY, false);
        List<IBodyElement> bodyElements = super.getBodyElements(footer);
        StylableTableCell tableCell = getHeaderFooterTableCell(pdfFooter, bodyElements);
        visitBodyElements(bodyElements, tableCell);
        masterPage.setFooter(pdfFooter);
        this.currentPageWidth = null;
        this.pdfFooter = null;
    }

    private StylableTableCell getHeaderFooterTableCell(StylableHeaderFooter pdfHeaderFooter,
            List<IBodyElement> bodyElements) throws DocumentException {
        return pdfHeaderFooter.getTableCell();
    }

    public void setActiveMasterPage(StylableMasterPage masterPage) {
        pdfDocument.setActiveMasterPage(masterPage);

    }

    public StylableMasterPage createMasterPage(CTSectPr sectPr) {
        return new StylableMasterPage(sectPr);
    }

    // ------------------------- Paragraph

    @Override
    protected IITextContainer startVisitParagraph(XWPFParagraph docxParagraph, ListItemContext itemContext,
            IITextContainer pdfParentContainer) throws Exception {
        this.currentRunX = null;

        // create PDF paragraph
        StylableParagraph pdfParagraph = pdfDocument.createParagraph(pdfParentContainer);

        // indentation left
        Float indentationLeft = stylesDocument.getIndentationLeft(docxParagraph);
        if (indentationLeft != null) {
            pdfParagraph.setIndentationLeft(indentationLeft);
        }
        // indentation right
        Float indentationRight = stylesDocument.getIndentationRight(docxParagraph);
        if (indentationRight != null) {
            pdfParagraph.setIndentationRight(indentationRight);
        }
        // indentation first line
        Float indentationFirstLine = stylesDocument.getIndentationFirstLine(docxParagraph);
        if (indentationFirstLine != null) {
            pdfParagraph.setFirstLineIndent(indentationFirstLine);
        }
        // indentation hanging (remove first line)
        Float indentationHanging = stylesDocument.getIndentationHanging(docxParagraph);
        if (indentationHanging != null) {
            pdfParagraph.setFirstLineIndent(-indentationHanging);
        }

        // // spacing before
        Float spacingBefore = stylesDocument.getSpacingBefore(docxParagraph);
        if (spacingBefore != null) {
            pdfParagraph.setSpacingBefore(spacingBefore);
        }

        // spacing after
        Float spacingAfter = stylesDocument.getSpacingAfter(docxParagraph);
        if (spacingAfter != null) {
            pdfParagraph.setSpacingAfter(spacingAfter);
        }

        ParagraphLineSpacing lineSpacing = stylesDocument.getParagraphSpacing(docxParagraph);
        if (lineSpacing != null) {
            if (lineSpacing.getLeading() != null && lineSpacing.getMultipleLeading() != null) {
                pdfParagraph.setLeading(lineSpacing.getLeading(), lineSpacing.getMultipleLeading());
            } else {
                if (lineSpacing.getLeading() != null) {
                    pdfParagraph.setLeading(lineSpacing.getLeading());
                }
                if (lineSpacing.getMultipleLeading() != null) {
                    pdfParagraph.setMultipliedLeading(lineSpacing.getMultipleLeading());
                }
            }

        }

        // text-align
        ParagraphAlignment alignment = stylesDocument.getParagraphAlignment(docxParagraph);
        if (alignment != null) {
            switch (alignment) {
            case LEFT:
                pdfParagraph.setAlignment(Element.ALIGN_LEFT);
                break;
            case RIGHT:
                pdfParagraph.setAlignment(Element.ALIGN_RIGHT);
                break;
            case CENTER:
                pdfParagraph.setAlignment(Element.ALIGN_CENTER);
                break;
            case BOTH:
                pdfParagraph.setAlignment(Element.ALIGN_JUSTIFIED);
                break;
            default:
                break;
            }
        }

        // background-color
        Color backgroundColor = stylesDocument.getBackgroundColor(docxParagraph);
        if (backgroundColor != null) {
            pdfParagraph.setBackgroundColor(Converter.toAwtColor(backgroundColor));
        }

        // border
        CTBorder borderTop = stylesDocument.getBorderTop(docxParagraph);
        pdfParagraph.setBorder(borderTop, Rectangle.TOP);

        CTBorder borderBottom = stylesDocument.getBorderBottom(docxParagraph);
        pdfParagraph.setBorder(borderBottom, Rectangle.BOTTOM);

        CTBorder borderLeft = stylesDocument.getBorderLeft(docxParagraph);
        pdfParagraph.setBorder(borderLeft, Rectangle.LEFT);

        CTBorder borderRight = stylesDocument.getBorderRight(docxParagraph);
        pdfParagraph.setBorder(borderRight, Rectangle.RIGHT);

        if (itemContext != null) {
            CTLvl lvl = itemContext.getLvl();
            CTPPr lvlPPr = lvl.getPPr();
            if (lvlPPr != null) {
                if (ParagraphIndentationLeftValueProvider.INSTANCE
                        .getValue(docxParagraph.getCTP().getPPr()) == null) {

                    // search the indentation from the level properties only if
                    // paragraph has not override it
                    // see
                    // https://code.google.com/p/xdocreport/issues/detail?id=239
                    Float indLeft = ParagraphIndentationLeftValueProvider.INSTANCE.getValue(lvlPPr);
                    if (indLeft != null) {
                        pdfParagraph.setIndentationLeft(indLeft);
                    }
                }
                if (ParagraphIndentationHangingValueProvider.INSTANCE
                        .getValue(docxParagraph.getCTP().getPPr()) == null) {
                    // search the hanging from the level properties only if
                    // paragraph has not override it
                    // see
                    // https://code.google.com/p/xdocreport/issues/detail?id=239
                    Float hanging = stylesDocument.getIndentationHanging(lvlPPr);
                    if (hanging != null) {
                        pdfParagraph.setFirstLineIndent(-hanging);
                    }
                }
            }
            CTRPr lvlRPr = lvl.getRPr();
            if (lvlRPr != null) {
                // Font family
                String listItemFontFamily = stylesDocument.getFontFamilyAscii(lvlRPr);

                // Get font size
                Float listItemFontSize = stylesDocument.getFontSize(lvlRPr);

                // Get font style
                int listItemFontStyle = Font.NORMAL;
                Boolean bold = stylesDocument.getFontStyleBold(lvlRPr);
                if (bold != null && bold) {
                    listItemFontStyle |= Font.BOLD;
                }
                Boolean italic = stylesDocument.getFontStyleItalic(lvlRPr);
                if (italic != null && italic) {
                    listItemFontStyle |= Font.ITALIC;
                }
                Boolean strike = stylesDocument.getFontStyleStrike(lvlRPr);
                if (strike != null && strike) {
                    listItemFontStyle |= Font.STRIKETHRU;
                }

                // Font color
                Color listItemFontColor = stylesDocument.getFontColor(lvlRPr);

                pdfParagraph.setListItemFontFamily(listItemFontFamily);
                pdfParagraph.setListItemFontSize(listItemFontSize);
                pdfParagraph.setListItemFontStyle(listItemFontStyle);
                pdfParagraph.setListItemFontColor(Converter.toAwtColor(listItemFontColor));

            }
            pdfParagraph.setListItemText(itemContext.getText());
        }
        return pdfParagraph;
    }

    @Override
    protected void endVisitParagraph(XWPFParagraph docxParagraph, IITextContainer pdfParentContainer,
            IITextContainer pdfParagraphContainer) throws Exception {
        // add the iText paragraph in the current parent container.
        ExtendedParagraph pdfParagraph = (ExtendedParagraph) pdfParagraphContainer;
        pdfParentContainer.addElement(pdfParagraph.getElement());

        this.currentRunX = null;
    }

    // ------------------------- Run

    @Override
    protected void visitEmptyRun(IITextContainer pdfParagraphContainer) throws Exception {
        StylableParagraph paragraph = (StylableParagraph) pdfParagraphContainer;
        IITextContainer parent = paragraph.getParent();
        if (parent instanceof StylableTableCell) {
            StylableTableCell cell = (StylableTableCell) parent;
            if (cell.getRotation() > 0) {
                // Run paragraph belongs to Cell which has rotation, ignore the
                // empty run.
                return;
            }
        }
        // Add new PDF line
        pdfParagraphContainer.addElement(Chunk.NEWLINE);
    }

    @Override
    protected void visitRun(XWPFRun docxRun, boolean pageNumber, String url, IITextContainer pdfParagraphContainer)
            throws Exception {
        // Font family
        String fontFamilyAscii = stylesDocument.getFontFamilyAscii(docxRun);
        String fontFamilyEastAsia = stylesDocument.getFontFamilyEastAsia(docxRun);
        String fontFamilyHAnsi = stylesDocument.getFontFamilyHAnsi(docxRun);

        // Get font size
        Float fontSize = stylesDocument.getFontSize(docxRun);
        if (fontSize == null) {
            fontSize = -1f;
        }

        // Get font style
        int fontStyle = Font.NORMAL;
        Boolean bold = stylesDocument.getFontStyleBold(docxRun);
        if (bold != null && bold) {
            fontStyle |= Font.BOLD;
        }
        Boolean italic = stylesDocument.getFontStyleItalic(docxRun);
        if (italic != null && italic) {
            fontStyle |= Font.ITALIC;
        }
        Boolean strike = stylesDocument.getFontStyleStrike(docxRun);
        if (strike != null && strike) {
            fontStyle |= Font.STRIKETHRU;
        }

        // Font color
        Color fontColor = stylesDocument.getFontColor(docxRun);

        // Font
        this.currentRunFontAscii = getFont(fontFamilyAscii, fontSize, fontStyle, fontColor);
        this.currentRunFontEastAsia = getFont(fontFamilyEastAsia, fontSize, fontStyle, fontColor);
        this.currentRunFontHAnsi = getFont(fontFamilyHAnsi, fontSize, fontStyle, fontColor);

        // Underline patterns
        this.currentRunUnderlinePatterns = stylesDocument.getUnderline(docxRun);

        // background color
        this.currentRunBackgroundColor = stylesDocument.getBackgroundColor(docxRun);

        // highlight
        if (currentRunBackgroundColor == null) {
            this.currentRunBackgroundColor = stylesDocument.getTextHighlighting(docxRun);
        }

        StylableParagraph pdfParagraph = (StylableParagraph) pdfParagraphContainer;
        pdfParagraph.adjustMultipliedLeading(currentRunFontAscii);

        // addd symbol list item chunk if needed.
        String listItemText = pdfParagraph.getListItemText();
        if (StringUtils.isNotEmpty(listItemText)) {
            // FIXME: add some space after the list item
            listItemText += "    ";

            String listItemFontFamily = pdfParagraph.getListItemFontFamily();
            Float listItemFontSize = pdfParagraph.getListItemFontSize();
            int listItemFontStyle = pdfParagraph.getListItemFontStyle();
            java.awt.Color listItemFontColor = pdfParagraph.getListItemFontColor();
            Font listItemFont = options.getFontProvider().getFont(
                    listItemFontFamily != null ? listItemFontFamily : fontFamilyAscii, options.getFontEncoding(),
                    listItemFontSize != null ? listItemFontSize : fontSize,
                    listItemFontStyle != Font.NORMAL ? listItemFontStyle : fontStyle,
                    listItemFontColor != null ? listItemFontColor : Converter.toAwtColor(fontColor));
            Chunk symbol = createTextChunk(listItemText, false, listItemFont, currentRunUnderlinePatterns,
                    currentRunBackgroundColor);
            pdfParagraph.add(symbol);
            pdfParagraph.setListItemText(null);
        }

        IITextContainer container = pdfParagraphContainer;
        if (url != null) {
            // URL is not null, generate a PDF hyperlink.
            StylableAnchor pdfAnchor = new StylableAnchor();
            pdfAnchor.setReference(url);
            pdfAnchor.setITextContainer(container);
            container = pdfAnchor;
        }
        super.visitRun(docxRun, pageNumber, url, container);

        if (url != null) {
            // URL is not null, add the PDF hyperlink in the PDF paragraph
            pdfParagraphContainer.addElement((StylableAnchor) container);
        }

        this.currentRunFontAscii = null;
        this.currentRunFontEastAsia = null;
        this.currentRunFontHAnsi = null;
        this.currentRunUnderlinePatterns = null;
        this.currentRunBackgroundColor = null;
    }

    private Font getFont(String fontFamily, Float fontSize, int fontStyle, Color fontColor) {

        String fontToUse = stylesDocument.getFontNameToUse(fontFamily);
        if (StringUtils.isNotEmpty(fontToUse)) {
            return options.getFontProvider().getFont(fontToUse, options.getFontEncoding(), fontSize, fontStyle,
                    Converter.toAwtColor(fontColor));
        }

        Font font = options.getFontProvider().getFont(fontFamily, options.getFontEncoding(), fontSize, fontStyle,
                Converter.toAwtColor(fontColor));
        if (!isFontExists(font)) {
            // font is not found
            try {
                List<String> altNames = stylesDocument.getFontsAltName(fontFamily);
                if (altNames != null) {
                    // Loop for each alternative names font (from the
                    // fontTable.xml) to find the well font.
                    for (String altName : altNames) {
                        // check if the current font name is not the same that
                        // original (o avoid StackOverFlow : see
                        // https://code.google.com/p/xdocreport/issues/detail?id=393)
                        if (!fontFamily.equals(altName)) {
                            font = getFont(altName, fontSize, fontStyle, fontColor);
                            if (isFontExists(font)) {
                                stylesDocument.setFontNameToUse(fontFamily, altName);
                                return font;
                            }
                        }
                    }
                }
            } catch (Exception e) {
                LOGGER.severe(e.getMessage());
            }
        }
        return font;
    }

    /**
     * Returns true if the iText font exists and false otherwise.
     * 
     * @param font
     * @return
     */
    private boolean isFontExists(Font font) {
        // FIXME : is it like this to test that font exists?
        return font != null && font.getBaseFont() != null;
    }

    @Override
    protected void visitText(CTText docxText, boolean pageNumber, IITextContainer pdfParagraphContainer)
            throws Exception {
        Font font = currentRunFontAscii;
        Font fontAsian = currentRunFontEastAsia;
        Font fontComplex = currentRunFontHAnsi;
        createAndAddChunks(pdfParagraphContainer, docxText.getStringValue(), currentRunUnderlinePatterns,
                currentRunBackgroundColor, pageNumber, font, fontAsian, fontComplex);
    }

    private Chunk createTextChunk(String text, boolean pageNumber, Font currentRunFont,
            UnderlinePatterns currentRunUnderlinePatterns, Color currentRunBackgroundColor) {
        // Chunk textChunk =
        // pageNumber ? new ExtendedChunk( pdfDocument, true, currentRunFont ) :
        // new Chunk( text, currentRunFont );

        Chunk textChunk = null;
        if (processingTotalPageCountField && expectedPageCount != null) {
            textChunk = new Chunk(String.valueOf(expectedPageCount), currentRunFont);
        } else {
            textChunk = pageNumber ? new ExtendedChunk(pdfDocument, true, currentRunFont)
                    : new Chunk(text, currentRunFont);
        }
        if (currentRunUnderlinePatterns != null) {
            // underlined
            boolean singleUnderlined = false;
            switch (currentRunUnderlinePatterns) {
            case SINGLE:
                singleUnderlined = true;
                break;

            default:
                break;
            }
            if (singleUnderlined) {
                textChunk.setUnderline(1, -2);
            }
        }

        // background color
        if (currentRunBackgroundColor != null) {
            textChunk.setBackground(Converter.toAwtColor(currentRunBackgroundColor));
        }
        if (currentRunX != null) {
            this.currentRunX += textChunk.getWidthPoint();
        }
        return textChunk;
    }

    private void createAndAddChunks(IITextContainer parent, String textContent, UnderlinePatterns underlinePatterns,
            Color backgroundColor, boolean pageNumber, Font font, Font fontAsian, Font fontComplex) {
        StringBuilder sbuf = new StringBuilder();
        FontGroup currentGroup = FontGroup.WESTERN;
        for (int i = 0; i < textContent.length(); i++) {
            char ch = textContent.charAt(i);
            FontGroup group = FontGroup.getUnicodeGroup(ch, font, fontAsian, fontComplex);
            if (sbuf.length() == 0 || currentGroup.equals(group)) {
                // continue current chunk
                sbuf.append(ch);
            } else {
                // end chunk
                Font chunkFont = getFont(font, fontAsian, fontComplex, currentGroup);
                Chunk chunk = createTextChunk(sbuf.toString(), pageNumber, chunkFont, underlinePatterns,
                        backgroundColor);
                parent.addElement(chunk);
                // start new chunk
                sbuf.setLength(0);
                sbuf.append(ch);
            }
            currentGroup = group;
        }
        // end chunk
        Font chunkFont = getFont(font, fontAsian, fontComplex, currentGroup);
        Chunk chunk = createTextChunk(sbuf.toString(), pageNumber, chunkFont, underlinePatterns, backgroundColor);
        parent.addElement(chunk);
    }

    private Font getFont(Font font, Font fontAsian, Font fontComplex, FontGroup group) {
        switch (group) {
        case WESTERN:
            return font;
        case ASIAN:
            return fontAsian;
        case COMPLEX:
            return fontComplex;
        }
        return font;
    }

    @Override
    protected void visitTab(CTPTab tab, IITextContainer pdfParagraphContainer) throws Exception {
        // TODO manage this case.
        //
        // if ( tab == null )
        // {
        // float defaultTabStop = stylesDocument.getDefaultTabStop();
        // Chunk pdfTab = new Chunk( new VerticalPositionMark(), defaultTabStop,
        // false );
        // pdfParagraphContainer.addElement( pdfTab );
        // }
        // else
        // {
        // Chunk pdfTab = new Chunk( new VerticalPositionMark() );
        // pdfParagraphContainer.addElement( pdfTab );
        // }
    }

    @Override
    protected void visitTabs(CTTabs tabs, IITextContainer pdfParagraphContainer) throws Exception {
        if (currentRunX == null) {
            Paragraph paragraph = null;
            if (pdfParagraphContainer instanceof Paragraph) {
                paragraph = (Paragraph) pdfParagraphContainer;
            } else {
                paragraph = (Paragraph) ((StylableAnchor) pdfParagraphContainer).getITextContainer();
            }
            currentRunX = paragraph.getFirstLineIndent();
            List<Chunk> chunks = paragraph.getChunks();
            for (Chunk chunk : chunks) {
                currentRunX += chunk.getWidthPoint();
            }
        } else {
            if (currentRunX >= pdfDocument.getPageWidth()) {
                currentRunX = 0f;
            }
        }

        Float tabPosition = null;
        STTabTlc.Enum tabLeader = null;
        STTabJc.Enum tabVal = null;
        boolean useDefaultTabStop = false;
        if (tabs != null) {
            List<CTTabStop> tabList = tabs.getTabList();

            CTTabStop tabStop = getTabStop(tabList);
            if (tabStop != null) {

                float lastX = DxaUtil.dxa2points(tabStop.getPos().floatValue());
                if (lastX > currentRunX) {
                    tabPosition = lastX;
                    tabLeader = tabStop.getLeader();
                    tabVal = tabStop.getVal();
                } else {
                    useDefaultTabStop = true;
                }
            }
        }
        if (tabs == null || useDefaultTabStop) {
            // default tab
            float defaultTabStop = stylesDocument.getDefaultTabStop();
            float pageWidth = pdfDocument.getPageWidth();
            int nbInterval = (int) (pageWidth / defaultTabStop);
            Float lastX = getTabStopPosition(currentRunX, defaultTabStop, nbInterval);
            if (lastX != null) {
                tabPosition = lastX;
            }
        }

        if (tabPosition != null) {
            currentRunX = tabPosition;
            // tab leader : Specifies the character which shall be used to fill
            // in the space created by a tab
            // which
            // ends
            // at this custom tab stop. This character shall be repeated as
            // required to completely fill the
            // tab spacing generated by the tab character.
            VerticalPositionMark mark = createVerticalPositionMark(tabLeader);
            Chunk pdfTab = null;
            if (STTabJc.RIGHT.equals(tabVal)) {
                pdfTab = new Chunk(mark);
            } else {
                pdfTab = new Chunk(mark, currentRunX);
            }
            pdfParagraphContainer.addElement(pdfTab);
        }
    }

    private Float getTabStopPosition(float currentPosition, float interval, int nbInterval) {
        Float nextPosition = null;
        float newPosition = 0f;
        for (int i = 1; i < nbInterval; i++) {
            newPosition = interval * i;
            if (currentPosition < newPosition) {
                nextPosition = newPosition;
                break;
            }
        }
        return nextPosition;
    }

    private VerticalPositionMark createVerticalPositionMark(
            org.openxmlformats.schemas.wordprocessingml.x2006.main.STTabTlc.Enum leader) {
        if (leader != null) {
            if (leader == STTabTlc.DOT) {
                return new DottedLineSeparator();
            } else if (leader == STTabTlc.UNDERSCORE) {
                return new LineSeparator();
            }
        }
        return new VerticalPositionMark();
    }

    private CTTabStop getTabStop(List<CTTabStop> tabList) {
        if (tabList.size() == 1) {
            CTTabStop tabStop = tabList.get(0);
            if (isClearTab(tabStop)) {
                return null;
            }
            return tabStop;

        }
        CTTabStop selectedTabStop = null;
        for (CTTabStop tabStop : tabList) {
            if (isClearTab(tabStop)) {
                continue;
            }

            if (canApplyTabStop(tabStop)) {
                return tabStop;
            }
            //
            // if ( selectedTabStop == null )
            // {
            // selectedTabStop = tabStop;
            // }
            // else
            // {
            // if ( tabStop.getPos().floatValue() >
            // selectedTabStop.getPos().floatValue() )
            // {
            // selectedTabStop = tabStop;
            // }
            // }
        }
        // TODO : retrieve the well tab stop according the current width of the
        // line.
        return null;
    }

    private boolean canApplyTabStop(CTTabStop tabStop) {
        if (tabStop.getVal().equals(STTabJc.LEFT)) {

            if (currentRunX < DxaUtil.dxa2points(tabStop.getPos().floatValue())) {
                return true;
            }
        } else if (tabStop.getVal().equals(STTabJc.RIGHT)) {
            if (isWordDocumentPartParsing()) {
                if (pdfDocument.getWidthLimit()
                        - (currentRunX + DxaUtil.dxa2points(tabStop.getPos().floatValue())) <= 0) {
                    return true;
                }
            } else {
                if (currentPageWidth == null) {
                    return true;
                }
                if (currentPageWidth.floatValue()
                        - (currentRunX + DxaUtil.dxa2points(tabStop.getPos().floatValue())) <= 0) {
                    return true;
                }
            }

        } else if (tabStop.getVal().equals(STTabJc.CENTER)) {

        }
        return false;
    }

    private boolean isClearTab(CTTabStop tabStop) {
        org.openxmlformats.schemas.wordprocessingml.x2006.main.STTabJc.Enum tabVal = tabStop.getVal();
        if (tabVal != null) {
            if (tabVal.equals(STTabJc.CLEAR)) {
                // Specifies that the current tab stop is cleared and shall be
                // removed and ignored when processing
                // the contents of this document
                return true;
            }
        }
        return false;
    }

    @Override
    protected void addNewLine(CTBr br, IITextContainer pdfParagraphContainer) throws Exception {
        pdfParagraphContainer.addElement(Chunk.NEWLINE);
    }

    @Override
    protected void visitBR(CTBr br, IITextContainer paragraphContainer) throws Exception {
        currentRunX = 0f;
        super.visitBR(br, paragraphContainer);
    }

    @Override
    protected void pageBreak() throws Exception {
        pdfDocument.pageBreak();
    }

    @Override
    protected void visitBookmark(CTBookmark bookmark, XWPFParagraph paragraph, IITextContainer paragraphContainer)
            throws Exception {
        // destination for a local anchor
        // chunk with empty text does not work as local anchor
        // so we create chunk with invisible but not empty text content
        // if bookmark is the last chunk in a paragraph something must be added
        // after or it does not work
        Chunk chunk = new Chunk(TAB);
        chunk.setLocalDestination(bookmark.getName());
        paragraphContainer.addElement(chunk);
    }

    // ----------------- Table

    @Override
    protected IITextContainer startVisitTable(XWPFTable table, float[] colWidths,
            IITextContainer pdfParentContainer) throws Exception {
        StylableTable pdfPTable = createPDFTable(table, colWidths, pdfParentContainer);
        return pdfPTable;
    }

    private StylableTable createPDFTable(XWPFTable table, float[] colWidths, IITextContainer pdfParentContainer)
            throws DocumentException {
        // 2) Compute tableWith
        TableWidth tableWidth = stylesDocument.getTableWidth(table);
        StylableTable pdfPTable = pdfDocument.createTable(pdfParentContainer, colWidths.length);
        pdfPTable.setTotalWidth(colWidths);
        if (tableWidth != null && tableWidth.width > 0) {
            if (tableWidth.percentUnit) {
                pdfPTable.setWidthPercentage(tableWidth.width);
            } else {
                pdfPTable.setTotalWidth(tableWidth.width);
            }
        }
        pdfPTable.setLockedWidth(true);

        // Table alignment
        ParagraphAlignment alignment = stylesDocument.getTableAlignment(table);
        if (alignment != null) {
            switch (alignment) {
            case LEFT:
                pdfPTable.setHorizontalAlignment(Element.ALIGN_LEFT);
                break;
            case RIGHT:
                pdfPTable.setHorizontalAlignment(Element.ALIGN_RIGHT);
                break;
            case CENTER:
                pdfPTable.setHorizontalAlignment(Element.ALIGN_CENTER);
                break;
            case BOTH:
                pdfPTable.setHorizontalAlignment(Element.ALIGN_JUSTIFIED);
                break;
            default:
                break;
            }
        }

        // Table indentation
        Float indentation = stylesDocument.getTableIndentation(table);
        if (indentation != null) {
            pdfPTable.setPaddingLeft(indentation);
        }
        return pdfPTable;
    }

    @Override
    protected void endVisitTable(XWPFTable table, IITextContainer pdfParentContainer,
            IITextContainer pdfTableContainer) throws Exception {
        pdfParentContainer.addElement(((ExtendedPdfPTable) pdfTableContainer).getElement());

    }

    // ------------------------- Table Row

    @Override
    protected void startVisitTableRow(XWPFTableRow row, IITextContainer tableContainer, int rowIndex,
            boolean headerRow) throws Exception {
        if (headerRow) {
            PdfPTable table = (PdfPTable) tableContainer;
            table.setHeaderRows(table.getHeaderRows() + 1);
        }
        super.startVisitTableRow(row, tableContainer, rowIndex, headerRow);
    }

    // ------------------------- Table Cell

    @Override
    protected IITextContainer startVisitTableCell(final XWPFTableCell cell, IITextContainer pdfTableContainer,
            boolean firstRow, boolean lastRow, boolean firstCol, boolean lastCol, List<XWPFTableCell> vMergeCells)
            throws Exception {
        XWPFTableRow row = cell.getTableRow();
        XWPFTable table = row.getTable();

        // 1) store table cell info
        stylesDocument.getTableInfo(table).addCellInfo(cell, firstRow, lastRow, firstCol, lastCol);

        // 2) create PDF cell
        StylableTable pdfPTable = (StylableTable) pdfTableContainer;

        StylableTableCell pdfPCell = pdfDocument.createTableCell(pdfPTable);
        // pdfPCell.setUseAscender( true );
        // pdfPCell.setUseDescender( true );

        XWPFTableCell lastVMergedCell = null;
        if (vMergeCells != null) {
            pdfPCell.setRowspan(vMergeCells.size());
            lastVMergedCell = vMergeCells.get(vMergeCells.size() - 1);
            stylesDocument.getTableInfo(table).addCellInfo(lastVMergedCell, false, lastRow, firstCol, lastCol);
        }

        // border-top
        TableCellBorder borderTop = stylesDocument.getTableCellBorderWithConflicts(cell, BorderSide.TOP);
        if (borderTop != null) {
            boolean borderTopInside = stylesDocument.isBorderInside(cell, BorderSide.TOP);
            if (borderTopInside) {
                // Manage conflict border with the adjacent border bottom

            }
        }

        pdfPCell.setBorderTop(borderTop, false);

        // border-bottom

        XWPFTableCell theCell = lastVMergedCell != null ? lastVMergedCell : cell;
        TableCellBorder borderBottom = stylesDocument.getTableCellBorderWithConflicts(theCell, BorderSide.BOTTOM);
        pdfPCell.setBorderBottom(borderBottom, stylesDocument.isBorderInside(theCell, BorderSide.BOTTOM));

        // border-left
        TableCellBorder borderLeft = stylesDocument.getTableCellBorderWithConflicts(cell, BorderSide.LEFT);
        pdfPCell.setBorderLeft(borderLeft, stylesDocument.isBorderInside(cell, BorderSide.LEFT));

        // border-right
        TableCellBorder borderRight = stylesDocument.getTableCellBorderWithConflicts(cell, BorderSide.RIGHT);
        pdfPCell.setBorderRight(borderRight, stylesDocument.isBorderInside(cell, BorderSide.RIGHT));

        // Text direction <w:textDirection
        CTTextDirection direction = stylesDocument.getTextDirection(cell);
        if (direction != null) {
            int dir = direction.getVal().intValue();
            switch (dir) {
            case STTextDirection.INT_BT_LR:
                pdfPCell.setRotation(90);
                break;
            case STTextDirection.INT_TB_RL:
                pdfPCell.setRotation(270);
                break;
            }
        }

        // Colspan
        BigInteger gridSpan = stylesDocument.getTableCellGridSpan(cell);
        if (gridSpan != null) {
            pdfPCell.setColspan(gridSpan.intValue());
        }

        // Background Color
        Color backgroundColor = stylesDocument.getTableCellBackgroundColor(cell);
        if (backgroundColor != null) {
            pdfPCell.setBackgroundColor(Converter.toAwtColor(backgroundColor));
        }

        // Vertical aligment
        Enum jc = stylesDocument.getTableCellVerticalAlignment(cell);
        if (jc != null) {
            switch (jc.intValue()) {
            case STVerticalJc.INT_BOTTOM:
                pdfPCell.setVerticalAlignment(Element.ALIGN_BOTTOM);
                break;
            case STVerticalJc.INT_CENTER:
                pdfPCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
                break;
            case STVerticalJc.INT_TOP:
                pdfPCell.setVerticalAlignment(Element.ALIGN_TOP);
                break;
            }
        }
        // Cell margin
        Float marginTop = stylesDocument.getTableCellMarginTop(cell);
        if (marginTop == null) {
            marginTop = stylesDocument.getTableRowMarginTop(row);
            if (marginTop == null) {
                marginTop = stylesDocument.getTableMarginTop(table);
            }
        }
        if (marginTop != null) {
            pdfPCell.setPaddingTop(marginTop);
        }
        Float marginBottom = stylesDocument.getTableCellMarginBottom(cell);
        if (marginBottom == null) {
            marginBottom = stylesDocument.getTableRowMarginBottom(row);
            if (marginBottom == null) {
                marginBottom = stylesDocument.getTableMarginBottom(table);
            }
        }
        if (marginBottom != null && marginBottom > 0) {
            pdfPCell.setPaddingBottom(marginBottom);
        }
        Float marginLeft = stylesDocument.getTableCellMarginLeft(cell);
        if (marginLeft == null) {
            marginLeft = stylesDocument.getTableRowMarginLeft(row);
            if (marginLeft == null) {
                marginLeft = stylesDocument.getTableMarginLeft(table);
            }
        }
        if (marginLeft != null) {
            pdfPCell.setPaddingLeft(marginLeft);
        }
        Float marginRight = stylesDocument.getTableCellMarginRight(cell);
        if (marginRight == null) {
            marginRight = stylesDocument.getTableRowMarginRight(row);
            if (marginRight == null) {
                marginRight = stylesDocument.getTableMarginRight(table);
            }
        }
        if (marginRight != null) {
            pdfPCell.setPaddingRight(marginRight);
        }

        // Row height
        TableHeight tableHeight = stylesDocument.getTableRowHeight(row);
        if (tableHeight != null) {
            if (tableHeight.minimum) {
                pdfPCell.setMinimumHeight(tableHeight.height);
            } else {
                pdfPCell.setFixedHeight(tableHeight.height);
            }
        }
        // No wrap
        Boolean noWrap = stylesDocument.getTableCellNoWrap(cell);
        if (noWrap != null) {
            pdfPCell.setNoWrap(noWrap);
        }
        return pdfPCell;
    }

    @Override
    protected void endVisitTableCell(XWPFTableCell cell, IITextContainer tableContainer,
            IITextContainer tableCellContainer) {
        ExtendedPdfPTable pdfPTable = (ExtendedPdfPTable) tableContainer;
        ExtendedPdfPCell pdfPCell = (ExtendedPdfPCell) tableCellContainer;
        pdfPTable.addCell(pdfPCell);
    }

    @Override
    protected void visitPicture(CTPicture picture, Float offsetX,
            org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STRelFromH.Enum relativeFromH,
            Float offsetY,
            org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STRelFromV.Enum relativeFromV,
            STWrapText.Enum wrapText, IITextContainer pdfParentContainer) throws Exception {

        CTPositiveSize2D ext = picture.getSpPr().getXfrm().getExt();
        long x = ext.getCx();
        long y = ext.getCy();

        XWPFPictureData pictureData = super.getPictureData(picture);
        if (pictureData != null) {
            try {
                Image img = Image.getInstance(pictureData.getData());
                img.scaleAbsolute(emu2points(x), emu2points(y));

                IITextContainer parentOfParentContainer = pdfParentContainer.getITextContainer();
                if (parentOfParentContainer != null && parentOfParentContainer instanceof PdfPCell) {
                    parentOfParentContainer.addElement(img);
                } else {
                    float chunkOffsetX = 0;
                    if (offsetX != null) {
                        if (STRelFromH.CHARACTER.equals(relativeFromH)) {
                            chunkOffsetX = offsetX;
                        } else if (STRelFromH.COLUMN.equals(relativeFromH)) {
                            chunkOffsetX = offsetX;
                        } else if (STRelFromH.INSIDE_MARGIN.equals(relativeFromH)) {
                            chunkOffsetX = offsetX;
                        } else if (STRelFromH.LEFT_MARGIN.equals(relativeFromH)) {
                            chunkOffsetX = offsetX;
                        } else if (STRelFromH.MARGIN.equals(relativeFromH)) {
                            chunkOffsetX = pdfDocument.left() + offsetX;
                        } else if (STRelFromH.OUTSIDE_MARGIN.equals(relativeFromH)) {
                            chunkOffsetX = offsetX;
                        } else if (STRelFromH.PAGE.equals(relativeFromH)) {
                            chunkOffsetX = offsetX - pdfDocument.left();
                        }
                    }

                    float chunkOffsetY = 0;
                    boolean useExtendedImage = false;
                    if (STRelFromV.PARAGRAPH.equals(relativeFromV)) {
                        useExtendedImage = true;
                    }

                    if (useExtendedImage) {
                        ExtendedImage extImg = new ExtendedImage(img, -offsetY);

                        if (STRelFromV.PARAGRAPH.equals(relativeFromV)) {
                            chunkOffsetY = -extImg.getScaledHeight();
                        }

                        Chunk chunk = new Chunk(extImg, chunkOffsetX, chunkOffsetY, false);
                        pdfParentContainer.addElement(chunk);
                    }
                    /*
                     * float chunkOffsetY = 0; if ( wrapText != null ) {
                     * chunkOffsetY = -img.getScaledHeight(); } boolean
                     * useExtendedImage = offsetY != null; // if (
                     * STRelFromV.PARAGRAPH.equals( relativeFromV ) ) // { //
                     * useExtendedImage = true; // } // if ( useExtendedImage )
                     * { float imgY = -offsetY; if ( pdfHeader != null ) { float
                     * headerY = pdfHeader.getY() != null ? pdfHeader.getY() :
                     * 0; imgY += - img.getScaledHeight() + headerY; }
                     * ExtendedImage extImg = new ExtendedImage( img, imgY ); //
                     * if ( STRelFromV.PARAGRAPH.equals( relativeFromV ) ) // {
                     * // chunkOffsetY = -extImg.getScaledHeight(); // } Chunk
                     * chunk = new Chunk( extImg, chunkOffsetX, chunkOffsetY,
                     * false ); pdfParentContainer.addElement( chunk ); }
                     */
                    else {
                        if (pdfParentContainer instanceof Paragraph) {
                            // I don't know why but we need add some spacing
                            // before in the paragraph
                            // otherwise the image cut the text of the below
                            // paragraph (see FormattingTests JUnit)?
                            Paragraph paragraph = (Paragraph) pdfParentContainer;
                            paragraph.setSpacingBefore(paragraph.getSpacingBefore() + 5f);
                        }
                        pdfParentContainer.addElement(new Chunk(img, chunkOffsetX, chunkOffsetY, false));
                    }
                }

            } catch (Exception e) {
                LOGGER.severe(e.getMessage());
            }

        }
    }

    public int getPageCount() {
        if (pdfDocument.isOpen()) {
            return pdfDocument.getPageNumber();
        } else {
            return pdfDocument.getPageNumber() - 1;
        }
    }

    public boolean useTotalPageField() {
        return totalPageFieldUsed;
    }

}