Create a bookmark tree for PDF document

Recently I updated a PDF viewer application, add a bookmark tree to the viewer. Here is my solution:

  • Use ITextPDF library to get the bookmark.
  • BookmarkNode.java: represents a bookmark node in the tree.
  • BookmarkTreeModel.java: tree model.
  • BookmarkTreeBuilder.java: build a tree of BookmarkNodes recursively, based on the bookmark retrieved from ITextPDF.
  • BookmarkTreeNodeRenderer.java: renderer for a bookmark node.
  • BookmarkTreeSelectionListener.java: selection listener for the tree. When a node is clicked, the program goes to the appropriate page.

Code of the BookmarkNode.java class:

import java.util.ArrayList;
import java.util.List;

/**
 * Represents a node in bookmark tree
 * @author Ha Minh Nam
 */
public class BookmarkNode {
    private String title;
    private int pageNumber;
    private List<BookmarkNode> childNodes;

    public BookmarkNode(String title) {
        this.title = title;
        this.childNodes = new ArrayList();
    }

    public String toString() {
        return this.title;
    }

    public void addChild(BookmarkNode child) {
        childNodes.add(child);
    }

    public void removeChild(BookmarkNode child) {
        childNodes.remove(child);
    }

    public int getChildCount() {
           return childNodes.size();
    }

    public BookmarkNode getChildAt(int index) {
        return childNodes.get(index);
    }

    public int getIndexOfChild(BookmarkNode child) {
        return childNodes.indexOf(child);
    }

    /**
     * @return the pageNumber
     */
    public int getPageNumber() {
        return pageNumber;
    }

    /**
     * @param pageNumber the pageNumber to set
     */
    public void setPageNumber(int pageNumber) {
        this.pageNumber = pageNumber;
    }
}

  Code of the BookmarkTreeModel.java class:

import java.util.ArrayList;
import java.util.List;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

/**
 * Represents a tree model for bookmark view
 * @author Ha Minh Nam
 */
public class BookmarkTreeModel implements TreeModel {

    private BookmarkNode rootNode;
    private List<TreeModelListener> listeners = new ArrayList<TreeModelListener>();

    public BookmarkTreeModel(BookmarkNode rootNode) {
        this.rootNode = rootNode;
    }

    public Object getRoot() {
        return rootNode;
    }

    public Object getChild(Object parent, int index) {
        BookmarkNode parentNode = (BookmarkNode) parent;
        return parentNode.getChildAt(index);
    }

    public int getChildCount(Object parent) {
        BookmarkNode parentNode = (BookmarkNode) parent;
        return parentNode.getChildCount();
    }

    public boolean isLeaf(Object node) {
        BookmarkNode parentNode = (BookmarkNode) node;
        return parentNode.getChildCount() == 0;
    }

    public void valueForPathChanged(TreePath path, Object newValue) {
    }

    public int getIndexOfChild(Object parent, Object child) {
        BookmarkNode parentNode = (BookmarkNode) parent;
        BookmarkNode childNode = (BookmarkNode) child;
        return parentNode.getIndexOfChild(childNode);
    }

    public void addTreeModelListener(TreeModelListener l) {
        listeners.add(l);
    }

    public void removeTreeModelListener(TreeModelListener l) {
        listeners.remove(l);
    }

}

  Code of the BookmarkTreeBuilder.java class:

import java.util.HashMap;
import java.util.List;

/**
 * Builds bookmark as a tree
 * @author Ha Minh Nam
 */
public final class BookmarkTreeBuilder {
    public static BookmarkNode buildBookmark(List<HashMap<String, Object>> bookmarks) {
        BookmarkNode rootNode = new BookmarkNode("Root");
        for (HashMap<String, Object> aBookmark : bookmarks) {
            Object title = aBookmark.get("Title");
            BookmarkNode node = new BookmarkNode(title != null ? title.toString() : "");

            Object actionObj = aBookmark.get("Action");
            String action = actionObj != null ? actionObj.toString() : "";

            if ("GoTo".equals(action)) {
                Object pageObj =  aBookmark.get("Page");
                if (pageObj != null) {
                    String page = aBookmark.get("Page").toString();
                    String pageNum = page.split(" ")[0];
                    node.setPageNumber(Integer.parseInt(pageNum));
                } else {
                    node.setPageNumber(-3);
                }
            } else {
                node.setPageNumber(-3);
            }

            rootNode.addChild(node);
            buildBookmarkRecursive(aBookmark, node);
        }
        return rootNode;
    }

