All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.discovery.jasperreports.jasper2word.J2WDocxPoiHelper Maven / Gradle / Ivy

The newest version!
package it.discovery.jasperreports.jasper2word;

import it.discovery.jasperreports.jasper2word.J2WAbstractJRExporter.PageOrder;
import it.discovery.jasperreports.jasper2word.J2WAbstractPrintElementVisitorContext.DocxDocumentPart;
import it.discovery.jasperreports.jasper2word.J2WAbstractPrintElementVisitorContext.LastDocxElement;
import it.discovery.jasperreports.jasper2word.J2WGridPageLayout.ComponentPosition;
import it.discovery.jasperreports.jasper2word.J2WGridPageLayout.ComponentPositionInTable;
import it.discovery.jasperreports.jasper2word.J2WGridPageLayout.ComponentTableInfo;
import it.discovery.jasperreports.jasper2word.J2WGridPageLayout.HeaderFooterPageInfo;
import it.discovery.jasperreports.jasper2word.J2WReportConfiguration.ESpacingPolicy;
import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.base.JRBaseStyle;
import net.sf.jasperreports.engine.base.JRBoxPen;
import net.sf.jasperreports.engine.fill.JRTemplatePrintElement;
import net.sf.jasperreports.engine.type.FillEnum;
import net.sf.jasperreports.engine.type.LineSpacingEnum;
import net.sf.jasperreports.engine.type.ModeEnum;
import net.sf.jasperreports.engine.type.VerticalAlignEnum;
import net.sf.jasperreports.engine.util.JRStyledText;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.impl.xb.xmlschema.SpaceAttribute.Space;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;

import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
import java.awt.font.TextAttribute;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.text.AttributedCharacterIterator;
import java.text.AttributedCharacterIterator.Attribute;
import java.text.MessageFormat;
import java.util.*;
import java.util.List;

/**
 * Helper class to prodoce docx document with poi-apache library.
 * @author discovery
 * @date 13/08/15 10.07
 */
class J2WDocxPoiHelper {
    /** Macro for automatic color */
    private static final String COLOR_AUTO = "auto";

    /**
     * Hide the constructor.
     */
    private J2WDocxPoiHelper() {
    }

