马塞尔的回答解决了最初的问题。我在最初的问题下添加了他回答的后续问题。后续问题以类似的引文开头,内容是“后续问题从这里开始:”
我正在尝试学习 luatex 节点库,以下是我想作为实验做的事情:
- 在每一行文本中附加一个 whatsit save_pos 节点。
- 遍历所有 whatsis save_pos 节点以检索换行符/行尾的位置,并将其保存到某个数组中以在页面末尾或下一页开头使用。
我知道如何做 1,但不知道如何做 2(无法从 luatex 手册中找出如何做 2 的方法)。
据我所知,页面上的位置只有在页面处理的某个点之后才为人所知,所以我可能无法立即使用换行回调来了解该信息。虽然我不知道如何遍历页面的所有元素(在页面构建过程结束时,或在下一页开始时的上一页)以获取所有换行符的位置列表。理想情况下,我希望遍历页面上的所有元素,因为 save_pos 可以放在任何地方(hbox 内、vbox 内、主垂直列表、页眉/页脚或 textpos 覆盖等)。
这是我目前的代码:
\documentclass[notitlepage,letterpaper]{article}
%\usepackage{lua-visual-debug}
\usepackage[absolute]{textpos}
\usepackage[letterpaper,left=0in,right=0in,top=1in,bottom=1in]{geometry}
\usepackage[expansion=alltext,shrink=20,stretch=20]{microtype}
\usepackage{fontspec}
\usepackage{blindtext}
%\setmainfont{Verdana} % Commented this as standard linux installs do not come with Verdana, check Marcel's comment to this question.
\directlua{
function my_post_lb_filter(head,groupcode)
local HLIST = node.id("hlist") % node.id for a line of text in vertical list
for n in node.traverse(head) do % For every subnode within node head
if n.id==HLIST then % If its a line of text
local savepos = node.new("whatsit","save_pos")
n.head = node.insert_after(n.head,node.tail(node.list),savepos)
end
end
return head
end
luatexbase.add_to_callback('post_linebreak_filter', my_post_lb_filter, 'Play with luatex node library')
}
\begin{document}
\raggedright
\newbox\myoddvbox
\newbox\myoddvboxsplit
\global\setbox\myoddvbox=\vbox{{\hsize=2in \blindtext[1]\endgraf}}%
\setbox\myoddvboxsplit=\vsplit\myoddvbox to 4cm
\begin{textblock*}{0.5\linewidth}[0,0](2in,2in)
\begin{minipage}[t][1cm][t]{0.5\linewidth}%
\box\myoddvboxsplit%
\end{minipage}
\end{textblock*}
\begin{textblock*}{0.5\linewidth}[0,0](5in,2in)
\begin{minipage}[t][1cm][t]{0.5\linewidth}%
\box\myoddvbox%
\end{minipage}
\end{textblock*}
\null
\newpage
% replace following line with a iterate function to print all the positions from lastpage
\the\lastxpos, \the\lastypos.
\end{document}
后续问题从这里开始:
- late_lua节点的执行顺序是怎样的?是FIFO模型吗?
- 可以采取行动的数据性质是什么?我们可以通过 latelua 函数访问并延迟处理 hlist 节点(例如 insert_to)吗?还是说现在太晚了?hlist 节点的生命周期到底有多长,页面发送出去后会释放吗?
- 我们可以将参数传递给 late_lua 函数吗?我尝试将参数传递给该函数
savepos.data = savepos_func("hello")
,但出现了 lua 段错误。 - luatex 手册说 late_lua 节点的字段类型
data
可以是字符串或函数。什么时候应该将字符串分配给 late_lua 节点的数据字段?我尝试分配一个 lua 字符串savepos.data = "hello world"
,但在 shipout 期间出现错误[\latelua]:1: syntax error near 'world'.
position_array
directlua 中的代码范围是什么?我尝试使用\AtBeginShipout
包打印表的大小\usepackage{atbegshi}
,它会nil
在终端上打印(宏print(tostring(table.getn(position_array)))
中的代码很简单\directlua
)。如果我local
从声明中删除local position_array = {}
,那么它会打印数组的正确值(同时处理下一页)。- 为什么不选择函数 getpos_and_reset 作为本地函数,而将函数 getpos_and_reset 和表 position_array 作为本地函数?
答案1
如果您使用的是 Lua 代码,则基本上不需要save_pos
whatsits。whatsitssave_pos
是由 pdfTeX 的 \savepos 原语插入的 whatsits。只有最后一个的位置save_pos
被保存,因此如果您不在这两个 whatsits 之间运行代码,则第一个基本上是无用的。(的典型用途\pdfsavepos
是与 结合使用\write
。使用类似代码\pdfsavepos\write42{(\the\pdflastxpos,\the\pdflastypos)}
可以将位置写入某个文件。)虽然您可以将它与late_lua
回调一起使用以获取当前坐标,但这已经可以在没有save_pos
通过 的情况下获得pdf.getpos
。
所以要得到所有换行符的数组,最简单的方法就是直接添加late_lua
在 shipout 过程中保存换行符位置的节点:
\documentclass[notitlepage,letterpaper]{article}
%\usepackage{lua-visual-debug}
\usepackage[absolute]{textpos}
\usepackage[letterpaper,left=0in,right=0in,top=1in,bottom=1in]{geometry}
\usepackage[expansion=alltext,shrink=20,stretch=20]{microtype}
\usepackage{blindtext}
\directlua{
local position_array = {}
local function savepos_func()
position_array[\csstring\#position_array+1] = {pdf.getpos()}
end
function getpos_and_reset()
local oldpos = position_array
position_array = {}
return oldpos
end
local function my_post_lb_filter(head,groupcode)
local HLIST = node.id("hlist") % node.id for a line of text in vertical list
for n in node.traverse(head) do % For every subnode within node head
if n.id==HLIST then % If its a line of text
local savepos = node.new("whatsit","late_lua")
savepos.data = savepos_func
n.head = node.insert_after(n.head,node.tail(node.list),savepos)
end
end
return head
end
luatexbase.add_to_callback('post_linebreak_filter', my_post_lb_filter, 'Play with luatex node library')
}
\begin{document}
\raggedright
\newbox\myoddvbox
\newbox\myoddvboxsplit
\global\setbox\myoddvbox=\vbox{{\hsize=2in \blindtext[1]\endgraf}}%
\setbox\myoddvboxsplit=\vsplit\myoddvbox to 4cm
\begin{textblock*}{0.5\linewidth}[0,0](2in,2in)
\begin{minipage}[t][1cm][t]{0.5\linewidth}%
\box\myoddvboxsplit%
\end{minipage}
\end{textblock*}
\begin{textblock*}{0.5\linewidth}[0,0](5in,2in)
\begin{minipage}[t][1cm][t]{0.5\linewidth}%
\box\myoddvbox%
\end{minipage}
\end{textblock*}
\null
\newpage
% replace following line with a iterate function to print all the positions from lastpage
\directlua{
for _, pos in ipairs(getpos_and_reset()) do
tex.sprint('(' .. tostring(pos[1]) .. ', ' .. tostring(pos[2]) .. ') ')
end
}
\end{document}
关于使用局部变量和全局变量的注意事项:如您所见,我将大多数变量声明为local
。这通常是一个好主意,可以避免污染全局命名空间。(它也比使用全局变量快一点)尤其是从不在本地my_post_lb_filtersavepos_func
之外使用。\directlua``block, so it is best declared
. Similar for
position_array
可以是本地的也可以是全局的,这取决于你何时需要访问它。当前代码要求你在每个页面上重置数组以获得可靠的条目,因此将其getpos_and_reset
用作全局接口,而不是position_array
直接访问,以试图强迫人们重置数组。如果你在每次发货前添加另一种可靠地重置它的方法,你可以安全地将其设为position_array
全局的。