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 85b1b005a..ca6a81899 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxAccessibilityHelper.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxAccessibilityHelper.java @@ -1214,7 +1214,7 @@ 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 annotation, 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. 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 df1ce8c87..a6b5fa39f 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastLinkManager.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastLinkManager.java @@ -13,21 +13,24 @@ 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.PDAnnotationLink; -import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary; +import org.apache.pdfbox.pdmodel.interactive.annotation.*; 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.nio.file.Paths; import java.util.*; import java.util.List; import java.util.Map.Entry; @@ -224,23 +227,58 @@ private void addUriAsLink(RenderingContext c, Box box, PDPage page, float pageHe PDAnnotationLink annot = new PDAnnotationLink(); annot.setAction(action); - if (!placeAnnotation(transform, linkShape, targetArea, annot)) + if (!placeAnnotation(transform, linkShape, targetArea, new PDAnnotationLinkContainer(annot))) return; - addLinkToPage(page, annot, box, target); + addLinkToPage(page, new PDAnnotationLinkContainer(annot), box, target); } else { XRLog.general(Level.WARNING, "Could not find valid target for link. Link href = " + 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); + + PDAnnotationLink annotationLink = new PDAnnotationLink(); + PDActionURI uriAct = new PDActionURI(); + uriAct.setURI(uri); + annotationLink.setAction(uriAct); + PDAnnotationContainer annot = new PDAnnotationLinkContainer(annotationLink); + + if ("true".equals(elem.getAttribute("data-embed-file"))) { + byte[] file = _sharedContext.getUserAgentCallback().getBinaryResource(uri); + if (file != null) { + try { + PDComplexFileSpecification fs = new PDComplexFileSpecification(); + PDEmbeddedFile embeddedFile = new PDEmbeddedFile(_od.getWriter(), new ByteArrayInputStream(file)); + String contentType = "".equals(elem.getAttribute("data-content-type")) ? "application/octet-stream" : elem.getAttribute("data-content-type"); + embeddedFile.setSubtype(contentType); + fs.setEmbeddedFile(embeddedFile); + String fileName = Paths.get(uri).getFileName().toString(); + 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); + // + + annot = new PDAnnotationFileAttachmentContainer(annotationFileAttachment); + } catch (IOException e) { + XRLog.exception("Was not able to create an embedded file for embedding with uri " + uri, e); + } + } else { + XRLog.general("Was not able to load file from uri for embedding" + uri); + } + } + if (!placeAnnotation(transform, linkShape, targetArea, annot)) return; @@ -257,9 +295,60 @@ private static boolean isURI(String uri) { } } + private interface PDAnnotationContainer { + default void setRectangle(PDRectangle rectangle) {getPdAnnotation().setRectangle(rectangle);}; + default void setPrinted(boolean printed) {getPdAnnotation().setPrinted(printed);}; + default void setQuadPoints(float[] quadPoints) {}; + + void setBorderStyle(PDBorderStyleDictionary styleDict); + + PDAnnotation getPdAnnotation(); + } + + private static class PDAnnotationFileAttachmentContainer implements PDAnnotationContainer { + private final PDAnnotationFileAttachment pdAnnotationFileAttachment; + + PDAnnotationFileAttachmentContainer(PDAnnotationFileAttachment pdAnnotationFileAttachment) { + this.pdAnnotationFileAttachment = pdAnnotationFileAttachment; + } + + @Override + public PDAnnotation getPdAnnotation() { + return pdAnnotationFileAttachment; + } + + @Override + public void setBorderStyle(PDBorderStyleDictionary styleDict) { + pdAnnotationFileAttachment.setBorderStyle(styleDict); + } + } + + private static class PDAnnotationLinkContainer implements PDAnnotationContainer { + private final PDAnnotationLink pdAnnotationLink; + + private 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); + } + } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean placeAnnotation(AffineTransform transform, Shape linkShape, Rectangle2D targetArea, - PDAnnotationLink annot) { + PDAnnotationContainer annot) { annot.setRectangle(new PDRectangle((float) targetArea.getMinX(), (float) targetArea.getMinY(), (float) targetArea.getWidth(), (float) targetArea.getHeight())); @@ -376,7 +465,7 @@ 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, PDAnnotationContainer annot, Box anchor, Box target) { PDBorderStyleDictionary styleDict = new PDBorderStyleDictionary(); styleDict.setWidth(0); styleDict.setStyle(PDBorderStyleDictionary.STYLE_SOLID); @@ -386,14 +475,14 @@ private void addLinkToPage(PDPage page, PDAnnotationLink annot, Box anchor, Box List annots = page.getAnnotations(); if (annots == null) { - annots = new ArrayList(); + annots = new ArrayList<>(); 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);