One scene, three formats

Backend
The same vellum scene rendered to PNG, SVG, and PDF. Layout is solved once and each backend walks that one answer, rather than re-solving against its own metrics.

This is the layer beneath the grammar. A vellum scene is described once with low-level grobs, then compiled: text measured, layout solved, every position resolved. Each backend then walks that one solved scene instead of re-solving the layout against its own font metrics, so a raster (PNG), a vector (SVG), and a print-ready document (PDF) agree on geometry rather than merely resembling each other. Where a backend cannot honour something, it says so instead of silently dropping it.

scene <- vl_scene(width = 6, height = 3, bg = "#faf5ea") |>
  draw(rect_grob(
    width = 0.9, height = 0.78,
    gp = vl_gpar(fill = linear_gradient(c("#6b4f2c", "#c9a874")), col = NA)
  )) |>
  draw(circle_grob(
    x = 0.18, y = 0.5, r = 0.22,
    gp = vl_gpar(fill = "#f7c948", col = NA)
  )) |>
  draw(text_grob(
    "vellum", x = 0.60, y = 0.5,
    gp = vl_gpar(fontsize = 54, col = "white", fontface = "bold")
  ))

One scene value, three calls to render(). The output format follows the file extension:

render(scene, "vellum-logo.png")   # raster  (tiny-skia)
render(scene, "vellum-logo.svg")   # vector  (hand-rolled SVG writer)
render(scene, "vellum-logo.pdf")   # print   (krilla)

vellum logo scene rendered as a raster PNG

Rendered to PNG

the same vellum logo scene rendered as vector SVG

The same scene as SVG

Pixel-for-pixel the raster and the vector agree, because both come from one scene graph and one layout solve. Grab any of the three:

Download PNG Download SVG Download PDF

Because rendering is deterministic, the bytes are stable across platforms, which is what makes vellum graphics snapshot-testable. This retained scene graph is also what vellumplot compiles into, and what vellumwidget reads to wire up interactivity.

Back to top