diff --git a/Data/Text.hs b/Data/Text.hs
--- a/Data/Text.hs
+++ b/Data/Text.hs
@@ -79,6 +79,7 @@
     , toCaseFold
     , toLower
     , toUpper
+    , toTitle
 
     -- ** Justification
     , justifyLeft
@@ -177,7 +178,7 @@
     , findIndex
     , count
 
-    -- * Zipping and unzipping
+    -- * Zipping
     , zip
     , zipWith
 
@@ -201,11 +202,7 @@
 #endif
 import Data.Char (isSpace)
 import Data.Data (Data(gfoldl, toConstr, gunfold, dataTypeOf))
-#if __GLASGOW_HASKELL__ >= 612
 import Data.Data (mkNoRepType)
-#else
-import Data.Data (mkNorepType)
-#endif
 import Control.Monad (foldM)
 import qualified Data.Text.Array as A
 import qualified Data.List as L
@@ -350,11 +347,7 @@
   gfoldl f z txt = z pack `f` (unpack txt)
   toConstr _     = P.error "Data.Text.Text.toConstr"
   gunfold _ _    = P.error "Data.Text.Text.gunfold"
-#if __GLASGOW_HASKELL__ >= 612
   dataTypeOf _   = mkNoRepType "Data.Text.Text"
-#else
-  dataTypeOf _   = mkNorepType "Data.Text.Text"
-#endif
 
 -- | /O(n)/ Compare two 'Text' values lexicographically.
 compareText :: Text -> Text -> Ordering
@@ -623,12 +616,29 @@
 --
 -- In (unlikely) bad cases, this function's time complexity degrades
 -- towards /O(n*m)/.
