Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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.

enter image description here

Survey of 2D packing algorithms

share|improve this question
2  
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
2  
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
2  
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
2  
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
1  
@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

1 Answer 1

up vote 7 down vote accepted

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:

enter image description here

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) enter image description here

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 enter image description here

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:

enter image description here

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.