如何使用 LuaTeX 访问段落的最后一行

如何使用 LuaTeX 访问段落的最后一行

考虑以下代码,使用传统的 TeX 编程,通过一系列的装箱和拆箱,抓住段落的最后一行并将其居中。

\documentclass{article}
\usepackage{lipsum}
\begin{document}
\def\centerlastline#1{%
       \bgroup
       \setbox0=\vbox{\bfseries\noindent #1}%
       \setbox1=\vbox{%
            \unvbox0
            \setbox2=\lastbox
            \hbox to \linewidth{\hfill\unhbox2 \hfill}%
       }%
       \unvbox1
      \egroup
  }%
\centerlastline{\lipsum[3]}
\end{document}

如何使用 LuaTeX 访问段落构建器及其节点来实现相同的目的?

答案1

经过几个小时的失败尝试后,我终于找到了一个可行的解决方案。问题在于如何为glue节点设置正确的值。不幸的是,这方面的文档很少,所以我花了太多时间破解节点转储,但毫无进展。

\documentclass{article}
\usepackage{lipsum}
\usepackage{luatexbase}
% we will use node attributes to start/stop processing of paragraphs. 
% This is the most flexible way to support multiple paragraphs at once
\newluatexattribute\centerattr
\directlua{%
  luatexbase.add_to_callback("post_linebreak_filter", function(head)
  local first_glyph = function(h)
        for i in node.traverse_id(37,h) do 
                return i 
           end
        end
        local get_last = function(head, id)
          local x = node.count(id, head)
          for n in node.traverse_id(id,head) do
            x = x - 1
            if x == 0 then return n end
          end
          return false
        end
        % get last line
        local n = get_last(head,0)
        local attr_num = luatexbase.attributes.centerattr
        local attr_val = node.has_attribute(n, attr_num) or 0
        if  attr_val < 1 then return head end
        % make hfill glue
        local make_hfill  = function()
           local hfill = node.new("glue")
           hfill.spec = node.new("glue_spec")
           hfill.spec.stretch = 2^15
            % set stretch_order to glue_order value of parent hlist
           hfill.spec.stretch_order = n.glue_order %3
           return hfill
        end

        local hfill = make_hfill()
        local hfill2 = make_hfill()
        % add first hfill at the beginning
        hfill.next = n.head
        local last_glyph = get_last(hfill, 10)
        hfill2.next = last_glyph.next
        % and second hfill at the end
        last_glyph.next= hfill2
    n.head = hfill 
    return head
   end, "center last line")
  }
\begin{document}
\def\luacenterlastline#1{%
       \bgroup
             \centerattr=1
       \bfseries\noindent #1%
      \egroup
}
\luacenterlastline{\lipsum[3]}

\lipsum[3]

\noindent hello world

\centerattr=1
\noindent hello world
\end{document}

我决定使用与 Yannis 的框处理示例不同的方法。使用节点处理回调,因为它可以提供更大的灵活性。我们可以一次处理多个段落,甚至可以处理整个文档。为了指导处理,使用节点属性。这是 LuaTeX 中的新概念,我们可以声明属性并为其分配一个值,并且所有后续节点的属性都将设置为该值。

使用luatexbase包,我们可以声明新属性并分配一个值

\newluatexattribute\centerattr
\centerattr=1

另一方面Lua,我们可以使用以下命令获取属性编号和值:

local attr_num = luatexbase.attributes.centerattr
local attr_val = node.has_attribute(n, attr_num) or 0

属性是数字,因此我们首先必须从LaTeX端使用的名称中获取属性编号,然后获取具有该节点编号的属性值n

if  attr_val < 1 then return head end

在这种情况下,如果属性未设置或者我们手动将其设置为 0,我们将返回未改变的节点列表。

现在我们必须决定使用哪个节点回调。用于处理换行后的段落post_linebreak_filter。我们可以在此回调中访问的节点列表结构如下:

 hlist
 glue
 hlist
 ...

hlist是一个水平框,其中的子节点可在head字段中访问。glue这里来自\baselineskip

使用此函数很容易获取最后一行:

        local get_last = function(head, id)
          local x = node.count(id, head)
          for n in node.traverse_id(id,head) do
            x = x - 1
            if x == 0 then return n end
          end
          return false
        end

head是要处理的节点列表,id是要处理的节点类型。因此我们可以hlist使用以下命令获取最后一个

 local n = get_last(head,0)

