]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Added support for bookmarks to intermediate format (including PDF painter).
authorJeremias Maerki <jeremias@apache.org>
Tue, 12 Aug 2008 14:44:55 +0000 (14:44 +0000)
committerJeremias Maerki <jeremias@apache.org>
Tue, 12 Aug 2008 14:44:55 +0000 (14:44 +0000)
Added infrastructure for IF testing similar to AT testing with XPaths. To minimize the additional processing needed it is attached to the layout engine tests and only executed if there are IF checks in the test case.

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_AreaTreeNewDesign@685170 13f79535-47bb-0310-9956-ffa450edef68

17 files changed:
src/java/META-INF/services/org.apache.fop.util.ContentHandlerFactory
src/java/org/apache/fop/render/intermediate/IFRenderer.java
src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java [new file with mode: 0644]
src/java/org/apache/fop/render/intermediate/extensions/Bookmark.java [new file with mode: 0644]
src/java/org/apache/fop/render/intermediate/extensions/BookmarkExtensionConstants.java [new file with mode: 0644]
src/java/org/apache/fop/render/intermediate/extensions/BookmarkExtensionHandlerFactory.java [new file with mode: 0644]
src/java/org/apache/fop/render/intermediate/extensions/BookmarkTree.java [new file with mode: 0644]
src/java/org/apache/fop/render/intermediate/extensions/GoToXYAction.java [new file with mode: 0644]
src/java/org/apache/fop/render/intermediate/extensions/URIAction.java [new file with mode: 0644]
src/java/org/apache/fop/render/pdf/PDFPainter.java
test/java/org/apache/fop/intermediate/IFCheck.java [new file with mode: 0644]
test/java/org/apache/fop/intermediate/IFTester.java [new file with mode: 0644]
test/java/org/apache/fop/layoutengine/EvalCheck.java
test/java/org/apache/fop/layoutengine/LayoutEngineTester.java
test/java/org/apache/fop/layoutengine/TrueCheck.java
test/layoutengine/standard-testcases/bookmarks_1.xml
test/layoutengine/testcase2checks.xsl

index 53fe41b13e5de134a8c08f695989ae85095139be..d7adf6a6c1c21743207a4a86e4043fd0933a7332 100644 (file)
@@ -1,3 +1,4 @@
 org.apache.fop.render.afp.extensions.AFPExtensionHandlerFactory\r
 org.apache.fop.render.ps.extensions.PSExtensionHandlerFactory\r
 org.apache.fop.fo.extensions.xmp.XMPContentHandlerFactory\r
+org.apache.fop.render.intermediate.extensions.BookmarkExtensionHandlerFactory\r
index 641e4686c9598006470d66396b3d2a281807e863..345854cdebe95376b427a5d46d80fc7c1643fef1 100644 (file)
@@ -21,6 +21,7 @@ package org.apache.fop.render.intermediate;
 
 import java.awt.Color;
 import java.awt.Dimension;
+import java.awt.Point;
 import java.awt.Rectangle;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Rectangle2D;
@@ -49,9 +50,12 @@ import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema;
 import org.apache.fop.Version;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.apps.MimeConstants;
+import org.apache.fop.area.Area;
 import org.apache.fop.area.Block;
 import org.apache.fop.area.BlockViewport;
+import org.apache.fop.area.BookmarkData;
 import org.apache.fop.area.CTM;
+import org.apache.fop.area.DestinationData;
 import org.apache.fop.area.OffDocumentExtensionAttachment;
 import org.apache.fop.area.OffDocumentItem;
 import org.apache.fop.area.PageSequence;
@@ -61,6 +65,7 @@ import org.apache.fop.area.Trait;
 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.SpaceArea;
 import org.apache.fop.area.inline.TextArea;
 import org.apache.fop.area.inline.Viewport;
@@ -75,6 +80,10 @@ 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.Bookmark;
+import org.apache.fop.render.intermediate.extensions.BookmarkTree;
+import org.apache.fop.render.intermediate.extensions.GoToXYAction;
+import org.apache.fop.render.pdf.PDFEventProducer;
 
 /**
  * This renderer implementation is an adapter to the {@code IFPainter} interface. It is used
@@ -102,6 +111,28 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
 
     private Metadata documentMetadata;
 
+    /**
+     * Maps XSL-FO element IDs to their on-page XY-positions
+     * Must be used in conjunction with the page reference to fully specify the details
+     * of a "go-to" action.
+     */
+    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
+     */
+    private List unfinishedGoTos = new java.util.ArrayList();
+    // can't use a Set because PDFGoTo.equals returns true if the target is the same,
+    // even if the object number differs
+
+    private BookmarkTree bookmarkTree;
+
     /**
      * Main constructor
      */
@@ -200,17 +231,29 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
                 painter.endPageSequence();
                 this.inPageSequence = false;
             }
+            finishOpenGoTos();
+            if (this.bookmarkTree != null) {
+                painter.handleExtensionObject(this.bookmarkTree);
+            }
             painter.endDocument();
         } catch (IFException e) {
             handleIFExceptionWithIOException(e);
         }
+        idPositions.clear();
+        idGoTos.clear();
         super.stopRenderer();
         log.debug("Rendering finished.");
     }
 
     /** {@inheritDoc} */
     public void processOffDocumentItem(OffDocumentItem odi) {
-        if (odi instanceof OffDocumentExtensionAttachment) {
+        if (odi instanceof DestinationData) {
+            // render Destinations
+            renderDestination((DestinationData) odi);
+        } else if (odi instanceof BookmarkData) {
+            // render Bookmark-Tree
+            renderBookmarkTree((BookmarkData) odi);
+        } else if (odi instanceof OffDocumentExtensionAttachment) {
             ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)odi).getAttachment();
             if (XMPMetadata.CATEGORY.equals(attachment.getCategory())) {
                 renderXMPMetadata((XMPMetadata)attachment);
@@ -218,10 +261,169 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
         }
     }
 
