diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Changelog
+
+`pdfreader` uses [PVP Versioning][1].
+
+## 0.1.0.0
+
+* Initially created.
+
+[1]: https://pvp.haskell.org
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Scott Sedgwick
+
+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.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+# Data.Pdf.FieldReader
+
+[![Hackage](https://img.shields.io/hackage/v/pdfreader.svg?logo=haskell)](https://hackage.haskell.org/package/pdfreader)
+[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
+
+This is a minimal library for reading the data in form fields out of a PDF document.
+
+A trivial example of how to use this library can be found in `app/Main.hs` in the source repository.
diff --git a/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,27 @@
+module Main (main) where
+
+import Data.Pdf.FieldReader (readPdfFields)
+import qualified Data.ByteString as B
+import Options.Applicative
+
+data Options = Options {
+  datafile :: String
+}
+
+options :: Parser Options
+options = Options
+  <$> strOption ( short 'f' <> long "datafile" <> metavar "DATAFILE" <> help "PDF file to read" )
+  
+main :: IO ()
+main = run =<< execParser opts
+  where
+    opts = info (options <**> helper)
+      ( fullDesc
+     <> progDesc "Read a PDF file and print all the values in its form fields"
+     <> header "pdfreader - a test for Data.Pdf.FieldReader" )
+     
+run :: Options -> IO()
+run opts = do
+  xs <- B.readFile (datafile opts)
+  let ys = readPdfFields xs
+  print ys
diff --git a/data-pdf-fieldreader.cabal b/data-pdf-fieldreader.cabal
new file mode 100644
--- /dev/null
+++ b/data-pdf-fieldreader.cabal
@@ -0,0 +1,67 @@
+cabal-version:       2.4
+name:                data-pdf-fieldreader
+version:             0.1.0.0
+description:         Simple function to extract PDF form field values from a PDF file.
+synopsis:            Read PDF form fields
+category:            Data
+homepage:            https://github.com/ScottSedgwick/Data.Pdf.FieldReader
+license:             MIT
+license-file:        LICENSE
+author:              Scott Sedgwick
+maintainer:          Scott Sedgwick <scott.sedgwick@gmail.com>
+copyright:           2021 Scott Sedgwick
+build-type:          Simple
+extra-doc-files:     README.md
+                     CHANGELOG.md
+tested-with:         GHC == 8.8.3
+
+common common-options
+  build-depends:       base == 4.*
+                     , bytestring >= 0.10.12 && < 0.11
+                     , containers >= 0.6.4 && < 0.7
+                     , text >= 1.2.4 && < 1.3
+  
+  ghc-options:         -Wall
+                       -Wcompat
+                       -Widentities
+                       -Wincomplete-uni-patterns
+                       -Wincomplete-record-updates
+  if impl(ghc >= 8.0)
+    ghc-options:       -Wredundant-constraints
+  if impl(ghc >= 8.2)
+    ghc-options:       -fhide-source-paths
+  if impl(ghc >= 8.4)
+    ghc-options:       -Wmissing-export-lists
+                       -Wpartial-fields
+  if impl(ghc >= 8.8)
+    ghc-options:       -Wmissing-deriving-strategies
+
+  default-language:    Haskell2010
+  default-extensions:  ConstraintKinds
+                       DeriveGeneric
+                       GeneralizedNewtypeDeriving
+                       InstanceSigs
+                       KindSignatures
+                       LambdaCase
+                       OverloadedStrings
+                       RecordWildCards
+                       ScopedTypeVariables
+                       StandaloneDeriving
+                       TupleSections
+                       TypeApplications
+                       ViewPatterns
+
+library
+  import:              common-options
+  hs-source-dirs:      src
+  exposed-modules:     Data.Pdf.FieldReader
+
+executable pdfreader
+  import:              common-options
+  hs-source-dirs:      app
+  main-is:             Main.hs
+  build-depends:       data-pdf-fieldreader
+                     , optparse-applicative >= 0.16.1 && < 0.17
+  ghc-options:         -threaded
+                       -rtsopts
+                       -with-rtsopts=-N
diff --git a/src/Data/Pdf/FieldReader.hs b/src/Data/Pdf/FieldReader.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Pdf/FieldReader.hs
@@ -0,0 +1,55 @@
+{- |
+Copyright: (c) 2021 Scott Sedgwick
+SPDX-License-Identifier: MIT
+Maintainer: Scott Sedgwick <scott.sedgwick@gmail.com>
+Stability: experimental
+Portability: unknown
+
+Simple function to extract PDF form field values from a PDF file.
+-}
+
+module Data.Pdf.FieldReader
+  ( -- * File data parser
+    --
+    -- $fileDataParser
+    readPdfFields
+  ) where
+
+import Prelude hiding (drop, init, lines)
+import Data.ByteString (ByteString)
+import Data.ByteString.Char8 (unpack)
+import Data.List (foldl')
+import Data.Map (Map, fromList)
+import Data.Text (Text, drop, init, isPrefixOf, lines, pack, replace, strip)
+
+-- $fileDataParser
+-- 
+-- Extract a Map of name-value pairs from the data read from a PDF file.
+-- For example:
+--
+-- >import qualified Data.ByteString as B
+-- >import Data.Pdf.FieldReader (readPdfFields)
+-- >
+-- >main :: IO()
+-- >main = do
+-- >  xs <- Data.ByteString.readFile "filename"
+-- >  let ys = readPdfFields xs
+-- >  print ys
+
+-- | Read fields from file data
+readPdfFields :: ByteString -> Map Text Text
+readPdfFields = fromList . snd . foldl' f (Nothing, []) . lines . pack . unpack
+  where
+    f (Nothing,  b) x | isFldName x  = (Just (fmtFldName x), b) 
+                      | otherwise    = (Nothing, b)
+    f ((Just n), b) x | isFldName x  = (Just (fmtFldName x), b)
+                      | isFldValue x = (Nothing, (n, (fmtFldValue x)) : b)
+                      | otherwise    = ((Just n), b)
+    isFldName     = isPrefixOf "/T("
+    fmtFldName    = stripBrackets
+    isFldValue    = isPrefixOf "/V("
+    fmtFldValue   = unescape . stripBrackets
+    stripBrackets = init . drop 3 . strip
+    unescape xs   = foldr (\(a, b) c -> replace a b c) xs escPairs
+    escPairs      = [("\\n", "\n"), ("\\(", "("), ("\\)", ")")]
+       
