特征

特征

请帮我创建一个正则表达式,例如$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 告诉我,你想标记数学以便于访问。下面我介绍一下我的解决方案。

特征

  1. 在回调内部,函数mlist以线性方式(而非递归方式)遍历,并且convert对列表中的每个节点调用该函数。

  2. convert函数检查节点的类型,并在表中查找适当的转换函数converters。如果没有可用的转换器,则会在日志中打印警告并返回空字符串,即忽略该节点。

  3. 转换函数通常只调用convert每个字段并以合理的方式连接结果。只有当遇到被忽略的节点或节点的类型为 时,转换才会停止,math_char因为我们知道该怎么做。

  4. 对于类型的节点,math_char我们提取字符。字符存储为字体中插槽的编号。这就是我们需要的原因unicode-math,否则结果太模糊,因为它取决于字体。对于 Unicode 字符,我们可以直接发出 Unicode 字符,也可以使用 Unicode 数学符号表来查找生成该符号的命令并发出该符号。

限制

  1. 下面的代码只是一个例子。许多节点类型都没有处理,尤其是类型sub_box,因此嵌入的水平和垂直模式材料完全丢失。

  2. 嵌套mlists 目前表现不佳。我认为这是 的一个限制/ActualText,无法嵌套。

  3. 栅栏仅输出为带有\left和 的字符\right。手动缩放的分隔符是嵌套在sub_box节点中的栅栏,目前尚未处理。

  4. 在源文件中,我们使用一个数学运算符\Res。当前输出为Res,即标记完全丢失。这实际上非常棘手,因为\Res本质上是\mathop{<switch to text font but stay in math mode> Res}。切换到文本字体的操作原则上是在math_char节点的fam字段中编码的,但现在无法处理。

  5. 代码中更多限制以 标记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}

往返输出

相关内容