@@ -16,6 +16,7 @@ module Text.Pandoc.Writers.LaTeX.Table
1616 ) where
1717import Control.Monad.State.Strict
1818import Data.List (intersperse)
19+import Data.List.NonEmpty (NonEmpty ((:|)))
1920import Data.Text (Text)
2021import qualified Data.Text as T
2122import Text.Pandoc.Class.PandocMonad (PandocMonad)
@@ -23,102 +24,196 @@ import Text.Pandoc.Definition
2324import Text.DocLayout
2425 ( Doc, braces, cr, empty, hcat, hsep, isEmpty, literal, nest
2526 , text, vcat, ($$) )
26−import Text.Pandoc.Shared (splitBy)
27+import Text.Pandoc.Shared (blocksToInlines, splitBy, tshow)
2728import Text.Pandoc.Walk (walk)
28−import Text.Pandoc.Writers.Shared (toLegacyTable)
2929import Text.Pandoc.Writers.LaTeX.Caption (getCaption)
3030import Text.Pandoc.Writers.LaTeX.Notes (notesToLaTeX)
3131import Text.Pandoc.Writers.LaTeX.Types
32− ( LW, WriterState (stBeamer, stExternalNotes, stInMinipage, stNotes, stTable) )
32+ ( LW, WriterState (stBeamer, stExternalNotes, stInMinipage, stMultiRow
33+ , stNotes, stTable) )
3334import Text.Printf (printf)
35+import qualified Text.Pandoc.Builder as B
36+import qualified Text.Pandoc.Writers.AnnotatedTable as Ann
3437
3538tableToLaTeX :: PandocMonad m
3639 => ([Inline] -> LW m (Doc Text))
3740 -> ([Block] -> LW m (Doc Text))
38− -> Caption -> [ColSpec] -> TableHead -> [TableBody] -> TableFoot
41+ -> Ann.Table
3942 -> LW m (Doc Text)
40−tableToLaTeX inlnsToLaTeX blksToLaTeX blkCapt specs thead tbody tfoot = do
41− let (caption, aligns, widths, heads, rows) =
42− toLegacyTable blkCapt specs thead tbody tfoot
43− -- simple tables have to have simple cells:
44− let isSimple = \case
45− [Plain _] -> True
46− [Para _] -> True
47− [] -> True
48− _ -> False
49− let widths' = if all (== 0) widths && not (all (all isSimple) rows)
50− then replicate (length aligns)
51− (1 / fromIntegral (length aligns))
52− else widths
53− (captionText, captForLof, captNotes) <- getCaption inlnsToLaTeX False caption
54− let toHeaders hs = do contents <- tableRowToLaTeX blksToLaTeX True aligns hs
55− return ("\\toprule" $$ contents $$ "\\midrule")
43+tableToLaTeX inlnsToLaTeX blksToLaTeX tbl = do
44+ let (Ann.Table _attr caption _specs thead tbodies tfoot) = tbl
45+ CaptionDocs capt captNotes <- captionToLaTeX inlnsToLaTeX caption
5646 let removeNote (Note _) = Span ("", [], []) []
5747 removeNote x = x
58− firsthead <- if isEmpty captionText || all null heads
59− then return empty
60− else ($$ text "\\endfirsthead") <$> toHeaders heads
61− head' <- if all null heads
62− then return "\\toprule"
63− -- avoid duplicate notes in head and firsthead:
64− else toHeaders (if isEmpty firsthead
65− then heads
66− else walk removeNote heads)
67− let capt = if isEmpty captionText
68− then empty
69− else "\\caption" <> captForLof <> braces captionText
70− <> "\\tabularnewline"
71− rows' <- mapM (tableRowToLaTeX blksToLaTeX False aligns) rows
72− let colDescriptors =
73− (if all (== 0) widths'
74− then hcat . map literal
75− else (\xs -> cr <> nest 2 (vcat $ map literal xs))) $
76− zipWith (toColDescriptor (length widths')) aligns widths'
48+ firsthead <- if isEmpty capt || isEmptyHead thead
49+ then return empty
50+ else ($$ text "\\endfirsthead") <$>
51+ headToLaTeX blksToLaTeX thead
52+ head' <- if isEmptyHead thead
53+ then return "\\toprule"
54+ -- avoid duplicate notes in head and firsthead:
55+ else headToLaTeX blksToLaTeX
56+ (if isEmpty firsthead
57+ then thead
58+ else walk removeNote thead)
59+ rows' <- mapM (rowToLaTeX blksToLaTeX BodyCell) $
60+ mconcat (map bodyRows tbodies) <> footRows tfoot
7761 modify $ \s -> s{ stTable = True }
7862 notes <- notesToLaTeX <$> gets stNotes
79− return $ "\\begin{longtable}[]" <>
80− braces ("@{}" <> colDescriptors <> "@{}")
81− -- the @{} removes extra space at beginning and end
82− $$ capt
83− $$ firsthead
84− $$ head'
85− $$ "\\endhead"
86− $$ vcat rows'
87− $$ "\\bottomrule"
88− $$ "\\end{longtable}"
89− $$ captNotes
90− $$ notes
91−
92−toColDescriptor :: Int -> Alignment -> Double -> Text
93−toColDescriptor _numcols align 0 =
94− case align of
95− AlignLeft -> "l"
96− AlignRight -> "r"
97− AlignCenter -> "c"
98− AlignDefault -> "l"
99−toColDescriptor numcols align width =
100− T.pack $ printf
101− ">{%s\\arraybackslash}p{(\\columnwidth - %d\\tabcolsep) * \\real{%0.2f}}"
102− align'
103− ((numcols - 1) * 2)
104− width
105− where
106− align' :: String
107− align' = case align of
108− AlignLeft -> "\\raggedright"
109− AlignRight -> "\\raggedleft"
110− AlignCenter -> "\\centering"
111− AlignDefault -> "\\raggedright"
112−
113−tableRowToLaTeX :: PandocMonad m
114− => ([Block] -> LW m (Doc Text))
115− -> Bool
116− -> [Alignment]
117− -> [[Block]]
118− -> LW m (Doc Text)
119−tableRowToLaTeX blockListToLaTeX header aligns cols = do
120− cells <- mapM (tableCellToLaTeX blockListToLaTeX header) $ zip aligns cols
121− return $ hsep (intersperse "&" cells) <> " \\\\ \\addlinespace"
63+ return
64+ $ "\\begin{longtable}[]" <>
65+ braces ("@{}" <> colDescriptors tbl <> "@{}")
66+ -- the @{} removes extra space at beginning and end
67+ $$ capt
68+ $$ firsthead
69+ $$ head'
70+ $$ "\\endhead"
71+ $$ vcat rows'
72+ $$ "\\bottomrule"
73+ $$ "\\end{longtable}"
74+ $$ captNotes
75+ $$ notes
76+
77+-- | Creates column descriptors for the table.
78+colDescriptors :: Ann.Table -> Doc Text
79+colDescriptors (Ann.Table _attr _caption specs thead tbodies tfoot) =
80+ let (aligns, widths) = unzip specs
81+
82+ defaultWidthsOnly = all (== ColWidthDefault) widths
83+ isSimpleTable = all (all isSimpleCell) $ mconcat
84+ [ headRows thead
85+ , concatMap bodyRows tbodies
86+ , footRows tfoot
87+ ]
88+
89+ relativeWidths = if defaultWidthsOnly
90+ then replicate (length specs)
91+ (1 / fromIntegral (length specs))
92+ else map toRelWidth widths
93+ in if defaultWidthsOnly && isSimpleTable
94+ then hcat $ map (literal . colAlign) aligns
95+ else (cr <>) . nest 2 . vcat . map literal $
96+ zipWith (toColDescriptor (length specs))
97+ aligns
98+ relativeWidths
99+ where
100+ toColDescriptor :: Int -> Alignment -> Double -> Text
101+ toColDescriptor numcols align width =
102+ T.pack $ printf
103+ ">{%s\\arraybackslash}p{(\\columnwidth - %d\\tabcolsep) * \\real{%0.2f}}"
104+ (T.unpack (alignCommand align))
105+ ((numcols - 1) * 2)
106+ width
107+
108+ isSimpleCell (Ann.Cell _ _ (Cell _attr _align _rowspan _colspan blocks)) =
109+ case blocks of
110+ [Para _] -> True
111+ [Plain _] -> True
112+ [] -> True
113+ _ -> False
114+
115+ toRelWidth ColWidthDefault = 0
116+ toRelWidth (ColWidth w) = w
117+
118+alignCommand :: Alignment -> Text
119+alignCommand = \case
120+ AlignLeft -> "\\raggedright"
121+ AlignRight -> "\\raggedleft"
122+ AlignCenter -> "\\centering"
123+ AlignDefault -> "\\raggedright"
124+
125+colAlign :: Alignment -> Text
126+colAlign = \case
127+ AlignLeft -> "l"
128+ AlignRight -> "r"
129+ AlignCenter -> "c"
130+ AlignDefault -> "l"
131+
132+data CaptionDocs =
133+ CaptionDocs
134+ { captionCommand :: Doc Text
135+ , captionNotes :: Doc Text
136+ }
137+
138+captionToLaTeX :: PandocMonad m
139+ => ([Inline] -> LW m (Doc Text))
140+ -> Caption
141+ -> LW m CaptionDocs
142+captionToLaTeX inlnsToLaTeX (Caption _maybeShort longCaption) = do
143+ let caption = blocksToInlines longCaption
144+ (captionText, captForLof, captNotes) <- getCaption inlnsToLaTeX False caption
145+ return $ CaptionDocs
146+ { captionNotes = captNotes
147+ , captionCommand = if isEmpty captionText
148+ then empty
149+ else "\\caption" <> captForLof <>
150+ braces captionText <> "\\tabularnewline"
151+ }
152+
153+type BlocksWriter m = [Block] -> LW m (Doc Text)
154+
155+headToLaTeX :: PandocMonad m
156+ => BlocksWriter m
157+ -> Ann.TableHead
158+ -> LW m (Doc Text)
159+headToLaTeX blocksWriter (Ann.TableHead _attr headerRows) = do
160+ rowsContents <- mapM (rowToLaTeX blocksWriter HeaderCell . headerRowCells)
161+ headerRows
162+ return ("\\toprule" $$ vcat rowsContents $$ "\\midrule")
163+
164+-- | Converts a row of table cells into a LaTeX row.
165+rowToLaTeX :: PandocMonad m
166+ => BlocksWriter m
167+ -> CellType
168+ -> [Ann.Cell]
169+ -> LW m (Doc Text)
170+rowToLaTeX blocksWriter celltype row = do
171+ cellsDocs <- mapM (cellToLaTeX blocksWriter celltype) (fillRow row)
172+ return $ hsep (intersperse "&" cellsDocs) <> " \\\\ \\addlinespace"
173+
174+-- | Pads row with empty cells to adjust for rowspans above this row.
175+fillRow :: [Ann.Cell] -> [Ann.Cell]
176+fillRow = go 0
177+ where
178+ go _ [] = []
179+ go n (acell@(Ann.Cell _spec (Ann.ColNumber colnum) cell):cells) =
180+ let (Cell _ _ _ (ColSpan colspan) _) = cell
181+ in map mkEmptyCell [n .. colnum - 1] ++
182+ acell : go (colnum + colspan) cells
183+
184+ mkEmptyCell :: Int -> Ann.Cell
185+ mkEmptyCell colnum =
186+ Ann.Cell ((AlignDefault, ColWidthDefault):|[])
187+ (Ann.ColNumber colnum)
188+ B.emptyCell
189+
190+isEmptyHead :: Ann.TableHead -> Bool
191+isEmptyHead (Ann.TableHead _attr []) = True
192+isEmptyHead (Ann.TableHead _attr rows) = all (null . headerRowCells) rows
193+
194+-- | Gets all cells in a header row.
195+headerRowCells :: Ann.HeaderRow -> [Ann.Cell]
196+headerRowCells (Ann.HeaderRow _attr _rownum cells) = cells
197+
198+-- | Gets all cells in a body row.
199+bodyRowCells :: Ann.BodyRow -> [Ann.Cell]
200+bodyRowCells (Ann.BodyRow _attr _rownum rowhead cells) = rowhead <> cells
201+
202+-- | Gets a list of rows of the table body, where a row is a simple
203+-- list of cells.
204+bodyRows :: Ann.TableBody -> [[Ann.Cell]]
205+bodyRows (Ann.TableBody _attr _rowheads headerRows rows) =
206+ map headerRowCells headerRows <> map bodyRowCells rows
207+
208+-- | Gets a list of rows of the table head, where a row is a simple
209+-- list of cells.
210+headRows :: Ann.TableHead -> [[Ann.Cell]]
211+headRows (Ann.TableHead _attr rows) = map headerRowCells rows
212+
213+-- | Gets a list of rows from the foot, where a row is a simple list
214+-- of cells.
215+footRows :: Ann.TableFoot -> [[Ann.Cell]]
216+footRows (Ann.TableFoot _attr rows) = map headerRowCells rows
122217
123218-- For simple latex tables (without minipages or parboxes),
124219-- we need to go to some lengths to get line breaks working:
@@ -144,11 +239,14 @@ displayMathToInline :: Inline -> Inline
144239displayMathToInline (Math DisplayMath x) = Math InlineMath x
145240displayMathToInline x = x
146241
147−tableCellToLaTeX :: PandocMonad m
148− => ([Block] -> LW m (Doc Text))
149− -> Bool -> (Alignment, [Block])
150− -> LW m (Doc Text)
151−tableCellToLaTeX blockListToLaTeX header (align, blocks) = do
242+cellToLaTeX :: PandocMonad m
243+ => BlocksWriter m
244+ -> CellType
245+ -> Ann.Cell
246+ -> LW m (Doc Text)
247+cellToLaTeX blockListToLaTeX celltype annotatedCell = do
248+ let (Ann.Cell _specs _colnum cell) = annotatedCell
249+ let (Cell _attr align rowspan colspan blocks) = cell
152250 beamer <- gets stBeamer
153251 externalNotes <- gets stExternalNotes
154252 inMinipage <- gets stInMinipage
@@ -167,15 +265,30 @@ tableCellToLaTeX blockListToLaTeX header (align, blocks) = do
167265 modify $ \st -> st{ stInMinipage = True }
168266 cellContents <- blockListToLaTeX blocks
169267 modify $ \st -> st{ stInMinipage = inMinipage }
170− let valign = text $ if header then "[b]" else "[t]"
171− let halign = case align of
172− AlignLeft -> "\\raggedright"
173− AlignRight -> "\\raggedleft"
174− AlignCenter -> "\\centering"
175− AlignDefault -> "\\raggedright"
268+ let valign = text $ case celltype of
269+ HeaderCell -> "[b]"
270+ BodyCell -> "[t]"
271+ let halign = literal $ alignCommand align
176272 return $ "\\begin{minipage}" <> valign <>
177273 braces "\\linewidth" <> halign <> cr <>
178274 cellContents <> cr <>
179275 "\\end{minipage}"
180276 modify $ \st -> st{ stExternalNotes = externalNotes }
181− return result
277+ when (rowspan /= RowSpan 1) $
278+ modify (\st -> st{ stMultiRow = True })
279+ let inMultiColumn x = case colspan of
280+ (ColSpan 1) -> x
281+ (ColSpan n) -> "\\multicolumn"
282+ <> braces (literal (tshow n))
283+ <> braces (literal $ colAlign align)
284+ <> braces x
285+ let inMultiRow x = case rowspan of
286+ (RowSpan 1) -> x
287+ (RowSpan n) -> let nrows = literal (tshow n)
288+ in "\\multirow" <> braces nrows
289+ <> braces "*" <> braces x
290+ return . inMultiColumn . inMultiRow $ result
291+
292+data CellType
293+ = HeaderCell
294+ | BodyCell