feat(image): configurable templates for math expressions. Closes #1338

This commit is contained in:
Folke Lemaitre 2025-02-20 17:11:07 +01:00
parent 6922aab01f
commit e039139291
No known key found for this signature in database
GPG key ID: 41F8B1FBACAE2040
3 changed files with 61 additions and 31 deletions

View file

@ -41,13 +41,11 @@ M.transforms = {
if not img.content then if not img.content then
return return
end end
local fg = Snacks.util.color("SnacksImageMath") or "#000000" img.content = Snacks.picker.util.tpl(Snacks.image.config.math.typst.tpl, {
img.content = ([[ color = Snacks.util.color("SnacksImageMath") or "#000000",
#set page(width: auto, height: auto, margin: (x: 2pt, y: 2pt)) header = M.get_header(ctx.buf),
#show math.equation.where(block: false): set text(top-edge: "bounds", bottom-edge: "bounds") content = img.content,
#set text(size: 12pt, fill: rgb("%s")) }, { indent = true, prefix = "$" })
%s
%s]]):format(fg, M.get_header(ctx.buf), img.content)
end, end,
latex = function(img, ctx) latex = function(img, ctx)
if not img.content then if not img.content then
@ -62,7 +60,7 @@ M.transforms = {
content = ("\\[%s\\]"):format(content) content = ("\\[%s\\]"):format(content)
end end
local packages = { "xcolor" } local packages = { "xcolor" }
vim.list_extend(packages, Snacks.image.config.convert.math.packages) vim.list_extend(packages, Snacks.image.config.math.latex.packages)
for _, line in ipairs(vim.api.nvim_buf_get_lines(ctx.buf, 0, -1, false)) do for _, line in ipairs(vim.api.nvim_buf_get_lines(ctx.buf, 0, -1, false)) do
if line:find("\\usepackage") then if line:find("\\usepackage") then
for _, p in ipairs(vim.split(line:match("{(.-)}") or "", ",%s*")) do for _, p in ipairs(vim.split(line:match("{(.-)}") or "", ",%s*")) do
@ -73,17 +71,13 @@ M.transforms = {
end end
end end
table.sort(packages) table.sort(packages)
local fs = Snacks.image.config.convert.math.font_size or "large" img.content = Snacks.picker.util.tpl(Snacks.image.config.math.latex.tpl, {
img.content = ([[ font_size = Snacks.image.config.math.latex.font_size or "large",
\documentclass[preview,border=2pt,varwidth,12pt]{standalone} packages = table.concat(packages, ", "),
\usepackage{%s} header = M.get_header(ctx.buf),
\begin{document} color = fg:upper():sub(2),
%s content = content,
{ \%s \selectfont }, { indent = true, prefix = "$" })
\color[HTML]{%s}
%s}
\end{document}
]]):format(table.concat(packages, ", "), M.get_header(ctx.buf), fs, fg:upper():sub(2), content)
end, end,
} }
@ -233,7 +227,7 @@ function M._img(ctx)
if img.src then if img.src then
img.src = M.resolve(ctx.buf, img.src) img.src = M.resolve(ctx.buf, img.src)
end end
if not Snacks.image.config.doc.math and img.ext and img.ext:find("math") then if not Snacks.image.config.math.enabled and img.ext and img.ext:find("math") then
return return
end end
if img.content and not img.src then if img.content and not img.src then

View file

@ -68,7 +68,6 @@ local defaults = {
-- enable image viewer for documents -- enable image viewer for documents
-- a treesitter parser must be available for the enabled languages. -- a treesitter parser must be available for the enabled languages.
enabled = true, enabled = true,
math = true, -- enable math expression rendering
-- render the image inline in the buffer -- render the image inline in the buffer
-- if your env doesn't support unicode placeholders, this will be disabled -- if your env doesn't support unicode placeholders, this will be disabled
-- takes precedence over `opts.float` on supported terminals -- takes precedence over `opts.float` on supported terminals
@ -105,12 +104,6 @@ local defaults = {
---@class snacks.image.convert.Config ---@class snacks.image.convert.Config
convert = { convert = {
notify = true, -- show a notification on error notify = true, -- show a notification on error
math = {
font_size = "Large", -- see https://www.sascha-frank.com/latex-font-size.html
-- for latex documents, the doc packages are included automatically,
-- but you can add more packages here. Useful for markdown documents.
packages = { "amsmath", "amssymb", "amsfonts", "amscd", "mathtools" },
},
---@type snacks.image.args ---@type snacks.image.args
mermaid = function() mermaid = function()
local theme = vim.o.background == "light" and "neutral" or "dark" local theme = vim.o.background == "light" and "neutral" or "dark"
@ -124,6 +117,36 @@ local defaults = {
pdf = { "-density", 192, "{src}[0]", "-background", "white", "-alpha", "remove", "-trim" }, pdf = { "-density", 192, "{src}[0]", "-background", "white", "-alpha", "remove", "-trim" },
}, },
}, },
math = {
enabled = true, -- enable math expression rendering
-- in the templates below, `${header}` comes from any section in your document,
-- between a start/end header comment. Comment syntax is language-specific.
-- * start comment: `// snacks: header start`
-- * end comment: `// snacks: header end`
typst = {
tpl = [[
#set page(width: auto, height: auto, margin: (x: 2pt, y: 2pt))
#show math.equation.where(block: false): set text(top-edge: "bounds", bottom-edge: "bounds")
#set text(size: 12pt, fill: rgb("${color}"))
${header}
${content}]],
},
latex = {
font_size = "Large", -- see https://www.sascha-frank.com/latex-font-size.html
-- for latex documents, the doc packages are included automatically,
-- but you can add more packages here. Useful for markdown documents.
packages = { "amsmath", "amssymb", "amsfonts", "amscd", "mathtools" },
tpl = [[
\documentclass[preview,border=2pt,varwidth,12pt]{standalone}
\usepackage{${packages}}
\begin{document}
${header}
{ \${font_size} \selectfont
\color[HTML]{${color}}
${content}}
\end{document}]],
},
},
} }
M.config = Snacks.config.get("image", defaults) M.config = Snacks.config.get("image", defaults)

View file

@ -172,13 +172,15 @@ end
---@param str string ---@param str string
---@param data table<string, string> ---@param data table<string, string>
function M.tpl(str, data) ---@param opts? {prefix?: string, indent?: boolean, offset?: number[]}
return ( function M.tpl(str, data, opts)
opts = opts or {}
local ret = (
str:gsub( str:gsub(
"(%b{})", "(" .. vim.pesc(opts.prefix or "") .. "%b{}" .. ")",
---@param w string ---@param w string
function(w) function(w)
local inner = w:sub(2, -2) local inner = w:sub(2 + #(opts.prefix or ""), -2)
local key, default = inner:match("^(.-):(.*)$") local key, default = inner:match("^(.-):(.*)$")
local ret = data[key or inner] local ret = data[key or inner]
if ret == "" and default then if ret == "" and default then
@ -188,6 +190,17 @@ function M.tpl(str, data)
end end
) )
) )
if opts.indent then
local lines = vim.split(ret:gsub("\t", " "), "\n", { plain = true })
local indent = 1000
for _, line in ipairs(lines) do
indent = math.min(indent, line:find("%S") or 1000)
end
for l, line in ipairs(lines) do
lines[l] = line:sub(indent)
end
end
return ret
end end
---@param str string ---@param str string