{-# 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$" -- -- <> 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 "中文" -- -- <> 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}}" -- -- <> 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\ -}