+    private void renderDestination(DestinationData dd) {
+        String targetID = dd.getIDRef();
+        if (targetID == null || targetID.length() == 0) {
+            throw new IllegalArgumentException("DestinationData must contain a ID reference");
+        }
+        PageViewport pv = dd.getPageViewport();
+        if (pv != null) {
+            GoToXYAction action = getGoToActionForID(targetID, pv.getPageIndex());
+            /*
+            pdfDoc.getFactory().makeDestination(
+                    dd.getIDRef(), gt.makeReference());
+                    */
+        } else {
+            //Warning already issued by AreaTreeHandler (debug level is sufficient)
+            log.debug("Unresolved destination item received: " + dd.getIDRef());
+        }
+    }
+
+    /**
+     * Renders a Bookmark-Tree object
+     * @param bookmarks the BookmarkData object containing all the Bookmark-Items
+     */
+    protected void renderBookmarkTree(BookmarkData bookmarks) {
+        assert this.bookmarkTree == null;
+        this.bookmarkTree = new BookmarkTree();
+        for (int i = 0; i < bookmarks.getCount(); i++) {
+            BookmarkData ext = bookmarks.getSubData(i);
+            Bookmark b = renderBookmarkItem(ext);
+            bookmarkTree.addBookmark(b);
+        }
+    }
+
+    private Bookmark renderBookmarkItem(BookmarkData bookmarkItem) {
+
+        String targetID = bookmarkItem.getIDRef();
+        if (targetID == null || targetID.length() == 0) {
+            throw new IllegalArgumentException("DestinationData must contain a ID reference");
+        }
+        GoToXYAction action = null;
+        PageViewport pv = bookmarkItem.getPageViewport();
+
+        if (pv != null) {
+            action = getGoToActionForID(targetID, pv.getPageIndex());
+        } else {
+            //Warning already issued by AreaTreeHandler (debug level is sufficient)
+            log.debug("Bookmark with IDRef \"" + targetID + "\" has a null PageViewport.");
+        }
+
+        Bookmark b = new Bookmark(
+                bookmarkItem.getBookmarkTitle(),
+                bookmarkItem.showChildItems(),
+                action);
+        for (int i = 0; i < bookmarkItem.getCount(); i++) {
+            b.addChildBookmark(renderBookmarkItem(bookmarkItem.getSubData(i)));
+        }
+        return b;
+    }
+
     private void renderXMPMetadata(XMPMetadata metadata) {
         this.documentMetadata = metadata.getMetadata();
     }
 
+    private GoToXYAction getGoToActionForID(String targetID, int pageIndex) {
+        // Already a PDFGoTo present for this target? If not, create.
+        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);
+            } else {
+                // Not complete yet, can't use getPDFGoTo:
+                action = new GoToXYAction(pageIndex, null);
+                unfinishedGoTos.add(action);
+            }
+            idGoTos.put(targetID, action);
+        }
+        return action;
+    }
+
+    private void finishOpenGoTos() {
+        int count = unfinishedGoTos.size();
+        if (count > 0) {
+            Point defaultPos = new Point(0, 0);  // top-o-page
+            while (!unfinishedGoTos.isEmpty()) {
+                GoToXYAction action = (GoToXYAction)unfinishedGoTos.get(0);
+                noteGoToPosition(action, defaultPos);
+            }
+            PDFEventProducer eventProducer = PDFEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.nonFullyResolvedLinkTargets(this, count);
+            // dysfunctional if pageref is null
+        }
+    }
+
+    private void noteGoToPosition(GoToXYAction action, Point position) {
+        action.setTargetLocation(position);
+        unfinishedGoTos.remove(action);
+    }
+
+    private void noteGoToPosition(GoToXYAction action, PageViewport pv, Point position) {
+        noteGoToPosition(action, position);
+    }
+
+    private void saveAbsolutePosition(String id, PageViewport pv,
+            int relativeIPP, int relativeBPP, AffineTransform tf) {
+        Point position = new Point(relativeIPP, relativeBPP);
+        tf.transform(position, position);
+        idPositions.put(id, position);
+        // is there already a PDFGoTo waiting to be completed?
+        GoToXYAction action = (GoToXYAction)idGoTos.get(id);
+        if (action != null) {
+            noteGoToPosition(action, pv, position);
+        }
+    }
+
+    private void saveAbsolutePosition(String id, int relativeIPP, int relativeBPP) {
+        saveAbsolutePosition(id, this.currentPageViewport,
+                             relativeIPP, relativeBPP, graphicContext.getTransform());
+    }
+
+    protected void saveBlockPosIfTargetable(Block block) {
+        String id = getTargetableID(block);
+        if (id != null) {
+            // FIXME: Like elsewhere in the renderer code, absolute and relative
+            //        directions are happily mixed here. This makes sure that the
+            //        links point to the right location, but it is not correct.
+            int ipp = block.getXOffset();
+            int bpp = block.getYOffset() + block.getSpaceBefore();
+            int positioning = block.getPositioning();
+            if (!(positioning == Block.FIXED || positioning == Block.ABSOLUTE)) {
+                ipp += currentIPPosition;
+                bpp += currentBPPosition;
+            }
+            saveAbsolutePosition(id, currentPageViewport, ipp, bpp, graphicContext.getTransform());
+        }
+    }
+
+    private void saveInlinePosIfTargetable(InlineArea inlineArea) {
+        String id = getTargetableID(inlineArea);
+        if (id != null) {
+            int extraMarginBefore = 5000; // millipoints
+            int ipp = currentIPPosition;
+            int bpp = currentBPPosition + inlineArea.getOffset() - extraMarginBefore;
+            saveAbsolutePosition(id, ipp, bpp);
+        }
+    }
+
+    private String getTargetableID(Area area) {
+        String id = (String) area.getTrait(Trait.PROD_ID);
+        if (id == null || id.length() == 0
+            || !currentPageViewport.isFirstWithID(id)
+            || idPositions.containsKey(id)) {
+            return null;
+        } else {
+            return id;
+        }
+    }
+
     /** {@inheritDoc} */
     public void startPageSequence(PageSequence pageSequence) {
         try {
@@ -235,7 +437,6 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
                 painter.endDocumentHeader();
                 this.inPageSequence = true;
             }
-            //TODO Put the page-sequence's ID in the area tree
             painter.startPageSequence(null);
         } catch (IFException e) {
             handleIFException(e);
@@ -572,16 +773,18 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
         restoreGraphicsState();
     }
 
-    /*
-    protected void renderReferenceArea(Block block) {
-        // TODO Auto-generated method stub
-    }*/
+    /** {@inheritDoc} */
+    protected void renderInlineArea(InlineArea inlineArea) {
+        saveInlinePosIfTargetable(inlineArea);
+        super.renderInlineArea(inlineArea);
+    }
 
     /** {@inheritDoc} */
     protected void renderBlock(Block block) {
         if (log.isTraceEnabled()) {
             log.trace("renderBlock() " + block);
         }
+        saveBlockPosIfTargetable(block);
         super.renderBlock(block);
     }
 
