diff --git a/README.md b/README.md index 3e245c0..f700760 100644 --- a/README.md +++ b/README.md @@ -157,3 +157,17 @@ scaling = adjustSpeed 2 $ syncAll ## Valentine's Day ![Valentine's Day](gifs/valentine.gif) + +## Basic LaTeX + +``` +latex_basic :: Ani () +latex_basic = proc () -> do + duration 1 -< () + emit -< do + rect_ [width_ "100%", height_ "100%", fill_ "black"] + g_ [transform_ $ translate 5 50] $ + g_ [transform_ $ scale 4 4, fill_ "white"] $ + latex "$\\sum_{k=1}^\\infty {1 \\over k^2} = {\\pi^2 \\over 6}$" +``` +![Basic LaTeX](gifs/latex_basic.gif) diff --git a/reanimate.cabal b/reanimate.cabal index a930ccd..8c9d2bb 100644 --- a/reanimate.cabal +++ b/reanimate.cabal @@ -20,6 +20,7 @@ library Reanimate.Render Reanimate.Examples Reanimate.Combinators + Reanimate.LaTeX build-depends: base >=4.12 && <4.13, lucid-svg, time, text, unix, lucid, filepath, process, directory diff --git a/src/Reanimate/Examples.hs b/src/Reanimate/Examples.hs index f482ae9..5d75a30 100644 --- a/src/Reanimate/Examples.hs +++ b/src/Reanimate/Examples.hs @@ -10,8 +10,7 @@ import Numeric import Reanimate.Arrow import Reanimate.Combinators - -import Debug.Trace +import Reanimate.LaTeX sinewave :: Ani () sinewave = proc () -> do @@ -326,3 +325,13 @@ frequencies = proc () -> do (idx*width, 0) wave1 n = approxFnData 1000 $ \idx -> (idx*width, sum [ sin ((idx+n)*pi*2*freq) * 20 | freq <- freqs ]) + + +latex_basic :: Ani () +latex_basic = proc () -> do + duration 1 -< () + emit -< do + rect_ [width_ "100%", height_ "100%", fill_ "black"] + g_ [transform_ $ translate 5 50] $ + g_ [transform_ $ scale 4 4, fill_ "white"] $ + latex "$\\sum_{k=1}^\\infty {1 \\over k^2} = {\\pi^2 \\over 6}$" diff --git a/src/Reanimate/LaTeX.hs b/src/Reanimate/LaTeX.hs new file mode 100644 index 0000000..85a0e3b --- /dev/null +++ b/src/Reanimate/LaTeX.hs @@ -0,0 +1,98 @@ +{-# LANGUAGE ScopedTypeVariables, OverloadedStrings #-} +module Reanimate.LaTeX where + +import System.Process +import System.Exit +import System.IO +import System.IO.Unsafe +import System.FilePath +import System.Directory +import Control.Exception +import Lucid (toHtmlRaw, toHtml) +import Lucid.Svg (Svg, text_, font_size_, fill_) +--toHtmlRaw svgFile + +--latex -interaction=batchmode -halt-on-error +--dvisvgm {file} -n -v 0 -o out + +latex :: String -> Svg () +latex = unsafePerformIO . latexToSVG + +latexToSVG :: String -> IO (Svg ()) +latexToSVG tex = handle (\(e::SomeException) -> return (failedSvg tex)) $ do + latex <- requireExecutable "latex" + dvisvgm <- requireExecutable "dvisvgm" + withTempDir $ \tmp_dir -> withTempFile "tex" $ \tex_file -> withTempFile "svg" $ \svg_file -> do + let dvi_file = tmp_dir replaceExtension (takeFileName tex_file) "dvi" + writeFile tex_file tex_prologue + appendFile tex_file tex + appendFile tex_file tex_epilogue + runCmd latex ["-interaction=batchmode", "-halt-on-error", "-output-directory="++tmp_dir, tex_file] + runCmd dvisvgm [dvi_file, "-n","-v", "0", "-o",svg_file] + svg_data <- readFile svg_file + evaluate (length svg_data) + return $ toHtmlRaw $ unlines $ drop 1 $ lines svg_data + +failedSvg :: String -> Svg () +failedSvg tex = + text_ [ font_size_ "20" + , fill_ "white"] (toHtml $ "bad latex: "++tex) + +runCmd exec args = do + (ret, stdout, stderr) <- readProcessWithExitCode exec args "" + evaluate (length stdout + length stderr) + case ret of + ExitSuccess -> return () + ExitFailure err -> do + putStrLn $ + "Failed to run: " ++ showCommandForUser exec args ++ "\n" ++ + "Error code: " ++ show err ++ "\n" ++ + "stderr: " ++ show stderr + throwIO (ExitFailure err) + +withTempDir action = do + dir <- getTemporaryDirectory + (path, handle) <- openTempFile dir "reanimate-XXXXXX" + hClose handle + removeFile path + createDirectory (dir path) + action (dir path) `finally` removeDirectoryRecursive (dir path) + +withTempFile ext action = do + dir <- getTemporaryDirectory + (path, handle) <- openTempFile dir ("reanimate-XXXXXX" <.> ext) + hClose handle + action path `finally` removeFile path + +requireExecutable :: String -> IO FilePath +requireExecutable exec = do + mbPath <- findExecutable exec + case mbPath of + Nothing -> error $ "Couldn't find executable: " ++ exec + Just path -> return path + + +tex_prologue = + "\\documentclass[preview]{standalone}\n\ + \\\usepackage[english]{babel}\n\ + \\\usepackage{amsmath}\n\ + \\\usepackage{amssymb}\n\ + \\\usepackage{dsfont}\n\ + \\\usepackage{setspace}\n\ + \\\usepackage{tipa}\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{microtype}\n\ + \\\DisableLigatures{encoding = *, family = * }\n\ + \%\\usepackage[UTF8]{ctex}\n\ + \\\linespread{1}\n\ + \\\begin{document}\n" + +tex_epilogue = "\n\\end{document}"