考虑以下代码,使用传统的 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
父级的值相同hlist
。hlist
包含显式\hfill
的值为glue_order
3,但普通hlist
的设置glue_order
为 2,因此没有应用收缩。我真的不知道为什么,TeX
希望一些内部专家能解释一下。
所以我将 的值改为stretch_order
。glue_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
类型的新节点,并将和字段设置为最大可拉伸性。然后我尝试将此节点插入当前节点的开头,但这就是我卡住的地方,因为它没有显示在文档中。glue
stretch
stretch_order
hlist
我确信这是我这边的一个愚蠢问题,但我无法解决它。为了测试,我添加了另一个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]}