module Graphics.PDF.LowLevel
(
PdfObject(..),PdfDictionary(..),PDF(..),Content(..),PdfCmd,CreatedObject,ObjectKind(..), Cmd(..), PdfString(..),TextRendering(..)
,dictionaryExtract, pdfDictionaryEmpty, emptyDictionary, emptyPdf
,nbObjects, objectIndex, allObjects,pdfDictionary
,addObject, addFont, addState, addNewContent, applyCommand,computeObjectPos,(<>),withContext
) where
import qualified Data.Map as Map
import Data.Monoid
import Text.Printf
import Text.Show
newtype PdfString = S String
data TextRendering = TextStroke
| TextFill
| TextFillStroke
| TextClip
data Cmd = PdfRgbSpace
| PdfSC Float Float Float
| PdfSF Float Float Float
| PdfBT String Int
| PdfCM Float Float Float Float Float Float
| PdfL Float Float Float Float
| PdfW Float
| PdfRect Float Float Float Float
| PdfFillRect Float Float Float Float
| PdfDash [Float] Float
| PdfStartPath Float Float
| PdfAddLineToPath Float Float
| PdfAddRectangleToPath Float Float Float Float
| PdfText TextRendering Float Float [PdfString]
| PdfStroke
| PdfFill
| PdfClip
| PdfFillAndStroke
| PdfClosePath
| PdfAlpha String
| PdfQ [Cmd]
| PdfResetAlpha
| PdfCharSpacing Float
| PdfWordSpacing Float
| PdfLeading Float
| PdfStrokePattern String
| PdfFillPattern String
| PdfNone
instance Show PdfString where
show (S a) = "("++a++")"
showList l = \s -> r ++ s
where
r = case l of
[] -> ""
(a:l) -> (show a) ++ " Tj " ++ concat (zipWith (++) (map show l) (repeat " ' "))
instance Show Cmd where
show (PdfQ l) = error "Require a special processing"
show PdfNone = ""
show PdfClip = "\nW"
show PdfResetAlpha = "\n/standardAlpha gs"
show (PdfAlpha s) = printf "\n/%s gs" s
show (PdfStrokePattern s) = printf "\n/Pattern CS /%s SCN" s
show (PdfFillPattern s) = printf "\n/Pattern cs /%s scn" s
show PdfRgbSpace = "\n/DeviceRGB CS\n/DeviceRGB cs\n/standardAlpha gs"
show (PdfSC r g b) = printf "\n%f %f %f SC" r g b
show (PdfSF r g b) = printf "\n%f %f %f sc" r g b
show (PdfBT fontName fontSize) = printf "\nBT /%s %d Tf ET" fontName fontSize
show (PdfCM a b c d e f) = printf "\n%f %f %f %f %f %f cm" a b c d e f
show (PdfL xa ya xb yb) = printf "\nh %f %f m %f %f l S" xa ya xb yb
show (PdfW w) = printf "\n%f w" w
show (PdfRect xa ya width height) = printf "\nh %f %f %f %f re S" xa ya width height
show (PdfFillRect xa ya width height) = printf "\nh %f %f %f %f re f" xa ya width height
show (PdfDash a phase) = printf "\n[%s] %f d" (unwords . map show $ a) phase
show (PdfStartPath xa ya) = printf "\nh %f %f m" xa ya
show (PdfClosePath) = "\nh"
show (PdfAddLineToPath xa ya) = printf "\n%f %f l" xa ya
show (PdfAddRectangleToPath xa ya width height) = printf "\n%f %f %f %f re" xa ya width height
show PdfStroke = "\nS"
show PdfFill = "\nf"
show PdfFillAndStroke = "\nB"
show (PdfText TextStroke px py s) = printf "\n1 Tr BT %f %f Td %s ET" px py (show s)
show (PdfText TextFill px py s) = printf "\n0 Tr BT %f %f Td %s ET" px py (show s)
show (PdfText TextFillStroke px py s) = printf "\n2 Tr BT %f %f Td %s ET" px py (show s)
show (PdfText TextClip px py s) = printf "\n7 Tr BT %f %f Td %s ET" px py (show s)
show (PdfCharSpacing x) = printf "\n%f Tc" x
show (PdfWordSpacing x) = printf "\n%f Tw" x
show (PdfLeading x) = printf "\n%f TL" x
newtype Content = Content ([Cmd])
instance Monoid Content where
mempty = Content ([])
mappend (Content(ca)) (Content(cb)) = Content(ca ++ cb)
data PdfObject = PdfInt Int
| PdfFloat Float
| PdfString String
| PdfName String
| PdfDict PdfDictionary
| PdfUnknownPointer String
| PdfPointer Int
| PdfBool Bool
| PdfArray [PdfObject]
| PdfStream Content
newtype PdfDictionary = PdfDictionary (Map.Map String PdfObject)
dictionaryExtract :: PdfDictionary -> Map.Map String PdfObject
dictionaryExtract (PdfDictionary a) = a
data ObjectKind = PdfAnyObject | PdfFont | PdfState | PdfShading | PdfPatternObject
type CreatedObject = (ObjectKind,String,PdfObject)
type PdfCmd = (Cmd,[CreatedObject])
nbObjects :: PDF -> Int
nbObjects p = let PdfDictionary d = (objects p)
in
Map.size d
objectIndex :: String -> PDF -> Int
objectIndex s p = let PdfDictionary d = (objects p)
in
(Map.findIndex s d) + 1
allObjects :: PDF -> [(Int,PdfObject)]
allObjects p = let d = dictionaryExtract (objects p)
in
map createItem . Map.toAscList $ d
where
createItem (k,s) = ((objectIndex k p),s)
contentName :: String
contentName = "Main"
data PDF = PDF {content :: Content,
objects :: PdfDictionary,
extgstate :: PdfDictionary,
xobject :: PdfDictionary,
font :: PdfDictionary,
pattern :: PdfDictionary,
shading :: PdfDictionary,
x :: Float,
y :: Float
}
pdfDictionaryEmpty :: PdfDictionary -> Bool
pdfDictionaryEmpty (PdfDictionary m) | Map.null m = True
| otherwise = False
emptyDictionary :: PdfDictionary
emptyDictionary = PdfDictionary Map.empty
emptyPdf :: Float -> Float -> PDF
emptyPdf width height = PDF {
objects = emptyDictionary,
content = mempty,
extgstate=emptyDictionary,
xobject=emptyDictionary,
font=emptyDictionary,
pattern=emptyDictionary,
shading=emptyDictionary,
x = width,
y = height
}
insertObject :: String -> PdfObject -> PdfDictionary -> PdfDictionary
insertObject name object (PdfDictionary d) = PdfDictionary (Map.insert name object d)
addObject :: String -> PdfObject -> PDF -> PDF
addObject name object p = p {objects = insertObject name object (objects p)}
addFont :: String -> PdfObject -> PDF -> PDF
addFont name object p = p {font=insertObject name object (font p)}
addState :: String -> PdfObject -> PDF -> PDF
addState name object p = p {extgstate=insertObject name object (extgstate p)}
addShading :: String -> PdfObject -> PDF -> PDF
addShading name object p = p {shading=insertObject name object (shading p)}
addPattern :: String -> PdfObject -> PDF -> PDF
addPattern name object p = p {pattern=insertObject name object (pattern p)}
addCreatedObject :: CreatedObject -> PDF -> PDF
addCreatedObject (PdfFont,name,object) p = addFont name object p
addCreatedObject (PdfState,name,object) p = addState name object p
addCreatedObject (PdfShading,name,object) p = addShading name object p
addCreatedObject (PdfPatternObject,name,object) p = addPattern name object p
addCreatedObject (_,name,object) p = addObject name object p
pdfDictionary :: [(String,PdfObject)] -> PdfObject
pdfDictionary l = PdfDict (PdfDictionary(Map.fromList l))
instance Monoid PDF where
mempty = emptyPdf 0 0
mappend pdfa pdfb = PDF {objects = union (objects pdfa) (objects pdfb),
content = (content pdfa) `mappend` (content pdfb),
extgstate = union (extgstate pdfa) (extgstate pdfb),
xobject = union (xobject pdfa) (xobject pdfb),
font = union (font pdfa) (font pdfb),
pattern = union (pattern pdfa) (pattern pdfb),
shading = union (shading pdfa) (shading pdfb),
x = max (x pdfa) (x pdfb),
y = max (y pdfa) (y pdfb)
} where
union (PdfDictionary a) (PdfDictionary b) = PdfDictionary (Map.union a b)
addNewContent :: Cmd -> PDF -> PDF
addNewContent s p = p {content = Content(s : c)}
where
Content(c) = content p
replaceContent :: Content -> PDF -> PDF
replaceContent s p = p {content = s}
withContext :: PDF -> PdfCmd
withContext f = (PdfQ l,newobjs)
where
Content(l) = content f
newobjs = listOfObjects PdfState (extgstate f) ++ listOfObjects PdfFont (font f)
listOfObjects s d = map tag (Map.toList (dictionaryExtract d))
where
tag (n,o) = (s,n,o)
applyCommand :: Cmd -> PDF -> PDF
applyCommand cmd p = addNewContent cmd p
computeObjectPos :: PDF -> PDF
computeObjectPos p = p {content = mempty,
objects = renumber newobjects,
extgstate= emptyDictionary,
xobject= emptyDictionary,
font= emptyDictionary,
pattern= emptyDictionary,
shading= emptyDictionary
}
where
newobjects = foldl insertObjs (objects p) [(contentName,(PdfStream (content p))),
("ExtGState",PdfDict (extgstate p)),
("XObject",PdfDict (xobject p)),
("Font",PdfDict (font p)),
("Pattern",PdfDict (pattern p)),
("Shading",PdfDict (shading p))
]
insertObjs dict (s,o) = insertObject s o dict
renumber (PdfDictionary objs) = PdfDictionary(Map.map (setNumber objs) objs)
setNumber objs (PdfUnknownPointer s) = PdfPointer ((Map.findIndex s objs)+1)
setNumber objs (PdfDict (PdfDictionary d)) = PdfDict . PdfDictionary . Map.map (setNumber objs) $ d
setNumber objs (PdfArray l) = PdfArray . map (setNumber objs) $ l
setNumber _ a = a
infixr 9 <>
(<>) :: PdfCmd -> PDF -> PDF
(<>) (c,l) p = addNewContent c (foldr addCreatedObject p l)