diff --git a/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java b/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java
new file mode 100644 (file)
index 0000000..37a3032
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * 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.apache.xmlgraphics.util.XMLizable;
+
+/**
+ * Abstract base class for document actions, like "go-to" actions with absolute page coordinates.
+ */
+public abstract class AbstractAction implements XMLizable {
+
+}
diff --git a/src/java/org/apache/fop/render/intermediate/extensions/Bookmark.java b/src/java/org/apache/fop/render/intermediate/extensions/Bookmark.java
new file mode 100644 (file)
index 0000000..a96bbd0
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * 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.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+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.util.XMLUtil;
+
+/**
+ * This class is a bookmark element for use in the intermediate format.
+ */
+public class Bookmark implements XMLizable, BookmarkExtensionConstants {
+
+    private String title;
+    private boolean show;
+    private List childBookmarks;
+    private AbstractAction action;
+
+    /**
+     * Creates a new bookmark.
+     * @param title the bookmark's title
+     * @param show true if the bookmark shall be shown, false for hidden
+     * @param action the action performed when the bookmark is clicked
+     */
+    public Bookmark(String title, boolean show, AbstractAction action) {
+        this.title = title;
+        this.show = show;
+        this.action = action;
+    }
+
+    /**
+     * Returns the bookmark's title.
+     * @return the title
+     */
+    public String getTitle() {
+        return this.title;
+    }
+
+    /**
+     * Indicates whether the bookmark shall be shown initially.
+     * @return true if it shall be shown
+     */
+    public boolean isShown() {
+        return this.show;
+    }
+
+    /**
+     * Returns the action performed when the bookmark is clicked.
+     * @return the action
+     */
+    public AbstractAction getAction() {
+        return this.action;
+    }
+
+    /**
+     * Sets the action performed when the bookmark is clicked.
+     * @param action the action
+     */
+    public void setAction(AbstractAction action) {
+        this.action = action;
+    }
+
+    /**
+     * Adds a child bookmark.
+     * @param bookmark the child bookmark
+     */
+    public void addChildBookmark(Bookmark bookmark) {
+        if (this.childBookmarks == null) {
+            this.childBookmarks = new java.util.ArrayList();
+        }
+        this.childBookmarks.add(bookmark);
+    }
+
+    /**
+     * Returns a list of child bookmarks.
+     * @return the child bookmarks
+     */
+    public List getChildBookmarks() {
+        if (this.childBookmarks == null) {
+            return Collections.EMPTY_LIST;
+        } else {
+            return Collections.unmodifiableList(this.childBookmarks);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void toSAX(ContentHandler handler) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute(null, "title", "title", XMLUtil.CDATA, getTitle());
+        atts.addAttribute(null, "starting-state", "starting-state",
+                XMLUtil.CDATA, isShown() ? "show" : "hide");
+        handler.startElement(BOOKMARK.getNamespaceURI(),
+                BOOKMARK.getLocalName(), BOOKMARK.getQName(), atts);
+        if (getAction() != null) {
+            getAction().toSAX(handler);
+        }
+        if (this.childBookmarks != null) {
+            Iterator iter = this.childBookmarks.iterator();
+            while (iter.hasNext()) {
+                Bookmark b = (Bookmark)iter.next();
+                b.toSAX(handler);
+            }
+        }
+        handler.endElement(BOOKMARK.getNamespaceURI(),
+                BOOKMARK.getLocalName(), BOOKMARK.getQName());
+    }
+
+}
diff --git a/src/java/org/apache/fop/render/intermediate/extensions/BookmarkExtensionConstants.java b/src/java/org/apache/fop/render/intermediate/extensions/BookmarkExtensionConstants.java
new file mode 100644 (file)
index 0000000..184c71d
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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.apache.xmlgraphics.util.QName;
+
+import org.apache.fop.render.intermediate.IFConstants;
+
+/**
+ * Constants for the IF bookmark extension.
+ */
+public interface BookmarkExtensionConstants {
+
+    /** Namespace URI for the bookmark extension */
+    String NAMESPACE = IFConstants.NAMESPACE + "/bookmarks";
+
+    /** the bookmark-tree element */
+    QName BOOKMARK_TREE = new QName(NAMESPACE, "bookmark-tree");
+    /** the bookmark element */
+    QName BOOKMARK = new QName(NAMESPACE, "bookmark");
+    /** the goto-xy element */
+    QName GOTO_XY = new QName(NAMESPACE, "goto-xy");
+    /** the goto-uri element */
+    QName GOTO_URI = new QName(NAMESPACE, "goto-uri");
+
+}
diff --git a/src/java/org/apache/fop/render/intermediate/extensions/BookmarkExtensionHandlerFactory.java b/src/java/org/apache/fop/render/intermediate/extensions/BookmarkExtensionHandlerFactory.java
new file mode 100644 (file)
index 0000000..ac3f956
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * 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.Point;
+import java.util.Stack;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.render.ps.extensions.PSExtensionAttachment;
+import org.apache.fop.util.ContentHandlerFactory;
+import org.apache.fop.util.XMLUtil;
+
+/**
+ * Factory for the ContentHandler that handles the IF bookmarks namespace.
+ */
+public class BookmarkExtensionHandlerFactory
+        implements ContentHandlerFactory, BookmarkExtensionConstants {
+
+    /** Logger instance */
+    protected static Log log = LogFactory.getLog(BookmarkExtensionHandlerFactory.class);
+
+    /** {@inheritDoc} */
+    public String[] getSupportedNamespaces() {
+        return new String[] {NAMESPACE};
+    }
+
+    /** {@inheritDoc} */
+    public ContentHandler createContentHandler() {
+        return new BookmarkExtensionHandler();
+    }
+
+    private static class BookmarkExtensionHandler extends DefaultHandler
+                implements ContentHandlerFactory.ObjectSource {
+
+        private StringBuffer content = new StringBuffer();
+        //private Attributes lastAttributes;
+        private Stack objectStack = new Stack();
+        private BookmarkTree bookmarkTree;
+
+        private ObjectBuiltListener listener;
+
+        /** {@inheritDoc} */
+        public void startElement(String uri, String localName, String qName, Attributes attributes)
+                throws SAXException {
+            boolean handled = false;
+            if (NAMESPACE.equals(uri)) {
+                if (BOOKMARK_TREE.getLocalName().equals(localName)) {
+                    if (bookmarkTree != null) {
+                        throw new SAXException(localName + " must be the root element!");
+                    }
+                    bookmarkTree = new BookmarkTree();
+                    objectStack.push(bookmarkTree);
+                } else if (BOOKMARK.getLocalName().equals(localName)) {
+                    String title = attributes.getValue("title");
+                    String s = attributes.getValue("starting-state");
+                    boolean show = !"hide".equals(s);
+                    Bookmark b = new Bookmark(title, show, null);
+                    Object o = objectStack.peek();
+                    if (o instanceof AbstractAction) {
+                        AbstractAction action = (AbstractAction)objectStack.pop();
+                        o = objectStack.peek();
+                        ((Bookmark)o).setAction(action);
+                    }
+                    if (o instanceof BookmarkTree) {
+                        ((BookmarkTree)o).addBookmark(b);
+                    } else {
+                        ((Bookmark)o).addChildBookmark(b);
+                    }
+                    objectStack.push(b);
+                } else if (GOTO_XY.getLocalName().equals(localName)) {
+                    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));
+                    objectStack.push(action);
+                } else if (GOTO_URI.getLocalName().equals(localName)) {
+                    String gotoURI = attributes.getValue("uri");
+                    URIAction action = new URIAction(gotoURI);
+                    objectStack.push(action);
+                } else {
+                    throw new SAXException(
+                            "Invalid element " + localName + " in namespace: " + uri);
+                }
+                handled = true;
+            }
+            if (!handled) {
+                if (PSExtensionAttachment.CATEGORY.equals(uri)) {
+                    throw new SAXException("Unhandled element " + localName + " in namespace: "
+                            + uri);
+                } else {
+                    log.warn("Unhandled element " + localName + " in namespace: " + uri);
+                }
+            }
+        }
+
+        /** {@inheritDoc} */
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+            if (NAMESPACE.equals(uri)) {
+                if (BOOKMARK_TREE.getLocalName().equals(localName)) {
+                    //nop
+                } else if (BOOKMARK.getLocalName().equals(localName)) {
+                    if (objectStack.peek() instanceof AbstractAction) {
+                        AbstractAction action = (AbstractAction)objectStack.pop();
+                        Bookmark b = (Bookmark)objectStack.pop();
+                        b.setAction(action);
+                    } else {
+                        objectStack.pop();
+                    }
+                }
+            }
+            content.setLength(0); // Reset text buffer (see characters())
+        }
+
+        /** {@inheritDoc} */
+        public void characters(char[] ch, int start, int length) throws SAXException {
+            content.append(ch, start, length);
+        }
+
+        /** {@inheritDoc} */
+        public void endDocument() throws SAXException {
+            if (listener != null) {
+                listener.notifyObjectBuilt(getObject());
+            }
+        }
+
+        /** {@inheritDoc} */
+        public Object getObject() {
+            return bookmarkTree;
+        }
+
+        /** {@inheritDoc} */
+        public void setObjectBuiltListener(ObjectBuiltListener listener) {
+            this.listener = listener;
+        }
+    }
+
+}
diff --git a/src/java/org/apache/fop/render/intermediate/extensions/BookmarkTree.java b/src/java/org/apache/fop/render/intermediate/extensions/BookmarkTree.java
new file mode 100644 (file)
index 0000000..8ecd871
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * 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.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.xmlgraphics.util.XMLizable;
+
+/**
+ * This class is the root of the bookmark tree for use in the intermediate format.
+ */
+public class BookmarkTree implements XMLizable, BookmarkExtensionConstants {
+
+    private List bookmarks = new java.util.ArrayList();
+
+    /**
+     * Constructs a new bookmark tree.
+     */
+    public BookmarkTree() {
+        //nop
+    }
+
+    /**
+     * Adds a new top-level bookmark.
+     * @param bookmark the bookmark
+     */
+    public void addBookmark(Bookmark bookmark) {
+        this.bookmarks.add(bookmark);
+    }
+
+    /**
+     * Returns a list of top-level bookmarks.
+     * @return the top-level bookmarks
+     */
+    public List getBookmarks() {
+        return Collections.unmodifiableList(this.bookmarks);
+    }
+
+    /** {@inheritDoc} */
+    public void toSAX(ContentHandler handler) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        handler.startElement(BOOKMARK_TREE.getNamespaceURI(),
+                BOOKMARK_TREE.getLocalName(), BOOKMARK_TREE.getQName(), atts);
+        Iterator iter = this.bookmarks.iterator();
+        while (iter.hasNext()) {
+            Bookmark b = (Bookmark)iter.next();
+            b.toSAX(handler);
+        }
+        handler.endElement(BOOKMARK_TREE.getNamespaceURI(),
+                BOOKMARK_TREE.getLocalName(), BOOKMARK_TREE.getQName());
+    }
+
+}
diff --git a/src/java/org/apache/fop/render/intermediate/extensions/GoToXYAction.java b/src/java/org/apache/fop/render/intermediate/extensions/GoToXYAction.java
new file mode 100644 (file)
index 0000000..ef6d9c2
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * 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.Point;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.fop.util.XMLUtil;
+
+/**
+ * Action class which represents a "go-to" action to an absolute coordinate on a page.
+ */
+public class GoToXYAction extends AbstractAction implements BookmarkExtensionConstants {
+
+    private int pageIndex;
+    private Point targetLocation;
+
+    /**
+     * Creates a new instance.
+     * @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) {
+        this.pageIndex = pageIndex;
+        this.targetLocation = targetLocation;
+    }
+
+    /**
+     * Returns the page index of the target page.
+     * @return the page index (0-based)
+     */
+    public int getPageIndex() {
+        return this.pageIndex;
+    }
+
+    /**
+     * Returns the absolute coordinates of the target location on the page.
+     * @return the target location (coordinates in millipoints)
+     */
+    public Point getTargetLocation() {
+        return this.targetLocation;
+    }
+
+    /**
+     * Sets the absolute coordinates of the target location on the page.
+     * @param location the location (coordinates in millipoints)
+     */
+    public void setTargetLocation(Point location) {
+        this.targetLocation = location;
+    }
+
+    /** {@inheritDoc} */
+    public void toSAX(ContentHandler handler) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute(null, "page-index", "page-index",
+                XMLUtil.CDATA, Integer.toString(pageIndex));
+        atts.addAttribute(null, "x", "x", XMLUtil.CDATA, Integer.toString(targetLocation.x));
+        atts.addAttribute(null, "y", "y", XMLUtil.CDATA, Integer.toString(targetLocation.y));
+        handler.startElement(GOTO_XY.getNamespaceURI(),
+                GOTO_XY.getLocalName(), GOTO_XY.getQName(), atts);
+        handler.endElement(GOTO_XY.getNamespaceURI(),
+                GOTO_XY.getLocalName(), GOTO_XY.getQName());
+    }
+
+}
diff --git a/src/java/org/apache/fop/render/intermediate/extensions/URIAction.java b/src/java/org/apache/fop/render/intermediate/extensions/URIAction.java
new file mode 100644 (file)
index 0000000..c5d56d9
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * 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;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.fop.util.XMLUtil;
+
+/**
+ * Action class which represents a "URI" action, i.e. an action that will call up an external
+ * resource identified by a URI.
+ */
+public class URIAction extends AbstractAction implements BookmarkExtensionConstants {
+
+    private String uri;
+
+    /**
+     * Creates a new instance.
+     * @param uri the target URI
+     */
+    public URIAction(String uri) {
+        this.uri = uri;
+    }
+
+    /**
+     * Returns the target URI.
+     * @return the target URI
+     */
+    public String getURI() {
+        return this.uri;
+    }
+
+    /** {@inheritDoc} */
+    public void toSAX(ContentHandler handler) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute(null, "uri", "uri", XMLUtil.CDATA, getURI());
+        handler.startElement(GOTO_URI.getNamespaceURI(),
+                GOTO_URI.getLocalName(), GOTO_URI.getQName(), atts);
+        handler.endElement(GOTO_URI.getNamespaceURI(),
+                GOTO_URI.getLocalName(), GOTO_URI.getQName());
+    }
+
+}
index 389025034c0191080edbda08b11913c1734db8d2..1456cb9fdc14b986eb705f3c5caaba2327ae4a28 100644 (file)
@@ -24,7 +24,9 @@ import java.awt.Dimension;
 import java.awt.Paint;
 import java.awt.Rectangle;
 import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
 import java.io.IOException;
