packages feed

pdf-toolbox-document (empty) → 0.0.1.0

raw patch · 15 files changed

+831/−0 lines, 15 filesdep +basedep +bytestringdep +cipher-rc4setup-changed

Dependencies added: base, bytestring, cipher-rc4, containers, cryptohash, io-streams, pdf-toolbox-core, transformers

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2013, Yuras Shumovich++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Yuras Shumovich nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ lib/Pdf/Toolbox/Document.hs view
@@ -0,0 +1,43 @@++-- | Mid level utils for processing PDF file+--+-- Basic example how to get number of pages in document+--+-- @+--  withBinaryFile \"input.pdf\" ReadMode $ \handle ->+--    'runPdfWithHandle' handle 'knownFilters' $ do+--      pdf <- 'document'+--      catalog <- 'documentCatalog' pdf+--      rootNode <- 'catalogPageNode' catalog+--      cout <- 'pageNodeNKids' rootNode+--      liftIO $ print count+-- @++module Pdf.Toolbox.Document+(+  module Pdf.Toolbox.Document.Types,+  module Pdf.Toolbox.Document.Monad,+  module Pdf.Toolbox.Document.Pdf,+  module Pdf.Toolbox.Document.Document,+  module Pdf.Toolbox.Document.Catalog,+  module Pdf.Toolbox.Document.PageNode,+  module Pdf.Toolbox.Document.Page,+  module Pdf.Toolbox.Document.Info,+  module Pdf.Toolbox.Core.Error,+  module Pdf.Toolbox.Core.Object.Types,+  module Pdf.Toolbox.Core.Object.Util+)+where++import Pdf.Toolbox.Core.Error+import Pdf.Toolbox.Core.Object.Types+import Pdf.Toolbox.Core.Object.Util++import Pdf.Toolbox.Document.Types+import Pdf.Toolbox.Document.Monad+import Pdf.Toolbox.Document.Pdf+import Pdf.Toolbox.Document.Document+import Pdf.Toolbox.Document.Info+import Pdf.Toolbox.Document.Catalog+import Pdf.Toolbox.Document.PageNode+import Pdf.Toolbox.Document.Page
+ lib/Pdf/Toolbox/Document/Catalog.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Document datalog++module Pdf.Toolbox.Document.Catalog+(+  Catalog,+  catalogPageNode+)+where++import Pdf.Toolbox.Core++import Pdf.Toolbox.Document.Monad+import Pdf.Toolbox.Document.Internal.Types+import Pdf.Toolbox.Document.Internal.Util++-- | Get root node of page tree+catalogPageNode :: MonadPdf m => Catalog -> PdfE m PageNode+catalogPageNode (Catalog _ dict) = do+  ref <- lookupDict "Pages" dict >>= fromObject+  node <- lookupObject ref >>= fromObject+  ensureType "Pages" node+  return $ PageNode ref node
+ lib/Pdf/Toolbox/Document/Document.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE OverloadedStrings #-}++-- | PDF document++module Pdf.Toolbox.Document.Document+(+  Document,+  documentCatalog,+  documentEncryption,+  documentInfo+)+where++import Pdf.Toolbox.Core++import Pdf.Toolbox.Document.Monad+import Pdf.Toolbox.Document.Internal.Types+import Pdf.Toolbox.Document.Internal.Util++-- | Get the document catalog+documentCatalog :: MonadPdf m => Document -> PdfE m Catalog+documentCatalog (Document _ dict) = do+  ref <- lookupDict "Root" dict >>= fromObject+  cat <- lookupObject ref >>= fromObject+  ensureType "Catalog" cat+  return $ Catalog ref cat++-- | Document encryption dictionary+documentEncryption :: MonadPdf m => Document -> PdfE m (Maybe Dict)+documentEncryption (Document _ dict) = do+  case lookupDict' "Encrypt" dict of+    Nothing -> return Nothing+    Just o -> do+      o' <- deref o >>= fromObject+      return $ Just o'++-- | Infornation dictionary for the document+documentInfo :: MonadPdf m => Document -> PdfE m (Maybe Info)+documentInfo (Document _ dict) =+  case lookupDict' "Info" dict of+    Nothing -> return Nothing+    Just r -> do+      ref <- fromObject r+      info <- lookupObject ref >>= fromObject+      return $ Just $ Info ref info
+ lib/Pdf/Toolbox/Document/Encryption.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Basic support for encrypted PDF documents++module Pdf.Toolbox.Document.Encryption+(+  Decryptor,+  defaultUserPassord,+  mkStandardDecryptor,+  decryptObject+)+where++import Data.IORef+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BSL+import Data.ByteString.Lazy.Builder+import Control.Monad+import qualified System.IO.Streams as Streams+import qualified Crypto.Cipher.RC4 as RC4+import qualified Crypto.Hash.MD5 as MD5++import Pdf.Toolbox.Core++-- | Decrypt input stream+type Decryptor = Ref -> IS -> IO IS++-- | Decrypt object with the decryptor+decryptObject :: (IS -> IO IS) -> Object a -> IO (Object a)+decryptObject decryptor (OStr str) = OStr `liftM` decryptStr decryptor str+decryptObject decryptor (ODict dict) = ODict `liftM` decryptDict decryptor dict+decryptObject _ o = return o++decryptStr :: (IS -> IO IS) -> Str -> IO Str+decryptStr decryptor (Str str) = do+  is <- Streams.fromList [str]+  res <- decryptor is >>= Streams.toList+  return $ Str $ BS.concat res++decryptDict :: (IS -> IO IS) -> Dict -> IO Dict+decryptDict decryptor (Dict vals) = Dict `liftM` forM vals decr+  where+  decr (key, val) = do+    res <- decryptObject decryptor val+    return (key, res)++-- | The default user password+defaultUserPassord :: ByteString+defaultUserPassord = BS.pack [+  0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E,+  0x56, 0xFF, 0xFA, 0x01, 0x08, 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68,+  0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A+  ]++-- | Standard decryptor. RC4+mkStandardDecryptor :: Monad m+                    => Dict            -- ^ document trailer+                    -> Dict            -- ^ encryption dictionary+                    -> ByteString      -- ^ user password+                    -> PdfE m Decryptor+mkStandardDecryptor tr enc pass = do+  Name filterType <- lookupDict "Filter" enc >>= fromObject+  unless (filterType == "Standard") $ left $ UnexpectedError $ "Unsupported encryption handler: " ++ show filterType+  vVal <- lookupDict "V" enc >>= fromObject >>= intValue+  n <- case vVal of+         1 -> return 5+         _ -> do+           len <- lookupDict "Length" enc >>= fromObject >>= intValue+           return $ len `div` 8+  Str oVal <- lookupDict "O" enc >>= fromObject+  pVal <- (BS.pack . BSL.unpack . toLazyByteString . word32LE . fromIntegral)+    `liftM` (lookupDict "P" enc >>= fromObject >>= intValue)+  Str idVal <- do+    Array ids <- lookupDict "ID" tr >>= fromObject+    case ids of+      [] -> left $ UnexpectedError $ "ID array is empty"+      (x:_) -> fromObject x+  let ekey' = BS.take n $ MD5.hash $ BS.concat [pass', oVal, pVal, idVal]+      pass' = pass   -- XXX: padding+  rVal <- lookupDict "R" enc >>= fromObject >>= intValue+  let ekey = if rVal < 3+               then ekey'+               else foldl (\bs _ -> BS.take n $ MD5.hash bs) ekey'  [1 :: Int .. 50]+  return $ \(Ref index gen) is -> do+    let key = BS.take (16 `min` n + 5) $ MD5.hash $ BS.concat [+          ekey,+          BS.pack $ take 3 $ BSL.unpack $ toLazyByteString $ int32LE $ fromIntegral index,+          BS.pack $ take 2 $ BSL.unpack $ toLazyByteString $ int32LE $ fromIntegral gen+          ]+        ctx = RC4.initCtx key+    ioRef <- newIORef ctx+    let readNext = do+          chunk <- Streams.read is+          case chunk of+            Nothing -> return Nothing+            Just c -> do+              ctx' <- readIORef ioRef+              let (ctx'', res) = RC4.combine ctx' c+              writeIORef ioRef ctx''+              return (Just res)+    Streams.makeInputStream readNext
+ lib/Pdf/Toolbox/Document/Info.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Document info dictionary++module Pdf.Toolbox.Document.Info+(+  infoTitle+)+where++import Control.Monad++import Pdf.Toolbox.Core++import Pdf.Toolbox.Document.Monad+import Pdf.Toolbox.Document.Internal.Types++-- | Document title+infoTitle :: MonadPdf m => Info -> PdfE m (Maybe Str)+infoTitle (Info _ dict) =+  case lookupDict' "Title" dict of+    Nothing -> return Nothing+    Just o -> liftM Just $ deref o >>= fromObject
+ lib/Pdf/Toolbox/Document/Internal/Types.hs view
@@ -0,0 +1,43 @@++-- | Internal type declarations++module Pdf.Toolbox.Document.Internal.Types+(+  Document(..),+  Catalog(..),+  PageTree(..),+  PageNode(..),+  Page(..),+  Info(..)+)+where++import Pdf.Toolbox.Core++-- | PDF document+--+-- It is a trailer under the hood+data Document = Document XRef Dict+  deriving Show++-- | Document catalog+data Catalog = Catalog Ref Dict+  deriving Show++-- | Page tree+data PageTree =+  PageTreeNode PageNode |+  PageTreeLeaf Page+  deriving Show++-- | Page tree node, contains pages or other nodes+data PageNode = PageNode Ref Dict+  deriving Show++-- | Pdf document page+data Page = Page Ref Dict+  deriving Show++-- | Information dictionary+data Info = Info Ref Dict+  deriving Show
+ lib/Pdf/Toolbox/Document/Internal/Util.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Utilities for internal use++module Pdf.Toolbox.Document.Internal.Util+(+  ensureType,+  dictionaryType+)+where++import Control.Monad++import Pdf.Toolbox.Core++-- | Check that the dictionary has the specified \"Type\" filed+ensureType :: Monad m => Name -> Dict -> PdfE m ()+ensureType name dict = do+  n <- dictionaryType dict+  unless (n == name) $ left $ UnexpectedError $ "Expected type: " ++ show name ++ ", but found: " ++ show n++-- | Get dictionary type, name at key \"Type\"+dictionaryType :: Monad m => Dict -> PdfE m Name+dictionaryType dict = lookupDict "Type" dict >>= fromObject
+ lib/Pdf/Toolbox/Document/Monad.hs view
@@ -0,0 +1,30 @@++-- | Interface to the underlying PDF file++module Pdf.Toolbox.Document.Monad+(+  MonadPdf(..),+  deref+)+where++import Data.Int++import Pdf.Toolbox.Core++-- | Interface to the underlying PDF file+class Monad m => MonadPdf m where+  -- | find object by it's reference+  lookupObject :: Ref -> PdfE m (Object Int64)+  -- | decoded stream content+  --+  -- Note: the 'IS' returned is valid only until the next 'lookupObject'+  -- or any other operation, that requares seek+  streamContent :: Ref -> Stream Int64 -> PdfE m (Stream IS)++-- | Recursively load indirect object+deref :: (MonadPdf m, Show a) => Object a -> PdfE m (Object ())+deref (ORef ref) = do+  o <- lookupObject ref+  deref o+deref o = return $ mapObject (const ()) o
+ lib/Pdf/Toolbox/Document/Page.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE OverloadedStrings #-}++-- | PDF document page++module Pdf.Toolbox.Document.Page+(+  Page,+  pageParentNode,+  pageContents,+  pageMediaBox+)+where++import Control.Monad++import Pdf.Toolbox.Core++import Pdf.Toolbox.Document.Types+import Pdf.Toolbox.Document.Monad+import Pdf.Toolbox.Document.PageNode+import Pdf.Toolbox.Document.Internal.Types++-- | Page's parent node+pageParentNode :: MonadPdf m => Page -> PdfE m PageNode+pageParentNode (Page _ dict) = do+  ref <- lookupDict "Parent" dict >>= fromObject+  node <- loadPageNode ref+  case node of+    PageTreeNode n -> return n+    PageTreeLeaf _ -> left $ UnexpectedError "page parent should be a note, but leaf should"++-- | List of references to page's content streams+pageContents :: MonadPdf m => Page -> PdfE m [Ref]+pageContents page@(Page _ dict) = annotateError ("contents for page: " ++ show page) $ do+  case lookupDict' "Contents" dict of+    Nothing -> return []+    Just (ORef ref) -> do+      -- it could be reference to the only content stream,+      -- or to an array of content streams+      o <- lookupObject ref+      case o of+        OStream _ -> return [ref]+        OArray (Array objs) -> mapM fromObject objs+        _ -> left $ UnexpectedError $ "Unexpected value in page content ref: " ++ show o+    Just (OArray (Array objs)) -> mapM fromObject objs+    _ -> left $ UnexpectedError "Unexpected value in page contents"++-- | Media box, inheritable+pageMediaBox :: MonadPdf m => Page -> PdfE m (Rectangle Double)+pageMediaBox page = mediaBox (PageTreeLeaf page)++mediaBox :: MonadPdf m => PageTree -> PdfE m (Rectangle Double)+mediaBox tree = do+  let dict = case tree of+               PageTreeNode (PageNode _ d) -> d+               PageTreeLeaf (Page _ d) -> d+  case lookupDict' "MediaBox" dict of+    Just box -> fromObject box >>= rectangleFromArray+    Nothing -> do+      parent <- case tree of+                  PageTreeNode node -> do+                    parent <- pageNodeParent node+                    case parent of+                      Nothing -> left $ UnexpectedError $ "Media box not found"+                      Just p -> return $ PageTreeNode p+                  PageTreeLeaf page -> PageTreeNode `liftM` pageParentNode page+      mediaBox parent
+ lib/Pdf/Toolbox/Document/PageNode.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Page tree node++module Pdf.Toolbox.Document.PageNode+(+  PageNode,+  PageTree(..),+  pageNodeNKids,+  pageNodeParent,+  pageNodeKids,+  loadPageNode,+  pageNodePageByNum+)+where++import Pdf.Toolbox.Core++import Pdf.Toolbox.Document.Monad+import Pdf.Toolbox.Document.Internal.Types+import Pdf.Toolbox.Document.Internal.Util++-- | Total number of child leaf nodes, including deep children+pageNodeNKids :: MonadPdf m => PageNode -> PdfE m Int+pageNodeNKids (PageNode _ dict) =+  lookupDict "Count" dict >>= fromObject >>= intValue++-- | Parent page node+pageNodeParent :: MonadPdf m => PageNode -> PdfE m (Maybe PageNode)+pageNodeParent (PageNode _ dict) =+  case lookupDict' "Parent" dict of+    Nothing -> return Nothing+    Just o -> do+      ref <- fromObject o+      node <- lookupObject ref >>= fromObject+      ensureType "Pages" node+      return $ Just $ PageNode ref node++-- | Referencies to all kids+pageNodeKids :: MonadPdf m => PageNode -> PdfE m [Ref]+pageNodeKids (PageNode _ dict) = do+  Array kids <- lookupDict "Kids" dict >>= fromObject+  mapM fromObject kids++-- | Load page tree node by reference+loadPageNode :: MonadPdf m => Ref -> PdfE m PageTree+loadPageNode ref = do+  node <- lookupObject ref >>= fromObject+  nodeType <- dictionaryType node+  case nodeType of+    "Pages" -> return $ PageTreeNode $ PageNode ref node+    "Page" -> return $ PageTreeLeaf $ Page ref node+    _ -> left $ UnexpectedError $ "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 :: MonadPdf m => PageNode -> Int -> PdfE m Page+pageNodePageByNum node num = annotateError ("page #" ++ show num ++ " for node: " ++ show node) $ do+  pageNodeKids node >>= loop num+  where+  loop _ [] = left $ UnexpectedError "Page not found"+  loop i (x:xs) = do+    kid <- loadPageNode 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
+ lib/Pdf/Toolbox/Document/Pdf.hs view
@@ -0,0 +1,253 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++-- | Basic implementation of pdf monad++module Pdf.Toolbox.Document.Pdf+(+  Pdf,+  Pdf',+  runPdf,+  runPdfWithHandle,+  document,+  flushObjectCache,+  withoutObjectCache,+  getRIS,+  knownFilters,+  isEncrypted,+  setUserPassword,+  defaultUserPassord,+  decrypt,+  getDecryptor,+  MonadIO(..)+)+where++import Data.Int+import Data.ByteString (ByteString)+import Data.Map (Map)+import qualified Data.Map as Map+import Control.Monad+import Control.Monad.Trans.Class+import Control.Monad.Trans.State+import System.IO+import qualified System.IO.Streams as Streams++import Pdf.Toolbox.Core++import Pdf.Toolbox.Document.Monad+import Pdf.Toolbox.Document.Encryption+import Pdf.Toolbox.Document.Internal.Types++data PdfState = PdfState {+  stRIS :: RIS,+  stFilters :: [StreamFilter],+  stLastXRef :: Maybe XRef,+  stAddToObjectCache :: Bool,+  stObjectCache :: Map Ref (Object Int64),+  stXRefStreamCache :: Map Int64 [ByteString],+  stDecryptor :: Maybe Decryptor+  }++-- | Basic implementation of pdf monad+newtype Pdf' m a = Pdf' (StateT PdfState m a)+  deriving (Monad, Functor, MonadIO, MonadTrans)++-- | Convenient type alias+type Pdf m = PdfE (Pdf' m)++instance MonadIO m => MonadPdf (Pdf' m) where+  lookupObject ref = annotateError ("lookupObject: " ++ show ref) $ do+    cached <- getFromCache ref+    case cached of+      Just o -> return o+      Nothing -> do+        xref <- getLastXRef+        entry <- lookupEntryRec ref xref+        o <- readObjectForEntry ref entry+        addObjectToCache ref o+        return o+  streamContent ref s = do+    decryptor <- do+      dec <- getDecryptor+      case dec of+        Nothing -> return return+        Just d -> return $ d ref+    takeStreamContent decryptor s++readObjectForEntry :: MonadIO m => Ref -> XRefEntry -> Pdf m (Object Int64)+readObjectForEntry ref (XRefTableEntry entry)+  | teIsFree entry = return ONull+  | otherwise = do+    ris <- getRIS+    readObjectAtOffset ris (teOffset entry) (teGen entry) >>= decrypt ref+readObjectForEntry ref (XRefStreamEntry entry) =+  case entry of+    StreamEntryFree _ _ -> return ONull+    StreamEntryUsed off gen -> do+      ris <- getRIS+      readObjectAtOffset ris off gen >>= decrypt ref+    StreamEntryCompressed index num -> do+      objStream <- lookupObject (Ref index 0) >>= toStream+      Stream dict is <- streamContent (Ref index 0) objStream+      first <- lookupDict "First" dict >>= fromObject >>= intValue+      mapObject (error "readObjectForEntry: impossible") `liftM`+        readCompressedObject is (fromIntegral first) num++getXRefStream :: MonadIO m => Stream Int64 -> Pdf m (Stream IS)+getXRefStream s@(Stream dict off) = do+  cache <- lift $ Pdf' $ gets stXRefStreamCache+  content <-+    case Map.lookup off cache of+      Just content -> return content+      Nothing -> do+        Stream _ is <- takeStreamContent return s+        content <- liftIO $ Streams.toList is+        lift $ Pdf' $ modify $ \st -> st {stXRefStreamCache = Map.insert off content $ stXRefStreamCache st}+        return content+  is <- liftIO $ Streams.fromList content+  return $ Stream dict is++lookupEntryRec :: MonadIO m => Ref -> XRef -> Pdf m XRefEntry+lookupEntryRec ref = annotateError ("Can't find xref entry for ref: " ++ show ref) . loop+  where+  loop xref = do+    res <- lookupXRefEntry ref xref+    case res of+      Just e -> return e+      Nothing -> do+        ris <- getRIS+        prev <- prevXRef ris xref+        case prev of+          Just xref' -> loop xref'+          Nothing -> left $ UnexpectedError "There are no more xrefs"++lookupXRefEntry :: MonadIO m => Ref -> XRef -> Pdf m (Maybe XRefEntry)+lookupXRefEntry ref (XRefTable off) = do+  ris <- getRIS+  seek ris off+  _ <- inputStream ris >>= isTable+  fmap XRefTableEntry `liftM` lookupTableEntry ris ref+lookupXRefEntry ref (XRefStream _ s) = do+  decoded <- getXRefStream s+  fmap XRefStreamEntry `liftM` lookupStreamEntry decoded ref++takeStreamContent :: MonadIO m => (IS -> IO IS) -> Stream Int64 -> Pdf m (Stream IS)+takeStreamContent decryptor s@(Stream dict _) = annotateError ("reading stream content: " ++ show s) $ do+  len <- do+    obj <- lookupDict "Length" dict+    case obj of+      ONumber _ -> fromObject obj >>= intValue+      ORef ref -> lookupObject ref >>= fromObject >>= intValue+      _ -> left $ UnexpectedError $ "Unexpected length object in stream: " ++ show obj+  ris <- getRIS+  filters <- lift $ Pdf' $ gets stFilters+  decodedStreamContent ris filters decryptor len s++getLastXRef :: MonadIO m => Pdf m XRef+getLastXRef = do+  cached <- lift $ Pdf' $ gets stLastXRef+  case cached of+    Just xref -> return xref+    Nothing -> do+      xref <- getRIS >>= lastXRef+      lift $ Pdf' $ modify $ \st -> st {stLastXRef = Just xref}+      return xref++-- | Access to the underlying random access input stream.+-- Can be used when you need to switch from high level+-- to low level of details+getRIS :: Monad m => Pdf m RIS+getRIS = lift $ Pdf' $ gets stRIS++--lookupM :: MonadIO m => Ref -> Pdf m (Object ())+--lookupM r = mapObject (const ()) `liftM` lookupObject r++getFromCache :: Monad m => Ref -> Pdf m (Maybe (Object Int64))+getFromCache ref = do+  cache <- lift $ Pdf' $ gets stObjectCache+  return $ Map.lookup ref cache++addObjectToCache :: Monad m => Ref -> Object Int64 -> Pdf m ()+addObjectToCache ref o = do+  add <- lift $ Pdf' $ gets stAddToObjectCache+  when add $+    lift $ Pdf' $ modify $ \st ->+      st {stObjectCache = Map.insert ref o $ stObjectCache st}++-- | Perform action without adding objects to cache.+-- Note: the existent cache is not flushed, and is used+-- within the action+withoutObjectCache :: Monad m => Pdf m () -> Pdf m ()+withoutObjectCache action = do+  old <- lift $ Pdf' $ do+    val <- gets stAddToObjectCache+    modify $ \st -> st {stAddToObjectCache = False}+    return val+  action+  lift $ Pdf' $ modify $ \st -> st {stAddToObjectCache = old}++-- | Remove all objects from cache+flushObjectCache :: Monad m => Pdf m ()+flushObjectCache = lift $ Pdf' $ modify $ \st -> st {stObjectCache = Map.empty}++-- | Execute PDF action with 'RIS'+runPdf :: MonadIO m => RIS -> [StreamFilter] -> Pdf m a -> m (Either PdfError a)+runPdf ris filters action = runPdf' ris filters $ runEitherT action++-- | Execute PDF action with 'Handle'+runPdfWithHandle :: MonadIO m => Handle -> [StreamFilter] -> Pdf m a -> m (Either PdfError a)+runPdfWithHandle handle filters action = do+  ris <- liftIO $ fromHandle handle+  runPdf ris filters action++runPdf' :: MonadIO m => RIS -> [StreamFilter] -> Pdf' m a -> m a+runPdf' ris filters (Pdf' action) = evalStateT action $ PdfState {+  stRIS = ris,+  stFilters = filters,+  stLastXRef = Nothing,+  stObjectCache = Map.empty,+  stXRefStreamCache = Map.empty,+  stDecryptor = Nothing,+  stAddToObjectCache = True+  }++-- | Get PDF document+document :: MonadIO m => Pdf m Document+document = do+  ris <- getRIS+  xref <- lastXRef ris+  tr <- trailer ris xref+  return $ Document xref tr++-- | Whether the PDF document it encrypted+isEncrypted :: MonadIO m => Pdf m Bool+isEncrypted = annotateError "isEncrypted" $ do+  ris <- getRIS+  tr <- lastXRef ris >>= trailer ris+  case lookupDict' "Encrypt" tr of+    Nothing -> return False+    Just _ -> return True++-- | Set the password to be user for decryption+setUserPassword :: MonadIO m => ByteString -> Pdf m ()+setUserPassword pass = annotateError "setUserPassword" $ do+  ris <- getRIS+  tr <- lastXRef ris >>= trailer ris+  enc <- case lookupDict' "Encrypt" tr of+    Nothing -> left $ UnexpectedError "The document is not encrypted"+    Just enc -> deref enc >>= fromObject+  decryptor <- mkStandardDecryptor tr enc pass+  lift $ Pdf' $ modify $ \s -> s {stDecryptor = Just decryptor}++-- | Decryptor+getDecryptor :: Monad m => Pdf m (Maybe Decryptor)+getDecryptor = lift $ Pdf' $ gets stDecryptor++-- | Decrypt PDF object using user password is set+decrypt :: MonadIO m => Ref -> Object a -> Pdf m (Object a)+decrypt ref o = do+  decryptor <- getDecryptor+  case decryptor of+    Nothing -> return o+    Just decr -> liftIO $ decryptObject (decr ref) o
+ lib/Pdf/Toolbox/Document/Types.hs view
@@ -0,0 +1,25 @@++-- | Various types++module Pdf.Toolbox.Document.Types+(+  Rectangle(..),+  rectangleFromArray+)+where++import Pdf.Toolbox.Core++-- | Rectangle+data Rectangle a = Rectangle a a a a+  deriving Show++-- | Create rectangle form an array of 4 numbers+rectangleFromArray :: Monad m => Array -> PdfE m (Rectangle Double)+rectangleFromArray (Array [a', b', c', d']) = do+  a <- fromObject a' >>= realValue+  b <- fromObject b' >>= realValue+  c <- fromObject c' >>= realValue+  d <- fromObject d' >>= realValue+  return $ Rectangle a b c d+rectangleFromArray array = left $ UnexpectedError $ "rectangleFromArray: " ++ show array
+ pdf-toolbox-document.cabal view
@@ -0,0 +1,43 @@+name:                pdf-toolbox-document+version:             0.0.1.0+synopsis:            A collection of tools for processing PDF files.+license:             BSD3+license-file:        LICENSE+author:              Yuras Shumovich+maintainer:          Yuras Shumovich <shumovichy@gmail.com>+copyright:           Copyright (c) Yuras Shumovich 2012-2013+category:            PDF+build-type:          Simple+cabal-version:       >=1.8+homepage:            https://github.com/Yuras/pdf-toolbox+description:+  Mid level tools for processing PDF files.+  .+  Level of abstraction: document, catalog, page++source-repository head+  type:                git+  location:            git://github.com/Yuras/pdf-toolbox.git++library+  hs-source-dirs:      lib+  exposed-modules:     Pdf.Toolbox.Document+                       Pdf.Toolbox.Document.Types+                       Pdf.Toolbox.Document.Monad+                       Pdf.Toolbox.Document.Pdf+                       Pdf.Toolbox.Document.Document+                       Pdf.Toolbox.Document.Info+                       Pdf.Toolbox.Document.Catalog+                       Pdf.Toolbox.Document.PageNode+                       Pdf.Toolbox.Document.Page+                       Pdf.Toolbox.Document.Encryption+                       Pdf.Toolbox.Document.Internal.Types+                       Pdf.Toolbox.Document.Internal.Util+  build-depends:       base ==4.6.*,+                       transformers,+                       containers,+                       bytestring,+                       io-streams,+                       cipher-rc4,+                       cryptohash,+                       pdf-toolbox-core ==0.0.1.*