From 342147f014c1eefe5fd113ef028848fade400c35 Mon Sep 17 00:00:00 2001 From: Jeremias Maerki Date: Tue, 9 Dec 2008 23:50:41 +0000 Subject: [PATCH] Forgot to add link support. First version added now. Generalizing this for the IF is not so easy it seems. Moved document navigation features (bookmarks, links, named destinations) into a separate handler interface that can be optionally implemented by document handler implementations. This will need a bit more work to be complete (parsing for document navigation from IF, cleanup in PDF library etc.). git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_AreaTreeNewDesign@724932 13f79535-47bb-0310-9956-ffa450edef68 --- src/java/org/apache/fop/pdf/PDFReference.java | 30 +++- src/java/org/apache/fop/pdf/PDFUri.java | 16 +- .../AbstractIFDocumentHandler.java | 5 + .../intermediate/IFDocumentHandler.java | 6 + .../IFDocumentNavigationHandler.java | 52 ++++++ .../fop/render/intermediate/IFRenderer.java | 151 +++++++++++++--- .../fop/render/intermediate/IFSerializer.java | 42 ++++- .../extensions/AbstractAction.java | 58 +++++++ .../intermediate/extensions/ActionSet.java | 87 ++++++++++ .../intermediate/extensions/Bookmark.java | 7 +- .../DocumentNavigationExtensionConstants.java | 5 + ...mentNavigationExtensionHandlerFactory.java | 7 +- .../intermediate/extensions/GoToXYAction.java | 32 +++- .../render/intermediate/extensions/Link.java | 99 +++++++++++ .../extensions/NamedDestination.java | 14 +- .../extensions/ReferencedAction.java | 58 +++++++ .../intermediate/extensions/URIAction.java | 45 ++++- .../fop/render/pdf/PDFContentGenerator.java | 11 -- .../fop/render/pdf/PDFDocumentHandler.java | 92 +++------- .../pdf/PDFDocumentNavigationHandler.java | 162 ++++++++++++++++++ .../org/apache/fop/pdf/PDFObjectTestCase.java | 18 ++ 21 files changed, 878 insertions(+), 119 deletions(-) create mode 100644 src/java/org/apache/fop/render/intermediate/IFDocumentNavigationHandler.java create mode 100644 src/java/org/apache/fop/render/intermediate/extensions/ActionSet.java create mode 100644 src/java/org/apache/fop/render/intermediate/extensions/Link.java create mode 100644 src/java/org/apache/fop/render/intermediate/extensions/ReferencedAction.java create mode 100644 src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java diff --git a/src/java/org/apache/fop/pdf/PDFReference.java b/src/java/org/apache/fop/pdf/PDFReference.java index 5c3bdf3dc..1d9ddc349 100644 --- a/src/java/org/apache/fop/pdf/PDFReference.java +++ b/src/java/org/apache/fop/pdf/PDFReference.java @@ -33,7 +33,8 @@ import java.lang.ref.SoftReference; */ public class PDFReference implements PDFWritable { - private String indirectReference; + private int objectNumber; + private int generation; private Reference objReference; @@ -42,7 +43,8 @@ public class PDFReference implements PDFWritable { * @param obj the object to be referenced */ public PDFReference(PDFObject obj) { - this.indirectReference = obj.referencePDF(); + this.objectNumber = obj.getObjectNumber(); + this.generation = obj.getGeneration(); this.objReference = new SoftReference(obj); } @@ -54,7 +56,11 @@ public class PDFReference implements PDFWritable { if (ref == null) { throw new NullPointerException("ref must not be null"); } - this.indirectReference = ref; + String[] parts = ref.split(" "); + assert parts.length == 3; + this.objectNumber = Integer.parseInt(parts[0]); + this.generation = Integer.parseInt(parts[1]); + assert "R".equals(parts[2]); } /** @@ -73,9 +79,25 @@ public class PDFReference implements PDFWritable { } } + /** + * Returns the object number. + * @return the object number + */ + public int getObjectNumber() { + return this.objectNumber; + } + + /** + * Returns the generation. + * @return the generation + */ + public int getGeneration() { + return this.generation; + } + /** {@inheritDoc} */ public String toString() { - return this.indirectReference; + return getObjectNumber() + " " + getGeneration() + " R"; } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/pdf/PDFUri.java b/src/java/org/apache/fop/pdf/PDFUri.java index 296e38945..a6124ec03 100644 --- a/src/java/org/apache/fop/pdf/PDFUri.java +++ b/src/java/org/apache/fop/pdf/PDFUri.java @@ -41,14 +41,22 @@ public class PDFUri extends PDFAction { * @return the action to place next to /A within a Link */ public String getAction() { + if (hasObjectNumber()) { + return referencePDF(); + } else { + return getDictString(); + } + } + + private String getDictString() { return "<< /URI (" + uri + ")\n/S /URI >>"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public String toPDFString() { - throw new UnsupportedOperationException("This method should not be called"); + //TODO Convert this class into a dictionary + return getObjectID() + getDictString() + "\nendobj\n"; + //throw new UnsupportedOperationException("This method should not be called"); } } diff --git a/src/java/org/apache/fop/render/intermediate/AbstractIFDocumentHandler.java b/src/java/org/apache/fop/render/intermediate/AbstractIFDocumentHandler.java index b1f1f20f9..4894604a2 100644 --- a/src/java/org/apache/fop/render/intermediate/AbstractIFDocumentHandler.java +++ b/src/java/org/apache/fop/render/intermediate/AbstractIFDocumentHandler.java @@ -56,6 +56,11 @@ public abstract class AbstractIFDocumentHandler implements IFDocumentHandler { return this.userAgent; } + /** {@inheritDoc} */ + public IFDocumentNavigationHandler getDocumentNavigationHandler() { + return null; //By default, this is not supported + } + /** {@inheritDoc} */ public void startDocumentHeader() throws IFException { //nop diff --git a/src/java/org/apache/fop/render/intermediate/IFDocumentHandler.java b/src/java/org/apache/fop/render/intermediate/IFDocumentHandler.java index e3634e9ef..f1d6e2057 100644 --- a/src/java/org/apache/fop/render/intermediate/IFDocumentHandler.java +++ b/src/java/org/apache/fop/render/intermediate/IFDocumentHandler.java @@ -112,6 +112,12 @@ public interface IFDocumentHandler { */ IFDocumentHandlerConfigurator getConfigurator(); + /** + * Returns a document navigation handler if this feature is supported. + * @return the document navigation handler or null if not supported + */ + IFDocumentNavigationHandler getDocumentNavigationHandler(); + /** * Indicates whether the painter supports to handle the pages in mixed order rather than * ascending order. diff --git a/src/java/org/apache/fop/render/intermediate/IFDocumentNavigationHandler.java b/src/java/org/apache/fop/render/intermediate/IFDocumentNavigationHandler.java new file mode 100644 index 000000000..eef13e105 --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/IFDocumentNavigationHandler.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import org.apache.fop.render.intermediate.extensions.AbstractAction; +import org.apache.fop.render.intermediate.extensions.BookmarkTree; +import org.apache.fop.render.intermediate.extensions.Link; +import org.apache.fop.render.intermediate.extensions.NamedDestination; + + +/** + * Interface to handle document navigation features. This is an optional interface for + * document handler implementations which support document navigation features. + */ +public interface IFDocumentNavigationHandler { + + /** + * Renders a named destination. + * @param destination the named destination + * @throws IFException if an error occurs while handling this event + */ + void renderNamedDestination(NamedDestination destination) throws IFException; + + /** + * Render the bookmark tree. + * @param tree the bookmark tree + * @throws IFException if an error occurs while handling this event + */ + void renderBookmarkTree(BookmarkTree tree) throws IFException; + + void renderLink(Link link) throws IFException; + + void addResolvedAction(AbstractAction action) throws IFException; + +} diff --git a/src/java/org/apache/fop/render/intermediate/IFRenderer.java b/src/java/org/apache/fop/render/intermediate/IFRenderer.java index fecde8ae4..4b2b04395 100644 --- a/src/java/org/apache/fop/render/intermediate/IFRenderer.java +++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java @@ -28,6 +28,7 @@ import java.awt.geom.Rectangle2D; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Stack; @@ -67,6 +68,7 @@ import org.apache.fop.area.inline.AbstractTextArea; import org.apache.fop.area.inline.ForeignObject; import org.apache.fop.area.inline.Image; import org.apache.fop.area.inline.InlineArea; +import org.apache.fop.area.inline.InlineParent; import org.apache.fop.area.inline.Leader; import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; @@ -82,10 +84,14 @@ import org.apache.fop.fonts.LazyFont; import org.apache.fop.fonts.Typeface; import org.apache.fop.render.AbstractPathOrientedRenderer; import org.apache.fop.render.Renderer; +import org.apache.fop.render.intermediate.extensions.AbstractAction; +import org.apache.fop.render.intermediate.extensions.ActionSet; import org.apache.fop.render.intermediate.extensions.Bookmark; import org.apache.fop.render.intermediate.extensions.BookmarkTree; import org.apache.fop.render.intermediate.extensions.GoToXYAction; +import org.apache.fop.render.intermediate.extensions.Link; import org.apache.fop.render.intermediate.extensions.NamedDestination; +import org.apache.fop.render.intermediate.extensions.URIAction; import org.apache.fop.render.pdf.PDFEventProducer; import org.apache.fop.traits.BorderProps; import org.apache.fop.traits.RuleStyle; @@ -129,12 +135,6 @@ public class IFRenderer extends AbstractPathOrientedRenderer { */ private Map idPositions = new java.util.HashMap(); - /** - * Maps XSL-FO element IDs to "go-to" actions targeting the corresponding areas - * These objects may not all be fully filled in yet - */ - private Map idGoTos = new java.util.HashMap(); - /** * The "go-to" actions in idGoTos that are not complete yet */ @@ -143,6 +143,9 @@ public class IFRenderer extends AbstractPathOrientedRenderer { // even if the object number differs private BookmarkTree bookmarkTree; + private List deferredDestinations = new java.util.ArrayList(); + private List deferredLinks = new java.util.ArrayList(); + private ActionSet actionSet = new ActionSet(); private TextUtil textUtil = new TextUtil(); @@ -201,6 +204,22 @@ public class IFRenderer extends AbstractPathOrientedRenderer { ? this.documentHandler.supportsPagesOutOfOrder() : false); } + /** + * Returns the document navigation handler if available/supported. + * @return the document navigation handler or null if not supported + */ + protected IFDocumentNavigationHandler getDocumentNavigationHandler() { + return this.documentHandler.getDocumentNavigationHandler(); + } + + /** + * Indicates whether document navigation features are supported by the document handler. + * @return true if document navigation features are available + */ + protected boolean hasDocumentNavigation() { + return getDocumentNavigationHandler() != null; + } + /** * Creates a default {@code IFDocumentHandler} when none has been set. * @return the default IFDocumentHandler @@ -246,17 +265,32 @@ public class IFRenderer extends AbstractPathOrientedRenderer { this.inPageSequence = false; } documentHandler.startDocumentTrailer(); + + //Wrap up document navigation finishOpenGoTos(); + Iterator iter; + iter = this.actionSet.getActions(); + while (iter.hasNext()) { + getDocumentNavigationHandler().addResolvedAction((AbstractAction)iter.next()); + } + iter = this.deferredDestinations.iterator(); + while (iter.hasNext()) { + NamedDestination dest = (NamedDestination)iter.next(); + iter.remove(); + getDocumentNavigationHandler().renderNamedDestination(dest); + } + if (this.bookmarkTree != null) { - documentHandler.handleExtensionObject(this.bookmarkTree); + getDocumentNavigationHandler().renderBookmarkTree(this.bookmarkTree); } + documentHandler.endDocumentTrailer(); documentHandler.endDocument(); } catch (IFException e) { handleIFExceptionWithIOException(e); } idPositions.clear(); - idGoTos.clear(); + actionSet.clear(); super.stopRenderer(); log.debug("Rendering finished."); } @@ -278,6 +312,9 @@ public class IFRenderer extends AbstractPathOrientedRenderer { } private void renderDestination(DestinationData dd) { + if (!hasDocumentNavigation()) { + return; + } String targetID = dd.getIDRef(); if (targetID == null || targetID.length() == 0) { throw new IllegalArgumentException("DestinationData must contain a ID reference"); @@ -285,12 +322,9 @@ public class IFRenderer extends AbstractPathOrientedRenderer { PageViewport pv = dd.getPageViewport(); if (pv != null) { GoToXYAction action = getGoToActionForID(targetID, pv.getPageIndex()); - NamedDestination namedDestination = new NamedDestination(targetID, action); - try { - documentHandler.handleExtensionObject(namedDestination); - } catch (IFException ife) { - handleIFException(ife); - } + NamedDestination namedDestination = new NamedDestination(targetID, + action.createReference()); + this.deferredDestinations.add(namedDestination); } else { //Warning already issued by AreaTreeHandler (debug level is sufficient) log.debug("Unresolved destination item received: " + dd.getIDRef()); @@ -303,6 +337,9 @@ public class IFRenderer extends AbstractPathOrientedRenderer { */ protected void renderBookmarkTree(BookmarkData bookmarks) { assert this.bookmarkTree == null; + if (!hasDocumentNavigation()) { + return; + } this.bookmarkTree = new BookmarkTree(); for (int i = 0; i < bookmarks.getCount(); i++) { BookmarkData ext = bookmarks.getSubData(i); @@ -330,7 +367,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { Bookmark b = new Bookmark( bookmarkItem.getBookmarkTitle(), bookmarkItem.showChildItems(), - action); + (action != null ? action.createReference() : null)); for (int i = 0; i < bookmarkItem.getCount(); i++) { b.addChildBookmark(renderBookmarkItem(bookmarkItem.getSubData(i))); } @@ -342,23 +379,21 @@ public class IFRenderer extends AbstractPathOrientedRenderer { } private GoToXYAction getGoToActionForID(String targetID, int pageIndex) { - // Already a PDFGoTo present for this target? If not, create. - GoToXYAction action = (GoToXYAction)idGoTos.get(targetID); + // Already a GoToXY present for this target? If not, create. + GoToXYAction action = (GoToXYAction)actionSet.get(targetID); + //GoToXYAction action = (GoToXYAction)idGoTos.get(targetID); if (action == null) { - //String pdfPageRef = (String) pageReferences.get(pvKey); Point position = (Point)idPositions.get(targetID); // can the GoTo already be fully filled in? - if (/*pdfPageRef != null &&*/ position != null) { - // getPDFGoTo shares PDFGoTo objects as much as possible. - // It also takes care of assignObjectNumber and addTrailerObject. - //action = pdfDoc.getFactory().getPDFGoTo(pdfPageRef, position); - action = new GoToXYAction(pageIndex, position); + if (position != null) { + action = new GoToXYAction(targetID, pageIndex, position); } else { // Not complete yet, can't use getPDFGoTo: - action = new GoToXYAction(pageIndex, null); + action = new GoToXYAction(targetID, pageIndex, null); unfinishedGoTos.add(action); } - idGoTos.put(targetID, action); + action = (GoToXYAction)actionSet.put(action); + //idGoTos.put(targetID, action); } return action; } @@ -393,7 +428,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { tf.transform(position, position); idPositions.put(id, position); // is there already a PDFGoTo waiting to be completed? - GoToXYAction action = (GoToXYAction)idGoTos.get(id); + GoToXYAction action = (GoToXYAction)actionSet.get(id); if (action != null) { noteGoToPosition(action, pv, position); } @@ -507,9 +542,18 @@ public class IFRenderer extends AbstractPathOrientedRenderer { super.renderPage(page); this.painter = null; documentHandler.endPageContent(); + documentHandler.startPageTrailer(); - //TODO Handle page trailer + if (hasDocumentNavigation()) { + Iterator iter = this.deferredLinks.iterator(); + while (iter.hasNext()) { + Link link = (Link)iter.next(); + iter.remove(); + getDocumentNavigationHandler().renderLink(link); + } + } documentHandler.endPageTrailer(); + documentHandler.endPage(); } catch (IFException e) { handleIFException(e); @@ -799,6 +843,59 @@ public class IFRenderer extends AbstractPathOrientedRenderer { super.renderInlineArea(inlineArea); } + /** {@inheritDoc} */ + public void renderInlineParent(InlineParent ip) { + // stuff we only need if a link must be created: + Rectangle ipRect = null; + AbstractAction action = null; + // make sure the rect is determined *before* calling super! + int ipp = currentIPPosition; + int bpp = currentBPPosition + ip.getOffset(); + ipRect = new Rectangle(ipp, bpp, ip.getIPD(), ip.getBPD()); + AffineTransform transform = graphicContext.getTransform(); + ipRect = transform.createTransformedShape(ipRect).getBounds(); + + // render contents + super.renderInlineParent(ip); + + boolean linkTraitFound = false; + + // try INTERNAL_LINK first + Trait.InternalLink intLink = (Trait.InternalLink) ip.getTrait(Trait.INTERNAL_LINK); + if (intLink != null) { + linkTraitFound = true; + String pvKey = intLink.getPVKey(); + + String idRef = intLink.getIDRef(); + boolean pvKeyOK = pvKey != null && pvKey.length() > 0; + boolean idRefOK = idRef != null && idRef.length() > 0; + if (pvKeyOK && idRefOK) { + action = getGoToActionForID(idRef, this.currentPageViewport.getPageIndex()); + } else { + //Warnings already issued by AreaTreeHandler + } + } + + // no INTERNAL_LINK, look for EXTERNAL_LINK + if (!linkTraitFound) { + Trait.ExternalLink extLink = (Trait.ExternalLink) ip.getTrait(Trait.EXTERNAL_LINK); + if (extLink != null) { + String extDest = extLink.getDestination(); + if (extDest != null && extDest.length() > 0) { + linkTraitFound = true; + action = new URIAction(extDest, extLink.newWindow()); + action = actionSet.put(action); + } + } + } + + // warn if link trait found but not allowed, else create link + if (linkTraitFound) { + Link link = new Link(action.createReference(), ipRect); + this.deferredLinks.add(link); + } + } + /** {@inheritDoc} */ protected void renderBlock(Block block) { if (log.isTraceEnabled()) { diff --git a/src/java/org/apache/fop/render/intermediate/IFSerializer.java b/src/java/org/apache/fop/render/intermediate/IFSerializer.java index 661a2fddd..c4fb0cb0d 100644 --- a/src/java/org/apache/fop/render/intermediate/IFSerializer.java +++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java @@ -38,6 +38,10 @@ import org.apache.xmlgraphics.util.XMLizable; import org.apache.fop.fonts.FontInfo; import org.apache.fop.render.RenderingContext; +import org.apache.fop.render.intermediate.extensions.AbstractAction; +import org.apache.fop.render.intermediate.extensions.BookmarkTree; +import org.apache.fop.render.intermediate.extensions.Link; +import org.apache.fop.render.intermediate.extensions.NamedDestination; import org.apache.fop.traits.BorderProps; import org.apache.fop.traits.RuleStyle; import org.apache.fop.util.ColorUtil; @@ -47,7 +51,8 @@ import org.apache.fop.util.XMLUtil; /** * IFPainter implementation that serializes the intermediate format to XML. */ -public class IFSerializer extends AbstractXMLWritingIFDocumentHandler implements IFConstants, IFPainter { +public class IFSerializer extends AbstractXMLWritingIFDocumentHandler + implements IFConstants, IFPainter, IFDocumentNavigationHandler { private IFDocumentHandler mimicHandler; @@ -83,6 +88,11 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler implements } } + /** {@inheritDoc} */ + public IFDocumentNavigationHandler getDocumentNavigationHandler() { + return this; + } + public void mimicDocumentHandler(IFDocumentHandler targetHandler) { this.mimicHandler = targetHandler; } @@ -553,4 +563,34 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler implements XMLUtil.addAttribute(atts, localName, value); } + // ---=== IFDocumentNavigationHandler ===--- + + /** {@inheritDoc} */ + public void renderNamedDestination(NamedDestination destination) throws IFException { + renderXMLizable(destination); + } + + /** {@inheritDoc} */ + public void renderBookmarkTree(BookmarkTree tree) throws IFException { + renderXMLizable(tree); + } + + /** {@inheritDoc} */ + public void addResolvedAction(AbstractAction action) throws IFException { + renderXMLizable(action); + } + + /** {@inheritDoc} */ + public void renderLink(Link link) throws IFException { + renderXMLizable(link); + } + + private void renderXMLizable(XMLizable object) throws IFException { + try { + object.toSAX(handler); + } catch (SAXException e) { + throw new IFException("SAX error serializing object", e); + } + } + } diff --git a/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java b/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java index 37a3032cd..f5bde16e1 100644 --- a/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java +++ b/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java @@ -26,4 +26,62 @@ import org.apache.xmlgraphics.util.XMLizable; */ public abstract class AbstractAction implements XMLizable { + private String id; + + /** + * Sets an ID to make the action referencable. + * @param id the ID + */ + public void setID(String id) { + this.id = id; + } + + /** + * Returns an optional ID for this action. + * @return the ID or null + */ + public String getID() { + return this.id; + } + + /** + * Indicates whether the action has an ID and is therefore referencable. + * @return true if the action has an ID + */ + public boolean hasID() { + return this.id != null; + } + + /** + * Indicates whether two action are equal. Note: this is not the same as + * {@link Object#equals(Object)}! + * @param other the other action to compare to + * @return true if the actions are equal + */ + public abstract boolean isSame(AbstractAction other); + + /** + * Indicates whether this action is a reference. + * @return true if this action is a reference, false for a normal action + */ + public boolean isReference() { + return false; + } + + /** + * Creates a reference to this action. + * @return the reference + */ + public AbstractAction createReference() { + return new ReferencedAction(getID()); + } + + /** + * Returns a string that is used to prefix a generated ID to make it unique. + * @return the prefix string + */ + public String getIDPrefix() { + return null; + } + } diff --git a/src/java/org/apache/fop/render/intermediate/extensions/ActionSet.java b/src/java/org/apache/fop/render/intermediate/extensions/ActionSet.java new file mode 100644 index 000000000..df663baef --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/extensions/ActionSet.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate.extensions; + +import java.util.Iterator; +import java.util.Map; + +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + +import org.apache.xmlgraphics.util.XMLizable; + +/** + * This class manages actions and action references. Some action (like {@link GoToXYAction}s) + * cannot be fully resolved at the time they are needed, so they are deferred. This class + * helps manages the references and resolution. + */ +public class ActionSet implements XMLizable { + + private int lastGeneratedID = 0; + private Map actionRegistry = new java.util.HashMap(); + + public synchronized String generateNewID(AbstractAction action) { + this.lastGeneratedID++; + String prefix = action.getIDPrefix(); + if (prefix == null) { + throw new IllegalArgumentException("Action class is not compatible"); + } + return prefix + this.lastGeneratedID; + } + + public AbstractAction get(String id) { + return (AbstractAction)this.actionRegistry.get(id); + } + + public AbstractAction put(AbstractAction action) { + if (!action.hasID()) { + action.setID(generateNewID(action)); + } + AbstractAction effAction = normalize(action); + if (effAction == action) { + this.actionRegistry.put(action.getID(), action); + } + return effAction; + } + + public void clear() { + this.actionRegistry.clear(); + } + + public AbstractAction normalize(AbstractAction action) { + Iterator iter = this.actionRegistry.values().iterator(); + while (iter.hasNext()) { + AbstractAction a = (AbstractAction)iter.next(); + if (a.isSame(action)) { + return a; + } + } + return action; + } + + public Iterator getActions() { + return this.actionRegistry.values().iterator(); + } + + public void toSAX(ContentHandler handler) throws SAXException { + // TODO Auto-generated method stub + + } +} diff --git a/src/java/org/apache/fop/render/intermediate/extensions/Bookmark.java b/src/java/org/apache/fop/render/intermediate/extensions/Bookmark.java index 446da6ef9..b4c4bf192 100644 --- a/src/java/org/apache/fop/render/intermediate/extensions/Bookmark.java +++ b/src/java/org/apache/fop/render/intermediate/extensions/Bookmark.java @@ -29,6 +29,7 @@ import org.xml.sax.helpers.AttributesImpl; import org.apache.xmlgraphics.util.XMLizable; +import org.apache.fop.util.XMLConstants; import org.apache.fop.util.XMLUtil; /** @@ -114,9 +115,13 @@ public class Bookmark implements XMLizable, DocumentNavigationExtensionConstants atts.addAttribute(null, "title", "title", XMLUtil.CDATA, getTitle()); atts.addAttribute(null, "starting-state", "starting-state", XMLUtil.CDATA, isShown() ? "show" : "hide"); + if (getAction().isReference()) { + atts.addAttribute(null, ACTION_REF, ACTION_REF, + XMLConstants.CDATA, getAction().getID()); + } handler.startElement(BOOKMARK.getNamespaceURI(), BOOKMARK.getLocalName(), BOOKMARK.getQName(), atts); - if (getAction() != null) { + if (!getAction().isReference()) { getAction().toSAX(handler); } if (this.childBookmarks != null) { diff --git a/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationExtensionConstants.java b/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationExtensionConstants.java index a8f458f97..6c61f219e 100644 --- a/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationExtensionConstants.java +++ b/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationExtensionConstants.java @@ -38,10 +38,15 @@ public interface DocumentNavigationExtensionConstants { /** the named-destination element */ QName NAMED_DESTINATION = new QName(NAMESPACE, "named-destination"); + /** the link element */ + QName LINK = new QName(NAMESPACE, "link"); /** the goto-xy element */ QName GOTO_XY = new QName(NAMESPACE, "goto-xy"); /** the goto-uri element */ QName GOTO_URI = new QName(NAMESPACE, "goto-uri"); + /** Attribute name for the action reference */ + String ACTION_REF = "action-ref"; + } diff --git a/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationExtensionHandlerFactory.java b/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationExtensionHandlerFactory.java index 2822ff2ad..9044a4a62 100644 --- a/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationExtensionHandlerFactory.java +++ b/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationExtensionHandlerFactory.java @@ -98,14 +98,17 @@ public class DocumentNavigationExtensionHandlerFactory NamedDestination dest = new NamedDestination(name, null); objectStack.push(dest); } else if (GOTO_XY.getLocalName().equals(localName)) { + String id = attributes.getValue("id"); int pageIndex = XMLUtil.getAttributeAsInt(attributes, "page-index"); int x = XMLUtil.getAttributeAsInt(attributes, "x"); int y = XMLUtil.getAttributeAsInt(attributes, "y"); - GoToXYAction action = new GoToXYAction(pageIndex, new Point(x, y)); + GoToXYAction action = new GoToXYAction(id, pageIndex, new Point(x, y)); objectStack.push(action); } else if (GOTO_URI.getLocalName().equals(localName)) { String gotoURI = attributes.getValue("uri"); - URIAction action = new URIAction(gotoURI); + String showDestination = attributes.getValue("show-destination"); + boolean newWindow = "new".equals(showDestination); + URIAction action = new URIAction(gotoURI, newWindow); objectStack.push(action); } else { throw new SAXException( diff --git a/src/java/org/apache/fop/render/intermediate/extensions/GoToXYAction.java b/src/java/org/apache/fop/render/intermediate/extensions/GoToXYAction.java index 67cd5b592..4148b76f6 100644 --- a/src/java/org/apache/fop/render/intermediate/extensions/GoToXYAction.java +++ b/src/java/org/apache/fop/render/intermediate/extensions/GoToXYAction.java @@ -40,9 +40,10 @@ public class GoToXYAction extends AbstractAction implements DocumentNavigationEx * @param pageIndex the page index (0-based) of the target page * @param targetLocation the absolute location on the page (coordinates in millipoints) */ - public GoToXYAction(int pageIndex, Point targetLocation) { + public GoToXYAction(String id, int pageIndex, Point targetLocation) { + setID(id); this.pageIndex = pageIndex; - this.targetLocation = targetLocation; + setTargetLocation(targetLocation); } /** @@ -69,9 +70,36 @@ public class GoToXYAction extends AbstractAction implements DocumentNavigationEx this.targetLocation = location; } + /** {@inheritDoc} */ + public boolean isSame(AbstractAction other) { + if (other == null) { + throw new NullPointerException("other must not be null"); + } + if (!(other instanceof GoToXYAction)) { + return false; + } + GoToXYAction otherAction = (GoToXYAction)other; + if (getPageIndex() != otherAction.getPageIndex()) { + return false; + } + if (getTargetLocation() == null && otherAction.getTargetLocation() != null) { + return false; + } + if (!getTargetLocation().equals(otherAction.getTargetLocation())) { + return false; + } + return true; + } + /** {@inheritDoc} */ public void toSAX(ContentHandler handler) throws SAXException { + if (getTargetLocation() == null) { + setTargetLocation(new Point(0, 0)); + } AttributesImpl atts = new AttributesImpl(); + if (hasID()) { + atts.addAttribute(null, "id", "id", XMLUtil.CDATA, getID()); + } atts.addAttribute(null, "page-index", "page-index", XMLUtil.CDATA, Integer.toString(pageIndex)); atts.addAttribute(null, "x", "x", XMLUtil.CDATA, Integer.toString(targetLocation.x)); diff --git a/src/java/org/apache/fop/render/intermediate/extensions/Link.java b/src/java/org/apache/fop/render/intermediate/extensions/Link.java new file mode 100644 index 000000000..b54697ac4 --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/extensions/Link.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate.extensions; + +import java.awt.Rectangle; + +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.apache.xmlgraphics.util.XMLizable; + +import org.apache.fop.render.intermediate.IFUtil; +import org.apache.fop.util.XMLConstants; + +/** + * This class is a link element for use in the intermediate format. + */ +public class Link implements XMLizable, DocumentNavigationExtensionConstants { + + /** Attribute name for the target rectangle */ + public static final String RECT = "rect"; + + private AbstractAction action; + private Rectangle targetRect; + + /** + * Creates a new named destination. + * @param action the action performed when the destination is selected + * @param targetRect the target rectangle (coordinates in millipoints) + */ + public Link(AbstractAction action, Rectangle targetRect) { + this.action = action; + this.targetRect = targetRect; + } + + /** + * Returns the action performed when the destination is selected. + * @return the action + */ + public AbstractAction getAction() { + return this.action; + } + + /** + * Returns the target rectangle, i.e. the hot zone in which the link is activated. + * @return the target rectangle + */ + public Rectangle getTargetRect() { + return new Rectangle(this.targetRect); + } + + /** + * Sets the action performed when the destination is selected. + * @param action the action + */ + public void setAction(AbstractAction action) { + this.action = action; + } + + /** {@inheritDoc} */ + public void toSAX(ContentHandler handler) throws SAXException { + if (getAction() == null) { + throw new IllegalStateException("Action has not been set"); + } + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute(null, RECT, RECT, + XMLConstants.CDATA, IFUtil.toString(getTargetRect())); + if (getAction().isReference()) { + atts.addAttribute(null, ACTION_REF, ACTION_REF, + XMLConstants.CDATA, getAction().getID()); + } + handler.startElement(LINK.getNamespaceURI(), + LINK.getLocalName(), LINK.getQName(), atts); + if (!getAction().isReference()) { + getAction().toSAX(handler); + } + handler.endElement(LINK.getNamespaceURI(), + LINK.getLocalName(), LINK.getQName()); + } + +} diff --git a/src/java/org/apache/fop/render/intermediate/extensions/NamedDestination.java b/src/java/org/apache/fop/render/intermediate/extensions/NamedDestination.java index 85b6aca19..21676832b 100644 --- a/src/java/org/apache/fop/render/intermediate/extensions/NamedDestination.java +++ b/src/java/org/apache/fop/render/intermediate/extensions/NamedDestination.java @@ -32,6 +32,9 @@ import org.apache.fop.util.XMLConstants; */ public class NamedDestination implements XMLizable, DocumentNavigationExtensionConstants { + /** Attribute name for the destination name */ + public static final String NAME = "name"; + private String name; private AbstractAction action; @@ -71,11 +74,18 @@ public class NamedDestination implements XMLizable, DocumentNavigationExtensionC /** {@inheritDoc} */ public void toSAX(ContentHandler handler) throws SAXException { + if (getAction() == null) { + throw new IllegalStateException("Action has not been set"); + } AttributesImpl atts = new AttributesImpl(); - atts.addAttribute(null, "name", "name", XMLConstants.CDATA, getName()); + atts.addAttribute(null, NAME, NAME, XMLConstants.CDATA, getName()); + if (getAction().isReference()) { + atts.addAttribute(null, ACTION_REF, ACTION_REF, + XMLConstants.CDATA, getAction().getID()); + } handler.startElement(NAMED_DESTINATION.getNamespaceURI(), NAMED_DESTINATION.getLocalName(), NAMED_DESTINATION.getQName(), atts); - if (getAction() != null) { + if (!getAction().isReference()) { getAction().toSAX(handler); } handler.endElement(NAMED_DESTINATION.getNamespaceURI(), diff --git a/src/java/org/apache/fop/render/intermediate/extensions/ReferencedAction.java b/src/java/org/apache/fop/render/intermediate/extensions/ReferencedAction.java new file mode 100644 index 000000000..20ec11362 --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/extensions/ReferencedAction.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate.extensions; + +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + +/** + * Action class which references another action. + */ +public class ReferencedAction extends AbstractAction + implements DocumentNavigationExtensionConstants { + + /** + * Creates a new instance. + * @param id the ID + */ + public ReferencedAction(String id) { + if (id == null || id.length() == 0) { + throw new NullPointerException("ID must not be set"); + } + setID(id); + } + + /** {@inheritDoc} */ + public boolean isReference() { + return true; + } + + /** {@inheritDoc} */ + public void toSAX(ContentHandler handler) throws SAXException { + //nop, handled by referencer + } + + /** {@inheritDoc} */ + public boolean isSame(AbstractAction other) { + throw new UnsupportedOperationException( + "isSame() may not be called on " + getClass().getName()); + } + +} diff --git a/src/java/org/apache/fop/render/intermediate/extensions/URIAction.java b/src/java/org/apache/fop/render/intermediate/extensions/URIAction.java index e3020dac3..835a4b589 100644 --- a/src/java/org/apache/fop/render/intermediate/extensions/URIAction.java +++ b/src/java/org/apache/fop/render/intermediate/extensions/URIAction.java @@ -32,13 +32,19 @@ import org.apache.fop.util.XMLUtil; public class URIAction extends AbstractAction implements DocumentNavigationExtensionConstants { private String uri; + private boolean newWindow; /** * Creates a new instance. * @param uri the target URI + * @param newWindow true if the link should be opened in a new window */ - public URIAction(String uri) { + public URIAction(String uri, boolean newWindow) { + if (uri == null) { + throw new NullPointerException("uri must not be null"); + } this.uri = uri; + this.newWindow = newWindow; } /** @@ -49,10 +55,47 @@ public class URIAction extends AbstractAction implements DocumentNavigationExten return this.uri; } + /** + * Indicates whether the link shall be opened in a new window. + * @return true if a new window shall be opened + */ + public boolean isNewWindow() { + return this.newWindow; + } + + /** {@inheritDoc} */ + public boolean isSame(AbstractAction other) { + if (other == null) { + throw new NullPointerException("other must not be null"); + } + if (!(other instanceof URIAction)) { + return false; + } + URIAction otherAction = (URIAction)other; + if (!getURI().equals(otherAction.getURI())) { + return false; + } + if (isNewWindow() != otherAction.isNewWindow()) { + return false; + } + return true; + } + + /** {@inheritDoc} */ + public String getIDPrefix() { + return "fop:" + GOTO_URI.getLocalName(); + } + /** {@inheritDoc} */ public void toSAX(ContentHandler handler) throws SAXException { AttributesImpl atts = new AttributesImpl(); + if (hasID()) { + atts.addAttribute(null, "id", "id", XMLUtil.CDATA, getID()); + } atts.addAttribute(null, "uri", "uri", XMLUtil.CDATA, getURI()); + if (isNewWindow()) { + atts.addAttribute(null, "show-destination", "show-destination", XMLUtil.CDATA, "new"); + } handler.startElement(GOTO_URI.getNamespaceURI(), GOTO_URI.getLocalName(), GOTO_URI.getQName(), atts); handler.endElement(GOTO_URI.getNamespaceURI(), diff --git a/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java index 4b0f35bec..567ef0507 100644 --- a/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java +++ b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java @@ -237,17 +237,6 @@ public class PDFContentGenerator { } } - /** - * Establishes a new foreground or fill color. In contrast to updateColor - * this method does not check the PDFState for optimization possibilities. - * @param col the color to apply - * @param fill true to set the fill color, false for the foreground color - * @param pdf StringBuffer to write the PDF code to - *//* - public void setColor(Color col, boolean fill, StringBuffer pdf) { - assert pdf != null; - }*/ - /** * Establishes a new foreground or fill color. * @param col the color to apply diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java index 1358b1c5e..6535be758 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java @@ -21,9 +21,7 @@ package org.apache.fop.render.pdf; import java.awt.Dimension; import java.awt.geom.AffineTransform; -import java.awt.geom.Point2D; import java.io.IOException; -import java.util.Iterator; import java.util.Map; import org.apache.commons.logging.Log; @@ -34,23 +32,17 @@ import org.apache.xmlgraphics.xmp.Metadata; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.MimeConstants; import org.apache.fop.fo.extensions.xmp.XMPMetadata; -import org.apache.fop.pdf.PDFAction; import org.apache.fop.pdf.PDFAnnotList; import org.apache.fop.pdf.PDFDocument; -import org.apache.fop.pdf.PDFOutline; import org.apache.fop.pdf.PDFPage; import org.apache.fop.pdf.PDFReference; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFResources; import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; +import org.apache.fop.render.intermediate.IFDocumentNavigationHandler; import org.apache.fop.render.intermediate.IFException; import org.apache.fop.render.intermediate.IFPainter; -import org.apache.fop.render.intermediate.extensions.AbstractAction; -import org.apache.fop.render.intermediate.extensions.Bookmark; -import org.apache.fop.render.intermediate.extensions.BookmarkTree; -import org.apache.fop.render.intermediate.extensions.GoToXYAction; -import org.apache.fop.render.intermediate.extensions.NamedDestination; /** * {@code IFDocumentHandler} implementation that produces PDF. @@ -81,12 +73,15 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** the current page to add annotations to */ protected PDFPage currentPage; - /** the current page's PDF reference string (to avoid numerous function calls) */ - protected String currentPageRef; + /** the current page's PDF reference */ + protected PageReference currentPageRef; /** Used for bookmarks/outlines. */ protected Map pageReferences = new java.util.HashMap(); + private PDFDocumentNavigationHandler documentNavigationHandler + = new PDFDocumentNavigationHandler(this); + /** * Default constructor. */ @@ -114,6 +109,11 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { return new PDFRendererConfigurator(getUserAgent()); } + /** {@inheritDoc} */ + public IFDocumentNavigationHandler getDocumentNavigationHandler() { + return this.documentNavigationHandler; + } + PDFRenderingUtil getPDFUtil() { return this.pdfUtil; } @@ -142,6 +142,7 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** {@inheritDoc} */ public void endDocument() throws IFException { try { + this.documentNavigationHandler.commit(); pdfDoc.getResources().addFonts(pdfDoc, fontInfo); pdfDoc.outputTrailer(this.outputStream); @@ -182,15 +183,14 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { pdfUtil.generatePageLabel(index, name); - currentPageRef = currentPage.referencePDF(); - this.pageReferences.put(new Integer(index), new PageReference(currentPage, size)); + currentPageRef = new PageReference(currentPage, size); + this.pageReferences.put(new Integer(index), currentPageRef); this.generator = new PDFContentGenerator(this.pdfDoc, this.outputStream, this.currentPage); // Transform the PDF's default coordinate system (0,0 at lower left) to the PDFPainter's AffineTransform basicPageTransform = new AffineTransform(1, 0, 0, -1, 0, size.height / 1000f); generator.concatenate(basicPageTransform); - } /** {@inheritDoc} */ @@ -220,51 +220,6 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { } } - private void renderBookmarkTree(BookmarkTree tree) { - Iterator iter = tree.getBookmarks().iterator(); - while (iter.hasNext()) { - Bookmark b = (Bookmark)iter.next(); - renderBookmark(b, null); - } - } - - private void renderBookmark(Bookmark bookmark, PDFOutline parent) { - if (parent == null) { - parent = pdfDoc.getOutlineRoot(); - } - PDFAction action = getAction(bookmark.getAction()); - PDFOutline pdfOutline = pdfDoc.getFactory().makeOutline(parent, - bookmark.getTitle(), action, bookmark.isShown()); - Iterator iter = bookmark.getChildBookmarks().iterator(); - while (iter.hasNext()) { - Bookmark b = (Bookmark)iter.next(); - renderBookmark(b, pdfOutline); - } - } - - private void renderNamedDestination(NamedDestination destination) { - PDFAction action = getAction(destination.getAction()); - pdfDoc.getFactory().makeDestination( - destination.getName(), action.makeReference()); - } - - private PDFAction getAction(AbstractAction action) { - if (action instanceof GoToXYAction) { - GoToXYAction a = (GoToXYAction)action; - PageReference pageRef = (PageReference)this.pageReferences.get( - new Integer(a.getPageIndex())); - //Convert target location from millipoints to points and adjust for different - //page origin - Point2D p2d = new Point2D.Double( - a.getTargetLocation().x / 1000.0, - (pageRef.pageDimension.height - a.getTargetLocation().y) / 1000.0); - return pdfDoc.getFactory().getPDFGoTo(pageRef.pageRef.toString(), p2d); - } else { - throw new UnsupportedOperationException("Unsupported action type: " - + action + " (" + action.getClass().getName() + ")"); - } - } - /** {@inheritDoc} */ public void handleExtensionObject(Object extension) throws IFException { if (extension instanceof XMPMetadata) { @@ -272,17 +227,18 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { } else if (extension instanceof Metadata) { XMPMetadata wrapper = new XMPMetadata(((Metadata)extension)); pdfUtil.renderXMPMetadata(wrapper); - } else if (extension instanceof BookmarkTree) { - renderBookmarkTree((BookmarkTree)extension); - } else if (extension instanceof NamedDestination) { - renderNamedDestination((NamedDestination)extension); } else { log.debug("Don't know how to handle extension object. Ignoring: " + extension + " (" + extension.getClass().getName() + ")"); } } - private static final class PageReference { + PageReference getPageReference(int pageIndex) { + return (PageReference)this.pageReferences.get( + new Integer(pageIndex)); + } + + static final class PageReference { private PDFReference pageRef; private Dimension pageDimension; @@ -291,6 +247,14 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { this.pageRef = page.makeReference(); this.pageDimension = new Dimension(dim); } + + public PDFReference getPageRef() { + return this.pageRef; + } + + public Dimension getPageDimension() { + return this.pageDimension; + } } } diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java new file mode 100644 index 000000000..a50fb85db --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import java.awt.Rectangle; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.Iterator; +import java.util.List; + +import org.apache.fop.pdf.PDFAction; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFFactory; +import org.apache.fop.pdf.PDFLink; +import org.apache.fop.pdf.PDFOutline; +import org.apache.fop.pdf.PDFReference; +import org.apache.fop.render.intermediate.IFDocumentNavigationHandler; +import org.apache.fop.render.intermediate.IFException; +import org.apache.fop.render.intermediate.extensions.AbstractAction; +import org.apache.fop.render.intermediate.extensions.ActionSet; +import org.apache.fop.render.intermediate.extensions.Bookmark; +import org.apache.fop.render.intermediate.extensions.BookmarkTree; +import org.apache.fop.render.intermediate.extensions.GoToXYAction; +import org.apache.fop.render.intermediate.extensions.Link; +import org.apache.fop.render.intermediate.extensions.NamedDestination; +import org.apache.fop.render.intermediate.extensions.URIAction; +import org.apache.fop.render.pdf.PDFDocumentHandler.PageReference; + +/** + * Implementation of the {@link IFDocumentNavigationHandler} interface for PDF output. + */ +public class PDFDocumentNavigationHandler implements IFDocumentNavigationHandler { + + private PDFDocumentHandler documentHandler; + + private ActionSet actionSet = new ActionSet(); + private List deferredLinks = new java.util.ArrayList(); + + /** + * Default constructor. + * @param documentHandler the parent document handler + */ + public PDFDocumentNavigationHandler(PDFDocumentHandler documentHandler) { + super(); + this.documentHandler = documentHandler; + } + + PDFDocument getPDFDoc() { + return this.documentHandler.pdfDoc; + } + + /** {@inheritDoc} */ + public void renderNamedDestination(NamedDestination destination) throws IFException { + PDFReference actionRef = getAction(destination.getAction()); + getPDFDoc().getFactory().makeDestination( + destination.getName(), actionRef); + } + + /** {@inheritDoc} */ + public void renderBookmarkTree(BookmarkTree tree) throws IFException { + Iterator iter = tree.getBookmarks().iterator(); + while (iter.hasNext()) { + Bookmark b = (Bookmark)iter.next(); + renderBookmark(b, null); + } + } + + private void renderBookmark(Bookmark bookmark, PDFOutline parent) { + if (parent == null) { + parent = getPDFDoc().getOutlineRoot(); + } + PDFReference actionRef = getAction(bookmark.getAction()); + PDFOutline pdfOutline = getPDFDoc().getFactory().makeOutline(parent, + bookmark.getTitle(), actionRef.toString(), bookmark.isShown()); + Iterator iter = bookmark.getChildBookmarks().iterator(); + while (iter.hasNext()) { + Bookmark b = (Bookmark)iter.next(); + renderBookmark(b, pdfOutline); + } + } + + /** {@inheritDoc} */ + public void renderLink(Link link) throws IFException { + this.deferredLinks.add(link); + } + + /** + * Commits all pending elements to the PDF document. + */ + public void commit() { + Iterator iter = this.deferredLinks.iterator(); + while (iter.hasNext()) { + Link link = (Link)iter.next(); + Rectangle targetRect = link.getTargetRect(); + int pageHeight = documentHandler.currentPageRef.getPageDimension().height; + Rectangle2D targetRect2D = new Rectangle2D.Double( + targetRect.getMinX() / 1000.0, + (pageHeight - targetRect.getMinY()) / 1000.0, + targetRect.getWidth() / 1000.0, + targetRect.getHeight() / 1000.0); + PDFReference actionRef = getAction(link.getAction()); + //makeLink() currently needs a PDFAction and not a reference + //TODO Revisit when PDFLink is converted to a PDFDictionary + PDFAction pdfAction = (PDFAction)actionRef.getObject(); + PDFLink pdfLink = getPDFDoc().getFactory().makeLink( + targetRect2D, pdfAction); + documentHandler.currentPage.addAnnotation(pdfLink); + } + } + + /** {@inheritDoc} */ + public void addResolvedAction(AbstractAction action) throws IFException { + actionSet.put(action); + } + + private PDFReference getAction(AbstractAction action) { + if (action.isReference()) { + String id = action.getID(); + action = actionSet.get(id); + if (action == null) { + throw new IllegalStateException("Action could not be resolved: " + id); + } + } + PDFFactory factory = getPDFDoc().getFactory(); + if (action instanceof GoToXYAction) { + GoToXYAction a = (GoToXYAction)action; + PageReference pageRef = this.documentHandler.getPageReference(a.getPageIndex()); + //Convert target location from millipoints to points and adjust for different + //page origin + Point2D p2d = new Point2D.Double( + a.getTargetLocation().x / 1000.0, + (pageRef.getPageDimension().height - a.getTargetLocation().y) / 1000.0); + return factory.getPDFGoTo(pageRef.getPageRef().toString(), p2d).makeReference(); + } else if (action instanceof URIAction) { + URIAction u = (URIAction)action; + PDFAction pdfAction = factory.getExternalAction(u.getURI(), u.isNewWindow()); + getPDFDoc().registerObject(pdfAction); + return pdfAction.makeReference(); + } else { + throw new UnsupportedOperationException("Unsupported action type: " + + action + " (" + action.getClass().getName() + ")"); + } + } + +} diff --git a/test/java/org/apache/fop/pdf/PDFObjectTestCase.java b/test/java/org/apache/fop/pdf/PDFObjectTestCase.java index 40ddec6ba..d41a0f0f3 100644 --- a/test/java/org/apache/fop/pdf/PDFObjectTestCase.java +++ b/test/java/org/apache/fop/pdf/PDFObjectTestCase.java @@ -56,4 +56,22 @@ public class PDFObjectTestCase extends TestCase { } + /** + * Tests PDF object references. + * @throws Exception if an error occurs + */ + public void testReference() throws Exception { + PDFDictionary dict = new PDFDictionary(); + dict.setObjectNumber(7); + PDFReference ref = dict.makeReference(); + assertEquals(ref.getObjectNumber(), 7); + assertEquals(ref.getGeneration(), 0); + assertEquals(ref.toString(), "7 0 R"); + + ref = new PDFReference("8 0 R"); + assertEquals(ref.getObjectNumber(), 8); + assertEquals(ref.getGeneration(), 0); + assertEquals(ref.toString(), "8 0 R"); + } + } -- 2.39.5