Skip to content

Instantly share code, notes, and snippets.

@hcoona
Created September 23, 2025 05:42
Show Gist options
  • Select an option

  • Save hcoona/915d0b8599cf1d283dae65076e7c14b8 to your computer and use it in GitHub Desktop.

Select an option

Save hcoona/915d0b8599cf1d283dae65076e7c14b8 to your computer and use it in GitHub Desktop.
Pandoc Filter to Convert Math Blocks into Png Images
-- Render inline ($...$) and display ($$...$$) math to PNG via latex + dvipng.
-- Caches to math-img/<sha1>.png
-- pandoc -f markdown+tex_math_dollars+raw_tex --lua-filter=filter-math-png.lua
local system = require 'pandoc.system'
local OUTDIR = "math-img"
local function is_html()
return FORMAT:match('html') ~= nil
end
local function file_exists(path)
local f = io.open(path, "rb")
if f then f:close(); return true end
return false
end
local function ensure_dir()
if system and system.make_directory then
pcall(system.make_directory, OUTDIR, true)
else
os.execute('mkdir -p "' .. OUTDIR .. '"')
end
end
local function write_file(path, content)
local f, err = io.open(path, "wb")
if not f then error("cannot write " .. path .. ": " .. tostring(err)) end
f:write(content); f:close()
end
local function need_wrap_display(src)
if src:match("\\begin%s*{[%a*@]+}") then return false end -- \begin{align*} 等
if src:match("\\%[") or src:match("\\%]") then return false end -- \[...\]
if src:match("\\%(") or src:match("\\%)") then return false end -- \(...\)
return true
end
local function build_tex(math_src, is_inline)
local body
if is_inline then
body = "$" .. math_src .. "$"
else
if need_wrap_display(math_src) then
body = "\\[" .. math_src .. "\\]"
else
body = math_src
end
end
local tex = table.concat({
"\\documentclass[12pt]{article}",
"\\usepackage[T1]{fontenc}",
"\\usepackage[utf8]{inputenc}",
"\\usepackage{amsmath,amssymb}",
"\\usepackage[active,displaymath,textmath]{preview}",
"\\pagestyle{empty}",
"\\begin{document}",
"\\begin{preview}",
body,
"\\end{preview}",
"\\end{document}",
""
}, "\n")
return tex
end
local function compile_to_png(tex_src, jobname)
ensure_dir()
local texfile = OUTDIR .. "/" .. jobname .. ".tex"
write_file(texfile, tex_src)
-- latex → dvi
pandoc.pipe("latex", {
"-halt-on-error", "-interaction=nonstopmode",
"-output-directory=" .. OUTDIR,
"-jobname=" .. jobname,
texfile
}, "")
-- dvi → png
local dvi = OUTDIR .. "/" .. jobname .. ".dvi"
local png = OUTDIR .. "/" .. jobname .. ".png"
pandoc.pipe("dvipng", {
"-T","tight",
"-D","200",
"-bg","Transparent",
"-o", png, dvi
}, "")
return png
end
local function math_to_image(el)
if not is_html() then return nil end
local is_inline = (el.mathtype == "InlineMath")
local src = el.text or ""
local flavor = is_inline and "i:" or "d:"
local hash = pandoc.utils.sha1(flavor .. src)
local png_path = OUTDIR .. "/" .. hash .. ".png"
if not file_exists(png_path) then
local tex_src = build_tex(src, is_inline)
compile_to_png(tex_src, hash)
end
local classes = { "math", is_inline and "math-inline" or "math-display" }
local attr = pandoc.Attr("", classes, { ["data-math"] = src })
if is_inline then
local style = (attr.attributes.style or "")
attr.attributes.style = (style ~= "" and style .. " " or "") .. "vertical-align: middle;"
end
return pandoc.Image({ pandoc.Str(src) }, png_path, "", attr)
end
return {
{ Math = math_to_image }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment