palapala_pdf 0.1.11 → 0.1.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +60 -7
- data/examples/headers_and_footers.pdf +0 -0
- data/examples/headers_and_footers.rb +18 -8
- data/examples/js_based_rendering.pdf +0 -0
- data/examples/paged_css.pdf +0 -0
- data/examples/paged_css.rb +6 -5
- data/lib/palapala/chrome_process.rb +1 -1
- data/lib/palapala/pdf.rb +42 -3
- data/lib/palapala/renderer.rb +12 -0
- data/lib/palapala/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a5231c5a9994723db5fafc42682644a6320a69813444bc0939e179225e7181b
|
4
|
+
data.tar.gz: 5f3600f8fb867ee059e1a67fcda2eeeff0d7f290c8621d8bc055e6fb4cbda39c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a50a38a6482f04f7af087b0469d0fcf0d5bc02e79f3ad2fe26a91c5b06aa07345fe15b99472355eab1daef95c3069d54e1d9990820b401872681172b278f43d2
|
7
|
+
data.tar.gz: 2697106dfcad72207bd20fcad857c9a474ffacf49a67fa10e1d2801a8b5c41226e9d9b7145066b78f6dc002dc951f706c1f5fd83c346b196674f59294d124bc0
|
data/README.md
CHANGED
@@ -18,6 +18,8 @@ And this while having the most modern HTML/CSS/JS availlable to you: flex, grid,
|
|
18
18
|
|
19
19
|
A core goal of this project is performance, and it is designed to be exceptionally fast. By leveraging **direct communication** with a headless Chrome or Chromium browser via a **raw web socket**, the gem minimizes overhead and dependencies, enabling PDF generation at speeds that significantly outperform other solutions. Whether generating simple or complex documents, this gem ensures that your Ruby applications can handle PDF tasks efficiently and at scale.
|
20
20
|
|
21
|
+
[Example: paged_css.pdf](https://raw.githubusercontent.com/palapala-app/palapala_pdf/main/examples/paged_css.pdf)
|
22
|
+
|
21
23
|
## Sponsor This Project
|
22
24
|
|
23
25
|
If you find this project useful and would like to support its development, consider sponsoring or buying a coffee to help keep it going:
|
@@ -73,7 +75,7 @@ HEADLESS_CHROME_URL=http://192.168.1.1:9222 ruby examples/performance_benchmark.
|
|
73
75
|
```
|
74
76
|
|
75
77
|
```sh
|
76
|
-
|
78
|
+
HEADLESS_CHROME_PATH=/var/to/chrome ruby examples/performance_benchmark.rb
|
77
79
|
```
|
78
80
|
|
79
81
|
**Create a PDF from HTML**
|
@@ -95,6 +97,7 @@ binary_data = Palapala::Pdf.new("<h1>Hello, world! #{Time.now}</h1>").binary_dat
|
|
95
97
|
## Advanced Examples
|
96
98
|
|
97
99
|
- headers and footers
|
100
|
+
- watermark
|
98
101
|
- paged css for paper sizes, paper margins, pages breaks, etc
|
99
102
|
- js based rendering
|
100
103
|
|
@@ -187,7 +190,29 @@ In this example, `pdf_data` is the binary data of the PDF file. The `filename` o
|
|
187
190
|
|
188
191
|
TODO
|
189
192
|
|
190
|
-
|
193
|
+
```Dockerfile
|
194
|
+
# Install Nodejs and Chromium, to import the chrome headless shell dependencies easily (chrome itself is not used)
|
195
|
+
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && apt-get install --no-install-recommends -y nodejs chromium && \
|
196
|
+
rm -rf /var/lib/apthet/lists /var/cache/apt/archives
|
197
|
+
|
198
|
+
# Install Chrome Headless Shell
|
199
|
+
RUN npx --yes @puppeteer/browsers install chrome-headless-shell@stable
|
200
|
+
```
|
201
|
+
|
202
|
+
Use a script like the below, to launch chrome headless shell from e.g. docker entrypoint script.
|
203
|
+
|
204
|
+
*launch_chrome_headless_shell.rb*
|
205
|
+
|
206
|
+
```sh
|
207
|
+
#!/bin/bash
|
208
|
+
# find the installation path of chrome headless shell (latest version)
|
209
|
+
export CHROME_PATH=$(npx --yes @puppeteer/browsers install chrome-headless-shell@stable | awk '{print $2}')
|
210
|
+
# start chrome headless with remote debugging on
|
211
|
+
$CHROME_PATH --disable-gpu --remote-debugging-port=9222 --disable-software-rasterizer --disable-bluetooth --no-sandbox
|
212
|
+
```
|
213
|
+
|
214
|
+
*It has also been reported that the Chrome process repeatedly crashes when running inside a Docker container on an M1 Mac. Chrome should work asexpected when deployed to a Docker container on a non-M1 Mac.*
|
215
|
+
|
191
216
|
|
192
217
|
## Thread-safety
|
193
218
|
|
@@ -201,12 +226,40 @@ thread safe in the sense that every web socket get's a new tab in the underlying
|
|
201
226
|
|
202
227
|
TODO
|
203
228
|
|
204
|
-
|
229
|
+
This buildpack installs chrome and chromedriver (chromedriver is actually not needed, but at least the buildpack is maintained)
|
205
230
|
|
206
|
-
|
231
|
+
```sh
|
232
|
+
https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-google-chrome
|
233
|
+
```
|
207
234
|
|
208
|
-
|
235
|
+
### launch as child process
|
209
236
|
|
210
|
-
|
237
|
+
set `HEADLESS_CHROME_PATH=chrome` as an ENV variable as this buildpacks adds `chrome` to the path.
|
211
238
|
|
212
|
-
|
239
|
+
### run seperately
|
240
|
+
|
241
|
+
In your `Procfile` adjust the web worker command
|
242
|
+
|
243
|
+
```yaml
|
244
|
+
web: bin/start
|
245
|
+
```
|
246
|
+
|
247
|
+
Create a bin/start script
|
248
|
+
|
249
|
+
```sh
|
250
|
+
#!/bin/bash
|
251
|
+
# Start Rails app
|
252
|
+
bundle exec rails server -p $PORT -e $RAILS_ENV &
|
253
|
+
|
254
|
+
# Start the background app
|
255
|
+
command_to_start_your_background_app &
|
256
|
+
|
257
|
+
# Wait for all processes to finish
|
258
|
+
wait -n
|
259
|
+
```
|
260
|
+
|
261
|
+
ensure the script is executable
|
262
|
+
|
263
|
+
```sh
|
264
|
+
chmod +x bin/start
|
265
|
+
```
|
Binary file
|
@@ -3,18 +3,28 @@
|
|
3
3
|
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
4
4
|
require 'palapala'
|
5
5
|
|
6
|
-
|
7
|
-
'<div
|
6
|
+
header =
|
7
|
+
'<div class="center">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>'
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
left_center_right = <<~HTML
|
10
|
+
<div style="display: flex; justify-content: space-between; width: 100%; margin-left: 1cm; margin-right: 1cm;">
|
11
|
+
<div style="text-align: left; flex: 1;">Left Text</div>
|
12
|
+
<div style="text-align: center; flex: 1;">Center Text</div>
|
13
|
+
<div style="text-align: right; flex: 1;">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>
|
14
|
+
</div>
|
15
|
+
HTML
|
16
|
+
|
17
|
+
footer =
|
18
|
+
'<span>Generated at <span class="date"></span></span>'
|
11
19
|
|
12
20
|
Palapala::Pdf.new(
|
13
21
|
"<h1>Title</h1><p>Hello world #{Time.now}</>",
|
14
|
-
|
15
|
-
|
16
|
-
margin_top:
|
17
|
-
|
22
|
+
header: left_center_right,
|
23
|
+
footer:,
|
24
|
+
margin_top: 1,
|
25
|
+
margin_left: 1,
|
26
|
+
margin_bottom: 1,
|
27
|
+
watermark: "DRAFT").save('headers_and_footers.pdf')
|
18
28
|
|
19
29
|
puts "Generated headers_and_footers.pdf"
|
20
30
|
# `open headers_and_footers.pdf`
|
Binary file
|
data/examples/paged_css.pdf
CHANGED
Binary file
|
data/examples/paged_css.rb
CHANGED
@@ -43,6 +43,7 @@ document = <<~HTML
|
|
43
43
|
margin: 0;
|
44
44
|
padding: 0;
|
45
45
|
font-family: Arial, sans-serif;
|
46
|
+
/* background-color: yellow; */
|
46
47
|
}
|
47
48
|
h1 {
|
48
49
|
page-break-before: always;
|
@@ -165,21 +166,21 @@ end
|
|
165
166
|
def header_footer_template(debug_color: nil)
|
166
167
|
<<~HTML
|
167
168
|
#{ debug(color: debug_color) if debug_color }
|
168
|
-
<div style="
|
169
|
+
<div style="font-size: 12pt;">#{yield}</div>
|
169
170
|
HTML
|
170
171
|
end
|
171
172
|
|
172
|
-
|
173
|
+
footer = header_footer_template do
|
173
174
|
"Page <span class='pageNumber'></span> of <span class='totalPages'></span>"
|
174
175
|
end
|
175
176
|
|
176
|
-
|
177
|
+
header = header_footer_template do
|
177
178
|
"Generated with Palapala PDF"
|
178
179
|
end
|
179
180
|
|
180
181
|
Palapala::Pdf.new(document,
|
181
|
-
|
182
|
-
|
182
|
+
header:,
|
183
|
+
footer:).save("paged_css.pdf")
|
183
184
|
|
184
185
|
puts "Generated paged_css.pdf"
|
185
186
|
|
@@ -82,7 +82,7 @@ module Palapala
|
|
82
82
|
# Display the version
|
83
83
|
system("#{chrome_path} --version") if Palapala.debug
|
84
84
|
# Launch chrome-headless-shell with the --remote-debugging-port parameter
|
85
|
-
params = [ "--disable-gpu", "--remote-debugging-port=9222" ]
|
85
|
+
params = [ "--disable-gpu", "--remote-debugging-port=9222", "--remote-debugging-address=0.0.0.0" ]
|
86
86
|
params.merge!(Palapala.chrome_params) if Palapala.chrome_params
|
87
87
|
pid = if Palapala.debug
|
88
88
|
spawn(chrome_path, *params)
|
data/lib/palapala/pdf.rb
CHANGED
@@ -12,8 +12,10 @@ module Palapala
|
|
12
12
|
#
|
13
13
|
# @param content [String] the HTML content to convert to PDF
|
14
14
|
# @param footer_html [String] the HTML content for the footer
|
15
|
+
# @param footer [String] the footer content that is centered
|
15
16
|
# @param generate_tagged_pdf [Boolean] whether to generate a tagged PDF
|
16
17
|
# @param header_html [String] the HTML content for the header
|
18
|
+
# @param header [String] the header content that is centered
|
17
19
|
# @param landscape [Boolean] whether to use landscape orientation
|
18
20
|
# @param margin_bottom [Integer] the bottom margin in inches
|
19
21
|
# @param margin_left [Integer] the left margin in inches
|
@@ -27,8 +29,10 @@ module Palapala
|
|
27
29
|
# @param scale [Float] the scale of the PDF rendering
|
28
30
|
def initialize(content,
|
29
31
|
footer_template: nil,
|
32
|
+
footer: nil,
|
30
33
|
generate_tagged_pdf: nil,
|
31
34
|
header_template: nil,
|
35
|
+
header: nil,
|
32
36
|
landscape: nil,
|
33
37
|
margin_bottom: nil,
|
34
38
|
margin_left: nil,
|
@@ -39,11 +43,14 @@ module Palapala
|
|
39
43
|
paper_width: nil,
|
40
44
|
prefer_css_page_size: nil,
|
41
45
|
print_background: nil,
|
42
|
-
scale: nil
|
46
|
+
scale: nil,
|
47
|
+
watermark: nil)
|
43
48
|
@content = content || raise(ArgumentError, "Content is required and can't be nil")
|
44
49
|
@opts = {}
|
45
|
-
|
46
|
-
|
50
|
+
raise(ArgumentError, "Either footer or footer_template is expected") if !footer_template.nil? && !footer.nil?
|
51
|
+
raise(ArgumentError, "Either header or header_template is expected") if !header_template.nil? && !header.nil?
|
52
|
+
@opts[:headerTemplate] = header_template || hf_template(from: header) || Palapala.defaults[:header_template]
|
53
|
+
@opts[:footerTemplate] = footer_template || hf_template(from: footer) || Palapala.defaults[:footer_template]
|
47
54
|
@opts[:pageRanges] = page_ranges || Palapala.defaults[:page_ranges]
|
48
55
|
@opts[:generateTaggedPDF] = generate_tagged_pdf || Palapala.defaults[:generate_tagged_pdf]
|
49
56
|
@opts[:paperWidth] = paper_width || Palapala.defaults[:paper_width]
|
@@ -56,11 +63,43 @@ module Palapala
|
|
56
63
|
@opts[:preferCSSPageSize] = prefer_css_page_size || Palapala.defaults[:prefer_css_page_size]
|
57
64
|
@opts[:printBackground] = print_background || Palapala.defaults[:print_background]
|
58
65
|
@opts[:scale] = scale || Palapala.defaults[:scale]
|
66
|
+
@opts[:headerTemplate] = (@opts[:headerTemplate].to_s + watermark(watermark)) if watermark
|
59
67
|
@opts[:displayHeaderFooter] = (@opts[:headerTemplate] || @opts[:footerTemplate]) ? true : false
|
60
68
|
@opts[:encoding] = :binary
|
61
69
|
@opts.compact!
|
62
70
|
end
|
63
71
|
|
72
|
+
def watermark(watermark, angle: "-15deg", color: "rgba(25,25,25,0.25)")
|
73
|
+
<<~HTML
|
74
|
+
<style>
|
75
|
+
.palapala_pdf_watermark {
|
76
|
+
position: fixed;
|
77
|
+
top: 50%;
|
78
|
+
left: 50%;
|
79
|
+
transform: translate(-50%, -50%) rotate(#{angle});
|
80
|
+
font-size: 8em;
|
81
|
+
color: #{color};
|
82
|
+
z-index: 9999;
|
83
|
+
}
|
84
|
+
</style>
|
85
|
+
<span class="palapala_pdf_watermark">#{watermark}</span>
|
86
|
+
HTML
|
87
|
+
end
|
88
|
+
|
89
|
+
def hf_template(from:)
|
90
|
+
return if from.nil?
|
91
|
+
style = <<~HTML.freeze
|
92
|
+
<style>
|
93
|
+
#header, #footer {
|
94
|
+
font-size: 10pt;
|
95
|
+
display: flex;
|
96
|
+
justify-content: center;
|
97
|
+
}
|
98
|
+
</style>
|
99
|
+
HTML
|
100
|
+
style + from
|
101
|
+
end
|
102
|
+
|
64
103
|
# Render the PDF content to a binary string.
|
65
104
|
#
|
66
105
|
# The params from the initializer are converted to the expected casing and merged with the options passed to this method.
|
data/lib/palapala/renderer.rb
CHANGED
@@ -103,8 +103,20 @@ module Palapala
|
|
103
103
|
Base64.decode64(result["data"])
|
104
104
|
end
|
105
105
|
|
106
|
+
def ping
|
107
|
+
result = send_command_and_wait_for_result("Runtime.evaluate", params: { expression: "1 + 1" })
|
108
|
+
raise "Ping failed" unless result["result"]["value"] == 2
|
109
|
+
end
|
110
|
+
|
106
111
|
def self.html_to_pdf(html, params: {})
|
107
112
|
thread_local_instance.html_to_pdf(html, params: params)
|
113
|
+
rescue StandardError
|
114
|
+
reset # Reset the renderer on error, the websocket connection might be broken
|
115
|
+
thread_local_instance.html_to_pdf(html, params: params) # Retry (once)
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.ping
|
119
|
+
thread_local_instance.ping
|
108
120
|
end
|
109
121
|
|
110
122
|
def close
|
data/lib/palapala/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: palapala_pdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Koen Handekyn
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: base64
|