packages feed

marxup-3.0.0: MarXup/Tex.hs

{-# LANGUAGE GeneralizedNewtypeDeriving, TypeFamilies,TypeSynonymInstances,FlexibleInstances, PackageImports #-}

module MarXup.Tex where

import MarXup
import "mtl" Control.Monad.Reader
import "mtl" Control.Monad.RWS
import Control.Applicative
import GHC.Exts( IsString(..) )
import Data.List (intersperse)
import MarXup.MultiRef
import System.Process
import System.Directory (doesFileExist)

newtype Tex a = Tex {fromTex :: Multi a}
  deriving (Monad, MonadFix, Applicative, Functor)

---------------------------------
-- MarXup interface
instance Textual Tex where
    textual s = tex $ concatMap escape s

kern :: String -> TeX
kern x = braces $ tex $ "\\kern " ++ x

escape '\\' = "\\ensuremath{\\backslash{}}"
escape '~' = "\\ensuremath{\\sim{}}"
escape '<' = "\\ensuremath{<}"
escape '>' = "\\ensuremath{>}"
escape c | c `elem` "#^_{}&$%" = '\\':c:[]
escape c = [c]

instance Element (Tex a) where
  type Target (Tex a) = Tex a
  element = id

texInMode ::  Mode -> String ->TeX
texInMode mode s = whenMode mode $ Tex $ raw s

tex :: String -> TeX
tex = texInMode (`elem` [Regular,InsideBox])

texComment :: String -> TeX
texComment s =
  forM_ (lines s) $ \line ->
    tex $ "% " <> line <> "\n"

type TeX = Tex ()

reference :: Label -> Tex ()
reference l = tex (show l)

instance Monoid (TeX) where
  mempty = textual ""
  mappend = (>>)

instance IsString (TeX) where
  fromString = textual

texLn :: String -> TeX
texLn s = tex s >> tex "\n"

texLines :: [String] -> Tex ()
texLines = mapM_ texLn

genParen :: String -> Tex a -> Tex a
genParen [l,r] x = tex [l] *> x <* tex [r]

braces,brackets :: Tex a -> Tex a
braces = genParen "{}"
brackets = genParen "[]"

backslash :: TeX
backslash = tex ['\\']

nil :: TeX
nil = braces (tex "")

-- | Command with no argument
cmd0 :: String -> Tex ()
cmd0 c = cmdn' c [] [] >> return ()

-- | Command with one argument
cmd :: String -> Tex a -> Tex a
cmd c = cmd' c []

-- | Command with options
cmd' :: String -> [String] -> Tex b -> Tex b
cmd' cmd options arg = do
  [x] <- cmdn' cmd options [arg]
  return x

-- | Command with options and many arguments
cmdn' :: String -> [String] -> [Tex a] -> Tex [a]
cmdn' cmd options args = do
  backslash >> tex cmd
  when (not $ null options) $ brackets $ sequence_ $ map tex $ intersperse "," options
  res <- sequence $ map braces args
  when (null args) $ tex "{}" -- so that this does not get glued with the next thing.
  return res

-- | Command with tex options and many arguments
cmdm :: String -> [Tex a] -> [Tex a] -> Tex [a]
cmdm cmd options args = do
  backslash >> tex cmd
  when (not $ null options) $ sequence_ $ map brackets $ options
  res <- sequence $ map braces args
  when (null args) $ tex "{}" -- so that this does not get glued with the next thing.
  return res


cmdn'_ :: String -> [String] -> [TeX] -> Tex ()
cmdn'_ cmd options args = cmdn' cmd options args >> return ()

-- | Command with n arguments
cmdn :: String -> [Tex a] -> Tex [a]
cmdn c args = cmdn' c [] args

cmdn_ :: String -> [TeX] -> Tex ()
cmdn_ cmd args = cmdn'_ cmd [] args

-- | Environment
env :: String -> Tex a -> Tex a
env x = env' x []

-- | Environment with options
env' :: String -> [String] -> Tex a -> Tex a
env' e opts body = env'' e opts [] body

-- | Environment with a tex option
env'' :: String -> [String] -> [TeX] -> Tex a -> Tex a
env'' e opts args body = do
  cmd "begin" $ tex e
  when (not $ null opts) $ brackets $ sequence_ $ map tex $ intersperse "," opts
  mapM_ braces args
  x <- body
  cmd "end" $ tex e
  return x

------------------
-- Sorted labels

data SortedLabel =  SortedLabel String Label

label :: String -> Tex SortedLabel
label s = do
  l <- Tex newLabel
  cmd "label" (reference l)
  return $ SortedLabel s l

xref :: SortedLabel -> TeX
xref (SortedLabel _ l) = do
  cmd "ref" (reference l)
  return ()

fxref :: SortedLabel -> TeX
fxref l@(SortedLabel s _) = do
  textual s
  tex "~" -- non-breakable space here
  xref l

pageref :: SortedLabel -> TeX
pageref (SortedLabel _ l) = do
  cmd "pageref" (reference l)
  return ()
  
instance Element SortedLabel where
  type Target SortedLabel = TeX
  element x = fxref x >> return ()

-----------------
-- Generate boxes

outputAlsoInBoxMode :: Tex a -> Tex a
outputAlsoInBoxMode (Tex a) = Tex $ local moveInBox $ a
         where moveInBox m = case m of
                 OutsideBox -> InsideBox
                 _ -> m

texAlways = texInMode (const True)

inBoxComputMode :: String -> TeX
inBoxComputMode = texInMode (`elem` [OutsideBox,InsideBox])

whenMode :: Mode -> Tex () -> Tex ()
whenMode mode act = do
  interpretMode <- Tex ask
  when (mode interpretMode) act

inBox :: Tex a -> Tex (a, BoxSpec)
inBox x = do
  inBoxComputMode $ "\n\\savebox{\\marxupbox}{"
  a <- outputAlsoInBoxMode x
  inBoxComputMode $ 
    "}"
    ++ writeBox "wd"
    ++ writeBox "ht"
    ++ writeBox "dp"
    ++ "\n"
  b <- Tex getBoxSpec

  return (a,b)
  where writeBox l = "\\immediate\\write\\boxesfile{\\number\\"++ l ++"\\marxupbox}"


justBox :: Tex a -> Tex BoxSpec
justBox x = do
  whenMode (`elem` [OutsideBox, InsideBox]) $ outputAlsoInBoxMode $ do
    tex "\n\\savebox{\\marxupbox}{"
    x
    tex $ 
      "}"
      ++ writeBox "wd"
      ++ writeBox "ht"
      ++ writeBox "dp"
      ++ "\n"
  b <- Tex getBoxSpec

  return b
  where writeBox l = "\\immediate\\write\\boxesfile{\\number\\"++ l ++"\\marxupbox}"

renderWithBoxes :: [BoxSpec] -> InterpretMode -> Tex a -> String
renderWithBoxes bs mode (Tex t) = doc
  where (_,_,doc) = runRWS (fromMulti $ t) mode (0,bs)

renderSimple :: TeX -> String
renderSimple = renderWithBoxes [] Regular
  
renderTex :: (Bool -> TeX) -> TeX -> IO String
renderTex preamble body = do
  let bxsTex = renderWithBoxes (repeat nilBoxSpec) OutsideBox (wholeDoc True)
      boxesName = "mpboxes"
      boxesTxt = boxesName ++ ".txt"
      wholeDoc inBoxMode = do
        outputAlsoInBoxMode (preamble inBoxMode)
        inBoxComputMode $ "\\newwrite\\boxesfile"
        texAlways "\\begin{document}"
        inBoxComputMode $ "\\immediate\\openout\\boxesfile="++boxesTxt++"\n \\newsavebox{\\marxupbox}"
        body
        inBoxComputMode "\n\\immediate\\closeout\\boxesfile"
        texAlways "\\end{document}"
  writeFile (boxesName ++ ".tex") bxsTex
  system $ "latex " ++ boxesName
  boxes <- do
    e <- doesFileExist boxesTxt
    if e
      then do
        boxData <- map read . lines <$> readFile boxesTxt
        return $ getBoxInfo boxData
      else return []
  putStrLn $ "Number of boxes found: " ++ show (length boxes)
  return $ renderWithBoxes boxes Regular $ (wholeDoc False)

getBoxInfo :: [Int] -> [BoxSpec]
getBoxInfo [] = []
getBoxInfo (width:height:depth:bs) = BoxSpec (scale width) (scale height) (scale depth):getBoxInfo bs
  where scale x = fromIntegral x / 65536