2007-11-03 23:27:58 +00:00
|
|
|
{-
|
2008-01-08 17:26:16 +00:00
|
|
|
Copyright (C) 2006-8 John MacFarlane <jgm@berkeley.edu>
|
2007-11-03 23:27:58 +00:00
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
-}
|
|
|
|
|
|
|
|
{- |
|
|
|
|
Module : Text.Pandoc.Writers.LaTeX
|
2008-01-08 17:26:16 +00:00
|
|
|
Copyright : Copyright (C) 2006-8 John MacFarlane
|
2007-11-03 23:27:58 +00:00
|
|
|
License : GNU GPL, version 2 or above
|
|
|
|
|
|
|
|
Maintainer : John MacFarlane <jgm@berkeley.edu>
|
|
|
|
Stability : alpha
|
|
|
|
Portability : portable
|
|
|
|
|
|
|
|
Conversion of 'Pandoc' format into LaTeX.
|
|
|
|
-}
|
|
|
|
module Text.Pandoc.Writers.LaTeX ( writeLaTeX ) where
|
|
|
|
import Text.Pandoc.Definition
|
|
|
|
import Text.Pandoc.Shared
|
|
|
|
import Text.Printf ( printf )
|
2008-01-16 02:18:23 +00:00
|
|
|
import Data.List ( (\\), isSuffixOf )
|
2007-11-03 23:27:58 +00:00
|
|
|
import Data.Char ( toLower )
|
|
|
|
import qualified Data.Set as S
|
|
|
|
import Control.Monad.State
|
|
|
|
import Text.PrettyPrint.HughesPJ hiding ( Str )
|
|
|
|
|
|
|
|
data WriterState =
|
2007-11-15 03:11:33 +00:00
|
|
|
WriterState { stIncludes :: S.Set String -- strings to include in header
|
|
|
|
, stInNote :: Bool -- @True@ if we're in a note
|
|
|
|
, stOLLevel :: Int -- level of ordered list nesting
|
|
|
|
, stOptions :: WriterOptions -- writer options, so they don't have to be parameter
|
|
|
|
}
|
2007-11-03 23:27:58 +00:00
|
|
|
|
|
|
|
-- | Add line to header.
|
|
|
|
addToHeader :: String -> State WriterState ()
|
|
|
|
addToHeader str = do
|
|
|
|
st <- get
|
|
|
|
let includes = stIncludes st
|
|
|
|
put st {stIncludes = S.insert str includes}
|
|
|
|
|
|
|
|
-- | Convert Pandoc to LaTeX.
|
|
|
|
writeLaTeX :: WriterOptions -> Pandoc -> String
|
|
|
|
writeLaTeX options document =
|
|
|
|
render $ evalState (pandocToLaTeX options document) $
|
2007-11-15 03:11:33 +00:00
|
|
|
WriterState { stIncludes = S.empty, stInNote = False, stOLLevel = 1, stOptions = options }
|
2007-11-03 23:27:58 +00:00
|
|
|
|
|
|
|
pandocToLaTeX :: WriterOptions -> Pandoc -> State WriterState Doc
|
|
|
|
pandocToLaTeX options (Pandoc meta blocks) = do
|
|
|
|
main <- blockListToLaTeX blocks
|
2008-07-13 16:53:06 +00:00
|
|
|
head' <- if writerStandalone options
|
2007-11-03 23:27:58 +00:00
|
|
|
then latexHeader options meta
|
|
|
|
else return empty
|
|
|
|
let before = if null (writerIncludeBefore options)
|
|
|
|
then empty
|
|
|
|
else text (writerIncludeBefore options)
|
|
|
|
let after = if null (writerIncludeAfter options)
|
|
|
|
then empty
|
|
|
|
else text (writerIncludeAfter options)
|
|
|
|
let body = before $$ main $$ after
|
|
|
|
let toc = if writerTableOfContents options
|
|
|
|
then text "\\tableofcontents\n"
|
|
|
|
else empty
|
|
|
|
let foot = if writerStandalone options
|
|
|
|
then text "\\end{document}"
|
|
|
|
else empty
|
2008-07-13 16:53:06 +00:00
|
|
|
return $ head' $$ toc $$ body $$ foot
|
2007-11-03 23:27:58 +00:00
|
|
|
|
|
|
|
-- | Insert bibliographic information into LaTeX header.
|
|
|
|
latexHeader :: WriterOptions -- ^ Options, including LaTeX header
|
|
|
|
-> Meta -- ^ Meta with bibliographic information
|
|
|
|
-> State WriterState Doc
|
|
|
|
latexHeader options (Meta title authors date) = do
|
|
|
|
titletext <- if null title
|
|
|
|
then return empty
|
|
|
|
else inlineListToLaTeX title >>= return . inCmd "title"
|
|
|
|
headerIncludes <- get >>= return . S.toList . stIncludes
|
|
|
|
let extras = text $ unlines headerIncludes
|
|
|
|
let verbatim = if "\\usepackage{fancyvrb}" `elem` headerIncludes
|
|
|
|
then text "\\VerbatimFootnotes % allows verbatim text in footnotes"
|
|
|
|
else empty
|
|
|
|
let authorstext = text $ "\\author{" ++
|
|
|
|
joinWithSep "\\\\" (map stringToLaTeX authors) ++ "}"
|
|
|
|
let datetext = if date == ""
|
|
|
|
then empty
|
|
|
|
else text $ "\\date{" ++ stringToLaTeX date ++ "}"
|
|
|
|
let maketitle = if null title then empty else text "\\maketitle"
|
|
|
|
let secnumline = if (writerNumberSections options)
|
|
|
|
then empty
|
|
|
|
else text "\\setcounter{secnumdepth}{0}"
|
|
|
|
let baseHeader = text $ writerHeader options
|
|
|
|
let header = baseHeader $$ extras
|
|
|
|
return $ header $$ secnumline $$ verbatim $$ titletext $$ authorstext $$
|
|
|
|
datetext $$ text "\\begin{document}" $$ maketitle $$ text ""
|
|
|
|
|
|
|
|
-- escape things as needed for LaTeX
|
|
|
|
|
|
|
|
stringToLaTeX :: String -> String
|
|
|
|
stringToLaTeX = escapeStringUsing latexEscapes
|
|
|
|
where latexEscapes = backslashEscapes "{}$%&_#" ++
|
|
|
|
[ ('^', "\\^{}")
|
|
|
|
, ('\\', "\\textbackslash{}")
|
|
|
|
, ('~', "\\ensuremath{\\sim}")
|
|
|
|
, ('|', "\\textbar{}")
|
|
|
|
, ('<', "\\textless{}")
|
|
|
|
, ('>', "\\textgreater{}")
|
2008-07-11 01:24:15 +00:00
|
|
|
, ('\160', "~")
|
2007-11-03 23:27:58 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
-- | Puts contents into LaTeX command.
|
|
|
|
inCmd :: String -> Doc -> Doc
|
|
|
|
inCmd cmd contents = char '\\' <> text cmd <> braces contents
|
|
|
|
|
|
|
|
-- | Remove all code elements from list of inline elements
|
|
|
|
-- (because it's illegal to have verbatim inside some command arguments)
|
|
|
|
deVerb :: [Inline] -> [Inline]
|
|
|
|
deVerb [] = []
|
|
|
|
deVerb ((Code str):rest) =
|
|
|
|
(TeX $ "\\texttt{" ++ stringToLaTeX str ++ "}"):(deVerb rest)
|
|
|
|
deVerb (other:rest) = other:(deVerb rest)
|
|
|
|
|
|
|
|
-- | Convert Pandoc block element to LaTeX.
|
|
|
|
blockToLaTeX :: Block -- ^ Block to convert
|
|
|
|
-> State WriterState Doc
|
|
|
|
blockToLaTeX Null = return empty
|
2007-11-15 03:11:33 +00:00
|
|
|
blockToLaTeX (Plain lst) = do
|
|
|
|
st <- get
|
|
|
|
let opts = stOptions st
|
2007-11-17 18:42:11 +00:00
|
|
|
wrapTeXIfNeeded opts True inlineListToLaTeX lst
|
2007-11-15 03:11:33 +00:00
|
|
|
blockToLaTeX (Para lst) = do
|
|
|
|
st <- get
|
|
|
|
let opts = stOptions st
|
2007-11-17 18:42:11 +00:00
|
|
|
result <- wrapTeXIfNeeded opts True inlineListToLaTeX lst
|
2007-11-15 03:11:33 +00:00
|
|
|
return $ result <> char '\n'
|
2007-11-03 23:27:58 +00:00
|
|
|
blockToLaTeX (BlockQuote lst) = do
|
|
|
|
contents <- blockListToLaTeX lst
|
|
|
|
return $ text "\\begin{quote}" $$ contents $$ text "\\end{quote}"
|
2008-02-09 03:18:22 +00:00
|
|
|
blockToLaTeX (CodeBlock _ str) = do
|
2007-11-03 23:27:58 +00:00
|
|
|
st <- get
|
|
|
|
env <- if stInNote st
|
|
|
|
then do addToHeader "\\usepackage{fancyvrb}"
|
|
|
|
return "Verbatim"
|
|
|
|
else return "verbatim"
|
|
|
|
return $ text ("\\begin{" ++ env ++ "}\n") <> text str <>
|
|
|
|
text ("\n\\end{" ++ env ++ "}")
|
2008-07-13 16:53:06 +00:00
|
|
|
blockToLaTeX (RawHtml _) = return empty
|
2007-11-03 23:27:58 +00:00
|
|
|
blockToLaTeX (BulletList lst) = do
|
|
|
|
items <- mapM listItemToLaTeX lst
|
|
|
|
return $ text "\\begin{itemize}" $$ vcat items $$ text "\\end{itemize}"
|
|
|
|
blockToLaTeX (OrderedList (start, numstyle, numdelim) lst) = do
|
|
|
|
st <- get
|
|
|
|
let oldlevel = stOLLevel st
|
|
|
|
put $ st {stOLLevel = oldlevel + 1}
|
|
|
|
items <- mapM listItemToLaTeX lst
|
2008-07-13 16:53:06 +00:00
|
|
|
modify (\s -> s {stOLLevel = oldlevel})
|
2007-11-03 23:27:58 +00:00
|
|
|
exemplar <- if numstyle /= DefaultStyle || numdelim /= DefaultDelim
|
|
|
|
then do addToHeader "\\usepackage{enumerate}"
|
|
|
|
return $ char '[' <>
|
|
|
|
text (head (orderedListMarkers (1, numstyle,
|
|
|
|
numdelim))) <> char ']'
|
|
|
|
else return empty
|
|
|
|
let resetcounter = if start /= 1 && oldlevel <= 4
|
|
|
|
then text $ "\\setcounter{enum" ++
|
|
|
|
map toLower (toRomanNumeral oldlevel) ++
|
|
|
|
"}{" ++ show (start - 1) ++ "}"
|
|
|
|
else empty
|
|
|
|
return $ text "\\begin{enumerate}" <> exemplar $$ resetcounter $$
|
|
|
|
vcat items $$ text "\\end{enumerate}"
|
|
|
|
blockToLaTeX (DefinitionList lst) = do
|
|
|
|
items <- mapM defListItemToLaTeX lst
|
|
|
|
return $ text "\\begin{description}" $$ vcat items $$
|
|
|
|
text "\\end{description}"
|
|
|
|
blockToLaTeX HorizontalRule = return $ text $
|
|
|
|
"\\begin{center}\\rule{3in}{0.4pt}\\end{center}\n"
|
|
|
|
blockToLaTeX (Header level lst) = do
|
|
|
|
txt <- inlineListToLaTeX (deVerb lst)
|
|
|
|
return $ if (level > 0) && (level <= 3)
|
|
|
|
then text ("\\" ++ (concat (replicate (level - 1) "sub")) ++
|
|
|
|
"section{") <> txt <> text "}\n"
|
|
|
|
else txt <> char '\n'
|
|
|
|
blockToLaTeX (Table caption aligns widths heads rows) = do
|
|
|
|
headers <- tableRowToLaTeX heads
|
|
|
|
captionText <- inlineListToLaTeX caption
|
|
|
|
rows' <- mapM tableRowToLaTeX rows
|
|
|
|
let colWidths = map (printf "%.2f") widths
|
|
|
|
let colDescriptors = concat $ zipWith
|
|
|
|
(\width align -> ">{\\PBS" ++
|
|
|
|
(case align of
|
|
|
|
AlignLeft -> "\\raggedright"
|
|
|
|
AlignRight -> "\\raggedleft"
|
|
|
|
AlignCenter -> "\\centering"
|
|
|
|
AlignDefault -> "\\raggedright") ++
|
|
|
|
"\\hspace{0pt}}p{" ++ width ++
|
|
|
|
"\\columnwidth}")
|
|
|
|
colWidths aligns
|
|
|
|
let tableBody = text ("\\begin{tabular}{" ++ colDescriptors ++ "}") $$
|
|
|
|
headers $$ text "\\hline" $$ vcat rows' $$
|
|
|
|
text "\\end{tabular}"
|
|
|
|
let centered txt = text "\\begin{center}" $$ txt $$ text "\\end{center}"
|
2008-07-31 23:55:27 +00:00
|
|
|
addToHeader $ "\\usepackage{array}\n" ++
|
|
|
|
"% This is needed because raggedright in table elements redefines \\\\:\n" ++
|
|
|
|
"\\newcommand{\\PreserveBackslash}[1]{\\let\\temp=\\\\#1\\let\\\\=\\temp}\n" ++
|
|
|
|
"\\let\\PBS=\\PreserveBackslash"
|
2007-11-03 23:27:58 +00:00
|
|
|
return $ if isEmpty captionText
|
|
|
|
then centered tableBody <> char '\n'
|
|
|
|
else text "\\begin{table}[h]" $$ centered tableBody $$
|
|
|
|
inCmd "caption" captionText $$ text "\\end{table}\n"
|
|
|
|
|
2008-07-13 16:53:06 +00:00
|
|
|
blockListToLaTeX :: [Block] -> State WriterState Doc
|
2007-11-03 23:27:58 +00:00
|
|
|
blockListToLaTeX lst = mapM blockToLaTeX lst >>= return . vcat
|
|
|
|
|
2008-07-13 16:53:06 +00:00
|
|
|
tableRowToLaTeX :: [[Block]] -> State WriterState Doc
|
2007-11-03 23:27:58 +00:00
|
|
|
tableRowToLaTeX cols = mapM blockListToLaTeX cols >>=
|
|
|
|
return . ($$ text "\\\\") . foldl (\row item -> row $$
|
|
|
|
(if isEmpty row then empty else text " & ") <> item) empty
|
|
|
|
|
2008-07-13 16:53:06 +00:00
|
|
|
listItemToLaTeX :: [Block] -> State WriterState Doc
|
2007-11-03 23:27:58 +00:00
|
|
|
listItemToLaTeX lst = blockListToLaTeX lst >>= return . (text "\\item " $$) .
|
|
|
|
(nest 2)
|
|
|
|
|
2008-07-13 16:53:06 +00:00
|
|
|
defListItemToLaTeX :: ([Inline], [Block]) -> State WriterState Doc
|
2007-11-03 23:27:58 +00:00
|
|
|
defListItemToLaTeX (term, def) = do
|
|
|
|
term' <- inlineListToLaTeX $ deVerb term
|
|
|
|
def' <- blockListToLaTeX def
|
|
|
|
return $ text "\\item[" <> term' <> text "]" $$ def'
|
|
|
|
|
|
|
|
-- | Convert list of inline elements to LaTeX.
|
|
|
|
inlineListToLaTeX :: [Inline] -- ^ Inlines to convert
|
|
|
|
-> State WriterState Doc
|
|
|
|
inlineListToLaTeX lst = mapM inlineToLaTeX lst >>= return . hcat
|
|
|
|
|
|
|
|
isQuoted :: Inline -> Bool
|
|
|
|
isQuoted (Quoted _ _) = True
|
|
|
|
isQuoted Apostrophe = True
|
|
|
|
isQuoted _ = False
|
|
|
|
|
|
|
|
-- | Convert inline element to LaTeX
|
|
|
|
inlineToLaTeX :: Inline -- ^ Inline to convert
|
|
|
|
-> State WriterState Doc
|
|
|
|
inlineToLaTeX (Emph lst) =
|
|
|
|
inlineListToLaTeX (deVerb lst) >>= return . inCmd "emph"
|
|
|
|
inlineToLaTeX (Strong lst) =
|
|
|
|
inlineListToLaTeX (deVerb lst) >>= return . inCmd "textbf"
|
|
|
|
inlineToLaTeX (Strikeout lst) = do
|
|
|
|
contents <- inlineListToLaTeX $ deVerb lst
|
|
|
|
addToHeader "\\usepackage[normalem]{ulem}"
|
|
|
|
return $ inCmd "sout" contents
|
|
|
|
inlineToLaTeX (Superscript lst) =
|
|
|
|
inlineListToLaTeX (deVerb lst) >>= return . inCmd "textsuperscript"
|
|
|
|
inlineToLaTeX (Subscript lst) = do
|
|
|
|
contents <- inlineListToLaTeX $ deVerb lst
|
|
|
|
-- oddly, latex includes \textsuperscript but not \textsubscript
|
2008-06-08 03:29:09 +00:00
|
|
|
-- so we have to define it (using a different name so as not to conflict with memoir class):
|
|
|
|
addToHeader "\\newcommand{\\textsubscr}[1]{\\ensuremath{_{\\scriptsize\\textrm{#1}}}}"
|
|
|
|
return $ inCmd "textsubscr" contents
|
2008-07-15 23:26:06 +00:00
|
|
|
inlineToLaTeX (SmallCaps lst) =
|
|
|
|
inlineListToLaTeX (deVerb lst) >>= return . inCmd "textsc"
|
2007-11-03 23:27:58 +00:00
|
|
|
inlineToLaTeX (Code str) = do
|
|
|
|
st <- get
|
|
|
|
if stInNote st
|
|
|
|
then do addToHeader "\\usepackage{fancyvrb}"
|
|
|
|
else return ()
|
|
|
|
let chr = ((enumFromTo '!' '~') \\ str) !! 0
|
|
|
|
return $ text $ "\\verb" ++ [chr] ++ str ++ [chr]
|
|
|
|
inlineToLaTeX (Quoted SingleQuote lst) = do
|
|
|
|
contents <- inlineListToLaTeX lst
|
|
|
|
let s1 = if (not (null lst)) && (isQuoted (head lst))
|
|
|
|
then text "\\,"
|
|
|
|
else empty
|
|
|
|
let s2 = if (not (null lst)) && (isQuoted (last lst))
|
|
|
|
then text "\\,"
|
|
|
|
else empty
|
|
|
|
return $ char '`' <> s1 <> contents <> s2 <> char '\''
|
|
|
|
inlineToLaTeX (Quoted DoubleQuote lst) = do
|
|
|
|
contents <- inlineListToLaTeX lst
|
|
|
|
let s1 = if (not (null lst)) && (isQuoted (head lst))
|
|
|
|
then text "\\,"
|
|
|
|
else empty
|
|
|
|
let s2 = if (not (null lst)) && (isQuoted (last lst))
|
|
|
|
then text "\\,"
|
|
|
|
else empty
|
|
|
|
return $ text "``" <> s1 <> contents <> s2 <> text "''"
|
|
|
|
inlineToLaTeX Apostrophe = return $ char '\''
|
|
|
|
inlineToLaTeX EmDash = return $ text "---"
|
|
|
|
inlineToLaTeX EnDash = return $ text "--"
|
|
|
|
inlineToLaTeX Ellipses = return $ text "\\ldots{}"
|
|
|
|
inlineToLaTeX (Str str) = return $ text $ stringToLaTeX str
|
2007-11-29 08:09:31 +00:00
|
|
|
inlineToLaTeX (Math str) = return $ char '$' <> text str <> char '$'
|
2007-11-03 23:27:58 +00:00
|
|
|
inlineToLaTeX (TeX str) = return $ text str
|
2008-07-13 16:53:06 +00:00
|
|
|
inlineToLaTeX (HtmlInline _) = return empty
|
2007-11-03 23:27:58 +00:00
|
|
|
inlineToLaTeX (LineBreak) = return $ text "\\\\"
|
|
|
|
inlineToLaTeX Space = return $ char ' '
|
|
|
|
inlineToLaTeX (Link txt (src, _)) = do
|
|
|
|
addToHeader "\\usepackage[breaklinks=true]{hyperref}"
|
|
|
|
case txt of
|
|
|
|
[Code x] | x == src -> -- autolink
|
|
|
|
do addToHeader "\\usepackage{url}"
|
|
|
|
return $ text $ "\\url{" ++ x ++ "}"
|
|
|
|
_ -> do contents <- inlineListToLaTeX $ deVerb txt
|
|
|
|
return $ text ("\\href{" ++ src ++ "}{") <> contents <>
|
|
|
|
char '}'
|
2008-07-13 16:53:06 +00:00
|
|
|
inlineToLaTeX (Image _ (source, _)) = do
|
2007-11-03 23:27:58 +00:00
|
|
|
addToHeader "\\usepackage{graphicx}"
|
|
|
|
return $ text $ "\\includegraphics{" ++ source ++ "}"
|
|
|
|
inlineToLaTeX (Note contents) = do
|
|
|
|
st <- get
|
|
|
|
put (st {stInNote = True})
|
|
|
|
contents' <- blockListToLaTeX contents
|
2008-07-13 16:53:06 +00:00
|
|
|
modify (\s -> s {stInNote = False})
|
2007-11-03 23:27:58 +00:00
|
|
|
let rawnote = stripTrailingNewlines $ render contents'
|
|
|
|
-- note: a \n before } is needed when note ends with a Verbatim environment
|
|
|
|
let optNewline = "\\end{Verbatim}" `isSuffixOf` rawnote
|
2007-12-04 04:14:16 +00:00
|
|
|
return $ text "\\footnote{" <>
|
2007-11-03 23:27:58 +00:00
|
|
|
text rawnote <> (if optNewline then char '\n' else empty) <> char '}'
|