{-# LANGUAGE CPP #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} --------------------------------------------------------- -- | -- Copyright : (c) 2006-2016, alpheccar.org -- License : BSD-style -- -- Maintainer : misc@NOSPAMalpheccar.org -- Stability : experimental -- Portability : portable -- -- Low level stuff --------------------------------------------------------- -- #hide module Graphics.PDF.LowLevel.Types where import qualified Data.Map.Strict as M import Data.List(intersperse) import Data.Int import Control.Monad.State import Control.Monad.Writer import Data.Binary.Builder(Builder,fromByteString) import Graphics.PDF.LowLevel.Serializer import Data.Complex import qualified Data.ByteString as S import qualified Data.ByteString.Lazy.Internal as L(ByteString(..)) import Data.Text.Encoding import qualified Data.Text as T import qualified Data.ByteString.Char8 as C import Data.Word import Data.Char(ord) import Text.Printf(printf) {- Low level typesetting types -} data SpecialChar = NormalChar !Char | BreakingHyphen | BiggerSpace | NormalSpace {- PDF Specific low level types -} -- | PDF Objects class PdfObject a where toPDF :: a -> Builder class PdfLengthInfo a where pdfLengthInfo :: a -> Maybe (Int64 , PDFReference MaybeLength) pdfLengthInfo _ = Nothing -- | Anonymous PDF object data AnyPdfObject = forall a . (PdfObject a, PdfLengthInfo a) => AnyPdfObject !a instance PdfObject AnyPdfObject where toPDF (AnyPdfObject a) = toPDF a instance PdfLengthInfo AnyPdfObject where pdfLengthInfo (AnyPdfObject a) = pdfLengthInfo a -- | An integer in a PDF document newtype PDFInteger = PDFInteger Int deriving(Eq,Show,Ord,Num) -- | A length in a PDF document newtype PDFLength = PDFLength Int64 deriving(Eq,Show,Ord,Num) data MaybeLength = UnknownLength | KnownLength !PDFLength instance PdfObject MaybeLength where toPDF (KnownLength a) = toPDF a toPDF (UnknownLength) = error "Trying to process an unknown length during PDF generation" instance PdfLengthInfo MaybeLength where -- | A real number in a PDF document type PDFFloat = Double instance PdfObject PDFInteger where toPDF (PDFInteger a) = serialize a instance PdfLengthInfo PDFInteger where instance PdfObject Int where toPDF a = serialize a instance PdfLengthInfo Int where instance PdfObject PDFLength where toPDF (PDFLength a) = serialize (show a) instance PdfLengthInfo PDFLength where instance PdfObject PDFFloat where toPDF a = serialize a instance PdfLengthInfo PDFFloat where instance PdfObject (Complex PDFFloat) where toPDF (x :+ y) = mconcat [ serialize x , serialize ' ' , serialize y ] instance PdfLengthInfo (Complex PDFFloat) where instance PdfObject Bool where toPDF (True) = serialize ("true" :: String) toPDF (False) = serialize ("false" :: String) instance PdfLengthInfo Bool where -- | A PDFString containing a strict bytestring (serialied as UTF16BE) newtype PDFString = PDFString S.ByteString deriving(Eq,Ord,Show) -- | A list of glyph to be used in text operators newtype PDFGlyph = PDFGlyph S.ByteString deriving(Eq,Ord,Show) -- | A list of glyph to be used in text operators newtype EscapedPDFGlyph = EscapedPDFGlyph S.ByteString deriving(Eq,Ord,Show) -- | 7 bit encoded ASCII string newtype AsciiString = AsciiString S.ByteString deriving(Eq,Ord,Show) -- | 7 bit encoded ASCII string newtype EscapedAsciiString = EscapedAsciiString S.ByteString deriving(Eq,Ord,Show) escapeText :: Char -> T.Text escapeText '(' = "\\(" escapeText ')' = "\\)" escapeText '\\' = "\\\\" escapeText a = T.singleton a escapeByteString :: Char -> S.ByteString escapeByteString '(' = C.pack "\\(" escapeByteString ')' = C.pack "\\)" escapeByteString '\\' = C.pack "\\\\" escapeByteString a = C.singleton a -- | Create a PDF string from an Haskell one toPDFString :: T.Text -> PDFString toPDFString = PDFString . encodeUtf16BE toPDFGlyph :: S.ByteString -> PDFGlyph toPDFGlyph = PDFGlyph toAsciiString :: String -> AsciiString toAsciiString s = AsciiString (C.pack s) class HasHexaStream a where toHexaStream :: a -> S.ByteString instance HasHexaStream S.ByteString where toHexaStream x = let hexChar c = C.pack (printf "%02X" (ord c) :: String) in C.cons 'F' . C.cons 'E' . C.cons 'F' . C.cons 'F' . C.concatMap hexChar $ x instance HasHexaStream PDFString where toHexaStream (PDFString x) = toHexaStream x instance HasHexaStream PDFGlyph where toHexaStream (PDFGlyph x) = let hexChar c = C.pack (printf "%02X" (ord c) :: String) in C.concatMap hexChar $ x newtype GlyphCode = GlyphCode Word8 deriving(Eq,Ord,Show,Integral,Bounded,Enum,Real,Num) instance SerializeValue L.ByteString PDFString where serialize (PDFString t) = L.Chunk t L.Empty instance SerializeValue Builder PDFString where serialize (PDFString t) = fromByteString t instance SerializeValue L.ByteString PDFGlyph where serialize (PDFGlyph t) = L.Chunk t L.Empty instance SerializeValue Builder EscapedPDFGlyph where serialize (EscapedPDFGlyph t) = fromByteString t instance SerializeValue L.ByteString AsciiString where serialize (AsciiString t) = L.Chunk t L.Empty instance SerializeValue Builder EscapedAsciiString where serialize (EscapedAsciiString t) = fromByteString t -- Misc strings useful to build bytestrings lparen :: SerializeValue s Char => s lparen = serialize '(' rparen :: SerializeValue s Char => s rparen = serialize ')' lbracket :: SerializeValue s Char => s lbracket = serialize '[' rbracket :: SerializeValue s Char => s rbracket = serialize ']' bspace :: SerializeValue s Char => s bspace = serialize ' ' blt :: SerializeValue s Char => s blt = serialize '<' bgt :: SerializeValue s Char => s bgt = serialize '>' newline :: SerializeValue s Char => s newline = serialize '\n' noPdfObject :: Monoid s => s noPdfObject = mempty espacePDFGlyph :: PDFGlyph -> EscapedPDFGlyph espacePDFGlyph (PDFGlyph t) = EscapedPDFGlyph . C.concatMap escapeByteString $ t espaceAsciiString :: AsciiString -> EscapedAsciiString espaceAsciiString (AsciiString t) = EscapedAsciiString . C.concatMap escapeByteString $ t instance PdfObject PDFString where toPDF a = mconcat [ blt , fromByteString $ toHexaStream a , bgt ] instance PdfLengthInfo PDFString where instance PdfObject PDFGlyph where toPDF a = mconcat [ blt --, serialize . espacePDFGlyph $ a , fromByteString $ toHexaStream a , bgt ] instance PdfLengthInfo PDFGlyph where instance PdfLengthInfo AsciiString where instance PdfObject AsciiString where toPDF a = mconcat [ lparen , serialize . espaceAsciiString $ a , rparen ] -- | A PDFName object newtype PDFName = PDFName String deriving(Eq,Ord) instance PdfObject PDFName where toPDF (PDFName a) = serialize ("/" ++ a) instance PdfLengthInfo PDFName where -- | A PDFArray type PDFArray = [AnyPdfObject] instance PdfObject a => PdfObject [a] where toPDF l = mconcat $ (lbracket:intersperse bspace (map toPDF l)) ++ [bspace] ++ [rbracket] instance PdfObject a => PdfLengthInfo [a] where -- | A PDFDictionary newtype PDFDictionary = PDFDictionary (M.Map PDFName AnyPdfObject) instance PdfObject PDFDictionary where toPDF (PDFDictionary a) = mconcat $ [blt,blt,newline] ++ [convertLevel a] ++ [bgt,bgt] where convertLevel _ = let convertItem key value current = mconcat $ [ toPDF key , bspace , toPDF value , newline , current ] in M.foldrWithKey convertItem mempty a instance PdfLengthInfo PDFDictionary where -- | Am empty dictionary emptyDictionary :: PDFDictionary emptyDictionary = PDFDictionary M.empty isEmptyDictionary :: PDFDictionary -> Bool isEmptyDictionary (PDFDictionary d) = M.null d insertInPdfDict :: PDFName -> AnyPdfObject -> PDFDictionary -> PDFDictionary insertInPdfDict key obj (PDFDictionary d) = PDFDictionary $ M.insert key obj d pdfDictUnion :: PDFDictionary -> PDFDictionary -> PDFDictionary pdfDictUnion (PDFDictionary a) (PDFDictionary b) = PDFDictionary $ M.union a b -- | A PDF rectangle data PDFRect = PDFRect !Double !Double !Double !Double instance PdfObject PDFRect where toPDF (PDFRect a b c d) = toPDF . map AnyPdfObject $ [a,b,c,d] instance PdfLengthInfo PDFRect where -- | A Referenced objects data PDFReferencedObject a = PDFReferencedObject !Int !a instance PdfObject a => PdfObject (PDFReferencedObject a) where toPDF (PDFReferencedObject referenceId obj) = mconcat $ [ serialize . show $ referenceId , serialize (" 0 obj" :: String) , newline , toPDF obj , newline , serialize ("endobj" :: String) , newline , newline ] instance PdfObject a => PdfLengthInfo (PDFReferencedObject a) where -- | A reference to a PDF object data PDFReference s = PDFReference !Int deriving(Eq,Ord,Show) -- | Get the reference value referenceValue :: PDFReference s -> Int referenceValue (PDFReference i) = i instance PdfObject s => Num (PDFReference s) where (+) (PDFReference a) (PDFReference b) = PDFReference (a+b) (*) (PDFReference a) (PDFReference b) = PDFReference (a*b) negate (PDFReference a) = PDFReference (negate a) abs (PDFReference a) = PDFReference (abs a) signum (PDFReference a) = PDFReference (signum a) fromInteger a = PDFReference (fromInteger a) instance PdfObject s => PdfObject (PDFReference s) where toPDF (PDFReference i) = mconcat $ [ serialize . show $ i , serialize (" 0 R" :: String)] instance PdfObject s => PdfLengthInfo (PDFReference s) where instance (PdfObject a,PdfObject b) => PdfObject (Either a b) where toPDF (Left a) = toPDF a toPDF (Right a) = toPDF a instance (PdfObject a, PdfObject b) => PdfLengthInfo (Either a b) where modifyStrict :: (MonadState s m) => (s -> s) -> m () modifyStrict f = do s <- get put $! (f s) -- | A monad where paths can be created class MonadWriter Builder m => MonadPath m {- Font types -} data EmbeddedFont instance PdfObject EmbeddedFont where toPDF _ = noPdfObject instance PdfLengthInfo EmbeddedFont where