aboutsummaryrefslogtreecommitdiff
path: root/src/Text/Pandoc/Writers/LaTeX
diff options
context:
space:
mode:
authorAlbert Krewinkel <albert@zeitkraut.de>2020-12-12 16:45:50 +0100
committerAlbert Krewinkel <albert@zeitkraut.de>2020-12-12 16:48:28 +0100
commitccd235e31ff00c4741ba52552ba58669f700bbdc (patch)
tree9c0bb9e6ac7a2c33f4a72cce364ac83bf218156e /src/Text/Pandoc/Writers/LaTeX
parentfcd065818901e57f01aca4c919f6102f9a047ba0 (diff)
downloadpandoc-ccd235e31ff00c4741ba52552ba58669f700bbdc.tar.gz
LaTeX writer: extract table handling into separate module.
Diffstat (limited to 'src/Text/Pandoc/Writers/LaTeX')
-rw-r--r--src/Text/Pandoc/Writers/LaTeX/Caption.hs48
-rw-r--r--src/Text/Pandoc/Writers/LaTeX/Notes.hs34
-rw-r--r--src/Text/Pandoc/Writers/LaTeX/Table.hs181
-rw-r--r--src/Text/Pandoc/Writers/LaTeX/Types.hs80
4 files changed, 343 insertions, 0 deletions
diff --git a/src/Text/Pandoc/Writers/LaTeX/Caption.hs b/src/Text/Pandoc/Writers/LaTeX/Caption.hs
new file mode 100644
index 000000000..61ca41fb1
--- /dev/null
+++ b/src/Text/Pandoc/Writers/LaTeX/Caption.hs
@@ -0,0 +1,48 @@
+{-# LANGUAGE OverloadedStrings #-}
+{- |
+ Module : Text.Pandoc.Writers.LaTeX.Caption
+ Copyright : Copyright (C) 2006-2020 John MacFarlane
+ License : GNU GPL, version 2 or above
+
+ Maintainer : John MacFarlane <jgm@berkeley.edu>
+ Stability : alpha
+ Portability : portable
+
+Write figure or table captions as LaTeX.
+-}
+module Text.Pandoc.Writers.LaTeX.Caption
+ ( getCaption
+ ) where
+
+import Control.Monad.State.Strict
+import Data.Monoid (Any(..))
+import Data.Text (Text)
+import Text.Pandoc.Class.PandocMonad (PandocMonad)
+import Text.Pandoc.Definition
+import Text.DocLayout (Doc, brackets, empty)
+import Text.Pandoc.Shared
+import Text.Pandoc.Walk
+import Text.Pandoc.Writers.LaTeX.Notes (notesToLaTeX)
+import Text.Pandoc.Writers.LaTeX.Types
+ ( LW, WriterState (stExternalNotes, stNotes) )
+
+getCaption :: PandocMonad m
+ => ([Inline] -> LW m (Doc Text))
+ -> Bool -> [Inline]
+ -> LW m (Doc Text, Doc Text, Doc Text)
+getCaption inlineListToLaTeX externalNotes txt = do
+ oldExternalNotes <- gets stExternalNotes
+ modify $ \st -> st{ stExternalNotes = externalNotes, stNotes = [] }
+ capt <- inlineListToLaTeX txt
+ footnotes <- if externalNotes
+ then notesToLaTeX <$> gets stNotes
+ else return empty
+ modify $ \st -> st{ stExternalNotes = oldExternalNotes, stNotes = [] }
+ -- We can't have footnotes in the list of figures/tables, so remove them:
+ let getNote (Note _) = Any True
+ getNote _ = Any False
+ let hasNotes = getAny . query getNote
+ captForLof <- if hasNotes txt
+ then brackets <$> inlineListToLaTeX (walk deNote txt)
+ else return empty
+ return (capt, captForLof, footnotes)
diff --git a/src/Text/Pandoc/Writers/LaTeX/Notes.hs b/src/Text/Pandoc/Writers/LaTeX/Notes.hs
new file mode 100644
index 000000000..216a7bfc3
--- /dev/null
+++ b/src/Text/Pandoc/Writers/LaTeX/Notes.hs
@@ -0,0 +1,34 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{- |
+ Module : Text.Pandoc.Writers.LaTeX.Notes
+ Copyright : Copyright (C) 2006-2020 John MacFarlane
+ License : GNU GPL, version 2 or above
+
+ Maintainer : John MacFarlane <jgm@berkeley.edu>
+ Stability : alpha
+ Portability : portable
+
+Output tables as LaTeX.
+-}
+module Text.Pandoc.Writers.LaTeX.Notes
+ ( notesToLaTeX
+ ) where
+
+import Data.List (intersperse)
+import Text.DocLayout ( Doc, braces, empty, text, vcat, ($$))
+import Data.Text (Text)
+
+notesToLaTeX :: [Doc Text] -> Doc Text
+notesToLaTeX = \case
+ [] -> empty
+ ns -> (case length ns of
+ n | n > 1 -> "\\addtocounter" <>
+ braces "footnote" <>
+ braces (text $ show $ 1 - n)
+ | otherwise -> empty)
+ $$
+ vcat (intersperse
+ ("\\addtocounter" <> braces "footnote" <> braces "1")
+ $ map (\x -> "\\footnotetext" <> braces x)
+ $ reverse ns)
diff --git a/src/Text/Pandoc/Writers/LaTeX/Table.hs b/src/Text/Pandoc/Writers/LaTeX/Table.hs
new file mode 100644
index 000000000..5299efa37
--- /dev/null
+++ b/src/Text/Pandoc/Writers/LaTeX/Table.hs
@@ -0,0 +1,181 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{- |
+ Module : Text.Pandoc.Writers.LaTeX.Table
+ Copyright : Copyright (C) 2006-2020 John MacFarlane
+ License : GNU GPL, version 2 or above
+
+ Maintainer : John MacFarlane <jgm@berkeley.edu>
+ Stability : alpha
+ Portability : portable
+
+Output LaTeX formatted tables.
+-}
+module Text.Pandoc.Writers.LaTeX.Table
+ ( tableToLaTeX
+ ) where
+import Control.Monad.State.Strict
+import Data.List (intersperse)
+import Data.Text (Text)
+import qualified Data.Text as T
+import Text.Pandoc.Class.PandocMonad (PandocMonad)
+import Text.Pandoc.Definition
+import Text.DocLayout
+ ( Doc, braces, cr, empty, hcat, hsep, isEmpty, literal, nest
+ , text, vcat, ($$) )
+import Text.Pandoc.Shared (splitBy)
+import Text.Pandoc.Walk (walk)
+import Text.Pandoc.Writers.Shared (toLegacyTable)
+import Text.Pandoc.Writers.LaTeX.Caption (getCaption)
+import Text.Pandoc.Writers.LaTeX.Notes (notesToLaTeX)
+import Text.Pandoc.Writers.LaTeX.Types
+ ( LW, WriterState (stBeamer, stExternalNotes, stInMinipage, stNotes, stTable) )
+import Text.Printf (printf)
+
+tableToLaTeX :: PandocMonad m
+ => ([Inline] -> LW m (Doc Text))
+ -> ([Block] -> LW m (Doc Text))
+ -> Caption -> [ColSpec] -> TableHead -> [TableBody] -> TableFoot
+ -> LW m (Doc Text)
+tableToLaTeX inlnsToLaTeX blksToLaTeX blkCapt specs thead tbody tfoot = do
+ let (caption, aligns, widths, heads, rows) =
+ toLegacyTable blkCapt specs thead tbody tfoot
+ -- simple tables have to have simple cells:
+ let isSimple = \case
+ [Plain _] -> True
+ [Para _] -> True
+ [] -> True
+ _ -> False
+ let widths' = if all (== 0) widths && not (all (all isSimple) rows)
+ then replicate (length aligns)
+ (1 / fromIntegral (length aligns))
+ else widths
+ (captionText, captForLof, captNotes) <- getCaption inlnsToLaTeX False caption
+ let toHeaders hs = do contents <- tableRowToLaTeX blksToLaTeX True aligns hs
+ return ("\\toprule" $$ contents $$ "\\midrule")
+ let removeNote (Note _) = Span ("", [], []) []
+ removeNote x = x
+ firsthead <- if isEmpty captionText || all null heads
+ then return empty
+ else ($$ text "\\endfirsthead") <$> toHeaders heads
+ head' <- if all null heads
+ then return "\\toprule"
+ -- avoid duplicate notes in head and firsthead:
+ else toHeaders (if isEmpty firsthead
+ then heads
+ else walk removeNote heads)
+ let capt = if isEmpty captionText
+ then empty
+ else "\\caption" <> captForLof <> braces captionText
+ <> "\\tabularnewline"
+ rows' <- mapM (tableRowToLaTeX blksToLaTeX False aligns) rows
+ let colDescriptors =
+ (if all (== 0) widths'
+ then hcat . map literal
+ else (\xs -> cr <> nest 2 (vcat $ map literal xs))) $
+ zipWith (toColDescriptor (length widths')) aligns widths'
+ modify $ \s -> s{ stTable = True }
+ notes <- notesToLaTeX <$> gets stNotes
+ return $ "\\begin{longtable}[]" <>
+ braces ("@{}" <> colDescriptors <> "@{}")
+ -- the @{} removes extra space at beginning and end
+ $$ capt
+ $$ firsthead
+ $$ head'
+ $$ "\\endhead"
+ $$ vcat rows'
+ $$ "\\bottomrule"
+ $$ "\\end{longtable}"
+ $$ captNotes
+ $$ notes
+
+toColDescriptor :: Int -> Alignment -> Double -> Text
+toColDescriptor _numcols align 0 =
+ case align of
+ AlignLeft -> "l"
+ AlignRight -> "r"
+ AlignCenter -> "c"
+ AlignDefault -> "l"
+toColDescriptor numcols align width =
+ T.pack $ printf
+ ">{%s\\arraybackslash}p{(\\columnwidth - %d\\tabcolsep) * \\real{%0.2f}}"
+ align'
+ ((numcols - 1) * 2)
+ width
+ where
+ align' :: String
+ align' = case align of
+ AlignLeft -> "\\raggedright"
+ AlignRight -> "\\raggedleft"
+ AlignCenter -> "\\centering"
+ AlignDefault -> "\\raggedright"
+
+tableRowToLaTeX :: PandocMonad m
+ => ([Block] -> LW m (Doc Text))
+ -> Bool
+ -> [Alignment]
+ -> [[Block]]
+ -> LW m (Doc Text)
+tableRowToLaTeX blockListToLaTeX header aligns cols = do
+ cells <- mapM (tableCellToLaTeX blockListToLaTeX header) $ zip aligns cols
+ return $ hsep (intersperse "&" cells) <> " \\\\ \\addlinespace"
+
+-- For simple latex tables (without minipages or parboxes),
+-- we need to go to some lengths to get line breaks working:
+-- as LineBreak bs = \vtop{\hbox{\strut as}\hbox{\strut bs}}.
+fixLineBreaks :: Block -> Block
+fixLineBreaks (Para ils) = Para $ fixLineBreaks' ils
+fixLineBreaks (Plain ils) = Plain $ fixLineBreaks' ils
+fixLineBreaks x = x
+
+fixLineBreaks' :: [Inline] -> [Inline]
+fixLineBreaks' ils = case splitBy (== LineBreak) ils of
+ [] -> []
+ [xs] -> xs
+ chunks -> RawInline "tex" "\\vtop{" :
+ concatMap tohbox chunks <>
+ [RawInline "tex" "}"]
+ where tohbox ys = RawInline "tex" "\\hbox{\\strut " : ys <>
+ [RawInline "tex" "}"]
+
+-- We also change display math to inline math, since display
+-- math breaks in simple tables.
+displayMathToInline :: Inline -> Inline
+displayMathToInline (Math DisplayMath x) = Math InlineMath x
+displayMathToInline x = x
+
+tableCellToLaTeX :: PandocMonad m
+ => ([Block] -> LW m (Doc Text))
+ -> Bool -> (Alignment, [Block])
+ -> LW m (Doc Text)
+tableCellToLaTeX blockListToLaTeX header (align, blocks) = do
+ beamer <- gets stBeamer
+ externalNotes <- gets stExternalNotes
+ inMinipage <- gets stInMinipage
+ -- See #5367 -- footnotehyper/footnote don't work in beamer,
+ -- so we need to produce the notes outside the table...
+ modify $ \st -> st{ stExternalNotes = beamer }
+ let isPlainOrPara = \case
+ Para{} -> True
+ Plain{} -> True
+ _ -> False
+ result <-
+ if all isPlainOrPara blocks
+ then
+ blockListToLaTeX $ walk fixLineBreaks $ walk displayMathToInline blocks
+ else do
+ modify $ \st -> st{ stInMinipage = True }
+ cellContents <- blockListToLaTeX blocks
+ modify $ \st -> st{ stInMinipage = inMinipage }
+ let valign = text $ if header then "[b]" else "[t]"
+ let halign = case align of
+ AlignLeft -> "\\raggedright"
+ AlignRight -> "\\raggedleft"
+ AlignCenter -> "\\centering"
+ AlignDefault -> "\\raggedright"
+ return $ "\\begin{minipage}" <> valign <>
+ braces "\\linewidth" <> halign <> cr <>
+ cellContents <> cr <>
+ "\\end{minipage}"
+ modify $ \st -> st{ stExternalNotes = externalNotes }
+ return result
diff --git a/src/Text/Pandoc/Writers/LaTeX/Types.hs b/src/Text/Pandoc/Writers/LaTeX/Types.hs
new file mode 100644
index 000000000..a76388729
--- /dev/null
+++ b/src/Text/Pandoc/Writers/LaTeX/Types.hs
@@ -0,0 +1,80 @@
+module Text.Pandoc.Writers.LaTeX.Types
+ ( LW
+ , WriterState (..)
+ , startingState
+ ) where
+
+import Control.Monad.State.Strict (StateT)
+import Data.Text (Text)
+import Text.DocLayout (Doc)
+import Text.Pandoc.Options
+ ( WriterOptions (writerIncremental, writerTopLevelDivision)
+ , TopLevelDivision (..)
+ )
+
+-- | LaTeX writer type. The type constructor @m@ will typically be an
+-- instance of PandocMonad.
+type LW m = StateT WriterState m
+
+data WriterState =
+ WriterState
+ { stInNote :: Bool -- ^ true if we're in a note
+ , stInQuote :: Bool -- ^ true if in a blockquote
+ , stExternalNotes :: Bool -- ^ true if in context where
+ -- we need to store footnotes
+ , stInMinipage :: Bool -- ^ true if in minipage
+ , stInHeading :: Bool -- ^ true if in a section heading
+ , stInItem :: Bool -- ^ true if in \item[..]
+ , stNotes :: [Doc Text] -- ^ notes in a minipage
+ , stOLLevel :: Int -- ^ level of ordered list nesting
+ , stOptions :: WriterOptions -- ^ writer options, so they don't have to
+ -- be parameter
+ , stVerbInNote :: Bool -- ^ true if document has verbatim text in note
+ , stTable :: Bool -- ^ true if document has a table
+ , stStrikeout :: Bool -- ^ true if document has strikeout
+ , stUrl :: Bool -- ^ true if document has visible URL link
+ , stGraphics :: Bool -- ^ true if document contains images
+ , stLHS :: Bool -- ^ true if document has literate haskell code
+ , stHasChapters :: Bool -- ^ true if document has chapters
+ , stCsquotes :: Bool -- ^ true if document uses csquotes
+ , stHighlighting :: Bool -- ^ true if document has highlighted code
+ , stIncremental :: Bool -- ^ true if beamer lists should be
+ -- displayed bit by bit
+ , stInternalLinks :: [Text] -- ^ list of internal link targets
+ , stBeamer :: Bool -- ^ produce beamer
+ , stEmptyLine :: Bool -- ^ true if no content on line
+ , stHasCslRefs :: Bool -- ^ has a Div with class refs
+ , stIsFirstInDefinition :: Bool -- ^ first block in a defn list
+ }
+
+startingState :: WriterOptions -> WriterState
+startingState options =
+ WriterState
+ { stInNote = False
+ , stInQuote = False
+ , stExternalNotes = False
+ , stInHeading = False
+ , stInMinipage = False
+ , stInItem = False
+ , stNotes = []
+ , stOLLevel = 1
+ , stOptions = options
+ , stVerbInNote = False
+ , stTable = False
+ , stStrikeout = False
+ , stUrl = False
+ , stGraphics = False
+ , stLHS = False
+ , stHasChapters = case writerTopLevelDivision options of
+ TopLevelPart -> True
+ TopLevelChapter -> True
+ _ -> False
+ , stCsquotes = False
+ , stHighlighting = False
+ , stIncremental = writerIncremental options
+ , stInternalLinks = []
+ , stBeamer = False
+ , stEmptyLine = True
+ , stHasCslRefs = False
+ , stIsFirstInDefinition = False
+ }