packages feed

pdf-toolbox-document-0.1.4: lib/Pdf/Document/Pdf.hs

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}

module Pdf.Document.Pdf
(
  Pdf,
  withPdfFile,
  fromFile,
  fromHandle,
  fromBytes,
  document,
  lookupObject,
  streamContent,
  rawStreamContent,
  deref,
  isEncrypted,
  setUserPassword,
  defaultUserPassword,
  EncryptedError (..),
  enableCache,
  disableCache,
)
where

import Pdf.Core.Object
import Pdf.Core.Stream (knownFilters)
import Pdf.Core.File (File)
import qualified Pdf.Core.File as File
import Pdf.Core.Encryption (defaultUserPassword)

import Pdf.Document.Internal.Types

import Data.Typeable
import Data.IORef
import Data.ByteString (ByteString)
import Data.Text (Text)
import qualified Data.HashMap.Strict as HashMap
import Control.Monad
import Control.Exception hiding (throw)
import System.IO (Handle)
import System.IO.Streams (InputStream)

withPdfFile :: FilePath -> (Pdf -> IO a) -> IO a
withPdfFile path action = File.withPdfFile path $ \f -> do
  pdf <- fromFile f
  action pdf

-- | Make Pdf with interface to pdf file
fromFile :: File -> IO Pdf
fromFile f = Pdf f
  <$> newIORef (False, HashMap.empty)

-- | Make Pdf with seekable handle
fromHandle :: Handle -> IO Pdf
fromHandle h = do
  File.fromHandle knownFilters h >>= fromFile

-- | Make Pdf from a ByteString
fromBytes :: ByteString -> IO Pdf
fromBytes h = do
  File.fromBytes knownFilters h >>= fromFile

file :: Pdf -> File
file (Pdf f _) = f

-- | Get PDF document
document :: Pdf -> IO Document
document pdf = do
  status <- File.encryptionStatus (file pdf)
  case status of
    File.Encrypted -> throwIO $
      EncryptedError "File is encrypted, use 'setUserPassword'"
    File.Decrypted -> return ()
    File.Plain -> return ()

  Document pdf <$> File.lastTrailer (file pdf)

-- | Find object by it's reference
lookupObject :: Pdf -> Ref -> IO Object
lookupObject pdf ref = do
  let Pdf _ cacheRef = pdf
  (useCache, cache) <- readIORef cacheRef
  case HashMap.lookup ref cache of
    Just obj -> return obj
    Nothing -> do
      obj <- File.findObject (file pdf) ref
      when useCache $
        writeIORef cacheRef (useCache, HashMap.insert ref obj cache)
      return obj

-- | Cache object for future lookups
enableCache :: Pdf -> IO ()
enableCache (Pdf _ cacheRef) = do
  (_, cache) <- readIORef cacheRef
  writeIORef cacheRef (True, cache)

-- | Don't cache object for future lookups
disableCache :: Pdf -> IO ()
disableCache (Pdf _ cacheRef) = do
  (_, cache) <- readIORef cacheRef
  writeIORef cacheRef (False, cache)

-- | Get stream content, decoded and decrypted
--
-- Note: length of the content may differ from the raw one
streamContent :: Pdf
              -> Ref
              -> Stream
              -> IO (InputStream ByteString)
streamContent pdf ref s =
  File.streamContent (file pdf) ref s

-- | Get stream content without decoding it
rawStreamContent
  :: Pdf
  -> Ref
  -> Stream
  -> IO (InputStream ByteString)
rawStreamContent pdf ref s =
  File.rawStreamContent (file pdf) ref s

-- | Whether the PDF document it encrypted
isEncrypted :: Pdf -> IO Bool
isEncrypted pdf = do
  status <- File.encryptionStatus (file pdf)
  return $ case status of
    File.Encrypted -> True
    File.Decrypted -> True
    File.Plain -> False

-- | Set the password to be user for decryption
--
-- Returns False when the password is wrong
setUserPassword :: Pdf -> ByteString -> IO Bool
setUserPassword pdf password =
  File.setUserPassword (file pdf) password

deref :: Pdf -> Object -> IO Object
deref pdf (Ref r) = lookupObject pdf r
deref _ o = return o

-- | File is enctypted
data EncryptedError = EncryptedError Text
  deriving (Show, Typeable)

instance Exception EncryptedError