Free tools Windows power users keep installed
One-click scans. No signup required.
Render the R Markdown document to HTML, add print-color CSS, and let Puppeteer print that HTML with backgrounds enabled. Puppeteer uses the print media type by default, and Chromium can alter colors for print unless the stylesheet requests exact color adjustment. Add both -webkit-print-color-adjust: exact and print-color-adjust: exact, set printBackground: true, and use preferCSSPageSize: true when your CSS defines the page size.
If you want the PDF to follow the on-screen stylesheet instead, call page.emulateMediaType('screen') before page.pdf(). The workflow below covers the R Markdown configuration, a complete Puppeteer script, page sizing, dependencies, troubleshooting, and an API alternative.
| # | Preview | Product | Price | |
|---|---|---|---|---|
| 1 |
|
R Markdown: The Definitive Guide (Chapman & Hall/CRC The R Series) | $20.00 | Buy on Amazon |
| 2 |
|
R Markdown Cookbook (Chapman & Hall/CRC The R Series) | $25.31 | Buy on Amazon |
| 3 |
|
bookdown (Chapman & Hall/CRC The R Series) | $22.90 | Buy on Amazon |
| 4 |
|
Analyzing Social Networks Using R | $42.11 | Buy on Amazon |
The reliable workflow
- Write a print stylesheet that preserves exact foreground and background colors.
- Attach it to
html_documentin the R Markdown YAML. - Render the
.Rmdto HTML withrmarkdown::render(). - Open the generated HTML in Chromium through Puppeteer.
- Print with
printBackground: trueand the page-size option that matches your CSS.
This is an HTML-to-PDF workflow. It is different from R Markdown’s LaTeX-based pdf_document(), where browser print CSS and Puppeteer settings do not participate.
1. Add print-color CSS
Create a file named print-colors.css beside your R Markdown source:
#1 Best Overall
@media print {
*, *::before, *::after {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
The vendor-prefixed declaration supports Chromium’s print implementation; the unprefixed declaration is the standards spelling. The @media print wrapper keeps the rule scoped to printed output. If a color is currently defined only inside @media screen, move the print-critical declaration into @media print or choose the screen-media option shown later.
Preserving colored panels and callouts
Exact color adjustment does not by itself force background graphics into the PDF. Puppeteer’s printBackground option must also be enabled. For example:
.callout-warning {
color: #5c3b00;
background: #fff3cd;
border-left: 0.35rem solid #f0ad4e;
}
@media print {
.callout-warning {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
2. Configure R Markdown to produce HTML
Use html_document and attach the stylesheet through YAML:
---
title: "Color-preserving report"
output:
html_document:
css: print-colors.css
self_contained: true
---
css accepts a CSS or Sass file. self_contained: true embeds linked stylesheets, images, and scripts as data URIs so the generated HTML is easier to move to another machine. MathJax remains an external dependency even for a self-contained document, so an offline environment needs a separately available MathJax setup if the report contains equations.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsRender it from R:
rmarkdown::render(
"report.Rmd",
output_format = "html_document",
output_file = "report.html"
)
Passing output_format = 'html_document' makes the browser route explicit, even if the document has other output formats in its metadata. Open report.html in Chromium before automating it. If the colors are absent there, Puppeteer cannot restore them; fix the HTML or CSS first.
Inline CSS instead of a separate file
You can put the same declarations in an R Markdown CSS chunk when keeping the report in one source file is more convenient. The important part is that the generated HTML contains the @media print rule and both exact-color properties. A separate file is generally easier to inspect and reuse across reports.
3. Install and run Puppeteer
In a Node.js project, install Puppeteer:
npm install puppeteer
The following script renders a local HTML file to PDF. Save it as print-report.mjs:
import puppeteer from 'puppeteer';
import { pathToFileURL } from 'node:url';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
const htmlUrl = pathToFileURL(process.argv[2] || 'report.html').href;
await page.goto(htmlUrl, { waitUntil: 'networkidle0' });
await page.evaluate(() => document.fonts?.ready);
// Print media is Puppeteer's default. Keep this line for clarity.
await page.emulateMediaType('print');
await page.pdf({
path: process.argv[3] || 'report.pdf',
printBackground: true,
preferCSSPageSize: true
});
} finally {
await browser.close();
}
Run it after rendering:
node print-report.mjs report.html report.pdf
networkidle0 waits until network activity has settled, and waiting for document.fonts.ready reduces substitutions caused by late-loading web fonts. For a self-contained HTML file these waits are usually quick; for externally hosted assets they depend on network access.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Use the screen stylesheet instead
If the intended PDF should match what a viewer sees on screen, replace the print-media call with:
await page.emulateMediaType('screen');
await page.pdf({
path: 'report.pdf',
printBackground: true,
preferCSSPageSize: true
});
This makes screen media queries active while printing. It does not remove the value of printBackground: true; background graphics can still be omitted without that option.
4. Control paper size, margins, and pagination
When the document owns its page geometry, define it in CSS and let Puppeteer honor it:
@page {
size: A4;
margin: 16mm 14mm 18mm;
}
@media print {
.page-break {
break-after: page;
}
}
With preferCSSPageSize: true, an @page size takes priority over Puppeteer’s format, width, or height settings. If you omit @page, you can instead select a Puppeteer format such as A4 in the PDF options, but do not expect a CSS page declaration to win unless preferCSSPageSize is enabled.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Rank #3
- bookdown: Authoring Books and Technical Documents with R Markdown
- ABIS BOOK
- CRC Press
Keep page-break markers print-only if they should not appear in the browser preview:
.page-break {
display: none;
}
@media print {
.page-break {
display: block;
break-after: page;
}
}
Modern Chromium honors break-after; older stylesheets may also contain page-break-after for compatibility. Check the generated PDF when a break falls inside a table, figure, or colored callout, because the browser may move the entire block.
Choosing the correct R Markdown output path
| Path | Renderer | Browser print CSS | Best use |
|---|---|---|---|
html_document → Puppeteer |
HTML, then Chromium | Yes | CSS-defined colors, web fonts, responsive layouts, and browser-accurate styling |
html_document → pagedown::chrome_print() |
HTML, then headless Chrome managed by R | Yes | An R-owned workflow that still needs Chromium printing |
pdf_document |
LaTeX engine | No | LaTeX typography and PDF graphics controls rather than browser CSS |
The pagedown package’s chrome_print() is an alternative when you want the R process to own the Chrome step. It remains an HTML-to-Chromium route; switching to pdf_document() changes the renderer and therefore the styling controls.
Diagnose missing colors in order
- Inspect the HTML. Open the generated file in Chromium and use DevTools to verify that the element has the expected computed foreground and background colors.
- Check media conditions. A rule under
@media screenis inactive when Puppeteer prints with the defaultprintmedia type. Move the rule to@media printor callemulateMediaType('screen'). - Verify both color-adjust declarations. Include
-webkit-print-color-adjust: exactandprint-color-adjust: exacton the elements or a broad print rule. - Enable backgrounds. Set
printBackground: true; otherwise colored panels, fills, and background images can disappear. - Check asset paths. Relative CSS, image, font, and script URLs resolve from the HTML file’s location. Move the whole directory together, use correct absolute paths, or enable
self_contained: truefor dependencies that R Markdown can embed. - Check external services. MathJax remains external in a self-contained document. A blocked CDN, proxy, or offline runner can leave equations or dependent styling incomplete.
- Check pagination separately. Define
@pagemargins and size, then enablepreferCSSPageSize: true. A layout change can make a color block appear missing when it was pushed to another page. - Record versions. Save the Chromium revision and Puppeteer version when comparing output. Print defaults and implementation details can change between releases, so compare the generated HTML as well as the PDF.
Common failure modes and fixes
The PDF is black-and-white but the HTML is colored
This usually means print media rules are overriding the screen palette, or exact color adjustment is absent. Inspect computed styles under print media, add the two color-adjust declarations, and ensure backgrounds are enabled.
Colored backgrounds vanish while text color survives
Background graphics are controlled independently from text. Add printBackground: true to page.pdf(), then inspect whether the background is a CSS background or an image whose URL failed to resolve.
The PDF is blank or missing late content
Do not print immediately after goto when the report loads assets asynchronously. Use waitUntil: 'networkidle0', wait for fonts, and add an explicit selector wait when your report has a known completion marker.
Rank #4
Fonts or images differ between machines
Use self-contained HTML where practical, install the required fonts in the Chromium environment, and avoid relying on a user profile’s local files. For network resources, verify that the runner can reach every URL before printing.
Page size settings appear ignored
If CSS contains @page, set preferCSSPageSize: true. Otherwise remove the CSS size declaration and specify Puppeteer’s format, width, or height deliberately.
Performance, reproducibility, and cost considerations
- Render once, print once. Generate the HTML with
rmarkdown::render(), then reuse that artifact for retries instead of rerunning expensive R code. - Keep assets local when repeatability matters. Embedded CSS and images reduce network variability. External MathJax still needs a controlled, reachable source.
- Reuse a browser for batches. Launching Chromium for every report adds startup overhead; keep one browser process and create a fresh page per document when processing multiple files.
- Use deterministic inputs. Pin the Node, Puppeteer, and Chromium versions in CI, record the R and rmarkdown versions, and keep the same fonts installed on every runner.
- Validate output. For important reports, inspect representative pages for colored callouts, syntax highlighting, charts, and page breaks rather than checking only that a PDF file exists.
- Expect environment limits. Sandboxed CI containers may require the documented Chromium launch settings for that environment. Do not disable security flags globally; change them only when the runner requires it and isolate the job.
Or skip the browser setup
If your rendered report is available at a reachable URL, ScreenshotNeo can capture it through one API request instead of maintaining a local browser workflow. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
Use the API examples from the ScreenshotNeo documentation after publishing the HTML URL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/report.html -o shot.webp
import requests
r = requests.get('https://api.screenshotneo.com/v1/shot', params={'access_key': 'YOUR_API_KEY', 'url': 'https://example.com/report.html'}, timeout=90)
open('shot.webp', 'wb').write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com/report.html' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo’s output options include PNG, JPEG, WebP, and PDF; consult the linked documentation for the format and print settings you need. Every feature is included on every plan. The Free plan provides 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots, and yearly billing gives two months free. Create a free ScreenshotNeo account to try it.
FAQ
Frequently Asked Questions
Will this preserve colors in a LaTeX PDF generated by pdf_document()?
No. That output uses a LaTeX engine, so Puppeteer print CSS and Chromium color settings do not control it. Use the HTML-plus-Chromium route when CSS fidelity is the requirement.
Recommended Free Tools
Why does self_contained: true not make MathJax work offline?
R Markdown embeds most linked assets, but MathJax remains external. An offline or restricted runner must provide MathJax separately.
Can I let Puppeteer choose the page size instead of CSS?
Yes. Omit the CSS @page size and set Puppeteer’s format, width, or height; use preferCSSPageSize: true when CSS should take precedence.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

