Created
September 23, 2025 05:42
-
-
Save hcoona/915d0b8599cf1d283dae65076e7c14b8 to your computer and use it in GitHub Desktop.
Pandoc Filter to Convert Math Blocks into Png Images
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- 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