Export grid graphics directly to CeTZ drawing commands for Typst, analogous to what gridSVG does for SVG.
Standard R graphics devices receive a normalised graphics context where all parameters are always fully populated — we can’t tell whether col = "black" was set explicitly by the user or defaulted there automatically.
gridcetz follows the gridSVG approach and works at the grob level instead, walking the display list directly. A gpar() object only contains the fields that were explicitly set, so:
library(grid)
library(gridcetz)
grid.text("default colour is black") # gpar: (none) -> no #set text
grid.text("explicit colour set", y=0.6, gp = gpar(col = "red")) # gpar: col -> fill: rgb(255, 0, 0)
This means Typst-level #set rules propagate through naturally:
#set text(font: "Source Code Pro", fill: blue)
#include "myplot.typ" // only explicitly-styled elements override thisUsage
library(grid)
library(gridcetz)
grid.newpage()
pushViewport(viewport(width = 0.8, height = 0.8))
grid.rect(gp = gpar(fill = "white"))
grid.text("Hello Typst", gp = gpar(fontsize = 24))
popViewport()
# grid_to_cetz("output.typ")Alternatively, use gridcetz_open(...) and gridcetz_close() like standard devices.
Then compile with Typst:
Only grid graphics are supported, but that includes lattice and ggplot2, and gridBase could also be used as a bridge for base graphics.
Gggplot example
library(ggplot2)
library(gridcetz)
p <- ggplot(mtcars, aes(mpg, wt)) + geom_point() + theme_bw()
print(p)
# grid_to_cetz("output.typ")Fun Typography + Math Labels
A big advantage of this device is that the typography is left to typst, which has much finer control than R. This is especially true for mathematical expressions: instead of using plotmath, we can simply provide the typst string and typst will process it as any other text. Note that unlike LaTeX, typst does not use backslashes, so no crazy \\\\\\\ escaping is needed.
The following plot adds a gratuitous equation, and typesets with Pennstander and Pennstander Math.
library(ggplot2)
library(gridcetz)
set.seed(42)
stars <- data.frame(
temp = seq(3200, 9000, length.out = 12),
mag = c(4.8, 4.4, 4.2, 3.8, 3.4, 3.1, 2.8, 2.5, 2.2, 1.9, 1.6, 1.4),
class = rep(c("dwarf", "giant"), each = 6)
)
p <- ggplot(stars, aes(temp, mag, colour = class)) +
annotate(
"text",
x = 6000,
y = 4.55,
label = "$ B_lambda(T) = (2 h c^2) / lambda^5 times 1 / (e^(h c / (lambda k T)) - 1) $",
hjust = 0.5,
size = 5,
colour = "#2F3E46"
) +
annotate(
"text",
x = 9000,
y = 3,
label = "ridiculously-long-equation",
hjust = 0,
size = 4,
colour = "darkred"
) +
geom_path(aes(group = 1), linewidth = 1.1, alpha = 0.5, colour = "#4A5D4F") +
geom_point(size = 3.0) +
scale_x_reverse() +
scale_colour_manual(values = c(dwarf = "#F9C784", giant = "#37515F")) +
labs(
title = "Stellar cheat sheet: $E = h nu$",
subtitle = "Wien and Stefan-Boltzmann: $lambda_(max) = b / T$, $F = sigma T^4$",
x = "Temperature $T$ (K)",
y = "Apparent magnitude $m$"
) +
theme_grey(base_size = 13) +
theme(
plot.title = element_text(face = "bold"),
panel.grid.minor = element_blank()
)
print(p)
The output gives:
How it works
-
grid.force()resolves lazy grobs so deferred layout-dependent content is concrete. -
grid.grab()snapshots the current scene into a traversable grob tree. - The walker traverses that tree and navigates each grob’s viewport on the live grid stack (
downViewport/seekViewport/pushViewport). - Coordinates are emitted as absolute device-space centimeters via
deviceLoc()/convertWidth()/convertHeight()(flat output, no viewport groups in Typst output). - Styling is read from explicitly-set
gpfields, and grob classes are dispatched to CeTZ primitives viaprim_to_cetz().
Not yet supported
- Clipping (viewport clipping is noted as a comment)
- xspline / bezier curves
- Plotmath (Typst handles typesetting natively)
Raster images are supported via a workaround (embedded image with raw bytes). There may be issues.
Installation
remotes::install_git("https://codeberg.org/baptiste/gridcetz")