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

net.sf.jasperreports.engine.export.JRPdfExporter Maven / Gradle / Ivy

There is a newer version: 6.21.3
Show newest version
/*
 * 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 .
 */

/*
 * Contributors:
 * Adrian Jackson - [email protected]
 * David Taylor - [email protected]
 * Lars Kristensen - [email protected]
 * Ling Li - [email protected]
 * Martin Clough - [email protected]
 */
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.ArrayList;
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.ibm.icu.util.StringTokenizer;
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.BaseField;
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.PdfFormField;
import com.lowagie.text.pdf.PdfGState;
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 com.lowagie.text.pdf.RadioCheckField;
import com.lowagie.text.pdf.TextField;

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.JRBoxContainer;
import net.sf.jasperreports.engine.JRCommonGraphicElement;
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.JRBasePen;
import net.sf.jasperreports.engine.base.JRBasePrintText;
import net.sf.jasperreports.engine.export.type.PdfFieldBorderStyleEnum;
import net.sf.jasperreports.engine.export.type.PdfFieldCheckTypeEnum;
import net.sf.jasperreports.engine.export.type.PdfFieldTypeEnum;
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.
 * 

* 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. *

* 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. *

* 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. *

Font Mappings

* Exporting to PDF requires mapping the fonts using three attributes: * pdfFontName, pdfEncoding and isPdfEmbedded. * 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. *

* When exporting documents to PDF, for each combination of the three fontName, * isBold, and isItalic font attributes, there must be an equivalent * combination of the PDF-related font attributes pdfFontName, pdfEncoding * and isPdfEmbedded. *

* Equivalent combination 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. *

* 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 getFont(Map, Locale, boolean) * method. *

Batch Mode Bookmarks

* 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. *

* These bookmarks have the same name as the original JasperPrint document as * specified by the jasperPrint.getName() 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. *

Encrypted PDF