+import java.util.Iterator;
 import java.util.Map;
 
 import org.w3c.dom.Document;
@@ -42,10 +44,13 @@ import org.apache.fop.fonts.FontTriplet;
 import org.apache.fop.fonts.LazyFont;
 import org.apache.fop.fonts.SingleByteFont;
 import org.apache.fop.fonts.Typeface;
+import org.apache.fop.pdf.PDFAction;
 import org.apache.fop.pdf.PDFAnnotList;
 import org.apache.fop.pdf.PDFDocument;
 import org.apache.fop.pdf.PDFNumber;
+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.pdf.PDFTextUtil;
@@ -54,6 +59,10 @@ import org.apache.fop.render.RenderingContext;
 import org.apache.fop.render.intermediate.AbstractBinaryWritingIFPainter;
 import org.apache.fop.render.intermediate.IFException;
 import org.apache.fop.render.intermediate.IFState;
+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.util.CharUtilities;
 
 /**
@@ -85,19 +94,15 @@ public class PDFPainter extends AbstractBinaryWritingIFPainter {
     /** the current annotation list to add annotations to */
     protected PDFResourceContext currentContext;
 
-    /**
-     * Map of pages using the PageViewport as the key
-     * this is used for prepared pages that cannot be immediately
-     * rendered
-     */
-    protected Map pages;
-
     /** 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;
 
+    /** Used for bookmarks/outlines. */
+    private Map pageReferences = new java.util.HashMap();
+
     /**
      * Default constructor.
      */
