module Text.Pandoc.PDF ( tex2pdf ) where
import System.IO.Temp
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as B
import qualified Data.ByteString.Lazy.Char8 as BC
import System.Exit (ExitCode (..))
import System.FilePath
import System.Directory
import System.Process
import Control.Exception (evaluate)
import System.IO (hClose)
import Control.Concurrent (putMVar, takeMVar, newEmptyMVar, forkIO)
import Text.Pandoc.UTF8 as UTF8
import Control.Monad (unless)
import Data.List (isInfixOf)
tex2pdf :: String
-> String
-> IO (Either ByteString ByteString)
tex2pdf program source = withSystemTempDirectory "tex2pdf" $ \tmpdir ->
tex2pdf' tmpdir program source
tex2pdf' :: FilePath
-> String
-> String
-> IO (Either ByteString ByteString)
tex2pdf' tmpDir program source = do
let numruns = if "\\tableofcontents" `isInfixOf` source
then 3
else 2
(exit, log', mbPdf) <- runTeXProgram program numruns tmpDir source
let msg = "Error producing PDF from TeX source."
case (exit, mbPdf) of
(ExitFailure _, _) -> return $ Left $
msg <> "\n" <> extractMsg log'
(ExitSuccess, Nothing) -> return $ Left msg
(ExitSuccess, Just pdf) -> return $ Right pdf
(<>) :: ByteString -> ByteString -> ByteString
(<>) = B.append
extractMsg :: ByteString -> ByteString
extractMsg log' = do
let msg' = dropWhile (not . ("!" `BC.isPrefixOf`)) $ BC.lines log'
let (msg'',rest) = break ("l." `BC.isPrefixOf`) msg'
let lineno = take 1 rest
if null msg'
then log'
else BC.unlines (msg'' ++ lineno)
runTeXProgram :: String -> Int -> FilePath -> String
-> IO (ExitCode, ByteString, Maybe ByteString)
runTeXProgram program runsLeft tmpDir source = do
let file = tmpDir </> "input.tex"
exists <- doesFileExist file
unless exists $ UTF8.writeFile file source
let programArgs = ["-halt-on-error", "-interaction", "nonstopmode",
"-output-directory", tmpDir, file]
(exit, out, err) <- readCommand program programArgs
if runsLeft > 1
then runTeXProgram program (runsLeft 1) tmpDir source
else do
let pdfFile = replaceDirectory (replaceExtension file ".pdf") tmpDir
pdfExists <- doesFileExist pdfFile
pdf <- if pdfExists
then Just `fmap` B.readFile pdfFile
else return Nothing
return (exit, out <> err, pdf)
readCommand :: FilePath
-> [String]
-> IO (ExitCode,ByteString,ByteString)
readCommand cmd args = do
(Just inh, Just outh, Just errh, pid) <-
createProcess (proc cmd args){ std_in = CreatePipe,
std_out = CreatePipe,
std_err = CreatePipe }
outMVar <- newEmptyMVar
out <- B.hGetContents outh
_ <- forkIO $ evaluate (B.length out) >> putMVar outMVar ()
err <- B.hGetContents errh
_ <- forkIO $ evaluate (B.length err) >> putMVar outMVar ()
hClose inh
takeMVar outMVar
takeMVar outMVar
hClose outh
ex <- waitForProcess pid
return (ex, out, err)