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
Copy link

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
Copy link

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
Copy link
Author

lierdakil commented Sep 9, 2020

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

@lierdakil
Copy link
Author

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
Copy link

pazz commented Sep 10, 2020

@PassionPenguin
Copy link

PassionPenguin commented Feb 14, 2022

Hi @lierdakil i try to make a lua filter, but i am a newer in either lua or haskell, and i failed to install pandoc via cabal...thus i try to make a lua version:

local default_template = [[
\documentclass[12pt,preview]{standalone}
{{ preamble }}
\begin{document}
\begin{preview}
{{ code }}
\end{preview}
\end{document}
]]

local default_preamble = [[
\usepackage[utf8x]{inputenc}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{amstext}
\usepackage{newtxtext}
\usepackage{mhchem}
\usepackage{chemfig}
\usepackage[libertine]{newtxmath}
\DeclareOldFontCommand{\rm}{\normalfont\rmfamily}{\mathrm}
\DeclareOldFontCommand{\sf}{\normalfont\sffamily}{\mathsf}
\DeclareOldFontCommand{\tt}{\normalfont\ttfamily}{\mathtt}
\DeclareOldFontCommand{\bf}{\normalfont\bfseries}{\mathbf}
\DeclareOldFontCommand{\it}{\normalfont\itshape}{\mathit}
\DeclareOldFontCommand{\sl}{\normalfont\slshape}{\@nomath\sl}
\DeclareOldFontCommand{\sc}{\normalfont\scshape}{\@nomath\sc}
\newcommand{\N}{\mathbb{N}}
\newcommand{\R}{\mathbb{R}}
\newcommand{\Z}{\mathbb{Z}}
]]

function exec_math (elem)
    -- Convert Math El -> DVI via latex --
    mathEq = default_template:gsub("{{ preamble }}", default_preamble):gsub("{{ code }}", elem.text)
    local tex = io.open("input.tex", "w")
    tex:write(mathEq)
    io.close(tex)
    os.execute("latex input.tex")
    os.execute("dvisvgm --no-fonts input.dvi output.svg")
    local svg = io.open("output.svg", "rb")
    if not svg then
        return nil
    end
    local content = svg:read "*a"
    local output
    if elem.mathtype == 'InlineMath' then
        output = content
    else
        output = "<p>" .. content .. "<p>"
    end
    elem = pandoc.RawInline('html', output)
    return elem
end

return {
    { Math = exec_math }
}

but after generating svg with markdown math $\chemfig{C([2]-H)([6]=H)}$ and return a RawInline elem, it still renders a <span class="math inline">$\chemfig{C([2]-H)([6]=H)}$</span>, and log a warning in the console:

[WARNING] Could not convert TeX math \chemfig{C([2]-H)([6]=H)}, rendering as TeX:
  \chemfig{C([2]-H)([6]=H)}
          ^
  unexpected control sequence \chemfig
  expecting "%", "\\label", "\\tag", "\\nonumber" or whitespace

Do you have any idea how to fix it?.....

@PassionPenguin
Copy link

PassionPenguin commented Feb 14, 2022

a google group link pubed by myself.. https://groups.google.com/g/pandoc-discuss/c/4CirnNd-ETs

@lierdakil
Copy link
Author

lierdakil commented Feb 14, 2022

To begin with, your call to dvisvgm is not quite right... Try using dvisvgm --no-fonts input.dvi -o output.svg. Besides, I'm pretty sure that call to table.insert will fail, since neither argument is a table. I don't think you need it to begin with.

@PassionPenguin
Copy link

PassionPenguin commented Feb 18, 2022

sorry but i find that you're right... as i have read a wrong document... thx:)

Here's my gist https://gist.github.com/PassionPenguin/43fc09b942cedc428ae246bff8b6e193 :)

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