First steps with HPDF

I’ve been fiddling with HPDF for a new project, and ran into a bit of a documentation barrier. So i’m writing down some notes, which may some day lead to a better-documented, higher-level HPDF wrapper.

1 I’ll be using HPDF v1.4.2, hot off of
hackage.

First off, let’s write a very minimal pdf1.

module Main where
import Graphics.PDF

main :: IO()
main = do
  runPdf "mydemo.pdf" standardDocInfo (PDFRect 0 0 612 792) $ do
         page1 <- addPage Nothing
         drawWithPage page1 $ do
           drawText $ do
             setFont (PDFFont Times_Roman 12)
             textStart 36.0 396.0
             displayText (toPDFString "the quick brown fox jumps over the lazy dog")

That’s a lot of nested do‘s. Step-by-step:

  • runPdf is the big output function. It takes a filename, a PDFDocumentInfo, a PDFRect which specifies the page size, and a PDF action.
    • A PDFDocumentInfo gives you access to document-level info, including author and subject, but also PDF-spec stuff like pageMode and whether the document is compressed. See the PDF spec for more info on this structure, or just use standardDocInfo.
    • PDFRect is used in a bunch of places, but here it’s for the default paper size for your document. The numbers are points (assuming 72 points per inch), so this document is sized like a standard US Letter page (8.5“x11”). PDF coordinates are traditional cartesian (i.e., 0,0 is bottom-left, not top-left like a computer screen). We could conceivably start our page at (-20,-20) or (70,30) instead of (0,0), but doing so would make the rest of the process a pain in the butt. So let’s not.
  • The PDF monad describes the PDF actions we want to encode in the document. We just want to print a single line of text, but to do so requires some effort.
    • First, we create a page to play with, using addPage. The argument to addPage is either a page size for that page, or Nothing if we’re using the document default (set above to be Letter size).
    • drawWithPage takes a bunch of drawing commands, and writes them to the provided page, returning the whole PDF monad. The drawing commands themselves are pretty straightforward (and imperative).
      • In order to write text, we need to setFont (which takes a font defined in Graphics.PDF.Text
      • We don’t technically need to set a starting point, but if we don’t, we’ll start printing at (0.0,0.0), which we probably don’t want to do (go ahead and try, you’ll see what i mean).
      • Finally, displayText does what it claims (after we convert to the PDFString type)

Phew. Unfortunately, this is pretty verbose, and not terribly flexible. But it’s enough to write some wrappers and abstractions around. First, let’s make paper sizes look like paper sizes.

data PaperSize = PaperSize Int Int

letter = PaperSize 612 792
a4 = PaperSize 595 841
a5 = PaperSize 419 595

landscape (PaperSize x y) = PaperSize y x

toRect (PaperSize x y) = PDFRect 0 0 x y

Now let’s make it easier to stick text on a page.

writeOnPageAt p x y font string = drawWithPage p $ do
                                    drawText $ do
                                      setFont font
                                      textStart x y
                                      displayText (toPDFString string)

All that doesn’t seem like much, but allows us to start doing things like this:

main = do
  runPdf "mydemo.pdf" standardDocInfo (toRect $ landscape letter) $ do
         p <- addPage Nothing
         let
             header = (PDFFont Helvetica 36)
             bullet = (PDFFont Helvetica 18)
         writeOnPageAt p 36.0 540.0 header "HPDF is Powerful"
         writeOnPageAt p 108.0 468.0 bullet "- Low-level access to PDF primitives"
         writeOnPageAt p 108.0 396.0 bullet "- The power of Haskell"
         writeOnPageAt p 108.0 324.0 bullet "- A recipe for success"

Which is a nontrivial step forward. Still, we’re a long way from general usefulness. For one, our text doesn’t actually wrap yet (go ahead and try a long string—it will run right off the page). For another, i’d rather not have to specify the start point of every string. Paragraphs and bounding boxes would be nice.

Fortunately, HPDF provides some help here, in the form of its Container system. I’ll explore that a bit next time.

Tagged with: code haskell hpdf pdf

By john on July 7, 2009

Comments

No comments posted yet. Go for it!

Comments currently disabled due to impressive comment-spam efforts.