diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/lib/Pdf/Toolbox/Document.hs b/lib/Pdf/Toolbox/Document.hs
new file mode 100644
--- /dev/null
+++ b/lib/Pdf/Toolbox/Document.hs
@@ -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
diff --git a/lib/Pdf/Toolbox/Document/Catalog.hs b/lib/Pdf/Toolbox/Document/Catalog.hs
new file mode 100644
--- /dev/null
+++ b/lib/Pdf/Toolbox/Document/Catalog.hs
@@ -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
diff --git a/lib/Pdf/Toolbox/Document/Document.hs b/lib/Pdf/Toolbox/Document/Document.hs
new file mode 100644
--- /dev/null
+++ b/lib/Pdf/Toolbox/Document/Document.hs
@@ -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
diff --git a/lib/Pdf/Toolbox/Document/Encryption.hs b/lib/Pdf/Toolbox/Document/Encryption.hs
new file mode 100644
--- /dev/null
+++ b/lib/Pdf/Toolbox/Document/Encryption.hs
@@ -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
diff --git a/lib/Pdf/Toolbox/Document/Info.hs b/lib/Pdf/Toolbox/Document/Info.hs
new file mode 100644
--- /dev/null
+++ b/lib/Pdf/Toolbox/Document/Info.hs
@@ -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
diff --git a/lib/Pdf/Toolbox/Document/Internal/Types.hs b/lib/Pdf/Toolbox/Document/Internal/Types.hs
new file mode 100644
--- /dev/null
+++ b/lib/Pdf/Toolbox/Document/Internal/Types.hs
@@ -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
diff --git a/lib/Pdf/Toolbox/Document/Internal/Util.hs b/lib/Pdf/Toolbox/Document/Internal/Util.hs
new file mode 100644
--- /dev/null
+++ b/lib/Pdf/Toolbox/Document/Internal/Util.hs
@@ -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
diff --git a/lib/Pdf/Toolbox/Document/Monad.hs b/lib/Pdf/Toolbox/Document/Monad.hs
new file mode 100644
--- /dev/null
+++ b/lib/Pdf/Toolbox/Document/Monad.hs
@@ -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
diff --git a/lib/Pdf/Toolbox/Document/Page.hs b/lib/Pdf/Toolbox/Document/Page.hs
new file mode 100644
--- /dev/null
+++ b/lib/Pdf/Toolbox/Document/Page.hs
@@ -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
diff --git a/lib/Pdf/Toolbox/Document/PageNode.hs b/lib/Pdf/Toolbox/Document/PageNode.hs
new file mode 100644
--- /dev/null
+++ b/lib/Pdf/Toolbox/Document/PageNode.hs
@@ -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
diff --git a/lib/Pdf/Toolbox/Document/Pdf.hs b/lib/Pdf/Toolbox/Document/Pdf.hs
new file mode 100644
--- /dev/null
+++ b/lib/Pdf/Toolbox/Document/Pdf.hs
@@ -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
diff --git a/lib/Pdf/Toolbox/Document/Types.hs b/lib/Pdf/Toolbox/Document/Types.hs
new file mode 100644
--- /dev/null
+++ b/lib/Pdf/Toolbox/Document/Types.hs
@@ -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
diff --git a/pdf-toolbox-document.cabal b/pdf-toolbox-document.cabal
new file mode 100644
--- /dev/null
+++ b/pdf-toolbox-document.cabal
@@ -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.*
