LaTeX writer: extract table handling into separate module.
This commit is contained in:
parent
fcd0658189
commit
ccd235e31f
6 changed files with 359 additions and 237 deletions
|
@ -630,6 +630,10 @@ library
|
||||||
Text.Pandoc.Writers.Docx.StyleMap,
|
Text.Pandoc.Writers.Docx.StyleMap,
|
||||||
Text.Pandoc.Writers.JATS.Table,
|
Text.Pandoc.Writers.JATS.Table,
|
||||||
Text.Pandoc.Writers.JATS.Types,
|
Text.Pandoc.Writers.JATS.Types,
|
||||||
|
Text.Pandoc.Writers.LaTeX.Caption,
|
||||||
|
Text.Pandoc.Writers.LaTeX.Notes,
|
||||||
|
Text.Pandoc.Writers.LaTeX.Table,
|
||||||
|
Text.Pandoc.Writers.LaTeX.Types,
|
||||||
Text.Pandoc.Writers.Roff,
|
Text.Pandoc.Writers.Roff,
|
||||||
Text.Pandoc.Writers.Powerpoint.Presentation,
|
Text.Pandoc.Writers.Powerpoint.Presentation,
|
||||||
Text.Pandoc.Writers.Powerpoint.Output,
|
Text.Pandoc.Writers.Powerpoint.Output,
|
||||||
|
|
|
@ -20,7 +20,6 @@ module Text.Pandoc.Writers.LaTeX (
|
||||||
) where
|
) where
|
||||||
import Control.Applicative ((<|>))
|
import Control.Applicative ((<|>))
|
||||||
import Control.Monad.State.Strict
|
import Control.Monad.State.Strict
|
||||||
import Data.Monoid (Any(..))
|
|
||||||
import Data.Char (isAlphaNum, isAscii, isDigit, isLetter, isSpace,
|
import Data.Char (isAlphaNum, isAscii, isDigit, isLetter, isSpace,
|
||||||
isPunctuation, ord)
|
isPunctuation, ord)
|
||||||
import Data.List (foldl', intersperse, nubBy, (\\), uncons)
|
import Data.List (foldl', intersperse, nubBy, (\\), uncons)
|
||||||
|
@ -42,69 +41,14 @@ import Text.Pandoc.Options
|
||||||
import Text.DocLayout
|
import Text.DocLayout
|
||||||
import Text.Pandoc.Shared
|
import Text.Pandoc.Shared
|
||||||
import Text.Pandoc.Slides
|
import Text.Pandoc.Slides
|
||||||
import Text.Pandoc.Walk
|
import Text.Pandoc.Walk (query, walk, walkM)
|
||||||
|
import Text.Pandoc.Writers.LaTeX.Caption (getCaption)
|
||||||
|
import Text.Pandoc.Writers.LaTeX.Table (tableToLaTeX)
|
||||||
|
import Text.Pandoc.Writers.LaTeX.Types (LW, WriterState (..), startingState)
|
||||||
import Text.Pandoc.Writers.Shared
|
import Text.Pandoc.Writers.Shared
|
||||||
import Text.Printf (printf)
|
import Text.Printf (printf)
|
||||||
import qualified Data.Text.Normalize as Normalize
|
import qualified Data.Text.Normalize as Normalize
|
||||||
|
|
||||||
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 }
|
|
||||||
|
|
||||||
-- | Convert Pandoc to LaTeX.
|
-- | Convert Pandoc to LaTeX.
|
||||||
writeLaTeX :: PandocMonad m => WriterOptions -> Pandoc -> m Text
|
writeLaTeX :: PandocMonad m => WriterOptions -> Pandoc -> m Text
|
||||||
writeLaTeX options document =
|
writeLaTeX options document =
|
||||||
|
@ -117,8 +61,6 @@ writeBeamer options document =
|
||||||
evalStateT (pandocToLaTeX options document) $
|
evalStateT (pandocToLaTeX options document) $
|
||||||
(startingState options){ stBeamer = True }
|
(startingState options){ stBeamer = True }
|
||||||
|
|
||||||
type LW m = StateT WriterState m
|
|
||||||
|
|
||||||
pandocToLaTeX :: PandocMonad m
|
pandocToLaTeX :: PandocMonad m
|
||||||
=> WriterOptions -> Pandoc -> LW m Text
|
=> WriterOptions -> Pandoc -> LW m Text
|
||||||
pandocToLaTeX options (Pandoc meta blocks) = do
|
pandocToLaTeX options (Pandoc meta blocks) = do
|
||||||
|
@ -573,7 +515,7 @@ blockToLaTeX (Plain lst) =
|
||||||
blockToLaTeX (Para [Image attr@(ident, _, _) txt (src,tgt)])
|
blockToLaTeX (Para [Image attr@(ident, _, _) txt (src,tgt)])
|
||||||
| Just tit <- T.stripPrefix "fig:" tgt
|
| Just tit <- T.stripPrefix "fig:" tgt
|
||||||
= do
|
= do
|
||||||
(capt, captForLof, footnotes) <- getCaption True txt
|
(capt, captForLof, footnotes) <- getCaption inlineListToLaTeX True txt
|
||||||
lab <- labelFor ident
|
lab <- labelFor ident
|
||||||
let caption = "\\caption" <> captForLof <> braces capt <> lab
|
let caption = "\\caption" <> captForLof <> braces capt <> lab
|
||||||
img <- inlineToLaTeX (Image attr txt (src,tit))
|
img <- inlineToLaTeX (Image attr txt (src,tit))
|
||||||
|
@ -774,181 +716,14 @@ blockToLaTeX (Header level (id',classes,_) lst) = do
|
||||||
hdr <- sectionHeader classes id' level lst
|
hdr <- sectionHeader classes id' level lst
|
||||||
modify $ \s -> s{stInHeading = False}
|
modify $ \s -> s{stInHeading = False}
|
||||||
return hdr
|
return hdr
|
||||||
blockToLaTeX (Table _ blkCapt specs thead tbody tfoot) = do
|
blockToLaTeX (Table _ blkCapt specs thead tbodies tfoot) =
|
||||||
let (caption, aligns, widths, heads, rows) = toLegacyTable blkCapt specs thead tbody tfoot
|
tableToLaTeX inlineListToLaTeX blockListToLaTeX
|
||||||
-- simple tables have to have simple cells:
|
blkCapt specs thead tbodies tfoot
|
||||||
let isSimple [Plain _] = True
|
|
||||||
isSimple [Para _] = True
|
|
||||||
isSimple [] = True
|
|
||||||
isSimple _ = 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 False caption
|
|
||||||
let toHeaders hs = do contents <- tableRowToLaTeX 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 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
|
|
||||||
|
|
||||||
getCaption :: PandocMonad m
|
|
||||||
=> Bool -> [Inline] -> LW m (Doc Text, Doc Text, Doc Text)
|
|
||||||
getCaption 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)
|
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
blockListToLaTeX :: PandocMonad m => [Block] -> LW m (Doc Text)
|
blockListToLaTeX :: PandocMonad m => [Block] -> LW m (Doc Text)
|
||||||
blockListToLaTeX lst =
|
blockListToLaTeX lst =
|
||||||
vsep `fmap` mapM (\b -> setEmptyLine True >> blockToLaTeX b) lst
|
vsep `fmap` mapM (\b -> setEmptyLine True >> blockToLaTeX b) lst
|
||||||
|
|
||||||
tableRowToLaTeX :: PandocMonad m
|
|
||||||
=> Bool
|
|
||||||
-> [Alignment]
|
|
||||||
-> [[Block]]
|
|
||||||
-> LW m (Doc Text)
|
|
||||||
tableRowToLaTeX header aligns cols = do
|
|
||||||
cells <- mapM (tableCellToLaTeX 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
|
|
||||||
=> Bool -> (Alignment, [Block])
|
|
||||||
-> LW m (Doc Text)
|
|
||||||
tableCellToLaTeX 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 Para{} = True
|
|
||||||
isPlainOrPara Plain{} = True
|
|
||||||
isPlainOrPara _ = 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
|
|
||||||
|
|
||||||
|
|
||||||
notesToLaTeX :: [Doc Text] -> Doc Text
|
|
||||||
notesToLaTeX [] = empty
|
|
||||||
notesToLaTeX 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)
|
|
||||||
|
|
||||||
listItemToLaTeX :: PandocMonad m => [Block] -> LW m (Doc Text)
|
listItemToLaTeX :: PandocMonad m => [Block] -> LW m (Doc Text)
|
||||||
listItemToLaTeX lst
|
listItemToLaTeX lst
|
||||||
-- we need to put some text before a header if it's the first
|
-- we need to put some text before a header if it's the first
|
||||||
|
|
48
src/Text/Pandoc/Writers/LaTeX/Caption.hs
Normal file
48
src/Text/Pandoc/Writers/LaTeX/Caption.hs
Normal file
|
@ -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)
|
34
src/Text/Pandoc/Writers/LaTeX/Notes.hs
Normal file
34
src/Text/Pandoc/Writers/LaTeX/Notes.hs
Normal file
|
@ -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)
|
181
src/Text/Pandoc/Writers/LaTeX/Table.hs
Normal file
181
src/Text/Pandoc/Writers/LaTeX/Table.hs
Normal file
|
@ -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
|
80
src/Text/Pandoc/Writers/LaTeX/Types.hs
Normal file
80
src/Text/Pandoc/Writers/LaTeX/Types.hs
Normal file
|
@ -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
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue