Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Pandoc filter to convert math to inline svg using latex and dvisvgm
{-# LANGUAGE OverloadedStrings #-}
import Text.Pandoc.JSON
import System.Directory
import System.FilePath ((</>))
import qualified Data.Hash.MD5 as MD5
import qualified Data.Text as T
import System.IO.Temp
import System.Process
import Control.Monad (unless)
main :: IO ()
main = toJSONFilter mathToSvg
mathToSvg :: Inline -> IO Inline
mathToSvg m@(Math mathType x) = do
let wrap = T.unpack . removeNewline . case mathType of
InlineMath -> \x' -> "\\(" <> x' <> "\\)"
DisplayMath -> \x' -> "\\[" <> x' <> "\\]"
preamble =[
"\\documentclass[border=1pt,varwidth]{standalone}",
"\\usepackage{standalone}" <>
"\\usepackage{amsmath}" <>
"\\usepackage{amssymb}" <>
"\\usepackage{cancel}" <>
"\\begin{document}"
]
postamble = [ "\\end{document}" ]
removeNewline = T.filter (`notElem` ("\r\n" :: [Char]))
tempDir <- getTemporaryDirectory
let cacheDir = tempDir </> "pandoc.texsvg.cache"
createDirectoryIfMissing True cacheDir
let mathHash = MD5.md5s $ MD5.Str $ show m
outfilename = cacheDir </> mathHash <> ".svg"
fileExists <- doesFileExist outfilename
unless fileExists $
withSystemTempDirectory "pandoc.dir" $ \tmpDir ->
do
origDir <- getCurrentDirectory
setCurrentDirectory tmpDir
_ <- readProcess "latex" (preamble <> [wrap x] <> postamble) []
_ <- readProcess "dvisvgm"
["-b2pt", "-Z1.2", "-n", "-o", outfilename, "standalone.dvi"] []
setCurrentDirectory origDir
svg <- T.pack <$> readFile outfilename
return $ RawInline (Format "html") $ case mathType of
InlineMath -> svg
DisplayMath -> "<p>" <> svg <> "</p>"
mathToSvg x = return x
@pazz

This comment has been minimized.

Copy link

@pazz pazz commented Sep 9, 2020

Sorry but how do I run this? I tried

pandoc -s README.md --filter texsvg.hs -o r.html

but this gives me syntax errors as it expects bash syntax.
Adding

#!/usr/bin/env runhaskell

to the top yields

/home/pazz/.pandoc/filters/texsvg.hs:6:1: error:
    Could not find module ‘Data.Hash.MD5’
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
  |
6 | import qualified Data.Hash.MD5 as MD5
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error running filter /home/pazz/.pandoc/filters/texsvg.hs:
Filter returned error status 1

I'm on pandoc 2.9.1.1. Cheers!

@pazz

This comment has been minimized.

Copy link

@pazz pazz commented Sep 9, 2020

Sorry, I seem to have lacked a library. I've now installed "missingH" via debians package manager but get more serious looking errors (see below).

/home/pazz/.pandoc/filters/texsvg.hs:43:53: error:
    • Couldn't match expected type ‘[Char]’
                  with actual type ‘Data.Text.Internal.Text’
    • In the first argument of ‘wrap’, namely ‘x’
      In the expression: wrap x
      In the first argument of ‘(++)’, namely ‘[wrap x]’
   |
43 |         _ <- readProcess "latex" (preamble ++ [wrap x] ++ postamble) []
   |                                                     ^

/home/pazz/.pandoc/filters/texsvg.hs:49:30: error:
    • Couldn't match expected type ‘Data.Text.Internal.Text’
                  with actual type ‘[Char]’
    • In the first argument of ‘Format’, namely ‘"html"’
      In the first argument of ‘RawInline’, namely ‘(Format "html")’
      In the expression: RawInline (Format "html")
   |
49 |   return $ RawInline (Format "html") $ case mathType of
   |                              ^^^^^^

/home/pazz/.pandoc/filters/texsvg.hs:50:19: error:
    • Couldn't match type ‘[Char]’ with ‘Data.Text.Internal.Text’
      Expected type: Data.Text.Internal.Text
        Actual type: String
    • In the expression: svg
      In a case alternative: InlineMath -> svg
      In the second argument of ‘($)’, namely
        ‘case mathType of
           InlineMath -> svg
           DisplayMath -> "<p>" ++ svg ++ "</p>"’
   |
50 |     InlineMath -> svg
   |                   ^^^

/home/pazz/.pandoc/filters/texsvg.hs:51:20: error:
    • Couldn't match expected type ‘Data.Text.Internal.Text’
                  with actual type ‘[Char]’
    • In the expression: "<p>" ++ svg ++ "</p>"
      In a case alternative: DisplayMath -> "<p>" ++ svg ++ "</p>"
      In the second argument of ‘($)’, namely
        ‘case mathType of
           InlineMath -> svg
           DisplayMath -> "<p>" ++ svg ++ "</p>"’
   |
51 |     DisplayMath -> "<p>"++svg++"</p>"
   |                    ^^^^^^^^^^^^^^^^^^
Error running filter /home/pazz/.pandoc/filters/texsvg.hs:
Filter returned error status 1

@lierdakil

This comment has been minimized.

Copy link
Owner Author

@lierdakil lierdakil commented Sep 9, 2020

Yeah... this filter is pretty outdated. Let me see if I can easily update it.

@lierdakil

This comment has been minimized.

Copy link
Owner Author

@lierdakil lierdakil commented Sep 9, 2020

Okay, updated, but tbh, it'd be probably easier to write a lua filter that would essentially do the same thing. All this filter does is basically run latex and then convert its output to SVG using dvisvgm for each math element in document. Should be pretty easy to implement in pandoc Lua I think. The only caveat is it does some hashing for caching, because otherwise it's very slow, and hashing isn't that easy with Lua. Doable though.

@pazz

This comment has been minimized.

Copy link

@pazz pazz commented Sep 10, 2020

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment