Skip to content

Commit 9925228

Browse files
committed
Support structure destinations for outlines
DEVSIX-7956
1 parent 6ab7119 commit 9925228

File tree

7 files changed

+116
-5
lines changed

7 files changed

+116
-5
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,14 @@ public void addDestination(PdfDestination destination) {
210210
* @param action instance of {@link PdfAction}.
211211
*/
212212
public void addAction(PdfAction action) {
213+
PdfName actionType = action.getPdfObject().getAsName(PdfName.S);
214+
if (PdfName.GoTo.equals(actionType)) {
215+
PdfObject destObject = action.getPdfObject().get(PdfName.D);
216+
if (destObject != null) {
217+
setDestination(PdfDestination.makeDestination(destObject));
218+
}
219+
}
220+
213221
content.put(PdfName.A, action.getPdfObject());
214222
}
215223

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

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ This file is part of the iText (R) project.
3030
import com.itextpdf.kernel.exceptions.PdfException;
3131
import com.itextpdf.kernel.logs.KernelLogMessageConstant;
3232
import com.itextpdf.kernel.pdf.PdfReader.StrictnessLevel;
33+
import com.itextpdf.kernel.pdf.action.PdfAction;
3334
import com.itextpdf.kernel.pdf.navigation.PdfDestination;
3435
import com.itextpdf.kernel.pdf.navigation.PdfExplicitDestination;
3536
import com.itextpdf.kernel.pdf.navigation.PdfStringDestination;
@@ -41,7 +42,6 @@ This file is part of the iText (R) project.
4142
import com.itextpdf.test.annotations.type.IntegrationTest;
4243

4344
import java.io.ByteArrayOutputStream;
44-
import java.io.FileOutputStream;
4545
import java.io.IOException;
4646
import java.util.ArrayList;
4747
import java.util.Collections;
@@ -801,6 +801,52 @@ public void createOutlinesWithDifferentVariantsOfChildrenTest() throws IOExcepti
801801
}
802802
}
803803

