Skip to content

Commit 834bae3

Browse files
committed
Add PDF/UA-2 annotation types checks
DEVSIX-9016
1 parent 4d6eeb7 commit 834bae3

18 files changed

+1405
-215
lines changed

kernel/src/main/java/com/itextpdf/kernel/pdf/PdfPage.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ This file is part of the iText (R) project.
2424

2525
import com.itextpdf.commons.utils.MessageFormatUtil;
2626
import com.itextpdf.io.logs.IoLogMessageConstant;
27-
import com.itextpdf.kernel.pdf.event.PdfDocumentEvent;
2827
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
2928
import com.itextpdf.kernel.exceptions.PdfException;
3029
import com.itextpdf.kernel.geom.PageSize;
@@ -34,6 +33,7 @@ This file is part of the iText (R) project.
3433
import com.itextpdf.kernel.pdf.annot.PdfLinkAnnotation;
3534
import com.itextpdf.kernel.pdf.annot.PdfPrinterMarkAnnotation;
3635
import com.itextpdf.kernel.pdf.annot.PdfWidgetAnnotation;
36+
import com.itextpdf.kernel.pdf.event.PdfDocumentEvent;
3737
import com.itextpdf.kernel.pdf.filespec.PdfFileSpec;
3838
import com.itextpdf.kernel.pdf.layer.PdfLayer;
3939
import com.itextpdf.kernel.pdf.tagging.PdfStructTreeRoot;
@@ -50,6 +50,8 @@ This file is part of the iText (R) project.
5050
import com.itextpdf.kernel.xmp.XMPMeta;
5151
import com.itextpdf.kernel.xmp.XMPMetaFactory;
5252
import com.itextpdf.kernel.xmp.options.SerializeOptions;
53+
import org.slf4j.Logger;
54+
import org.slf4j.LoggerFactory;
5355

5456
import java.io.IOException;
5557
import java.util.ArrayList;
@@ -59,9 +61,6 @@ This file is part of the iText (R) project.
5961
import java.util.List;
6062
import java.util.Set;
6163

62-
import org.slf4j.Logger;
63-
import org.slf4j.LoggerFactory;
64-
6564
public class PdfPage extends PdfObjectWrapper<PdfDictionary> {
6665

6766
private PdfResources resources = null;
@@ -952,6 +951,13 @@ private boolean addAnnotationTag(TagTreePointer tagPointer, PdfAnnotation annota
952951
}
953952
}
954953
}
954+
if (annotation instanceof PdfPrinterMarkAnnotation &&
955+
PdfVersion.PDF_2_0.compareTo(getDocument().getPdfVersion()) <= 0) {
956+
if (!StandardRoles.ARTIFACT.equals(tagPointer.getRole())) {
957+
tagPointer.addTag(StandardRoles.ARTIFACT);
958+
return true;
959+
}
960+
}
955961
}
956962
return false;
957963
}

