git.iko.soy/typeable/pandoc
on master help sign in
Rewrote LaTeX writer to use the prettyprinting library,

so we get word wrapping, etc. git-svn-id: https://pandoc.googlecode.com/svn/trunk@964 788f1e2b-df1e-0410-8736-df70ead52e1b

commit1827ab40c35f1233b2f3fdee58bab4ab500c8e40 authorfiddlosopher <fiddlosopher@788f1e2b-df1e-0410-8736-df70ead52e1b> dateThu 30 Aug 2007, 22:48 (19 years ago) parent18f150c
1 file changed+130−104
src/Text/Pandoc/Writers/LaTeX.hs+130−104
src/Text/Pandoc/Writers/LaTeX.hs+130−104
@@ -31,10 +31,11 @@ module Text.Pandoc.Writers.LaTeX ( writeLaTeX ) where
3131import Text.Pandoc.Definition
3232import Text.Pandoc.Shared
3333import Text.Printf ( printf )
34−import Data.List ( (\\), isInfixOf )
34+import Data.List ( (\\), isInfixOf, intersperse )
3535import Data.Char ( toLower )
3636import qualified Data.Set as S
3737import Control.Monad.State
38+import Text.PrettyPrint.HughesPJ hiding ( Str )
3839
3940data WriterState =
4041 WriterState { stIncludes :: S.Set String -- strings to include in header
@@ -51,51 +52,56 @@ addToHeader str = do
5152-- | Convert Pandoc to LaTeX.
5253writeLaTeX :: WriterOptions -> Pandoc -> String
5354writeLaTeX options document =
54− evalState (pandocToLaTeX options document) $
55+ render $ evalState (pandocToLaTeX options document) $
5556 WriterState { stIncludes = S.empty, stInNote = False, stOLLevel = 1 }
5657
57−pandocToLaTeX :: WriterOptions -> Pandoc -> State WriterState String
58+pandocToLaTeX :: WriterOptions -> Pandoc -> State WriterState Doc
5859pandocToLaTeX options (Pandoc meta blocks) = do
5960 main <- blockListToLaTeX blocks
6061 head <- if writerStandalone options
6162 then latexHeader options meta
62− else return ""
63− let body = writerIncludeBefore options ++ main ++
64− writerIncludeAfter options
63+ else return empty
64+ let before = if null (writerIncludeBefore options)
65+ then empty
66+ else text (writerIncludeBefore options)
67+ let after = if null (writerIncludeAfter options)
68+ then empty
69+ else text (writerIncludeAfter options)
70+ let body = before $$ main $$ after
6571 let toc = if writerTableOfContents options
66− then "\\tableofcontents\n\n"
67− else ""
72+ then text "\\tableofcontents\n"
73+ else empty
6874 let foot = if writerStandalone options
69− then "\n\\end{document}\n"
70− else ""
71− return $ head ++ toc ++ body ++ foot
75+ then text "\\end{document}"
76+ else empty
77+ return $ head $$ toc $$ body $$ foot
7278
7379-- | Insert bibliographic information into LaTeX header.
7480latexHeader :: WriterOptions -- ^ Options, including LaTeX header
7581 -> Meta -- ^ Meta with bibliographic information
76− -> State WriterState String
82+ -> State WriterState Doc
7783latexHeader options (Meta title authors date) = do
7884 titletext <- if null title
79− then return ""
80− else do title' <- inlineListToLaTeX title
81− return $ "\\title{" ++ title' ++ "}\n"
82− extras <- get >>= (return . unlines . S.toList. stIncludes)
83− let verbatim = if "\\usepackage{fancyvrb}" `isInfixOf` extras
84− then "\\VerbatimFootnotes % allows verbatim text in footnotes\n"
85− else ""
86− let authorstext = "\\author{" ++
87− joinWithSep "\\\\" (map stringToLaTeX authors) ++ "}\n"
85+ then return empty
86+ else inlineListToLaTeX title >>= return . inCmd "title"
87+ headerIncludes <- get >>= return . S.toList . stIncludes
88+ let extras = text $ unlines headerIncludes
89+ let verbatim = if "\\usepackage{fancyvrb}" `elem` headerIncludes
90+ then text "\\VerbatimFootnotes % allows verbatim text in footnotes"
91+ else empty
92+ let authorstext = text $ "\\author{" ++
93+ joinWithSep "\\\\" (map stringToLaTeX authors) ++ "}"
8894 let datetext = if date == ""
89− then ""
90− else "\\date{" ++ stringToLaTeX date ++ "}\n"
91− let maketitle = if null title then "" else "\\maketitle\n"
95+ then empty
96+ else text $ "\\date{" ++ stringToLaTeX date ++ "}"
97+ let maketitle = if null title then empty else text "\\maketitle"
9298 let secnumline = if (writerNumberSections options)
93− then ""
94− else "\\setcounter{secnumdepth}{0}\n"
95− let baseHeader = writerHeader options
96− let header = baseHeader ++ extras
97− return $ header ++ secnumline ++ verbatim ++ titletext ++ authorstext ++
98− datetext ++ "\\begin{document}\n" ++ maketitle ++ "\n"
99+ then empty
100+ else text "\\setcounter{secnumdepth}{0}"
101+ let baseHeader = text $ writerHeader options
102+ let header = baseHeader $$ extras
103+ return $ header $$ secnumline $$ verbatim $$ titletext $$ authorstext $$
104+ datetext $$ text "\\begin{document}" $$ maketitle $$ text ""
99105
100106-- escape things as needed for LaTeX
101107
@@ -110,6 +116,10 @@ stringToLaTeX = escapeStringUsing latexEscapes
110116 , ('>', "\\textgreater{}")
111117 ]
112118
119+-- | Puts contents into LaTeX command.
120+inCmd :: String -> Doc -> Doc
121+inCmd cmd contents = char '\\' <> text cmd <> braces contents
122+
113123-- | Remove all code elements from list of inline elements
114124-- (because it's illegal to have verbatim inside some command arguments)
115125deVerb :: [Inline] -> [Inline]
@@ -120,23 +130,26 @@ deVerb (other:rest) = other:(deVerb rest)
120130
121131-- | Convert Pandoc block element to LaTeX.
122132blockToLaTeX :: Block -- ^ Block to convert
123− -> State WriterState String
124−blockToLaTeX Null = return ""
125−blockToLaTeX (Plain lst) = inlineListToLaTeX lst >>= return . (++ "\n")
126−blockToLaTeX (Para lst) = inlineListToLaTeX lst >>= return . (++ "\n\n")
133+ -> State WriterState Doc
134+blockToLaTeX Null = return empty
135+blockToLaTeX (Plain lst) = wrapped inlineListToLaTeX lst >>= return
136+blockToLaTeX (Para lst) =
137+ wrapped inlineListToLaTeX lst >>= return . (<> char '\n')
127138blockToLaTeX (BlockQuote lst) = do
128139 contents <- blockListToLaTeX lst
129− return $ "\\begin{quote}\n" ++ contents ++ "\\end{quote}\n"
140+ return $ text "\\begin{quote}" $$ contents $$ text "\\end{quote}"
130141blockToLaTeX (CodeBlock str) = do
131142 st <- get
132− if stInNote st
133− then do addToHeader "\\usepackage{fancyvrb}"
134− return $ "\\begin{Verbatim}\n" ++ str ++ "\n\\end{Verbatim}\n"
135− else return $ "\\begin{verbatim}\n" ++ str ++ "\n\\end{verbatim}\n"
136−blockToLaTeX (RawHtml str) = return ""
143+ env <- if stInNote st
144+ then do addToHeader "\\usepackage{fancyvrb}"
145+ return "Verbatim"
146+ else return "verbatim"
147+ return $ text ("\\begin{" ++ env ++ "}\n") <> text str <>
148+ text ("\n\\end{" ++ env ++ "}")
149+blockToLaTeX (RawHtml str) = return empty
137150blockToLaTeX (BulletList lst) = do
138151 items <- mapM listItemToLaTeX lst
139− return $ "\\begin{itemize}\n" ++ concat items ++ "\\end{itemize}\n"
152+ return $ text "\\begin{itemize}" $$ vcat items $$ text "\\end{itemize}"
140153blockToLaTeX (OrderedList (start, numstyle, numdelim) lst) = do
141154 st <- get
142155 let oldlevel = stOLLevel st
@@ -145,26 +158,29 @@ blockToLaTeX (OrderedList (start, numstyle, numdelim) lst) = do
145158 modify (\st -> st {stOLLevel = oldlevel})
146159 exemplar <- if numstyle /= DefaultStyle || numdelim /= DefaultDelim
147160 then do addToHeader "\\usepackage{enumerate}"
148− return $ "[" ++ head (orderedListMarkers (1, numstyle, numdelim)) ++ "]"
149− else return ""
161+ return $ char '[' <>
162+ text (head (orderedListMarkers (1, numstyle,
163+ numdelim))) <> char ']'
164+ else return empty
150165 let resetcounter = if start /= 1 && oldlevel <= 4
151− then "\\setcounter{enum" ++
166+ then text $ "\\setcounter{enum" ++
152167 map toLower (toRomanNumeral oldlevel) ++
153− "}{" ++ show (start - 1) ++ "}\n"
154− else ""
155− return $ "\\begin{enumerate}" ++ exemplar ++ "\n" ++
156− resetcounter ++ concat items ++ "\\end{enumerate}\n"
168+ "}{" ++ show (start - 1) ++ "}"
169+ else empty
170+ return $ text "\\begin{enumerate}" <> exemplar $$ resetcounter $$
171+ vcat items $$ text "\\end{enumerate}"
157172blockToLaTeX (DefinitionList lst) = do
158173 items <- mapM defListItemToLaTeX lst
159− return $ "\\begin{description}\n" ++ concat items ++ "\\end{description}\n"
160−blockToLaTeX HorizontalRule = return $
161− "\\begin{center}\\rule{3in}{0.4pt}\\end{center}\n\n"
174+ return $ text "\\begin{description}" $$ vcat items $$
175+ text "\\end{description}"
176+blockToLaTeX HorizontalRule = return $ text $
177+ "\\begin{center}\\rule{3in}{0.4pt}\\end{center}\n"
162178blockToLaTeX (Header level lst) = do
163− text <- inlineListToLaTeX (deVerb lst)
179+ txt <- inlineListToLaTeX (deVerb lst)
164180 return $ if (level > 0) && (level <= 3)
165− then "\\" ++ (concat (replicate (level - 1) "sub")) ++
166− "section{" ++ text ++ "}\n\n"
167− else text ++ "\n\n"
181+ then text ("\\" ++ (concat (replicate (level - 1) "sub")) ++
182+ "section{") <> txt <> text "}\n"
183+ else txt <> char '\n'
168184blockToLaTeX (Table caption aligns widths heads rows) = do
169185 headers <- tableRowToLaTeX heads
170186 captionText <- inlineListToLaTeX caption
@@ -180,34 +196,37 @@ blockToLaTeX (Table caption aligns widths heads rows) = do
180196 "\\hspace{0pt}}p{" ++ width ++
181197 "\\columnwidth}")
182198 colWidths aligns
183− let tableBody = "\\begin{tabular}{" ++ colDescriptors ++ "}\n" ++
184− headers ++ "\\hline\n" ++ concat rows' ++ "\\end{tabular}\n"
185− let centered str = "\\begin{center}\n" ++ str ++ "\\end{center}\n"
199+ let tableBody = text ("\\begin{tabular}{" ++ colDescriptors ++ "}") $$
200+ headers $$ text "\\hline" $$ vcat rows' $$
201+ text "\\end{tabular}"
202+ let centered txt = text "\\begin{center}" $$ txt $$ text "\\end{center}"
186203 addToHeader "\\usepackage{array}\n\
187204 \% This is needed because raggedright in table elements redefines \\\\:\n\
188205 \\\newcommand{\\PreserveBackslash}[1]{\\let\\temp=\\\\#1\\let\\\\=\\temp}\n\
189206 \\\let\\PBS=\\PreserveBackslash"
190− return $ if null captionText
191− then centered tableBody ++ "\n"
192− else "\\begin{table}[h]\n" ++ centered tableBody ++
193− "\\caption{" ++ captionText ++ "}\n" ++ "\\end{table}\n\n"
207+ return $ if isEmpty captionText
208+ then centered tableBody <> char '\n'
209+ else text "\\begin{table}[h]" $$ centered tableBody $$
210+ inCmd "caption" captionText $$ text "\\end{table}\n"
194211
195−blockListToLaTeX lst = mapM blockToLaTeX lst >>= return . concat
212+blockListToLaTeX lst = mapM blockToLaTeX lst >>= return . vcat
196213
197−tableRowToLaTeX cols =
198− mapM blockListToLaTeX cols >>= return . (++ "\\\\\n") . (joinWithSep " & ")
214+tableRowToLaTeX cols = mapM blockListToLaTeX cols >>=
215+ return . ($$ text "\\\\") . foldl (\row item -> row $$
216+ (if isEmpty row then empty else text " & ") <> item) empty
199217
200−listItemToLaTeX lst = blockListToLaTeX lst >>= return . ("\\item "++)
218+listItemToLaTeX lst = blockListToLaTeX lst >>= return . (text "\\item " $$) .
219+ (nest 2)
201220
202221defListItemToLaTeX (term, def) = do
203222 term' <- inlineListToLaTeX $ deVerb term
204223 def' <- blockListToLaTeX def
205− return $ "\\item[" ++ term' ++ "] " ++ def'
224+ return $ text "\\item[" <> term' <> text "]" $$ def'
206225
207226-- | Convert list of inline elements to LaTeX.
208227inlineListToLaTeX :: [Inline] -- ^ Inlines to convert
209− -> State WriterState String
210−inlineListToLaTeX lst = mapM inlineToLaTeX lst >>= return . concat
228+ -> State WriterState Doc
229+inlineListToLaTeX lst = mapM inlineToLaTeX lst >>= return . hcat
211230
212231isQuoted :: Inline -> Bool
213232isQuoted (Quoted _ _) = True
@@ -216,68 +235,75 @@ isQuoted _ = False
216235
217236-- | Convert inline element to LaTeX
218237inlineToLaTeX :: Inline -- ^ Inline to convert
219− -> State WriterState String
220−inlineToLaTeX (Emph lst) = do
221− contents <- inlineListToLaTeX $ deVerb lst
222− return $ "\\emph{" ++ contents ++ "}"
223−inlineToLaTeX (Strong lst) = do
224− contents <- inlineListToLaTeX $ deVerb lst
225− return $ "\\textbf{" ++ contents ++ "}"
238+ -> State WriterState Doc
239+inlineToLaTeX (Emph lst) =
240+ inlineListToLaTeX (deVerb lst) >>= return . inCmd "emph"
241+inlineToLaTeX (Strong lst) =
242+ inlineListToLaTeX (deVerb lst) >>= return . inCmd "textbf"
226243inlineToLaTeX (Strikeout lst) = do
227244 contents <- inlineListToLaTeX $ deVerb lst
228245 addToHeader "\\usepackage[normalem]{ulem}"
229− return $ "\\sout{" ++ contents ++ "}"
230−inlineToLaTeX (Superscript lst) = do
231− contents <- inlineListToLaTeX $ deVerb lst
232− return $ "\\textsuperscript{" ++ contents ++ "}"
246+ return $ inCmd "sout" contents
247+inlineToLaTeX (Superscript lst) =
248+ inlineListToLaTeX (deVerb lst) >>= return . inCmd "textsuperscript"
233249inlineToLaTeX (Subscript lst) = do
234250 contents <- inlineListToLaTeX $ deVerb lst
235251 -- oddly, latex includes \textsuperscript but not \textsubscript
236252 -- so we have to define it:
237253 addToHeader "\\newcommand{\\textsubscript}[1]{\\ensuremath{_{\\scriptsize\\textrm{#1}}}}"
238− return $ "\\textsubscript{" ++ contents ++ "}"
254+ return $ inCmd "textsubscript" contents
239255inlineToLaTeX (Code str) = do
240256 st <- get
241257 if stInNote st
242258 then do addToHeader "\\usepackage{fancyvrb}"
243259 else return ()
244260 let chr = ((enumFromTo '!' '~') \\ str) !! 0
245− return $ "\\verb" ++ [chr] ++ str ++ [chr]
261+ return $ text $ "\\verb" ++ [chr] ++ str ++ [chr]
246262inlineToLaTeX (Quoted SingleQuote lst) = do
247263 contents <- inlineListToLaTeX lst
248− let s1 = if (not (null lst)) && (isQuoted (head lst)) then "\\," else ""
249− let s2 = if (not (null lst)) && (isQuoted (last lst)) then "\\," else ""
250− return $ "`" ++ s1 ++ contents ++ s2 ++ "'"
264+ let s1 = if (not (null lst)) && (isQuoted (head lst))
265+ then text "\\,"
266+ else empty
267+ let s2 = if (not (null lst)) && (isQuoted (last lst))
268+ then text "\\,"
269+ else empty
270+ return $ char '`' <> s1 <> contents <> s2 <> char '\''
251271inlineToLaTeX (Quoted DoubleQuote lst) = do
252272 contents <- inlineListToLaTeX lst
253− let s1 = if (not (null lst)) && (isQuoted (head lst)) then "\\," else ""
254− let s2 = if (not (null lst)) && (isQuoted (last lst)) then "\\," else ""
255− return $ "``" ++ s1 ++ contents ++ s2 ++ "''"
256−inlineToLaTeX Apostrophe = return "'"
257−inlineToLaTeX EmDash = return "---"
258−inlineToLaTeX EnDash = return "--"
259−inlineToLaTeX Ellipses = return "\\ldots{}"
260−inlineToLaTeX (Str str) = return $ stringToLaTeX str
261−inlineToLaTeX (TeX str) = return str
262−inlineToLaTeX (HtmlInline str) = return ""
263−inlineToLaTeX (LineBreak) = return "\\\\\n"
264−inlineToLaTeX Space = return " "
265−inlineToLaTeX (Link text (src, _)) = do
273+ let s1 = if (not (null lst)) && (isQuoted (head lst))
274+ then text "\\,"
275+ else empty
276+ let s2 = if (not (null lst)) && (isQuoted (last lst))
277+ then text "\\,"
278+ else empty
279+ return $ text "``" <> s1 <> contents <> s2 <> text "''"
280+inlineToLaTeX Apostrophe = return $ char '\''
281+inlineToLaTeX EmDash = return $ text "---"
282+inlineToLaTeX EnDash = return $ text "--"
283+inlineToLaTeX Ellipses = return $ text "\\ldots{}"
284+inlineToLaTeX (Str str) = return $ text $ stringToLaTeX str
285+inlineToLaTeX (TeX str) = return $ text str
286+inlineToLaTeX (HtmlInline str) = return empty
287+inlineToLaTeX (LineBreak) = return $ text "\\\\"
288+inlineToLaTeX Space = return $ char ' '
289+inlineToLaTeX (Link txt (src, _)) = do
266290 addToHeader "\\usepackage[breaklinks=true]{hyperref}"
267− case text of
291+ case txt of
268292 [Code x] | x == src -> -- autolink
269293 do addToHeader "\\usepackage{url}"
270− return $ "\\url{" ++ x ++ "}"
271− _ -> do contents <- inlineListToLaTeX $ deVerb text
272− return $ "\\href{" ++ src ++ "}{" ++ contents ++ "}"
294+ return $ text $ "\\url{" ++ x ++ "}"
295+ _ -> do contents <- inlineListToLaTeX $ deVerb txt
296+ return $ text ("\\href{" ++ src ++ "}{") <> contents <>
297+ char '}'
273298inlineToLaTeX (Image alternate (source, tit)) = do
274299 addToHeader "\\usepackage{graphicx}"
275− return $ "\\includegraphics{" ++ source ++ "}"
300+ return $ text $ "\\includegraphics{" ++ source ++ "}"
276301inlineToLaTeX (Note contents) = do
277302 st <- get
278303 put (st {stInNote = True})
279304 contents' <- blockListToLaTeX contents
280305 modify (\st -> st {stInNote = False})
281− return $ "\\footnote{" ++ stripTrailingNewlines contents' ++ "\n}"
306+ return $ text "\\footnote{" $$
307+ (nest 11 $ text (stripTrailingNewlines $ render contents') <> text "\n}")
282308 -- note: the \n before } is important; removing it causes problems
283309 -- if a Verbatim environment occurs at the end of the footnote.