Skip to content

Commit 278e26e

Browse files
Merge pull request #3 from ferdinandkeller/dev
Dev
2 parents d6f1f59 + 845700b commit 278e26e

File tree

6 files changed

+880
-332
lines changed

6 files changed

+880
-332
lines changed

.prettierrc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"tabWidth": 4,
3+
"useTabs": false,
4+
"semi": false,
5+
"singleQuote": true
6+
}

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# start from the official node v18 image
22
# we use alpine to keep the image (relatively) small
3-
FROM node:18-alpine
3+
FROM node:20-alpine
44

55
# install chromium and its dependencies
6-
RUN apk add --no-cache \
6+
RUN apk update && apk upgrade && apk add --no-cache \
77
msttcorefonts-installer font-noto fontconfig \
88
freetype ttf-dejavu ttf-droid ttf-freefont ttf-liberation \
99
chromium \

README.md

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This action will automatically render any local webpage into a PDF.
44

55
Because it integrates its own web-server, it has no problems importing css, js and fonts (whether available locally or referenced online, including from websites like google fonts), and will wait for everything to render properly before rending the output to a PDF. It means your webpage doesn't have to be a static HTML page, but can also use any javascript framework.
66

7-
The action internally uses chromium, so the rendered PDF won't have any of the weird problems that exist with Firefox or WebKit, like:
7+
The action internally uses chromium, so the rendered PDF won't have any of the weird problems that can sometimes exist like:
88
- font kerning
99
- missing styles or resources
1010
- javascript not executed before rendering
@@ -14,12 +14,47 @@ Here is an example on how to use the action:
1414

1515
```yml
1616
- name: HTML website to PDF converter
17-
uses: ferdinandkeller/html-to-pdf-action@v1
17+
uses: ferdinandkeller/html-to-pdf-action@v2
1818
with:
19-
- source-path: './dist'
20-
- destination-path: './out.pdf'
19+
- source-path: "./dist"
20+
- destination-path: "./out.pdf"
2121
```
2222
2323
The `source-path` parameter specify which directory contains your builded html (your main file must be called `index.html`), while `destination-path` is the path where you want to put your PDF.
2424

25+
With a bit more context:
26+
27+
```yml
28+
name: Convert HTML to PDF
29+
30+
on:
31+
push:
32+
branches:
33+
- main
34+
workflow_dispatch: # allows manual trigger of the action
35+
36+
jobs:
37+
convert-html:
38+
runs-on: ubuntu-latest
39+
40+
steps:
41+
# Checkout the repository
42+
- name: Checkout code
43+
uses: actions/checkout@v4
44+
45+
# Run the HTML to PDF converter action
46+
- name: HTML website to PDF converter
47+
uses: ferdinandkeller/html-to-pdf-action@v2
48+
with:
49+
source-path: "./dist"
50+
destination-path: "./out.pdf"
51+
52+
# Upload the PDF as an artifact
53+
- name: Upload PDF
54+
uses: actions/upload-artifact@v4
55+
with:
56+
name: generated-pdf
57+
path: ./out.pdf
58+
```
59+
2560
Even though puppeteer (which this generator uses) only supports `AMD64`, thanks to some tweeking this Dockerfile and this GitHub action can work on pretty much any architecture supported by chromium, including on `AMD64` and `ARM64`.