    private static void buildBookmarkRecursive(HashMap<String, Object> parentBookmark,
            BookmarkNode parentNode) {
        List<HashMap<String, Object>> subBookmarks = (List<HashMap<String, Object>>) parentBookmark.get("Kids");
        if (subBookmarks != null && !subBookmarks.isEmpty()) {
            for (HashMap aSubBookmark : subBookmarks) {
                Object title = aSubBookmark.get("Title");
                BookmarkNode node = new BookmarkNode(title != null ? title.toString() : "");

                Object actionObj = aSubBookmark.get("Action");
                String action = actionObj != null ? actionObj.toString() : "";

                if ("GoTo".equals(action)) {
                    Object pageObj =  aSubBookmark.get("Page");
                    if (pageObj != null) {
                        String page = aSubBookmark.get("Page").toString();
                        String pageNum = page.split(" ")[0];
                        node.setPageNumber(Integer.parseInt(pageNum));
                    } else {
                        node.setPageNumber(-3);
                    }
                } else {
                    node.setPageNumber(-3);
                }

                parentNode.addChild(node);
                buildBookmarkRecursive(aSubBookmark, node);
            }
        }
    }
}

Code of the BookmarkTreeNodeRenderer.java class:

import java.awt.Component;
import javax.swing.ImageIcon;
import javax.swing.JTree;
import javax.swing.tree.DefaultTreeCellRenderer;

/**
 * A render is used to set icon for tree node.
 * @author Ha Minh Nam
 */
public class BookmarkTreeNodeRenderer extends DefaultTreeCellRenderer {
    private ImageIcon bookmarkLeaf
            = new ImageIcon(getClass().getResource("/bookmark/icons/bookmark_leaf.png"));
    private ImageIcon bookmarkBranch
            = new ImageIcon(getClass().getResource("/bookmark/icons/bookmark_branch.png"));
    private ImageIcon bookmarkOpen
            = new ImageIcon(getClass().getResource("/bookmark/icons/bookmark_open.png"));

    public Component getTreeCellRendererComponent(
                                                    JTree tree,
                                                    Object value,
                                                    boolean sel,
                                                    boolean expanded,
                                                    boolean leaf,
                                                    int row,
                                                    boolean hasFocus) {

        super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
        if (leaf) {
            setIcon(bookmarkLeaf);
        } else {
            if (expanded) {
                setIcon(bookmarkOpen);
            } else {
                setIcon(bookmarkBranch);
            }
        }

        return this;
    }
}

Code of the BookmarkTreeSelectionListener.java class:

import com.sun.pdfview.PDFViewer;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;

/**
 * Listens to tree selection events.
 * @author Ha Minh Nam
 */
public class BookmarkTreeSelectionListener implements TreeSelectionListener {
    private JTree tree;
    private PDFViewer viewer;

    public BookmarkTreeSelectionListener(JTree tree, PDFViewer viewer) {
        this.tree = tree;
        this.viewer = viewer;
    }

    public void valueChanged(TreeSelectionEvent e) {
        Object node = tree.getLastSelectedPathComponent();
        if (node == null) {
            return;
        }
        BookmarkNode bookmark = (BookmarkNode) node;
        System.out.println("Go to: " + bookmark.getPageNumber());
        viewer.gotoPage(bookmark.getPageNumber() - 1);
    }

}

The viewer is the PDF viewer application. It implements the gotoPage(page_number) method. The following method is to create the bookmark tree. You can place this method inside a container such as a JPanel:

    public void load(List<HashMap<String, Object>> bookmarks) {
        BookmarkTreeModel model = new BookmarkTreeModel(BookmarkTreeBuilder.buildBookmark(bookmarks));
        treeBookmark.setModel(model);
        treeBookmark.getSelectionModel().setSelectionMode(
                TreeSelectionModel.SINGLE_TREE_SELECTION);
        treeBookmark.setRowHeight(20);
        treeBookmark.setCellRenderer(new BookmarkTreeNodeRenderer());
        treeBookmark.setToggleClickCount(1);
        treeBookmark.setRootVisible(false);
        treeBookmark.addTreeSelectionListener(new BookmarkTreeSelectionListener(treeBookmark, viewer));
        for (int i = 0; i < treeBookmark.getRowCount(); i++) {
            treeBookmark.expandRow(i);
        }
    }

where treeBookmark is an instance JTree class. The following code to load the bookmark data:

        private List<HashMap<String, Object>> bookmarks;

        // load bookmarks
        try {
            PdfReader reader = new PdfReader(fileName);
            bookmarks = SimpleBookmark.getBookmark(reader);
            reader.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
Be Sociable, Share!

One thought on “Create a bookmark tree for PDF document

Leave a Reply

Your email address will not be published. Required fields are marked *

*


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <font color="" face="" size=""> <span style="">