    /**
     * Append, if necessary, a new table in the doc.
     * @param tablePos The position of an element in a table.
     * @param context The exporter visitor context.
     * @return The metadata of the built table.
     */
    static TableDocInfo buildTables(ComponentPositionInTable tablePos, J2WDocxPrintElementVisitorContext context) {
        Map tables = context.tables;

        ComponentTableInfo table = tablePos.getTableInfo();
        TableDocInfo res = tables.get(table.getTableId());
        XWPFDocument document = context.getDocument();
        // Check for pre-built table
        if (res == null) {
            // if the previous element in document was a table or table must be moved more down, a new space paragraph is appended
            if (context.getLastDocxElement() == LastDocxElement.TABLE || context.getLastY(tablePos.getDocumentPart()) < tablePos.getTableInfo().getY()) {
                CTP paragraph;
                if (tablePos.getDocumentPart() == DocxDocumentPart.HEADER)
                    paragraph = context.headerContent.addNewP();
                else if (tablePos.getDocumentPart() == DocxDocumentPart.FOOTER)
                    paragraph = context.footerContent.addNewP();
                else
                    paragraph = document.createParagraph().getCTP();

                CTR run = paragraph.addNewR();

                CTPPr pPr = paragraph.getPPr();
                if (pPr == null)
                    pPr = paragraph.addNewPPr();

                CTString pStyle = pPr.getPStyle();
                if (pStyle == null)
                    pStyle = pPr.addNewPStyle();
                pStyle.setVal("Normal");

                CTRPr rPr = run.getRPr();
                if (rPr == null)
                    run.addNewRPr();

                CTSpacing spacing = pPr.getSpacing();
                if (spacing == null)
                    spacing = pPr.addNewSpacing();

                spacing.setBefore(BigInteger.ZERO);
                spacing.setBeforeLines(BigInteger.ZERO);
                spacing.setAfterLines(BigInteger.ZERO);

//                if (table.getParent() != null)
//                    setParagraphMargins(paragraph, context.getLastParagraphX(), context.getLastParagraphWidth(), context);
//                else
//                    setParagraphMargins(paragraph, table.getX(), table.getWidth(), context);

                if (context.getLastY(tablePos.getDocumentPart()) < tablePos.getTableInfo().getY()) {
                    BigInteger gap = EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, tablePos.getTableInfo().getY() - context.getLastY(tablePos.getDocumentPart())).divide(BigInteger.valueOf(2));
                    BigInteger check = BigInteger.valueOf(240);
                    if (gap.min(check).equals(check)) {
                        spacing.setAfter(BigInteger.ZERO);
                        spacing.setLine(gap.add(gap));
                    }
                    else {
                        spacing.setAfter(gap);
                        spacing.setLine(gap);
                    }
                    spacing.setLineRule(STLineSpacingRule.EXACT);
                }
                else {
                    spacing.setAfter(BigInteger.ZERO);
                    spacing.setLineRule(STLineSpacingRule.EXACT);
                    spacing.setLine(BigInteger.ONE);
                }

            }

            TableDocInfo parent = null;
            if (table.getParent() instanceof ComponentPositionInTable)
                parent = buildTables((ComponentPositionInTable) table.getParent(), context);

            // Building the table
            int[] rowsHeight = table.getRows();
            int[] columnsWidth = table.getColumns();
            BigInteger columnsX[] = new BigInteger[columnsWidth.length + 1];
            columnsX[0] = BigInteger.ZERO;

            CTTbl xwpfTable;
            if (parent == null) {
                if (tablePos.getDocumentPart() == DocxDocumentPart.HEADER)
                    xwpfTable = context.headerContent.addNewTbl();
                else if (tablePos.getDocumentPart() == DocxDocumentPart.FOOTER)
                    xwpfTable = context.footerContent.addNewTbl();
                else {
                    XWPFTable emptyTable = context.getDocument().createTable();
                    while (!emptyTable.getRows().isEmpty())
                        emptyTable.removeRow(0);
                    xwpfTable = emptyTable.getCTTbl();
                }

            }
            else {
                CTRow row = parent.table.getTrList().get(tablePos.getRow());
                CTTc cell = row.getTcList().get(tablePos.getCol());

                while (!cell.getPList().isEmpty())
                    cell.removeP(0);
                xwpfTable = cell.addNewTbl();
            }

            for (int rowHeight : rowsHeight) {
                CTRow ctRow = xwpfTable.addNewTr();
                CTTrPr trPr = ctRow.getTrPr();
                if (trPr == null)
                    trPr = ctRow.addNewTrPr();
                // Rows height
                CTHeight ctHeight;
                if (trPr.getTrHeightList().isEmpty())
                    ctHeight = trPr.addNewTrHeight();
                else
                    ctHeight = trPr.getTrHeightList().get(0);
                ctHeight.setVal(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, rowHeight));
                if (table.isForceRowHeight())
                    ctHeight.setHRule(STHeightRule.EXACT);
                else
                    ctHeight.setHRule(STHeightRule.AUTO);

                // Columns width (paragraph)
                for (int aColumnsWidth : columnsWidth) {
                    CTTc tc = ctRow.addNewTc();
                    tc.addNewP();

                    CTTcPr tcPr = tc.getTcPr();
                    if (tcPr == null)
                        tcPr = tc.addNewTcPr();

                    CTTblWidth w = tcPr.getTcW();
                    if (w == null)
                        w = tcPr.addNewTcW();
                    w.setType(STTblWidth.DXA);
                    w.setW(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, aColumnsWidth));
                    tcPr.setTcW(w);
                }
            }

            CTTblPr tblPr = xwpfTable.getTblPr();
            if (tblPr == null)
                tblPr = xwpfTable.addNewTblPr();

            // Table width
            CTTblWidth tblW = tblPr.getTblW();
            if (tblW == null)
                tblW = tblPr.addNewTblW();
            tblW.setW(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, table.getWidth()));
            tblW.setType(STTblWidth.DXA);

            // Table right margin
            CTTblWidth tblInd = tblPr.getTblInd();
            if (tblInd == null)
                tblInd = tblPr.addNewTblInd();
            if (table.getParent() == null)
                tblInd.setW(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, table.getX() - context.getLayout().getPageInfo().getLeftMargin()));
            else
                tblInd.setW(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, table.getX() - table.getParent().getX()));
            tblInd.setType(STTblWidth.DXA);

            // Table borders
            if (table.isBordered()) {
                CTTblBorders tblBorders = tblPr.getTblBorders();
                if (tblBorders == null)
                    tblBorders = tblPr.addNewTblBorders();
                updateBorder(tblBorders.addNewInsideH(), 1, 2, "000000", STBorder.SINGLE);
                updateBorder(tblBorders.addNewInsideV(), 1, 2, "000000", STBorder.SINGLE);
                updateBorder(tblBorders.addNewBottom(), 1, 2, "000000", STBorder.SINGLE);
                updateBorder(tblBorders.addNewLeft(), 1, 2, "000000", STBorder.SINGLE);
                updateBorder(tblBorders.addNewRight(), 1, 2, "000000", STBorder.SINGLE);
                updateBorder(tblBorders.addNewTop(), 1, 2, "000000", STBorder.SINGLE);
            }
            else {
                if (tblPr.isSetTblBorders())
                    tblPr.unsetTblBorders();
//                updateBorder(tblBorders.addNewInsideH(), 0, 0, "000000", STBorder.SINGLE);
//                updateBorder(tblBorders.addNewInsideV(), 0, 0, "000000", STBorder.SINGLE);
//                updateBorder(tblBorders.addNewBottom(), 0, 0, "000000", STBorder.SINGLE);
//                updateBorder(tblBorders.addNewLeft(), 0, 0, "000000", STBorder.SINGLE);
//                updateBorder(tblBorders.addNewRight(), 0, 0, "000000", STBorder.SINGLE);
//                updateBorder(tblBorders.addNewTop(), 0, 0, "000000", STBorder.SINGLE);
            }

            // Table grid column definition
            CTTblGrid tblGrid = xwpfTable.getTblGrid();
            if (tblGrid == null)
                tblGrid = xwpfTable.addNewTblGrid();
            for (int i = 0; i < columnsWidth.length; i++) {
                int colWidth = columnsWidth[i];
                CTTblGridCol col = tblGrid.addNewGridCol();
                BigInteger width = EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, colWidth);
                col.setW(width);
                columnsX[i + 1] = width;
            }


            res = new TableDocInfo(xwpfTable);
            SortedSet cellToRemove = new TreeSet<>(new CellToRemovePointComparator());
            for (Rectangle mergedRegion : table.getMergedRegions()) {
                CTRow row = xwpfTable.getTrList().get(mergedRegion.y);
                CTTc cell = row.getTcList().get(mergedRegion.x);

                CTTcPr tcPr = cell.getTcPr();
                if (tcPr == null)
                    tcPr = cell.addNewTcPr();

                if (mergedRegion.width > 1)
                    tcPr.addNewGridSpan().setVal(BigInteger.valueOf((long) mergedRegion.width));
                if (mergedRegion.height > 1)
                    tcPr.addNewVMerge().setVal(STMerge.RESTART);

                CTTblWidth tcW = tcPr.getTcW();
                if (tcW == null)
                    tcW = tcPr.addNewTcW();
                tcW.setW(columnsX[mergedRegion.x + 1].subtract(columnsX[mergedRegion.x]));
                tcW.setType(STTblWidth.DXA);
                tcPr.setTcW(tcW);

                for (int rowIndex = mergedRegion.y; rowIndex < mergedRegion.height + mergedRegion.y; rowIndex++) {
                    CTRow rowSpan = xwpfTable.getTrList().get(rowIndex);
                    for (int columnIndex = mergedRegion.x; columnIndex < mergedRegion.x + mergedRegion.width ; columnIndex++) {
                        if (columnIndex > mergedRegion.x)
                            cellToRemove.add(new Point(columnIndex, rowIndex));
                        if (rowIndex > mergedRegion.y && columnIndex == mergedRegion.x) {
                            cell = rowSpan.getTcList().get(columnIndex);
                            tcPr = cell.getTcPr();
                            if (tcPr == null)
                                tcPr = cell.addNewTcPr();

                            if (mergedRegion.width > 1)
                                tcPr.addNewGridSpan().setVal(BigInteger.valueOf((long) mergedRegion.width));
                            if (mergedRegion.height > 1) {
                                tcPr.addNewVMerge().setVal(STMerge.CONTINUE);
                            }
                            tcPr.setTcW(tcW);
                        }
                    }
                }
            }

            for (Point cellCoord : cellToRemove) {
                CTRow rowSpan = xwpfTable.getTrList().get(cellCoord.y);
                rowSpan.removeTc(cellCoord.x);
            }

            tables.put(table.getTableId(), res);

            context.setLastY(tablePos.getDocumentPart(), table.getY() + table.getHeight());
        }

        context.setLastDocxElement(LastDocxElement.TABLE);

        return res;
    }

    /**
     * Update a table border style.
     * @param ctBorder The border to update.
     * @param size The border size in points.
     * @param space The space between border and cell content in points.
     * @param color The border color.
     * @param type The border style.
     */
    private static void updateBorder(CTBorder ctBorder, int size, int space, String color, org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder.Enum type) {
        ctBorder.setColor(color);
        ctBorder.setVal(type);
        ctBorder.setSz(BigInteger.valueOf(size));
        ctBorder.setSpace(BigInteger.valueOf(space));
    }

    /**
     * Format a {@link Color color} in a hexadecimal string (RRGGBB).
     * @param color The java awt Color.
     * @return The formatted string color.
     */
    static String colorToStringFormat(Color color) {
        Color base = new Color(255, 255, 255, 0);
        return String.format("%6s", Integer.toHexString(color.getRGB() & base.getRGB())).replaceAll(" ", "0");
    }

    /**
     * Parse string (or byte[]) color into a java awt Color.
     * @param color It can be a hexadecimal {@link String String} like RRGGBB or a {@code byte[]} like byte[] {R, G, B}.
     * @return The java awt Color.
     */
    static Color stringToColorParse(Object color) {
        if (color == null)
            return null;
        if (color instanceof String) {
            String s_color = (String) color;
            if (s_color.equals(COLOR_AUTO))
                return null;
            String tokens[] = s_color.split("(?<=\\G\\d{2})", 3);
            return new Color(Integer.parseInt(tokens[0], 16), Integer.parseInt(tokens[1], 16), Integer.parseInt(tokens[2], 16), 0);
        }
        else if (color instanceof byte[]) {
            byte c[] = (byte[]) color;
            return new Color(c[0], c[1], c[2]);
        }
        else
            throw new JRRuntimeException("Unknown color type: " + color.getClass().getName());
    }

    /**
     * Reset document status variables and build document styles.
     * @param firstReport {@code true} if first report, {@code} false otherwise.
     * @param context The visitor context.
     * @param report Thre report to export.
     */
    static void prepareNewDocument(boolean firstReport, J2WDocxPrintElementVisitorContext context, JasperPrint report) {
        context.resetDocumentStatus();
        context.setReport(report);
        if (firstReport)
            context.currentSection = null;
        buildStyles(context, report.getStylesList());
    }

    /**
     * Prepare new page (build new section if required), set page size and (optiona) header and footer.
     * @param context The visitor context.
     * @param jrPage The jasper page.
     * @param pageNumber The relative page number.
     * @param pageOrder The absolute page position.
     */
    static void prepareNewPage(J2WDocxPrintElementVisitorContext context, JRPrintPage jrPage, int pageNumber, PageOrder pageOrder) {
        if (!pageOrder.isFirstestPage() && context.currentSection == null) {
            XWPFDocument document = context.getDocument();

            XWPFParagraph xwpfParagraph = document.createParagraph();
            CTP paragraph = xwpfParagraph.getCTP();
            CTR run = paragraph.addNewR();

            CTBr breakObj = run.addNewBr();
            breakObj.setType(STBrType.PAGE);
            breakObj.setClear(STBrClear.ALL);
        }
        context.currentSection = null;
        context.policy = null;
        setPageSizeAndMargins(context, pageOrder);
        buildHeaderFooter(context, pageNumber, pageOrder);
    }

    /**
     * Finalize the page (add the page section, if present).
     * @param context The visitor context.
     * @param jrPage The jasper page.
     * @param pageNumber The relative page number.
     * @param pageOrder The absolute page position.
     */
    static void finalizePage(J2WDocxPrintElementVisitorContext context, JRPrintPage jrPage, int pageNumber, PageOrder pageOrder) {
        XWPFDocument document = context.getDocument();
        if (context.currentSection != null && !pageOrder.isLastestPage()) {
            CTP paragraph = document.createParagraph().getCTP();
            CTPPr pPr = paragraph.getPPr();
            if (pPr == null)
                pPr = paragraph.addNewPPr();
            pPr.setSectPr(context.currentSection);

            CTR run = paragraph.addNewR();
            run.addNewLastRenderedPageBreak();
        }
    }

    /**
     * Return the current section if exits, or create a new one.
     * @param context The visitor context.
     * @param pageOrder The absolute page position.
     * @return The current section or new one.
     */
    private static CTSectPr getOrCreateCurrentSection(J2WDocxPrintElementVisitorContext context, PageOrder pageOrder) {
        CTSectPr sectPr;
        sectPr = context.currentSection;
        if (sectPr == null) {
            if (pageOrder.isLastestPage()) {
                CTBody body = context.getDocument().getDocument().getBody();
                sectPr = body.getSectPr();
                if (sectPr == null)
                    sectPr = body.addNewSectPr();
            }
            else
                sectPr = CTSectPr.Factory.newInstance();
        }

        context.currentSection = sectPr;
        return sectPr;
    }

    /**
     * Set the page size and margins. If the report has not margins, default margins are created with 5pt size.
     * @param context The visitor context.
     * @param pageOrder The absolute page position.
     */
    private static void setPageSizeAndMargins(J2WDocxPrintElementVisitorContext context, PageOrder pageOrder) {
        JasperPrint report = context.getReport();

        HeaderFooterPageInfo pageInfo = context.getLayout().getPageInfo();
        if (context.getLastPageInfo() == null || context.getLastPageInfo().containsHeaderFooter() || pageInfo.containsHeaderFooter() || pageOrder.isLastestPage()) {
            CTSectPr sectPr = getOrCreateCurrentSection(context, pageOrder);

            int top;
            int bottom;
            int right = 5;
            int left = 5;
            int header = 0;
            int footer = 0;

            if (pageInfo.getRightMargin() > 0)
                right = pageInfo.getRightMargin();
            if (pageInfo.getLeftMargin() > 0)
                left = pageInfo.getLeftMargin();

            if (pageInfo.getMinHeader() != null) {
                top = pageInfo.getTopMargin() + pageInfo.getMinHeader();
                header = pageInfo.getMinHeader();
            }
            else if (pageInfo.getTopMargin() > 0)
                top = pageInfo.getTopMargin();
            else
                top = 5;

            if (pageInfo.getMaxFooter() != null) {
                bottom = pageInfo.getMaxFooter() + pageInfo.getBottomMargin();
                footer = pageInfo.getMaxFooter();
            }
            else if (pageInfo.getBottomMargin() > 0)
                bottom = pageInfo.getBottomMargin();
            else
                bottom = 5;

            CTPageSz ctPageSz = sectPr.getPgSz();
            if (ctPageSz == null)
                ctPageSz = sectPr.addNewPgSz();


            ctPageSz.setH(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, report.getPageHeight()));
            ctPageSz.setW(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, report.getPageWidth()));

            CTPageMar ctPageMar = sectPr.getPgMar();
            if (ctPageMar == null)
                ctPageMar = sectPr.addNewPgMar();

            ctPageMar.setBottom(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, bottom));
            ctPageMar.setTop(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, top));
            ctPageMar.setLeft(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, left));
            ctPageMar.setRight(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, right));
            ctPageMar.setHeader(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, header));
            ctPageMar.setFooter(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, footer));

            ctPageMar.setGutter(BigInteger.ZERO);

            sectPr.setPgMar(ctPageMar);
        }
    }

    /**
     * Build the header and footer, if exist.
     * @param context The visitor context.
     * @param pageNumber The relative page number.
     * @param pageOrder The absolute page position.
     */
    static void buildHeaderFooter(J2WDocxPrintElementVisitorContext context, int pageNumber, PageOrder pageOrder) {
        try {
            HeaderFooterPageInfo pageInfo = context.getLayout().getPageInfo();
            if (context.getLastPageInfo() == null || context.getLastPageInfo().containsHeaderFooter() || context.getLayout().getPageInfo().containsHeaderFooter()) {
                XWPFDocument document = context.getDocument();

                // Get last section SectPr and create a new one if it doesn't exist
                CTSectPr sectPr = getOrCreateCurrentSection(context, pageOrder);
                CTBody body = document.getDocument().getBody();
                CTSectPr oldSectPr = body.getSectPr();
                body.setSectPr(sectPr);

                XWPFHeaderFooterPolicy policy = context.policy;
                if (policy == null) {
                    policy = new XWPFHeaderFooterPolicy(document, sectPr);
                    context.policy = policy;
                }

                if (pageInfo.getHeaderHeight() != null) {
                    XWPFHeader header = policy.createHeader(XWPFHeaderFooterPolicy.DEFAULT, new XWPFParagraph[]{});
                    context.headerContent = header._getHdrFtr();
                    CTHdrFtrRef ctHdrFtrRef;
                    if (sectPr.getHeaderReferenceList().isEmpty())
                        ctHdrFtrRef = sectPr.addNewHeaderReference();
                    else
                        ctHdrFtrRef = sectPr.getHeaderReferenceArray(0);
                    ctHdrFtrRef.setId(header.getPackageRelationship().getId());
                    ctHdrFtrRef.setType(STHdrFtr.DEFAULT);
                }
                else {
                    context.headerContent = null;
                }

                if (pageInfo.getFooterHeight() != null) {
                    XWPFFooter footer = policy.createFooter(XWPFHeaderFooterPolicy.DEFAULT, new XWPFParagraph[]{});
                    context.footerContent = footer._getHdrFtr();
                    CTHdrFtrRef ctHdrFtrRef;
                    if (sectPr.getFooterReferenceList().isEmpty())
                        ctHdrFtrRef = sectPr.addNewFooterReference();
                    else
                        ctHdrFtrRef = sectPr.getFooterReferenceArray(0);
                    ctHdrFtrRef.setId(footer.getPackageRelationship().getId());
                    ctHdrFtrRef.setType(STHdrFtr.DEFAULT);
                }
                else {
                    context.footerContent = null;
                }
                sectPr.addNewTitlePg().setVal(STOnOff.FALSE);

                if (oldSectPr != null)
                    body.setSectPr(oldSectPr);
                else
                    body.unsetSectPr();
            }
        } catch (Exception e) {
            throw new JRRuntimeException(e);
        }
    }

    /**
     * Update doc style with jasper style attributes.
     * @param ctStyle The doc style.
     * @param jrStyle The jasper style.
     * @param context The visito context.
     * @param update {@code true} to update doc style, {@code false} to create a new one doc style with jasper style attributes.
     * @return The updated doc style.
     */
    private static String updateStyle(CTStyle ctStyle, JRStyle jrStyle, J2WDocxPrintElementVisitorContext context, boolean update) {
        JRPen linePen = jrStyle.getLinePen();

        String styleName = jrStyle.getName();
        if (!update) {
            int styleCount = 1;
            while (context.allStyles.contains(styleName)) {
                styleName = jrStyle.getName() + styleCount;
                styleCount++;
            }
            context.allStyles.add(styleName);

            CTString name = ctStyle.getName();
            if (name == null)
                name = ctStyle.addNewName();
            name.setVal(styleName);
            ctStyle.setName(name);
            ctStyle.setStyleId(styleName);
            if (!styleName.equals(jrStyle.getName())) {
                CTString aliases = ctStyle.getAliases();
                if (aliases == null)
                    aliases = ctStyle.addNewAliases();
                aliases.setVal(jrStyle.getName());
                ctStyle.setAliases(aliases);
            }
            ctStyle.setType(STStyleType.PARAGRAPH);

            CTOnOff qFormat = ctStyle.getQFormat();
            if (qFormat == null)
                qFormat = ctStyle.addNewQFormat();
            qFormat.setVal(STOnOff.ON);
        }

        // Default run settings
        CTRPr ctrPr = ctStyle.getRPr();
        if (ctrPr == null)
            ctrPr = ctStyle.addNewRPr();
        // Default paragraph settings
        CTPPr ctpPr = ctStyle.getPPr();
        if (ctpPr == null)
            ctpPr = ctStyle.addNewPPr();

        // Bold
        if (jrStyle.isOwnBold() != null) {
            CTOnOff b = ctrPr.getB();
            if (b == null)
                b = ctrPr.addNewB();
            b.setVal(jrStyle.isOwnBold() ? STOnOff.ON : STOnOff.OFF);
        }

        // Italic
        if (jrStyle.isOwnItalic() != null) {
            CTOnOff i = ctrPr.getI();
            if (i == null)
                i = ctrPr.addNewI();
            i.setVal(jrStyle.isOwnItalic() ? STOnOff.ON : STOnOff.OFF);
        }

        // Underline
        if (jrStyle.isOwnUnderline() != null && jrStyle.isOwnUnderline()) {
            CTUnderline ctUnderline = ctrPr.getU();
            if (ctUnderline == null)
                ctUnderline = ctrPr.addNewU();
            if (linePen == null) {
                ctUnderline.setVal(STUnderline.SINGLE);
                if (jrStyle.getForecolor() != null)
                    ctUnderline.setColor(colorToStringFormat(jrStyle.getForecolor()));
                else
                    ctUnderline.setColor(COLOR_AUTO);
            }
            else {
                if (linePen.getOwnLineStyleValue() != null) {
                    switch (linePen.getOwnLineStyleValue()) {
                        case SOLID:
                            ctUnderline.setVal(STUnderline.SINGLE);
                            break;
                        case DASHED:
                            ctUnderline.setVal(STUnderline.DASH);
                            break;
                        case DOTTED:
                            ctUnderline.setVal(STUnderline.DOTTED);
                            break;
                        case DOUBLE:
                            ctUnderline.setVal(STUnderline.DOUBLE);
                            break;
                        default:
                            ctUnderline.setVal(STUnderline.SINGLE);
                            break;
                    }
                }
                else
                    ctUnderline.setVal(STUnderline.SINGLE);
                if (linePen.getLineColor() != null)
                    ctUnderline.setColor(colorToStringFormat(linePen.getLineColor()));
                else if (jrStyle.getForecolor() != null)
                    ctUnderline.setColor(colorToStringFormat(jrStyle.getForecolor()));
                else
                    ctUnderline.setColor(colorToStringFormat(Color.black));
            }
        }

        // Strike Through
        if (jrStyle.isOwnStrikeThrough() != null) {
            CTOnOff strike = ctrPr.getStrike();
            if (strike == null)
                strike = ctrPr.addNewStrike();
            strike.setVal(jrStyle.isOwnStrikeThrough() ? STOnOff.ON : STOnOff.OFF);
        }

        // Fore color
        CTColor foreColor = ctrPr.getColor();
        if (foreColor == null)
            foreColor = ctrPr.addNewColor();
        if (jrStyle.getOwnForecolor() != null)
            foreColor.setVal(colorToStringFormat(jrStyle.getOwnForecolor()));
        else
            foreColor.setVal(COLOR_AUTO);
        ctrPr.setColor(foreColor);


        // Background / Highlight
        if (jrStyle.getOwnBackcolor() != null) {
            CTShd shdp = ctpPr.getShd();
            if (shdp == null)
                shdp = ctpPr.addNewShd();
            shdp.setVal(STShd.CLEAR);
            shdp.setFill(colorToStringFormat(jrStyle.getOwnBackcolor()));
        }

        // Text alignment
        CTTextAlignment alignment = ctpPr.getTextAlignment();
        if (alignment == null)
            alignment = ctpPr.addNewTextAlignment();
        if (jrStyle.getOwnVerticalAlignmentValue() != null) {
            switch (jrStyle.getOwnVerticalAlignmentValue()) {
                case TOP:
                    alignment.setVal(STTextAlignment.TOP);
                    break;
                case MIDDLE:
                    alignment.setVal(STTextAlignment.CENTER);
                    break;
                case BOTTOM:
                    alignment.setVal(STTextAlignment.BOTTOM);
                    break;
                case JUSTIFIED:
                    alignment.setVal(STTextAlignment.BASELINE);
                    break;
                default:
                    alignment.setVal(STTextAlignment.AUTO);
                    break;
            }
        }
        else
            alignment.setVal(STTextAlignment.AUTO);
        ctpPr.setTextAlignment(alignment);
        if (jrStyle.getOwnHorizontalAlignmentValue() != null) {
            CTJc ctJc = ctpPr.getJc();
            if (ctJc == null)
                ctJc = ctpPr.addNewJc();
            switch (jrStyle.getOwnHorizontalAlignmentValue()) {
                case LEFT:
                    ctJc.setVal(STJc.LEFT);
                    break;
                case CENTER:
                    ctJc.setVal(STJc.CENTER);
                    break;
                case RIGHT:
                    ctJc.setVal(STJc.RIGHT);
                    break;
                case JUSTIFIED:
                    ctJc.setVal(STJc.BOTH);
                    break;
                default:
                    ctJc.setVal(STJc.LEFT);
                    break;
            }
        }

        // Font
        if (jrStyle.getOwnFontName() != null) {
            CTFonts ctFonts = ctrPr.getRFonts();
            if (ctFonts == null)
                ctFonts = ctrPr.addNewRFonts();
            ctFonts.setAscii(jrStyle.getOwnFontName());
            ctFonts.setHAnsi(jrStyle.getOwnFontName());
        }
        if (jrStyle.getOwnFontsize() != null) {
            CTHpsMeasure ctHpsMeasure = ctrPr.getSz();
            if (ctHpsMeasure == null)
                ctHpsMeasure = ctrPr.addNewSz();
            ctHpsMeasure.setVal(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.HALF_POINTS, jrStyle.getOwnFontsize()));
        }

        // Paragraph advanced settings
        JRParagraph paragraph = jrStyle.getParagraph();
        if (paragraph != null) {
            CTInd ctInd = ctpPr.getInd();
            if (ctInd == null)
                ctInd = ctpPr.addNewInd();

            if (paragraph.getOwnFirstLineIndent() != null)
                ctInd.setFirstLine(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, paragraph.getOwnFirstLineIndent()));
            if (paragraph.getOwnLeftIndent() != null)
                ctInd.setLeft(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, paragraph.getOwnLeftIndent()));
            if (paragraph.getOwnRightIndent() != null)
                ctInd.setRight(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, paragraph.getOwnRightIndent()));


            buildParagraphSpacing(paragraph, ctpPr.getSpacing(), ctpPr, update);
        }

        if (jrStyle.getStyle() != null && ctStyle.getBasedOn() == null) {
            CTString basedOn = ctStyle.addNewBasedOn();
            basedOn.setVal(jrStyle.getStyle().getName());
        }

        return styleName;
    }

    /**
     * Build all the jaspser styles.
     * @param context The visitor context.
     * @param stylesList The jasper styles.
     */
    private static void buildStyles(J2WDocxPrintElementVisitorContext context, java.util.List stylesList) {
        if (!stylesList.isEmpty()) {
            XWPFDocument document = context.getDocument();
            XWPFStyles styles = document.getStyles();
            if (styles == null)
                styles = document.createStyles();

            CTOnOff ctOnOff_ON = CTOnOff.Factory.newInstance();
            CTOnOff ctOnOff_OFF = CTOnOff.Factory.newInstance();
            ctOnOff_ON.setVal(STOnOff.ON);
            ctOnOff_OFF.setVal(STOnOff.OFF);

            for (JRStyle jrStyle : stylesList) {
                CTStyle ctStyle = CTStyle.Factory.newInstance();
                String styleName = updateStyle(ctStyle, jrStyle, context, false);

                XWPFStyle style = new XWPFStyle(ctStyle, styles);
                styles.addStyle(style);
                context.remapStylesNameByJrStyle.put(jrStyle.getName(), styleName);
            }
        }
    }

    /**
     * Find a style by name.
     * @param styleName The style name to find.
     * @param context The visitor context.
     * @param safe {@code true} return {@code null} in case of errors; {@code false} throw a {@link JRRuntimeException JRRuntimeException} in case of error.
     * @return The found style, or {@code null} if not exists.
     * @throws JRRuntimeException In case of IO errors.
     */
    private static CTStyle findStyle(String styleName, J2WDocxPrintElementVisitorContext context, boolean safe) throws JRRuntimeException {
        CTStyle style = context.builtFoundStyle.get(styleName);
        if (style == null) {
            XWPFDocument document = context.getDocument();
            CTStyles stylePart;
            try {
                stylePart = document.getStyle();
            } catch (Exception e) {
                if (safe)
                    return null;
                else
                    throw new JRRuntimeException(e);
            }
            List styleList = stylePart.getStyleList();
            for (CTStyle ctStyle : styleList) {
                if (ctStyle.getName() != null && ctStyle.getName().getVal().equalsIgnoreCase(styleName)) {
                    style = ctStyle;
                    break;
                }
            }
            if (style != null)
                context.builtFoundStyle.put(styleName, style);
        }
        return style;
    }

    /**
     * Utility method: return {@code valOverride} if {@code useOverriding} is {@code true} and {@code valOverride} is not {@code null};
     * return {@code vale} otherwise.
     * @param val The default value.
     * @param valOverride The overriding value.
     * @param useOverriding {@code} true to use {@code valOverride}, {@code false} to use {@code val}.
     * @param  Type of value.
     * @return {@code valOverride} if {@code useOverriding} is {@code true} and {@code valOverride} is not {@code null};
     * return {@code vale} otherwise.
     */
    static  T getOverridingValue(T val, T valOverride, boolean useOverriding) {
        if (useOverriding)
            return valOverride != null? valOverride : val;
        else
            return val;
    }

    /**
     * Set paragraph spacing: before, after and line spacing.
     * @param paragraph The jasper paragraph.
     * @param from The input/output spacing object (can be {@code null}).
     * @param paragraphProperties The doc paragraph properties.
     * @param useOverriding {@code true} to use paragraph own spacing, {@code false} otherwise.
     * @return The spacing object.
     * @see #getOverridingValue(Object, Object, boolean)
     */
    static boolean buildParagraphSpacing(JRParagraph paragraph, CTSpacing from, CTPPr paragraphProperties, boolean useOverriding) {
        boolean setSpacing = false;
        CTSpacing ctSpacing = from;
        if (ctSpacing == null)
            ctSpacing = paragraphProperties.addNewSpacing();
        else
            setSpacing = true;

        Integer spacingAfter = getOverridingValue(paragraph.getSpacingAfter(), paragraph.getOwnSpacingAfter(), useOverriding);
        if (spacingAfter != null) {
            ctSpacing.setAfter(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, spacingAfter));
            setSpacing = true;
        }
        Integer spacingBefore = getOverridingValue(paragraph.getSpacingBefore(), paragraph.getOwnSpacingBefore(), useOverriding);
        if (spacingBefore != null) {
            ctSpacing.setBefore(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, spacingBefore));
            setSpacing = true;
        }
        LineSpacingEnum lineSpacingEnum = getOverridingValue(paragraph.getLineSpacing(), paragraph.getOwnLineSpacing(), useOverriding);
        if (lineSpacingEnum != null) {
            Float lineSpacingSize = getOverridingValue(paragraph.getLineSpacingSize(), paragraph.getOwnLineSpacingSize(), useOverriding);
            switch (lineSpacingEnum) {
                case SINGLE:
                    ctSpacing.setLineRule(STLineSpacingRule.AUTO);
                    ctSpacing.setLine(EConvertMisure.toLineSpacing(null, 1D));
                    break;
                case ONE_AND_HALF:
                    ctSpacing.setLineRule(STLineSpacingRule.AUTO);
                    ctSpacing.setLine(EConvertMisure.toLineSpacing(null, 1.5D));
                    break;
                case DOUBLE:
                    ctSpacing.setLineRule(STLineSpacingRule.AUTO);
                    ctSpacing.setLine(EConvertMisure.toLineSpacing(null, 2D));
                    break;
                case AT_LEAST:
                    ctSpacing.setLineRule(STLineSpacingRule.AT_LEAST);
                    if (lineSpacingSize != null)
                        ctSpacing.setLine(EConvertMisure.toLineSpacing(lineSpacingSize.doubleValue(), 1D));
                    else
                        ctSpacing.setLine(EConvertMisure.toLineSpacing(null, 1D));
                    break;
                case FIXED:
                    ctSpacing.setLineRule(STLineSpacingRule.EXACT);
                    if (lineSpacingSize != null)
                        ctSpacing.setLine(EConvertMisure.toLineSpacing(lineSpacingSize.doubleValue(), 1D));
                    else
                        ctSpacing.setLine(EConvertMisure.toLineSpacing(null, 1D));
                    break;
                case PROPORTIONAL:
                    ctSpacing.setLineRule(STLineSpacingRule.AUTO);
                    if (lineSpacingSize != null)
                        ctSpacing.setLine(EConvertMisure.toLineSpacing(lineSpacingSize.doubleValue(), 1D));
                    else
                        ctSpacing.setLine(EConvertMisure.toLineSpacing(null, 1D));
                    break;
            }
            setSpacing = true;
        }

        if (!setSpacing) {
            paragraphProperties.unsetSpacing();
            return false;
        }
        else
            return true;
    }

    /**
     * Set paragraph margins (left and right).
     * @param paragraph The doc paragraph.
     * @param x The left margin.
     * @param width The paragraph width.
     * @param context The visitor context.
     */
    static void setParagraphMargins(CTP paragraph, Integer x, Integer width, J2WDocxPrintElementVisitorContext context) {
        if (x != null && width != null) {
            CTPPr pPr = paragraph.getPPr();
            if (pPr == null)
                pPr = paragraph.addNewPPr();
            CTInd ind = pPr.getInd();
            if (ind == null)
                ind = pPr.addNewInd();

            HeaderFooterPageInfo pageInfo = context.getLayout().getPageInfo();
            ind.setLeft(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, Math.max(x - pageInfo.getLeftMargin(), 0)));
            ind.setRight(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, Math.max(pageInfo.getPageSize().width - pageInfo.getRightMargin() - x - width, 0)));

            context.setLastParagraphX(x);
            context.setLastParagraphWidth(width);
        }
    }

    /**
     * Set the paragraph borders.
     * @param paragraph The doc baragraph.
     * @param element The box element container with border infos.
     */
    static void setParagraphBorders(CTP paragraph, JRBoxContainer element) {
        if (element != null) {
            JRLineBox lineBox = element.getLineBox();
            if (lineBox != null) {
                CTPPr pPr = paragraph.getPPr();
                if (pPr == null)
                    pPr = paragraph.addNewPPr();
                CTPBdr pBdr = pPr.getPBdr();
                if (pBdr == null)
                    pBdr = pPr.addNewPBdr();


                CTBorder ctBorder = buildBorder(lineBox.getBottomPen());
                if (ctBorder != null)
                    pBdr.setBottom(ctBorder);
                else if (pBdr.isSetBottom())
                    pBdr.unsetBottom();

                ctBorder = buildBorder(lineBox.getTopPen());
                if (ctBorder != null)
                    pBdr.setTop(ctBorder);
                else if (pBdr.isSetTop())
                    pBdr.unsetTop();

                ctBorder = buildBorder(lineBox.getRightPen());
                if (ctBorder != null)
                    pBdr.setRight(ctBorder);
                else if (pBdr.isSetRight())
                    pBdr.unsetRight();

                ctBorder = buildBorder(lineBox.getLeftPen());
                if (ctBorder != null)
                    pBdr.setLeft(ctBorder);
                else if (pBdr.isSetLeft())
                    pBdr.unsetLeft();
            }
        }
    }

    /**
     * Set table cell borders.
     * @param cell A doc table cell.
     * @param element The box element container with border infos.
     */
    static void setTableCellBorders(ComponentPositionInTable position, CTTbl xwpfTable, CTTc cell, JRBoxContainer element) {


        if (element != null) {
            CTTc cellTop[];
            CTTc cellLeft[];
            CTTc cellBottom[];
            CTTc cellRight[];

            JRLineBox lineBox = element.getLineBox();

            if (lineBox != null) {
                if (position.getColSpan() > 1 || position.getRowSpan() > 1) {
                    List mergedRegions = position.getTableInfo().getMergedRegions();
                    Rectangle region = null;
                    Point coord = position.getTableInfo().getInverseCellCoordinate(position.getCol(), position.getRow());
                    for (Rectangle mergedRegion : mergedRegions) {
                        if (mergedRegion.x <= coord.x &&
                                mergedRegion.y <= coord.y &&
                                mergedRegion.x + mergedRegion.width - 1 >= coord.x &&
                                mergedRegion.y + mergedRegion.height - 1 >= coord.y) {
                            region = mergedRegion;
                            break;
                        }
                    }
                    if (region == null) {
                        String detail = "";
                        if (element instanceof JRIdentifiable)
                            detail += "ID = " + ((JRIdentifiable) element).getUUID();
                        if (element instanceof JRCommonElement) {
                            if (!detail.isEmpty())
                                detail += ", ";
                            detail += "Name = " + ((JRCommonElement) element).getKey();
                        }
                        throw new JRRuntimeException("Merged region not found for cell (element " + detail + ")");
                    }

                    coord = position.getTableInfo().getCellCoordinate(region.x, region.y);
                    CTRow row = xwpfTable.getTrList().get(coord.y);
                    cellTop = new CTTc[]{row.getTcList().get(coord.x), cell};

                    coord = position.getTableInfo().getCellCoordinate(region.x + region.width - 1, region.y + region.height - 1);
                    row = xwpfTable.getTrList().get(coord.y);
                    cellBottom = new CTTc[]{row.getTcList().get(coord.x), cell};

                    cellLeft = new CTTc[region.height + 1];
                    cellRight = new CTTc[region.height + 1];
                    cellLeft[0] = cellRight[0] = cell;
                    for (int r = region.y, i = 1; r < region.y + region.height; r++, i++) {
                        coord = position.getTableInfo().getCellCoordinate(region.x + region.width - 1, r);
                        row = xwpfTable.getTrList().get(coord.y);
                        cellRight[i] = row.getTcList().get(coord.x);
                        coord = position.getTableInfo().getCellCoordinate(region.x, r);
                        cellLeft[i] = row.getTcList().get(coord.x);
                    }
                }
                else {
                    cellTop = cellBottom = new CTTc[]{cell};
                    cellLeft = cellRight = new CTTc[]{cell};
                }


                for (int edge = 0; edge < 4; edge++) {

                    CTTc cells[];
                    switch (edge) {
                        case 0:
                            cells = cellTop;
                            break;
                        case 1:
                            cells = cellLeft;
                            break;
                        case 2:
                            cells = cellBottom;
                            break;
                        case 3:
                            cells = cellRight;
                            break;
                        default:
                            continue;
                    }

                    for (CTTc cellb: cells) {
                        CTTcPr tcPr = cellb.getTcPr();
                        if (tcPr == null)
                            tcPr = cellb.addNewTcPr();

                        CTTcBorders tcBorders = tcPr.getTcBorders();
                        if (tcBorders == null)
                            tcBorders = tcPr.addNewTcBorders();

                        CTBorder ctBorder;
                        switch (edge) {
                            case 0:
                                ctBorder = buildBorder(lineBox.getTopPen());
                                if (ctBorder != null)
                                    tcBorders.setTop(ctBorder);
                                else if (tcBorders.isSetTop())
                                    tcBorders.unsetTop();
                                break;
                            case 1:
                                ctBorder = buildBorder(lineBox.getLeftPen());
                                if (ctBorder != null)
                                    tcBorders.setLeft(ctBorder);
                                else if (tcBorders.isSetLeft())
                                    tcBorders.unsetLeft();
                                break;
                            case 2:
                                ctBorder = buildBorder(lineBox.getBottomPen());
                                if (ctBorder != null)
                                    tcBorders.setBottom(ctBorder);
                                else if (tcBorders.isSetBottom())
                                    tcBorders.unsetBottom();
                                break;
                            case 3:
                                ctBorder = buildBorder(lineBox.getRightPen());
                                if (ctBorder != null)
                                    tcBorders.setRight(ctBorder);
                                else if (tcBorders.isSetRight())
                                    tcBorders.unsetRight();
                                break;
                        }
                    }
                }
            }
        }

    }

    /**
     * Build a single edge for a cell.
     * @param pen The border style.
     * @return The border for cell.
     */
    static CTBorder buildBorder(JRBoxPen pen) {
        if (pen == null || pen.getLineWidth() == 0f)
            return null;
        else {
            CTBorder ctBorder = CTBorder.Factory.newInstance();
            ctBorder.setColor(colorToStringFormat(pen.getPen(pen.getBox()).getLineColor()));
            ctBorder.setSz(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.POINTS, pen.getLineWidth()));
            switch (pen.getLineStyleValue()) {
                case SOLID:
                    ctBorder.setVal(STBorder.SINGLE);
                    break;
                case DASHED:
                    ctBorder.setVal(STBorder.DASHED);
                    break;
                case DOTTED:
                    ctBorder.setVal(STBorder.DOTTED);
                    break;
                case DOUBLE:
                    ctBorder.setVal(STBorder.DOUBLE);
                    break;
                default:
                    ctBorder.setVal(STBorder.SINGLE);
                    break;
            }
            ctBorder.setColor(colorToStringFormat(pen.getLineColor()));

            return ctBorder;
        }
    }

    /**
     * Print visitor context. This class improves the base visitor context with extra information useful to build the output document,
     * like jaxb object factories, table infos, styles, etc...
     */
    static class J2WDocxPrintElementVisitorContext extends J2WAbstractPrintElementVisitorContext {
        /** Table infos */
        private Map tables;
        /** Remapped style name in document */
        private Map remapStylesNameByJrStyle;
        /** Built style's name */
        private final Set allStyles;
        /** The current header */
        private CTHdrFtr headerContent;
        /** The current footer */
        private CTHdrFtr footerContent;
        /** The current section */
        private CTSectPr currentSection;
        /** Cache built found style by name */
        private final Map builtFoundStyle;
        /** New header/footer policy */
        private XWPFHeaderFooterPolicy policy;

        /**
         * Construct the context.
         */
        public J2WDocxPrintElementVisitorContext() {
            this.allStyles = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
            this.builtFoundStyle = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
        }

        /**
         * Reset the document status (reset remapped style's name).
         */
        private void resetDocumentStatus() {
            this.remapStylesNameByJrStyle = new TreeMap<>();
            this.builtFoundStyle.clear();
        }

        /**
         * Reset the page status (tables end last docx element).
         */
        public void resetPageStatus() {
            this.tables = new TreeMap<>();
            this.setLastDocxElement(null);
        }
    }

    /**
     * A table informations.
     */
    static class TableDocInfo {
        /** Docx table */
        private final CTTbl table;

        /**
         * Construct a table info with a docx table,
         * @param table The docx table.
         */
        public TableDocInfo(CTTbl table) {
            this.table = table;
        }
    }

    /**
     * Comparator that sorts Point from bottom to top and from right to left.
     */
    private static class CellToRemovePointComparator implements Comparator {
        /** Constructor */
        public CellToRemovePointComparator() {
        }

        @Override
        public int compare(Point o1, Point o2) {
            if (o1.y > o2.y)
                return 1;
            else if (o1.y < o2.y)
                return -1;
            else if (o1.x > o2.x)
                return -1;
            else if (o1.x < o2.x)
                return 1;
            else
                return 0;
        }
    }

    /**
     * Poi-apache print element visitor. This class can "visit" every jasper report element and draw it in the
     * output document.
     */
    protected static class J2WDocxPrintElementVisitor implements PrintElementVisitor {
        /** Attribute styled text selector (all attributes) */
        protected JRStyledTextAttributeSelector allSelector;
        /** Jasper context */
        protected JasperReportsContext jasperReportsContext;

        /**
         * Construct a visitor context.
         * @param allSelector The attribute selector.
         * @param jasperReportsContext The jasper context.
         */
        public J2WDocxPrintElementVisitor(JRStyledTextAttributeSelector allSelector, JasperReportsContext jasperReportsContext) {
            this.allSelector = allSelector;
            this.jasperReportsContext = jasperReportsContext;
        }

        /**
         * Search or create the output paragraph for the current element to visit.
         * @param compPosition The element position.
         * @param container The element container.
         * @param arg The visitor context.
         * @param createParagraph {@code true} to create the missing paragraph.
         * @return The paragraph for the element (both free paragraphs and table cell paragraphs).
         */
        protected FoundDocParagraph findParagraph(ComponentPosition compPosition, JRBoxContainer container, J2WDocxPrintElementVisitorContext arg, boolean createParagraph) {
            CTP paragraph = null;
            XWPFDocument document = arg.getDocument();

            FoundDocParagraph parent = null;

            if (compPosition.getParent() != null)
                parent = this.findParagraph(compPosition.getParent(), container, arg, false);

            CTTc cell = null;
            // For element in table cell
            if (compPosition instanceof ComponentPositionInTable) {
                ComponentPositionInTable position = (ComponentPositionInTable) compPosition;
                TableDocInfo tableDocInfo = buildTables(position, arg);
                CTTbl xwpfTable = tableDocInfo.table;

                CTRow row = xwpfTable.getTrList().get(position.getRow());
                cell = row.getTcList().get(position.getCol());
                if (cell.getPList().isEmpty())
                    paragraph = cell.addNewP();
                else
                    paragraph = cell.getPArray(0);

                CTTcPr tcPr = cell.getTcPr();
                if (tcPr == null)
                    tcPr = cell.addNewTcPr();
                // Background
                CTShd ctShd = tcPr.getShd();
                if (ctShd == null)
                    ctShd = tcPr.addNewShd();
                ctShd.setFill(COLOR_AUTO);
                ctShd.setVal(STShd.CLEAR);

                CTVerticalJc ctVerticalJc = tcPr.getVAlign();
                if (ctVerticalJc == null)
                    ctVerticalJc = tcPr.addNewVAlign();
                ctVerticalJc.setVal(STVerticalJc.CENTER);
                if (container != null) {
                    setTableCellBorders(position, xwpfTable, cell, container);
                    JRLineBox lineBox = container.getLineBox();
                    if (lineBox != null) {
                        CTTcMar ctTcMar = tcPr.getTcMar();
                        if (ctTcMar == null)
                            ctTcMar = tcPr.addNewTcMar();
                        CTTblWidth b = CTTblWidth.Factory.newInstance();
                        CTTblWidth t = CTTblWidth.Factory.newInstance();
                        CTTblWidth l = CTTblWidth.Factory.newInstance();
                        CTTblWidth r = CTTblWidth.Factory.newInstance();

                        b.setType(STTblWidth.DXA);
                        t.setType(STTblWidth.DXA);
                        l.setType(STTblWidth.DXA);
                        r.setType(STTblWidth.DXA);

                        if (lineBox.getBottomPadding() != null)
                            b.setW(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, lineBox.getBottomPadding()));
                        else
                            b = null;
                        if (lineBox.getTopPadding() != null)
                            t.setW(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, lineBox.getTopPadding()));
                        else
                            t = null;
                        if (lineBox.getLeftPadding() != null)
                            l.setW(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, lineBox.getLeftPadding()));
                        else
                            l = null;
                        if (lineBox.getRightPadding() != null)
                            r.setW(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, lineBox.getRightPadding()));
                        else
                            r = null;

                        ctTcMar.setBottom(b);
                        ctTcMar.setTop(t);
                        ctTcMar.setLeft(l);
                        ctTcMar.setRight(r);
                    }
                }
            }
            if (paragraph == null && parent != null)
                paragraph = parent.getParagraph();
            if (cell == null && parent != null)
                cell = parent.getCell();

            if (paragraph == null && createParagraph) {
                // Add the new paragraph in the document
                if (compPosition.getDocumentPart() == DocxDocumentPart.HEADER)
                    paragraph = arg.headerContent.addNewP();
                else if (compPosition.getDocumentPart() == DocxDocumentPart.FOOTER)
                    paragraph = arg.footerContent.addNewP();
                else
                    paragraph = arg.getDocument().createParagraph().getCTP();
            }
            if (paragraph != null) {
                if (container != null && cell == null)
                    setParagraphBorders(paragraph, container);

                CTPPr pPr = paragraph.getPPr();
                if (pPr == null)
                    pPr = paragraph.addNewPPr();
                CTParaRPr rPr = pPr.getRPr();
                if (rPr == null)
                    pPr.addNewRPr();

                if (container != null && container.getStyle() != null) {
                    CTString pStyle = pPr.getPStyle();
                    if (pStyle == null)
                        pStyle = pPr.addNewPStyle();
                    pStyle.setVal(arg.remapStylesNameByJrStyle.get(container.getStyle().getName()));
                }
                if (cell == null) {
                    int leftPadding = 0;
                    int rightPadding = 0;
                    if (container != null && container.getLineBox() != null) {
                        JRLineBox lineBox = container.getLineBox();
                        if (lineBox.getLeftPadding() != null)
                            leftPadding = lineBox.getLeftPadding();
                        if (lineBox.getRightPadding() != null)
                            rightPadding = lineBox.getRightPadding();
                    }
                    setParagraphMargins(paragraph, compPosition.getX(), compPosition.getWidth() - rightPadding - leftPadding, arg);
                }
            }

            int pIndex = -1;
            if (paragraph != null && cell == null) {
                if (compPosition.getDocumentPart() == DocxDocumentPart.HEADER)
                    pIndex = arg.headerContent.sizeOfPArray() - 1;
                else if (compPosition.getDocumentPart() == DocxDocumentPart.FOOTER)
                    pIndex = arg.footerContent.sizeOfPArray() - 1;
                else
                    pIndex = arg.getDocument().getParagraphs().size() - 1;
            }
            return new FoundDocParagraph(cell, paragraph, pIndex);
        }

        public void visit(JRPrintText textElement, J2WDocxPrintElementVisitorContext arg) {
            ComponentPosition compPosition = arg.getLayout().getElementPosition(textElement);
            FoundDocParagraph foundDocParagraph = this.findParagraph(compPosition, textElement, arg, true);
            CTP paragraph = foundDocParagraph.getParagraph();

            boolean setInd = false;
            boolean setPPr = false;
            int pCount = 0;

            CTPPr pPr = paragraph.getPPr();
            if (pPr == null)
                pPr = paragraph.addNewPPr();
            else
                setPPr = true;

            CTInd ind = pPr.getInd();
            if (ind == null)
                ind = pPr.addNewInd();
            else
                setInd = true;

            boolean spacing = buildParagraphSpacing(textElement.getParagraph(), pPr.getSpacing(), pPr, true);

            if (textElement.getOwnHorizontalAlignmentValue() != null) {
                CTJc jc = pPr.getJc();
                if (jc == null)
                    jc = pPr.addNewJc();
                switch (textElement.getOwnHorizontalAlignmentValue()) {
                    case LEFT:
                        jc.setVal(STJc.LEFT);
                        break;
                    case CENTER:
                        jc.setVal(STJc.CENTER);
                        break;
                    case RIGHT:
                        jc.setVal(STJc.RIGHT);
                        break;
                    case JUSTIFIED:
                        jc.setVal(STJc.BOTH);
                        break;
                }
                setPPr = true;
            }

            if (textElement.getOwnVerticalAlignmentValue() != null) {
                CTTextAlignment alignment = pPr.getTextAlignment();
                if (alignment == null)
                    alignment = pPr.addNewTextAlignment();
                switch (textElement.getOwnVerticalAlignmentValue()) {
                    case TOP:
                        alignment.setVal(STTextAlignment.TOP);
                        break;
                    case MIDDLE:
                        alignment.setVal(STTextAlignment.CENTER);
                        break;
                    case BOTTOM:
                        alignment.setVal(STTextAlignment.BOTTOM);
                        break;
                    case JUSTIFIED:
                        alignment.setVal(STTextAlignment.AUTO);
                        break;
                }
                setPPr = true;
            }


            JRParagraph jrParagraph = textElement.getParagraph();
            if (jrParagraph.getOwnFirstLineIndent() != null) {
                ind.setFirstLine(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, jrParagraph.getOwnFirstLineIndent()));
                setInd = true;
            }
            if (jrParagraph.getOwnLeftIndent() != null) {
                ind.setLeft(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, jrParagraph.getOwnLeftIndent()));
                setInd = true;
            }
            if (jrParagraph.getOwnRightIndent() != null) {
                ind.setRight(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, jrParagraph.getOwnRightIndent()));
                setInd = true;
            }

            if (textElement.getOwnBackcolor() != null) {
                CTShd shd = pPr.getShd();
                if (shd == null) {
                    shd = pPr.addNewShd();
                    pPr.setShd(shd);
                }

                shd.setFill(colorToStringFormat(textElement.getOwnBackcolor()));
                shd.setVal(STShd.CLEAR);
                setPPr = true;
            }


            if (!setPPr && !setInd & !spacing)
                paragraph.unsetPPr();
            if (!setInd)
                pPr.unsetInd();

            if (!spacing)
                pPr.unsetSpacing();

            JRStyledText styledText = textElement.getFullStyledText(allSelector);

            String fullText = styledText.getText();


            AttributedCharacterIterator iterator = styledText.getAttributedString().getIterator();

            int runLimit = 0;
            boolean lastNewLine = false;
            XWPFDocument document = arg.getDocument();
            while(runLimit < styledText.length() && (runLimit = iterator.getRunLimit()) <= styledText.length()) {
                CTR docRun = paragraph.addNewR();
                this.setRunFontStyle(docRun, textElement, iterator.getAttributes(), arg);
                String value = fullText.substring(iterator.getIndex(), runLimit);

                StringTokenizer tkzer = new StringTokenizer(value, "\n", true);
                while(tkzer.hasMoreTokens()) {
                    String token = tkzer.nextToken();
                    boolean isNewLine = "\n".equals(token);
                    if (lastNewLine || isNewLine) {
                        if (tkzer.hasMoreTokens() || lastNewLine) {
                            CTP newParagraph;
                            pCount++;
                            if (foundDocParagraph.isTableCell())
                                newParagraph = foundDocParagraph.getCell().addNewP();
                            else {
                                if (compPosition.getDocumentPart() == DocxDocumentPart.HEADER)
                                    newParagraph = arg.headerContent.addNewP();
                                else if (compPosition.getDocumentPart() == DocxDocumentPart.FOOTER)
                                    newParagraph = arg.footerContent.addNewP();
                                else
                                    newParagraph = document.createParagraph().getCTP();
                            }
                            CTPPr newPPr = this.cloneParagraphProperties(pPr, newParagraph);
                            if (pPr.getSpacing() != null)
                                pPr.getSpacing().setAfter(BigInteger.ZERO);
                            if (newPPr.getSpacing() != null)
                                newPPr.getSpacing().setBefore(BigInteger.ZERO);

                            paragraph = newParagraph;
                            docRun = paragraph.addNewR();
                            this.setRunFontStyle(docRun, textElement, iterator.getAttributes(), arg);
                            lastNewLine = false;
                        }
                        else
                            lastNewLine = true;
                    }
                    if (!isNewLine) {
                        CTText text = docRun.addNewT();
                        text.setStringValue(token);
                        text.setSpace(Space.PRESERVE);

                    }
                }
                iterator.setIndex(runLimit);
            }

            if (!foundDocParagraph.isTableCell()) {
                float textHeight = textElement.getTextHeight();

                int realHeight = 0;
                int topMargin = 0;
                if (jrParagraph.getSpacingAfter() != null)
                    realHeight += jrParagraph.getSpacingAfter();
                if (jrParagraph.getSpacingBefore() != null) {
                    realHeight += jrParagraph.getSpacingBefore();
                    topMargin = jrParagraph.getSpacingBefore();
                }
                JRLineBox lineBox = textElement.getLineBox();
                if (lineBox != null) {
                    if (lineBox.getBottomPadding() != null)
                        realHeight += lineBox.getBottomPadding();
                    if (lineBox.getTopPadding() != null)
                        realHeight += lineBox.getTopPadding();
                }
                realHeight += textHeight; // EConvertMisure.HALF_POINTS.convertInto(EConvertMisure.POINTS, textHeight);


                CTStyle style = findStyle("Normal", arg, true);
                int defaultFontSize = 12;
                String defaultFontName = "Times New Roman";
                if (style != null && style.getPPr() != null && style.getPPr().getRPr() != null) {
                    CTParaRPr defaultRPr = style.getPPr().getRPr();
                    if (defaultRPr.getRFonts() != null && defaultRPr.getRFonts().getAscii() != null)
                        defaultFontName = defaultRPr.getRFonts().getAscii();
                    if (defaultRPr.getSz() != null && defaultRPr.getSz().getVal() != null)
                        defaultFontSize = (int) EConvertMisure.HALF_POINTS.convertInto(EConvertMisure.POINTS, defaultRPr.getSz().getVal().doubleValue());
                }

                Font newLineFont = new Font(defaultFontName, Font.PLAIN, defaultFontSize);
                LineMetrics lineMetrics = newLineFont.getLineMetrics("\n", new FontRenderContext(null, false, true));

                if (realHeight < compPosition.getHeight()) {
                    int gap = compPosition.getHeight() - realHeight;
                    int newLines;
                    if (arg.getConfiguration().getSpacingPolicy() == ESpacingPolicy.EDITABLE)
                        newLines = (int) (gap / lineMetrics.getHeight());
                    else
                        newLines = 0;
                    gap -= (newLines * defaultFontSize);
                    if (gap > defaultFontSize) {
                        CTSpacing spacingLast = paragraph.getPPr().getSpacing();
                        if (spacingLast == null)
                            spacingLast = paragraph.getPPr().addNewSpacing();
                        CTSpacing spacingFirst = foundDocParagraph.getParagraph().getPPr().getSpacing();
                        if (spacingFirst == null)
                            spacingFirst = foundDocParagraph.getParagraph().getPPr().addNewSpacing();

                        BigInteger bigGap = EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, gap);

                        VerticalAlignEnum alignmentValue = textElement.getVerticalAlignmentValue();
                        if (alignmentValue == null)
                            alignmentValue = VerticalAlignEnum.JUSTIFIED;

                        switch (alignmentValue) {
                            case TOP:
                                spacingLast.setAfter(spacingLast.getAfter() != null ? spacingLast.getAfter().add(bigGap) : bigGap);
                                break;
                            case MIDDLE:
                            case JUSTIFIED: {
                                bigGap = bigGap.divide(BigInteger.valueOf(2));
                                spacingLast.setAfter(spacingLast.getAfter() != null ? spacingLast.getAfter().add(bigGap) : bigGap);
                                spacingFirst.setBefore(spacingFirst.getBefore() != null ? spacingFirst.getBefore().add(bigGap) : bigGap);
                            }
                            break;
                            case BOTTOM:
                                spacingFirst.setBefore(spacingFirst.getBefore() != null ? spacingFirst.getBefore().add(bigGap) : bigGap);
                                break;

                        }
                    }
                    this.createBeforeNewLinesParagraphs(arg, newLines, defaultFontSize, defaultFontName, foundDocParagraph.getParagraphIndexInDoc() + pCount + 1, compPosition.getDocumentPart());
                }
                if (arg.getLastY(compPosition.getDocumentPart()) < compPosition.getY()) {
                    int gap = compPosition.getY() - arg.getLastY(compPosition.getDocumentPart());
                    int newLines;
                    if (arg.getConfiguration().getSpacingPolicy() == ESpacingPolicy.EDITABLE)
                        newLines = (int) (gap / lineMetrics.getHeight());
                    else
                        newLines = 0;
                    gap -= (newLines * defaultFontSize);

                    if (gap > 0) {
                        CTSpacing spacingFirst = foundDocParagraph.getParagraph().getPPr().getSpacing();
                        if (spacingFirst == null)
                            spacingFirst = foundDocParagraph.getParagraph().getPPr().addNewSpacing();
                        BigInteger bigGap = EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, gap + topMargin);
                        spacingFirst.setBefore(spacingFirst.getBefore() != null ? spacingFirst.getBefore().add(bigGap) : bigGap);
                    }
                    this.createBeforeNewLinesParagraphs(arg, newLines, defaultFontSize, defaultFontName, foundDocParagraph.getParagraphIndexInDoc(), compPosition.getDocumentPart());
                }

                arg.setLastY(compPosition.getDocumentPart(), compPosition.getY() + compPosition.getHeight());

                arg.setLastDocxElement(LastDocxElement.TEXT_PARAGRAPH);
            }
        }

        /**
         * Add many paragraphs those indicated in {@code newLines}.
         * @param arg The visitor context.
         * @param newLines How many lines to add.
         * @param defaultFontSize The font size.
         * @param defaultFontName The font name.
         * @param startIndex The index in the document objects list.
         * @param part The document part.
         */
        protected void createBeforeNewLinesParagraphs(J2WDocxPrintElementVisitorContext arg, int newLines, int defaultFontSize,
                                                      String defaultFontName, int startIndex, DocxDocumentPart part) {
            for (int i = 0; i < newLines; i++) {
                CTP newLineP;
                switch (part) {
                    case HEADER:
                        newLineP = arg.headerContent.insertNewP(startIndex);
                        break;
                    case FOOTER:
                        newLineP = arg.footerContent.insertNewP(startIndex);
                        break;
                    default:
                    case BODY: {
                        XWPFDocument document = arg.getDocument();
                        XWPFParagraph paragraphAt;
                        if (startIndex < document.getParagraphs().size()) {
                            paragraphAt = document.getParagraphs().get(startIndex);
                            newLineP = document.insertNewParagraph(paragraphAt.getCTP().newCursor()).getCTP();
                        }
                        else
                            newLineP = document.createParagraph().getCTP();
                        

                    }
                    break;
                }

                CTPPr pPr = newLineP.addNewPPr();
                pPr.addNewRPr();
                CTHpsMeasure hpsMeasure = pPr.getRPr().addNewSz();
                hpsMeasure.setVal(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.HALF_POINTS, defaultFontSize));
                pPr.getRPr().addNewRFonts();
                pPr.getRPr().getRFonts().setAscii(defaultFontName);

                CTSpacing spacing = pPr.addNewSpacing();
                spacing.setAfter(BigInteger.ZERO);
                spacing.setBefore(BigInteger.ZERO);
                spacing.setBeforeLines(BigInteger.ZERO);
                spacing.setLine(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.th20_POINTS, defaultFontSize));
                spacing.setLineRule(STLineSpacingRule.EXACT);

                setParagraphMargins(newLineP, arg.getLastParagraphX(), arg.getLastParagraphWidth(), arg);

                CTR newLineR = newLineP.addNewR();
                newLineR.addNewRPr();
                CTText text = newLineR.addNewT();
                text.setSpace(Space.PRESERVE);
                text.setStringValue("");
            }
        }

        /**
         * Clone paragraphs properties.
         * @param pPr The source paragraph properties.
         * @param paragraphDest The paragraph where to clone the properties.
         * @return Return a clone of the {@code pPr} properties.
         */
        protected CTPPr cloneParagraphProperties(CTPPr pPr, CTP paragraphDest) {
            CTSpacing spacing = pPr.getSpacing();

            CTPPr newPPr = paragraphDest.getPPr();
            if (newPPr == null)
                newPPr = paragraphDest.addNewPPr();
            if (pPr.getInd() != null)
                newPPr.setInd(pPr.getInd());
            if (pPr.getJc() != null)
                newPPr.setJc(pPr.getJc());
            if (pPr.getPBdr() != null)
                newPPr.setPBdr(pPr.getPBdr());
            if (pPr.getPStyle() != null)
                newPPr.setPStyle(pPr.getPStyle());
            if (pPr.getRPr() != null)
                newPPr.setRPr(pPr.getRPr());
            if (pPr.getShd() != null)
                newPPr.setShd(pPr.getShd());
            if (spacing != null) {
                CTSpacing newSpacing = newPPr.getSpacing();
                if (newSpacing == null)
                    newSpacing = newPPr.addNewSpacing();
                newSpacing.setAfter(spacing.getAfter());
                newSpacing.setAfterAutospacing(spacing.getAfterAutospacing());
                newSpacing.setAfterLines(spacing.getAfterLines());
                newSpacing.setBefore(spacing.getBefore());
                newSpacing.setBeforeAutospacing(spacing.getBeforeAutospacing());
                newSpacing.setBeforeLines(spacing.getBeforeLines());
                newSpacing.setLine(spacing.getLine());
                newSpacing.setLineRule(spacing.getLineRule());
            }
            newPPr.setTextAlignment(pPr.getTextAlignment());
            newPPr.setTextDirection(pPr.getTextDirection());

            return newPPr;
        }

        /**
         * Set the run (a text block in a paragraph) properties (font, size, colors, etc.)
         * @param docRun The document run.
         * @param textElement The jasper text element.
         * @param attributes The global jasper text element attributes.
         * @param arg The visitor context.
         */
        protected void setRunFontStyle(CTR docRun, JRPrintText textElement, Map attributes, J2WDocxPrintElementVisitorContext arg) {
            String styleName = textElement.getStyle() != null? textElement.getStyle().getName() : null;
            boolean setRPr = false;
            CTRPr ctrPr = docRun.getRPr();
            if (ctrPr == null)
                ctrPr = docRun.addNewRPr();

            if (textElement.isOwnBold() != null) {
                CTOnOff value = ctrPr.getB();
                if (value == null)
                    value = ctrPr.addNewB();
                value.setVal(textElement.isOwnBold()? STOnOff.TRUE : STOnOff.FALSE);
                setRPr = true;
            }
            if (textElement.getOwnForecolor() != null) {
                CTColor color = ctrPr.getColor();
                if (color == null)
                    color = ctrPr.addNewColor();
                color.setVal(colorToStringFormat(textElement.getOwnForecolor()));
                setRPr = true;
            }
            if (textElement.getOwnFontName() != null) {
                CTFonts rFonts = ctrPr.getRFonts();
                if (rFonts == null)
                    rFonts = ctrPr.addNewRFonts();
                rFonts.setAscii(textElement.getOwnFontName());
                rFonts.setHAnsi(textElement.getOwnFontName());
                setRPr = true;
            }
            if (textElement.getOwnFontsize() != null) {
                CTHpsMeasure sz = ctrPr.getSz();
                if (sz == null)
                    sz = ctrPr.addNewSz();
                sz.setVal(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.HALF_POINTS, textElement.getOwnFontsize()));
                setRPr = true;
            }
            if (textElement.isOwnItalic() != null) {
                CTOnOff i = ctrPr.getI();
                if (i == null)
                    i = ctrPr.addNewI();
                i.setVal(textElement.isOwnItalic()? STOnOff.TRUE : STOnOff.FALSE);
                setRPr = true;
            }
            if (textElement.isOwnStrikeThrough() != null) {
                CTOnOff strike = ctrPr.getStrike();
                if (strike == null)
                    strike = ctrPr.addNewStrike();
                strike.setVal(textElement.isOwnStrikeThrough()? STOnOff.TRUE : STOnOff.FALSE);
                setRPr = true;
            }
            if (textElement.isOwnUnderline() != null && textElement.isOwnUnderline()) {
                CTUnderline value = ctrPr.getU();
                if (value == null)
                    value = ctrPr.addNewU();
                if (textElement.getStyle() != null && textElement.getStyle().getLinePen() != null && textElement.getStyle().getLinePen().getLineStyleValue() != null) {
                    switch (textElement.getStyle().getLinePen().getLineStyleValue()) {
                        case SOLID:
                            value.setVal(STUnderline.SINGLE);
                            break;
                        case DASHED:
                            value.setVal(STUnderline.DASH);
                            break;
                        case DOTTED:
                            value.setVal(STUnderline.DOTTED);
                            break;
                        case DOUBLE:
                            value.setVal(STUnderline.DOUBLE);
                            break;
                        default:
                            value.setVal(STUnderline.SINGLE);
                            break;
                    }
                    if (textElement.getStyle().getLinePen().getOwnLineColor() != null)
                        value.setColor(colorToStringFormat(textElement.getStyle().getLinePen().getOwnLineColor()));
                }
                else {
                    value.setVal(STUnderline.SINGLE);
                }

                setRPr = true;
            }

            Map docRunAttributes = buildDocRunAttributes(docRun, styleName, arg);


            if (attributes != null) {
                String fontFamily = this.readAttribute(attributes, docRunAttributes, TextAttribute.FAMILY);
                if (fontFamily != null) {
                    CTFonts rFonts = ctrPr.getRFonts();
                    if (rFonts == null)
                        rFonts = ctrPr.addNewRFonts();
                    rFonts.setAscii(fontFamily);
                    rFonts.setHAnsi(fontFamily);
                    setRPr = true;
                }

                Number weight = this.readAttribute(attributes, docRunAttributes, TextAttribute.WEIGHT);
                if (weight != null) {
                    CTOnOff value = ctrPr.getB();
                    if (value == null)
                        value = ctrPr.addNewB();
                    value.setVal(weight.floatValue() >= TextAttribute.WEIGHT_REGULAR? STOnOff.TRUE : STOnOff.FALSE);
                    setRPr = true;
                }

                Paint color = this.readAttribute(attributes, docRunAttributes, TextAttribute.FOREGROUND);
                if (color instanceof Color) {
                    CTColor color2 = ctrPr.getColor();
                    if (color2 == null)
                        color2 = ctrPr.addNewColor();
                    color2.setVal(colorToStringFormat((Color) color));
                    setRPr = true;
                }

                Number size = this.readAttribute(attributes, docRunAttributes, TextAttribute.SIZE);
                if (size != null) {
                    CTHpsMeasure sz = ctrPr.getSz();
                    if (sz == null)
                        sz = ctrPr.addNewSz();
                    sz.setVal(EConvertMisure.POINTS.convertIntoBigInteger(EConvertMisure.HALF_POINTS, size.doubleValue()));
                    setRPr = true;
                }

                Number posture = this.readAttribute(attributes, docRunAttributes, TextAttribute.POSTURE);
                if (posture != null) {
                    CTOnOff i = ctrPr.getI();
                    if (i == null)
                        i = ctrPr.addNewI();
                    i.setVal(posture.floatValue() >= TextAttribute.POSTURE_OBLIQUE? STOnOff.TRUE : STOnOff.FALSE);
                    setRPr = true;
                }

                Boolean strikeThrough = this.readAttribute(attributes, docRunAttributes, TextAttribute.STRIKETHROUGH);
                if (strikeThrough != null) {
                    CTOnOff strike = ctrPr.getStrike();
                    if (strike == null)
                        strike = ctrPr.addNewStrike();
                    strike.setVal(strikeThrough? STOnOff.TRUE : STOnOff.FALSE);
                    setRPr = true;
                }

                Integer underline = this.readAttribute(attributes, docRunAttributes, TextAttribute.UNDERLINE);
                if (underline != null) {
                    CTUnderline value = ctrPr.getU();
                    if (value == null)
                        value = ctrPr.addNewU();
                    if (underline.equals(TextAttribute.UNDERLINE_ON))
                        value.setVal(STUnderline.SINGLE);
                    else if (underline.equals(TextAttribute.UNDERLINE_LOW_ONE_PIXEL))
                        value.setVal(STUnderline.THICK);
                    else if (underline.equals(TextAttribute.UNDERLINE_LOW_TWO_PIXEL))
                        value.setVal(STUnderline.DOUBLE);
                    else if (underline.equals(TextAttribute.UNDERLINE_LOW_DOTTED))
                        value.setVal(STUnderline.DOTTED_HEAVY);
                    else if (underline.equals(TextAttribute.UNDERLINE_LOW_GRAY))
                        value.setVal(STUnderline.DOTTED);
                    else if (underline.equals(TextAttribute.UNDERLINE_LOW_DASHED))
                        value.setVal(STUnderline.DASH);
                    else
                        value.setVal(STUnderline.SINGLE);

                    setRPr = true;
                }

                Paint background = this.readAttribute(attributes, docRunAttributes, TextAttribute.BACKGROUND);
                if (background instanceof Color) {
                    CTShd shd = ctrPr.getShd();
                    if (shd == null)
                        shd = ctrPr.addNewShd();

                    shd.setFill(colorToStringFormat((Color) background));
                    shd.setVal(STShd.CLEAR);
                    setRPr = true;
                }
            }

            Locale locale = JRStyledTextAttributeSelector.getTextLocale(textElement);
            if (locale != null) {
                CTLanguage language = ctrPr.getLang();
                if (language == null)
                    language = ctrPr.addNewLang();
                language.setVal(locale.toLanguageTag());
                ctrPr.setLang(language);
                setRPr = true;
            }

            if (!setRPr)
                docRun.unsetRPr();
        }

        /**
         * Check for {@code flag} if {@code true} or {@code false}.
         * @param flag The flag to check.
         * @return {@code true} if {@code flag} is one of this: {@code STOnOff.TRUE}, {@code STOnOff.ON} or {@code STOnOff.X_1};
         * {@code false} otherwise or {@code flag} is {@code null}.
         */
        private boolean checkForDocFlag(STOnOff flag) {
            return flag != null && (flag.enumValue() == STOnOff.TRUE || flag.enumValue() == STOnOff.ON || flag.enumValue() == STOnOff.X_1);
        }

        /**
         * Extract text attributes from document run.
         * @param docRun The document run.
         * @param styleName The style name of the run paragraph.
         * @return The text attributes of the run.
         */
        protected Map buildDocRunAttributes(CTR docRun, String styleName, J2WDocxPrintElementVisitorContext arg) {
            Map res = new HashMap<>();
            CTStyle ctStyle = findStyle(styleName, arg, true);
            CTPPr stylePPr = null;
            CTRPr styleRPr = null;

            CTRPr rPr = docRun.getRPr();
            if (ctStyle != null) {
                stylePPr = ctStyle.getPPr();
                styleRPr = ctStyle.getRPr();
            }

            // Font family
            if (rPr != null && rPr.getRFonts() != null)
                res.put(TextAttribute.FAMILY, rPr.getRFonts().getAscii());
            else if (styleRPr != null && styleRPr.getRFonts() != null && styleRPr.getRFonts().getAscii() != null)
                res.put(TextAttribute.FAMILY, styleRPr.getRFonts().getAscii());

            // Bold
            if (rPr != null && rPr.getB() != null)
                res.put(TextAttribute.WEIGHT, this.checkForDocFlag(rPr.getB().xgetVal())? TextAttribute.WEIGHT_BOLD : TextAttribute.WEIGHT_REGULAR);
            else if (styleRPr != null && styleRPr.getB() != null)
                res.put(TextAttribute.WEIGHT, this.checkForDocFlag(styleRPr.getB().xgetVal())? TextAttribute.WEIGHT_BOLD : TextAttribute.WEIGHT_REGULAR);

            // Fore color
            if (rPr != null && rPr.getColor() != null)
                res.put(TextAttribute.FOREGROUND, stringToColorParse(rPr.getColor().getVal()));
            else if (styleRPr != null && styleRPr.getColor() != null)
                res.put(TextAttribute.FOREGROUND, stringToColorParse(styleRPr.getColor().getVal()));

            // Font size
            if (rPr != null && rPr.getSz() != null)
                res.put(TextAttribute.SIZE, EConvertMisure.HALF_POINTS.convertInto(EConvertMisure.POINTS, rPr.getSz().getVal().doubleValue()));
            else if (styleRPr != null && styleRPr.getSz() != null)
                res.put(TextAttribute.SIZE, EConvertMisure.HALF_POINTS.convertInto(EConvertMisure.POINTS, styleRPr.getSz().getVal().doubleValue()));

            // Italic
            if (rPr != null && rPr.getI() != null)
                res.put(TextAttribute.POSTURE, this.checkForDocFlag(rPr.getI().xgetVal())? TextAttribute.POSTURE_OBLIQUE : TextAttribute.POSTURE_REGULAR);
            else if (styleRPr != null && styleRPr.getI() != null)
                res.put(TextAttribute.POSTURE, this.checkForDocFlag(styleRPr.getI().xgetVal())? TextAttribute.POSTURE_OBLIQUE : TextAttribute.POSTURE_REGULAR);

            // Strike
            if (rPr != null && rPr.getStrike() != null)
                res.put(TextAttribute.STRIKETHROUGH, this.checkForDocFlag(rPr.getStrike().xgetVal()));
            else if (styleRPr != null && styleRPr.getStrike() != null)
                res.put(TextAttribute.STRIKETHROUGH, this.checkForDocFlag(styleRPr.getStrike().xgetVal()));

            // Underline
            CTUnderline ctUnderline = null;
            if (rPr != null && rPr.getU() != null)
                ctUnderline = rPr.getU();
            else if (styleRPr != null && styleRPr.getU() != null)
                ctUnderline = styleRPr.getU();

            if (ctUnderline != null) {
                STUnderline val = ctUnderline.xgetVal();
                if (val.enumValue() == STUnderline.SINGLE)
                    res.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
                else if (val.enumValue() == STUnderline.DOUBLE)
                    res.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_LOW_TWO_PIXEL);
                else if (val.enumValue() == STUnderline.THICK)
                    res.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_LOW_ONE_PIXEL);
                else if (val.enumValue() == STUnderline.DOTTED_HEAVY)
                    res.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_LOW_DOTTED);
                else if (val.enumValue() == STUnderline.DOTTED)
                    res.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_LOW_GRAY);
                else if (val.enumValue() == STUnderline.DASH)
                    res.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_LOW_DASHED);
                else if (val.enumValue() != STUnderline.NONE)
                    res.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
            }


            // Background
            if (rPr != null && rPr.getShd() != null)
                res.put(TextAttribute.BACKGROUND, stringToColorParse(rPr.getShd().getColor()));
            else if (styleRPr != null && styleRPr.getShd() != null)
                res.put(TextAttribute.BACKGROUND, stringToColorParse(styleRPr.getShd().getColor()));
            else if (stylePPr != null && stylePPr.getShd() != null)
                res.put(TextAttribute.BACKGROUND, stringToColorParse(stylePPr.getShd().getColor()));

            return res;
        }

        /**
         * Read the text attribute difference between the two parameters map:
         * 
    *
  • if run has not the attribute, return the jasper attribute
  • *
  • if both run and jasper have not the attribute, return {@code null}
  • *
  • if both attributes are present but they are not equal, return the jasper value
  • *
  • otherwise return {@code null}
  • *
* @param jasperAttributes The jasper attributes. * @param runAttributes The run attributes. * @param attributeName The attribute name. * @param The type of attribute. * @return The different attribute value. */ private V readAttribute(Map jasperAttributes, Map runAttributes, Attribute attributeName) { @SuppressWarnings("unchecked") V jasperValue = (V) jasperAttributes.get(attributeName); @SuppressWarnings("unchecked") V runValue = (V) runAttributes.get(attributeName); if (runValue == null) return jasperValue; else if (jasperValue == null) return null; else if (!jasperValue.equals(runValue)) return jasperValue; else return null; } public void visit(JRPrintImage image, J2WDocxPrintElementVisitorContext arg) { Renderable renderable = image.getRenderable(); int imageType; if (renderable != null) { switch (renderable.getImageTypeValue()) { case UNKNOWN: throw new JRRuntimeException("Unknown image type: " + image.getOrigin()); case GIF: imageType = Document.PICTURE_TYPE_GIF; break; case JPEG: imageType = Document.PICTURE_TYPE_JPEG; break; case PNG: imageType = Document.PICTURE_TYPE_PNG; break; case TIFF: imageType = Document.PICTURE_TYPE_TIFF; break; default: throw new JRRuntimeException("Unknown image type: (" + renderable.getImageTypeValue() + ") " + image.getOrigin()); } try { XWPFDocument document = arg.getDocument(); ByteArrayInputStream inputImage = new ByteArrayInputStream(renderable.getImageData(jasperReportsContext)); ComponentPosition position = arg.getLayout().getElementPosition(image); CTP paragraph = this.findParagraph(position, image, arg, true).getParagraph(); CTR run = paragraph.addNewR(); XWPFParagraph xwpfParagraph = new XWPFParagraph(paragraph, document); // switch (position.getDocumentPart()) { // case HEADER: // xwpfParagraph = new XWPFParagraph(paragraph, arg.headerContent); // break; // case FOOTER: // break; // case BODY: // break; // } XWPFRun xrun = new XWPFRun(run, xwpfParagraph); xrun.addPicture(inputImage, imageType, "", Units.toEMU(position.getWidth()), Units.toEMU(position.getHeight())); } catch (InvalidFormatException e) { throw new JRRuntimeException("Unknown image type", e); } catch (JRException e) { throw new JRRuntimeException("Unreadable image", e); } catch (IOException e) { throw new JRRuntimeException("Write image error", e); } } } public void visit(JRPrintRectangle rectangle, J2WDocxPrintElementVisitorContext arg) { if (rectangle.getLinePen() != null && (rectangle.getLinePen().getLineWidth() > 0f || (rectangle.getModeValue() == ModeEnum.OPAQUE && rectangle.getFillValue() == FillEnum.SOLID && rectangle.getBackcolor() != null))) { ComponentPosition position = arg.getLayout().getElementPosition(rectangle); FoundDocParagraph foundDocParagraph = this.findParagraph(position, rectangle.getStyle(), arg, true); CTP paragraph = foundDocParagraph.getParagraph(); CTR run = paragraph.addNewR(); CTPicture ctPicture = run.addNewPict(); XmlObject docLine; try { String zIndex = "z-index:251661312"; String color = J2WDocxPoiHelper.colorToStringFormat(rectangle.getForecolor()); String fillcolor; String filltemplate; if (rectangle.getModeValue() == ModeEnum.OPAQUE && rectangle.getFillValue() == FillEnum.SOLID) { fillcolor = "filled=\"t\" fillcolor=\"#" + J2WDocxPoiHelper.colorToStringFormat(rectangle.getBackcolor()) + "\""; filltemplate = "\n\t"; } else { fillcolor = ""; filltemplate = ""; } String xmlAsString = MessageFormat.format( "" + // "\n\t" + filltemplate + "\n\t" + "" , position.getX(), position.getY(), position.getWidth(), position.getHeight(), zIndex, String.valueOf(rectangle.hashCode()), color, rectangle.getLinePen().getLineWidth(), fillcolor); docLine = XmlObject.Factory.parse(xmlAsString); } catch (XmlException e) { throw new JRRuntimeException("Rectangle error:", e); } ctPicture.set(docLine); } } public void visit(JRPrintLine line, J2WDocxPrintElementVisitorContext arg) { if (line.getLinePen() != null && line.getLinePen().getLineWidth() > 0f) { ComponentPosition position = arg.getLayout().getElementPosition(line); CTP paragraph = this.findParagraph(position, line.getStyle(), arg, true).getParagraph(); CTR run = paragraph.addNewR(); CTPicture ctPicture = run.addNewPict(); XmlObject docLine; try { String zIndex = "z-index:251661312;"; String color = J2WDocxPoiHelper.colorToStringFormat(line.getLinePen().getLineColor()); docLine = XmlObject.Factory.parse( MessageFormat.format( "" + "" , position.getX(), position.getY(), position.getX() + position.getWidth(), position.getY() + position.getHeight(), zIndex, String.valueOf(line.hashCode()), color, line.getLinePen().getLineWidth()) ); } catch (XmlException e) { throw new JRRuntimeException("Line error:", e); } ctPicture.set(docLine); } } public void visit(JRPrintEllipse ellipse, J2WDocxPrintElementVisitorContext arg) { if (ellipse.getLinePen() != null && ellipse.getLinePen().getLineWidth() > 0f) { ComponentPosition position = arg.getLayout().getElementPosition(ellipse); CTP paragraph = this.findParagraph(position, ellipse.getStyle(), arg, true).getParagraph(); CTR run = paragraph.addNewR(); CTPicture ctPicture = run.addNewPict(); XmlObject docLine; try { String zIndex = "z-index:251661312"; String color = J2WDocxPoiHelper.colorToStringFormat(ellipse.getLinePen().getLineColor()); String fillcolor; if (ellipse.getModeValue() == ModeEnum.OPAQUE && ellipse.getFillValue() == FillEnum.SOLID) fillcolor = "fillcolor=\"" + J2WDocxPoiHelper.colorToStringFormat(ellipse.getLinePen().getLineColor()) + "\""; else fillcolor = ""; docLine = XmlObject.Factory.parse( MessageFormat.format( "" + "id=\"{5}\" o:spid=\"_x0000_s1026\" strokecolor=\"{6}\" strokeweight=\"{7,number,0.00}\" {8}>" + "" , position.getX(), position.getY(), position.getWidth(), position.getHeight(), zIndex, String.valueOf(ellipse.hashCode()), color, ellipse.getLinePen().getLineWidth(), fillcolor) ); } catch (XmlException e) { throw new JRRuntimeException("Ellipse error:", e); } ctPicture.set(docLine); } } public void visit(JRPrintFrame frame, J2WDocxPrintElementVisitorContext arg) { ComponentPosition framePosition = arg.getLayout().getElementPosition(frame); // if (framePosition.getParent() == null || !(framePosition.getParent() instanceof ComponentPositionInTable)) // this.findParagraph(framePosition, frame, arg, true); Collection elements = arg.getLayout().listJRPrintElements(frame); JRStyle frameStyle = frame.getStyle(); for (JRPrintElement element : elements) { JRStyle elementStyle = element.getStyle(); if (elementStyle == null) element.setStyle(frameStyle); else if (frameStyle != null) { if (arg.getReport().getStylesMap().containsKey(elementStyle.getName())) { JRStyle newElementStyle = (JRStyle) elementStyle.clone(); String newStyleName = arg.remapStylesNameByJrStyle.get(newElementStyle.getName()) + "BasedOn" + arg.remapStylesNameByJrStyle.get(frameStyle.getName()); if (newElementStyle instanceof JRBaseStyle) { ((JRBaseStyle) newElementStyle).rename(newStyleName); } else throw new JRRuntimeException("Operation not supported"); if (!arg.allStyles.contains(newStyleName)) { buildStyles(arg, Collections.singletonList(newElementStyle)); XWPFStyles styles = arg.getDocument().getStyles(); XWPFStyle style = styles.getStyle(newStyleName); style.getCTStyle().addNewBasedOn().setVal(arg.remapStylesNameByJrStyle.get(frameStyle.getName())); } if (element instanceof JRTemplatePrintElement) ((JRTemplatePrintElement) element).getTemplate().setStyle(newElementStyle); else element.setStyle(newElementStyle); } } element.accept(this, arg); if (element instanceof JRTemplatePrintElement) ((JRTemplatePrintElement) element).getTemplate().setStyle(elementStyle); else element.setStyle(elementStyle); } } public void visit(JRGenericPrintElement printElement, J2WDocxPrintElementVisitorContext arg) { } /** * Found or built paragraph. */ protected static class FoundDocParagraph { /** The doc paragraph */ private final CTP paragraph; /** The table cell (can be {@code null}) */ private final CTTc cell; /** The index in the document list objects */ private final int paragraphIndexInDoc; /** * Construct the found paragraph. * @param cell The table cell (can be {@code null}) if the paragraph is outside a table. * @param paragraph The doc paragraph. * @param paragraphIndexInDoc The index in the jaxb document list objects. */ public FoundDocParagraph(CTTc cell, CTP paragraph, int paragraphIndexInDoc) { this.paragraph = paragraph; this.cell = cell; this.paragraphIndexInDoc = paragraphIndexInDoc; } /** * Return the paragraph. * @return The paragraph. */ public CTP getParagraph() { return paragraph; } /** * Return the table cell. * @return The table cell or {@code null}. */ public CTTc getCell() { return cell; } /** * Return if the paragraph is inside a table. * @return {@code true} if in table, {@code false} otherwise. */ public boolean isTableCell() { return cell != null; } /** * Return the index in the jaxb document list objects. * @return The index in the jaxb document list objects. */ public int getParagraphIndexInDoc() { return paragraphIndexInDoc; } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy