poster templates
with quarto, markdown, knitr, pandoc, yaml, lua, xelatex
“What program do you use to make posters?”
is a question people wish they hadn’t asked me, as soon as I start describing it. You’ve been warned.
Here’s the process I’ve been experimenting with, separating layout and content.
In practice, all it takes to produce the output is to call
quarto render
and the intermediate steps happen automatically, converting the input (left: layout + quarto source) into the output document (right).
It starts with Figma
or any SVG editor (I used BoxySVG for a while).
I create a frame with dimensions 1189x841
(in px, but I’ll only use the numerical values and interpret those as mm), onto which I draw rectangular boxes that will contain specific parts of the poster (title, introduction, image, etc.).
The advantage of using a visual editor is that I can conveniently resize content, align edges, snap to grid, toggle visibility, measure/calculate distances, etc. I assign a name (id
) to each rectangle I want to use in the layout, which will be retrieved programmatically in later stages.
The red blocks are only there to make sure the spacings are consistent, I make them invisible before exporting. I export the layout to SVG, making sure to include the id
attribute.
The SVG looks like this,
svg width="1189" height="841" viewBox="0 0 1189 841" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="1189" height="841" fill="#F5F5F5"/>
<g id="_layout">
<rect width="1189" height="841" fill="#EFEFEF"/>
<rect id="reference" x="971.25" y="601.25" width="157.5" height="178.5" fill="#D9D9D9" fill-opacity="0.8" stroke="black" stroke-width="0.5"/>
<rect id="dev" x="800.25" y="601.25" width="156.5" height="179.5" fill="#D9D9D9" fill-opacity="0.8" stroke="black" stroke-width="0.5"/>
<rect id="outlook" x="629.25" y="601.25" width="156.5" height="179.5" fill="#D9D9D9" fill-opacity="0.8" stroke="black" stroke-width="0.5"/>
<rect id="method" x="847.25" y="60.25" width="271.5" height="392.5" fill="#D9D9D9" fill-opacity="0.8" stroke="black" stroke-width="0.5"/>
<rect id="illustration" x="629.25" y="479.25" width="499.5" height="107.5" fill="#D9D9D9" fill-opacity="0.8" stroke="black" stroke-width="0.5"/>
<rect id="description" x="629.25" y="60.25" width="188.5" height="402.5" fill="#D9D9D9" fill-opacity="0.8" stroke="black" stroke-width="0.5"/>
<rect id="introduction" x="186.25" y="657.25" width="373.5" height="123.5" fill="#D9D9D9" fill-opacity="0.8" stroke="black" stroke-width="0.5"/>
<rect id="author" x="66.25" y="669.25" width="105.5" height="111.5" fill="#D9D9D9" fill-opacity="0.8" stroke="black" stroke-width="0.5"/>
<rect id="subtitle" x="66.25" y="618.25" width="493.5" height="24.5" fill="#D9D9D9" fill-opacity="0.8" stroke="black" stroke-width="0.5"/>
<rect id="title" x="60.25" y="563.25" width="499.5" height="54.5" fill="#D9D9D9" fill-opacity="0.8" stroke="black" stroke-width="0.5"/>
<rect id="image" x="60.25" y="60.25" width="499.5" height="499.5" fill="#D9D9D9" fill-opacity="0.8" stroke="black" stroke-width="0.5"/>
<rect width="1189" height="841" stroke="black"/>
<g>
</svg> </
Quarto executes a pre-render
R script, which parses the SVG to extract the layout information and produce the corresponding LaTeX code to inject in the document.
I re-use the same script within the main qmd
document to have direct access to the size and position of each rectangle, stored in the following dataframe,
x | y | width | height | id |
---|---|---|---|---|
971.25 | 601.25 | 157.5 | 178.5 | reference |
800.25 | 601.25 | 156.5 | 179.5 | dev |
629.25 | 601.25 | 156.5 | 179.5 | outlook |
847.25 | 60.25 | 271.5 | 392.5 | method |
629.25 | 479.25 | 499.5 | 107.5 | illustration |
629.25 | 60.25 | 188.5 | 402.5 | description |
186.25 | 657.25 | 373.5 | 123.5 | introduction |
66.25 | 669.25 | 105.5 | 111.5 | author |
66.25 | 618.25 | 493.5 | 24.5 | subtitle |
60.25 | 563.25 | 499.5 | 54.5 | title |
60.25 | 60.25 | 499.5 | 499.5 | image |
NA | NA | 1189.0 | 841.0 | page |
The LaTeX template
\(\LaTeX\) (LaTeX henceforth, to avoid this abomination of a logo) is good at typesetting, but doesn’t easily lend itself to custom layouts. The trick here is to use the flowfram
package, and define “staticframes” with the dimensions and positions obtained from the SVG layout. A R script produces the following directives, stored in _layout.tex
:
\newstaticframe[1]{157.5mm}{178.5mm}{971.25mm}{61.25mm}[reference]
\newstaticframe[1]{156.5mm}{179.5mm}{800.25mm}{60.25mm}[dev]
\newstaticframe[1]{156.5mm}{179.5mm}{629.25mm}{60.25mm}[outlook]
\newstaticframe[1]{271.5mm}{392.5mm}{847.25mm}{388.25mm}[method]
\newstaticframe[1]{499.5mm}{107.5mm}{629.25mm}{254.25mm}[illustration]
\newstaticframe[1]{188.5mm}{402.5mm}{629.25mm}{378.25mm}[description]
\newstaticframe[1]{373.5mm}{123.5mm}{186.25mm}{60.25mm}[introduction]
\newstaticframe[1]{105.5mm}{111.5mm}{66.25mm}{60.25mm}[author]
\newstaticframe[1]{493.5mm}{24.5mm}{66.25mm}{198.25mm}[subtitle]
\newstaticframe[1]{499.5mm}{54.5mm}{60.25mm}{223.25mm}[title]
\newstaticframe[1]{499.5mm}{499.5mm}{60.25mm}{281.25mm}[image]
With these frames in place, the content can be directed to the right location on the page. But what is the content? So far we’ve only looked at a bunch of rectangles.
The main document
The poster contains text and images, some tables, and graphics. All of this is described in plain text in the Quarto source file, consisting of a Yaml header, and a body mixing markdown text and code chunks.
---
title: "penguin sliding"
subtitle: "a poster with quarto • markdown • knitr • pandoc • yaml • lua • xelatex"
author:
- "Chin Strap"
- "Gen Too"
- "Adé Lie"
date: today
format:
pdf:
template: _template.tex
keep-tex: true
shift-heading-level-by: -1
highlight-style: a11y
bibliography: bibliography.bib
csl: nature.csl
flowfram: final
filters:
- bullet.lua
- frame.lua
- columns.lua
environments: [image,introduction,description,method,illustration,reference,outlook,dev]
---
```{r setup, include=FALSE}
source('_setup.R') # device, knitr opts, ggplot theme
source('_get-layout.R') # grab frame size information
```
::: image![picture of penguins](image.jpg){width="`r img$width`mm" height="`r img$height`mm"}
:::
::: introduction
::: columns
::: column
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore $\alpha \approx \beta$ et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
[@Horst:2020tp; @Gorman:2014wk].
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
:::
::: column
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua (@eq-alpha). Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
$$
\alpha = \beta
$$ {#eq-alpha}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore. Ut enim ad minim veniam, quis nostrud exercitation.
:::
:::
:::
::: description## an ancient art {.unnumbered}
[@Avatar:2011tn]. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat
```{r, echo=FALSE, fig.width=desc$width/25.4, fig.height=desc$width/25.4*3/4}
library(ggplot2)
library(palmerpenguins)
library(ggtext) # element_textbox_simple
ggplot(data = penguins, aes(x = flipper_length_mm,
y = body_mass_g)) +
geom_point(aes(color = species,
shape = species),
size = 3,
alpha = 0.8) +
scale_color_manual(values = c("darkorange","purple","cyan4")) +
labs(title = "Penguin size, Palmer Station LTER",
subtitle = "Flipper length and body mass for <span style = 'color:darkorange;'>Adélie</span>, <span style = 'color:purple;'>Chinstrap</span> and <span style = 'color:cyan4;'>Gentoo</span>",
x = "Flipper length (mm)",
y = "Body mass (g)",
color = "Penguin species",
shape = "Penguin species") +
theme(legend.position = c(0.21, 0.7),
plot.subtitle = element_textbox_simple(),
axis.title.y = element_text(margin = margin(r=10)),
plot.title.position = "plot",panel.grid = element_line(colour = 'grey92'),
plot.caption = element_text(hjust = 0, face= "italic"),
plot.caption.position = "plot",
plot.margin = margin(t=15,b=12,unit='pt'))
```
### Everything is on the table {.unnumbered}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
```{r}
library(gt)
head(penguins)[,-2] %>%
gt() %>% cols_label(
bill_length_mm = "length",
bill_depth_mm = "depth",
flipper_length_mm = "flipper",
body_mass_g = "mass"
)
```
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.
:::
::: method## technically speaking {.unnumbered}
::: columns
::: column
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
$$
\begin{aligned} \\
E & = T + U \\
& = -\frac{G m M}{\left | \mathbf{r} \right |} + \frac{1}{2} m \left | \mathbf{v} \right |^2 \\
& = m \left ( - \frac{GM}{\left | \mathbf{r} \right |} + \frac{\left | \boldsymbol{\omega} \times \mathbf{r} \right |^2}{2} \right )
& = - \frac{GmM}{2 \left | \mathbf{r} \right |}
\end{aligned}
$$
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
$$
{\displaystyle dP\left(1-M^{2}\right)=\rho V^{2}\left({\frac {dA}{A}}\right)}
$$ {#eq-nonsense}
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur (@eq-nonsense).
:::
::: column
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
$$
{\displaystyle {\frac {\partial \mathbf {u} }{\partial t}}+(\mathbf {u} \cdot \nabla )\mathbf {u} =-{\frac {1}{\rho }}\nabla P+\nu \nabla ^{2}\mathbf {u} }
$$
### More nonsense {.unnumbered}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
$$
\iiint Q\,dV+\iint F\,d\mathbf {A} =0
$$
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident.
:::
:::
\vfil
```{r, echo=FALSE, fig.width=method$width/25.4, fig.height=4}
library(ggplot2)
library(palmerpenguins)
library(ggtext)
ggplot(data = penguins, aes(x = flipper_length_mm,
y = body_mass_g)) +
geom_point(aes(color = species,
shape = species),
size = 3,
alpha = 0.8) +
facet_wrap(~species, nrow=1) +
scale_color_manual(values = c("darkorange","purple","cyan4")) +
labs(title = "Penguin size, Palmer Station LTER",
subtitle = "Flipper length and body mass for <span style = 'color:darkorange;'>Adélie</span>, <span style = 'color:purple;'>Chinstrap</span> and <span style = 'color:cyan4;'>Gentoo</span>",
x = "Flipper length (mm)",
y = "Body mass (g)",
color = "Penguin species",
shape = "Penguin species") +
theme(legend.position = 'none',
plot.subtitle = element_textbox_simple(),
plot.title.position = "plot",panel.grid = element_line(colour = 'grey95'),
axis.title.y = element_text(margin = margin(r=10)),
strip.background = element_blank(),
strip.text = element_blank(),
plot.caption = element_text(hjust = 0, face= "italic"),
plot.caption.position = "plot",
plot.margin = margin(t=10,b=10,unit='pt'))
```
[@Chamberlain:2022uu].
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
:::
::: illustration```{r, echo=FALSE, fig.width=illust$width/25.4, fig.height=illust$height/25.4}
illu <- data.frame(variable=LETTERS[1:5])
library(rphylopic) # recolor_phylopic
library(egg) # geom_custom
library(grid) # rasterGrob
library(glue)
library(png)
library(elementalist) # roundrect
# devtools::install_github("teunbrand/elementalist")
imgs <- map(glue('penguin{1:5}.png'), png::readPNG, native=FALSE)
mats <- map(imgs, rphylopic:::recolor_phylopic, alpha= 0.2, col='#643d35')
illu$mats <- I(mats)
ggplot(data = illu) +
geom_custom(aes(data = mats, x=0, y = 0),
grob_fun = function(x) grid::rasterGrob(x, interpolate = FALSE,
width=unit(3,'cm'), vp = viewport(angle=-30),
height=unit(3,'cm'))) +
facet_wrap(~variable, nrow=1, strip.position = 'bottom') +
theme(plot.margin = margin(t=0,b=0,unit='pt'),
strip.background = element_rect(fill=alpha('grey98',0.6), colour = NA),
strip.text = element_text(colour = "#643d35"),
axis.title = element_blank(),
axis.text = element_blank(),
axis.line = element_blank(),
axis.ticks = element_blank(),
panel.grid = element_blank(),
panel.background = element_rect_round(color = NA, fill=alpha('grey98',0.4),
radius = unit(5, "pt")),
plot.background = element_blank())
```
:::
::: outlook## outlook {.unnumbered}
Lorem ipsum dolor sit amet,
1. consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
2. labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
3. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident,
4. sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur elit.
:::
::: dev## recent developments {.unnumbered}
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
- Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
- Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Mollit anim id est laborum,``` {{r}}
head(penguins) |> tail()
```
:::
::: reference## references {.unnumbered}
::: {#refs}
:::
\vfill### Acknowledgments {.unnumbered}
Thanks for that. :::
The YAML header
---
title: "penguin sliding"
subtitle: "a poster with quarto • markdown • knitr • pandoc • yaml • lua • xelatex"
author:
- "Chin Strap"
- "Gen Too"
- "Adé Lie"
date: today
format:
pdf:
template: _template.tex
shift-heading-level-by: -1
highlight-style: a11y
bibliography: bibliography.bib
csl: nature.csl
flowfram: final
filters:
- bullet.lua
- frame.lua
- columns.lua
environments: [image,introduction,description,method,illustration,reference,outlook,dev]
---
Beside some basic info for the poster such as the title, date, author(s), there are directives passed to the following processing steps, such as the use of Lua filters described below.
The quarto/markdown content
The core content of the poster is then written as a mix of markdown text, and code chunks. A few representative excerpts follow:
::: image![picture of penguins](image.jpg){width="`R img$width`mm" height="`R img$height`mm"}
:::
::: introduction
::: columns
::: column[@Horst:2020tp; @Gorman:2014wk].
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore $\alpha \approx \beta$ et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit
:::[...]
:::
### Everything is on the table {.unnumbered}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
```{r}
library(gt)
head(penguins)[,-2] %>%
gt() %>% cols_label(
bill_length_mm = "length",
bill_depth_mm = "depth",
flipper_length_mm = "flipper",
body_mass_g = "mass"
)
```
The markdown divs (:::
) are used to identify the frame in which the enclosed content should be placed in the final document. This is achieved by a custom Lua filter that creates “LaTeX environments”, or more accurately named staticframes, matching the list of frames pre-defined above. Passing the draft
option to flowfram
visualises the output,
Multicolumn output within a frame is obtained with the standard Quarto convention, which turns ::: columns
directives into LaTeX \multicols
.
Maths is handled as one would expect, for both inline and display formulas.
Citations use the standard Pandoc notation, and refer to the bibliography file listed in the Yaml header.
Sections and subsections are written in standard markdown notation; for some reason I was unable to turn off numbering for the whole document.
Code chunks are evaluated via knitr, and the output integrated with the document (graphics, tables, or text).
The final output
It’s probably a good time to look more closely at the final output, and see what details we’re still missing:
Among the things I haven’t yet described:
- How is the text formatted in the different frames?
- How do the graphics know what size they should have?
- Where does the background gradient come from?
- How are those • bullets coloured in the subtitle?
LaTeX tweaks
In the Yaml directives above, we refer to a _template.tex
file, which contains the custom format for this poster (including the various LaTeX packages and macros needed for typesetting). Naturally, the first step is to set the paper size to match the layout
\usepackage[paperwidth=1189mm, paperheight=841mm, margin=0mm]{geometry}
The fonts for each frame are set with macros such as
\newfontfamily{\ttlight}{Gill Sans Nova Light}
\newcommand{\titlespecs}{\fontsize{200}{200}\ttlight\addfontfeature{Color=643d35}\scshape \selectfont}
which are inserted at the start of each frame, before the content. Sections are styled similarly with the titlesec
package:
\usepackage{titlesec}
\titleformat{\section}
\fontsize{34}{36} \ttregular\addfontfeature{Color=643d35}\scshape\selectfont}
{\fontsize{34}{36} \ttregular\addfontfeature{Color=643d35}\scshape\selectfont\thesection}{2em}{} {
The frame definitions are inserted into the main document with an \input{}
directive, and we can then fine-tune their alignment settings:
\usepackage{flowfram}
\input{_layout.tex}
\setallstaticframes{valign=t}
\setstaticframe*{image}{valign=c}
\setstaticframe*{title}{valign=c}
The background of the poster is exported from Figma as a pdf file, and inserted in LaTeX via the wallpaper
package:
\usepackage{wallpaper}
\ULCornerWallPaper{1}{_background.pdf}
The various graphics are sized according to the layout information, that was collected in a dataframe; for example,
#| fig-width: !expr description$width/25.4
#| fig.height: !expr description$width/25.4*3/4
# [...] plotting code
This leaves us with the important question: how are the bullets • coloured?
Lua filters
As far as (Xe)LaTeX is concerned, • is just a character, so we need to replace it with something custom in a previous step. This is achieved with the following Lua filter, which looks for the • character in the input stream, and replaces it with a custom LaTeX macro,
function replace (elem)
if elem.text == "•" then
return pandoc.RawInline("latex","\\mybullet")
end
end
return {{Str = replace}}
where \mybullet
is defined in LaTeX as (don’t ask):
\newcommand{\Hair}{\ifmmode\mskip1mu\else\kern0.01em\fi}
\newcommand{\mybullet}{{\addfontfeature{Color=E48B23}\!\Hair \raisebox{-.2ex}{•}\Hair}}
Quirks and annoyances
Quarto stuff
For some reason, the margins of pdf figures are automatically cropped, and I didn’t find a way to turn this off. I wanted to keep the margins, so that the figures remain well separated from the surrounding text, so I ended up having to short-circuit the crop
hook for the time being,
```{r}
knitr::knit_hooks$set(crop = function(...){})
```
For some reason I was unable to turn off section numbering for the whole document, so I resorted to adding an explicit {.unnumbered}
everywhere.
The pre-render script to extract layout dimensions from the SVG is run twice; once to produce the _layout.tex
file, and a second time to have the dimensions available to code chunks in th e main quarto document. It would be nice if there was the option to run the pre-render script as a “setup” chunk, so to speak.
Inserting external images didn’t work as expected; when I specified only the width, such as width=100%
to fill its container frame, the image was stretched vertically, and I could not find the right attributes to keep the correct aspect ratio. As a workwaround, I’ve had to extract the physical dimensions of the frame from the layout.
LaTeX stuff
Flowfram is doing some magic to achieve absolute positioning, and there are a few occasional hiccups:
% add dummy flow frames, otherwise no output
\newflowframe[1]{\textwidth}{0pt}{0pt}{0pt}
\newflowframe[>1]{\textwidth}{\textheight}{0pt}{0pt}
It also messes with \parskip
, meaning that I rededined it locally at the beginning of each frame.
Multicol brings its own headaches; I had to include a \setcounter{unbalance}{100}
directive to allow unbalanced columns, otherwise the frame would become inexplicably empty when LaTeX judged it was not aesthetically pleasing ¯\_(ツ)_/¯.
Pandoc stuff
Pandoc brings its own magic too, and it’s not alway clear what missing macros should be included or where to find tem, when using your own template. One example is the CSLReferences environment used to format the bibliography; I was unable to tune the vertical spacing to my liking.
\newenvironment{CSLReferences}[2] % #1 hanging-ident, #2 entry spacing
% don't indent paragraphs
{\setlength{\parindent}{0pt}
% set entry spacing
\setlength{\cslentryspacing}{#2\baselineskip - \parskip}
% turn on hanging indent if param 1 is 1
\let\oldpar\par
\ifodd #1
\def\par{\hangindent=\cslhangindent\oldpar\vspace{\cslentryspacing}}
\else
\def\par{\oldpar\vspace{\cslentryspacing}}
\fi
%
} {}
Parting thoughts
Now, someone may ask: why not stick with Figma for the whole thing? (Or Indesign, or …).
In my experience, as soon as a few equations are included in the text, the process becomes quite convoluted no matter which tools I use. Similarly, getting graphics at the right size and well aligned takes some effort, and is usually an iterative process.
Having the output automatically update with the latest graphics/images/tables/bibliography is a big advantage of this literate programming approach (plus, I can copy/paste previous plain text material from other documents).
On the other hand, LaTeX-based workflows are usually very constrained for such visual outputs (poster here, but the same sentiment applies to presentation slides and other documents where design input is essential). I like the immediate feedback and tools offered by design software (alignment guides, etc.). For example, I can overlay the current output with my original layout, fine-tune the dimensions, and then simply toggle the visibility of the image layer to re-export the tweaked layout.
This workflow is my latest attempt at combining two complementary sets of tools – literate programming and design – as best I can. I imagine an alternative approach, for those who know Figma/Indesign/etc. is to write suitable plugins to attack the problem from the other side.
A minimal example, for a dummy business card, is at this repo.
Here’s the actual poster (which was made before Quarto, using similar Rmarkdown trickery).
Thanks for all that
- pandoc
- lua
- xelatex, flowfram
- quarto, Rmd, knitr
- R and packages
- phylopic.org
- Figma (pre-Adobe)
- Danielle Barnes for the photo (https://unsplash.com/photos/kGNaS3lYCso)