# Create a “yearbook-style” multi-page layout of photos

I would like to create a "yearbook-style" layout of photos in LaTeX. Each photo would have a caption and the objective would be to automatically layout the differently sized rectangular photos to minimize the whitespace on the page (with a mandatory margin around each photo and caption). If I could do this with a browser as well that would also be helpful, but that is a question for a different forum. I would have expected this problem to be solved problematically with some kind of open sourced package but I have found none.

Survey of 2D packing algorithms

-
This sounds like an NP-complete problem. Even if not, what you are after is likely quite difficult to achieve algorithmically. Doing it by hand isn't an option? –  Scott H. Jul 23 '12 at 2:00
Here's something. Google "2d rectangle packing", looks like there are some algorithms out there. FYI I'm just commenting, there's no way I can personally help you with this. Good luck, it will be interesting to see if someone can come up with something! –  Scott H. Jul 23 '12 at 2:06
I bit off a lot with this problem, hopefully not more than I can chew. How can an existing solution in a programing language be integrated with a LaTeX output? –  denilw Jul 23 '12 at 2:19
You can embed python in LaTeX, an example is here If you can find some python code, you may be able to do something with it. –  Scott H. Jul 23 '12 at 2:22
@denilw This surely means some more important pictures (because nicer, better, more symbolic, etc.) will end up in some edge of the page, and some not-so-great ones will make the centre. Wouldn't it be better to arrange them yourself in a composition software (or eventually in LaTeX)? Whitespace is not a sin but a blessing. If it's the number of pages you're worried about, you can downsize some less important pictures a bit. –  ienissei Jul 23 '12 at 8:55

This is not complete answer to this question, it's rather showcase of idea I had about generative layouts and luatex. I don't think that any of following examples is useful as is, but maybe in can serve as basis for more interesting solutions in the future, maybe something like this.

My idea is following: we have LaTeX command that stores it's content in a box ant then add list id and dimensions to the list.

\ProvidesPackage{generativelayout}
\RequirePackage{luacode}

\luaexec{%
generators = require('generativelayout')
genboxes = {}
curr_box_id =  2222
function findFreeBox(id)
if type(tex.box[id]) == 'nil' then
return id
else
return findFreeBox(id + 1)
end
end
gen = generators.getGenerator("default")
}

\newcommand\gensavebox[1]{%
\setbox0=\hbox{\fbox{#1}}
\luaexec{%
local box = tex.box[0]
curr_box_id= findFreeBox(curr_box_id)
tex.setbox('global',curr_box_id, node.copy_list(box))
table.insert(genboxes,{width=box.width / 65536 ,height=(box.height / 65536 + box.depth / 65536),box=curr_box_id})
curr_box_id = curr_box_id + 1
}
%\box0
}


Next we have environment genlayout that selects layout algorithm and run it on list of boxes:

\newenvironment{genlayout}[1][]{%
\luaexec{%
local gen_name = "\luatexluaescapestring{#1}"
if gen_name == "" then gen_name = "default" end
gen = generators.getGenerator(gen_name)
gen:init()
}
}{%
\luaexec{%
gen:run(genboxes)
}
}


This package can be used like this:

\documentclass{article}
\usepackage{lmodern,generativelayout}
\begin{document}
\pagestyle{empty}
\begin{genlayout}[]
\gensavebox{%
Hello world
}
\gensavebox{%
\begin{tabular}{l l}
hello & world\\
second & line\\
\end{tabular}
}
\gensavebox{%
\begin{minipage}{.3\textwidth}
\begin{itemize}
\item First
\item Second
\end{itemize}
\end{minipage}
}
\gensavebox{%
little bit smaller box
}
\gensavebox{totaly small box}
\gensavebox{azsvm}
\end{genlayout}

\end{document}


Now the lua library generativelayout.lua

module(...,package.seeall)

generators = {}
empty_generator = {init = function() end, run = function() end}

function getGenerator(name)
return generators[name] or empty_generator
end

printBox = function(self,box_number)
tex.print('\\fbox{\\copy'..box_number.."}")
end

generators.default = {
init = empty_generator.init,
printer = printBox,
run = function(self,boxes)
for k, v in pairs(boxes) do
self:printer(v.box)
end
end
}


Every generator is added as object with methods run and init, which are called form genlayout environment. Generator can be selected with optional parameter of genlayout, if empty, default is selected. The output is following:

We can sort the boxes:

generators.bigToSmall = {
init = generators.default.init,
printer = printBox,
run = function(self,boxes)
table.sort(boxes,function(a, b) return a.height > b.height end)
generators.default.run(self,boxes)
end
}


You can use functions from other generators, as generators.default.run(self,boxes)

More funny example of putting boxes on random places on the page:

generators.randomize = {
width = 150,
height = 150,
init = generators.default.init,
printer = function(self,box,x,y)
tex.print("\\put(",x,",",y,"){\\copy",box,"}")
end,
run = function(self,boxes)
tex.print("\\setlength\\unitlength{1pt}")
tex.print("\\begin{picture}("..self.width..","..self.height..")")
math.randomseed( os.time() )
for k,v in pairs(boxes) do
local x = math.random(self.width - v.width)
local y = math.random(self.height - v.height)
self:printer(v.box,x,y)
end
tex.print("\\end{picture}")
end
}


Here we use trick with picture environment to place things on exact place on the page

And finally, my port of http://codeincomplete.com/posts/2011/5/7/bin_packing/ to lua:

generators.binPack = {
root = {},
init = function(self) self.root = {x=0,y=0,w=280,h=250} end,
printer = generators.randomize.printer,
fit = function(self,boxes)
local node
for _,block in ipairs(boxes) do
print("Velikost boxu",block.width,block.height)
node = self:findNode(self.root, block.width,block.height)
if node then
block.fit = self:splitNode(node, block.width, block.height)
end
end
end,
findNode = function(self,root,w,h)
if root.used then
local right = self:findNode(root.right,w,h)
if right then
return right
else
return  self:findNode(root.down,w,h)
end
elseif w <= root.w and h <= root.h then
return root
else
return nil
end
end,
splitNode = function (self,node,w,h)
print("Split box: ",node.x,node.y,node.w,node.h,w,h)
node.used = true
node.down  = { x= node.x,     y= node.y + h, w= node.w,     h= node.h - h }
node.right = { x= node.x + w, y= node.y,     w= node.w - w, h= h}
return node
end,
run = function(self,boxes)
--generators.randomize.run(self,boxes)
table.sort(boxes,function(a, b) return a.height > b.height end)
tex.print("\\setlength\\unitlength{1pt}")
tex.print("\\begin{picture}("..self.root.w..","..self.root.h..")")
self:fit(boxes)
for _,v in ipairs(boxes) do
if v.fit then
x= v.fit.x
y= self.root.h-v.fit.y
self:printer(v.box,x,y)
end
end
tex.print("\\end{picture}")
end
}


I don't think the result is really nice and I think there is some error in placing boxes on their exact place, but maybe someone will like it:

-