luatex 节点库:迭代 whatsit save_pos 以获取换行符的位置

luatex 节点库:迭代 whatsit save_pos 以获取换行符的位置

马塞尔的回答解决了最初的问题。我在最初的问题下添加了他回答的后续问题。后续问题以类似的引文开头,内容是“后续问题从这里开始:”

我正在尝试学习 luatex 节点库,以下是我想作为实验做的事情:

  1. 在每一行文本中附加一个 whatsit save_pos 节点。
  2. 遍历所有 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}

后续问题从这里开始:

  1. late_lua节点的执行顺序是怎样的?是FIFO模型吗?
  2. 可以采取行动的数据性质是什么?我们可以通过 latelua 函数访问并延迟处理 hlist 节点(例如 insert_to)吗?还是说现在太晚了?hlist 节点的生命周期到底有多长,页面发送出去后会释放吗?
  3. 我们可以将参数传递给 late_lua 函数吗?我尝试将参数传递给该函数savepos.data = savepos_func("hello"),但出现了 lua 段错误。
  4. luatex 手册说 late_lua 节点的字段类型data可以是字符串或函数。什么时候应该将字符串分配给 late_lua 节点的数据字段?我尝试分配一个 lua 字符串savepos.data = "hello world",但在 shipout 期间出现错误[\latelua]:1: syntax error near 'world'.
  5. position_arraydirectlua 中的代码范围是什么?我尝试使用\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_poswhatsits。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全局的。

相关内容