{-# LANGUAGE CPP #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE CPP #-}
module Graphics.PDF
(
PDF
, runPdf
, pdfByteString
, PDFRect(..)
, PDFFloat
, PDFReference
, PDFString
, PDFPage
, Pages
, module Graphics.PDF.Document
, module Graphics.PDF.Shapes
, module Graphics.PDF.Colors
, module Graphics.PDF.Coordinates
, applyMatrix
, module Graphics.PDF.Text
, module Graphics.PDF.Navigation
, module Graphics.PDF.Annotation
, module Graphics.PDF.Action
, module Graphics.PDF.Image
, module Graphics.PDF.Pattern
, module Graphics.PDF.Shading
, module Graphics.PDF.Fonts.Font
, module Graphics.PDF.Fonts.StandardFont
, module Graphics.PDF.Fonts.Type1
, readType1Font
, mkType1Font
, module Graphics.PDF.Typesetting
) where
#if !MIN_VERSION_base(4,8,0)
import Data.Monoid
#endif
import Graphics.PDF.Typesetting
import Graphics.PDF.Shading
import Graphics.PDF.Pattern
import Graphics.PDF.Navigation
import Graphics.PDF.Text
import qualified Data.IntMap as IM
import qualified Data.Map.Strict as M
import qualified Data.ByteString.Lazy as B
import Data.Int
import Text.Printf(printf)
import Control.Monad.State
import Graphics.PDF.Annotation
import Graphics.PDF.LowLevel.Types
import Graphics.PDF.Draw
import Graphics.PDF.Colors
import Graphics.PDF.Shapes
import Graphics.PDF.Coordinates
import Graphics.PDF.Pages
import Graphics.PDF.Document
import Codec.Compression.Zlib
import Graphics.PDF.Action
import Graphics.PDF.Image
import Graphics.PDF.Resources(emptyResource)
import Data.Binary.Builder(Builder,fromLazyByteString, toLazyByteString)
import Graphics.PDF.LowLevel.Serializer
import Data.List(unfoldr)
import qualified Data.Text as T
import Graphics.PDF.Fonts.Font
import Graphics.PDF.Fonts.StandardFont
import Graphics.PDF.Fonts.Type1
createPDF :: PDF ()
createPDF = do
return ()
createStreams :: PDF ()
createStreams = do
ls <- gets streams >>= return . IM.toList
modifyStrict $ \s -> s {streams = IM.empty}
mapM_ addStream ls
where
addStream (k,(p,(state',w'))) = do
r <- supply
let ref = PDFReference r :: PDFReference MaybeLength
resources <- if (emptyResource (rsrc state')) && (not (pdfDictMember (PDFName "PatternType") (otherRsrcs state')))
then do
case p of
Nothing -> return (otherRsrcs state')
Just pageRef -> do
setPageAnnotations (annots state') pageRef
return emptyDictionary
else do
rsrcRef <- addObject (rsrc state')
case p of
Nothing -> do
return $ (otherRsrcs state') `pdfDictUnion` (PDFDictionary . M.fromList $ [(PDFName "Resources",AnyPdfObject rsrcRef)])
Just pageRef -> do
setPageAnnotations (annots state') pageRef
setPageResource rsrcRef pageRef
return emptyDictionary
infos <- gets docInfo
if (compressed infos) && (not (pdfDictMember (PDFName "Filter") resources))
then do
let w''' = compress . toLazyByteString $ w'
w'' = fromLazyByteString w'''
updateObject (PDFReference k :: PDFReference PDFStream) (PDFStream w'' True ref resources)
updateObject ref (UnknownLength)
else do
updateObject (PDFReference k :: PDFReference PDFStream) (PDFStream w' False ref resources)
updateObject ref (UnknownLength)
saveObjects :: PDF (PDFReference PDFCatalog)
saveObjects = do
createStreams
infos <- gets docInfo
pRef <- addPages
o <- gets outline
oref <- addOutlines o
cat <- addObject $ PDFCatalog oref pRef (pageMode infos) (pageLayout infos) (viewerPreferences infos)
modifyStrict $ \s -> s {catalog = cat}
gets catalog
#ifndef __HADDOCK__
data PDFTrailer = PDFTrailer
!Int
!(PDFReference PDFCatalog)
!(PDFDocumentInfo)
#else
data PDFTrailer
#endif
instance PdfObject PDFTrailer where
toPDF (PDFTrailer size root infos) = toPDF $ PDFDictionary. M.fromList $
[ (PDFName "Size",AnyPdfObject . PDFInteger $ size)
, (PDFName "Root",AnyPdfObject root)
, (PDFName "Info",AnyPdfObject . PDFDictionary . M.fromList $ allInfos)
]
where
allInfos = [ (PDFName "Author",AnyPdfObject . toPDFString . author $ infos)
, (PDFName "Subject",AnyPdfObject . toPDFString . subject $ infos)
, (PDFName "Producer",AnyPdfObject $ toPDFString (T.pack "HPDF - The Haskell PDF Library" ))
]
instance PdfLengthInfo PDFTrailer where
writeObjectsAndCreateToc :: [Builder]
-> (Int,Int64,[Builder])
writeObjectsAndCreateToc l =
let lengths = tail . scanl (\len obj -> len + (B.length . toLazyByteString $ obj)) 0 $ l
createEntry x = serialize $ (printf "%010d 00000 n \n" ((fromIntegral x)::Integer) :: String)
entries = map createEntry (init lengths)
in
(length l,last lengths,entries)
generateStreams :: PDFReference PDFCatalog -> PDFDocumentInfo -> Int -> Int64 -> [Builder]
-> [Builder] -> B.ByteString
generateStreams root di !nb !totalLen ens [] =
let entries = reverse (tail ens)
in
toLazyByteString $ mconcat $ [ serialize "xref\n"
, serialize $ "0 " ++ show nb ++ "\n"
, serialize "0000000000 65535 f \n"
]
++
entries
++
[ serialize "\ntrailer\n"
, toPDF $ PDFTrailer nb root di
, serialize "\nstartxref\n"
, serialize (show totalLen)
, serialize "\n%%EOF"
]
generateStreams root di !nb !totalLen ens (obj:t) =
let s = toLazyByteString obj
createEntry x = serialize $ (printf "%010d 00000 n \n" ((fromIntegral x)::Integer) :: String)
newLen = B.length s + totalLen
en = createEntry $! newLen
in
(s `B.append`) . generateStreams root di (nb+1) newLen (en : ens) $ t
defaultPdfSettings :: PdfState
defaultPdfSettings =
PdfState {
supplySrc = 1
, objects = IM.empty
, pages = noPages
, streams = IM.empty
, catalog = PDFReference 0
, defaultRect = PDFRect 0 0 600 400
, docInfo = standardDocInfo { author=T.pack "Unknown", compressed = True}
, outline = Nothing
, currentPage = Nothing
, xobjectBound = IM.empty
, firstOutline = [True]
}
createObjectByteStrings :: PdfState -> PDF a -> B.ByteString
createObjectByteStrings pdfState m =
let header = serialize "%PDF-1.5\n"
objectEncoding (x,a) = toPDF . PDFReferencedObject (fromIntegral $! x) $ a
(root,s) = flip runState pdfState . unPDF $ createPDF >> m >> saveObjects
objs = objects s
encodeAnObject (_,[]) = Nothing
encodeAnObject (im,k:t) =
let Just o = IM.lookup k im
result = do
(l,PDFReference ref) <- pdfLengthInfo o
let im' = IM.insert ref (AnyPdfObject (KnownLength (PDFLength l))) im
return im'
in
case result of
Nothing -> Just (objectEncoding (k,o),(im,t))
Just im' -> Just (objectEncoding (k,o),(im',t))
encodedObjects = unfoldr encodeAnObject (objs,IM.keys objs)
objectContents = header : encodedObjects
(_nb, _len, _toc) = writeObjectsAndCreateToc objectContents
in
generateStreams root (docInfo pdfState) 0 0 [] objectContents
pdfByteString :: PDFDocumentInfo
-> PDFRect
-> PDF a
-> B.ByteString
pdfByteString infos rect m = createObjectByteStrings (defaultPdfSettings {defaultRect = rect, docInfo = infos} ) m
runPdf :: String
-> PDFDocumentInfo
-> PDFRect
-> PDF a
-> IO ()
runPdf filename infos rect m = do
let bytestring = pdfByteString infos rect m
B.writeFile filename bytestring