{-# LANGUAGE CPP                 #-}
{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleContexts    #-}
{- |
   Module      : Text.Pandoc.PDF
   Copyright   : Copyright (C) 2012-2024 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.Trans (MonadIO (..))
import Control.Monad (foldM_)
import Crypto.Hash (hashWith, SHA1(SHA1))
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 (hClose)
import System.IO.Temp (withSystemTempDirectory, withTempDirectory,
                       withTempFile)
import qualified System.IO.Error as IE
import Text.DocLayout (literal, render, hsep)
import Text.Pandoc.Definition
import Text.Pandoc.Error (PandocError (PandocPDFProgramNotFoundError))
import Text.Pandoc.SelfContained (makeSelfContained)
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.MediaBag (mediaItems)
import Text.Pandoc.Class (fillMediaBag, getMediaBag, getVerbosity, setVerbosity,
                          readFileStrict, fileExists,
                          report, extractMedia, PandocMonad, runIOorExplode)
import Text.Pandoc.Logging
import Text.DocTemplates ( FromContext(lookupContext) )

#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 :: forall (m :: * -> *).
(PandocMonad m, MonadIO m, MonadMask m) =>
[Char]
-> [[Char]]
-> (WriterOptions -> Pandoc -> m Text)
-> WriterOptions
-> Pandoc
-> m (Either ByteString ByteString)
makePDF [Char]
program [[Char]]
pdfargs WriterOptions -> Pandoc -> m Text
writer WriterOptions
opts Pandoc
doc = Bool
-> [Char]
-> ([Char] -> m (Either ByteString ByteString))
-> m (Either ByteString ByteString)
forall (m :: * -> *) a.
(PandocMonad m, MonadMask m, MonadIO m) =>
Bool -> [Char] -> ([Char] -> m a) -> m a
withTempDir ([Char]
program [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char]
"typst") [Char]
"media" (([Char] -> m (Either ByteString ByteString))
 -> m (Either ByteString ByteString))
-> ([Char] -> m (Either ByteString ByteString))
-> m (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ \[Char]
mediaDir -> do
#ifdef _WINDOWS
  -- note:  we want / even on Windows, for TexLive
  let tmpdir = changePathSeparators mediaDir
#else
  let tmpdir :: [Char]
tmpdir = [Char]
mediaDir
#endif
  let isTeXFormat :: a -> Bool
isTeXFormat a
"context" = Bool
True
      isTeXFormat a
"tectonic" = Bool
True
      isTeXFormat a
"latexmk" = Bool
True
      isTeXFormat a
"lualatex" = Bool
True
      isTeXFormat a
"lualatex-dev" = Bool
True
      isTeXFormat a
"pdflatex" = Bool
True
      isTeXFormat a
"pdflatex-dev" = Bool
True
      isTeXFormat a
"xelatex" = Bool
True
      isTeXFormat a
_ = Bool
False
  let opts' :: WriterOptions
opts' = if [Char] -> Bool
forall {a}. (Eq a, IsString a) => a -> Bool
isTeXFormat [Char]
program
                 then -- disable quote ligatures to avoid bad ligatures like ?`
                      WriterOptions
opts{ writerExtensions = disableExtension Ext_smart
                                (writerExtensions opts) }
                 else WriterOptions
opts
  source <- Pandoc -> m Pandoc
forall (m :: * -> *). PandocMonad m => Pandoc -> m Pandoc
fillMediaBag Pandoc
doc
              m Pandoc -> (Pandoc -> m Pandoc) -> m Pandoc
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= [Char] -> Pandoc -> m Pandoc
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char] -> Pandoc -> m Pandoc
extractMedia [Char]
tmpdir
              m Pandoc -> (Pandoc -> m Pandoc) -> m Pandoc
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (if [Char] -> Bool
forall {a}. (Eq a, IsString a) => a -> Bool
isTeXFormat [Char]
program
                      then (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
forall (m :: * -> *).
(Monad m, Applicative m, Functor m) =>
(Inline -> m Inline) -> Pandoc -> m Pandoc
walkM (WriterOptions -> [Char] -> Inline -> m Inline
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
WriterOptions -> [Char] -> Inline -> m Inline
convertImages WriterOptions
opts' [Char]
tmpdir)
                      else Pandoc -> m Pandoc
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return)
              m Pandoc -> (Pandoc -> m Text) -> m Text
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= WriterOptions -> Pandoc -> m Text
writer WriterOptions
opts'

  verbosity <- getVerbosity
  let compileHTML [Char] -> [[Char]]
mkOutArgs = do
        -- check to see if there is anything in mediabag, and if so,
        -- make the HTML self contained
        mediabag <- m MediaBag
forall (m :: * -> *). PandocMonad m => m MediaBag
getMediaBag
        source' <- case mediaItems mediabag of
                      [] -> Text -> m Text
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Text
source
                      [([Char], Text, ByteString)]
_ -> Text -> m Text
forall (m :: * -> *). PandocMonad m => Text -> m Text
makeSelfContained Text
source
        liftIO $
          toPdfViaTempFile verbosity program pdfargs mkOutArgs ".html" source'
  case takeBaseName program of
    [Char]
"wkhtmltopdf" -> [Char]
-> [[Char]]
-> (WriterOptions -> Pandoc -> m Text)
-> WriterOptions
-> Pandoc
-> m (Either ByteString ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]]
-> (WriterOptions -> Pandoc -> m Text)
-> WriterOptions
-> Pandoc
-> m (Either ByteString ByteString)
makeWithWkhtmltopdf [Char]
program [[Char]]
pdfargs WriterOptions -> Pandoc -> m Text
writer WriterOptions
opts' Pandoc
doc
    [Char]
"pagedjs-cli" -> ([Char] -> [[Char]]) -> m (Either ByteString ByteString)
forall {m :: * -> *}.
(PandocMonad m, MonadIO m) =>
([Char] -> [[Char]]) -> m (Either ByteString ByteString)
compileHTML (\[Char]
f -> [[Char]
"-o", [Char]
f])
    [Char]
"prince"      -> ([Char] -> [[Char]]) -> m (Either ByteString ByteString)
forall {m :: * -> *}.
(PandocMonad m, MonadIO m) =>
([Char] -> [[Char]]) -> m (Either ByteString ByteString)
compileHTML (\[Char]
f -> [[Char]
"-o", [Char]
f])
    [Char]
"weasyprint"  -> ([Char] -> [[Char]]) -> m (Either ByteString ByteString)
forall {m :: * -> *}.
(PandocMonad m, MonadIO m) =>
([Char] -> [[Char]]) -> m (Either ByteString ByteString)
compileHTML ([Char] -> [[Char]] -> [[Char]]
forall a. a -> [a] -> [a]
:[])
    [Char]
"typst" -> IO (Either ByteString ByteString)
-> m (Either ByteString ByteString)
forall a. IO a -> m a
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
-> [Char]
-> [[Char]]
-> ([Char] -> [[Char]])
-> [Char]
-> Text
-> IO (Either ByteString ByteString)
toPdfViaTempFile Verbosity
verbosity [Char]
program ([Char]
"compile"[Char] -> [[Char]] -> [[Char]]
forall a. a -> [a] -> [a]
:[[Char]]
pdfargs) ([Char] -> [[Char]] -> [[Char]]
forall a. a -> [a] -> [a]
:[]) [Char]
".typ" Text
source
    [Char]
"pdfroff" -> do
      let paperargs :: [[Char]]
paperargs =
            case Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
lookupContext Text
"papersize" (WriterOptions -> Context Text
writerVariables WriterOptions
opts') of
              Just Text
s
                | Int -> Text -> Text
T.takeEnd Int
1 Text
s Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"l" -> [[Char]
"-P-p" [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<>
                                           Text -> [Char]
T.unpack (Int -> Text -> Text
T.dropEnd Int
1 Text
s), [Char]
"-P-l"]
                | Bool
otherwise -> [[Char]
"-P-p" [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> Text -> [Char]
T.unpack Text
s]
              Maybe Text
Nothing -> []
      let args :: [[Char]]
args   = [[Char]
"-ms", [Char]
"-mpdfmark", [Char]
"-mspdf",
                    [Char]
"-e", [Char]
"-t", [Char]
"-k", [Char]
"-KUTF-8", [Char]
"-i"] [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++
                   [[Char]
"-U" | Text
".PDFPIC" Text -> Text -> Bool
`T.isInfixOf` Text
source] [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++
                    [[Char]]
paperargs [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [[Char]]
pdfargs
      [Char] -> [[Char]] -> Text -> m (Either ByteString ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char] -> [[Char]] -> Text -> m (Either ByteString ByteString)
generic2pdf [Char]
program [[Char]]
args Text
source
    [Char]
"groff" -> do
      let paperargs :: [[Char]]
paperargs =
            case Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
lookupContext Text
"papersize" (WriterOptions -> Context Text
writerVariables WriterOptions
opts') of
              Just Text
s
                | Int -> Text -> Text
T.takeEnd Int
1 Text
s Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"l" -> [[Char]
"-P-p" [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<>
                                           Text -> [Char]
T.unpack (Int -> Text -> Text
T.dropEnd Int
1 Text
s), [Char]
"-P-l"]
                | Bool
otherwise -> [[Char]
"-P-p" [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> Text -> [Char]
T.unpack Text
s]
              Maybe Text
Nothing -> []
      let args :: [[Char]]
args   = [[Char]
"-ms", [Char]
"-Tpdf",
                    [Char]
"-e", [Char]
"-t", [Char]
"-k", [Char]
"-KUTF-8"] [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++
                   [[Char]
"-U" | Text
".PDFPIC" Text -> Text -> Bool
`T.isInfixOf` Text
source] [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++
                    [[Char]]
paperargs [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [[Char]]
pdfargs
      [Char] -> [[Char]] -> Text -> m (Either ByteString ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char] -> [[Char]] -> Text -> m (Either ByteString ByteString)
generic2pdf [Char]
program [[Char]]
args Text
source
    [Char]
"context"      -> [Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
context2pdf [Char]
program [[Char]]
pdfargs [Char]
tmpdir Text
source
    [Char]
"tectonic"     -> [Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
tectonic2pdf [Char]
program [[Char]]
pdfargs [Char]
tmpdir Text
source
    [Char]
"latexmk"      -> [Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
tex2pdf [Char]
program [[Char]]
pdfargs [Char]
tmpdir Text
source
    [Char]
"lualatex"     -> [Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
tex2pdf [Char]
program [[Char]]
pdfargs [Char]
tmpdir Text
source
    [Char]
"lualatex-dev" -> [Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
tex2pdf [Char]
program [[Char]]
pdfargs [Char]
tmpdir Text
source
    [Char]
"pdflatex"     -> [Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
tex2pdf [Char]
program [[Char]]
pdfargs [Char]
tmpdir Text
source
    [Char]
"pdflatex-dev" -> [Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
tex2pdf [Char]
program [[Char]]
pdfargs [Char]
tmpdir Text
source
    [Char]
"xelatex"      -> [Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
tex2pdf [Char]
program [[Char]]
pdfargs [Char]
tmpdir Text
source
    [Char]
_ -> Either ByteString ByteString -> m (Either ByteString ByteString)
forall a. a -> m a
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
$ [Char] -> ByteString
UTF8.fromStringLazy ([Char] -> ByteString) -> [Char] -> ByteString
forall a b. (a -> b) -> a -> b
$ [Char]
"Unknown program " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
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)
            => Bool -> FilePath -> (FilePath -> m a) -> m a
withTempDir :: forall (m :: * -> *) a.
(PandocMonad m, MonadMask m, MonadIO m) =>
Bool -> [Char] -> ([Char] -> m a) -> m a
withTempDir Bool
useWorkingDirectory [Char]
templ [Char] -> m a
action = do
  tmp <- IO [Char] -> m [Char]
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO [Char]
getTemporaryDirectory
  uname <- liftIO $ E.catch
    (do (ec, sout, _) <- readProcessWithExitCode "uname" ["-o"] ""
        if ec == ExitSuccess
           then return $ Just $ filter (not . isSpace) sout
           else return Nothing)
    (\(SomeException
_  :: E.SomeException) -> Maybe [Char] -> IO (Maybe [Char])
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe [Char]
forall a. Maybe a
Nothing)
  if useWorkingDirectory || '~' `elem` tmp || uname == Just "Cygwin" -- see #5451
     then withTempDirectory "." templ action
     else withSystemTempDirectory templ 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 :: forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]]
-> (WriterOptions -> Pandoc -> m Text)
-> WriterOptions
-> Pandoc
-> m (Either ByteString ByteString)
makeWithWkhtmltopdf [Char]
program [[Char]]
pdfargs WriterOptions -> Pandoc -> m Text
writer WriterOptions
opts doc :: Pandoc
doc@(Pandoc Meta
meta [Block]
_) = do
  let mathArgs :: [[Char]]
mathArgs = case WriterOptions -> HTMLMathMethod
writerHTMLMathMethod WriterOptions
opts of
                 -- with MathJax, wait til all math is rendered:
                      MathJax Text
_ -> [[Char]
"--run-script", [Char]
"MathJax.Hub.Register.StartupHook('End Typeset', function() { window.status = 'mathjax_loaded' });",
                                    [Char]
"--window-status", [Char]
"mathjax_loaded"]
                      HTMLMathMethod
_ -> []
  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 a. a -> m a
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 a. a -> m a
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 ([Char]
f, Maybe Text
mbd) = [[Char]] -> (Text -> [[Char]]) -> Maybe Text -> [[Char]]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] (\Text
d -> [[Char]
"--" [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> [Char]
f, Text -> [Char]
T.unpack Text
d]) Maybe Text
mbd
  let args   = [[Char]]
mathArgs [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ (([Char], Maybe Text) -> [[Char]])
-> [([Char], Maybe Text)] -> [[Char]]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ([Char], Maybe Text) -> [[Char]]
toArgs
                 [([Char]
"page-size", Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
getField Text
"papersize" Context Text
meta')
                 ,([Char]
"title", Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
getField Text
"title" Context Text
meta')
                 ,([Char]
"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 Text
"1.2in"
                            (Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
getField Text
"margin-bottom" Context Text
meta'))
                 ,([Char]
"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 Text
"1.25in"
                            (Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
getField Text
"margin-top" Context Text
meta'))
                 ,([Char]
"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 Text
"1.25in"
                            (Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
getField Text
"margin-right" Context Text
meta'))
                 ,([Char]
"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 Text
"1.25in"
                            (Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
getField Text
"margin-left" Context Text
meta'))
                 ,([Char]
"footer-html", Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
getField Text
"footer-html" Context Text
meta')
                 ,([Char]
"header-html", Text -> Context Text -> Maybe Text
forall a b. FromContext a b => Text -> Context a -> Maybe b
getField Text
"header-html" Context Text
meta')
                 ] [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ ([Char]
"--enable-local-file-access" [Char] -> [[Char]] -> [[Char]]
forall a. a -> [a] -> [a]
: [[Char]]
pdfargs)
                 -- see #6474
  source <- writer opts doc
  verbosity <- getVerbosity
  liftIO $ toPdfViaTempFile verbosity program args (:[]) ".html" source

-- convert SVG to PDF, and pixel formats to PNG or JPEG, for TeX formats
convertImages :: (PandocMonad m, MonadIO m)
              => WriterOptions -> FilePath -> Inline -> m Inline
convertImages :: forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
WriterOptions -> [Char] -> Inline -> m Inline
convertImages WriterOptions
opts [Char]
tmpdir (Image Attr
attr [Inline]
ils (Text
src, Text
tit)) = do
  img <- IO (Either Text [Char]) -> m (Either Text [Char])
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Either Text [Char]) -> m (Either Text [Char]))
-> IO (Either Text [Char]) -> m (Either Text [Char])
forall a b. (a -> b) -> a -> b
$ WriterOptions -> [Char] -> [Char] -> IO (Either Text [Char])
convertImage WriterOptions
opts [Char]
tmpdir ([Char] -> IO (Either Text [Char]))
-> [Char] -> IO (Either Text [Char])
forall a b. (a -> b) -> a -> b
$ Text -> [Char]
T.unpack Text
src
  newPath <-
    case img of
      Left 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 a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Text
src
      Right [Char]
fp -> Text -> m Text
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Text -> m Text) -> Text -> m Text
forall a b. (a -> b) -> a -> b
$ [Char] -> Text
T.pack [Char]
fp
  return (Image attr ils (newPath, tit))
convertImages WriterOptions
_ [Char]
_ Inline
x = Inline -> m Inline
forall a. a -> m a
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 -> [Char] -> [Char] -> IO (Either Text [Char])
convertImage WriterOptions
opts [Char]
tmpdir [Char]
fname = do
  let dpi :: [Char]
dpi = Int -> [Char]
forall a. Show a => a -> [Char]
show (Int -> [Char]) -> Int -> [Char]
forall a b. (a -> b) -> a -> b
$ WriterOptions -> Int
writerDpi WriterOptions
opts
  case Maybe Text
mime of
    Just Text
"image/png" -> IO (Either Text [Char])
forall {a}. IO (Either a [Char])
doNothing
    Just Text
"image/jpeg" -> IO (Either Text [Char])
forall {a}. IO (Either a [Char])
doNothing
    Just Text
"application/pdf" -> IO (Either Text [Char])
forall {a}. IO (Either a [Char])
doNothing
    -- Note: eps is converted by pdflatex using epstopdf.pl
    Just Text
"application/eps" -> IO (Either Text [Char])
forall {a}. IO (Either a [Char])
doNothing
    Just Text
"image/svg+xml" -> IO (Either Text [Char])
-> (SomeException -> IO (Either Text [Char]))
-> IO (Either Text [Char])
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch (do
      (exit, _) <- Maybe [([Char], [Char])]
-> [Char] -> [[Char]] -> ByteString -> IO (ExitCode, ByteString)
pipeProcess Maybe [([Char], [Char])]
forall a. Maybe a
Nothing [Char]
"rsvg-convert"
                     [[Char]
"-f",[Char]
"pdf",[Char]
"-a",[Char]
"--dpi-x",[Char]
dpi,[Char]
"--dpi-y",[Char]
dpi,
                      [Char]
"-o",[Char]
pdfOut,[Char]
svgIn] ByteString
BL.empty
      if exit == ExitSuccess
         then return $ Right pdfOut
         else return $ Left "conversion from SVG failed")
      (\(SomeException
e :: E.SomeException) -> Either Text [Char] -> IO (Either Text [Char])
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either Text [Char] -> IO (Either Text [Char]))
-> Either Text [Char] -> IO (Either Text [Char])
forall a b. (a -> b) -> a -> b
$ Text -> Either Text [Char]
forall a b. a -> Either a b
Left (Text -> Either Text [Char]) -> Text -> Either Text [Char]
forall a b. (a -> b) -> a -> b
$
          Text
"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)
    Maybe Text
_ -> [Char] -> IO (Either [Char] DynamicImage)
JP.readImage [Char]
fname IO (Either [Char] DynamicImage)
-> (Either [Char] DynamicImage -> IO (Either Text [Char]))
-> IO (Either Text [Char])
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
               Left [Char]
e    -> Either Text [Char] -> IO (Either Text [Char])
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either Text [Char] -> IO (Either Text [Char]))
-> Either Text [Char] -> IO (Either Text [Char])
forall a b. (a -> b) -> a -> b
$ Text -> Either Text [Char]
forall a b. a -> Either a b
Left (Text -> Either Text [Char]) -> Text -> Either Text [Char]
forall a b. (a -> b) -> a -> b
$ [Char] -> Text
T.pack [Char]
e
               Right DynamicImage
img ->
                 IO (Either Text [Char])
-> (SomeException -> IO (Either Text [Char]))
-> IO (Either Text [Char])
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch ([Char] -> Either Text [Char]
forall a b. b -> Either a b
Right [Char]
pngOut Either Text [Char] -> IO () -> IO (Either Text [Char])
forall a b. a -> IO b -> IO a
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ [Char] -> DynamicImage -> IO ()
JP.savePngImage [Char]
pngOut DynamicImage
img) ((SomeException -> IO (Either Text [Char]))
 -> IO (Either Text [Char]))
-> (SomeException -> IO (Either Text [Char]))
-> IO (Either Text [Char])
forall a b. (a -> b) -> a -> b
$
                     \(SomeException
e :: E.SomeException) -> Either Text [Char] -> IO (Either Text [Char])
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Text -> Either Text [Char]
forall a b. a -> Either a b
Left (SomeException -> Text
forall a. Show a => a -> Text
tshow SomeException
e))
  where
    sha :: [Char]
sha = Digest SHA1 -> [Char]
forall a. Show a => a -> [Char]
show (SHA1 -> ByteString -> Digest SHA1
forall ba alg.
(ByteArrayAccess ba, HashAlgorithm alg) =>
alg -> ba -> Digest alg
hashWith SHA1
SHA1 ([Char] -> ByteString
UTF8.fromString [Char]
fname))
    pngOut :: [Char]
pngOut = [Char] -> [Char]
normalise ([Char] -> [Char]) -> [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ [Char]
tmpdir [Char] -> [Char] -> [Char]
</> [Char]
sha [Char] -> [Char] -> [Char]
<.> [Char]
"png"
    pdfOut :: [Char]
pdfOut = [Char] -> [Char]
normalise ([Char] -> [Char]) -> [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ [Char]
tmpdir [Char] -> [Char] -> [Char]
</> [Char]
sha [Char] -> [Char] -> [Char]
<.> [Char]
"pdf"
    svgIn :: [Char]
svgIn = [Char] -> [Char]
normalise [Char]
fname
    mime :: Maybe Text
mime = [Char] -> Maybe Text
getMimeType [Char]
fname
    doNothing :: IO (Either a [Char])
doNothing = Either a [Char] -> IO (Either a [Char])
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ([Char] -> Either a [Char]
forall a b. b -> Either a b
Right [Char]
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 :: forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
tectonic2pdf [Char]
program [[Char]]
args [Char]
tmpDir Text
source = do
  (exit, log', mbPdf) <- [Char]
-> [[Char]]
-> [Char]
-> Text
-> m (ExitCode, ByteString, Maybe ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]]
-> [Char]
-> Text
-> m (ExitCode, ByteString, Maybe ByteString)
runTectonic [Char]
program [[Char]]
args [Char]
tmpDir Text
source
  case (exit, mbPdf) of
       (ExitFailure Int
_, Maybe ByteString
_)      -> Either ByteString ByteString -> m (Either ByteString ByteString)
forall a. a -> m a
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'
       (ExitCode
ExitSuccess, Maybe ByteString
Nothing)  -> Either ByteString ByteString -> m (Either ByteString ByteString)
forall a. a -> m a
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
""
       (ExitCode
ExitSuccess, Just ByteString
pdf) -> do
          ByteString -> m ()
forall (m :: * -> *). PandocMonad m => ByteString -> m ()
missingCharacterWarnings ByteString
log'
          Either ByteString ByteString -> m (Either ByteString ByteString)
forall a. a -> m a
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 :: forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
tex2pdf [Char]
program [[Char]]
args [Char]
tmpDir Text
source = do
  let isOutdirArg :: [Char] -> Bool
isOutdirArg [Char]
x = [Char]
"-outdir=" [Char] -> [Char] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [Char]
x Bool -> Bool -> Bool
||
                      [Char]
"-output-directory=" [Char] -> [Char] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [Char]
x
  let outDir :: [Char]
outDir =
        case ([Char] -> Bool) -> [[Char]] -> Maybe [Char]
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find [Char] -> Bool
isOutdirArg [[Char]]
args of
          Just [Char]
x  -> Int -> [Char] -> [Char]
forall a. Int -> [a] -> [a]
drop Int
1 ([Char] -> [Char]) -> [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> [Char] -> [Char]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/=Char
'=') [Char]
x
          Maybe [Char]
Nothing -> [Char]
tmpDir
  let file :: [Char]
file = [Char]
tmpDir [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"/input.tex"  -- note: tmpDir has / path separators
  IO () -> m ()
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ [Char] -> ByteString -> IO ()
BS.writeFile [Char]
file (ByteString -> IO ()) -> ByteString -> IO ()
forall a b. (a -> b) -> a -> b
$ Text -> ByteString
UTF8.fromText Text
source
  (exit, log', mbPdf) <- [Char]
-> [[Char]]
-> [Char]
-> [Char]
-> m (ExitCode, ByteString, Maybe ByteString)
forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]]
-> [Char]
-> [Char]
-> m (ExitCode, ByteString, Maybe ByteString)
runTeXProgram [Char]
program [[Char]]
args [Char]
tmpDir [Char]
outDir
  case (exit, mbPdf) of
       (ExitFailure Int
_, Maybe ByteString
_)      -> do
          let logmsg :: ByteString
logmsg = ByteString -> ByteString
extractMsg ByteString
log'
          let extramsg :: ByteString
extramsg =
                case ByteString
logmsg of
                     ByteString
x | ByteString
"! Package inputenc Error" ByteString -> ByteString -> Bool
`BC.isPrefixOf` ByteString
x
                           Bool -> Bool -> Bool
&& [Char]
program [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
/= [Char]
"xelatex"
                       -> ByteString
"\nTry running pandoc with --pdf-engine=xelatex."
                     ByteString
_ -> ByteString
""
          Either ByteString ByteString -> m (Either ByteString ByteString)
forall a. a -> m a
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
       (ExitCode
ExitSuccess, Maybe ByteString
Nothing)  -> Either ByteString ByteString -> m (Either ByteString ByteString)
forall a. a -> m a
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
""
       (ExitCode
ExitSuccess, Just ByteString
pdf) -> do
          ByteString -> m ()
forall (m :: * -> *). PandocMonad m => ByteString -> m ()
latexWarnings ByteString
log'
          ByteString -> m ()
forall (m :: * -> *). PandocMonad m => ByteString -> m ()
missingCharacterWarnings ByteString
log'
          Either ByteString ByteString -> m (Either ByteString ByteString)
forall a. a -> m a
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 :: forall (m :: * -> *). PandocMonad m => ByteString -> m ()
missingCharacterWarnings ByteString
log' = do
  let ls :: [ByteString]
ls = ByteString -> [ByteString]
BC.lines ByteString
log'
  let isMissingCharacterWarning :: ByteString -> Bool
isMissingCharacterWarning = ByteString -> ByteString -> Bool
BC.isPrefixOf ByteString
"Missing character:"
  let toCodePoint :: Char -> Text
toCodePoint Char
c
        | Char -> Bool
isAscii Char
c   = Char -> Text
T.singleton Char
c
        | Bool
otherwise   = [Char] -> Text
T.pack ([Char] -> Text) -> [Char] -> Text
forall a b. (a -> b) -> a -> b
$ Char
c Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
: [Char]
" (U+" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char] -> Int -> [Char]
forall r. PrintfType r => [Char] -> r
printf [Char]
"%04X" (Char -> Int
ord Char
c) [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
")"
  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 Int64
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

latexWarnings :: PandocMonad m => ByteString -> m ()
latexWarnings :: forall (m :: * -> *). PandocMonad m => ByteString -> m ()
latexWarnings ByteString
log' = (Maybe ByteString -> ByteString -> m (Maybe ByteString))
-> Maybe ByteString -> [ByteString] -> m ()
forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m ()
foldM_ Maybe ByteString -> ByteString -> m (Maybe ByteString)
forall {f :: * -> *}.
PandocMonad f =>
Maybe ByteString -> ByteString -> f (Maybe ByteString)
go Maybe ByteString
forall a. Maybe a
Nothing (ByteString -> [ByteString]
BC.lines ByteString
log')
 where
   go :: Maybe ByteString -> ByteString -> f (Maybe ByteString)
go Maybe ByteString
Nothing ByteString
ln
     | ByteString -> ByteString -> Bool
BC.isPrefixOf ByteString
"LaTeX Warning:" ByteString
ln =
       Maybe ByteString -> f (Maybe ByteString)
forall a. a -> f a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe ByteString -> f (Maybe ByteString))
-> Maybe ByteString -> f (Maybe ByteString)
forall a b. (a -> b) -> a -> b
$ ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just ByteString
ln
     | Bool
otherwise = Maybe ByteString -> f (Maybe ByteString)
forall a. a -> f a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe ByteString
forall a. Maybe a
Nothing
   go (Just ByteString
msg) ByteString
ln
     | ByteString
ln ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ByteString
"" = do -- emit report and reset accumulator
         LogMessage -> f ()
forall (m :: * -> *). PandocMonad m => LogMessage -> m ()
report (LogMessage -> f ()) -> LogMessage -> f ()
forall a b. (a -> b) -> a -> b
$ Text -> LogMessage
MakePDFWarning (Text -> LogMessage) -> Text -> LogMessage
forall a b. (a -> b) -> a -> b
$ Maybe Int -> Doc Text -> Text
forall a. HasChars a => Maybe Int -> Doc a -> a
render (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
60) (Doc Text -> Text) -> Doc Text -> Text
forall a b. (a -> b) -> a -> b
$
            [Doc Text] -> Doc Text
forall a. [Doc a] -> Doc a
hsep ([Doc Text] -> Doc Text) -> [Doc Text] -> Doc Text
forall a b. (a -> b) -> a -> b
$ (Text -> Doc Text) -> [Text] -> [Doc Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Doc Text
forall a. HasChars a => a -> Doc a
literal ([Text] -> [Doc Text]) -> [Text] -> [Doc Text]
forall a b. (a -> b) -> a -> b
$ Text -> [Text]
T.words (Text -> [Text]) -> Text -> [Text]
forall a b. (a -> b) -> a -> b
$ ByteString -> Text
utf8ToText ByteString
msg
         Maybe ByteString -> f (Maybe ByteString)
forall a. a -> f a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe ByteString
forall a. Maybe a
Nothing
     | Bool
otherwise = Maybe ByteString -> f (Maybe ByteString)
forall a. a -> f a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe ByteString -> f (Maybe ByteString))
-> Maybe ByteString -> f (Maybe ByteString)
forall a b. (a -> b) -> a -> b
$ ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just (ByteString
msg ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
ln)

-- parsing output

extractMsg :: ByteString -> ByteString
extractMsg :: ByteString -> ByteString
extractMsg 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 -> ByteString -> Bool
`BC.isPrefixOf`)) ([ByteString] -> [ByteString]) -> [ByteString] -> [ByteString]
forall a b. (a -> b) -> a -> b
$ ByteString -> [ByteString]
BC.lines ByteString
log'
  let ([ByteString]
msg'',[ByteString]
rest) = (ByteString -> Bool)
-> [ByteString] -> ([ByteString], [ByteString])
forall a. (a -> Bool) -> [a] -> ([a], [a])
break (ByteString
"l." ByteString -> ByteString -> Bool
`BC.isPrefixOf`) [ByteString]
msg'
  let lineno :: [ByteString]
lineno = Int -> [ByteString] -> [ByteString]
forall a. Int -> [a] -> [a]
take Int
1 [ByteString]
rest
  if [ByteString] -> Bool
forall a. [a] -> 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 ByteString
log' = do
  let msg' :: [ByteString]
msg'  = Int -> [ByteString] -> [ByteString]
forall a. Int -> [a] -> [a]
take Int
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
. (ByteString
"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 a. [a] -> 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 :: forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]]
-> [Char]
-> Text
-> m (ExitCode, ByteString, Maybe ByteString)
runTectonic [Char]
program [[Char]]
args' [Char]
tmpDir' Text
source = do
    let getOutDir :: [a] -> [a] -> ([a], Maybe a)
getOutDir [a]
acc (a
a:a
b:[a]
xs) = if a
a a -> [a] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [a
"-o", a
"--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 [a]
acc [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)
        ([[Char]]
args, Maybe [Char]
outDir) = [[Char]] -> [[Char]] -> ([[Char]], Maybe [Char])
forall {a}. (Eq a, IsString a) => [a] -> [a] -> ([a], Maybe a)
getOutDir [] [[Char]]
args'
        tmpDir :: [Char]
tmpDir = [Char] -> Maybe [Char] -> [Char]
forall a. a -> Maybe a -> a
fromMaybe [Char]
tmpDir' Maybe [Char]
outDir
    IO () -> m ()
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ Bool -> [Char] -> IO ()
createDirectoryIfMissing Bool
True [Char]
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 :: [[Char]]
programArgs = [[Char]
"--outdir", [Char]
tmpDir] [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [[Char]]
args [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [[Char]
"-"]
    env <- IO [([Char], [Char])] -> m [([Char], [Char])]
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO [([Char], [Char])]
getEnvironment
    showVerboseInfo (Just tmpDir) program programArgs env (utf8ToText sourceBL)
    (exit, out) <- liftIO $ E.catch
      (pipeProcess (Just env) program programArgs sourceBL)
      (handlePDFProgramNotFound program)
    report $ MakePDFInfo "tectonic output" (utf8ToText out)
    let pdfFile = [Char]
tmpDir [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"/texput.pdf"
    (_, pdf) <- getResultingPDF Nothing pdfFile
    return (exit, out, 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 :: forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
Maybe [Char] -> [Char] -> m (Maybe ByteString, Maybe ByteString)
getResultingPDF Maybe [Char]
logFile [Char]
pdfFile = do
    pdfExists <- [Char] -> m Bool
forall (m :: * -> *). PandocMonad m => [Char] -> m Bool
fileExists [Char]
pdfFile
    pdf <- if 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 (Just . BL.fromChunks . (:[])) `fmap`
                   (readFileStrict pdfFile)
              else return Nothing
    -- Note that some things like Missing character warnings
    -- appear in the log but not on stderr, so we prefer the log:
    log' <- case logFile of
              Just [Char]
logFile' -> do
                logExists <- [Char] -> m Bool
forall (m :: * -> *). PandocMonad m => [Char] -> m Bool
fileExists [Char]
logFile'
                if logExists
                  then Just . BL.fromStrict <$> readFileStrict logFile'
                  else return Nothing
              Maybe [Char]
Nothing -> Maybe ByteString -> m (Maybe ByteString)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe ByteString
forall a. Maybe a
Nothing
    return (log', pdf)

-- Run a TeX program once in a temp directory (on input.tex) and return (exit code,
-- contents of stdout, contents of produced PDF if any).
runTeXProgram :: (PandocMonad m, MonadIO m)
              => String -> [String] -> FilePath -> FilePath
              -> m (ExitCode, ByteString, Maybe ByteString)
runTeXProgram :: forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]]
-> [Char]
-> [Char]
-> m (ExitCode, ByteString, Maybe ByteString)
runTeXProgram [Char]
program [[Char]]
args [Char]
tmpDir [Char]
outDir = do
    let isLatexMk :: Bool
isLatexMk = [Char] -> [Char]
takeBaseName [Char]
program [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char]
"latexmk"
        programArgs :: [[Char]]
programArgs | Bool
isLatexMk =
                      [[Char]
"-interaction=batchmode", [Char]
"-halt-on-error", [Char]
"-pdf",
                       [Char]
"-quiet", [Char]
"-outdir=" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
outDir] [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [[Char]]
args [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [[Char]
file]
                    | Bool
otherwise =
                      [[Char]
"-halt-on-error", [Char]
"-interaction", [Char]
"nonstopmode",
                       [Char]
"-output-directory", [Char]
outDir] [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [[Char]]
args [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [[Char]
file]
    env' <- IO [([Char], [Char])] -> m [([Char], [Char])]
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO [([Char], [Char])]
getEnvironment
    let sep = [Char
searchPathSeparator]
    let texinputs = [Char] -> ([Char] -> [Char]) -> Maybe [Char] -> [Char]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe ([Char]
tmpDir [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
sep) (([Char]
tmpDir [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
sep) [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++)
          (Maybe [Char] -> [Char]) -> Maybe [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ [Char] -> [([Char], [Char])] -> Maybe [Char]
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup [Char]
"TEXINPUTS" [([Char], [Char])]
env'
    let env'' = ([Char]
"TEXINPUTS", [Char]
texinputs) ([Char], [Char]) -> [([Char], [Char])] -> [([Char], [Char])]
forall a. a -> [a] -> [a]
:
                ([Char]
"TEXMFOUTPUT", [Char]
outDir) ([Char], [Char]) -> [([Char], [Char])] -> [([Char], [Char])]
forall a. a -> [a] -> [a]
:
                  [([Char]
k,[Char]
v) | ([Char]
k,[Char]
v) <- [([Char], [Char])]
env'
                         , [Char]
k [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
/= [Char]
"TEXINPUTS" Bool -> Bool -> Bool
&& [Char]
k [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
/= [Char]
"TEXMFOUTPUT"]
    liftIO (UTF8.readFile file) >>=
      showVerboseInfo (Just tmpDir) program programArgs env''
    go env'' programArgs (1 :: Int)
 where
   file :: [Char]
file = [Char]
tmpDir [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"/input.tex"
   outfile :: [Char]
outfile = [Char]
outDir [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"/input.pdf"
   go :: [([Char], [Char])]
-> [[Char]] -> t -> m (ExitCode, ByteString, Maybe ByteString)
go [([Char], [Char])]
env'' [[Char]]
programArgs t
runNumber = do
     let maxruns :: t
maxruns = t
4 -- stop if warnings present after 4 runs
     LogMessage -> m ()
forall (m :: * -> *). PandocMonad m => LogMessage -> m ()
report (LogMessage -> m ()) -> LogMessage -> m ()
forall a b. (a -> b) -> a -> b
$ Text -> Text -> LogMessage
MakePDFInfo (Text
"LaTeX run number " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> t -> Text
forall a. Show a => a -> Text
tshow t
runNumber) Text
forall a. Monoid a => a
mempty
     (exit, out) <- IO (ExitCode, ByteString) -> m (ExitCode, ByteString)
forall a. IO a -> m a
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)
-> (IOException -> IO (ExitCode, ByteString))
-> IO (ExitCode, ByteString)
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch
       (Maybe [([Char], [Char])]
-> [Char] -> [[Char]] -> ByteString -> IO (ExitCode, ByteString)
pipeProcess ([([Char], [Char])] -> Maybe [([Char], [Char])]
forall a. a -> Maybe a
Just [([Char], [Char])]
env'') [Char]
program [[Char]]
programArgs ByteString
BL.empty)
       ([Char] -> IOException -> IO (ExitCode, ByteString)
forall a. [Char] -> IOException -> IO a
handlePDFProgramNotFound [Char]
program)
     report $ MakePDFInfo "LaTeX output" (utf8ToText out)
     -- parse log to see if we need to rerun LaTeX
     let logFile = [Char] -> [Char] -> [Char]
replaceExtension [Char]
outfile [Char]
".log"
     logExists <- fileExists logFile
     logContents <- if logExists
                       then BL.fromStrict <$> readFileStrict logFile
                       else return mempty
     let rerunWarnings = ByteString -> [ByteString]
checkForRerun ByteString
logContents
     tocFileExists <- fileExists (replaceExtension outfile ".toc")
       -- if we have a TOC we always need 3 runs, see #10308
     let rerunWarnings' = [ByteString]
rerunWarnings [ByteString] -> [ByteString] -> [ByteString]
forall a. [a] -> [a] -> [a]
++ [ByteString
"TOC is present" | Bool
tocFileExists]
     if not (null rerunWarnings') && runNumber < maxruns
        then do
          report $ MakePDFInfo "Rerun needed"
                    (T.intercalate "\n" (map utf8ToText rerunWarnings'))
          go env'' programArgs (runNumber + 1)
       else do
          (log', pdf) <- getResultingPDF (Just logFile) outfile
          return (exit, fromMaybe out log', pdf)

   checkForRerun :: ByteString -> [ByteString]
checkForRerun ByteString
log' = (ByteString -> Bool) -> [ByteString] -> [ByteString]
forall a. (a -> Bool) -> [a] -> [a]
filter ByteString -> Bool
isRerunWarning ([ByteString] -> [ByteString]) -> [ByteString] -> [ByteString]
forall a b. (a -> b) -> a -> b
$ ByteString -> [ByteString]
BC.lines ByteString
log'

   isRerunWarning :: ByteString -> Bool
isRerunWarning ByteString
ln =
     let ln' :: ByteString
ln' = ByteString -> ByteString
BL.toStrict ByteString
ln
       in ByteString -> ByteString -> Bool
BS.isInfixOf ByteString
"Warning:" ByteString
ln' Bool -> Bool -> Bool
&& ByteString -> ByteString -> Bool
BS.isInfixOf ByteString
"Rerun" ByteString
ln'

generic2pdf :: (PandocMonad m, MonadIO m)
            => String
            -> [String]
            -> Text
            -> m (Either ByteString ByteString)
generic2pdf :: forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char] -> [[Char]] -> Text -> m (Either ByteString ByteString)
generic2pdf [Char]
program [[Char]]
args Text
source = do
  env' <- IO [([Char], [Char])] -> m [([Char], [Char])]
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO [([Char], [Char])]
getEnvironment
  showVerboseInfo Nothing program args env' source
  (exit, out) <- liftIO $ E.catch
    (pipeProcess (Just env') program args
                     (BL.fromStrict $ UTF8.fromText source))
    (handlePDFProgramNotFound program)
  return $ case exit of
             ExitFailure Int
_ -> ByteString -> Either ByteString ByteString
forall a b. a -> Either a b
Left ByteString
out
             ExitCode
ExitSuccess   -> ByteString -> Either ByteString ByteString
forall a b. b -> Either a b
Right ByteString
out

toPdfViaTempFile  ::
             Verbosity    -- ^ Verbosity level
          -> String       -- ^ Program (program name or path)
          -> [String]     -- ^ Args to program
          -> (String -> [String]) -- ^ Construct args for output file
          -> String       -- ^ extension to use for input file (e.g. '.html')
          -> Text         -- ^ Source
          -> IO (Either ByteString ByteString)
toPdfViaTempFile :: Verbosity
-> [Char]
-> [[Char]]
-> ([Char] -> [[Char]])
-> [Char]
-> Text
-> IO (Either ByteString ByteString)
toPdfViaTempFile Verbosity
verbosity [Char]
program [[Char]]
args [Char] -> [[Char]]
mkOutArgs [Char]
extension Text
source =
  [Char]
-> [Char]
-> ([Char] -> Handle -> IO (Either ByteString ByteString))
-> IO (Either ByteString ByteString)
forall (m :: * -> *) a.
(MonadIO m, MonadMask m) =>
[Char] -> [Char] -> ([Char] -> Handle -> m a) -> m a
withTempFile [Char]
"." ([Char]
"toPdfViaTempFile" [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> [Char]
extension) (([Char] -> Handle -> IO (Either ByteString ByteString))
 -> IO (Either ByteString ByteString))
-> ([Char] -> Handle -> IO (Either ByteString ByteString))
-> IO (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ \[Char]
file Handle
h1 ->
    [Char]
-> [Char]
-> ([Char] -> Handle -> IO (Either ByteString ByteString))
-> IO (Either ByteString ByteString)
forall (m :: * -> *) a.
(MonadIO m, MonadMask m) =>
[Char] -> [Char] -> ([Char] -> Handle -> m a) -> m a
withTempFile [Char]
"." [Char]
"toPdfViaTempFile.pdf" (([Char] -> Handle -> IO (Either ByteString ByteString))
 -> IO (Either ByteString ByteString))
-> ([Char] -> Handle -> IO (Either ByteString ByteString))
-> IO (Either ByteString ByteString)
forall a b. (a -> b) -> a -> b
$ \[Char]
pdfFile Handle
h2 -> do
      Handle -> IO ()
hClose Handle
h1
      Handle -> IO ()
hClose Handle
h2
      [Char] -> ByteString -> IO ()
BS.writeFile [Char]
file (ByteString -> IO ()) -> ByteString -> IO ()
forall a b. (a -> b) -> a -> b
$ Text -> ByteString
UTF8.fromText Text
source
      let programArgs :: [[Char]]
programArgs = [[Char]]
args [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [[Char]
file] [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [Char] -> [[Char]]
mkOutArgs [Char]
pdfFile
      env' <- IO [([Char], [Char])]
getEnvironment
      fileContents <- UTF8.readFile file
      runIOorExplode $ do
        setVerbosity verbosity
        showVerboseInfo Nothing program programArgs env' fileContents
      (exit, out) <- E.catch
        (pipeProcess (Just env') program programArgs BL.empty)
        (handlePDFProgramNotFound program)
      runIOorExplode $ do
        setVerbosity verbosity
        report $ MakePDFInfo "pdf-engine output" (utf8ToText out)
      pdfExists <- doesFileExist pdfFile
      mbPdf <- if 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 Just . BL.fromChunks . (:[]) <$> BS.readFile pdfFile
                else return Nothing
      return $ case (exit, mbPdf) of
                 (ExitFailure Int
_, Maybe ByteString
_)      -> ByteString -> Either ByteString ByteString
forall a b. a -> Either a b
Left ByteString
out
                 (ExitCode
ExitSuccess, Maybe ByteString
Nothing)  -> ByteString -> Either ByteString ByteString
forall a b. a -> Either a b
Left ByteString
""
                 (ExitCode
ExitSuccess, Just 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 :: forall (m :: * -> *).
(PandocMonad m, MonadIO m) =>
[Char]
-> [[Char]] -> [Char] -> Text -> m (Either ByteString ByteString)
context2pdf [Char]
program [[Char]]
pdfargs [Char]
tmpDir Text
source = do
  let file :: [Char]
file = [Char]
"input.tex"
  let programArgs :: [[Char]]
programArgs = [Char]
"--batchmode" [Char] -> [[Char]] -> [[Char]]
forall a. a -> [a] -> [a]
: [[Char]]
pdfargs [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [[Char]
file]
  env' <- IO [([Char], [Char])] -> m [([Char], [Char])]
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO [([Char], [Char])]
getEnvironment
  verbosity <- getVerbosity
  liftIO $ BS.writeFile (tmpDir </> file) $ UTF8.fromText source
  liftIO (UTF8.readFile (tmpDir </> file)) >>=
    showVerboseInfo (Just tmpDir) program programArgs env'
  liftIO $ inDirectory tmpDir $ do
    (exit, out) <- E.catch
      (pipeProcess (Just env') program programArgs BL.empty)
      (handlePDFProgramNotFound program)
    runIOorExplode $ do
      setVerbosity verbosity
      report $ MakePDFInfo "ConTeXt run output" (utf8ToText out)
    let pdfFile = [Char] -> [Char] -> [Char]
replaceExtension [Char]
file [Char]
".pdf"
    pdfExists <- doesFileExist pdfFile
    mbPdf <- if 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 (Just . BL.fromChunks . (:[])) `fmap` BS.readFile pdfFile
              else return Nothing
    case (exit, mbPdf) of
         (ExitFailure Int
_, Maybe ByteString
_)      -> do
            let logmsg :: ByteString
logmsg = ByteString -> ByteString
extractConTeXtMsg ByteString
out
            Either ByteString ByteString -> IO (Either ByteString ByteString)
forall a. a -> IO a
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
         (ExitCode
ExitSuccess, Maybe ByteString
Nothing)  -> Either ByteString ByteString -> IO (Either ByteString ByteString)
forall a. a -> IO a
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
""
         (ExitCode
ExitSuccess, Just ByteString
pdf) -> Either ByteString ByteString -> IO (Either ByteString ByteString)
forall a. a -> IO a
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 :: PandocMonad m
                => Maybe FilePath
                -> String
                -> [String]
                -> [(String, String)]
                -> Text
                -> m ()
showVerboseInfo :: forall (m :: * -> *).
PandocMonad m =>
Maybe [Char]
-> [Char] -> [[Char]] -> [([Char], [Char])] -> Text -> m ()
showVerboseInfo Maybe [Char]
mbTmpDir [Char]
program [[Char]]
programArgs [([Char], [Char])]
env Text
source = do
  case Maybe [Char]
mbTmpDir of
    Just [Char]
tmpDir -> LogMessage -> m ()
forall (m :: * -> *). PandocMonad m => LogMessage -> m ()
report (LogMessage -> m ()) -> LogMessage -> m ()
forall a b. (a -> b) -> a -> b
$ Text -> Text -> LogMessage
MakePDFInfo Text
"Temp dir:" ([Char] -> Text
T.pack [Char]
tmpDir)
    Maybe [Char]
Nothing -> () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  LogMessage -> m ()
forall (m :: * -> *). PandocMonad m => LogMessage -> m ()
report (LogMessage -> m ()) -> LogMessage -> m ()
forall a b. (a -> b) -> a -> b
$ Text -> Text -> LogMessage
MakePDFInfo Text
"Command line:"
           ([Char] -> Text
T.pack [Char]
program Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> [Char] -> Text
T.pack ([[Char]] -> [Char]
unwords (([Char] -> [Char]) -> [[Char]] -> [[Char]]
forall a b. (a -> b) -> [a] -> [b]
map [Char] -> [Char]
forall a. Show a => a -> [Char]
show [[Char]]
programArgs)))
  -- we filter out irrelevant stuff to avoid leaking passwords and keys!
  let isRelevant :: [Char] -> Bool
isRelevant [Char]
e = ([Char]
e [Char] -> [[Char]] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ [Char]
"PKFONTS"
                               , [Char]
"AFMFONTS"
                               , [Char]
"BIBINPUTS"
                               , [Char]
"BLTXMLINPUTS"
                               , [Char]
"BSTINPUTS"
                               , [Char]
"CLUAINPUTS"
                               , [Char]
"CMAPFONTS"
                               , [Char]
"CWEBINPUTS"
                               , [Char]
"DVIPSHEADERS"
                               , [Char]
"ENCFONTS"
                               , [Char]
"FONTCIDMAPS"
                               , [Char]
"FONTFEATURES"
                               , [Char]
"GFFONTS"
                               , [Char]
"GLYPHFONTS"
                               , [Char]
"HOME"
                               , [Char]
"INDEXSTYLE"
                               , [Char]
"KPATHSEA_DEBUG"
                               , [Char]
"KPATHSEA_WARNING"
                               , [Char]
"LANG"
                               , [Char]
"LIGFONTS"
                               , [Char]
"LUAINPUTS"
                               , [Char]
"LUA_CPATH"
                               , [Char]
"LUA_PATH"
                               , [Char]
"MFBASES"
                               , [Char]
"MFINPUTS"
                               , [Char]
"MFPOOL"
                               , [Char]
"MFTINPUTS"
                               , [Char]
"MISCFONTS"
                               , [Char]
"MISSFONT_LOG"
                               , [Char]
"MLBIBINPUTS"
                               , [Char]
"MLBSTINPUTS"
                               , [Char]
"MPINPUTS"
                               , [Char]
"MPMEMS"
                               , [Char]
"MPPOOL"
                               , [Char]
"MPSUPPORT"
                               , [Char]
"OCPINPUTS"
                               , [Char]
"OFMFONTS"
                               , [Char]
"OPENTYPEFONTS"
                               , [Char]
"OPLFONTS"
                               , [Char]
"OTPINPUTS"
                               , [Char]
"OVFFONTS"
                               , [Char]
"OVPFONTS"
                               , [Char]
"PATH"
                               , [Char]
"PDFTEXCONFIG"
                               , [Char]
"PROGRAMFONTS"
                               , [Char]
"PSHEADERS"
                               , [Char]
"PWD"
                               , [Char]
"RISINPUTS"
                               , [Char]
"SELFAUTODIR"
                               , [Char]
"SELFAUTOLOC"
                               , [Char]
"SELFAUTOPARENT"
                               , [Char]
"SFDFONTS"
                               , [Char]
"SHELL"
                               , [Char]
"SOURCE_DATE_EPOCH"
                               , [Char]
"T1FONTS"
                               , [Char]
"T1INPUTS"
                               , [Char]
"T42FONTS"
                               , [Char]
"TEXBIB"
                               , [Char]
"TEXCONFIG"
                               , [Char]
"TEXDOCS"
                               , [Char]
"TEXFONTMAPS"
                               , [Char]
"TEXFONTS"
                               , [Char]
"TEXFORMATS"
                               , [Char]
"TEXINDEXSTYLE"
                               , [Char]
"TEXINPUTS"
                               , [Char]
"TEXMFCNF"
                               , [Char]
"TEXMFDBS"
                               , [Char]
"TEXMFINI"
                               , [Char]
"TEXMFSCRIPTS"
                               , [Char]
"TEXMFVAR"
                               , [Char]
"TEXPICTS"
                               , [Char]
"TEXPKS"
                               , [Char]
"TEXPOOL"
                               , [Char]
"TEXPSHEADERS"
                               , [Char]
"TEXSOURCES"
                               , [Char]
"TEX_HUSH"
                               , [Char]
"TFMFONTS"
                               , [Char]
"TMPDIR"
                               , [Char]
"TRFONTS"
                               , [Char]
"TTFONTS"
                               , [Char]
"USERPROFILE"
                               , [Char]
"USE_TEXMFVAR"
                               , [Char]
"USE_VARTEXFONTS"
                               , [Char]
"VARTEXFONTS"
                               , [Char]
"VFFONTS"
                               , [Char]
"WEB2C"
                               , [Char]
"WEBINPUTS"
                               ]) Bool -> Bool -> Bool
|| [Char]
"TEXMF" [Char] -> [Char] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [Char]
e
  LogMessage -> m ()
forall (m :: * -> *). PandocMonad m => LogMessage -> m ()
report (LogMessage -> m ()) -> LogMessage -> m ()
forall a b. (a -> b) -> a -> b
$ Text -> Text -> LogMessage
MakePDFInfo Text
"Relevant environment variables:"
             (Text -> [Text] -> Text
T.intercalate Text
"\n" ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$ (([Char], [Char]) -> Text) -> [([Char], [Char])] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map ([Char], [Char]) -> Text
forall a. Show a => a -> Text
tshow ([([Char], [Char])] -> [Text]) -> [([Char], [Char])] -> [Text]
forall a b. (a -> b) -> a -> b
$ (([Char], [Char]) -> Bool)
-> [([Char], [Char])] -> [([Char], [Char])]
forall a. (a -> Bool) -> [a] -> [a]
filter ([Char] -> Bool
isRelevant ([Char] -> Bool)
-> (([Char], [Char]) -> [Char]) -> ([Char], [Char]) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([Char], [Char]) -> [Char]
forall a b. (a, b) -> a
fst) [([Char], [Char])]
env)
  LogMessage -> m ()
forall (m :: * -> *). PandocMonad m => LogMessage -> m ()
report (LogMessage -> m ()) -> LogMessage -> m ()
forall a b. (a -> b) -> a -> b
$ Text -> Text -> LogMessage
MakePDFInfo Text
"Source:" Text
source

handlePDFProgramNotFound :: String -> IE.IOError -> IO a
handlePDFProgramNotFound :: forall a. [Char] -> IOException -> IO a
handlePDFProgramNotFound [Char]
program IOException
e
  | IOException -> Bool
IE.isDoesNotExistError IOException
e =
      PandocError -> IO a
forall e a. (HasCallStack, 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
$ [Char] -> Text
T.pack [Char]
program
  | Bool
otherwise = IOException -> IO a
forall e a. (HasCallStack, Exception e) => e -> IO a
E.throwIO IOException
e

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