{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Text.Pandoc.Writers.Powerpoint.Presentation ( documentToPresentation
, Presentation(..)
, DocProps(..)
, Slide(..)
, Layout(..)
, SpeakerNotes(..)
, SlideId(..)
, Shape(..)
, Graphic(..)
, BulletType(..)
, Algnment(..)
, Paragraph(..)
, ParaElem(..)
, ParaProps(..)
, RunProps(..)
, TableProps(..)
, Strikethrough(..)
, Capitals(..)
, Pixels
, PicProps(..)
, URL
, TeXString(..)
, LinkTarget(..)
) where
import Control.Monad.Reader
import Control.Monad.State
import Data.List (intercalate)
import Data.Default
import Text.Pandoc.Definition
import Text.Pandoc.ImageSize
import Text.Pandoc.Slides (getSlideLevel)
import Text.Pandoc.Options
import Text.Pandoc.Logging
import Text.Pandoc.Walk
import Data.Time (UTCTime)
import qualified Text.Pandoc.Shared as Shared
import Text.Pandoc.Shared (tshow)
import Text.Pandoc.Writers.Shared (lookupMetaInlines, lookupMetaBlocks
, lookupMetaString, toTableOfContents)
import qualified Data.Map as M
import qualified Data.Set as S
import Data.Maybe (maybeToList, fromMaybe)
import Text.Pandoc.Highlighting
import qualified Data.Text as T
import Control.Applicative ((<|>))
import Skylighting
data WriterEnv = WriterEnv { envMetadata :: Meta
, envRunProps :: RunProps
, envParaProps :: ParaProps
, envSlideLevel :: Int
, envOpts :: WriterOptions
, envSlideHasHeader :: Bool
, envInList :: Bool
, envInNoteSlide :: Bool
, envCurSlideId :: SlideId
, envInSpeakerNotes :: Bool
}
deriving (Show)
instance Default WriterEnv where
def = WriterEnv { envMetadata = mempty
, envRunProps = def
, envParaProps = def
, envSlideLevel = 2
, envOpts = def
, envSlideHasHeader = False
, envInList = False
, envInNoteSlide = False
, envCurSlideId = SlideId "Default"
, envInSpeakerNotes = False
}
data WriterState = WriterState { stNoteIds :: M.Map Int [Block]
, stAnchorMap :: M.Map T.Text SlideId
, stSlideIdSet :: S.Set SlideId
, stLog :: [LogMessage]
, stSpeakerNotes :: SpeakerNotes
} deriving (Show, Eq)
instance Default WriterState where
def = WriterState { stNoteIds = mempty
, stAnchorMap = mempty
, stSlideIdSet = reservedSlideIds
, stLog = []
, stSpeakerNotes = mempty
}
metadataSlideId :: SlideId
metadataSlideId = SlideId "Metadata"
tocSlideId :: SlideId
tocSlideId = SlideId "TOC"
endNotesSlideId :: SlideId
endNotesSlideId = SlideId "EndNotes"
reservedSlideIds :: S.Set SlideId
reservedSlideIds = S.fromList [ metadataSlideId
, tocSlideId
, endNotesSlideId
]
uniqueSlideId' :: Integer -> S.Set SlideId -> T.Text -> SlideId
uniqueSlideId' n idSet s =
let s' = if n == 0 then s else s <> "-" <> tshow n
in if SlideId s' `S.member` idSet
then uniqueSlideId' (n+1) idSet s
else SlideId s'
uniqueSlideId :: S.Set SlideId -> T.Text -> SlideId
uniqueSlideId = uniqueSlideId' 0
runUniqueSlideId :: T.Text -> Pres SlideId
runUniqueSlideId s = do
idSet <- gets stSlideIdSet
let sldId = uniqueSlideId idSet s
modify $ \st -> st{stSlideIdSet = S.insert sldId idSet}
return sldId
addLogMessage :: LogMessage -> Pres ()
addLogMessage msg = modify $ \st -> st{stLog = msg : stLog st}
type Pres = ReaderT WriterEnv (State WriterState)
runPres :: WriterEnv -> WriterState -> Pres a -> (a, [LogMessage])
runPres env st p = (pres, reverse $ stLog finalSt)
where (pres, finalSt) = runState (runReaderT p env) st
concatMapM :: (Monad m) => (a -> m [b]) -> [a] -> m [b]
concatMapM f xs = liftM concat (mapM f xs)
type Pixels = Integer
data Presentation = Presentation DocProps [Slide]
deriving (Show)
data DocProps = DocProps { dcTitle :: Maybe T.Text
, dcSubject :: Maybe T.Text
, dcCreator :: Maybe T.Text
, dcKeywords :: Maybe [T.Text]
, dcDescription :: Maybe T.Text
, cpCategory :: Maybe T.Text
, dcCreated :: Maybe UTCTime
, customProperties :: Maybe [(T.Text, T.Text)]
} deriving (Show, Eq)
data Slide = Slide { slideId :: SlideId
, slideLayout :: Layout
, slideSpeakerNotes :: SpeakerNotes
} deriving (Show, Eq)
newtype SlideId = SlideId T.Text
deriving (Show, Eq, Ord)
newtype SpeakerNotes = SpeakerNotes {fromSpeakerNotes :: [Paragraph]}
deriving (Show, Eq, Monoid, Semigroup)
data Layout = MetadataSlide [ParaElem] [ParaElem] [[ParaElem]] [ParaElem]
| TitleSlide [ParaElem]
| ContentSlide [ParaElem] [Shape]
| TwoColumnSlide [ParaElem] [Shape] [Shape]
deriving (Show, Eq)
data Shape = Pic PicProps FilePath [ParaElem]
| GraphicFrame [Graphic] [ParaElem]
| TextBox [Paragraph]
| RawOOXMLShape T.Text
deriving (Show, Eq)
type Cell = [Paragraph]
data TableProps = TableProps { tblPrFirstRow :: Bool
, tblPrBandRow :: Bool
} deriving (Show, Eq)
data Graphic = Tbl TableProps [Cell] [[Cell]]
deriving (Show, Eq)
data Paragraph = Paragraph { paraProps :: ParaProps
, paraElems :: [ParaElem]
} deriving (Show, Eq)
data BulletType = Bullet
| AutoNumbering ListAttributes
deriving (Show, Eq)
data Algnment = AlgnLeft | AlgnRight | AlgnCenter
deriving (Show, Eq)
data ParaProps = ParaProps { pPropMarginLeft :: Maybe Pixels
, pPropMarginRight :: Maybe Pixels
, pPropLevel :: Int
, pPropBullet :: Maybe BulletType
, pPropAlign :: Maybe Algnment
, pPropSpaceBefore :: Maybe Pixels
, pPropIndent :: Maybe Pixels
} deriving (Show, Eq)
instance Default ParaProps where
def = ParaProps { pPropMarginLeft = Just 0
, pPropMarginRight = Just 0
, pPropLevel = 0
, pPropBullet = Nothing
, pPropAlign = Nothing
, pPropSpaceBefore = Nothing
, pPropIndent = Just 0
}
newtype TeXString = TeXString {unTeXString :: T.Text}
deriving (Eq, Show)
data ParaElem = Break
| Run RunProps T.Text
| MathElem MathType TeXString
| RawOOXMLParaElem T.Text
deriving (Show, Eq)
data Strikethrough = NoStrike | SingleStrike | DoubleStrike
deriving (Show, Eq)
data Capitals = NoCapitals | SmallCapitals | AllCapitals
deriving (Show, Eq)
type URL = T.Text
data LinkTarget = ExternalTarget (URL, T.Text)
| InternalTarget SlideId
deriving (Show, Eq)
data RunProps = RunProps { rPropBold :: Bool
, rPropItalics :: Bool
, rStrikethrough :: Maybe Strikethrough
, rBaseline :: Maybe Int
, rCap :: Maybe Capitals
, rLink :: Maybe LinkTarget
, rPropCode :: Bool
, rPropBlockQuote :: Bool
, rPropForceSize :: Maybe Pixels
, rSolidFill :: Maybe Color
, rPropUnderline :: Bool
} deriving (Show, Eq)
instance Default RunProps where
def = RunProps { rPropBold = False
, rPropItalics = False
, rStrikethrough = Nothing
, rBaseline = Nothing
, rCap = Nothing
, rLink = Nothing
, rPropCode = False
, rPropBlockQuote = False
, rPropForceSize = Nothing
, rSolidFill = Nothing
, rPropUnderline = False
}
data PicProps = PicProps { picPropLink :: Maybe LinkTarget
, picWidth :: Maybe Dimension
, picHeight :: Maybe Dimension
} deriving (Show, Eq)
instance Default PicProps where
def = PicProps { picPropLink = Nothing
, picWidth = Nothing
, picHeight = Nothing
}
inlinesToParElems :: [Inline] -> Pres [ParaElem]
inlinesToParElems ils = concatMapM inlineToParElems ils
inlineToParElems :: Inline -> Pres [ParaElem]
inlineToParElems (Str s) = do
pr <- asks envRunProps
return [Run pr s]
inlineToParElems (Emph ils) =
local (\r -> r{envRunProps = (envRunProps r){rPropItalics=True}}) $
inlinesToParElems ils
inlineToParElems (Strong ils) =
local (\r -> r{envRunProps = (envRunProps r){rPropBold=True}}) $
inlinesToParElems ils
inlineToParElems (Strikeout ils) =
local (\r -> r{envRunProps = (envRunProps r){rStrikethrough=Just SingleStrike}}) $
inlinesToParElems ils
inlineToParElems (Superscript ils) =
local (\r -> r{envRunProps = (envRunProps r){rBaseline=Just 30000}}) $
inlinesToParElems ils
inlineToParElems (Subscript ils) =
local (\r -> r{envRunProps = (envRunProps r){rBaseline=Just (-25000)}}) $
inlinesToParElems ils
inlineToParElems (SmallCaps ils) =
local (\r -> r{envRunProps = (envRunProps r){rCap = Just SmallCapitals}}) $
inlinesToParElems ils
inlineToParElems Space = inlineToParElems (Str " ")
inlineToParElems SoftBreak = inlineToParElems (Str " ")
inlineToParElems LineBreak = return [Break]
inlineToParElems (Link _ ils (url, title)) =
local (\r ->r{envRunProps = (envRunProps r){rLink = Just $ ExternalTarget (url, title)}}) $
inlinesToParElems ils
inlineToParElems (Code _ str) =
local (\r ->r{envRunProps = (envRunProps r){rPropCode = True}}) $
inlineToParElems $ Str str
inlineToParElems (Math mathtype str) =
return [MathElem mathtype (TeXString str)]
inlineToParElems (Note blks) = do
inSpNotes <- asks envInSpeakerNotes
if inSpNotes
then return []
else do
notes <- gets stNoteIds
let maxNoteId = case M.keys notes of
[] -> 0
lst -> maximum lst
curNoteId = maxNoteId + 1
modify $ \st -> st { stNoteIds = M.insert curNoteId blks notes }
local (\env -> env{envRunProps = (envRunProps env){rLink = Just $ InternalTarget endNotesSlideId}}) $
inlineToParElems $ Superscript [Str $ tshow curNoteId]
inlineToParElems (Span (_, ["underline"], _) ils) =
local (\r -> r{envRunProps = (envRunProps r){rPropUnderline=True}}) $
inlinesToParElems ils
inlineToParElems (Span _ ils) = inlinesToParElems ils
inlineToParElems (Quoted quoteType ils) =
inlinesToParElems $ [Str open] ++ ils ++ [Str close]
where (open, close) = case quoteType of
SingleQuote -> ("\x2018", "\x2019")
DoubleQuote -> ("\x201C", "\x201D")
inlineToParElems il@(RawInline fmt s) =
case fmt of
Format "openxml" -> return [RawOOXMLParaElem s]
_ -> do addLogMessage $ InlineNotRendered il
return []
inlineToParElems (Cite _ ils) = inlinesToParElems ils
inlineToParElems (Image _ alt _) = inlinesToParElems alt
isListType :: Block -> Bool
isListType (OrderedList _ _) = True
isListType (BulletList _) = True
isListType (DefinitionList _) = True
isListType _ = False
registerAnchorId :: T.Text -> Pres ()
registerAnchorId anchor = do
anchorMap <- gets stAnchorMap
sldId <- asks envCurSlideId
unless (T.null anchor) $
modify $ \st -> st {stAnchorMap = M.insert anchor sldId anchorMap}
blockQuoteSize :: Pixels
blockQuoteSize = 20
noteSize :: Pixels
noteSize = 18
blockToParagraphs :: Block -> Pres [Paragraph]
blockToParagraphs (Plain ils) = blockToParagraphs (Para ils)
blockToParagraphs (Para ils) = do
parElems <- inlinesToParElems ils
pProps <- asks envParaProps
return [Paragraph pProps parElems]
blockToParagraphs (LineBlock ilsList) = do
parElems <- inlinesToParElems $ intercalate [LineBreak] ilsList
pProps <- asks envParaProps
return [Paragraph pProps parElems]
blockToParagraphs (CodeBlock attr str) = do
pProps <- asks envParaProps
local (\r -> r{ envParaProps = def{ pPropMarginLeft = Nothing
, pPropBullet = Nothing
, pPropLevel = pPropLevel pProps
, pPropIndent = Just 0
}
, envRunProps = (envRunProps r){rPropCode = True}}) $ do
mbSty <- writerHighlightStyle <$> asks envOpts
synMap <- writerSyntaxMap <$> asks envOpts
case mbSty of
Just sty ->
case highlight synMap (formatSourceLines sty) attr str of
Right pElems -> do pPropsNew <- asks envParaProps
return [Paragraph pPropsNew pElems]
Left _ -> blockToParagraphs $ Para [Str str]
Nothing -> blockToParagraphs $ Para [Str str]
blockToParagraphs (BlockQuote (blk : blks)) | isListType blk = do
ps <- blockToParagraphs blk
ps' <- blockToParagraphs $ BlockQuote blks
return $ ps ++ ps'
blockToParagraphs (BlockQuote blks) =
local (\r -> r{ envParaProps = (envParaProps r){ pPropMarginLeft = Just 100
, pPropIndent = Just 0
}
, envRunProps = (envRunProps r){rPropForceSize = Just blockQuoteSize}})$
concatMapM blockToParagraphs blks
blockToParagraphs blk@(RawBlock _ _) = do addLogMessage $ BlockNotRendered blk
return []
blockToParagraphs (Header _ (ident, _, _) ils) = do
registerAnchorId ident
parElems <- local (\e->e{envRunProps = (envRunProps e){rPropBold=True}}) $
inlinesToParElems ils
return [Paragraph def{pPropSpaceBefore = Just 30} parElems]
blockToParagraphs (BulletList blksLst) = do
pProps <- asks envParaProps
let lvl = pPropLevel pProps
local (\env -> env{ envInList = True
, envParaProps = pProps{ pPropLevel = lvl + 1
, pPropBullet = Just Bullet
, pPropMarginLeft = Nothing
, pPropIndent = Nothing
}}) $
concatMapM multiParBullet blksLst
blockToParagraphs (OrderedList listAttr blksLst) = do
pProps <- asks envParaProps
let lvl = pPropLevel pProps
local (\env -> env{ envInList = True
, envParaProps = pProps{ pPropLevel = lvl + 1
, pPropBullet = Just (AutoNumbering listAttr)
, pPropMarginLeft = Nothing
, pPropIndent = Nothing
}}) $
concatMapM multiParBullet blksLst
blockToParagraphs (DefinitionList entries) = do
let go :: ([Inline], [[Block]]) -> Pres [Paragraph]
go (ils, blksLst) = do
term <-blockToParagraphs $ Para [Strong ils]
definition <- concatMapM (blockToParagraphs . BlockQuote) blksLst
return $ term ++ definition
concatMapM go entries
blockToParagraphs (Div _ blks) = concatMapM blockToParagraphs blks
blockToParagraphs blk = do
addLogMessage $ BlockNotRendered blk
return []
multiParBullet :: [Block] -> Pres [Paragraph]
multiParBullet [] = return []
multiParBullet (b:bs) = do
pProps <- asks envParaProps
p <- blockToParagraphs b
ps <- local (\env -> env{envParaProps = pProps{pPropBullet = Nothing}}) $
concatMapM blockToParagraphs bs
return $ p ++ ps
cellToParagraphs :: Alignment -> TableCell -> Pres [Paragraph]
cellToParagraphs algn tblCell = do
paras <- mapM blockToParagraphs tblCell
let alignment = case algn of
AlignLeft -> Just AlgnLeft
AlignRight -> Just AlgnRight
AlignCenter -> Just AlgnCenter
AlignDefault -> Nothing
paras' = map (map (\p -> p{paraProps = (paraProps p){pPropAlign = alignment}})) paras
return $ concat paras'
rowToParagraphs :: [Alignment] -> [TableCell] -> Pres [[Paragraph]]
rowToParagraphs algns tblCells = do
let pairs = zip (algns ++ repeat AlignDefault) tblCells
mapM (uncurry cellToParagraphs) pairs
withAttr :: Attr -> Shape -> Shape
withAttr attr (Pic picPr url caption) =
let picPr' = picPr { picWidth = dimension Width attr
, picHeight = dimension Height attr
}
in
Pic picPr' url caption
withAttr _ sp = sp
blockToShape :: Block -> Pres Shape
blockToShape (Plain ils) = blockToShape (Para ils)
blockToShape (Para (il:_)) | Image attr ils (url, _) <- il =
(withAttr attr . Pic def (T.unpack url)) <$> inlinesToParElems ils
blockToShape (Para (il:_)) | Link _ (il':_) target <- il
, Image attr ils (url, _) <- il' =
(withAttr attr . Pic def{picPropLink = Just $ ExternalTarget target} (T.unpack url))
<$> inlinesToParElems ils
blockToShape (Table caption algn _ hdrCells rows) = do
caption' <- inlinesToParElems caption
hdrCells' <- rowToParagraphs algn hdrCells
rows' <- mapM (rowToParagraphs algn) rows
let tblPr = if null hdrCells
then TableProps { tblPrFirstRow = False
, tblPrBandRow = True
}
else TableProps { tblPrFirstRow = True
, tblPrBandRow = True
}
return $ GraphicFrame [Tbl tblPr hdrCells' rows'] caption'
blockToShape (RawBlock (Format "openxml") str) = return $ RawOOXMLShape str
blockToShape blk = do paras <- blockToParagraphs blk
let paras' = map (\par -> par{paraElems = combineParaElems $ paraElems par}) paras
return $ TextBox paras'
combineShapes :: [Shape] -> [Shape]
combineShapes [] = []
combineShapes (pic@Pic{} : ss) = pic : combineShapes ss
combineShapes (TextBox [] : ss) = combineShapes ss
combineShapes (s : TextBox [] : ss) = combineShapes (s : ss)
combineShapes (TextBox (p:ps) : TextBox (p':ps') : ss) =
combineShapes $ TextBox ((p:ps) ++ (p':ps')) : ss
combineShapes (s:ss) = s : combineShapes ss
isNotesDiv :: Block -> Bool
isNotesDiv (Div (_, ["notes"], _) _) = True
isNotesDiv _ = False
blocksToShapes :: [Block] -> Pres [Shape]
blocksToShapes blks = combineShapes <$> mapM blockToShape blks
isImage :: Inline -> Bool
isImage Image{} = True
isImage (Link _ (Image{} : _) _) = True
isImage _ = False
splitBlocks' :: [Block] -> [[Block]] -> [Block] -> Pres [[Block]]
splitBlocks' cur acc [] = return $ acc ++ ([cur | not (null cur)])
splitBlocks' cur acc (HorizontalRule : blks) =
splitBlocks' [] (acc ++ ([cur | not (null cur)])) blks
splitBlocks' cur acc (h@(Header n _ _) : blks) = do
slideLevel <- asks envSlideLevel
let (nts, blks') = span isNotesDiv blks
case compare n slideLevel of
LT -> splitBlocks' [] (acc ++ ([cur | not (null cur)]) ++ [h : nts]) blks'
EQ -> splitBlocks' (h:nts) (acc ++ ([cur | not (null cur)])) blks'
GT -> splitBlocks' (cur ++ (h:nts)) acc blks'
splitBlocks' cur acc (Plain ils : blks) = splitBlocks' cur acc (Para ils : blks)
splitBlocks' cur acc (Para (il:ils) : blks) | isImage il = do
slideLevel <- asks envSlideLevel
let (nts, blks') = if null ils
then span isNotesDiv blks
else ([], blks)
case cur of
[Header n _ _] | n == slideLevel ->
splitBlocks' []
(acc ++ [cur ++ [Para [il]] ++ nts])
(if null ils then blks' else Para ils : blks')
_ -> splitBlocks' []
(acc ++ ([cur | not (null cur)]) ++ [Para [il] : nts])
(if null ils then blks' else Para ils : blks')
splitBlocks' cur acc (tbl@Table{} : blks) = do
slideLevel <- asks envSlideLevel
let (nts, blks') = span isNotesDiv blks
case cur of
[Header n _ _] | n == slideLevel ->
splitBlocks' [] (acc ++ [cur ++ [tbl] ++ nts]) blks'
_ -> splitBlocks' [] (acc ++ ([cur | not (null cur)]) ++ [tbl : nts]) blks'
splitBlocks' cur acc (d@(Div (_, classes, _) _): blks) | "columns" `elem` classes = do
slideLevel <- asks envSlideLevel
let (nts, blks') = span isNotesDiv blks
case cur of
[Header n _ _] | n == slideLevel ->
splitBlocks' [] (acc ++ [cur ++ [d] ++ nts]) blks'
_ -> splitBlocks' [] (acc ++ ([cur | not (null cur)]) ++ [d : nts]) blks'
splitBlocks' cur acc (blk : blks) = splitBlocks' (cur ++ [blk]) acc blks
splitBlocks :: [Block] -> Pres [[Block]]
splitBlocks = splitBlocks' [] []
blocksToSlide' :: Int -> [Block] -> SpeakerNotes -> Pres Slide
blocksToSlide' lvl (Header n (ident, _, _) ils : blks) spkNotes
| n < lvl = do
registerAnchorId ident
sldId <- asks envCurSlideId
hdr <- inlinesToParElems ils
return $ Slide sldId (TitleSlide hdr) spkNotes
| n == lvl = do
registerAnchorId ident
hdr <- inlinesToParElems ils
slide <- blocksToSlide' lvl blks spkNotes
let layout = case slideLayout slide of
ContentSlide _ cont -> ContentSlide hdr cont
TwoColumnSlide _ contL contR -> TwoColumnSlide hdr contL contR
layout' -> layout'
return $ slide{slideLayout = layout}
blocksToSlide' _ (blk : blks) spkNotes
| Div (_, classes, _) divBlks <- blk
, "columns" `elem` classes
, Div (_, clsL, _) blksL : Div (_, clsR, _) blksR : remaining <- divBlks
, "column" `elem` clsL, "column" `elem` clsR = do
mapM_ (addLogMessage . BlockNotRendered) (blks ++ remaining)
mbSplitBlksL <- splitBlocks blksL
mbSplitBlksR <- splitBlocks blksR
let blksL' = case mbSplitBlksL of
bs : _ -> bs
[] -> []
let blksR' = case mbSplitBlksR of
bs : _ -> bs
[] -> []
shapesL <- blocksToShapes blksL'
shapesR <- blocksToShapes blksR'
sldId <- asks envCurSlideId
return $ Slide
sldId
(TwoColumnSlide [] shapesL shapesR)
spkNotes
blocksToSlide' _ (blk : blks) spkNotes = do
inNoteSlide <- asks envInNoteSlide
shapes <- if inNoteSlide
then forceFontSize noteSize $ blocksToShapes (blk : blks)
else blocksToShapes (blk : blks)
sldId <- asks envCurSlideId
return $
Slide
sldId
(ContentSlide [] shapes)
spkNotes
blocksToSlide' _ [] spkNotes = do
sldId <- asks envCurSlideId
return $
Slide
sldId
(ContentSlide [] [])
spkNotes
blockToSpeakerNotes :: Block -> Pres SpeakerNotes
blockToSpeakerNotes (Div (_, ["notes"], _) blks) =
local (\env -> env{envInSpeakerNotes=True}) $
SpeakerNotes <$> concatMapM blockToParagraphs blks
blockToSpeakerNotes _ = return mempty
handleSpeakerNotes :: Block -> Pres ()
handleSpeakerNotes blk = do
spNotes <- blockToSpeakerNotes blk
modify $ \st -> st{stSpeakerNotes = stSpeakerNotes st <> spNotes}
handleAndFilterSpeakerNotes' :: [Block] -> Pres [Block]
handleAndFilterSpeakerNotes' blks = do
mapM_ handleSpeakerNotes blks
return $ filter (not . isNotesDiv) blks
handleAndFilterSpeakerNotes :: [Block] -> Pres ([Block], SpeakerNotes)
handleAndFilterSpeakerNotes blks = do
modify $ \st -> st{stSpeakerNotes = mempty}
blks' <- walkM handleAndFilterSpeakerNotes' blks
spkNotes <- gets stSpeakerNotes
return (blks', spkNotes)
blocksToSlide :: [Block] -> Pres Slide
blocksToSlide blks = do
(blks', spkNotes) <- handleAndFilterSpeakerNotes blks
slideLevel <- asks envSlideLevel
blocksToSlide' slideLevel blks' spkNotes
makeNoteEntry :: (Int, [Block]) -> [Block]
makeNoteEntry (n, blks) =
let enum = Str (tshow n <> ".")
in
case blks of
(Para ils : blks') -> (Para $ enum : Space : ils) : blks'
_ -> Para [enum] : blks
forceFontSize :: Pixels -> Pres a -> Pres a
forceFontSize px x = do
rpr <- asks envRunProps
local (\r -> r {envRunProps = rpr{rPropForceSize = Just px}}) x
makeEndNotesSlideBlocks :: Pres [Block]
makeEndNotesSlideBlocks = do
noteIds <- gets stNoteIds
slideLevel <- asks envSlideLevel
exts <- writerExtensions <$> asks envOpts
meta <- asks envMetadata
anchorSet <- M.keysSet <$> gets stAnchorMap
if M.null noteIds
then return []
else let title = case lookupMetaInlines "notes-title" meta of
[] -> [Str "Notes"]
ls -> ls
ident = Shared.uniqueIdent exts title anchorSet
hdr = Header slideLevel (ident, [], []) title
blks = concatMap makeNoteEntry $
M.toList noteIds
in return $ hdr : blks
getMetaSlide :: Pres (Maybe Slide)
getMetaSlide = do
meta <- asks envMetadata
title <- inlinesToParElems $ docTitle meta
subtitle <- inlinesToParElems $ lookupMetaInlines "subtitle" meta
authors <- mapM inlinesToParElems $ docAuthors meta
date <- inlinesToParElems $ docDate meta
if null title && null subtitle && null authors && null date
then return Nothing
else return $
Just $
Slide
metadataSlideId
(MetadataSlide title subtitle authors date)
mempty
addSpeakerNotesToMetaSlide :: Slide -> [Block] -> Pres (Slide, [Block])
addSpeakerNotesToMetaSlide (Slide sldId layout@(MetadataSlide{}) spkNotes) blks =
do let (ntsBlks, blks') = span isNotesDiv blks
spkNotes' <- mconcat <$> mapM blockToSpeakerNotes ntsBlks
return (Slide sldId layout (spkNotes <> spkNotes'), blks')
addSpeakerNotesToMetaSlide sld blks = return (sld, blks)
makeTOCSlide :: [Block] -> Pres Slide
makeTOCSlide blks = local (\env -> env{envCurSlideId = tocSlideId}) $ do
opts <- asks envOpts
let contents = toTableOfContents opts blks
meta <- asks envMetadata
slideLevel <- asks envSlideLevel
let tocTitle = case lookupMetaInlines "toc-title" meta of
[] -> [Str "Table of Contents"]
ls -> ls
hdr = Header slideLevel nullAttr tocTitle
blocksToSlide [hdr, contents]
combineParaElems' :: Maybe ParaElem -> [ParaElem] -> [ParaElem]
combineParaElems' mbPElem [] = maybeToList mbPElem
combineParaElems' Nothing (pElem : pElems) =
combineParaElems' (Just pElem) pElems
combineParaElems' (Just pElem') (pElem : pElems)
| Run rPr' s' <- pElem'
, Run rPr s <- pElem
, rPr == rPr' =
combineParaElems' (Just $ Run rPr' $ s' <> s) pElems
| otherwise =
pElem' : combineParaElems' (Just pElem) pElems
combineParaElems :: [ParaElem] -> [ParaElem]
combineParaElems = combineParaElems' Nothing
applyToParagraph :: Monad m => (ParaElem -> m ParaElem) -> Paragraph -> m Paragraph
applyToParagraph f para = do
paraElems' <- mapM f $ paraElems para
return $ para {paraElems = paraElems'}
applyToShape :: Monad m => (ParaElem -> m ParaElem) -> Shape -> m Shape
applyToShape f (Pic pPr fp pes) = Pic pPr fp <$> mapM f pes
applyToShape f (GraphicFrame gfx pes) = GraphicFrame gfx <$> mapM f pes
applyToShape f (TextBox paras) = TextBox <$> mapM (applyToParagraph f) paras
applyToShape _ (RawOOXMLShape str) = return $ RawOOXMLShape str
applyToLayout :: Monad m => (ParaElem -> m ParaElem) -> Layout -> m Layout
applyToLayout f (MetadataSlide title subtitle authors date) = do
title' <- mapM f title
subtitle' <- mapM f subtitle
authors' <- mapM (mapM f) authors
date' <- mapM f date
return $ MetadataSlide title' subtitle' authors' date'
applyToLayout f (TitleSlide title) = TitleSlide <$> mapM f title
applyToLayout f (ContentSlide hdr content) = do
hdr' <- mapM f hdr
content' <- mapM (applyToShape f) content
return $ ContentSlide hdr' content'
applyToLayout f (TwoColumnSlide hdr contentL contentR) = do
hdr' <- mapM f hdr
contentL' <- mapM (applyToShape f) contentL
contentR' <- mapM (applyToShape f) contentR
return $ TwoColumnSlide hdr' contentL' contentR'
applyToSlide :: Monad m => (ParaElem -> m ParaElem) -> Slide -> m Slide
applyToSlide f slide = do
layout' <- applyToLayout f $ slideLayout slide
let paras = fromSpeakerNotes $ slideSpeakerNotes slide
notes' <- SpeakerNotes <$> mapM (applyToParagraph f) paras
return slide{slideLayout = layout', slideSpeakerNotes = notes'}
replaceAnchor :: ParaElem -> Pres ParaElem
replaceAnchor (Run rProps s)
| Just (ExternalTarget (T.uncons -> Just ('#', anchor), _)) <- rLink rProps
= do
anchorMap <- gets stAnchorMap
let rProps' = case M.lookup anchor anchorMap of
Just n -> rProps{rLink = Just $ InternalTarget n}
Nothing -> rProps{rLink = Nothing}
return $ Run rProps' s
replaceAnchor pe = return pe
emptyParaElem :: ParaElem -> Bool
emptyParaElem (Run _ s) =
T.null $ Shared.trim s
emptyParaElem (MathElem _ ts) =
T.null $ Shared.trim $ unTeXString ts
emptyParaElem _ = False
emptyParagraph :: Paragraph -> Bool
emptyParagraph para = all emptyParaElem $ paraElems para
emptyShape :: Shape -> Bool
emptyShape (TextBox paras) = all emptyParagraph paras
emptyShape _ = False
emptyLayout :: Layout -> Bool
emptyLayout layout = case layout of
MetadataSlide title subtitle authors date ->
all emptyParaElem title &&
all emptyParaElem subtitle &&
all (all emptyParaElem) authors &&
all emptyParaElem date
TitleSlide hdr -> all emptyParaElem hdr
ContentSlide hdr shapes ->
all emptyParaElem hdr &&
all emptyShape shapes
TwoColumnSlide hdr shapes1 shapes2 ->
all emptyParaElem hdr &&
all emptyShape shapes1 &&
all emptyShape shapes2
emptySlide :: Slide -> Bool
emptySlide (Slide _ layout notes) = (notes == mempty) && emptyLayout layout
blocksToPresentationSlides :: [Block] -> Pres [Slide]
blocksToPresentationSlides blks = do
opts <- asks envOpts
mbMetadataSlide <- getMetaSlide
(metadataslides, blks') <- case mbMetadataSlide of
Just sld ->
do (s, bs) <- addSpeakerNotesToMetaSlide sld blks
return ([s], bs)
Nothing -> return ([], blks)
blksLst <- splitBlocks blks'
bodySlideIds <- mapM
(\n -> runUniqueSlideId $ "BodySlide" <> tshow n)
(take (length blksLst) [1..] :: [Integer])
bodyslides <- mapM
(\(bs, ident) ->
local (\st -> st{envCurSlideId = ident}) (blocksToSlide bs))
(zip blksLst bodySlideIds)
endNotesSlideBlocks <- makeEndNotesSlideBlocks
tocSlides <- if writerTableOfContents opts
then do toc <- makeTOCSlide $ blks' ++ endNotesSlideBlocks
return [toc]
else return []
endNotesSlides <- if null endNotesSlideBlocks
then return []
else do endNotesSlide <- local
(\env -> env { envCurSlideId = endNotesSlideId
, envInNoteSlide = True
})
(blocksToSlide endNotesSlideBlocks)
return [endNotesSlide]
let slides = metadataslides ++ tocSlides ++ bodyslides ++ endNotesSlides
slides' = filter (not . emptySlide) slides
mapM (applyToSlide replaceAnchor) slides'
metaToDocProps :: Meta -> DocProps
metaToDocProps meta =
let keywords = case lookupMeta "keywords" meta of
Just (MetaList xs) -> Just $ map Shared.stringify xs
_ -> Nothing
authors = case map Shared.stringify $ docAuthors meta of
[] -> Nothing
ss -> Just $ T.intercalate "; " ss
description = case map Shared.stringify $ lookupMetaBlocks "description" meta of
[] -> Nothing
ss -> Just $ T.intercalate "_x000d_\n" ss
customProperties' = case [(k, lookupMetaString k meta) | k <- M.keys (unMeta meta)
, k `notElem` ["title", "author", "keywords", "description"
, "subject","lang","category"]] of
[] -> Nothing
ss -> Just ss
in
DocProps{ dcTitle = Shared.stringify <$> lookupMeta "title" meta
, dcSubject = Shared.stringify <$> lookupMeta "subject" meta
, dcCreator = authors
, dcKeywords = keywords
, dcDescription = description
, cpCategory = Shared.stringify <$> lookupMeta "category" meta
, dcCreated = Nothing
, customProperties = customProperties'
}
documentToPresentation :: WriterOptions
-> Pandoc
-> (Presentation, [LogMessage])
documentToPresentation opts (Pandoc meta blks) =
let env = def { envOpts = opts
, envMetadata = meta
, envSlideLevel = fromMaybe (getSlideLevel blks) (writerSlideLevel opts)
}
(presSlides, msgs) = runPres env def $ blocksToPresentationSlides blks
docProps = metaToDocProps meta
in
(Presentation docProps presSlides, msgs)
applyTokStyToRunProps :: TokenStyle -> RunProps -> RunProps
applyTokStyToRunProps tokSty rProps =
rProps{ rSolidFill = tokenColor tokSty <|> rSolidFill rProps
, rPropBold = tokenBold tokSty || rPropBold rProps
, rPropItalics = tokenItalic tokSty || rPropItalics rProps
, rPropUnderline = tokenUnderline tokSty || rPropUnderline rProps
}
formatToken :: Style -> Token -> ParaElem
formatToken sty (tokType, txt) =
let rProps = def{rPropCode = True, rSolidFill = defaultColor sty}
rProps' = case M.lookup tokType (tokenStyles sty) of
Just tokSty -> applyTokStyToRunProps tokSty rProps
Nothing -> rProps
in
Run rProps' txt
formatSourceLine :: Style -> FormatOptions -> SourceLine -> [ParaElem]
formatSourceLine sty _ srcLn = map (formatToken sty) srcLn
formatSourceLines :: Style -> FormatOptions -> [SourceLine] -> [ParaElem]
formatSourceLines sty opts srcLns = intercalate [Break] $
map (formatSourceLine sty opts) srcLns