@@ -42,8 +42,9 @@ import Data.Aeson (FromJSON, object, (.=))
4242import Data.Char (isAlphaNum, isAscii, isDigit, isLetter, isPunctuation, ord,
4343 toLower)
4444import Data.List (foldl', intercalate, intersperse, isInfixOf, nubBy,
45− stripPrefix, (\\))
45+ stripPrefix, (\\), uncons)
4646import Data.Maybe (catMaybes, fromMaybe, isJust, mapMaybe, isNothing)
47+import qualified Data.Map as M
4748import Data.Text (Text)
4849import qualified Data.Text as T
4950import Network.URI (unEscapeString)
@@ -63,6 +64,7 @@ import Text.Pandoc.Walk
6364import Text.Pandoc.Writers.Shared
6465import qualified Text.Parsec as P
6566import Text.Printf (printf)
67+import qualified Data.Text.Normalize as Normalize
6668
6769data WriterState =
6870 WriterState { stInNote :: Bool -- true if we're in a note
@@ -318,46 +320,110 @@ data StringContext = TextString
318320
319321-- escape things as needed for LaTeX
320322stringToLaTeX :: PandocMonad m => StringContext -> String -> LW m String
321−stringToLaTeX _ [] = return ""
322−stringToLaTeX ctx (x:xs) = do
323+stringToLaTeX context zs = do
323324 opts <- gets stOptions
324− rest <- stringToLaTeX ctx xs
325− let ligatures = isEnabled Ext_smart opts && ctx == TextString
326− let isUrl = ctx == URLString
327− return $
325+ go opts context $
326+ if writerPreferAscii opts
327+ then T.unpack $ Normalize.normalize Normalize.NFD $ T.pack zs
328+ else zs
329+ where
330+ go _ _ [] = return ""
331+ go opts ctx (x:xs) = do
332+ let ligatures = isEnabled Ext_smart opts && ctx == TextString
333+ let isUrl = ctx == URLString
334+ let mbAccentCmd =
335+ if writerPreferAscii opts && ctx == TextString
336+ then uncons xs >>= \(c,_) -> M.lookup c accents
337+ else Nothing
338+ let emits s =
339+ case mbAccentCmd of
340+ Just cmd -> ((cmd ++ "{" ++ s ++ "}") ++)
341+ <$> go opts ctx (drop 1 xs) -- drop combining accent
342+ Nothing -> (s++) <$> go opts ctx xs
343+ let emitc c =
344+ case mbAccentCmd of
345+ Just cmd -> ((cmd ++ "{" ++ [c] ++ "}") ++)
346+ <$> go opts ctx (drop 1 xs) -- drop combining accent
347+ Nothing -> (c:) <$> go opts ctx xs
328348 case x of
329− '{' -> "\\{" ++ rest
330− '}' -> "\\}" ++ rest
331− '`' | ctx == CodeString -> "\\textasciigrave{}" ++ rest
332− '$' | not isUrl -> "\\$" ++ rest
333− '%' -> "\\%" ++ rest
334− '&' -> "\\&" ++ rest
335− '_' | not isUrl -> "\\_" ++ rest
336− '#' -> "\\#" ++ rest
337− '-' | not isUrl -> case xs of
338− -- prevent adjacent hyphens from forming ligatures
339− ('-':_) -> "-\\/" ++ rest
340− _ -> '-' : rest
341− '~' | not isUrl -> "\\textasciitilde{}" ++ rest
342− '^' -> "\\^{}" ++ rest
343− '\\'| isUrl -> '/' : rest -- NB. / works as path sep even on Windows
344− | otherwise -> "\\textbackslash{}" ++ rest
345− '|' | not isUrl -> "\\textbar{}" ++ rest
346− '<' -> "\\textless{}" ++ rest
347− '>' -> "\\textgreater{}" ++ rest
348− '[' -> "{[}" ++ rest -- to avoid interpretation as
349− ']' -> "{]}" ++ rest -- optional arguments
350− '\'' | ctx == CodeString -> "\\textquotesingle{}" ++ rest
351− '\160' -> "~" ++ rest
352− '\x202F' -> "\\," ++ rest
353− '\x2026' -> "\\ldots{}" ++ rest
354− '\x2018' | ligatures -> "`" ++ rest
355− '\x2019' | ligatures -> "'" ++ rest
356− '\x201C' | ligatures -> "``" ++ rest
357− '\x201D' | ligatures -> "''" ++ rest
358− '\x2014' | ligatures -> "---" ++ rest
359− '\x2013' | ligatures -> "--" ++ rest
360− _ -> x : rest
349+ '{' -> emits "\\{"
350+ '}' -> emits "\\}"
351+ '`' | ctx == CodeString -> emits "\\textasciigrave{}"
352+ '$' | not isUrl -> emits "\\$"
353+ '%' -> emits "\\%"
354+ '&' -> emits "\\&"
355+ '_' | not isUrl -> emits "\\_"
356+ '#' -> emits "\\#"
357+ '-' | not isUrl -> case xs of
358+ -- prevent adjacent hyphens from forming ligatures
359+ ('-':_) -> emits "-\\/"
360+ _ -> emitc '-'
361+ '~' | not isUrl -> emits "\\textasciitilde{}"
362+ '^' -> emits "\\^{}"
363+ '\\'| isUrl -> emitc '/' -- NB. / works as path sep even on Windows
364+ | otherwise -> emits "\\textbackslash{}"
365+ '|' | not isUrl -> emits "\\textbar{}"
366+ '<' -> emits "\\textless{}"
367+ '>' -> emits "\\textgreater{}"
368+ '[' -> emits "{[}" -- to avoid interpretation as
369+ ']' -> emits "{]}" -- optional arguments
370+ '\'' | ctx == CodeString -> emits "\\textquotesingle{}"
371+ '\160' -> emits "~"
372+ '\x202F' -> emits "\\,"
373+ '\x2026' -> emits "\\ldots{}"
374+ '\x2018' | ligatures -> emits "`"
375+ '\x2019' | ligatures -> emits "'"
376+ '\x201C' | ligatures -> emits "``"
377+ '\x201D' | ligatures -> emits "''"
378+ '\x2014' | ligatures -> emits "---"
379+ '\x2013' | ligatures -> emits "--"
380+ _ | writerPreferAscii opts
381+ -> case x of
382+ 'ı' -> emits "\\i "
383+ 'ȷ' -> emits "\\j "
384+ 'å' -> emits "\\aa "
385+ 'Å' -> emits "\\AA "
386+ 'ß' -> emits "\\ss "
387+ 'ø' -> emits "\\o "
388+ 'Ø' -> emits "\\O "
389+ 'Ł' -> emits "\\L "
390+ 'ł' -> emits "\\l "
391+ 'æ' -> emits "\\ae "
392+ 'Æ' -> emits "\\AE "
393+ 'œ' -> emits "\\oe "
394+ 'Œ' -> emits "\\OE "
395+ '£' -> emits "\\pounds "
396+ '€' -> emits "\\euro "
397+ '©' -> emits "\\copyright "
398+ _ -> emitc x
399+ | otherwise -> emitc x
400+
401+accents :: M.Map Char String
402+accents = M.fromList
403+ [ ('\779' , "\\H")
404+ , ('\768' , "\\`")
405+ , ('\769' , "\\'")
406+ , ('\770' , "\\^")
407+ , ('\771' , "\\~")
408+ , ('\776' , "\\\"")
409+ , ('\775' , "\\.")
410+ , ('\772' , "\\=")
411+ , ('\781' , "\\|")
412+ , ('\817' , "\\b")
413+ , ('\807' , "\\c")
414+ , ('\783' , "\\G")
415+ , ('\777' , "\\h")
416+ , ('\803' , "\\d")
417+ , ('\785' , "\\f")
418+ , ('\778' , "\\r")
419+ , ('\865' , "\\t")
420+ , ('\782' , "\\U")
421+ , ('\780' , "\\v")
422+ , ('\774' , "\\u")
423+ , ('\808' , "\\k")
424+ , ('\785' , "\\newtie")
425+ , ('\8413', "\\textcircled")
426+ ]
361427
362428toLabel :: PandocMonad m => String -> LW m String
363429toLabel z = go `fmap` stringToLaTeX URLString z