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..ac7b652e9 100644
--- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java
+++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java
@@ -24,6 +24,7 @@
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;
@@ -39,6 +40,7 @@
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 +1062,28 @@ 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);
+ })) {
+ // TODO: Renable this assertion when we have figured out a way
+ // to avoid duplicate file embeds when the link is broken
+ // up into boxes (eg. multiple lines).
+ // assertThat(doc.getPage(0).getAnnotations().size(), equalTo(1));
+
+ PDAnnotationFileAttachment fileAttach = (PDAnnotationFileAttachment) doc.getPage(0).getAnnotations().get(0);
+ assertThat(fileAttach.getFile().getFile(), equalTo("basic.css"));
+
+ // TODO:
+ // More asserts.
+
+ remove("issue-508-file-embed", doc);
+ }
+ }
// TODO:
// + More form controls.
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..0bacbc159 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;
@@ -14,19 +15,26 @@
import com.openhtmltopdf.util.XRLog;
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.*;
@@ -225,29 +233,91 @@ 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);
+ AnnotationContainer annotContainer = null;
- 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;
+ if (!elem.hasAttribute("download")) {
+ PDActionURI uriAct = new PDActionURI();
+ uriAct.setURI(uri);
- addLinkToPage(page, annot, box, null);
- }
- }
+ 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);
+ }
+ }
+ }
+
+ private AnnotationContainer createFileEmbedLinkAnnotation(
+ Element elem, String uri) {
+ byte[] file = _sharedContext.getUserAgentCallback().getBinaryResource(uri, ExternalResourceType.FILE_EMBED);
+
+ if (file != null) {
+ try {
+ PDComplexFileSpecification fs = new PDComplexFileSpecification();
+ PDEmbeddedFile embeddedFile = new PDEmbeddedFile(_od.getWriter(), new ByteArrayInputStream(file));
+
+ String contentType = elem.getAttribute("data-content-type").isEmpty() ?
+ "application/octet-stream" :
+ elem.getAttribute("data-content-type");
+
+ embeddedFile.setSubtype(contentType);
+
+ fs.setEmbeddedFile(embeddedFile);
+
+ String fileName = elem.getAttribute("download");
+
+ fs.setFile(fileName);
+ fs.setFileUnicode(fileName);
+
+ PDAnnotationFileAttachment annotationFileAttachment = new PDAnnotationFileAttachment();
+ annotationFileAttachment.setFile(fs);
+
+ // hide the pin icon used by various pdf reader for signaling an embedded file
+ PDAppearanceDictionary appearanceDictionary = new PDAppearanceDictionary();
+ PDAppearanceStream appearanceStream = new PDAppearanceStream(_od.getWriter());
+ appearanceStream.setResources(new PDResources());
+ appearanceDictionary.setNormalAppearance(appearanceStream);
+ annotationFileAttachment.setAppearance(appearanceDictionary);
+
+ return new AnnotationContainer.PDAnnotationFileAttachmentContainer(annotationFileAttachment);
+ } catch (IOException e) {
+ // TODO
+ //XRLog.exception("Was not able to create an embedded file for embedding with uri " + uri, e);
+ }
+ } else {
+ // TODO
+ //XRLog.general("Was not able to load file from uri for embedding" + uri);
+ }
+
+ return null;
+ }
private static boolean isURI(String uri) {
try {
@@ -260,7 +330,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 +447,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 +462,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);