2020-02-11 08:29:08 +01:00
|
|
|
{-# LANGUAGE NamedFieldPuns #-}
|
|
|
|
{-# LANGUAGE ConstraintKinds #-}
|
|
|
|
{-# LANGUAGE FlexibleContexts #-}
|
2020-02-15 10:22:42 +01:00
|
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
2020-02-11 08:29:08 +01:00
|
|
|
module PDF.Object.Navigation (
|
2020-02-15 10:22:42 +01:00
|
|
|
Error(..)
|
|
|
|
, (//)
|
2020-02-11 17:35:35 +01:00
|
|
|
, dictionaryById
|
2020-02-11 08:29:08 +01:00
|
|
|
, getDictionary
|
|
|
|
, getField
|
2020-02-11 17:35:35 +01:00
|
|
|
, follow
|
|
|
|
, objectById
|
2020-02-11 08:29:08 +01:00
|
|
|
, openStream
|
|
|
|
, origin
|
|
|
|
) where
|
|
|
|
|
|
|
|
import Codec.Compression.Zlib (decompress)
|
2020-02-15 10:22:42 +01:00
|
|
|
import Control.Applicative (Alternative(..))
|
|
|
|
import Control.Monad (MonadPlus(..))
|
2020-02-11 08:29:08 +01:00
|
|
|
import Control.Monad.Reader (MonadReader(..))
|
|
|
|
import Control.Monad.Fail (MonadFail(..))
|
|
|
|
import Data.ByteString (ByteString)
|
|
|
|
import qualified Data.ByteString.Lazy as Lazy (fromStrict, toStrict)
|
|
|
|
import Data.Map ((!))
|
|
|
|
import qualified Data.Map as Map (lookup)
|
|
|
|
import PDF.Object (
|
|
|
|
Content(..), Dictionary, DirectObject(..), IndirectObjCoordinates(..)
|
|
|
|
, Name(..), Object(..), Structure(..)
|
|
|
|
)
|
|
|
|
import PDF.Output (ObjectId)
|
|
|
|
import Prelude hiding (fail)
|
|
|
|
import Text.Printf (printf)
|
|
|
|
|
2020-02-15 10:22:42 +01:00
|
|
|
type PDFContent m = (Alternative m, MonadReader Content m, MonadFail m)
|
|
|
|
newtype Error a = Error {
|
|
|
|
runError :: Either String a
|
|
|
|
} deriving (Alternative, Functor, Applicative, Monad, MonadPlus)
|
|
|
|
instance MonadFail Error where
|
|
|
|
fail = Error . Left
|
2020-02-11 08:29:08 +01:00
|
|
|
|
2020-02-11 17:35:35 +01:00
|
|
|
castDictionary :: MonadFail m => Object -> m Dictionary
|
|
|
|
castDictionary (Direct (Dictionary aDict)) = return aDict
|
2020-02-15 10:23:32 +01:00
|
|
|
castDictionary (Stream {header}) = return header
|
2020-02-11 17:35:35 +01:00
|
|
|
castDictionary obj = expected "dictionary : " obj
|
|
|
|
|
|
|
|
castObjectId :: MonadFail m => DirectObject -> m ObjectId
|
|
|
|
castObjectId (Reference (IndirectObjCoordinates {objectId})) = return objectId
|
|
|
|
castObjectId directObject = expected "reference" directObject
|
|
|
|
|
|
|
|
dictionaryById :: PDFContent m => ObjectId -> m Dictionary
|
|
|
|
dictionaryById objectId = objectById objectId >>= castDictionary
|
|
|
|
|
|
|
|
expected :: (MonadFail m, Show a) => String -> a -> m b
|
2020-02-11 08:29:08 +01:00
|
|
|
expected name = fail . printf "Not a %s: %s" name . show
|
|
|
|
|
2020-02-11 22:41:46 +01:00
|
|
|
getField :: MonadFail m => String -> Dictionary -> m DirectObject
|
|
|
|
getField key aDictionary =
|
|
|
|
maybe (fail errorMessage) return (Map.lookup (Name key) aDictionary)
|
2020-02-11 08:29:08 +01:00
|
|
|
where
|
|
|
|
errorMessage =
|
|
|
|
printf "Key %s not found in dictionary %s" key (show aDictionary)
|
|
|
|
|
2020-02-11 17:35:35 +01:00
|
|
|
follow :: PDFContent m => DirectObject -> m Object
|
|
|
|
follow directObject = castObjectId directObject >>= objectById
|
2020-02-11 08:29:08 +01:00
|
|
|
|
2020-02-11 17:35:35 +01:00
|
|
|
objectById :: PDFContent m => ObjectId -> m Object
|
|
|
|
objectById objectId = do
|
2020-02-11 08:29:08 +01:00
|
|
|
content <- ask
|
|
|
|
return (objects content ! objectId)
|
|
|
|
|
|
|
|
getDictionary :: PDFContent m => DirectObject -> m Dictionary
|
|
|
|
getDictionary (Dictionary aDictionary) = return aDictionary
|
|
|
|
getDictionary (Reference (IndirectObjCoordinates {objectId})) =
|
2020-02-11 17:35:35 +01:00
|
|
|
objectById objectId >>= castDictionary
|
2020-02-11 08:29:08 +01:00
|
|
|
getDictionary aDirectObject =
|
|
|
|
expected "resource (dictionary or reference)" aDirectObject
|
|
|
|
|
|
|
|
(//) :: PDFContent m => Dictionary -> [String] -> m DirectObject
|
|
|
|
(//) aDict [] = return $ Dictionary aDict
|
|
|
|
(//) aDict [key] = getField key aDict
|
|
|
|
(//) aDict (key:keys) = getField key aDict >>= getDictionary >>= (// keys)
|
|
|
|
|
|
|
|
origin :: PDFContent m => m Dictionary
|
|
|
|
origin = trailer . docStructure <$> ask
|
|
|
|
|
2020-02-11 17:35:35 +01:00
|
|
|
openStream :: MonadFail m => Object -> m ByteString
|
2020-02-11 08:29:08 +01:00
|
|
|
openStream (Stream {header, streamContent}) = return $
|
|
|
|
case Map.lookup (Name "Filter") header of
|
|
|
|
Just (NameObject (Name "FlateDecode")) ->
|
|
|
|
Lazy.toStrict . decompress . Lazy.fromStrict $ streamContent
|
|
|
|
_ -> streamContent
|
|
|
|
openStream obj = expected "stream" obj
|