1{-# LANGUAGE OverloadedStrings, CPP, ScopedTypeVariables #-}
3Copyright (C) 2012-2016 John MacFarlane <jgm@berkeley.edu>
5This program is free software; you can redistribute it and/or modify
6it under the terms of the GNU General Public License as published by
7the Free Software Foundation; either version 2 of the License, or
8(at your option) any later version.
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13GNU General Public License for more details.
15You should have received a copy of the GNU General Public License
16along with this program; if not, write to the Free Software
17Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 Module : Text.Pandoc.PDF
22 Copyright : Copyright (C) 2012-2016 John MacFarlane
23 License : GNU GPL, version 2 or above
25 Maintainer : John MacFarlane <jgm@berkeley.edu>
27 Portability : portable
29Conversion of LaTeX documents to PDF.
31module Text.Pandoc.PDF ( makePDF ) where
33import Data.ByteString.Lazy (ByteString)
34import qualified Data.ByteString.Lazy as B
35import qualified Data.ByteString.Lazy.Char8 as BC
36import qualified Data.ByteString as BS
37import Data.Monoid ((<>))
38import System.Exit (ExitCode (..))
40import System.IO (stderr, stdout)
41import System.IO.Temp (withTempFile)
42import System.Directory
43import Data.Digest.Pure.SHA (showDigest, sha1)
44import System.Environment
45import Control.Monad (unless, when, (<=<))
46import qualified Control.Exception as E
47import Data.List (isInfixOf)
48import Data.Maybe (fromMaybe)
49import qualified Text.Pandoc.UTF8 as UTF8
50import Text.Pandoc.Definition
51import Text.Pandoc.Walk (walkM)
52import Text.Pandoc.Shared (fetchItem', warn, withTempDir, inDirectory,
54import Text.Pandoc.Writers.Shared (getField, metaToJSON)
55import Text.Pandoc.Options (WriterOptions(..), HTMLMathMethod(..))
56import Text.Pandoc.MIME (extensionFromMimeType, getMimeType)
57import Text.Pandoc.Process (pipeProcess)
58import qualified Data.ByteString.Lazy as BL
59import qualified Codec.Picture as JP
61import Data.List (intercalate)
65changePathSeparators :: FilePath -> FilePath
66changePathSeparators = intercalate "/" . splitDirectories
69makePDF :: String -- ^ pdf creator (pdflatex, lualatex,
70 -- xelatex, context, wkhtmltopdf)
71 -> (WriterOptions -> Pandoc -> String) -- ^ writer
72 -> WriterOptions -- ^ options
73 -> Pandoc -- ^ document
74 -> IO (Either ByteString ByteString)
75makePDF "wkhtmltopdf" writer opts doc@(Pandoc meta _) = do
76 let mathArgs = case writerHTMLMathMethod opts of
77 -- with MathJax, wait til all math is rendered:
78 MathJax _ -> ["--run-script", "MathJax.Hub.Register.StartupHook('End Typeset', function() { window.status = 'mathjax_loaded' });",
79 "--window-status", "mathjax_loaded"]
81 meta' <- metaToJSON opts (return . stringify) (return . stringify) meta
82 let toArgs (f, mbd) = maybe [] (\d -> ['-':'-':f, d]) mbd
83 let args = mathArgs ++
85 [("page-size", getField "papersize" meta')
86 ,("title", getField "title" meta')
87 ,("margin-bottom", fromMaybe (Just "1.2in")
88 (getField "margin-bottom" meta'))
89 ,("margin-top", fromMaybe (Just "1.25in")
90 (getField "margin-top" meta'))
91 ,("margin-right", fromMaybe (Just "1.25in")
92 (getField "margin-right" meta'))
93 ,("margin-left", fromMaybe (Just "1.25in")
94 (getField "margin-left" meta'))
96 let source = writer opts doc
97 html2pdf (writerVerbose opts) args source
98makePDF program writer opts doc = withTempDir "tex2pdf." $ \tmpdir -> do
99 doc' <- handleImages opts tmpdir doc
100 let source = writer opts doc'
101 args = writerLaTeXArgs opts
102 case takeBaseName program of
103 "context" -> context2pdf (writerVerbose opts) tmpdir source
104 prog | prog `elem` ["pdflatex", "lualatex", "xelatex"]
105 -> tex2pdf' (writerVerbose opts) args tmpdir program source
106 _ -> return $ Left $ UTF8.fromStringLazy $ "Unknown program " ++ program
108handleImages :: WriterOptions
109 -> FilePath -- ^ temp dir to store images
110 -> Pandoc -- ^ document
112handleImages opts tmpdir = walkM (convertImages tmpdir) <=< walkM (handleImage' opts tmpdir)
114handleImage' :: WriterOptions
118handleImage' opts tmpdir (Image attr ils (src,tit)) = do
119 exists <- doesFileExist src
121 then return $ Image attr ils (src,tit)
123 res <- fetchItem' (writerMediaBag opts) (writerSourceURL opts) src
125 Right (contents, Just mime) -> do
126 let ext = fromMaybe (takeExtension src) $
127 extensionFromMimeType mime
128 let basename = showDigest $ sha1 $ BL.fromChunks [contents]
129 let fname = tmpdir </> basename <.> ext
130 BS.writeFile fname contents
131 return $ Image attr ils (fname,tit)
133 warn $ "Could not find image `" ++ src ++ "', skipping..."
136handleImage' _ _ x = return x
138convertImages :: FilePath -> Inline -> IO Inline
139convertImages tmpdir (Image attr ils (src, tit)) = do
140 img <- convertImage tmpdir src
143 Left e -> src <$ warn e
144 Right fp -> return fp
145 return (Image attr ils (newPath, tit))
146convertImages _ x = return x
148-- Convert formats which do not work well in pdf to png
149convertImage :: FilePath -> FilePath -> IO (Either String FilePath)
150convertImage tmpdir fname =
152 Just "image/png" -> doNothing
153 Just "image/jpeg" -> doNothing
154 Just "application/pdf" -> doNothing
155 _ -> JP.readImage fname >>= \res ->
157 Left _ -> return $ Left $ "Unable to convert `" ++
158 fname ++ "' for use with pdflatex."
160 E.catch (Right fileOut <$ JP.savePngImage fileOut img) $
161 \(e :: E.SomeException) -> return (Left (show e))
163 fileOut = replaceDirectory (replaceExtension fname ".png") tmpdir
164 mime = getMimeType fname
165 doNothing = return (Right fname)
167tex2pdf' :: Bool -- ^ Verbose output
168 -> [String] -- ^ Arguments to the latex-engine
169 -> FilePath -- ^ temp directory for output
170 -> String -- ^ tex program
171 -> String -- ^ tex source
172 -> IO (Either ByteString ByteString)
173tex2pdf' verbose args tmpDir program source = do
174 let numruns = if "\\tableofcontents" `isInfixOf` source
175 then 3 -- to get page numbers
176 else 2 -- 1 run won't give you PDF bookmarks
177 (exit, log', mbPdf) <- runTeXProgram verbose program args 1 numruns tmpDir source
178 case (exit, mbPdf) of
179 (ExitFailure _, _) -> do
180 let logmsg = extractMsg log'
183 x | "! Package inputenc Error" `BC.isPrefixOf` x
184 && program /= "xelatex"
185 -> "\nTry running pandoc with --latex-engine=xelatex."
187 return $ Left $ logmsg <> extramsg
188 (ExitSuccess, Nothing) -> return $ Left ""
189 (ExitSuccess, Just pdf) -> return $ Right pdf
193extractMsg :: ByteString -> ByteString
195 let msg' = dropWhile (not . ("!" `BC.isPrefixOf`)) $ BC.lines log'
196 let (msg'',rest) = break ("l." `BC.isPrefixOf`) msg'
197 let lineno = take 1 rest
200 else BC.unlines (msg'' ++ lineno)
202extractConTeXtMsg :: ByteString -> ByteString
203extractConTeXtMsg log' = do
205 dropWhile (not . ("tex error" `BC.isPrefixOf`)) $ BC.lines log'
210-- running tex programs
212-- Run a TeX program on an input bytestring and return (exit code,
213-- contents of stdout, contents of produced PDF if any). Rerun
214-- a fixed number of times to resolve references.
215runTeXProgram :: Bool -> String -> [String] -> Int -> Int -> FilePath -> String
216 -> IO (ExitCode, ByteString, Maybe ByteString)
217runTeXProgram verbose program args runNumber numRuns tmpDir source = do
218 let file = tmpDir </> "input.tex"
219 exists <- doesFileExist file
220 unless exists $ UTF8.writeFile file source
222 -- note: we want / even on Windows, for TexLive
223 let tmpDir' = changePathSeparators tmpDir
224 let file' = changePathSeparators file
229 let programArgs = ["-halt-on-error", "-interaction", "nonstopmode",
230 "-output-directory", tmpDir'] ++ args ++ [file']
231 env' <- getEnvironment
232 let sep = [searchPathSeparator]
233 let texinputs = maybe (tmpDir' ++ sep) ((tmpDir' ++ sep) ++)
234 $ lookup "TEXINPUTS" env'
235 let env'' = ("TEXINPUTS", texinputs) :
236 [(k,v) | (k,v) <- env', k /= "TEXINPUTS"]
237 when (verbose && runNumber == 1) $ do
238 putStrLn "[makePDF] temp dir:"
240 putStrLn "[makePDF] Command line:"
241 putStrLn $ program ++ " " ++ unwords (map show programArgs)
243 putStrLn "[makePDF] Environment:"
246 putStrLn $ "[makePDF] Contents of " ++ file' ++ ":"
247 B.readFile file' >>= B.putStr
249 (exit, out, err) <- pipeProcess (Just env'') program programArgs BL.empty
251 putStrLn $ "[makePDF] Run #" ++ show runNumber
255 if runNumber <= numRuns
256 then runTeXProgram verbose program args (runNumber + 1) numRuns tmpDir source
258 let pdfFile = replaceDirectory (replaceExtension file ".pdf") tmpDir
259 pdfExists <- doesFileExist pdfFile
261 -- We read PDF as a strict bytestring to make sure that the
262 -- temp directory is removed on Windows.
263 -- See https://github.com/jgm/pandoc/issues/1192.
264 then (Just . B.fromChunks . (:[])) `fmap` BS.readFile pdfFile
266 return (exit, out <> err, pdf)
268html2pdf :: Bool -- ^ Verbose output
269 -> [String] -- ^ Args to wkhtmltopdf
270 -> String -- ^ HTML5 source
271 -> IO (Either ByteString ByteString)
272html2pdf verbose args source = do
273 file <- withTempFile "." "html2pdf.html" $ \fp _ -> return fp
274 pdfFile <- withTempFile "." "html2pdf.pdf" $ \fp _ -> return fp
275 UTF8.writeFile file source
276 let programArgs = args ++ [file, pdfFile]
277 env' <- getEnvironment
279 putStrLn "[makePDF] Command line:"
280 putStrLn $ "wkhtmltopdf" ++ " " ++ unwords (map show programArgs)
282 putStrLn "[makePDF] Environment:"
285 putStrLn $ "[makePDF] Contents of " ++ file ++ ":"
286 B.readFile file >>= B.putStr
288 (exit, out, err) <- pipeProcess (Just env') "wkhtmltopdf"
295 pdfExists <- doesFileExist pdfFile
296 mbPdf <- if pdfExists
297 -- We read PDF as a strict bytestring to make sure that the
298 -- temp directory is removed on Windows.
299 -- See https://github.com/jgm/pandoc/issues/1192.
301 res <- (Just . B.fromChunks . (:[])) `fmap` BS.readFile pdfFile
305 let log' = out <> err
306 return $ case (exit, mbPdf) of
307 (ExitFailure _, _) -> Left log'
308 (ExitSuccess, Nothing) -> Left ""
309 (ExitSuccess, Just pdf) -> Right pdf
311context2pdf :: Bool -- ^ Verbose output
312 -> FilePath -- ^ temp directory for output
313 -> String -- ^ ConTeXt source
314 -> IO (Either ByteString ByteString)
315context2pdf verbose tmpDir source = inDirectory tmpDir $ do
316 let file = "input.tex"
317 UTF8.writeFile file source
319 -- note: we want / even on Windows, for TexLive
320 let tmpDir' = changePathSeparators tmpDir
324 let programArgs = "--batchmode" : [file]
325 env' <- getEnvironment
326 let sep = [searchPathSeparator]
327 let texinputs = maybe (".." ++ sep) ((".." ++ sep) ++)
328 $ lookup "TEXINPUTS" env'
329 let env'' = ("TEXINPUTS", texinputs) :
330 [(k,v) | (k,v) <- env', k /= "TEXINPUTS"]
332 putStrLn "[makePDF] temp dir:"
334 putStrLn "[makePDF] Command line:"
335 putStrLn $ "context" ++ " " ++ unwords (map show programArgs)
337 putStrLn "[makePDF] Environment:"
340 putStrLn $ "[makePDF] Contents of " ++ file ++ ":"
341 B.readFile file >>= B.putStr
343 (exit, out, err) <- pipeProcess (Just env'') "context" programArgs BL.empty
348 let pdfFile = replaceExtension file ".pdf"
349 pdfExists <- doesFileExist pdfFile
350 mbPdf <- if pdfExists
351 -- We read PDF as a strict bytestring to make sure that the
352 -- temp directory is removed on Windows.
353 -- See https://github.com/jgm/pandoc/issues/1192.
354 then (Just . B.fromChunks . (:[])) `fmap` BS.readFile pdfFile
356 let log' = out <> err
357 case (exit, mbPdf) of
358 (ExitFailure _, _) -> do
359 let logmsg = extractConTeXtMsg log'
361 (ExitSuccess, Nothing) -> return $ Left ""
362 (ExitSuccess, Just pdf) -> return $ Right pdf
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list