804+
@Test
805+
public void createOutlinesWithActionsTest() throws IOException, InterruptedException {
806+
String filename = "createOutlinesWithActions.pdf";
807+
try (PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(DESTINATION_FOLDER + filename))) {
808+
pdfDoc.getCatalog().setPageMode(PdfName.UseOutlines);
809+
810+
PdfPage firstPage = pdfDoc.addNewPage();
811+
PdfPage secondPage = pdfDoc.addNewPage();
812+
813+
PdfOutline rootOutline = pdfDoc.getOutlines(false);
814+
PdfOutline firstOutline = rootOutline.addOutline("First Page");
815+
PdfOutline secondOutline = rootOutline.addOutline("Second Page");
816+
817+
PdfDestination page1Dest = PdfExplicitDestination.createFit(firstPage);
818+
PdfAction page1Action = PdfAction.createGoTo(page1Dest);
819+
firstOutline.addAction(page1Action);
820+
Assert.assertEquals(page1Dest.getPdfObject(), firstOutline.getDestination().getPdfObject());
821+
822+
PdfAction page2Action = PdfAction.createGoTo(PdfExplicitDestination.createFit(secondPage));
823+
secondOutline.addAction(page2Action);
824+
}
825+
826+
Assert.assertNull(new CompareTool().compareByContent(DESTINATION_FOLDER + filename, SOURCE_FOLDER + "cmp_" + filename,
827+
DESTINATION_FOLDER, "diff_"));
828+
}
829+
830+
@Test
831+
public void createOutlinesWithURIActionTest() throws IOException, InterruptedException {
832+
String filename = "createOutlinesWithURIAction.pdf";
833+
try (PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(DESTINATION_FOLDER + filename))) {
834+
pdfDoc.getCatalog().setPageMode(PdfName.UseOutlines);
835+
836+
PdfOutline rootOutline = pdfDoc.getOutlines(false);
837+
PdfOutline firstOutline = rootOutline.addOutline("First Page");
838+
839+
// The test was created to improve the coverage but
840+
// Apparently it works!
841+
PdfAction action1 = PdfAction.createURI("https://example.com");
842+
firstOutline.addAction(action1);
843+
Assert.assertNull(firstOutline.getDestination());
844+
}
845+
846+
Assert.assertNull(new CompareTool().compareByContent(DESTINATION_FOLDER + filename, SOURCE_FOLDER + "cmp_" + filename,
847+
DESTINATION_FOLDER, "diff_"));
848+
}
849+
804850
private static final class EmptyNameTree implements IPdfNameTreeAccess {
805851
@Override
806852
public PdfObject getEntry(PdfString key) {

layout/src/main/java/com/itextpdf/layout/renderer/AbstractRenderer.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1953,6 +1953,7 @@ protected void applyDestination(PdfDocument document) {
19531953
logMessageArg));
19541954
return;
19551955
}
1956+
19561957
PdfArray array = new PdfArray();
19571958
array.add(document.getPage(pageNumber).getPdfObject());
19581959
array.add(PdfName.XYZ);
@@ -1964,9 +1965,8 @@ protected void applyDestination(PdfDocument document) {
19641965

19651966
final boolean isPdf20 = document.getPdfVersion().compareTo(PdfVersion.PDF_2_0) >= 0;
19661967
if (linkActionDict != null && isPdf20 && document.isTagged()) {
1967-
TagStructureContext context = document.getTagStructureContext();
1968-
TagTreePointer tagPointer = context.getAutoTaggingPointer();
1969-
PdfStructElem structElem = context.getPointerStructElem(tagPointer);
1968+
// Add structure destination for the action for tagged pdf 2.0
1969+
PdfStructElem structElem = getCurrentStructElem(document);
19701970
PdfStructureDestination dest = PdfStructureDestination.createFit(structElem);
19711971
linkActionDict.put(PdfName.SD, dest.getPdfObject());
19721972
}
@@ -2842,4 +2842,10 @@ private static UnitValue[] getPaddings(IRenderer renderer) {
28422842
private static boolean hasOwnOrModelProperty(IRenderer renderer, int property) {
28432843
return renderer.hasOwnProperty(property) || (null != renderer.getModelElement() && renderer.getModelElement().hasProperty(property));
28442844
}
2845+
2846+
private static PdfStructElem getCurrentStructElem(PdfDocument document) {
2847+
TagStructureContext context = document.getTagStructureContext();
2848+
TagTreePointer tagPointer = context.getAutoTaggingPointer();
2849+
return context.getPointerStructElem(tagPointer);
2850+
}
28452851
}

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

Lines changed: 52 additions & 1 deletion
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.layout;
2424

25+
import com.itextpdf.commons.datastructures.Tuple2;
2526
import com.itextpdf.commons.utils.MessageFormatUtil;
2627
import com.itextpdf.kernel.colors.ColorConstants;
2728
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
@@ -36,7 +37,9 @@ This file is part of the iText (R) project.
3637
import com.itextpdf.kernel.pdf.PdfDocumentInfo;
3738
import com.itextpdf.kernel.pdf.PdfName;
3839
import com.itextpdf.kernel.pdf.PdfNumber;
40+
import com.itextpdf.kernel.pdf.PdfOutline;
3941
import com.itextpdf.kernel.pdf.PdfPage;
42+
import com.itextpdf.kernel.pdf.PdfReader;
4043
import com.itextpdf.kernel.pdf.PdfString;
4144
import com.itextpdf.kernel.pdf.PdfVersion;
4245
import com.itextpdf.kernel.pdf.PdfViewerPreferences;
@@ -67,6 +70,7 @@ This file is part of the iText (R) project.
6770
import com.itextpdf.layout.element.Text;
6871
import com.itextpdf.layout.properties.HorizontalAlignment;
6972
import com.itextpdf.layout.properties.ListNumberingType;
73+
import com.itextpdf.layout.properties.Property;
7074
import com.itextpdf.test.ExtendedITextTest;
7175
import com.itextpdf.test.annotations.type.IntegrationTest;
7276
import com.itextpdf.test.pdfa.VeraPdfValidator; // Android-Conversion-Skip-Line (TODO DEVSIX-7377 introduce pdf\a validation on Android)
@@ -724,7 +728,8 @@ public void checkStructureDestinationTest() throws IOException, InterruptedExcep
724728
String outFile = DESTINATION_FOLDER + "structureDestination01Test.pdf";
725729
String cmpFile = SOURCE_FOLDER + "cmp_structureDestination01Test.pdf";
726730

727-
try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFile, new WriterProperties().setPdfVersion(PdfVersion.PDF_2_0)))) {
731+
try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFile,
732+
new WriterProperties().setPdfVersion(PdfVersion.PDF_2_0)))) {
728733
Document document = new Document(pdfDocument);
729734
PdfFont font = PdfFontFactory.createFont(FONT_FOLDER + "FreeSans.ttf",
730735
"WinAnsi", EmbeddingStrategy.FORCE_EMBEDDED);
@@ -756,6 +761,52 @@ public void checkStructureDestinationTest() throws IOException, InterruptedExcep
756761
compareAndValidate(outFile, cmpFile);
757762
}
758763

