请帮我创建一个正则表达式,例如$a+b$
用 和\(a+b\)
替换$$a-b$$
,\[a-b\]
这样我就可以挂接到\(...\)
和\[...\]
宏中,用 注释所包含的材料,以实现可访问性。如果每个 之前/ActualText
没有字符,则必须进行替换。\
$
这是我的代码,它$$
工作得很好,但$
我有错误,因为在某些地方$
被替换为\(
,或者,\)
即使之前有\
char。
\documentclass{article}
\begin{document}
\directlua{require("test.lua")}
test
summ
\$$$a=\sum_{i=0}^n{a_i^2}+1\$$$ \$$a-b=0$\$
\[a=\sum_{i=0}^n{a_i^2}-1\] \(a=\sum_{i=0}^n{a_i^2}\)
\end{document}
test.lua
function process_input_buffer(buffer)
texio.write_nl("callback get line "..buffer)
buffer=buffer:gsub("([^\\]?)%$%$(.-)([^\\]?)%$%$","%1\\[%2%3\\]")
texio.write_nl("callback get line "..buffer)
buffer=buffer:gsub("([^\\]?)%$(.-)([^\\]?)%$","%1\\(%2%3\\)")
texio.write_nl("callback get line "..buffer)
return buffer
end
luatexbase.add_to_callback("process_input_buffer",process_input_buffer,"buffer")
答案1
与其尝试处理文档源并处理各种问题(例如 catcodes),不如在回调/ActualText
中标记公式要容易得多mlist_to_hlist
。
在一封私人邮件中,Ulrike 告诉我,你想标记数学以便于访问。下面我介绍一下我的解决方案。
特征
在回调内部,函数
mlist
以线性方式(而非递归方式)遍历,并且convert
对列表中的每个节点调用该函数。该
convert
函数检查节点的类型,并在表中查找适当的转换函数converters
。如果没有可用的转换器,则会在日志中打印警告并返回空字符串,即忽略该节点。转换函数通常只调用
convert
每个字段并以合理的方式连接结果。只有当遇到被忽略的节点或节点的类型为 时,转换才会停止,math_char
因为我们知道该怎么做。对于类型的节点,
math_char
我们提取字符。字符存储为字体中插槽的编号。这就是我们需要的原因unicode-math
,否则结果太模糊,因为它取决于字体。对于 Unicode 字符,我们可以直接发出 Unicode 字符,也可以使用 Unicode 数学符号表来查找生成该符号的命令并发出该符号。
限制
下面的代码只是一个例子。许多节点类型都没有处理,尤其是类型
sub_box
,因此嵌入的水平和垂直模式材料完全丢失。嵌套
mlist
s 目前表现不佳。我认为这是 的一个限制/ActualText
,无法嵌套。栅栏仅输出为带有
\left
和 的字符\right
。手动缩放的分隔符是嵌套在sub_box
节点中的栅栏,目前尚未处理。在源文件中,我们使用一个数学运算符
\Res
。当前输出为Res
,即标记完全丢失。这实际上非常棘手,因为\Res
本质上是\mathop{<switch to text font but stay in math mode> Res}
。切换到文本字体的操作原则上是在math_char
节点的fam
字段中编码的,但现在无法处理。代码中更多限制以 标记
FIXME
。
您还可以在我的 GitHub 上找到此代码以及其他一些随机改进:
https://gist.github.com/hmenke/a41574ef0b5000635986f0dec73e066f
tag_math.lua
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()
local function convert_char(c)
return unimath_symbols[c] or utf.char(c)
end
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" or subtype == "opdisplaylimits" then
result = result .. "\\limits"
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 tag_math(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 node.mlist_to_hlist(head, display_type, need_penalties)
end
luatexbase.add_to_callback("mlist_to_hlist", tag_math, "tag_math")
test.tex
\documentclass{article}
\pagestyle{empty}
\usepackage{amsmath}
\usepackage{unicode-math}
\DeclareMathOperator\Res{Res}
\AtBeginDocument{\directlua{dofile("tag_math.lua")}}
\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}{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)\,.
\]
\end{document}
运行pdftotext
输出后我得到
$ pdftotext test.pdf -
\(\frac{1}{2\mitpi\miti}\int\limits_{\mitgamma}\mitf\left\lparen\mitx^{\mbfN\in\BbbC^{\mitN\times10}}\right\rparen\equal\sum\limits_{\mitk\equal1}^{\mitm}\mitn\lparen\mitgamma\mathsemicolon\mita_{\mitk}\rparenRes\lparen\mitf\mathsemicolon\mita_{\mitk}\rparen \mathperiod\)
\[\frac{1}{2\mitpi\miti}\int\limits_{\mitgamma}\mitf\left\lparen\mitx^{\mbfN\in\BbbC^{\mitN\times10}}\right\rparen\equal\sum\limits_{\mitk\equal1}^{\mitm}\mitn\lparen\mitgamma\mathsemicolon\mita_{\mitk}\rparenRes\lparen\mitf\mathsemicolon\mita_{\mitk}\rparen \mathperiod\]
这是几乎有效的 TeX 代码和它几乎往返。
在这个例子中,必须将 修复\rparenRes
为\rparen\opertorname{Res}
,但它会给出几乎相同的输出(除了\int
缺少\limits
)。
\documentclass{article}
\pagestyle{empty}
\usepackage{amsmath}
\usepackage{unicode-math}
\begin{document}
\(\frac{1}{2\mitpi\miti}\int\limits_{\mitgamma}\mitf\left\lparen\mitx^{\mbfN\in\BbbC^{\mitN\times10}}\right\rparen\equal\sum\limits_{\mitk\equal1}^{\mitm}\mitn\lparen\mitgamma\mathsemicolon\mita_{\mitk}\rparen\operatorname{Res}\lparen\mitf\mathsemicolon\mita_{\mitk}\rparen \mathperiod\)
\[\frac{1}{2\mitpi\miti}\int\limits_{\mitgamma}\mitf\left\lparen\mitx^{\mbfN\in\BbbC^{\mitN\times10}}\right\rparen\equal\sum\limits_{\mitk\equal1}^{\mitm}\mitn\lparen\mitgamma\mathsemicolon\mita_{\mitk}\rparen\operatorname{Res}\lparen\mitf\mathsemicolon\mita_{\mitk}\rparen \mathperiod\]
\end{document}