@@ -18,10 +18,9 @@ module Text.Pandoc.Writers.LaTeX (
1818 writeLaTeX
1919 , writeBeamer
2020 ) where
21−import Control.Applicative ((<|>))
2221import Control.Monad.State.Strict
23−import Data.Char (isAlphaNum, isAscii, isDigit, isLetter, isSpace, ord)
24−import Data.List (intersperse, nubBy, (\\), uncons)
22+import Data.Char (isDigit)
23+import Data.List (intersperse, nubBy, (\\))
2524import Data.Maybe (catMaybes, fromMaybe, isJust, mapMaybe, isNothing)
2625import qualified Data.Map as M
2726import Data.Text (Text)
@@ -33,7 +32,7 @@ import Text.Pandoc.BCP47 (Lang (..), getLang, renderLang)
3332import Text.Pandoc.Class.PandocMonad (PandocMonad, report, toLang)
3433import Text.Pandoc.Definition
3534import Text.Pandoc.Highlighting (formatLaTeXBlock, formatLaTeXInline, highlight,
36− styleToLaTeX, toListingsLanguage)
35+ styleToLaTeX)
3736import Text.Pandoc.ImageSize
3837import Text.Pandoc.Logging
3938import Text.Pandoc.Options
@@ -46,11 +45,12 @@ import Text.Pandoc.Writers.LaTeX.Table (tableToLaTeX)
4645import Text.Pandoc.Writers.LaTeX.Citation (citationsToNatbib,
4746 citationsToBiblatex)
4847import Text.Pandoc.Writers.LaTeX.Types (LW, WriterState (..), startingState)
49−import Text.Pandoc.Writers.LaTeX.Lang (toPolyglossiaEnv, toPolyglossia,
50− toBabel)
48+import Text.Pandoc.Writers.LaTeX.Lang (toPolyglossia, toBabel)
49+import Text.Pandoc.Writers.LaTeX.Util (stringToLaTeX, StringContext(..),
50+ toLabel, inCmd,
51+ wrapDiv, hypertarget, labelFor,
52+ getListingsLanguage, mbBraced)
5153import Text.Pandoc.Writers.Shared
52−import Text.Printf (printf)
53−import qualified Data.Text.Normalize as Normalize
5454import qualified Text.Pandoc.Writers.AnnotatedTable as Ann
5555
5656-- | Convert Pandoc to LaTeX.
@@ -244,152 +244,6 @@ pandocToLaTeX options (Pandoc meta blocks) = do
244244 Nothing -> main
245245 Just tpl -> renderTemplate tpl context'
246246
247−data StringContext = TextString
248− | URLString
249− | CodeString
250− deriving (Eq)
251−
252−-- escape things as needed for LaTeX
253−stringToLaTeX :: PandocMonad m => StringContext -> Text -> LW m Text
254−stringToLaTeX context zs = do
255− opts <- gets stOptions
256− return $ T.pack $
257− foldr (go opts context) mempty $ T.unpack $
258− if writerPreferAscii opts
259− then Normalize.normalize Normalize.NFD zs
260− else zs
261− where
262− go :: WriterOptions -> StringContext -> Char -> String -> String
263− go opts ctx x xs =
264− let ligatures = isEnabled Ext_smart opts && ctx == TextString
265− isUrl = ctx == URLString
266− mbAccentCmd =
267− if writerPreferAscii opts && ctx == TextString
268− then uncons xs >>= \(c,_) -> lookupAccent c
269− else Nothing
270− emits s =
271− case mbAccentCmd of
272− Just cmd ->
273− cmd <> "{" <> s <> "}" <> drop 1 xs -- drop combining accent
274− Nothing -> s <> xs
275− emitc c =
276− case mbAccentCmd of
277− Just cmd ->
278− cmd <> "{" <> [c] <> "}" <> drop 1 xs -- drop combining accent
279− Nothing -> c : xs
280− emitcseq cs =
281− case xs of
282− c:_ | isLetter c
283− , ctx == TextString
284− -> cs <> " " <> xs
285− | isSpace c -> cs <> "{}" <> xs
286− | ctx == TextString
287− -> cs <> xs
288− _ -> cs <> "{}" <> xs
289− emitquote cs =
290− case xs of
291− '`':_ -> cs <> "\\," <> xs -- add thin space
292− '\'':_ -> cs <> "\\," <> xs -- add thin space
293− _ -> cs <> xs
294− in case x of
295− '?' | ligatures -> -- avoid ?` ligature
296− case xs of
297− '`':_ -> emits "?{}"
298− _ -> emitc x
299− '!' | ligatures -> -- avoid !` ligature
300− case xs of
301− '`':_ -> emits "!{}"
302− _ -> emitc x
303− '{' -> emits "\\{"
304− '}' -> emits "\\}"
305− '`' | ctx == CodeString -> emitcseq "\\textasciigrave"
306− '$' | not isUrl -> emits "\\$"
307− '%' -> emits "\\%"
308− '&' -> emits "\\&"
309− '_' | not isUrl -> emits "\\_"
310− '#' -> emits "\\#"
311− '-' | not isUrl -> case xs of
312− -- prevent adjacent hyphens from forming ligatures
313− ('-':_) -> emits "-\\/"
314− _ -> emitc '-'
315− '~' | not isUrl -> emitcseq "\\textasciitilde"
316− '^' -> emits "\\^{}"
317− '\\'| isUrl -> emitc '/' -- NB. / works as path sep even on Windows
318− | otherwise -> emitcseq "\\textbackslash"
319− '|' | not isUrl -> emitcseq "\\textbar"
320− '<' -> emitcseq "\\textless"
321− '>' -> emitcseq "\\textgreater"
322− '[' -> emits "{[}" -- to avoid interpretation as
323− ']' -> emits "{]}" -- optional arguments
324− '\'' | ctx == CodeString -> emitcseq "\\textquotesingle"
325− '\160' -> emits "~"
326− '\x200B' -> emits "\\hspace{0pt}" -- zero-width space
327− '\x202F' -> emits "\\,"
328− '\x2026' -> emitcseq "\\ldots"
329− '\x2018' | ligatures -> emitquote "`"
330− '\x2019' | ligatures -> emitquote "'"
331− '\x201C' | ligatures -> emitquote "``"
332− '\x201D' | ligatures -> emitquote "''"
333− '\x2014' | ligatures -> emits "---"
334− '\x2013' | ligatures -> emits "--"
335− _ | writerPreferAscii opts
336− -> case x of
337− 'ı' -> emitcseq "\\i"
338− 'ȷ' -> emitcseq "\\j"
339− 'å' -> emitcseq "\\aa"
340− 'Å' -> emitcseq "\\AA"
341− 'ß' -> emitcseq "\\ss"
342− 'ø' -> emitcseq "\\o"
343− 'Ø' -> emitcseq "\\O"
344− 'Ł' -> emitcseq "\\L"
345− 'ł' -> emitcseq "\\l"
346− 'æ' -> emitcseq "\\ae"
347− 'Æ' -> emitcseq "\\AE"
348− 'œ' -> emitcseq "\\oe"
349− 'Œ' -> emitcseq "\\OE"
350− '£' -> emitcseq "\\pounds"
351− '€' -> emitcseq "\\euro"
352− '©' -> emitcseq "\\copyright"
353− _ -> emitc x
354− | otherwise -> emitc x
355−
356−lookupAccent :: Char -> Maybe String
357−lookupAccent '\779' = Just "\\H"
358−lookupAccent '\768' = Just "\\`"
359−lookupAccent '\769' = Just "\\'"
360−lookupAccent '\770' = Just "\\^"
361−lookupAccent '\771' = Just "\\~"
362−lookupAccent '\776' = Just "\\\""
363−lookupAccent '\775' = Just "\\."
364−lookupAccent '\772' = Just "\\="
365−lookupAccent '\781' = Just "\\|"
366−lookupAccent '\817' = Just "\\b"
367−lookupAccent '\807' = Just "\\c"
368−lookupAccent '\783' = Just "\\G"
369−lookupAccent '\777' = Just "\\h"
370−lookupAccent '\803' = Just "\\d"
371−lookupAccent '\785' = Just "\\f"
372−lookupAccent '\778' = Just "\\r"
373−lookupAccent '\865' = Just "\\t"
374−lookupAccent '\782' = Just "\\U"
375−lookupAccent '\780' = Just "\\v"
376−lookupAccent '\774' = Just "\\u"
377−lookupAccent '\808' = Just "\\k"
378−lookupAccent '\8413' = Just "\\textcircled"
379−lookupAccent _ = Nothing
380−
381−toLabel :: PandocMonad m => Text -> LW m Text
382−toLabel z = go `fmap` stringToLaTeX URLString z
383− where
384− go = T.concatMap $ \x -> case x of
385− _ | (isLetter x || isDigit x) && isAscii x -> T.singleton x
386− | x `elemText` "_-+=:;." -> T.singleton x
387− | otherwise -> T.pack $ "ux" <> printf "%x" (ord x)
388−
389−-- | Puts contents into LaTeX command.
390−inCmd :: Text -> Doc Text -> Doc Text
391−inCmd cmd contents = char '\\' <> literal cmd <> braces contents
392−
393247toSlides :: PandocMonad m => [Block] -> LW m [Block]
394248toSlides bs = do
395249 opts <- gets stOptions
@@ -854,91 +708,6 @@ sectionHeader classes ident level lst = do
854708 braces txtNoNotes
855709 else empty
856710
857−mapAlignment :: Text -> Text
858−mapAlignment a = case a of
859− "top" -> "T"
860− "top-baseline" -> "t"
861− "bottom" -> "b"
862− "center" -> "c"
863− _ -> a
864−
865−wrapDiv :: PandocMonad m => Attr -> Doc Text -> LW m (Doc Text)
866−wrapDiv (_,classes,kvs) t = do
867− beamer <- gets stBeamer
868− let align dir txt = inCmd "begin" dir $$ txt $$ inCmd "end" dir
869− lang <- toLang $ lookup "lang" kvs
870− let wrapColumns = if beamer && "columns" `elem` classes
871− then \contents ->
872− let valign = maybe "T" mapAlignment (lookup "align" kvs)
873− totalwidth = maybe [] (\x -> ["totalwidth=" <> x])
874− (lookup "totalwidth" kvs)
875− onlytextwidth = filter ("onlytextwidth" ==) classes
876− options = text $ T.unpack $ T.intercalate "," $
877− valign : totalwidth ++ onlytextwidth
878− in inCmd "begin" "columns" <> brackets options
879− $$ contents
880− $$ inCmd "end" "columns"
881− else id
882− wrapColumn = if beamer && "column" `elem` classes
883− then \contents ->
884− let valign =
885− maybe ""
886− (brackets . text . T.unpack . mapAlignment)
887− (lookup "align" kvs)
888− w = maybe "0.48" fromPct (lookup "width" kvs)
889− in inCmd "begin" "column" <>
890− valign <>
891− braces (literal w <> "\\textwidth")
892− $$ contents
893− $$ inCmd "end" "column"
894− else id
895− fromPct xs =
896− case T.unsnoc xs of
897− Just (ds, '%') -> case safeRead ds of
898− Just digits -> showFl (digits / 100 :: Double)
899− Nothing -> xs
900− _ -> xs
901− wrapDir = case lookup "dir" kvs of
902− Just "rtl" -> align "RTL"
903− Just "ltr" -> align "LTR"
904− _ -> id
905− wrapLang txt = case lang of
906− Just lng -> let (l, o) = toPolyglossiaEnv lng
907− ops = if T.null o
908− then ""
909− else brackets $ literal o
910− in inCmd "begin" (literal l) <> ops
911− $$ blankline <> txt <> blankline
912− $$ inCmd "end" (literal l)
913− Nothing -> txt
914− return $ wrapColumns . wrapColumn . wrapDir . wrapLang $ t
915−
916−hypertarget :: PandocMonad m => Bool -> Text -> Doc Text -> LW m (Doc Text)
917−hypertarget _ "" x = return x
918−hypertarget addnewline ident x = do
919− ref <- literal `fmap` toLabel ident
920− return $ text "\\hypertarget"
921− <> braces ref
922− <> braces ((if addnewline && not (isEmpty x)
923− then "%" <> cr
924− else empty) <> x)
925−
926−labelFor :: PandocMonad m => Text -> LW m (Doc Text)
927−labelFor "" = return empty
928−labelFor ident = do
929− ref <- literal `fmap` toLabel ident
930− return $ text "\\label" <> braces ref
931−
932−-- Determine listings language from list of class attributes.
933−getListingsLanguage :: [Text] -> Maybe Text
934−getListingsLanguage xs
935− = foldr ((<|>) . toListingsLanguage) Nothing xs
936−
937−mbBraced :: Text -> Text
938−mbBraced x = if not (T.all isAlphaNum x)
939− then "{" <> x <> "}"
940− else x
941−
942711-- | Convert list of inline elements to LaTeX.
943712inlineListToLaTeX :: PandocMonad m
944713 => [Inline] -- ^ Inlines to convert
@@ -963,10 +732,6 @@ inlineListToLaTeX lst = hcat <$>
963732 fixInitialLineBreaks xs
964733 fixInitialLineBreaks xs = xs
965734
966−isQuoted :: Inline -> Bool
967−isQuoted (Quoted _ _) = True
968−isQuoted _ = False
969−
970735-- | Convert inline element to LaTeX
971736inlineToLaTeX :: PandocMonad m
972737 => Inline -- ^ Inline to convert
@@ -1102,6 +867,9 @@ inlineToLaTeX (Quoted qt lst) = do
1102867 if isEnabled Ext_smart opts
1103868 then char '`' <> inner <> char '\''
1104869 else char '\x2018' <> inner <> char '\x2019'
870+ where
871+ isQuoted (Quoted _ _) = True
872+ isQuoted _ = False
1105873inlineToLaTeX (Str str) = do
1106874 setEmptyLine False
1107875 liftM literal $ stringToLaTeX TextString str