{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Language.Ltml.ToLaTeX.PDFGenerator
( generatePDFFromSection
, generatePDF
, generateLaTeX
) where
import Control.Exception (Exception (displayException), SomeException, try)
import Control.Lens (view)
import Control.Monad.State (runState)
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy as BSL
import Data.Text (Text)
import qualified Data.Text.Encoding as TE
import Language.Ltml.ToLaTeX.GlobalState
( initialGlobalState
, labelToRef
, preDocument
)
import Language.Ltml.ToLaTeX.PreLaTeXType (document)
import Language.Ltml.ToLaTeX.Renderer (renderLaTeX)
import Language.Ltml.ToLaTeX.ToLaTeX (toLaTeX)
import Language.Ltml.ToLaTeX.ToPreLaTeXM (ToPreLaTeXM (toPreLaTeXM))
import System.Exit (ExitCode (..))
import System.FilePath ((</>))
import System.IO (hClose)
import System.IO.Temp (withSystemTempDirectory)
import System.Process
( CreateProcess (cwd, std_err, std_in, std_out)
, StdStream (CreatePipe)
, createProcess
, proc
, waitForProcess
)
generatePDF
:: (ToPreLaTeXM a) => a -> IO (Either String BSL.ByteString)
generatePDF :: forall a. ToPreLaTeXM a => a -> IO (Either String ByteString)
generatePDF a
input = do
Either SomeException ByteString
res <- IO ByteString -> IO (Either SomeException ByteString)
forall e a. Exception e => IO a -> IO (Either e a)
try (IO ByteString -> IO (Either SomeException ByteString))
-> IO ByteString -> IO (Either SomeException ByteString)
forall a b. (a -> b) -> a -> b
$ a -> IO ByteString
forall p. ToPreLaTeXM p => p -> IO ByteString
compilePDF a
input
case Either SomeException ByteString
res of
Left (SomeException
e :: SomeException) -> Either String ByteString -> IO (Either String ByteString)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String ByteString -> IO (Either String ByteString))
-> Either String ByteString -> IO (Either String ByteString)
forall a b. (a -> b) -> a -> b
$ String -> Either String ByteString
forall a b. a -> Either a b
Left (SomeException -> String
forall e. Exception e => e -> String
displayException SomeException
e)
Right ByteString
pdf -> Either String ByteString -> IO (Either String ByteString)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String ByteString -> IO (Either String ByteString))
-> Either String ByteString -> IO (Either String ByteString)
forall a b. (a -> b) -> a -> b
$ ByteString -> Either String ByteString
forall a b. b -> Either a b
Right ByteString
pdf
compilePDF
:: (ToPreLaTeXM p) => p -> IO BSL.ByteString
compilePDF :: forall p. ToPreLaTeXM p => p -> IO ByteString
compilePDF p
input =
String -> (String -> IO ByteString) -> IO ByteString
forall (m :: * -> *) a.
(MonadIO m, MonadMask m) =>
String -> (String -> m a) -> m a
withSystemTempDirectory String
"latex-temp" ((String -> IO ByteString) -> IO ByteString)
-> (String -> IO ByteString) -> IO ByteString
forall a b. (a -> b) -> a -> b
$ \String
tmpDir -> do
let texFile :: String
texFile = String
tmpDir String -> String -> String
</> String
"input.tex"
pdfFile :: String
pdfFile = String
tmpDir String -> String -> String
</> String
"input.pdf"
let latexSource :: Text
latexSource = p -> Text
forall a. ToPreLaTeXM a => a -> Text
generateLaTeX p
input
String -> ByteString -> IO ()
BS.writeFile String
texFile (Text -> ByteString
TE.encodeUtf8 Text
latexSource)
(ExitCode
exitCode, ByteString
stdout, ByteString
_) <- String -> String -> IO (ExitCode, ByteString, ByteString)
runLatex String
texFile String
tmpDir
case ExitCode
exitCode of
ExitFailure Int
_ -> ByteString -> IO ByteString
forall a. ByteString -> IO a
failLatex ByteString
stdout
ExitCode
ExitSuccess -> String -> IO ByteString
BSL.readFile String
pdfFile
generateLaTeX :: (ToPreLaTeXM a) => a -> Text
generateLaTeX :: forall a. ToPreLaTeXM a => a -> Text
generateLaTeX a
input =
let (PreLaTeX
res, GlobalState
gs) = State GlobalState PreLaTeX
-> GlobalState -> (PreLaTeX, GlobalState)
forall s a. State s a -> s -> (a, s)
runState (a -> State GlobalState PreLaTeX
forall a. ToPreLaTeXM a => a -> State GlobalState PreLaTeX
toPreLaTeXM a
input) GlobalState
initialGlobalState
in LaTeX -> Text
renderLaTeX (LaTeX -> Text) -> LaTeX -> Text
forall a b. (a -> b) -> a -> b
$
Map Label Text -> PreLaTeX -> LaTeX
toLaTeX
(Getting (Map Label Text) GlobalState (Map Label Text)
-> GlobalState -> Map Label Text
forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Getting (Map Label Text) GlobalState (Map Label Text)
Lens' GlobalState (Map Label Text)
labelToRef GlobalState
gs)
(Getting PreLaTeX GlobalState PreLaTeX -> GlobalState -> PreLaTeX
forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Getting PreLaTeX GlobalState PreLaTeX
Lens' GlobalState PreLaTeX
preDocument GlobalState
gs PreLaTeX -> PreLaTeX -> PreLaTeX
forall a. Semigroup a => a -> a -> a
<> PreLaTeX -> PreLaTeX
document PreLaTeX
res)
runLatex :: FilePath -> FilePath -> IO (ExitCode, BS.ByteString, BS.ByteString)
runLatex :: String -> String -> IO (ExitCode, ByteString, ByteString)
runLatex String
texFile String
workDir = do
(Just Handle
hin, Just Handle
hout, Just Handle
herr, ProcessHandle
ph) <-
CreateProcess
-> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
createProcess
(String -> [String] -> CreateProcess
proc String
"latexmk" [String
"-pdf", String
"-interaction=nonstopmode", String
"-halt-on-error", String
texFile])
{ cwd = Just workDir
, std_in = CreatePipe
, std_out = CreatePipe
, std_err = CreatePipe
}
Handle -> IO ()
hClose Handle
hin
ByteString
out <- Handle -> IO ByteString
BS.hGetContents Handle
hout
ByteString
err <- Handle -> IO ByteString
BS.hGetContents Handle
herr
ExitCode
exitCode <- ProcessHandle -> IO ExitCode
waitForProcess ProcessHandle
ph
(ExitCode, ByteString, ByteString)
-> IO (ExitCode, ByteString, ByteString)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (ExitCode
exitCode, ByteString
out, ByteString
err)
failLatex :: BS.ByteString -> IO a
failLatex :: forall a. ByteString -> IO a
failLatex ByteString
stdout =
String -> IO a
forall a. String -> IO a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (ByteString -> String
BS.unpack ByteString
stdout)
generatePDFFromSection :: Text -> IO (Either String BSL.ByteString)
generatePDFFromSection :: Text -> IO (Either String ByteString)
generatePDFFromSection = Text -> IO (Either String ByteString)
forall a. HasCallStack => a
undefined