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/>. */ /* * Contributors: * Adrian Jackson - iapetus@users.sourceforge.net * David Taylor - exodussystems@users.sourceforge.net * Lars Kristensen - llk@users.sourceforge.net * Ling Li - lonecatz@users.sourceforge.net * Martin Clough - mtclough@users.sourceforge.net */ package net.sf.jasperreports.engine.export; import java.awt.Color; import java.awt.Graphics2D; import java.awt.color.ICC_Profile; import java.awt.font.TextAttribute; import java.awt.geom.AffineTransform; import java.awt.geom.Dimension2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.Character.UnicodeBlock; import java.text.AttributedCharacterIterator; import java.text.AttributedCharacterIterator.Attribute; import java.text.AttributedString; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.lowagie.text.BadElementException; import com.lowagie.text.Chunk; import com.lowagie.text.Document; import com.lowagie.text.DocumentException; import com.lowagie.text.Element; import com.lowagie.text.Font; import com.lowagie.text.FontFactory; import com.lowagie.text.Image; import com.lowagie.text.Phrase; import com.lowagie.text.Rectangle; import com.lowagie.text.SplitCharacter; import com.lowagie.text.pdf.BaseFont; import com.lowagie.text.pdf.ColumnText; import com.lowagie.text.pdf.FontMapper; import com.lowagie.text.pdf.PdfAction; import com.lowagie.text.pdf.PdfArray; import com.lowagie.text.pdf.PdfBoolean; import com.lowagie.text.pdf.PdfContentByte; import com.lowagie.text.pdf.PdfDestination; import com.lowagie.text.pdf.PdfDictionary; import com.lowagie.text.pdf.PdfICCBased; import com.lowagie.text.pdf.PdfName; import com.lowagie.text.pdf.PdfOutline; import com.lowagie.text.pdf.PdfString; import com.lowagie.text.pdf.PdfTemplate; import com.lowagie.text.pdf.PdfWriter; 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.JRAnchor; import net.sf.jasperreports.engine.JRException; import net.sf.jasperreports.engine.JRFont; 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.JRPrintAnchor; import net.sf.jasperreports.engine.JRPrintElement; 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.JRPropertiesUtil.PropertySuffix; import net.sf.jasperreports.engine.JRRuntimeException; import net.sf.jasperreports.engine.JasperReportsContext; import net.sf.jasperreports.engine.PrintPageFormat; import net.sf.jasperreports.engine.base.JRBaseFont; import net.sf.jasperreports.engine.base.JRBasePrintText; import net.sf.jasperreports.engine.fonts.AwtFontAttribute; import net.sf.jasperreports.engine.fonts.FontFace; import net.sf.jasperreports.engine.fonts.FontFamily; import net.sf.jasperreports.engine.fonts.FontInfo; import net.sf.jasperreports.engine.type.HyperlinkTypeEnum; import net.sf.jasperreports.engine.type.LineDirectionEnum; import net.sf.jasperreports.engine.type.LineStyleEnum; import net.sf.jasperreports.engine.type.ModeEnum; import net.sf.jasperreports.engine.type.OrientationEnum; import net.sf.jasperreports.engine.util.BreakIteratorSplitCharacter; import net.sf.jasperreports.engine.util.ImageUtil; import net.sf.jasperreports.engine.util.JRImageLoader; import net.sf.jasperreports.engine.util.JRLoader; import net.sf.jasperreports.engine.util.JRPdfaIccProfileNotFoundException; import net.sf.jasperreports.engine.util.JRStyledText; import net.sf.jasperreports.engine.util.JRTextAttribute; import net.sf.jasperreports.engine.util.NullOutputStream; import net.sf.jasperreports.export.ExportInterruptedException; import net.sf.jasperreports.export.ExporterInputItem; import net.sf.jasperreports.export.OutputStreamExporterOutput; import net.sf.jasperreports.export.PdfExporterConfiguration; import net.sf.jasperreports.export.PdfReportConfiguration; import net.sf.jasperreports.export.type.PdfPermissionsEnum; import net.sf.jasperreports.export.type.PdfPrintScalingEnum; import net.sf.jasperreports.export.type.PdfVersionEnum; import net.sf.jasperreports.export.type.PdfaConformanceEnum; import net.sf.jasperreports.properties.PropertyConstants; import net.sf.jasperreports.renderers.DataRenderable; import net.sf.jasperreports.renderers.DimensionRenderable; import net.sf.jasperreports.renderers.Graphics2DRenderable; import net.sf.jasperreports.renderers.Renderable; import net.sf.jasperreports.renderers.RenderersCache; import net.sf.jasperreports.renderers.ResourceRenderer; import net.sf.jasperreports.renderers.WrappingImageDataToGraphics2DRenderer; import net.sf.jasperreports.renderers.WrappingSvgDataToGraphics2DRenderer; import net.sf.jasperreports.renderers.util.RendererUtil; import net.sf.jasperreports.repo.RepositoryUtil; /** * Exports a JasperReports document to PDF format. It has binary output type and exports the document to * a free-form layout. * <p/> * As its name indicates, PDF is a very precise and complex document format that ensures * documents will look and print the same on all platforms. * This is why the PDF exporter implemented by the * {@link net.sf.jasperreports.engine.export.JRPdfExporter} class in JasperReports is * one of the best exporters. The output it produces is almost of the same quality as that * produced by the {@link net.sf.jasperreports.engine.export.JRGraphics2DExporter}, * which is always the reference. * <p/> * The {@link net.sf.jasperreports.engine.export.JRPdfExporter} implementation uses iText, * which is a specialized PDF-generating library. PDF is a binary document format that allows * absolute positioning of the elements inside a page, so the existing PDF exporter does not * have the limitations of a grid exporter. * <p/> * It also works very well in batch mode because it allows concatenation of multiple * documents within the same PDF file, even if the files have different page sizes. * <h3>Font Mappings</h3> * Exporting to PDF requires mapping the fonts using three attributes: * <code>pdfFontName</code>, <code>pdfEncoding</code> and <code>isPdfEmbedded</code>. * Even though these three attributes are still supported in JRXML and * the API, we recommend making the PDF font mappings at export time using font * extensions. * <p/> * When exporting documents to PDF, for each combination of the three <code>fontName</code>, * <code>isBold</code>, and <code>isItalic</code> font attributes, there must be an equivalent * combination of the PDF-related font attributes <code>pdfFontName</code>, <code>pdfEncoding</code> * and <code>isPdfEmbedded</code>. * <p/> * <i>Equivalent combination</i> means one that causes the text elements to be rendered exactly * the same (or at least as closely as possible) in PDF and the built-in Graphics2D * exporter, which is the reference. * <p/> * In some cases, there is no font file available to use with the pdfFontName attribute in * order to render bold and italic text exactly like the Graphics2D exporter renders it in * AWT. Those fonts might only have a normal style variant and no variants for bold and * italic. In such cases, the PDF exporter (the iText library, to be more precise) is able to * simulate those styles by applying transformations to the normal font glyphs. The * {@link net.sf.jasperreports.engine.export.JRPdfExporter} internally acquires the needed PDF * font based on the font extension mechanism (see the <code>getFont(Map, Locale, boolean)</code> * method. * <h3>Batch Mode Bookmarks</h3> * When several JasperPrint documents must be concatenated in the same PDF file by * batch export, one can introduce PDF bookmarks in the resulting PDF document to mark * the beginning of each individual document that was part of the initial document list. * <p/> * These bookmarks have the same name as the original JasperPrint document as * specified by the <code>jasperPrint.getName()</code> property. However, users can turn on and off * the creation of those bookmarks by turning on or off the * {@link net.sf.jasperreports.export.PdfExporterConfiguration#isCreatingBatchModeBookmarks() isCreatingBatchModeBookmarks()} * exporter configuration setting. The exporter does not create such bookmarks by default. * <h3>Encrypted PDF</h3> * In some cases, users might want to encrypt the PDF documents generated by * JasperReports so that only authorized viewers can have access to those documents. * There are five exporter configuration settings for this (see {@link net.sf.jasperreports.export.PdfExporterConfiguration}): * <ul> * <li>{@link net.sf.jasperreports.export.PdfExporterConfiguration#isEncrypted() isEncrypted()}</li> * <li>{@link net.sf.jasperreports.export.PdfExporterConfiguration#is128BitKey() is128BitKey()}</li> * <li>{@link net.sf.jasperreports.export.PdfExporterConfiguration#getUserPassword() getUserPassword()}</li> * <li>{@link net.sf.jasperreports.export.PdfExporterConfiguration#getOwnerPassword() getOwnerPassword()}</li> * <li>{@link net.sf.jasperreports.export.PdfExporterConfiguration#getPermissions() getPermissions()}</li> * </ul> * <h3>PDF Version and Compression</h3> * Some applications require marking the generated files with a particular PDF * specifications version. Related export configuration settings are the following * (see {@link net.sf.jasperreports.export.PdfExporterConfiguration}): * <ul> * <li>{@link net.sf.jasperreports.export.PdfExporterConfiguration#getPdfVersion() getPdfVersion()}</li> * <li>{@link net.sf.jasperreports.export.PdfExporterConfiguration#isCompressed() isCompressed()}</li> * </ul> * Since version 1.5, the PDF format supports compression. By default, the PDF exporter in * JasperReports does not create compressed PDF documents, but this feature can be turned * on using the {@link net.sf.jasperreports.export.PdfExporterConfiguration#isCompressed() isCompressed()} * exporter configuration setting. Note that because compressed PDFs * are available only since PDF version 1.5, the PDF version of the resulting document is * set to 1.5 automatically if compression is turned on. * <h3>Word Wrap and Line Break Policy</h3> * By default, the PDF exporter does not guarantee that text with the same style properties * will be rendered exactly as it is using AWT. The word wrap and line break policy is * slightly different, and in some cases it might cause portions of text to disappear at the * end of longer text paragraphs. * <p/> * To make sure this does not happen, one can configure the PDF exporter to use the AWT * word wrap and line break policy by setting the * {@link net.sf.jasperreports.export.PdfReportConfiguration#isForceLineBreakPolicy() isForceLineBreakPolicy()} * exporter configuration setting to true. Note that this feature is not turned on by default, because it affects the * exporter performance. This default behavior that applies in the absence of the mentioned * export parameter can be controlled using the * {@link net.sf.jasperreports.export.PdfReportConfiguration#PROPERTY_FORCE_LINEBREAK_POLICY net.sf.jasperreports.export.pdf.force.linebreak.policy} configuration * property * <h3>JavaScript Actions</h3> * The PDF specifications provide a means for the automation of various processes, such as * the automatic printing of the document when it is opened. PDF viewer applications are * able to execute Acrobat JavaScript code that is embedded in the PDF and associated with * different events. * <p/> * JasperReports only allows inserting Acrobat JavaScript code. This code gets executed * when the PDF document is opened in the viewer. This can be achieved using the * {@link net.sf.jasperreports.export.PdfExporterConfiguration#getPdfJavaScript() getPdfJavaScript()} * configuration setting, which retrieve the Acrobat JavaScript source code. * Note that Acrobat JavaScript is a programming language based on JavaScript. More * details about this can be found in the iText documentation. * <h3>Metadata Information</h3> * PDF documents can store metadata information such as the author of the document, its * title, and keywords. JasperReports exposes this feature of PDF through special exporter * configuration settings available in the {@link net.sf.jasperreports.export.PdfExporterConfiguration} * class. They are all listed following: * <ul> * <li>{@link net.sf.jasperreports.export.PdfExporterConfiguration#getMetadataAuthor() getMetadataAuthor()}</li> * <li>{@link net.sf.jasperreports.export.PdfExporterConfiguration#getMetadataCreator() getMetadataCreator()}</li> * <li>{@link net.sf.jasperreports.export.PdfExporterConfiguration#getMetadataKeywords() getMetadataKeywords()}</li> * <li>{@link net.sf.jasperreports.export.PdfExporterConfiguration#getMetadataSubject() getMetadataSubject()}</li> * <li>{@link net.sf.jasperreports.export.PdfExporterConfiguration#getMetadataTitle() getMetadataTitle()}</li> * </ul> * <h3>Rendering SVG Using Shapes</h3> * The {@link net.sf.jasperreports.export.PdfReportConfiguration#isForceSvgShapes() isForceSvgShapes()} * flag is used to force the rendering of SVG images using shapes on the PDF <code>Graphics2D</code> * context. This allows fonts to be rendered as shapes, thus avoiding any font mapping issues that * might cause Unicode text to not show up properly; however, it has the disadvantage of producing * larger PDF files. * <p/> * By default, the flag is set to true, mainly due to backward-compatibility reasons. To * reduce PDF file size for documents containing SVG images such as charts, this flag * should be set to false. However, in such a case, the accuracy of the text content * rendered by the SVG element in PDF depends on the correct PDF font information being * available in the SVG implementation itself. * <p/> * In JasperReports, SVG elements are rendered using * {@link net.sf.jasperreports.renderers.Renderable} implementations, * which are most likely subclasses of the {@link net.sf.jasperreports.renderers.AbstractRenderToImageDataRenderer} * class. SVG renderer implementations should be concerned only with * implementing the * <p/> * <code>public void render(JasperReportsContext jasperReportsContext, Graphics2D grx, Rectangle2D rectangle)</code> * <p/> * method, which should contain all the code * required for rendering the SVG on a Graphics2D context. Correct PDF font information * means that the <code>java.awt.Font</code> objects used to draw text on the <code>Graphics2D</code> * context should have PDF-related text attributes embedded so that when rendered on a PDF * <code>Graphics2D</code> context, the exporter can make use of them. Embedding PDF-related text * attributes into the SVG means using the following text attributes when creating * <code>java.awt.Font</code> to render text in the SVG renderer implementation: * <ul> * <li>{@link net.sf.jasperreports.engine.util.JRTextAttribute#PDF_FONT_NAME PDF_FONT_NAME}</li> * <li>{@link net.sf.jasperreports.engine.util.JRTextAttribute#PDF_ENCODING PDF_ENCODING}</li> * <li>{@link net.sf.jasperreports.engine.util.JRTextAttribute#IS_PDF_EMBEDDED IS_PDF_EMBEDDED}</li> * </ul> * <p/> * The built-in chart component in JasperReports hides this complexity of dealing with * fonts in a SVG renderer by exposing to the end user the usual three PDF-specific font * attributes (<code>pdfFontName</code>, <code>pdfEncoding</code>, and <code>isPdfEmbedded</code>) * to be set along with the normal font attributes every time a font setting is made for the chart * title, subtitle, chart legend, or axis. This feature can be controlled system-wide using the * {@link net.sf.jasperreports.export.PdfReportConfiguration#PROPERTY_FORCE_SVG_SHAPES net.sf.jasperreports.export.pdf.force.svg.shapes} configuration property. * The {@link net.sf.jasperreports.export.PdfReportConfiguration#isForceSvgShapes() isForceSvgShapes()} * export configuration setting overrides the configuration property value, if present. * <h3>Section 508 Compliance</h3> * PDF files can contain hidden tags that describe the structure of the document. Some of * the tags are used by the automated reader tool that reads PDF documents aloud to people * with disabilities. * <p/> * The PDF tags feature of JasperReports allows adding hidden PDF tags to the files * generated by the JasperReports PDF exporter. The resulting files comply with the * requirements of the Section 508 of the U.S. Rehabilitation Act * (<a href="http://www.section508.gov">http://www.section508.gov/</a>). * <h3>Producing Tagged PDF Files</h3> * By default, the JasperReports exporter does not put any hidden structural tags inside its * generated PDF files. In order to turn on the creation of hidden structural tags, any of the * following can be used: * <ul> * <li>setting to true the {@link net.sf.jasperreports.export.PdfExporterConfiguration#isTagged() isTagged()} * configuration flag</li> * <li>setting to true the {@link net.sf.jasperreports.export.PdfExporterConfiguration#PROPERTY_TAGGED net.sf.jasperreports.export.pdf.tagged} configuration property.</li> * </ul> * <h3>Setting the PDF File Language</h3> * When a full accessibility check is requested from Acrobat Professional, among the things * it determines is whether the PDF file or the various pieces of content inside it have a * language set. JasperReports allows setting the language for the entire content by doing * any one of the following: * <ul> * <li>using the {@link net.sf.jasperreports.export.PdfExporterConfiguration#getTagLanguage() getTagLanguage()} * configuration setting to retrieve the language as a <code>java.lang.String</code> value;</li> * <li>using the {@link net.sf.jasperreports.export.PdfExporterConfiguration#PROPERTY_TAG_LANGUAGE net.sf.jasperreports.export.pdf.tag.language} configuration property * globally or at report level</li> * </ul> * <h3>Alternate Text for Images</h3> * In tagged PDF files, image elements can be described in alternate text that is read by the * automated reader. The text is specified using the <code>hyperlinkTooltipExpression</code> * property of the image element in JRXML. * <p/> * For more information about tagged PDF documents in JasperReports, * please consult the {@link net.sf.jasperreports.engine.export.JRPdfExporterTagHelper} class. * * @see net.sf.jasperreports.export.PdfExporterConfiguration * @see net.sf.jasperreports.export.PdfReportConfiguration * @see net.sf.jasperreports.engine.util.JRTextAttribute#IS_PDF_EMBEDDED * @see net.sf.jasperreports.engine.util.JRTextAttribute#PDF_ENCODING * @see net.sf.jasperreports.engine.util.JRTextAttribute#PDF_FONT_NAME * @author Teodor Danciu (teodord@users.sourceforge.net) */ public class JRPdfExporter extends JRAbstractExporter<PdfReportConfiguration, PdfExporterConfiguration, OutputStreamExporterOutput, JRPdfExporterContext> { private static final Log log = LogFactory.getLog(JRPdfExporter.class); public static final String PDF_EXPORTER_PROPERTIES_PREFIX = JRPropertiesUtil.PROPERTY_PREFIX + "export.pdf."; public static final String EXCEPTION_MESSAGE_KEY_DOCUMENT_ERROR = "export.pdf.document.error"; public static final String EXCEPTION_MESSAGE_KEY_FONT_LOADING_ERROR = "export.pdf.font.loading.error"; public static final String EXCEPTION_MESSAGE_KEY_REPORT_GENERATION_ERROR = "export.pdf.report.generation.error"; /** * Prefix of properties that specify font files for the PDF exporter. */ @Property(name = "net.sf.jasperreports.export.pdf.font.{arbitrary_name}", category = PropertyConstants.CATEGORY_EXPORT, scopes = { PropertyScope.GLOBAL }, sinceVersion = PropertyConstants.VERSION_1_0_0) public static final String PDF_FONT_FILES_PREFIX = PDF_EXPORTER_PROPERTIES_PREFIX + "font."; /** * Prefix of properties that specify font directories for the PDF exporter. */ @Property(name = "net.sf.jasperreports.export.pdf.fontdir.{arbitrary_name}", category = PropertyConstants.CATEGORY_EXPORT, scopes = { PropertyScope.GLOBAL }, sinceVersion = PropertyConstants.VERSION_1_0_0) public static final String PDF_FONT_DIRS_PREFIX = PDF_EXPORTER_PROPERTIES_PREFIX + "fontdir."; /** * The exporter key, as used in * {@link GenericElementHandlerEnviroment#getElementHandler(JRGenericElementType, String)}. */ public static final String PDF_EXPORTER_KEY = JRPropertiesUtil.PROPERTY_PREFIX + "pdf"; private static final String EMPTY_BOOKMARK_TITLE = ""; /** * */ protected static final String JR_PAGE_ANCHOR_PREFIX = "JR_PAGE_ANCHOR_"; protected static boolean fontsRegistered; protected class ExporterContext extends BaseExporterContext implements JRPdfExporterContext { @Override public PdfWriter getPdfWriter() { return pdfWriter; } } /** * */ protected Document document; protected PdfContentByte pdfContentByte; protected PdfWriter pdfWriter; protected Document imageTesterDocument; protected PdfContentByte imageTesterPdfContentByte; protected JRPdfExporterTagHelper tagHelper = new JRPdfExporterTagHelper(this); protected int reportIndex; protected PrintPageFormat pageFormat; protected int crtDocumentPageNumber; protected int permissions; /** * */ protected RenderersCache renderersCache; protected Map<String, Image> loadedImagesMap; protected Image pxImage; private BookmarkStack bookmarkStack; private SplitCharacter splitCharacter; private int crtOddPageOffsetX; private int crtOddPageOffsetY; private int crtEvenPageOffsetX; private int crtEvenPageOffsetY; private boolean awtIgnoreMissingFont; private Set<UnicodeBlock> glyphRendererBlocks; private boolean glyphRendererAddActualText; private PdfVersionEnum minimalVersion; private Map<FontKey, Boolean> glyphRendererFonts; /** * @see #JRPdfExporter(JasperReportsContext) */ public JRPdfExporter() { this(DefaultJasperReportsContext.getInstance()); } /** * */ public JRPdfExporter(JasperReportsContext jasperReportsContext) { super(jasperReportsContext); exporterContext = new ExporterContext(); glyphRendererFonts = new HashMap<JRPdfExporter.FontKey, Boolean>(); } @Override protected Class<PdfExporterConfiguration> getConfigurationInterface() { return PdfExporterConfiguration.class; } @Override protected Class<PdfReportConfiguration> getItemConfigurationInterface() { return PdfReportConfiguration.class; } @Override @SuppressWarnings("deprecation") protected void ensureOutput() { if (exporterOutput == null) { exporterOutput = new net.sf.jasperreports.export.parameters.ParametersOutputStreamExporterOutput( getJasperReportsContext(), getParameters(), getCurrentJasperPrint()); } } /** * */ protected Image getPxImage() { if (pxImage == null) { try { pxImage = Image.getInstance(JRLoader.loadBytesFromResource(JRImageLoader.PIXEL_IMAGE_RESOURCE)); } catch (Exception e) { throw new JRRuntimeException(e); } } return pxImage; } @Override public void exportReport() throws JRException { registerFonts(); /* */ ensureJasperReportsContext(); ensureInput(); initExport(); ensureOutput(); OutputStream outputStream = getExporterOutput().getOutputStream(); try { exportReportToStream(outputStream); } finally { getExporterOutput().close(); resetExportContext(); } } @Override protected void initExport() { super.initExport(); PdfExporterConfiguration configuration = getCurrentConfiguration(); Boolean isTagged = configuration.isTagged(); if (isTagged != null) { tagHelper.setTagged(isTagged); } tagHelper.setLanguage(configuration.getTagLanguage()); this.permissions = getIntegerPermissions(configuration.getAllowedPermissions()) & (~getIntegerPermissions(configuration.getDeniedPermissions())); crtDocumentPageNumber = 0; awtIgnoreMissingFont = getPropertiesUtil() .getBooleanProperty(JRStyledText.PROPERTY_AWT_IGNORE_MISSING_FONT);//FIXMECONTEXT replace with getPropertiesUtil in all exporters glyphRendererAddActualText = propertiesUtil .getBooleanProperty(PdfReportConfiguration.PROPERTY_GLYPH_RENDERER_ADD_ACTUAL_TEXT, false); if (glyphRendererAddActualText && !tagHelper.isTagged && PdfGlyphRenderer.supported()) { minimalVersion = PdfVersionEnum.VERSION_1_5; } } @Override protected void initReport() { super.initReport(); PdfReportConfiguration configuration = getCurrentItemConfiguration(); if (configuration.isForceLineBreakPolicy()) { splitCharacter = new BreakIteratorSplitCharacter(); } crtOddPageOffsetX = configuration.getOddPageOffsetX(); crtOddPageOffsetY = configuration.getOddPageOffsetY(); crtEvenPageOffsetX = configuration.getEvenPageOffsetX(); crtEvenPageOffsetY = configuration.getEvenPageOffsetY(); initGlyphRenderer(); renderersCache = new RenderersCache(getJasperReportsContext()); loadedImagesMap = new HashMap<String, Image>(); } protected void initGlyphRenderer() { glyphRendererBlocks = new HashSet<Character.UnicodeBlock>(); List<PropertySuffix> props = propertiesUtil.getAllProperties(getCurrentJasperPrint(), PdfReportConfiguration.PROPERTY_PREFIX_GLYPH_RENDERER_BLOCKS); for (PropertySuffix prop : props) { String blocks = prop.getValue(); for (String blockToken : blocks.split(",")) { UnicodeBlock block = resolveUnicodeBlock(blockToken); if (block != null) { if (log.isDebugEnabled()) { log.debug("glyph renderer block " + block); } glyphRendererBlocks.add(block); } } } } protected UnicodeBlock resolveUnicodeBlock(String name) { if (name.trim().isEmpty()) { return null; } try { return UnicodeBlock.forName(name.trim()); } catch (IllegalArgumentException e) { log.warn("Could not resolve \"" + name + "\" to a Unicode block"); return null; } } /** * */ protected void exportReportToStream(OutputStream os) throws JRException { //ByteArrayOutputStream baos = new ByteArrayOutputStream(); PdfExporterConfiguration configuration = getCurrentConfiguration(); pageFormat = jasperPrint.getPageFormat(0); document = new Document(new Rectangle(pageFormat.getPageWidth(), pageFormat.getPageHeight())); imageTesterDocument = new Document(new Rectangle(10, //jasperPrint.getPageWidth(), 10 //jasperPrint.getPageHeight() )); boolean closeDocuments = true; try { pdfWriter = PdfWriter.getInstance(document, os); pdfWriter.setCloseStream(false); tagHelper.setPdfWriter(pdfWriter); PdfVersionEnum pdfVersion = configuration.getPdfVersion(); if (pdfVersion != null) { pdfWriter.setPdfVersion(pdfVersion.getName().charAt(0)); } if (minimalVersion != null) { pdfWriter.setAtLeastPdfVersion(minimalVersion.getName().charAt(0)); } if (configuration.isCompressed()) { pdfWriter.setFullCompression(); } if (configuration.isEncrypted()) { int perms = configuration.isOverrideHints() == null || configuration.isOverrideHints() ? (configuration.getPermissions() != null ? (Integer) configuration.getPermissions() : permissions) : (permissions != 0 ? permissions : (configuration.getPermissions() != null ? (Integer) configuration.getPermissions() : 0)); pdfWriter.setEncryption(PdfWriter.getISOBytes(configuration.getUserPassword()), PdfWriter.getISOBytes(configuration.getOwnerPassword()), perms, configuration.is128BitKey() ? PdfWriter.STANDARD_ENCRYPTION_128 : PdfWriter.STANDARD_ENCRYPTION_40); } PdfPrintScalingEnum printScaling = configuration.getPrintScaling(); if (PdfPrintScalingEnum.DEFAULT == printScaling) { pdfWriter.addViewerPreference(PdfName.PRINTSCALING, PdfName.APPDEFAULT); } else if (PdfPrintScalingEnum.NONE == printScaling) { pdfWriter.addViewerPreference(PdfName.PRINTSCALING, PdfName.NONE); } boolean justifiedLetterSpacing = propertiesUtil.getBooleanProperty(jasperPrint, PdfExporterConfiguration.PROPERTY_JUSTIFIED_LETTER_SPACING, false); if (!justifiedLetterSpacing) { pdfWriter.setSpaceCharRatio(PdfWriter.NO_SPACE_CHAR_RATIO); } // Add meta-data parameters to generated PDF document // mtclough@users.sourceforge.net 2005-12-05 String title = configuration.getMetadataTitle(); if (title != null) { document.addTitle(title); if (configuration.isDisplayMetadataTitle()) { pdfWriter.addViewerPreference(PdfName.DISPLAYDOCTITLE, new PdfBoolean(true)); } } String author = configuration.getMetadataAuthor(); if (author != null) { document.addAuthor(author); } String subject = configuration.getMetadataSubject(); if (subject != null) { document.addSubject(subject); } String keywords = configuration.getMetadataKeywords(); if (keywords != null) { document.addKeywords(keywords); } String creator = configuration.getMetadataCreator(); if (creator == null) { creator = "JasperReports Library version " + Package.getPackage("net.sf.jasperreports.engine").getImplementationVersion(); } document.addCreator(creator); //accessibility check: tab order follows the structure of the document pdfWriter.setTabs(PdfName.S); //accessibility check: setting the document primary language String language = configuration.getTagLanguage(); if (language != null) { pdfWriter.getExtraCatalog().put(PdfName.LANG, new PdfString(language)); } // BEGIN: PDF/A support PdfaConformanceEnum pdfaConformance = configuration.getPdfaConformance(); boolean gotPdfa = false; if (PdfaConformanceEnum.PDFA_1A == pdfaConformance) { pdfWriter.setPDFXConformance(PdfWriter.PDFA1A); gotPdfa = true; } else if (PdfaConformanceEnum.PDFA_1B == pdfaConformance) { pdfWriter.setPDFXConformance(PdfWriter.PDFA1B); gotPdfa = true; } if (gotPdfa) { if (PdfXmpCreator.supported()) { byte[] metadata = PdfXmpCreator.createXmpMetadata(pdfWriter); pdfWriter.setXmpMetadata(metadata); } else { if ((title != null || subject != null || keywords != null) && log.isWarnEnabled()) { // iText 2.1.7 does not properly write localized properties and keywords log.warn("XMP metadata might be non conforming, include the Adobe XMP library to correct"); } pdfWriter.createXmpMetadata(); } } else { pdfWriter.setRgbTransparencyBlending(true); } // END: PDF/A support document.open(); // BEGIN: PDF/A support if (gotPdfa) { String iccProfilePath = configuration.getIccProfilePath(); if (iccProfilePath != null) { PdfDictionary pdfDictionary = new PdfDictionary(PdfName.OUTPUTINTENT); pdfDictionary.put(PdfName.OUTPUTCONDITIONIDENTIFIER, new PdfString("sRGB IEC61966-2.1")); pdfDictionary.put(PdfName.INFO, new PdfString("sRGB IEC61966-2.1")); pdfDictionary.put(PdfName.S, PdfName.GTS_PDFA1); InputStream iccIs = RepositoryUtil.getInstance(jasperReportsContext) .getInputStreamFromLocation(iccProfilePath);//FIXME use getRepository? PdfICCBased pdfICCBased = new PdfICCBased(ICC_Profile.getInstance(iccIs)); pdfICCBased.remove(PdfName.ALTERNATE); pdfDictionary.put(PdfName.DESTOUTPUTPROFILE, pdfWriter.addToBody(pdfICCBased).getIndirectReference()); pdfWriter.getExtraCatalog().put(PdfName.OUTPUTINTENTS, new PdfArray(pdfDictionary)); } else { throw new JRPdfaIccProfileNotFoundException(); } } // END: PDF/A support String pdfJavaScript = configuration.getPdfJavaScript(); if (pdfJavaScript != null) { pdfWriter.addJavaScript(pdfJavaScript); } pdfContentByte = pdfWriter.getDirectContent(); tagHelper.init(pdfContentByte); PdfWriter imageTesterPdfWriter = PdfWriter.getInstance(imageTesterDocument, new NullOutputStream() // discard the output ); imageTesterDocument.open(); imageTesterDocument.newPage(); imageTesterPdfContentByte = imageTesterPdfWriter.getDirectContent(); imageTesterPdfContentByte.setLiteral("\n"); List<ExporterInputItem> items = exporterInput.getItems(); initBookmarks(items); boolean isCreatingBatchModeBookmarks = configuration.isCreatingBatchModeBookmarks(); for (reportIndex = 0; reportIndex < items.size(); reportIndex++) { ExporterInputItem item = items.get(reportIndex); setCurrentExporterInputItem(item); pageFormat = jasperPrint.getPageFormat(0); setPageSize(null); List<JRPrintPage> pages = jasperPrint.getPages(); if (pages != null && pages.size() > 0) { if (items.size() > 1) { document.newPage(); if (isCreatingBatchModeBookmarks) { //add a new level to our outline for this report addBookmark(0, jasperPrint.getName(), 0, 0); } } PdfReportConfiguration lcItemConfiguration = getCurrentItemConfiguration(); boolean sizePageToContent = lcItemConfiguration.isSizePageToContent(); PrintPageFormat oldPageFormat = null; PageRange pageRange = getPageRange(); int startPageIndex = (pageRange == null || pageRange.getStartPageIndex() == null) ? 0 : pageRange.getStartPageIndex(); int endPageIndex = (pageRange == null || pageRange.getEndPageIndex() == null) ? (pages.size() - 1) : pageRange.getEndPageIndex(); for (int pageIndex = startPageIndex; pageIndex <= endPageIndex; pageIndex++) { if (Thread.interrupted()) { throw new ExportInterruptedException(); } JRPrintPage page = pages.get(pageIndex); pageFormat = jasperPrint.getPageFormat(pageIndex); if (sizePageToContent || oldPageFormat != pageFormat) { setPageSize(sizePageToContent ? page : null); } document.newPage(); pdfContentByte = pdfWriter.getDirectContent(); pdfContentByte.setLineCap(2);//PdfContentByte.LINE_CAP_PROJECTING_SQUARE since iText 1.02b writePageAnchor(pageIndex); crtDocumentPageNumber++; /* */ exportPage(page); oldPageFormat = pageFormat; } } else { document.newPage(); pdfContentByte = pdfWriter.getDirectContent(); pdfContentByte.setLiteral("\n"); } } closeDocuments = false; document.close(); imageTesterDocument.close(); } catch (DocumentException e) { throw new JRException(EXCEPTION_MESSAGE_KEY_DOCUMENT_ERROR, new Object[] { jasperPrint.getName() }, e); } catch (IOException e) { throw new JRException(EXCEPTION_MESSAGE_KEY_REPORT_GENERATION_ERROR, new Object[] { jasperPrint.getName() }, e); } finally { if (closeDocuments) //only on exception { try { document.close(); } catch (Exception e) { // ignore, let the original exception propagate } try { imageTesterDocument.close(); } catch (Exception e) { // ignore, let the original exception propagate } } } //return os.toByteArray(); } protected void writePageAnchor(int pageIndex) throws DocumentException { Map<Attribute, Object> attributes = new HashMap<Attribute, Object>(); fontUtil.getAttributesWithoutAwtFont(attributes, new JRBasePrintText(jasperPrint.getDefaultStyleProvider())); Font pdfFont = getFont(attributes, getLocale(), false); Chunk chunk = new Chunk(" ", pdfFont); chunk.setLocalDestination(JR_PAGE_ANCHOR_PREFIX + reportIndex + "_" + (pageIndex + 1)); tagHelper.startPageAnchor(); ColumnText colText = new ColumnText(pdfContentByte); colText.setSimpleColumn(new Phrase(chunk), 0, pageFormat.getPageHeight(), 1, 1, 0, Element.ALIGN_LEFT); colText.go(); tagHelper.endPageAnchor(); } /** * */ protected void setPageSize(JRPrintPage page) throws JRException, DocumentException, IOException { int pageWidth = 0; int pageHeight = 0; if (page != null) { Collection<JRPrintElement> elements = page.getElements(); for (JRPrintElement element : elements) { int elementRight = element.getX() + element.getWidth(); int elementBottom = element.getY() + element.getHeight(); pageWidth = pageWidth < elementRight ? elementRight : pageWidth; pageHeight = pageHeight < elementBottom ? elementBottom : pageHeight; } pageWidth += pageFormat.getRightMargin(); pageHeight += pageFormat.getBottomMargin(); } pageWidth = pageWidth < pageFormat.getPageWidth() ? pageFormat.getPageWidth() : pageWidth; pageHeight = pageHeight < pageFormat.getPageHeight() ? pageFormat.getPageHeight() : pageHeight; Rectangle pageSize; switch (pageFormat.getOrientation()) { case LANDSCAPE: // using rotate to indicate landscape page pageSize = new Rectangle(pageHeight, pageWidth).rotate(); break; default: pageSize = new Rectangle(pageWidth, pageHeight); break; } document.setPageSize(pageSize); } /** * */ protected void exportPage(JRPrintPage page) throws JRException, DocumentException, IOException { tagHelper.startPage(); Collection<JRPrintElement> elements = page.getElements(); exportElements(elements); tagHelper.endPage(); JRExportProgressMonitor progressMonitor = getCurrentItemConfiguration().getProgressMonitor(); if (progressMonitor != null) { progressMonitor.afterPageExport(); } } protected void exportElements(Collection<JRPrintElement> elements) throws DocumentException, IOException, JRException { if (elements != null && elements.size() > 0) { for (Iterator<JRPrintElement> it = elements.iterator(); it.hasNext();) { JRPrintElement element = it.next(); if (filter == null || filter.isToExport(element)) { tagHelper.startElement(element); if (element instanceof JRPrintLine) { exportLine((JRPrintLine) element); } else if (element instanceof JRPrintRectangle) { exportRectangle((JRPrintRectangle) element); } else if (element instanceof JRPrintEllipse) { exportEllipse((JRPrintEllipse) element); } else if (element instanceof JRPrintImage) { exportImage((JRPrintImage) element); } else if (element instanceof JRPrintText) { exportText((JRPrintText) element); } else if (element instanceof JRPrintFrame) { exportFrame((JRPrintFrame) element); } else if (element instanceof JRGenericPrintElement) { exportGenericElement((JRGenericPrintElement) element); } tagHelper.endElement(element); } } } } /** * */ protected void exportLine(JRPrintLine line) { int lcOffsetX = getOffsetX(); int lcOffsetY = getOffsetY(); float lineWidth = line.getLinePen().getLineWidth(); if (lineWidth > 0f) { preparePen(pdfContentByte, line.getLinePen(), PdfContentByte.LINE_CAP_BUTT); if (line.getWidth() == 1) { if (line.getHeight() != 1) { //Vertical line if (line.getLinePen().getLineStyleValue() == LineStyleEnum.DOUBLE) { pdfContentByte.moveTo(line.getX() + lcOffsetX + 0.5f - lineWidth / 3, pageFormat.getPageHeight() - line.getY() - lcOffsetY); pdfContentByte.lineTo(line.getX() + lcOffsetX + 0.5f - lineWidth / 3, pageFormat.getPageHeight() - line.getY() - lcOffsetY - line.getHeight()); pdfContentByte.stroke(); pdfContentByte.moveTo(line.getX() + lcOffsetX + 0.5f + lineWidth / 3, pageFormat.getPageHeight() - line.getY() - lcOffsetY); pdfContentByte.lineTo(line.getX() + lcOffsetX + 0.5f + lineWidth / 3, pageFormat.getPageHeight() - line.getY() - lcOffsetY - line.getHeight()); } else { pdfContentByte.moveTo(line.getX() + lcOffsetX + 0.5f, pageFormat.getPageHeight() - line.getY() - lcOffsetY); pdfContentByte.lineTo(line.getX() + lcOffsetX + 0.5f, pageFormat.getPageHeight() - line.getY() - lcOffsetY - line.getHeight()); } } } else { if (line.getHeight() == 1) { //Horizontal line if (line.getLinePen().getLineStyleValue() == LineStyleEnum.DOUBLE) { pdfContentByte.moveTo(line.getX() + lcOffsetX, pageFormat.getPageHeight() - line.getY() - lcOffsetY - 0.5f + lineWidth / 3); pdfContentByte.lineTo(line.getX() + lcOffsetX + line.getWidth(), pageFormat.getPageHeight() - line.getY() - lcOffsetY - 0.5f + lineWidth / 3); pdfContentByte.stroke(); pdfContentByte.moveTo(line.getX() + lcOffsetX, pageFormat.getPageHeight() - line.getY() - lcOffsetY - 0.5f - lineWidth / 3); pdfContentByte.lineTo(line.getX() + lcOffsetX + line.getWidth(), pageFormat.getPageHeight() - line.getY() - lcOffsetY - 0.5f - lineWidth / 3); } else { pdfContentByte.moveTo(line.getX() + lcOffsetX, pageFormat.getPageHeight() - line.getY() - lcOffsetY - 0.5f); pdfContentByte.lineTo(line.getX() + lcOffsetX + line.getWidth(), pageFormat.getPageHeight() - line.getY() - lcOffsetY - 0.5f); } } else { //Oblique line if (line.getDirectionValue() == LineDirectionEnum.TOP_DOWN) { if (line.getLinePen().getLineStyleValue() == LineStyleEnum.DOUBLE) { double xtrans = lineWidth / (3 * Math.sqrt(1 + Math.pow(line.getWidth(), 2) / Math.pow(line.getHeight(), 2))); double ytrans = lineWidth / (3 * Math.sqrt(1 + Math.pow(line.getHeight(), 2) / Math.pow(line.getWidth(), 2))); pdfContentByte.moveTo(line.getX() + lcOffsetX + (float) xtrans, pageFormat.getPageHeight() - line.getY() - lcOffsetY + (float) ytrans); pdfContentByte.lineTo(line.getX() + lcOffsetX + line.getWidth() + (float) xtrans, pageFormat.getPageHeight() - line.getY() - lcOffsetY - line.getHeight() + (float) ytrans); pdfContentByte.stroke(); pdfContentByte.moveTo(line.getX() + lcOffsetX - (float) xtrans, pageFormat.getPageHeight() - line.getY() - lcOffsetY - (float) ytrans); pdfContentByte.lineTo(line.getX() + lcOffsetX + line.getWidth() - (float) xtrans, pageFormat.getPageHeight() - line.getY() - lcOffsetY - line.getHeight() - (float) ytrans); } else { pdfContentByte.moveTo(line.getX() + lcOffsetX, pageFormat.getPageHeight() - line.getY() - lcOffsetY); pdfContentByte.lineTo(line.getX() + lcOffsetX + line.getWidth(), pageFormat.getPageHeight() - line.getY() - lcOffsetY - line.getHeight()); } } else { if (line.getLinePen().getLineStyleValue() == LineStyleEnum.DOUBLE) { double xtrans = lineWidth / (3 * Math.sqrt(1 + Math.pow(line.getWidth(), 2) / Math.pow(line.getHeight(), 2))); double ytrans = lineWidth / (3 * Math.sqrt(1 + Math.pow(line.getHeight(), 2) / Math.pow(line.getWidth(), 2))); pdfContentByte.moveTo(line.getX() + lcOffsetX + (float) xtrans, pageFormat.getPageHeight() - line.getY() - lcOffsetY - line.getHeight() - (float) ytrans); pdfContentByte.lineTo(line.getX() + lcOffsetX + line.getWidth() + (float) xtrans, pageFormat.getPageHeight() - line.getY() - lcOffsetY - (float) ytrans); pdfContentByte.stroke(); pdfContentByte.moveTo(line.getX() + lcOffsetX - (float) xtrans, pageFormat.getPageHeight() - line.getY() - lcOffsetY - line.getHeight() + (float) ytrans); pdfContentByte.lineTo(line.getX() + lcOffsetX + line.getWidth() - (float) xtrans, pageFormat.getPageHeight() - line.getY() - lcOffsetY + (float) ytrans); } else { pdfContentByte.moveTo(line.getX() + lcOffsetX, pageFormat.getPageHeight() - line.getY() - lcOffsetY - line.getHeight()); pdfContentByte.lineTo(line.getX() + lcOffsetX + line.getWidth(), pageFormat.getPageHeight() - line.getY() - lcOffsetY); } } } } pdfContentByte.stroke(); pdfContentByte.setLineDash(0f); pdfContentByte.setLineCap(PdfContentByte.LINE_CAP_PROJECTING_SQUARE); } } /** * */ protected void exportRectangle(JRPrintRectangle rectangle) { pdfContentByte.setRGBColorFill(rectangle.getBackcolor().getRed(), rectangle.getBackcolor().getGreen(), rectangle.getBackcolor().getBlue()); preparePen(pdfContentByte, rectangle.getLinePen(), PdfContentByte.LINE_CAP_PROJECTING_SQUARE); float lineWidth = rectangle.getLinePen().getLineWidth(); int lcOffsetX = getOffsetX(); int lcOffsetY = getOffsetY(); if (rectangle.getModeValue() == ModeEnum.OPAQUE) { pdfContentByte.roundRectangle(rectangle.getX() + lcOffsetX, pageFormat.getPageHeight() - rectangle.getY() - lcOffsetY - rectangle.getHeight(), rectangle.getWidth(), rectangle.getHeight(), rectangle.getRadius()); pdfContentByte.fill(); } if (lineWidth > 0f) { if (rectangle.getLinePen().getLineStyleValue() == LineStyleEnum.DOUBLE) { pdfContentByte.roundRectangle(rectangle.getX() + lcOffsetX - lineWidth / 3, pageFormat.getPageHeight() - rectangle.getY() - lcOffsetY - rectangle.getHeight() - lineWidth / 3, rectangle.getWidth() + 2 * lineWidth / 3, rectangle.getHeight() + 2 * lineWidth / 3, rectangle.getRadius()); pdfContentByte.stroke(); pdfContentByte.roundRectangle(rectangle.getX() + lcOffsetX + lineWidth / 3, pageFormat.getPageHeight() - rectangle.getY() - lcOffsetY - rectangle.getHeight() + lineWidth / 3, rectangle.getWidth() - 2 * lineWidth / 3, rectangle.getHeight() - 2 * lineWidth / 3, rectangle.getRadius()); pdfContentByte.stroke(); } else { pdfContentByte.roundRectangle(rectangle.getX() + lcOffsetX, pageFormat.getPageHeight() - rectangle.getY() - lcOffsetY - rectangle.getHeight(), rectangle.getWidth(), rectangle.getHeight(), rectangle.getRadius()); pdfContentByte.stroke(); } } pdfContentByte.setLineDash(0f); } /** * */ protected void exportEllipse(JRPrintEllipse ellipse) { pdfContentByte.setRGBColorFill(ellipse.getBackcolor().getRed(), ellipse.getBackcolor().getGreen(), ellipse.getBackcolor().getBlue()); preparePen(pdfContentByte, ellipse.getLinePen(), PdfContentByte.LINE_CAP_PROJECTING_SQUARE); float lineWidth = ellipse.getLinePen().getLineWidth(); int lcOffsetX = getOffsetX(); int lcOffsetY = getOffsetY(); if (ellipse.getModeValue() == ModeEnum.OPAQUE) { pdfContentByte.ellipse(ellipse.getX() + lcOffsetX, pageFormat.getPageHeight() - ellipse.getY() - lcOffsetY - ellipse.getHeight(), ellipse.getX() + lcOffsetX + ellipse.getWidth(), pageFormat.getPageHeight() - ellipse.getY() - lcOffsetY); pdfContentByte.fill(); } if (lineWidth > 0f) { if (ellipse.getLinePen().getLineStyleValue() == LineStyleEnum.DOUBLE) { pdfContentByte.ellipse(ellipse.getX() + lcOffsetX - lineWidth / 3, pageFormat.getPageHeight() - ellipse.getY() - lcOffsetY - ellipse.getHeight() - lineWidth / 3, ellipse.getX() + lcOffsetX + ellipse.getWidth() + lineWidth / 3, pageFormat.getPageHeight() - ellipse.getY() - lcOffsetY + lineWidth / 3); pdfContentByte.stroke(); pdfContentByte.ellipse(ellipse.getX() + lcOffsetX + lineWidth / 3, pageFormat.getPageHeight() - ellipse.getY() - lcOffsetY - ellipse.getHeight() + lineWidth / 3, ellipse.getX() + lcOffsetX + ellipse.getWidth() - lineWidth / 3, pageFormat.getPageHeight() - ellipse.getY() - lcOffsetY - lineWidth / 3); pdfContentByte.stroke(); } else { pdfContentByte.ellipse(ellipse.getX() + lcOffsetX, pageFormat.getPageHeight() - ellipse.getY() - lcOffsetY - ellipse.getHeight(), ellipse.getX() + lcOffsetX + ellipse.getWidth(), pageFormat.getPageHeight() - ellipse.getY() - lcOffsetY); pdfContentByte.stroke(); } } pdfContentByte.setLineDash(0f); } /** * */ public void exportImage(JRPrintImage printImage) throws DocumentException, IOException, JRException { if (printImage.getModeValue() == ModeEnum.OPAQUE) { pdfContentByte.setRGBColorFill(printImage.getBackcolor().getRed(), printImage.getBackcolor().getGreen(), printImage.getBackcolor().getBlue()); pdfContentByte.rectangle(printImage.getX() + getOffsetX(), pageFormat.getPageHeight() - printImage.getY() - getOffsetY(), printImage.getWidth(), -printImage.getHeight()); pdfContentByte.fill(); } InternalImageProcessor imageProcessor = new InternalImageProcessor(printImage); Renderable renderer = printImage.getRenderer(); if (renderer != null && imageProcessor.availableImageWidth > 0 && imageProcessor.availableImageHeight > 0) { InternalImageProcessorResult imageProcessorResult = null; try { imageProcessorResult = imageProcessor.process(renderer); } catch (Exception e) { Renderable onErrorRenderer = getRendererUtil().handleImageError(e, printImage.getOnErrorTypeValue()); if (onErrorRenderer != null) { imageProcessorResult = imageProcessor.process(onErrorRenderer); } } if (imageProcessorResult != null) { setAnchor(imageProcessorResult.chunk, printImage, printImage); setHyperlinkInfo(imageProcessorResult.chunk, printImage); tagHelper.startImage(printImage); ColumnText colText = new ColumnText(pdfContentByte); int upperY = pageFormat.getPageHeight() - printImage.getY() - imageProcessor.topPadding - getOffsetY() - imageProcessorResult.yoffset; int lowerX = printImage.getX() + imageProcessor.leftPadding + getOffsetX() + imageProcessorResult.xoffset; colText.setSimpleColumn(new Phrase(imageProcessorResult.chunk), lowerX, upperY - imageProcessorResult.scaledHeight, lowerX + imageProcessorResult.scaledWidth, upperY, imageProcessorResult.scaledHeight, Element.ALIGN_LEFT); colText.go(); tagHelper.endImage(); } } if (printImage.getLineBox().getTopPen().getLineWidth() <= 0f && printImage.getLineBox().getLeftPen().getLineWidth() <= 0f && printImage.getLineBox().getBottomPen().getLineWidth() <= 0f && printImage.getLineBox().getRightPen().getLineWidth() <= 0f) { if (printImage.getLinePen().getLineWidth() > 0f) { exportPen(printImage.getLinePen(), printImage); } } else { /* */ exportBox(printImage.getLineBox(), printImage); } } private class InternalImageProcessor { private final JRPrintImage printImage; private final RenderersCache imageRenderersCache; private final int topPadding; private final int leftPadding; private final int bottomPadding; private final int rightPadding; private final int availableImageWidth; private final int availableImageHeight; private InternalImageProcessor(JRPrintImage printImage) { this.printImage = printImage; this.imageRenderersCache = printImage.isUsingCache() ? renderersCache : new RenderersCache(getJasperReportsContext()); topPadding = printImage.getLineBox().getTopPadding(); leftPadding = printImage.getLineBox().getLeftPadding(); bottomPadding = printImage.getLineBox().getBottomPadding(); rightPadding = printImage.getLineBox().getRightPadding(); int tmpAvailableImageWidth = printImage.getWidth() - leftPadding - rightPadding; availableImageWidth = tmpAvailableImageWidth < 0 ? 0 : tmpAvailableImageWidth; int tmpAvailableImageHeight = printImage.getHeight() - topPadding - bottomPadding; availableImageHeight = tmpAvailableImageHeight < 0 ? 0 : tmpAvailableImageHeight; } private InternalImageProcessorResult process(Renderable renderer) throws JRException, IOException, BadElementException { InternalImageProcessorResult imageProcessorResult = null; if (renderer instanceof ResourceRenderer) { renderer = imageRenderersCache.getLoadedRenderer((ResourceRenderer) renderer); } if (renderer instanceof Graphics2DRenderable) { imageProcessorResult = processGraphics2D((Graphics2DRenderable) renderer); } else if (renderer instanceof DataRenderable) { boolean isSvgData = getRendererUtil().isSvgData((DataRenderable) renderer); if (isSvgData) { imageProcessorResult = processGraphics2D( new WrappingSvgDataToGraphics2DRenderer((DataRenderable) renderer)); } else { switch (printImage.getScaleImageValue()) { case CLIP: { imageProcessorResult = processImageClip( new WrappingImageDataToGraphics2DRenderer((DataRenderable) renderer)); break; } case FILL_FRAME: { imageProcessorResult = processImageFillFrame(renderer.getId(), (DataRenderable) renderer); break; } case RETAIN_SHAPE: default: { imageProcessorResult = processImageRetainShape(renderer.getId(), (DataRenderable) renderer); } } } } else { throw new JRException(RendererUtil.EXCEPTION_MESSAGE_KEY_RENDERABLE_MUST_IMPLEMENT_INTERFACE, new Object[] { renderer.getClass().getName(), DataRenderable.class.getName() + " or " + Graphics2DRenderable.class.getName() }); } return imageProcessorResult; } private InternalImageProcessorResult processImageClip(Graphics2DRenderable renderer) throws JRException, IOException, BadElementException { int normalWidth = availableImageWidth; int normalHeight = availableImageHeight; Dimension2D dimension = renderer instanceof DimensionRenderable ? ((DimensionRenderable) renderer).getDimension(jasperReportsContext) : null; if (dimension != null) { normalWidth = (int) dimension.getWidth(); normalHeight = (int) dimension.getHeight(); } int xoffset = (int) (ImageUtil.getXAlignFactor(printImage) * (availableImageWidth - normalWidth)); int yoffset = (int) (ImageUtil.getYAlignFactor(printImage) * (availableImageHeight - normalHeight)); int minWidth = Math.min(normalWidth, availableImageWidth); int minHeight = Math.min(normalHeight, availableImageHeight); BufferedImage bi = new BufferedImage(minWidth, minHeight, BufferedImage.TYPE_INT_ARGB); Graphics2D g = bi.createGraphics(); try { if (printImage.getModeValue() == ModeEnum.OPAQUE) { g.setColor(printImage.getBackcolor()); g.fillRect(0, 0, minWidth, minHeight); } renderer.render(jasperReportsContext, g, new java.awt.Rectangle((xoffset > 0 ? 0 : xoffset), (yoffset > 0 ? 0 : yoffset), normalWidth, normalHeight)); } finally { g.dispose(); } xoffset = (xoffset < 0 ? 0 : xoffset); yoffset = (yoffset < 0 ? 0 : yoffset); //awtImage = bi.getSubimage(0, 0, minWidth, minHeight); //image = com.lowagie.text.Image.getInstance(awtImage, printImage.getBackcolor()); Image image = Image.getInstance(bi, null); return new InternalImageProcessorResult(new Chunk(image, 0, 0), image.getScaledWidth(), image.getScaledHeight(), xoffset, yoffset); } private InternalImageProcessorResult processImageFillFrame(String rendererId, DataRenderable renderer) throws JRException { Image image = null; if (printImage.isUsingCache() && loadedImagesMap.containsKey(rendererId)) { image = loadedImagesMap.get(rendererId); } else { try { image = Image.getInstance(renderer.getData(jasperReportsContext)); imageTesterPdfContentByte.addImage(image, 10, 0, 0, 10, 0, 0); } catch (Exception e) { throw new JRException(e); } if (printImage.isUsingCache()) { loadedImagesMap.put(rendererId, image); } } image.scaleAbsolute(availableImageWidth, availableImageHeight); return new InternalImageProcessorResult(new Chunk(image, 0, 0), image.getScaledWidth(), image.getScaledHeight(), 0, 0); } private InternalImageProcessorResult processImageRetainShape(String rendererId, DataRenderable renderer) throws JRException { Image image = null; if (printImage.isUsingCache() && loadedImagesMap.containsKey(rendererId)) { image = loadedImagesMap.get(rendererId); } else { try { image = Image.getInstance(renderer.getData(jasperReportsContext)); imageTesterPdfContentByte.addImage(image, 10, 0, 0, 10, 0, 0); } catch (Exception e) { throw new JRException(e); } if (printImage.isUsingCache()) { loadedImagesMap.put(rendererId, image); } } image.scaleToFit(availableImageWidth, availableImageHeight); int xoffset = (int) (ImageUtil.getXAlignFactor(printImage) * (availableImageWidth - image.getPlainWidth())); int yoffset = (int) (ImageUtil.getYAlignFactor(printImage) * (availableImageHeight - image.getPlainHeight())); xoffset = (xoffset < 0 ? 0 : xoffset); yoffset = (yoffset < 0 ? 0 : yoffset); return new InternalImageProcessorResult(new Chunk(image, 0, 0), image.getScaledWidth(), image.getScaledHeight(), xoffset, yoffset); } private InternalImageProcessorResult processGraphics2D(Graphics2DRenderable renderer) throws JRException, IOException { int xoffset = 0; int yoffset = 0; double normalWidth = availableImageWidth; double normalHeight = availableImageHeight; double displayWidth = availableImageWidth; double displayHeight = availableImageHeight; double ratioX = 1f; double ratioY = 1f; Rectangle2D clip = null; Dimension2D dimension = renderer instanceof DimensionRenderable ? ((DimensionRenderable) renderer).getDimension(jasperReportsContext) : null; if (dimension != null) { normalWidth = dimension.getWidth(); normalHeight = dimension.getHeight(); displayWidth = normalWidth; displayHeight = normalHeight; switch (printImage.getScaleImageValue()) { case CLIP: { xoffset = (int) (ImageUtil.getXAlignFactor(printImage) * (availableImageWidth - normalWidth)); yoffset = (int) (ImageUtil.getYAlignFactor(printImage) * (availableImageHeight - normalHeight)); clip = new Rectangle2D.Double(-xoffset, -yoffset, availableImageWidth, availableImageHeight); break; } case FILL_FRAME: { ratioX = availableImageWidth / normalWidth; ratioY = availableImageHeight / normalHeight; normalWidth *= ratioX; normalHeight *= ratioY; xoffset = 0; yoffset = 0; break; } case RETAIN_SHAPE: default: { ratioX = availableImageWidth / normalWidth; ratioY = availableImageHeight / normalHeight; ratioX = ratioX < ratioY ? ratioX : ratioY; ratioY = ratioX; normalWidth *= ratioX; normalHeight *= ratioY; xoffset = (int) (ImageUtil.getXAlignFactor(printImage) * (availableImageWidth - normalWidth)); yoffset = (int) (ImageUtil.getYAlignFactor(printImage) * (availableImageHeight - normalHeight)); break; } } } PdfTemplate template = pdfContentByte.createTemplate((float) displayWidth, (float) displayHeight); Graphics2D g = getCurrentItemConfiguration().isForceSvgShapes() ? template.createGraphicsShapes((float) displayWidth, (float) displayHeight) : template.createGraphics(availableImageWidth, availableImageHeight, new LocalFontMapper()); try { if (clip != null) { g.setClip(clip); } if (printImage.getModeValue() == ModeEnum.OPAQUE) { g.setColor(printImage.getBackcolor()); g.fillRect(0, 0, (int) displayWidth, (int) displayHeight); } renderer.render(jasperReportsContext, g, new Rectangle2D.Double(0, 0, displayWidth, displayHeight)); } finally { g.dispose(); } pdfContentByte.saveState(); pdfContentByte.addTemplate(template, (float) ratioX, 0f, 0f, (float) ratioY, printImage.getX() + leftPadding + getOffsetX() + xoffset, pageFormat.getPageHeight() - printImage.getY() - topPadding - getOffsetY() - (int) normalHeight - yoffset); pdfContentByte.restoreState(); Image image = getPxImage(); image.scaleAbsolute(availableImageWidth, availableImageHeight); InternalImageProcessorResult result = new InternalImageProcessorResult(new Chunk(image, 0, 0), availableImageWidth, availableImageHeight, xoffset, yoffset); pdfWriter.releaseTemplate(template); return result; } } private class InternalImageProcessorResult { private final Chunk chunk; private final float scaledWidth; private final float scaledHeight; private final int xoffset; private final int yoffset; private InternalImageProcessorResult(Chunk chunk, float scaledWidth, float scaledHeight, int xoffset, int yoffset) { this.chunk = chunk; this.scaledWidth = scaledWidth; this.scaledHeight = scaledHeight; this.xoffset = xoffset; this.yoffset = yoffset; } } /** * */ protected void setHyperlinkInfo(Chunk chunk, JRPrintHyperlink link) { if (link != null) { Boolean ignoreHyperlink = HyperlinkUtil .getIgnoreHyperlink(PdfReportConfiguration.PROPERTY_IGNORE_HYPERLINK, link); if (ignoreHyperlink == null) { ignoreHyperlink = getCurrentItemConfiguration().isIgnoreHyperlink(); } if (!ignoreHyperlink) { switch (link.getHyperlinkTypeValue()) { case REFERENCE: { if (link.getHyperlinkReference() != null) { switch (link.getHyperlinkTargetValue()) { case BLANK: { chunk.setAction(PdfAction.javaScript("if (app.viewerVersion < 7)" + "{this.getURL(\"" + link.getHyperlinkReference() + "\");}" + "else {app.launchURL(\"" + link.getHyperlinkReference() + "\", true);};", pdfWriter)); break; } case SELF: default: { chunk.setAnchor(link.getHyperlinkReference()); break; } } } break; } case LOCAL_ANCHOR: { if (link.getHyperlinkAnchor() != null) { chunk.setLocalGoto(link.getHyperlinkAnchor()); } break; } case LOCAL_PAGE: { if (link.getHyperlinkPage() != null) { chunk.setLocalGoto( JR_PAGE_ANCHOR_PREFIX + reportIndex + "_" + link.getHyperlinkPage().toString()); } break; } case REMOTE_ANCHOR: { if (link.getHyperlinkReference() != null && link.getHyperlinkAnchor() != null) { chunk.setRemoteGoto(link.getHyperlinkReference(), link.getHyperlinkAnchor()); } break; } case REMOTE_PAGE: { if (link.getHyperlinkReference() != null && link.getHyperlinkPage() != null) { chunk.setRemoteGoto(link.getHyperlinkReference(), link.getHyperlinkPage()); } break; } case CUSTOM: { JRHyperlinkProducerFactory hyperlinkProducerFactory = getCurrentItemConfiguration() .getHyperlinkProducerFactory(); if (hyperlinkProducerFactory != null) { String hyperlink = hyperlinkProducerFactory.produceHyperlink(link); if (hyperlink != null) { switch (link.getHyperlinkTargetValue()) { case BLANK: { chunk.setAction(PdfAction.javaScript( "if (app.viewerVersion < 7)" + "{this.getURL(\"" + hyperlink + "\");}" + "else {app.launchURL(\"" + hyperlink + "\", true);};", pdfWriter)); break; } case SELF: default: { chunk.setAnchor(hyperlink); break; } } } } } case NONE: default: { break; } } } } } @Override protected Locale getTextLocale(JRPrintText text) { // only overriding for package access return super.getTextLocale(text); } /** * */ protected Phrase getPhrase(AttributedString as, String text, JRPrintText textElement) { Phrase phrase = new Phrase(); int runLimit = 0; AttributedCharacterIterator iterator = as.getIterator(); Locale locale = getTextLocale(textElement); boolean firstChunk = true; while (runLimit < text.length() && (runLimit = iterator.getRunLimit()) <= text.length()) { Map<Attribute, Object> attributes = iterator.getAttributes(); Chunk chunk = getChunk(attributes, text.substring(iterator.getIndex(), runLimit), locale); if (firstChunk) { // only set anchor + bookmark for the first chunk in the text setAnchor(chunk, textElement, textElement); } JRPrintHyperlink hyperlink = textElement; if (hyperlink.getHyperlinkTypeValue() == HyperlinkTypeEnum.NONE) { hyperlink = (JRPrintHyperlink) attributes.get(JRTextAttribute.HYPERLINK); } setHyperlinkInfo(chunk, hyperlink); phrase.add(chunk); iterator.setIndex(runLimit); firstChunk = false; } return phrase; } /** * */ protected Chunk getChunk(Map<Attribute, Object> attributes, String text, Locale locale) { // underline and strikethrough are set on the chunk below Font font = getFont(attributes, locale, false); Chunk chunk = new Chunk(text, font); if (hasUnderline(attributes)) { // using the same values as sun.font.Fond2D chunk.setUnderline(null, 0, 1f / 18, 0, -1f / 12, 0); } if (hasStrikethrough(attributes)) { // using the same thickness as sun.font.Fond2D. // the position is calculated in Fond2D based on the ascent, defaulting // to iText default position which depends on the font size chunk.setUnderline(null, 0, 1f / 18, 0, 1f / 3, 0); } Color backcolor = (Color) attributes.get(TextAttribute.BACKGROUND); if (backcolor != null) { chunk.setBackground(backcolor); } Object script = attributes.get(TextAttribute.SUPERSCRIPT); if (script != null) { if (TextAttribute.SUPERSCRIPT_SUPER.equals(script)) { chunk.setTextRise(font.getCalculatedLeading(1f) / 2); } else if (TextAttribute.SUPERSCRIPT_SUB.equals(script)) { chunk.setTextRise(-font.getCalculatedLeading(1f) / 2); } } if (splitCharacter != null) { //TODO use line break offsets if available? chunk.setSplitCharacter(splitCharacter); } return chunk; } protected boolean hasUnderline(Map<Attribute, Object> textAttributes) { Integer underline = (Integer) textAttributes.get(TextAttribute.UNDERLINE); return TextAttribute.UNDERLINE_ON.equals(underline); } protected boolean hasStrikethrough(Map<Attribute, Object> textAttributes) { Boolean strike = (Boolean) textAttributes.get(TextAttribute.STRIKETHROUGH); return TextAttribute.STRIKETHROUGH_ON.equals(strike); } /** * Creates a PDF font. * * @param attributes the text attributes of the font * @param locale the locale for which to create the font * @param setFontLines whether to set underline and strikethrough as font style * @return the PDF font for the specified attributes */ protected Font getFont(Map<Attribute, Object> attributes, Locale locale, boolean setFontLines) { JRFont jrFont = new JRBaseFont(attributes); Exception initialException = null; Color forecolor = (Color) attributes.get(TextAttribute.FOREGROUND); // use the same font scale ratio as in JRStyledText.getAwtAttributedString float fontSizeScale = 1f; Integer scriptStyle = (Integer) attributes.get(TextAttribute.SUPERSCRIPT); if (scriptStyle != null && (TextAttribute.SUPERSCRIPT_SUB.equals(scriptStyle) || TextAttribute.SUPERSCRIPT_SUPER.equals(scriptStyle))) { fontSizeScale = 2f / 3; } Font font = null; String pdfFontName = null; String pdfEncoding = null; boolean isPdfEmbedded = false; boolean isPdfSimulatedBold = false; boolean isPdfSimulatedItalic = false; FontInfo fontInfo = (FontInfo) attributes.get(JRTextAttribute.FONT_INFO); if (fontInfo == null) { fontInfo = fontUtil.getFontInfo(jrFont.getFontName(), locale); } if (fontInfo == null) { //fontName NOT found in font extensions pdfFontName = jrFont.getPdfFontName(); pdfEncoding = jrFont.getPdfEncoding(); isPdfEmbedded = jrFont.isPdfEmbedded(); } else { //fontName found in font extensions FontFamily family = fontInfo.getFontFamily(); int pdfFontStyle = java.awt.Font.PLAIN; FontFace fontFace = fontInfo.getFontFace(); if (fontFace != null) { pdfFontName = fontFace.getPdf(); pdfFontName = pdfFontName == null ? fontFace.getTtf() : pdfFontName; pdfFontStyle = fontInfo.getStyle(); } if (pdfFontName == null && jrFont.isBold() && jrFont.isItalic()) { fontFace = family.getBoldItalicFace(); if (fontFace != null) { pdfFontName = fontFace.getPdf(); pdfFontName = pdfFontName == null ? fontFace.getTtf() : pdfFontName; pdfFontStyle = java.awt.Font.BOLD | java.awt.Font.ITALIC; } } if (pdfFontName == null && jrFont.isBold()) { fontFace = family.getBoldFace(); if (fontFace != null) { pdfFontName = fontFace.getPdf(); pdfFontName = pdfFontName == null ? fontFace.getTtf() : pdfFontName; pdfFontStyle = java.awt.Font.BOLD; } } if (pdfFontName == null && jrFont.isItalic()) { fontFace = family.getItalicFace(); if (fontFace != null) { pdfFontName = fontFace.getPdf(); pdfFontName = pdfFontName == null ? fontFace.getTtf() : pdfFontName; pdfFontStyle = java.awt.Font.ITALIC; } } if (pdfFontName == null) { fontFace = family.getNormalFace(); if (fontFace != null) { pdfFontName = fontFace.getPdf(); pdfFontName = pdfFontName == null ? fontFace.getTtf() : pdfFontName; pdfFontStyle = java.awt.Font.PLAIN; } } if (pdfFontName == null) { pdfFontName = jrFont.getPdfFontName(); } pdfEncoding = family.getPdfEncoding() == null ? jrFont.getPdfEncoding() : family.getPdfEncoding(); isPdfEmbedded = family.isPdfEmbedded() == null ? jrFont.isPdfEmbedded() : family.isPdfEmbedded(); isPdfSimulatedBold = jrFont.isBold() && ((pdfFontStyle & java.awt.Font.BOLD) == 0); isPdfSimulatedItalic = jrFont.isItalic() && ((pdfFontStyle & java.awt.Font.ITALIC) == 0); } int pdfFontStyle = (isPdfSimulatedBold ? Font.BOLD : 0) | (isPdfSimulatedItalic ? Font.ITALIC : 0); if (setFontLines) { pdfFontStyle |= (jrFont.isUnderline() ? Font.UNDERLINE : 0) | (jrFont.isStrikeThrough() ? Font.STRIKETHRU : 0); } try { font = FontFactory.getFont(pdfFontName, pdfEncoding, isPdfEmbedded, jrFont.getFontsize() * fontSizeScale, pdfFontStyle, forecolor); // check if FontFactory didn't find the font if (font != null && font.getBaseFont() == null && font.getFamily() == Font.UNDEFINED) { font = null; } } catch (Exception e) { initialException = e; } if (font == null) { byte[] bytes = null; try { bytes = getRepository().getBytesFromLocation(pdfFontName); } catch (JRException e) { throw //NOPMD new JRRuntimeException(EXCEPTION_MESSAGE_KEY_FONT_LOADING_ERROR, new Object[] { pdfFontName, pdfEncoding, isPdfEmbedded }, initialException); } BaseFont baseFont = null; try { baseFont = BaseFont.createFont(pdfFontName, pdfEncoding, isPdfEmbedded, true, bytes, null); } catch (DocumentException e) { throw new JRRuntimeException(e); } catch (IOException e) { throw new JRRuntimeException(e); } font = new Font(baseFont, jrFont.getFontsize() * fontSizeScale, pdfFontStyle, forecolor); } return font; } /** * */ public void exportText(JRPrintText text) throws DocumentException { JRStyledText styledText = styledTextUtil.getProcessedStyledText(text, noBackcolorSelector, null); if (styledText == null) { return; } AbstractPdfTextRenderer textRenderer = getTextRenderer(text, styledText); textRenderer.initialize(this, pdfContentByte, text, styledText, getOffsetX(), getOffsetY()); double angle = 0; switch (text.getRotationValue()) { case LEFT: { angle = Math.PI / 2; break; } case RIGHT: { angle = -Math.PI / 2; break; } case UPSIDE_DOWN: { angle = Math.PI; break; } case NONE: default: { } } AffineTransform atrans = new AffineTransform(); atrans.rotate(angle, textRenderer.getX(), pageFormat.getPageHeight() - textRenderer.getY()); pdfContentByte.transform(atrans); if (text.getModeValue() == ModeEnum.OPAQUE) { Color backcolor = text.getBackcolor(); pdfContentByte.setRGBColorFill(backcolor.getRed(), backcolor.getGreen(), backcolor.getBlue()); pdfContentByte.rectangle(textRenderer.getX(), pageFormat.getPageHeight() - textRenderer.getY(), textRenderer.getWidth(), -textRenderer.getHeight()); pdfContentByte.fill(); } if (glyphRendererAddActualText && textRenderer instanceof PdfGlyphRenderer) { tagHelper.startText(styledText.getText(), text.getLinkType() != null); } else { tagHelper.startText(text.getLinkType() != null); } /* rendering only non empty texts */ if (styledText.length() > 0) { textRenderer.render(); } tagHelper.endText(); atrans = new AffineTransform(); atrans.rotate(-angle, textRenderer.getX(), pageFormat.getPageHeight() - textRenderer.getY()); pdfContentByte.transform(atrans); /* */ exportBox(text.getLineBox(), text); } protected AbstractPdfTextRenderer getTextRenderer(JRPrintText text, JRStyledText styledText) { AbstractPdfTextRenderer textRenderer; if (toUseGlyphRenderer(text) && PdfGlyphRenderer.supported() && canUseGlyphRendering(text, styledText)) { textRenderer = new PdfGlyphRenderer(jasperReportsContext, awtIgnoreMissingFont, glyphRendererAddActualText && !tagHelper.isTagged); } else if (text.getLeadingOffset() == 0) { textRenderer = new PdfTextRenderer(jasperReportsContext, awtIgnoreMissingFont); } else { textRenderer = new SimplePdfTextRenderer(jasperReportsContext, awtIgnoreMissingFont);//FIXMETAB optimize this } return textRenderer; } protected boolean canUseGlyphRendering(JRPrintText text, JRStyledText styledText) { Locale locale = getTextLocale(text); AttributedCharacterIterator attributesIterator = styledText.getAttributedString().getIterator(); int index = 0; while (index < styledText.length()) { FontKey fontKey = extractFontKey(attributesIterator.getAttributes(), locale); if (!fontKey.fontAttribute.hasAttribute()) { return false; } Boolean canUse = glyphRendererFonts.get(fontKey); if (canUse == null) { canUse = canUseGlyphRendering(fontKey); glyphRendererFonts.put(fontKey, canUse); } if (!canUse) { return false; } index = attributesIterator.getRunLimit(); attributesIterator.setIndex(index); } return true; } protected FontKey extractFontKey(Map<Attribute, Object> attributes, Locale locale) { AwtFontAttribute fontAttribute = AwtFontAttribute.fromAttributes(attributes); Number posture = (Number) attributes.get(TextAttribute.POSTURE); boolean italic = TextAttribute.POSTURE_OBLIQUE.equals(posture);//FIXME check for non standard posture Number weight = (Number) attributes.get(TextAttribute.WEIGHT); boolean bold = TextAttribute.WEIGHT_BOLD.equals(weight); return new FontKey(fontAttribute, italic, bold, locale); } protected boolean canUseGlyphRendering(FontKey fontKey) { Map<Attribute, Object> fontAttributes = new HashMap<Attribute, Object>(); fontKey.fontAttribute.putAttributes(fontAttributes); fontAttributes.put(TextAttribute.SIZE, 10f); int style = 0; if (fontKey.italic) { style |= java.awt.Font.ITALIC; fontAttributes.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE); } if (fontKey.bold) { style |= java.awt.Font.BOLD; fontAttributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD); } Font pdfFont = getFont(fontAttributes, fontKey.locale, false); BaseFont baseFont = pdfFont.getBaseFont(); if (baseFont.getFontType() != BaseFont.FONT_TYPE_TTUNI || baseFont.isFontSpecific()) { if (log.isDebugEnabled()) { log.debug("pdf font for " + fontKey + " has type " + baseFont.getFontType() + ", symbol " + baseFont.isFontSpecific() + ", cannot use glyph rendering"); } return false; } java.awt.Font awtFont = fontUtil.getAwtFontFromBundles(fontKey.fontAttribute, style, 10f, fontKey.locale, awtIgnoreMissingFont); if (awtFont == null) { awtFont = new java.awt.Font(fontAttributes); } String awtFontName = awtFont.getFontName(); if (log.isDebugEnabled()) { log.debug(fontKey + " resolved to awt font " + awtFontName); } // we need the fonts to be identical. // it would be safer to only allow fonts from extensions, // but for now we are just checking the font names. // we need to compare full names because we can't get the base name from awt. String[][] pdfFontNames = baseFont.getFullFontName(); boolean nameMatch = false; for (String[] nameArray : pdfFontNames) { if (nameArray.length >= 4) { if (log.isDebugEnabled()) { log.debug(fontKey + " resolved to pdf font " + nameArray[3]); } if (awtFontName.equals(nameArray[3])) { nameMatch = true; break; } } } return nameMatch; } protected boolean toUseGlyphRenderer(JRPrintText text) { String value = styledTextUtil.getTruncatedText(text); if (value == null) { return false; } if (glyphRendererBlocks.isEmpty()) { return false; } int charCount = value.length(); char[] chars = new char[charCount]; value.getChars(0, charCount, chars, 0); for (char c : chars) { UnicodeBlock block = UnicodeBlock.of(c); if (glyphRendererBlocks.contains(block)) { if (log.isTraceEnabled()) { log.trace("found character in block " + block + ", using the glyph renderer"); } return true; } } return false; } /** * */ protected void exportBox(JRLineBox box, JRPrintElement element) { exportTopPen(box.getTopPen(), box.getLeftPen(), box.getRightPen(), element); exportLeftPen(box.getTopPen(), box.getLeftPen(), box.getBottomPen(), element); exportBottomPen(box.getLeftPen(), box.getBottomPen(), box.getRightPen(), element); exportRightPen(box.getTopPen(), box.getBottomPen(), box.getRightPen(), element); pdfContentByte.setLineDash(0f); pdfContentByte.setLineCap(PdfContentByte.LINE_CAP_PROJECTING_SQUARE); } /** * */ protected void exportPen(JRPen pen, JRPrintElement element) { exportTopPen(pen, pen, pen, element); exportLeftPen(pen, pen, pen, element); exportBottomPen(pen, pen, pen, element); exportRightPen(pen, pen, pen, element); pdfContentByte.setLineDash(0f); pdfContentByte.setLineCap(PdfContentByte.LINE_CAP_PROJECTING_SQUARE); } /** * */ protected void exportTopPen(JRPen topPen, JRPen leftPen, JRPen rightPen, JRPrintElement element) { if (topPen.getLineWidth() > 0f) { float leftOffset = leftPen.getLineWidth() / 2; float rightOffset = rightPen.getLineWidth() / 2; int lcOffsetX = getOffsetX(); int lcOffsetY = getOffsetY(); preparePen(pdfContentByte, topPen, PdfContentByte.LINE_CAP_BUTT); if (topPen.getLineStyleValue() == LineStyleEnum.DOUBLE) { float topOffset = topPen.getLineWidth(); pdfContentByte.moveTo(element.getX() + lcOffsetX - leftOffset, pageFormat.getPageHeight() - element.getY() - lcOffsetY + topOffset / 3); pdfContentByte.lineTo(element.getX() + lcOffsetX + element.getWidth() + rightOffset, pageFormat.getPageHeight() - element.getY() - lcOffsetY + topOffset / 3); pdfContentByte.stroke(); pdfContentByte.moveTo(element.getX() + lcOffsetX + leftOffset / 3, pageFormat.getPageHeight() - element.getY() - lcOffsetY - topOffset / 3); pdfContentByte.lineTo(element.getX() + lcOffsetX + element.getWidth() - rightOffset / 3, pageFormat.getPageHeight() - element.getY() - lcOffsetY - topOffset / 3); pdfContentByte.stroke(); } else { pdfContentByte.moveTo(element.getX() + lcOffsetX - leftOffset, pageFormat.getPageHeight() - element.getY() - lcOffsetY); pdfContentByte.lineTo(element.getX() + lcOffsetX + element.getWidth() + rightOffset, pageFormat.getPageHeight() - element.getY() - lcOffsetY); pdfContentByte.stroke(); } } } /** * */ protected void exportLeftPen(JRPen topPen, JRPen leftPen, JRPen bottomPen, JRPrintElement element) { if (leftPen.getLineWidth() > 0f) { float topOffset = topPen.getLineWidth() / 2; float bottomOffset = bottomPen.getLineWidth() / 2; int lcOffsetX = getOffsetX(); int lcOffsetY = getOffsetY(); preparePen(pdfContentByte, leftPen, PdfContentByte.LINE_CAP_BUTT); if (leftPen.getLineStyleValue() == LineStyleEnum.DOUBLE) { float leftOffset = leftPen.getLineWidth(); pdfContentByte.moveTo(element.getX() + lcOffsetX - leftOffset / 3, pageFormat.getPageHeight() - element.getY() - lcOffsetY + topOffset); pdfContentByte.lineTo(element.getX() + lcOffsetX - leftOffset / 3, pageFormat.getPageHeight() - element.getY() - lcOffsetY - element.getHeight() - bottomOffset); pdfContentByte.stroke(); pdfContentByte.moveTo(element.getX() + lcOffsetX + leftOffset / 3, pageFormat.getPageHeight() - element.getY() - lcOffsetY - topOffset / 3); pdfContentByte.lineTo(element.getX() + lcOffsetX + leftOffset / 3, pageFormat.getPageHeight() - element.getY() - lcOffsetY - element.getHeight() + bottomOffset / 3); pdfContentByte.stroke(); } else { pdfContentByte.moveTo(element.getX() + lcOffsetX, pageFormat.getPageHeight() - element.getY() - lcOffsetY + topOffset); pdfContentByte.lineTo(element.getX() + lcOffsetX, pageFormat.getPageHeight() - element.getY() - lcOffsetY - element.getHeight() - bottomOffset); pdfContentByte.stroke(); } } } /** * */ protected void exportBottomPen(JRPen leftPen, JRPen bottomPen, JRPen rightPen, JRPrintElement element) { if (bottomPen.getLineWidth() > 0f) { float leftOffset = leftPen.getLineWidth() / 2; float rightOffset = rightPen.getLineWidth() / 2; int lcOffsetX = getOffsetX(); int lcOffsetY = getOffsetY(); preparePen(pdfContentByte, bottomPen, PdfContentByte.LINE_CAP_BUTT); if (bottomPen.getLineStyleValue() == LineStyleEnum.DOUBLE) { float bottomOffset = bottomPen.getLineWidth(); pdfContentByte.moveTo(element.getX() + lcOffsetX - leftOffset, pageFormat.getPageHeight() - element.getY() - lcOffsetY - element.getHeight() - bottomOffset / 3); pdfContentByte.lineTo(element.getX() + lcOffsetX + element.getWidth() + rightOffset, pageFormat.getPageHeight() - element.getY() - lcOffsetY - element.getHeight() - bottomOffset / 3); pdfContentByte.stroke(); pdfContentByte.moveTo(element.getX() + lcOffsetX + leftOffset / 3, pageFormat.getPageHeight() - element.getY() - lcOffsetY - element.getHeight() + bottomOffset / 3); pdfContentByte.lineTo(element.getX() + lcOffsetX + element.getWidth() - rightOffset / 3, pageFormat.getPageHeight() - element.getY() - lcOffsetY - element.getHeight() + bottomOffset / 3); pdfContentByte.stroke(); } else { pdfContentByte.moveTo(element.getX() + lcOffsetX - leftOffset, pageFormat.getPageHeight() - element.getY() - lcOffsetY - element.getHeight()); pdfContentByte.lineTo(element.getX() + lcOffsetX + element.getWidth() + rightOffset, pageFormat.getPageHeight() - element.getY() - lcOffsetY - element.getHeight()); pdfContentByte.stroke(); } } } /** * */ protected void exportRightPen(JRPen topPen, JRPen bottomPen, JRPen rightPen, JRPrintElement element) { if (rightPen.getLineWidth() > 0f) { float topOffset = topPen.getLineWidth() / 2; float bottomOffset = bottomPen.getLineWidth() / 2; int lcOffsetX = getOffsetX(); int lcOffsetY = getOffsetY(); preparePen(pdfContentByte, rightPen, PdfContentByte.LINE_CAP_BUTT); if (rightPen.getLineStyleValue() == LineStyleEnum.DOUBLE) { float rightOffset = rightPen.getLineWidth(); pdfContentByte.moveTo(element.getX() + lcOffsetX + element.getWidth() + rightOffset / 3, pageFormat.getPageHeight() - element.getY() - lcOffsetY + topOffset); pdfContentByte.lineTo(element.getX() + lcOffsetX + element.getWidth() + rightOffset / 3, pageFormat.getPageHeight() - element.getY() - lcOffsetY - element.getHeight() - bottomOffset); pdfContentByte.stroke(); pdfContentByte.moveTo(element.getX() + lcOffsetX + element.getWidth() - rightOffset / 3, pageFormat.getPageHeight() - element.getY() - lcOffsetY - topOffset / 3); pdfContentByte.lineTo(element.getX() + lcOffsetX + element.getWidth() - rightOffset / 3, pageFormat.getPageHeight() - element.getY() - lcOffsetY - element.getHeight() + bottomOffset / 3); pdfContentByte.stroke(); } else { pdfContentByte.moveTo(element.getX() + lcOffsetX + element.getWidth(), pageFormat.getPageHeight() - element.getY() - lcOffsetY + topOffset); pdfContentByte.lineTo(element.getX() + lcOffsetX + element.getWidth(), pageFormat.getPageHeight() - element.getY() - lcOffsetY - element.getHeight() - bottomOffset); pdfContentByte.stroke(); } } } /** * */ private static void preparePen(PdfContentByte pdfContentByte, JRPen pen, int lineCap) { float lineWidth = pen.getLineWidth(); if (lineWidth <= 0) { return; } pdfContentByte.setLineWidth(lineWidth); pdfContentByte.setLineCap(lineCap); Color color = pen.getLineColor(); pdfContentByte.setRGBColorStroke(color.getRed(), color.getGreen(), color.getBlue()); switch (pen.getLineStyleValue()) { case DOUBLE: { pdfContentByte.setLineWidth(lineWidth / 3); pdfContentByte.setLineDash(0f); break; } case DOTTED: { switch (lineCap) { case PdfContentByte.LINE_CAP_BUTT: { pdfContentByte.setLineDash(lineWidth, lineWidth, 0f); break; } case PdfContentByte.LINE_CAP_PROJECTING_SQUARE: { pdfContentByte.setLineDash(0, 2 * lineWidth, 0f); break; } } break; } case DASHED: { switch (lineCap) { case PdfContentByte.LINE_CAP_BUTT: { pdfContentByte.setLineDash(5 * lineWidth, 3 * lineWidth, 0f); break; } case PdfContentByte.LINE_CAP_PROJECTING_SQUARE: { pdfContentByte.setLineDash(4 * lineWidth, 4 * lineWidth, 0f); break; } } break; } case SOLID: default: { pdfContentByte.setLineDash(0f); break; } } } protected static synchronized void registerFonts() { if (!fontsRegistered) { List<PropertySuffix> fontFiles = JRPropertiesUtil.getInstance(DefaultJasperReportsContext.getInstance()) .getProperties(PDF_FONT_FILES_PREFIX);//FIXMECONTEXT no default here and below if (!fontFiles.isEmpty()) { for (Iterator<PropertySuffix> i = fontFiles.iterator(); i.hasNext();) { JRPropertiesUtil.PropertySuffix font = i.next(); String file = font.getValue(); if (file.toLowerCase().endsWith(".ttc")) { FontFactory.register(file); } else { String alias = font.getSuffix(); FontFactory.register(file, alias); } } } List<PropertySuffix> fontDirs = JRPropertiesUtil.getInstance(DefaultJasperReportsContext.getInstance()) .getProperties(PDF_FONT_DIRS_PREFIX); if (!fontDirs.isEmpty()) { for (Iterator<PropertySuffix> i = fontDirs.iterator(); i.hasNext();) { JRPropertiesUtil.PropertySuffix dir = i.next(); FontFactory.registerDirectory(dir.getValue()); } } fontsRegistered = true; } } static protected class Bookmark { final PdfOutline pdfOutline; final int level; Bookmark(Bookmark parent, int x, int top, String title) { this(parent, new PdfDestination(PdfDestination.XYZ, x, top, 0), title); } Bookmark(Bookmark parent, PdfDestination destination, String title) { this.pdfOutline = new PdfOutline(parent.pdfOutline, destination, title, false); this.level = parent.level + 1; } Bookmark(PdfOutline pdfOutline, int level) { this.pdfOutline = pdfOutline; this.level = level; } } static protected class BookmarkStack { LinkedList<Bookmark> stack; BookmarkStack() { stack = new LinkedList<Bookmark>(); } void push(Bookmark bookmark) { stack.add(bookmark); } Bookmark pop() { return stack.removeLast(); } Bookmark peek() { return stack.getLast(); } } protected void initBookmarks(List<ExporterInputItem> items) { bookmarkStack = new BookmarkStack(); int rootLevel = items.size() > 1 && getCurrentConfiguration().isCreatingBatchModeBookmarks() ? -1 : 0; Bookmark bookmark = new Bookmark(pdfContentByte.getRootOutline(), rootLevel); bookmarkStack.push(bookmark); } protected void addBookmark(int level, String title, int x, int y) { Bookmark parent = bookmarkStack.peek(); // searching for parent while (parent.level >= level) { bookmarkStack.pop(); parent = bookmarkStack.peek(); } if (!getCurrentItemConfiguration().isCollapseMissingBookmarkLevels()) { // creating empty bookmarks in order to preserve the bookmark level for (int i = parent.level + 1; i < level; ++i) { Bookmark emptyBookmark = new Bookmark(parent, parent.pdfOutline.getPdfDestination(), EMPTY_BOOKMARK_TITLE); bookmarkStack.push(emptyBookmark); parent = emptyBookmark; } } int height = OrientationEnum.PORTRAIT.equals(pageFormat.getOrientation()) ? pageFormat.getPageHeight() - y : y; Bookmark bookmark = new Bookmark(parent, x, height, title); bookmarkStack.push(bookmark); } protected void setAnchor(Chunk chunk, JRPrintAnchor anchor, JRPrintElement element) { String anchorName = anchor.getAnchorName(); if (anchorName != null) { chunk.setLocalDestination(anchorName); if (anchor.getBookmarkLevel() != JRAnchor.NO_BOOKMARK) { int x = OrientationEnum.PORTRAIT.equals(pageFormat.getOrientation()) ? getOffsetX() + element.getX() : getOffsetY() + element.getY(); int y = OrientationEnum.PORTRAIT.equals(pageFormat.getOrientation()) ? getOffsetY() + element.getY() : getOffsetX() + element.getX(); addBookmark(anchor.getBookmarkLevel(), anchor.getAnchorName(), x, y); } } } public void exportFrame(JRPrintFrame frame) throws DocumentException, IOException, JRException { if (frame.getModeValue() == ModeEnum.OPAQUE) { int x = frame.getX() + getOffsetX(); int y = frame.getY() + getOffsetY(); Color backcolor = frame.getBackcolor(); pdfContentByte.setRGBColorFill(backcolor.getRed(), backcolor.getGreen(), backcolor.getBlue()); pdfContentByte.rectangle(x, pageFormat.getPageHeight() - y, frame.getWidth(), -frame.getHeight()); pdfContentByte.fill(); } setFrameElementsOffset(frame, false); try { exportElements(frame.getElements()); } finally { restoreElementOffsets(); } exportBox(frame.getLineBox(), frame); } /** * */ protected PrintPageFormat getCurrentPageFormat() { return pageFormat; } @Override protected int getOffsetX() { return super.getOffsetX() + (insideFrame() ? 0 : (crtDocumentPageNumber % 2 == 0 ? crtEvenPageOffsetX : crtOddPageOffsetX)); } @Override protected int getOffsetY() { return super.getOffsetY() + (insideFrame() ? 0 : (crtDocumentPageNumber % 2 == 0 ? crtEvenPageOffsetY : crtOddPageOffsetY)); } /** * */ class LocalFontMapper implements FontMapper { public LocalFontMapper() { } @Override public BaseFont awtToPdf(java.awt.Font font) { // not setting underline and strikethrough as we only need the base font. // underline and strikethrough will not work here because PdfGraphics2D // doesn't check the font attributes. Map<Attribute, Object> atts = new HashMap<Attribute, Object>(); atts.putAll(font.getAttributes()); return getFont(atts, null, false).getBaseFont(); } @Override public java.awt.Font pdfToAwt(BaseFont font, int size) { return null; } } /** * */ protected void exportGenericElement(JRGenericPrintElement element) { GenericElementPdfHandler handler = (GenericElementPdfHandler) GenericElementHandlerEnviroment .getInstance(getJasperReportsContext()) .getElementHandler(element.getGenericType(), PDF_EXPORTER_KEY); if (handler != null) { handler.exportElement(exporterContext, element); } else { if (log.isDebugEnabled()) { log.debug("No PDF generic element handler for " + element.getGenericType()); } } } @Override public String getExporterKey() { return PDF_EXPORTER_KEY; } @Override public String getExporterPropertiesPrefix() { return PDF_EXPORTER_PROPERTIES_PREFIX; } public static int getIntegerPermissions(String permissions) { int permission = 0; if (permissions != null && permissions.length() > 0) { String[] perms = permissions.split("\\|"); for (String perm : perms) { if (PdfPermissionsEnum.ALL.equals(PdfPermissionsEnum.getByName(perm))) { permission = PdfExporterConfiguration.ALL_PERMISSIONS; break; } if (perm != null && perm.length() > 0) { permission |= PdfPermissionsEnum.getByName(perm).getPdfPermission(); } } } return permission; } protected static class FontKey { AwtFontAttribute fontAttribute; boolean italic; boolean bold; Locale locale; public FontKey(AwtFontAttribute fontAttribute, boolean italic, boolean bold, Locale locale) { this.fontAttribute = fontAttribute; this.italic = italic; this.bold = bold; this.locale = locale; } @Override public int hashCode() { int hash = 43; hash = hash * 29 + fontAttribute.hashCode(); hash = hash * 29 + (italic ? 1231 : 1237); hash = hash * 29 + (bold ? 1231 : 1237); hash = hash * 29 + (locale == null ? 0 : locale.hashCode()); return hash; } @Override public boolean equals(Object obj) { FontKey key = (FontKey) obj; return fontAttribute.equals(key.fontAttribute) && italic == key.italic && bold == key.bold && ((locale == null) ? (key.locale == null) : (key.locale != null && locale.equals(key.locale))); } @Override public String toString() { return "{font: " + fontAttribute + ", italic: " + italic + ", bold: " + bold + "}"; } } }