reanimate-0.4.2.0: src/Reanimate/LaTeX.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-|
Copyright : Written by David Himmelstrup
License : Unlicense
Maintainer : lemmih@gmail.com
Stability : experimental
Portability : POSIX
-}
module Reanimate.LaTeX
( latex
, latexWithHeaders
, latexChunks
, xelatex
, xelatexWithHeaders
, ctex
, ctexWithHeaders
, latexAlign
)
where
import qualified Data.ByteString as B
import Data.Text ( Text )
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Graphics.SvgTree ( Tree(..)
, parseSvgFile
)
import Reanimate.Cache
import Reanimate.Misc
import Reanimate.Svg
import Reanimate.Parameters
import System.FilePath ( replaceExtension
, takeFileName
, (</>)
)
import System.IO.Unsafe ( unsafePerformIO )
-- | Invoke latex and import the result as an SVG object. SVG objects are
-- cached to improve performance.
--
-- Example:
--
-- > latex "$e^{i\\pi}+1=0$"
--
-- <<docs/gifs/doc_latex.gif>>
latex :: T.Text -> Tree
latex = latexWithHeaders []
-- | Invoke latex with extra script headers.
latexWithHeaders :: [T.Text] -> T.Text -> Tree
latexWithHeaders = someTexWithHeaders "latex" "dvi" []
someTexWithHeaders :: String -> String -> [String] -> [T.Text] -> T.Text -> Tree
someTexWithHeaders _exec _dvi _args _headers tex | pNoExternals = mkText tex
someTexWithHeaders exec dvi args headers tex =
(unsafePerformIO . (cacheMem . cacheDiskSvg) (latexToSVG dvi exec args))
script
where
script = mkTexScript exec args headers tex
-- | Invoke latex and separate results.
latexChunks :: [T.Text] -> [Tree]
latexChunks chunks | pNoExternals = map mkText chunks
latexChunks chunks = worker (svgGlyphs $ latex $ T.concat chunks) chunks
where
merge lst = mkGroup [ fmt svg | (fmt, _, svg) <- lst ]
worker [] [] = []
worker _ [] = error "latex chunk mismatch"
worker everything (x : xs) =
let width = length $ svgGlyphs (latex x)
in merge (take width everything) : worker (drop width everything) xs
-- | Invoke xelatex and import the result as an SVG object. SVG objects are
-- cached to improve performance. Xelatex has support for non-western scripts.
xelatex :: Text -> Tree
xelatex = xelatexWithHeaders []
-- | Invoke xelatex with extra script headers.
xelatexWithHeaders :: [T.Text] -> T.Text -> Tree
xelatexWithHeaders = someTexWithHeaders "xelatex" "xdv" ["-no-pdf"]
-- | Invoke xelatex with "\usepackage[UTF8]{ctex}" and import the result as an
-- SVG object. SVG objects are cached to improve performance. Xelatex has
-- support for non-western scripts.
--
-- Example:
--
-- > ctex "中文"
--
-- <<docs/gifs/doc_ctex.gif>>
ctex :: T.Text -> Tree
ctex = ctexWithHeaders []
-- | Invoke xelatex with extra script headers + ctex headers.
ctexWithHeaders :: [T.Text] -> T.Text -> Tree
ctexWithHeaders headers = xelatexWithHeaders ("\\usepackage[UTF8]{ctex}" : headers)
-- | Invoke latex and import the result as an SVG object. SVG objects are
-- cached to improve performance. This wraps the TeX code in an 'align*'
-- context.
--
-- Example:
--
-- > latexAlign "R = \\frac{{\\Delta x}}{{kA}}"
--
-- <<docs/gifs/doc_latexAlign.gif>>
latexAlign :: Text -> Tree
latexAlign tex = latex $ T.unlines ["\\begin{align*}", tex, "\\end{align*}"]
postprocess :: Tree -> Tree
postprocess = simplify
-- executable, arguments, header, tex
latexToSVG :: String -> String -> [String] -> Text -> IO Tree
latexToSVG dviExt latexExec latexArgs tex = do
latexBin <- requireExecutable latexExec
dvisvgm <- requireExecutable "dvisvgm"
withTempDir $ \tmp_dir -> withTempFile "tex" $ \tex_file ->
withTempFile "svg" $ \svg_file -> do
let dvi_file =
tmp_dir </> replaceExtension (takeFileName tex_file) dviExt
B.writeFile tex_file (T.encodeUtf8 tex)
runCmd
latexBin
( latexArgs
++ [ "-interaction=nonstopmode"
, "-halt-on-error"
, "-output-directory=" ++ tmp_dir
, tex_file
]
)
runCmd
dvisvgm
[ dvi_file
, "--precision=5"
, "--exact" -- better bboxes.
, "--no-fonts" -- use glyphs instead of fonts.
, "--scale=0.1,-0.1"
, "--verbosity=0"
, "-o"
, svg_file
]
svg_data <- B.readFile svg_file
case parseSvgFile svg_file svg_data of
Nothing -> error "Malformed svg"
Just svg -> return $ postprocess $ unbox $ replaceUses svg
mkTexScript :: String -> [String] -> [Text] -> Text -> Text
mkTexScript latexExec latexArgs texHeaders tex =
T.unlines
$ [ "% " <> T.pack (unwords (latexExec : latexArgs))
, "\\documentclass[preview]{standalone}"
, "\\usepackage{amsmath}"
, "\\usepackage{gensymb}"
]
++ texHeaders
++ [ "\\usepackage[english]{babel}"
, "\\linespread{1}"
, "\\begin{document}"
, tex
, "\\end{document}"
]
{- Packages used by manim.
\\\usepackage{amsmath}\n\
\\\usepackage{amssymb}\n\
\\\usepackage{dsfont}\n\
\\\usepackage{setspace}\n\
\\\usepackage{relsize}\n\
\\\usepackage{textcomp}\n\
\\\usepackage{mathrsfs}\n\
\\\usepackage{calligra}\n\
\\\usepackage{wasysym}\n\
\\\usepackage{ragged2e}\n\
\\\usepackage{physics}\n\
\\\usepackage{xcolor}\n\
\\\usepackage{textcomp}\n\
\\\usepackage{xfrac}\n\
\\\usepackage{microtype}\n\
-}