diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Franz Thoma (c) 2017
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * 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.
+
+    * Neither the name of Franz Thoma 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 COPYRIGHT HOLDERS AND CONTRIBUTORS
+"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 COPYRIGHT
+OWNER 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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,68 @@
+ASCII art to Unicode converter
+==============================
+
+Small CLI program to convert ASCII box drawings to unicode. Inspired
+by [svgbob](https://github.com/ivanceras/svgbobrus)
+and
+[The Monads Hidden Behind Every Zipper](http://blog.sigfpe.com/2007/01/monads-hidden-behind-every-zipper.html).
+
+
+```
+> aa2u
++-------------+             |
+| Hello World |             |  -----+
++-------------+             |       |     +--+
+                            |       +-----+  |
+----------------------------+                |
+                            |         +------+
+          +-----+---.       |         |
+          |     +---+       |   +--+  |
+      .---|         |---.   |   |  |  |
+      |   +---------+   |#  |   |  |  |
+      |                 |#  |   |  +--+
+      '-----------------'#  |   +---------+
+        ##################  |
+
++------+-----+---------+
+| This | is  | a table |
++------+-----+---------+
+| It   | has | some    |
++------+-----+---------+
+| values     |         |
++------+-----+---------+
+| 0    | 1   | 2  | 3  |
++------+-----+----+----+
+^D
+┌─────────────┐             │
+│ Hello World │             │  ─────┐
+└─────────────┘             │       │     ┌──┐
+                            │       └─────┘  │
+────────────────────────────┤                │
+                            │         ┌──────┘
+          ┌─────┬───╮       │         │
+          │     └───┤       │   ┌──┐  │
+      ╭───│         │───╮   │   │  │  │
+      │   └─────────┘   │█  │   │  │  │
+      │                 │█  │   │  └──┘
+      ╰─────────────────╯█  │   └──────────
+        ██████████████████  │
+
+┌──────┬─────┬─────────┐
+│ This │ is  │ a table │
+├──────┼─────┼─────────┤
+│ It   │ has │ some    │
+├──────┴─────┼─────────┤
+│ values     │         │
+├──────┬─────┼─────────┤
+│ 0    │ 1   │ 2  │ 3  │
+└──────┴─────┴────┴────┘
+```
+
+### Usage:
+
+```bash
+aa2u > outfile  # Reads from stdin
+aa2u < infile   # Prints to stdout
+aa2u infile     # Input file as argument works as well
+aa2u < infile > outfile  # Convert a file
+```
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,46 @@
+{-# LANGUAGE LambdaCase #-}
+module Main where
+
+import           System.Environment
+import qualified System.IO.Strict as Strict
+import           Text.AsciiArt
+
+data Mode
+    = Inplace FilePath
+    | Read FilePath
+    | Pipe
+
+main :: IO ()
+main = do
+    mode <- getArgs <&> \case
+        []           -> Pipe
+        ["-i", file] -> Inplace file
+        [file]       -> Read file
+        _            -> error "Usage: \n\
+                              \    aa2u            # reads from stdin, prints to stdout\n\
+                              \    aa2u FILE       # reads FILE, prints to stdout\n\
+                              \    aa2u -i FILE    # reads from and writes to FILE\n"
+    input <- case mode of
+        Inplace file -> Strict.readFile file
+        Read    file -> readFile file
+        Pipe         -> getContents
+    let inputLines = lines input
+        plane = planeFromList ' ' inputLines
+        width = maximum (fmap length inputLines)
+        height = length inputLines
+    let unicodeArt
+            = unlines
+            . fmap trimRight
+            . planeToList height width
+            . renderAsciiToUnicode
+            $ plane
+    case mode of
+        Inplace file -> writeFile file unicodeArt
+        Read    _    -> putStr unicodeArt
+        Pipe         -> putStr unicodeArt
+
+trimRight :: String -> String
+trimRight = reverse . dropWhile (== ' ') . reverse
+
+(<&>) :: Functor f => f a -> (a -> b) -> f b
+(<&>) = flip (<$>)
diff --git a/ascii-art-to-unicode.cabal b/ascii-art-to-unicode.cabal
new file mode 100644
--- /dev/null
+++ b/ascii-art-to-unicode.cabal
@@ -0,0 +1,56 @@
+name:                ascii-art-to-unicode
+version:             0.1.0.0
+synopsis:            ASCII Art to Unicode Box Drawing converter
+description:
+  @aa2u@ converts ASCII Art box drawings to Unicode.
+  .
+  > > aa2u
+  > +-------------+
+  > | Hello World |
+  > +-------------+
+  > ^D
+  > ┌─────────────┐
+  > │ Hello World │
+  > └─────────────┘
+  .
+  For more examples see the @doctest@ suite in "Text.AsciiArt".
+homepage:            https://github.com/fmthoma/ascii-art-to-unicode#readme
+license:             BSD3
+license-file:        LICENSE
+author:              Franz Thoma
+maintainer:          f.m.thoma@googlemail.com
+copyright:           2017 Franz Thoma
+build-type:          Simple
+extra-source-files:  README.md
+                   , stack.yaml
+cabal-version:       >=1.10
+
+library
+  hs-source-dirs:      src
+  exposed-modules:     Text.AsciiArt
+  build-depends:       base >= 4.7 && < 5
+                     , comonad
+  default-language:    Haskell2010
+
+executable aa2u
+  hs-source-dirs:      app
+  main-is:             Main.hs
+  ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N
+  build-depends:       base
+                     , ascii-art-to-unicode
+                     , strict
+  default-language:    Haskell2010
+
+test-suite ascii-art-to-unicode-test
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      test
+  main-is:             Spec.hs
+  build-depends:       base
+                     , ascii-art-to-unicode
+                     , doctest
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
+  default-language:    Haskell2010
+
+source-repository head
+  type:     git
+  location: https://github.com/githubuser/ascii-art-to-unicode
diff --git a/src/Text/AsciiArt.hs b/src/Text/AsciiArt.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/AsciiArt.hs
@@ -0,0 +1,532 @@
+{- |
+=Basic Elements
+
+==Simple Boxes
+
+>>> :{
+aa2u "++  +-----+  +--+--+-----+  \n\
+     \++  +--+  |  |  |  |     |  \n\
+     \       |  |  +--+--+--+--+  \n\
+     \+---+  |  |  |     |  |  |  \n\
+     \+---+  +--+  +-----+--+--+  "
+:}
+┌┐  ┌─────┐  ┌──┬──┬─────┐
+└┘  └──┐  │  │  │  │     │
+       │  │  ├──┴──┼──┬──┤
+┌───┐  │  │  │     │  │  │
+└───┘  └──┘  └─────┴──┴──┘
+
+==Rounded Boxes
+
+>>> :{
+aa2u "..  .-----.  .--+--+-----.  \n\
+     \''  '--.  |  |  |  |     |  \n\
+     \       |  |  +--+--+--+--+  \n\
+     \.---.  |  |  |     |  |  |  \n\
+     \'---'  '--'  '-----+--+--'  "
+:}
+╭╮  ╭─────╮  ╭──┬──┬─────╮
+╰╯  ╰──╮  │  │  │  │     │
+       │  │  ├──┴──┼──┬──┤
+╭───╮  │  │  │     │  │  │
+╰───╯  ╰──╯  ╰─────┴──┴──╯
+
+==Dotted and double strokes
+
+>>> :{
+aa2u "++  .-----.  +==+==+=====+  \n\
+     \++  +==+  :  |  :  |     |  \n\
+     \       :  :  +==+==+==+==+  \n\
+     \+===+  :  :  |     |  :  |  \n\
+     \+---+  '--'  +=====+==+==+  "
+:}
+┌┐  ╭─────╮  ╒══╤══╤═════╕
+└┘  ╘══╕  ┆  │  ┆  │     │
+       ┆  ┆  ╞══╧══╪══╤══╡
+╒═══╕  ┆  ┆  │     │  ┆  │
+└───┘  ╰──╯  ╘═════╧══╧══╛
+
+==Cast shadows
+
+>>> :{
+aa2u "+-------------+   \n\
+     \|             |   \n\
+     \+---+     +---+#  \n\
+     \  ##|     |#####  \n\
+     \    |     |#      \n\
+     \    +-----+#      \n\
+     \      ######      "
+:}
+┌─────────────┐
+│             │
+└───┐     ┌───┘█
+  ██│     │█████
+    │     │█
+    └─────┘█
+      ██████
+
+=Properties
+
+==Idempotent
+
+Already rendered portions are not affected:
+
+>>> :{
+aa2u "┌┐  ╭─────╮  ╒══╤══╤═════╕  ┌───┐   \n\
+     \└┘  ╘══╕  ┆  │  ┆  │     │  │   │   \n\
+     \       ┆  ┆  ╞══╧══╪══╤══╡  │   │█  \n\
+     \╒═══╕  ┆  ┆  │     │  ┆  │  └───┘█  \n\
+     \└───┘  ╰──╯  ╘═════╧══╧══╛    ████  "
+:}
+┌┐  ╭─────╮  ╒══╤══╤═════╕  ┌───┐
+└┘  ╘══╕  ┆  │  ┆  │     │  │   │
+       ┆  ┆  ╞══╧══╪══╤══╡  │   │█
+╒═══╕  ┆  ┆  │     │  ┆  │  └───┘█
+└───┘  ╰──╯  ╘═════╧══╧══╛    ████
+
+==Incremental
+
+Existing characters can be removed:
+
+>>> :{
+aa2u "┌──┬ ┬──┐\n\
+     \   │ │  │\n\
+     \╞══╪ ╪══╡\n\
+     \│        \n\
+     \├──┼ ┼──┤\n\
+     \   │ │  │\n\
+     \└──┴ ┴──┘"
+:}
+───┐ ┌──┐
+   │ │  │
+╒══╛ ╘══╛
+│
+└──┐ ┌──┐
+   │ │  │
+───┘ └──┘
+
+New connections can be added:
+
+>>> :{
+aa2u "┌──┐-┌──┐\n\
+     \│  │ │  │\n\
+     \╘══╛=╘══╛\n\
+     \|  : :  |\n\
+     \┌──┐-┌──┐\n\
+     \│  │ │  │\n\
+     \└──┘-└──┘"
+:}
+┌──┬─┬──┐
+│  │ │  │
+╞══╪═╪══╡
+│  ┆ ┆  │
+├──┼─┼──┤
+│  │ │  │
+└──┴─┴──┘
+
+Existing connections can be altered by replacing/adding characters:
+
+>>> :{
+aa2u "┌──+──┐  .─────+─────.  ┌────┐    \n\
+     \│  +==+  │     |     │  │####│    \n\
+     \+==+  |  │     |     │  │#   │█   \n\
+     \└──+─-┘  │     +-----+  └────┘█#  \n\
+     \         +=====+     │    █████#  \n\
+     \╭──+─-╮  │     |     │     #####  \n\
+     \│  |  |  │     |     │            \n\
+     \╰──+──╯  '───────────'            "
+:}
+┌──┬──┐  ╭─────┬─────╮  ┌────┐
+│  ╞══╡  │     │     │  │████│
+╞══╡  │  │     │     │  │█   │█
+└──┴──┘  │     ├─────┤  └────┘██
+         ╞═════╡     │    ██████
+╭──┬──╮  │     │     │     █████
+│  │  │  │     │     │
+╰──┴──╯  ╰───────────╯
+
+=Limitations
+
+Some connections do not work as expected (mostly because the corresponding
+Unicode characters do not exist), e.g. rounded corners with double-stroke lines,
+or connection pieces connecting horizontal single- and double-stroke lines:
+
+>>> :{
+aa2u "--+==  .==.  .--.--.   \n\
+     \  |    |  |  |  |  |   \n\
+     \==+--  '=='  .--+--'   \n\
+     \  |          |  |  |   \n\
+     \--+==  --==  '--'--'   "
+:}
+──┐══  ╒══╕  ╭──╮──╮
+  │    │  │  │  │  │
+══├──  ╘══╛  ╰──┼──╯
+  │          │  │  │
+──┘══  ──══  ╰──╰──╯
+
+-}
+{-# LANGUAGE DeriveFunctor   #-}
+{-# LANGUAGE LambdaCase      #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Text.AsciiArt where
+
+import           Control.Comonad
+import           Data.Maybe
+
+
+
+--------------------------------------------------------------------------------
+-- * Zipper
+--------------------------------------------------------------------------------
+
+-- | The 'Zipper' is assumed to be infinite, i.e. filled with empty values
+-- outside the defined area.
+data Zipper a = Zipper
+    { before  :: [a]
+    , current :: a
+    , after   :: [a] }
+    deriving (Functor)
+
+moveBefore, moveAfter :: Zipper a -> Zipper a
+moveBefore zipper@Zipper { before = a : as, current = b, after = cs }
+    = zipper { before = as, current = a, after = b : cs }
+moveAfter  zipper@Zipper { before = as, current = b, after = c : cs }
+    = zipper { before = b : as, current = c, after = cs }
+
+-- | Renders the 'current' and the @n - 1@ elements 'after' as list.
+zipperToList :: Int -> Zipper a -> [a]
+zipperToList n Zipper{..} = current : take (n - 1) after
+
+-- | An infinite 'Zipper' filled with @a@s.
+zipperOf :: a -> Zipper a
+zipperOf a = Zipper { before = repeat a, current = a, after = repeat a }
+
+-- | Takes a list and creates a 'Zipper' from it. The 'current' element will be
+-- the 'head' of the list, and 'after' that 'tail'. The rest will be filled with
+-- @a@s to an infinite 'Zipper'.
+zipperFromList :: a -> [a] -> Zipper a
+zipperFromList a = \case
+    []     -> zipperOf a
+    b : bs -> (zipperOf a) { current = b, after = bs ++ repeat a }
+
+
+instance Comonad Zipper where
+    extract = current
+    extend f zipper = fmap f $ Zipper
+        { before  = iterate1 moveBefore zipper
+        , current = zipper
+        , after   = iterate1 moveAfter zipper }
+      where
+        iterate1 f x = tail (iterate f x)
+
+
+
+--------------------------------------------------------------------------------
+-- * Plane (two-dimensional 'Zipper')
+--------------------------------------------------------------------------------
+
+-- | A plane is a 'Zipper' of 'Zipper's. The outer layer zips through lines
+-- (up\/down), the inner layer through columns (left\/right).
+-- Like the 'Zipper', the 'Plane' is assumed to be infinite in all directions.
+newtype Plane a = Plane { unPlane :: Zipper (Zipper a) }
+    deriving (Functor)
+
+moveLeft, moveRight, moveUp, moveDown :: Plane a -> Plane a
+moveLeft  = Plane . fmap moveBefore . unPlane
+moveRight = Plane . fmap moveAfter  . unPlane
+moveUp    = Plane . moveBefore      . unPlane
+moveDown  = Plane . moveAfter       . unPlane
+
+
+-- | Renders @m@ lines and @n@ columns as nested list.
+planeToList :: Int -> Int -> Plane a -> [[a]]
+planeToList m n (Plane Zipper{..}) = fmap (zipperToList n) $
+    current : take (m - 1) after
+
+-- | An infinite 'Plane' filled with @a@s.
+planeOf :: a -> Plane a
+planeOf a = Plane $ Zipper
+    { before  = repeat (zipperOf a)
+    , current = zipperOf a
+    , after   = repeat (zipperOf a) }
+
+-- | Create a 'Plane' from a list of lists, filling the rest with @a@s in all
+-- directions.
+planeFromList :: a -> [[a]] -> Plane a
+planeFromList a = \case
+    []       -> planeOf a
+    as : ass -> Plane $ (zipperOf (zipperOf a))
+        { current = zipperFromList a as
+        , after = fmap (zipperFromList a) ass ++ repeat (zipperOf a) }
+
+
+instance Comonad Plane where
+    extract = current . current . unPlane
+    extend f plane = fmap f $ Plane $ Zipper
+        { before  = fmap foo (iterate1 moveUp plane)
+        , current = foo plane
+        , after   = fmap foo (iterate1 moveDown plane) }
+      where
+        foo p = Zipper
+            { before = iterate1 moveLeft p
+            , current = p
+            , after = iterate1 moveRight p }
+        iterate1 f x = tail (iterate f x)
+
+
+
+--------------------------------------------------------------------------------
+-- * Patterns
+--------------------------------------------------------------------------------
+
+newtype Pattern = Pattern ((Char, Char, Char), (Char, Char, Char), (Char, Char, Char))
+
+
+patternFromString :: String -> Pattern
+patternFromString [a, b, c, d, e, f, g, h, i] = Pattern ((a, b, c), (d, e, f), (g, h, i))
+patternFromString _ = undefined
+
+patternToString :: Pattern -> String
+patternToString (Pattern ((a, b, c), (d, e, f), (g, h, i))) = [a, b, c, d, e, f, g, h, i]
+
+-- | Find the 'Char' to replace the center of a 'Pattern'.
+lookupPattern :: Pattern -> Maybe Char
+lookupPattern pattern = case filter (satisfies pattern) patterns of
+    []    -> Nothing
+    a : _ -> Just (snd a)
+  where
+    satisfies :: Pattern -> (Pattern, Char) -> Bool
+    satisfies diagram (pattern, _)
+        = and (zipWith connectsLike (patternToString diagram) (patternToString pattern))
+
+-- | Whether a character can connect to another character. For example, @+@
+-- connects both horizontally (like @-@) and vertically (like @|@), so it
+-- 'connectsLike' @-@, @|@, and of course like itself.
+connectsLike :: Char -> Char -> Bool
+char `connectsLike` pattern = case pattern of
+    '-'   -> char `elem` ['-', '>', '<', '─'] || char `connectsLike` '+'
+    '='   -> char `elem` ['=', '>', '<', '═'] || char `connectsLike` '+'
+    '|'   -> char `elem` ['|', '^', 'v', '│'] || char `connectsLike` ':' || char `connectsLike` '+'
+    ':'   -> char `elem` [':', '┆']
+    '+'   -> char `elem` [ '+'
+                         , '└', '┘', '┌', '┐'
+                         , '╘', '╛', '╒', '╕'
+                         , '├', '┤', '┬', '┴', '┼'
+                         , '╞', '╡', '╤', '╧', '╪' ]
+                         || char `connectsLike` '.'
+    '.'   -> char `elem` [ '\'', '.'
+                         , '╭', '╮', '╯', '╰' ]
+    '\''  -> char `connectsLike` '.'
+    ' '   -> True
+    other -> char == other
+
+-- | The actual pattern definitions. For convenience, the simple patterns are at
+-- the top, and more complex ones at the bottom. 'lookupPattern' will first try
+-- the most complex pattern and work its way to the simpler patterns, thus
+-- avoiding to choose a simpler pattern and forgetting some connection.
+patterns :: [(Pattern, Char)]
+patterns = reverse $ fmap (\(a, b) -> (patternFromString a, b))
+    [ ( "   \
+        \ --\
+        \   ", '─' )
+
+    , ( "   \
+        \-- \
+        \   ", '─' )
+
+    , ( "   \
+        \ ==\
+        \   ", '═' )
+
+    , ( "   \
+        \== \
+        \   ", '═' )
+
+    , ( "   \
+        \ | \
+        \ | ", '│' )
+
+    , ( " | \
+        \ | \
+        \   ", '│' )
+
+    , ( "   \
+        \ : \
+        \ | ", '┆' )
+
+    , ( " | \
+        \ : \
+        \   ", '┆' )
+
+    , ( "   \
+        \ : \
+        \ : ", '┆' )
+
+    , ( " : \
+        \ : \
+        \   ", '┆' )
+
+    , ( " | \
+        \=+ \
+        \   ", '╛' )
+
+    , ( " | \
+        \ +=\
+        \   ", '╘' )
+
+    , ( "   \
+        \ +=\
+        \ | ", '╒' )
+
+    , ( "   \
+        \=+ \
+        \ | ", '╕' )
+
+    , ( " | \
+        \ +=\
+        \ | ", '╞' )
+
+    , ( " | \
+        \=+ \
+        \ | ", '╡' )
+
+    , ( "   \
+        \=+=\
+        \ | ", '╤' )
+
+    , ( " | \
+        \=+=\
+        \   ", '╧' )
+
+    , ( " | \
+        \=+=\
+        \ | ", '╪' )
+
+    , ( " | \
+        \-+ \
+        \   ", '┘' )
+
+    , ( " | \
+        \ +-\
+        \   ", '└' )
+
+    , ( "   \
+        \ +-\
+        \ | ", '┌' )
+
+    , ( "   \
+        \-+ \
+        \ | ", '┐' )
+
+    , ( " | \
+        \ +-\
+        \ | ", '├' )
+
+    , ( " | \
+        \-+ \
+        \ | ", '┤' )
+
+    , ( "   \
+        \-+-\
+        \ | ", '┬' )
+
+    , ( " | \
+        \-+-\
+        \   ", '┴' )
+
+    , ( " | \
+        \-+-\
+        \ | ", '┼' )
+
+    , ( "   \
+        \ .-\
+        \ | ", '╭' )
+
+    , ( "   \
+        \-. \
+        \ | ", '╮' )
+
+    , ( " | \
+        \-' \
+        \   ", '╯' )
+
+    , ( " | \
+        \ '-\
+        \   ", '╰' )
+
+    , ( "   \
+        \ # \
+        \ # ", '█' )
+
+    , ( "   \
+        \## \
+        \   ", '█' )
+
+    , ( "   \
+        \ ##\
+        \   ", '█' )
+
+    , ( " # \
+        \ # \
+        \   ", '█' )
+
+    , ( "   \
+        \-> \
+        \   ", '▷' )
+
+    , ( "   \
+        \ <-\
+        \   ", '◁' )
+
+    , ( "   \
+        \ ^ \
+        \ | ", '△' )
+
+    , ( " | \
+        \ v \
+        \   ", '▽' )
+    ]
+
+
+
+--------------------------------------------------------------------------------
+-- * Transforming ASCII to Unicode
+--------------------------------------------------------------------------------
+
+-- | Match the 'current' element and its eight neighbours against the defined
+-- 'patterns' and choose the 'Char' from the matching 'Pattern'.
+substituteChar :: Plane Char -> Char
+substituteChar = \case
+    Plane ( Zipper ((Zipper (a : as) b (c : cs)) : _)
+                   ( Zipper (d : ds) e (f : fs))
+                   ((Zipper (g : gs) h (i : is)) : _) )
+        -> fromMaybe e (lookupPattern (patternFromString [a, b, c, d, e, f, g, h, i]))
+    _ -> undefined -- We assume an infinite Zipper!
+
+-- | Transform a 'Plane' of ASCII characters to an equivalent plane where the
+-- ASCII box drawings have been replaced by their Unicode counterpart.
+--
+-- This function is a convolution with 'substituteChar' using the 'Comonad'ic
+-- 'extend'.
+renderAsciiToUnicode :: Plane Char -> Plane Char
+renderAsciiToUnicode = extend substituteChar
+
+
+
+{- $setup
+>>> :{
+aa2u :: String -> IO ()
+aa2u input =
+    let inputLines = lines input
+        plane = planeFromList ' ' inputLines
+        width = maximum (fmap length inputLines)
+        height = length inputLines
+    in  putStr
+        . unlines
+        . fmap (reverse . dropWhile (== ' ') . reverse)
+        . planeToList height width
+        . renderAsciiToUnicode
+        $ plane
+:}
+-}
diff --git a/stack.yaml b/stack.yaml
new file mode 100644
--- /dev/null
+++ b/stack.yaml
@@ -0,0 +1,66 @@
+# This file was automatically generated by 'stack init'
+#
+# Some commonly used options have been documented as comments in this file.
+# For advanced use and comprehensive documentation of the format, please see:
+# http://docs.haskellstack.org/en/stable/yaml_configuration/
+
+# Resolver to choose a 'specific' stackage snapshot or a compiler version.
+# A snapshot resolver dictates the compiler version and the set of packages
+# to be used for project dependencies. For example:
+#
+# resolver: lts-3.5
+# resolver: nightly-2015-09-21
+# resolver: ghc-7.10.2
+# resolver: ghcjs-0.1.0_ghc-7.10.2
+# resolver:
+#  name: custom-snapshot
+#  location: "./custom-snapshot.yaml"
+resolver: lts-8.5
+
+# User packages to be built.
+# Various formats can be used as shown in the example below.
+#
+# packages:
+# - some-directory
+# - https://example.com/foo/bar/baz-0.0.2.tar.gz
+# - location:
+#    git: https://github.com/commercialhaskell/stack.git
+#    commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
+# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a
+#   extra-dep: true
+#  subdirs:
+#  - auto-update
+#  - wai
+#
+# A package marked 'extra-dep: true' will only be built if demanded by a
+# non-dependency (i.e. a user package), and its test suites and benchmarks
+# will not be run. This is useful for tweaking upstream packages.
+packages:
+- '.'
+# Dependency packages to be pulled from upstream that are not in the resolver
+# (e.g., acme-missiles-0.3)
+extra-deps: []
+
+# Override default flag values for local packages and extra-deps
+flags: {}
+
+# Extra package databases containing global packages
+extra-package-dbs: []
+
+# Control whether we use the GHC we find on the path
+# system-ghc: true
+#
+# Require a specific version of stack, using version ranges
+# require-stack-version: -any # Default
+# require-stack-version: ">=1.3"
+#
+# Override the architecture used by stack, especially useful on Windows
+# arch: i386
+# arch: x86_64
+#
+# Extra directories used by stack for building
+# extra-include-dirs: [/path/to/dir]
+# extra-lib-dirs: [/path/to/dir]
+#
+# Allow a newer minor version of GHC than the snapshot specifies
+# compiler-check: newer-minor
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,12 @@
+import Test.DocTest
+
+main :: IO ()
+main =
+    let extensions =
+            [ "-XLambdaCase"
+            , "-XMultiWayIf"
+            , "-XOverloadedStrings" ]
+        sourceFolders =
+            [ "src"
+            , "app" ]
+    in  doctest (extensions ++ sourceFolders)
