Skip to content

Commit 3413140

Browse files
committed
Add classes for correct creation of explicit destinations
DEVSIX-1779
1 parent e114c8c commit 3413140

File tree

8 files changed

+196
-8
lines changed

8 files changed

+196
-8
lines changed

io/src/main/java/com/itextpdf/io/LogMessageConstant.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public final class LogMessageConstant {
5959
public static final String COLOR_NOT_PARSED = "Color \"{0}\" was not parsed. It has invalid value. Defaulting to black color.";
6060
public static final String COULD_NOT_FIND_GLYPH_WITH_CODE = "Could not find glyph with the following code: {0}";
6161
public static final String DESTINATION_NOT_PERMITTED_WHEN_ACTION_IS_SET = "Destinations are not permitted for link annotations that already have actions. The old action will be removed.";
62+
public static final String EMBEDDED_GO_TO_DESTINATION_NOT_SPECIFIED = "No destination in the target was specified for action. Destination entry is mandatory for embedded go-to actions.";
6263
public static final String DIRECTONLY_OBJECT_CANNOT_BE_INDIRECT = "DirectOnly object cannot be indirect";
6364
public static final String DOCFONT_HAS_ILLEGAL_DIFFERENCES = "Document Font has illegal differences array. Entry {0} references a glyph ID over 255 and will be ignored.";
6465
public static final String DOCUMENT_ALREADY_HAS_FIELD = "The document already has field {0}. Annotations of the fields with this name will be added to the existing one as children. If you want to have separate fields, please, rename them manually before copying.";
@@ -85,6 +86,7 @@ public final class LogMessageConstant {
8586
public static final String INDIRECT_REFERENCE_USED_IN_FLUSHED_OBJECT_MADE_FREE = "An attempt is made to free an indirect reference which was already used in the flushed object. Indirect reference wasn't freed.";
8687
public static final String INLINE_BLOCK_ELEMENT_WILL_BE_CLIPPED = "Inline block element does not fit into parent element and will be clipped";
8788
public static final String INPUT_STREAM_CONTENT_IS_LOST_ON_PDFSTREAM_SERIALIZATION = "PdfStream contains not null input stream. It's content will be lost in serialized object.";
89+
public static final String INVALID_DESTINATION_TYPE = "When destination's not associated with a Remote or Embedded Go-To action, it shall specify page dictionary instead of page number. Otherwise destination might be considered invalid";
8890
public static final String INVALID_INDIRECT_REFERENCE = "Invalid indirect reference {0} {1} R";
8991
public static final String INVALID_KEY_VALUE_KEY_0_HAS_NULL_VALUE = "Invalid key value: key {0} has null value.";
9092
public static final String LAST_ROW_IS_NOT_COMPLETE = "Last row is not completed. Table bottom border may collapse as you do not expect it";

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,6 +1296,8 @@ public void initializeOutlines() {
12961296
*/
12971297
public void addNamedDestination(String key, PdfObject value) {
12981298
checkClosingStatus();
1299+
if (value.isArray() && ((PdfArray)value).get(0).isNumber())
1300+
LoggerFactory.getLogger(PdfDocument.class).warn(LogMessageConstant.INVALID_DESTINATION_TYPE);
12991301
catalog.addNamedDestination(key, value);
13001302
}
13011303

kernel/src/main/java/com/itextpdf/kernel/pdf/action/PdfAction.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ This file is part of the iText (R) project.
4343
*/
4444
package com.itextpdf.kernel.pdf.action;
4545

46+
import com.itextpdf.io.LogMessageConstant;
4647
import com.itextpdf.kernel.PdfException;
4748
import com.itextpdf.kernel.pdf.PdfArray;
4849
import com.itextpdf.kernel.pdf.PdfBoolean;
@@ -59,7 +60,9 @@ This file is part of the iText (R) project.
5960
import com.itextpdf.kernel.pdf.filespec.PdfStringFS;
6061
import com.itextpdf.kernel.pdf.navigation.PdfDestination;
6162
import com.itextpdf.kernel.pdf.navigation.PdfExplicitDestination;
63+
import com.itextpdf.kernel.pdf.navigation.PdfExplicitRemoteGoToDestination;
6264
import com.itextpdf.kernel.pdf.navigation.PdfStringDestination;
65+
import org.slf4j.LoggerFactory;
6366

6467
import java.util.List;
6568

@@ -154,6 +157,7 @@ public PdfAction(PdfDictionary pdfObject) {
154157
* @return created action
155158
*/
156159
public static PdfAction createGoTo(PdfDestination destination) {
160+
validateNotRemoteDestination(destination);
157161
return new PdfAction().put(PdfName.S, PdfName.GoTo).put(PdfName.D, destination.getPdfObject());
158162
}
159163

@@ -176,8 +180,7 @@ public static PdfAction createGoTo(String destination) {
176180
* @return created action
177181
*/
178182
public static PdfAction createGoToR(PdfFileSpec fileSpec, PdfDestination destination, boolean newWindow) {
179-
return new PdfAction().put(PdfName.S, PdfName.GoToR).put(PdfName.F, fileSpec.getPdfObject()).
180-
put(PdfName.D, destination.getPdfObject()).put(PdfName.NewWindow, PdfBoolean.valueOf(newWindow));
183+
return createGoToR(fileSpec, destination).put(PdfName.NewWindow, PdfBoolean.valueOf(newWindow));
181184
}
182185

183186
/**
@@ -188,6 +191,7 @@ public static PdfAction createGoToR(PdfFileSpec fileSpec, PdfDestination destina
188191
* @return created action
189192
*/
190193
public static PdfAction createGoToR(PdfFileSpec fileSpec, PdfDestination destination) {
194+
validateRemoteDestination(destination);
191195
return new PdfAction().put(PdfName.S, PdfName.GoToR).put(PdfName.F, fileSpec.getPdfObject()).
192196
put(PdfName.D, destination.getPdfObject());
193197
}
@@ -212,7 +216,7 @@ public static PdfAction createGoToR(String filename, int pageNum) {
212216
* @return created action
213217
*/
214218
public static PdfAction createGoToR(String filename, int pageNum, boolean newWindow) {
215-
return createGoToR(new PdfStringFS(filename), PdfExplicitDestination.createFitH(pageNum, 10000), newWindow);
219+
return createGoToR(new PdfStringFS(filename), PdfExplicitRemoteGoToDestination.createFitH(pageNum, 10000), newWindow);
216220
}
217221

218222
/**
@@ -271,7 +275,10 @@ public static PdfAction createGoToE(PdfFileSpec fileSpec, PdfDestination destina
271275
action.put(PdfName.F, fileSpec.getPdfObject());
272276
}
273277
if (destination != null) {
278+
validateRemoteDestination(destination);
274279
action.put(PdfName.D, destination.getPdfObject());
280+
} else {
281+
LoggerFactory.getLogger(PdfAction.class).warn(LogMessageConstant.EMBEDDED_GO_TO_DESTINATION_NOT_SPECIFIED);
275282
}
276283
if (targetDictionary != null) {
277284
action.put(PdfName.T, targetDictionary.getPdfObject());
@@ -683,4 +690,30 @@ else if (obj instanceof PdfAnnotation)
683690
}
684691
return array;
685692
}
693+
694+
private static void validateRemoteDestination(PdfDestination destination) {
695+
// No page object can be specified for a destination associated with a remote go-to action because the
696+
// destination page is in a different PDF document. In this case, the page parameter specifies an integer
697+
// page number within the remote document instead of a page object in the current document.
698+
// See section 12.3.2.2 of ISO 32000-1.
699+
if (destination instanceof PdfExplicitDestination) {
700+
PdfObject firstObj = ((PdfArray)destination.getPdfObject()).get(0);
701+
if (firstObj.isDictionary()) {
702+
throw new IllegalArgumentException("Explicit destinations shall specify page number in remote go-to actions instead of page dictionary");
703+
}
704+
}
705+
}
706+
707+
public static void validateNotRemoteDestination(PdfDestination destination) {
708+
if (destination instanceof PdfExplicitRemoteGoToDestination) {
709+
LoggerFactory.getLogger(PdfAction.class).warn(LogMessageConstant.INVALID_DESTINATION_TYPE);
710+
} else if (destination instanceof PdfExplicitDestination) {
711+
// No page number can be specified for a destination associated with a not remote go-to action because the
712+
// destination page is in a current PDF document. See section 12.3.2.2 of ISO 32000-1.
713+
PdfObject firstObj = ((PdfArray)destination.getPdfObject()).get(0);
714+
if (firstObj.isNumber()) {
715+
LoggerFactory.getLogger(PdfAction.class).warn(LogMessageConstant.INVALID_DESTINATION_TYPE);
716+
}
717+
}
718+
}
686719
}

kernel/src/main/java/com/itextpdf/kernel/pdf/annot/PdfLinkAnnotation.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ public PdfLinkAnnotation setDestination(PdfObject destination) {
9494
getPdfObject().remove(PdfName.A);
9595
logger.warn(LogMessageConstant.DESTINATION_NOT_PERMITTED_WHEN_ACTION_IS_SET);
9696
}
97+
if (destination.isArray() && ((PdfArray)destination).get(0).isNumber())
98+
LoggerFactory.getLogger(PdfLinkAnnotation.class).warn(LogMessageConstant.INVALID_DESTINATION_TYPE);
9799
return (PdfLinkAnnotation) put(PdfName.Dest, destination);
98100
}
99101

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,22 @@ public static PdfDestination makeDestination(PdfObject pdfObject) {
7474
return new PdfStringDestination((PdfString) pdfObject);
7575
else if (pdfObject.getType() == PdfObject.NAME)
7676
return new PdfNamedDestination((PdfName) pdfObject);
77-
else if (pdfObject.getType() == PdfObject.ARRAY)
78-
return new PdfExplicitDestination((PdfArray) pdfObject);
79-
else
77+
else if (pdfObject.getType() == PdfObject.ARRAY) {
78+
PdfArray destArray = (PdfArray) pdfObject;
79+
if (destArray.size() == 0) {
80+
throw new IllegalArgumentException();
81+
} else {
82+
PdfObject firstObj = destArray.get(0);
83+
// In case of explicit destination for remote go-to action this is a page number
84+
if (firstObj.isNumber()) {
85+
return new PdfExplicitRemoteGoToDestination(destArray);
86+
} else {
87+
// In case of explicit destination for not remote go-to action this is a page dictionary
88+
return new PdfExplicitDestination(destArray);
89+
}
90+
}
91+
} else {
8092
throw new UnsupportedOperationException();
93+
}
8194
}
8295
}

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ This file is part of the iText (R) project.
5151

5252
import java.util.Map;
5353

54+
/**
55+
* This class shall be used for creation of destinations, associated with outline items, annotations
56+
* or actions within current document.
57+
* If you need to create a destination, associated with an object in another PDF
58+
* (e.g. Remote Go-To actions oe Embedded Go-To actions), you should use {@link PdfExplicitRemoteGoToDestination} class instead.
59+
* Note that despite methods with integer value for page parameter are deprecated in this class,
60+
* Adobe Acrobat handles such destinations correctly, but removes them completely from a PDF,
61+
* when it is saved as an optimized pdf with the "discard-invalid-links" option.
62+
* Therefore it is strongly recommended to use methods accepting pdfPage instance, if the destination is inside of the current document.
63+
*/
5464
public class PdfExplicitDestination extends PdfDestination {
5565

5666
private static final long serialVersionUID = -1515785642472963298L;
@@ -81,6 +91,10 @@ public static PdfExplicitDestination createXYZ(PdfPage page, float left, float t
8191
return create(page, PdfName.XYZ, left, Float.NaN, Float.NaN, top, zoom);
8292
}
8393

94+
/**
95+
* @deprecated Use {@link PdfExplicitRemoteGoToDestination#createXYZ(int, float, float, float)} instead.
96+
*/
97+
@Deprecated
8498
public static PdfExplicitDestination createXYZ(int pageNum, float left, float top, float zoom) {
8599
return create(pageNum, PdfName.XYZ, left, Float.NaN, Float.NaN, top, zoom);
86100
}
@@ -89,6 +103,10 @@ public static PdfExplicitDestination createFit(PdfPage page) {
89103
return create(page, PdfName.Fit, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN);
90104
}
91105

106+
/**
107+
* @deprecated Use {@link PdfExplicitRemoteGoToDestination#createFit(int)} instead.
108+
*/
109+
@Deprecated
92110
public static PdfExplicitDestination createFit(int pageNum) {
93111
return create(pageNum, PdfName.Fit, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN);
94112
}
@@ -97,6 +115,10 @@ public static PdfExplicitDestination createFitH(PdfPage page, float top) {
97115
return create(page, PdfName.FitH, Float.NaN, Float.NaN, Float.NaN, top, Float.NaN);
98116
}
99117

118+
/**
119+
* @deprecated Use {@link PdfExplicitRemoteGoToDestination#createFitH(int, float)} instead.
120+
*/
121+
@Deprecated
100122
public static PdfExplicitDestination createFitH(int pageNum, float top) {
101123
return create(pageNum, PdfName.FitH, Float.NaN, Float.NaN, Float.NaN, top, Float.NaN);
102124
}
@@ -105,6 +127,10 @@ public static PdfExplicitDestination createFitV(PdfPage page, float left) {
105127
return create(page, PdfName.FitV, left, Float.NaN, Float.NaN, Float.NaN, Float.NaN);
106128
}
107129

130+
/**
131+
* @deprecated Use {@link PdfExplicitRemoteGoToDestination#createFitV(int, float)} instead.
132+
*/
133+
@Deprecated
108134
public static PdfExplicitDestination createFitV(int pageNum, float left) {
109135
return create(pageNum, PdfName.FitV, left, Float.NaN, Float.NaN, Float.NaN, Float.NaN);
110136
}
@@ -113,6 +139,10 @@ public static PdfExplicitDestination createFitR(PdfPage page, float left, float
113139
return create(page, PdfName.FitR, left, bottom, right, top, Float.NaN);
114140
}
115141

142+
/**
143+
* @deprecated Use {@link PdfExplicitRemoteGoToDestination#createFitR(int, float, float, float, float)} instead.
144+
*/
145+
@Deprecated
116146
public static PdfExplicitDestination createFitR(int pageNum, float left, float bottom, float right, float top) {
117147
return create(pageNum, PdfName.FitR, left, bottom, right, top, Float.NaN);
118148
}
@@ -121,6 +151,10 @@ public static PdfExplicitDestination createFitB(PdfPage page) {
121151
return create(page, PdfName.FitB, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN);
122152
}
123153

154+
/**
155+
* @deprecated Use {@link PdfExplicitRemoteGoToDestination#createFitB(int)} instead.
156+
*/
157+
@Deprecated
124158
public static PdfExplicitDestination createFitB(int pageNum) {
125159
return create(pageNum, PdfName.FitB, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN);
126160
}
@@ -129,6 +163,10 @@ public static PdfExplicitDestination createFitBH(PdfPage page, float top) {
129163
return create(page, PdfName.FitBH, Float.NaN, Float.NaN, Float.NaN, top, Float.NaN);
130164
}
131165

166+
/**
167+
* @deprecated Use {@link PdfExplicitRemoteGoToDestination#createFitBH(int, float)} instead.
168+
*/
169+
@Deprecated
132170
public static PdfExplicitDestination createFitBH(int pageNum, float top) {
133171
return create(pageNum, PdfName.FitBH, Float.NaN, Float.NaN, Float.NaN, top, Float.NaN);
134172
}
@@ -137,6 +175,10 @@ public static PdfExplicitDestination createFitBV(PdfPage page, float left) {
137175
return create(page, PdfName.FitBH, left, Float.NaN, Float.NaN, Float.NaN, Float.NaN);
138176
}
139177

178+
/**
179+
* @deprecated Use {@link PdfExplicitRemoteGoToDestination#createFitBV(int, float)} instead.
180+
*/
181+
@Deprecated
140182
public static PdfExplicitDestination createFitBV(int pageNum, float left) {
141183
return create(pageNum, PdfName.FitBH, left, Float.NaN, Float.NaN, Float.NaN, Float.NaN);
142184
}
@@ -145,6 +187,10 @@ public static PdfExplicitDestination create(PdfPage page, PdfName type, float le
145187
return new PdfExplicitDestination().add(page).add(type).add(left).add(bottom).add(right).add(top).add(zoom);
146188
}
147189

190+
/**
191+
* @deprecated Use {@link PdfExplicitRemoteGoToDestination#create(int, PdfName, float, float, float, float, float)} instead.
192+
*/
193+
@Deprecated
148194
public static PdfExplicitDestination create(int pageNum, PdfName type, float left, float bottom, float right, float top, float zoom) {
149195
return new PdfExplicitDestination().add(--pageNum).add(type).add(left).add(bottom).add(right).add(top).add(zoom);
150196
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.itextpdf.kernel.pdf.navigation;
2+
3+
import com.itextpdf.kernel.pdf.PdfArray;
4+
import com.itextpdf.kernel.pdf.PdfName;
5+
import com.itextpdf.kernel.pdf.PdfNumber;
6+
import com.itextpdf.kernel.pdf.PdfObject;
7+
8+
import java.util.Map;
9+
10+
/**
11+
* This class shall be used for creation of destinations, associated Remote Go-To and Embedded Go-To actions only,
12+
* i.e. the destination point is in another PDF.
13+
* If you need to create a destination, associated with an object inside current PDF, you should use {@link PdfExplicitDestination} class instead.
14+
*/
15+
public class PdfExplicitRemoteGoToDestination extends PdfDestination {
16+
17+
private static final long serialVersionUID = 5354781072160968173L;
18+
19+
public PdfExplicitRemoteGoToDestination() {
20+
this(new PdfArray());
21+
}
22+
23+
public PdfExplicitRemoteGoToDestination(PdfArray pdfObject) {
24+
super(pdfObject);
25+
}
26+
27+
@Override
28+
public PdfObject getDestinationPage(Map<String, PdfObject> names) {
29+
return ((PdfArray)getPdfObject()).get(0);
30+
}
31+
32+
@Override
33+
public PdfDestination replaceNamedDestination(Map<Object, PdfObject> names) { return this; }
34+
35+
public static PdfExplicitRemoteGoToDestination createXYZ(int pageNum, float left, float top, float zoom) {
36+
return create(pageNum, PdfName.XYZ, left, Float.NaN, Float.NaN, top, zoom);
37+
}
38+
39+
public static PdfExplicitRemoteGoToDestination createFit(int pageNum) {
40+
return create(pageNum, PdfName.Fit, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN);
41+
}
42+
43+
public static PdfExplicitRemoteGoToDestination createFitH(int pageNum, float top) {
44+
return create(pageNum, PdfName.FitH, Float.NaN, Float.NaN, Float.NaN, top, Float.NaN);
45+
}
46+
47+
public static PdfExplicitRemoteGoToDestination createFitV(int pageNum, float left) {
48+
return create(pageNum, PdfName.FitV, left, Float.NaN, Float.NaN, Float.NaN, Float.NaN);
49+
}
50+
51+
public static PdfExplicitRemoteGoToDestination createFitR(int pageNum, float left, float bottom, float right, float top) {
52+
return create(pageNum, PdfName.FitR, left, bottom, right, top, Float.NaN);
53+
}
54+
55+
public static PdfExplicitRemoteGoToDestination createFitB(int pageNum) {
56+
return create(pageNum, PdfName.FitB, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN);
57+
}
58+
59+
public static PdfExplicitRemoteGoToDestination createFitBH(int pageNum, float top) {
60+
return create(pageNum, PdfName.FitBH, Float.NaN, Float.NaN, Float.NaN, top, Float.NaN);
61+
}
62+
63+
public static PdfExplicitRemoteGoToDestination createFitBV(int pageNum, float left) {
64+
return create(pageNum, PdfName.FitBH, left, Float.NaN, Float.NaN, Float.NaN, Float.NaN);
65+
}
66+
67+
public static PdfExplicitRemoteGoToDestination create(int pageNum, PdfName type, float left, float bottom, float right, float top, float zoom) {
68+
return new PdfExplicitRemoteGoToDestination().add(--pageNum).add(type).add(left).add(bottom).add(right).add(top).add(zoom);
69+
}
70+
71+
@Override
72+
protected boolean isWrappedObjectMustBeIndirect() {
73+
return false;
74+
}
75+
76+
private PdfExplicitRemoteGoToDestination add(float value) {
77+
if (!Float.isNaN(value)) {
78+
((PdfArray) getPdfObject()).add(new PdfNumber(value));
79+
}
80+
return this;
81+
}
82+
83+
private PdfExplicitRemoteGoToDestination add(int value) {
84+
((PdfArray)getPdfObject()).add(new PdfNumber(value));
85+
return this;
86+
}
87+
88+
private PdfExplicitRemoteGoToDestination add(PdfName type) {
89+
((PdfArray)getPdfObject()).add(type);
90+
return this;
91+
}
92+
}

kernel/src/test/java/com/itextpdf/kernel/pdf/PdfOutlineTest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,10 @@ This file is part of the iText (R) project.
5252
import com.itextpdf.test.annotations.LogMessages;
5353
import com.itextpdf.test.annotations.type.IntegrationTest;
5454
import org.junit.Assert;
55-
import org.junit.Before;
5655
import org.junit.BeforeClass;
5756
import org.junit.Test;
5857
import org.junit.experimental.categories.Category;
5958

60-
import java.io.FileNotFoundException;
6159
import java.io.IOException;
6260
import java.util.ArrayList;
6361
import java.util.List;

0 commit comments

Comments
 (0)