Did you know you can reproduce an IEEE Spectrum-style page layout in pure LaTeX?
Why lua-pagemaker?
LaTeX excels at structure, mathematics, and text composition — but page layout remains rigid.
Even with advanced packages, creating designed, magazine-style layouts (multiple columns of arbitrary widths, figures spanning columns, sidebars, banners) is difficult and often fragile. Most solutions rely on floats and heuristics, which makes precise layout control hard to achieve and hard to reproduce.
lua-pagemaker addresses this gap by separating concerns:
- Lua computes page geometry deterministically.
- LaTeX flows text into precomputed frames.
- No floats, no guessing, no page-breaking heuristics.
Each page is described declaratively in a small Lua DSL. At compile time, Lua computes all frame coordinates and emits the corresponding flowfram primitives. LaTeX then behaves like a DTP engine: text flows into fixed regions exactly as specified.
The goal is predictable, reproducible, magazine-style layout: closer in spirit to Aldus PageMaker or InDesign than to newspaper-style balancing.
This approach is especially useful for:
- magazine articles
- research papers with complex layouts
- documents mixing text, code, screenshots, and diagrams
- situations where exact geometry matters more than automatic balancing
The repository includes an example very close to a pixel-perfect reproduction of a layout from the IEEE Spectrum magazine (see image above).
You can generate pages containing:
- a hero image
- a large pull-quote
- columns of different widths
- bottom-aligned figures
- decorative horizontal and vertical rules
git clone https://github.com/sylvainhalle/lua-pagemaker.git
cd lua-pagemakerlualatex example.texYou must use LuaLaTeX.
Adjust pages, columns, static boxes, and decorative rule positions.
return {
width = 7.88,
height = 10.75,
left = 0.5,
right = 0.75,
top = 0.5,
bottom = 0.75,
colsep = 0.25,
figtop = 0,
figbottom = 0.5,
pages = {
{
cols = 2,
boxes = {
{ name="topbanner", colfrom=1, colto=2,
top=0, h=3.875 }
}
},
{
cols = 3,
boxes = {
{ name="guifig", colfrom=2, colto=3,
top=0,
image="fig/GUI_pipe.png",
label="fig:bbgui",
figbottom=1,
caption="A pipeline in BeepBeep Studio" },
{ name="code-examples", colfrom=1, colto=2,
bottom=0, h=2.5 }
}
}
}
}Each page is described in pages.lua as a clean, readable structure:
- global page geometry
- a list of pages
- each page defines:
- its own columns (with arbitrary fractional widths)
- optional static boxes
- optional decorative borders
The system encourages thinking like a magazine designer: columns, blocks, banners, sidebars — not TeX primitives.
lua-pagemaker distinguishes two categories:
Flow frames
Where your normal body text goes.
Each column is automatically clipped above and below to avoid overlapping static boxes.
Static frames
Reserved space for:
- hero images
- banners
- sidebars
- bottom boxes
- pull quotes
Static frames can be anchored:
- at the top (
top = ...) - or the bottom (
bottom = ...) - and can span any range of columns (
colfrom–colto)
Static boxes may also specify:
image = "foo.png"→ height computed from aspect ratiovalign = "t" | "c" | "b"→ vertical alignment inside the box
Coordinates are given in an intuitive system:
- origin = top-left of the text block
- x grows to the right
- y grows downward
This mirrors CSS, SVG, and GUI toolkits.
layout.lua converts to TeX’s bottom-left–based coordinates automatically.
You specify:
width = 7.88,
height = 10.5,
left = 0.5,
right = 0.5,
top = 0.75,
bottom = 1.0,
colsep = 0.25,lua-pagemaker computes the text block and uses it as the reference system for all coordinates.
- Per-page column definitions
- Arbitrary (fractional) column widths
- Static boxes spanning multiple columns
- Image-based automatic box height
- Simple DSL for decorative rules (
hline_left,hline_right,vline_left,vline_right) - TikZ overlay for line drawing
- Coordinates in inches
- Lua-based preprocessing for predictable TeX output
The system uses three components.
This file does not contain logic.
It receives a DSL table (dsl) and returns a configuration table.
Example (simplified):
return function(dsl)
local hline_right = dsl.hline_right
local vline_right = dsl.vline_right
local config = {
width = 7.88,
height = 10.5,
top = 0.75,
bottom = 1.0,
left = 0.5,
right = 0.5,
colsep = 0.25,
pages = {}
}
local H = config.height
local T = config.top
local B = config.bottom
config.pages[1] = {
columns = {
{ width = 1/2 },
{ width = 1/2 }
},
boxes = {
{
name = "hero",
colfrom = 1, colto = 2,
top = 0,
h = 5.5
}
},
borders = {
hline_right(0.6),
vline_right(1.5, 0)
}
}
return config
endThis script:
- creates the DSL helpers (
hline_right,vline_left, etc.) - loads and evaluates
pages.lua - computes:
- the text block dimensions
- column coordinates
- how static boxes clip columns
- positions of decorative borders
- emits:
\newflowframefor text columns\newstaticframefor boxes
- installs a TikZ overlay for line drawing
You load it in the preamble:
\directlua{dofile("layout.lua")}Your TeX file defines the content of static boxes:
\begin{staticcontents*}{hero}
\centering
\includegraphics[width=\textwidth]{fig/hero.png}
\end{staticcontents*}Then you write the body text normally. It automatically flows into whatever frames the Lua layer defined.
- Requires LuaLaTeX (not pdfLaTeX or XeLaTeX).
- May conflict with packages that heavily alter page breaking.
- No automatic column balancing (by design).
- With highly variable column widths and deep static boxes,
flowframmay warn about unequal frame widths.
- Anchor-based positioning (
anchor="top-right", withdx,dyoffsets). - Predefined layout templates (
threecol_topfigure,sidebar_right, etc.). - Debug overlay showing all frames.
- Conversion into a package (
lua-pagemaker.sty). - Visual tooling for debugging layouts.
MIT License.
You are free to use, modify, and distribute.
- Hans Hagen and the LuaTeX team
- Nicola Talbot for
flowfram - The Lua community
- Everyone who wants LaTeX to behave a little more like a real DTP engine
