package rst.pdfbox.layout.text.annotations; import java.awt.Color; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageXYZDestination; import rst.pdfbox.layout.text.DrawContext; import rst.pdfbox.layout.text.Position; import rst.pdfbox.layout.text.annotations.Annotations.AnchorAnnotation; import rst.pdfbox.layout.text.annotations.Annotations.HyperlinkAnnotation; import rst.pdfbox.layout.text.annotations.Annotations.HyperlinkAnnotation.LinkStyle; import rst.pdfbox.layout.util.CompatibilityHelper; /** * This annotation processor handles both {@link HyperlinkAnnotation}s and * {@link AnchorAnnotation}s, and adds the needed hyperlink metadata to the PDF * document. */ public class HyperlinkAnnotationProcessor implements AnnotationProcessor { private Map<String, PageAnchor> anchorMap = new HashMap<String, PageAnchor>(); private Map<PDPage, List<Hyperlink>> linkMap = new HashMap<PDPage, List<Hyperlink>>(); @Override public void annotatedObjectDrawn(Annotated drawnObject, DrawContext drawContext, Position upperLeft, float width, float height) throws IOException { if (!(drawnObject instanceof AnnotatedStyledText)) { return; } AnnotatedStyledText annotatedText = (AnnotatedStyledText) drawnObject; handleHyperlinkAnnotations(annotatedText, drawContext, upperLeft, width, height); handleAnchorAnnotations(annotatedText, drawContext, upperLeft); } protected void handleAnchorAnnotations(AnnotatedStyledText annotatedText, DrawContext drawContext, Position upperLeft) { Iterable<AnchorAnnotation> anchorAnnotations = annotatedText .getAnnotationsOfType(AnchorAnnotation.class); for (AnchorAnnotation anchorAnnotation : anchorAnnotations) { anchorMap.put( anchorAnnotation.getAnchor(), new PageAnchor(drawContext.getCurrentPage(), upperLeft .getX(), upperLeft.getY())); } } protected void handleHyperlinkAnnotations( AnnotatedStyledText annotatedText, DrawContext drawContext, Position upperLeft, float width, float height) { Iterable<HyperlinkAnnotation> hyperlinkAnnotations = annotatedText .getAnnotationsOfType(HyperlinkAnnotation.class); for (HyperlinkAnnotation hyperlinkAnnotation : hyperlinkAnnotations) { List<Hyperlink> links = linkMap.get(drawContext.getCurrentPage()); if (links == null) { links = new ArrayList<Hyperlink>(); linkMap.put(drawContext.getCurrentPage(), links); } PDRectangle bounds = new PDRectangle(); bounds.setLowerLeftX(upperLeft.getX()); bounds.setLowerLeftY(upperLeft.getY() - height); bounds.setUpperRightX(upperLeft.getX() + width); bounds.setUpperRightY(upperLeft.getY()); links.add(new Hyperlink(bounds, annotatedText.getColor(), hyperlinkAnnotation.getLinkStyle(), hyperlinkAnnotation .getHyperlinkURI())); } } @Override public void beforePage(DrawContext drawContext) { // nothing to do here } @Override public void afterPage(DrawContext drawContext) { // nothing to do here } @Override public void afterRender(PDDocument document) throws IOException { for (Entry<PDPage, List<Hyperlink>> entry : linkMap.entrySet()) { PDPage page = entry.getKey(); List<Hyperlink> links = entry.getValue(); for (Hyperlink hyperlink : links) { PDAnnotationLink pdLink = null; if (hyperlink.getHyperlinkURI().startsWith("#")) { pdLink = createGotoLink(hyperlink); } else { pdLink = CompatibilityHelper.createLink(page, hyperlink.getRect(), hyperlink.getColor(), hyperlink.getLinkStyle(), hyperlink.getHyperlinkURI()); } page.getAnnotations().add(pdLink); } } } private PDAnnotationLink createGotoLink(Hyperlink hyperlink) { String anchor = hyperlink.getHyperlinkURI().substring(1); PageAnchor pageAnchor = anchorMap.get(anchor); if (pageAnchor == null) { throw new IllegalArgumentException(String.format( "anchor named '%s' not found", anchor)); } PDPageXYZDestination xyzDestination = new PDPageXYZDestination(); xyzDestination.setPage(pageAnchor.getPage()); xyzDestination.setLeft((int) pageAnchor.getX()); xyzDestination.setTop((int) pageAnchor.getY()); return CompatibilityHelper.createLink(pageAnchor.getPage(), hyperlink.getRect(), hyperlink.getColor(), hyperlink.getLinkStyle(), xyzDestination); } private static class PageAnchor { private final PDPage page; private final float x; private final float y; public PageAnchor(PDPage page, float x, float y) { this.page = page; this.x = x; this.y = y; } public PDPage getPage() { return page; } public float getX() { return x; } public float getY() { return y; } @Override public String toString() { return "PageAnchor [page=" + page + ", x=" + x + ", y=" + y + "]"; } } private static class Hyperlink { private final PDRectangle rect; private final Color color; private final String hyperlinkUri; private final LinkStyle linkStyle; public Hyperlink(PDRectangle rect, Color color, LinkStyle linkStyle, String hyperlinkUri) { this.rect = rect; this.color = color; this.hyperlinkUri = hyperlinkUri; this.linkStyle = linkStyle; } public PDRectangle getRect() { return rect; } public Color getColor() { return color; } public String getHyperlinkURI() { return hyperlinkUri; } public LinkStyle getLinkStyle() { return linkStyle; } @Override public String toString() { return "Hyperlink [rect=" + rect + ", color=" + color + ", hyperlinkUri=" + hyperlinkUri + ", linkStyle=" + linkStyle + "]"; } } }