Output & qualification: backends, requirements, and the CDISC pilot
Source:vignettes/articles/output.qmd
This article is about rendering and proving — choosing a backend, meeting its system requirements, and the cross-backend validation. It does not cover building or styling a table (see the other articles).
emit() and as_grid()
emit() writes a file, dispatching on the extension:
A .pdf target compiles through one of two engines. With no format =, emit() probes the machine LaTeX-first: a usable TeX keeps the LaTeX path; otherwise a discoverable typst binary (standalone typst, or the copy bundled inside Quarto ≥ 1.4) takes over; with neither, the call aborts up front naming both remedies. Pick an engine explicitly with format = "latex" or format = "typst":
It returns the written path invisibly, so the emit is chainable into scripted batch runs. One spec, any backend:
data(cdisc_saf_demo, package = "tabular")
spec <- tabular(cdisc_saf_demo, titles = "Demographics") |>
cols(
variable = col_spec(label = ""),
stat_label = col_spec(label = "")
) |>
group_rows(by = "variable")
path <- emit(spec, tempfile(fileext = ".rtf"))
file.exists(path)
#> [1] TRUEas_grid(spec) resolves the fully-laid-out grid without writing a file — useful for testing or programmatic inspection. The grid carries the resolved pages plus a metadata block (pagination counts, resolved column names, the effective preset):
Backend capability matrix
One spec renders to every backend, but the page-oriented features differ:
| Capability | RTF | HTML | DOCX | PDF/LaTeX | PDF/Typst | MD |
|---|---|---|---|---|---|---|
| Vertical pagination | ✓ | n/a¹ | ✓ | ✓ | ✓ | n/a |
Horizontal panels (panels=) |
✓ | n/a¹ | ✓ | ✓ | ✓ | n/a |
| Per-page running header/footer | ✓ | – | ✓ | ✓ | ✓ | – |
subgroup() per-page BigN |
✓ | row² | ✓ | ✓ | ✓ | row² |
| Continuation marker | panels only | – | – | ✓ | panels only | – |
| Keep-together / orphan control | ✓ | n/a¹ | ✓ | ✓ | ✓ | n/a |
| Decimal alignment (NBSP) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| System dependency | none | none | none | TeX install | typst³ | none |
¹ HTML/MD are one continuous document; the browser repeats <thead> on print.
² On HTML/MD the per-page N renders as a row under each subgroup banner instead of in the repeating header.
³ The standalone typst binary, or the copy bundled inside Quarto ≥ 1.4 — so machines with RStudio / Posit Workbench typically need nothing. The .tex and .typ source backends have no system dependency at all; only the compile to PDF does.
System requirements
RTF, HTML, DOCX, LaTeX source, Typst source, Markdown need nothing beyond the R package. Only the compile to PDF has a system dependency, and either engine satisfies it:
-
LaTeX engine. The two packages missing from common TeX distributions (
tabularray,ninecolors) ship withtabularand are staged next to the generated.texwhenever the local TeX cannot resolve them, so a locked-down server (Domino, Posit Workbench) needs notlmgr install. -
Typst engine. No TeX at all:
emit()runs the standalonetypstbinary, or Quarto’s bundled copy (quarto typst). Fonts are typst’s one quiet failure mode — a missing family substitutes silently — soemit()warns when a family you explicitly named cannot be found, andcheck_typst()names the face PDFs actually render in.
Check readiness and, on a fresh machine, install an engine once:
check_latex() # LaTeX engine: probes via kpsewhich — what a compile will find
check_typst() # Typst engine: binary, version floor, font chain
tinytex::install_tinytex(bundle = "TinyTeX") # one-time, fresh machines only
# or install Quarto (https://quarto.org), which bundles the typst engineOS-managed TeX Live (RHEL/dnf, Debian/apt):
tlmgris locked and refuses to install (“will likely destroy the … TeXLive install”). Do not force it with--ignore-warning. Usually nothing is needed anyway — the bundled copies cover the gap; ifcheck_latex()still reports a missing package, install a user-space TinyTeX you control:tinytex::install_tinytex(bundle = "TinyTeX"), then restart R. On images whose TeX Live is frozen on a pre-2023 kernel (too old fortabularray),emit()skips LaTeX automatically and compiles through typst instead.
For decimal alignment in paper backends, metric-compatible fonts matter — check with check_fonts(spec).
Troubleshooting
-
A relative DOCX path works.
emit(spec, "out/x.docx")resolves the path against your working directory like every other backend (the output path is absolutised before the OOXML zip is staged). NonormalizePath()dance is needed. -
If a PDF build appears to hang, it is the LaTeX engine stopping at an interactive error prompt — fix the underlying LaTeX dependency (run
check_latex()), or sidestep TeX entirely withemit(spec, "out.pdf", format = "typst"); render RTF/HTML to keep working in the meantime. -
If a typst-compiled PDF renders in an unexpected font, run
check_typst()— it names the first available family of the chain, i.e. the face typst actually uses. Missing later members of the built-in fallback chain are normal cross-OS variance and are not warned about; only a family you explicitly named triggers theemit()warning.
Cross-backend qualification (CDISC pilot)
The package ships a qualification (inst/qualification/) that rebuilds representative CDISC-pilot tables (demographics, populations, AE overview, AE by SOC/PT) from the public PHUSE Test Data Factory ADaM and renders each to every backend, checking three things per cell: it emits without error, the file is structurally valid, and an independent count computed from the ADaM appears in the rendered text of that backend (cross-backend content parity). The four pilot tables across all four file backends give a 16/16 PASS matrix:
| Table | RTF | HTML | DOCX | PDF¹ |
|---|---|---|---|---|
| 14-2.01 Demographics | PASS | PASS | PASS | PASS |
| 14-1.01 Populations | PASS | PASS | PASS | PASS |
| 14-3.01 TEAE overview | PASS | PASS | PASS | PASS |
| 14-3.04 TEAE by SOC/PT | PASS | PASS | PASS | PASS |
¹ The PDF column is verified manually in a local environment with a LaTeX engine (check_latex()) and pdftotext for the text-parity check. It is not run in continuous integration, which carries no TeX install; CI exercises the RTF, HTML, and DOCX backends.
The runnable script (inst/qualification/qualify_tabular_cdisc.R) and how to fetch the data are in the qualification README in that same inst/qualification/ folder; it is the most direct evidence that one tabular spec produces consistent, correct output across all backends.