我有一个期刊布局,其中第一页(包含文章标题)采用奇数 1.5 列布局。包含元数据的第一列比包含正文前几段的右列要窄。从第二页开始,布局采用均匀的两列布局,两列大小相同。
我通过定义一个宏来实现这一点\@output@first@dblcol
,该宏为第一页分叉 LaTeX 内核,并使用包中的钩子\@outputdblcol
在标题页之后切换回内核定义,除非作者/编辑手动设置了分页符。\AtBeginShipoutNext
atbegshi
MWE 的整个源代码是:
\documentclass[twocolumn]{article}
\RequirePackage{lipsum}
\RequirePackage{atbegshi}
\textwidth124mm
\columnwidth60mm
\columnsep4mm
\author{Max Mustermann*, John Doe}
\title{Test document with a longer title}
\makeatletter
\let\ltx@outputdblcol\@outputdblcol
\def\@output@first@dblcol{%
\if@firstcolumn
\global\@firstcolumnfalse
\global\setbox\@leftcolumn\copy\@outputbox
\splitmaxdepth\maxdimen
\vbadness\maxdimen
\setbox\@outputbox\vbox{\unvbox\@outputbox\unskip}%
\setbox\@outputbox\vsplit\@outputbox to\maxdimen
\toks@\expandafter{\topmark}%
\xdef\@firstcoltopmark{\the\toks@}%
\toks@\expandafter{\splitfirstmark}%
\xdef\@firstcolfirstmark{\the\toks@}%
\ifx\@firstcolfirstmark\@empty
\global\let\@setmarks\relax
\else
\gdef\@setmarks{%
\let\firstmark\@firstcolfirstmark
\let\topmark\@firstcoltopmark}%
\fi
\else
\global\@firstcolumntrue
\setbox\@outputbox\vbox{%
\hb@xt@\textwidth{%
\hb@xt@40mm{\box\@leftcolumn \hss}%
{\normalcolor\vrule \@width\columnseprule}%
\hfil
\hb@xt@80mm{\box\@outputbox \hss}}}%
\@combinedblfloats
\@setmarks
\@outputpage
\begingroup
\@dblfloatplacement
\@startdblcolumn
\@whilesw\if@fcolmade \fi{\@outputpage
\@startdblcolumn}%
\endgroup
\fi}%
\def\maketitle{%
\let\is@firstpage\relax
\let\@outputdblcol\@output@first@dblcol
\twocolumn[{\bfseries\large\@author}\par\vskip10bp{\LARGE\bfseries\sffamily\@title}\vskip18bp]%
\par
\bgroup
\raggedright
\hsize40mm
\parindent\z@
\textbf{Email:} [email protected]\par
\textbf{Max Mustermann:} University of Life\par
\textbf{Abtract:} \lipsum*[1][1-3]\par
\egroup
\newpage
\hsize80mm
}
\def\titlebreak{%
\ifhmode\vadjust{\par\newpage\noindent}\else\newpage\fi%
\global\let\@outputdblcol\ltx@outputdblcol
\global\let\is@firstpage\@undefined
\global\columnwidth60mm
\global\hsize\columnwidth
\global\linewidth\columnwidth
}
\AtBeginShipoutNext{\ifx\is@firstpage\relax\titlebreak\fi}
\makeatother
\begin{document}
\maketitle
\lipsum[1-3]
\lipsum[4-9]
\end{document}
如您所见,除了分页符所在的段落的其余部分外,此方法效果很好。如果第一个分页符出现在段落内,则要求作者/编辑\titlebreak
在分页符所在的行中手动设置宏。
但我想知道是否有办法使用 LuaLaTeX 自动执行第一个分页符?
问题:有没有办法(可能是 lua hook)让我能够在\titlebreak
LuaLaTeX 中断页面的精确位置(或行)自动注入宏?我如何在现有代码中实现这一点?
答案1
我将在这里使用简化的 MWE,以便更容易看到效果:
\documentclass{article}
\usepackage[english]{babel}
\usepackage{blindtext}
\usepackage{microtype}
\AddToHookNext{shipout}{%
\global\columnwidth=0.5\columnwidth
\global\textwidth=\columnwidth
\global\hsize=\columnwidth
\AddToHookNext{shipout}{%
\global\columnwidth=2\columnwidth
\global\textwidth=\columnwidth
\global\hsize=\columnwidth
}
}
\newdimen\bigright \bigright=\dimexpr\oddsidemargin+1in+\textwidth\relax
\newdimen\smallright \smallright=\dimexpr\oddsidemargin+1in+0.5\textwidth\relax
\AddToHook{shipout/foreground}{%
\put(\dimexpr\oddsidemargin+1in, 0in){%
\line(0,-1){\paperheight}
}%
\put(\bigright, 0in){%
\line(0,-1){\paperheight}
}%
\put(\smallright, 0in){%
\line(0,-1){\paperheight}
}%
}
\begin{document}
\Blindtext[10]
\end{document}
您应该能够使用任何其他方式来更改此处的页面布局;关键是您需要设置下一页的列宽前前一页已运出。这也意味着此解决方案将仅调整每页第一列的宽度,因此任何给定页面上的列都必须具有相同的宽度。
有没有一种方法(可能是 lua hook)可以让我
\titlebreak
在 LuaLaTeX 中断页面的精确点(或行)处自动注入宏?
并非如此。分段严格发生在分页之前(或与分页同时发生),因此在创建段落时,它无法“知道”段落将在何处拆分或是否会跨页面拆分。
使用传统的 TeX 方法,您可以定义一个宏,让段落.aux
在第一次运行时将其分割点写入文件,然后使用\parshape
技巧在第二次运行时正确调整段落的大小。不过这会相当繁琐,而且我不太确定它的效果如何。
另一个选择是使用一些 Lua 技巧。在每个段落被拆分之前,我们可以用属性标记每个节点(字形 + 胶水 + 等)并保存一份副本。然后,当触发输出例程时,我们可以查看contrib_head
(任何保留到下一页的段落),将拆分的节点与拆分前的节点进行匹配,并用新拆分的正确大小的段落替换该段落。这里唯一棘手的事情是没有post_output_filter
,所以我们需要在 shipout 之前而不是在输出例程之后实际运行它。
\documentclass{article}
\usepackage[english]{babel}
\usepackage{blindtext}
\usepackage{microtype}
\csname @ifl@t@r\endcsname\fmtversion{2020-10-01}{}{
% TL2019 compatibility
\usepackage{atbegshi}
\directlua{luatexbase.create_callback("pre_shipout_filter", "list", false)}
\AtBeginShipout{%
\csname columns@run\endcsname
\directlua{luatexbase.call_callback("pre_shipout_filter", tex.box.AtBeginShipoutBox)}%
}
\usepackage{picture}
\def\AddToHookNext#1#2{
\global\expandafter\def\csname columns@run\endcsname{#2}
}
\def\AddToHook#1#2{\AtBeginShipout{\AtBeginShipoutUpperLeftForeground{#2}}}
}
\AddToHookNext{shipout/firstpage}{%
\global\columnwidth=0.5\columnwidth
\global\textwidth=\columnwidth
\global\hsize=\columnwidth
\AddToHookNext{shipout/before}{%
\global\columnwidth=2\columnwidth
\global\textwidth=\columnwidth
\global\hsize=\columnwidth
}
}
\newdimen\bigright \bigright=\dimexpr\oddsidemargin+1in+\textwidth\relax
\newdimen\smallright \smallright=\dimexpr\oddsidemargin+1in+0.5\textwidth\relax
\AddToHook{shipout/foreground}{%
\put(\dimexpr\oddsidemargin+1in, 0in){%
\line(0,-1){\paperheight}
}%
\put(\bigright, 0in){%
\line(0,-1){\paperheight}
}%
\put(\smallright, 0in){%
\line(0,-1){\paperheight}
}%
}
\usepackage{luacode}
\begin{luacode*}
-- Save some LuaTeX internal node functions
local copy_list = node.copy_list
local find_attribute = node.find_attribute
local get_attribute = node.get_attribute
local has_attribute = node.has_attribute
local set_attribute = node.set_attribute
local slide = node.slide
local traverse = node.traverse
local traverse_list = node.traverse_list
-- Define some module-global variables
local attr = luatexbase.new_attribute("mark_paragraph")
local count = 1
local saved = node.new("glue")
local columnwidth = 0
-- Mark each node in a paragraph and save the pre-broken tagged contents
local function save_paragraph(head)
for n in traverse(head) do
set_attribute(n, attr, count)
count = count + 1
end
slide(saved).next = copy_list(head)
columnwidth = tex.dimen.columnwidth
return true
end
luatexbase.add_to_callback("pre_linebreak_filter", save_paragraph, "save_paragraph")
-- Fix the width of any partial paragraphs carried over from the previous page
local function fix_paragraph(head)
-- do nothing if columnwidth has not been changed
if columnwidth == tex.dimen.columnwidth then return true end
-- do nothing if the last contribution is a penalty, meaning that the page break was forced manually
if tex.lists.contrib_head.id == 14 then return true end
local first_index, first_n, first_list
local last_index, last_n, last_list
for list, _, _, inner in traverse_list(tex.lists.contrib_head) do
if not first_index then
first_index = find_attribute(inner, attr)
first_list = list
end
local prev_last_index
local m = inner
repeat
last_index = prev_last_index
prev_last_index, m = find_attribute(m.next, attr)
until (not prev_last_index) or (not m.next)
last_list = list
end
for n in traverse(saved) do
if not first_n and
(get_attribute(n, attr) or 0) >= first_index
then
first_n = n
end
if has_attribute(n, attr, last_index) then
last_n = n
break
end
end
first_n.prev.next = nil
node.flush_list(saved)
saved = node.new("glue")
local new = copy_list(first_n, last_n.next)
local broken = tex.linebreak(new)
first_list.prev.next = broken
slide(broken).next = last_list.next
last_list.next = nil
node.flush_list(first_list)
return true
end
luatexbase.add_to_callback("pre_shipout_filter", fix_paragraph, "fix_paragraph")
\end{luacode*}
\begin{document}
\Blindtext[10]
\end{document}
可能仍存在一些错误,但在我的测试中这似乎运行良好。