Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Browser Rendering] Guide for PDF Rendering from HTML and CSS #17510

Merged
merged 3 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions src/content/docs/browser-rendering/how-to/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: How To
pcx_content_type: navigation
sidebar:
order: 4
group:
hideIndex: true
---

import { DirectoryListing } from "~/components";

<DirectoryListing />
267 changes: 267 additions & 0 deletions src/content/docs/browser-rendering/how-to/pdf-generation.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
---
pcx_content_type: how-to
title: Generate PDFs Using HTML and CSS
sidebar:
order: 1
---

import { Aside } from "~/components";

As seen in the [Getting Started guide](/browser-rendering/get-started/screenshots/), Browser Rendering can be used to generate screenshots for any given URL. Alongside screenshots, we can also generate full PDF documents for a given webpage, and can also provide the webpage markup and style ourselves.
Oxyjun marked this conversation as resolved.
Show resolved Hide resolved

## Prerequisites

1. Use the `create-cloudflare` CLI to generate a new Hello World Cloudflare Worker script:

```sh
npm create cloudflare@latest -- browser-worker
```

2. Install `@cloudflare/puppeteer`, which allows you to control the Browser Rendering instance:

```sh
npm install @cloudflare/puppeteer --save-dev
```

3. Add our Browser Rendering binding to your new `wrangler.toml` configuration:
Oxyjun marked this conversation as resolved.
Show resolved Hide resolved

```yaml
browser = { binding = "BROWSER" }
```

4. Replace the contents of `src/index.ts` (or `src/index.js` for JavaScript projects) with the following skeleton script:

```ts
import puppeteer from "@cloudflare/puppeteer";

const generateDocument = (name: string) => {};

export default {
async fetch(request, env) {
const { searchParams } = new URL(request.url);
let name = searchParams.get("name");

if (!name) {
return new Response("Please provide a name using the ?name= parameter");
}

const browser = await puppeteer.launch(env.BROWSER);
const page = await browser.newPage();

// Step 1: Define HTML and CSS
const document = generateDocument(name);

// Step 2: Send HTML and CSS to our browser
await page.setContent(document);

// Step 3: Generate and return PDF

return new Response();
},
};
```

## 1. Define HTML and CSS

Rather than using Browser Rendering to navigate to a user-provided URL, manually generate a webpage, then provide that webpage to the Browser Rendering instance. This allows you to render any design you want.

:::note
It's worth noting that you can generate your HTML or CSS using any method
you'd like. For now we're using string interpolation, but this method is
fully-compatible with web frameworks capable of rendering HTML on Workers such
as React, Remix, and Vue.
:::

For this example, we're going to take in user-provided content (via a '?name=' parameter), and have that name output in the final PDF document.

To start, let's fill out our 'generateDocument' function with the following:
Oxyjun marked this conversation as resolved.
Show resolved Hide resolved

```ts
const generateDocument = (name: string) => {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<style>
html,
body,
#container {
width: 100%;
height: 100%;
margin: 0;
}
body {
font-family: Baskerville, Georgia, Times, serif;
background-color: #f7f1dc;
}
strong {
color: #5c594f;
font-size: 128px;
margin: 32px 0 48px 0;
}
em {
font-size: 24px;
}
#container {
flex-direction: column;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
</style>
</head>

<body>
<div id="container">
<em>This is to certify that</em>
<strong>${name}</strong>
<em>has rendered a PDF using Cloudflare Workers</em>
</div>
</body>
</html>
`;
};
```

This example HTML document should render a beige background imitating a certificate showing that the user-provided name has successfully rendered a PDF using Cloudflare Workers.

:::note
It's usually best to avoid directly interpolating user-provided content into
an image or PDF renderer in production applications. To render contents like
an invoice, it wold be best to validate the data input, and fetch data
yourself using tools like [D1](/d1/) or
[Workers KV](/kv/).
:::

## 2. Load HTML and CSS Into Browser

Now that you have your fully styled HTML document, you can take the contents and send it to your browser instance. Create an empty page to store this document as follows:

```ts
const browser = await puppeteer.launch(env.BROWSER);
const page = await browser.newPage();
```

The [`page.setContent()`](https://github.com/cloudflare/puppeteer/blob/main/docs/api/puppeteer.page.setcontent.md) function can then be used to set the page's HTML contents from a string, so we pass in our created document directly like so:
Oxyjun marked this conversation as resolved.
Show resolved Hide resolved

```ts
await page.setContent(document);
```

## 3. Generate and Return PDF

With your Browser Rendering instance now rendering our provided HTML and CSS, you can use the [`page.pdf()`](https://github.com/cloudflare/puppeteer/blob/main/docs/api/puppeteer.page.pdf.md) command to generate a PDF file and return it to the client.
Oxyjun marked this conversation as resolved.
Show resolved Hide resolved

```ts
let pdf = page.pdf({ printBackground: true });
```

The `page.pdf()` call supports a [number of options](https://github.com/cloudflare/puppeteer/blob/main/docs/api/puppeteer.pdfoptions.md), including setting the dimensions of the generated PDF to a specific paper size, setting specific margins, and allowing fully-transparent backgrounds. For now, you are only overriding the `printBackground` option to allow your `body` background styles to show up.

Now that you have your PDF data, return it to the client in the `Response` with an `application/pdf` content type:

```ts
return new Response(pdf, {
headers: {
"content-type": "application/pdf",
},
});
```

## Conclusion

The full Worker script now looks as follows:

```ts
import puppeteer from "@cloudflare/puppeteer";

const generateDocument = (name: string) => {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<style>
html, body, #container {
width: 100%;
height: 100%;
margin: 0;
}
body {
font-family: Baskerville, Georgia, Times, serif;
background-color: #f7f1dc;
}
strong {
color: #5c594f;
font-size: 128px;
margin: 32px 0 48px 0;
}
em {
font-size: 24px;
}
#container {
flex-direction: column;
display: flex;
align-items: center;
justify-content: center;
text-align: center
}
</style>
</head>

<body>
<div id="container">
<em>This is to certify that</em>
<strong>${name}</strong>
<em>has rendered a PDF using Cloudflare Workers</em>
</div>
</body>
</html>
`;
};

export default {
async fetch(request, env) {
const { searchParams } = new URL(request.url);
let name = searchParams.get("name");

if (!name) {
return new Response("Please provide a name using the ?name= parameter");
}

const browser = await puppeteer.launch(env.BROWSER);
const page = await browser.newPage();

// Step 1: Define HTML and CSS
const document = generateDocument(name);

// // Step 2: Send HTML and CSS to our browser
await page.setContent(document);

// // Step 3: Generate and return PDF
const pdf = await page.pdf({ printBackground: true });

return new Response(pdf, {
headers: {
"content-type": "application/pdf",
},
});
},
};
```

You can run this script to test it using Wrangler’s `--remote` flag:

```sh
npx wrangler@latest dev --remote
```

With your script now running, you can pass in a `?name` parameter to the local URL (such as `http://localhost:8787/?name=Harley`) and should see the following:

![A screenshot of a generated PDF, with the author's name shown in a mock certificate.](~/assets/images/browser-rendering/pdf-generation.png).

---

Dynamically generating PDF documents solves a number of common use-cases, from invoicing customers to archiving documents to creating dynamic certificates (as seen in the simple example here).
Loading