自动装盒

自动装盒

我有许多不同形状的子图,想浪费更少的空间来展示它们;它们的顺序并不重要,我更喜欢 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}

输出:

在此处输入图片描述

相关内容