2007-11-03 23:27:58 +00:00
|
|
|
{-
|
|
|
|
Copyright (C) 2006-7 John MacFarlane <jgm@berkeley.edu>
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
-}
|
|
|
|
|
|
|
|
{- |
|
|
|
|
Module : Text.Pandoc.Writers.RTF
|
|
|
|
Copyright : Copyright (C) 2006-7 John MacFarlane
|
|
|
|
License : GNU GPL, version 2 or above
|
|
|
|
|
|
|
|
Maintainer : John MacFarlane <jgm@berkeley.edu>
|
|
|
|
Stability : alpha
|
|
|
|
Portability : portable
|
|
|
|
|
|
|
|
Conversion of 'Pandoc' documents to RTF (rich text format).
|
|
|
|
-}
|
|
|
|
module Text.Pandoc.Writers.RTF ( writeRTF ) where
|
|
|
|
import Text.Pandoc.Definition
|
|
|
|
import Text.Pandoc.Shared
|
2007-12-02 00:36:32 +00:00
|
|
|
import Text.Pandoc.Readers.TeXMath
|
2008-09-08 06:36:28 +00:00
|
|
|
import Data.List ( isSuffixOf, intercalate )
|
2008-02-09 03:21:04 +00:00
|
|
|
import Data.Char ( ord, isDigit )
|
2007-11-03 23:27:58 +00:00
|
|
|
|
|
|
|
-- | Convert Pandoc to a string in rich text format.
|
|
|
|
writeRTF :: WriterOptions -> Pandoc -> String
|
|
|
|
writeRTF options (Pandoc meta blocks) =
|
2008-07-13 23:25:52 +00:00
|
|
|
let head' = if writerStandalone options
|
|
|
|
then rtfHeader (writerHeader options) meta
|
|
|
|
else ""
|
2007-11-03 23:27:58 +00:00
|
|
|
toc = if writerTableOfContents options
|
|
|
|
then tableOfContents $ filter isHeaderBlock blocks
|
|
|
|
else ""
|
|
|
|
foot = if writerStandalone options then "\n}\n" else ""
|
|
|
|
body = writerIncludeBefore options ++
|
|
|
|
concatMap (blockToRTF 0 AlignDefault) blocks ++
|
|
|
|
writerIncludeAfter options
|
2008-07-13 23:25:52 +00:00
|
|
|
in head' ++ toc ++ body ++ foot
|
2007-11-03 23:27:58 +00:00
|
|
|
|
|
|
|
-- | Construct table of contents from list of header blocks.
|
|
|
|
tableOfContents :: [Block] -> String
|
|
|
|
tableOfContents headers =
|
|
|
|
let contentsTree = hierarchicalize headers
|
|
|
|
in concatMap (blockToRTF 0 AlignDefault) $
|
|
|
|
[Header 1 [Str "Contents"],
|
|
|
|
BulletList (map elementToListItem contentsTree)]
|
|
|
|
|
|
|
|
elementToListItem :: Element -> [Block]
|
|
|
|
elementToListItem (Blk _) = []
|
|
|
|
elementToListItem (Sec sectext subsecs) = [Plain sectext] ++
|
|
|
|
if null subsecs
|
|
|
|
then []
|
|
|
|
else [BulletList (map elementToListItem subsecs)]
|
|
|
|
|
|
|
|
-- | Convert unicode characters (> 127) into rich text format representation.
|
|
|
|
handleUnicode :: String -> String
|
|
|
|
handleUnicode [] = []
|
|
|
|
handleUnicode (c:cs) =
|
|
|
|
if ord c > 127
|
|
|
|
then '\\':'u':(show (ord c)) ++ "?" ++ handleUnicode cs
|
|
|
|
else c:(handleUnicode cs)
|
|
|
|
|
|
|
|
-- | Escape special characters.
|
|
|
|
escapeSpecial :: String -> String
|
|
|
|
escapeSpecial = escapeStringUsing (('\t',"\\tab "):(backslashEscapes "{\\}"))
|
|
|
|
|
|
|
|
-- | Escape strings as needed for rich text format.
|
|
|
|
stringToRTF :: String -> String
|
|
|
|
stringToRTF = handleUnicode . escapeSpecial
|
|
|
|
|
|
|
|
-- | Escape things as needed for code block in RTF.
|
|
|
|
codeStringToRTF :: String -> String
|
2008-09-08 06:36:28 +00:00
|
|
|
codeStringToRTF str = intercalate "\\line\n" $ lines (stringToRTF str)
|
2007-11-03 23:27:58 +00:00
|
|
|
|
|
|
|
-- | Make a paragraph with first-line indent, block indent, and space after.
|
|
|
|
rtfParSpaced :: Int -- ^ space after (in twips)
|
|
|
|
-> Int -- ^ block indent (in twips)
|
|
|
|
-> Int -- ^ first line indent (relative to block) (in twips)
|
|
|
|
-> Alignment -- ^ alignment
|
|
|
|
-> String -- ^ string with content
|
|
|
|
-> String
|
|
|
|
rtfParSpaced spaceAfter indent firstLineIndent alignment content =
|
|
|
|
let alignString = case alignment of
|
|
|
|
AlignLeft -> "\\ql "
|
|
|
|
AlignRight -> "\\qr "
|
|
|
|
AlignCenter -> "\\qc "
|
|
|
|
AlignDefault -> "\\ql "
|
|
|
|
in "{\\pard " ++ alignString ++
|
|
|
|
"\\f0 \\sa" ++ (show spaceAfter) ++ " \\li" ++ (show indent) ++
|
|
|
|
" \\fi" ++ (show firstLineIndent) ++ " " ++ content ++ "\\par}\n"
|
|
|
|
|
|
|
|
-- | Default paragraph.
|
|
|
|
rtfPar :: Int -- ^ block indent (in twips)
|
|
|
|
-> Int -- ^ first line indent (relative to block) (in twips)
|
|
|
|
-> Alignment -- ^ alignment
|
|
|
|
-> String -- ^ string with content
|
|
|
|
-> String
|
|
|
|
rtfPar = rtfParSpaced 180
|
|
|
|
|
|
|
|
-- | Compact paragraph (e.g. for compact list items).
|
|
|
|
rtfCompact :: Int -- ^ block indent (in twips)
|
|
|
|
-> Int -- ^ first line indent (relative to block) (in twips)
|
|
|
|
-> Alignment -- ^ alignment
|
|
|
|
-> String -- ^ string with content
|
|
|
|
-> String
|
|
|
|
rtfCompact = rtfParSpaced 0
|
|
|
|
|
|
|
|
-- number of twips to indent
|
2008-07-13 23:25:52 +00:00
|
|
|
indentIncrement :: Int
|
2007-11-03 23:27:58 +00:00
|
|
|
indentIncrement = 720
|
2008-07-13 23:25:52 +00:00
|
|
|
|
|
|
|
listIncrement :: Int
|
2007-11-03 23:27:58 +00:00
|
|
|
listIncrement = 360
|
|
|
|
|
|
|
|
-- | Returns appropriate bullet list marker for indent level.
|
|
|
|
bulletMarker :: Int -> String
|
|
|
|
bulletMarker indent = case indent `mod` 720 of
|
2008-07-13 23:25:52 +00:00
|
|
|
0 -> "\\bullet "
|
|
|
|
_ -> "\\endash "
|
2007-11-03 23:27:58 +00:00
|
|
|
|
|
|
|
-- | Returns appropriate (list of) ordered list markers for indent level.
|
|
|
|
orderedMarkers :: Int -> ListAttributes -> [String]
|
|
|
|
orderedMarkers indent (start, style, delim) =
|
|
|
|
if style == DefaultStyle && delim == DefaultDelim
|
|
|
|
then case indent `mod` 720 of
|
2008-07-13 23:25:52 +00:00
|
|
|
0 -> orderedListMarkers (start, Decimal, Period)
|
|
|
|
_ -> orderedListMarkers (start, LowerAlpha, Period)
|
2007-11-03 23:27:58 +00:00
|
|
|
else orderedListMarkers (start, style, delim)
|
|
|
|
|
|
|
|
-- | Returns RTF header.
|
|
|
|
rtfHeader :: String -- ^ header text
|
|
|
|
-> Meta -- ^ bibliographic information
|
|
|
|
-> String
|
|
|
|
rtfHeader headerText (Meta title authors date) =
|
|
|
|
let titletext = if null title
|
|
|
|
then ""
|
|
|
|
else rtfPar 0 0 AlignCenter $
|
|
|
|
"\\b \\fs36 " ++ inlineListToRTF title
|
|
|
|
authorstext = if null authors
|
|
|
|
then ""
|
2008-09-08 06:36:28 +00:00
|
|
|
else rtfPar 0 0 AlignCenter (" " ++ (intercalate "\\" $
|
2007-11-03 23:27:58 +00:00
|
|
|
map stringToRTF authors))
|
|
|
|
datetext = if date == ""
|
|
|
|
then ""
|
|
|
|
else rtfPar 0 0 AlignCenter (" " ++ stringToRTF date) in
|
|
|
|
let spacer = if null (titletext ++ authorstext ++ datetext)
|
|
|
|
then ""
|
|
|
|
else rtfPar 0 0 AlignDefault "" in
|
|
|
|
headerText ++ titletext ++ authorstext ++ datetext ++ spacer
|
|
|
|
|
|
|
|
-- | Convert Pandoc block element to RTF.
|
|
|
|
blockToRTF :: Int -- ^ indent level
|
|
|
|
-> Alignment -- ^ alignment
|
|
|
|
-> Block -- ^ block to convert
|
|
|
|
-> String
|
|
|
|
blockToRTF _ _ Null = ""
|
|
|
|
blockToRTF indent alignment (Plain lst) =
|
|
|
|
rtfCompact indent 0 alignment $ inlineListToRTF lst
|
|
|
|
blockToRTF indent alignment (Para lst) =
|
|
|
|
rtfPar indent 0 alignment $ inlineListToRTF lst
|
|
|
|
blockToRTF indent alignment (BlockQuote lst) =
|
|
|
|
concatMap (blockToRTF (indent + indentIncrement) alignment) lst
|
2008-02-09 03:18:22 +00:00
|
|
|
blockToRTF indent _ (CodeBlock _ str) =
|
2007-11-03 23:27:58 +00:00
|
|
|
rtfPar indent 0 AlignLeft ("\\f1 " ++ (codeStringToRTF str))
|
2008-07-13 23:25:52 +00:00
|
|
|
blockToRTF _ _ (RawHtml _) = ""
|
2007-11-03 23:27:58 +00:00
|
|
|
blockToRTF indent alignment (BulletList lst) = spaceAtEnd $
|
|
|
|
concatMap (listItemToRTF alignment indent (bulletMarker indent)) lst
|
|
|
|
blockToRTF indent alignment (OrderedList attribs lst) = spaceAtEnd $ concat $
|
|
|
|
zipWith (listItemToRTF alignment indent) (orderedMarkers indent attribs) lst
|
|
|
|
blockToRTF indent alignment (DefinitionList lst) = spaceAtEnd $
|
|
|
|
concatMap (definitionListItemToRTF alignment indent) lst
|
|
|
|
blockToRTF indent _ HorizontalRule =
|
|
|
|
rtfPar indent 0 AlignCenter "\\emdash\\emdash\\emdash\\emdash\\emdash"
|
|
|
|
blockToRTF indent alignment (Header level lst) = rtfPar indent 0 alignment $
|
|
|
|
"\\b \\fs" ++ (show (40 - (level * 4))) ++ " " ++ inlineListToRTF lst
|
|
|
|
blockToRTF indent alignment (Table caption aligns sizes headers rows) =
|
|
|
|
tableRowToRTF True indent aligns sizes headers ++
|
|
|
|
concatMap (tableRowToRTF False indent aligns sizes) rows ++
|
|
|
|
rtfPar indent 0 alignment (inlineListToRTF caption)
|
|
|
|
|
2008-09-06 02:51:44 +00:00
|
|
|
tableRowToRTF :: Bool -> Int -> [Alignment] -> [Double] -> [[Block]] -> String
|
2007-11-03 23:27:58 +00:00
|
|
|
tableRowToRTF header indent aligns sizes cols =
|
|
|
|
let columns = concat $ zipWith (tableItemToRTF indent) aligns cols
|
|
|
|
totalTwips = 6 * 1440 -- 6 inches
|
|
|
|
rightEdges = tail $ scanl (\sofar new -> sofar + floor (new * totalTwips))
|
2008-07-15 00:32:21 +00:00
|
|
|
(0 :: Integer) sizes
|
2007-11-03 23:27:58 +00:00
|
|
|
cellDefs = map (\edge -> (if header
|
|
|
|
then "\\clbrdrb\\brdrs"
|
|
|
|
else "") ++ "\\cellx" ++ show edge)
|
|
|
|
rightEdges
|
|
|
|
start = "{\n\\trowd \\trgaph120\n" ++ concat cellDefs ++ "\n" ++
|
|
|
|
"\\trkeep\\intbl\n{\n"
|
|
|
|
end = "}\n\\intbl\\row}\n"
|
|
|
|
in start ++ columns ++ end
|
|
|
|
|
|
|
|
tableItemToRTF :: Int -> Alignment -> [Block] -> String
|
|
|
|
tableItemToRTF indent alignment item =
|
|
|
|
let contents = concatMap (blockToRTF indent alignment) item
|
|
|
|
in "{\\intbl " ++ contents ++ "\\cell}\n"
|
|
|
|
|
|
|
|
-- | Ensure that there's the same amount of space after compact
|
|
|
|
-- lists as after regular lists.
|
|
|
|
spaceAtEnd :: String -> String
|
|
|
|
spaceAtEnd str =
|
|
|
|
if isSuffixOf "\\par}\n" str
|
|
|
|
then (take ((length str) - 6) str) ++ "\\sa180\\par}\n"
|
|
|
|
else str
|
|
|
|
|
|
|
|
-- | Convert list item (list of blocks) to RTF.
|
|
|
|
listItemToRTF :: Alignment -- ^ alignment
|
|
|
|
-> Int -- ^ indent level
|
|
|
|
-> String -- ^ list start marker
|
|
|
|
-> [Block] -- ^ list item (list of blocks)
|
|
|
|
-> [Char]
|
|
|
|
listItemToRTF alignment indent marker [] =
|
|
|
|
rtfCompact (indent + listIncrement) (0 - listIncrement) alignment
|
|
|
|
(marker ++ "\\tx" ++ (show listIncrement) ++ "\\tab ")
|
|
|
|
listItemToRTF alignment indent marker list =
|
2008-02-09 03:21:04 +00:00
|
|
|
let (first:rest) = map (blockToRTF (indent + listIncrement) alignment) list
|
|
|
|
listMarker = "\\fi" ++ show (0 - listIncrement) ++ " " ++ marker ++ "\\tx" ++
|
|
|
|
show listIncrement ++ "\\tab"
|
|
|
|
insertListMarker ('\\':'f':'i':'-':d:xs) | isDigit d =
|
|
|
|
listMarker ++ dropWhile isDigit xs
|
|
|
|
insertListMarker ('\\':'f':'i':d:xs) | isDigit d =
|
|
|
|
listMarker ++ dropWhile isDigit xs
|
|
|
|
insertListMarker (x:xs) =
|
|
|
|
x : insertListMarker xs
|
2008-07-13 23:25:52 +00:00
|
|
|
insertListMarker [] = []
|
2008-02-09 03:21:04 +00:00
|
|
|
-- insert the list marker into the (processed) first block
|
|
|
|
in insertListMarker first ++ concat rest
|
2007-11-03 23:27:58 +00:00
|
|
|
|
|
|
|
-- | Convert definition list item (label, list of blocks) to RTF.
|
|
|
|
definitionListItemToRTF :: Alignment -- ^ alignment
|
|
|
|
-> Int -- ^ indent level
|
|
|
|
-> ([Inline],[Block]) -- ^ list item (list of blocks)
|
|
|
|
-> [Char]
|
|
|
|
definitionListItemToRTF alignment indent (label, items) =
|
|
|
|
let labelText = blockToRTF indent alignment (Plain label)
|
|
|
|
itemsText = concatMap (blockToRTF (indent + listIncrement) alignment) items
|
|
|
|
in labelText ++ itemsText
|
|
|
|
|
|
|
|
-- | Convert list of inline items to RTF.
|
|
|
|
inlineListToRTF :: [Inline] -- ^ list of inlines to convert
|
|
|
|
-> String
|
|
|
|
inlineListToRTF lst = concatMap inlineToRTF lst
|
|
|
|
|
|
|
|
-- | Convert inline item to RTF.
|
|
|
|
inlineToRTF :: Inline -- ^ inline to convert
|
|
|
|
-> String
|
2008-03-26 21:46:12 +00:00
|
|
|
inlineToRTF (Emph lst) = "{\\i " ++ (inlineListToRTF lst) ++ "}"
|
|
|
|
inlineToRTF (Strong lst) = "{\\b " ++ (inlineListToRTF lst) ++ "}"
|
|
|
|
inlineToRTF (Strikeout lst) = "{\\strike " ++ (inlineListToRTF lst) ++ "}"
|
|
|
|
inlineToRTF (Superscript lst) = "{\\super " ++ (inlineListToRTF lst) ++ "}"
|
|
|
|
inlineToRTF (Subscript lst) = "{\\sub " ++ (inlineListToRTF lst) ++ "}"
|
2008-07-15 23:26:06 +00:00
|
|
|
inlineToRTF (SmallCaps lst) = "{\\scaps " ++ (inlineListToRTF lst) ++ "}"
|
2007-11-03 23:27:58 +00:00
|
|
|
inlineToRTF (Quoted SingleQuote lst) =
|
|
|
|
"\\u8216'" ++ (inlineListToRTF lst) ++ "\\u8217'"
|
|
|
|
inlineToRTF (Quoted DoubleQuote lst) =
|
|
|
|
"\\u8220\"" ++ (inlineListToRTF lst) ++ "\\u8221\""
|
|
|
|
inlineToRTF Apostrophe = "\\u8217'"
|
|
|
|
inlineToRTF Ellipses = "\\u8230?"
|
|
|
|
inlineToRTF EmDash = "\\u8212-"
|
|
|
|
inlineToRTF EnDash = "\\u8211-"
|
2008-03-26 21:46:12 +00:00
|
|
|
inlineToRTF (Code str) = "{\\f1 " ++ (codeStringToRTF str) ++ "}"
|
2007-11-03 23:27:58 +00:00
|
|
|
inlineToRTF (Str str) = stringToRTF str
|
2008-08-13 03:02:42 +00:00
|
|
|
inlineToRTF (Math _ str) = inlineListToRTF $ readTeXMath str
|
2008-08-04 03:15:12 +00:00
|
|
|
inlineToRTF (Cite _ lst) = inlineListToRTF lst
|
2008-07-13 23:25:52 +00:00
|
|
|
inlineToRTF (TeX _) = ""
|
|
|
|
inlineToRTF (HtmlInline _) = ""
|
2007-11-03 23:27:58 +00:00
|
|
|
inlineToRTF (LineBreak) = "\\line "
|
|
|
|
inlineToRTF Space = " "
|
2008-07-13 23:25:52 +00:00
|
|
|
inlineToRTF (Link text (src, _)) =
|
2007-11-03 23:27:58 +00:00
|
|
|
"{\\field{\\*\\fldinst{HYPERLINK \"" ++ (codeStringToRTF src) ++
|
|
|
|
"\"}}{\\fldrslt{\\ul\n" ++ (inlineListToRTF text) ++ "\n}}}\n"
|
2008-07-13 23:25:52 +00:00
|
|
|
inlineToRTF (Image _ (source, _)) =
|
2007-11-03 23:27:58 +00:00
|
|
|
"{\\cf1 [image: " ++ source ++ "]\\cf0}"
|
|
|
|
inlineToRTF (Note contents) =
|
|
|
|
"{\\super\\chftn}{\\*\\footnote\\chftn\\~\\plain\\pard " ++
|
|
|
|
(concatMap (blockToRTF 0 AlignDefault) contents) ++ "}"
|