@@ -152,23 +157,15 @@ public class PDFPainter extends AbstractBinaryWritingIFPainter {
     /** {@inheritDoc} */
     public void endDocument() throws IFException {
         try {
-            //finishOpenGoTos();
-
             pdfDoc.getResources().addFonts(pdfDoc, fontInfo);
             pdfDoc.outputTrailer(this.outputStream);
 
             this.pdfDoc = null;
 
-            this.pages = null;
-
-            //pageReferences.clear();
             pdfResources = null;
             this.generator = null;
             currentContext = null;
             currentPage = null;
-
-            //idPositions.clear();
-            //idGoTos.clear();
         } catch (IOException ioe) {
             throw new IFException("I/O error in endDocument()", ioe);
         }
@@ -200,6 +197,7 @@ public class PDFPainter extends AbstractBinaryWritingIFPainter {
         pdfUtil.generatePageLabel(index, name);
 
         currentPageRef = currentPage.referencePDF();
+        this.pageReferences.put(new Integer(index), new PageReference(currentPage, size));
 
         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
@@ -488,6 +486,45 @@ public class PDFPainter extends AbstractBinaryWritingIFPainter {
         }
     }
 
+    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 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) {
@@ -495,9 +532,22 @@ public class PDFPainter extends AbstractBinaryWritingIFPainter {
         } else if (extension instanceof Metadata) {
             XMPMetadata wrapper = new XMPMetadata(((Metadata)extension));
             pdfUtil.renderXMPMetadata(wrapper);
+        } else if (extension instanceof BookmarkTree) {
+            renderBookmarkTree((BookmarkTree)extension);
         } else {
-            throw new UnsupportedOperationException(
-                    "Don't know how to handle extension object: " + extension);
+            log.warn("Don't know how to handle extension object: "
+                    + extension + " (" + extension.getClass().getName());
+        }
+    }
+
+    private static final class PageReference {
+
+        private PDFReference pageRef;
+        private Dimension pageDimension;
+
+        private PageReference(PDFPage page, Dimension dim) {
+            this.pageRef = page.makeReference();
+            this.pageDimension = new Dimension(dim);
         }
     }
 
diff --git a/test/java/org/apache/fop/intermediate/IFCheck.java b/test/java/org/apache/fop/intermediate/IFCheck.java
new file mode 100644 (file)
index 0000000..dc54048
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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.intermediate;
+
+import org.w3c.dom.Document;
+
+/**
+ * Check interface for intermediate format checks.
+ */
+public interface IFCheck {
+
+    /**
+     * Called to perform the check.
+     * @param intermediate the intermediate format file as a DOM document
+     */
+    void check(Document intermediate);
+
+}
diff --git a/test/java/org/apache/fop/intermediate/IFTester.java b/test/java/org/apache/fop/intermediate/IFTester.java
new file mode 100644 (file)
index 0000000..ff5015a
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * 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.intermediate;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.FopFactory;
+import org.apache.fop.area.AreaTreeModel;
+import org.apache.fop.area.AreaTreeParser;
+import org.apache.fop.area.RenderPagesModel;
+import org.apache.fop.fonts.FontInfo;
+import org.apache.fop.layoutengine.EvalCheck;
+import org.apache.fop.layoutengine.TrueCheck;
+import org.apache.fop.render.intermediate.IFRenderer;
+import org.apache.fop.render.intermediate.IFSerializer;
+import org.apache.fop.util.DelegatingContentHandler;
+
+/**
+ * Does tests on the intermediate format.
+ */
+public class IFTester {
+
+    private static final Map IF_CHECK_CLASSES = new java.util.HashMap();
+
+    static {
+        IF_CHECK_CLASSES.put("true", TrueCheck.class);
+        IF_CHECK_CLASSES.put("eval", EvalCheck.class);
+    }
+
+    private FopFactory fopFactory = FopFactory.newInstance();
+
+    private SAXTransformerFactory tfactory
+                = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
+
+    private File backupDir;
+
+    /**
+     * Main constructor
+     * @param backupDir an optional directory in which to write the serialized
+     *                  intermediate format file (may be null)
+     */
+    public IFTester(File backupDir) {
+        this.backupDir = backupDir;
+    }
+
+    /**
+     * Factory method to create IF checks from DOM elements.
+     * @param el DOM element to create the check from
+     * @return The newly create check
+     */
+    protected IFCheck createIFCheck(Element el) {
+        String name = el.getTagName();
+        Class clazz = (Class)IF_CHECK_CLASSES.get(name);
+        if (clazz != null) {
+            try {
+                Constructor c = clazz.getDeclaredConstructor(new Class[] {Node.class});
+                IFCheck instance = (IFCheck)c.newInstance(new Object[] {el});
+                return instance;
+            } catch (Exception e) {
+                throw new RuntimeException("Error while instantiating check '"
+                        + name + "': " + e.getMessage());
+            }
+        } else {
+            throw new IllegalArgumentException("No check class found: " + name);
+        }
+    }
+
+    private Document createIF(Document areaTreeXML) throws TransformerException {
+        try {
+            FOUserAgent ua = fopFactory.newFOUserAgent();
+
+            IFRenderer ifRenderer = new IFRenderer();
+            ifRenderer.setUserAgent(ua);
+
+            IFSerializer serializer = new IFSerializer();
+            DOMResult result = new DOMResult();
+            serializer.setResult(result);
+            ifRenderer.setPainter(serializer);
+
+            ua.setRendererOverride(ifRenderer);
+            FontInfo fontInfo = new FontInfo();
+            //Construct the AreaTreeModel that will received the individual pages
+            final AreaTreeModel treeModel = new RenderPagesModel(ua,
+                    null, fontInfo, null);
+
+            //Iterate over all intermediate files
+            AreaTreeParser parser = new AreaTreeParser();
+            ContentHandler handler = parser.getContentHandler(treeModel, ua);
+
+            DelegatingContentHandler proxy = new DelegatingContentHandler() {
+
+                public void endDocument() throws SAXException {
+                    super.endDocument();
+                    //Signal the end of the processing.
+                    //The renderer can finalize the target document.
+                    treeModel.endDocument();
+                }
+
+            };
+            proxy.setDelegateContentHandler(handler);
+
+            Transformer transformer = tfactory.newTransformer();
+            transformer.transform(new DOMSource(areaTreeXML), new SAXResult(proxy));
+
+            return (Document)result.getNode();
+        } catch (Exception e) {
+            throw new TransformerException(
+                    "Error while generating intermediate format file: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Runs the intermediate format checks.
+     * @param testFile the original test file
+     * @param checksRoot the root element containing the IF checks
+     * @param areaTreeXML the area tree XML
+     * @throws TransformerException if an error occurs while transforming the content
+     */
+    public void doIFChecks(File testFile, Element checksRoot, Document areaTreeXML)
+                throws TransformerException {
+        Document ifDocument = createIF(areaTreeXML);
+        if (this.backupDir != null) {
+            Transformer transformer = tfactory.newTransformer();
+            Source src = new DOMSource(ifDocument);
+            File targetFile = new File(this.backupDir, testFile.getName() + ".if.xml");
+            Result res = new StreamResult(targetFile);
+            transformer.transform(src, res);
+        }
+
+        //First create check before actually running them
+        List checks = new java.util.ArrayList();
+        NodeList nodes = checksRoot.getChildNodes();
+        for (int i = 0; i < nodes.getLength(); i++) {
+            Node node = nodes.item(i);
+            if (node instanceof Element) {
+                checks.add(createIFCheck((Element)node));
+            }
+        }
+
+        if (checks.size() == 0) {
+            throw new RuntimeException("No checks are available!");
+        }
+
+        //Run the actual tests now that we know that the checks themselves are ok
+        doIFChecks(checks, ifDocument);
+    }
+
+    private void doIFChecks(List checks, Document ifDocument) {
+        Iterator i = checks.iterator();
+        while (i.hasNext()) {
+            IFCheck check = (IFCheck)i.next();
+            check.check(ifDocument);
+        }
+    }
+
+}
index 2dd9405bffe0880fb5a167422e9c09c405e3fd06..01804d55e05a200369882185e01020e96b3ed63f 100644 (file)
@@ -21,16 +21,20 @@ package org.apache.fop.layoutengine;
 
 import javax.xml.transform.TransformerException;
 
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
 import org.apache.xml.utils.PrefixResolver;
 import org.apache.xml.utils.PrefixResolverDefault;
 import org.apache.xpath.XPathAPI;
 import org.apache.xpath.objects.XObject;
-import org.w3c.dom.Node;
+
+import org.apache.fop.intermediate.IFCheck;
 
 /**
  * Simple check that requires an XPath expression to evaluate to true.
  */
-public class EvalCheck implements LayoutEngineCheck {
+public class EvalCheck implements LayoutEngineCheck, IFCheck {
 
     private String expected;
     private String xpath;
@@ -58,14 +62,23 @@ public class EvalCheck implements LayoutEngineCheck {
         if (nd != null) {
             this.tolerance = Double.parseDouble(nd.getNodeValue());
         }
-        this.prefixResolver = new PrefixResolverDefault(node);
+        this.prefixResolver = new MyPrefixResolver(new PrefixResolverDefault(node));
     }
 
-    /** @see org.apache.fop.layoutengine.LayoutEngineCheck */
+    /** {@inheritDoc} */
     public void check(LayoutResult result) {
+        doCheck(result.getAreaTree());
+    }
+
+    /** {@inheritDoc} */
+    public void check(Document intermediate) {
+        doCheck(intermediate);
+    }
+
+    private void doCheck(Document doc) {
         XObject res;
         try {
-            res = XPathAPI.eval(result.getAreaTree(), xpath, prefixResolver);
+            res = XPathAPI.eval(doc, xpath, prefixResolver);
         } catch (TransformerException e) {
             throw new RuntimeException("XPath evaluation failed: " + e.getMessage());
         }
@@ -87,9 +100,45 @@ public class EvalCheck implements LayoutEngineCheck {
         }
     }
 
-    /** @see java.lang.Object#toString() */
+    /** {@inheritDoc} */
     public String toString() {
         return "XPath: " + xpath;
     }
 
+    private static class MyPrefixResolver implements PrefixResolver {
+
+        private PrefixResolver delegate;
+
+        public MyPrefixResolver(PrefixResolver delegate) {
+            this.delegate = delegate;
+        }
+
+        /** {@inheritDoc} */
+        public String getBaseIdentifier() {
+            String s = delegate.getBaseIdentifier();
+            System.out.println(s);
+            return s;
+        }
+
+        /** {@inheritDoc} */
+        public String getNamespaceForPrefix(String prefix) {
+            String s = delegate.getNamespaceForPrefix(prefix);
+            System.out.println(s);
+            return s;
+        }
+
+        /** {@inheritDoc} */
+        public String getNamespaceForPrefix(String prefix, Node context) {
+            String s = delegate.getNamespaceForPrefix(prefix, context);
+            System.out.println(s);
+            return s;
+        }
+
+        /** {@inheritDoc} */
+        public boolean handlesNullPrefixes() {
+            return delegate.handlesNullPrefixes();
+        }
+
+    }
+
 }
index 013a622a460306523e353f5c4606a4d3fd5a9e89..27209e37ffc95ac578570dee8903b9b46d503fca 100644 (file)
@@ -57,6 +57,7 @@ import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.apps.Fop;
 import org.apache.fop.apps.FopFactory;
 import org.apache.fop.apps.FormattingResults;
+import org.apache.fop.intermediate.IFTester;
 import org.apache.fop.layoutmgr.ElementListObserver;
 import org.apache.fop.render.xml.XMLRenderer;
 import org.apache.fop.util.ConsoleEventListenerForTests;
@@ -67,7 +68,7 @@ import org.apache.fop.util.ConsoleEventListenerForTests;
  */
 public class LayoutEngineTester {
 
-    private static final Map CHECK_CLASSES = new java.util.HashMap();
+    private static final Map AT_CHECK_CLASSES = new java.util.HashMap();
 
     // configure fopFactory as desired
     private FopFactory fopFactory = FopFactory.newInstance();
@@ -80,12 +81,13 @@ public class LayoutEngineTester {
     private Templates testcase2checks;
 
     private File areaTreeBackupDir;
+    private IFTester ifTester;
 
     static {
-        CHECK_CLASSES.put("true", TrueCheck.class);
-        CHECK_CLASSES.put("eval", EvalCheck.class);
-        CHECK_CLASSES.put("element-list", ElementListCheck.class);
-        CHECK_CLASSES.put("result", ResultCheck.class);
+        AT_CHECK_CLASSES.put("true", TrueCheck.class);
+        AT_CHECK_CLASSES.put("eval", EvalCheck.class);
+        AT_CHECK_CLASSES.put("element-list", ElementListCheck.class);
+        AT_CHECK_CLASSES.put("result", ResultCheck.class);
     }
 
     /**
@@ -97,6 +99,7 @@ public class LayoutEngineTester {
         this.areaTreeBackupDir = areaTreeBackupDir;
         fopFactory.getFontManager().setBase14KerningEnabled(false);
         fopFactoryWithBase14Kerning.getFontManager().setBase14KerningEnabled(true);
+        this.ifTester = new IFTester(areaTreeBackupDir);
     }
 
     private Templates getTestcase2FOStylesheet() throws TransformerConfigurationException {
@@ -183,13 +186,13 @@ public class LayoutEngineTester {
     }
 
     /**
-     * Factory method to create checks from DOM elements.
+     * Factory method to create AT checks from DOM elements.
      * @param el DOM element to create the check from
      * @return The newly create check
      */
-    protected LayoutEngineCheck createCheck(Element el) {
+    protected LayoutEngineCheck createATCheck(Element el) {
         String name = el.getTagName();
-        Class clazz = (Class)CHECK_CLASSES.get(name);
+        Class clazz = (Class)AT_CHECK_CLASSES.get(name);
         if (clazz != null) {
             try {
                 Constructor c = clazz.getDeclaredConstructor(new Class[] {Node.class});
@@ -204,8 +207,9 @@ public class LayoutEngineTester {
         }
     }
 
+
     /**
-     * Perform all checks on the area tree.
+     * Perform all checks on the area tree and, optionally, on the intermediate format.
      * @param testFile Test case XML file
      * @param result The layout results
      * @throws TransformerException if a problem occurs in XSLT/JAXP
@@ -216,19 +220,41 @@ public class LayoutEngineTester {
         DOMResult res = new DOMResult();
         transformer.transform(src, res);
 
-        List checks = new java.util.ArrayList();
         Document doc = (Document)res.getNode();
-        NodeList nodes = doc.getDocumentElement().getChildNodes();
+        Element root = doc.getDocumentElement();
+
+        Element atChecks = (Element)root.getElementsByTagName("at-checks").item(0);
+        doATChecks(atChecks, result);
+
+        //IF tests only when checks are available
+        NodeList nodes;
+        nodes = root.getElementsByTagName("if-checks");
+        if (nodes.getLength() > 0) {
+            Element ifChecks = (Element)nodes.item(0);
+            ifTester.doIFChecks(testFile, ifChecks, result.getAreaTree());
+        }
+    }
+
+    private void doATChecks(Element checksRoot, LayoutResult result) {
+        //First create check before actually running them
+        List checks = new java.util.ArrayList();
+        NodeList nodes = checksRoot.getChildNodes();
         for (int i = 0; i < nodes.getLength(); i++) {
             Node node = nodes.item(i);
             if (node instanceof Element) {
-                checks.add(createCheck((Element)node));
+                checks.add(createATCheck((Element)node));
             }
         }
 
         if (checks.size() == 0) {
             throw new RuntimeException("No checks are available!");
         }
+
+        //Run the actual tests now that we know that the checks themselves are ok
+        doATChecks(checks, result);
+    }
+
+    private void doATChecks(List checks, LayoutResult result) {
         Iterator i = checks.iterator();
         while (i.hasNext()) {
             LayoutEngineCheck check = (LayoutEngineCheck)i.next();
index 202398d2f844636352121ba26017bc3ed7fced6d..94ae942dea6c6c7fa811a71421654da2ec21d80a 100644 (file)
@@ -21,17 +21,21 @@ package org.apache.fop.layoutengine;
 
 import javax.xml.transform.TransformerException;
 
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
 import org.apache.xml.utils.PrefixResolver;
 import org.apache.xml.utils.PrefixResolverDefault;
 import org.apache.xpath.XPathAPI;
 import org.apache.xpath.objects.XBoolean;
 import org.apache.xpath.objects.XObject;
-import org.w3c.dom.Node;
+
+import org.apache.fop.intermediate.IFCheck;
 
 /**
  * Simple check that requires an XPath expression to evaluate to true.
  */
-public class TrueCheck implements LayoutEngineCheck {
+public class TrueCheck implements LayoutEngineCheck, IFCheck {
 
     private String xpath;
     private String failureMessage;
@@ -58,11 +62,20 @@ public class TrueCheck implements LayoutEngineCheck {
         this.prefixResolver = new PrefixResolverDefault(node);
     }
 
-    /** @see org.apache.fop.layoutengine.LayoutEngineCheck */
+    /** {@inheritDoc} */
     public void check(LayoutResult result) {
+        doCheck(result.getAreaTree());
+    }
+
+    /** {@inheritDoc} */
+    public void check(Document intermediate) {
+        doCheck(intermediate);
+    }
+
+    private void doCheck(Document doc) {
         XObject res;
         try {
-            res = XPathAPI.eval(result.getAreaTree(), xpath, prefixResolver);
+            res = XPathAPI.eval(doc, xpath, prefixResolver);
         } catch (TransformerException e) {
             throw new RuntimeException("XPath evaluation failed: " + e.getMessage());
         }
@@ -78,7 +91,7 @@ public class TrueCheck implements LayoutEngineCheck {
 
     }
 
-    /** @see java.lang.Object#toString() */
+    /** {@inheritDoc} */
     public String toString() {
         return "XPath: " + xpath;
     }
index a55215bb2c1020ef682b97e63acd1a85a9f4f44d..36214e204a21d601dcc9eeb15a5b4988cd4cd844 100644 (file)
@@ -43,6 +43,9 @@
             <fo:bookmark-title>Section 2</fo:bookmark-title>
           </fo:bookmark>
         </fo:bookmark>
+        <fo:bookmark internal-destination="bc">
+          <fo:bookmark-title>Fixed Block Container</fo:bookmark-title>
+        </fo:bookmark>
       </fo:bookmark-tree>
       <fo:page-sequence id="page-sequence" master-reference="normal" white-space-collapse="true">
         <fo:flow flow-name="xsl-region-body">
           <fo:block>Blah blah bla.</fo:block>
           <fo:block id="chapter2-sec2" font-weight="bold">Section 2</fo:block>
           <fo:block>Blah blah bla.</fo:block>
+          <fo:block-container absolute-position="fixed" left="3in" top="3in" width="1.5in" height="1in">
+            <fo:block id="bc">
+              Text in a block-container.
+            </fo:block>
+          </fo:block-container>"
         </fo:flow>
       </fo:page-sequence>
     </fo:root>
     <eval expected="(P2,chapter2)" xpath="//bookmarkTree/bookmark[2]/@internal-link"/>
     <eval expected="(P2,chapter2-sec1)" xpath="//bookmarkTree/bookmark[2]/bookmark[1]/@internal-link"/>
     <eval expected="(P2,chapter2-sec2)" xpath="//bookmarkTree/bookmark[2]/bookmark[2]/@internal-link"/>
-    
   </checks>
+  <if-checks xmlns:bm="http://xmlgraphics.apache.org/fop/intermediate/bookmarks">
+    <eval expected="show" xpath="//bm:bookmark-tree/bm:bookmark[1]/@starting-state"/>
+    <eval expected="Chapter 1" xpath="//bm:bookmark-tree/bm:bookmark[1]/@title"/>
+    <eval expected="0" xpath="//bm:bookmark-tree/bm:bookmark[1]/bm:goto-xy/@page-index"/>
+    <eval expected="20000" xpath="//bm:bookmark-tree/bm:bookmark[1]/bm:goto-xy/@x"/>
+    <eval expected="20000" xpath="//bm:bookmark-tree/bm:bookmark[1]/bm:goto-xy/@y"/>
+
+    <eval expected="hide" xpath="//bm:bookmark-tree/bm:bookmark[2]/@starting-state"/>
+    <eval expected="Chapter 2" xpath="//bm:bookmark-tree/bm:bookmark[2]/@title"/>
+    <eval expected="1" xpath="//bm:bookmark-tree/bm:bookmark[2]/bm:goto-xy/@page-index"/>
+    <eval expected="20000" xpath="//bm:bookmark-tree/bm:bookmark[2]/bm:goto-xy/@x"/>
+    <eval expected="20000" xpath="//bm:bookmark-tree/bm:bookmark[2]/bm:goto-xy/@y"/>
+
+    <eval expected="show" xpath="//bm:bookmark-tree/bm:bookmark[2]/bm:bookmark[1]/@starting-state"/>
+    <eval expected="Section 1" xpath="//bm:bookmark-tree/bm:bookmark[2]/bm:bookmark[1]/@title"/>
+    <eval expected="1" xpath="//bm:bookmark-tree/bm:bookmark[2]/bm:bookmark[1]/bm:goto-xy/@page-index"/>
+    <eval expected="20000" xpath="//bm:bookmark-tree/bm:bookmark[2]/bm:bookmark[1]/bm:goto-xy/@x"/>
+    <eval expected="51680" xpath="//bm:bookmark-tree/bm:bookmark[2]/bm:bookmark[1]/bm:goto-xy/@y"/>
+    
+    <eval expected="show" xpath="//bm:bookmark-tree/bm:bookmark[3]/@starting-state"/>
+    <eval expected="Fixed Block Container" xpath="//bm:bookmark-tree/bm:bookmark[3]/@title"/>
+    <eval expected="1" xpath="//bm:bookmark-tree/bm:bookmark[3]/bm:goto-xy/@page-index"/>
+    <eval expected="216000" xpath="//bm:bookmark-tree/bm:bookmark[3]/bm:goto-xy/@x"/>
+    <eval expected="216000" xpath="//bm:bookmark-tree/bm:bookmark[3]/bm:goto-xy/@y"/>
+  </if-checks>
 </testcase>
index cb31d583a9e85a435b0dfad51983b3b40c563bcc..ae3a8fe72d19c0f327b62a78fb5c7f7f6217759e 100644 (file)
 <xsl:variable name="basic-checks" select="document('basic-checks.xml')/checks/*" />
 
 <xsl:template match="testcase">
-  <xsl:apply-templates select="checks" />
+  <checks>
+    <xsl:apply-templates select="checks"/>
+    <xsl:apply-templates select="if-checks"/>
+  </checks>
 </xsl:template>
 
 <xsl:template match="checks">
-  <checks>
+  <at-checks>
     <xsl:copy-of select="$basic-checks" />
     <xsl:copy-of select="*" />
-  </checks>
+  </at-checks>
 </xsl:template>
 
+<xsl:template match="if-checks">
+  <if-checks>
+    <xsl:copy-of select="*"/>
+  </if-checks>
+</xsl:template>
+  
 <xsl:template match="text()" />
 
 </xsl:stylesheet>