Java tutorial
/* * JasperReports - Free Java Reporting Library. * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved. * http://www.jaspersoft.com * * Unless you have purchased a commercial license agreement from Jaspersoft, * the following license terms apply: * * This program is part of JasperReports. * * JasperReports is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * JasperReports is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with JasperReports. If not, see <http://www.gnu.org/licenses/>. */ package net.sf.jasperreports.engine.export.ooxml; import java.awt.Color; import java.awt.Dimension; import java.awt.font.TextAttribute; import java.awt.geom.Dimension2D; import java.io.IOException; import java.io.OutputStream; import java.io.Writer; import java.text.AttributedCharacterIterator; import java.text.AttributedCharacterIterator.Attribute; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import net.sf.jasperreports.annotations.properties.Property; import net.sf.jasperreports.annotations.properties.PropertyScope; import net.sf.jasperreports.engine.DefaultJasperReportsContext; import net.sf.jasperreports.engine.JRAbstractExporter; import net.sf.jasperreports.engine.JRException; import net.sf.jasperreports.engine.JRGenericElementType; import net.sf.jasperreports.engine.JRGenericPrintElement; import net.sf.jasperreports.engine.JRLineBox; import net.sf.jasperreports.engine.JRPen; import net.sf.jasperreports.engine.JRPrintElement; import net.sf.jasperreports.engine.JRPrintElementIndex; import net.sf.jasperreports.engine.JRPrintEllipse; import net.sf.jasperreports.engine.JRPrintFrame; import net.sf.jasperreports.engine.JRPrintHyperlink; import net.sf.jasperreports.engine.JRPrintImage; import net.sf.jasperreports.engine.JRPrintLine; import net.sf.jasperreports.engine.JRPrintPage; import net.sf.jasperreports.engine.JRPrintRectangle; import net.sf.jasperreports.engine.JRPrintText; import net.sf.jasperreports.engine.JRPropertiesUtil; import net.sf.jasperreports.engine.JRRuntimeException; import net.sf.jasperreports.engine.JRStyle; import net.sf.jasperreports.engine.JasperReportsContext; import net.sf.jasperreports.engine.PrintPageFormat; import net.sf.jasperreports.engine.base.JRBaseLineBox; import net.sf.jasperreports.engine.export.CutsInfo; import net.sf.jasperreports.engine.export.ElementGridCell; import net.sf.jasperreports.engine.export.ExporterNature; import net.sf.jasperreports.engine.export.GenericElementHandlerEnviroment; import net.sf.jasperreports.engine.export.Grid; import net.sf.jasperreports.engine.export.GridRow; import net.sf.jasperreports.engine.export.HyperlinkUtil; import net.sf.jasperreports.engine.export.JRExportProgressMonitor; import net.sf.jasperreports.engine.export.JRExporterGridCell; import net.sf.jasperreports.engine.export.JRGridLayout; import net.sf.jasperreports.engine.export.JRHyperlinkProducer; import net.sf.jasperreports.engine.export.JRXmlExporter; import net.sf.jasperreports.engine.export.LengthUtil; import net.sf.jasperreports.engine.export.OccupiedGridCell; import net.sf.jasperreports.engine.export.zip.FileBufferedZipEntry; import net.sf.jasperreports.engine.type.HorizontalTextAlignEnum; import net.sf.jasperreports.engine.type.HyperlinkTypeEnum; import net.sf.jasperreports.engine.type.LineDirectionEnum; import net.sf.jasperreports.engine.type.ModeEnum; import net.sf.jasperreports.engine.type.ScaleImageEnum; import net.sf.jasperreports.engine.util.JRStringUtil; import net.sf.jasperreports.engine.util.JRStyledText; import net.sf.jasperreports.engine.util.JRTextAttribute; import net.sf.jasperreports.engine.util.JRTypeSniffer; import net.sf.jasperreports.export.DocxExporterConfiguration; import net.sf.jasperreports.export.DocxReportConfiguration; import net.sf.jasperreports.export.ExportInterruptedException; import net.sf.jasperreports.export.ExporterInputItem; import net.sf.jasperreports.export.OutputStreamExporterOutput; import net.sf.jasperreports.export.ReportExportConfiguration; import net.sf.jasperreports.properties.PropertyConstants; import net.sf.jasperreports.renderers.DataRenderable; import net.sf.jasperreports.renderers.DimensionRenderable; import net.sf.jasperreports.renderers.Renderable; import net.sf.jasperreports.renderers.RenderersCache; import net.sf.jasperreports.renderers.ResourceRenderer; /** * Exports a JasperReports document to DOCX format. It has binary output type and exports the document to a * grid-based layout, therefore having the known limitations of grid exporters. * <p/> * It can work in batch mode and supports all types of * exporter input and output, content filtering, and font mappings. * <p/> * Currently, there are the following special configurations that can be made to a DOCX * exporter instance (see {@link net.sf.jasperreports.export.DocxReportConfiguration}): * <ul> * <li>Forcing the use of nested tables to render the content of frame elements using either * the {@link net.sf.jasperreports.export.DocxReportConfiguration#isFramesAsNestedTables() isFramesAsNestedTables()} * exporter configuration flag or its corresponding exporter hint called * {@link net.sf.jasperreports.export.DocxReportConfiguration#PROPERTY_FRAMES_AS_NESTED_TABLES net.sf.jasperreports.export.docx.frames.as.nested.tables}.</li> * <li>Allowing table rows to adjust their height if more text is typed into their cells using * the Word editor. This is controlled using either the * {@link net.sf.jasperreports.export.DocxReportConfiguration#isFlexibleRowHeight() isFlexibleRowHeight()} * exporter configuration flag, or its corresponding exporter hint called * {@link net.sf.jasperreports.export.DocxReportConfiguration#PROPERTY_FLEXIBLE_ROW_HEIGHT net.sf.jasperreports.export.docx.flexible.row.height}.</li> * <li>Ignoring hyperlinks in generated documents if they are not intended for the DOCX output format. This can be * customized using either the * {@link net.sf.jasperreports.export.DocxReportConfiguration#isIgnoreHyperlink() isIgnoreHyperlink()} * exporter configuration flag, or its corresponding exporter hint called * {@link net.sf.jasperreports.export.DocxReportConfiguration#PROPERTY_IGNORE_HYPERLINK net.sf.jasperreports.export.docx.ignore.hyperlink}</li> * </ul> * * @see net.sf.jasperreports.export.DocxReportConfiguration * @author Sanda Zaharia (shertage@users.sourceforge.net) */ public class JRDocxExporter extends JRAbstractExporter<DocxReportConfiguration, DocxExporterConfiguration, OutputStreamExporterOutput, JRDocxExporterContext> { private static final Log log = LogFactory.getLog(JRDocxExporter.class); /** * The exporter key, as used in * {@link GenericElementHandlerEnviroment#getElementHandler(JRGenericElementType, String)}. */ public static final String DOCX_EXPORTER_KEY = JRPropertiesUtil.PROPERTY_PREFIX + "docx"; public static final String EXCEPTION_MESSAGE_KEY_COLUMN_COUNT_OUT_OF_RANGE = "export.docx.column.count.out.of.range"; public static final String DOCX_EXPORTER_PROPERTIES_PREFIX = JRPropertiesUtil.PROPERTY_PREFIX + "export.docx."; /** * This property is used to mark text elements as being hidden either for printing or on-screen display. * @see JRPropertiesUtil */ @Property(category = PropertyConstants.CATEGORY_EXPORT, defaultValue = PropertyConstants.BOOLEAN_FALSE, scopes = { PropertyScope.TEXT_ELEMENT }, sinceVersion = PropertyConstants.VERSION_3_7_6, valueType = Boolean.class) public static final String PROPERTY_HIDDEN_TEXT = DOCX_EXPORTER_PROPERTIES_PREFIX + "hidden.text"; /** * */ public static final String JR_PAGE_ANCHOR_PREFIX = "JR_PAGE_ANCHOR_"; /** * */ public static final String IMAGE_NAME_PREFIX = "img_"; protected static final int IMAGE_NAME_PREFIX_LEGTH = IMAGE_NAME_PREFIX.length(); public static final String IMAGE_LINK_PREFIX = "link_" + IMAGE_NAME_PREFIX; /** * */ protected DocxZip docxZip; protected DocxDocumentHelper docHelper; protected Writer docWriter; protected Map<String, String> rendererToImagePathMap; protected RenderersCache renderersCache; // protected Map imageMaps; protected int reportIndex; protected int pageIndex; protected int startPageIndex; protected int endPageIndex; protected int tableIndex; protected boolean startPage; protected String invalidCharReplacement; protected PrintPageFormat pageFormat; protected JRGridLayout pageGridLayout; protected LinkedList<Color> backcolorStack = new LinkedList<Color>(); protected Color backcolor; protected DocxRunHelper runHelper; protected ExporterNature nature; protected long bookmarkIndex; protected String pageAnchor; protected DocxRelsHelper relsHelper; protected PropsAppHelper appHelper; protected PropsCoreHelper coreHelper; protected DocxFontHelper docxFontHelper; protected DocxFontTableHelper docxFontTableHelper; protected DocxFontTableRelsHelper docxFontTableRelsHelper; boolean emptyPageState; protected class ExporterContext extends BaseExporterContext implements JRDocxExporterContext { DocxTableHelper tableHelper = null; public ExporterContext(DocxTableHelper tableHelper) { this.tableHelper = tableHelper; } @Override public DocxTableHelper getTableHelper() { return tableHelper; } } /** * @see #JRDocxExporter(JasperReportsContext) */ public JRDocxExporter() { this(DefaultJasperReportsContext.getInstance()); } /** * */ public JRDocxExporter(JasperReportsContext jasperReportsContext) { super(jasperReportsContext); exporterContext = new ExporterContext(null); } @Override protected Class<DocxExporterConfiguration> getConfigurationInterface() { return DocxExporterConfiguration.class; } @Override protected Class<DocxReportConfiguration> getItemConfigurationInterface() { return DocxReportConfiguration.class; } @Override @SuppressWarnings("deprecation") protected void ensureOutput() { if (exporterOutput == null) { exporterOutput = new net.sf.jasperreports.export.parameters.ParametersOutputStreamExporterOutput( getJasperReportsContext(), getParameters(), getCurrentJasperPrint()); } } @Override public void exportReport() throws JRException { /* */ ensureJasperReportsContext(); ensureInput(); initExport(); ensureOutput(); OutputStream outputStream = getExporterOutput().getOutputStream(); try { exportReportToStream(outputStream); } catch (IOException e) { throw new JRRuntimeException(e); } finally { getExporterOutput().close(); resetExportContext(); } } @Override protected void initExport() { super.initExport(); rendererToImagePathMap = new HashMap<String, String>();//FIXMEIMAGE why this is reset at export and not report; are there any others? // imageMaps = new HashMap(); // hyperlinksMap = new HashMap(); } @Override protected void initReport() { super.initReport(); if (jasperPrint.hasProperties() && jasperPrint.getPropertiesMap().containsProperty(JRXmlExporter.PROPERTY_REPLACE_INVALID_CHARS)) { // allows null values for the property invalidCharReplacement = jasperPrint.getProperty(JRXmlExporter.PROPERTY_REPLACE_INVALID_CHARS); } else { invalidCharReplacement = getPropertiesUtil().getProperty(JRXmlExporter.PROPERTY_REPLACE_INVALID_CHARS, jasperPrint); } DocxReportConfiguration configuration = getCurrentItemConfiguration(); nature = new JRDocxExporterNature(jasperReportsContext, filter, !configuration.isFramesAsNestedTables()); renderersCache = new RenderersCache(getJasperReportsContext()); } /** * */ protected void exportReportToStream(OutputStream os) throws JRException, IOException { docxZip = new DocxZip(); docWriter = docxZip.getDocumentEntry().getWriter(); docHelper = new DocxDocumentHelper(jasperReportsContext, docWriter); docHelper.exportHeader(); relsHelper = new DocxRelsHelper(jasperReportsContext, docxZip.getRelsEntry().getWriter()); relsHelper.exportHeader(); appHelper = new PropsAppHelper(jasperReportsContext, docxZip.getAppEntry().getWriter()); coreHelper = new PropsCoreHelper(jasperReportsContext, docxZip.getCoreEntry().getWriter()); appHelper.exportHeader(); DocxExporterConfiguration configuration = getCurrentConfiguration(); String application = configuration.getMetadataApplication(); if (application == null) { application = "JasperReports Library version " + Package.getPackage("net.sf.jasperreports.engine").getImplementationVersion(); } appHelper.exportProperty(PropsAppHelper.PROPERTY_APPLICATION, application); coreHelper.exportHeader(); String title = configuration.getMetadataTitle(); if (title != null) { coreHelper.exportProperty(PropsCoreHelper.PROPERTY_TITLE, title); } String subject = configuration.getMetadataSubject(); if (subject != null) { coreHelper.exportProperty(PropsCoreHelper.PROPERTY_SUBJECT, subject); } String author = configuration.getMetadataAuthor(); if (author != null) { coreHelper.exportProperty(PropsCoreHelper.PROPERTY_CREATOR, author); } String keywords = configuration.getMetadataKeywords(); if (keywords != null) { coreHelper.exportProperty(PropsCoreHelper.PROPERTY_KEYWORDS, keywords); } List<ExporterInputItem> items = exporterInput.getItems(); boolean isEmbedFonts = Boolean.TRUE.equals(configuration.isEmbedFonts()); docxFontHelper = new DocxFontHelper(jasperReportsContext, docxZip, isEmbedFonts); DocxStyleHelper styleHelper = new DocxStyleHelper(this, docxZip.getStylesEntry().getWriter(), docxFontHelper); styleHelper.export(exporterInput); styleHelper.close(); DocxSettingsHelper settingsHelper = new DocxSettingsHelper(jasperReportsContext, docxZip.getSettingsEntry().getWriter()); settingsHelper.export(jasperPrint, isEmbedFonts); settingsHelper.close(); docxFontTableHelper = new DocxFontTableHelper(jasperReportsContext, docxZip.getFontTableEntry().getWriter()); docxFontTableHelper.exportHeader(); docxFontTableRelsHelper = new DocxFontTableRelsHelper(jasperReportsContext, docxZip.getFontTableRelsEntry().getWriter()); docxFontTableRelsHelper.exportHeader(); runHelper = new DocxRunHelper(jasperReportsContext, docWriter, docxFontHelper); pageFormat = null; PrintPageFormat oldPageFormat = null; for (reportIndex = 0; reportIndex < items.size(); reportIndex++) { ExporterInputItem item = items.get(reportIndex); setCurrentExporterInputItem(item); bookmarkIndex = 0; emptyPageState = false; List<JRPrintPage> pages = jasperPrint.getPages(); if (pages != null && pages.size() > 0) { PageRange pageRange = getPageRange(); startPageIndex = (pageRange == null || pageRange.getStartPageIndex() == null) ? 0 : pageRange.getStartPageIndex(); endPageIndex = (pageRange == null || pageRange.getEndPageIndex() == null) ? (pages.size() - 1) : pageRange.getEndPageIndex(); JRPrintPage page = null; for (pageIndex = startPageIndex; pageIndex <= endPageIndex; pageIndex++) { if (Thread.interrupted()) { throw new ExportInterruptedException(); } page = pages.get(pageIndex); pageFormat = jasperPrint.getPageFormat(pageIndex); if (oldPageFormat != null && oldPageFormat != pageFormat) { docHelper.exportSection(oldPageFormat, pageGridLayout, false); } exportPage(page); oldPageFormat = pageFormat; } } } if (oldPageFormat != null) { docHelper.exportSection(oldPageFormat, pageGridLayout, true); } docHelper.exportFooter(); docHelper.close(); // if ((hyperlinksMap != null && hyperlinksMap.size() > 0)) // { // for(Iterator it = hyperlinksMap.keySet().iterator(); it.hasNext();) // { // String href = (String)it.next(); // String id = (String)hyperlinksMap.get(href); // // relsHelper.exportHyperlink(id, href); // } // } relsHelper.exportFooter(); relsHelper.close(); appHelper.exportFooter(); appHelper.close(); coreHelper.exportFooter(); coreHelper.close(); docxFontHelper.exportFonts(); docxFontTableHelper.exportFooter(); docxFontTableHelper.close(); docxFontTableRelsHelper.exportFooter(); docxFontTableRelsHelper.close(); docxZip.zipEntries(os); docxZip.dispose(); } /** * */ protected void exportPage(JRPrintPage page) throws JRException { startPage = true; pageAnchor = JR_PAGE_ANCHOR_PREFIX + reportIndex + "_" + (pageIndex + 1); ReportExportConfiguration configuration = getCurrentItemConfiguration(); pageGridLayout = new JRGridLayout(nature, page.getElements(), pageFormat.getPageWidth(), pageFormat.getPageHeight(), configuration.getOffsetX() == null ? 0 : configuration.getOffsetX(), configuration.getOffsetY() == null ? 0 : configuration.getOffsetY(), null //address ); exportGrid(pageGridLayout, null); JRExportProgressMonitor progressMonitor = configuration.getProgressMonitor(); if (progressMonitor != null) { progressMonitor.afterPageExport(); } } /** * */ protected void exportGrid(JRGridLayout gridLayout, JRPrintElementIndex frameIndex) throws JRException { CutsInfo xCuts = gridLayout.getXCuts(); Grid grid = gridLayout.getGrid(); DocxTableHelper tableHelper = null; int rowCount = grid.getRowCount(); if (rowCount > 0 && grid.getColumnCount() > 63) { throw new JRException(EXCEPTION_MESSAGE_KEY_COLUMN_COUNT_OUT_OF_RANGE, new Object[] { grid.getColumnCount() }); } // an empty page is encountered; // if it's the first one in a series of consecutive empty pages, emptyPageState == false, otherwise emptyPageState == true if (rowCount == 0 && (pageIndex < endPageIndex || !emptyPageState)) { tableHelper = new DocxTableHelper(jasperReportsContext, docWriter, xCuts, false, pageFormat, frameIndex); int maxReportIndex = exporterInput.getItems().size() - 1; // while the first and last page in the JasperPrint list need single breaks, all the others require double-breaking boolean twice = (pageIndex > startPageIndex && pageIndex < endPageIndex && !emptyPageState) || (reportIndex < maxReportIndex && pageIndex == endPageIndex); tableHelper.getParagraphHelper().exportEmptyPage(pageAnchor, bookmarkIndex, twice); bookmarkIndex++; emptyPageState = true; return; } tableHelper = new DocxTableHelper(jasperReportsContext, docWriter, xCuts, frameIndex == null && (reportIndex != 0 || pageIndex != startPageIndex), pageFormat, frameIndex); tableHelper.exportHeader(); boolean isFlexibleRowHeight = getCurrentItemConfiguration().isFlexibleRowHeight(); for (int row = 0; row < rowCount; row++) { int emptyCellColSpan = 0; //int emptyCellWidth = 0; boolean allowRowResize = false; int maxBottomPadding = 0; //for some strange reason, the bottom margin affects the row height; subtracting it here GridRow gridRow = grid.getRow(row); int rowSize = gridRow.size(); for (int col = 0; col < rowSize; col++) { JRExporterGridCell gridCell = gridRow.get(col); JRLineBox box = gridCell.getBox(); if (box != null && box.getBottomPadding() != null && maxBottomPadding < box.getBottomPadding()) { maxBottomPadding = box.getBottomPadding(); } allowRowResize = isFlexibleRowHeight && (allowRowResize || (gridCell.getElement() instanceof JRPrintText || (gridCell.getType() == JRExporterGridCell.TYPE_OCCUPIED_CELL && ((OccupiedGridCell) gridCell).getOccupier() .getElement() instanceof JRPrintText))); } int rowHeight = gridLayout.getRowHeight(row) - maxBottomPadding; if (row == 0 && frameIndex == null) { rowHeight -= Math.min(rowHeight, pageFormat.getTopMargin()); } tableHelper.exportRowHeader(rowHeight, allowRowResize); for (int col = 0; col < rowSize; col++) { JRExporterGridCell gridCell = gridRow.get(col); if (gridCell.getType() == JRExporterGridCell.TYPE_OCCUPIED_CELL) { if (emptyCellColSpan > 0) { //tableHelper.exportEmptyCell(gridCell, emptyCellColSpan); emptyCellColSpan = 0; //emptyCellWidth = 0; } OccupiedGridCell occupiedGridCell = (OccupiedGridCell) gridCell; ElementGridCell elementGridCell = (ElementGridCell) occupiedGridCell.getOccupier(); tableHelper.exportOccupiedCells(elementGridCell, startPage, bookmarkIndex, pageAnchor); if (startPage) { // increment the bookmarkIndex for the first cell in the sheet, due to page anchor creation bookmarkIndex++; } col += elementGridCell.getColSpan() - 1; } else if (gridCell.getType() == JRExporterGridCell.TYPE_ELEMENT_CELL) { if (emptyCellColSpan > 0) { //writeEmptyCell(tableHelper, gridCell, emptyCellColSpan, emptyCellWidth, rowHeight); emptyCellColSpan = 0; //emptyCellWidth = 0; } JRPrintElement element = gridCell.getElement(); if (element instanceof JRPrintLine) { exportLine(tableHelper, (JRPrintLine) element, gridCell); } else if (element instanceof JRPrintRectangle) { exportRectangle(tableHelper, (JRPrintRectangle) element, gridCell); } else if (element instanceof JRPrintEllipse) { exportEllipse(tableHelper, (JRPrintEllipse) element, gridCell); } else if (element instanceof JRPrintImage) { exportImage(tableHelper, (JRPrintImage) element, gridCell); } else if (element instanceof JRPrintText) { exportText(tableHelper, (JRPrintText) element, gridCell); } else if (element instanceof JRPrintFrame) { exportFrame(tableHelper, (JRPrintFrame) element, gridCell); } else if (element instanceof JRGenericPrintElement) { exportGenericElement(tableHelper, (JRGenericPrintElement) element, gridCell); } col += gridCell.getColSpan() - 1; } else { emptyCellColSpan++; //emptyCellWidth += gridCell.getWidth(); tableHelper.exportEmptyCell(gridCell, 1, startPage, bookmarkIndex, pageAnchor); if (startPage) { // increment the bookmarkIndex for the first cell in the sheet, due to page anchor creation bookmarkIndex++; } } startPage = false; } // if (emptyCellColSpan > 0) // { // //writeEmptyCell(tableHelper, null, emptyCellColSpan, emptyCellWidth, rowHeight); // } tableHelper.exportRowFooter(); } tableHelper.exportFooter(); // if a non-empty page was exported, the series of previous empty pages is ended emptyPageState = false; } /** * */ protected void exportLine(DocxTableHelper tableHelper, JRPrintLine line, JRExporterGridCell gridCell) { JRLineBox box = new JRBaseLineBox(null); JRPen pen = null; float ratio = line.getWidth() / line.getHeight(); if (ratio > 1) { if (line.getDirectionValue() == LineDirectionEnum.TOP_DOWN) { pen = box.getTopPen(); } else { pen = box.getBottomPen(); } } else { if (line.getDirectionValue() == LineDirectionEnum.TOP_DOWN) { pen = box.getLeftPen(); } else { pen = box.getRightPen(); } } pen.setLineColor(line.getLinePen().getLineColor()); pen.setLineStyle(line.getLinePen().getLineStyleValue()); pen.setLineWidth(line.getLinePen().getLineWidth()); gridCell.setBox(box);//CAUTION: only some exporters set the cell box tableHelper.getCellHelper().exportHeader(line, gridCell); tableHelper.getParagraphHelper().exportEmptyParagraph(startPage, bookmarkIndex, pageAnchor); if (startPage) { // increment the bookmarkIndex for the first cell in the sheet, due to page anchor creation bookmarkIndex++; } tableHelper.getCellHelper().exportFooter(); } /** * */ protected void exportRectangle(DocxTableHelper tableHelper, JRPrintRectangle rectangle, JRExporterGridCell gridCell) { JRLineBox box = new JRBaseLineBox(null); JRPen pen = box.getPen(); pen.setLineColor(rectangle.getLinePen().getLineColor()); pen.setLineStyle(rectangle.getLinePen().getLineStyleValue()); pen.setLineWidth(rectangle.getLinePen().getLineWidth()); gridCell.setBox(box);//CAUTION: only some exporters set the cell box tableHelper.getCellHelper().exportHeader(rectangle, gridCell); tableHelper.getParagraphHelper().exportEmptyParagraph(startPage, bookmarkIndex, pageAnchor); if (startPage) { // increment the bookmarkIndex for the first cell in the sheet, due to page anchor creation bookmarkIndex++; } tableHelper.getCellHelper().exportFooter(); } /** * */ protected void exportEllipse(DocxTableHelper tableHelper, JRPrintEllipse ellipse, JRExporterGridCell gridCell) { JRLineBox box = new JRBaseLineBox(null); JRPen pen = box.getPen(); pen.setLineColor(ellipse.getLinePen().getLineColor()); pen.setLineStyle(ellipse.getLinePen().getLineStyleValue()); pen.setLineWidth(ellipse.getLinePen().getLineWidth()); gridCell.setBox(box);//CAUTION: only some exporters set the cell box tableHelper.getCellHelper().exportHeader(ellipse, gridCell); tableHelper.getParagraphHelper().exportEmptyParagraph(startPage, bookmarkIndex, pageAnchor); if (startPage) { // increment the bookmarkIndex for the first cell in the sheet, due to page anchor creation bookmarkIndex++; } tableHelper.getCellHelper().exportFooter(); } /** * */ public void exportText(DocxTableHelper tableHelper, JRPrintText text, JRExporterGridCell gridCell) { tableHelper.getCellHelper().exportHeader(text, gridCell); JRStyledText styledText = getStyledText(text); int textLength = 0; if (styledText != null) { textLength = styledText.length(); } // if (styleBuffer.length() > 0) // { // writer.write(" style=\""); // writer.write(styleBuffer.toString()); // writer.write("\""); // } // // writer.write(">"); docHelper.write(" <w:p>\n"); tableHelper.getParagraphHelper().exportProps(text); if (startPage) { insertBookmark(pageAnchor, docHelper); } if (text.getAnchorName() != null) { insertBookmark(text.getAnchorName(), docHelper); } boolean startedHyperlink = startHyperlink(text, true); boolean isNewLineAsParagraph = false; if (HorizontalTextAlignEnum.JUSTIFIED.equals(text.getHorizontalTextAlign())) { if (text.hasProperties() && text.getPropertiesMap() .containsProperty(DocxReportConfiguration.PROPERTY_NEW_LINE_AS_PARAGRAPH)) { isNewLineAsParagraph = getPropertiesUtil().getBooleanProperty(text, DocxReportConfiguration.PROPERTY_NEW_LINE_AS_PARAGRAPH, false); } else { isNewLineAsParagraph = getCurrentItemConfiguration().isNewLineAsParagraph(); } } if (textLength > 0) { exportStyledText( getCurrentJasperPrint().getDefaultStyleProvider().getStyleResolver().getBaseStyle(text), styledText, getTextLocale(text), getPropertiesUtil().getBooleanProperty(text, PROPERTY_HIDDEN_TEXT, false), startedHyperlink, isNewLineAsParagraph); } if (startedHyperlink) { endHyperlink(true); } docHelper.write(" </w:p>\n"); tableHelper.getCellHelper().exportFooter(); } /** * */ protected void exportStyledText(JRStyle style, JRStyledText styledText, Locale locale, boolean hiddenText, boolean startedHyperlink, boolean isNewLineJustified) { Color elementBackcolor = null; Map<AttributedCharacterIterator.Attribute, Object> globalAttributes = styledText.getGlobalAttributes(); if (globalAttributes != null) { elementBackcolor = (Color) styledText.getGlobalAttributes().get(TextAttribute.BACKGROUND); } String text = styledText.getText(); int runLimit = 0; AttributedCharacterIterator iterator = styledText.getAttributedString().getIterator(); while (runLimit < styledText.length() && (runLimit = iterator.getRunLimit()) <= styledText.length()) { Map<Attribute, Object> attributes = iterator.getAttributes(); boolean localHyperlink = false; if (!startedHyperlink) { JRPrintHyperlink hyperlink = (JRPrintHyperlink) attributes.get(JRTextAttribute.HYPERLINK); if (hyperlink != null) { localHyperlink = startHyperlink(hyperlink, true); } } runHelper.export(style, iterator.getAttributes(), text.substring(iterator.getIndex(), runLimit), locale, hiddenText, invalidCharReplacement, elementBackcolor, isNewLineJustified); if (localHyperlink) { endHyperlink(true); } iterator.setIndex(runLimit); } } /** * */ public void exportImage(DocxTableHelper tableHelper, JRPrintImage image, JRExporterGridCell gridCell) throws JRException { int leftPadding = image.getLineBox().getLeftPadding(); int topPadding = image.getLineBox().getTopPadding();//FIXMEDOCX maybe consider border thickness int rightPadding = image.getLineBox().getRightPadding(); int bottomPadding = image.getLineBox().getBottomPadding(); int availableImageWidth = image.getWidth() - leftPadding - rightPadding; availableImageWidth = availableImageWidth < 0 ? 0 : availableImageWidth; int availableImageHeight = image.getHeight() - topPadding - bottomPadding; availableImageHeight = availableImageHeight < 0 ? 0 : availableImageHeight; tableHelper.getCellHelper().exportHeader(image, gridCell); docHelper.write("<w:p>\n");//FIXMEDOCX why is this here and not further down? tableHelper.getParagraphHelper().exportProps(image); Renderable renderer = image.getRenderer(); if (renderer != null && availableImageWidth > 0 && availableImageHeight > 0) { InternalImageProcessor imageProcessor = new InternalImageProcessor(image, image.getScaleImageValue() != ScaleImageEnum.FILL_FRAME, gridCell, availableImageWidth, availableImageHeight); InternalImageProcessorResult imageProcessorResult = null; try { imageProcessorResult = imageProcessor.process(renderer); } catch (Exception e) { Renderable onErrorRenderer = getRendererUtil().handleImageError(e, image.getOnErrorTypeValue()); if (onErrorRenderer != null) { imageProcessorResult = imageProcessor.process(onErrorRenderer); } } if (imageProcessorResult != null) { int width = availableImageWidth; int height = availableImageHeight; double cropTop = 0; double cropLeft = 0; double cropBottom = 0; double cropRight = 0; switch (image.getScaleImageValue()) { case FILL_FRAME: { width = availableImageWidth; height = availableImageHeight; break; } case CLIP: { double normalWidth = availableImageWidth; double normalHeight = availableImageHeight; Dimension2D dimension = imageProcessorResult.dimension; if (dimension != null) { normalWidth = dimension.getWidth(); normalHeight = dimension.getHeight(); } if (normalWidth > availableImageWidth) { switch (image.getHorizontalImageAlign()) { case RIGHT: { cropLeft = 65536 * (normalWidth - availableImageWidth) / normalWidth; cropRight = 0; break; } case CENTER: { cropLeft = 65536 * (-availableImageWidth + normalWidth) / normalWidth / 2; cropRight = cropLeft; break; } case LEFT: default: { cropLeft = 0; cropRight = 65536 * (normalWidth - availableImageWidth) / normalWidth; break; } } width = availableImageWidth; cropLeft = cropLeft / 0.75d; cropRight = cropRight / 0.75d; } else { width = (int) normalWidth; } if (normalHeight > availableImageHeight) { switch (image.getVerticalImageAlign()) { case TOP: { cropTop = 0; cropBottom = 65536 * (normalHeight - availableImageHeight) / normalHeight; break; } case MIDDLE: { cropTop = 65536 * (normalHeight - availableImageHeight) / normalHeight / 2; cropBottom = cropTop; break; } case BOTTOM: default: { cropTop = 65536 * (normalHeight - availableImageHeight) / normalHeight; cropBottom = 0; break; } } height = availableImageHeight; cropTop = cropTop / 0.75d; cropBottom = cropBottom / 0.75d; } else { height = (int) normalHeight; } break; } case RETAIN_SHAPE: default: { double normalWidth = availableImageWidth; double normalHeight = availableImageHeight; Dimension2D dimension = imageProcessorResult.dimension; if (dimension != null) { normalWidth = dimension.getWidth(); normalHeight = dimension.getHeight(); } double ratio = normalWidth / normalHeight; if (ratio > availableImageWidth / (double) availableImageHeight) { width = availableImageWidth; height = (int) (width / ratio); } else { height = availableImageHeight; width = (int) (ratio * height); } } } if (startPage) { insertBookmark(pageAnchor, docHelper); } if (image.getAnchorName() != null) { insertBookmark(image.getAnchorName(), docHelper); } // boolean startedHyperlink = startHyperlink(image,false); docHelper.write("<w:r>\n"); docHelper.write("<w:rPr/>\n"); docHelper.write("<w:drawing>\n"); docHelper.write("<wp:inline distT=\"0\" distB=\"0\" distL=\"0\" distR=\"0\">\n"); docHelper.write("<wp:extent cx=\"" + LengthUtil.emu(width) + "\" cy=\"" + LengthUtil.emu(height) + "\"/>\n"); docHelper.write("<wp:effectExtent l=\"0\" t=\"0\" r=\"0\" b=\"0\"/>\n"); int imageId = image.hashCode() > 0 ? image.hashCode() : -image.hashCode(); String rId = IMAGE_LINK_PREFIX + getElementIndex(gridCell); docHelper.write("<wp:docPr id=\"" + imageId + "\" name=\"Picture\">\n"); if (getHyperlinkURL(image) != null) { docHelper.write( "<a:hlinkClick xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" r:id=\"" + rId + "\"/>\n"); } docHelper.write("</wp:docPr>\n"); docHelper.write("<a:graphic>\n"); docHelper.write( "<a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">\n"); docHelper.write("<pic:pic>\n"); docHelper.write("<pic:nvPicPr><pic:cNvPr id=\"" + imageId + "\" name=\"Picture\"/><pic:cNvPicPr/></pic:nvPicPr>\n"); docHelper.write("<pic:blipFill>\n"); docHelper.write("<a:blip r:embed=\"" + imageProcessorResult.imagePath + "\"/>"); docHelper.write("<a:srcRect"); if (cropLeft > 0) { docHelper.write(" l=\"" + (int) cropLeft + "\""); } if (cropTop > 0) { docHelper.write(" t=\"" + (int) cropTop + "\""); } if (cropRight > 0) { docHelper.write(" r=\"" + (int) cropRight + "\""); } if (cropBottom > 0) { docHelper.write(" b=\"" + (int) cropBottom + "\""); } docHelper.write("/>"); docHelper.write("<a:stretch><a:fillRect/></a:stretch>\n"); docHelper.write("</pic:blipFill>\n"); docHelper.write("<pic:spPr><a:xfrm><a:off x=\"0\" y=\"0\"/><a:ext cx=\"" + LengthUtil.emu(width) + "\" cy=\"" + LengthUtil.emu(height) + "\"/>"); docHelper.write("</a:xfrm><a:prstGeom prst=\"rect\"></a:prstGeom></pic:spPr>\n"); docHelper.write("</pic:pic>\n"); docHelper.write("</a:graphicData>\n"); docHelper.write("</a:graphic>\n"); docHelper.write("</wp:inline>\n"); docHelper.write("</w:drawing>\n"); docHelper.write("</w:r>"); String url = getHyperlinkURL(image); if (url != null) { String targetMode = ""; switch (image.getHyperlinkTypeValue()) { case LOCAL_PAGE: case LOCAL_ANCHOR: { relsHelper.exportImageLink(rId, "#" + url.replaceAll("\\W", ""), targetMode); break; } case REMOTE_PAGE: case REMOTE_ANCHOR: case REFERENCE: { targetMode = " TargetMode=\"External\""; relsHelper.exportImageLink(rId, url, targetMode); break; } default: { break; } } } // if (startedHyperlink) // { // endHyperlink(false); // } } } docHelper.write("</w:p>"); tableHelper.getCellHelper().exportFooter(); } private class InternalImageProcessor { private final JRPrintElement imageElement; private final RenderersCache imageRenderersCache; private final boolean needDimension; private final JRExporterGridCell cell; private final int availableImageWidth; private final int availableImageHeight; protected InternalImageProcessor(JRPrintImage imageElement, boolean needDimension, JRExporterGridCell cell, int availableImageWidth, int availableImageHeight) { this.imageElement = imageElement; this.imageRenderersCache = imageElement.isUsingCache() ? renderersCache : new RenderersCache(getJasperReportsContext()); this.needDimension = needDimension; this.cell = cell; this.availableImageWidth = availableImageWidth; this.availableImageHeight = availableImageHeight; } private InternalImageProcessorResult process(Renderable renderer) throws JRException { if (renderer instanceof ResourceRenderer) { renderer = imageRenderersCache.getLoadedRenderer((ResourceRenderer) renderer); } // check dimension first, to avoid caching renderers that might not be used eventually, due to their dimension errors Dimension2D dimension = null; if (needDimension) { DimensionRenderable dimensionRenderer = imageRenderersCache.getDimensionRenderable(renderer); dimension = dimensionRenderer == null ? null : dimensionRenderer.getDimension(jasperReportsContext); } String imagePath = null; // if (image.isLazy()) //FIXMEDOCX learn how to link images // { // // } // else // { if (renderer instanceof DataRenderable //we do not cache imagePath for non-data renderers because they render width different width/height each time && rendererToImagePathMap.containsKey(renderer.getId())) { imagePath = rendererToImagePathMap.get(renderer.getId()); } else { JRPrintElementIndex imageIndex = getElementIndex(cell); DataRenderable imageRenderer = getRendererUtil().getImageDataRenderable(imageRenderersCache, renderer, new Dimension(availableImageWidth, availableImageHeight), ModeEnum.OPAQUE == imageElement.getModeValue() ? imageElement.getBackcolor() : null); byte[] imageData = imageRenderer.getData(jasperReportsContext); String fileExtension = JRTypeSniffer.getImageTypeValue(imageData).getFileExtension(); String imageName = IMAGE_NAME_PREFIX + imageIndex.toString() + (fileExtension == null ? "" : ("." + fileExtension)); docxZip.addEntry(//FIXMEDOCX optimize with a different implementation of entry new FileBufferedZipEntry("word/media/" + imageName, imageData)); relsHelper.exportImage(imageName); imagePath = imageName; //imagePath = "Pictures/" + imageName; if (imageRenderer == renderer) { //cache imagePath only for true ImageRenderable instances because the wrapping ones render with different width/height each time rendererToImagePathMap.put(renderer.getId(), imagePath); } } // } return new InternalImageProcessorResult(imagePath, dimension); } } private class InternalImageProcessorResult { protected final String imagePath; protected final Dimension2D dimension; protected InternalImageProcessorResult(String imagePath, Dimension2D dimension) { this.imagePath = imagePath; this.dimension = dimension; } } protected JRPrintElementIndex getElementIndex(JRExporterGridCell gridCell) { JRPrintElementIndex imageIndex = new JRPrintElementIndex(reportIndex, pageIndex, gridCell.getElementAddress()); return imageIndex; } /** * * protected void writeImageMap(String imageMapName, JRPrintHyperlink mainHyperlink, List imageMapAreas) { writer.write("<map name=\"" + imageMapName + "\">\n"); for (Iterator it = imageMapAreas.iterator(); it.hasNext();) { JRPrintImageAreaHyperlink areaHyperlink = (JRPrintImageAreaHyperlink) it.next(); JRPrintImageArea area = areaHyperlink.getArea(); writer.write(" <area shape=\"" + JRPrintImageArea.getHtmlShape(area.getShape()) + "\""); writeImageAreaCoordinates(area); writeImageAreaHyperlink(areaHyperlink.getHyperlink()); writer.write("/>\n"); } if (mainHyperlink.getHyperlinkTypeValue() != NONE) { writer.write(" <area shape=\"default\""); writeImageAreaHyperlink(mainHyperlink); writer.write("/>\n"); } writer.write("</map>\n"); } protected void writeImageAreaCoordinates(JRPrintImageArea area) { int[] coords = area.getCoordinates(); if (coords != null && coords.length > 0) { StringBuilder coordsEnum = new StringBuilder(coords.length * 4); coordsEnum.append(coords[0]); for (int i = 1; i < coords.length; i++) { coordsEnum.append(','); coordsEnum.append(coords[i]); } writer.write(" coords=\"" + coordsEnum + "\""); } } protected void writeImageAreaHyperlink(JRPrintHyperlink hyperlink) { String href = getHyperlinkURL(hyperlink); if (href == null) { writer.write(" nohref=\"nohref\""); } else { writer.write(" href=\"" + href + "\""); String target = getHyperlinkTarget(hyperlink); if (target != null) { writer.write(" target=\""); writer.write(target); writer.write("\""); } } if (hyperlink.getHyperlinkTooltip() != null) { writer.write(" title=\""); writer.write(JRStringUtil.xmlEncode(hyperlink.getHyperlinkTooltip())); writer.write("\""); } } /** * */ public static JRPrintElementIndex getPrintElementIndex(String imageName) { if (!imageName.startsWith(IMAGE_NAME_PREFIX)) { throw new JRRuntimeException(EXCEPTION_MESSAGE_KEY_INVALID_IMAGE_NAME, new Object[] { imageName }); } return JRPrintElementIndex.parsePrintElementIndex(imageName.substring(IMAGE_NAME_PREFIX_LEGTH)); } /** * In deep grids, this is called only for empty frames. */ protected void exportFrame(DocxTableHelper tableHelper, JRPrintFrame frame, JRExporterGridCell gridCell) throws JRException { tableHelper.getCellHelper().exportHeader(frame, gridCell); // tableHelper.getCellHelper().exportProps(gridCell); boolean appendBackcolor = frame.getModeValue() == ModeEnum.OPAQUE && (backcolor == null || frame.getBackcolor().getRGB() != backcolor.getRGB()); if (appendBackcolor) { setBackcolor(frame.getBackcolor()); } try { JRGridLayout layout = ((ElementGridCell) gridCell).getLayout(); JRPrintElementIndex frameIndex = new JRPrintElementIndex(reportIndex, pageIndex, gridCell.getElementAddress()); exportGrid(layout, frameIndex); } finally { if (appendBackcolor) { restoreBackcolor(); } } tableHelper.getParagraphHelper().exportEmptyParagraph(); tableHelper.getCellHelper().exportFooter(); } /** * */ protected void exportGenericElement(DocxTableHelper tableHelper, JRGenericPrintElement element, JRExporterGridCell gridCell) { GenericElementDocxHandler handler = (GenericElementDocxHandler) GenericElementHandlerEnviroment .getInstance(getJasperReportsContext()) .getElementHandler(element.getGenericType(), DOCX_EXPORTER_KEY); if (handler != null) { JRDocxExporterContext exporterContext = new ExporterContext(tableHelper); handler.exportElement(exporterContext, element, gridCell); } else { if (log.isDebugEnabled()) { log.debug("No DOCX generic element handler for " + element.getGenericType()); } } } /** * */ protected void setBackcolor(Color color) { backcolorStack.addLast(backcolor); backcolor = color; } protected void restoreBackcolor() { backcolor = backcolorStack.removeLast(); } protected boolean startHyperlink(JRPrintHyperlink link, boolean isText) { String href = getHyperlinkURL(link); if (href != null) { // String id = (String)hyperlinksMap.get(href); // if (id == null) // { // id = "link" + hyperlinksMap.size(); // hyperlinksMap.put(href, id); // } // // docHelper.write("<w:hyperlink r:id=\"" + id + "\""); // // String target = getHyperlinkTarget(link);//FIXMETARGET // if (target != null) // { // docHelper.write(" tgtFrame=\"" + target + "\""); // } // // docHelper.write(">\n"); docHelper.write("<w:r><w:fldChar w:fldCharType=\"begin\"/></w:r>\n"); String localType = (HyperlinkTypeEnum.LOCAL_ANCHOR == link.getHyperlinkTypeValue() || HyperlinkTypeEnum.LOCAL_PAGE == link.getHyperlinkTypeValue()) ? "\\l " : ""; docHelper.write("<w:r><w:instrText xml:space=\"preserve\"> HYPERLINK " + localType + "\"" + JRStringUtil.xmlEncode(href, invalidCharReplacement) + "\""); String target = getHyperlinkTarget(link);//FIXMETARGET if (target != null) { docHelper.write(" \\t \"" + target + "\""); } String tooltip = link.getHyperlinkTooltip(); if (tooltip != null) { docHelper.write(" \\o \"" + JRStringUtil.xmlEncode(tooltip, invalidCharReplacement) + "\""); } docHelper.write(" </w:instrText></w:r>\n"); docHelper.write("<w:r><w:fldChar w:fldCharType=\"separate\"/></w:r>\n"); } return href != null; } protected String getHyperlinkTarget(JRPrintHyperlink link) { String target = null; switch (link.getHyperlinkTargetValue()) { case SELF: { target = "_self"; break; } case BLANK: default: { target = "_blank"; break; } } return target; } protected String getHyperlinkURL(JRPrintHyperlink link) { String href = null; Boolean ignoreHyperlink = HyperlinkUtil .getIgnoreHyperlink(DocxReportConfiguration.PROPERTY_IGNORE_HYPERLINK, link); if (ignoreHyperlink == null) { ignoreHyperlink = getCurrentItemConfiguration().isIgnoreHyperlink(); } if (!ignoreHyperlink) { JRHyperlinkProducer customHandler = getHyperlinkProducer(link); if (customHandler == null) { switch (link.getHyperlinkTypeValue()) { case REFERENCE: { if (link.getHyperlinkReference() != null) { href = link.getHyperlinkReference(); } break; } case LOCAL_ANCHOR: { if (link.getHyperlinkAnchor() != null) { href = link.getHyperlinkAnchor().replaceAll("\\W", ""); } break; } case LOCAL_PAGE: { if (link.getHyperlinkPage() != null) { href = JR_PAGE_ANCHOR_PREFIX + reportIndex + "_" + link.getHyperlinkPage().toString(); } break; } case REMOTE_ANCHOR: { if (link.getHyperlinkReference() != null && link.getHyperlinkAnchor() != null) { href = link.getHyperlinkReference() + "#" + link.getHyperlinkAnchor(); } break; } case REMOTE_PAGE: { if (link.getHyperlinkReference() != null && link.getHyperlinkPage() != null) { href = link.getHyperlinkReference() + "#" + JR_PAGE_ANCHOR_PREFIX + reportIndex + "_" + link.getHyperlinkPage().toString(); } break; } case NONE: default: { break; } } } else { href = customHandler.getHyperlink(link); } } return href; } protected void endHyperlink(boolean isText) { // docHelper.write("</w:hyperlink>\n"); docHelper.write("<w:r><w:fldChar w:fldCharType=\"end\"/></w:r>\n"); } protected void insertBookmark(String bookmark, BaseHelper helper) { helper.write("<w:bookmarkStart w:id=\"" + bookmarkIndex); helper.write("\" w:name=\"" + (bookmark == null ? null : bookmark.replaceAll("\\W", ""))); helper.write("\"/><w:bookmarkEnd w:id=\"" + bookmarkIndex++); helper.write("\"/>"); } @Override protected void ensureInput() { super.ensureInput(); } @Override protected JRStyledText getStyledText(JRPrintText textElement, boolean setBackcolor) { return styledTextUtil.getProcessedStyledText(textElement, setBackcolor ? allSelector : noBackcolorSelector, getExporterKey()); } @Override public String getExporterKey() { return DOCX_EXPORTER_KEY; } @Override public String getExporterPropertiesPrefix() { return DOCX_EXPORTER_PROPERTIES_PREFIX; } }