packages feed

pdf-toolbox-document-0.1.1: lib/Pdf/Document/PageNode.hs

{-# LANGUAGE OverloadedStrings #-}

-- | Page tree node

module Pdf.Document.PageNode
(
  PageNode,
  PageTree(..),
  pageNodeNKids,
  pageNodeParent,
  pageNodeKids,
  loadPageNode,
  pageNodePageByNum,
)
where

import Pdf.Core
import Pdf.Core.Exception
import Pdf.Core.Util
import Pdf.Core.Object.Util

import Pdf.Document.Pdf
import Pdf.Document.Internal.Types
import Pdf.Document.Internal.Util

import qualified Data.Vector as Vector
import qualified Data.HashMap.Strict as HashMap
import Control.Monad
import Control.Exception hiding (throw)

-- | Total number of child leaf nodes, including deep children
pageNodeNKids :: PageNode -> IO Int
pageNodeNKids (PageNode _ _ dict) = sure $
  (HashMap.lookup "Count" dict >>= intValue)
  `notice` "Count should be an integer"

-- | Parent page node
pageNodeParent :: PageNode -> IO (Maybe PageNode)
pageNodeParent (PageNode pdf _ dict) =
  case HashMap.lookup "Parent" dict of
    Nothing -> return Nothing
    Just o@(Ref ref) -> do
      obj <- deref pdf o
      node <- sure $ dictValue obj `notice` "Parent should be a dictionary"
      ensureType "Pages" node
      return $ Just (PageNode pdf ref node)
    _ -> throwIO (Corrupted "Parent should be an indirect ref" [])

-- | Referencies to all kids
pageNodeKids :: PageNode -> IO [Ref]
pageNodeKids (PageNode pdf _ dict) = do
  obj <- sure (HashMap.lookup "Kids" dict
                `notice` "Page node should have Kids")
        >>= deref pdf
  kids <- sure $ arrayValue obj
    `notice` "Kids should be an array"
  forM (Vector.toList kids) $ \k -> sure $
    refValue k `notice` "each kid should be a reference"

-- | Load page tree node by reference
loadPageNode :: Pdf -> Ref -> IO PageTree
loadPageNode pdf ref = do
  obj <- lookupObject pdf ref >>= deref pdf
  node <- sure $ dictValue obj `notice` "page should be a dictionary"
  nodeType <- sure $ dictionaryType node
  case nodeType of
    "Pages" -> return $ PageTreeNode (PageNode pdf ref node)
    "Page" -> return $ PageTreeLeaf (Page pdf ref node)
    _ -> throwIO $ Corrupted ("Unexpected page tree node type: "
                              ++ show nodeType) []

-- | Find page by it's number
--
-- Note: it is not efficient for PDF files with a lot of pages,
-- because it performs traversal through the page tree each time.
-- Use 'pageNodeNKids', 'pageNodeKids' and 'loadPageNode' for
-- efficient traversal.
pageNodePageByNum :: PageNode -> Int -> IO Page
pageNodePageByNum node@(PageNode pdf nodeRef _) num =
  message ("page #" ++ show num ++ " for node: " ++ show nodeRef) $ do
  pageNodeKids node >>= loop num
  where
  loop _ [] = throwIO $ Corrupted "Page not found" []
  loop i (x:xs) = do
    kid <- loadPageNode pdf x
    case kid  of
      PageTreeNode n -> do
        nkids <- pageNodeNKids n
        if i < nkids
          then pageNodePageByNum n i
          else loop (i - nkids) xs
      PageTreeLeaf page ->
        if i == 0
          then return page
          else loop (i - 1) xs