Haskell and PDF Generation with HPDF

Posted on February 11, 2014

I recently needed to generate a pdf and thought “I can just generate html with Blaze HTML and the convert it with Pandoc!”.

However the pandoc library was either not as simple as I thought, I’m a bit daft, or it had a bug. This led me to go search PDF libraries on Hackage. There I came across a pdf generation library called HPDF.

I read the description and went to the most top level namespace called Graphics.PDF. Upon reading the introduction paragraph, I was excited to see that the author created an examples package. I think this is a good idea and I will try to do the same in any Haskell libraries I may create.

However, there was the problem of the imports not being listed in the examples that took me a good 10-15 minutes to figure out. I tried to Hoogle the functions first. However it appears Hoogle has indexed the HPDF package, but searching for something like PDFRect doesn’t work. If I had remembered it’s name, I would have remembered to search Hayoo and it would have found everything I needed. It wasn’t wasted since I learned the format of the library, but it kept me from getting my task done quickly.

Protip: Use Hayoo instead of Hoogle

So here I am, just going through the package with examples and I just can’t find the imports. I feel really dumb, but then I see something about a github for the project. I search “github HPDF” and duckduckgo finds it for me

Then I navigate to Test.hs as the library author recommended and I’m greeted with a huge 662 line file. This wouldn’t be a problem if the majority of the functions weren’t dependent on one another. This stems from the Authors choice to write the PDF generation library in an imperative style. The imperative style he used does have the advantage of being faster in some (maybe all?) cases. I wonder if he could get similar performance with something like Pipes using a functional style. I’m pretty sure the API would be nicer.

At this point I just copied the test.hs file and started cutting things out. Eventually I got to this:


{-# LANGUAGE ScopedTypeVariables, MultiParamTypeClasses #-}
import Graphics.PDF
import Graphics.PDF.Typesetting

main :: IO()
main = do
    let rect = PDFRect 0 0 600 400
    runPdf "demo.pdf" (standardDocInfo { author=toPDFString "alpheccar", compressed = False}) rect $ do
        myDocument

myContent = displayFormattedText (Rectangle (80 :+ 0) (500 :+ 300)) NormalPara Normal $ do 
   paragraph $ do
        txt $ "Your Company: "
        forceNewLine
        txt $ "State: "
        forceNewLine
        txt $ "Email: "
        forceNewLine
        txt $ "Name: "

-- myDocument :: PDF () 
myDocument = do
   page1 <- addPage Nothing
   drawWithPage page1 $ do
    strokeColor red
    setWidth 0.5
    stroke $ Rectangle 800 800
    myContent

data MyVertStyles = NormalPara
                  | CirclePara !PDFFloat

data MyParaStyles = Normal
                  | Bold

instance ComparableStyle MyParaStyles where
  isSameStyleAs Normal Normal = True
  isSameStyleAs Bold Bold = True
  isSameStyleAs _ _ = False

instance Style MyParaStyles where
    textStyle Normal = TextStyle (PDFFont Times_Roman 10) black black FillText 1.0 1.0 1.0 1.0
    textStyle Bold = TextStyle (PDFFont Times_Bold 12) black black FillText 1.0 1.0 1.0 1.0

    sentenceStyle _ = Nothing
    wordStyle _ = Nothing
    
instance ParagraphStyle MyVertStyles MyParaStyles where
    lineWidth _ w _ = w
           
instance ComparableStyle MyVertStyles where
    isSameStyleAs NormalPara NormalPara = True
    isSameStyleAs _ _ = False

Not exactly what I would call simple and not as elegant as Haskell typically is, but things are understandable. Now that I’ve figured this out I can finish the task I was working on :)