{-# LANGUAGE OverloadedStrings #-} -- | The final purpose of this module is to render a Text value -- from a 'LaTeX' value. The interface is abstracted via a typeclass -- so you can cast to 'Text' other types as well. Also, some other -- handy 'Text'-related functions are defined. module Text.LaTeX.Base.Render ( -- * Re-exports Text , module Data.String -- * Render class , Render (..) , renderAppend , renderChars , renderCommas , renderFile , rendertex -- * Reading files , readFileTex -- * Util , showFloat ) where import Text.LaTeX.Base.Syntax import Text.LaTeX.Base.Class import Data.String import Data.List (intersperse) import qualified Data.ByteString as B import Data.Word (Word8) import Numeric (showFFloat) import Data.Text (Text,lines,unlines) import Data.Text.Encoding import Data.Text.Lazy (toStrict) import Data.Text.Lazy.Builder (Builder) import qualified Data.Text.Lazy.Builder as Builder import qualified Data.Text.Lazy.Builder.Int as Builder import qualified Data.Text.Lazy.Builder.RealFloat as Builder -- | Class of values that can be transformed to 'Text'. -- You mainly will use this to obtain the 'Text' output -- of a 'LaTeX' value. If you are going to write the result -- in a file, consider to use 'renderFile'. -- -- Consider also to use 'rendertex' to get 'Render'able values -- into 'LaTeX' blocks. -- -- If you want to make a type instance of 'Render' and you already -- have a 'Show' instance, you can use the default instance. -- -- > render = fromString . show -- class Show a => Render a where render :: a -> Text renderBuilder :: a -> Builder -- render = fromString . show renderBuilder = Builder.fromText . render renderDefault :: Render a => a -> Text renderDefault = toStrict . Builder.toLazyText . renderBuilder -- | This instance escapes LaTeX reserved characters. instance Render Text where render = protectText -- | Render every element of a list and append results. renderAppend :: Render a => [a] -> Text renderAppend = mconcat . fmap render renderAppendBuilder :: Render a => [a] -> Builder renderAppendBuilder = foldMap renderBuilder -- | Render every element of a list and append results, -- separated by the given 'Char'. renderChars :: Render a => Char -> [a] -> Text renderChars c = mconcat . intersperse (fromString [c]) . fmap render renderCharsBuilder :: Render a => Char -> [a] -> Builder renderCharsBuilder c = mconcat . intersperse (Builder.singleton c) . fmap renderBuilder -- | Render every element of a list and append results, -- separated by commas. renderCommas :: Render a => [a] -> Text renderCommas = renderChars ',' renderCommasBuilder :: Render a => [a] -> Builder renderCommasBuilder = renderCharsBuilder ',' -- | Use this function to render a 'LaTeX' (or another -- one in the 'Render' class) value directly -- in a file. renderFile :: Render a => FilePath -> a -> IO () renderFile f = B.writeFile f . encodeUtf8 . render -- | If you are going to insert the content of a file -- in your 'LaTeX' data, use this function to ensure -- your encoding is correct. readFileTex :: FilePath -> IO Text readFileTex = fmap decodeUtf8 . B.readFile -- | If you can transform a value to 'Text', you can -- insert that 'Text' in your 'LaTeX' code. -- That is what this function does. -- -- /Warning: /'rendertex'/ does not escape LaTeX reserved characters./ -- /Use /'protectText'/ to escape them./ rendertex :: (Render a,LaTeXC l) => a -> l rendertex = fromLaTeX . TeXRaw . render -- Render instances instance Render Measure where render (Pt x) = render x <> "pt" render (Mm x) = render x <> "mm" render (Cm x) = render x <> "cm" render (In x) = render x <> "in" render (Ex x) = render x <> "ex" render (Em x) = render x <> "em" render (CustomMeasure x) = render x -- LaTeX instances instance Render LaTeX where renderBuilder (TeXRaw t) = Builder.fromText t renderBuilder (TeXComm name []) = "\\" <> fromString name <> "{}" renderBuilder (TeXComm name args) = "\\" <> fromString name <> renderAppendBuilder args renderBuilder (TeXCommS name) = "\\" <> fromString name renderBuilder (TeXEnv name args c) = "\\begin{" <> fromString name <> "}" <> renderAppendBuilder args <> renderBuilder c <> "\\end{" <> fromString name <> "}" renderBuilder (TeXMath Dollar l) = "$" <> renderBuilder l <> "$" renderBuilder (TeXMath DoubleDollar l) = "$$" <> renderBuilder l <> "$$" renderBuilder (TeXMath Square l) = "\\[" <> renderBuilder l <> "\\]" renderBuilder (TeXMath Parentheses l) = "\\(" <> renderBuilder l <> "\\)" renderBuilder (TeXLineBreak m b) = "\\\\" <> maybe mempty (\x -> "[" <> renderBuilder x <> "]") m <> ( if b then "*" else mempty ) renderBuilder (TeXBraces l) = "{" <> renderBuilder l <> "}" renderBuilder (TeXComment c) = let xs = Data.Text.lines c in if null xs then "%\n" else Builder.fromText $ Data.Text.unlines $ fmap ("%" <>) xs renderBuilder (TeXSeq l1 l2) = renderBuilder l1 <> renderBuilder l2 renderBuilder TeXEmpty = mempty render = renderDefault instance Render TeXArg where renderBuilder (FixArg l) = "{" <> renderBuilder l <> "}" renderBuilder (OptArg l) = "[" <> renderBuilder l <> "]" renderBuilder (MOptArg []) = mempty renderBuilder (MOptArg ls) = "[" <> renderCommasBuilder ls <> "]" renderBuilder (SymArg l) = "<" <> renderBuilder l <> ">" renderBuilder (MSymArg []) = mempty renderBuilder (MSymArg ls) = "<" <> renderCommasBuilder ls <> ">" renderBuilder (ParArg l) = "(" <> renderBuilder l <> ")" renderBuilder (MParArg []) = mempty renderBuilder (MParArg ls) = "(" <> renderCommasBuilder ls <> ")" render = renderDefault -- Other instances -- | Show a signed floating number using standard decimal notation using 5 decimals. showFloat :: RealFloat a => a -> String showFloat x = showFFloat (Just 5) x [] instance Render Int where renderBuilder = Builder.decimal render = renderDefault instance Render Integer where renderBuilder = Builder.decimal render = renderDefault instance Render Float where renderBuilder = Builder.formatRealFloat Builder.Fixed (Just 5) render = renderDefault instance Render Double where renderBuilder = Builder.formatRealFloat Builder.Fixed (Just 5) render = renderDefault instance Render Word8 where renderBuilder = Builder.decimal render = renderDefault -- | 'Render' instance for 'Bool'. It satisfies @render True = "true"@ and @render False = "false"@. instance Render Bool where render True = "true" render _ = "false" instance Render a => Render [a] where renderBuilder xs = "[" <> renderCommasBuilder xs <> "]" render = renderDefault