diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright 2008 by Audrey Tang <audreyt@audreyt.org>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README b/README
new file mode 100644
--- /dev/null
+++ b/README
@@ -0,0 +1,1 @@
+"line2pdf" is a simple command-line utility to convert text into PDF.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,14 @@
+#!/usr/bin/env runhaskell
+\begin{code}
+import System.Cmd
+import Distribution.Simple
+
+main = defaultMainWithHooks $ simpleUserHooks
+    { buildHook = myBuild (buildHook simpleUserHooks) }
+
+myBuild doBuild x y z w = do
+    rv <- doBuild x y z w
+    system "make -f Makefile.pdfdump"
+    return rv
+
+\end{code}
diff --git a/line2pdf.cabal b/line2pdf.cabal
new file mode 100644
--- /dev/null
+++ b/line2pdf.cabal
@@ -0,0 +1,19 @@
+name:               line2pdf
+version:            0.0.1
+copyright:          2008 Audrey Tang
+license:            BSD3
+license-file:       LICENSE
+author:             Audrey Tang <audreyt@audreyt.org>
+maintainer:         Audrey Tang <audreyt@audreyt.org>
+synopsis:           Simple command-line utility to convert text into PDF
+description:        Simple command-line utility to convert text into PDF
+stability:          experimental
+build-type:         Simple
+category:           Text
+extra-source-files: README
+
+executable:         line2pdf
+main-is:            line2pdf.hs
+extensions:         ImplicitParams
+build-depends:      base, bytestring, containers
+hs-source-dirs:     .
diff --git a/line2pdf.hs b/line2pdf.hs
new file mode 100644
--- /dev/null
+++ b/line2pdf.hs
@@ -0,0 +1,264 @@
+{-# LANGUAGE ImplicitParams #-}
+module Main where
+import Data.IORef
+import System.IO
+import System.IO.Unsafe
+import System.Exit
+import System.Environment
+import Control.Applicative
+import qualified Data.ByteString.Lazy.Char8 as L
+import qualified Data.IntMap as IM
+
+{-# NOINLINE __POS__ #-}
+__POS__ :: IORef Int
+__POS__ = unsafePerformIO (newRef 0)
+
+{-# NOINLINE __OBJ__ #-}
+__OBJ__ :: IORef Int
+__OBJ__ = unsafePerformIO (newRef 0)
+
+{-# NOINLINE __LOC__ #-}
+__LOC__ :: IORef (IM.IntMap String)
+__LOC__ = unsafePerformIO (newRef IM.empty)
+
+{-# NOINLINE __PAGE__ #-}
+__PAGE__ :: IORef [Obj]
+__PAGE__ = unsafePerformIO (newRef [])
+
+type M = IO
+type Obj = Int
+
+main = do
+    args <- getArgs
+
+    input <- case args of
+        []      -> do
+            putStrLn "Usage: line2pdf input.txt > output.pdf"
+            putStrLn "  (Form feed (^L) in input denotes a pagebreak.)"
+            exitWith ExitSuccess
+        (i:_)   -> return i
+
+    hSetBinaryMode stdout True
+
+    pr$ "%PDF-1.2\n" ++ "%\xE2\xE3\xCF\xD3\n"
+
+    (info, root, tPages, resources) <- writeHeader
+
+    pageObjs <- let ?tPages = tPages
+                    ?resources = resources in writePages =<< (L.lines <$> L.readFile input)
+
+    markLocation root
+    writeObj root $ do
+        pr$ "/Type/Catalog" ++ "/Pages "
+        pr$ show tPages ++ " 0 R" 
+
+    markLocation tPages
+    writeObj tPages $ do
+        pr$ "/Type/Pages" ++ "/Count "
+        pr$ show (length pageObjs)
+         ++ "/MediaBox[0 0 "
+         ++ show pageWidth ++ " " ++ show pageHeight
+        pr$ "]" ++ "/Kids["
+        pr$ concatMap ((++ " 0 R ") . show) pageObjs
+        pr$ "]"
+
+    xfer <- currentLocation
+
+    objCount <- incrObj
+    pr$ "xref\n" ++ "0 " ++ show objCount ++ "\n"
+     ++ "0000000000 65535 f \r"
+
+    writeLocations
+
+    pr$ "trailer\n" ++ "<<" ++ "/Size "
+    pr$ show objCount
+
+    pr$ "/Root " ++ show root ++ " 0 R"
+    pr$ "/Info " ++ show info ++ " 0 R"
+    pr$ ">>\n" ++ "startxref\n"
+    pr$ show xfer ++ "\n" ++ "%%EOF\n"
+
+writeObj :: Obj -> M a -> M a
+writeObj obj f = do
+    pr$ show obj ++ " 0 obj" ++ "<<"
+    rv <- f
+    pr$ ">>" ++ "endobj\n"
+    return rv
+
+writeLocations = do
+    locs <- IM.elems <$> readRef __LOC__
+    pr$ concatMap fmt locs
+    where
+    fmt x = pad ++ x ++ " 00000 n \r"
+        where
+        pad = replicate (10-l) '0'
+        l = length x 
+
+writeHeader = do
+    info <- markObj $ \info -> do
+        pr$ "/CreationDate(D:20080707163949+08'00')"
+         ++ "/Producer(line2pdf.hs)"
+         ++ "/Title(Untitled)"
+        return info
+    encoding <- markObj $ \encoding -> do
+        pr$ strDefaultEncoding
+        return encoding
+    everyFont <- (`mapM` ([1..] `zip` baseFonts)) $ \(n, font) -> do
+        markObj $ \obj -> do
+            pr$ "/Type/Font" ++ "/Subtype/Type1" ++ "/Name/F"
+            pr$ show n
+            pr$ "/Encoding " ++ show encoding ++ " 0 R"
+            pr$ font
+            return $ "/F" ++ show n ++ " " ++ show obj ++ " 0 R"
+    root    <- incrObj
+    tPages  <- incrObj
+    markObj $ \resources -> do
+        pr$ "/Font<<" ++ concat everyFont ++ ">>"
+            ++ "/ProcSet[/PDF/Text]" ++ "/XObject<<>>"
+        return (info, root, tPages, resources)
+
+baseFonts =
+    [ "/BaseFont/Courier"
+    , "/BaseFont/Courier-Oblique"
+    , "/BaseFont/Courier-Bold"
+    , "/BaseFont/Courier-BoldOblique"
+    , "/BaseFont/Helvetica"
+    , "/BaseFont/Helvetica-Oblique"
+    , "/BaseFont/Helvetica-Bold"
+    , "/BaseFont/Helvetica-BoldOblique"
+    , "/BaseFont/Times-Roman"
+    , "/BaseFont/Times-Italic"
+    , "/BaseFont/Times-Bold"
+    , "/BaseFont/Times-BoldItalic"
+    , "/BaseFont/Symbol"
+    , "/BaseFont/ZapfDingbats"
+    ]
+    
+pr :: String -> M ()
+pr str = do
+    putStr str
+    modifyRef __POS__ (+ (length str))
+
+currentLocation = readRef __POS__
+
+newRef = newIORef
+readRef = readIORef
+writeRef = writeIORef
+modifyRef = modifyIORef
+
+incrObj :: M Obj
+incrObj = do
+    obj <- succ <$> readRef __OBJ__
+    writeRef __OBJ__ obj
+    return obj
+
+markObj :: (Obj -> M a) -> M a
+markObj f = do
+    obj <- incrObj
+    markLocation obj
+    writeObj obj (f obj)
+
+markLocation :: Obj -> M ()
+markLocation obj = do
+    loc <- currentLocation
+    modifyRef __LOC__ $ IM.insert obj (show loc)
+
+startPage :: (?tPages :: Obj, ?resources :: Obj) => M Int
+startPage = do
+    markObj $ \obj -> do
+        modifyRef __PAGE__ (obj:)
+        pr$ "/Type/Page"
+        pr$ "/Parent " ++ show ?tPages ++ " 0 R"
+        pr$ "/Resources " ++ show ?resources ++ " 0 R"
+        pr$ "/Contents " ++ show (succ obj) ++ " 0 R"
+        pr$ "/Rotate 0"
+
+    obj <- incrObj
+    markLocation obj
+    pr$ show obj ++ " 0 obj" ++ "<<"
+    pr$ "/Length " ++ show (succ obj) ++ " 0 R"
+    pr$ ">>" ++ "stream\n"
+
+    streamPos <- currentLocation
+    pr$ "BT\n";
+    let fontN = 1
+        ptSize = 12
+    pr$ "/F" ++ show fontN ++ " " ++ show ptSize ++ " Tf\n"
+    pr$ "1 0 0 1 50 " ++ show (pageHeight - 40) ++ " Tm\n"
+    pr$ "12 TL\n"
+
+    return streamPos
+
+endPage streamStart = do
+    pr$ "ET\n"
+    streamEnd <- currentLocation
+    pr$ "endstream\n"
+     ++ "endobj\n"
+
+    obj <- incrObj
+    markLocation obj
+
+    pr$ show obj ++ " 0 obj\n"
+     ++ show (streamEnd - streamStart) ++ "\n"
+     ++ "endobj\n"
+
+writePages :: (?tPages :: Obj, ?resources :: Obj) => [L.ByteString] -> M [Obj]
+writePages lns = do
+    pos <- newRef =<< startPage
+
+    (`mapM_` lns) $ \ln -> do
+        case fromEnum (L.length ln) of
+            1 | L.head ln == '\f' -> do
+                endPage =<< readRef pos
+                writeRef pos =<< startPage
+            len -> do
+                pr$ "T*("
+                L.putStr ln
+                modifyRef __POS__ (+ len)
+                pr$ ")Tj\n"
+
+    endPage =<< readRef pos
+    reverse <$> readRef __PAGE__
+
+strDefaultEncoding = "/Type/Encoding"
+    ++ "/Differences[0 /.notdef/.notdef/.notdef/.notdef"
+    ++ "/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef"
+    ++ "/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef"
+    ++ "/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef"
+    ++ "/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef"
+    ++ "/.notdef/.notdef/.notdef/.notdef/space/exclam"
+    ++ "/quotedbl/numbersign/dollar/percent/ampersand"
+    ++ "/quoteright/parenleft/parenright/asterisk/plus/comma"
+    ++ "/hyphen/period/slash/zero/one/two/three/four/five"
+    ++ "/six/seven/eight/nine/colon/semicolon/less/equal"
+    ++ "/greater/question/at/A/B/C/D/E/F/G/H/I/J/K/L"
+    ++ "/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft"
+    ++ "/backslash/bracketright/asciicircum/underscore"
+    ++ "/quoteleft/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p"
+    ++ "/q/r/s/t/u/v/w/x/y/z/braceleft/bar/braceright"
+    ++ "/asciitilde/.notdef/.notdef/.notdef/.notdef/.notdef"
+    ++ "/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef"
+    ++ "/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef"
+    ++ "/dotlessi/grave/acute/circumflex/tilde/macron/breve"
+    ++ "/dotaccent/dieresis/.notdef/ring/cedilla/.notdef"
+    ++ "/hungarumlaut/ogonek/caron/space/exclamdown/cent"
+    ++ "/sterling/currency/yen/brokenbar/section/dieresis"
+    ++ "/copyright/ordfeminine/guillemotleft/logicalnot/hyphen"
+    ++ "/registered/macron/degree/plusminus/twosuperior"
+    ++ "/threesuperior/acute/mu/paragraph/periodcentered"
+    ++ "/cedilla/onesuperior/ordmasculine/guillemotright"
+    ++ "/onequarter/onehalf/threequarters/questiondown/Agrave"
+    ++ "/Aacute/Acircumflex/Atilde/Adieresis/Aring/AE"
+    ++ "/Ccedilla/Egrave/Eacute/Ecircumflex/Edieresis/Igrave"
+    ++ "/Iacute/Icircumflex/Idieresis/Eth/Ntilde/Ograve"
+    ++ "/Oacute/Ocircumflex/Otilde/Odieresis/multiply/Oslash"
+    ++ "/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn"
+    ++ "/germandbls/agrave/aacute/acircumflex/atilde/adieresis"
+    ++ "/aring/ae/ccedilla/egrave/eacute/ecircumflex"
+    ++ "/edieresis/igrave/iacute/icircumflex/idieresis/eth"
+    ++ "/ntilde/ograve/oacute/ocircumflex/otilde/odieresis"
+    ++ "/divide/oslash/ugrave/uacute/ucircumflex/udieresis"
+    ++ "/yacute/thorn/ydieresis]"
+
+pageWidth = 792
+pageHeight = 612
