Skip to content

Commit 7f98c12

Browse files
author
Eugene Bochilo
committed
Support Intra-document destinations related rules in UA-2
DEVSIX-9006
1 parent f8d2086 commit 7f98c12

File tree

14 files changed

+903
-25
lines changed

14 files changed

+903
-25
lines changed

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

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ This file is part of the iText (R) project.
3535
import com.itextpdf.kernel.pdf.navigation.PdfExplicitDestination;
3636
import com.itextpdf.kernel.pdf.navigation.PdfStringDestination;
3737
import com.itextpdf.kernel.utils.NullCopyFilter;
38+
import com.itextpdf.kernel.validation.context.PdfDestinationAdditionContext;
3839

3940
import java.util.ArrayList;
4041
import java.util.Arrays;
@@ -178,6 +179,7 @@ protected boolean isWrappedObjectMustBeIndirect() {
178179
* @return destination
179180
*/
180181
public PdfCatalog setOpenAction(PdfDestination destination) {
182+
checkIsoConformanceForDestination(destination);
181183
return put(PdfName.OpenAction, destination.getPdfObject());
182184
}
183185

@@ -190,6 +192,7 @@ public PdfCatalog setOpenAction(PdfDestination destination) {
190192
* @return action
191193
*/
192194
public PdfCatalog setOpenAction(PdfAction action) {
195+
checkIsoConformanceForAction(action);
193196
return put(PdfName.OpenAction, action.getPdfObject());
194197
}
195198

@@ -203,6 +206,7 @@ public PdfCatalog setOpenAction(PdfAction action) {
203206
* @return additional action
204207
*/
205208
public PdfCatalog setAdditionalAction(PdfName key, PdfAction action) {
209+
checkIsoConformanceForAction(action);
206210
PdfAction.setAdditionalAction(this, key, action);
207211
return this;
208212
}
@@ -762,6 +766,16 @@ PdfDictionary fillAndGetOcPropertiesDictionary() {
762766
return getPdfObject().getAsDictionary(PdfName.OCProperties);
763767
}
764768

769+
private void checkIsoConformanceForDestination(PdfDestination destination) {
770+
getDocument().checkIsoConformance(new PdfDestinationAdditionContext(destination));
771+
}
772+
773+
private void checkIsoConformanceForAction(PdfAction action) {
774+
if (action != null && action.getPdfObject() != null) {
775+
getDocument().checkIsoConformance(new PdfDestinationAdditionContext(action));
776+
}
777+
}
778+
765779
private PdfDestination createDestinationFromPageNum(PdfObject dest, PdfDocument toDocument) {
766780
return new PdfExplicitDestination((PdfArray) dest.copyTo(toDocument, false,
767781
NullCopyFilter.getInstance()));
@@ -829,6 +843,7 @@ private void addOutlineToPage(PdfOutline outline, PdfDictionary item, IPdfNameTr
829843
PdfObject dest = item.get(PdfName.Dest);
830844
if (dest != null) {
831845
PdfDestination destination = PdfDestination.makeDestination(dest);
846+
checkIsoConformanceForDestination(destination);
832847
outline.setDestination(destination);
833848
addOutlineToPage(outline, names);
834849
} else {
@@ -838,11 +853,15 @@ private void addOutlineToPage(PdfOutline outline, PdfDictionary item, IPdfNameTr
838853
PdfName actionType = action.getAsName(PdfName.S);
839854
//Check if it is a go to action
840855
if (PdfName.GoTo.equals(actionType)) {
841-
//Retrieve destination if it is.
842-
PdfObject destObject = action.get(PdfName.D);
843-
if (destObject != null) {
844-
//Page is always the first object
845-
PdfDestination destination = PdfDestination.makeDestination(destObject);
856+
checkIsoConformanceForAction(new PdfAction(action));
857+
//First check if structure destination is present.
858+
PdfObject structureDestinationObject = action.get(PdfName.SD);
859+
if (structureDestinationObject != null) {
860+
PdfDestination destination = PdfDestination.makeDestination(structureDestinationObject);
861+
outline.setDestination(destination);
862+
addOutlineToPage(outline, names);
863+
} else if (action.get(PdfName.D) != null) {
864+
PdfDestination destination = PdfDestination.makeDestination(action.get(PdfName.D));
846865
outline.setDestination(destination);
847866
addOutlineToPage(outline, names);
848867
}

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ This file is part of the iText (R) project.
2727
import com.itextpdf.kernel.pdf.action.PdfAction;
2828
import com.itextpdf.kernel.pdf.colorspace.PdfColorSpace;
2929
import com.itextpdf.kernel.pdf.navigation.PdfDestination;
30+
import com.itextpdf.kernel.validation.context.PdfDestinationAdditionContext;
3031

3132
import java.util.ArrayList;
3233
import java.util.List;
@@ -212,9 +213,12 @@ public void addDestination(PdfDestination destination) {
212213
public void addAction(PdfAction action) {
213214
PdfName actionType = action.getPdfObject().getAsName(PdfName.S);
214215
if (PdfName.GoTo.equals(actionType)) {
215-
PdfObject destObject = action.getPdfObject().get(PdfName.D);
216-
if (destObject != null) {
217-
setDestination(PdfDestination.makeDestination(destObject));
216+
pdfDoc.checkIsoConformance(new PdfDestinationAdditionContext(action));
217+
PdfObject structureDestinationObject = action.getPdfObject().get(PdfName.SD);
218+
if (structureDestinationObject != null) {
219+
setDestination(PdfDestination.makeDestination(structureDestinationObject));
220+
} else if(action.getPdfObject().get(PdfName.D) != null) {
221+
setDestination(PdfDestination.makeDestination(action.getPdfObject().get(PdfName.D)));
218222
}
219223
}
220224

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ This file is part of the iText (R) project.
3232
import com.itextpdf.kernel.pdf.action.PdfAction;
3333
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
3434
import com.itextpdf.kernel.pdf.annot.PdfLinkAnnotation;
35-
import com.itextpdf.kernel.pdf.annot.PdfMarkupAnnotation;
3635
import com.itextpdf.kernel.pdf.annot.PdfPrinterMarkAnnotation;
3736
import com.itextpdf.kernel.pdf.annot.PdfWidgetAnnotation;
3837
import com.itextpdf.kernel.pdf.filespec.PdfFileSpec;
@@ -45,6 +44,7 @@ This file is part of the iText (R) project.
4544
import com.itextpdf.kernel.pdf.xobject.PdfImageXObject;
4645
import com.itextpdf.kernel.utils.ICopyFilter;
4746
import com.itextpdf.kernel.utils.NullCopyFilter;
47+
import com.itextpdf.kernel.validation.context.PdfDestinationAdditionContext;
4848
import com.itextpdf.kernel.validation.context.PdfPageValidationContext;
4949
import com.itextpdf.kernel.xmp.XMPException;
5050
import com.itextpdf.kernel.xmp.XMPMeta;
@@ -914,10 +914,24 @@ public PdfPage addAnnotation(int index, PdfAnnotation annotation, boolean tagAnn
914914
//Annots are indirect so need to be marked as modified
915915
annots.setModified();
916916
}
917+
checkIsoConformanceForDestinations(annotation);
917918

918919
return this;
919920
}
920921

922+
private void checkIsoConformanceForDestinations(PdfAnnotation annotation) {
923+
if (annotation instanceof PdfLinkAnnotation) {
924+
PdfLinkAnnotation linkAnnotation = (PdfLinkAnnotation) annotation;
925+
getDocument().checkIsoConformance(new PdfDestinationAdditionContext(linkAnnotation.getDestinationObject()));
926+
if (linkAnnotation.getAction() != null && PdfName.GoTo.equals(linkAnnotation.getAction().get(PdfName.S))) {
927+
// We only care about destinations, whose target lies within this document.
928+
// That's why GoToR and GoToE are ignored.
929+
getDocument().checkIsoConformance(
930+
new PdfDestinationAdditionContext(new PdfAction(linkAnnotation.getAction())));
931+
}
932+
}
933+
}
934+
921935
private boolean addAnnotationTag(TagTreePointer tagPointer, PdfAnnotation annotation) {
922936
if (annotation instanceof PdfLinkAnnotation) {
923937
// "Link" tag was added starting from PDF 1.4

kernel/src/main/java/com/itextpdf/kernel/pdf/navigation/PdfDestination.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,38 @@ protected PdfDestination(PdfObject pdfObject) {
4141
public abstract PdfObject getDestinationPage(IPdfNameTreeAccess names);
4242

4343

44+
/**
45+
* Creates {@link PdfDestination} implementation based on provided {@link PdfObject}.
46+
*
47+
* @param pdfObject {@link PdfObject} from which {@link PdfDestination} shall be created
48+
*
49+
* @return created {@link PdfDestination} implementation
50+
*/
4451
public static PdfDestination makeDestination(PdfObject pdfObject) {
52+
return makeDestination(pdfObject, true);
53+
}
54+
55+
/**
56+
* Creates {@link PdfDestination} implementation based on provided {@link PdfObject}.
57+
*
58+
* @param pdfObject {@link PdfObject} from which {@link PdfDestination} shall be created
59+
* @param throwException if {@code true}, throws exception in case of invalid parameter
60+
*
61+
* @return created {@link PdfDestination} implementation
62+
*/
63+
public static PdfDestination makeDestination(PdfObject pdfObject, boolean throwException) {
4564
if (pdfObject.getType() == PdfObject.STRING) {
4665
return new PdfStringDestination((PdfString) pdfObject);
4766
} else if (pdfObject.getType() == PdfObject.NAME) {
4867
return new PdfNamedDestination((PdfName) pdfObject);
4968
} else if (pdfObject.getType() == PdfObject.ARRAY) {
5069
PdfArray destArray = (PdfArray) pdfObject;
51-
if (destArray.size() == 0) {
52-
throw new IllegalArgumentException();
70+
if (destArray.isEmpty()) {
71+
if (throwException) {
72+
throw new IllegalArgumentException();
73+
} else {
74+
return null;
75+
}
5376
} else {
5477
PdfObject firstObj = destArray.get(0);
5578
// In case of explicit destination for remote go-to action this is a page number
@@ -63,8 +86,10 @@ public static PdfDestination makeDestination(PdfObject pdfObject) {
6386
// In case of structure destination this is a struct element dictionary or a string ID. Type is not required for structure elements
6487
return new PdfStructureDestination(destArray);
6588
}
66-
} else {
89+
} else if (throwException) {
6790
throw new UnsupportedOperationException();
91+
} else {
92+
return null;
6893
}
6994
}
7095
}

kernel/src/main/java/com/itextpdf/kernel/pdf/navigation/PdfStringDestination.java

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

2525
import com.itextpdf.kernel.pdf.IPdfNameTreeAccess;
2626
import com.itextpdf.kernel.pdf.PdfArray;
27+
import com.itextpdf.kernel.pdf.PdfDictionary;
28+
import com.itextpdf.kernel.pdf.PdfName;
2729
import com.itextpdf.kernel.pdf.PdfObject;
2830
import com.itextpdf.kernel.pdf.PdfString;
2931

@@ -40,9 +42,14 @@ public PdfStringDestination(PdfString pdfObject) {
4042

4143
@Override
4244
public PdfObject getDestinationPage(IPdfNameTreeAccess names) {
43-
PdfArray array = (PdfArray) names.getEntry((PdfString) getPdfObject());
44-
45-
return array != null ? array.get(0) : null;
45+
PdfObject destination = names.getEntry((PdfString) getPdfObject());
46+
if (destination instanceof PdfArray) {
47+
return ((PdfArray) destination).get(0);
48+
} else if (destination instanceof PdfDictionary) {
49+
PdfArray destinationArray = ((PdfDictionary) destination).getAsArray(PdfName.D);
50+
return destinationArray != null ? destinationArray.get(0) : null;
51+
}
52+
return null;
4653
}
4754

4855
@Override

kernel/src/main/java/com/itextpdf/kernel/validation/ValidationType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,6 @@ public enum ValidationType {
4747
CANVAS_BEGIN_MARKED_CONTENT,
4848
CANVAS_WRITING_CONTENT,
4949
LAYOUT,
50-
DUPLICATE_ID_ENTRY
50+
DUPLICATE_ID_ENTRY,
51+
DESTINATION_ADDITION
5152
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.itextpdf.kernel.validation.context;
2+
3+
import com.itextpdf.kernel.pdf.PdfArray;
4+
import com.itextpdf.kernel.pdf.PdfObject;
5+
import com.itextpdf.kernel.pdf.action.PdfAction;
6+
import com.itextpdf.kernel.pdf.navigation.PdfDestination;
7+
import com.itextpdf.kernel.validation.IValidationContext;
8+
import com.itextpdf.kernel.validation.ValidationType;
9+
10+
/**
11+
* Class which contains context in which destination was added.
12+
*/
13+
public class PdfDestinationAdditionContext implements IValidationContext {
14+
private final PdfDestination destination;
15+
private final PdfAction action;
16+
17+
/**
18+
* Creates {@link PdfDestinationAdditionContext} instance.
19+
*
20+
* @param destination {@link PdfDestination} instance which was added
21+
*/
22+
public PdfDestinationAdditionContext(PdfDestination destination) {
23+
this.destination = destination;
24+
this.action = null;
25+
}
26+
27+
/**
28+
* Creates {@link PdfDestinationAdditionContext} instance.
29+
*
30+
* @param destinationObject {@link PdfObject} which represents destination
31+
*/
32+
public PdfDestinationAdditionContext(PdfObject destinationObject) {
33+
// Second check is needed in case of destination page being partially flushed.
34+
if (destinationObject != null && !destinationObject.isFlushed() &&
35+
(!(destinationObject instanceof PdfArray) || !((PdfArray) destinationObject).get(0).isFlushed())) {
36+
this.destination = PdfDestination.makeDestination(destinationObject, false);
37+
} else {
38+
this.destination = null;
39+
}
40+
this.action = null;
41+
}
42+
43+
public PdfDestinationAdditionContext(PdfAction action) {
44+
this.destination = null;
45+
this.action = action;
46+
}
47+
48+
@Override
49+
public ValidationType getType() {
50+
return ValidationType.DESTINATION_ADDITION;
51+
}
52+
53+
/**
54+
* Gets {@link PdfDestination} instance.
55+
*
56+
* @return {@link PdfDestination} instance
57+
*/
58+
public PdfDestination getDestination() {
59+
return destination;
60+
}
61+
62+
public PdfAction getAction() {
63+
return action;
64+
}
65+
}

layout/src/test/java/com/itextpdf/layout/PdfUA2Test.java

Lines changed: 11 additions & 4 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.kernel.pdf.action.PdfAction;
4949
import com.itextpdf.kernel.pdf.annot.PdfLinkAnnotation;
5050
import com.itextpdf.kernel.pdf.filespec.PdfFileSpec;
51+
import com.itextpdf.kernel.pdf.navigation.PdfDestination;
5152
import com.itextpdf.kernel.pdf.navigation.PdfStructureDestination;
5253
import com.itextpdf.kernel.pdf.tagging.IStructureNode;
5354
import com.itextpdf.kernel.pdf.tagging.PdfNamespace;
@@ -984,10 +985,16 @@ public void checkOutlinesAsStructureDestinationsTest() throws IOException, XMPEx
984985

985986
try (PdfDocument pdfDocument = new PdfDocument(new PdfReader(outFile))) {
986987
PdfOutline outline = pdfDocument.getOutlines(false);
987-
Assertions.assertEquals("header1", outline.getAllChildren().get(0)
988-
.getDestination().getPdfObject().toString());
989-
Assertions.assertEquals("header1.1", outline.getAllChildren().get(0).getAllChildren().get(0)
990-
.getDestination().getPdfObject().toString());
988+
989+
PdfDictionary firstAction = outline.getAllChildren().get(0).getContent().getAsDictionary(PdfName.A);
990+
Assertions.assertEquals("header1", firstAction.getAsString(PdfName.D).toString());
991+
Assertions.assertTrue(PdfDestination.makeDestination(firstAction.get(PdfName.SD)) instanceof PdfStructureDestination);
992+
Assertions.assertTrue(outline.getAllChildren().get(0).getDestination() instanceof PdfStructureDestination);
993+
994+
PdfDictionary secondAction = outline.getAllChildren().get(0).getAllChildren().get(0).getContent().getAsDictionary(PdfName.A);
995+
Assertions.assertEquals("header1.1", secondAction.getAsString(PdfName.D).toString());
996+
Assertions.assertTrue(PdfDestination.makeDestination(secondAction.get(PdfName.SD)) instanceof PdfStructureDestination);
997+
Assertions.assertTrue(outline.getAllChildren().get(0).getAllChildren().get(0).getDestination() instanceof PdfStructureDestination);
991998
}
992999
}
9931000

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ This file is part of the iText (R) project.
3838
import com.itextpdf.kernel.validation.context.CanvasBmcValidationContext;
3939
import com.itextpdf.kernel.validation.context.CanvasWritingContentValidationContext;
4040
import com.itextpdf.kernel.validation.context.FontValidationContext;
41+
import com.itextpdf.kernel.validation.context.PdfDestinationAdditionContext;
4142
import com.itextpdf.kernel.validation.context.PdfDocumentValidationContext;
4243
import com.itextpdf.kernel.xmp.XMPConst;
4344
import com.itextpdf.kernel.xmp.XMPException;
@@ -48,6 +49,7 @@ This file is part of the iText (R) project.
4849
import com.itextpdf.pdfua.checkers.utils.PdfUAValidationContext;
4950
import com.itextpdf.pdfua.checkers.utils.tables.TableCheckUtil;
5051
import com.itextpdf.pdfua.checkers.utils.ua2.PdfUA2FormChecker;
52+
import com.itextpdf.pdfua.checkers.utils.ua2.PdfUA2DestinationsChecker;
5153
import com.itextpdf.pdfua.checkers.utils.ua2.PdfUA2FormulaChecker;
5254
import com.itextpdf.pdfua.checkers.utils.ua2.PdfUA2HeadingsChecker;
5355
import com.itextpdf.pdfua.checkers.utils.ua2.PdfUA2ListChecker;
@@ -94,6 +96,7 @@ public void validate(IValidationContext context) {
9496
checkCatalog(pdfDocContext.getPdfDocument().getCatalog());
9597
checkStructureTreeRoot(pdfDocContext.getPdfDocument().getStructTreeRoot());
9698
checkFonts(pdfDocContext.getDocumentFonts());
99+
new PdfUA2DestinationsChecker(pdfDocument).checkDestinations();
97100
PdfUA2XfaCheckUtil.check(pdfDocContext.getPdfDocument());
98101
break;
99102
case FONT:
@@ -114,6 +117,10 @@ public void validate(IValidationContext context) {
114117
new LayoutCheckUtil(this.context).checkRenderer(layoutContext.getRenderer());
115118
new PdfUA2HeadingsChecker(this.context).checkLayoutElement(layoutContext.getRenderer());
116119
break;
120+
case DESTINATION_ADDITION:
121+
PdfDestinationAdditionContext destinationAdditionContext = (PdfDestinationAdditionContext) context;
122+
new PdfUA2DestinationsChecker(destinationAdditionContext, pdfDocument).checkDestinationsOnCreation();
123+
break;
117124
}
118125
}
119126

0 commit comments

Comments
 (0)