Skip to contents

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:

emit(spec, "table.rtf") # RTF
emit(spec, "table.html") # HTML
emit(spec, "table.docx") # Word
emit(spec, "table.tex") # LaTeX source
emit(spec, "table.typ") # Typst source
emit(spec, "table.pdf") # PDF (LaTeX engine, or typst without a TeX)
emit(spec, "table.md") # Markdown

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":

emit(spec, "table.pdf", format = "latex") # force the TeX compile
emit(spec, "table.pdf", format = "typst") # force typst — no TeX needed

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] TRUE

as_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):

grid <- as_grid(spec)
length(grid@pages)
#> [1] 1
grid@metadata$col_names
#> [1] "variable"   "stat_label" "placebo"    "drug_50"    "drug_100"  
#> [6] "Total"

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 with tabular and are staged next to the generated .tex whenever the local TeX cannot resolve them, so a locked-down server (Domino, Posit Workbench) needs no tlmgr install.
  • Typst engine. No TeX at all: emit() runs the standalone typst binary, or Quarto’s bundled copy (quarto typst). Fonts are typst’s one quiet failure mode — a missing family substitutes silently — so emit() warns when a family you explicitly named cannot be found, and check_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 engine

OS-managed TeX Live (RHEL/dnf, Debian/apt): tlmgr is 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; if check_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 for tabularray), 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). No normalizePath() 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 with emit(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 the emit() 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.