通常很难判断在\looseness=-1
段落中添加内容是否真的会成功减少一行。如何向段落生成器添加回调,以便对每个段落进行注释以显示其作用位置?
我在想类似的事情:对于每个段落,正常构建它,然后使用减少的松散度再次构建它以比较行数。然后输出“正常”段落,但如果减少松散度的段落更短,则最后一行显示为红色。
我问这个问题的原因是:我正在手动调整一本书以摆脱孤行和孤行,我花了很多时间在各个页面之间跳来跳去寻找 +1 或 -1 松散度可以起作用的地方,并思考如何将几个调整结合起来。目前我必须暂时添加\looseness=-1
、重新编译并检查结果,因此建议的回调将有很大帮助。
答案1
编辑:代码(更新 2)现在使用不同的颜色来提供更多信息:
- 红色:段落可以缩小
- 蓝色:段落可以展开
- 洋红色:段落可以扩大或缩小
- 暗青色:可以使用当前紧急拉伸值扩展段落
- 暗粉色:段落可以通过紧急拉伸进行扩展或缩小
老的:以下是解决方案的首次尝试:
\documentclass[twocolumn]{article}
\usepackage[margin=2cm]{geometry}
\usepackage{luatexbase}
\usepackage{lipsum}
% This doesn't seem to work when using tex.linebreak
\setlength\parskip{0pt}
\begin{document}
\directlua{
% Use method aliases for better performance
local nodeid = node.id
local nodenew = node.new
local nodecopy = node.copy
local nodetail = node.tail
local nodeinsertbefore = node.insert_before
local nodeinsertafter = node.insert_after
local nodetraverseid = node.traverse_id
% Get node ids from their names
local HLIST = nodeid("hlist")
local WHAT = nodeid("whatsit")
local COL = node.subtype("pdf_colorstack")
% Make nodes for beginning and end of colored regions
local color_push = nodenew(WHAT,COL)
local color_pop = nodenew(WHAT,COL)
color_push.stack = 0
color_pop.stack = 0
color_push.data = "1 0 0 rg" % PDF code for RGB red
color_push.command = 1
color_pop.command = 2
% Function to color the last line in the given list
local color_last_line = function (n)
% Get the last hlist in the given list
local lastLine
for line in nodetraverseid(HLIST, n) do
lastLine = line
end
% Surround it with color start/stop
lastLine.head = nodeinsertbefore(lastLine.head, lastLine.head, nodecopy(color_push))
nodeinsertafter(lastLine.head, nodetail(lastLine.head), nodecopy(color_pop))
end
% Callback to color the last line wherever a decreased looseness would work
local linebreak_filter_test_looseness = function (head, is_display)
% Build a copy of the paragraph with decreased looseness
local nM1, iM1 = tex.linebreak(node.copy_list(head), {looseness=tex.looseness-1})
% Build the paragraph normally
local n, i = tex.linebreak(head)
% If decreasing the looseness does decrease the line count, color the last line
if iM1.prevgraf < i.prevgraf then
color_last_line(n)
end
return n
end
% Register callback
luatexbase.add_to_callback("linebreak_filter", linebreak_filter_test_looseness, "linebreak_filter_test_looseness")
}
\lipsum[1]
\lipsum[6]
\lipsum[71]
\lipsum[3]
\lipsum[35]
\lipsum[76]
\lipsum[7-8]
\end{document}
你可以看到结果这里。
问题是 parskip 设置不再起作用。有关比较,请参阅这里对于不使用回调时的输出:左右列的行完全对齐。
这似乎是使用回调调用 tex.linebreak 构建段落时的一个普遍问题……只需这样做\directlua{ luatexbase.add_to_callback("linebreak_filter", tex.linebreak, "myfilter") }
即可重现该问题。
更新:这是解决该问题的方法tex.linebreak
:换行前过滤器测试是否\looseness=-1
可以工作,并设置一个变量,由换行后过滤器检查该变量以进行着色。通过外部变量传达信息有点不方便,但这样我就让 LuaTeX 在内部构建段落,这样tex.linebreak
就不会有机会弄乱基线跳过粘连。
\documentclass[twocolumn]{article}
\usepackage[margin=2cm]{geometry}
\usepackage{luatexbase}
\usepackage{lipsum}
\usepackage[callback=]{nodetree}
% This doesn't seem to work when using tex.linebreak
\setlength\parskip{0pt}
\begin{document}
\directlua{
% Use method aliases for better performance
local nodeid = node.id
local nodenew = node.new
local nodecopy = node.copy
local nodetail = node.tail
local nodeinsertbefore = node.insert_before
local nodeinsertafter = node.insert_after
local nodetraverseid = node.traverse_id
% Get node ids from their names
local HLIST = nodeid("hlist")
local WHAT = nodeid("whatsit")
local COL = node.subtype("pdf_colorstack")
% Make nodes for beginning and end of colored regions
local color_push = nodenew(WHAT,COL)
local color_pop = nodenew(WHAT,COL)
color_push.stack = 0
color_pop.stack = 0
color_push.data = "1 0 0 rg" % PDF code for RGB red
color_push.command = 1
color_pop.command = 2
% Set to true when the next post-linebreak filter should color the last line
local ColorLastLine = false
% Function to color the last line in the given list
local color_last_line = function (n)
% Get the last hlist in the given list
local lastLine
for line in nodetraverseid(HLIST, n) do
lastLine = line
end
% Surround it with color start/stop
lastLine.head = nodeinsertbefore(lastLine.head, lastLine.head, nodecopy(color_push))
nodeinsertafter(lastLine.head, nodetail(lastLine.head), nodecopy(color_pop))
end
% Callback to check if decreasing the looseness would decrease the line count
local pre_linebreak_test_looseness = function (head, groupeCode)
% Build a copy of the paragraph with decreased looseness
local nM1, iM1 = tex.linebreak(node.copy_list(head), {looseness=tex.looseness-1})
% Build a copy of the paragraph normally
local n, i = tex.linebreak(node.copy_list(head))
% Store whether decreasing the looseness does decrease the line count, for "post" callback
ColorLastLine = iM1.prevgraf < i.prevgraf
return true
end
% Callback to colorize the last line of the paragraph when ColorLastLine is true
local post_linebreak_color_last_line = function (head, groupcode)
if ColorLastLine then
color_last_line(head)
end
return true
end
% Register callbacks
luatexbase.add_to_callback("pre_linebreak_filter", pre_linebreak_test_looseness, "pre_linebreak_test_looseness")
luatexbase.add_to_callback("post_linebreak_filter", post_linebreak_color_last_line, "post_linebreak_color_last_line")
}
\lipsum[1]
\lipsum[6]
\lipsum[71]
\lipsum[3]
\lipsum[35]
\lipsum[76]
\lipsum[7-8]
\end{document}
更新 2:这是一个独立的 Lua 文件,它对段落的最后一行使用不同的颜色,取决于它是否可以收缩、扩展或两者兼而有之(如本答案顶部所述)。
文件widow-assist.lua
:
-- Use method aliases for better performance
local nodecopy = node.copy
local nodecopylist = node.copy_list
local nodetail = node.tail
local nodeinsertbefore = node.insert_before
local nodeinsertafter = node.insert_after
local nodetraverseid = node.traverse_id
-- Get node ids from their names
local HLIST = node.id("hlist")
local WHAT = node.id("whatsit")
local COL = node.subtype("pdf_colorstack")
-- Make nodes for beginning and end of colored regions
local color_p1 = node.new(WHAT,COL)
local color_p1s = node.new(WHAT,COL)
local color_m1 = node.new(WHAT,COL)
local color_pm1 = node.new(WHAT,COL)
local color_pm1s = node.new(WHAT,COL)
local color_pop = node.new(WHAT,COL)
color_p1.stack = 0
color_p1.command = 1
color_p1.data = "0 0 1 rg" -- PDF code for RGB blue
color_p1s.stack = 0
color_p1s.command = 1
color_p1s.data = "0 0.7 0.7 rg" -- PDF code for RGB dark cyan
color_m1.stack = 0
color_m1.command = 1
color_m1.data = "1 0 0 rg" -- PDF code for RGB red
color_pm1.stack = 0
color_pm1.command = 1
color_pm1.data = "1 0 1 rg" -- PDF code for RGB magenta
color_pm1s.stack = 0
color_pm1s.command = 1
color_pm1s.data = "1 .5 .5 rg" -- PDF code for RGB pink
color_pop.stack = 0
color_pop.command = 2
-- Color to use for last line in the next post-linebreak filter call (nil = no color)
local LastLineColor = nil
-- Function to color the last line in the given list
local color_last_line = function (n)
-- Get the last hlist in the given list
local lastLine
for line in nodetraverseid(HLIST, n) do
lastLine = line
end
-- Surround it with color start/stop
lastLine.head = nodeinsertbefore(lastLine.head, lastLine.head, nodecopy(LastLineColor))
nodeinsertafter(lastLine.head, nodetail(lastLine.head), nodecopy(color_pop))
end
-- Callback to check if changing the looseness by +-1 would affect the line count
local pre_linebreak_test_looseness = function (head, groupeCode)
-- Disable underfull and overfull boxes reporting
luatexbase.add_to_callback("hpack_quality", function() end, "hpqfilter")
luatexbase.add_to_callback("vpack_quality", function() end, "vpqfilter")
-- Build a copy of the paragraph normally
local n, i = tex.linebreak(nodecopylist(head))
-- Build a copy of the paragraph with increased looseness and default emergency stretch
local nP1s, iP1s = tex.linebreak(nodecopylist(head), {looseness=tex.looseness+1})
local nP1, iP1
if iP1s.prevgraf > i.prevgraf then
-- It worked with the default emergency stretch, let's try without
nP1, iP1 = tex.linebreak(nodecopylist(head), {looseness=tex.looseness+1, emergencystretch=0})
else
-- Didn't work with emergency stretch, no point to try without
nP1, iP1 = n, i
end
-- Build a copy of the paragraph with decreased looseness
local nM1, iM1 = tex.linebreak(nodecopylist(head), {looseness=tex.looseness-1})
-- Reenable underfull and overfull boxes reporting
luatexbase.remove_from_callback("hpack_quality", "hpqfilter")
luatexbase.remove_from_callback("vpack_quality", "vpqfilter")
-- Set color to use in the post-linebreak callback
if iP1.prevgraf > i.prevgraf and iM1.prevgraf < i.prevgraf then
-- Both +1 and -1 looseness would work
LastLineColor = color_pm1
elseif iP1s.prevgraf > i.prevgraf and iM1.prevgraf < i.prevgraf then
-- Both +1 and -1 looseness would work, but +1 only with emergency stretch
LastLineColor = color_pm1s
elseif iP1.prevgraf > i.prevgraf then
-- Only +1 looseness would work
LastLineColor = color_p1
elseif iP1s.prevgraf > i.prevgraf then
-- Only +1 looseness would work and only thanks to the emergency stretch
LastLineColor = color_p1s
elseif iM1.prevgraf < i.prevgraf then
-- Only -1 looseness would work
LastLineColor = color_m1
else
LastLineColor = nil
end
return true
end
-- Callback to colorize the last line of the paragraph when ColorLastLine is true
local post_linebreak_color_last_line = function (head, groupcode)
if LastLineColor then
color_last_line(head)
end
return true
end
-- Register callbacks
luatexbase.add_to_callback("pre_linebreak_filter", pre_linebreak_test_looseness, "pre_linebreak_test_looseness")
luatexbase.add_to_callback("post_linebreak_filter", post_linebreak_color_last_line, "post_linebreak_color_last_line")
使用方法如下:
\documentclass{article}
\usepackage{lipsum}
\usepackage{luatexbase}
\directlua{dofile("widow-assist.lua")}
\begin{document}
\lipsum[1-5]
\end{document}