pandoc/src/Text/Pandoc/PDF.hs

153 lines
5.7 KiB
Haskell
Raw Normal View History

{-# LANGUAGE OverloadedStrings, CPP #-}
{-
Copyright (C) 2012 John MacFarlane <jgm@berkeley.edu>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-}
{- |
Module : Text.Pandoc.PDF
Copyright : Copyright (C) 2012 John MacFarlane
License : GNU GPL, version 2 or above
Maintainer : John MacFarlane <jgm@berkeley.edu>
Stability : alpha
Portability : portable
Conversion of LaTeX documents to PDF.
-}
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
2012-01-20 19:04:08 -08:00
import System.Exit (ExitCode (..))
import System.FilePath
import System.Directory
import System.Process
import System.Environment
2012-01-20 19:04:08 -08:00
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)
withTempDir :: String -> (FilePath -> IO a) -> IO a
withTempDir =
#ifdef _WINDOWS
withTempDirectory "."
#else
withSystemTempDirectory
#endif
tex2pdf :: String -- ^ tex program (pdflatex, lualatex, xelatex)
-> String -- ^ latex source
-> IO (Either ByteString ByteString)
tex2pdf program source = withTempDir "tex2pdf." $ \tmpdir ->
2012-01-20 19:04:08 -08:00
tex2pdf' tmpdir program source
2012-01-20 19:04:08 -08:00
tex2pdf' :: FilePath -- ^ temp directory for output
-> String -- ^ tex program
-> String -- ^ tex source
-> IO (Either ByteString ByteString)
2012-01-20 19:04:08 -08:00
tex2pdf' tmpDir program source = do
let numruns = if "\\tableofcontents" `isInfixOf` source
then 3 -- to get page numbers
else 2 -- 1 run won't give you PDF bookmarks
(exit, log', mbPdf) <- runTeXProgram program numruns tmpDir source
2012-01-20 19:52:56 -08:00
let msg = "Error producing PDF from TeX source."
case (exit, mbPdf) of
2012-01-20 19:52:56 -08:00
(ExitFailure _, _) -> return $ Left $
msg <> "\n" <> extractMsg log'
(ExitSuccess, Nothing) -> return $ Left msg
(ExitSuccess, Just pdf) -> return $ Right pdf
2012-01-20 19:52:56 -08:00
(<>) :: ByteString -> ByteString -> ByteString
(<>) = B.append
-- parsing output
extractMsg :: ByteString -> ByteString
2012-01-20 19:04:08 -08:00
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
2012-01-21 14:18:36 -08:00
if null msg'
then log'
else BC.unlines (msg'' ++ lineno)
-- running tex programs
-- Run a TeX program on an input bytestring and return (exit code,
-- contents of stdout, contents of produced PDF if any). Rerun
-- a fixed number of times to resolve references.
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]
env' <- getEnvironment
let texinputs = maybe (tmpDir ++ ":") ((tmpDir ++ ":") ++)
$ lookup "TEXINPUTS" env'
let env'' = ("TEXINPUTS", texinputs) :
[(k,v) | (k,v) <- env', k /= "TEXINPUTS"]
(exit, out, err) <- readCommand (Just env'') 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)
-- utility functions
-- Run a command and return exitcode, contents of stdout, and
-- contents of stderr. (Based on
-- 'readProcessWithExitCode' from 'System.Process'.)
readCommand :: Maybe [(String, String)] -- ^ environment variables
-> FilePath -- ^ command to run
-> [String] -- ^ any arguments
-> IO (ExitCode,ByteString,ByteString) -- ^ exit, stdout, stderr
readCommand mbenv cmd args = do
(Just inh, Just outh, Just errh, pid) <-
createProcess (proc cmd args){ env = mbenv,
std_in = CreatePipe,
std_out = CreatePipe,
std_err = CreatePipe }
outMVar <- newEmptyMVar
-- fork off a thread to start consuming stdout
out <- B.hGetContents outh
_ <- forkIO $ evaluate (B.length out) >> putMVar outMVar ()
-- fork off a thread to start consuming stderr
err <- B.hGetContents errh
_ <- forkIO $ evaluate (B.length err) >> putMVar outMVar ()
-- now write and flush any input
hClose inh -- done with stdin
-- wait on the output
takeMVar outMVar
takeMVar outMVar
hClose outh
-- wait on the process
ex <- waitForProcess pid
return (ex, out, err)