module Language.LaTeX.Printer where
import Data.Monoid
import Data.List (intersperse)
import Data.Ratio (numerator, denominator)
import Data.Foldable (foldMap)
import Data.Char
import GHC.Float (formatRealFloat, FFFormat(FFFixed))
import Language.LaTeX.Types
import Language.LaTeX.Checker (checkDocument)
import Language.LaTeX.Builder.MonoidUtils
optionals :: [a] -> Arg a
optionals [] = NoArg
optionals xs = Optional xs
text :: String -> ShowS
text = showString
between :: String -> String -> ShowS -> ShowS
between opening closing x = text opening ⊕ x ⊕ text closing
braces, brackets, parens :: ShowS -> ShowS
braces = between "{" "}"
brackets = between "[" "]"
parens = between "(" ")"
nl, nl2, irrNl, backslash, sp :: ShowS
backslash = text "\\"
nl = text "\n"
nl2 = nl ⊕ nl
sp = text " "
irrNl = text "%\n"
($$) :: ShowS -> ShowS -> ShowS
($$) x y = x ⊕ nl ⊕ y
($$$) :: ShowS -> ShowS -> ShowS
($$$) x y = x ⊕ nl2 ⊕ y
vcat, vcat2 :: [ShowS] -> ShowS
vcat = mconcat . intersperse nl
vcat2 = mconcat . intersperse nl2
ppNamed :: Named ShowS -> ShowS
ppNamed (Named name val)
| null name = val
| otherwise = text name ⊕ text "=" ⊕ val
commas :: [ShowS] -> ShowS
commas = mconcat . intersperse (text ",")
ppArg :: Arg ShowS -> ShowS
ppArg NoArg = id
ppArg StarArg = text "*"
ppArg (Optional []) = error "ppArg: impossible: Optional []"
ppArg (NamedOpts []) = error "ppArg: impossible: NamedOpts []"
ppArg (Mandatory xs) = braces . commas $ xs
ppArg (Optional xs) = brackets . commas $ xs
ppArg (NamedArgs xs) = braces . commas . map ppNamed $ xs
ppArg (NamedOpts xs) = brackets . commas . map ppNamed $ xs
ppArg (Coordinates x y) = parens (x ⊕ text " " ⊕ y)
ppArg (LiftArg x) = x
ppArg (RawArg x) = text x
ppArg (PackageAction _) = id
ppEnv :: String -> [Arg ShowS] -> ShowS -> ShowS
ppEnv envName args contents =
backslash ⊕ begin ⊕ braces envNameS ⊕ mconcat (map ppArg args)
$$
contents
$$
backslash ⊕ end ⊕ braces envNameS
where envNameS = text envName
begin = text "begin"
end = text "end"
ppCmdArgs :: String -> [Arg ShowS] -> ShowS
ppCmdArgs cmdName args = backslash ⊕ text cmdName ⊕ mconcat (map ppArg args)
ppDecl :: String -> ShowS -> ShowS
ppDecl declName declArgs = backslash ⊕ text declName ⊕ declArgs ⊕ text " "
ppTexDecl :: TexDcl -> ShowS
ppTexDecl (TexDcl declName declArgs) = ppDecl declName (foldMap (ppArg . fmap ppAny) declArgs)
ppMathDecl :: MathDcl -> ShowS
ppMathDecl (MathDcl declName) = ppDecl declName ø
pp :: LatexItm -> ShowS
pp (LatexCmdArgs cmdName args) = ppCmdArgs cmdName $ map (fmap pp) args
pp (LatexCmdAnyArgs cmdName args) = ppCmdArgs cmdName $ map (fmap ppAny) args
pp (TexDecls decls) = foldMap ppTexDecl decls
pp (TexCmdNoArg cmdName) = braces $ ppCmdArgs cmdName []
pp (TexCmdArg cmdName contents)
= braces (backslash ⊕ text cmdName ⊕ text " " ⊕ pp contents)
pp (Environment envName args contents) = ppEnv envName (map (fmap ppAny) args) $ ppAny contents
pp (RawTex s) = text s
pp (LatexCast (MathItm m)) = text "$ " ⊕ ppMath m ⊕ text " $"
pp (LatexCast x) = ppAny x
pp (TexGroup t) = braces $ pp t
pp LatexEmpty = ø
pp (LatexAppend x y) = pp x ⊕ pp y
pp (LatexNote key note t) = ppNote key note pp t
ppParMode :: ParItm -> ShowS
ppParMode (ParCast (MathItm m)) = text "\\[ " ⊕ ppMath m ⊕ text " \\]"
ppParMode (ParCast t) = ppAny t
ppParMode (ParCmdArgs cmdName args) = ppCmdArgs cmdName $ map (fmap ppAny) args
ppParMode (RawParMode x) = text x
ppParMode (ParGroup p) = braces $ ppParMode p
ppParMode (ParEnv envName args contents)
= ppEnv envName (map (fmap ppAny) args) $ ppAny contents
ppParMode (Tabular specs rows) =
ppEnv "tabular" [Mandatory . (:[]) . mconcat $ map (ppRowSpec . fmap pp) specs] (ppRows pp rows)
ppParMode (ParConcat contents) = vcat2 $ map ppParMode contents
ppParMode (ParNote key note t) = ppNote key note ppParMode t
ppMath :: MathItm -> ShowS
ppMath (MathDecls decls) = foldMap ppMathDecl decls
ppMath (MathCmdArgs cmdName args) = ppCmdArgs cmdName $ map (fmap ppAny) args
ppMath (RawMath s) = text s
ppMath (MathCast x) = ppAny x
ppMath (MathRat r) | denominator r == 1 = shows (numerator r)
| otherwise = shows (numerator r) ⊕ text " / " ⊕ shows (denominator r)
ppMath (MathArray specs rows) =
ppEnv "array" [Mandatory . (:[]) . mconcat $ map (ppRowSpec . fmap ppMath) specs] (ppRows ppMath rows)
ppMath (MathGroup m) = braces $ ppMath m
ppMath (MathConcat ms) = mconcat $ map ppMath ms
ppMath (MathUnOp op m) = text op ⊕ sp ⊕ ppMath m
ppMath (MathBinOp op l r) = parens (ppMath l ⊕ sp ⊕ text op ⊕ sp ⊕ ppMath r)
ppMath (MathNote key note m) = ppNote key note ppMath m
ppAny :: AnyItm -> ShowS
ppAny (PreambleItm x) = ppPreamble x
ppAny (LatexItm x) = pp x
ppAny (MathItm x) = ppMath x
ppAny (ParItm x) = ppParMode x
ppAny (LocSpecs locs) = text . map locSpecChar $ locs
ppAny (Key key) = text . getKey $ key
ppAny (Length len) = ppTexLength len
ppAny (Coord (MkCoord x y)) = ppTexLength x ⊕ text " " ⊕ ppTexLength y
ppAny (SaveBin bin) = text $ "\\hlatexSaveBin" ++ (map enc . show $ unsafeGetSaveBin bin)
where enc i = chr (ord 'a' + digitToInt i)
ppAny (PackageName pkg) = text $ getPkgName pkg
ppRowSpec :: RowSpec ShowS -> ShowS
ppRowSpec Rc = text "c"
ppRowSpec Rl = text "l"
ppRowSpec Rr = text "r"
ppRowSpec Rvline = text "|"
ppRowSpec (Rtext x) = text "@" ⊕ braces x
ppRows :: (a -> ShowS) -> [Row a] -> ShowS
ppRows _ []
= ø
ppRows ppCell (Cells cells : rows)
= (mconcat . intersperse (text " & ") . map ppCell $ cells)
⊕ (if null rows then ø else backslash ⊕ backslash $$ ppRows ppCell rows)
ppRows ppCell (Hline : rows)
= backslash ⊕ text "hline " ⊕ ppRows ppCell rows
ppRows ppCell (Cline c1 c2 : rows)
= ppCmdArgs "cline" [Mandatory . (:[]) . text $ show c1 ++ "-" ++ show c2] ⊕ ppRows ppCell rows
unitName :: TexUnit -> String
unitName u =
case u of
Cm -> "cm"
Mm -> "mm"
Em -> "em"
Ex -> "ex"
Pt -> "pt"
Pc -> "pc"
In -> "in"
Sp -> "sp"
Bp -> "bp"
Dd -> "dd"
Cc -> "cc"
Mu -> "mu"
ppTexLength :: LatexLength -> ShowS
ppTexLength s =
case s of
LengthCmd cmd -> ppCmdArgs cmd []
LengthCmdRatArg cmd r -> braces $ ppCmdArgs cmd [Mandatory . (:[]) $ showr r]
LengthScaledBy _ (LengthScaledBy _ _) ->
error "broken invariant: nested LengthScaledBy"
LengthScaledBy r l -> showr r ⊕ ppTexLength l
LengthCst munit r -> showr r ⊕ foldMap (text . unitName) munit
where showr r | denominator r == 1 = shows $ numerator r
| otherwise = text $ formatRealFloat FFFixed (Just 2) (fromRational r :: Double)
ppPreamble :: PreambleItm -> ShowS
ppPreamble (PreambleCmdArgs cmdName args) = ppCmdArgs cmdName $ map (fmap ppAny) args
ppPreamble (PreambleEnv envName args contents) = ppEnv envName (map (fmap ppAny) args) (ppAny contents)
ppPreamble (PreambleCast x) = ppAny x
ppPreamble (PreambleConcat ps) = vcat $ map ppPreamble ps
ppPreamble (RawPreamble raw) = text raw
ppPreamble (PreambleNote key note p) = ppNote key note ppPreamble p
ppNote :: Key -> Note -> (a -> ShowS) -> a -> ShowS
ppNote (MkKey key) note ppElt elt = irrNl ⊕ comment (key ⊕ ": " ⊕ showNote note) ⊕ ppElt elt ⊕ irrNl
where showNote (TextNote s) = s
showNote (IntNote i) = show i
showNote (LocNote loc) = showLoc loc
comment = mconcat . map (text . ('%':) . (⊕ "\n") . stripRight) . lines
stripRight = reverse . dropWhile isSpace . reverse
showLoc :: Loc -> String
showLoc (Loc fp line char) = unwords [fp, ":", show line, ":", show char]
showDocClassKind :: DocumentClassKind -> String
showDocClassKind Article = "article"
showDocClassKind Book = "book"
showDocClassKind Report = "report"
showDocClassKind Letter = "letter"
showDocClassKind (OtherDocumentClassKind x) = x
preambOfDocClass :: DocumentClss -> PreambleItm
preambOfDocClass (DocClass kind opts) =
PreambleCmdArgs "documentclass"
[optionals opts, Mandatory . (:[]) . LatexItm . RawTex $ showDocClassKind kind]
ppDocument :: Document -> ShowS
ppDocument (Document docClass preamb doc) =
ppPreamble (preambOfDocClass docClass) $$$
ppPreamble preamb $$$
ppEnv "document" [] (nl ⊕ ppParMode doc ⊕ nl) ⊕ nl
showsLaTeX :: LatexM Document -> Either ErrorMessage ShowS
showsLaTeX mdoc = do
doc <- runLatexM mdoc
maybe (return ()) Left $ checkDocument doc
return . ppDocument $ doc
showLaTeX :: LatexM Document -> Either ErrorMessage String
showLaTeX = fmap ($"") . showsLaTeX