764+
@Test
765+
public void checkOutlinesAsStructureDestinationsTest() throws IOException, XMPException, InterruptedException {
766+
String outFile = DESTINATION_FOLDER + "checkOutlinesAsStructureDestinations.pdf";
767+
String cmpFile = SOURCE_FOLDER + "cmp_checkOutlinesAsStructureDestinations.pdf";
768+
769+
try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFile,
770+
new WriterProperties().setPdfVersion(PdfVersion.PDF_2_0)))) {
771+
Document document = new Document(pdfDocument);
772+
PdfFont font = PdfFontFactory.createFont(FONT_FOLDER + "FreeSans.ttf",
773+
"WinAnsi", EmbeddingStrategy.FORCE_EMBEDDED);
774+
document.setFont(font);
775+
776+
createSimplePdfUA2Document(pdfDocument);
777+
778+
PdfOutline topOutline = pdfDocument.getOutlines(false);
779+
PdfOutline header1Outline = topOutline.addOutline("header1 title");
780+
PdfAction action1 = PdfAction.createGoTo("header1");
781+
header1Outline.addAction(action1);
782+
783+
PdfOutline header11Outline = header1Outline.addOutline("header1.1 title");
784+
PdfAction action11 = PdfAction.createGoTo("header1.1");
785+
header11Outline.addAction(action11);
786+
787+
788+
Paragraph header1 = new Paragraph("header1 text");
789+
header1.setProperty(Property.DESTINATION,
790+
new Tuple2<String, PdfDictionary>("header1", action1.getPdfObject()));
791+
Paragraph header11 = new Paragraph("header1.1 text");
792+
header11.setProperty(Property.DESTINATION,
793+
new Tuple2<String, PdfDictionary>("header1.1", action11.getPdfObject()));
794+
795+
document.add(header1);
796+
document.add(header11);
797+
}
798+
799+
compareAndValidate(outFile, cmpFile);
800+
801+
try (PdfDocument pdfDocument = new PdfDocument(new PdfReader(outFile))) {
802+
PdfOutline outline = pdfDocument.getOutlines(false);
803+
Assert.assertEquals("header1", outline.getAllChildren().get(0)
804+
.getDestination().getPdfObject().toString());
805+
Assert.assertEquals("header1.1", outline.getAllChildren().get(0).getAllChildren().get(0)
806+
.getDestination().getPdfObject().toString());
807+
}
808+
}
809+
759810
private void createSimplePdfUA2Document(PdfDocument pdfDocument) throws IOException, XMPException {
760811
byte[] bytes = Files.readAllBytes(Paths.get(SOURCE_FOLDER + "simplePdfUA2.xmp"));
761812
XMPMeta xmpMeta = XMPMetaFactory.parse(new ByteArrayInputStream(bytes));

0 commit comments

Comments
 (0)