generate_pdf.js

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ if (!('destination-path' in args)) {
4242
// 'file:///path/to/root/style.css' to make it work
4343
// to avoid this, we start a server and serve the files from there, and then we can use the url 'http://127.0.0.1'
4444
let server = hs.createServer({
45-
root: path.join(process.env.GITHUB_WORKSPACE || __dirname, args['source-path']),
45+
root: path.join(
46+
process.env.GITHUB_WORKSPACE || __dirname,
47+
args['source-path']
48+
),
4649
cache: -1,
4750
})
4851
server.listen(8000, '0.0.0.0', generate_pdf)
@@ -52,37 +55,80 @@ async function generate_pdf() {
5255
// launch the browser
5356
const browser = await puppeteer.launch({
5457
args: [
55-
// because we are using github action's docker container,
56-
// we need to run as root (we can't change that)
57-
// chromium throws security warning when running as root, so we need to disable the sandbox
58-
'--no-sandbox',
59-
// we also need to disable the shared memory, as we are running in a docker container
60-
// with a limited amount of memory
61-
'--disable-dev-shm-usage',
62-
// absolutely needed, else you will see font kerning problems in the pdf
63-
'--font-render-hinting=none',
64-
// we want to use the srgb color profile, as this is the default color profile for the web
65-
// without that, we will see color differences between the website and the pdf
66-
'--force-color-profile=srgb',
58+
// we are running inside docker, we don't want a window
59+
'--headless',
60+
// we provide a default (virtual) window size
61+
// that way it's consistent across different environments
62+
'--window-size=1920,1080',
63+
// we want the window at the top left corner of the (virtual) screen
64+
'--window-position=0,0',
65+
66+
// when starting the browser for the first time, we don't want to see a welcome page
67+
// we want to directly go to the website
68+
'--no-first-run',
69+
// we don't want to see scrollbars
70+
'--hide-scrollbars',
71+
// we don't want popup saying "chromium is being controlled by automated software"
72+
'--disable-infobars',
73+
// we don't want to see any notifications, there is no one to interact with them
74+
'--disable-notifications',
75+
6776
// because we are generating a pdf, we don't need any extensions
77+
// there shouldn't be any anyway, but just to be sure
6878
'--disable-extensions',
69-
// we are running inside docker, we don't need a window
70-
'--headless',
79+
80+
// we are inside a docker container, where sandboxing is broken
81+
// we (likely) don't need the sandbox anyway
82+
'--no-sandbox',
83+
// absolutely required with our execution environment, else chromium will not start
84+
'--disable-setuid-sandbox',
85+
86+
// we also need to disable the shared memory
87+
// our container has a very limited amount of memory
88+
'--disable-dev-shm-usage',
89+
// we won't be using the audio, so we can mute it
90+
'--mute-audio',
91+
// we don't have a GPU in the docker container
92+
'--disable-gpu',
93+
// we don't have access to hardware acceleration
94+
'--disable-accelerated-2d-canvas',
7195
// we don't want chromium to run any background tasks,
7296
// we don't need them and it might slow down the process
7397
'--disable-background-networking',
7498
'--disable-component-extensions-with-background-pages',
7599
'--dns-prefetch-disable',
76-
// we don't need scrollbars
77-
'--hide-scrollbars',
100+
// we don't care about certificate errors that could hinder the process
101+
'--ignore-certificate-errors',
102+
// we want that to also apply to the skip list
103+
'--ignore-certificate-errors-skip-list',
104+
// chromium can throttle background pages, we don't want that
105+
'--disable-background-timer-throttling',
106+
// chromium can limit the resources of background pages, we don't want that
107+
'--disable-renderer-backgrounding',
108+
// chromium can also stop rendering when the page is occluded, we don't want that
109+
'--disable-backgrounding-occluded-windows',
110+
// in case of crash, we don't want to send a crash report
111+
'--disable-breakpad',
112+
// no translation is wanted
113+
'--disable-features=TranslateUI,BlinkGenPropertyTrees,IsolateOrigins,site-per-process',
114+
115+
// we want to use the srgb color profile, as this is the default color profile for the web
116+
// without that, we will see color differences between the website and the pdf
117+
'--force-color-profile=srgb',
118+
// we don't want the browser to scale the website
119+
'--force-device-scale-factor=1',
120+
// absolutely needed, else you will see font kerning problems in the pdf
121+
'--font-render-hinting=none',
78122
],
79123
})
80124

81125
// create a page
82126
const page = await browser.newPage()
83127

84-
// set a porper user agent to act like a normal browser
85-
await page.setUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36");
128+
// set a proper user agent to act like a normal browser
129+
await page.setUserAgent(
130+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36'
131+
)
86132

87133
// go to the website
88134
await page.goto('http://localhost:8000', {
@@ -92,7 +138,10 @@ async function generate_pdf() {
92138

93139
// render the page to a pdf
94140
await page.pdf({
95-
path: path.join(process.env.GITHUB_WORKSPACE || __dirname, args['destination-path']),
141+
path: path.join(
142+
process.env.GITHUB_WORKSPACE || __dirname,
143+
args['destination-path']
144+
),
96145
format: 'A4',
97146
printBackground: true,
98147
})
@@ -102,4 +151,4 @@ async function generate_pdf() {
102151

103152
// close the server
104153
server.close()
105-
}
154+
}

0 commit comments

Comments
 (0)