现在困难的事情是,我仍然还没有完全理解,而且是我经过大量实验后才发现的。hfill类型glue

        local make_hfill  = function()
           local hfill = node.new("glue")
           hfill.spec = node.new("glue_spec")
           hfill.spec.stretch = 2^15
           % set stretch_order to glue_order value of parent hlist
           hfill.spec.stretch_order = n.glue_order %3
           return hfill
        end

使用此功能,将创建两个粘合节点,一个添加到节点列表的开头,第二个添加到结尾,因此内容将居中。

现在我将解释一下我在找到此解决方案之前遇到的问题:

当我检查用\hfill命令插入的节点时,它们具有以下规格值:

 stretch = 2^16
 stretch_order = 3

当我将glue具有这些值的节点添加到节点列表时,什么也没发生。后来我发现的值stretch_order必须与glue_order父级的值相同hlisthlist包含显式\hfill的值为glue_order3,但普通hlist的设置glue_order为 2,因此没有应用收缩。我真的不知道为什么,TeX希望一些内部专家能解释一下。

所以我将 的值改为stretch_orderglue_order但现在线条向右对齐,忽略了第二个hfill节点。然后我调整了stretch值,发现 的值2^15有效。我不知道为什么 不起作用2^16,当 插入的节点\hfill有这个值时。

在实现 hfill 的正确值之后glue,其余的工作就很容易了,只需next在节点之间设置指针即可。

在此处输入图片描述


旧答案

我将发布部分解决方案,因为我遇到了一些问题,也许有人知道解决方案。

\documentclass{article}
\usepackage{lipsum}
\begin{document}
\def\luacenterlastline#1{%
       \bgroup
       \setbox0=\vbox{\bfseries\noindent #1}%
             \directlua{%
                local char =unicode.utf8.char
                % get contents of box 0
                local head = tex.box[0].head
                % get last hlist -- this means line
                local x = node.count(0,head)
                for n in node.traverse_id(0, head) do
                  x = x - 1
                    % if x is 0 then we are in the last line
                  if x == 0 then 
                      % make hfill node
                    local hfill = node.new("glue")
                    hfill.spec = node.new("glue_spec")
                    hfill.spec.stretch = 2^16
                    hfill.spec.stretch_order = 3
                        % make testing node, because node inserting doesn't work
                    local pok = node.new(37)
                    pok.char = 64
                    pok.next = n.head
                        % print all glyphs on a line
                        % note the @ at the beginning. our test node is printed
                        for c in  node.traverse_id(37,pok) do
                          print(char(c.char))
                    end
                        n.head = pok 
                        % this doesn't work neither
                    %n.head = node.insert_before(n.head,n.head,pok)
                    end
                end
                tex.box[0].head = head
             }
             \unvbox0
      \egroup
}
\luacenterlastline{\lipsum[3]}
\end{document}

框的内容可通过tex.box函数访问。节点列表在head字段中。在本例中,有多个 id 为 的节点0,即hlist,节点被分成几行。我们计算这些节点并循环它们,仅在最后一个 中激活处理。创建hlist类型的新节点,并将和字段设置为最大可拉伸性。然后我尝试将此节点插入当前节点的开头,但这就是我卡住的地方,因为它没有显示在文档中。gluestretchstretch_orderhlist

我确信这是我这边的一个愚蠢问题,但我无法解决它。为了测试,我添加了另一个glyph类型的节点,它应该将@char 添加到 hlist 中,但它也不起作用。奇怪的是,如果我打印 hlist 的内容,@字符会打印在终端上,因此这个字形是新 hlist 的一部分,但它无法进入最终文档中使用的节点列表。我确信这只是一个愚蠢的错误,但我现在应该去睡觉了,所以也许我明天会解决这个问题。

答案2

在此处输入图片描述

\documentclass{article}
\usepackage{lipsum}
\begin{document}


\directlua{function last_line(h)
ll=node.slide(h)
newh = node.remove(h,ll)
callback.register("post_linebreak_filter",nil)
tex.setbox(0,ll)
return h
end
}
\directlua{callback.register("post_linebreak_filter", last_line)}
\lipsum[3]


\begin{center}
\fbox{\unhbox0}
\end{center}
\end{document}

答案3

您也可以在 LuaTeX 中使用传统的 TeX 编程。你不需要使用装箱和拆箱完成你的任务。你可以尝试:

\def\centerlastline#1{\par
   {\leftskip=0pt plus1fil \rightskip=0pt plus-1fil \parfillskip=0pt plus2fil
   \noindent #1\par}%
}

\centerlastline{\lipsum[3]}          

在此处输入图片描述

相关内容