{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} module Skylighting.Format.LaTeX ( formatLaTeXInline , formatLaTeXBlock , styleToLaTeX ) where import Control.Monad (mplus) import Data.Char (isSpace) import Data.List (sort) import qualified Data.Map as Map import Data.Text (Text) import qualified Data.Text as Text import Skylighting.Types import Text.Printf #if !MIN_VERSION_base(4,11,0) import Data.Semigroup #endif formatLaTeX :: Bool -> [SourceLine] -> Text formatLaTeX inline = Text.intercalate (Text.singleton '\n') . map (sourceLineToLaTeX inline) -- | Formats tokens as LaTeX using custom commands inside -- @|@ characters. Assumes that @|@ is defined as a short verbatim -- command by the macros produced by 'styleToLaTeX'. -- A @KeywordTok@ is rendered using @\\KeywordTok{..}@, and so on. formatLaTeXInline :: FormatOptions -> [SourceLine] -> Text formatLaTeXInline _opts ls = "\\VERB|" <> formatLaTeX True ls <> "|" sourceLineToLaTeX :: Bool -> SourceLine -> Text sourceLineToLaTeX inline = mconcat . map (tokenToLaTeX inline) tokenToLaTeX :: Bool -> Token -> Text tokenToLaTeX inline (NormalTok, txt) | Text.all isSpace txt = escapeLaTeX inline txt tokenToLaTeX inline (toktype, txt) = Text.cons '\\' (Text.pack (show toktype) <> "{" <> escapeLaTeX inline txt <> "}") escapeLaTeX :: Bool -> Text -> Text escapeLaTeX inline = Text.concatMap escapeLaTeXChar where escapeLaTeXChar c = case c of '\\' -> "\\textbackslash{}" '{' -> "\\{" '}' -> "\\}" '|' | inline -> "\\VerbBar{}" -- used in inline verbatim '_' -> "\\_" '&' -> "\\&" '%' -> "\\%" '#' -> "\\#" '`' -> "\\textasciigrave{}" '\'' -> "\\textquotesingle{}" '-' -> "{-}" -- prevent ligatures '~' -> "\\textasciitilde{}" '^' -> "\\^{}" '>' -> "\\textgreater{}" '<' -> "\\textless{}" _ -> Text.singleton c -- LaTeX -- | Format tokens as a LaTeX @Highlighting@ environment inside a -- @Shaded@ environment. @Highlighting@ and @Shaded@ are -- defined by the macros produced by 'styleToLaTeX'. @Highlighting@ -- is a verbatim environment using @fancyvrb@; @\\@, @{@, and @}@ -- have their normal meanings inside this environment, so that -- formatting commands work. @Shaded@ is either nothing -- (if the style's background color is default) or a @snugshade@ -- environment from @framed@, providing a background color -- for the whole code block, even if it spans multiple pages. formatLaTeXBlock :: FormatOptions -> [SourceLine] -> Text formatLaTeXBlock opts ls = Text.unlines ["\\begin{Shaded}" ,"\\begin{Highlighting}[" <> (if numberLines opts then "numbers=left," <> (if startNumber opts == 1 then "" else ",firstnumber=" <> Text.pack (show (startNumber opts))) <> "," else Text.empty) <> "]" ,formatLaTeX False ls ,"\\end{Highlighting}" ,"\\end{Shaded}"] -- | Converts a 'Style' to a set of LaTeX macro definitions, -- which should be placed in the document's preamble. -- Note: default LaTeX setup doesn't allow boldface typewriter font. -- To make boldface work in styles, you need to use a different typewriter -- font. This will work for computer modern: -- -- > \DeclareFontShape{OT1}{cmtt}{bx}{n}{<5><6><7><8><9><10><10.95><12><14.4><17.28><20.74><24.88>cmttb10}{} -- -- Or, with xelatex: -- -- > \usepackage{fontspec} -- > \setmainfont[SmallCapsFont={* Caps}]{Latin Modern Roman} -- > \setsansfont{Latin Modern Sans} -- > \setmonofont[SmallCapsFont={Latin Modern Mono Caps}]{Latin Modern Mono Light} -- styleToLaTeX :: Style -> Text styleToLaTeX f = Text.unlines $ [ "\\usepackage{color}" , "\\usepackage{fancyvrb}" , "\\newcommand{\\VerbBar}{|}" , "\\newcommand{\\VERB}{\\Verb[commandchars=\\\\\\{\\}]}" , "\\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\\\\{\\}}" , "% Add ',fontsize=\\small' for more characters per line" ] ++ (case backgroundColor f of Nothing -> ["\\newenvironment{Shaded}{}{}"] Just (RGB r g b) -> ["\\usepackage{framed}" ,Text.pack (printf "\\definecolor{shadecolor}{RGB}{%d,%d,%d}" r g b) ,"\\newenvironment{Shaded}{\\begin{snugshade}}{\\end{snugshade}}"]) ++ sort (map (macrodef (defaultColor f) (Map.toList (tokenStyles f))) (enumFromTo KeywordTok NormalTok)) macrodef :: Maybe Color -> [(TokenType, TokenStyle)] -> TokenType -> Text macrodef defaultcol tokstyles tokt = "\\newcommand{\\" <> Text.pack (show tokt) <> "}[1]{" <> Text.pack (co . ul . bf . it . bg $ "#1") <> "}" where tokf = case lookup tokt tokstyles of Nothing -> defStyle Just x -> x ul x = if tokenUnderline tokf then "\\underline{" <> x <> "}" else x it x = if tokenItalic tokf then "\\textit{" <> x <> "}" else x bf x = if tokenBold tokf then "\\textbf{" <> x <> "}" else x bcol = fromColor `fmap` tokenBackground tokf :: Maybe (Double, Double, Double) bg x = case bcol of Nothing -> x Just (r, g, b) -> printf "\\colorbox[rgb]{%0.2f,%0.2f,%0.2f}{%s}" r g b x col = fromColor `fmap` (tokenColor tokf `mplus` defaultcol) :: Maybe (Double, Double, Double) co x = case col of Nothing -> x Just (r, g, b) -> printf "\\textcolor[rgb]{%0.2f,%0.2f,%0.2f}{%s}" r g b x