@@ -42,6 +42,7 @@ module Data.Text.Fusion.Common
43 , toCaseFold
43 , toCaseFold
47 -- ** Justification
48 -- ** Justification
@@ -102,13 +103,15 @@ module Data.Text.Fusion.Common
103import Prelude (Bool(..), Char, Eq(..), Int, Integral, Maybe(..),
104import Prelude (Bool(..), Char, Eq(..), Int, Integral, Maybe(..),
104 Ord(..), Ordering(..), String, (.), ($), (+), (-), (*), (++),
105 Ord(..), Ordering(..), String, (.), ($), (+), (-), (*), (++),
105− (&&), fromIntegral, otherwise)
106+ (&&), fromIntegral, not, otherwise)
106import qualified Data.List as L
107import qualified Data.List as L
107import qualified Prelude as P
108import qualified Prelude as P
108import Data.Bits (shiftL)
109import Data.Bits (shiftL)
110+import Data.Char (isLetter)
109import Data.Int (Int64)
111import Data.Int (Int64)
110import Data.Text.Fusion.Internal
112import Data.Text.Fusion.Internal
111−import Data.Text.Fusion.CaseMapping (foldMapping, lowerMapping, upperMapping)
113+import Data.Text.Fusion.CaseMapping (foldMapping, lowerMapping, titleMapping,
112import Data.Text.Fusion.Size
115import Data.Text.Fusion.Size
113import GHC.Prim (Addr#, chr#, indexCharOffAddr#, ord#)
116import GHC.Prim (Addr#, chr#, indexCharOffAddr#, ord#)
114import GHC.Types (Char(..), Int(..))
117import GHC.Types (Char(..), Int(..))
@@ -432,6 +435,38 @@ toLower :: Stream Char -> Stream Char
432toLower = caseConvert lowerMapping
435toLower = caseConvert lowerMapping
433{-# INLINE [0] toLower #-}
436{-# INLINE [0] toLower #-}
438+-- | /O(n)/ Convert a string to title case, using simple case
441+-- Unlike the other case conversion functions, this function does not
442+-- convert every letter of its input. Instead, the first letter is
443+-- converted to title case, as is every subsequent letter that
444+-- immediately follows a non-letter.
446+-- The result string may be longer than the input string. For example,
447+-- the Latin small ligature fl (U+FB02) is converted to the
448+-- sequence Latin capital letter F (U+0046) followed by Latin small
449+-- letter l (U+006C).
451+-- /Note/: this function does not take language or culture specific
452+-- rules into account. For instance, in English, different style
453+-- guides disagree on whether the book name \"The Hill of the Red
454+-- Fox\" is correctly title cased—but this function will
455+-- capitalize /every/ word.
456+toTitle :: Stream Char -> Stream Char
457+toTitle (Stream next0 s0 len) = Stream next (CC (False :*: s0) '\0' '\0') len
459+ next (CC (letter :*: s) '\0' _) =
462+ Skip s' -> Skip (CC (letter :*: s') '\0' '\0')
464+ | not letter && letter' -> titleMapping c (letter' :*: s')
465+ | otherwise -> Yield c (CC (letter' :*: s') '\0' '\0')
466+ where letter' = isLetter c
467+ next (CC s a b) = Yield a (CC s b '\0')
468+{-# INLINE [0] toTitle #-}
435justifyLeftI :: Integral a => a -> Char -> Stream Char -> Stream Char
470justifyLeftI :: Integral a => a -> Char -> Stream Char -> Stream Char
436justifyLeftI k c (Stream next0 s0 len) =
471justifyLeftI k c (Stream next0 s0 len) =
437 Stream next (s0 :*: S1 :*: 0) (larger (fromIntegral k) len)
472 Stream next (s0 :*: S1 :*: 0) (larger (fromIntegral k) len)