Skip to content

Instantly share code, notes, and snippets.

@3TUSK
Last active June 11, 2020 06:11
Show Gist options
  • Save 3TUSK/648986e88d8767a38e3d6daf4f71c7ec to your computer and use it in GitHub Desktop.
Save 3TUSK/648986e88d8767a38e3d6daf4f71c7ec to your computer and use it in GitHub Desktop.
Use correct tool to solve problems efficiently

latex2svg-filter

What is it?

This is a simple PanDoc filter that will converts all math blocks and raw LaTeX blocks to SVGs.

With this filter, you can start writing raw LaTeX in Markdown and get a neatly generated SVG in output HTML:

\begin{align*} \sum_{n=0}^\infty \frac{1}{n} &= \frac{1}{0!} + \frac{1}{1!} + \frac{1}{2!} + \dots \ &= 1+1+\frac{1}{2}+\frac{1}{6}+\dots \ &= \mathrm{e} \end{align*}

How to use?

  1. You will need pdftex and dvisvgm in order to use this filter. If you have TeX Live, you should be fine.
  2. In options.lua, adjust values according to your setup:
    • template defines a minimal LaTeX document structure, used for producing a minimal SVG.
    • preamble defines the preamble of the intermediate LaTeX document. Include all your \usepackegs there.
    • pdftex_exe defines the path to the pdftex executable. It is used to generate a dvi file from a tex document.
    • pdftex_arg defines the arguments used to run pdftex.
    • dvisvgm_exe defines the path to the dvisvgm executable. It is used to convert dvi files to svg images.
    • dvisvgm_arg defines the arguments used to run dvisvgm. The default arguments should be sufficient for general use cases.
  3. Use --lua-filter options while using pandoc, for example:
    pandoc --lua-filter latex2svg.lua -t html5 --self-contained -o README.html --metadata title="latex2svg-filter manual" README.markdown

Why not [insert things that can render math formulas]?

One major reason of me pursuing this solution is to use environments like align* and tikzpicture. As far as I know of, there is no alternatives that can render them without a real TeX setup.

Special thanks

Many thanks to a Haskell package called latex-svg for inspring me to make this!

local tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
return function (str)
local i, len = 1, #str
local encoded = ""
while i <= len do
local a, b, c = str:byte(i), str:byte(i + 1), str:byte(i + 2)
local ea, eb, ec, ed = tab:byte((a >> 2) + 1)
if not b then
eb = tab:byte(((a << 4) & 63) + 1)
ec = 61
ed = 61
elseif not c then
eb = tab:byte((((a << 4) | (b >> 4)) & 63) + 1)
ec = tab:byte(((b << 2) & 63) + 1)
ed = 61
else
eb = tab:byte((((a << 4) | (b >> 4)) & 63) + 1)
ec = tab:byte((((b << 2) | (c >> 6)) & 63) + 1)
ed = tab:byte((c & 63) + 1)
end
encoded = encoded .. utf8.char(ea, eb, ec, ed)
i = i + 3
end
return encoded
end
local options = require "options"
local text = require "text"
local base64 = require "base64"
function to_svg(tex_snippet)
local hash = pandoc.utils.sha1(tex_snippet)
local file = io.open(hash .. ".dvi", "r")
if not file then
-- In case that file doesn't exist yet, we get nil.
-- So it is safe to just reassign this local var.
local tex_file = hash .. ".tex"
file = io.open(tex_file, "w+")
-- Has to be local var because there are two return values
-- and we only need the first one.
local dump = options.template:gsub("%%(%w+)%%", { preamble = options.preamble, content = tex_snippet })
file:write(dump)
file:flush()
file:close()
local ok = os.execute(options.pdftex_exe .. " " .. options.pdftex_arg .. " " .. tex_file)
os.remove(tex_file)
os.remove(hash .. ".log")
os.remove(hash .. ".aux")
if not ok then
return nil
end
file = io.open(hash .. ".dvi", "r")
end
local result = pandoc.pipe(options.dvisvgm_exe, options.dvisvgm_arg, file:read("a"))
file:close()
if type(result) == "string" then
return "data:image/svg+xml;base64," .. base64(result)
else
return nil
end
end
function RawBlock(element)
if element.format == "tex" then
local img = pandoc.Image({}, to_svg(element.text))
img.attr = { class = "tex raw", dvisvgm = "true" } -- some useful attributes to help writing CSS
return pandoc.Para({ img })
else
return element
end
end
function RawInline(element)
if element.format == "tex" then
local img = pandoc.Image({}, to_svg(element.text))
img.attr = { class = "tex raw", dvisvgm = "true" } -- some useful attributes to help writing CSS
return img
else
return element
end
end
function Math(element)
local transformed
if element.mathtype == "InlineMath" then
transformed = pandoc.Image({}, to_svg("$" .. element.text .. "$"))
transformed.attr = { class = "tex inlinemath", dvisvgm = "true" }
elseif element.mathtype == "DisplayMath" then
transformed = pandoc.Image({}, to_svg("\\[" .. element.text .."\\]"))
transformed.attr = { class = "tex displaymath", dvisvgm = "true" }
else
transformed = element
end
return transformed
end
return {
-- TeX template used to generate minimal svg
template = [=[\documentclass[12pt]{article}
\pagestyle{empty}
\usepackage[active,tightpage]{preview}
%preamble%
\begin{document}
\begin{preview}
%content%
\end{preview}
\end{document}
]=],
-- TeX packages that should be used when generating svg
preamble = [=[
\usepackage{amssymb}
\usepackage{amsmath}
\usepackage{tikz}
\usetikzlibrary{automata,positioning}
]=],
-- The path to pdftex executable.
pdftex_exe = "latex",
-- Arguments to be fed to pdftex.
pdftex_arg = "",
-- The path to dvisvgm executable.
dvisvgm_exe = "dvisvgm",
-- Arguments to be fed to dvisvgm.
-- Optionally you cab add --exact-bbox if you use newer versions of dvisvgm
dvisvgm_arg = { "--no-font=1", "--clipjoin", "--bbox=min", "--exact", "--stdin", "--stdout" }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment