git.iko.soy/typeable/pandoc
on master help sign in
Added preliminary support for PDF creation via wkhtmltopdf.

To use this: pandoc -t html5 -o result.pdf (and add `--mathjax` if you have math.)

commit0a768f1cc568722973302554265efa414d097da3 authorJohn MacFarlane <jgm@berkeley.edu> dateMon 21 Dec 2015, 17:22 (11 years ago) parent32d2789
3 files changed+79−21
README+6−5 pandoc.hs+15−13 src/Text/Pandoc/PDF.hs+58−3
README+6−5
@@ -24,7 +24,7 @@ markup], [Haddock markup], [OPML], [Emacs Org mode], [DocBook],
2424[FictionBook2], [Textile], [groff man] pages, [Emacs Org mode],
2525[AsciiDoc], [InDesign ICML], and [Slidy], [Slideous], [DZSlides],
2626[reveal.js] or [S5] HTML slide shows. It can also produce [PDF] output
27−on systems where LaTeX or ConTeXt is installed.
27+on systems where LaTeX, ConTeXt, or `wkhtmltopdf` is installed.
2828
2929Pandoc's enhanced version of Markdown includes syntax for [footnotes],
3030[tables], flexible [ordered lists], [definition lists], [fenced code blocks],
@@ -189,12 +189,13 @@ any header file. The [`natbib`], [`biblatex`], [`bibtex`], and [`biber`]
189189packages can optionally be used for [citation rendering]. These are
190190included with all recent versions of [TeX Live].
191191
192−Alternatively, pandoc can use ConTeXt to create a PDF.
192+Alternatively, pandoc can use ConTeXt or `wkhtmltopdf` to create a PDF.
193193To do this, specify an output file with a `.pdf` extension,
194−as before, but add `-t context` to the command line.
194+as before, but add `-t context` or `-t html5` to the command line.
195195
196−PDF output can be controlled using [variables for LaTeX]
197−or [variables for ConTeXt].
196+PDF output can be controlled using [variables for LaTeX] (if
197+LaTeX is used), [variables for ConTeXt] (if ConTeXt is used),
198+and `--css` (if `wkhtmltopdf` is used).
198199
199200[`amsfonts`]: https://ctan.org/pkg/amsfonts
200201[`amsmath`]: https://ctan.org/pkg/amsmath
pandoc.hs+15−13
@@ -1168,6 +1168,7 @@ convertWithOpts opts args = do
11681168 let laTeXOutput = "latex" `isPrefixOf` writerName' ||
11691169 "beamer" `isPrefixOf` writerName'
11701170 let conTeXtOutput = "context" `isPrefixOf` writerName'
1171+ let html5Output = "html5" `isPrefixOf` writerName'
11711172
11721173 let laTeXInput = "latex" `isPrefixOf` readerName' ||
11731174 "beamer" `isPrefixOf` readerName'
@@ -1367,27 +1368,28 @@ convertWithOpts opts args = do
13671368 IOByteStringWriter f -> f writerOptions doc' >>= writeBinary
13681369 PureStringWriter f
13691370 | pdfOutput -> do
1370− -- make sure writer is latex or beamer
1371− unless (laTeXOutput || conTeXtOutput) $
1371+ -- make sure writer is latex or beamer or context or html5
1372+ unless (laTeXOutput || conTeXtOutput || html5Output) $
13721373 err 47 $ "cannot produce pdf output with " ++ writerName' ++
13731374 " writer"
13741375
1375− let texprog = if conTeXtOutput
1376− then "context"
1377− else latexEngine
1378− -- check for latex program
1379− mbLatex <- findExecutable texprog
1380− when (isNothing mbLatex) $
1381− err 41 $ texprog ++ " not found. " ++
1382− texprog ++ " is needed for pdf output."
1383−
1384− res <- makePDF texprog f writerOptions doc'
1376+ let pdfprog = case () of
1377+ _ | conTeXtOutput -> "context"
1378+ _ | html5Output -> "wkhtmltopdf"
1379+ _ -> latexEngine
1380+ -- check for pdf creating program
1381+ mbPdfProg <- findExecutable pdfprog
1382+ when (isNothing mbPdfProg) $
1383+ err 41 $ pdfprog ++ " not found. " ++
1384+ pdfprog ++ " is needed for pdf output."
1385+
1386+ res <- makePDF pdfprog f writerOptions doc'
13851387 case res of
13861388 Right pdf -> writeBinary pdf
13871389 Left err' -> do
13881390 B.hPutStr stderr err'
13891391 B.hPut stderr $ B.pack [10]
1390− err 43 "Error producing PDF from TeX source"
1392+ err 43 "Error producing PDF"
13911393 | otherwise -> selfcontain (f writerOptions doc' ++
13921394 ['\n' | not standalone'])
13931395 >>= writerFn outputFile . handleEntities
src/Text/Pandoc/PDF.hs+58−3
@@ -38,6 +38,7 @@ import Text.Pandoc.Compat.Monoid ((<>))
3838import System.Exit (ExitCode (..))
3939import System.FilePath
4040import System.IO (stderr, stdout)
41+import System.IO.Temp (withTempFile)
4142import System.Directory
4243import Data.Digest.Pure.SHA (showDigest, sha1)
4344import System.Environment
@@ -49,7 +50,7 @@ import qualified Text.Pandoc.UTF8 as UTF8
4950import Text.Pandoc.Definition
5051import Text.Pandoc.Walk (walkM)
5152import Text.Pandoc.Shared (fetchItem', warn, withTempDir, inDirectory)
52−import Text.Pandoc.Options (WriterOptions(..))
53+import Text.Pandoc.Options (WriterOptions(..), HTMLMathMethod(..))
5354import Text.Pandoc.MIME (extensionFromMimeType, getMimeType)
5455import Text.Pandoc.Process (pipeProcess)
5556import qualified Data.ByteString.Lazy as BL
@@ -63,18 +64,29 @@ changePathSeparators :: FilePath -> FilePath
6364changePathSeparators = intercalate "/" . splitDirectories
6465#endif
6566
66−makePDF :: String -- ^ pdf creator (pdflatex, lualatex, xelatex)
67+makePDF :: String -- ^ pdf creator (pdflatex, lualatex,
68+ -- xelatex, context, wkhtmltopdf)
6769 -> (WriterOptions -> Pandoc -> String) -- ^ writer
6870 -> WriterOptions -- ^ options
6971 -> Pandoc -- ^ document
7072 -> IO (Either ByteString ByteString)
73+makePDF "wkhtmltopdf" writer opts doc =
74+ wkhtml2pdf (writerVerbose opts) args source
75+ where args = case writerHTMLMathMethod opts of
76+ -- with MathJax, wait til all math is rendered:
77+ MathJax _ -> ["--run-script", "MathJax.Hub.Register.StartupHook('End Typeset', function() { window.status = 'mathjax_loaded' });",
78+ "--window-status", "mathjax_loaded"]
79+ _ -> []
80+ source = writer opts doc
7181makePDF program writer opts doc = withTempDir "tex2pdf." $ \tmpdir -> do
7282 doc' <- handleImages opts tmpdir doc
7383 let source = writer opts doc'
7484 args = writerLaTeXArgs opts
7585 case program of
7686 "context" -> context2pdf (writerVerbose opts) tmpdir source
77− _ -> tex2pdf' (writerVerbose opts) args tmpdir program source
87+ _ | program `elem` ["pdflatex", "lualatex", "xelatex"]
88+ -> tex2pdf' (writerVerbose opts) args tmpdir program source
89+ _ -> return $ Left $ UTF8.fromStringLazy $ "Unknown program " ++ program
7890
7991handleImages :: WriterOptions
8092 -> FilePath -- ^ temp dir to store images
@@ -235,6 +247,49 @@ runTeXProgram verbose program args runNumber numRuns tmpDir source = do
235247 else return Nothing
236248 return (exit, out <> err, pdf)
237249
250+wkhtml2pdf :: Bool -- ^ Verbose output
251+ -> [String] -- ^ Args to wkhtmltopdf
252+ -> String -- ^ HTML5 source
253+ -> IO (Either ByteString ByteString)
254+wkhtml2pdf verbose args source = do
255+ file <- withTempFile "." "wkhtml2pdf.html" $ \fp _ -> return fp
256+ pdfFile <- withTempFile "." "wkhtml2pdf.pdf" $ \fp _ -> return fp
257+ UTF8.writeFile file source
258+ let programArgs = args ++ [file, pdfFile]
259+ env' <- getEnvironment
260+ when verbose $ do
261+ putStrLn "[makePDF] Command line:"
262+ putStrLn $ "wkhtmltopdf" ++ " " ++ unwords (map show programArgs)
263+ putStr "\n"
264+ putStrLn "[makePDF] Environment:"
265+ mapM_ print env'
266+ putStr "\n"
267+ putStrLn $ "[makePDF] Contents of " ++ file ++ ":"
268+ B.readFile file >>= B.putStr
269+ putStr "\n"
270+ (exit, out, err) <- pipeProcess (Just env') "wkhtmltopdf"
271+ programArgs BL.empty
272+ removeFile file
273+ when verbose $ do
274+ B.hPutStr stdout out
275+ B.hPutStr stderr err
276+ putStr "\n"
277+ pdfExists <- doesFileExist pdfFile
278+ mbPdf <- if pdfExists
279+ -- We read PDF as a strict bytestring to make sure that the
280+ -- temp directory is removed on Windows.
281+ -- See https://github.com/jgm/pandoc/issues/1192.
282+ then do
283+ res <- (Just . B.fromChunks . (:[])) `fmap` BS.readFile pdfFile
284+ removeFile pdfFile
285+ return res
286+ else return Nothing
287+ let log' = out <> err
288+ return $ case (exit, mbPdf) of
289+ (ExitFailure _, _) -> Left log'
290+ (ExitSuccess, Nothing) -> Left ""
291+ (ExitSuccess, Just pdf) -> Right pdf
292+
238293context2pdf :: Bool -- ^ Verbose output
239294 -> FilePath -- ^ temp directory for output
240295 -> String -- ^ ConTeXt source