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

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

There is a newer version: 6.21.3
Show newest version
/*
 * JasperReports - Free Java Reporting Library.
 * Copyright (C) 2001 - 2014 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.util.Stack;

import net.sf.jasperreports.crosstabs.JRCellContents;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintFrame;
import net.sf.jasperreports.engine.JRPrintImage;

import com.itextpdf.text.pdf.PdfAConformanceLevel;
import com.itextpdf.text.pdf.PdfArray;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfDictionary;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfNumber;
import com.itextpdf.text.pdf.PdfString;
import com.itextpdf.text.pdf.PdfStructureElement;
import com.itextpdf.text.pdf.PdfStructureTreeRoot;
import com.itextpdf.text.pdf.PdfWriter;


/**
 * Provides support for tagged PDF documents.
 * 

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

Marking Headings

* JasperReports currently supports specifying type 1, 2 and 3 level headings. * In order to mark a text field as a level 1 heading, the following custom element property * should be used in JRXML: *

* <property name="net.sf.jasperreports.export.pdf.tag.h1" value="full"/> *

* Value full means that a full <H1> tag will be embedded in the PDF wrapping the * current text element. * If two or more text fields make up a single level 1 heading, there are two ways to mark * the heading: *

    *
  • In the first, the text elements making up the heading are placed inside a frame and * the frame is marked with the following custom property: *
    * <property name="net.sf.jasperreports.export.pdf.tag.h1" value="full"/>
  • *
  • In the second, the first element of the heading (respective to the Z-Order, or the * order in which the elements appear in JRXML) is tagged with: *
    * <property name="net.sf.jasperreports.export.pdf.tag.h1" value="start"/> *
    * and the last element from the heading (respective to the same order) is marked with *
    * <property name="net.sf.jasperreports.export.pdf.tag.h1" value="end"/>
  • *
*

* Level 2 and level 3 headings are marked the same way, except that the properties are: *

* net.sf.jasperreports.export.pdf.tag.h2 *

* and *

* net.sf.jasperreports.export.pdf.tag.h3. *

Marking Tables

* Tables are comprised of column headers, row headers, and a data section. Each table * section is made of cells. Marking table structures in PDF is similar to the way tables are * described in HTML and uses the same techniques as those for marking headings. *

* When marking a table, the user has to indicate in the report template where the table * starts and where it ends. *

* If the entire table is placed in a container, such as a frame element, marking the table * requires only marking the parent frame with the following custom element property: *

* <property name="net.sf.jasperreports.export.pdf.tag.table" value="full"/> *

* However, most of the time, tables cannot be isolated in a frame unless they are * subreports, because they generally span multiple report sections and bands. In such * cases, marking a table requires marking in JRXML the first and last element making up * the table structure. *

* The first element of the table (probably the first element in the table header) should be * marked with the following custom property: *

* <property name="net.sf.jasperreports.export.pdf.tag.table" value="start"/> *

* The last element of the table should be marked with: *

* <property name="net.sf.jasperreports.export.pdf.tag.table" value="end"/> *

* Tables are made of rows, and each row has to be precisely delimited within the table * structure. This includes the column header rows at the top of the table. Similar to the * headings and table marking, a table row can be identified in two ways: *

    *
  • If the entire content that makes up the row is isolated within a frame, the frame can be * marked with the following custom property: *
    * <property name="net.sf.jasperreports.export.pdf.tag.tr" value="full"/> *
  • *
  • If the content of the row is not grouped in a container frame, its first and last elements * (respective to the Z-order or the order in which they appear in JRXML) have to be * marked with the following custom properties: *
    * <property name="net.sf.jasperreports.export.pdf.tag.tr" value="start"/> *
    * for the first element and *
    * <property name="net.sf.jasperreports.export.pdf.tag.tr" value="start"/> *
    * for the last element.
  • *
*

* Each table row can contain header cells or data cells. Regardless of their type, and * similar to headings, tables, and table rows, cells can be marked either by marking a * single element representing the cell content (this single cell element can actually be a * frame element), or by marking the first and last element from the cell content. *

* Header cells made of a single element (this single element can actually be a frame) are * marked with *

* <property name="net.sf.jasperreports.export.pdf.tag.th" value="full"/> *

* A header cell made of multiple elements is marked with *

* <property name="net.sf.jasperreports.export.pdf.tag.th" value="start"/> * on its first element and *

* <property name="net.sf.jasperreports.export.pdf.tag.th" value="end"/> * on its last element. *

* Normal data cells made of a single element (that can be frame) are marked with *

* <property name="net.sf.jasperreports.export.pdf.tag.td" value="full"/> *

* Normal data cells made of multiple elements are marked with *

* <property name="net.sf.jasperreports.export.pdf.tag.td" value="start"/> * on their first element and *

* <property name="net.sf.jasperreports.export.pdf.tag.td" value="end"/> * on their last element. *

* Just as in HTML tables, cells can span multiple rows and/or columns. Column span and * row span values for the current table cell can be specified using the following custom * properties on the same element where the cell start was marked (the element with the * full or start property marking the cell): *

* <property name="net.sf.jasperreports.export.pdf.tag.colspan" value="<number>"/> *

* <property name="net.sf.jasperreports.export.pdf.tag.rowspan" value="<number>"/> *

PDF Content Reading Order

* JasperReports uses the Z-order of the elements as present in the report template * (JRXML) to control reading order in the resulting PDF files. This is usually the intended * way for the documents to be read, so no specific modifications were required in order to * achieve it. * * @author Teodor Danciu ([email protected]) */ public class JRPdfExporterTagHelper { public static final String PROPERTY_TAG_TABLE = JRPdfExporter.PDF_EXPORTER_PROPERTIES_PREFIX + "tag.table"; public static final String PROPERTY_TAG_TR = JRPdfExporter.PDF_EXPORTER_PROPERTIES_PREFIX + "tag.tr"; public static final String PROPERTY_TAG_TH = JRPdfExporter.PDF_EXPORTER_PROPERTIES_PREFIX + "tag.th"; public static final String PROPERTY_TAG_TD = JRPdfExporter.PDF_EXPORTER_PROPERTIES_PREFIX + "tag.td"; public static final String PROPERTY_TAG_H1 = JRPdfExporter.PDF_EXPORTER_PROPERTIES_PREFIX + "tag.h1"; public static final String PROPERTY_TAG_H2 = JRPdfExporter.PDF_EXPORTER_PROPERTIES_PREFIX + "tag.h2"; public static final String PROPERTY_TAG_H3 = JRPdfExporter.PDF_EXPORTER_PROPERTIES_PREFIX + "tag.h3"; public static final String PROPERTY_TAG_COLSPAN = JRPdfExporter.PDF_EXPORTER_PROPERTIES_PREFIX + "tag.colspan"; public static final String PROPERTY_TAG_ROWSPAN = JRPdfExporter.PDF_EXPORTER_PROPERTIES_PREFIX + "tag.rowspan"; public static final String TAG_START = "start"; public static final String TAG_END = "end"; public static final String TAG_FULL = "full"; protected JRPdfExporter exporter; protected PdfContentByte pdfContentByte; protected PdfWriter pdfWriter; protected PdfStructureElement allTag; protected Stack tagStack; protected boolean isTagEmpty = true; protected int crtCrosstabRowY = -1; protected int crosstabFrameDepth; protected boolean isDataCellPrinted; protected boolean isTagged; protected String language; protected PdfAConformanceLevel conformanceLevel; /** * */ protected JRPdfExporterTagHelper(JRPdfExporter exporter) { this.exporter = exporter; } /** * */ protected void setTagged(boolean isTagged) { this.isTagged = isTagged; } /** * */ protected void setLanguage(String language) { this.language = language; } /** * */ protected void setPdfWriter(PdfWriter pdfWriter) { this.pdfWriter = pdfWriter; if (isTagged) { pdfWriter.setTagged(); } } /** * */ protected void init(PdfContentByte pdfContentByte) { this.pdfContentByte = pdfContentByte; if (isTagged) { PdfStructureTreeRoot root = pdfWriter.getStructureTreeRoot(); root.mapRole(PdfName.ALL, PdfName.SECT); root.mapRole(PdfName.IMAGE, PdfName.FIGURE); root.mapRole(PdfName.TEXT, PdfName.TEXT); allTag = new PdfStructureElement(root, PdfName.ALL); if(PdfAConformanceLevel.PDF_A_1A.equals(conformanceLevel)) { root.mapRole(new PdfName("Anchor"), PdfName.NONSTRUCT); root.mapRole(PdfName.TEXT, PdfName.SPAN); } else { root.mapRole(new PdfName("Anchor"), PdfName.TEXT); } if (language != null) { allTag.put(PdfName.LANG, new PdfString(language)); } tagStack = new Stack(); tagStack.push(allTag); } } protected void startPageAnchor() { if (isTagged) { PdfStructureElement textTag = new PdfStructureElement(allTag, new PdfName("Anchor")); pdfContentByte.beginMarkedContentSequence(textTag); } } protected void endPageAnchor() { if (isTagged) { pdfContentByte.endMarkedContentSequence(); } } protected void startPage() { crtCrosstabRowY = -1; crosstabFrameDepth = 0; isDataCellPrinted = false; } protected void endPage() { if (isTagged) { if (crtCrosstabRowY >= 0) //crosstab still open { //end the current row pdfContentByte.endMarkedContentSequence(); tagStack.pop(); //end the table pdfContentByte.endMarkedContentSequence(); tagStack.pop(); } } } protected void startElement(JRPrintElement element) { if (isTagged) { if (element instanceof JRPrintFrame) { JRPrintFrame frame = (JRPrintFrame) element; boolean isCellContentsFrame = frame.getPropertiesMap().hasProperties() && frame.getPropertiesMap().getProperty(JRCellContents.PROPERTY_TYPE) != null; if (crtCrosstabRowY >= 0) //crosstab already started { //frame depth must be incremented for all frame, when inside a crosstab crosstabFrameDepth++; if (isCellContentsFrame) { if (JRCellContents.TYPE_DATA.equals(frame.getPropertiesMap().getProperty(JRCellContents.PROPERTY_TYPE))) { isDataCellPrinted = true; } if (crtCrosstabRowY != frame.getY()) { //this is the first cell on a new row //end the current row pdfContentByte.endMarkedContentSequence(); tagStack.pop(); if ( isDataCellPrinted && (JRCellContents.TYPE_CROSSTAB_HEADER.equals(frame.getPropertiesMap().getProperty(JRCellContents.PROPERTY_TYPE)) || JRCellContents.TYPE_COLUMN_HEADER.equals(frame.getPropertiesMap().getProperty(JRCellContents.PROPERTY_TYPE))) ) { //end the table pdfContentByte.endMarkedContentSequence(); tagStack.pop(); //start table createTableStartTag(); //start crosstab isDataCellPrinted = false; } //start the new row createTrStartTag(); //keep crosstab open, but mark new row position and frame depth crtCrosstabRowY = frame.getY(); } } else { if (crosstabFrameDepth == 1) { //normal frame outside crosstab //end the current row pdfContentByte.endMarkedContentSequence(); tagStack.pop(); //end the table pdfContentByte.endMarkedContentSequence(); tagStack.pop(); //end crosstab crtCrosstabRowY = -1; //make depth zero because it will not be decremented after frame export // due to crosstab being ended here crosstabFrameDepth = 0; } } } else { if (isCellContentsFrame) { //start table and firts row createTableStartTag(); createTrStartTag(); //start crosstab crtCrosstabRowY = frame.getY(); crosstabFrameDepth++; isDataCellPrinted = false; } // else // { // //normal frame outside crosstab // } } } else { if (crtCrosstabRowY >= 0) //crosstab already started { if (crosstabFrameDepth == 0) { //normal element outside crosstab //end the current row pdfContentByte.endMarkedContentSequence(); tagStack.pop(); //end the table pdfContentByte.endMarkedContentSequence(); tagStack.pop(); //end crosstab crtCrosstabRowY = -1; crosstabFrameDepth = 0; } } } createStartTags(element); } } protected void endElement(JRPrintElement element) { if (isTagged) { if (element instanceof JRPrintFrame) { if (crtCrosstabRowY >= 0) // crosstab started { crosstabFrameDepth--; } } createEndTags(element); } } protected void startImage(JRPrintImage printImage) { if (isTagged) { PdfStructureElement imageTag = new PdfStructureElement(allTag, PdfName.IMAGE); pdfContentByte.beginMarkedContentSequence(imageTag); if (printImage.getHyperlinkTooltip() != null) { imageTag.put(PdfName.ALT, new PdfString(printImage.getHyperlinkTooltip())); } } } protected void endImage() { if (isTagged) { pdfContentByte.endMarkedContentSequence(); } } protected void startText() { if (isTagged) { // PdfStructureElement parentTag = tableCellTag == null ? (tableHeaderTag == null ? allTag : tableHeaderTag): tableCellTag; // PdfStructureElement textTag = new PdfStructureElement(parentTag, PdfName.TEXT); PdfStructureElement textTag = new PdfStructureElement(tagStack.peek(), PdfName.TEXT); pdfContentByte.beginMarkedContentSequence(textTag); } } protected void endText() { if (isTagged) { pdfContentByte.endMarkedContentSequence(); isTagEmpty = false; } } protected void createStartTags(JRPrintElement element) { if (element.hasProperties()) { String prop = element.getPropertiesMap().getProperty(PROPERTY_TAG_TABLE); if (prop != null && (TAG_START.equals(prop) || TAG_FULL.equals(prop))) { createTableStartTag(); } prop = element.getPropertiesMap().getProperty(PROPERTY_TAG_TR); if (prop != null && (TAG_START.equals(prop) || TAG_FULL.equals(prop))) { createTrStartTag(); } prop = element.getPropertiesMap().getProperty(PROPERTY_TAG_TH); if (prop != null && (TAG_START.equals(prop) || TAG_FULL.equals(prop))) { createThStartTag(element); } prop = element.getPropertiesMap().getProperty(PROPERTY_TAG_TD); if (prop != null && (TAG_START.equals(prop) || TAG_FULL.equals(prop))) { createTdStartTag(element); } prop = element.getPropertiesMap().getProperty(JRCellContents.PROPERTY_TYPE); if ( prop != null && (JRCellContents.TYPE_CROSSTAB_HEADER.equals(prop) || JRCellContents.TYPE_COLUMN_HEADER.equals(prop) || JRCellContents.TYPE_ROW_HEADER.equals(prop))) { createThStartTag(element); } if (prop != null && (JRCellContents.TYPE_DATA.equals(prop))) { createTdStartTag(element); } prop = element.getPropertiesMap().getProperty(PROPERTY_TAG_H1); if (prop != null && (TAG_START.equals(prop) || TAG_FULL.equals(prop))) { PdfStructureElement headingTag = new PdfStructureElement(tagStack.peek(), new PdfName("H1")); pdfContentByte.beginMarkedContentSequence(headingTag); headingTag.put(PdfName.K, new PdfArray()); tagStack.push(headingTag); isTagEmpty = true; } prop = element.getPropertiesMap().getProperty(PROPERTY_TAG_H2); if (prop != null && (TAG_START.equals(prop) || TAG_FULL.equals(prop))) { PdfStructureElement headingTag = new PdfStructureElement(tagStack.peek(), new PdfName("H2")); pdfContentByte.beginMarkedContentSequence(headingTag); headingTag.put(PdfName.K, new PdfArray()); tagStack.push(headingTag); isTagEmpty = true; } prop = element.getPropertiesMap().getProperty(PROPERTY_TAG_H3); if (prop != null && (TAG_START.equals(prop) || TAG_FULL.equals(prop))) { PdfStructureElement headingTag = new PdfStructureElement(tagStack.peek(), new PdfName("H3")); pdfContentByte.beginMarkedContentSequence(headingTag); headingTag.put(PdfName.K, new PdfArray()); tagStack.push(headingTag); isTagEmpty = true; } } } protected void createTableStartTag() { PdfStructureElement tableTag = new PdfStructureElement(allTag, new PdfName("Table")); pdfContentByte.beginMarkedContentSequence(tableTag); tableTag.put(PdfName.K, new PdfArray()); tagStack.push(tableTag); } protected void createTrStartTag() { PdfStructureElement tableRowTag = new PdfStructureElement(tagStack.peek(), new PdfName("TR")); pdfContentByte.beginMarkedContentSequence(tableRowTag); tableRowTag.put(PdfName.K, new PdfArray()); tagStack.push(tableRowTag); } protected void createThStartTag(JRPrintElement element) { PdfStructureElement tableHeaderTag = new PdfStructureElement(tagStack.peek(), new PdfName("TH")); pdfContentByte.beginMarkedContentSequence(tableHeaderTag); tableHeaderTag.put(PdfName.K, new PdfArray()); tagStack.push(tableHeaderTag); isTagEmpty = true; createSpanTags(element, tableHeaderTag); } protected void createTdStartTag(JRPrintElement element) { PdfStructureElement tableCellTag = new PdfStructureElement(tagStack.peek(), new PdfName("TD")); pdfContentByte.beginMarkedContentSequence(tableCellTag); tableCellTag.put(PdfName.K, new PdfArray()); tagStack.push(tableCellTag); isTagEmpty = true; createSpanTags(element, tableCellTag); } protected void createSpanTags(JRPrintElement element, PdfStructureElement parentTag) { int colSpan = 0; int rowSpan = 0; try { colSpan = Integer.valueOf(element.getPropertiesMap().getProperty(PROPERTY_TAG_COLSPAN)).intValue(); } catch (NumberFormatException e) { try { colSpan = Integer.valueOf(element.getPropertiesMap().getProperty(JRCellContents.PROPERTY_COLUMN_SPAN)).intValue(); } catch (NumberFormatException ex) {} } try { rowSpan = Integer.valueOf(element.getPropertiesMap().getProperty(PROPERTY_TAG_ROWSPAN)).intValue(); } catch (NumberFormatException e) { try { rowSpan = Integer.valueOf(element.getPropertiesMap().getProperty(JRCellContents.PROPERTY_ROW_SPAN)).intValue(); } catch (NumberFormatException ex) {} } if (colSpan > 1 || rowSpan > 1) { PdfArray a = new PdfArray(); PdfDictionary dict = new PdfDictionary(); if (colSpan > 1) { dict.put(new PdfName("ColSpan"), new PdfNumber(colSpan)); } if (rowSpan > 1) { dict.put(new PdfName("RowSpan"), new PdfNumber(rowSpan)); } dict.put(PdfName.O, new PdfName("Table")); a.add(dict); parentTag.put(PdfName.A, a); } } protected void createEndTags(JRPrintElement element)// throws DocumentException, IOException, JRException { if (element.hasProperties()) { String prop = element.getPropertiesMap().getProperty(PROPERTY_TAG_TABLE); if (prop != null && (TAG_END.equals(prop) || TAG_FULL.equals(prop))) { pdfContentByte.endMarkedContentSequence(); tagStack.pop(); } prop = element.getPropertiesMap().getProperty(PROPERTY_TAG_TR); if (prop != null && (TAG_END.equals(prop) || TAG_FULL.equals(prop))) { pdfContentByte.endMarkedContentSequence(); tagStack.pop(); } prop = element.getPropertiesMap().getProperty(PROPERTY_TAG_TH); if (prop != null && (TAG_END.equals(prop) || TAG_FULL.equals(prop))) { pdfContentByte.endMarkedContentSequence(); if (isTagEmpty) { pdfContentByte.beginMarkedContentSequence(new PdfStructureElement(tagStack.peek(), PdfName.SPAN)); pdfContentByte.endMarkedContentSequence(); } tagStack.pop(); } prop = element.getPropertiesMap().getProperty(PROPERTY_TAG_TD); if (prop != null && (TAG_END.equals(prop) || TAG_FULL.equals(prop))) { pdfContentByte.endMarkedContentSequence(); if (isTagEmpty) { pdfContentByte.beginMarkedContentSequence(new PdfStructureElement(tagStack.peek(), PdfName.SPAN)); pdfContentByte.endMarkedContentSequence(); } tagStack.pop(); } prop = element.getPropertiesMap().getProperty(JRCellContents.PROPERTY_TYPE); if ( prop != null && (JRCellContents.TYPE_CROSSTAB_HEADER.equals(prop) || JRCellContents.TYPE_COLUMN_HEADER.equals(prop) || JRCellContents.TYPE_ROW_HEADER.equals(prop) || JRCellContents.TYPE_DATA.equals(prop))) { pdfContentByte.endMarkedContentSequence(); if (isTagEmpty) { pdfContentByte.beginMarkedContentSequence(new PdfStructureElement(tagStack.peek(), PdfName.SPAN)); pdfContentByte.endMarkedContentSequence(); } tagStack.pop(); } prop = element.getPropertiesMap().getProperty(PROPERTY_TAG_H1); if (prop != null && (TAG_END.equals(prop) || TAG_FULL.equals(prop))) { pdfContentByte.endMarkedContentSequence(); if (isTagEmpty) { pdfContentByte.beginMarkedContentSequence(new PdfStructureElement(tagStack.peek(), PdfName.SPAN)); pdfContentByte.endMarkedContentSequence(); } tagStack.pop(); } prop = element.getPropertiesMap().getProperty(PROPERTY_TAG_H2); if (prop != null && (TAG_END.equals(prop) || TAG_FULL.equals(prop))) { pdfContentByte.endMarkedContentSequence(); if (isTagEmpty) { pdfContentByte.beginMarkedContentSequence(new PdfStructureElement(tagStack.peek(), PdfName.SPAN)); pdfContentByte.endMarkedContentSequence(); } tagStack.pop(); } prop = element.getPropertiesMap().getProperty(PROPERTY_TAG_H3); if (prop != null && (TAG_END.equals(prop) || TAG_FULL.equals(prop))) { pdfContentByte.endMarkedContentSequence(); if (isTagEmpty) { pdfContentByte.beginMarkedContentSequence(new PdfStructureElement(tagStack.peek(), PdfName.SPAN)); pdfContentByte.endMarkedContentSequence(); } tagStack.pop(); } } } public PdfAConformanceLevel getConformanceLevel() { return conformanceLevel; } public void setConformanceLevel(PdfAConformanceLevel conformanceLevel) { this.conformanceLevel = conformanceLevel; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy