diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) Brent Yorgey 2008
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. Neither the name of the author nor the names of other contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,3 @@
+#!/usr/bin/env runhaskell
+> import Distribution.Simple
+> main = defaultMain
diff --git a/Text/PrettyPrint/Boxes.hs b/Text/PrettyPrint/Boxes.hs
new file mode 100644
--- /dev/null
+++ b/Text/PrettyPrint/Boxes.hs
@@ -0,0 +1,382 @@
+{-# LANGUAGE OverloadedStrings #-}
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Text.PrettyPrint.Boxes
+-- Copyright   :  (c) Brent Yorgey 2009
+-- License     :  BSD-style (see LICENSE)
+-- Maintainer  :  byorgey@cis.upenn.edu
+-- Stability   :  experimental
+-- Portability :  portable
+--
+-- A pretty-printing library for laying out text in two dimensions,
+-- using a simple box model.
+--
+-----------------------------------------------------------------------------
+module Text.PrettyPrint.Boxes
+    ( -- * Constructing boxes
+
+      nullBox
+    , emptyBox
+    , char
+    , text
+    , para
+    , columns
+
+      -- * Layout of boxes
+
+    , (<>)
+    , (<+>)
+    , hcat
+    , hsep
+
+    , (//)
+    , (/+/)
+    , vcat
+    , vsep
+
+    , punctuateH, punctuateV
+
+    -- * Alignment
+
+    , left, right
+    , top, bottom
+    , center1, center2
+
+    , moveLeft
+    , moveRight
+    , moveUp
+    , moveDown
+
+    , alignHoriz
+    , alignVert
+    , align
+
+    -- * Rendering boxes
+
+    , render
+    , printBox
+
+    ) where
+
+import Data.String
+import Control.Arrow ((***), first)
+import Data.List (foldl', intersperse)
+
+import Data.List.Split
+
+-- | The basic data type.  A box has a specified size and some sort of
+--   contents.
+data Box = Box { rows    :: Int
+               , cols    :: Int
+               , content :: Content
+               }
+  deriving (Show)
+
+-- | Convenient ability to use bare string literals as boxes.
+instance IsString Box where
+  fromString = text
+
+-- | Data type for specifying the alignment of boxes.
+data Alignment = AlignFirst    -- ^ Align at the top/left.
+               | AlignCenter1  -- ^ Centered, biased to the top/left.
+               | AlignCenter2  -- ^ Centered, biased to the bottom/right.
+               | AlignLast     -- ^ Align at the bottom/right.
+  deriving (Eq, Read, Show)
+
+-- | Align boxes along their tops.
+top :: Alignment
+top        = AlignFirst
+
+-- | Align boxes along their bottoms.
+bottom :: Alignment
+bottom     = AlignLast
+
+-- | Align boxes to the left.
+left :: Alignment
+left       = AlignFirst
+
+-- | Align boxes to the right.
+right :: Alignment
+right      = AlignLast
+
+-- | Align boxes centered, but biased to the left/top in case of
+--   unequal parities.
+center1 :: Alignment
+center1    = AlignCenter1
+
+-- | Align boxes centered, but biased to the right/bottom in case of
+--   unequal parities.
+center2 :: Alignment
+center2    = AlignCenter2
+
+-- | Contents of a box.
+data Content = Blank        -- ^ No content.
+             | Text String  -- ^ A raw string.
+             | Row [Box]    -- ^ A row of sub-boxes.
+             | Col [Box]    -- ^ A column of sub-boxes.
+             | SubBox Alignment Alignment Box
+                            -- ^ A sub-box with a specified alignment.
+  deriving (Show)
+
+-- | The null box, which has no content and no size.  It is quite
+--   useless.
+nullBox :: Box
+nullBox = emptyBox 0 0
+
+-- | @emptyBox r c@ is an empty box with @r@ rows and @c@ columns.
+--   Useful for effecting more fine-grained positioning of other
+--   boxes, by inserting empty boxes of the desired size in between
+--   them.
+emptyBox :: Int -> Int -> Box
+emptyBox r c = Box r c Blank
+
+-- | A @1x1@ box containing a single character.
+char :: Char -> Box
+char c = Box 1 1 (Text [c])
+
+-- | A (@1 x len@) box containing a string of length @len@.
+text :: String -> Box
+text t = Box 1 (length t) (Text t)
+
+-- | Paste two boxes together horizontally, using a default (top)
+--   alignment.
+(<>) :: Box -> Box -> Box
+l <> r = hcat top [l,r]
+
+-- | Paste two boxes together horizontally with a single intervening
+--   column of space, using a default (top) alignment.
+(<+>) :: Box -> Box -> Box
+l <+> r = hcat top [l, emptyBox 0 1, r]
+
+-- | Paste two boxes together vertically, using a default (left)
+--   alignment.
+(//) :: Box -> Box -> Box
+t // b = vcat left [t,b]
+
+-- | Paste two boxes together vertically with a single intervening row
+--   of space, using a default (left) alignment.
+(/+/) :: Box -> Box -> Box
+t /+/ b = vcat left [t, emptyBox 1 0, b]
+
+-- | Glue a list of boxes together horizontally, with the given alignment.
+hcat :: Alignment -> [Box] -> Box
+hcat a bs = Box h w (Row $ map (alignVert a h) bs)
+  where h = maximum . (0:) . map rows $ bs
+        w = sum . map cols $ bs
+
+-- | @hsep sep a bs@ lays out @bs@ horizontally with alignment @a@,
+--   with @sep@ amount of space in between each.
+hsep :: Int -> Alignment -> [Box] -> Box
+hsep sep a bs = punctuateH a (emptyBox 0 sep) bs
+
+-- | Glue a list of boxes together vertically, with the given alignment.
+vcat :: Alignment -> [Box] -> Box
+vcat a bs = Box h w (Col $ map (alignHoriz a w) bs)
+  where h = sum . map rows $ bs
+        w = maximum . (0:) . map cols $ bs
+
+-- | @vsep sep a bs@ lays out @bs@ vertically with alignment @a@,
+--   with @sep@ amount of space in between each.
+vsep :: Int -> Alignment -> [Box] -> Box
+vsep sep a bs = punctuateV a (emptyBox sep 0) bs
+
+-- | @punctuateH a p bs@ horizontally lays out the boxes @bs@ with a
+--   copy of @p@ interspersed between each.
+punctuateH :: Alignment -> Box -> [Box] -> Box
+punctuateH a p bs = hcat a (intersperse p bs)
+
+-- | A vertical version of 'punctuateH'.
+punctuateV :: Alignment -> Box -> [Box] -> Box
+punctuateV a p bs = vcat a (intersperse p bs)
+
+--------------------------------------------------------------------------------
+--  Paragraph flowing  ---------------------------------------------------------
+--------------------------------------------------------------------------------
+
+-- | @para algn w t@ is a box of width @w@, containing text @t@,
+--   aligned according to @algn@, flowed to fit within the given
+--   width.
+para :: Alignment -> Int -> String -> Box
+para a n t = (\ss -> mkParaBox a (length ss) ss) $ flow n t
+
+-- | @columns w h t@ is a list of boxes, each of width @w@ and height
+--   at most @h@, containing text @t@ flowed into as many columns as
+--   necessary.
+columns :: Alignment -> Int -> Int -> String -> [Box]
+columns a w h t = map (mkParaBox a h) . chunk h $ flow w t
+
+-- | @mkParaBox a n s@ makes a box of height @n@ with the text @s@
+--   aligned according to @a@.
+mkParaBox :: Alignment -> Int -> [String] -> Box
+mkParaBox a n = alignVert top n . vcat a . map text
+
+-- | Flow the given text into the given width.
+flow :: Int -> String -> [String]
+flow n t = map (take n)
+         . getLines
+         $ foldl' addWordP (emptyPara n) (map mkWord . words $ t)
+
+data Para = Para { paraWidth   :: Int
+                 , paraContent :: ParaContent
+                 }
+data ParaContent = Block { fullLines :: [Line]
+                         , lastLine  :: Line
+                         }
+
+emptyPara :: Int -> Para
+emptyPara pw = Para pw (Block [] (Line 0 []))
+
+getLines :: Para -> [String]
+getLines (Para _ (Block ls l))
+  | lLen l == 0 = process ls
+  | otherwise   = process (l:ls)
+  where process = map (unwords . reverse . map getWord . getWords) . reverse
+
+data Line = Line { lLen :: Int, getWords :: [Word] }
+
+mkLine :: [Word] -> Line
+mkLine ws = Line (sum (map wLen ws) + length ws - 1) ws
+
+startLine :: Word -> Line
+startLine = mkLine . (:[])
+
+data Word = Word { wLen :: Int, getWord  :: String }
+
+mkWord :: String -> Word
+mkWord w = Word (length w) w
+
+addWordP :: Para -> Word -> Para
+addWordP (Para pw (Block fl l)) w
+  | wordFits pw w l = Para pw (Block fl (addWordL w l))
+  | otherwise       = Para pw (Block (l:fl) (startLine w))
+
+addWordL :: Word -> Line -> Line
+addWordL w (Line len ws) = Line (len + wLen w + 1) (w:ws)
+
+wordFits :: Int -> Word -> Line -> Bool
+wordFits pw w l = lLen l == 0 || lLen l + wLen w + 1 <= pw
+
+--------------------------------------------------------------------------------
+--  Alignment  -----------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+-- | @alignHoriz algn n bx@ creates a box of width @n@, with the
+--   contents and height of @bx@, horizontally aligned according to
+--   @algn@.
+alignHoriz :: Alignment -> Int -> Box -> Box
+alignHoriz a c b = Box (rows b) c $ SubBox a AlignFirst b
+
+-- | @alignVert algn n bx@ creates a box of height @n@, with the
+--   contents and width of @bx@, vertically aligned according to
+--   @algn@.
+alignVert :: Alignment -> Int -> Box -> Box
+alignVert a r b = Box r (cols b) $ SubBox AlignFirst a b
+
+-- | @align ah av r c bx@ creates an @r@ x @c@ box with the contents
+--   of @bx@, aligned horizontally according to @ah@ and vertically
+--   according to @av@.
+align :: Alignment -> Alignment -> Int -> Int -> Box -> Box
+align ah av r c = Box r c . SubBox ah av
+
+-- | Move a box \"up\" by putting it in a larger box with extra rows,
+--   aligned to the top.  See the disclaimer for 'moveLeft'.
+moveUp :: Int -> Box -> Box
+moveUp n b = alignVert top (rows b + n) b
+
+-- | Move a box down by putting it in a larger box with extra rows,
+--   aligned to the bottom.  See the disclaimer for 'moveLeft'.
+moveDown :: Int -> Box -> Box
+moveDown n b = alignVert bottom (rows b + n) b
+
+-- | Move a box left by putting it in a larger box with extra columns,
+--   aligned left.  Note that the name of this function is
+--   something of a white lie, as this will only result in the box
+--   being moved left by the specified amount if it is already in a
+--   larger right-aligned context.
+moveLeft :: Int -> Box -> Box
+moveLeft n b = alignHoriz left (cols b + n) b
+
+-- | Move a box right by putting it in a larger box with extra
+--   columns, aligned right.  See the disclaimer for 'moveLeft'.
+moveRight :: Int -> Box -> Box
+moveRight n b = alignHoriz right (cols b + n) b
+
+--------------------------------------------------------------------------------
+--  Implementation  ------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+-- | Render a 'Box' as a String, suitable for writing to the screen or
+--   a file.
+render :: Box -> String
+render = unlines . renderBox
+
+-- XXX make QC properties for takeP
+
+-- | \"Padded take\": @takeP a n xs@ is the same as @take n xs@, if @n
+--   <= length xs@; otherwise it is @xs@ followed by enough copies of
+--   @a@ to make the length equal to @n@.
+takeP :: a -> Int -> [a] -> [a]
+takeP _ n _      | n <= 0 = []
+takeP b n []              = replicate n b
+takeP b n (x:xs)          = x : takeP b (n-1) xs
+
+-- | @takePA @ is like 'takeP', but with alignment.  That is, we
+--   imagine a copy of @xs@ extended infinitely on both sides with
+--   copies of @a@, and a window of size @n@ placed so that @xs@ has
+--   the specified alignment within the window; @takePA algn a n xs@
+--   returns the contents of this window.
+takePA :: Alignment -> a -> Int -> [a] -> [a]
+takePA c b n = glue . (takeP b (numRev c n) *** takeP b (numFwd c n)) . split
+  where split t = first reverse . splitAt (numRev c (length t)) $ t
+        glue    = uncurry (++) . first reverse
+        numFwd AlignFirst    n = n
+        numFwd AlignLast     _ = 0
+        numFwd AlignCenter1  n = n `div` 2
+        numFwd AlignCenter2  n = (n+1) `div` 2
+        numRev AlignFirst    _ = 0
+        numRev AlignLast     n = n
+        numRev AlignCenter1  n = (n+1) `div` 2
+        numRev AlignCenter2  n = n `div` 2
+
+-- | Generate a string of spaces.
+blanks :: Int -> String
+blanks = flip replicate ' '
+
+-- | Render a box as a list of lines.
+renderBox :: Box -> [String]
+
+renderBox (Box r c Blank)            = resizeBox r c [""]
+renderBox (Box r c (Text t))         = resizeBox r c [t]
+renderBox (Box r c (Row bs))         = resizeBox r c
+                                       . merge
+                                       . map (renderBoxWithRows r)
+                                       $ bs
+                           where merge = foldr (zipWith (++)) (repeat [])
+
+renderBox (Box r c (Col bs))         = resizeBox r c
+                                       . concatMap (renderBoxWithCols c)
+                                       $ bs
+
+renderBox (Box r c (SubBox ha va b)) = resizeBoxAligned r c ha va
+                                       . renderBox
+                                       $ b
+
+-- | Render a box as a list of lines, using a given number of rows.
+renderBoxWithRows :: Int -> Box -> [String]
+renderBoxWithRows r b = renderBox (b{rows = r})
+
+-- | Render a box as a list of lines, using a given number of columns.
+renderBoxWithCols :: Int -> Box -> [String]
+renderBoxWithCols c b = renderBox (b{cols = c})
+
+-- | Resize a rendered list of lines.
+resizeBox :: Int -> Int -> [String] -> [String]
+resizeBox r c = takeP (blanks c) r . map (takeP ' ' c)
+
+-- | Resize a rendered list of lines, using given alignments.
+resizeBoxAligned :: Int -> Int -> Alignment -> Alignment -> [String] -> [String]
+resizeBoxAligned r c ha va = takePA va (blanks c) r . map (takePA ha ' ' c)
+
+-- | A convenience function for rendering a box to stdout.
+printBox :: Box -> IO ()
+printBox = putStr . render
diff --git a/boxes.cabal b/boxes.cabal
new file mode 100644
--- /dev/null
+++ b/boxes.cabal
@@ -0,0 +1,14 @@
+name:                boxes
+version:             0.1
+synopsis:            2D text pretty-printing library
+description:         A pretty-printing library for laying out text in
+                     two dimensions, using a simple box model.
+category:            Text
+license:             BSD3
+license-file:        LICENSE
+author:              Brent Yorgey
+maintainer:          byorgey@cis.upenn.edu
+homepage:            http://code.haskell.org/~byorgey/code/boxes
+build-Depends:       base >= 3 && < 5, split >=0.1 && <0.2
+build-type:          Simple
+exposed-modules:     Text.PrettyPrint.Boxes
