朋友们,我有一个简单的布局来为我的教区排版歌谱,它由软件包提供的两列multicol
和一个简单包装每首歌曲的自定义环境组成。以下文档可能会让你们对布局有所了解(我添加了颜色只是为了直观地指示每个块;这些颜色可能有助于我们稍后理解我的问题):
\documentclass[a5]{article}
\usepackage{xcolor}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{ \repeatword } { m m }
{
\prg_replicate:nn { #1 } { #2 ~ }
}
\bool_gset_true:N \g_tmpa_bool
\NewDocumentCommand{ \getcolour } { }
{
\bool_if:nTF \g_tmpa_bool
{
\color{blue}
\bool_gset_false:N \g_tmpa_bool
}
{
\color{red}
\bool_gset_true:N \g_tmpa_bool
}
}
\NewDocumentEnvironment{ textblock } { }
{
\setlength{\parindent}{0pt}
\getcolour
}
{ }
\ExplSyntaxOff
\usepackage{multicol}
\begin{document}
\begin{multicols*}{2}
\begin{textblock}
\repeatword{100}{duck}
\end{textblock}
\begin{textblock}
\repeatword{200}{goose}
\end{textblock}
\begin{textblock}
\repeatword{130}{parrot}
\end{textblock}
\begin{textblock}
\repeatword{100}{falcon}
\end{textblock}
\begin{textblock}
\repeatword{150}{owl}
\end{textblock}
\begin{textblock}
\repeatword{150}{swan}
\end{textblock}
\begin{textblock}
\repeatword{100}{chicken}
\end{textblock}
\begin{textblock}
\repeatword{50}{mallard}
\end{textblock}
\end{multicols*}
\end{document}
我有以下输出:
我使用这个布局已经有一年了,我发现它在大多数情况下都很好用,但有时我们的一些歌曲会从一页开始,在下一页结束。在我的示例中,区块owl
反映了我的意思:看到蓝色文本在第 1 页结束,并在第 2 页继续。
我的第一个计划:好吧,让我把每首歌都弄得牢不可破。:)
假设我决定将每个文本块放在一个 中\parbox
。现在事情就完全不同了:
现在我的歌曲不再出现断点。:)
但由于 TeX 保留了顺序,文本中出现了一些空白。现在让我们仔细看看我第一次尝试的第 1 页:
请注意,parrot
文本块比文本块小goose
;如果我可以交换它们之间的位置,也就是说,如果parrot
可以排在前面goose
,那么我就可以在第一页放三首歌曲,而不仅仅是两首:
我想知道是否有办法模拟某种浮点放置行为。请注意,我很清楚这个问题比看起来要难得多。:)
这是区域划分,也是人工智能中一个有趣的研究领域。:)
另请注意,我可以忍受非常天真解决方案,如果有的话。我也接受不,这不可能回答。:)
颇具讽刺意味的是,我有一篇论文介绍了一种解决这个问题的方法。:)
问题是:我讨论的方法使用了 TeX 环境中不可用的几个变量。:(
答案1
较长的文章
我来给大家介绍一下我的解决方案。我用了一把大炮:LuaTeX。算法可以描述如下:
- 发送 LuaTeX
\textheight
并\columnwidth
保存它们。 - 在盒子中虚拟处理所有物品/歌曲并将高度发送到 LuaTeX。
- 优化一下,过程中不断的向终端发送信息。
- 请告知我们有关歌曲重新编排的情况。
- 将结果排版为真实文档(PDF)。
当我们输入您的原始数据时,我们会在终端中获取以下信息:
Processing page 1...
Adding... 8 to 1
Processing page 2...
Adding... 3 to 2
Processing page 3...
Adding... 5 to 4
Processing page 4...
Adding... 7 to 6
1 402.24998 1
2 284.09523 2
3 256.30554 2
4 234.94444 3
5 198.94444 3
6 198.94444 4
7 174.94444 4
8 138.94444 1
The highest processed page is 4.
第一列是歌曲的编号,第二列是歌曲的高度,最后一列是歌曲的放置位置。这意味着第一列是歌曲 1+8,第二列是歌曲 2+3(将构成第 1 页),第三列是歌曲 4+5,最后一列是歌曲 6+7(将构成第 2 页)。我相信一张图片胜过千言万语。
请允许我简单说几句关于优化阶段的话。没有什么奇迹发生(开玩笑的,神父)。
- 第一步是按高度降序排列。
- 开始新的一栏。
- 然后从顶部选择第一首未分配的歌曲并将其放在该页面上。
- 如果所有未分配的歌曲适合剩余空间的该列,则按降序测试它们。如果适合,则标记它们。
- 移至下一页,直至所有歌曲都分配完毕。
在原始数据中,我们可以看到每列只有两首歌曲。我玩了一下,添加了两首新歌,然后开始手动(也可以编程)增加单词数量以完全填满这两页。就是这样,如果你将任何数字增加 1,你将得到三页。我这样做只是为了好玩。
\repeatword{119}{duck}
\repeatword{234}{goose}
\repeatword{132}{parrot}
\repeatword{108}{falcon}
\repeatword{153}{owl}
\repeatword{154}{swan}
\repeatword{100}{chicken}
\repeatword{54}{mallard}
\repeatword{64}{pivo}
\repeatword{56}{mali}
终端显示(在我们的示例中,一页是一列):
Processing page 1...
Adding... 10 to 1
Processing page 2...
Adding... 3 to 2
Processing page 3...
Adding... 5 to 4
Adding... 9 to 4
Processing page 4...
Adding... 7 to 6
Adding... 8 to 6
1 462.24998 1
2 284.09523 2
3 256.30554 2
4 234.94444 3
5 210.94444 3
6 198.94444 4
7 198.94444 4
8 138.94444 4
9 92.62303 3
10 78.94444 1
The highest processed page is 4.
简单来说,就是这些安排:歌曲 1+10(第 1 列)、2+3(第 2 列)、4+5+9(第 3 列),最后歌曲 6+7+8 将被排版在最后一列。这是 PDF 文件的预览。
我们无法再在那里挤进一个单词。在该文档中获得更多单词的唯一机会是删除较长的单词,例如chicken
,然后添加较小的单词,例如owl
。
源代码中有一个新的条件,名为\ifmychoice
从原始数据或更新的数据中选择数据。这是一个很好的起点,您可以自己尝试并进行实验。
我必须说我的 Lua 代码远非最佳,我遇到了一些困难,因为我无法调用table.maxn
或tonumber
作为参数print
,好吧,我使用了一些解决方法。
还有很大的改进空间:
- 测量和排版只需一次运行即可完成(我使用了两次运行),但由于内存限制,我不会冒这个险。
- 在测试阶段应该使用带有删除项目的 Lua 表,否则我将测试已分配的项目并重复跳过它们,这在小范围内不花费任何成本,但在实际排版项目中会花费很多(时间、金钱)。
- 我原本想
\repeatword{100}{duck}
用纯数据(例如)来替换命令(例如)100 duck
,但事实证明这不是一个好主意,因为排版阶段的内容一般来说并不清楚。 - 我不确定发送到终端的报告是否被刷新。这需要进行另一项小测试。
- 为了排序目的,可能会有第二个甚至更多的键,我只使用这些高度(加上无意中的歌曲编号,但它是自动完成的,因为它是从 TeX 命令接收数据的顺序)。
- 该程序可以为我们唱一首歌。
:-)
我附上了代码。祝你的歌本好运!
%! lualatex arrangeme.tex
\batchmode % Let's have some TeX fun...
\documentclass[letterpaper]{article}
\pagestyle{empty}
\parindent=0pt
\usepackage{xcolor}
\usepackage{multicol}
\usepackage{luacode}
\ifx\relax % A new approach and syntax...
\usepackage{xparse}
\ExplSyntaxOn % Wow, but I am not going to use it...
\NewDocumentCommand{\repeatword}{mm}{\getcolour\prg_replicate:nn{#1}{#2\ }\par}
\bool_gset_true:N\g_tmpa_bool
\NewDocumentCommand{\getcolour}{}
{\bool_if:nTF\g_tmpa_bool {\color{blue}\bool_gset_false:N\g_tmpa_bool}{\color{red}\bool_gset_true:N\g_tmpa_bool}}
\NewDocumentEnvironment{textblock}{}{\setlength{\parindent}{0pt}\getcolour}{}
\ExplSyntaxOff
\fi % End of \ifx...
\begin{document}
% Initializing all the TeX variables...
\newcount\mywords
\newif\ifmycolour
\newbox\mybox
\newsavebox\mysavebox
\newdimen\mycolumn
\newdimen\mytotal
\newif\ifmyrun
\newcount\bigcount
% What is the column width? Save it!
\begin{multicols*}{2}
\global\mycolumn=\columnwidth
\end{multicols*}
% A width of the column is \the\mycolumn.
\bigcount=0
\def\repeatword#1#2{%
\savebox\mybox{\parbox{\mycolumn}{%
\ifmycolour % Change of used colour...
\color{blue}\global\mycolourfalse\else
\color{red}\global\mycolourtrue
\fi
\mywords=0
\loop % Typesetting all the words...
\advance\mywords by 1%
#2 % Send me the word and the space...
\ifnum\mywords<#1\repeat
}}% End of \parbox and \savebox...
%Width of the box is \the\wd\mybox.\par
%Height of the box is \the\ht\mybox.\par
%Depth of the box is \the\dp\mybox.\par
\mytotal=\ht\mybox
\advance\mytotal by \dp\mybox\medskip
\ifnum\bigcount=0
\directlua{collectthem("\the\mytotal","#1","#2")}% Analyse it only first!
\else
\copy\mybox % This is the second run: typeset it, don't analyse it again.
\fi
}% End of \repeatword...
\begin{luacode*}
-- An easy part of the code...
function collectme (textheighttemp)
textheight=string.gsub(textheighttemp,"pt","") -- Substract "pt" from dimension.
textheight=tonumber(textheight)
--textheight=textheighttemp
--print("textheight",textheight,"\n")
--print("mycolumn",mycolumn)
end--of collectme
local songheight={}
function collectthem(songheighttemp,tosavea,tosaveb)
--print(tosavea,tosaveb)
temp=string.gsub(songheighttemp,"pt","")
temp=tonumber(temp)
table.insert(songheight, {temp,0,tosavea,tosaveb})
end--of collectthem
-- The core of the document processing...
function processthem()
table.sort(songheight, function(a,b) return a[1]>b[1] end)
for k, v in pairs (songheight) do
mymax=k
--mymax=table.maxn(songheight) -- This didn't work for me.
end
local malpage=0
for i=1,mymax do --
myvalue=songheight[i][1]
-- To be typeset on that page/column...
if songheight[i][2]==0 then -- Continue only if this is an unassigned item...
malpage=malpage+1
songheight[i][2]=malpage
textpage=tostring(malpage)
print ("Processing page "..textpage.."...")
-- Could we add more items to that one?
-- Two items or even more? Test them all from the highest values...
for j=i+1, mymax do
-- The main part of the core function...
-- This part can be optimized by a new table a removing items from it, instead of retesting already assigned values.
if songheight[j][2]==0 and myvalue+songheight[j][1]<textheight then
print("Adding...",j,"to",i)
myvalue=myvalue+songheight[j][1] -- Adding height.
songheight[j][2]=malpage -- This item is occupied from now on.
end --of if songheight
end --of for j, the inner cycle
end --of unassigned item
end --of for i, the outer cycle
print() --An empty line, printing final results
for k, v in pairs (songheight) do
print (string.format("%3s%11s%4s", k, v[1], v[2]))
end
textpage=tostring(malpage)
print("The highest processed page is "..textpage..".")
end --of function typesetthem
-- We will have a big time now... Typeset it all!
-- I rather typeset it again than hold all pieces in memory...
function typesetthem()
local finalpages=0
table.sort(songheight, function(a,b) return a[2]<b[2] end)
for k, v in pairs (songheight) do
if v[2]~=finalpage then
tex.print("\\vfill\\columnbreak")
finalpage=v[2]
end --of if v[2]
--print(v[1],v[2],v[3],v[4])
tex.print("\\repeatword{"..v[3].."}{"..v[4].."}")
end --of for k, v
end --of function typesetthem
\end{luacode*}
\def\allsongs#1{%
\directlua{collectme("\the\textheight")}% Save the textheight...
#1% Collect all the necessary data...
\global\bigcount=1% The second round of the same computations (saving memory)...
\directlua{processthem()}% Rearranging items...
\directlua{typesetthem()}% Typesetting them...
}% End of \allsongs...
\newif\ifmychoice
% Do I want to process the original data?
%\mychoicetrue % or
\mychoicefalse
\begin{multicols*}{2}
\allsongs{% To get dimensions and typeset the songs...
\ifmychoice % Yes, I do!
% The original values...
\repeatword{100}{duck}
\repeatword{200}{goose}
\repeatword{130}{parrot}
\repeatword{100}{falcon}
\repeatword{150}{owl}
\repeatword{150}{swan}
\repeatword{100}{chicken}
\repeatword{50}{mallard}
\else % No, I don't!
% The updated values (manually I must say)...
\repeatword{119}{duck}
\repeatword{234}{goose}
\repeatword{132}{parrot}
\repeatword{108}{falcon}
\repeatword{153}{owl}
\repeatword{154}{swan}
\repeatword{100}{chicken}
\repeatword{54}{mallard}
\repeatword{64}{pivo}
\repeatword{56}{mali}
\fi
}% End of \allsongs...
\end{multicols*}
\end{document}