{-# LANGUAGE CPP                 #-}
{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleContexts    #-}
{- |
   Module      : Text.Pandoc.PDF
   Copyright   : Copyright (C) 2012-2022 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 ( makePDF ) where

import qualified Codec.Picture as JP
import qualified Control.Exception as E
import Control.Monad (when)
import Control.Monad.Trans (MonadIO (..))
import qualified Data.ByteString as BS
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BC
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import Data.Text.Lazy.Encoding (decodeUtf8')
import Text.Printf (printf)
import Data.Char (ord, isAscii, isSpace)
import System.Directory
import System.Environment
import System.Exit (ExitCode (..))
import System.FilePath
import System.IO (stderr, hClose)
import System.IO.Temp (withSystemTempDirectory, withTempDirectory,
                       withTempFile)
import qualified System.IO.Error as IE
import Text.DocLayout (literal)
import Text.Pandoc.Definition
import Text.Pandoc.Error (PandocError (PandocPDFProgramNotFoundError))
import Text.Pandoc.MIME (getMimeType)
import Text.Pandoc.Options (HTMLMathMethod (..), WriterOptions (..))
import Text.Pandoc.Extensions (disableExtension, Extension(Ext_smart))
import Text.Pandoc.Process (pipeProcess)
import System.Process (readProcessWithExitCode)
import Text.Pandoc.Shared (inDirectory, stringify, tshow)
import qualified Text.Pandoc.UTF8 as UTF8
import Text.Pandoc.Walk (walkM)
import Text.Pandoc.Writers.Shared (getField, metaToContext)
import Control.Monad.Catch (MonadMask)
#ifdef _WINDOWS
import Data.List (intercalate)
#endif
import Data.List (isPrefixOf, find)
import Text.Pandoc.Class (fillMediaBag, getVerbosity,
                          report, extractMedia, PandocMonad)
import Text.Pandoc.Logging

#ifdef _WINDOWS
changePathSeparators :: FilePath -> FilePath
changePathSeparators =
  -- We filter out backslashes because an initial `C:\` gets
  -- retained by `splitDirectories`, see #6173:
  intercalate "/" . map (filter (/='\\')) . splitDirectories
#endif

makePDF :: (PandocMonad m, MonadIO m, MonadMask m)
        => String              -- ^ pdf creator (pdflatex, lualatex, xelatex,
                               -- wkhtmltopdf, weasyprint, prince, context,
                               -- pdfroff, pagedjs,
                               -- or path to executable)
        -> [String]            -- ^ arguments to pass to pdf creator
        -> (WriterOptions -> Pandoc -> m Text)  -- ^ writer
        -> WriterOptions       -- ^ options
        -> Pandoc              -- ^ document
        -> m (Either ByteString ByteString)
makePDF :: String
-> [String]
-> (WriterOptions -> Pandoc -> m Text)
-> WriterOptions
-> Pandoc
-> m (Either ByteString ByteString)
makePDF program :: String
program pdfargs :: [String]
pdfargs writer :: WriterOptions -> Pandoc -> m Text
writer opts :: WriterOptions
opts doc :: Pandoc
doc =
  case String -> String
takeBaseName String
program of
    "wkhtmltopdf" -> String
-> [String]
-> (WriterOptions -> Pandoc -> m Text)
-> WriterOptions
-> Pandoc
-> m (Either ByteString ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
String
-> [String]
-> (WriterOptions -> Pandoc -> m Text)
-> WriterOptions
-> Pandoc
-> m (Either ByteString ByteString)
makeWithWkhtmltopdf String
program [String]
pdfargs WriterOptions -> Pandoc -> m Text
writer WriterOptions
opts Pandoc
doc
    prog :: String
prog | String
prog String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["pagedjs-cli" ,"weasyprint", "prince"] -> do
      Text
source <- WriterOptions -> Pandoc -> m Text
writer WriterOptions
opts Pandoc
doc
      Verbosity
verbosity <- m Verbosity
forall (m :: * -> *). PandocMonad m => m Verbosity
getVerbosity
      IO (Either ByteString ByteString)
-> m (Either ByteString ByteString)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Either ByteString ByteString)
 -> m (Either ByteString ByteString))
-> IO (Either ByteString ByteString)
-> m (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ Verbosity
-> String -> [String] -> Text -> IO (Either ByteString ByteString)
html2pdf Verbosity
verbosity String
program [String]
pdfargs Text
source
    "pdfroff" -> do
      Text
source <- WriterOptions -> Pandoc -> m Text
writer WriterOptions
opts Pandoc
doc
      let args :: [String]
args   = ["-ms", "-mpdfmark", "-mspdf",
                    "-e", "-t", "-k", "-KUTF-8", "-i"] [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [String]
pdfargs
      String -> [String] -> Text -> m (Either ByteString ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
String -> [String] -> Text -> m (Either ByteString ByteString)
generic2pdf String
program [String]
args Text
source
    baseProg :: String
baseProg -> do
      String
-> (String -> m (Either ByteString ByteString))
-> m (Either ByteString ByteString)
forall (m :: * -> *) a.
(PandocMonad m, MonadMask m, MonadIO m) =>
String -> (String -> m a) -> m a
withTempDir "tex2pdf." ((String -> m (Either ByteString ByteString))
 -> m (Either ByteString ByteString))
-> (String -> m (Either ByteString ByteString))
-> m (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ \tmpdir' :: String
tmpdir' -> do
#ifdef _WINDOWS
        -- note:  we want / even on Windows, for TexLive
        let tmpdir = changePathSeparators tmpdir'
#else
        let tmpdir :: String
tmpdir = String
tmpdir'
#endif
        Pandoc
doc' <- WriterOptions -> String -> Pandoc -> m Pandoc
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
WriterOptions -> String -> Pandoc -> m Pandoc
handleImages WriterOptions
opts String
tmpdir Pandoc
doc
        Text
source <- WriterOptions -> Pandoc -> m Text
writer WriterOptions
opts{ writerExtensions :: Extensions
writerExtensions = -- disable use of quote
                                  -- ligatures to avoid bad ligatures like ?`
                                  Extension -> Extensions -> Extensions
disableExtension Extension
Ext_smart
                                   (WriterOptions -> Extensions
writerExtensions WriterOptions
opts) } Pandoc
doc'
        case String
baseProg of
          "context" -> String
-> [String] -> String -> Text -> m (Either ByteString ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
String
-> [String] -> String -> Text -> m (Either ByteString ByteString)
context2pdf String
program [String]
pdfargs String
tmpdir Text
source
          "tectonic" -> String
-> [String] -> String -> Text -> m (Either ByteString ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
String
-> [String] -> String -> Text -> m (Either ByteString ByteString)
tectonic2pdf String
program [String]
pdfargs String
tmpdir Text
source
          prog :: String
prog | String
prog String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["pdflatex", "lualatex", "xelatex", "latexmk"]
              -> String
-> [String] -> String -> Text -> m (Either ByteString ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
String
-> [String] -> String -> Text -> m (Either ByteString ByteString)
tex2pdf String
program [String]
pdfargs String
tmpdir Text
source
          _ -> Either ByteString ByteString -> m (Either ByteString ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either ByteString ByteString -> m (Either ByteString ByteString))
-> Either ByteString ByteString -> m (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ ByteString -> Either ByteString ByteString
forall a b. a -> Either a b
Left (ByteString -> Either ByteString ByteString)
-> ByteString -> Either ByteString ByteString
forall a b. (a -> b) -> a -> b
$ String -> ByteString
UTF8.fromStringLazy
                             (String -> ByteString) -> String -> ByteString
forall a b. (a -> b) -> a -> b
$ "Unknown program " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
program

-- latex has trouble with tildes in paths, which
-- you find in Windows temp dir paths with longer
-- user names (see #777)
withTempDir :: (PandocMonad m, MonadMask m, MonadIO m)
            => FilePath -> (FilePath -> m a) -> m a
withTempDir :: String -> (String -> m a) -> m a
withTempDir templ :: String
templ action :: String -> m a
action = do
  String
tmp <- IO String -> m String
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO String
getTemporaryDirectory
  Maybe String
uname <- IO (Maybe String) -> m (Maybe String)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Maybe String) -> m (Maybe String))
-> IO (Maybe String) -> m (Maybe String)
forall a b. (a -> b) -> a -> b
$ IO (Maybe String)
-> (SomeException -> IO (Maybe String)) -> IO (Maybe String)
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch
    (do (ec :: ExitCode
ec, sout :: String
sout, _) <- String -> [String] -> String -> IO (ExitCode, String, String)
readProcessWithExitCode "uname" ["-o"] ""
        if ExitCode
ec ExitCode -> ExitCode -> Bool
forall a. Eq a => a -> a -> Bool
== ExitCode
ExitSuccess
           then Maybe String -> IO (Maybe String)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe String -> IO (Maybe String))
-> Maybe String -> IO (Maybe String)
forall a b. (a -> b) -> a -> b
$ String -> Maybe String
forall a. a -> Maybe a
Just (String -> Maybe String) -> String -> Maybe String
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Bool
isSpace) String
sout
           else Maybe String -> IO (Maybe String)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe String
forall a. Maybe a
Nothing)
    (\(SomeException
_  :: E.SomeException) -> Maybe String -> IO (Maybe String)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe String
forall a. Maybe a
Nothing)
  if '~' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
tmp Bool -> Bool -> Bool
|| Maybe String
uname Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just "Cygwin" -- see #5451
         then String -> String -> (String -> m a) -> m a
forall (m :: * -> *) a.
(MonadMask m, MonadIO m) =>
String -> String -> (String -> m a) -> m a
withTempDirectory "." String
templ String -> m a
action
         else String -> (String -> m a) -> m a
forall (m :: * -> *) a.
(MonadIO m, MonadMask m) =>
String -> (String -> m a) -> m a
withSystemTempDirectory String
templ String -> m a
action

makeWithWkhtmltopdf :: (PandocMonad m, MonadIO m)
                    => String              -- ^ wkhtmltopdf or path
                    -> [String]            -- ^ arguments
                    -> (WriterOptions -> Pandoc -> m Text)  -- ^ writer
                    -> WriterOptions       -- ^ options
                    -> Pandoc              -- ^ document
                    -> m (Either ByteString ByteString)
makeWithWkhtmltopdf :: String
-> [String]
-> (WriterOptions -> Pandoc -> m Text)
-> WriterOptions
-> Pandoc
-> m (Either ByteString ByteString)
makeWithWkhtmltopdf program :: String
program pdfargs :: [String]
pdfargs writer :: WriterOptions -> Pandoc -> m Text
writer opts :: WriterOptions
opts doc :: Pandoc
doc@(Pandoc meta :: Meta
meta _) = do
  let mathArgs :: [String]
mathArgs = case WriterOptions -> HTMLMathMethod
writerHTMLMathMethod WriterOptions
opts of
                 -- with MathJax, wait til all math is rendered:
                      MathJax _ -> ["--run-script", "MathJax.Hub.Register.StartupHook('End Typeset', function() { window.status = 'mathjax_loaded' });",
                                    "--window-status", "mathjax_loaded"]
                      _ -> []
  Context Text
meta' <- WriterOptions
-> ([Block] -> m (Doc Text))
-> ([Inline] -> m (Doc Text))
-> Meta
-> m (Context Text)
forall (m :: * -> *) a.
(Monad m, TemplateTarget a) =>
WriterOptions
-> ([Block] -> m (Doc a))
-> ([Inline] -> m (Doc a))
-> Meta
-> m (Context a)
metaToContext WriterOptions
opts
             (Doc Text -> m (Doc Text)
forall (m :: * -> *) a. Monad m => a -> m a
return (Doc Text -> m (Doc Text))
-> ([Block] -> Doc Text) -> [Block] -> m (Doc Text)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Doc Text
forall a. HasChars a => a -> Doc a
literal (Text -> Doc Text) -> ([Block] -> Text) -> [Block] -> Doc Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Block] -> Text
forall a. Walkable Inline a => a -> Text
stringify)
             (Doc Text -> m (Doc Text)
forall (m :: * -> *) a. Monad m => a -> m a
return (Doc Text -> m (Doc Text))
-> ([Inline] -> Doc Text) -> [Inline] -> m (Doc Text)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Doc Text
forall a. HasChars a => a -> Doc a
literal (Text -> Doc Text) -> ([Inline] -> Text) -> [Inline] -> Doc Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Inline] -> Text
forall a. Walkable Inline a => a -> Text
stringify)
             Meta
meta
  let toArgs :: (String, Maybe Text) -> [String]
toArgs (f :: String
f, mbd :: Maybe Text
mbd) = [String] -> (Text -> [String]) -> Maybe Text -> [String]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] (\d :: Text
d -> ["--" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
f, Text -> String
T.unpack Text
d]) Maybe Text
mbd
  let args :: [String]
args   = [String]
mathArgs [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ ((String, Maybe Text) -> [String])
-> [(String, Maybe Text)] -> [String]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (String, Maybe Text) -> [String]
toArgs
                 [("page-size", Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
getField "papersize" Context Text
meta')
                 ,("title", Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
getField "title" Context Text
meta')
                 ,("margin-bottom", Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Maybe Text) -> Text -> Maybe Text
forall a b. (a -> b) -> a -> b
$ Text -> Maybe Text -> Text
forall a. a -> Maybe a -> a
fromMaybe "1.2in"
                            (Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
getField "margin-bottom" Context Text
meta'))
                 ,("margin-top", Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Maybe Text) -> Text -> Maybe Text
forall a b. (a -> b) -> a -> b
$ Text -> Maybe Text -> Text
forall a. a -> Maybe a -> a
fromMaybe "1.25in"
                            (Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
getField "margin-top" Context Text
meta'))
                 ,("margin-right", Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Maybe Text) -> Text -> Maybe Text
forall a b. (a -> b) -> a -> b
$ Text -> Maybe Text -> Text
forall a. a -> Maybe a -> a
fromMaybe "1.25in"
                            (Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
getField "margin-right" Context Text
meta'))
                 ,("margin-left", Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Maybe Text) -> Text -> Maybe Text
forall a b. (a -> b) -> a -> b
$ Text -> Maybe Text -> Text
forall a. a -> Maybe a -> a
fromMaybe "1.25in"
                            (Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
getField "margin-left" Context Text
meta'))
                 ,("footer-html", Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
getField "footer-html" Context Text
meta')
                 ,("header-html", Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
getField "header-html" Context Text
meta')
                 ] [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ ("--enable-local-file-access" String -> [String] -> [String]
forall a. a -> [a] -> [a]
: [String]
pdfargs)
                 -- see #6474
  Text
source <- WriterOptions -> Pandoc -> m Text
writer WriterOptions
opts Pandoc
doc
  Verbosity
verbosity <- m Verbosity
forall (m :: * -> *). PandocMonad m => m Verbosity
getVerbosity
  IO (Either ByteString ByteString)
-> m (Either ByteString ByteString)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Either ByteString ByteString)
 -> m (Either ByteString ByteString))
-> IO (Either ByteString ByteString)
-> m (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ Verbosity
-> String -> [String] -> Text -> IO (Either ByteString ByteString)
html2pdf Verbosity
verbosity String
program [String]
args Text
source

handleImages :: (PandocMonad m, MonadIO m)
             => WriterOptions
             -> FilePath      -- ^ temp dir to store images
             -> Pandoc        -- ^ document
             -> m Pandoc
handleImages :: WriterOptions -> String -> Pandoc -> m Pandoc
handleImages opts :: WriterOptions
opts tmpdir :: String
tmpdir doc :: Pandoc
doc =
  Pandoc -> m Pandoc
forall (m :: * -> *). PandocMonad m => Pandoc -> m Pandoc
fillMediaBag Pandoc
doc m Pandoc -> (Pandoc -> m Pandoc) -> m Pandoc
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>=
    String -> Pandoc -> m Pandoc
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
String -> Pandoc -> m Pandoc
extractMedia String
tmpdir m Pandoc -> (Pandoc -> m Pandoc) -> m Pandoc
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>=
    (Inline -> m Inline) -> Pandoc -> m Pandoc
forall a b (m :: * -> *).
(Walkable a b, Monad m, Applicative m, Functor m) =>
(a -> m a) -> b -> m b
walkM (WriterOptions -> String -> Inline -> m Inline
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
WriterOptions -> String -> Inline -> m Inline
convertImages WriterOptions
opts String
tmpdir)

convertImages :: (PandocMonad m, MonadIO m)
              => WriterOptions -> FilePath -> Inline -> m Inline
convertImages :: WriterOptions -> String -> Inline -> m Inline
convertImages opts :: WriterOptions
opts tmpdir :: String
tmpdir (Image attr :: Attr
attr ils :: [Inline]
ils (src :: Text
src, tit :: Text
tit)) = do
  Either Text String
img <- IO (Either Text String) -> m (Either Text String)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Either Text String) -> m (Either Text String))
-> IO (Either Text String) -> m (Either Text String)
forall a b. (a -> b) -> a -> b
$ WriterOptions -> String -> String -> IO (Either Text String)
convertImage WriterOptions
opts String
tmpdir (String -> IO (Either Text String))
-> String -> IO (Either Text String)
forall a b. (a -> b) -> a -> b
$ Text -> String
T.unpack Text
src
  Text
newPath <-
    case Either Text String
img of
      Left e :: Text
e -> do
        LogMessage -> m ()
forall (m :: * -> *). PandocMonad m => LogMessage -> m ()
report (LogMessage -> m ()) -> LogMessage -> m ()
forall a b. (a -> b) -> a -> b
$ Text -> Text -> LogMessage
CouldNotConvertImage Text
src Text
e
        Text -> m Text
forall (m :: * -> *) a. Monad m => a -> m a
return Text
src
      Right fp :: String
fp -> Text -> m Text
forall (m :: * -> *) a. Monad m => a -> m a
return (Text -> m Text) -> Text -> m Text
forall a b. (a -> b) -> a -> b
$ String -> Text
T.pack String
fp
  Inline -> m Inline
forall (m :: * -> *) a. Monad m => a -> m a
return (Attr -> [Inline] -> Target -> Inline
Image Attr
attr [Inline]
ils (Text
newPath, Text
tit))
convertImages _ _ x :: Inline
x = Inline -> m Inline
forall (m :: * -> *) a. Monad m => a -> m a
return Inline
x

-- Convert formats which do not work well in pdf to png
convertImage :: WriterOptions -> FilePath -> FilePath
             -> IO (Either Text FilePath)
convertImage :: WriterOptions -> String -> String -> IO (Either Text String)
convertImage opts :: WriterOptions
opts tmpdir :: String
tmpdir fname :: String
fname = do
  let dpi :: String
dpi = Int -> String
forall a. Show a => a -> String
show (Int -> String) -> Int -> String
forall a b. (a -> b) -> a -> b
$ WriterOptions -> Int
writerDpi WriterOptions
opts
  case Maybe Text
mime of
    Just "image/png" -> IO (Either Text String)
forall a. IO (Either a String)
doNothing
    Just "image/jpeg" -> IO (Either Text String)
forall a. IO (Either a String)
doNothing
    Just "application/pdf" -> IO (Either Text String)
forall a. IO (Either a String)
doNothing
    -- Note: eps is converted by pdflatex using epstopdf.pl
    Just "application/eps" -> IO (Either Text String)
forall a. IO (Either a String)
doNothing
    Just "image/svg+xml" -> IO (Either Text String)
-> (SomeException -> IO (Either Text String))
-> IO (Either Text String)
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch (do
      (exit :: ExitCode
exit, _) <- Maybe [(String, String)]
-> String -> [String] -> ByteString -> IO (ExitCode, ByteString)
pipeProcess Maybe [(String, String)]
forall a. Maybe a
Nothing "rsvg-convert"
                     ["-f","pdf","-a","--dpi-x",String
dpi,"--dpi-y",String
dpi,
                      "-o",String
pdfOut,String
svgIn] ByteString
BL.empty
      if ExitCode
exit ExitCode -> ExitCode -> Bool
forall a. Eq a => a -> a -> Bool
== ExitCode
ExitSuccess
         then Either Text String -> IO (Either Text String)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either Text String -> IO (Either Text String))
-> Either Text String -> IO (Either Text String)
forall a b. (a -> b) -> a -> b
$ String -> Either Text String
forall a b. b -> Either a b
Right String
pdfOut
         else Either Text String -> IO (Either Text String)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either Text String -> IO (Either Text String))
-> Either Text String -> IO (Either Text String)
forall a b. (a -> b) -> a -> b
$ Text -> Either Text String
forall a b. a -> Either a b
Left "conversion from SVG failed")
      (\(SomeException
e :: E.SomeException) -> Either Text String -> IO (Either Text String)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either Text String -> IO (Either Text String))
-> Either Text String -> IO (Either Text String)
forall a b. (a -> b) -> a -> b
$ Text -> Either Text String
forall a b. a -> Either a b
Left (Text -> Either Text String) -> Text -> Either Text String
forall a b. (a -> b) -> a -> b
$
          "check that rsvg-convert is in path.\n" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
          SomeException -> Text
forall a. Show a => a -> Text
tshow SomeException
e)
    _ -> String -> IO (Either String DynamicImage)
JP.readImage String
fname IO (Either String DynamicImage)
-> (Either String DynamicImage -> IO (Either Text String))
-> IO (Either Text String)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
               Left e :: String
e    -> Either Text String -> IO (Either Text String)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either Text String -> IO (Either Text String))
-> Either Text String -> IO (Either Text String)
forall a b. (a -> b) -> a -> b
$ Text -> Either Text String
forall a b. a -> Either a b
Left (Text -> Either Text String) -> Text -> Either Text String
forall a b. (a -> b) -> a -> b
$ String -> Text
T.pack String
e
               Right img :: DynamicImage
img ->
                 IO (Either Text String)
-> (SomeException -> IO (Either Text String))
-> IO (Either Text String)
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch (String -> Either Text String
forall a b. b -> Either a b
Right String
pngOut Either Text String -> IO () -> IO (Either Text String)
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ String -> DynamicImage -> IO ()
JP.savePngImage String
pngOut DynamicImage
img) ((SomeException -> IO (Either Text String))
 -> IO (Either Text String))
-> (SomeException -> IO (Either Text String))
-> IO (Either Text String)
forall a b. (a -> b) -> a -> b
$
                     \(SomeException
e :: E.SomeException) -> Either Text String -> IO (Either Text String)
forall (m :: * -> *) a. Monad m => a -> m a
return (Text -> Either Text String
forall a b. a -> Either a b
Left (SomeException -> Text
forall a. Show a => a -> Text
tshow SomeException
e))
  where
    pngOut :: String
pngOut = String -> String
normalise (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ String -> String -> String
replaceDirectory (String -> String -> String
replaceExtension String
fname ".png") String
tmpdir
    pdfOut :: String
pdfOut = String -> String
normalise (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ String -> String -> String
replaceDirectory (String -> String -> String
replaceExtension String
fname ".pdf") String
tmpdir
    svgIn :: String
svgIn = String -> String
normalise String
fname
    mime :: Maybe Text
mime = String -> Maybe Text
getMimeType String
fname
    doNothing :: IO (Either a String)
doNothing = Either a String -> IO (Either a String)
forall (m :: * -> *) a. Monad m => a -> m a
return (String -> Either a String
forall a b. b -> Either a b
Right String
fname)

tectonic2pdf :: (PandocMonad m, MonadIO m)
             => String                          -- ^ tex program
             -> [String]                        -- ^ Arguments to the latex-engine
             -> FilePath                        -- ^ temp directory for output
             -> Text                            -- ^ tex source
             -> m (Either ByteString ByteString)
tectonic2pdf :: String
-> [String] -> String -> Text -> m (Either ByteString ByteString)
tectonic2pdf program :: String
program args :: [String]
args tmpDir :: String
tmpDir source :: Text
source = do
  (exit :: ExitCode
exit, log' :: ByteString
log', mbPdf :: Maybe ByteString
mbPdf) <- String
-> [String]
-> String
-> Text
-> m (ExitCode, ByteString, Maybe ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
String
-> [String]
-> String
-> Text
-> m (ExitCode, ByteString, Maybe ByteString)
runTectonic String
program [String]
args String
tmpDir Text
source
  case (ExitCode
exit, Maybe ByteString
mbPdf) of
       (ExitFailure _, _)      -> Either ByteString ByteString -> m (Either ByteString ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either ByteString ByteString -> m (Either ByteString ByteString))
-> Either ByteString ByteString -> m (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ ByteString -> Either ByteString ByteString
forall a b. a -> Either a b
Left (ByteString -> Either ByteString ByteString)
-> ByteString -> Either ByteString ByteString
forall a b. (a -> b) -> a -> b
$ ByteString -> ByteString
extractMsg ByteString
log'
       (ExitSuccess, Nothing)  -> Either ByteString ByteString -> m (Either ByteString ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either ByteString ByteString -> m (Either ByteString ByteString))
-> Either ByteString ByteString -> m (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ ByteString -> Either ByteString ByteString
forall a b. a -> Either a b
Left ""
       (ExitSuccess, Just pdf :: ByteString
pdf) -> do
          ByteString -> m ()
forall (m :: * -> *). PandocMonad m => ByteString -> m ()
missingCharacterWarnings ByteString
log'
          Either ByteString ByteString -> m (Either ByteString ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either ByteString ByteString -> m (Either ByteString ByteString))
-> Either ByteString ByteString -> m (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ ByteString -> Either ByteString ByteString
forall a b. b -> Either a b
Right ByteString
pdf

tex2pdf :: (PandocMonad m, MonadIO m)
        => String                          -- ^ tex program
        -> [String]                        -- ^ Arguments to the latex-engine
        -> FilePath                        -- ^ temp directory for output
        -> Text                            -- ^ tex source
        -> m (Either ByteString ByteString)
tex2pdf :: String
-> [String] -> String -> Text -> m (Either ByteString ByteString)
tex2pdf program :: String
program args :: [String]
args tmpDir :: String
tmpDir source :: Text
source = do
  let numruns :: Int
numruns | String -> String
takeBaseName String
program String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "latexmk"        = 1
              | "\\tableofcontents" Text -> Text -> Bool
`T.isInfixOf` Text
source = 3  -- to get page numbers
              | Bool
otherwise                                = 2  -- 1 run won't give you PDF bookmarks
  (exit :: ExitCode
exit, log' :: ByteString
log', mbPdf :: Maybe ByteString
mbPdf) <- String
-> [String]
-> Int
-> String
-> Text
-> m (ExitCode, ByteString, Maybe ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
String
-> [String]
-> Int
-> String
-> Text
-> m (ExitCode, ByteString, Maybe ByteString)
runTeXProgram String
program [String]
args Int
numruns String
tmpDir Text
source
  case (ExitCode
exit, Maybe ByteString
mbPdf) of
       (ExitFailure _, _)      -> do
          let logmsg :: ByteString
logmsg = ByteString -> ByteString
extractMsg ByteString
log'
          let extramsg :: ByteString
extramsg =
                case ByteString
logmsg of
                     x :: ByteString
x | "! Package inputenc Error" ByteString -> ByteString -> Bool
`BC.isPrefixOf` ByteString
x
                           Bool -> Bool -> Bool
&& String
program String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= "xelatex"
                       -> "\nTry running pandoc with --pdf-engine=xelatex."
                     _ -> ""
          Either ByteString ByteString -> m (Either ByteString ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either ByteString ByteString -> m (Either ByteString ByteString))
-> Either ByteString ByteString -> m (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ ByteString -> Either ByteString ByteString
forall a b. a -> Either a b
Left (ByteString -> Either ByteString ByteString)
-> ByteString -> Either ByteString ByteString
forall a b. (a -> b) -> a -> b
$ ByteString
logmsg ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
extramsg
       (ExitSuccess, Nothing)  -> Either ByteString ByteString -> m (Either ByteString ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either ByteString ByteString -> m (Either ByteString ByteString))
-> Either ByteString ByteString -> m (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ ByteString -> Either ByteString ByteString
forall a b. a -> Either a b
Left ""
       (ExitSuccess, Just pdf :: ByteString
pdf) -> do
          ByteString -> m ()
forall (m :: * -> *). PandocMonad m => ByteString -> m ()
missingCharacterWarnings ByteString
log'
          Either ByteString ByteString -> m (Either ByteString ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either ByteString ByteString -> m (Either ByteString ByteString))
-> Either ByteString ByteString -> m (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ ByteString -> Either ByteString ByteString
forall a b. b -> Either a b
Right ByteString
pdf

missingCharacterWarnings :: PandocMonad m => ByteString -> m ()
missingCharacterWarnings :: ByteString -> m ()
missingCharacterWarnings log' :: ByteString
log' = do
  let ls :: [ByteString]
ls = ByteString -> [ByteString]
BC.lines ByteString
log'
  let isMissingCharacterWarning :: ByteString -> Bool
isMissingCharacterWarning = ByteString -> ByteString -> Bool
BC.isPrefixOf "Missing character: "
  let toCodePoint :: Char -> Text
toCodePoint c :: Char
c
        | Char -> Bool
isAscii Char
c   = Char -> Text
T.singleton Char
c
        | Bool
otherwise   = String -> Text
T.pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ Char
c Char -> String -> String
forall a. a -> [a] -> [a]
: " (U+" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> Int -> String
forall r. PrintfType r => String -> r
printf "%04X" (Char -> Int
ord Char
c) String -> String -> String
forall a. [a] -> [a] -> [a]
++ ")"
  let addCodePoint :: Text -> Text
addCodePoint = (Char -> Text) -> Text -> Text
T.concatMap Char -> Text
toCodePoint
  let warnings :: [Text]
warnings = [ Text -> Text
addCodePoint (ByteString -> Text
utf8ToText (Int64 -> ByteString -> ByteString
BC.drop 19 ByteString
l))
                 | ByteString
l <- [ByteString]
ls
                 , ByteString -> Bool
isMissingCharacterWarning ByteString
l
                 ]
  (Text -> m ()) -> [Text] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (LogMessage -> m ()
forall (m :: * -> *). PandocMonad m => LogMessage -> m ()
report (LogMessage -> m ()) -> (Text -> LogMessage) -> Text -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> LogMessage
MissingCharacter) [Text]
warnings

-- parsing output

extractMsg :: ByteString -> ByteString
extractMsg :: ByteString -> ByteString
extractMsg log' :: ByteString
log' = do
  let msg' :: [ByteString]
msg'  = (ByteString -> Bool) -> [ByteString] -> [ByteString]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Bool -> Bool
not (Bool -> Bool) -> (ByteString -> Bool) -> ByteString -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ("!" ByteString -> ByteString -> Bool
`BC.isPrefixOf`)) ([ByteString] -> [ByteString]) -> [ByteString] -> [ByteString]
forall a b. (a -> b) -> a -> b
$ ByteString -> [ByteString]
BC.lines ByteString
log'
  let (msg'' :: [ByteString]
msg'',rest :: [ByteString]
rest) = (ByteString -> Bool)
-> [ByteString] -> ([ByteString], [ByteString])
forall a. (a -> Bool) -> [a] -> ([a], [a])
break ("l." ByteString -> ByteString -> Bool
`BC.isPrefixOf`) [ByteString]
msg'
  let lineno :: [ByteString]
lineno = Int -> [ByteString] -> [ByteString]
forall a. Int -> [a] -> [a]
take 1 [ByteString]
rest
  if [ByteString] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [ByteString]
msg'
     then ByteString
log'
     else [ByteString] -> ByteString
BC.unlines ([ByteString]
msg'' [ByteString] -> [ByteString] -> [ByteString]
forall a. [a] -> [a] -> [a]
++ [ByteString]
lineno)

extractConTeXtMsg :: ByteString -> ByteString
extractConTeXtMsg :: ByteString -> ByteString
extractConTeXtMsg log' :: ByteString
log' = do
  let msg' :: [ByteString]
msg'  = Int -> [ByteString] -> [ByteString]
forall a. Int -> [a] -> [a]
take 1 ([ByteString] -> [ByteString]) -> [ByteString] -> [ByteString]
forall a b. (a -> b) -> a -> b
$
              (ByteString -> Bool) -> [ByteString] -> [ByteString]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Bool -> Bool
not (Bool -> Bool) -> (ByteString -> Bool) -> ByteString -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ("tex error" ByteString -> ByteString -> Bool
`BC.isPrefixOf`)) ([ByteString] -> [ByteString]) -> [ByteString] -> [ByteString]
forall a b. (a -> b) -> a -> b
$ ByteString -> [ByteString]
BC.lines ByteString
log'
  if [ByteString] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [ByteString]
msg'
     then ByteString
log'
     else [ByteString] -> ByteString
BC.unlines [ByteString]
msg'

-- running tex programs

runTectonic :: (PandocMonad m, MonadIO m)
            => String -> [String] -> FilePath
              -> Text -> m (ExitCode, ByteString, Maybe ByteString)
runTectonic :: String
-> [String]
-> String
-> Text
-> m (ExitCode, ByteString, Maybe ByteString)
runTectonic program :: String
program args' :: [String]
args' tmpDir' :: String
tmpDir' source :: Text
source = do
    let getOutDir :: [a] -> [a] -> ([a], Maybe a)
getOutDir acc :: [a]
acc (a :: a
a:b :: a
b:xs :: [a]
xs) = if a
a a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["-o", "--outdir"]
                                    then ([a] -> [a]
forall a. [a] -> [a]
reverse [a]
acc [a] -> [a] -> [a]
forall a. [a] -> [a] -> [a]
++ [a]
xs, a -> Maybe a
forall a. a -> Maybe a
Just a
b)
                                    else [a] -> [a] -> ([a], Maybe a)
getOutDir (a
ba -> [a] -> [a]
forall a. a -> [a] -> [a]
:a
aa -> [a] -> [a]
forall a. a -> [a] -> [a]
:[a]
acc) [a]
xs
        getOutDir acc :: [a]
acc xs :: [a]
xs = ([a] -> [a]
forall a. [a] -> [a]
reverse [a]
acc [a] -> [a] -> [a]
forall a. [a] -> [a] -> [a]
++ [a]
xs, Maybe a
forall a. Maybe a
Nothing)
        (args :: [String]
args, outDir :: Maybe String
outDir) = [String] -> [String] -> ([String], Maybe String)
forall a. (Eq a, IsString a) => [a] -> [a] -> ([a], Maybe a)
getOutDir [] [String]
args'
        tmpDir :: String
tmpDir = String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
tmpDir' Maybe String
outDir
    IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ Bool -> String -> IO ()
createDirectoryIfMissing Bool
True String
tmpDir
    -- run tectonic on stdin so it reads \include commands from $PWD instead of a temp directory
    let sourceBL :: ByteString
sourceBL = ByteString -> ByteString
BL.fromStrict (ByteString -> ByteString) -> ByteString -> ByteString
forall a b. (a -> b) -> a -> b
$ Text -> ByteString
UTF8.fromText Text
source
    let programArgs :: [String]
programArgs = ["--outdir", String
tmpDir] [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [String]
args [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ ["-"]
    [(String, String)]
env <- IO [(String, String)] -> m [(String, String)]
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO [(String, String)]
getEnvironment
    Verbosity
verbosity <- m Verbosity
forall (m :: * -> *). PandocMonad m => m Verbosity
getVerbosity
    Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Verbosity
verbosity Verbosity -> Verbosity -> Bool
forall a. Ord a => a -> a -> Bool
>= Verbosity
INFO) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$
      Maybe String
-> String -> [String] -> [(String, String)] -> Text -> IO ()
showVerboseInfo (String -> Maybe String
forall a. a -> Maybe a
Just String
tmpDir) String
program [String]
programArgs [(String, String)]
env
         (ByteString -> Text
utf8ToText ByteString
sourceBL)
    (exit :: ExitCode
exit, out :: ByteString
out) <- IO (ExitCode, ByteString) -> m (ExitCode, ByteString)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (ExitCode, ByteString) -> m (ExitCode, ByteString))
-> IO (ExitCode, ByteString) -> m (ExitCode, ByteString)
forall a b. (a -> b) -> a -> b
$ IO (ExitCode, ByteString)
-> (IOError -> IO (ExitCode, ByteString))
-> IO (ExitCode, ByteString)
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch
      (Maybe [(String, String)]
-> String -> [String] -> ByteString -> IO (ExitCode, ByteString)
pipeProcess ([(String, String)] -> Maybe [(String, String)]
forall a. a -> Maybe a
Just [(String, String)]
env) String
program [String]
programArgs ByteString
sourceBL)
      (String -> IOError -> IO (ExitCode, ByteString)
forall a. String -> IOError -> IO a
handlePDFProgramNotFound String
program)
    Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Verbosity
verbosity Verbosity -> Verbosity -> Bool
forall a. Ord a => a -> a -> Bool
>= Verbosity
INFO) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ do
      Handle -> Text -> IO ()
UTF8.hPutStrLn Handle
stderr "[makePDF] Running"
      Handle -> ByteString -> IO ()
BL.hPutStr Handle
stderr ByteString
out
      Handle -> Text -> IO ()
UTF8.hPutStr Handle
stderr "\n"
    let pdfFile :: String
pdfFile = String
tmpDir String -> String -> String
forall a. [a] -> [a] -> [a]
++ "/texput.pdf"
    (_, pdf :: Maybe ByteString
pdf) <- Maybe String -> String -> m (Maybe ByteString, Maybe ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
Maybe String -> String -> m (Maybe ByteString, Maybe ByteString)
getResultingPDF Maybe String
forall a. Maybe a
Nothing String
pdfFile
    (ExitCode, ByteString, Maybe ByteString)
-> m (ExitCode, ByteString, Maybe ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return (ExitCode
exit, ByteString
out, Maybe ByteString
pdf)

-- read a pdf that has been written to a temporary directory, and optionally read
-- logs
getResultingPDF :: (PandocMonad m, MonadIO m)
                => Maybe String -> String
                -> m (Maybe ByteString, Maybe ByteString)
getResultingPDF :: Maybe String -> String -> m (Maybe ByteString, Maybe ByteString)
getResultingPDF logFile :: Maybe String
logFile pdfFile :: String
pdfFile = do
    Bool
pdfExists <- IO Bool -> m Bool
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> m Bool) -> IO Bool -> m Bool
forall a b. (a -> b) -> a -> b
$ String -> IO Bool
doesFileExist String
pdfFile
    Maybe ByteString
pdf <- if Bool
pdfExists
              -- We read PDF as a strict bytestring to make sure that the
              -- temp directory is removed on Windows.
              -- See https://github.com/jgm/pandoc/issues/1192.
              then (ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just (ByteString -> Maybe ByteString)
-> (ByteString -> ByteString) -> ByteString -> Maybe ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [ByteString] -> ByteString
BL.fromChunks ([ByteString] -> ByteString)
-> (ByteString -> [ByteString]) -> ByteString -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ByteString -> [ByteString] -> [ByteString]
forall a. a -> [a] -> [a]
:[])) (ByteString -> Maybe ByteString)
-> m ByteString -> m (Maybe ByteString)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
`fmap`
                   IO ByteString -> m ByteString
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (String -> IO ByteString
BS.readFile String
pdfFile)
              else Maybe ByteString -> m (Maybe ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe ByteString
forall a. Maybe a
Nothing
    -- Note that some things like Missing character warnings
    -- appear in the log but not on stderr, so we prefer the log:
    Maybe ByteString
log' <- case Maybe String
logFile of
              Just logFile' :: String
logFile' -> do
                Bool
logExists <- IO Bool -> m Bool
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> m Bool) -> IO Bool -> m Bool
forall a b. (a -> b) -> a -> b
$ String -> IO Bool
doesFileExist String
logFile'
                if Bool
logExists
                  then IO (Maybe ByteString) -> m (Maybe ByteString)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Maybe ByteString) -> m (Maybe ByteString))
-> IO (Maybe ByteString) -> m (Maybe ByteString)
forall a b. (a -> b) -> a -> b
$ ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just (ByteString -> Maybe ByteString)
-> IO ByteString -> IO (Maybe ByteString)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO ByteString
BL.readFile String
logFile'
                  else Maybe ByteString -> m (Maybe ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe ByteString
forall a. Maybe a
Nothing
              Nothing -> Maybe ByteString -> m (Maybe ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe ByteString
forall a. Maybe a
Nothing
    (Maybe ByteString, Maybe ByteString)
-> m (Maybe ByteString, Maybe ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe ByteString
log', Maybe ByteString
pdf)

-- 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 :: (PandocMonad m, MonadIO m)
              => String -> [String] -> Int -> FilePath
              -> Text -> m (ExitCode, ByteString, Maybe ByteString)
runTeXProgram :: String
-> [String]
-> Int
-> String
-> Text
-> m (ExitCode, ByteString, Maybe ByteString)
runTeXProgram program :: String
program args :: [String]
args numRuns :: Int
numRuns tmpDir' :: String
tmpDir' source :: Text
source = do
    let isOutdirArg :: String -> Bool
isOutdirArg x :: String
x = "-outdir=" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
x Bool -> Bool -> Bool
||
                        "-output-directory=" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
x
    let tmpDir :: String
tmpDir =
          case (String -> Bool) -> [String] -> Maybe String
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find String -> Bool
isOutdirArg [String]
args of
            Just x :: String
x  -> Int -> String -> String
forall a. Int -> [a] -> [a]
drop 1 (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/='=') String
x
            Nothing -> String
tmpDir'
    IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ Bool -> String -> IO ()
createDirectoryIfMissing Bool
True String
tmpDir
    let file :: String
file = String
tmpDir String -> String -> String
forall a. [a] -> [a] -> [a]
++ "/input.tex"  -- note: tmpDir has / path separators
    IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ String -> ByteString -> IO ()
BS.writeFile String
file (ByteString -> IO ()) -> ByteString -> IO ()
forall a b. (a -> b) -> a -> b
$ Text -> ByteString
UTF8.fromText Text
source
    let isLatexMk :: Bool
isLatexMk = String -> String
takeBaseName String
program String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "latexmk"
        programArgs :: [String]
programArgs | Bool
isLatexMk = ["-interaction=batchmode", "-halt-on-error", "-pdf",
                                   "-quiet", "-outdir=" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
tmpDir] [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [String]
args [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [String
file]
                    | Bool
otherwise = ["-halt-on-error", "-interaction", "nonstopmode",
                                   "-output-directory", String
tmpDir] [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [String]
args [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [String
file]
    [(String, String)]
env' <- IO [(String, String)] -> m [(String, String)]
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO [(String, String)]
getEnvironment
    let sep :: String
sep = [Char
searchPathSeparator]
    let texinputs :: String
texinputs = String -> (String -> String) -> Maybe String -> String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (String
tmpDir String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
sep) ((String
tmpDir String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
sep) String -> String -> String
forall a. [a] -> [a] -> [a]
++)
          (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$ String -> [(String, String)] -> Maybe String
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup "TEXINPUTS" [(String, String)]
env'
    let env'' :: [(String, String)]
env'' = ("TEXINPUTS", String
texinputs) (String, String) -> [(String, String)] -> [(String, String)]
forall a. a -> [a] -> [a]
:
                ("TEXMFOUTPUT", String
tmpDir) (String, String) -> [(String, String)] -> [(String, String)]
forall a. a -> [a] -> [a]
:
                  [(String
k,String
v) | (k :: String
k,v :: String
v) <- [(String, String)]
env'
                         , String
k String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= "TEXINPUTS" Bool -> Bool -> Bool
&& String
k String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= "TEXMFOUTPUT"]
    Verbosity
verbosity <- m Verbosity
forall (m :: * -> *). PandocMonad m => m Verbosity
getVerbosity
    Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Verbosity
verbosity Verbosity -> Verbosity -> Bool
forall a. Ord a => a -> a -> Bool
>= Verbosity
INFO) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$
        String -> IO Text
UTF8.readFile String
file IO Text -> (Text -> IO ()) -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>=
         Maybe String
-> String -> [String] -> [(String, String)] -> Text -> IO ()
showVerboseInfo (String -> Maybe String
forall a. a -> Maybe a
Just String
tmpDir) String
program [String]
programArgs [(String, String)]
env''
    let runTeX :: Int -> m (ExitCode, ByteString, Maybe ByteString)
runTeX runNumber :: Int
runNumber = do
          (exit :: ExitCode
exit, out :: ByteString
out) <- IO (ExitCode, ByteString) -> m (ExitCode, ByteString)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (ExitCode, ByteString) -> m (ExitCode, ByteString))
-> IO (ExitCode, ByteString) -> m (ExitCode, ByteString)
forall a b. (a -> b) -> a -> b
$ IO (ExitCode, ByteString)
-> (IOError -> IO (ExitCode, ByteString))
-> IO (ExitCode, ByteString)
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch
            (Maybe [(String, String)]
-> String -> [String] -> ByteString -> IO (ExitCode, ByteString)
pipeProcess ([(String, String)] -> Maybe [(String, String)]
forall a. a -> Maybe a
Just [(String, String)]
env'') String
program [String]
programArgs ByteString
BL.empty)
            (String -> IOError -> IO (ExitCode, ByteString)
forall a. String -> IOError -> IO a
handlePDFProgramNotFound String
program)
          Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Verbosity
verbosity Verbosity -> Verbosity -> Bool
forall a. Ord a => a -> a -> Bool
>= Verbosity
INFO) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ do
            Handle -> Text -> IO ()
UTF8.hPutStrLn Handle
stderr (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$ "[makePDF] Run #" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall a. Show a => a -> Text
tshow Int
runNumber
            Handle -> ByteString -> IO ()
BL.hPutStr Handle
stderr ByteString
out
            Handle -> Text -> IO ()
UTF8.hPutStr Handle
stderr "\n"
          if Int
runNumber Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
numRuns
             then Int -> m (ExitCode, ByteString, Maybe ByteString)
runTeX (Int
runNumber Int -> Int -> Int
forall a. Num a => a -> a -> a
+ 1)
             else do
               let logFile :: String
logFile = String -> String -> String
replaceExtension String
file ".log"
               let pdfFile :: String
pdfFile = String -> String -> String
replaceExtension String
file ".pdf"
               (log' :: Maybe ByteString
log', pdf :: Maybe ByteString
pdf) <- Maybe String -> String -> m (Maybe ByteString, Maybe ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
Maybe String -> String -> m (Maybe ByteString, Maybe ByteString)
getResultingPDF (String -> Maybe String
forall a. a -> Maybe a
Just String
logFile) String
pdfFile
               (ExitCode, ByteString, Maybe ByteString)
-> m (ExitCode, ByteString, Maybe ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return (ExitCode
exit, ByteString -> Maybe ByteString -> ByteString
forall a. a -> Maybe a -> a
fromMaybe ByteString
out Maybe ByteString
log', Maybe ByteString
pdf)
    Int -> m (ExitCode, ByteString, Maybe ByteString)
forall (m :: * -> *).
(MonadIO m, PandocMonad m) =>
Int -> m (ExitCode, ByteString, Maybe ByteString)
runTeX 1

generic2pdf :: (PandocMonad m, MonadIO m)
            => String
            -> [String]
            -> Text
            -> m (Either ByteString ByteString)
generic2pdf :: String -> [String] -> Text -> m (Either ByteString ByteString)
generic2pdf program :: String
program args :: [String]
args source :: Text
source = do
  [(String, String)]
env' <- IO [(String, String)] -> m [(String, String)]
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO [(String, String)]
getEnvironment
  Verbosity
verbosity <- m Verbosity
forall (m :: * -> *). PandocMonad m => m Verbosity
getVerbosity
  Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Verbosity
verbosity Verbosity -> Verbosity -> Bool
forall a. Ord a => a -> a -> Bool
>= Verbosity
INFO) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
    IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ Maybe String
-> String -> [String] -> [(String, String)] -> Text -> IO ()
showVerboseInfo Maybe String
forall a. Maybe a
Nothing String
program [String]
args [(String, String)]
env' Text
source
  (exit :: ExitCode
exit, out :: ByteString
out) <- IO (ExitCode, ByteString) -> m (ExitCode, ByteString)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (ExitCode, ByteString) -> m (ExitCode, ByteString))
-> IO (ExitCode, ByteString) -> m (ExitCode, ByteString)
forall a b. (a -> b) -> a -> b
$ IO (ExitCode, ByteString)
-> (IOError -> IO (ExitCode, ByteString))
-> IO (ExitCode, ByteString)
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch
    (Maybe [(String, String)]
-> String -> [String] -> ByteString -> IO (ExitCode, ByteString)
pipeProcess ([(String, String)] -> Maybe [(String, String)]
forall a. a -> Maybe a
Just [(String, String)]
env') String
program [String]
args
                     (ByteString -> ByteString
BL.fromStrict (ByteString -> ByteString) -> ByteString -> ByteString
forall a b. (a -> b) -> a -> b
$ Text -> ByteString
UTF8.fromText Text
source))
    (String -> IOError -> IO (ExitCode, ByteString)
forall a. String -> IOError -> IO a
handlePDFProgramNotFound String
program)
  Either ByteString ByteString -> m (Either ByteString ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either ByteString ByteString -> m (Either ByteString ByteString))
-> Either ByteString ByteString -> m (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ case ExitCode
exit of
             ExitFailure _ -> ByteString -> Either ByteString ByteString
forall a b. a -> Either a b
Left ByteString
out
             ExitSuccess   -> ByteString -> Either ByteString ByteString
forall a b. b -> Either a b
Right ByteString
out


html2pdf  :: Verbosity    -- ^ Verbosity level
          -> String       -- ^ Program (wkhtmltopdf, weasyprint, prince, or path)
          -> [String]     -- ^ Args to program
          -> Text         -- ^ HTML5 source
          -> IO (Either ByteString ByteString)
html2pdf :: Verbosity
-> String -> [String] -> Text -> IO (Either ByteString ByteString)
html2pdf verbosity :: Verbosity
verbosity program :: String
program args :: [String]
args source :: Text
source =
  -- write HTML to temp file so we don't have to rewrite
  -- all links in `a`, `img`, `style`, `script`, etc. tags,
  -- and piping to weasyprint didn't work on Windows either.
  String
-> String
-> (String -> Handle -> IO (Either ByteString ByteString))
-> IO (Either ByteString ByteString)
forall (m :: * -> *) a.
(MonadIO m, MonadMask m) =>
String -> String -> (String -> Handle -> m a) -> m a
withTempFile "." "html2pdf.html" ((String -> Handle -> IO (Either ByteString ByteString))
 -> IO (Either ByteString ByteString))
-> (String -> Handle -> IO (Either ByteString ByteString))
-> IO (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ \file :: String
file h1 :: Handle
h1 ->
    String
-> String
-> (String -> Handle -> IO (Either ByteString ByteString))
-> IO (Either ByteString ByteString)
forall (m :: * -> *) a.
(MonadIO m, MonadMask m) =>
String -> String -> (String -> Handle -> m a) -> m a
withTempFile "." "html2pdf.pdf" ((String -> Handle -> IO (Either ByteString ByteString))
 -> IO (Either ByteString ByteString))
-> (String -> Handle -> IO (Either ByteString ByteString))
-> IO (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ \pdfFile :: String
pdfFile h2 :: Handle
h2 -> do
      Handle -> IO ()
hClose Handle
h1
      Handle -> IO ()
hClose Handle
h2
      String -> ByteString -> IO ()
BS.writeFile String
file (ByteString -> IO ()) -> ByteString -> IO ()
forall a b. (a -> b) -> a -> b
$ Text -> ByteString
UTF8.fromText Text
source
      let pdfFileArgName :: [String]
pdfFileArgName = ["-o" | String -> String
takeBaseName String
program String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem`
                                   ["pagedjs-cli", "prince"]]
      let programArgs :: [String]
programArgs = [String]
args [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [String
file] [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [String]
pdfFileArgName [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [String
pdfFile]
      [(String, String)]
env' <- IO [(String, String)]
getEnvironment
      Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Verbosity
verbosity Verbosity -> Verbosity -> Bool
forall a. Ord a => a -> a -> Bool
>= Verbosity
INFO) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
        String -> IO Text
UTF8.readFile String
file IO Text -> (Text -> IO ()) -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>=
          Maybe String
-> String -> [String] -> [(String, String)] -> Text -> IO ()
showVerboseInfo Maybe String
forall a. Maybe a
Nothing String
program [String]
programArgs [(String, String)]
env'
      (exit :: ExitCode
exit, out :: ByteString
out) <- IO (ExitCode, ByteString)
-> (IOError -> IO (ExitCode, ByteString))
-> IO (ExitCode, ByteString)
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch
        (Maybe [(String, String)]
-> String -> [String] -> ByteString -> IO (ExitCode, ByteString)
pipeProcess ([(String, String)] -> Maybe [(String, String)]
forall a. a -> Maybe a
Just [(String, String)]
env') String
program [String]
programArgs ByteString
BL.empty)
        (String -> IOError -> IO (ExitCode, ByteString)
forall a. String -> IOError -> IO a
handlePDFProgramNotFound String
program)
      Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Verbosity
verbosity Verbosity -> Verbosity -> Bool
forall a. Ord a => a -> a -> Bool
>= Verbosity
INFO) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
        Handle -> ByteString -> IO ()
BL.hPutStr Handle
stderr ByteString
out
        Handle -> Text -> IO ()
UTF8.hPutStr Handle
stderr "\n"
      Bool
pdfExists <- String -> IO Bool
doesFileExist String
pdfFile
      Maybe ByteString
mbPdf <- if Bool
pdfExists
                -- We read PDF as a strict bytestring to make sure that the
                -- temp directory is removed on Windows.
                -- See https://github.com/jgm/pandoc/issues/1192.
                then ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just (ByteString -> Maybe ByteString)
-> (ByteString -> ByteString) -> ByteString -> Maybe ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [ByteString] -> ByteString
BL.fromChunks ([ByteString] -> ByteString)
-> (ByteString -> [ByteString]) -> ByteString -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ByteString -> [ByteString] -> [ByteString]
forall a. a -> [a] -> [a]
:[]) (ByteString -> Maybe ByteString)
-> IO ByteString -> IO (Maybe ByteString)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO ByteString
BS.readFile String
pdfFile
                else Maybe ByteString -> IO (Maybe ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe ByteString
forall a. Maybe a
Nothing
      Either ByteString ByteString -> IO (Either ByteString ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either ByteString ByteString -> IO (Either ByteString ByteString))
-> Either ByteString ByteString
-> IO (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ case (ExitCode
exit, Maybe ByteString
mbPdf) of
                 (ExitFailure _, _)      -> ByteString -> Either ByteString ByteString
forall a b. a -> Either a b
Left ByteString
out
                 (ExitSuccess, Nothing)  -> ByteString -> Either ByteString ByteString
forall a b. a -> Either a b
Left ""
                 (ExitSuccess, Just pdf :: ByteString
pdf) -> ByteString -> Either ByteString ByteString
forall a b. b -> Either a b
Right ByteString
pdf

context2pdf :: (PandocMonad m, MonadIO m)
            => String       -- ^ "context" or path to it
            -> [String]     -- ^ extra arguments
            -> FilePath     -- ^ temp directory for output
            -> Text         -- ^ ConTeXt source
            -> m (Either ByteString ByteString)
context2pdf :: String
-> [String] -> String -> Text -> m (Either ByteString ByteString)
context2pdf program :: String
program pdfargs :: [String]
pdfargs tmpDir :: String
tmpDir source :: Text
source = do
  Verbosity
verbosity <- m Verbosity
forall (m :: * -> *). PandocMonad m => m Verbosity
getVerbosity
  IO (Either ByteString ByteString)
-> m (Either ByteString ByteString)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Either ByteString ByteString)
 -> m (Either ByteString ByteString))
-> IO (Either ByteString ByteString)
-> m (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ String
-> IO (Either ByteString ByteString)
-> IO (Either ByteString ByteString)
forall a. String -> IO a -> IO a
inDirectory String
tmpDir (IO (Either ByteString ByteString)
 -> IO (Either ByteString ByteString))
-> IO (Either ByteString ByteString)
-> IO (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ do
    let file :: String
file = "input.tex"
    String -> ByteString -> IO ()
BS.writeFile String
file (ByteString -> IO ()) -> ByteString -> IO ()
forall a b. (a -> b) -> a -> b
$ Text -> ByteString
UTF8.fromText Text
source
    let programArgs :: [String]
programArgs = "--batchmode" String -> [String] -> [String]
forall a. a -> [a] -> [a]
: [String]
pdfargs [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [String
file]
    [(String, String)]
env' <- IO [(String, String)]
getEnvironment
    Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Verbosity
verbosity Verbosity -> Verbosity -> Bool
forall a. Ord a => a -> a -> Bool
>= Verbosity
INFO) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ IO () -> IO ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
      String -> IO Text
UTF8.readFile String
file IO Text -> (Text -> IO ()) -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>=
        Maybe String
-> String -> [String] -> [(String, String)] -> Text -> IO ()
showVerboseInfo (String -> Maybe String
forall a. a -> Maybe a
Just String
tmpDir) String
program [String]
programArgs [(String, String)]
env'
    (exit :: ExitCode
exit, out :: ByteString
out) <- IO (ExitCode, ByteString)
-> (IOError -> IO (ExitCode, ByteString))
-> IO (ExitCode, ByteString)
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch
      (Maybe [(String, String)]
-> String -> [String] -> ByteString -> IO (ExitCode, ByteString)
pipeProcess ([(String, String)] -> Maybe [(String, String)]
forall a. a -> Maybe a
Just [(String, String)]
env') String
program [String]
programArgs ByteString
BL.empty)
      (String -> IOError -> IO (ExitCode, ByteString)
forall a. String -> IOError -> IO a
handlePDFProgramNotFound String
program)
    Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Verbosity
verbosity Verbosity -> Verbosity -> Bool
forall a. Ord a => a -> a -> Bool
>= Verbosity
INFO) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
      Handle -> ByteString -> IO ()
BL.hPutStr Handle
stderr ByteString
out
      Handle -> Text -> IO ()
UTF8.hPutStr Handle
stderr "\n"
    let pdfFile :: String
pdfFile = String -> String -> String
replaceExtension String
file ".pdf"
    Bool
pdfExists <- String -> IO Bool
doesFileExist String
pdfFile
    Maybe ByteString
mbPdf <- if Bool
pdfExists
              -- We read PDF as a strict bytestring to make sure that the
              -- temp directory is removed on Windows.
              -- See https://github.com/jgm/pandoc/issues/1192.
              then (ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just (ByteString -> Maybe ByteString)
-> (ByteString -> ByteString) -> ByteString -> Maybe ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [ByteString] -> ByteString
BL.fromChunks ([ByteString] -> ByteString)
-> (ByteString -> [ByteString]) -> ByteString -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ByteString -> [ByteString] -> [ByteString]
forall a. a -> [a] -> [a]
:[])) (ByteString -> Maybe ByteString)
-> IO ByteString -> IO (Maybe ByteString)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
`fmap` String -> IO ByteString
BS.readFile String
pdfFile
              else Maybe ByteString -> IO (Maybe ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe ByteString
forall a. Maybe a
Nothing
    case (ExitCode
exit, Maybe ByteString
mbPdf) of
         (ExitFailure _, _)      -> do
            let logmsg :: ByteString
logmsg = ByteString -> ByteString
extractConTeXtMsg ByteString
out
            Either ByteString ByteString -> IO (Either ByteString ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either ByteString ByteString -> IO (Either ByteString ByteString))
-> Either ByteString ByteString
-> IO (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ ByteString -> Either ByteString ByteString
forall a b. a -> Either a b
Left ByteString
logmsg
         (ExitSuccess, Nothing)  -> Either ByteString ByteString -> IO (Either ByteString ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either ByteString ByteString -> IO (Either ByteString ByteString))
-> Either ByteString ByteString
-> IO (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ ByteString -> Either ByteString ByteString
forall a b. a -> Either a b
Left ""
         (ExitSuccess, Just pdf :: ByteString
pdf) -> Either ByteString ByteString -> IO (Either ByteString ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either ByteString ByteString -> IO (Either ByteString ByteString))
-> Either ByteString ByteString
-> IO (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ ByteString -> Either ByteString ByteString
forall a b. b -> Either a b
Right ByteString
pdf


showVerboseInfo :: Maybe FilePath
                -> String
                -> [String]
                -> [(String, String)]
                -> Text
                -> IO ()
showVerboseInfo :: Maybe String
-> String -> [String] -> [(String, String)] -> Text -> IO ()
showVerboseInfo mbTmpDir :: Maybe String
mbTmpDir program :: String
program programArgs :: [String]
programArgs env :: [(String, String)]
env source :: Text
source = do
  case Maybe String
mbTmpDir of
    Just tmpDir :: String
tmpDir -> do
      Handle -> Text -> IO ()
UTF8.hPutStrLn Handle
stderr "[makePDF] temp dir:"
      Handle -> Text -> IO ()
UTF8.hPutStrLn Handle
stderr (String -> Text
T.pack String
tmpDir)
    Nothing -> () -> IO ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  Handle -> Text -> IO ()
UTF8.hPutStrLn Handle
stderr "[makePDF] Command line:"
  Handle -> Text -> IO ()
UTF8.hPutStrLn Handle
stderr (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$
       String -> Text
T.pack String
program Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> " " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack ([String] -> String
unwords ((String -> String) -> [String] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map String -> String
forall a. Show a => a -> String
show [String]
programArgs))
  Handle -> Text -> IO ()
UTF8.hPutStr Handle
stderr "\n"
  Handle -> Text -> IO ()
UTF8.hPutStrLn Handle
stderr "[makePDF] Relevant environment variables:"
  -- we filter out irrelevant stuff to avoid leaking passwords and keys!
  let isRelevant :: (a, b) -> Bool
isRelevant ("PATH",_) = Bool
True
      isRelevant ("TMPDIR",_) = Bool
True
      isRelevant ("PWD",_) = Bool
True
      isRelevant ("LANG",_) = Bool
True
      isRelevant ("HOME",_) = Bool
True
      isRelevant ("LUA_PATH",_) = Bool
True
      isRelevant ("LUA_CPATH",_) = Bool
True
      isRelevant ("SHELL",_) = Bool
True
      isRelevant ("TEXINPUTS",_) = Bool
True
      isRelevant ("TEXMFOUTPUT",_) = Bool
True
      isRelevant _ = Bool
False
  ((String, String) -> IO ()) -> [(String, String)] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Handle -> Text -> IO ()
UTF8.hPutStrLn Handle
stderr (Text -> IO ())
-> ((String, String) -> Text) -> (String, String) -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String, String) -> Text
forall a. Show a => a -> Text
tshow) (((String, String) -> Bool)
-> [(String, String)] -> [(String, String)]
forall a. (a -> Bool) -> [a] -> [a]
filter (String, String) -> Bool
forall a b. (Eq a, IsString a) => (a, b) -> Bool
isRelevant [(String, String)]
env)
  Handle -> Text -> IO ()
UTF8.hPutStr Handle
stderr "\n"
  Handle -> Text -> IO ()
UTF8.hPutStrLn Handle
stderr "[makePDF] Source:"
  Handle -> Text -> IO ()
UTF8.hPutStrLn Handle
stderr Text
source

handlePDFProgramNotFound :: String -> IE.IOError -> IO a
handlePDFProgramNotFound :: String -> IOError -> IO a
handlePDFProgramNotFound program :: String
program e :: IOError
e
  | IOError -> Bool
IE.isDoesNotExistError IOError
e =
      PandocError -> IO a
forall e a. Exception e => e -> IO a
E.throwIO (PandocError -> IO a) -> PandocError -> IO a
forall a b. (a -> b) -> a -> b
$ Text -> PandocError
PandocPDFProgramNotFoundError (Text -> PandocError) -> Text -> PandocError
forall a b. (a -> b) -> a -> b
$ String -> Text
T.pack String
program
  | Bool
otherwise = IOError -> IO a
forall e a. Exception e => e -> IO a
E.throwIO IOError
e

utf8ToText :: ByteString -> Text
utf8ToText :: ByteString -> Text
utf8ToText lbs :: ByteString
lbs =
  case ByteString -> Either UnicodeException Text
decodeUtf8' ByteString
lbs of
    Left _  -> String -> Text
T.pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ ByteString -> String
BC.unpack ByteString
lbs  -- if decoding fails, treat as latin1
    Right t :: Text
t -> Text -> Text
TL.toStrict Text
t