Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#508 add support for embedded file #509

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -224,23 +227,57 @@ 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));
embeddedFile.setSubtype(elem.getAttribute("data-content-type") != null ? elem.getAttribute("data-content-type") : "application/octet-stream");
syjer marked this conversation as resolved.
Show resolved Hide resolved
fs.setEmbeddedFile(embeddedFile);
String fileName = Paths.get(uri).getFileName().toString();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work for custom protocol handlers reliably. I think it may be better to use another attribute, possibly download.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, it may fail. Maybe data-file-name as an attribute is more telling?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree download isn't the best attribute name, but it is the standard html5 attribute for this purpose.

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#Attributes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know about the download attribute :D . So I could remove "data-embed-file" and use the download attribute with a name.

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;

Expand All @@ -257,9 +294,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()));

Expand Down Expand Up @@ -376,7 +464,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);
Expand All @@ -386,14 +474,14 @@ private void addLinkToPage(PDPage page, PDAnnotationLink annot, Box anchor, Box
List<PDAnnotation> annots = page.getAnnotations();

if (annots == null) {
annots = new ArrayList<PDAnnotation>();
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);
Expand Down