pdfua/src/main/java/com/itextpdf/pdfua/checkers/PdfUA1Checker.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ This file is part of the iText (R) project.
5353
import com.itextpdf.kernel.xmp.XMPException;
5454
import com.itextpdf.kernel.xmp.XMPMeta;
5555
import com.itextpdf.layout.validation.context.LayoutValidationContext;
56-
import com.itextpdf.pdfua.checkers.utils.AnnotationCheckUtil;
5756
import com.itextpdf.pdfua.checkers.utils.GraphicsCheckUtil;
5857
import com.itextpdf.pdfua.checkers.utils.LayoutCheckUtil;
5958
import com.itextpdf.pdfua.checkers.utils.PdfUAValidationContext;
6059
import com.itextpdf.pdfua.checkers.utils.tables.TableCheckUtil;
60+
import com.itextpdf.pdfua.checkers.utils.ua1.PdfUA1AnnotationChecker;
6161
import com.itextpdf.pdfua.checkers.utils.ua1.PdfUA1FormChecker;
6262
import com.itextpdf.pdfua.checkers.utils.ua1.PdfUA1FormulaChecker;
6363
import com.itextpdf.pdfua.checkers.utils.ua1.PdfUA1HeadingsChecker;
@@ -270,7 +270,7 @@ private void checkStructureTreeRoot(PdfStructTreeRoot structTreeRoot) {
270270
tagTreeIterator.addHandler(new PdfUA1NotesChecker.PdfUA1NotesTagHandler(context));
271271
tagTreeIterator.addHandler(new PdfUA1HeadingsChecker.PdfUA1HeadingHandler(context));
272272
tagTreeIterator.addHandler(new TableCheckUtil.TableHandler(context));
273-
tagTreeIterator.addHandler(new AnnotationCheckUtil.AnnotationHandler(context));
273+
tagTreeIterator.addHandler(new PdfUA1AnnotationChecker.PdfUA1AnnotationHandler(context));
274274
tagTreeIterator.addHandler(new PdfUA1FormChecker.PdfUA1FormTagHandler(context));
275275
tagTreeIterator.addHandler(new PdfUA1ListChecker.PdfUA1ListHandler(context));
276276
tagTreeIterator.traverse();

pdfua/src/main/java/com/itextpdf/pdfua/checkers/PdfUA2Checker.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ This file is part of the iText (R) project.
4848
import com.itextpdf.pdfua.checkers.utils.LayoutCheckUtil;
4949
import com.itextpdf.pdfua.checkers.utils.PdfUAValidationContext;
5050
import com.itextpdf.pdfua.checkers.utils.tables.TableCheckUtil;
51+
import com.itextpdf.pdfua.checkers.utils.ua2.PdfUA2AnnotationChecker;
5152
import com.itextpdf.pdfua.checkers.utils.ua2.PdfUA2DestinationsChecker;
5253
import com.itextpdf.pdfua.checkers.utils.ua2.PdfUA2EmbeddedFilesChecker;
5354
import com.itextpdf.pdfua.checkers.utils.ua2.PdfUA2FormChecker;
@@ -165,11 +166,21 @@ private void checkCatalog(PdfCatalog catalog) {
165166
checkMetadata(catalog);
166167
checkViewerPreferences(catalog);
167168
checkOCProperties(catalog.getPdfObject().getAsDictionary(PdfName.OCProperties));
169+
checkFormFieldsAndAnnotations(catalog);
170+
PdfUA2EmbeddedFilesChecker.checkEmbeddedFiles(catalog);
171+
}
172+
173+
/**
174+
* Validates all annotations and form fields present in the document against PDF/UA-2 standard.
175+
*
176+
* @param catalog {@link PdfCatalog} to check form fields present in the acroform
177+
*/
178+
private void checkFormFieldsAndAnnotations(PdfCatalog catalog) {
168179
PdfUA2FormChecker formChecker = new PdfUA2FormChecker(context);
169180
formChecker.checkFormFields(catalog.getPdfObject().getAsDictionary(PdfName.AcroForm));
170181
formChecker.checkWidgetAnnotations(this.pdfDocument);
171182
PdfUA2LinkChecker.checkLinkAnnotations(this.pdfDocument);
172-
PdfUA2EmbeddedFilesChecker.checkEmbeddedFiles(catalog);
183+
PdfUA2AnnotationChecker.checkAnnotations(this.pdfDocument);
173184
}
174185

175186
/**
@@ -217,8 +228,8 @@ private void checkStructureTreeRoot(PdfStructTreeRoot structTreeRoot) {
217228
tagTreeIterator.addHandler(new GraphicsCheckUtil.GraphicsHandler(context));
218229
tagTreeIterator.addHandler(new PdfUA2HeadingsChecker.PdfUA2HeadingHandler(context));
219230
tagTreeIterator.addHandler(new TableCheckUtil.TableHandler(context));
220-
// TODO DEVSIX-9016 Support PDF/UA-2 rules for annotation types
221231
tagTreeIterator.addHandler(new PdfUA2FormChecker.PdfUA2FormTagHandler(context));
232+
tagTreeIterator.addHandler(new PdfUA2AnnotationChecker.PdfUA2AnnotationHandler(context));
222233
tagTreeIterator.addHandler(new PdfUA2ListChecker.PdfUA2ListHandler(context));
223234
tagTreeIterator.addHandler(new PdfUA2NotesChecker.PdfUA2NotesHandler(context));
224235
tagTreeIterator.addHandler(new PdfUA2TableOfContentsChecker.PdfUA2TableOfContentsHandler(context));

pdfua/src/main/java/com/itextpdf/pdfua/checkers/utils/AnnotationCheckUtil.java

Lines changed: 9 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,16 @@ This file is part of the iText (R) project.
2222
*/
2323
package com.itextpdf.pdfua.checkers.utils;
2424

25-
import com.itextpdf.commons.utils.MessageFormatUtil;
26-
import com.itextpdf.kernel.exceptions.PdfException;
27-
import com.itextpdf.kernel.pdf.PdfArray;
2825
import com.itextpdf.kernel.pdf.PdfDictionary;
29-
import com.itextpdf.kernel.pdf.PdfName;
30-
import com.itextpdf.kernel.pdf.PdfObject;
31-
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
3226
import com.itextpdf.kernel.pdf.tagging.IStructureNode;
33-
import com.itextpdf.kernel.pdf.tagging.PdfObjRef;
34-
import com.itextpdf.kernel.pdf.tagging.PdfStructElem;
35-
import com.itextpdf.pdfua.exceptions.PdfUAConformanceException;
36-
import com.itextpdf.pdfua.exceptions.PdfUAExceptionMessageConstants;
27+
import com.itextpdf.pdfua.checkers.utils.ua1.PdfUA1AnnotationChecker;
3728

3829
/**
3930
* Class that provides methods for checking PDF/UA compliance of annotations.
31+
*
32+
* @deprecated in favor of {@link PdfUA1AnnotationChecker}
4033
*/
34+
@Deprecated
4135
public final class AnnotationCheckUtil {
4236
private AnnotationCheckUtil() {
4337
// Empty constructor.
@@ -48,36 +42,18 @@ private AnnotationCheckUtil() {
4842
* set and annotation intersects CropBox (default value is MediaBox).
4943
*
5044
* @param annotDict annotation to check
51-
*
5245
* @return {@code true} if annotation should be checked, otherwise {@code false}
5346
*/
5447
public static boolean isAnnotationVisible(PdfDictionary annotDict) {
55-
if (annotDict.getAsNumber(PdfName.F) != null) {
56-
int flags = annotDict.getAsNumber(PdfName.F).intValue();
57-
if ((flags & PdfAnnotation.HIDDEN) != 0) {
58-
return false;
59-
}
60-
}
61-
if (annotDict.getAsDictionary(PdfName.P) != null) {
62-
PdfDictionary page = annotDict.getAsDictionary(PdfName.P);
63-
PdfArray pageBox = page.getAsArray(PdfName.CropBox) == null ? page.getAsArray(PdfName.MediaBox) : page.getAsArray(PdfName.CropBox);
64-
if (pageBox != null && annotDict.getAsArray(PdfName.Rect) != null) {
65-
PdfArray annotBox = annotDict.getAsArray(PdfName.Rect);
66-
try {
67-
if (pageBox.toRectangle().getIntersection(annotBox.toRectangle()) == null) {
68-
return false;
69-
}
70-
} catch (PdfException ignore) {
71-
// ignore
72-
}
73-
}
74-
}
75-
return true;
48+
return PdfUA1AnnotationChecker.isAnnotationVisible(annotDict);
7649
}
7750

7851
/**
7952
* Helper class that checks the conformance of annotations while iterating the tag tree structure.
53+
*
54+
* @deprecated in favor of {@link PdfUA1AnnotationChecker.PdfUA1AnnotationHandler}
8055
*/
56+
@Deprecated
8157
public static class AnnotationHandler extends ContextAwareTagTreeIteratorHandler {
8258

8359
/**
@@ -96,70 +72,7 @@ public boolean accept(IStructureNode node) {
9672

9773
@Override
9874
public void processElement(IStructureNode elem) {
99-
if (!(elem instanceof PdfObjRef)) {
100-
return;
101-
}
102-
PdfObjRef objRef = (PdfObjRef) elem;
103-
PdfDictionary annotObj = objRef.getReferencedObject();
104-
if (annotObj == null) {
105-
return;
106-
}
107-
108-
if (annotObj.getAsDictionary(PdfName.P) != null) {
109-
PdfDictionary pageDict = annotObj.getAsDictionary(PdfName.P);
110-
if (!PdfName.S.equals(pageDict.getAsName(PdfName.Tabs))) {
111-
throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.PAGE_WITH_ANNOT_DOES_NOT_HAVE_TABS_WITH_S);
112-
}
113-
}
114-
115-
PdfName subtype = annotObj.getAsName(PdfName.Subtype);
116-
117-
if (!isAnnotationVisible(annotObj) || PdfName.Popup.equals(subtype)) {
118-
return;
119-
}
120-
121-
if (PdfName.PrinterMark.equals(subtype)) {
122-
throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.PRINTER_MARK_IS_NOT_PERMITTED);
123-
}
124-
125-
if (PdfName.TrapNet.equals(subtype)) {
126-
throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.ANNOT_TRAP_NET_IS_NOT_PERMITTED);
127-
}
128-
129-
PdfStructElem parent = (PdfStructElem) objRef.getParent();
130-
if (!PdfName.Widget.equals(subtype) &&
131-
!(annotObj.containsKey(PdfName.Contents) || (parent != null && parent.getAlt() != null))) {
132-
throw new PdfUAConformanceException(MessageFormatUtil.format(
133-
PdfUAExceptionMessageConstants.ANNOTATION_OF_TYPE_0_SHOULD_HAVE_CONTENTS_OR_ALT_KEY, subtype.getValue()));
134-
}
135-
136-
if (PdfName.Link.equals(subtype)) {
137-
PdfStructElem parentLink = context.getElementIfRoleMatches(PdfName.Link, objRef.getParent());
138-
if (parentLink == null) {
139-
throw new PdfUAConformanceException(
140-
PdfUAExceptionMessageConstants.LINK_ANNOT_IS_NOT_NESTED_WITHIN_LINK);
141-
}
142-
if (!annotObj.containsKey(PdfName.Contents)) {
143-
throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.LINK_ANNOTATION_SHOULD_HAVE_CONTENTS_KEY);
144-
}
145-
}
146-
147-
if (PdfName.Screen.equals(subtype)) {
148-
PdfDictionary action = annotObj.getAsDictionary(PdfName.A);
149-
PdfDictionary additionalActions = annotObj.getAsDictionary(PdfName.AA);
150-
ActionCheckUtil.checkAction(action);
151-
checkAAEntry(additionalActions);
152-
}
153-
}
154-
155-
private static void checkAAEntry(PdfDictionary additionalActions) {
156-
if (additionalActions != null) {
157-
for (PdfObject val : additionalActions.values()) {
158-
if (val instanceof PdfDictionary) {
159-
ActionCheckUtil.checkAction((PdfDictionary) val);
160-
}
161-
}
162-
}
75+
PdfUA1AnnotationChecker.checkElement(this.context, elem);
16376
}
16477
}
16578
}

pdfua/src/main/java/com/itextpdf/pdfua/checkers/utils/FormCheckUtil.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ This file is part of the iText (R) project.
2626
import com.itextpdf.kernel.pdf.PdfName;
2727
import com.itextpdf.kernel.pdf.tagging.IStructureNode;
2828
import com.itextpdf.kernel.pdf.tagging.PdfStructElem;
29+
import com.itextpdf.pdfua.checkers.utils.ua1.PdfUA1AnnotationChecker;
2930
import com.itextpdf.pdfua.checkers.utils.ua1.PdfUA1FormChecker;
3031
import com.itextpdf.pdfua.exceptions.PdfUAConformanceException;
3132
import com.itextpdf.pdfua.exceptions.PdfUAExceptionMessageConstants;
@@ -80,7 +81,7 @@ public void processElement(IStructureNode elem) {
8081
}
8182

8283
// Check is not applicable for hidden annotations
83-
if (!AnnotationCheckUtil.isAnnotationVisible(formField)) {
84+
if (!PdfUA1AnnotationChecker.isAnnotationVisible(formField)) {
8485
return;
8586
}
8687

pdfua/src/main/java/com/itextpdf/pdfua/checkers/utils/LayoutCheckUtil.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This file is part of the iText (R) project.
2222
*/
2323
package com.itextpdf.pdfua.checkers.utils;
2424

25+
import com.itextpdf.forms.form.renderer.SignatureAppearanceRenderer;
2526
import com.itextpdf.layout.IPropertyContainer;
2627
import com.itextpdf.layout.element.Image;
2728
import com.itextpdf.layout.element.Table;
@@ -53,11 +54,26 @@ public void checkRenderer(IRenderer renderer) {
5354
if (renderer == null) {
5455
return;
5556
}
57+
if (isPartOfSignatureAppearance(renderer)) {
58+
// Tagging of the current layout element will be skipped in that case.
59+
return;
60+
}
5661
IPropertyContainer layoutElement = renderer.getModelElement();
5762
if (layoutElement instanceof Image) {
5863
new GraphicsCheckUtil(context).checkLayoutElement((Image) layoutElement);
5964
} else if (layoutElement instanceof Table) {
6065
new TableCheckUtil(context).checkTable((Table) layoutElement);
6166
}
6267
}
68+
69+
private boolean isPartOfSignatureAppearance(IRenderer renderer) {
70+
IRenderer parent = renderer.getParent();
71+
while (parent != null) {
72+
if (parent instanceof SignatureAppearanceRenderer) {
73+
return true;
74+
}
75+
parent = parent.getParent();
76+
}
77+
return false;
78+
}
6379
}

0 commit comments

Comments
 (0)