* 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}): *
    *
  • {@link net.sf.jasperreports.export.PdfExporterConfiguration#isEncrypted() isEncrypted()}
  • *
  • {@link net.sf.jasperreports.export.PdfExporterConfiguration#is128BitKey() is128BitKey()}
  • *
  • {@link net.sf.jasperreports.export.PdfExporterConfiguration#getUserPassword() getUserPassword()}
  • *
  • {@link net.sf.jasperreports.export.PdfExporterConfiguration#getOwnerPassword() getOwnerPassword()}
  • *
  • {@link net.sf.jasperreports.export.PdfExporterConfiguration#getPermissions() getPermissions()}
  • *
*

PDF Version and Compression

* 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}): *
    *
  • {@link net.sf.jasperreports.export.PdfExporterConfiguration#getPdfVersion() getPdfVersion()}
  • *
  • {@link net.sf.jasperreports.export.PdfExporterConfiguration#isCompressed() isCompressed()}
  • *
* 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. *

Word Wrap and Line Break Policy

* 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. *

* 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 *

JavaScript Actions

* 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. *

* 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. *

Metadata Information

* 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: *
    *
  • {@link net.sf.jasperreports.export.PdfExporterConfiguration#getMetadataAuthor() getMetadataAuthor()}
  • *
  • {@link net.sf.jasperreports.export.PdfExporterConfiguration#getMetadataCreator() getMetadataCreator()}
  • *
  • {@link net.sf.jasperreports.export.PdfExporterConfiguration#getMetadataKeywords() getMetadataKeywords()}
  • *
  • {@link net.sf.jasperreports.export.PdfExporterConfiguration#getMetadataSubject() getMetadataSubject()}
  • *
  • {@link net.sf.jasperreports.export.PdfExporterConfiguration#getMetadataTitle() getMetadataTitle()}
  • *
*

Rendering SVG Using Shapes

* The {@link net.sf.jasperreports.export.PdfReportConfiguration#isForceSvgShapes() isForceSvgShapes()} * flag is used to force the rendering of SVG images using shapes on the PDF Graphics2D * 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. *

* 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. *

* 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 *

* public void render(JasperReportsContext jasperReportsContext, Graphics2D grx, Rectangle2D rectangle) *

* method, which should contain all the code * required for rendering the SVG on a Graphics2D context. Correct PDF font information * means that the java.awt.Font objects used to draw text on the Graphics2D * context should have PDF-related text attributes embedded so that when rendered on a PDF * Graphics2D context, the exporter can make use of them. Embedding PDF-related text * attributes into the SVG means using the following text attributes when creating * java.awt.Font to render text in the SVG renderer implementation: *

    *
  • {@link net.sf.jasperreports.engine.util.JRTextAttribute#PDF_FONT_NAME PDF_FONT_NAME}
  • *
  • {@link net.sf.jasperreports.engine.util.JRTextAttribute#PDF_ENCODING PDF_ENCODING}
  • *
  • {@link net.sf.jasperreports.engine.util.JRTextAttribute#IS_PDF_EMBEDDED IS_PDF_EMBEDDED}
  • *
*

* 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 (pdfFontName, pdfEncoding, and isPdfEmbedded) * 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. *

Section 508 Compliance

* 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. *

* 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 * (http://www.section508.gov/). *

Producing Tagged PDF Files

* 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: *
    *
  • setting to true the {@link net.sf.jasperreports.export.PdfExporterConfiguration#isTagged() isTagged()} * configuration flag
  • *
  • setting to true the {@link net.sf.jasperreports.export.PdfExporterConfiguration#PROPERTY_TAGGED net.sf.jasperreports.export.pdf.tagged} configuration property.
  • *
*

Setting the PDF File Language

* 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: *
    *
  • using the {@link net.sf.jasperreports.export.PdfExporterConfiguration#getTagLanguage() getTagLanguage()} * configuration setting to retrieve the language as a java.lang.String value;
  • *
  • 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
  • *
*

Alternate Text for Images

* 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 hyperlinkTooltipExpression * property of the image element in JRXML. *

* 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 ([email protected]) */ public class JRPdfExporter extends JRAbstractExporter { 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."; /** * */ @Property( category = PropertyConstants.CATEGORY_EXPORT, scopes = {PropertyScope.ELEMENT}, sinceVersion = PropertyConstants.VERSION_6_12_0, valueType = PdfFieldTypeEnum.class ) public static final String PDF_FIELD_TYPE = PDF_EXPORTER_PROPERTIES_PREFIX + "field.type"; /** * */ @Property( category = PropertyConstants.CATEGORY_EXPORT, scopes = {PropertyScope.ELEMENT}, sinceVersion = PropertyConstants.VERSION_6_12_0, valueType = Boolean.class, defaultValue = PropertyConstants.BOOLEAN_FALSE ) public static final String PDF_FIELD_TEXT_MULTILINE = PDF_EXPORTER_PROPERTIES_PREFIX + "field.text.multiline"; /** * */ @Property( category = PropertyConstants.CATEGORY_EXPORT, scopes = {PropertyScope.ELEMENT}, sinceVersion = PropertyConstants.VERSION_6_12_0 ) public static final String PDF_FIELD_VALUE = PDF_EXPORTER_PROPERTIES_PREFIX + "field.value"; /** * */ @Property( category = PropertyConstants.CATEGORY_EXPORT, scopes = {PropertyScope.ELEMENT}, sinceVersion = PropertyConstants.VERSION_6_12_0, valueType = PdfFieldCheckTypeEnum.class ) public static final String PDF_FIELD_CHECK_TYPE = PDF_EXPORTER_PROPERTIES_PREFIX + "field.check.type"; /** * */ @Property( category = PropertyConstants.CATEGORY_EXPORT, scopes = {PropertyScope.ELEMENT}, sinceVersion = PropertyConstants.VERSION_6_12_0 ) public static final String PDF_FIELD_NAME = PDF_EXPORTER_PROPERTIES_PREFIX + "field.name"; /** * */ @Property( category = PropertyConstants.CATEGORY_EXPORT, scopes = {PropertyScope.ELEMENT}, sinceVersion = PropertyConstants.VERSION_6_12_0, valueType = Boolean.class ) public static final String PDF_FIELD_CHECKED = PDF_EXPORTER_PROPERTIES_PREFIX + "field.checked"; /** * */ @Property( category = PropertyConstants.CATEGORY_EXPORT, scopes = {PropertyScope.ELEMENT}, sinceVersion = PropertyConstants.VERSION_6_12_0, valueType = Boolean.class ) public static final String PDF_FIELD_READ_ONLY = PDF_EXPORTER_PROPERTIES_PREFIX + "field.read.only"; /** * */ @Property( category = PropertyConstants.CATEGORY_EXPORT, scopes = {PropertyScope.GLOBAL, PropertyScope.CONTEXT, PropertyScope.REPORT, PropertyScope.ELEMENT}, sinceVersion = PropertyConstants.VERSION_6_12_0, valueType = PdfFieldBorderStyleEnum.class ) public static final String PDF_FIELD_BORDER_STYLE = PDF_EXPORTER_PROPERTIES_PREFIX + "field.border.style"; /** * */ @Property( category = PropertyConstants.CATEGORY_EXPORT, defaultValue = "|", scopes = {PropertyScope.GLOBAL, PropertyScope.CONTEXT, PropertyScope.REPORT, PropertyScope.ELEMENT}, sinceVersion = PropertyConstants.VERSION_6_12_0 ) public static final String PDF_FIELD_CHOICE_SEPARATORS = PDF_EXPORTER_PROPERTIES_PREFIX + "field.choice.separators"; /** * */ @Property( category = PropertyConstants.CATEGORY_EXPORT, scopes = {PropertyScope.ELEMENT}, sinceVersion = PropertyConstants.VERSION_6_12_0 ) public static final String PDF_FIELD_CHOICES = PDF_EXPORTER_PROPERTIES_PREFIX + "field.choices"; /** * */ @Property( category = PropertyConstants.CATEGORY_EXPORT, scopes = {PropertyScope.GLOBAL, PropertyScope.CONTEXT, PropertyScope.REPORT, PropertyScope.ELEMENT}, sinceVersion = PropertyConstants.VERSION_6_12_0, valueType = Boolean.class, defaultValue = PropertyConstants.BOOLEAN_FALSE ) public static final String PDF_FIELD_COMBO_EDIT = PDF_EXPORTER_PROPERTIES_PREFIX + "field.combo.edit"; /** * 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 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 boolean defaultIndentFirstLine; private boolean defaultJustifyLastLine; private Set glyphRendererBlocks; private boolean glyphRendererAddActualText; private PdfVersionEnum minimalVersion; private Map glyphRendererFonts; private Map radioFieldFactories; private Map radioGroups; private PdfGState[] fillAlphaStates = new PdfGState[256]; private boolean fillAlphaSet = false; private PdfGState[] strokeAlphaStates = new PdfGState[256]; private boolean strokeAlphaSet = false; /** * @see #JRPdfExporter(JasperReportsContext) */ public JRPdfExporter() { this(DefaultJasperReportsContext.getInstance()); } /** * */ public JRPdfExporter(JasperReportsContext jasperReportsContext) { super(jasperReportsContext); exporterContext = new ExporterContext(); glyphRendererFonts = new HashMap(); } @Override protected Class getConfigurationInterface() { return PdfExporterConfiguration.class; } @Override protected Class 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(); } defaultIndentFirstLine = propertiesUtil.getBooleanProperty(jasperPrint, JRPrintText.PROPERTY_AWT_INDENT_FIRST_LINE, true); defaultJustifyLastLine = propertiesUtil.getBooleanProperty(jasperPrint, JRPrintText.PROPERTY_AWT_JUSTIFY_LAST_LINE, false); crtOddPageOffsetX = configuration.getOddPageOffsetX(); crtOddPageOffsetY = configuration.getOddPageOffsetY(); crtEvenPageOffsetX = configuration.getEvenPageOffsetX(); crtEvenPageOffsetY = configuration.getEvenPageOffsetY(); initGlyphRenderer(); renderersCache = new RenderersCache(getJasperReportsContext()); loadedImagesMap = new HashMap(); } protected void initGlyphRenderer() { glyphRendererBlocks = new HashSet(); List 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 // [email protected] 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 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 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 attributes = new HashMap(); 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 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 elements = page.getElements(); exportElements(elements); if (radioGroups != null) { for (PdfFormField radioGroup : radioGroups.values()) { pdfWriter.addAnnotation(radioGroup); } radioGroups = null; radioFieldFactories = null; // radio groups that overflow unto next page don't seem to work; reset everything as it does not make sense to keep them } tagHelper.endPage(); JRExportProgressMonitor progressMonitor = getCurrentItemConfiguration().getProgressMonitor(); if (progressMonitor != null) { progressMonitor.afterPageExport(); } } protected void exportElements(Collection elements) throws DocumentException, IOException, JRException { if (elements != null && elements.size() > 0) { for(Iterator it = elements.iterator(); it.hasNext();) { JRPrintElement element = it.next(); if (filter == null || filter.isToExport(element)) { tagHelper.startElement(element); String strFieldType = element.getPropertiesMap().getProperty(PDF_FIELD_TYPE); PdfFieldTypeEnum fieldType = PdfFieldTypeEnum.getByName(strFieldType); if (fieldType == PdfFieldTypeEnum.CHECK) { exportFieldCheck(element); } else if (fieldType == PdfFieldTypeEnum.RADIO) { exportFieldRadio(element); } else 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) { if ( fieldType == PdfFieldTypeEnum.TEXT || fieldType == PdfFieldTypeEnum.COMBO || fieldType == PdfFieldTypeEnum.LIST ) { exportFieldText((JRPrintText)element, fieldType); } else { 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(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(); resetPen(); pdfContentByte.setLineDash(0f); pdfContentByte.setLineCap(PdfContentByte.LINE_CAP_PROJECTING_SQUARE); } } /** * */ protected void exportRectangle(JRPrintRectangle rectangle) { setFillColor(rectangle.getBackcolor()); preparePen(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(); } } resetPen(); resetFillColor(); pdfContentByte.setLineDash(0f); } /** * */ protected void exportEllipse(JRPrintEllipse ellipse) { setFillColor(ellipse.getBackcolor()); preparePen(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(); } } resetPen(); resetFillColor(); pdfContentByte.setLineDash(0f); } /** * */ public void exportImage(JRPrintImage printImage) throws DocumentException, IOException, JRException { if (printImage.getModeValue() == ModeEnum.OPAQUE) { setFillColor(printImage.getBackcolor()); pdfContentByte.rectangle( printImage.getX() + getOffsetX(), pageFormat.getPageHeight() - printImage.getY() - getOffsetY(), printImage.getWidth(), - printImage.getHeight() ); pdfContentByte.fill(); resetFillColor(); } 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, lowerX + imageProcessorResult.scaledWidth, upperY - imageProcessorResult.scaledHeight, 0, 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 minWidth = Math.min(normalWidth, availableImageWidth); int minHeight = Math.min(normalHeight, availableImageHeight); int xoffset = (int)(ImageUtil.getXAlignFactor(printImage) * (availableImageWidth - normalWidth)); int yoffset = (int)(ImageUtil.getYAlignFactor(printImage) * (availableImageHeight - normalHeight)); int translateX = xoffset; int translateY = yoffset; int angle = 0; switch (printImage.getRotation()) { case LEFT : { minWidth = Math.min(normalWidth, availableImageHeight); minHeight = Math.min(normalHeight, availableImageWidth); xoffset = (int)(ImageUtil.getYAlignFactor(printImage) * (availableImageWidth - normalHeight)); yoffset = (int)((1f - ImageUtil.getXAlignFactor(printImage)) * (availableImageHeight - normalWidth)); translateX = (int)(ImageUtil.getXAlignFactor(printImage) * (availableImageHeight - normalWidth)); translateY = xoffset; angle = 90; break; } case RIGHT : { minWidth = Math.min(normalWidth, availableImageHeight); minHeight = Math.min(normalHeight, availableImageWidth); xoffset = (int)((1f - ImageUtil.getYAlignFactor(printImage)) * (availableImageWidth - normalHeight)); yoffset = (int)(ImageUtil.getXAlignFactor(printImage) * (availableImageHeight - normalWidth)); translateX = yoffset; translateY = (int)(ImageUtil.getYAlignFactor(printImage) * (availableImageWidth - normalHeight)); angle = -90; break; } case UPSIDE_DOWN : { minWidth = Math.min(normalWidth, availableImageWidth); minHeight = Math.min(normalHeight, availableImageHeight); xoffset = (int)((1f - ImageUtil.getXAlignFactor(printImage)) * (availableImageWidth - normalWidth)); yoffset = (int)((1f - ImageUtil.getYAlignFactor(printImage)) * (availableImageHeight - normalHeight)); translateX = (int)(ImageUtil.getXAlignFactor(printImage) * (availableImageWidth - normalWidth)); translateY = (int)(ImageUtil.getYAlignFactor(printImage) * (availableImageHeight - normalHeight)); angle = 180; break; } case NONE : default : { } } 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( translateX > 0 ? 0 : translateX, translateY > 0 ? 0 : translateY, normalWidth, normalHeight ) ); } finally { g.dispose(); } //awtImage = bi.getSubimage(0, 0, minWidth, minHeight); //image = com.lowagie.text.Image.getInstance(awtImage, printImage.getBackcolor()); Image image = Image.getInstance(bi, null); image.setRotationDegrees(angle); return new InternalImageProcessorResult( new Chunk(image, 0, 0), image.getScaledWidth(), image.getScaledHeight(), xoffset < 0 ? 0 : xoffset, yoffset < 0 ? 0 : 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); } } switch (printImage.getRotation()) { case LEFT : { image.scaleAbsolute(availableImageHeight, availableImageWidth); image.setRotationDegrees(90); break; } case RIGHT : { image.scaleAbsolute(availableImageHeight, availableImageWidth); image.setRotationDegrees(-90); break; } case UPSIDE_DOWN : { image.scaleAbsolute(availableImageWidth, availableImageHeight); image.setRotationDegrees(180); break; } case NONE : default : { 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); } } int xoffset = 0; int yoffset = 0; image.setRotationDegrees(0); // reset in case the image is from cache switch (printImage.getRotation()) { case LEFT : { image.scaleToFit(availableImageHeight, availableImageWidth); image.setRotationDegrees(90); xoffset = (int)(ImageUtil.getYAlignFactor(printImage) * (availableImageWidth - image.getPlainHeight())); yoffset = (int)((1f - ImageUtil.getXAlignFactor(printImage)) * (availableImageHeight - image.getPlainWidth())); break; } case RIGHT : { image.scaleToFit(availableImageHeight, availableImageWidth); image.setRotationDegrees(-90); xoffset = (int)((1f - ImageUtil.getYAlignFactor(printImage)) * (availableImageWidth - image.getPlainHeight())); yoffset = (int)(ImageUtil.getXAlignFactor(printImage) * (availableImageHeight - image.getPlainWidth())); break; } case UPSIDE_DOWN : { image.scaleToFit(availableImageWidth, availableImageHeight); image.setRotationDegrees(180); xoffset = (int)((1f - ImageUtil.getXAlignFactor(printImage)) * (availableImageWidth - image.getPlainWidth())); yoffset = (int)((1f - ImageUtil.getYAlignFactor(printImage)) * (availableImageHeight - image.getPlainHeight())); break; } case NONE : default : { image.scaleToFit(availableImageWidth, availableImageHeight); xoffset = (int)(ImageUtil.getXAlignFactor(printImage) * (availableImageWidth - image.getPlainWidth())); 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; int translateX = 0; int translateY = 0; double templateWidth = 0; double templateHeight = 0; double renderWidth = 0; double renderHeight = 0; double ratioX = 1f; double ratioY = 1f; double angle = 0; switch (printImage.getScaleImageValue()) { case CLIP: { Dimension2D dimension = renderer instanceof DimensionRenderable ? ((DimensionRenderable)renderer).getDimension(jasperReportsContext) : null; if (dimension != null) { renderWidth = dimension.getWidth(); renderHeight = dimension.getHeight(); } templateWidth = availableImageWidth; templateHeight = availableImageHeight; switch (printImage.getRotation()) { case LEFT: if (dimension == null) { renderWidth = availableImageHeight; renderHeight = availableImageWidth; } translateX = (int)(ImageUtil.getYAlignFactor(printImage) * (availableImageWidth - renderHeight)); translateY = availableImageHeight - (int)(ImageUtil.getXAlignFactor(printImage) * (availableImageHeight - renderWidth)); angle = - Math.PI / 2; break; case RIGHT: if (dimension == null) { renderWidth = availableImageHeight; renderHeight = availableImageWidth; } translateX = availableImageWidth - (int)(ImageUtil.getYAlignFactor(printImage) * (availableImageWidth - renderHeight)); translateY = (int)(ImageUtil.getXAlignFactor(printImage) * (availableImageHeight - renderWidth)); angle = Math.PI / 2; break; case UPSIDE_DOWN: if (dimension == null) { renderWidth = availableImageWidth; renderHeight = availableImageHeight; } translateX = availableImageWidth - (int)(ImageUtil.getXAlignFactor(printImage) * (availableImageWidth - renderWidth)); translateY = availableImageHeight - (int)(ImageUtil.getYAlignFactor(printImage) * (availableImageHeight - renderHeight)); angle = Math.PI; break; case NONE: default: if (dimension == null) { renderWidth = availableImageWidth; renderHeight = availableImageHeight; } translateX = (int) (ImageUtil.getXAlignFactor(printImage) * (availableImageWidth - renderWidth)); translateY = (int) (ImageUtil.getYAlignFactor(printImage) * (availableImageHeight - renderHeight)); angle = 0; break; } break; } case FILL_FRAME: { templateWidth = availableImageWidth; templateHeight = availableImageHeight; switch (printImage.getRotation()) { case LEFT: renderWidth = availableImageHeight; renderHeight = availableImageWidth; translateX = 0; translateY = availableImageHeight; angle = - Math.PI / 2; break; case RIGHT: renderWidth = availableImageHeight; renderHeight = availableImageWidth; translateX = availableImageWidth; translateY = 0; angle = Math.PI / 2; break; case UPSIDE_DOWN: renderWidth = availableImageWidth; renderHeight = availableImageHeight; translateX = availableImageWidth; translateY = availableImageHeight; angle = Math.PI; break; case NONE: default: renderWidth = availableImageWidth; renderHeight = availableImageHeight; translateX = 0; translateY = 0; angle = 0; break; } break; } case RETAIN_SHAPE: default: { Dimension2D dimension = renderer instanceof DimensionRenderable ? ((DimensionRenderable)renderer).getDimension(jasperReportsContext) : null; if (dimension != null) { renderWidth = dimension.getWidth(); renderHeight = dimension.getHeight(); } switch (printImage.getRotation()) { case LEFT: if (dimension == null) { renderWidth = availableImageHeight; renderHeight = availableImageWidth; } ratioX = availableImageWidth / renderHeight; ratioY = availableImageHeight / renderWidth; ratioX = ratioX < ratioY ? ratioX : ratioY; ratioY = ratioX; templateWidth = renderHeight; templateHeight = renderWidth; translateX = 0; translateY = (int)renderWidth; xoffset = (int) (ImageUtil.getYAlignFactor(printImage) * (availableImageWidth - renderHeight * ratioX)); yoffset = (int) (ImageUtil.getXAlignFactor(printImage) * (availableImageHeight - renderWidth * ratioY)); angle = - Math.PI / 2; break; case RIGHT: if (dimension == null) { renderWidth = availableImageHeight; renderHeight = availableImageWidth; } ratioX = availableImageWidth / renderHeight; ratioY = availableImageHeight / renderWidth; ratioX = ratioX < ratioY ? ratioX : ratioY; ratioY = ratioX; templateWidth = renderHeight; templateHeight = renderWidth; translateX = (int)renderHeight; translateY = 0; xoffset = (int) ((1f - ImageUtil.getYAlignFactor(printImage)) * (availableImageWidth - renderHeight * ratioX)); yoffset = (int) ((1f - ImageUtil.getXAlignFactor(printImage)) * (availableImageHeight - renderWidth * ratioY)); angle = Math.PI / 2; break; case UPSIDE_DOWN: if (dimension == null) { renderWidth = availableImageWidth; renderHeight = availableImageHeight; } ratioX = availableImageWidth / renderWidth; ratioY = availableImageHeight / renderHeight; ratioX = ratioX < ratioY ? ratioX : ratioY; ratioY = ratioX; templateWidth = renderWidth; templateHeight = renderHeight; translateX = (int)renderWidth; translateY = (int)renderHeight; xoffset = (int) ((1f - ImageUtil.getXAlignFactor(printImage)) * (availableImageWidth - renderWidth * ratioX)); yoffset = (int) (ImageUtil.getYAlignFactor(printImage) * (availableImageHeight - renderHeight * ratioY)); angle = Math.PI; break; case NONE: default: if (dimension == null) { renderWidth = availableImageWidth; renderHeight = availableImageHeight; } ratioX = availableImageWidth / renderWidth; ratioY = availableImageHeight / renderHeight; ratioX = ratioX < ratioY ? ratioX : ratioY; ratioY = ratioX; templateWidth = renderWidth; templateHeight = renderHeight; translateX = 0; translateY = 0; xoffset = (int) (ImageUtil.getXAlignFactor(printImage) * (availableImageWidth - renderWidth * ratioX)); yoffset = (int) ((1f - ImageUtil.getYAlignFactor(printImage)) * (availableImageHeight - renderHeight * ratioY)); angle = 0; break; } break; } } PdfTemplate template = pdfContentByte.createTemplate((float)templateWidth, (float)templateHeight); Graphics2D g = getCurrentItemConfiguration().isForceSvgShapes() ? template.createGraphicsShapes((float)templateWidth, (float)templateHeight) : template.createGraphics((float)templateWidth, (float)templateHeight, new LocalFontMapper()); try { g.translate( translateX, translateY ); if (angle != 0) { g.rotate(angle); } if (printImage.getModeValue() == ModeEnum.OPAQUE) { g.setColor(printImage.getBackcolor()); g.fillRect(0, 0, (int)renderWidth, (int)renderHeight); } renderer.render(jasperReportsContext, g, new Rectangle2D.Double(0, 0, renderWidth, renderHeight)); } 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() - availableImageHeight + yoffset ); pdfContentByte.restoreState(); Image image = getPxImage(); image.scaleAbsolute(availableImageWidth, availableImageHeight); InternalImageProcessorResult result = new InternalImageProcessorResult( new Chunk(image, 0, 0), availableImageWidth, availableImageHeight, 0, 0 ); 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 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 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 textAttributes) { Integer underline = (Integer) textAttributes.get(TextAttribute.UNDERLINE); return TextAttribute.UNDERLINE_ON.equals(underline); } protected boolean hasStrikethrough(Map 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 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(); setFillColor(backcolor); pdfContentByte.rectangle( textRenderer.getX(), pageFormat.getPageHeight() - textRenderer.getY(), textRenderer.getWidth(), - textRenderer.getHeight() ); pdfContentByte.fill(); resetFillColor(); } if (glyphRendererAddActualText && textRenderer instanceof PdfGlyphRenderer) { tagHelper.startText(styledText.getText(), text.getLinkType() != null); } else { tagHelper.startText(text.getLinkType() != null); } int forecolorAlpha = getSingleForecolorAlpha(styledText); setFillColorAlpha(forecolorAlpha); /* rendering only non empty texts */ if (styledText.length() > 0) { textRenderer.render(); } tagHelper.endText(); resetFillColor(); atrans = new AffineTransform(); atrans.rotate(-angle, textRenderer.getX(), pageFormat.getPageHeight() - textRenderer.getY()); pdfContentByte.transform(atrans); /* */ exportBox( text.getLineBox(), text ); } protected int getSingleForecolorAlpha(JRStyledText styledText) { Color forecolor = (Color) styledText.getGlobalAttributes().get(TextAttribute.FOREGROUND); if (forecolor == null || forecolor.getAlpha() == 255) { return 255; } List runs = styledText.getRuns(); if (runs.size() > 1) { for (JRStyledText.Run run : runs) { Color runForecolor = (Color) run.attributes.get(TextAttribute.FOREGROUND); if (runForecolor != null && runForecolor.getAlpha() != forecolor.getAlpha()) { //per run alpha currently not working because there's no support in Chunk //falling back to opaque return 255; } } } return forecolor.getAlpha(); } /** * */ public void exportFieldText(JRPrintText text, PdfFieldTypeEnum fieldType) throws DocumentException { Rectangle rectangle = new Rectangle( text.getX() + exporterContext.getOffsetX(), jasperPrint.getPageHeight() - text.getY() - exporterContext.getOffsetY(), text.getX() + exporterContext.getOffsetX() + text.getWidth(), jasperPrint.getPageHeight() - text.getY() - exporterContext.getOffsetY() - text.getHeight() ); String fieldName = text.getPropertiesMap().getProperty(PDF_FIELD_NAME); fieldName = fieldName == null || fieldName.trim().length() == 0 ? "FIELD_" + text.getUUID() : fieldName; TextField pdfTextField = new TextField(pdfWriter, rectangle, fieldName); if (ModeEnum.OPAQUE == text.getModeValue()) { pdfTextField.setBackgroundColor(text.getBackcolor()); } pdfTextField.setTextColor(text.getForecolor()); switch (text.getHorizontalTextAlign()) { case RIGHT : pdfTextField.setAlignment(Element.ALIGN_RIGHT); break; case CENTER : pdfTextField.setAlignment(Element.ALIGN_CENTER); break; case JUSTIFIED : pdfTextField.setAlignment(Element.ALIGN_JUSTIFIED); break; case LEFT : default : pdfTextField.setAlignment(Element.ALIGN_LEFT); } JRPen pen = getFieldPen(text); if (pen != null) { float borderWidth = Math.round(pen.getLineWidth()); borderWidth = borderWidth > BaseField.BORDER_WIDTH_THICK ? BaseField.BORDER_WIDTH_THICK : borderWidth; if (borderWidth > 0) { pdfTextField.setBorderColor(pen.getLineColor()); pdfTextField.setBorderWidth(borderWidth); String strBorderStyle = propertiesUtil.getProperty(PDF_FIELD_BORDER_STYLE, text, jasperPrint); PdfFieldBorderStyleEnum borderStyle = PdfFieldBorderStyleEnum.getByName(strBorderStyle); if (borderStyle == null) { borderStyle = pen.getLineStyleValue() == LineStyleEnum.DASHED ? PdfFieldBorderStyleEnum.DASHED : PdfFieldBorderStyleEnum.SOLID; } pdfTextField.setBorderStyle(borderStyle.getValue()); } } String value = null; if (text.getPropertiesMap().containsProperty(PDF_FIELD_VALUE)) { value = text.getPropertiesMap().getProperty(PDF_FIELD_VALUE); } else { value = text.getFullText(); } if ( fieldType == PdfFieldTypeEnum.COMBO || fieldType == PdfFieldTypeEnum.LIST ) { //pdfTextField.setChoiceExports(new String[]{"one", "two", "three"}); String[] choices = null; String strChoices = text.getPropertiesMap().getProperty(PDF_FIELD_CHOICES); if (strChoices != null && strChoices.trim().length() > 0) { String choiceSeparators = propertiesUtil.getProperty(PDF_FIELD_CHOICE_SEPARATORS, text, jasperPrint); StringTokenizer tkzer = new StringTokenizer(strChoices, choiceSeparators); List choicesList = new ArrayList(); while (tkzer.hasMoreTokens()) { choicesList.add(tkzer.nextToken()); } choices = choicesList.toArray(new String[choicesList.size()]); pdfTextField.setChoices(choices); } if ( fieldType == PdfFieldTypeEnum.COMBO && propertiesUtil.getBooleanProperty(PDF_FIELD_COMBO_EDIT, false, text, jasperPrint) ) { pdfTextField.setOptions(pdfTextField.getOptions() | TextField.EDIT); } if (value != null && choices != null) { int i = 0; for (String choice : choices) { if (value.equals(choice)) { pdfTextField.setChoiceSelection(i); break; } i++; } } } else { if (value != null) { pdfTextField.setText(value); } // pdfTextField.setDefaultText("default:" + text.getFullText()); } String readOnly = text.getPropertiesMap().getProperty(PDF_FIELD_READ_ONLY); if (readOnly != null) { if (Boolean.valueOf(readOnly)) { pdfTextField.setOptions(pdfTextField.getOptions() | TextField.READ_ONLY); } } // pdfTextField.setExtraMargin(0, 0); Map attributes = new HashMap(); fontUtil.getAttributesWithoutAwtFont(attributes, text); Font pdfFont = getFont(attributes, getLocale(), false); pdfTextField.setFont(pdfFont.getBaseFont()); pdfTextField.setFontSize(text.getFontsize()); // pdfTextField.setExtensionFont(pdfFont.getBaseFont()); boolean isMultiLine = JRPropertiesUtil.asBoolean(text.getPropertiesMap().getProperty(PDF_FIELD_TEXT_MULTILINE), false); if (isMultiLine) { pdfTextField.setOptions(pdfTextField.getOptions() | TextField.MULTILINE); } // text.setRotation(90); pdfTextField.setVisibility(TextField.VISIBLE); PdfFormField field = null; try { field = fieldType == PdfFieldTypeEnum.COMBO ? pdfTextField.getComboField() : (fieldType == PdfFieldTypeEnum.LIST ? pdfTextField.getListField() : pdfTextField.getTextField()); } catch (IOException e) { throw new JRRuntimeException(e); } pdfWriter.addAnnotation(field); } /** * */ public void exportFieldCheck(JRPrintElement element) throws DocumentException { Rectangle rectangle = new Rectangle( element.getX() + exporterContext.getOffsetX(), jasperPrint.getPageHeight() - element.getY() - exporterContext.getOffsetY(), element.getX() + exporterContext.getOffsetX() + element.getWidth(), jasperPrint.getPageHeight() - element.getY() - exporterContext.getOffsetY() - element.getHeight() ); String fieldName = element.getPropertiesMap().getProperty(PDF_FIELD_NAME); fieldName = fieldName == null || fieldName.trim().length() == 0 ? "FIELD_" + element.getUUID() : fieldName; RadioCheckField checkField = new RadioCheckField(pdfWriter, rectangle, fieldName, "checked"); PdfFieldCheckTypeEnum checkType = PdfFieldCheckTypeEnum.getByName(element.getPropertiesMap().getProperty(PDF_FIELD_CHECK_TYPE)); if (checkType != null) { checkField.setCheckType(checkType.getValue()); } if (ModeEnum.OPAQUE == element.getModeValue()) { checkField.setBackgroundColor(element.getBackcolor()); } checkField.setTextColor(element.getForecolor()); JRPen pen = getFieldPen(element); if (pen != null) { float borderWidth = Math.round(pen.getLineWidth()); borderWidth = borderWidth > BaseField.BORDER_WIDTH_THICK ? BaseField.BORDER_WIDTH_THICK : borderWidth; if (borderWidth > 0) { checkField.setBorderColor(pen.getLineColor()); checkField.setBorderWidth(borderWidth); String strBorderStyle = propertiesUtil.getProperty(PDF_FIELD_BORDER_STYLE, element, jasperPrint); PdfFieldBorderStyleEnum borderStyle = PdfFieldBorderStyleEnum.getByName(strBorderStyle); if (borderStyle == null) { borderStyle = pen.getLineStyleValue() == LineStyleEnum.DASHED ? PdfFieldBorderStyleEnum.DASHED : PdfFieldBorderStyleEnum.SOLID; } checkField.setBorderStyle(borderStyle.getValue()); } } String checked = element.getPropertiesMap().getProperty(PDF_FIELD_CHECKED); if (checked != null) { checkField.setChecked(Boolean.valueOf(checked)); } String readOnly = element.getPropertiesMap().getProperty(PDF_FIELD_READ_ONLY); if (readOnly != null) { if (Boolean.valueOf(readOnly)) { checkField.setOptions(checkField.getOptions() | TextField.READ_ONLY); } } PdfFormField ck = null; try { ck = checkField.getCheckField(); } catch (Exception e) { throw new JRRuntimeException(e); } pdfWriter.addAnnotation(ck); } /** * */ public void exportFieldRadio(JRPrintElement element) throws DocumentException { Rectangle rectangle = new Rectangle( element.getX() + exporterContext.getOffsetX(), jasperPrint.getPageHeight() - element.getY() - exporterContext.getOffsetY(), element.getX() + exporterContext.getOffsetX() + element.getWidth(), jasperPrint.getPageHeight() - element.getY() - exporterContext.getOffsetY() - element.getHeight() ); String fieldName = element.getPropertiesMap().getProperty(PDF_FIELD_NAME); fieldName = fieldName == null || fieldName.trim().length() == 0 ? "FIELD_" + element.getUUID() : fieldName; RadioCheckField radioField = radioFieldFactories == null ? null : radioFieldFactories.get(fieldName); if (radioField == null) { radioField = new RadioCheckField(pdfWriter, rectangle, fieldName, "FIELD_" + element.getUUID()); if (radioFieldFactories == null) { radioFieldFactories = new HashMap(); } radioFieldFactories.put(fieldName, radioField); } PdfFieldCheckTypeEnum checkType = PdfFieldCheckTypeEnum.getByName(element.getPropertiesMap().getProperty(PDF_FIELD_CHECK_TYPE)); if (checkType != null) { radioField.setCheckType(checkType.getValue()); } radioField.setBox(rectangle); if (ModeEnum.OPAQUE == element.getModeValue()) { radioField.setBackgroundColor(element.getBackcolor()); } radioField.setTextColor(element.getForecolor()); JRPen pen = getFieldPen(element); if (pen != null) { float borderWidth = Math.round(pen.getLineWidth()); borderWidth = borderWidth > BaseField.BORDER_WIDTH_THICK ? BaseField.BORDER_WIDTH_THICK : borderWidth; if (borderWidth > 0) { radioField.setBorderColor(pen.getLineColor()); radioField.setBorderWidth(borderWidth); String strBorderStyle = propertiesUtil.getProperty(PDF_FIELD_BORDER_STYLE, element, jasperPrint); PdfFieldBorderStyleEnum borderStyle = PdfFieldBorderStyleEnum.getByName(strBorderStyle); if (borderStyle == null) { borderStyle = pen.getLineStyleValue() == LineStyleEnum.DASHED ? PdfFieldBorderStyleEnum.DASHED : PdfFieldBorderStyleEnum.SOLID; } radioField.setBorderStyle(borderStyle.getValue()); } } radioField.setOnValue("FIELD_" + element.getUUID()); String checked = element.getPropertiesMap().getProperty(PDF_FIELD_CHECKED); radioField.setChecked(Boolean.valueOf(checked)); // need to set to false if previous button was checked // setting the read-only option has to occur before the getRadioGroup() call String readOnly = element.getPropertiesMap().getProperty(PDF_FIELD_READ_ONLY); if (readOnly != null) { if (Boolean.valueOf(readOnly)) { radioField.setOptions(radioField.getOptions() | TextField.READ_ONLY); } } PdfFormField radioGroup = radioGroups == null ? null : radioGroups.get(fieldName); if (radioGroup == null) { if (radioGroups == null) { radioGroups = new HashMap(); } radioGroup = radioField.getRadioGroup(true, false); radioGroups.put(fieldName, radioGroup); } try { radioGroup.addKid(radioField.getRadioField()); } catch (Exception e) { throw new JRRuntimeException(e); } } protected JRPen getFieldPen(JRPrintElement element) { JRPen pen = null; JRLineBox box = element instanceof JRBoxContainer ? ((JRBoxContainer)element).getLineBox() : null; if (box == null) { pen = element instanceof JRCommonGraphicElement ? ((JRCommonGraphicElement)element).getLinePen() : null; } else { Float lineWidth = box.getPen().getLineWidth(); if (lineWidth == 0) { // PDF fields do not support side borders // in case side borders are defined for the report element, ensure that all 4 are declared and all of them come with the same settings if( ((JRBasePen)box.getTopPen()).isIdentical(box.getLeftPen()) && ((JRBasePen)box.getTopPen()).isIdentical(box.getBottomPen()) && ((JRBasePen)box.getTopPen()).isIdentical(box.getRightPen()) && box.getTopPen().getLineWidth() > 0 ) { pen = new JRBasePen(box); pen.setLineWidth(box.getTopPen().getLineWidth()); pen.setLineColor(box.getTopPen().getLineColor()); pen.setLineStyle(box.getTopPen().getLineStyleValue()); } } else { pen = new JRBasePen(box); pen.setLineWidth(lineWidth); pen.setLineColor(box.getPen().getLineColor()); pen.setLineStyle(box.getPen().getLineStyleValue()); } } return pen; } 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, defaultIndentFirstLine, defaultJustifyLastLine ); } else if (text.getLeadingOffset() == 0) { // leading offset is non-zero only for multiline texts that have at least one tab character or some paragraph indent (first, left or right) textRenderer = new PdfTextRenderer( jasperReportsContext, awtIgnoreMissingFont, defaultIndentFirstLine, defaultJustifyLastLine );//FIXMENOW make some reusable instances here and below } else { textRenderer = new SimplePdfTextRenderer( jasperReportsContext, awtIgnoreMissingFont, defaultIndentFirstLine, defaultJustifyLastLine );//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 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 fontAttributes = new HashMap(); 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(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(); } resetPen(); } } /** * */ 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(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(); } resetPen(); } } /** * */ 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(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(); } resetPen(); } } /** * */ 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(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(); } resetPen(); } } /** * */ private void preparePen(JRPen pen, int lineCap) { float lineWidth = pen.getLineWidth(); if (lineWidth <= 0) { return; } pdfContentByte.setLineWidth(lineWidth); pdfContentByte.setLineCap(lineCap); Color color = pen.getLineColor(); setStrokeColor(color); 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; } } } private void resetPen() { resetStrokeColor(); } protected void setStrokeColor(Color color) { int alpha = color.getAlpha(); if (alpha != 255) { setStrokeAlpha(alpha); strokeAlphaSet = true; } pdfContentByte.setRGBColorStroke( color.getRed(), color.getGreen(), color.getBlue()); } protected void resetStrokeColor() { if (strokeAlphaSet) { setStrokeAlpha(255); strokeAlphaSet = false; } } protected void setStrokeAlpha(int alpha) { PdfGState state = strokeAlphaStates[alpha]; if (state == null) { state = new PdfGState(); state.setStrokeOpacity(((float) alpha)/255); strokeAlphaStates[alpha] = state; } pdfContentByte.setGState(state); } protected void setFillColor(Color color) { setFillColorAlpha(color.getAlpha()); pdfContentByte.setRGBColorFill( color.getRed(), color.getGreen(), color.getBlue()); } protected void setFillColorAlpha(int alpha) { if (alpha != 255) { setFillAlpha(alpha); fillAlphaSet = true; } } protected void resetFillColor() { if (fillAlphaSet) { setFillAlpha(255); fillAlphaSet = false; } } protected void setFillAlpha(int alpha) { PdfGState state = fillAlphaStates[alpha]; if (state == null) { state = new PdfGState(); state.setFillOpacity(((float) alpha)/255); fillAlphaStates[alpha] = state; } pdfContentByte.setGState(state); } protected static synchronized void registerFonts () { if (!fontsRegistered) { List fontFiles = JRPropertiesUtil.getInstance(DefaultJasperReportsContext.getInstance()).getProperties(PDF_FONT_FILES_PREFIX);//FIXMECONTEXT no default here and below if (!fontFiles.isEmpty()) { for (Iterator 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 fontDirs = JRPropertiesUtil.getInstance(DefaultJasperReportsContext.getInstance()).getProperties(PDF_FONT_DIRS_PREFIX); if (!fontDirs.isEmpty()) { for (Iterator 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 stack; BookmarkStack() { stack = new LinkedList(); } void push(Bookmark bookmark) { stack.add(bookmark); } Bookmark pop() { return stack.removeLast(); } Bookmark peek() { return stack.getLast(); } } protected void initBookmarks(List 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(); setFillColor(backcolor); pdfContentByte.rectangle( x, pageFormat.getPageHeight() - y, frame.getWidth(), - frame.getHeight() ); pdfContentByte.fill(); resetFillColor(); } 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 atts = new HashMap(); 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 + "}"; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy