Skip to content

Add lt_export() to save tables as PDF or PNG#2

Merged
yihui merged 10 commits into
mainfrom
feat-lt-export
Jul 5, 2026
Merged

Add lt_export() to save tables as PDF or PNG#2
yihui merged 10 commits into
mainfrom
feat-lt-export

Conversation

@yihui

@yihui yihui commented Jul 4, 2026

Copy link
Copy Markdown
Owner

What

New lt_export(x, output = 'lt.pdf', crop = TRUE, padding = 8, ...) renders an lt_tbl in headless Chromium and writes it to a file. Format follows the output extension: .pdf → vector PDF (Chromium print-to-PDF), otherwise PNG.

Cropping

By default the output is cropped tight to the table (no surrounding page whitespace). A preliminary browser_dom() pass runs lt.js and measures the body's scroll size — this matters because the caption's border-top and footer spacing extend past the <table> border-box, so measuring the table box alone undercounts the height.

  • PDF: an @page{size:...} rule sets the page box exactly → single page, dep-free. (Measuring the table box was the cause of the two-page penguins output.)
  • PNG: Chromium's --screenshot size equals the window size and can't go below Chromium's minimum window / reserved-chrome height, which scales overflowing content. So we render onto a generous window (table unscaled, top-left) and crop to the exact box with magick. Without magick, PNG falls back to the full page with a warning.

SVG is not supported — Chromium cannot export a rendered page as SVG; PDF is the vector option.

Tests

tests/test-ci/test-js.R (browser-gated): PDF+PNG written with correct magic bytes, a large table crops to one PDF page (regression guard), and the cropped PNG matches the measured content box exactly (magick-gated).

🤖 Generated with Claude Code

Render an lt table in headless Chromium and write it to a file, choosing
the format from the output extension: .pdf for vector PDF (Chromium's
print-to-PDF) and PNG otherwise.

By default the output is cropped tightly to the table, removing the
surrounding page whitespace. A preliminary browser pass measures the
rendered table via browser_dom() (running lt.js), reading the body's
scroll size so the caption border and footer spacing are included; then:

- PDF: an @page rule sets the page box to the measured size, so the
  table fits on a single page (measuring the table's border-box alone
  undercounted the height and spilled onto a second page).
- PNG: Chromium's --screenshot output equals the window size and cannot
  be shrunk below Chromium's minimums, so we render onto a large window
  (table unscaled at the top-left) and crop to the exact box with magick.
  Without magick, PNG falls back to the full page with a warning.

Chromium cannot export SVG, so no vector option beyond PDF is offered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@yihui yihui left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should lt_html() be merged into lt_export()? lt_html() basically exports the table to html (without relying on js to render the table)

Comment thread R/render.R Outdated
Comment thread R/render.R Outdated
Comment thread R/render.R Outdated
Comment thread R/render.R Outdated
- lt_export() now writes .html too, delegating to lt_html() for a static
  table (no browser). One file-export entry point for html/pdf/png.
- Add width/height args to set the table size in CSS pixels for pdf/png,
  usable whether crop is TRUE or FALSE (crop no longer forces the
  measured width).
- Drop the SVG discussion from the docs.
- Use xfun::loadable() instead of requireNamespace() for the magick check.
- Return the output path plainly (not invisibly).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@yihui

yihui commented Jul 5, 2026

Copy link
Copy Markdown
Owner Author

Re: merging lt_html() into lt_export() — I kept lt_html() but wired it into lt_export() so there's now one file-export entry point:

  • lt_export(x, 'tbl.html') writes a static HTML table by delegating to lt_html() (no browser needed).
  • lt_export(x, 'tbl.pdf'|'tbl.png') renders via headless Chromium.

I left lt_html() as its own exported function because it returns the HTML string rather than writing a file, and it's used internally (knit_print / record_print / the lt.lt_html option / tests) — those need the string, not a file. So lt_export() is the 'write to disk' front door, lt_html() is the 'give me the markup' engine. If you'd rather fully fold it in (e.g. drop lt_html() and have callers use format()/an internal helper), I can do that — let me know.

lt_export() is now the sole public file-export API:

- lt_html() becomes an internal helper. It still renders the static HTML
  table and still backs the `lt.lt_html` option (knit_print/record_print),
  but callers reach it through lt_export(x, "file.html"). The Global option
  doc moves to lt_export(), and its `method`/`css` extras are documented as
  ... passthrough for the .html path.
- Drop the `height` argument: height is always derived from the content at
  the chosen `width`, so an explicit height only risked clipping or padding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@yihui

yihui commented Jul 5, 2026

Copy link
Copy Markdown
Owner Author

Done in 9581b89:

  • Unexported lt_html() — now an internal helper. lt_export() is the only public export API. lt_html() still renders the static table and still backs the lt.lt_html option (knit_print/record_print); its method/css args are reachable via lt_export(x, 'file.html', method=, css=). Moved the Global-option docs onto lt_export().
  • Dropped height — height is always derived from content at the chosen width; width alone remains, honored whether crop is TRUE or FALSE.

yihui and others added 6 commits July 4, 2026 21:35
lt_export() is now the single HTML/PDF/PNG entry point:

- Fold the former lt_html() into an internal render_html() and expose its
  controls as explicit lt_export() arguments: method, css, fragment (these
  apply to .html output only).
- Add method = "raw": write the JavaScript-spec HTML unchanged, so the
  table is built client-side by lt.js at view time (no Node.js or browser
  runs). The other methods ("node"/"browser"/"auto") bake a static <table>.
- Default output = "lt.html". When output is NA, return the HTML as a
  string (xfun::raw_string) instead of writing a file.
- The lt.lt_html option path (knit_print/record_print) now calls
  render_html() with the same method/css/fragment arguments.

NEWS: document lt_export() instead of lt_html().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Show each output form: method = "raw" HTML (runs unconditionally, as it
needs no external tool), a baked static table gated on Node.js/browser
availability, and PDF/PNG gated on a headless browser.
@yihui
yihui force-pushed the feat-lt-export branch from 8023e43 to 4fd7492 Compare July 5, 2026 02:04
@yihui
yihui merged commit 02cfe58 into main Jul 5, 2026
@yihui
yihui deleted the feat-lt-export branch July 5, 2026 02:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant