我有许多不同形状的子图,想浪费更少的空间来展示它们;它们的顺序并不重要,我更喜欢 2D 包装,而不是将它们全部排列在同一个基线上。
\documentclass{article}
\def\X#1#2{%
\fbox{\hbox to #1{\vbox to #2{}}}
}
\begin{document}
\begin{figure}
\X{2cm}{3cm}\X{2cm}{6cm}\X{3cm}{2cm}
\X{2cm}{3cm}\X{4cm}{3cm}\X{1cm}{4cm}
\X{3cm}{3cm}\X{2cm}{5cm}\X{2cm}{6cm}
\caption{My rectangles}
\end{figure}
\end{document}
产生这个:
我可以使用图片环境来手动排列盒子,但这似乎很麻烦,我宁愿有一个可以知道最大宽度和高度的环境并让它自动打包内容。
使用 LuaTex 似乎相对简单(即收集盒子,测量它们,运行矩形包装算法,如科尔夫使用尺寸将盒子放置在最佳坐标处)。
有人实现过类似的东西吗?我不一定需要最佳解决方案,但我找不到任何相关的东西。
答案1
我修改了代码这个答案相当实质上——我们不必特别标记环境中的每个项目,不需要图片环境,我们只使用临时框寄存器。
新的generativelayout.sty
:我们为根框创建了可以动态设置的新长度(我只是使用所有可用空间)。环境将其内容收集到一个框中,我们在 Lua 中将其解包,无需为每个项目使用一个框寄存器。
\ProvidesPackage{generativelayout}
\directlua{gen = require('generativelayout')}
\newdimen\generativewidth
\newdimen\generativeheight
\newenvironment{genlayout}{%
\generativewidth=\hsize%
\generativeheight=\vsize%
\setbox0=\hbox\bgroup%
}{%
\egroup%
\directlua{gen.process()}%
}
新的generativelayout.lua
:
module(...,package.seeall)
我们只是收集具有一定尺寸的所有内容,包括水平盒子、垂直盒子、规则和字形。
local function get_boxes(parent)
local boxes = {}
for n in node.traverse(parent.head) do
if n.width or n.height or n.depth then
table.insert(boxes, {
w = n.width,
h = n.height + n.depth,
box = node.copy(n),
})
end
end
return boxes
end
该算法在很大程度上由 michal.h21 实施
local function findNode(n, w, h)
if n.used then
local right = findNode(n.right, w, h)
if right then
return right
else
return findNode(n.down, w, h)
end
elseif w <= n.w and h <= n.h then
n.used = true
n.down = { x = n.x, y = n.y + h, w = n.w, h = n.h - h }
n.right = { x = n.x + w, y = n.y, w = n.w - w, h = h }
return n
else
return nil
end
end
local function binpack_tree(boxes)
table.sort(boxes, function(a, b) return a.h > b.h end)
local root = {
x = 0,
y = 0,
w = tex.dimen['generativewidth'],
h = tex.dimen['generativeheight']
}
for _, v in ipairs(boxes) do
local n = findNode(root, v.w, v.h)
if n then
v.x = n.x
v.y = n.y
end
end
return boxes
end
我们构建的不是图片环境,而是\vbox{\vskip<y> \hbox{\hskip<x> <node>}}
将节点定位在 处(<x>,<y>)
。结果节点的大小为零。
local function shift_by(n, w)
n.width = 0
n.height = 0
n.depth = 0
local g = node.new('glue', 0)
g.spec = node.new('glue_spec')
g.spec.width = w
n.head = node.insert_before(n.head, n.head, g)
return n
end
local function position_node(n, x, y)
n = node.hpack(n)
n = shift_by(n, x)
n = node.vpack(n)
n = shift_by(n, -y)
return n
end
定位所有盒子,将它们装入最小尺寸的根盒子中。
local function output(boxes)
local w = 0
local h = 0
local head = nil
for _, b in ipairs(boxes) do
if b.x and b.y then
-- add node to the list
local n = position_node(b.box, b.x, b.y + b.box.depth)
if head then
node.insert_after(head, head, n)
else
head = n
end
-- track extents
if b.x + b.w > w then w = b.x + b.w end
if b.y + b.h > h then h = b.y + b.h end
end
end
if head then
head = node.hpack(head)
-- natural size was zero
head.width = w
head.height = h
node.write(head)
end
end
入口点:就像在原始代码中一样,我们可以调用某些东西来binpack_tree
获得不同的布局。
function process()
local boxes = get_boxes(tex.box[0])
binpack_tree(boxes)
output(boxes)
end
测试文件生成了几个框等,请注意我们只是将任何内容扔进环境中genlayout
;还请注意rule
深度没有问题。
\documentclass{article}
\usepackage{generativelayout}
\newcounter{piece}
\def\X#1#2{%
\fbox{%
\vbox to #2{\vfill\hbox to #1{\hfill%
\stepcounter{piece}%
\thepiece%
\hfill}\vfill}}
}
\begin{document}
\begin{figure}\hfil
\begin{genlayout}
\X{2cm}{3cm}\X{2cm}{6cm}\X{3cm}{2cm}
\X{2cm}{3cm}\X{4cm}{3cm}\X{1cm}{4cm}
\X{3cm}{3cm}\X{2cm}{5cm}\X{2cm}{6cm}
\rule[-5mm]{1cm}{1cm}
\Huge Hello!
\end{genlayout}
\caption{My Rectangles}
\end{figure}
\end{document}
输出: