+
+
diff --git a/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PDFPage.java b/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PDFPage.java
index 49d1fc1ae08..91fbd60b1ee 100644
--- a/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PDFPage.java
+++ b/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PDFPage.java
@@ -19,7 +19,6 @@
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import java.io.ByteArrayInputStream;
-import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
@@ -37,7 +36,6 @@
import org.eclipse.birt.report.engine.layout.pdf.font.FontInfo;
import org.eclipse.birt.report.engine.nLayout.area.style.AreaConstants;
import org.eclipse.birt.report.engine.nLayout.area.style.TextStyle;
-import org.eclipse.birt.report.engine.util.FlashFile;
import org.eclipse.birt.report.engine.util.SvgFile;
import org.w3c.dom.css.CSSValue;
@@ -53,7 +51,6 @@
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfDestination;
import com.lowagie.text.pdf.PdfDictionary;
-import com.lowagie.text.pdf.PdfFileSpecification;
import com.lowagie.text.pdf.PdfName;
import com.lowagie.text.pdf.PdfRectangle;
import com.lowagie.text.pdf.PdfString;
@@ -85,7 +82,7 @@ public class PDFPage extends AbstractPage {
protected PDFPageDevice pageDevice;
- protected boolean inArtifact = false;
+ protected short artifactDepth = 0;
/**
* font size must greater than minimum font . if not,illegalArgumentException
@@ -154,7 +151,9 @@ protected void drawBackgroundColor(Color color, float x, float y, float width, f
return;
}
y = transformY(y, height);
- beginArtifact();
+ PdfDictionary properties = new PdfDictionary();
+ properties.put(PdfNames.TYPE, PdfNames.BACKGROUND);
+ beginArtifact(properties);
contentByte.saveState();
contentByte.setColorFill(color);
contentByte.concatCTM(1, 0, 0, 1, x, y);
@@ -167,7 +166,9 @@ protected void drawBackgroundColor(Color color, float x, float y, float width, f
@Override
protected void drawBackgroundImage(float x, float y, float width, float height, float imageWidth, float imageHeight,
int repeat, String imageUrl, byte[] imageData, float offsetX, float offsetY) throws Exception {
- beginArtifact();
+ PdfDictionary properties = new PdfDictionary();
+ properties.put(PdfNames.TYPE, PdfNames.BACKGROUND);
+ beginArtifact(properties);
contentByte.saveState();
clip(x, y, width, height);
@@ -231,24 +232,17 @@ protected void drawBackgroundImage(float x, float y, float width, float height,
protected void drawImage(String imageId, byte[] imageData, String extension, float imageX, float imageY,
float height, float width, String helpText, Map params) throws Exception {
- // Flash
- if (FlashFile.isFlash(null, null, extension)) {
- embedFlash(null, imageData, imageX, imageY, height, width, helpText, params);
- return;
- }
-
- if (isTagged && !inArtifact) {
- PdfDictionary dict = new PdfDictionary();
- pageDevice.structureCurrentLeaf.put(new PdfName("Alt"), new PdfString(helpText));
+ if (isTagged && artifactDepth == 0) {
+ pageDevice.structureCurrentNode.put(PdfNames.ALT, new PdfString(helpText));
- PdfDictionary attributes = pageDevice.structureCurrentLeaf.getAsDict(PdfName.A);
+ PdfDictionary attributes = pageDevice.structureCurrentNode.getAsDict(PdfName.A);
if (attributes == null) {
attributes = new PdfDictionary();
- pageDevice.structureCurrentLeaf.put(PdfName.A, attributes);
+ pageDevice.structureCurrentNode.put(PdfName.A, attributes);
}
attributes.put(PdfName.BBOX, new PdfRectangle(imageX, imageY, width, height));
- contentByte.beginMarkedContentSequence(pageDevice.structureCurrentLeaf);
+ contentByte.beginMarkedContentSequence(pageDevice.structureCurrentNode);
}
// Cached Image
@@ -259,7 +253,7 @@ protected void drawImage(String imageId, byte[] imageData, String extension, flo
}
if (template != null) {
drawImage(template, imageX, imageY, height, width, helpText);
- if (isTagged && !inArtifact) {
+ if (isTagged && artifactDepth == 0) {
contentByte.endMarkedContentSequence();
}
return;
@@ -286,7 +280,7 @@ protected void drawImage(String imageId, byte[] imageData, String extension, flo
if (imageId == null) {
// image without imageId, not able to cache.
drawImage(image, imageX, imageY, height, width, helpText);
- if (isTagged && !inArtifact) {
+ if (isTagged && artifactDepth == 0) {
contentByte.endMarkedContentSequence();
}
return;
@@ -302,7 +296,7 @@ protected void drawImage(String imageId, byte[] imageData, String extension, flo
drawImage(template, imageX, imageY, height, width, helpText);
}
- if (isTagged && !inArtifact) {
+ if (isTagged && artifactDepth == 0) {
contentByte.endMarkedContentSequence();
}
@@ -365,16 +359,26 @@ protected void drawLine(float startX, float startY, float endX, float endY, floa
contentByte.restoreState();
}
+ protected void drawDecorationLine(float textX, float textY, float width, float lineWidth, float verticalOffset,
+ Color color, boolean artifact) {
+ if (artifact) {
+ // FIXME: Can we really treat a strike-through line as an artifact?
+ // Probably one should mark the text itself as "deleted" instead, but how can we
+ // do this?
+ PdfDictionary properties = new PdfDictionary();
+ properties.put(PdfNames.TYPE, PdfNames.LAYOUT);
+ beginArtifact(properties);
+ super.drawDecorationLine(textX, textY, width, lineWidth, verticalOffset, color);
+ endArtifact();
+ } else {
+ super.drawDecorationLine(textX, textY, width, lineWidth, verticalOffset, color);
+ }
+ }
+
@Override
protected void drawDecorationLine(float textX, float textY, float width, float lineWidth, float verticalOffset,
Color color) {
-
- // FIXME: Can we really treat a strike-through line as an artifact?
- // Probably one should mark the text itself as "deleted" instead, but how can we
- // do this?
- beginArtifact();
- super.drawDecorationLine(textX, textY, width, lineWidth, verticalOffset, color);
- endArtifact();
+ drawDecorationLine(textX, textY, width, lineWidth, verticalOffset, color, true);
}
@Override
@@ -384,6 +388,13 @@ protected void drawText(String text, float textX, float textY, float baseline, f
convertToPoint(textStyle.getLetterSpacing()), convertToPoint(textStyle.getWordSpacing()),
textStyle.getColor(), textStyle.getAlign());
if (textStyle.isHasHyperlink() && textStyle.isHasHyperlinkDecoration()) {
+ // FIXME ATM, the underline is marked as Artifact (see drawDecorationLine).
+ // I think this is not quite correct and not necessary.
+ // LibreOffice does not do it this way.
+ // The underline decoration should be included in the /Link element.
+ // For this, we probably have to handle the beginMarkedContentSequence and
+ // endMarkedContentSequence stuff right here instead of in drawDecorationLine
+ // and the overloaded drawText methods.
FontInfo fontInfo = textStyle.getFontInfo();
float lineWidth = fontInfo.getLineWidth();
Color color = textStyle.getColor();
@@ -414,7 +425,16 @@ public void drawTotalPage(String text, int textX, int textY, int width, int heig
PdfContentByte tempCB = this.contentByte;
this.containerHeight = template.getHeight();
this.contentByte = template;
+ int artifactDepthOld = artifactDepth;
+ if (artifactDepthOld == 0) {
+ PdfDictionary properties = new PdfDictionary();
+ properties.put(PdfNames.TYPE, PdfNames.PAGINATION);
+ beginArtifact();
+ }
drawText(text, textX, textY, width, height, textInfo);
+ if (artifactDepthOld == 0) {
+ endArtifact();
+ }
this.contentByte = tempCB;
this.containerHeight = pageHeight;
}
@@ -438,7 +458,7 @@ private void createBookmark(String bookmark, float y) {
}
/**
- * Create the hyperlinks
+ * Create a hyperlink.
*
* @param hyperlink
* @param bookmark
@@ -448,18 +468,23 @@ private void createBookmark(String bookmark, float y) {
* @param y
* @param width
* @param height
+ *
+ * @return a new PdfAnnotation describing the hyperlink.
*/
- public void createHyperlink(String hyperlink, String bookmark, String targetWindow, int type, int x, int y,
+ public PdfAnnotation createHyperlink(String hyperlink, String bookmark, String targetWindow, int type, int x, int y,
int width, int height) {
- createHyperlink(hyperlink, bookmark, targetWindow, type, convertToPoint(x), convertToPoint(y),
+ return createHyperlink(hyperlink, bookmark, targetWindow, type, convertToPoint(x), convertToPoint(y),
convertToPoint(width), convertToPoint(height));
}
- private void createHyperlink(String hyperlink, String bookmark, String targetWindow, int type, float x, float y,
+ private PdfAnnotation createHyperlink(String hyperlink, String bookmark, String targetWindow, int type, float x,
+ float y,
float width, float height) {
y = transformY(y, height);
- writer.addAnnotation(new PdfAnnotation(writer, x, y, x + width, y + height,
- createPdfAction(hyperlink, bookmark, targetWindow, type)));
+ PdfAnnotation annotation = new PdfAnnotation(writer, x, y, x + width, y + height,
+ createPdfAction(hyperlink, bookmark, targetWindow, type));
+ writer.addAnnotation(annotation);
+ return annotation;
}
/**
@@ -509,14 +534,14 @@ private void drawRawLine(float startX, float startY, float endX, float endY, flo
startY = transformY(startY);
endY = transformY(endY);
contentByte.concatCTM(1, 0, 0, 1, startX, startY);
+ if (null != color && !Color.BLACK.equals(color)) {
+ contentByte.setColorStroke(color);
+ }
contentByte.moveTo(0, 0);
contentByte.lineTo(endX - startX, endY - startY);
contentByte.setLineWidth(width);
- if (null != color && !Color.BLACK.equals(color)) {
- contentByte.setColorStroke(color);
- }
contentByte.stroke();
}
@@ -530,8 +555,8 @@ private void drawText(String text, float textX, float textY, FontInfo fontInfo,
// start drawing the text content
contentByte.beginText();
- if (isTagged && !inArtifact) {
- contentByte.beginMarkedContentSequence(pageDevice.structureCurrentLeaf);
+ if (isTagged && artifactDepth == 0) {
+ contentByte.beginMarkedContentSequence(pageDevice.structureCurrentNode);
}
if (null != color && !Color.BLACK.equals(color)) {
@@ -552,6 +577,7 @@ private void drawText(String text, float textX, float textY, FontInfo fontInfo,
String defaultFontPdfA = this.pageDevice.getDefaultFontPdfA();
if (defaultFontPdfA != null) {
font = BaseFont.createFont(defaultFontPdfA, BaseFont.IDENTITY_H, true);
+ font.setIncludeCidSet(this.pageDevice.isIncludeCidSet());
}
logger.log(Level.WARNING,
"PDF/A: " + fontInfo.getFontName() + " not embeddable, fallback font used.");
@@ -572,7 +598,7 @@ private void drawText(String text, float textX, float textY, FontInfo fontInfo,
if (wordSpacing != 0) {
contentByte.setWordSpacing(wordSpacing);
}
- setTextMatrix(contentByte, fontInfo, textX, transformY(textY, 0, containerHeight));
+ setTextMatrix(contentByte, fontInfo);
if ((font.getFontType() == BaseFont.FONT_TYPE_TTUNI) && CSSValueConstants.JUSTIFY_VALUE.equals(align)
&& wordSpacing > 0) {
int idx = text.indexOf(' ');
@@ -594,7 +620,7 @@ private void drawText(String text, float textX, float textY, FontInfo fontInfo,
} else {
contentByte.showText(text);
}
- if (isTagged && !inArtifact) {
+ if (isTagged && artifactDepth == 0) {
contentByte.endMarkedContentSequence();
}
contentByte.endText();
@@ -641,7 +667,7 @@ private PdfAction createPdfAction(String hyperlink, String bookmark, String targ
}
}
- private void setTextMatrix(PdfContentByte cb, FontInfo fi, float x, float y) {
+ private void setTextMatrix(PdfContentByte cb, FontInfo fi) {
if (!fi.getSimulation()) {
cb.setTextMatrix(0, 0);
@@ -733,20 +759,6 @@ protected void drawImage(Image image, float imageX, float imageY, float height,
contentByte.restoreState();
}
- protected void embedFlash(String flashPath, byte[] flashData, float x, float y, float height, float width,
- String helpText, Map params) throws IOException {
- y = transformY(y, height);
- contentByte.saveState();
- PdfFileSpecification fs = PdfFileSpecification.fileEmbedded(writer, flashPath, helpText, flashData);
- PdfAnnotation annot = PdfAnnotation.createScreen(writer, new Rectangle(x, y, x + width, y + height), helpText,
- fs, "application/x-shockwave-flash", true);
- writer.addAnnotation(annot);
- if (helpText != null) {
- showHelpText(x, y, width, height, helpText);
- }
- contentByte.restoreState();
- }
-
protected PdfTemplate generateTemplateFromSVG(byte[] svgData, float height, float width)
throws Exception {
return transSVG(null, svgData, height, width);
@@ -774,32 +786,58 @@ protected PdfTemplate transSVG(String svgPath, byte[] svgData, float height, flo
return template;
}
+ /**
+ * Mark the beginning of an artifact. Artifact content is whatever is not
+ * essential for the reader, such as page headers and footers, or repeated table
+ * headers, or graphical elements like lines or boxes which do not really have a
+ * meaning.
+ */
public void beginArtifact() {
if (!isTagged) {
return;
}
- if (!inArtifact) {
- contentByte.beginMarkedContentSequence(new PdfName("Artifact"));
- inArtifact = true;
+ if (artifactDepth == 0) {
+ contentByte.beginMarkedContentSequence(PdfNames.ARTIFACT);
}
- else {
- logger.warning("beginArtifact called inside artifact!");
+ artifactDepth++;
+ }
+
+ /**
+ * Mark the beginning of an artifact. Artifact content is whatever is not
+ * essential for the reader, such as page headers and footers, or repeated table
+ * headers, or graphical elements like lines or boxes which do not really have a
+ * meaning.
+ *
+ * @param properties additional properties which are used in the call to
+ * beginMarkedContentSequence.
+ */
+ public void beginArtifact(PdfDictionary properties) {
+ if (!isTagged) {
+ return;
+ }
+ if (artifactDepth == 0) {
+ contentByte.beginMarkedContentSequence(PdfNames.ARTIFACT, properties, true);
}
+ artifactDepth++;
}
+ /**
+ * Mark the end of and artifact.
+ */
public void endArtifact() {
if (!isTagged) {
return;
}
- if (inArtifact) {
+ artifactDepth--;
+ if (artifactDepth == 0) {
contentByte.endMarkedContentSequence();
- inArtifact = false;
- } else {
- logger.warning("endArtifact called outside of an artifact!");
}
}
+ /**
+ * @return if we are currently in an artifact or not.
+ */
public boolean isInArtifact() {
- return inArtifact;
+ return artifactDepth > 0;
}
}
diff --git a/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PDFPageDevice.java b/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PDFPageDevice.java
index 14ff5d7faf9..62e81c2519a 100644
--- a/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PDFPageDevice.java
+++ b/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PDFPageDevice.java
@@ -38,8 +38,10 @@
import org.eclipse.birt.report.engine.api.ITOCTree;
import org.eclipse.birt.report.engine.api.TOCNode;
import org.eclipse.birt.report.engine.api.script.IReportContext;
+import org.eclipse.birt.report.engine.content.IBandContent;
import org.eclipse.birt.report.engine.content.IReportContent;
import org.eclipse.birt.report.engine.content.impl.CellContent;
+import org.eclipse.birt.report.engine.content.impl.RowContent;
import org.eclipse.birt.report.engine.i18n.EngineResourceHandle;
import org.eclipse.birt.report.engine.i18n.MessageConstants;
import org.eclipse.birt.report.engine.internal.util.BundleVersionUtil;
@@ -48,6 +50,7 @@
import org.eclipse.birt.report.engine.layout.emitter.IPageDevice;
import org.eclipse.birt.report.engine.nLayout.area.IArea;
import org.eclipse.birt.report.engine.nLayout.area.impl.CellArea;
+import org.eclipse.birt.report.engine.nLayout.area.impl.ContainerArea;
import com.ibm.icu.util.ULocale;
import com.lowagie.text.Document;
@@ -76,6 +79,7 @@
import com.lowagie.text.xml.xmp.PdfA1Schema;
import com.lowagie.text.xml.xmp.PdfSchema;
import com.lowagie.text.xml.xmp.XmpBasicSchema;
+import com.lowagie.text.xml.xmp.XmpSchema;
import com.lowagie.text.xml.xmp.XmpWriter;
/**
@@ -97,19 +101,36 @@ public class PDFPageDevice implements IPageDevice {
private static final String PDF_VERSION_1_6 = "1.6";
/** PDF version 1.7 */
private static final String PDF_VERSION_1_7 = "1.7";
+ /** PDF version 2.0 */
+ private static final String PDF_VERSION_2_0 = "2.0";
/** PDFX/PDFA conformance */
/** PDF conformance PDF Standard */
private static final String PDF_CONFORMANCE_STANDARD = "PDF.Standard";
- /** PDF conformance PDF X-3:2002 */
+ /** PDF conformance PDF/X-3:2002 */
private static final String PDF_CONFORMANCE_X32002 = "PDF.X32002";
- /** PDF conformance PDF A1A */
+ /** PDF conformance PDF/A-1a */
private static final String PDF_CONFORMANCE_A1A = "PDF.A1A";
- /** PDF conformance PDF A1B */
+ /** PDF conformance PDFA-1b */
private static final String PDF_CONFORMANCE_A1B = "PDF.A1B";
+ /** PDF conformance PDF/A-2a */
+ private static final String PDF_CONFORMANCE_A2A = "PDF.A2A";
+ /** PDF conformance PDFA-2b */
+ private static final String PDF_CONFORMANCE_A2B = "PDF.A2B";
+ /** PDF conformance PDF/A-3a */
+ private static final String PDF_CONFORMANCE_A3A = "PDF.A3A";
+ /** PDF conformance PDFA-3b */
+ private static final String PDF_CONFORMANCE_A3B = "PDF.A3B";
+ /** PDF conformance PDF/A-3u */
+ private static final String PDF_CONFORMANCE_A3U = "PDF.A3U";
+ /** PDF conformance PDF/A-4f */
+ private static final String PDF_CONFORMANCE_A4F = "PDF.A4F";
+
/** PDF conformance PDF X-1a:2001, unsupported (TODO: CMYK of PDF.X1A2001 */
private static final String PDF_UA_CONFORMANCE_1 = "PDF.UA-1";
+ private static final String PDF_UA_CONFORMANCE_2 = "PDF.UA-2";
+ private static final String PDF_UA_CONFORMANCE_NONE = "none";
/** PDF ICC color profile */
/** PDF ICC default color profile RGB */
@@ -132,7 +153,7 @@ public class PDFPageDevice implements IPageDevice {
// The StructureTree defines the logical structure of the content.
PdfStructureTreeRoot structureRoot = null;
PdfStructureElement structureDocument = null;
- PdfStructureElement structureCurrentLeaf = null;
+ PdfStructureElement structureCurrentNode = null;
protected IReportContext context;
@@ -280,7 +301,8 @@ public PDFPageDevice(OutputStream output, String title, String author, String su
throw new BirtException("The report needs a locale property for PDF/UA!");
}
Locale locale = new Locale(localeString);
- String language = locale.toLanguageTag();
+ String language = locale.toString();
+ language = language.replace('_', '-'); // 'de_de' is invalid, it should be 'de-DE'.
doc.setDocumentLanguage(language);
// In order to declare the main language of the document,
// we need to use the extraCatalog. That way we don't need to
@@ -324,7 +346,7 @@ public PDFPageDevice(OutputStream output, String title, String author, String su
// iterate over the list, and create a file inputstream for each file location.
for (String s : list.split(",")) {
// If there is an exception creating the input stream, don't stop execution.
- // Just graceffully let the user know that there was an error with the variable.
+ // Just gracefully let the user know that there was an error with the variable.
try {
String fileName = s.trim().replace("\\", "\\\\");
File f = new File(fileName);
@@ -346,15 +368,13 @@ public PDFPageDevice(OutputStream output, String title, String author, String su
}
// The other is a "Named Expression", which is basically a user property that is
- // the result
- // of an expression instead of a string literal. This should be set as an
- // arraylist through
- // BIRT script
+ // the result of an expression instead of a string literal. This should be set
+ // as an arraylist through BIRT script
if (result instanceof ArrayList) {
ArrayList pdfList = (ArrayList) result;
- for (String fileName : pdfList) {
+ for (String fileName : pdfList) {
// If there is an exception creating the input stream, don't stop execution.
- // Just graceffully let the user know that there was an error with the variable.
+ // Just gracefully let the user know that there was an error with the variable.
fileName = fileName.replace("\\", "\\\\");
try {
File f = new File(fileName);
@@ -389,11 +409,14 @@ public PDFPageDevice(OutputStream output, String title, String author, String su
}
}
+ /**
+ * Initialize the attributes for the PDF tag tree structure.
+ */
public void initStructure() {
structureRoot = writer.getStructureTreeRoot();
structureDocument = new PdfStructureElement(structureRoot, new PdfName("Document"));
- structureCurrentLeaf = structureDocument;
+ structureCurrentNode = structureDocument;
}
@@ -500,22 +523,21 @@ public void close() throws Exception {
// iterate over the list, and create a fileinputstream for each file location.
for (String s : list.split(",")) {
// If there is an exception creating the input stream, don't stop execution.
- // Just graceffully let the user know that there was an error with the variable.
+ // Just gracefully let the user know that there was an error with the variable.
try {
String fileName = s.trim().replace("\\", "\\\\");
- ;
- File f = new File(fileName);
- if (f.exists()) {
- FileInputStream fis = new FileInputStream(f);
- pdfs.add(fis);
- } else {
- // get the file using context.getResource() for relative or universal paths
- URL url = context.getResource(fileName);
- InputStream is = new BufferedInputStream(url.openStream());
- pdfs.add(is);
+ File f = new File(fileName);
+ if (f.exists()) {
+ FileInputStream fis = new FileInputStream(f);
+ pdfs.add(fis);
+ } else {
+ // get the file using context.getResource() for relative or universal paths
+ URL url = context.getResource(fileName);
+ InputStream is = new BufferedInputStream(url.openStream());
+ pdfs.add(is);
}
- } catch (Exception e) {
- logger.log(Level.WARNING, e.getMessage(), e);
+ } catch (Exception e) {
+ logger.log(Level.WARNING, e.getMessage(), e);
}
}
}
@@ -524,14 +546,13 @@ public void close() throws Exception {
// option 2: "Named Expression", which is basically a user property that is the
// result of an expression instead of a string literal. This should be set as an
- // arraylist through
- // BIRT script
+ // arraylist through BIRT script
if (result instanceof ArrayList) {
ArrayList pdfList = (ArrayList) result;
for (String fileName : pdfList) {
// If there is an exception creating the input stream, don't stop execution.
- // Just graceffully let the user know that there was an error with the variable.
+ // Just gracefully let the user know that there was an error with the variable.
fileName = fileName.replace("\\", "\\\\");
try {
File f = new File(fileName);
@@ -555,7 +576,7 @@ public void close() throws Exception {
}
}
- if (this.isPdfAFormat) {
+ if (this.isPdfAFormat || this.isPdfUAFormat) {
// PDF/A: set color profile and metadata
this.setPdfIccXmp();
}
@@ -702,8 +723,10 @@ public void concatPDFs(List streamOfPDFFiles, boolean paginate) {
/**
* Set the PDF version based on the user property
+ *
+ * @throws BirtException
*/
- private void setPdfVersion() {
+ private void setPdfVersion() throws BirtException {
String userPdfVersion;
// PDF version based on user property
if (this.userProperties != null && this.userProperties.containsKey(PDFPageDevice.PDF_VERSION)) {
@@ -718,8 +741,9 @@ private void setPdfVersion() {
* Set the PDF version
*
* @param version key to set the PDF version (e.g. 1.7)
+ * @throws BirtException
*/
- public void setPdfVersion(String version) {
+ public void setPdfVersion(String version) throws BirtException {
switch (version) {
case PDFPageDevice.PDF_VERSION_1_3:
this.pdfVersion = PdfWriter.VERSION_1_3;
@@ -736,6 +760,10 @@ public void setPdfVersion(String version) {
case PDFPageDevice.PDF_VERSION_1_7:
this.pdfVersion = PdfWriter.VERSION_1_7;
break;
+ case PDFPageDevice.PDF_VERSION_2_0:
+ // this.pdfVersion = PdfWriter.VERSION_2_0;
+ // OpenPDF does not yet support creation of PDF 2.0
+ throw new BirtException("OpenPDF does not yet support creation of PDF 2.0");
}
// version only set if the PDF version exists
if (this.pdfVersion != '0') {
@@ -760,7 +788,7 @@ private void setPdfUAConformance() {
/**
* Set the PDF version
*
- * @param conformance key to set the PDF version (e.g. 1.7)
+ * @param conformance key to set the PDF/UA version (e.g. PDF/UA-1)
*/
public void setPdfUAConformance(String conformance) {
switch (conformance) {
@@ -769,6 +797,11 @@ public void setPdfUAConformance(String conformance) {
this.isPdfUAFormat = true;
writer.setTagged();
break;
+ case PDFPageDevice.PDF_UA_CONFORMANCE_2:
+ this.pdfUAConformance = 2;
+ this.isPdfUAFormat = true;
+ writer.setTagged();
+ break;
}
}
@@ -781,8 +814,10 @@ public String getPdfUAConformance() {
switch (this.pdfUAConformance) {
case 1:
return PDFPageDevice.PDF_UA_CONFORMANCE_1;
+ case 2:
+ return PDFPageDevice.PDF_UA_CONFORMANCE_2;
default:
- return PDFPageDevice.PDF_CONFORMANCE_STANDARD;
+ return PDFPageDevice.PDF_UA_CONFORMANCE_NONE;
}
}
@@ -820,6 +855,7 @@ private void setPdfConformance() {
} else {
userPdfConformance = (String) getReportDesignConfiguration(this.report, PDFPageDevice.PDF_CONFORMANCE);
}
+
switch (userPdfConformance) {
case PDFPageDevice.PDF_CONFORMANCE_X32002:
this.pdfConformance = PdfWriter.PDFX32002;
@@ -833,6 +869,30 @@ private void setPdfConformance() {
this.pdfConformance = PdfWriter.PDFA1B;
this.isPdfAFormat = true;
break;
+ case PDFPageDevice.PDF_CONFORMANCE_A2A:
+ this.pdfConformance = PDFPageDevice.PDFA2A;
+ this.isPdfAFormat = true;
+ break;
+ case PDFPageDevice.PDF_CONFORMANCE_A2B:
+ this.pdfConformance = PDFPageDevice.PDFA2B;
+ this.isPdfAFormat = true;
+ break;
+ case PDFPageDevice.PDF_CONFORMANCE_A3A:
+ this.pdfConformance = PDFPageDevice.PDFA3A;
+ this.isPdfAFormat = true;
+ break;
+ case PDFPageDevice.PDF_CONFORMANCE_A3B:
+ this.pdfConformance = PDFPageDevice.PDFA3B;
+ this.isPdfAFormat = true;
+ break;
+ case PDFPageDevice.PDF_CONFORMANCE_A3U:
+ this.pdfConformance = PDFPageDevice.PDFA3U;
+ this.isPdfAFormat = true;
+ break;
+ case PDFPageDevice.PDF_CONFORMANCE_A4F:
+ this.pdfConformance = PDFPageDevice.PDFA4F;
+ this.isPdfAFormat = true;
+ break;
default:
this.pdfConformance = PdfWriter.PDFXNONE;
this.isPdfAFormat = false;
@@ -843,11 +903,11 @@ private void setPdfConformance() {
// PDFA: overwrite to get the document title independent of the openPDF
// issue of PDF/A-conformance
if (this.userProperties != null && this.userProperties.containsKey(PDFPageDevice.PDFA_ADD_DOCUMENT_TITLE)) {
- this.addPdfADocumentTitle = Boolean.parseBoolean(this.userProperties.get(PDFPageDevice.PDFA_ADD_DOCUMENT_TITLE).toString());
- } else {
this.addPdfADocumentTitle = Boolean
- .parseBoolean(
- (String) getReportDesignConfiguration(this.report, PDFPageDevice.PDFA_ADD_DOCUMENT_TITLE));
+ .parseBoolean(this.userProperties.get(PDFPageDevice.PDFA_ADD_DOCUMENT_TITLE).toString());
+ } else {
+ this.addPdfADocumentTitle = Boolean.parseBoolean(
+ (String) getReportDesignConfiguration(this.report, PDFPageDevice.PDFA_ADD_DOCUMENT_TITLE));
}
}
@@ -858,11 +918,26 @@ private void setPdfConformance() {
*/
public void setPdfConformance(int pdfConformance) {
writer.setPDFXConformance(pdfConformance);
- if (pdfConformance == PdfWriter.PDFA1A) {
+ switch (pdfConformance) {
+ case PdfWriter.PDFA1A:
+ case PDFA2A:
+ case PDFA3A:
writer.setTagged();
+ break;
+ default:
+ // do nothing
}
}
+ // FIXME The existence of these constants is a workaround for the fact that
+ // OpenPDF does not yet support PDF/A-2 or newer.
+ private static final int PDFA2A = 5;
+ private static final int PDFA2B = 6;
+ private static final int PDFA3A = 7;
+ private static final int PDFA3B = 8;
+ private static final int PDFA3U = 9;
+ private static final int PDFA4F = 10;
+
/**
* Get the PDF conformance
*
@@ -876,6 +951,18 @@ public String getPdfConformance() {
return PDFPageDevice.PDF_CONFORMANCE_A1A;
case PdfWriter.PDFA1B:
return PDFPageDevice.PDF_CONFORMANCE_A1B;
+ case PDFPageDevice.PDFA2A:
+ return PDFPageDevice.PDF_CONFORMANCE_A2A;
+ case PDFPageDevice.PDFA2B:
+ return PDFPageDevice.PDF_CONFORMANCE_A2B;
+ case PDFPageDevice.PDFA3A:
+ return PDFPageDevice.PDF_CONFORMANCE_A3A;
+ case PDFPageDevice.PDFA3B:
+ return PDFPageDevice.PDF_CONFORMANCE_A3B;
+ case PDFPageDevice.PDFA3U:
+ return PDFPageDevice.PDF_CONFORMANCE_A3U;
+ case PDFPageDevice.PDFA4F:
+ return PDFPageDevice.PDF_CONFORMANCE_A4F;
default:
return PDFPageDevice.PDF_CONFORMANCE_STANDARD;
}
@@ -900,21 +987,23 @@ public boolean isPDFUAFormat() {
}
/**
- * We need to override getXmlns because we have to define the pdfuaid namespace.
+ * PDF XMP schema for declaring that the PDF is PDF/UA conforming.
+ *
+ * @since 4.18
+ *
*/
- private static class DublinCoreAccessibleSchema extends DublinCoreSchema {
+ private static class PDFUASchema extends XmpSchema {
- public DublinCoreAccessibleSchema() {
- super();
- }
+ private static final long serialVersionUID = -6990512370284803429L;
- @Override
- public String getXmlns() {
- return super.getXmlns() + " xmlns:pdfuaid=\"http://www.aiim.org/pdfua/ns/id/\"";
+ public PDFUASchema() {
+ super("xmlns:pdfuaid=\"http://www.aiim.org/pdfua/ns/id/\"");
}
/**
* This is what declares the document to be PDF/UA-1, so it must be called.
+ *
+ * @param version the PDF/UA version. Valid values are 1 or 2.
*/
public void addPdfUAId(int version) {
setProperty("pdfuaid:part", String.valueOf(version));
@@ -923,8 +1012,57 @@ public void addPdfUAId(int version) {
}
/**
- * Create the XML for the XMPMetadata. We use the same method from PdfWriter as
- * a template and add what is neeeded for PDF/UA.
+ * PDF XMP schema for declaring that the PDF is PDF/A conforming.
+ *
+ * Since the document can be PDF/UA conforming at the same time, the PDF/A
+ * specification requires that this is other schema is also described here.
+ *
+ * @since 4.18
+ *
+ */
+ private static class PDFAExtensionSchema extends XmpSchema {
+
+ private static final long serialVersionUID = 6654512771721220538L;
+
+ public PDFAExtensionSchema() {
+ super("xmlns:pdfaExtension=\"http://www.aiim.org/pdfa/ns/extension/\" xmlns:pdfaProperty=\"http://www.aiim.org/pdfa/ns/property#\" xmlns:pdfaSchema=\"http://www.aiim.org/pdfa/ns/schema#\"");
+ }
+
+ public String toString() {
+ return " \r\n" + " \r\n"
+ + " \r\n"
+ + " http://www.aiim.org/pdfua/ns/id/\r\n"
+ + " pdfuaid\r\n"
+ + " PDF/UA identification schema\r\n"
+ + " \r\n" + " \r\n"
+ + " \r\n"
+ + " internal\r\n"
+ + " PDF/UA version identifier\r\n"
+ + " part\r\n"
+ + " Integer\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " internal\r\n"
+ + " PDF/UA amendment identifier\r\n"
+ + " amd\r\n"
+ + " Text\r\n"
+ + "\r\n" + " \r\n"
+ + " \r\n"
+ + " internal\r\n"
+ + " PDF/UA corrigenda identifier\r\n"
+ + " corr\r\n"
+ + " Text\r\n"
+ + " \r\n" + " \r\n"
+ + " \r\n" + " \r\n"
+ + " \r\n" + " \r\n" + "";
+ }
+ }
+
+ /**
+ * Create the XML for the XMPMetadata.
+ *
+ * We use the same method from {@link PdfWriter} as a template and add what is
+ * needed for PDF/UA.
*
* @return an XmpMetadata byte array
*/
@@ -939,7 +1077,7 @@ private byte[] createXmpMetadataBytes() {
// Not tested.
int PdfXConformance = writer.getPDFXConformance();
XmpWriter xmp = new XmpWriter(baos, "UTF-8", 4);
- DublinCoreAccessibleSchema dc = new DublinCoreAccessibleSchema();
+ DublinCoreSchema dc = new DublinCoreSchema();
PdfSchema p = new PdfSchema();
XmpBasicSchema basic = new XmpBasicSchema();
@@ -982,10 +1120,6 @@ private byte[] createXmpMetadataBytes() {
basic.addModDate(((PdfDate) obj).getW3CDate());
}
}
- if (this.isPDFUAFormat()) {
- // Declare the document to be PDF/UA conformant.
- dc.addPdfUAId(this.pdfUAConformance);
- }
if (dc.size() > 0)
xmp.addRdfDescription(dc);
@@ -994,13 +1128,56 @@ private byte[] createXmpMetadataBytes() {
if (basic.size() > 0)
xmp.addRdfDescription(basic);
- // Declare the document to be PDF/A conformant, if requested by the developer.
- if (PdfXConformance == PdfWriter.PDFA1A || PdfXConformance == PdfWriter.PDFA1B) {
+ if (this.isPDFUAFormat()) {
+ // Declare the document to be PDF/UA conforming.
+ PDFUASchema ua = new PDFUASchema();
+ ua.addPdfUAId(this.pdfUAConformance);
+ xmp.addRdfDescription(ua);
+ }
+
+ // Declare the document to be PDF/A conforming, if requested by the developer.
+ if (isPdfAFormat) {
+
+ // If the document is also PDF/UA conforming, we need to add a description of
+ // that schema as an PDF/A extension.
+ if (isPDFUAFormat()) {
+ PDFAExtensionSchema pdfaext = new PDFAExtensionSchema();
+ xmp.addRdfDescription(pdfaext);
+ }
+
PdfA1Schema a1 = new PdfA1Schema();
- if (PdfXConformance == PdfWriter.PDFA1A)
+ switch (PdfXConformance) {
+ case PdfWriter.PDFA1A:
+ case PdfWriter.PDFA1B:
+ a1.addPart("1");
+ break;
+ case PDFA2A:
+ case PDFA2B:
+ a1.addPart("2");
+ break;
+ case PDFA3A:
+ case PDFA3B:
+ case PDFA3U:
+ a1.addPart("3");
+ break;
+ case PDFA4F:
+ a1.addPart("4");
+ break;
+ }
+ switch (PdfXConformance) {
+ case PdfWriter.PDFA1A:
+ case PDFA2A:
+ case PDFA3A:
a1.addConformance("A");
- else
+ break;
+ case PdfWriter.PDFA1B:
+ case PDFA2B:
+ case PDFA3B:
a1.addConformance("B");
+ break;
+ case PDFA4F:
+ a1.addConformance("F");
+ }
xmp.addRdfDescription(a1);
}
@@ -1012,7 +1189,7 @@ private byte[] createXmpMetadataBytes() {
}
/**
- * Set the PDF icc color profile and the XMP meta data
+ * Set the PDF ICC color profile and the XMP meta data
*/
private void setPdfIccXmp() {
@@ -1031,10 +1208,13 @@ private void setPdfIccXmp() {
String fullFileNameIcc = "";
if (this.userProperties != null
&& userProperties.containsKey(PDFPageDevice.PDF_ICC_PROFILE_EXTERNAL_FILE)) {
- fullFileNameIcc = userProperties.get(PDFPageDevice.PDF_ICC_PROFILE_EXTERNAL_FILE).toString().trim();
+ fullFileNameIcc = userProperties.get(PDFPageDevice.PDF_ICC_PROFILE_EXTERNAL_FILE).toString();
} else {
fullFileNameIcc = ((String) getReportDesignConfiguration(this.report,
- PDFPageDevice.PDF_ICC_PROFILE_EXTERNAL_FILE)).trim();
+ PDFPageDevice.PDF_ICC_PROFILE_EXTERNAL_FILE));
+ }
+ if (fullFileNameIcc != null) {
+ fullFileNameIcc = fullFileNameIcc.trim();
}
if (fullFileNameIcc != null && fullFileNameIcc.length() > 0) {
try {
@@ -1186,70 +1366,192 @@ private Object getReportDesignConfiguration(IReportContent reportContent, String
}
/**
- * @param tagType
+ * Open a tag in the tag tree structure.
+ *
+ * Basically this means: Create a new child node for structureCurrentNode and
+ * let structureCurrentNode point to this child node. But several edge cases
+ * need special handling. For example, when we are in an artifact, we don't want
+ * to open a new tag. And containers need special handling for page-breaking to
+ * avoid the creation of unnecessary tags, e.g. a table that spans two pages
+ * must still a single table in the tag tree.
+ *
+ * If the PDF emitter is not configured to create tagged PDF, then this method
+ * is a no-op.
+ *
+ * @param tagType the tag type. Note that we use some special tag types AUTO,
+ * PAGE_HEADER and PAGE_FOOTER which are not actually PDF tags,
+ * but plcaeholders which trigger special handling here.
+ * @param area the area for which we create a tag.
*/
- public void pushTag(String tagType, IArea area) {
- if (!writer.isTagged()) {
+ public void openTag(String tagType, IArea area) {
+ if (!writer.isTagged() || tagType == null) {
return;
}
- if ("pageHeader".equals(tagType)) {
- currentPage.beginArtifact();
- } else if ("pageFooter".equals(tagType)) {
- currentPage.beginArtifact();
- } else if (currentPage.isInArtifact()) {
- ;
- } else {
- structureCurrentLeaf = new PdfStructureElement(structureCurrentLeaf, new PdfName(tagType));
- // FIXME Adding attributes should be made a method of the IArea classes.
- if ("Figure".equals(tagType)) {
- // Top-Level figure elements must have a placement attribute.
- if (PdfName.DOCUMENT.equals(structureCurrentLeaf.getParent().get(PdfName.S))) {
- PdfDictionary attributes = structureCurrentLeaf.getAsDict(PdfName.A);
- if (attributes == null) {
- attributes = new PdfDictionary();
- structureCurrentLeaf.put(PdfName.A, attributes);
- }
- attributes.put(new PdfName("Placement"), new PdfName("Block"));
- attributes.put(PdfName.O, new PdfName("Layout"));
- }
- }
- if ("TD".equals(tagType) || "TH".equals(tagType)) {
- if (area instanceof CellArea) {
- CellArea cellArea = (CellArea) area;
- int rowspan = cellArea.getRowSpan();
- int colspan = cellArea.getColSpan();
- String scope = ((CellContent) (cellArea.getContent())).getScope();
- String bookmark = cellArea.getBookmark();
- if (bookmark != null) {
- structureCurrentLeaf.put(PdfName.ID, new PdfString(bookmark));
- }
-
- String headers = ((CellContent) (cellArea.getContent())).getHeaders();
- if (rowspan != 1 || colspan != 1 || scope != null || headers != null) {
- PdfDictionary attributes = structureCurrentLeaf.getAsDict(PdfName.A);
- if (attributes == null) {
- attributes = new PdfDictionary();
- attributes.put(PdfName.O, PdfName.TABLE);
- structureCurrentLeaf.put(PdfName.A, attributes);
- }
- if (rowspan != 1) {
- attributes.put(new PdfName("RowSpan"), new PdfNumber(rowspan));
- }
- if (colspan != 1) {
- attributes.put(new PdfName("ColSpan"), new PdfNumber(colspan));
+ PdfDictionary properties = null;
+ switch (tagType) {
+ case PdfTag.AUTO:
+ System.err.println("TODO: auto TagType found for area: " + area);
+ break;
+ case PdfTag.PAGE_HEADER:
+ properties = new PdfDictionary();
+ properties.put(PdfNames.TYPE, PdfNames.PAGINATION);
+ properties.put(PdfNames.SUBTYPE, PdfNames.HEADER);
+ currentPage.beginArtifact(properties);
+ break;
+ case PdfTag.PAGE_FOOTER:
+ properties = new PdfDictionary();
+ properties.put(PdfNames.TYPE, PdfNames.PAGINATION);
+ properties.put(PdfNames.SUBTYPE, PdfNames.FOOTER);
+ currentPage.beginArtifact(properties);
+ break;
+ default:
+ if (area instanceof ContainerArea && ((ContainerArea) area).isArtifact()) {
+ properties = new PdfDictionary();
+ // FIXME: It is not clear if Pagination or Page is the appropriate type for
+ // repeated header rows.
+ properties.put(PdfNames.TYPE, PdfNames.PAGINATION);
+ currentPage.beginArtifact(properties);
+ } else if (currentPage.isInArtifact()) {
+ // Do not open a tag inside artifacts.
+ } else {
+ if (area instanceof ContainerArea) {
+ final ContainerArea container = (ContainerArea) area;
+ if (container.isFirstPart()) {
+ if (PdfTag.TR.equals(tagType)) {
+ beforeOpenTableSectionTag(container);
}
- if (scope != null && "TH".equals(tagType)) {
- attributes.put(new PdfName("Scope"), pdfScope((scope)));
+ structureCurrentNode = new PdfStructureElement(structureCurrentNode, new PdfName(tagType));
+ try {
+ container.setStructureElement(structureCurrentNode);
+ } catch (BirtException be) {
+ be.printStackTrace();
+ structureCurrentNode = new PdfStructureElement(structureCurrentNode, new PdfName(tagType));
}
- if (headers != null) {
- attributes.put(new PdfName("Headers"), commaSeparatedToPdfByteStringArray((headers)));
+ } else {
+ structureCurrentNode = container.getFirstPart().getStructureElement();
+ PdfName restored = structureCurrentNode.getAsName(PdfName.S);
+ if (PdfName.TABLE.equals(restored)) {
+ // Also restore the table section, e.g. TBody.
+ PdfArray children = structureCurrentNode.getAsArray(PdfName.K); // K means "kids" in this
+ // context
+ if (children != null && children.size() > 0) {
+ structureCurrentNode = (PdfStructureElement) children.getAsDict(children.size() - 1);
+ }
}
}
+ } else {
+ structureCurrentNode = new PdfStructureElement(structureCurrentNode, new PdfName(tagType));
+ }
+ if (PdfTag.FIGURE.equals(tagType)) {
+ addFigureAttributes();
+ }
+ if (PdfTag.TD.equals(tagType) || PdfTag.TH.equals(tagType)) {
+ addTableCellAttributes(tagType, area);
}
}
}
}
+ /**
+ * Open a tag for the table section THead, TBody, TFoot when necessary.
+ *
+ * When opening a row, we want to open e.g. a TBody tag if it is the first row
+ * of the table body. If we are still in the wrong section, we have to close
+ * that section tag first before opening the new section tag.
+ *
+ * @param row the RowArea.
+ */
+ private void beforeOpenTableSectionTag(final ContainerArea row) {
+ PdfName currentTag = structureCurrentNode.getAsName(PdfName.S);
+ RowContent rowContent = (RowContent) row.getContent();
+ PdfName inject = null;
+ boolean closeSection = false;
+ switch (rowContent.getBand().getBandType()) {
+ case IBandContent.BAND_HEADER:
+ // THead
+ if (!currentTag.equals(PdfName.THEAD)) {
+ inject = PdfName.THEAD;
+ }
+ break;
+ case IBandContent.BAND_FOOTER:
+ // TFoot
+ if (!currentTag.equals(PdfName.TFOOT)) {
+ inject = PdfName.TFOOT;
+ closeSection = !currentTag.equals(PdfName.TABLE);
+ }
+ break;
+ default:
+ // TBody
+ if (!currentTag.equals(PdfName.TBODY)) {
+ inject = PdfName.TBODY;
+ closeSection = !currentTag.equals(PdfName.TABLE);
+ }
+ }
+ if (closeSection) {
+ structureCurrentNode = (PdfStructureElement) structureCurrentNode.getParent();
+ }
+ if (inject != null) {
+ structureCurrentNode = new PdfStructureElement(structureCurrentNode, inject);
+ }
+ }
+
+ /**
+ * Add attributes for a table cell.
+ *
+ * They are necessary to connect TD cells with their corresponding TH cells.
+ */
+ private void addTableCellAttributes(String tagType, IArea area) {
+ if (area instanceof CellArea) {
+ CellArea cellArea = (CellArea) area;
+ int rowspan = cellArea.getRowSpan();
+ int colspan = cellArea.getColSpan();
+ String scope = ((CellContent) (cellArea.getContent())).getScope();
+ String bookmark = cellArea.getBookmark();
+ if (bookmark != null) {
+ structureCurrentNode.put(PdfName.ID, new PdfString(bookmark));
+ }
+
+ String headers = ((CellContent) (cellArea.getContent())).getHeaders();
+ if (rowspan != 1 || colspan != 1 || scope != null || headers != null) {
+ PdfDictionary attributes = structureCurrentNode.getAsDict(PdfName.A);
+ if (attributes == null) {
+ attributes = new PdfDictionary();
+ attributes.put(PdfName.O, PdfName.TABLE);
+ structureCurrentNode.put(PdfName.A, attributes);
+ }
+ if (rowspan != 1) {
+ attributes.put(PdfNames.ROWSPAN, new PdfNumber(rowspan));
+ }
+ if (colspan != 1) {
+ attributes.put(PdfNames.COLSPAN, new PdfNumber(colspan));
+ }
+ if (scope != null && PdfTag.TH.equals(tagType)) {
+ attributes.put(PdfNames.SCOPE, pdfScope((scope)));
+ }
+ if (headers != null) {
+ attributes.put(PdfNames.HEADERS, commaSeparatedToPdfByteStringArray((headers)));
+ }
+ }
+ }
+ }
+
+ /**
+ * Add necessary attributes for figure tags.
+ *
+ * Top-Level figure elements must have a placement attribute.
+ */
+ private void addFigureAttributes() {
+ if (PdfName.DOCUMENT.equals(structureCurrentNode.getParent().get(PdfName.S))) {
+ PdfDictionary attributes = structureCurrentNode.getAsDict(PdfName.A);
+ if (attributes == null) {
+ attributes = new PdfDictionary();
+ structureCurrentNode.put(PdfName.A, attributes);
+ }
+ attributes.put(PdfNames.PLACEMENT, PdfNames.BLOCK);
+ attributes.put(PdfName.O, PdfNames.LAYOUT);
+ }
+ }
+
/**
* Split a comma-separated string into a PDF bytestring array. Note that blanks
* are not stripped and empty values are allowed.
@@ -1274,35 +1576,53 @@ private PdfName pdfScope(String scope) {
return null;
}
if ("col".equals(scope)) {
- return new PdfName("Column");
+ return PdfNames.COLUMN;
}
if ("row".equals(scope)) {
- return new PdfName("Row");
+ return PdfNames.ROW;
}
logger.warning("Unsupported scope: " + scope);
return null;
}
/**
- * @param tagType
+ * This is the opposite of openTag.
+ *
+ * Every tag that has been opened must be closed exactly once.
+ *
+ *
+ * If the PDF emitter is not configured to create tagged PDF, then this method
+ * is a no-op.
+ *
+ * @param tagType must be the same as in the call to openTag.
+ * @param area must be the same as in the call to openTag.
*/
- public void popTag(String tagType) {
- if (!writer.isTagged()) {
+ public void closeTag(String tagType, IArea area) {
+ if (!writer.isTagged() || tagType == null) {
return;
}
if ("pageHeader".equals(tagType)) {
currentPage.endArtifact();
} else if ("pageFooter".equals(tagType)) {
currentPage.endArtifact();
+ } else if (area instanceof ContainerArea && ((ContainerArea) area).isArtifact()) {
+ currentPage.endArtifact();
} else if (currentPage.isInArtifact()) {
- ;
+ // do nothing
} else {
- structureCurrentLeaf = (PdfStructureElement) structureCurrentLeaf.getParent();
+ if (PdfTag.TABLE.equals(tagType)) {
+ PdfName currentTag = structureCurrentNode.getAsName(PdfName.S);
+ if (!currentTag.equals(PdfNames.TR)) {
+ // Close the THead/TBody/TFoot tag also
+ structureCurrentNode = (PdfStructureElement) structureCurrentNode.getParent();
+ }
+ }
+ structureCurrentNode = (PdfStructureElement) structureCurrentNode.getParent();
}
}
/**
- * Is the writer is expected to create tagged PDF or not?
+ * @return Is the writer is expected to create tagged PDF or not?
*/
public boolean isTagged() {
return writer.isTagged();
diff --git a/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PDFRender.java b/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PDFRender.java
index 40622312ca1..f2d9c7b7e09 100644
--- a/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PDFRender.java
+++ b/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PDFRender.java
@@ -37,11 +37,19 @@
import org.eclipse.birt.report.engine.nLayout.area.IImageArea;
import org.eclipse.birt.report.engine.nLayout.area.ITemplateArea;
import org.eclipse.birt.report.engine.nLayout.area.ITextArea;
+import org.eclipse.birt.report.engine.nLayout.area.impl.InlineTextArea;
import org.eclipse.birt.report.engine.nLayout.area.impl.TableArea;
import org.eclipse.birt.report.engine.nLayout.area.style.TextStyle;
import org.eclipse.birt.report.model.api.ReportDesignHandle;
+import com.lowagie.text.pdf.PdfAnnotation;
+import com.lowagie.text.pdf.PdfArray;
+import com.lowagie.text.pdf.PdfDictionary;
+import com.lowagie.text.pdf.PdfIndirectReference;
import com.lowagie.text.pdf.PdfName;
+import com.lowagie.text.pdf.PdfNumber;
+import com.lowagie.text.pdf.PdfObject;
+import com.lowagie.text.pdf.PdfString;
import com.lowagie.text.pdf.PdfTemplate;
/**
@@ -65,8 +73,6 @@ public class PDFRender extends PageDeviceRender {
protected HashSet bookmarks = new HashSet<>();
- private PdfName ARTIFACT = new PdfName("Artifact");
-
/**
* Constructor
*
@@ -99,18 +105,32 @@ protected void newPage(IContainerArea page) {
public void visitImage(IImageArea imageArea) {
int imageX = currentX + getX(imageArea);
int imageY = currentY + getY(imageArea);
+ IHyperlinkAction hlAction = imageArea.getAction();
+ if (null != hlAction) {
+ currentPageDevice.openTag(PdfTag.LINK, imageArea);
+ }
super.visitImage(imageArea);
createBookmark(imageArea, imageX, imageY);
- createHyperlink(imageArea, imageX, imageY);
+ if (null != hlAction) {
+ createHyperlink(imageArea, imageX, imageY);
+ currentPageDevice.closeTag(PdfTag.LINK, imageArea);
+ }
}
@Override
public void visitText(ITextArea textArea) {
+ IHyperlinkAction hlAction = textArea.getAction();
+ if (null != hlAction) {
+ currentPageDevice.openTag(PdfTag.LINK, textArea);
+ }
super.visitText(textArea);
int x = currentX + getX(textArea);
int y = currentY + getY(textArea);
createBookmark(textArea, x, y);
- createHyperlink(textArea, x, y);
+ if (null != hlAction) {
+ createHyperlink(textArea, x, y);
+ currentPageDevice.closeTag(PdfTag.LINK, textArea);
+ }
}
@Override
@@ -155,11 +175,22 @@ public void end(IReportContent rc) {
@Override
protected void drawContainer(IContainerArea container) {
+ IHyperlinkAction hlAction = container.getAction();
+ if (container instanceof InlineTextArea) {
+ // A Hyperlink is created for the text already, we don't need it here.
+ hlAction = null;
+ }
+ if (null != hlAction) {
+ currentPageDevice.openTag(PdfTag.LINK, container);
+ }
super.drawContainer(container);
int x = currentX + getX(container);
int y = currentY + getY(container);
createBookmark(container, x, y);
- createHyperlink(container, x, y);
+ if (hlAction != null) {
+ createHyperlink(container, x, y);
+ currentPageDevice.closeTag(PdfTag.LINK, container);
+ }
}
/**
@@ -190,7 +221,8 @@ protected void drawTextAt(ITextArea text, int x, int y, int width, int height, T
}
}
- private void createHyperlink(IArea area, int x, int y) {
+ private PdfAnnotation createHyperlink(IArea area, int x, int y) {
+ PdfAnnotation annotation = null;
IHyperlinkAction hlAction = area.getAction();
if (null != hlAction) {
try {
@@ -212,24 +244,61 @@ private void createHyperlink(IArea area, int x, int y) {
} else {
link = hlAction.getHyperlink();
}
-
switch (type) {
case IHyperlinkAction.ACTION_BOOKMARK:
- currentPage.createHyperlink(link, bookmark, targetWindow, type, x, y, width, height);
+ annotation = currentPage.createHyperlink(link, bookmark, targetWindow, type, x, y, width, height);
break;
case IHyperlinkAction.ACTION_HYPERLINK:
- currentPage.createHyperlink(link, null, targetWindow, type, x, y, width, height);
+ annotation = currentPage.createHyperlink(link, null, targetWindow, type, x, y, width, height);
break;
case IHyperlinkAction.ACTION_DRILLTHROUGH:
- currentPage.createHyperlink(link, null, targetWindow, type, x, y, width, height);
+ annotation = currentPage.createHyperlink(link, null, targetWindow, type, x, y, width, height);
break;
}
} catch (Exception e) {
logger.log(Level.WARNING, e.getMessage(), e);
}
+ if (currentPageDevice.isTagged()) {
+ PdfArray children;
+ PdfObject childObject = currentPageDevice.structureCurrentNode.get(PdfName.K);
+ // The PdfName K means "kids" in this context.
+ if (childObject == null) {
+ children = new PdfArray();
+ currentPageDevice.structureCurrentNode.put(PdfName.K, children);
+ } else {
+ children = new PdfArray();
+ children.add(childObject);
+ currentPageDevice.structureCurrentNode.put(PdfName.K, children);
+ }
+ PdfDictionary objr = new PdfDictionary(PdfName.OBJR);
+ PdfIndirectReference annotationRef = annotation.getIndirectReference();
+ objr.put(PdfName.OBJ, annotationRef);
+ objr.put(PdfName.PG, currentPageDevice.writer.getCurrentPage());
+ children.add(objr);
+ // The link should contain a /Contents key, because it is required by PDF/UA-1.
+ // However, according to the PDF/UA Best Practice Guide, many or most current
+ // generation AT do not process this key and relaxation of the /Contents key
+ // requirement is anticipated in PDF/UA-2.
+ // If the area has a tooltip, we use that for the /Contents, otherwise we do
+ // not generate the /Contents entry.
+ String tooltip = hlAction.getTooltip();
+ if (tooltip != null) {
+ annotation.put(PdfName.CONTENTS, new PdfString(tooltip));
+ }
+ PdfIndirectReference linkref = currentPageDevice.structureCurrentNode.getReference();
+ int key = currentPageDevice.structureRoot.addExistingObject(linkref);
+ annotation.put(PdfName.STRUCTPARENT, new PdfNumber(key));
+ if (currentPageDevice.isPdfAFormat()) {
+ // See PDF specification Table 165 - Annotation flags
+ // and PDF/A-3 specification rules 6.3.2-1 and 6.3.2-2
+ annotation.put(PdfName.F, new PdfNumber(4));
+ }
+ }
+
}
+ return annotation;
}
@SuppressWarnings("unchecked")
@@ -276,13 +345,13 @@ protected void visitChildren(IContainerArea container) {
if (container.getChildrenCount() > 0) {
tagType = container.getTagType();
if (tagType != null) {
- currentPageDevice.pushTag(tagType, container);
+ currentPageDevice.openTag(tagType, container);
}
}
}
super.visitChildren(container);
if (tagType != null) {
- currentPageDevice.popTag(tagType);
+ currentPageDevice.closeTag(tagType, container);
}
}
@@ -298,7 +367,9 @@ private void createTotalPageTemplate(int x, int y, int width, int height, float
public void drawTableBorder(TableArea table) {
boolean tagged = currentPageDevice.isTagged();
if (tagged) {
- currentPage.beginArtifact();
+ PdfDictionary properties = new PdfDictionary();
+ properties.put(new PdfName("Type"), new PdfName("Background"));
+ currentPage.beginArtifact(properties);
}
super.drawTableBorder(table);
if (tagged) {
diff --git a/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PdfNames.java b/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PdfNames.java
new file mode 100644
index 00000000000..7227716e248
--- /dev/null
+++ b/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PdfNames.java
@@ -0,0 +1,56 @@
+package org.eclipse.birt.report.engine.emitter.pdf;
+
+import com.lowagie.text.pdf.PdfName;
+
+/**
+ * Some standard PdfName constants which are used inside BIRT code, but are
+ * missing in OpenPDF's PdfName class.
+ *
+ * As soon as OpenPDF also defines theses name constants, the corresponding
+ * names should be removed here and references to them should be replace with
+ * references to the OpenPDF's PdfName.
+ *
+ * A long term goal is to get rid of this class in BIRT once all names are
+ * defined in OpenPDF.
+ *
+ * Some of these names are PDF tag names, some are names of attributes, some are
+ * used for values of attributes.
+ *
+ * If you wonder, why we only have some constants here and create other PdfName
+ * objects from strings on the fly: It's because we hope to get the best
+ * performance this way. This class is related to class {@link PdfTag}. Creating
+ * a {@link com.lowagie.text.pdf.PdfName} involves a little overhead, and
+ * comparing it to another instance is a bit more complex than comparing two
+ * strings. We create these constant values here a priori when we need the name
+ * in source code, whereas we create the PdfName objects on the fly when the
+ * name is read from a design file.
+ *
+ * For a description of the usage of the names, please refer to the PDF
+ * specification EN/ISO 32000, in particular the sections about tagged PDF and
+ * accessibility.
+ *
+ * @since 4.19
+ *
+ */
+@SuppressWarnings("javadoc")
+public final class PdfNames {
+
+ public static final PdfName ALT = new PdfName("Alt");
+ public static final PdfName ARTIFACT = new PdfName("Artifact");
+ public static final PdfName BACKGROUND = PdfName.BACKGROUND;
+ public static final PdfName BLOCK = new PdfName("Block");
+ public static final PdfName COLSPAN = new PdfName("ColSpan");
+ public static final PdfName COLUMN = new PdfName("Column");
+ public static final PdfName FOOTER = new PdfName("Footer");
+ public static final PdfName HEADER = new PdfName("Header");
+ public static final PdfName HEADERS = new PdfName("Headers");
+ public static final PdfName LAYOUT = new PdfName("Layout");
+ public static final PdfName PAGINATION = new PdfName("Pagination");
+ public static final PdfName PLACEMENT = new PdfName("Placement");
+ public static final PdfName ROW = new PdfName("Row");
+ public static final PdfName ROWSPAN = new PdfName("RowSpan");
+ public static final PdfName SCOPE = new PdfName("Scope");
+ public static final PdfName SUBTYPE = PdfName.SUBTYPE;
+ public static final PdfName TYPE = PdfName.TYPE;
+ public static final PdfName TR = new PdfName(PdfTag.TR);
+}
diff --git a/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PdfTag.java b/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PdfTag.java
new file mode 100644
index 00000000000..cbc3b6f3e67
--- /dev/null
+++ b/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/PdfTag.java
@@ -0,0 +1,42 @@
+package org.eclipse.birt.report.engine.emitter.pdf;
+
+/**
+ * String constants for PDF tag names which are used by BIRT. These are tag
+ * names used for tagged PDF and PDF/UA, with a few exceptions which are
+ * meaningful only inside BIRT.
+ *
+ * @since 4.19
+ */
+public final class PdfTag {
+
+ /**
+ * This is not a PDF tag name, but used as a place-holder.
+ *
+ * The actual PDF tag name will be set depending on the context.
+ */
+ public static final String AUTO = "auto";
+
+ /** PDF tag used for the page header. */
+ public static final String PAGE_HEADER = "pageHeader";
+
+ /** PDF tag used for the page footer. */
+ public static final String PAGE_FOOTER = "pageFooter";
+
+ /** PDF tag used for images and charts. */
+ public static final String FIGURE = "Figure";
+
+ /** PDF tag used for hyperlinks. */
+ public static final String LINK = "Link";
+
+ /** PDF tag used for tables (but not for grids!). */
+ public static final String TABLE = "Table";
+
+ /** PDF tag used for table rows. */
+ public static final String TR = "TR";
+
+ /** PDF tag used for table header cells. */
+ public static final String TH = "TH";
+
+ /** PDF tag used for table data cells. */
+ public static final String TD = "TD";
+}
diff --git a/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/README.md b/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/README.md
index 4503c0e5f0f..c9ae51d41b3 100644
--- a/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/README.md
+++ b/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/README.md
@@ -27,7 +27,7 @@ The following list get an overview of all supported user properties, the content
Content define the PDF conformance of the created PDF document, e.g. PDF.A1A
Location report
Data type string
- Values PDF.Standard, PDF.X1A2001, PDF.X32002, PDF.A1A, PDF.A1B
+ Values PDF.Standard, PDF.X1A2001, PDF.X32002, PDF.A1A, PDF.A1B, PDF.A2A, PDF.A2B, PDF.A3A, PDF.A3B, PDF.A3U, PDF.A4F
Default PDF.Standard
Reference see PdfEmitter.IccColorType, PdfEmitter.IccProfileFile, PdfEmitter.PDFA.AddDocumentTitle, PdfEmitter.PDFA.FallbackFont, PdfEmitter.IncludeCidSet
Since 4.16
@@ -39,8 +39,8 @@ The following list get an overview of all supported user properties, the content
Content define the PDF/UA conformance of the created PDF document, e.g. PDF.UA-1
Location report
Data type string
- Values PDF.Standard, PDF.UA-1
- Default PDF.Standard
+ Values none, PDF.UA-1, PDF.UA-2
+ Default none
Since 4.18
Designer 4.18
diff --git a/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/TOCHandler.java b/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/TOCHandler.java
index 4fea1450f66..7e531c291b6 100644
--- a/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/TOCHandler.java
+++ b/engine/org.eclipse.birt.report.engine.emitter.pdf/src/org/eclipse/birt/report/engine/emitter/pdf/TOCHandler.java
@@ -86,8 +86,8 @@ public void createTOC() {
/**
* create a PDF outline for tocNode, using the pol as the parent PDF outline.
*
- * @param tocNode The tocNode whose kids need to build a PDF outline tree
- * @param pol The parent PDF outline for these kids
+ * @param tocNode The tocNode whose children need to build a PDF outline tree
+ * @param pol The parent PDF outline for these children
* @param bookmarks All bookMarks created during rendering
*/
protected void createTOC(TOCNode tocNode, PdfOutline pol, Set bookmarks) {
diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/executor/ReportItemExecutor.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/executor/ReportItemExecutor.java
index 29fafadea46..e35bd7edf06 100644
--- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/executor/ReportItemExecutor.java
+++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/executor/ReportItemExecutor.java
@@ -30,14 +30,11 @@
import org.eclipse.birt.report.engine.api.DataID;
import org.eclipse.birt.report.engine.api.DataSetID;
import org.eclipse.birt.report.engine.api.InstanceID;
-import org.eclipse.birt.report.engine.content.IBandContent;
import org.eclipse.birt.report.engine.content.IContent;
import org.eclipse.birt.report.engine.content.IGroupContent;
import org.eclipse.birt.report.engine.content.IHyperlinkAction;
import org.eclipse.birt.report.engine.content.IReportContent;
-import org.eclipse.birt.report.engine.content.impl.CellContent;
import org.eclipse.birt.report.engine.content.impl.Column;
-import org.eclipse.birt.report.engine.content.impl.RowContent;
import org.eclipse.birt.report.engine.extension.IBaseResultSet;
import org.eclipse.birt.report.engine.extension.ICubeResultSet;
import org.eclipse.birt.report.engine.extension.IExecutorContext;
@@ -619,17 +616,6 @@ protected void initializeContent(ReportElementDesign design, IContent content) {
DesignElementHandle h = ((DesignElementHandle) (rid.getHandle()));
if (h != null) {
content.setTagType(h.getTagType());
- if (content instanceof CellContent) {
- CellContent cell = (CellContent) content;
- RowContent row = (RowContent)cell.getParent();
- if (row.getBand() != null) {
- int bandType = row.getBand().getBandType();
- // FIXME This prevents that the report designer can override the tag type.
- if (bandType == IBandContent.BAND_HEADER || bandType == IBandContent.BAND_GROUP_HEADER) {
- content.setTagType("TH");
- }
- }
- }
}
}
}
@@ -722,10 +708,11 @@ protected void finishTOCEntry() {
protected void startGroupTOCEntry(IGroupContent group) {
TOCBuilder tocBuilder = context.getTOCBuilder();
if (tocBuilder != null) {
- TOCEntry entry = getParentTOCEntry();
+ TOCEntry parentTOCEntry = getParentTOCEntry();
String hiddenFormats = group.getStyle().getVisibleFormat();
long elementId = getElementId();
- tocEntry = tocBuilder.startGroupEntry(entry, group.getTOC(), group.getBookmark(), hiddenFormats, elementId);
+ tocEntry = tocBuilder.startGroupEntry(parentTOCEntry, group.getTOC(), group.getBookmark(), hiddenFormats,
+ elementId);
String tocId = tocEntry.getNodeId();
if (group.getBookmark() == null) {
group.setBookmark(tocId);
diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/i18n/Messages.properties b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/i18n/Messages.properties
index 0dbb68c3b6e..887965add9d 100644
--- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/i18n/Messages.properties
+++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/i18n/Messages.properties
@@ -117,7 +117,7 @@ Error.ResultsetExtractError = Result set not found.
Error.FailedToInitializeEmitter = Failed to initialize emitter.
###########################################################
-PDFCreator = BIRT Report Engine {0}.
+PDFCreator = BIRT Report Engine {0}
###########################################################
Error.FlashObjectNotSupported = Flash object report items are not supported in this report format.
diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/layout/pdf/util/HTML2Content.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/layout/pdf/util/HTML2Content.java
index b7804456c2d..f26c1c0d929 100644
--- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/layout/pdf/util/HTML2Content.java
+++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/layout/pdf/util/HTML2Content.java
@@ -633,7 +633,7 @@ static void handleElement(Element ele, Map cssStyles,
if ("".equals(text.getText())) // add default list type when tag
attribute is empty.
{
text.setText("\u2022"); // the disc type
- text.setTagType("Lbl"); // TODO HVB check if this is correct
+ text.setTagType("Lbl");
}
}
@@ -684,6 +684,7 @@ else if (lTagName.equals(TAG_DD) || lTagName.equals(TAG_DT)) // $NON-NLS-1$
} else if (htmlBlockDisplay.contains(lTagName) || htmlInlineDisplay.contains(lTagName)) {
IContainerContent container = content.getReportContent().createContainerContent();
handleStyle(ele, cssStyles, container);
+ mapHtmlTagToPdfTag(container, lTagName);
addChild(content, container);
// handleStyle(ele, cssStyles, container);
processNodes(ele, cssStyles, container, action, nestCount);
@@ -692,6 +693,34 @@ else if (lTagName.equals(TAG_DD) || lTagName.equals(TAG_DT)) // $NON-NLS-1$
}
}
+ // FIXME: The mapping should be configurable.
+ // FIXME: In particular, it should be possible to adjust the heading levels,
+ // (i.e to specify a positive or negative offset, such that H1->H2, H2->H3).
+ static final Map HTML_TAG_TO_PDF_TAG = Map.of(
+ "DIV", "DIV",
+ "P", "P",
+ "H1", "H1",
+ "H2", "H2",
+ "H3", "H3",
+ "H4", "H4"
+ );
+
+ /**
+ * This sets the container's tag type corresponding to the HTML tag, if
+ * possible.
+ *
+ * @param container
+ * @param lTagName
+ */
+ private static void mapHtmlTagToPdfTag(IContainerContent container, String lTagName) {
+ if (lTagName == null)
+ return;
+ String mapped = HTML_TAG_TO_PDF_TAG.get(lTagName.toUpperCase());
+ if (mapped != null) {
+ container.setTagType(mapped);
+ }
+ }
+
/**
* Outputs the A element
*
diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/PdfTagConstant.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/PdfTagConstant.java
new file mode 100644
index 00000000000..0edd32d778c
--- /dev/null
+++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/PdfTagConstant.java
@@ -0,0 +1,19 @@
+package org.eclipse.birt.report.engine.nLayout;
+
+/**
+ * Some constants are duplicated here to avoid dependencies.
+ *
+ * @see org.eclipse.birt.report.engine.emitter.pdf.PdfTag
+ *
+ * @since 4.19
+ *
+ */
+public class PdfTagConstant {
+ public static final String AUTO = "auto";
+ public static final String NONSTRUCT = "NonStruct";
+ public static final String P = "P";
+ public static final String TD = "TD";
+ public static final String TH = "TH";
+ public static final String TR = "TR";
+
+}
diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/BlockContainerArea.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/BlockContainerArea.java
index 3a7c147080a..c50c5b50656 100644
--- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/BlockContainerArea.java
+++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/BlockContainerArea.java
@@ -23,9 +23,11 @@
import org.eclipse.birt.core.exception.BirtException;
import org.eclipse.birt.report.engine.content.IContent;
import org.eclipse.birt.report.engine.content.IStyle;
+import org.eclipse.birt.report.engine.content.impl.ForeignContent;
import org.eclipse.birt.report.engine.css.engine.StyleConstants;
import org.eclipse.birt.report.engine.css.engine.value.css.CSSValueConstants;
import org.eclipse.birt.report.engine.nLayout.LayoutContext;
+import org.eclipse.birt.report.engine.nLayout.PdfTagConstant;
import org.eclipse.birt.report.engine.nLayout.area.IArea;
import org.eclipse.birt.report.engine.nLayout.area.IContainerArea;
import org.eclipse.birt.report.engine.nLayout.area.style.BoxStyle;
@@ -252,16 +254,22 @@ public SplitResult splitLines(int lineCount) throws BirtException {
@Override
public SplitResult split(int height, boolean force) throws BirtException {
+ final SplitResult ret;
if (force) {
- return _split(height, true);
+ ret = _split(height, true);
} else if (isPageBreakInsideAvoid()) {
if (isPageBreakBeforeAvoid()) {
- return SplitResult.BEFORE_AVOID_WITH_NULL;
+ ret = SplitResult.BEFORE_AVOID_WITH_NULL;
+ } else {
+ ret = SplitResult.SUCCEED_WITH_NULL;
}
- return SplitResult.SUCCEED_WITH_NULL;
} else {
- return _split(height, false);
+ ret = _split(height, false);
}
+ if (ret.getResult() != null) {
+ setPreviousPart(ret.getResult());
+ }
+ return ret;
}
protected SplitResult _split(int height, boolean force) throws BirtException {
@@ -503,4 +511,15 @@ public void updateChildrenPosition() {
}
}
+ @Override
+ public String getTagType() {
+ String tagType = super.getTagType();
+ if (PdfTagConstant.AUTO.equals(tagType)) {
+ if (getContent() instanceof ForeignContent) {
+ tagType = PdfTagConstant.NONSTRUCT;
+ }
+ }
+ return tagType;
+ }
+
}
diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/BlockTextArea.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/BlockTextArea.java
index c1f4f18c5fd..b7df46d3261 100644
--- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/BlockTextArea.java
+++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/BlockTextArea.java
@@ -24,6 +24,7 @@
import org.eclipse.birt.report.engine.content.ITextContent;
import org.eclipse.birt.report.engine.layout.pdf.util.PropertyUtil;
import org.eclipse.birt.report.engine.nLayout.LayoutContext;
+import org.eclipse.birt.report.engine.nLayout.PdfTagConstant;
import org.eclipse.birt.report.engine.nLayout.area.IArea;
import org.eclipse.birt.report.engine.nLayout.area.ILayout;
import org.w3c.dom.css.CSSValue;
@@ -203,4 +204,13 @@ public void setHelpText(String helpText) {
this.helpText = helpText;
}
+ @Override
+ public String getTagType() {
+ String tagType = super.getTagType();
+ if (PdfTagConstant.AUTO.equals(tagType)) {
+ tagType = PdfTagConstant.P;
+ }
+ return tagType;
+ }
+
}
diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/CellArea.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/CellArea.java
index f5ff394980e..6ca4f85f0da 100644
--- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/CellArea.java
+++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/CellArea.java
@@ -17,14 +17,17 @@
import java.awt.Color;
import org.eclipse.birt.core.exception.BirtException;
+import org.eclipse.birt.report.engine.content.IBandContent;
import org.eclipse.birt.report.engine.content.ICellContent;
import org.eclipse.birt.report.engine.content.IContent;
import org.eclipse.birt.report.engine.content.IStyle;
import org.eclipse.birt.report.engine.content.impl.ReportContent;
+import org.eclipse.birt.report.engine.content.impl.RowContent;
import org.eclipse.birt.report.engine.css.engine.StyleConstants;
import org.eclipse.birt.report.engine.executor.ExecutionContext;
import org.eclipse.birt.report.engine.layout.pdf.util.PropertyUtil;
import org.eclipse.birt.report.engine.nLayout.LayoutContext;
+import org.eclipse.birt.report.engine.nLayout.PdfTagConstant;
import org.eclipse.birt.report.engine.nLayout.area.IContainerArea;
import org.eclipse.birt.report.engine.nLayout.area.style.BackgroundImageInfo;
import org.eclipse.birt.report.engine.nLayout.area.style.BoxStyle;
@@ -352,4 +355,27 @@ public CellArea deepClone() {
return cell;
}
+ @Override
+ public String getTagType() {
+ String tagType = super.getTagType();
+ if (PdfTagConstant.AUTO.equals(tagType)) {
+ tagType = PdfTagConstant.TD;
+ // In a table row, either TH or TD depending on the row.
+ // In a grid row, no tag at all is used.
+ RowArea row = (RowArea) getParent();
+ if (row.getTableArea().isGridDesign()) {
+ tagType = null;
+ } else {
+ RowContent rowContent = (RowContent)row.getContent();
+ if (rowContent.getBand() != null) {
+ int bandType = rowContent.getBand().getBandType();
+ if (bandType == IBandContent.BAND_HEADER || bandType == IBandContent.BAND_GROUP_HEADER) {
+ tagType = PdfTagConstant.TH;
+ }
+ }
+ }
+ }
+ return tagType;
+ }
+
}
diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/ContainerArea.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/ContainerArea.java
index fae1812323f..8c29c0ed540 100644
--- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/ContainerArea.java
+++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/ContainerArea.java
@@ -47,6 +47,7 @@
import org.w3c.dom.css.CSSValue;
import com.lowagie.text.Image;
+import com.lowagie.text.pdf.PdfStructureElement;
/**
*
@@ -97,7 +98,151 @@ public abstract class ContainerArea extends AbstractArea implements IContainerAr
protected transient boolean isInInlineStacking = false;
+ /**
+ * It seems as if this variable is no longer used. I cannot find any place where
+ * it is actually read, except in a cloning constructor.
+ */
protected transient boolean first = true;
+
+ /**
+ * This is needed for split tracking in PDF/UA. For example, if a table is
+ * longer than one page, it must be a single table in the tag structure
+ * nevertheless, and the table element on the next page must be marked as an
+ * artifact.
+ *
+ * @since 4.19
+ *
+ */
+ public static class SplitTrackingData {
+ public SplitTrackingData() {
+ };
+
+ public ContainerArea firstPart = null;
+ public short partNumber = 1;
+ public short lastPartNumber = 1; // This is only defined for the first part (when partNumber is 1).
+ public boolean artifact = false;
+ }
+
+ /**
+ * Split tracking data is only present if a split actually occurs and we
+ * generate tagged PDF output, to save memory.
+ */
+ protected transient SplitTrackingData splitTracking = null;
+
+ /**
+ * Mark this area is a paging artifact.
+ */
+ public void setArtifact() {
+ if (splitTracking == null) {
+ splitTracking = new SplitTrackingData();
+ splitTracking.artifact = true;
+ }
+ }
+
+ /**
+ * Is this a paging artifact or not?
+ *
+ * @return true if is a paging artifact.
+ */
+ public boolean isArtifact() {
+ if (splitTracking == null) {
+ return false;
+ }
+ return splitTracking.artifact;
+ }
+
+ /**
+ * Track the split. Link to the first part, increment the last part number and
+ * set the partNumber.
+ *
+ * Note: previousPart was just created.
+ *
+ * Some parts of the tracking data are stored in the firstPart, while others are
+ * stored in the current area.
+ *
+ */
+ protected void setPreviousPart(ContainerArea previousPart) {
+ if (splitTracking == null) {
+ splitTracking = new SplitTrackingData();
+ }
+ previousPart.splitTracking = new SplitTrackingData();
+ if (splitTracking.partNumber == 1) {
+ splitTracking.firstPart = previousPart;
+ } else {
+ previousPart.splitTracking.firstPart = splitTracking.firstPart;
+ }
+ previousPart.splitTracking.partNumber = splitTracking.firstPart.splitTracking.lastPartNumber;
+ splitTracking.firstPart.splitTracking.lastPartNumber++;
+ splitTracking.partNumber = splitTracking.firstPart.splitTracking.lastPartNumber;
+ }
+
+ /**
+ * @return true if this Area is not split at all or if it is the first part of
+ * the split areas sequence.
+ */
+ public boolean isFirstPart() {
+ if (splitTracking == null) {
+ return true;
+ }
+ return splitTracking.partNumber == 1;
+ }
+
+ /**
+ * This links to the first part of the split sequence.
+ *
+ * @return First area of the split sequence, or null if this is already the
+ * first area (or not split at all).
+ */
+ public ContainerArea getFirstPart() {
+ if (splitTracking == null) {
+ return null;
+ }
+ return splitTracking.firstPart;
+ }
+
+ /**
+ * Get the number of the last part of the split sequence. This is equal to split
+ * sequence length.
+ *
+ * @return a natural number.
+ */
+ public short getLastPartNumber() {
+ if (splitTracking == null) {
+ return 1;
+ }
+ return splitTracking.lastPartNumber;
+ }
+
+ PdfStructureElement structureElement;
+
+ /**
+ * The PDF structure element (that is, a node inside the tag structure tree).
+ *
+ * This is only defined for the first part of the split sequence.
+ *
+ * @return The structure element, or null if not called for the first part of
+ * the split sequence.
+ */
+ public PdfStructureElement getStructureElement() {
+ return structureElement;
+ }
+
+ /**
+ * Set the PDF structure element (that is, a node inside the tag structure
+ * tree).
+ *
+ * @param structureElement a PDF structure element, derived from the PDF tag
+ * type.
+ * @throws BirtException if not called for the first part of the split sequence.
+ */
+ public void setStructureElement(PdfStructureElement structureElement) throws BirtException {
+ if (!isFirstPart()) {
+ throw new BirtException("Can only store a PdfStructureElement for the first part of a split Area");
+
+ }
+ this.structureElement = structureElement;
+ }
+
protected transient boolean finished = false;
protected CSSValue pageBreakAfter = null;
@@ -470,20 +615,31 @@ public void setPageBreakInside(CSSValue pageBreakInside) {
public abstract void initialize() throws BirtException;
/**
- * Split container lines
+ * Split container lines.
+ *
+ * This method need better documentation. What is its purpose?
*
* @param lineCount
- * @return Return the splitted results
+ * @return Return the split result
* @throws BirtException
*/
public abstract SplitResult splitLines(int lineCount) throws BirtException;
/**
- * Split container lines
+ * Try to split the container which does not fit into the current page.
+ *
+ * If the container is actually split, then the result status is
+ * SPLIT_SUCCEED_WITH_PART, and the result container is a new container which
+ * contains the first part of the split sequence, while this container is
+ * modified in place to remove that first part.
+ *
+ * If we think of this containers content as a List (which is isn't), this is
+ * logically similar to firtPart = this.content.remove(0);
*
- * @param height
- * @param force
- * @return Return the splitted results
+ * @param height The remaining page height.
+ * @param force If this is true, ignore "page-break-inside: avoid" and
+ * "page-break-before: avoid" properties.
+ * @return The split result.
* @throws BirtException
*/
public abstract SplitResult split(int height, boolean force) throws BirtException;
diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/InlineContainerArea.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/InlineContainerArea.java
index 69dca5698d1..f7ffbf552ff 100644
--- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/InlineContainerArea.java
+++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/InlineContainerArea.java
@@ -251,8 +251,13 @@ public String getTagType() {
Object generateBy = content.getGenerateBy();
if (generateBy == null) {
return "Span";
+ // Hmm... I would also like to just return null, but that results
+ // in an InvalidArgumentException: "The structure has kids" when rendered.
}
- return ((ITagType) generateBy).getTagType();
+ if (generateBy instanceof ITagType) {
+ return ((ITagType) generateBy).getTagType();
+ }
+ return null;
}
}
diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/RepeatableArea.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/RepeatableArea.java
index d9be596f9ca..a547681a860 100644
--- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/RepeatableArea.java
+++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/RepeatableArea.java
@@ -142,7 +142,17 @@ public SplitResult split(int height, boolean force) throws BirtException {
}
}
}
- return super.split(height, force);
+ SplitResult ret = super.split(height, force);
+ if (ret.status == SplitResult.SPLIT_SUCCEED_WITH_PART) {
+ Iterator i = children.iterator();
+ while (i.hasNext()) {
+ ContainerArea area = (ContainerArea) i.next();
+ if (isInRepeatHeader(area) || "Caption".equals(area.getTagType())) {
+ area.setArtifact();
+ }
+ }
+ }
+ return ret;
}
@Override
diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/RowArea.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/RowArea.java
index 2c5df51162b..002bed19b9f 100644
--- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/RowArea.java
+++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/RowArea.java
@@ -23,6 +23,7 @@
import org.eclipse.birt.report.engine.css.engine.value.css.CSSConstants;
import org.eclipse.birt.report.engine.css.engine.value.css.CSSValueConstants;
import org.eclipse.birt.report.engine.nLayout.LayoutContext;
+import org.eclipse.birt.report.engine.nLayout.PdfTagConstant;
import org.eclipse.birt.report.engine.nLayout.area.IArea;
/**
@@ -262,7 +263,11 @@ public void addChildByColumnId(CellArea cell) {
@Override
public SplitResult split(int height, boolean force) throws BirtException {
if (force) {
- return _split(height, force);
+ SplitResult ret = _split(height, force);
+ if (ret.getResult() != null) {
+ setPreviousPart(ret.getResult());
+ }
+ return ret;
} else if (isPageBreakInsideAvoid()) {
if (isPageBreakBeforeAvoid()) {
return SplitResult.BEFORE_AVOID_WITH_NULL;
@@ -271,7 +276,11 @@ public SplitResult split(int height, boolean force) throws BirtException {
needResolveBorder = true;
return SplitResult.SUCCEED_WITH_NULL;
}
- return _split(height, force);
+ SplitResult ret = _split(height, force);
+ if (ret.getResult() != null) {
+ setPreviousPart(ret.getResult());
+ }
+ return ret;
}
protected void _splitSpanCell(int height, boolean force) throws BirtException {
@@ -470,4 +479,16 @@ private void updateCellBackgroundImage() {
}
}
+ @Override
+ public String getTagType() {
+ String tagType = super.getTagType();
+ if (PdfTagConstant.AUTO.equals(tagType)) {
+ tagType = PdfTagConstant.TR;
+ if (getTableArea().isGridDesign()) {
+ tagType = null;
+ }
+ }
+ return tagType;
+ }
+
}
diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/TableArea.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/TableArea.java
index 98ab4d9f0b3..816df2fb4e7 100644
--- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/TableArea.java
+++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/nLayout/area/impl/TableArea.java
@@ -230,8 +230,10 @@ protected void addCaption(String caption) throws BirtException {
}
ReportContent report = (ReportContent) content.getReportContent();
IRowContent row = report.createRowContent();
+ row.setTagType("Caption");
row.setParent(content);
ICellContent cell = report.createCellContent();
+ cell.setTagType(null);
cell.setColSpan(getColumnCount());
cell.setColumn(0);
StyleDeclaration cstyle = new StyleDeclaration(report.getCSSEngine());
@@ -241,11 +243,13 @@ protected void addCaption(String caption) throws BirtException {
cell.setInlineStyle(cstyle);
cell.setParent(row);
ILabelContent captionLabel = report.createLabelContent();
+ captionLabel.setTagType(null);
captionLabel.setParent(cell);
captionLabel.setText(caption);
StyleDeclaration style = new StyleDeclaration(report.getCSSEngine());
style.setProperty(StyleConstants.STYLE_TEXT_ALIGN, CSSValueConstants.CENTER_VALUE);
captionLabel.setInlineStyle(style);
+ captionLabel.setTagType(null);
RowArea captionRow = new RowArea(this, context, row);
captionRow.isDummy = true;
captionRow.setParent(this);
@@ -257,7 +261,6 @@ protected void addCaption(String caption) throws BirtException {
captionCell.initialize();
captionCell.isDummy = true;
captionCell.setRowSpan(1);
- captionRow.children.add(captionCell);
BlockTextArea captionText = new BlockTextArea(captionCell, context, captionLabel);
captionText.isDummy = true;
captionText.layout();
@@ -265,7 +268,6 @@ protected void addCaption(String caption) throws BirtException {
captionCell.setContentHeight(h);
captionRow.setHeight(captionCell.getAllocatedHeight());
captionRow.finished = true;
- add(captionRow);
if (repeatList == null) {
repeatList = new ArrayList();
}
@@ -311,7 +313,7 @@ public SplitResult split(int height, boolean force) throws BirtException {
InstanceID unresolvedTableIID = unresolvedRow.getTableArea().getContent().getInstanceID();
// this iid can be null, because the table may be generated
// from HTML2Content.
- // in this case, they are ignored by unresloved row hint.
+ // in this case, they are ignored by unresolved row hint.
// Currently, large HTML text is not supported to be split.
if (unresolvedTableIID != null) {
pageHintGenerator.addUnresolvedRowHint(unresolvedTableIID.toUniqueString(),
diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/toc/TOCBuilder.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/toc/TOCBuilder.java
index f02cc5b3e29..3594bf5c7db 100644
--- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/toc/TOCBuilder.java
+++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/toc/TOCBuilder.java
@@ -75,6 +75,17 @@ public TOCEntry startEntry(TOCEntry parent, Object tocValue, String bookmark, St
return startEntry(parent, tocValue, bookmark, hiddenFormats, false, elementId);
}
+ /**
+ * This method seems to be only used in tests, not in the engine!
+ *
+ * @param parent
+ * @param tocValue
+ * @param bookmark
+ * @param elementId
+ * @return
+ *
+ * @deprecated for removal. Use the variant with hiddenFormats argument instead.
+ */
public TOCEntry startEntry(TOCEntry parent, Object tocValue, String bookmark, long elementId) {
return startEntry(parent, tocValue, bookmark, null, false, elementId);
}
diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/toc/TOCEntry.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/toc/TOCEntry.java
index 19cad9916d0..fff1c4bb2cb 100644
--- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/toc/TOCEntry.java
+++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/toc/TOCEntry.java
@@ -44,4 +44,16 @@ public void setTreeNode(ITreeNode treeNode) {
this.treeNode = treeNode;
}
+ /**
+ * Returns the nesting level.
+ *
+ * @return 0 for the root note, 1 + parent level otherwise.
+ */
+ public int getLevel() {
+ if (parent == null) {
+ return 0;
+ }
+ return 1 + parent.getLevel();
+ }
+
}
diff --git a/model/org.eclipse.birt.report.model/src/org/eclipse/birt/report/model/api/elements/table/LayoutTable.java b/model/org.eclipse.birt.report.model/src/org/eclipse/birt/report/model/api/elements/table/LayoutTable.java
index fd64797bf27..fe8284fe245 100644
--- a/model/org.eclipse.birt.report.model/src/org/eclipse/birt/report/model/api/elements/table/LayoutTable.java
+++ b/model/org.eclipse.birt.report.model/src/org/eclipse/birt/report/model/api/elements/table/LayoutTable.java
@@ -108,7 +108,7 @@ public int getColumnCount() {
}
/**
- * Return a cell element with the given poistion. Uses this method to find cells
+ * Return a cell element with the given position. Uses this method to find cells
* in Table Header, Detail and Footer slots.
*
* @param slotId the slot index,
diff --git a/model/org.eclipse.birt.report.model/src/org/eclipse/birt/report/model/elements/rom.def b/model/org.eclipse.birt.report.model/src/org/eclipse/birt/report/model/elements/rom.def
index dd2f459a536..3297c1befe8 100644
--- a/model/org.eclipse.birt.report.model/src/org/eclipse/birt/report/model/elements/rom.def
+++ b/model/org.eclipse.birt.report.model/src/org/eclipse/birt/report/model/elements/rom.def
@@ -695,16 +695,24 @@
+
+
+
+
+
+
+
-
-
+
+
+
@@ -1919,7 +1927,7 @@
PDF.Standard
- PDF.Standard
+ noneRGB
@@ -2893,7 +2901,7 @@
- P
+ auto
@@ -2997,7 +3005,7 @@
- P
+ auto
@@ -3089,7 +3097,7 @@
true
- P
+ auto
@@ -3502,7 +3510,7 @@
NoChange
- TR
+ auto
@@ -3558,7 +3566,7 @@
NoChange
- TD
+ auto
@@ -3796,7 +3804,7 @@
true
- P
+ auto
diff --git a/model/org.eclipse.birt.report.model/src/org/eclipse/birt/report/model/i18n/Messages.properties b/model/org.eclipse.birt.report.model/src/org/eclipse/birt/report/model/i18n/Messages.properties
index a1ee07f59cf..9c41e3944d2 100644
--- a/model/org.eclipse.birt.report.model/src/org/eclipse/birt/report/model/i18n/Messages.properties
+++ b/model/org.eclipse.birt.report.model/src/org/eclipse/birt/report/model/i18n/Messages.properties
@@ -764,11 +764,18 @@ Choices.emitterPdfVersion.1.4=Version 1.4
Choices.emitterPdfVersion.1.5=Version 1.5
Choices.emitterPdfVersion.1.6=Version 1.6
Choices.emitterPdfVersion.1.7=Version 1.7
+Choices.emitterPdfVersion.2.0=Version 2.0
#96 emitterPdfConformance
Choices.emitterPdfConformance.X32002=PDF, X32002
-Choices.emitterPdfConformance.A1A=PDF, A1A
-Choices.emitterPdfConformance.A1B=PDF, A1B
+Choices.emitterPdfConformance.A1A=PDF/A-1a
+Choices.emitterPdfConformance.A1B=PDF/A-1b
+Choices.emitterPdfConformance.A2A=PDF/A-2a
+Choices.emitterPdfConformance.A2B=PDF/A-2b
+Choices.emitterPdfConformance.A3A=PDF/A-3a
+Choices.emitterPdfConformance.A3B=PDF/A-3b
+Choices.emitterPdfConformance.A3U=PDF/A-3u
+Choices.emitterPdfConformance.A4F=PDF/A-4f
Choices.emitterPdfConformance.Standard=Standard PDF
#97 emitterPdfIccColorType
@@ -780,7 +787,9 @@ Choices.textDecoration.normal=Normal
Choices.textDecoration.none=None
#99 emitterPdfUAConformance
-Choices.emitterPdfConformance.UA1=PDF/UA-1
+Choices.emitterPdfUAConformance.UA1=PDF/UA-1
+Choices.emitterPdfUAConformance.UA2=PDF/UA-2
+Choices.emitterPdfUAConformance.none=None
###########################################################
# Classes