diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LogMessageId.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LogMessageId.java index 4cead2564..517798c05 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LogMessageId.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LogMessageId.java @@ -111,6 +111,7 @@ enum LogMessageId1Param implements LogMessageId { LOAD_COULD_NOT_INSTANTIATE_CUSTOM_XML_READER(XRLog.LOAD, "Could not instantiate custom XMLReader class for XML parsing: {}. " + "Please check classpath. Use value 'default' in FS configuration if necessary. Will now try JDK default."), LOAD_UNABLE_TO_LOAD_CSS_FROM_URI(XRLog.LOAD, "Unable to load CSS from {}"), + LOAD_COULD_NOT_LOAD_EMBEDDED_FILE(XRLog.LOAD, "Was not able to load an embedded file for embedding with uri {}"), LOAD_PARSE_STYLESHEETS_TIME(XRLog.LOAD, "TIME: parse stylesheets {}ms"), LOAD_REQUESTING_STYLESHEET_AT_URI(XRLog.LOAD, "Requesting stylesheet: {}"), LOAD_UNRECOGNIZED_IMAGE_FORMAT_FOR_URI(XRLog.LOAD, "Unrecognized image format for: {}"), @@ -161,7 +162,8 @@ enum LogMessageId1Param implements LogMessageId { EXCEPTION_COULD_NOT_LOAD_FONT_FACE(XRLog.EXCEPTION, "Could not load @font-face font: {}"), EXCEPTION_COULD_NOT_LOAD_DEFAULT_CSS(XRLog.EXCEPTION, "Can't load default CSS from {}. This file must be on your CLASSPATH. Please check before continuing."), EXCEPTION_DEFAULT_USERAGENT_IS_NOT_ABLE_TO_RESOLVE_BASE_URL_FOR(XRLog.EXCEPTION, "The default NaiveUserAgent doesn't know how to resolve the base URL for {}"), - EXCEPTION_FAILED_TO_LOAD_BACKGROUND_IMAGE_AT_URI(XRLog.EXCEPTION, "Failed to load background image at uri {}"); + EXCEPTION_FAILED_TO_LOAD_BACKGROUND_IMAGE_AT_URI(XRLog.EXCEPTION, "Failed to load background image at uri {}"), + EXCEPTION_COULD_NOT_LOAD_EMBEDDED_FILE(XRLog.EXCEPTION, "Was not able to create an embedded file for embedding with uri {}"); private final String where; private final String messageFormat; diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-508-file-embed.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-508-file-embed.html new file mode 100644 index 000000000..5acad59d2 --- /dev/null +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-508-file-embed.html @@ -0,0 +1,12 @@ + + + + + +Embedded text
document
. + + diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java index 628e7a72b..cc0c5bf60 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java @@ -18,12 +18,16 @@ import java.util.stream.IntStream; import org.apache.commons.io.FileUtils; +import org.apache.pdfbox.cos.COSDocument; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.cos.COSObject; import org.apache.pdfbox.io.IOUtils; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocumentInformation; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.interactive.action.PDActionGoTo; import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationFileAttachment; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageXYZDestination; @@ -36,9 +40,9 @@ import org.apache.pdfbox.util.Charsets; import org.hamcrest.CustomTypeSafeMatcher; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; +import com.openhtmltopdf.outputdevice.helper.ExternalResourceControlPriority; import com.openhtmltopdf.pdfboxout.PdfRendererBuilder; import com.openhtmltopdf.testcases.TestcaseRunner; import com.openhtmltopdf.util.Diagnostic; @@ -1060,6 +1064,34 @@ public void testPr489DiagnosticConsumer() throws IOException { .allMatch(diag -> !diag.getFormattedMessage().isEmpty())); } + @Test + public void testIssue508FileEmbed() throws IOException { + try (PDDocument doc = run("issue-508-file-embed", + builder -> { + // File embeds are blocked by default, allow everything. + builder.useExternalResourceAccessControl((uri, type) -> true, ExternalResourceControlPriority.RUN_AFTER_RESOLVING_URI); + builder.useExternalResourceAccessControl((uri, type) -> true, ExternalResourceControlPriority.RUN_BEFORE_RESOLVING_URI); + })) { + + // There should be multiple file attachment annotations because the link + // is broken into two boxes on multiple lines. + assertThat(doc.getPage(0).getAnnotations().size(), equalTo(2)); + + PDAnnotationFileAttachment fileAttach1 = (PDAnnotationFileAttachment) doc.getPage(0).getAnnotations().get(0); + assertThat(fileAttach1.getFile().getFile(), equalTo("basic.css")); + + PDAnnotationFileAttachment fileAttach2 = (PDAnnotationFileAttachment) doc.getPage(0).getAnnotations().get(1); + assertThat(fileAttach2.getFile().getFile(), equalTo("basic.css")); + + try (COSDocument cosDoc = doc.getDocument()) { + // Make sure the file is only embedded once. + List files = cosDoc.getObjectsByType(COSName.FILESPEC); + assertThat(files.size(), equalTo(1)); + } + + remove("issue-508-file-embed", doc); + } + } // TODO: // + More form controls. diff --git a/openhtmltopdf-pdfa-testing/src/test/java/com/openhtmltopdf/pdfa/testing/PdfATester.java b/openhtmltopdf-pdfa-testing/src/test/java/com/openhtmltopdf/pdfa/testing/PdfATester.java index 3a1461a6b..1ee2f2783 100644 --- a/openhtmltopdf-pdfa-testing/src/test/java/com/openhtmltopdf/pdfa/testing/PdfATester.java +++ b/openhtmltopdf-pdfa-testing/src/test/java/com/openhtmltopdf/pdfa/testing/PdfATester.java @@ -28,6 +28,7 @@ import org.verapdf.pdfa.results.TestAssertion; import org.verapdf.pdfa.results.ValidationResult; +import com.openhtmltopdf.outputdevice.helper.ExternalResourceControlPriority; import com.openhtmltopdf.pdfboxout.PdfRendererBuilder; import com.openhtmltopdf.pdfboxout.PdfRendererBuilder.PdfAConformance; @@ -65,7 +66,11 @@ public boolean run(String resource, PDFAFlavour flavour, PdfAConformance conform builder.usePdfAConformance(conform); builder.useFont(new File("target/test/artefacts/Karla-Bold.ttf"), "TestFont"); builder.withHtmlContent(html, PdfATester.class.getResource("/html/").toString()); - + + // File embeds are blocked by default, allow everything. + builder.useExternalResourceAccessControl((uri, type) -> true, ExternalResourceControlPriority.RUN_AFTER_RESOLVING_URI); + builder.useExternalResourceAccessControl((uri, type) -> true, ExternalResourceControlPriority.RUN_BEFORE_RESOLVING_URI); + try (InputStream colorProfile = PdfATester.class.getResourceAsStream("/colorspaces/sRGB.icc")) { byte[] colorProfileBytes = IOUtils.toByteArray(colorProfile); builder.useColorProfile(colorProfileBytes); @@ -128,5 +133,12 @@ public void testAllInOnePdfA2a() throws Exception { public void testAllInOnePdfA2u() throws Exception { assertTrue(run("all-in-one", PDFAFlavour.PDFA_2_U, PdfAConformance.PDFA_2_U)); } - + + /** + * File embedding is allowed as of PDF/A3. + */ + @Test + public void testFileEmbedA3b() throws Exception { + assertTrue(run("file-embed", PDFAFlavour.PDFA_3_B, PdfAConformance.PDFA_3_B)); + } } diff --git a/openhtmltopdf-pdfa-testing/src/test/resources/html/file-embed.html b/openhtmltopdf-pdfa-testing/src/test/resources/html/file-embed.html new file mode 100644 index 000000000..7e7147109 --- /dev/null +++ b/openhtmltopdf-pdfa-testing/src/test/resources/html/file-embed.html @@ -0,0 +1,36 @@ + + + File embed testcase + + + + + + + + + + + +

File embed example

+ +

+ + + File embed + + +

+ + + diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/AnnotationContainer.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/AnnotationContainer.java new file mode 100644 index 000000000..d0ba6392e --- /dev/null +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/AnnotationContainer.java @@ -0,0 +1,64 @@ +package com.openhtmltopdf.pdfboxout; + +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationFileAttachment; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary; + +public abstract class AnnotationContainer { + public void setRectangle(PDRectangle rectangle) { + getPdAnnotation().setRectangle(rectangle); + } + + public void setPrinted(boolean printed) { + getPdAnnotation().setPrinted(printed); + } + + public void setQuadPoints(float[] quadPoints) {}; + + public abstract void setBorderStyle(PDBorderStyleDictionary styleDict); + + public abstract PDAnnotation getPdAnnotation(); + + public static class PDAnnotationFileAttachmentContainer extends AnnotationContainer { + private final PDAnnotationFileAttachment pdAnnotationFileAttachment; + + public PDAnnotationFileAttachmentContainer(PDAnnotationFileAttachment pdAnnotationFileAttachment) { + this.pdAnnotationFileAttachment = pdAnnotationFileAttachment; + } + + @Override + public PDAnnotation getPdAnnotation() { + return pdAnnotationFileAttachment; + } + + @Override + public void setBorderStyle(PDBorderStyleDictionary styleDict) { + pdAnnotationFileAttachment.setBorderStyle(styleDict); + } + } + + public static class PDAnnotationLinkContainer extends AnnotationContainer { + private final PDAnnotationLink pdAnnotationLink; + + public PDAnnotationLinkContainer(PDAnnotationLink pdAnnotationLink) { + this.pdAnnotationLink = pdAnnotationLink; + } + + @Override + public PDAnnotation getPdAnnotation() { + return pdAnnotationLink; + } + + @Override + public void setQuadPoints(float[] quadPoints) { + pdAnnotationLink.setQuadPoints(quadPoints); + } + + @Override + public void setBorderStyle(PDBorderStyleDictionary styleDict) { + pdAnnotationLink.setBorderStyle(styleDict); + } + } +} diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxAccessibilityHelper.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxAccessibilityHelper.java index 0ad3c4be9..6f3944b5f 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxAccessibilityHelper.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxAccessibilityHelper.java @@ -1210,17 +1210,17 @@ private static class AnnotationWithStructureParent { PDAnnotation annotation; } - public void addLink(Box anchor, Box target, PDAnnotationLink annotation, PDPage page) { + public void addLink(Box anchor, Box target, PDAnnotation pdAnnotation, PDPage page) { PDStructureElement struct = getStructualElementForBox(anchor); if (struct != null) { // We have to append the link annotationobject reference as a kid of its associated structure element. PDObjectReference ref = new PDObjectReference(); - ref.setReferencedObject(annotation); + ref.setReferencedObject(pdAnnotation); struct.appendKid(ref); // We also need to save the pair so we can add it to the number tree for reverse lookup. AnnotationWithStructureParent annotStructParentPair = new AnnotationWithStructureParent(); - annotStructParentPair.annotation = annotation; + annotStructParentPair.annotation = pdAnnotation; annotStructParentPair.structureParent = struct; _pageItems._pageAnnotations.add(annotStructParentPair); diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastLinkManager.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastLinkManager.java index bde829b29..ce07f0bb3 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastLinkManager.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastLinkManager.java @@ -3,6 +3,7 @@ import com.openhtmltopdf.extend.NamespaceHandler; import com.openhtmltopdf.extend.ReplacedElement; import com.openhtmltopdf.layout.SharedContext; +import com.openhtmltopdf.outputdevice.helper.ExternalResourceType; import com.openhtmltopdf.pdfboxout.PdfBoxLinkManager.IPdfBoxElementWithShapedLinks; import com.openhtmltopdf.pdfboxout.quads.KongAlgo; import com.openhtmltopdf.pdfboxout.quads.Triangle; @@ -13,20 +14,29 @@ import com.openhtmltopdf.util.LogMessageId; import com.openhtmltopdf.util.XRLog; +import org.apache.pdfbox.cos.COSArray; +import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; +import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; import org.apache.pdfbox.pdmodel.interactive.action.PDAction; import org.apache.pdfbox.pdmodel.interactive.action.PDActionGoTo; import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationFileAttachment; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream; import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary; import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageXYZDestination; import org.w3c.dom.Element; import java.awt.*; import java.awt.geom.*; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; import java.util.*; @@ -42,7 +52,19 @@ public class PdfBoxFastLinkManager { private final Box _root; private final PdfBoxFastOutputDevice _od; private final List _links; - private PdfBoxAccessibilityHelper _pdfUa; + private PdfBoxAccessibilityHelper _pdfUa; + + /** + * A map from uri to embedded file, so we don't embed files twice + * in case of a split link (example, two link boxes are formed when + * a link breaks in the middle). + */ + private final Map _embeddedFiles; + + /** + * The lazily created appearance dict for emedded files. + */ + private PDAppearanceDictionary _embeddedFileAppearance; public PdfBoxFastLinkManager(SharedContext ctx, float dotsPerPoint, Box root, PdfBoxFastOutputDevice od) { this._sharedContext = ctx; @@ -51,6 +73,7 @@ public PdfBoxFastLinkManager(SharedContext ctx, float dotsPerPoint, Box root, Pd this._od = od; this._linkTargetAreas = new HashMap<>(); this._links = new ArrayList<>(); + this._embeddedFiles = new HashMap<>(); } private Rectangle2D calcTotalLinkArea(RenderingContext c, Box box, float pageHeight, AffineTransform transform) { @@ -225,29 +248,152 @@ private void addUriAsLink(RenderingContext c, Box box, PDPage page, float pageHe PDAnnotationLink annot = new PDAnnotationLink(); annot.setAction(action); - if (!placeAnnotation(transform, linkShape, targetArea, annot)) + + AnnotationContainer annotContainer = new AnnotationContainer.PDAnnotationLinkContainer(annot); + + if (!placeAnnotation(transform, linkShape, targetArea, annotContainer)) return; - addLinkToPage(page, annot, box, target); + addLinkToPage(page, annotContainer, box, target); } else { XRLog.log(Level.WARNING, LogMessageId.LogMessageId1Param.GENERAL_PDF_COULD_NOT_FIND_VALID_TARGET_FOR_LINK, uri); } } else if (isURI(uri)) { - PDActionURI uriAct = new PDActionURI(); - uriAct.setURI(uri); - - Rectangle2D targetArea = checkLinkArea(page, c, box, pageHeight, transform, linkShape); - if (targetArea == null) { - return; - } - PDAnnotationLink annot = new PDAnnotationLink(); - annot.setAction(uriAct); - if (!placeAnnotation(transform, linkShape, targetArea, annot)) - return; - - addLinkToPage(page, annot, box, null); - } - } + AnnotationContainer annotContainer = null; + + if (!elem.hasAttribute("download")) { + PDActionURI uriAct = new PDActionURI(); + uriAct.setURI(uri); + + PDAnnotationLink annot = new PDAnnotationLink(); + annot.setAction(uriAct); + + annotContainer = new AnnotationContainer.PDAnnotationLinkContainer(annot); + } else { + annotContainer = createFileEmbedLinkAnnotation(elem, uri); + } + + if (annotContainer != null) { + Rectangle2D targetArea = checkLinkArea(page, c, box, pageHeight, transform, linkShape); + + if (targetArea == null) { + return; + } + + if (!placeAnnotation(transform, linkShape, targetArea, annotContainer)) { + return; + } + + addLinkToPage(page, annotContainer, box, null); + } + } + } + + /** + * Create a file attachment link, being careful not to embed the same + * file (as specified by uri) more than once. + * + * The element should have the following attributes: + * download="embedded-filename.ext", + * data-content-type="file-mime-type" which + * defaults to "application/octet-stream", + * relationship (required for PDF/A3), one of: + * "Source", "Supplement", "Data", "Alternative", "Unspecified", + * title="file description" (recommended for PDF/A3). + */ + private AnnotationContainer createFileEmbedLinkAnnotation( + Element elem, String uri) { + PDComplexFileSpecification fs = _embeddedFiles.get(uri); + + if (fs != null) { + PDAnnotationFileAttachment annotationFileAttachment = new PDAnnotationFileAttachment(); + + annotationFileAttachment.setFile(fs); + annotationFileAttachment.setAppearance(this._embeddedFileAppearance); + + return new AnnotationContainer.PDAnnotationFileAttachmentContainer(annotationFileAttachment); + } + + byte[] file = _sharedContext.getUserAgentCallback().getBinaryResource(uri, ExternalResourceType.FILE_EMBED); + + if (file != null) { + try { + String contentType = elem.getAttribute("data-content-type").isEmpty() ? + "application/octet-stream" : + elem.getAttribute("data-content-type"); + + PDEmbeddedFile embeddedFile = new PDEmbeddedFile(_od.getWriter(), new ByteArrayInputStream(file)); + embeddedFile.setSubtype(contentType); + embeddedFile.setSize(file.length); + + // PDF/A3 requires a mod date for the file. + if (elem.hasAttribute("relationship")) { + // FIXME: Should we make this specifiable. + embeddedFile.setModDate(Calendar.getInstance()); + } + + String fileName = elem.getAttribute("download"); + + fs = new PDComplexFileSpecification(); + fs.setEmbeddedFile(embeddedFile); + fs.setFile(fileName); + fs.setFileUnicode(fileName); + + // The PDF/A3 standard requires one to specify the relationship + // this embedded file has to the link annotation. + if (elem.hasAttribute("relationship") && + Arrays.asList("Source", "Supplement", "Data", "Alternative", "Unspecified") + .contains(elem.getAttribute("relationship"))) { + fs.getCOSObject().setItem( + COSName.getPDFName("AFRelationship"), + COSName.getPDFName(elem.getAttribute("relationship"))); + } + + if (elem.hasAttribute("title")) { + fs.setFileDescription(elem.getAttribute("title")); + } + + this._embeddedFiles.put(uri, fs); + + if (this._embeddedFileAppearance == null) { + this._embeddedFileAppearance = createFileEmbedLinkAppearance(); + } + + PDAnnotationFileAttachment annotationFileAttachment = new PDAnnotationFileAttachment(); + + annotationFileAttachment.setFile(fs); + annotationFileAttachment.setAppearance(this._embeddedFileAppearance); + + // PDF/A3 requires we explicitly list this link as associated with file. + if (elem.hasAttribute("relationship")) { + COSArray fileRefArray = new COSArray(); + fileRefArray.add(fs); + + annotationFileAttachment.getCOSObject().setItem(COSName.getPDFName("AF"), fileRefArray); + } + + return new AnnotationContainer.PDAnnotationFileAttachmentContainer(annotationFileAttachment); + } catch (IOException e) { + XRLog.log(Level.WARNING, LogMessageId.LogMessageId1Param.EXCEPTION_COULD_NOT_LOAD_EMBEDDED_FILE, uri, e); + } + } else { + XRLog.log(Level.WARNING, LogMessageId.LogMessageId1Param.LOAD_COULD_NOT_LOAD_EMBEDDED_FILE, uri); + } + + return null; + } + + /** + * Create an empty appearance stream to + * hide the pin icon used by various pdf reader for signaling an embedded file + */ + private PDAppearanceDictionary createFileEmbedLinkAppearance() { + PDAppearanceDictionary appearanceDictionary = new PDAppearanceDictionary(); + PDAppearanceStream appearanceStream = new PDAppearanceStream(_od.getWriter()); + appearanceStream.setResources(new PDResources()); + appearanceDictionary.setNormalAppearance(appearanceStream); + return appearanceDictionary; + } private static boolean isURI(String uri) { try { @@ -260,7 +406,7 @@ private static boolean isURI(String uri) { @SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean placeAnnotation(AffineTransform transform, Shape linkShape, Rectangle2D targetArea, - PDAnnotationLink annot) { + AnnotationContainer annot) { annot.setRectangle(new PDRectangle((float) targetArea.getMinX(), (float) targetArea.getMinY(), (float) targetArea.getWidth(), (float) targetArea.getHeight())); @@ -377,7 +523,8 @@ static QuadPointShape mapShapeToQuadPoints(AffineTransform transform, Shape link return result; } - private void addLinkToPage(PDPage page, PDAnnotationLink annot, Box anchor, Box target) { + private void addLinkToPage( + PDPage page, AnnotationContainer annot, Box anchor, Box target) { PDBorderStyleDictionary styleDict = new PDBorderStyleDictionary(); styleDict.setWidth(0); styleDict.setStyle(PDBorderStyleDictionary.STYLE_SOLID); @@ -391,10 +538,10 @@ private void addLinkToPage(PDPage page, PDAnnotationLink annot, Box anchor, Box page.setAnnotations(annots); } - annots.add(annot); + annots.add(annot.getPdAnnotation()); if (_pdfUa != null) { - _pdfUa.addLink(anchor, target, annot, page); + _pdfUa.addLink(anchor, target, annot.getPdAnnotation(), page); } } catch (IOException e) { throw new PdfContentStreamAdapter.PdfException("processLink", e);