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 +30/−0
- Setup.hs +2/−0
- lib/Pdf/Toolbox/Document.hs +43/−0
- lib/Pdf/Toolbox/Document/Catalog.hs +24/−0
- lib/Pdf/Toolbox/Document/Document.hs +45/−0
- lib/Pdf/Toolbox/Document/Encryption.hs +102/−0
- lib/Pdf/Toolbox/Document/Info.hs +23/−0
- lib/Pdf/Toolbox/Document/Internal/Types.hs +43/−0
- lib/Pdf/Toolbox/Document/Internal/Util.hs +24/−0
- lib/Pdf/Toolbox/Document/Monad.hs +30/−0
- lib/Pdf/Toolbox/Document/Page.hs +67/−0
- lib/Pdf/Toolbox/Document/PageNode.hs +77/−0
- lib/Pdf/Toolbox/Document/Pdf.hs +253/−0
- lib/Pdf/Toolbox/Document/Types.hs +25/−0
- pdf-toolbox-document.cabal +43/−0
+ 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.*