-replace :: Text                 -- ^ Text to search for
-        -> Text                 -- ^ Replacement text
-        -> Text                 -- ^ Input text
-        -> Text
-replace s d = intercalate d . splitOn s
-{-# INLINE replace #-}
+replace :: Text -> Text -> Text -> Text
+replace needle@(Text _      _      neeLen)
+               (Text repArr repOff repLen)
+      haystack@(Text hayArr hayOff hayLen)
+  | neeLen == 0 = emptyError "replace"
+  | L.null ixs  = haystack
+  | len > 0     = Text (A.run x) 0 len
+  | len < 0     = overflowError "replace"
+  | otherwise   = empty
+  where
+    ixs = indices needle haystack
+    len = hayLen - (neeLen - repLen) * L.length ixs
+    x = do
+      marr <- A.new len
+      let loop (i:is) o d = do
+            let d0 = d + i - o
+                d1 = d0 + repLen
+            A.copyI marr d  hayArr (hayOff+o) d0
+            A.copyI marr d0 repArr repOff d1
+            loop is (i + neeLen) d1
+          loop []     o d = A.copyI marr d hayArr (hayOff+o) len
+      loop ixs 0 0
+      return marr
 
 -- ----------------------------------------------------------------------------
 -- ** Case conversions (folds)
@@ -651,10 +661,11 @@
 -- functions from the @text-icu@ package:
 -- <http://hackage.haskell.org/package/text-icu>
 
--- | /O(n)/ Convert a string to folded case.  This function is mainly
--- useful for performing caseless (also known as case insensitive)
--- string comparisons.
+-- | /O(n)/ Convert a string to folded case.  Subject to fusion.
 --
+-- This function is mainly useful for performing caseless (also known
+-- as case insensitive) string comparisons.
+--
 -- A string @x@ is a caseless match for a string @y@ if and only if:
 --
 -- @toCaseFold x == toCaseFold y@
@@ -671,21 +682,47 @@
 {-# INLINE [0] toCaseFold #-}
 
 -- | /O(n)/ Convert a string to lower case, using simple case
--- conversion.  The result string may be longer than the input string.
--- For instance, \"&#x130;\" (Latin capital letter I with dot above,
--- U+0130) maps to the sequence \"i\" (Latin small letter i, U+0069) followed
--- by \" &#x307;\" (combining dot above, U+0307).
+-- conversion.  Subject to fusion.
+--
+-- The result string may be longer than the input string.  For
+-- instance, \"&#x130;\" (Latin capital letter I with dot above,
+-- U+0130) maps to the sequence \"i\" (Latin small letter i, U+0069)
+-- followed by \" &#x307;\" (combining dot above, U+0307).
 toLower :: Text -> Text
 toLower t = unstream (S.toLower (stream t))
 {-# INLINE toLower #-}
 
 -- | /O(n)/ Convert a string to upper case, using simple case
--- conversion.  The result string may be longer than the input string.
--- For instance, the German \"&#xdf;\" (eszett, U+00DF) maps to the
+-- conversion.  Subject to fusion.
+--
+-- The result string may be longer than the input string.  For
+-- instance, the German \"&#xdf;\" (eszett, U+00DF) maps to the
 -- two-letter sequence \"SS\".
 toUpper :: Text -> Text
 toUpper t = unstream (S.toUpper (stream t))
 {-# INLINE toUpper #-}
+
+-- | /O(n)/ Convert a string to title case, using simple case
+-- conversion. Subject to fusion.
+--
+-- The first letter of the input is converted to title case, as is
+-- every subsequent letter that immediately follows a non-letter.
+-- Every letter that immediately follows another letter is converted
+-- to lower case.
+--
+-- The result string may be longer than the input string. For example,
+-- the Latin small ligature &#xfb02; (U+FB02) is converted to the
+-- sequence Latin capital letter F (U+0046) followed by Latin small
+-- letter l (U+006C).
+--
+-- /Note/: this function does not take language or culture specific
+-- rules into account. For instance, in English, different style
+-- guides disagree on whether the book name \"The Hill of the Red
+-- Fox\" is correctly title cased&#x2014;but this function will
+-- capitalize /every/ word.
+toTitle :: Text -> Text
+toTitle t = unstream (S.toTitle (stream t))
+{-# INLINE toTitle #-}
 
 -- | /O(n)/ Left-justify a string to the given length, using the
 -- specified fill character on the right. Subject to fusion.
diff --git a/Data/Text/Encoding.hs b/Data/Text/Encoding.hs
--- a/Data/Text/Encoding.hs
+++ b/Data/Text/Encoding.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE BangPatterns, CPP, ForeignFunctionInterface, MagicHash,
+{-# LANGUAGE BangPatterns, CPP, ForeignFunctionInterface, GeneralizedNewtypeDeriving, MagicHash,
     UnliftedFFITypes #-}
 #if __GLASGOW_HASKELL__ >= 702
 {-# LANGUAGE Trustworthy #-}
@@ -43,6 +43,12 @@
     , decodeUtf32LEWith
     , decodeUtf32BEWith
 
+    -- ** Stream oriented decoding
+    -- $stream
+    , streamDecodeUtf8
+    , streamDecodeUtf8With
+    , Decoding(..)
+
     -- * Encoding Text to ByteStrings
     , encodeUtf8
     , encodeUtf16LE
@@ -57,20 +63,22 @@
 #else
 import Control.Monad.ST (unsafeIOToST, unsafeSTToIO)
 #endif
+import Control.Monad.ST (runST)
 import Data.Bits ((.&.))
 import Data.ByteString as B
 import Data.ByteString.Internal as B
+import Data.Text ()
 import Data.Text.Encoding.Error (OnDecodeError, UnicodeException, strictDecode)
 import Data.Text.Internal (Text(..), safe, textP)
 import Data.Text.Private (runText)
 import Data.Text.UnsafeChar (ord, unsafeWrite)
 import Data.Text.UnsafeShift (shiftL, shiftR)
-import Data.Word (Word8)
+import Data.Word (Word8, Word32)
 import Foreign.C.Types (CSize)
 import Foreign.ForeignPtr (withForeignPtr)
 import Foreign.Marshal.Utils (with)
-import Foreign.Ptr (Ptr, minusPtr, plusPtr)
-import Foreign.Storable (peek, poke)
+import Foreign.Ptr (Ptr, minusPtr, nullPtr, plusPtr)
+import Foreign.Storable (Storable, peek, poke)
 import GHC.Base (MutableByteArray#)
 import qualified Data.Text.Array as A
 import qualified Data.Text.Encoding.Fusion as E
@@ -78,6 +86,8 @@
 import qualified Data.Text.Fusion as F
 import Data.Text.Unsafe (unsafeDupablePerformIO)
 
+#include "text_cbits.h"
+
 -- $strict
 --
 -- All of the single-parameter functions for decoding bytestrings
@@ -139,6 +149,126 @@
   desc = "Data.Text.Encoding.decodeUtf8: Invalid UTF-8 stream"
 {- INLINE[0] decodeUtf8With #-}
 
+-- $stream
+--
+-- The 'streamDecodeUtf8' and 'streamDecodeUtf8With' functions accept
+-- a 'ByteString' that represents a possibly incomplete input (e.g. a
+-- packet from a network stream) that may not end on a UTF-8 boundary.
+--
+-- The first element of the result is the maximal chunk of 'Text' that
+-- can be decoded from the given input. The second is a function which
+-- accepts another 'ByteString'. That string will be assumed to
+-- directly follow the string that was passed as input to the original
+-- function, and it will in turn be decoded.
+--
+-- To help understand the use of these functions, consider the Unicode
+-- string @\"hi &#9731;\"@. If encoded as UTF-8, this becomes @\"hi
+-- \\xe2\\x98\\x83\"@; the final @\'&#9731;\'@ is encoded as 3 bytes.
+--
+-- Now suppose that we receive this encoded string as 3 packets that
+-- are split up on untidy boundaries: @[\"hi \\xe2\", \"\\x98\",
+-- \"\\x83\"]@. We cannot decode the entire Unicode string until we
+-- have received all three packets, but we would like to make progress
+-- as we receive each one.
+--
+-- @
+-- let 'Some' t0 f0 = 'streamDecodeUtf8' \"hi \\xe2\"
+-- t0 == \"hi \" :: 'Text'
+-- @
+--
+-- We use the continuation @f0@ to decode our second packet.
+--
+-- @
+-- let 'Some' t1 f1 = f0 \"\\x98\"
+-- t1 == \"\"
+-- @
+--
+-- We could not give @f0@ enough input to decode anything, so it
+-- returned an empty string. Once we feed our second continuation @f1@
+-- the last byte of input, it will make progress.
+--
+-- @
+-- let 'Some' t2 f2 = f1 \"\\x83\"
+-- t2 == \"&#9731;\"
+-- @
+--
+-- If given invalid input, an exception will be thrown by the function
+-- or continuation where it is encountered.
+
+-- | A stream oriented decoding result.
+data Decoding = Some Text ByteString (ByteString -> Decoding)
+
+instance Show Decoding where
+    showsPrec d (Some t bs _) = showParen (d > prec) $
+                                showString "Some " . showsPrec prec' t .
+                                showChar ' ' . showsPrec prec' bs .
+                                showString " _"
+      where prec = 10; prec' = prec + 1
+
+newtype CodePoint = CodePoint Word32 deriving (Eq, Show, Num, Storable)
+newtype DecoderState = DecoderState Word32 deriving (Eq, Show, Num, Storable)
+
+-- | Decode, in a stream oriented way, a 'ByteString' containing UTF-8
+-- encoded text that is known to be valid.
+--
+-- If the input contains any invalid UTF-8 data, an exception will be
+-- thrown (either by this function or a continuation) that cannot be
+-- caught in pure code.  For more control over the handling of invalid
+-- data, use 'streamDecodeUtf8With'.
+streamDecodeUtf8 :: ByteString -> Decoding
+streamDecodeUtf8 = streamDecodeUtf8With strictDecode
+
+-- | Decode, in a stream oriented way, a 'ByteString' containing UTF-8
+-- encoded text.
+streamDecodeUtf8With :: OnDecodeError -> ByteString -> Decoding
+streamDecodeUtf8With onErr = decodeChunk 0 0
+ where
+  -- We create a slightly larger than necessary buffer to accommodate a
+  -- potential surrogate pair started in the last buffer
+  decodeChunk :: CodePoint -> DecoderState -> ByteString -> Decoding
+  decodeChunk codepoint0 state0 bs@(PS fp off len) =
+    runST $ (unsafeIOToST . decodeChunkToBuffer) =<< A.new (len+1)
+   where
+    decodeChunkToBuffer :: A.MArray s -> IO Decoding
+    decodeChunkToBuffer dest = withForeignPtr fp $ \ptr ->
+      with (0::CSize) $ \destOffPtr ->
+      with codepoint0 $ \codepointPtr ->
+      with state0 $ \statePtr ->
+      with nullPtr $ \curPtrPtr ->
+        let end = ptr `plusPtr` (off + len)
+            loop curPtr = do
+              poke curPtrPtr curPtr
+              curPtr' <- c_decode_utf8_with_state (A.maBA dest) destOffPtr
+                         curPtrPtr end codepointPtr statePtr
+              state <- peek statePtr
+              case state of
+                UTF8_REJECT -> do
+                  -- We encountered an encoding error
+                  x <- peek curPtr'
+                  case onErr desc (Just x) of
+                    Nothing -> loop $ curPtr' `plusPtr` 1
+                    Just c -> do
+                      destOff <- peek destOffPtr
+                      w <- unsafeSTToIO $
+                           unsafeWrite dest (fromIntegral destOff) (safe c)
+                      poke destOffPtr (destOff + fromIntegral w)
+                      poke statePtr 0
+                      loop $ curPtr' `plusPtr` 1
+
+                _ -> do
+                  -- We encountered the end of the buffer while decoding
+                  n <- peek destOffPtr
+                  codepoint <- peek codepointPtr
+                  chunkText <- unsafeSTToIO $ do
+                      arr <- A.unsafeFreeze dest
+                      return $! textP arr 0 (fromIntegral n)
+                  lastPtr <- peek curPtrPtr
+                  let left = lastPtr `minusPtr` curPtr
+                  return $ Some chunkText (B.drop left bs)
+                           (decodeChunk codepoint state)
+        in loop (ptr `plusPtr` off)
+  desc = "Data.Text.Encoding.streamDecodeUtf8With: Invalid UTF-8 stream"
+
 -- | Decode a 'ByteString' containing UTF-8 encoded text that is known
 -- to be valid.
 --
@@ -152,7 +282,7 @@
 {-# RULES "STREAM stream/decodeUtf8 fusion" [1]
     forall bs. F.stream (decodeUtf8 bs) = E.streamUtf8 strictDecode bs #-}
 
--- | Decode a 'ByteString' containing UTF-8 encoded text..
+-- | Decode a 'ByteString' containing UTF-8 encoded text.
 --
 -- If the input contains any invalid UTF-8 data, the relevant
 -- exception will be returned, otherwise the decoded text.
@@ -295,6 +425,11 @@
 foreign import ccall unsafe "_hs_text_decode_utf8" c_decode_utf8
     :: MutableByteArray# s -> Ptr CSize
     -> Ptr Word8 -> Ptr Word8 -> IO (Ptr Word8)
+
+foreign import ccall unsafe "_hs_text_decode_utf8_state" c_decode_utf8_with_state
+    :: MutableByteArray# s -> Ptr CSize
+    -> Ptr (Ptr Word8) -> Ptr Word8
+    -> Ptr CodePoint -> Ptr DecoderState -> IO (Ptr Word8)
 
 foreign import ccall unsafe "_hs_text_decode_latin1" c_decode_latin1
     :: MutableByteArray# s -> Ptr Word8 -> Ptr Word8 -> IO ()
diff --git a/Data/Text/Encoding/Error.hs b/Data/Text/Encoding/Error.hs
--- a/Data/Text/Encoding/Error.hs
+++ b/Data/Text/Encoding/Error.hs
@@ -39,11 +39,7 @@
     ) where
 
 import Control.DeepSeq (NFData (..))
-#if __GLASGOW_HASKELL__ >= 610
 import Control.Exception (Exception, throw)
-#else
-import Control.Exception.Extensible (Exception, throw)
-#endif
 import Data.Typeable (Typeable)
 import Data.Word (Word8)
 import Numeric (showHex)
diff --git a/Data/Text/Foreign.hs b/Data/Text/Foreign.hs
--- a/Data/Text/Foreign.hs
+++ b/Data/Text/Foreign.hs
@@ -21,6 +21,9 @@
     , fromPtr
     , useAsPtr
     , asForeignPtr
+    -- ** Encoding as UTF-8
+    , peekCStringLen
+    , withCStringLen
     -- * Unsafe conversion code
     , lengthWord16
     , unsafeCopyToPtr
@@ -38,14 +41,17 @@
 #else
 import Control.Monad.ST (unsafeIOToST)
 #endif
+import Data.ByteString.Unsafe (unsafePackCStringLen, unsafeUseAsCStringLen)
+import Data.Text.Encoding (decodeUtf8, encodeUtf8)
 import Data.Text.Internal (Text(..), empty)
 import Data.Text.Unsafe (lengthWord16)
-import qualified Data.Text.Array as A
 import Data.Word (Word16)
+import Foreign.C.String (CStringLen)
+import Foreign.ForeignPtr (ForeignPtr, mallocForeignPtrArray, withForeignPtr)
 import Foreign.Marshal.Alloc (allocaBytes)
 import Foreign.Ptr (Ptr, castPtr, plusPtr)
-import Foreign.ForeignPtr (ForeignPtr, mallocForeignPtrArray, withForeignPtr)
 import Foreign.Storable (peek, poke)
+import qualified Data.Text.Array as A
 
 -- $interop
 --
@@ -148,3 +154,21 @@
   fp <- mallocForeignPtrArray len
   withForeignPtr fp $ unsafeCopyToPtr t
   return (fp, I16 len)
+
+-- | /O(n)/ Decode a C string with explicit length, which is assumed
+-- to have been encoded as UTF-8. If decoding fails, a
+-- 'UnicodeException' is thrown.
+peekCStringLen :: CStringLen -> IO Text
+peekCStringLen cs = do
+  bs <- unsafePackCStringLen cs
+  return $! decodeUtf8 bs
+
+-- | Marshal a 'Text' into a C string encoded as UTF-8 in temporary
+-- storage, with explicit length information. The encoded string may
+-- contain NUL bytes, and is not followed by a trailing NUL byte.
+--
+-- The temporary storage is freed when the subcomputation terminates
+-- (either normally or via an exception), so the pointer to the
+-- temporary storage must /not/ be used after this function returns.
+withCStringLen :: Text -> (CStringLen -> IO a) -> IO a
+withCStringLen t act = unsafeUseAsCStringLen (encodeUtf8 t) act
diff --git a/Data/Text/Fusion.hs b/Data/Text/Fusion.hs
--- a/Data/Text/Fusion.hs
+++ b/Data/Text/Fusion.hs
@@ -223,9 +223,9 @@
                                arr' <- A.new top'
                                A.copyM arr' 0 arr 0 top
                                outer arr' top' z s i
-                | otherwise -> do let (z',c) = f z x
-                                  d <- unsafeWrite arr i c
+                | otherwise -> do d <- unsafeWrite arr i c
                                   loop z' s' (i+d)
-                where j | ord x < 0x10000 = i
+                where (z',c) = f z x
+                      j | ord c < 0x10000 = i
                         | otherwise       = i + 1
 {-# INLINE [0] mapAccumL #-}
diff --git a/Data/Text/Fusion/CaseMapping.hs b/Data/Text/Fusion/CaseMapping.hs
--- a/Data/Text/Fusion/CaseMapping.hs
+++ b/Data/Text/Fusion/CaseMapping.hs
@@ -1,6 +1,11 @@
 {-# LANGUAGE Rank2Types #-}
 -- AUTOMATICALLY GENERATED - DO NOT EDIT
 -- Generated by scripts/SpecialCasing.hs
+-- CaseFolding-6.3.0.txt
+-- Date: 2012-12-20, 22:14:35 GMT [MD]
+-- SpecialCasing-6.3.0.txt
+-- Date: 2013-05-08, 13:54:51 GMT [MD]
+
 module Data.Text.Fusion.CaseMapping where
 import Data.Char
 import Data.Text.Fusion.Internal
@@ -217,6 +222,105 @@
 -- LATIN CAPITAL LETTER I WITH DOT ABOVE
 lowerMapping '\x0130' s = Yield '\x0069' (CC s '\x0307' '\x0000')
 lowerMapping c s = Yield (toLower c) (CC s '\0' '\0')
+titleMapping :: forall s. Char -> s -> Step (CC s) Char
+{-# INLINE titleMapping #-}
+-- LATIN SMALL LETTER SHARP S
+titleMapping '\x00df' s = Yield '\x0053' (CC s '\x0073' '\x0000')
+-- LATIN SMALL LIGATURE FF
+titleMapping '\xfb00' s = Yield '\x0046' (CC s '\x0066' '\x0000')
+-- LATIN SMALL LIGATURE FI
+titleMapping '\xfb01' s = Yield '\x0046' (CC s '\x0069' '\x0000')
+-- LATIN SMALL LIGATURE FL
+titleMapping '\xfb02' s = Yield '\x0046' (CC s '\x006c' '\x0000')
+-- LATIN SMALL LIGATURE FFI
+titleMapping '\xfb03' s = Yield '\x0046' (CC s '\x0066' '\x0069')
+-- LATIN SMALL LIGATURE FFL
+titleMapping '\xfb04' s = Yield '\x0046' (CC s '\x0066' '\x006c')
+-- LATIN SMALL LIGATURE LONG S T
+titleMapping '\xfb05' s = Yield '\x0053' (CC s '\x0074' '\x0000')
+-- LATIN SMALL LIGATURE ST
+titleMapping '\xfb06' s = Yield '\x0053' (CC s '\x0074' '\x0000')
+-- ARMENIAN SMALL LIGATURE ECH YIWN
+titleMapping '\x0587' s = Yield '\x0535' (CC s '\x0582' '\x0000')
+-- ARMENIAN SMALL LIGATURE MEN NOW
+titleMapping '\xfb13' s = Yield '\x0544' (CC s '\x0576' '\x0000')
+-- ARMENIAN SMALL LIGATURE MEN ECH
+titleMapping '\xfb14' s = Yield '\x0544' (CC s '\x0565' '\x0000')
+-- ARMENIAN SMALL LIGATURE MEN INI
+titleMapping '\xfb15' s = Yield '\x0544' (CC s '\x056b' '\x0000')
+-- ARMENIAN SMALL LIGATURE VEW NOW
+titleMapping '\xfb16' s = Yield '\x054e' (CC s '\x0576' '\x0000')
+-- ARMENIAN SMALL LIGATURE MEN XEH
+titleMapping '\xfb17' s = Yield '\x0544' (CC s '\x056d' '\x0000')
+-- LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+titleMapping '\x0149' s = Yield '\x02bc' (CC s '\x004e' '\x0000')
+-- GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+titleMapping '\x0390' s = Yield '\x0399' (CC s '\x0308' '\x0301')
+-- GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+titleMapping '\x03b0' s = Yield '\x03a5' (CC s '\x0308' '\x0301')
+-- LATIN SMALL LETTER J WITH CARON
+titleMapping '\x01f0' s = Yield '\x004a' (CC s '\x030c' '\x0000')
+-- LATIN SMALL LETTER H WITH LINE BELOW
+titleMapping '\x1e96' s = Yield '\x0048' (CC s '\x0331' '\x0000')
+-- LATIN SMALL LETTER T WITH DIAERESIS
+titleMapping '\x1e97' s = Yield '\x0054' (CC s '\x0308' '\x0000')
+-- LATIN SMALL LETTER W WITH RING ABOVE
+titleMapping '\x1e98' s = Yield '\x0057' (CC s '\x030a' '\x0000')
+-- LATIN SMALL LETTER Y WITH RING ABOVE
+titleMapping '\x1e99' s = Yield '\x0059' (CC s '\x030a' '\x0000')
+-- LATIN SMALL LETTER A WITH RIGHT HALF RING
+titleMapping '\x1e9a' s = Yield '\x0041' (CC s '\x02be' '\x0000')
+-- GREEK SMALL LETTER UPSILON WITH PSILI
+titleMapping '\x1f50' s = Yield '\x03a5' (CC s '\x0313' '\x0000')
+-- GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA
+titleMapping '\x1f52' s = Yield '\x03a5' (CC s '\x0313' '\x0300')
+-- GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA
+titleMapping '\x1f54' s = Yield '\x03a5' (CC s '\x0313' '\x0301')
+-- GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI
+titleMapping '\x1f56' s = Yield '\x03a5' (CC s '\x0313' '\x0342')
+-- GREEK SMALL LETTER ALPHA WITH PERISPOMENI
+titleMapping '\x1fb6' s = Yield '\x0391' (CC s '\x0342' '\x0000')
+-- GREEK SMALL LETTER ETA WITH PERISPOMENI
+titleMapping '\x1fc6' s = Yield '\x0397' (CC s '\x0342' '\x0000')
+-- GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA
+titleMapping '\x1fd2' s = Yield '\x0399' (CC s '\x0308' '\x0300')
+-- GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+titleMapping '\x1fd3' s = Yield '\x0399' (CC s '\x0308' '\x0301')
+-- GREEK SMALL LETTER IOTA WITH PERISPOMENI
+titleMapping '\x1fd6' s = Yield '\x0399' (CC s '\x0342' '\x0000')
+-- GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI
+titleMapping '\x1fd7' s = Yield '\x0399' (CC s '\x0308' '\x0342')
+-- GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA
+titleMapping '\x1fe2' s = Yield '\x03a5' (CC s '\x0308' '\x0300')
+-- GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA
+titleMapping '\x1fe3' s = Yield '\x03a5' (CC s '\x0308' '\x0301')
+-- GREEK SMALL LETTER RHO WITH PSILI
+titleMapping '\x1fe4' s = Yield '\x03a1' (CC s '\x0313' '\x0000')
+-- GREEK SMALL LETTER UPSILON WITH PERISPOMENI
+titleMapping '\x1fe6' s = Yield '\x03a5' (CC s '\x0342' '\x0000')
+-- GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI
+titleMapping '\x1fe7' s = Yield '\x03a5' (CC s '\x0308' '\x0342')
+-- GREEK SMALL LETTER OMEGA WITH PERISPOMENI
+titleMapping '\x1ff6' s = Yield '\x03a9' (CC s '\x0342' '\x0000')
+-- GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI
+titleMapping '\x1fb2' s = Yield '\x1fba' (CC s '\x0345' '\x0000')
+-- GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+titleMapping '\x1fb4' s = Yield '\x0386' (CC s '\x0345' '\x0000')
+-- GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI
+titleMapping '\x1fc2' s = Yield '\x1fca' (CC s '\x0345' '\x0000')
+-- GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+titleMapping '\x1fc4' s = Yield '\x0389' (CC s '\x0345' '\x0000')
+-- GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI
+titleMapping '\x1ff2' s = Yield '\x1ffa' (CC s '\x0345' '\x0000')
+-- GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+titleMapping '\x1ff4' s = Yield '\x038f' (CC s '\x0345' '\x0000')
+-- GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI
+titleMapping '\x1fb7' s = Yield '\x0391' (CC s '\x0342' '\x0345')
+-- GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI
+titleMapping '\x1fc7' s = Yield '\x0397' (CC s '\x0342' '\x0345')
+-- GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
+titleMapping '\x1ff7' s = Yield '\x03a9' (CC s '\x0342' '\x0345')
+titleMapping c s = Yield (toTitle c) (CC s '\0' '\0')
 foldMapping :: forall s. Char -> s -> Step (CC s) Char
 {-# INLINE foldMapping #-}
 -- MICRO SIGN
@@ -255,6 +359,10 @@
 foldMapping '\x03f5' s = Yield '\x03b5' (CC s '\x0000' '\x0000')
 -- ARMENIAN SMALL LIGATURE ECH YIWN
 foldMapping '\x0587' s = Yield '\x0565' (CC s '\x0582' '\x0000')
+-- GEORGIAN CAPITAL LETTER YN
+foldMapping '\x10c7' s = Yield '\x2d27' (CC s '\x0000' '\x0000')
+-- GEORGIAN CAPITAL LETTER AEN
+foldMapping '\x10cd' s = Yield '\x2d2d' (CC s '\x0000' '\x0000')
 -- LATIN SMALL LETTER H WITH LINE BELOW
 foldMapping '\x1e96' s = Yield '\x0068' (CC s '\x0331' '\x0000')
 -- LATIN SMALL LETTER T WITH DIAERESIS
@@ -429,6 +537,12 @@
 foldMapping '\x1ff7' s = Yield '\x03c9' (CC s '\x0342' '\x03b9')
 -- GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
 foldMapping '\x1ffc' s = Yield '\x03c9' (CC s '\x03b9' '\x0000')
+-- COPTIC CAPITAL LETTER BOHAIRIC KHEI
+foldMapping '\x2cf2' s = Yield '\x2cf3' (CC s '\x0000' '\x0000')
+-- LATIN CAPITAL LETTER C WITH BAR
+foldMapping '\xa792' s = Yield '\xa793' (CC s '\x0000' '\x0000')
+-- LATIN CAPITAL LETTER H WITH HOOK
+foldMapping '\xa7aa' s = Yield '\x0266' (CC s '\x0000' '\x0000')
 -- LATIN SMALL LIGATURE FF
 foldMapping '\xfb00' s = Yield '\x0066' (CC s '\x0066' '\x0000')
 -- LATIN SMALL LIGATURE FI
diff --git a/Data/Text/Fusion/Common.hs b/Data/Text/Fusion/Common.hs
--- a/Data/Text/Fusion/Common.hs
+++ b/Data/Text/Fusion/Common.hs
@@ -42,6 +42,7 @@
     -- $case
     , toCaseFold
     , toLower
+    , toTitle
     , toUpper
 
     -- ** Justification
@@ -102,13 +103,15 @@
 
 import Prelude (Bool(..), Char, Eq(..), Int, Integral, Maybe(..),
                 Ord(..), Ordering(..), String, (.), ($), (+), (-), (*), (++),
-                (&&), fromIntegral, otherwise)
+                (&&), fromIntegral, not, otherwise)
 import qualified Data.List as L
 import qualified Prelude as P
 import Data.Bits (shiftL)
+import Data.Char (isLetter)
 import Data.Int (Int64)
 import Data.Text.Fusion.Internal
-import Data.Text.Fusion.CaseMapping (foldMapping, lowerMapping, upperMapping)
+import Data.Text.Fusion.CaseMapping (foldMapping, lowerMapping, titleMapping,
+                                     upperMapping)
 import Data.Text.Fusion.Size
 import GHC.Prim (Addr#, chr#, indexCharOffAddr#, ord#)
 import GHC.Types (Char(..), Int(..))
@@ -431,6 +434,40 @@
 toLower :: Stream Char -> Stream Char
 toLower = caseConvert lowerMapping
 {-# INLINE [0] toLower #-}
+
+-- | /O(n)/ Convert a string to title case, using simple case
+-- conversion.
+--
+-- The first letter of the input is converted to title case, as is
+-- every subsequent letter that immediately follows a non-letter.
+-- Every letter that immediately follows another letter is converted
+-- to lower case.
+--
+-- The result string may be longer than the input string. For example,
+-- the Latin small ligature &#xfb02; (U+FB02) is converted to the
+-- sequence Latin capital letter F (U+0046) followed by Latin small
+-- letter l (U+006C).
+--
+-- /Note/: this function does not take language or culture specific
+-- rules into account. For instance, in English, different style
+-- guides disagree on whether the book name \"The Hill of the Red
+-- Fox\" is correctly title cased&#x2014;but this function will
+-- capitalize /every/ word.
+toTitle :: Stream Char -> Stream Char
+toTitle (Stream next0 s0 len) = Stream next (CC (False :*: s0) '\0' '\0') len
+  where
+    next (CC (letter :*: s) '\0' _) =
+      case next0 s of
+        Done           -> Done
+        Skip s'        -> Skip (CC (letter :*: s') '\0' '\0')
+        Yield c s'
+          | letter'    -> if letter
+                          then lowerMapping c (letter' :*: s')
+                          else titleMapping c (letter' :*: s')
+          | otherwise  -> Yield c (CC (letter' :*: s') '\0' '\0')
+          where letter' = isLetter c
+    next (CC s a b)     = Yield a (CC s b '\0')
+{-# INLINE [0] toTitle #-}
 
 justifyLeftI :: Integral a => a -> Char -> Stream Char -> Stream Char
 justifyLeftI k c (Stream next0 s0 len) =
diff --git a/Data/Text/IO.hs b/Data/Text/IO.hs
--- a/Data/Text/IO.hs
+++ b/Data/Text/IO.hs
@@ -46,10 +46,6 @@
                        putStr, putStrLn, readFile, writeFile)
 import System.IO (Handle, IOMode(..), hPutChar, openFile, stdin, stdout,
                   withFile)
-#if __GLASGOW_HASKELL__ <= 610
-import qualified Data.ByteString.Char8 as B
-import Data.Text.Encoding (decodeUtf8, encodeUtf8)
-#else
 import qualified Control.Exception as E
 import Control.Monad (liftM2, when)
 import Data.IORef (readIORef, writeIORef)
@@ -68,7 +64,6 @@
                             HandleType(..), Newline(..))
 import System.IO (hGetBuffering, hFileSize, hSetBuffering, hTell)
 import System.IO.Error (isEOFError)
-#endif
 
 -- $performance
 -- #performance#
@@ -137,9 +132,6 @@
 -- result to construct its result.  For files more than a half of
 -- available RAM in size, this may result in memory exhaustion.
 hGetContents :: Handle -> IO Text
-#if __GLASGOW_HASKELL__ <= 610
-hGetContents = fmap decodeUtf8 . B.hGetContents
-#else
 hGetContents h = do
   chooseGoodBuffering h
   wantReadableHandle "hGetContents" h readAll
@@ -171,21 +163,13 @@
            else E.throwIO e
       when (d > 0) . hSetBuffering h . BlockBuffering . Just . fromIntegral $ d
     _ -> return ()
-#endif
 
 -- | Read a single line from a handle.
 hGetLine :: Handle -> IO Text
-#if __GLASGOW_HASKELL__ <= 610
-hGetLine = fmap decodeUtf8 . B.hGetLine
-#else
 hGetLine = hGetLineWith T.concat
-#endif
 
 -- | Write a string to a handle.
 hPutStr :: Handle -> Text -> IO ()
-#if __GLASGOW_HASKELL__ <= 610
-hPutStr h = B.hPutStr h . encodeUtf8
-#else
 -- This function is lifted almost verbatim from GHC.IO.Handle.Text.
 hPutStr h t = do
   (buffer_mode, nl) <-
@@ -295,7 +279,6 @@
   wantWritableHandle "commitAndReleaseBuffer" hdl $
      commitBuffer' raw sz count flush release
 {-# INLINE commitBuffer #-}
-#endif
 
 -- | Write a string to a handle, followed by a newline.
 hPutStrLn :: Handle -> Text -> IO ()
diff --git a/Data/Text/IO/Internal.hs b/Data/Text/IO/Internal.hs
--- a/Data/Text/IO/Internal.hs
+++ b/Data/Text/IO/Internal.hs
@@ -12,13 +12,10 @@
 
 module Data.Text.IO.Internal
     (
-#if __GLASGOW_HASKELL__ >= 612
       hGetLineWith
     , readChunk
-#endif
     ) where
 
-#if __GLASGOW_HASKELL__ >= 612
 import qualified Control.Exception as E
 import Data.IORef (readIORef, writeIORef)
 import Data.Text (Text)
@@ -163,4 +160,3 @@
 
 sizeError :: String -> a
 sizeError loc = error $ "Data.Text.IO." ++ loc ++ ": bad internal buffer size"
-#endif
diff --git a/Data/Text/Lazy.hs b/Data/Text/Lazy.hs
--- a/Data/Text/Lazy.hs
+++ b/Data/Text/Lazy.hs
@@ -86,6 +86,7 @@
     , toCaseFold
     , toLower
     , toUpper
+    , toTitle
 
     -- ** Justification
     , justifyLeft
@@ -202,11 +203,7 @@
 import qualified Data.List as L
 import Data.Char (isSpace)
 import Data.Data (Data(gfoldl, toConstr, gunfold, dataTypeOf))
-#if __GLASGOW_HASKELL__ >= 612
 import Data.Data (mkNoRepType)
-#else
-import Data.Data (mkNorepType)
-#endif
 import Data.Monoid (Monoid(..))
 import Data.String (IsString(..))
 import qualified Data.Text as T
@@ -341,11 +338,7 @@
   gfoldl f z txt = z pack `f` (unpack txt)
   toConstr _     = error "Data.Text.Lazy.Text.toConstr"
   gunfold _ _    = error "Data.Text.Lazy.Text.gunfold"
-#if __GLASGOW_HASKELL__ >= 612
   dataTypeOf _   = mkNoRepType "Data.Text.Lazy.Text"
-#else
-  dataTypeOf _   = mkNorepType "Data.Text.Lazy.Text"
-#endif
 
 -- | /O(n)/ Convert a 'String' into a 'Text'.
 --
@@ -670,10 +663,11 @@
 -- functions may map one input character to two or three output
 -- characters.
 
--- | /O(n)/ Convert a string to folded case.  This function is mainly
--- useful for performing caseless (or case insensitive) string
--- comparisons.
+-- | /O(n)/ Convert a string to folded case.  Subject to fusion.
 --
+-- This function is mainly useful for performing caseless (or case
+-- insensitive) string comparisons.
+--
 -- A string @x@ is a caseless match for a string @y@ if and only if:
 --
 -- @toCaseFold x == toCaseFold y@
@@ -689,21 +683,48 @@
 {-# INLINE [0] toCaseFold #-}
 
 -- | /O(n)/ Convert a string to lower case, using simple case
--- conversion.  The result string may be longer than the input string.
--- For instance, the Latin capital letter I with dot above (U+0130)
--- maps to the sequence Latin small letter i (U+0069) followed by
--- combining dot above (U+0307).
+-- conversion.  Subject to fusion.
+--
+-- The result string may be longer than the input string.  For
+-- instance, the Latin capital letter I with dot above (U+0130) maps
+-- to the sequence Latin small letter i (U+0069) followed by combining
+-- dot above (U+0307).
 toLower :: Text -> Text
 toLower t = unstream (S.toLower (stream t))
 {-# INLINE toLower #-}
 
 -- | /O(n)/ Convert a string to upper case, using simple case
--- conversion.  The result string may be longer than the input string.
--- For instance, the German eszett (U+00DF) maps to the two-letter
+-- conversion.  Subject to fusion.
+--
+-- The result string may be longer than the input string.  For
+-- instance, the German eszett (U+00DF) maps to the two-letter
 -- sequence SS.
 toUpper :: Text -> Text
 toUpper t = unstream (S.toUpper (stream t))
 {-# INLINE toUpper #-}
+
+
+-- | /O(n)/ Convert a string to title case, using simple case
+-- conversion.  Subject to fusion.
+--
+-- The first letter of the input is converted to title case, as is
+-- every subsequent letter that immediately follows a non-letter.
+-- Every letter that immediately follows another letter is converted
+-- to lower case.
+--
+-- The result string may be longer than the input string. For example,
+-- the Latin small ligature &#xfb02; (U+FB02) is converted to the
+-- sequence Latin capital letter F (U+0046) followed by Latin small
+-- letter l (U+006C).
+--
+-- /Note/: this function does not take language or culture specific
+-- rules into account. For instance, in English, different style
+-- guides disagree on whether the book name \"The Hill of the Red
+-- Fox\" is correctly title cased&#x2014;but this function will
+-- capitalize /every/ word.
+toTitle :: Text -> Text
+toTitle t = unstream (S.toTitle (stream t))
+{-# INLINE toTitle #-}
 
 -- | /O(n)/ 'foldl', applied to a binary operator, a starting value
 -- (typically the left-identity of the operator), and a 'Text',
diff --git a/Data/Text/Lazy/Builder.hs b/Data/Text/Lazy/Builder.hs
--- a/Data/Text/Lazy/Builder.hs
+++ b/Data/Text/Lazy/Builder.hs
@@ -1,4 +1,7 @@
 {-# LANGUAGE BangPatterns, CPP, Rank2Types #-}
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
 
 -----------------------------------------------------------------------------
 -- |
@@ -26,8 +29,14 @@
 --
 -- > singleton 'a' `mappend` singleton 'b' `mappend` singleton 'c'
 --
--- as the latter associates @mappend@ to the left.
+-- as the latter associates @mappend@ to the left. Or, equivalently,
+-- prefer
 --
+--  > singleton 'a' <> singleton 'b' <> singleton 'c'
+--
+-- since the '<>' from recent versions of 'Data.Monoid' associates 
+-- to the right.
+
 -----------------------------------------------------------------------------
 
 module Data.Text.Lazy.Builder
diff --git a/Data/Text/Lazy/Builder/Int.hs b/Data/Text/Lazy/Builder/Int.hs
--- a/Data/Text/Lazy/Builder/Int.hs
+++ b/Data/Text/Lazy/Builder/Int.hs
@@ -1,4 +1,7 @@
 {-# LANGUAGE BangPatterns, CPP, MagicHash, RankNTypes, UnboxedTuples #-}
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
 
 -- Module:      Data.Text.Lazy.Builder.Int
 -- Copyright:   (c) 2013 Bryan O'Sullivan
@@ -30,9 +33,7 @@
 import Control.Monad.ST
 
 #ifdef  __GLASGOW_HASKELL__
-# if __GLASGOW_HASKELL__ < 611
-import GHC.Integer.Internals
-# elif defined(INTEGER_GMP)
+# if defined(INTEGER_GMP)
 import GHC.Integer.GMP.Internals
 # elif defined(INTEGER_SIMPLE)
 import GHC.Integer
diff --git a/Data/Text/Lazy/Builder/RealFloat.hs b/Data/Text/Lazy/Builder/RealFloat.hs
--- a/Data/Text/Lazy/Builder/RealFloat.hs
+++ b/Data/Text/Lazy/Builder/RealFloat.hs
@@ -1,4 +1,7 @@
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE CPP, OverloadedStrings #-}
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
 
 -- |
 -- Module:    Data.Text.Lazy.Builder.RealFloat
diff --git a/Data/Text/Lazy/Encoding.hs b/Data/Text/Lazy/Encoding.hs
--- a/Data/Text/Lazy/Encoding.hs
+++ b/Data/Text/Lazy/Encoding.hs
@@ -49,15 +49,14 @@
     ) where
 
 import Control.Exception (evaluate, try)
-import Data.Bits ((.&.))
 import Data.Text.Encoding.Error (OnDecodeError, UnicodeException, strictDecode)
 import Data.Text.Lazy.Internal (Text(..), chunk, empty, foldrChunks)
 import qualified Data.ByteString as S
 import qualified Data.ByteString.Lazy as B
 import qualified Data.ByteString.Lazy.Internal as B
-import qualified Data.ByteString.Unsafe as S
-import qualified Data.Text as T
+import qualified Data.ByteString.Unsafe as B
 import qualified Data.Text.Encoding as TE
+import qualified Data.Text.Lazy as L
 import qualified Data.Text.Lazy.Encoding.Fusion as E
 import qualified Data.Text.Lazy.Fusion as F
 import Data.Text.Unsafe (unsafeDupablePerformIO)
@@ -89,41 +88,20 @@
 
 -- | Decode a 'ByteString' containing UTF-8 encoded text.
 decodeUtf8With :: OnDecodeError -> B.ByteString -> Text
-decodeUtf8With onErr bs0 = fast bs0
+decodeUtf8With onErr (B.Chunk b0 bs0) =
+    case TE.streamDecodeUtf8With onErr b0 of
+      TE.Some t l f -> chunk t (go f l bs0)
   where
-    decode = TE.decodeUtf8With onErr
-    fast (B.Chunk p ps) | isComplete p = chunk (decode p) (fast ps)
-                        | otherwise    = chunk (decode h) (slow t ps)
-      where (h,t) = S.splitAt pivot p
-            pivot | at 1      = len-1
-                  | at 2      = len-2
-                  | otherwise = len-3
-            len  = S.length p
-            at n = len >= n && S.unsafeIndex p (len-n) .&. 0xc0 == 0xc0
-    fast B.Empty = empty
-    slow i bs = {-# SCC "decodeUtf8With'/slow" #-}
-                case B.uncons bs of
-                  Just (w,bs') | isComplete i' -> chunk (decode i') (fast bs')
-                               | otherwise     -> slow i' bs'
-                    where i' = S.snoc i w
-                  Nothing -> case S.uncons i of
-                               Just (j,i') ->
-                                 case onErr desc (Just j) of
-                                   Nothing -> slow i' bs
-                                   Just c  -> Chunk (T.singleton c) (slow i' bs)
-                               Nothing ->
-                                 case onErr desc Nothing of
-                                   Nothing -> empty
-                                   Just c  -> Chunk (T.singleton c) empty
-    isComplete bs = {-# SCC "decodeUtf8With'/isComplete" #-}
-                    ix 1 .&. 0x80 == 0 ||
-                    (len >= 2 && ix 2 .&. 0xe0 == 0xc0) ||
-                    (len >= 3 && ix 3 .&. 0xf0 == 0xe0) ||
-                    (len >= 4 && ix 4 .&. 0xf8 == 0xf0)
-      where len = S.length bs
-            ix n = S.unsafeIndex bs (len-n)
+    go f0 _ (B.Chunk b bs) =
+      case f0 b of
+        TE.Some t l f -> chunk t (go f l bs)
+    go _ l _
+      | S.null l  = empty
+      | otherwise = case onErr desc (Just (B.unsafeHead l)) of
+                      Nothing -> empty
+                      Just c  -> L.singleton c
     desc = "Data.Text.Lazy.Encoding.decodeUtf8With: Invalid UTF-8 stream"
-{-# INLINE[0] decodeUtf8With #-}
+decodeUtf8With _ _ = empty
 
 -- | Decode a 'ByteString' containing UTF-8 encoded text that is known
 -- to be valid.
diff --git a/Data/Text/Lazy/IO.hs b/Data/Text/Lazy/IO.hs
--- a/Data/Text/Lazy/IO.hs
+++ b/Data/Text/Lazy/IO.hs
@@ -47,11 +47,6 @@
                   withFile)
 import qualified Data.Text.IO as T
 import qualified Data.Text.Lazy as L
-#if __GLASGOW_HASKELL__ <= 610
-import Data.Text.Lazy.Encoding (decodeUtf8)
-import qualified Data.ByteString.Char8 as S8
-import qualified Data.ByteString.Lazy.Char8 as L8
-#else
 import qualified Control.Exception as E
 import Control.Monad (when)
 import Data.IORef (readIORef)
@@ -65,7 +60,6 @@
 import System.IO (BufferMode(..), hGetBuffering, hSetBuffering)
 import System.IO.Error (isEOFError)
 import System.IO.Unsafe (unsafeInterleaveIO)
-#endif
 
 -- $performance
 --
@@ -99,9 +93,6 @@
 -- | Lazily read the remaining contents of a 'Handle'.  The 'Handle'
 -- will be closed after the read completes, or on error.
 hGetContents :: Handle -> IO Text
-#if __GLASGOW_HASKELL__ <= 610
-hGetContents = fmap decodeUtf8 . L8.hGetContents
-#else
 hGetContents h = do
   chooseGoodBuffering h
   wantReadableHandle "hGetContents" h $ \hh -> do
@@ -138,15 +129,10 @@
                          then (hh', empty)
                          else (hh', L.singleton '\r')
            else E.throwIO (augmentIOError e "hGetContents" h)
-#endif
 
 -- | Read a single line from a handle.
 hGetLine :: Handle -> IO Text
-#if __GLASGOW_HASKELL__ <= 610
-hGetLine = fmap (decodeUtf8 . L8.fromChunks . (:[])) . S8.hGetLine
-#else
 hGetLine = hGetLineWith L.fromChunks
-#endif
 
 -- | Write a string to a handle.
 hPutStr :: Handle -> Text -> IO ()
diff --git a/Data/Text/Search.hs b/Data/Text/Search.hs
--- a/Data/Text/Search.hs
+++ b/Data/Text/Search.hs
@@ -42,7 +42,8 @@
 
 -- | /O(n+m)/ Find the offsets of all non-overlapping indices of
 -- @needle@ within @haystack@.  The offsets returned represent
--- locations in the low-level array.
+-- uncorrected indices in the low-level \"needle\" array, to which its
+-- offset must be added.
 --
 -- In (unlikely) bad cases, this algorithm's complexity degrades
 -- towards /O(n*m)/.
diff --git a/Data/Text/Unsafe.hs b/Data/Text/Unsafe.hs
--- a/Data/Text/Unsafe.hs
+++ b/Data/Text/Unsafe.hs
@@ -34,11 +34,7 @@
 import Data.Text.Unsafe.Base (inlineInterleaveST, inlinePerformIO)
 import Data.Text.UnsafeChar (unsafeChr)
 import qualified Data.Text.Array as A
-#if __GLASGOW_HASKELL__ >= 611
 import GHC.IO (unsafeDupablePerformIO)
-#else
-import GHC.IOBase (unsafeDupablePerformIO)
-#endif
 
 -- | /O(1)/ A variant of 'head' for non-empty 'Text'. 'unsafeHead'
 -- omits the check for the empty case, so there is an obligation on
@@ -51,7 +47,7 @@
           n = A.unsafeIndex arr (off+1)
 {-# INLINE unsafeHead #-}
 
--- | /O(1)/ A variant of 'tail' for non-empty 'Text'. 'unsafeHead'
+-- | /O(1)/ A variant of 'tail' for non-empty 'Text'. 'unsafeTail'
 -- omits the check for the empty case, so there is an obligation on
 -- the programmer to provide a proof that the 'Text' is non-empty.
 unsafeTail :: Text -> Text
diff --git a/Data/Text/Unsafe/Base.hs b/Data/Text/Unsafe/Base.hs
--- a/Data/Text/Unsafe/Base.hs
+++ b/Data/Text/Unsafe/Base.hs
@@ -18,11 +18,7 @@
 
 import GHC.ST (ST(..))
 #if defined(__GLASGOW_HASKELL__)
-# if __GLASGOW_HASKELL__ >= 611
 import GHC.IO (IO(IO))
-# else
-import GHC.IOBase (IO(IO))
-# endif
 import GHC.Base (realWorld#)
 #endif
 
diff --git a/benchmarks/haskell/Benchmarks/DecodeUtf8.hs b/benchmarks/haskell/Benchmarks/DecodeUtf8.hs
--- a/benchmarks/haskell/Benchmarks/DecodeUtf8.hs
+++ b/benchmarks/haskell/Benchmarks/DecodeUtf8.hs
@@ -20,6 +20,7 @@
 
 import Foreign.C.Types
 import Data.ByteString.Internal (ByteString(..))
+import Data.ByteString.Lazy.Internal (ByteString(..))
 import Foreign.Ptr (Ptr, plusPtr)
 import Foreign.ForeignPtr (withForeignPtr)
 import Data.Word (Word8)
@@ -38,8 +39,15 @@
     bs  <- B.readFile fp
     lbs <- BL.readFile fp
     let bench name = C.bench (name ++ "+" ++ kind)
+        decodeStream (Chunk b0 bs0) = case T.streamDecodeUtf8 b0 of
+                                        T.Some t0 _ f0 -> t0 : go f0 bs0
+          where go f (Chunk b bs1) = case f b of
+                                       T.Some t1 _ f1 -> t1 : go f1 bs1
+                go _ _ = []
+        decodeStream _ = []
     return $ bgroup "DecodeUtf8"
         [ bench "Strict" $ nf T.decodeUtf8 bs
+        , bench "Stream" $ nf decodeStream lbs
         , bench "IConv" $ iconv bs
         , bench "StrictLength" $ nf (T.length . T.decodeUtf8) bs
         , bench "StrictInitLength" $ nf (T.length . T.init . T.decodeUtf8) bs
@@ -52,7 +60,7 @@
         , bench "LazyStringUtf8Length" $ nf (length . U8.toString) lbs
         ]
 
-iconv :: ByteString -> IO CInt
+iconv :: B.ByteString -> IO CInt
 iconv (PS fp off len) = withForeignPtr fp $ \ptr ->
                         time_iconv (ptr `plusPtr` off) (fromIntegral len)
 
diff --git a/benchmarks/haskell/Benchmarks/Programs/BigTable.hs b/benchmarks/haskell/Benchmarks/Programs/BigTable.hs
new file mode 100644
--- /dev/null
+++ b/benchmarks/haskell/Benchmarks/Programs/BigTable.hs
@@ -0,0 +1,42 @@
+-- | Create a large HTML table and dump it to a handle
+--
+-- Tested in this benchmark:
+--
+-- * Creating a large HTML document using a builder
+--
+-- * Writing to a handle
+--
+{-# LANGUAGE OverloadedStrings #-}
+module Benchmarks.Programs.BigTable
+    ( benchmark
+    ) where
+
+import Criterion (Benchmark, bench)
+import Data.Monoid (mappend, mconcat)
+import Data.Text.Lazy.Builder (Builder, fromText, toLazyText)
+import Data.Text.Lazy.IO (hPutStr)
+import System.IO (Handle)
+import qualified Data.Text as T
+
+benchmark :: Handle -> IO Benchmark
+benchmark sink = return $ bench "BigTable" $ do
+    hPutStr sink "Content-Type: text/html\n\n<table>"
+    hPutStr sink . toLazyText . makeTable =<< rows
+    hPutStr sink "</table>"
+  where
+    -- We provide the number of rows in IO so the builder value isn't shared
+    -- between the benchmark samples.
+    rows :: IO Int
+    rows = return 20000
+    {-# NOINLINE rows #-}
+
+makeTable :: Int -> Builder
+makeTable n = mconcat $ replicate n $ mconcat $ map makeCol [1 .. 50]
+
+makeCol :: Int -> Builder
+makeCol 1 = fromText "<tr><td>1</td>"
+makeCol 50 = fromText "<td>50</td></tr>"
+makeCol i = fromText "<td>" `mappend` (fromInt i `mappend` fromText "</td>")
+
+fromInt :: Int -> Builder
+fromInt = fromText . T.pack . show
diff --git a/benchmarks/haskell/Benchmarks/Programs/Cut.hs b/benchmarks/haskell/Benchmarks/Programs/Cut.hs
new file mode 100644
--- /dev/null
+++ b/benchmarks/haskell/Benchmarks/Programs/Cut.hs
@@ -0,0 +1,98 @@
+-- | Cut into a file, selecting certain columns (e.g. columns 10 to 40)
+--
+-- Tested in this benchmark:
+--
+-- * Reading the file
+--
+-- * Splitting into lines
+--
+-- * Taking a number of characters from the lines
+--
+-- * Joining the lines
+--
+-- * Writing back to a handle
+--
+module Benchmarks.Programs.Cut
+    ( benchmark
+    ) where
+
+import Criterion (Benchmark, bgroup, bench)
+import System.IO (Handle, hPutStr)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Char8 as BC
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.ByteString.Lazy.Char8 as BLC
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
+import qualified Data.Text.IO as T
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Encoding as TL
+import qualified Data.Text.Lazy.IO as TL
+
+benchmark :: FilePath -> Handle -> Int -> Int -> IO Benchmark
+benchmark p sink from to = return $ bgroup "Cut"
+    [ bench' "String" string
+    , bench' "ByteString" byteString
+    , bench' "LazyByteString" lazyByteString
+    , bench' "Text" text
+    , bench' "LazyText" lazyText
+    , bench' "TextByteString" textByteString
+    , bench' "LazyTextByteString" lazyTextByteString
+    ]
+  where
+    bench' n s = bench n (s p sink from to)
+
+string :: FilePath -> Handle -> Int -> Int -> IO ()
+string fp sink from to = do
+    s <- readFile fp
+    hPutStr sink $ cut s
+  where
+    cut = unlines . map (take (to - from) . drop from) . lines
+
+byteString :: FilePath -> Handle -> Int -> Int -> IO ()
+byteString fp sink from to = do
+    bs <- B.readFile fp
+    B.hPutStr sink $ cut bs
+  where
+    cut = BC.unlines . map (B.take (to - from) . B.drop from) . BC.lines
+
+lazyByteString :: FilePath -> Handle -> Int -> Int -> IO ()
+lazyByteString fp sink from to = do
+    bs <- BL.readFile fp
+    BL.hPutStr sink $ cut bs
+  where
+    cut = BLC.unlines . map (BL.take (to' - from') . BL.drop from') . BLC.lines
+    from' = fromIntegral from
+    to' = fromIntegral to
+
+text :: FilePath -> Handle -> Int -> Int -> IO ()
+text fp sink from to = do
+    t <- T.readFile fp
+    T.hPutStr sink $ cut t
+  where
+    cut = T.unlines . map (T.take (to - from) . T.drop from) . T.lines
+
+lazyText :: FilePath -> Handle -> Int -> Int -> IO ()
+lazyText fp sink from to = do
+    t <- TL.readFile fp
+    TL.hPutStr sink $ cut t
+  where
+    cut = TL.unlines . map (TL.take (to' - from') . TL.drop from') . TL.lines
+    from' = fromIntegral from
+    to' = fromIntegral to
+
+textByteString :: FilePath -> Handle -> Int -> Int -> IO ()
+textByteString fp sink from to = do
+    t <- T.decodeUtf8 `fmap` B.readFile fp
+    B.hPutStr sink $ T.encodeUtf8 $ cut t
+  where
+    cut = T.unlines . map (T.take (to - from) . T.drop from) . T.lines
+
+lazyTextByteString :: FilePath -> Handle -> Int -> Int -> IO ()
+lazyTextByteString fp sink from to = do
+    t <- TL.decodeUtf8 `fmap` BL.readFile fp
+    BL.hPutStr sink $ TL.encodeUtf8 $ cut t
+  where
+    cut = TL.unlines . map (TL.take (to' - from') . TL.drop from') . TL.lines
+    from' = fromIntegral from
+    to' = fromIntegral to
diff --git a/benchmarks/haskell/Benchmarks/Programs/Fold.hs b/benchmarks/haskell/Benchmarks/Programs/Fold.hs
new file mode 100644
--- /dev/null
+++ b/benchmarks/haskell/Benchmarks/Programs/Fold.hs
@@ -0,0 +1,68 @@
+-- | Benchmark which formats paragraph, like the @sort@ unix utility.
+--
+-- Tested in this benchmark:
+--
+-- * Reading the file
+--
+-- * Splitting into paragraphs
+--
+-- * Reformatting the paragraphs to a certain line width
+--
+-- * Concatenating the results using the text builder
+--
+-- * Writing back to a handle
+--
+{-# LANGUAGE OverloadedStrings #-}
+module Benchmarks.Programs.Fold
+    ( benchmark
+    ) where
+
+import Data.List (foldl')
+import Data.List (intersperse)
+import Data.Monoid (mempty, mappend, mconcat)
+import System.IO (Handle)
+import Criterion (Benchmark, bench)
+import qualified Data.Text as T
+import qualified Data.Text.IO as T
+import qualified Data.Text.Lazy.Builder as TLB
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.IO as TL
+
+benchmark :: FilePath -> Handle -> IO Benchmark
+benchmark i o = return $
+    bench "Fold" $ T.readFile i >>= TL.hPutStr o . fold 80
+
+-- | We represent a paragraph by a word list
+--
+type Paragraph = [T.Text]
+
+-- | Fold a text
+--
+fold :: Int -> T.Text -> TL.Text
+fold maxWidth = TLB.toLazyText . mconcat .
+    intersperse "\n\n" . map (foldParagraph maxWidth) . paragraphs
+
+-- | Fold a paragraph
+--
+foldParagraph :: Int -> Paragraph -> TLB.Builder
+foldParagraph _    []       = mempty
+foldParagraph max' (w : ws) = fst $ foldl' go (TLB.fromText w, T.length w) ws
+  where
+    go (builder, width) word
+        | width + len + 1 <= max' =
+            (builder `mappend` " " `mappend` word', width + len + 1)
+        | otherwise =
+            (builder `mappend` "\n" `mappend` word', len)
+      where
+        word' = TLB.fromText word
+        len = T.length word
+
+-- | Divide a text into paragraphs
+--
+paragraphs :: T.Text -> [Paragraph]
+paragraphs = splitParagraphs . map T.words . T.lines
+  where
+    splitParagraphs ls = case break null ls of
+        ([], []) -> []
+        (p,  []) -> [concat p]
+        (p,  lr) -> concat p : splitParagraphs (dropWhile null lr)
diff --git a/benchmarks/haskell/Benchmarks/Programs/Sort.hs b/benchmarks/haskell/Benchmarks/Programs/Sort.hs
new file mode 100644
--- /dev/null
+++ b/benchmarks/haskell/Benchmarks/Programs/Sort.hs
@@ -0,0 +1,70 @@
+-- | This benchmark sorts the lines of a file, like the @sort@ unix utility.
+--
+-- Tested in this benchmark:
+--
+-- * Reading the file
+--
+-- * Splitting into lines
+--
+-- * Sorting the lines
+--
+-- * Joining the lines
+--
+-- * Writing back to a handle
+--
+{-# LANGUAGE OverloadedStrings #-}
+module Benchmarks.Programs.Sort
+    ( benchmark
+    ) where
+
+import Criterion (Benchmark, bgroup, bench)
+import Data.Monoid (mconcat)
+import System.IO (Handle, hPutStr)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Char8 as BC
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.ByteString.Lazy.Char8 as BLC
+import qualified Data.List as L
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
+import qualified Data.Text.IO as T
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Builder as TLB
+import qualified Data.Text.Lazy.Encoding as TL
+import qualified Data.Text.Lazy.IO as TL
+
+benchmark :: FilePath -> Handle -> IO Benchmark
+benchmark i o = return $ bgroup "Sort"
+    [ bench "String" $ readFile i >>= hPutStr o . string
+    , bench "ByteString" $ B.readFile i >>= B.hPutStr o . byteString
+    , bench "LazyByteString" $ BL.readFile i >>= BL.hPutStr o . lazyByteString
+    , bench "Text" $ T.readFile i >>= T.hPutStr o . text
+    , bench "LazyText" $ TL.readFile i >>= TL.hPutStr o . lazyText
+    , bench "TextByteString" $ B.readFile i >>=
+        B.hPutStr o . T.encodeUtf8 . text . T.decodeUtf8
+    , bench "LazyTextByteString" $ BL.readFile i >>=
+        BL.hPutStr o . TL.encodeUtf8 . lazyText .  TL.decodeUtf8
+    , bench "TextBuilder" $ B.readFile i >>=
+        BL.hPutStr o . TL.encodeUtf8 . textBuilder . T.decodeUtf8
+    ]
+
+string :: String -> String
+string = unlines . L.sort . lines
+
+byteString :: B.ByteString -> B.ByteString
+byteString = BC.unlines . L.sort . BC.lines
+
+lazyByteString :: BL.ByteString -> BL.ByteString
+lazyByteString = BLC.unlines . L.sort . BLC.lines
+
+text :: T.Text -> T.Text
+text = T.unlines . L.sort . T.lines
+
+lazyText :: TL.Text -> TL.Text
+lazyText = TL.unlines . L.sort . TL.lines
+
+-- | Text variant using a builder monoid for the final concatenation
+--
+textBuilder :: T.Text -> TL.Text
+textBuilder = TLB.toLazyText . mconcat . L.intersperse (TLB.singleton '\n') .
+    map TLB.fromText . L.sort . T.lines
diff --git a/benchmarks/haskell/Benchmarks/Programs/StripTags.hs b/benchmarks/haskell/Benchmarks/Programs/StripTags.hs
new file mode 100644
--- /dev/null
+++ b/benchmarks/haskell/Benchmarks/Programs/StripTags.hs
@@ -0,0 +1,53 @@
+-- | Program to replace HTML tags by whitespace
+--
+-- This program was originally contributed by Petr Prokhorenkov.
+--
+-- Tested in this benchmark:
+--
+-- * Reading the file
+--
+-- * Replacing text between HTML tags (<>) with whitespace
+--
+-- * Writing back to a handle
+--
+{-# OPTIONS_GHC -fspec-constr-count=5 #-}
+module Benchmarks.Programs.StripTags
+    ( benchmark
+    ) where
+
+import Criterion (Benchmark, bgroup, bench)
+import Data.List (mapAccumL)
+import System.IO (Handle, hPutStr)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Char8 as BC
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
+import qualified Data.Text.IO as T
+
+benchmark :: FilePath -> Handle -> IO Benchmark
+benchmark i o = return $ bgroup "StripTags"
+    [ bench "String" $ readFile i >>= hPutStr o . string
+    , bench "ByteString" $ B.readFile i >>= B.hPutStr o . byteString
+    , bench "Text" $ T.readFile i >>= T.hPutStr o . text
+    , bench "TextByteString" $
+        B.readFile i >>= B.hPutStr o . T.encodeUtf8 . text . T.decodeUtf8
+    ]
+
+string :: String -> String
+string = snd . mapAccumL step 0
+
+text :: T.Text -> T.Text
+text = snd . T.mapAccumL step 0
+
+byteString :: B.ByteString -> B.ByteString
+byteString = snd . BC.mapAccumL step 0
+
+step :: Int -> Char -> (Int, Char)
+step d c
+    | d > 0 || d' > 0 = (d', ' ')
+    | otherwise       = (d', c)
+  where
+    d' = d + depth c
+    depth '>' = 1
+    depth '<' = -1
+    depth _   = 0
diff --git a/benchmarks/haskell/Benchmarks/Programs/Throughput.hs b/benchmarks/haskell/Benchmarks/Programs/Throughput.hs
new file mode 100644
--- /dev/null
+++ b/benchmarks/haskell/Benchmarks/Programs/Throughput.hs
@@ -0,0 +1,41 @@
+-- | This benchmark simply reads and writes a file using the various string
+-- libraries. The point of it is that we can make better estimations on how
+-- much time the other benchmarks spend doing IO.
+--
+-- Note that we expect ByteStrings to be a whole lot faster, since they do not
+-- do any actual encoding/decoding here, while String and Text do have UTF-8
+-- encoding/decoding.
+--
+-- Tested in this benchmark:
+--
+-- * Reading the file
+--
+-- * Replacing text between HTML tags (<>) with whitespace
+--
+-- * Writing back to a handle
+--
+module Benchmarks.Programs.Throughput
+    ( benchmark
+    ) where
+
+import Criterion (Benchmark, bgroup, bench)
+import System.IO (Handle, hPutStr)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.Text.Encoding as T
+import qualified Data.Text.IO as T
+import qualified Data.Text.Lazy.Encoding as TL
+import qualified Data.Text.Lazy.IO as TL
+
+benchmark :: FilePath -> Handle -> IO Benchmark
+benchmark fp sink = return $ bgroup "Throughput"
+    [ bench "String" $ readFile fp >>= hPutStr sink
+    , bench "ByteString" $ B.readFile fp >>= B.hPutStr sink
+    , bench "LazyByteString" $ BL.readFile fp >>= BL.hPutStr sink
+    , bench "Text" $ T.readFile fp >>= T.hPutStr sink
+    , bench "LazyText" $ TL.readFile fp >>= TL.hPutStr sink
+    , bench "TextByteString" $
+        B.readFile fp >>= B.hPutStr sink . T.encodeUtf8 .  T.decodeUtf8
+    , bench "LazyTextByteString" $
+        BL.readFile fp >>= BL.hPutStr sink . TL.encodeUtf8 . TL.decodeUtf8
+    ]
diff --git a/benchmarks/haskell/Benchmarks/Pure.hs b/benchmarks/haskell/Benchmarks/Pure.hs
--- a/benchmarks/haskell/Benchmarks/Pure.hs
+++ b/benchmarks/haskell/Benchmarks/Pure.hs
@@ -18,7 +18,6 @@
 import GHC.Base (Char (..), Int (..), chr#, ord#, (+#))
 import qualified Data.ByteString.Char8 as BS
 import qualified Data.ByteString.Lazy.Char8 as BL
-import qualified Data.ByteString.Lazy.Internal as BL
 import qualified Data.ByteString.UTF8 as UTF8
 import qualified Data.List as L
 import qualified Data.Text as T
diff --git a/benchmarks/haskell/Benchmarks/Replace.hs b/benchmarks/haskell/Benchmarks/Replace.hs
--- a/benchmarks/haskell/Benchmarks/Replace.hs
+++ b/benchmarks/haskell/Benchmarks/Replace.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE BangPatterns #-}
 -- | Replace a string by another string
 --
 -- Tested in this benchmark:
@@ -12,6 +13,9 @@
 import qualified Data.ByteString.Char8 as B
 import qualified Data.ByteString.Lazy as BL
 import qualified Data.ByteString.Lazy.Search as BL
+import qualified Data.ByteString.Search as B
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
 import qualified Data.Text.Lazy as TL
 import qualified Data.Text.Lazy.Encoding as TL
 import qualified Data.Text.Lazy.IO as TL
@@ -20,12 +24,20 @@
 benchmark fp pat sub = do
     tl <- TL.readFile fp
     bl <- BL.readFile fp
-    return $ bgroup "Replace"
-        [ bench "LazyText"       $ nf (TL.length . TL.replace tpat tsub) tl
-        , bench "LazyByteString" $ nf (BL.length . BL.replace bpat bsub) bl
+    let !t = TL.toStrict tl
+        !b = T.encodeUtf8 t
+    return $ bgroup "Replace" [
+          bench "Text"           $ nf (T.length . T.replace tpat tsub) t
+        , bench "ByteString"     $ nf (BL.length . B.replace bpat bsub) b
+        , bench "LazyText"       $ nf (TL.length . TL.replace tlpat tlsub) tl
+        , bench "LazyByteString" $ nf (BL.length . BL.replace blpat blsub) bl
         ]
   where
-    tpat = TL.pack pat
-    tsub = TL.pack sub
-    bpat = B.concat $ BL.toChunks $ TL.encodeUtf8 tpat
-    bsub = B.concat $ BL.toChunks $ TL.encodeUtf8 tsub
+    tpat  = T.pack pat
+    tsub  = T.pack sub
+    tlpat = TL.pack pat
+    tlsub = TL.pack sub
+    bpat = T.encodeUtf8 tpat
+    bsub = T.encodeUtf8 tsub
+    blpat = B.concat $ BL.toChunks $ TL.encodeUtf8 tlpat
+    blsub = B.concat $ BL.toChunks $ TL.encodeUtf8 tlsub
diff --git a/benchmarks/text-benchmarks.cabal b/benchmarks/text-benchmarks.cabal
--- a/benchmarks/text-benchmarks.cabal
+++ b/benchmarks/text-benchmarks.cabal
@@ -23,6 +23,7 @@
   hs-source-dirs: haskell ..
   c-sources:      ../cbits/cbits.c
                   cbits/time_iconv.c
+  include-dirs:   ../include
   main-is:        Benchmarks.hs
   ghc-options:    -Wall -O2
   if flag(llvm)
diff --git a/cbits/cbits.c b/cbits/cbits.c
--- a/cbits/cbits.c
+++ b/cbits/cbits.c
@@ -9,6 +9,7 @@
 #include <string.h>
 #include <stdint.h>
 #include <stdio.h>
+#include "text_cbits.h"
 
 void _hs_text_memcpy(void *dest, size_t doff, const void *src, size_t soff,
 		     size_t n)
@@ -103,20 +104,41 @@
  * A best-effort decoder. Runs until it hits either end of input or
  * the start of an invalid byte sequence.
  *
- * At exit, updates *destoff with the next offset to write to, and
- * returns the next source offset to read from.
+ * At exit, we update *destoff with the next offset to write to, *src
+ * with the next source location past the last one successfully
+ * decoded, and return the next source location to read from.
+ *
+ * Moreover, we expose the internal decoder state (state0 and
+ * codepoint0), allowing one to restart the decoder after it
+ * terminates (say, due to a partial codepoint).
+ *
+ * In particular, there are a few possible outcomes,
+ *
+ *   1) We decoded the buffer entirely:
+ *      In this case we return srcend
+ *      state0 == UTF8_ACCEPT
+ *
+ *   2) We met an invalid encoding
+ *      In this case we return the address of the first invalid byte
+ *      state0 == UTF8_REJECT
+ *
+ *   3) We reached the end of the buffer while decoding a codepoint
+ *      In this case we return a pointer to the first byte of the partial codepoint
+ *      state0 != UTF8_ACCEPT, UTF8_REJECT
+ *
  */
-uint8_t const *
-_hs_text_decode_utf8(uint16_t *dest, size_t *destoff,
-		     const uint8_t const *src, const uint8_t const *srcend)
+const uint8_t *
+_hs_text_decode_utf8_state(uint16_t *const dest, size_t *destoff,
+                           const uint8_t **const src,
+                           const uint8_t *const srcend,
+                           uint32_t *codepoint0, uint32_t *state0)
 {
   uint16_t *d = dest + *destoff;
-  const uint8_t const *s = src;
-  uint32_t state = UTF8_ACCEPT;
+  const uint8_t *s = *src, *last = *src;
+  uint32_t state = *state0;
+  uint32_t codepoint = *codepoint0;
 
   while (s < srcend) {
-    uint32_t codepoint;
-
 #if defined(__i386__) || defined(__x86_64__)
     /*
      * This code will only work on a little-endian system that
@@ -144,6 +166,7 @@
 	*d++ = (uint16_t) ((codepoint >> 16) & 0xff);
 	*d++ = (uint16_t) ((codepoint >> 24) & 0xff);
       }
+      last = s;
     }
 #endif
 
@@ -159,13 +182,29 @@
       *d++ = (uint16_t) (0xD7C0 + (codepoint >> 10));
       *d++ = (uint16_t) (0xDC00 + (codepoint & 0x3FF));
     }
+    last = s;
   }
 
-  /* Error recovery - if we're not in a valid finishing state, back up. */
-  if (state != UTF8_ACCEPT)
+  /* Invalid encoding, back up to the errant character */
+  if (state == UTF8_REJECT)
     s -= 1;
 
   *destoff = d - dest;
+  *codepoint0 = codepoint;
+  *state0 = state;
+  *src = last;
 
   return s;
+}
+
+/*
+ * Helper to decode buffer and discard final decoder state
+ */
+const uint8_t *
+_hs_text_decode_utf8(uint16_t *const dest, size_t *destoff,
+                     const uint8_t *src, const uint8_t *const srcend)
+{
+  uint32_t codepoint;
+  uint32_t state = UTF8_ACCEPT;
+  return _hs_text_decode_utf8_state(dest, destoff, &src, srcend, &codepoint, &state);
 }
diff --git a/changelog b/changelog
new file mode 100644
--- /dev/null
+++ b/changelog
@@ -0,0 +1,16 @@
+1.0.0.0
+
+	* Added support for Unicode 6.3.0 to case conversion functions
+
+	* New function toTitle converts words in a string to title case
+
+	* New functions peekCStringLen and withCStringLen simplify
+	  interoperability with C functionns
+
+	* Added support for decoding UTF-8 in stream-friendly fashion
+
+	* Fixed a bug in mapAccumL
+
+	* Added trusted Haskell support
+
+	* Removed support for GHC 6.10 (released in 2008) and older
diff --git a/include/text_cbits.h b/include/text_cbits.h
new file mode 100644
--- /dev/null
+++ b/include/text_cbits.h
@@ -0,0 +1,11 @@
+/*
+ * Copyright (c) 2013 Bryan O'Sullivan <bos@serpentine.com>.
+ */
+
+#ifndef _text_cbits_h
+#define _text_cbits_h
+
+#define UTF8_ACCEPT 0
+#define UTF8_REJECT 12
+
+#endif
diff --git a/scripts/Arsec.hs b/scripts/Arsec.hs
--- a/scripts/Arsec.hs
+++ b/scripts/Arsec.hs
@@ -1,6 +1,7 @@
 module Arsec
     (
-      comment
+      Comment
+    , comment
     , semi
     , showC
     , unichar
@@ -23,13 +24,7 @@
 import Text.ParserCombinators.Parsec.Error
 import Text.ParserCombinators.Parsec.Prim hiding ((<|>), many)
 
-instance Applicative (GenParser s a) where
-    pure = return
-    (<*>) = ap
-
-instance Alternative (GenParser s a) where
-    empty = mzero
-    (<|>) = mplus
+type Comment = String
 
 unichar :: Parser Char
 unichar = chr . fst . head . readHex <$> many1 hexDigit
@@ -40,7 +35,7 @@
 semi :: Parser ()
 semi = char ';' *> spaces *> pure ()
 
-comment :: Parser String
+comment :: Parser Comment
 comment = (char '#' *> manyTill anyToken (char '\n')) <|> string "\n"
 
 showC :: Char -> String
diff --git a/scripts/CaseFolding.hs b/scripts/CaseFolding.hs
--- a/scripts/CaseFolding.hs
+++ b/scripts/CaseFolding.hs
@@ -4,7 +4,8 @@
 
 module CaseFolding
     (
-      Fold(..)
+      CaseFolding(..)
+    , Fold(..)
     , parseCF
     , mapCF
     ) where
@@ -18,19 +19,22 @@
     , name :: String
     } deriving (Eq, Ord, Show)
 
-entries :: Parser [Fold]
-entries = many comment *> many (entry <* many comment)
+data CaseFolding = CF { cfComments :: [Comment], cfFolding :: [Fold] }
+                 deriving (Show)
+
+entries :: Parser CaseFolding
+entries = CF <$> many comment <*> many (entry <* many comment)
   where
     entry = Fold <$> unichar <* semi
                  <*> oneOf "CFST" <* semi
                  <*> unichars
                  <*> (string "# " *> manyTill anyToken (char '\n'))
 
-parseCF :: FilePath -> IO (Either ParseError [Fold])
+parseCF :: FilePath -> IO (Either ParseError CaseFolding)
 parseCF name = parse entries name <$> readFile name
 
-mapCF :: [Fold] -> [String]
-mapCF ms = typ ++ (map nice . filter p $ ms) ++ [last]
+mapCF :: CaseFolding -> [String]
+mapCF (CF _ ms) = typ ++ (map nice . filter p $ ms) ++ [last]
   where
     typ = ["foldMapping :: forall s. Char -> s -> Step (CC s) Char"
            ,"{-# INLINE foldMapping #-}"]
diff --git a/scripts/CaseMapping.hs b/scripts/CaseMapping.hs
--- a/scripts/CaseMapping.hs
+++ b/scripts/CaseMapping.hs
@@ -19,14 +19,20 @@
            Left err -> print err >> return undefined
            Right ms -> return ms
   h <- openFile oname WriteMode
-  mapM_ (hPutStrLn h) ["{-# LANGUAGE Rank2Types #-}"
+  let comments = map ("--" ++) $
+                 take 2 (cfComments cfs) ++ take 2 (scComments scs)
+  mapM_ (hPutStrLn h) $
+                      ["{-# LANGUAGE Rank2Types #-}"
                       ,"-- AUTOMATICALLY GENERATED - DO NOT EDIT"
-                      ,"-- Generated by scripts/SpecialCasing.hs"
+                      ,"-- Generated by scripts/SpecialCasing.hs"] ++
+                      comments ++
+                      [""
                       ,"module Data.Text.Fusion.CaseMapping where"
                       ,"import Data.Char"
                       ,"import Data.Text.Fusion.Internal"
                       ,""]
   mapM_ (hPutStrLn h) (mapSC "upper" upper toUpper scs)
   mapM_ (hPutStrLn h) (mapSC "lower" lower toLower scs)
+  mapM_ (hPutStrLn h) (mapSC "title" title toTitle scs)
   mapM_ (hPutStrLn h) (mapCF cfs)
   hClose h
diff --git a/scripts/SpecialCasing.hs b/scripts/SpecialCasing.hs
--- a/scripts/SpecialCasing.hs
+++ b/scripts/SpecialCasing.hs
@@ -4,13 +4,17 @@
 
 module SpecialCasing
     (
-      Case(..)
+      SpecialCasing(..)
+    , Case(..)
     , parseSC
     , mapSC
     ) where
 
 import Arsec
 
+data SpecialCasing = SC { scComments :: [Comment], scCasing :: [Case] }
+                   deriving (Show)
+
 data Case = Case {
       code :: Char
     , lower :: [Char]
@@ -20,8 +24,8 @@
     , name :: String
     } deriving (Eq, Ord, Show)
 
-entries :: Parser [Case]
-entries = many comment *> many (entry <* many comment)
+entries :: Parser SpecialCasing
+entries = SC <$> many comment <*> many (entry <* many comment)
   where
     entry = Case <$> unichar <* semi
                  <*> unichars
@@ -30,11 +34,13 @@
                  <*> manyTill anyToken (string "# ")
                  <*> manyTill anyToken (char '\n')
 
-parseSC :: FilePath -> IO (Either ParseError [Case])
+parseSC :: FilePath -> IO (Either ParseError SpecialCasing)
 parseSC name = parse entries name <$> readFile name
 
-mapSC :: String -> (Case -> String) -> (Char -> Char) -> [Case] -> [String]
-mapSC which access twiddle ms = typ ++ (map nice . filter p $ ms) ++ [last]
+mapSC :: String -> (Case -> String) -> (Char -> Char) -> SpecialCasing
+         -> [String]
+mapSC which access twiddle (SC _ ms) =
+    typ ++ (map nice . filter p $ ms) ++ [last]
   where
     typ = [which ++ "Mapping :: forall s. Char -> s -> Step (CC s) Char"
            ,"{-# INLINE " ++ which ++ "Mapping #-}"]
diff --git a/tests/Tests/Properties.hs b/tests/Tests/Properties.hs
--- a/tests/Tests/Properties.hs
+++ b/tests/Tests/Properties.hs
@@ -1,7 +1,7 @@
 -- | General quicktest properties for the text library
 --
-{-# LANGUAGE BangPatterns, FlexibleInstances, OverloadedStrings,
-             ScopedTypeVariables, TypeSynonymInstances, CPP #-}
+{-# LANGUAGE BangPatterns, CPP, FlexibleInstances, OverloadedStrings,
+             ScopedTypeVariables, TypeSynonymInstances #-}
 {-# OPTIONS_GHC -fno-enable-rewrite-rules #-}
 {-# OPTIONS_GHC -fno-warn-missing-signatures #-}
 module Tests.Properties
@@ -27,7 +27,6 @@
 import Data.Text.Search (indices)
 import Data.Word (Word, Word8, Word16, Word32, Word64)
 import Numeric (showHex)
-import Prelude hiding (catch, replicate)
 import Test.Framework (Test, testGroup)
 import Test.Framework.Providers.QuickCheck2 (testProperty)
 import qualified Data.Bits as Bits (shiftL, shiftR)
@@ -54,6 +53,12 @@
 import Tests.Utils
 import qualified Tests.SlowFunctions as Slow
 
+#if MIN_VERSION_base(4,6,0)
+import Prelude hiding (replicate)
+#else
+import Prelude hiding (catch, replicate)
+#endif
+
 t_pack_unpack       = (T.unpack . T.pack) `eq` id
 tl_pack_unpack      = (TL.unpack . TL.pack) `eq` id
 t_stream_unstream   = (S.unstream . S.stream) `eq` id
@@ -93,6 +98,18 @@
 t_utf32BE    = forAll genUnicode $ (E.decodeUtf32BE . E.encodeUtf32BE) `eq` id
 tl_utf32BE   = forAll genUnicode $ (EL.decodeUtf32BE . EL.encodeUtf32BE) `eq` id
 
+t_utf8_incr  = do
+        Positive n <- arbitrary
+        forAll genUnicode $ recode n `eq` id
+    where recode n = T.concat . feedChunksOf n E.streamDecodeUtf8 . E.encodeUtf8
+          feedChunksOf :: Int -> (B.ByteString -> E.Decoding) -> B.ByteString
+                       -> [T.Text]
+          feedChunksOf n f bs
+            | B.null bs  = []
+            | otherwise  = let (a,b) = B.splitAt n bs
+                               E.Some t _ f' = f a
+                           in t : feedChunksOf n f' b
+
 -- This is a poor attempt to ensure that the error handling paths on
 -- decode are exercised in some way.  Proper testing would be rather
 -- more involved.
@@ -222,7 +239,7 @@
 
 splitOn :: (Eq a) => [a] -> [a] -> [[a]]
 splitOn pat src0
-    | l == 0    = error "empty"
+    | l == 0    = error "splitOn: empty"
     | otherwise = go src0
   where
     l           = length pat
@@ -771,6 +788,7 @@
       testProperty "tl_latin1" tl_latin1,
       testProperty "t_utf8" t_utf8,
       testProperty "t_utf8'" t_utf8',
+      testProperty "t_utf8_incr" t_utf8_incr,
       testProperty "tl_utf8" tl_utf8,
       testProperty "tl_utf8'" tl_utf8',
       testProperty "t_utf16LE" t_utf16LE,
diff --git a/tests/Tests/Regressions.hs b/tests/Tests/Regressions.hs
--- a/tests/Tests/Regressions.hs
+++ b/tests/Tests/Regressions.hs
@@ -8,7 +8,7 @@
 
 import Control.Exception (SomeException, handle)
 import System.IO
-import Test.HUnit (assertBool, assertFailure)
+import Test.HUnit (assertBool, assertEqual, assertFailure)
 import qualified Data.ByteString as B
 import Data.ByteString.Char8 ()
 import qualified Data.ByteString.Lazy as LB
@@ -17,6 +17,7 @@
 import qualified Data.Text.IO as T
 import qualified Data.Text.Lazy as LT
 import qualified Data.Text.Lazy.Encoding as LE
+import qualified Data.Text.Unsafe as T
 import qualified Test.Framework as F
 import qualified Test.Framework.Providers.HUnit as F
 
@@ -59,10 +60,23 @@
   let t = TE.decodeUtf8With (\_ _ -> Just '\xdc00') "\x80"
   assertBool "broken error recovery shouldn't break us" (t == "\xfffd")
 
+-- Reported by Eric Seidel: we mishandled mapping Chars that fit in a
+-- single Word16 to Chars that require two.
+mapAccumL_resize :: IO ()
+mapAccumL_resize = do
+  let f a _ = (a, '\65536')
+      count = 5
+      val   = T.mapAccumL f (0::Int) (T.replicate count "a")
+  assertEqual "mapAccumL should correctly fill buffers for two-word results"
+             (0, T.replicate count "\65536") val
+  assertEqual "mapAccumL should correctly size buffers for two-word results"
+             (count * 2) (T.lengthWord16 (snd val))
+
 tests :: F.Test
 tests = F.testGroup "Regressions"
     [ F.testCase "hGetContents_crash" hGetContents_crash
     , F.testCase "lazy_encode_crash" lazy_encode_crash
+    , F.testCase "mapAccumL_resize" mapAccumL_resize
     , F.testCase "replicate_crash" replicate_crash
     , F.testCase "utf8_decode_unsafe" utf8_decode_unsafe
     ]
diff --git a/tests/text-tests.cabal b/tests/text-tests.cabal
--- a/tests/text-tests.cabal
+++ b/tests/text-tests.cabal
@@ -64,6 +64,7 @@
 library
   hs-source-dirs: ..
   c-sources: ../cbits/cbits.c
+  include-dirs: ../include
   exposed-modules:
     Data.Text
     Data.Text.Array
diff --git a/text.cabal b/text.cabal
--- a/text.cabal
+++ b/text.cabal
@@ -1,5 +1,5 @@
 name:           text
-version:        0.11.3.1
+version:        1.0.0.0
 homepage:       https://github.com/bos/text
 bug-reports:    https://github.com/bos/text/issues
 synopsis:       An efficient packed Unicode text type.
@@ -30,16 +30,7 @@
     non-standard encodings, text breaking, and locales), see
     the @text-icu@ package:
     <http://hackage.haskell.org/package/text-icu>
-    .
-    &#8212;&#8212; RELEASE NOTES &#8212;&#8212;
-    .
-    Changes in 0.11.2.0:
-    .
-    * String literals are now converted directly from the format in
-      which GHC stores them into 'Text', without an intermediate
-      transformation through 'String', and without inlining of
-      conversion code at each site where a string literal is declared.
-    .
+
 license:        BSD3
 license-file:   LICENSE
 author:         Bryan O'Sullivan <bos@serpentine.com>
@@ -56,9 +47,12 @@
     benchmarks/cbits/*.c
     benchmarks/haskell/*.hs
     benchmarks/haskell/Benchmarks/*.hs
+    benchmarks/haskell/Benchmarks/Programs/*.hs
     benchmarks/python/*.py
     benchmarks/ruby/*.rb
     benchmarks/text-benchmarks.cabal
+    changelog
+    include/*.h
     scripts/*.hs
     tests-and-benchmarks.markdown
     tests/*.hs
@@ -77,7 +71,8 @@
   default: False
 
 library
-  c-sources: cbits/cbits.c
+  c-sources:    cbits/cbits.c
+  include-dirs: include
 
   exposed-modules:
     Data.Text
@@ -124,42 +119,32 @@
     Data.Text.Util
 
   build-depends:
-    array,
-    base       < 5,
-    bytestring >= 0.9
-  if impl(ghc >= 6.10)
-    build-depends:
-      ghc-prim, base >= 4, deepseq >= 1.1.0.0
-    cpp-options: -DHAVE_DEEPSEQ
-  else
-    build-depends: extensible-exceptions
-    extensions: ScopedTypeVariables
+    array      >= 0.3,
+    base       >= 4.2 && < 5,
+    bytestring >= 0.9,
+    deepseq    >= 1.1.0.0,
+    ghc-prim   >= 0.2
 
-  ghc-options: -Wall -funbox-strict-fields -O2
-  if impl(ghc >= 6.8)
-    ghc-options: -fwarn-tabs
+  cpp-options: -DHAVE_DEEPSEQ
+  ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2
   if flag(developer)
     ghc-prof-options: -auto-all
     ghc-options: -Werror
     cpp-options: -DASSERTS
 
-  if impl(ghc >= 6.11)
-    if flag(integer-simple)
-      cpp-options: -DINTEGER_SIMPLE
-      build-depends: integer-simple >= 0.1 && < 0.5
-    else
-      cpp-options: -DINTEGER_GMP
-      build-depends: integer-gmp >= 0.2
-
-  if impl(ghc >= 6.9) && impl(ghc < 6.11)
+  if flag(integer-simple)
+    cpp-options: -DINTEGER_SIMPLE
+    build-depends: integer-simple >= 0.1 && < 0.5
+  else
     cpp-options: -DINTEGER_GMP
-    build-depends: integer >= 0.1 && < 0.2
+    build-depends: integer-gmp >= 0.2
 
 test-suite tests
   type:           exitcode-stdio-1.0
   hs-source-dirs: tests .
   main-is:        Tests.hs
   c-sources:      cbits/cbits.c
+  include-dirs:   include
 
   ghc-options:
     -Wall -threaded -O0 -rtsopts
@@ -181,17 +166,12 @@
     test-framework-hunit >= 0.2,
     test-framework-quickcheck2 >= 0.2
 
-  if impl(ghc >= 6.11)
-    if flag(integer-simple)
-      cpp-options: -DINTEGER_SIMPLE
-      build-depends: integer-simple >= 0.1 && < 0.5
-    else
-      cpp-options: -DINTEGER_GMP
-      build-depends: integer-gmp >= 0.2
-
-  if impl(ghc >= 6.9) && impl(ghc < 6.11)
+  if flag(integer-simple)
+    cpp-options: -DINTEGER_SIMPLE
+    build-depends: integer-simple >= 0.1 && < 0.5
+  else
     cpp-options: -DINTEGER_GMP
-    build-depends: integer >= 0.1 && < 0.2
+    build-depends: integer-gmp >= 0.2
 
 source-repository head
   type:     git
