Hufflepdf/src/PDF/Object/Navigation.hs

83 lines
2.8 KiB
Haskell

{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
module PDF.Object.Navigation (
(//)
, dictionaryById
, getDictionary
, getField
, follow
, objectById
, openStream
, origin
) where
import Codec.Compression.Zlib (decompress)
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)
type PDFContent m = (MonadReader Content m, MonadFail m)
castDictionary :: MonadFail m => Object -> m Dictionary
castDictionary (Direct (Dictionary aDict)) = return aDict
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
expected name = fail . printf "Not a %s: %s" name . show
getField :: MonadFail m => String -> Dictionary -> m DirectObject
getField key aDictionary =
maybe (fail errorMessage) return (Map.lookup (Name key) aDictionary)
where
errorMessage =
printf "Key %s not found in dictionary %s" key (show aDictionary)
follow :: PDFContent m => DirectObject -> m Object
follow directObject = castObjectId directObject >>= objectById
objectById :: PDFContent m => ObjectId -> m Object
objectById objectId = do
content <- ask
return (objects content ! objectId)
getDictionary :: PDFContent m => DirectObject -> m Dictionary
getDictionary (Dictionary aDictionary) = return aDictionary
getDictionary (Reference (IndirectObjCoordinates {objectId})) =
objectById objectId >>= castDictionary
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
openStream :: MonadFail m => Object -> m ByteString
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