Skip to content

Instantly share code, notes, and snippets.

@hmenke
Last active June 13, 2019 02:02
Show Gist options
  • Save hmenke/a41574ef0b5000635986f0dec73e066f to your computer and use it in GitHub Desktop.
Save hmenke/a41574ef0b5000635986f0dec73e066f to your computer and use it in GitHub Desktop.
tag_math
local unimath_symbols = require("unimath_symbols")
local convert_char = unimath_symbols.convert_char
local converters = {}
local function convert(n)
local id = n.id
local type = node.type(id)
local typeconv = converters[type]
if typeconv then
return typeconv(n) or ""
else
texio.write_nl("tag_math warning: no conversion available for " .. type)
return ""
end
end
function converters.noad(n)
if not (n.nucleus.head or n.nucleus.char) then
-- This is a thing, e.g. ${}$ is just an empty noad
return
end
local result = convert(n.nucleus)
local subtype = node.subtypes(n.id)[n.subtype]
if subtype == "oplimits" then
result = result .. "\\limits"
elseif subtype == "opdisplaylimits" then
result = result .. "\\displaylimits"
end
if n.sub then
result = result .. "_{" .. convert(n.sub) .. "}"
end
if n.sup then
result = result .. "^{" .. convert(n.sup) .. "}"
end
return result
end
function converters.math_char(n)
return convert_char(n.char)
end
function converters.sub_mlist(n)
local result = ""
for n in node.traverse(n.head) do
result = result .. convert(n)
end
return result
end
function converters.fence(n, subtype)
local subtype = node.subtypes(n.id)[n.subtype]
local leftright = { left = "\\left", right = "\\right" }
local result
if n.delim.small_char ~= 0 then
result = convert_char(n.delim.small_char)
elseif n.delim.large_char ~= 0 then
result = convert_char(n.delim.large_char)
else
result = "."
end
return leftright[subtype] .. result
end
function converters.fraction(n)
local num = convert(n.num)
local denom = convert(n.denom)
return "\\frac{" .. num .. "}{" .. denom .. "}"
end
function converters.radical(n)
local result = "\\sqrt{" .. convert(n.nucleus) .. "}"
if n.sub then
result = result .. "_{" .. convert(n.sub) .. "}"
end
if n.sup then
result = result .. "^{" .. convert(n.sup) .. "}"
end
return result
end
function converters.style(n)
return "\\" .. n.style .. "style"
end
function converters.accent(n)
local result = convert(n.nucleus)
if n.accent then
result = convert(n.accent) .. "{" .. result .. "}"
end
if n.bot_accent then
result = convert(n.bot_accent) .. "{" .. result .. "}"
end
if n.sub then
result = result .. "_{" .. convert(n.sub) .. "}"
end
if n.sup then
result = result .. "^{" .. convert(n.sup) .. "}"
end
return result
end
function converters.glue(n)
-- FIXME: any glue is treated like space
return " "
end
function converters.kern(n)
-- FIXME: any kern is just dropped
return ""
end
local function callback(head, display_type, need_penalties)
local text = {}
for n in node.traverse(head) do
text[#text + 1] = convert(n)
end
-- concatenate, escape, and remove quotes
local actual_text = string.sub(string.format("%q", table.concat(text)), 2, -2)
if display_type == "display" then
actual_text = "\\\\[" .. actual_text .. "\\\\]"
elseif display_type == "text" then
actual_text = "\\\\(" .. actual_text .. "\\\\)"
end
local BDC = node.new("whatsit", "pdf_literal")
BDC.data = "/Span <</ActualText(" .. actual_text .. ")>> BDC"
BDC.mode = 2
head = node.insert_before(head, head, BDC)
local EMC = node.new("whatsit", "pdf_literal")
EMC.data = "EMC"
EMC.mode = 2
head = node.insert_after(head, node.tail(head), EMC)
return head
end
return { callback = callback }
\pdfvariable compresslevel 0
\pdfvariable objcompresslevel 0
\documentclass{article}
\pagestyle{empty}
\usepackage{amsmath}
\usepackage{unicode-math}
\DeclareMathOperator\Res{Res}
\directlua{tag_math = require("tag_math")}
\AtBeginDocument{\directlua{
luatexbase.add_to_callback("mlist_to_hlist",
function(head, display_type, need_penalties)
head = tag_math.callback(head, display_type, need_penalties)
return node.mlist_to_hlist(head, display_type, need_penalties)
end, "tag_math")
}}
\begin{document}
$
\frac{1}{2\pi i} \int\limits_\gamma f\left(x^{\symbf{N}\in\mathbb{C}^{N\times 10}}\right)
= \sum_{k=1}^m n(\gamma;a_k)\Res(f;a_k)\,.
$
\[
\frac{1}{\sqrt{2\pi}^2 i} \int\limits_\gamma f\left(x^{\symbf{N}\in\mathbb{C}^{N\times 10}}\right)
= \sum_{k=1}^m n(\gamma;a_k)\Res(f;a_k)\,.
\]
$a^2 + b^2 = c^2$
$$a^2 + b^2 = c^2$$
\(a^2 + b^2 = c^2\)
\[a^2 + b^2 = c^2\]
% tag_math warning: no conversion available for sub_box
% That is because \eqno is a \hbox in math mode
\begin{equation}
a^2 + b^2 = c^2
\end{equation}
% Ideas for handling environments:
%
% Instead of flushing /ActualText for each mlist immediately, insert whatsits
% to delimit the environment and insert a whatsit at the head or tail with the
% /ActualText inside for each mlist. Then in post_linebreak_filter (or
% earlier?), scan for environment and the nested /ActualText whatsits and
% assemble a single BDC and EMC whatsit from them.
% Clear downside: this is a two stage process which requires extensive
% bookkeeping across callbacks.
\begin{align}
a^2 + b^2 &= c^2 \\
a^2 + b^2 &= c^2
\end{align}
% Accents
% Why does this appear *before* the equation environment in the PDF???
$\threeunderdot{\hat{x}_a^b}_c^d$
\end{document}
local unimath_symbols = {}
local f = io.open(kpse.find_file("unicode-math-table.tex"), "r")
for line in f:lines() do
local slot, cmd = string.match(line, [[^\UnicodeMathSymbol{"([%a%d]*)}{([^}%s]*)%s*}]])
if slot then
unimath_symbols[tonumber(slot, 16)] = cmd
end
end
f:close()
return {
convert_char = function(c)
return unimath_symbols[c] or utf.char(c)
end
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment