重排页面中的文本块

重排页面中的文本块

朋友们,我有一个简单的布局来为我的教区排版歌谱,它由软件包提供的两列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}

我有以下输出:

嘎嘎 1

我使用这个布局已经有一年了,我发现它在大多数情况下都很好用,但有时我们的一些歌曲会从一页开始,在下一页结束。在我的示例中,区块owl反映了我的意思:看到蓝色文本在第 1 页结束,并在第 2 页继续。

我的第一个计划:好吧,让我把每首歌都弄得牢不可破。:)假设我决定将每个文本块放在一个 中\parbox。现在事情就完全不同了:

嘎嘎 2

现在我的歌曲不再出现断点。:)但由于 TeX 保留了顺序,文本中出现了一些空白。现在让我们仔细看看我第一次尝试的第 1 页:

嘎嘎 3

请注意,parrot文本块比文本块小goose;如果我可以交换它们之间的位置,也就是说,如果parrot可以排在前面goose,那么我就可以在第一页放三首歌曲,而不仅仅是两首:

嘎嘎 4

我想知道是否有办法模拟某种浮点放置行为。请注意,我很清楚这个问题比看起来要难得多。:)这是区域划分,也是人工智能中一个有趣的研究领域。:)

另请注意,我可以忍受非常天真解决方案,如果有的话。我也接受不,这不可能回答。:)

颇具讽刺意味的是,我有一篇论文介绍了一种解决这个问题的方法。:)问题是:我讨论的方法使用了 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 页)。我相信一张图片胜过千言万语。

mwe1

请允许我简单说几句关于优化阶段的话。没有什么奇迹发生(开玩笑的,神父)。

  • 第一步是按高度降序排列。
  • 开始新的一栏。
  • 然后从顶部选择第一首未分配的歌曲并将其放在该页面上。
  • 如果所有未分配的歌曲适合剩余空间的该列,则按降序测试它们。如果适合,则标记它们。
  • 移至下一页,直至所有歌曲都分配完毕。

在原始数据中,我们可以看到每列只有两首歌曲。我玩了一下,添加了两首新歌,然后开始手动(也可以编程)增加单词数量以完全填满这两页。就是这样,如果你将任何数字增加 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 文件的预览。

mwe2

我们无法再在那里挤进一个单词。在该文档中获得更多单词的唯一机会是删除较长的单词,例如chicken,然后添加较小的单词,例如owl

源代码中有一个新的条件,名为\ifmychoice从原始数据或更新的数据中选择数据。这是一个很好的起点,您可以自己尝试并进行实验。

我必须说我的 Lua 代码远非最佳,我遇到了一些困难,因为我无法调用table.maxntonumber作为参数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}

相关内容