palapala_pdf 0.1.19 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/README.md +30 -15
- data/examples/headers_and_footers.pdf +0 -0
- data/examples/js_based_rendering.pdf +0 -0
- data/examples/paged_css.pdf +0 -0
- data/examples/paged_css.rb +1 -1
- data/lib/palapala/chrome_process.rb +2 -2
- data/lib/palapala/helper.rb +7 -6
- data/lib/palapala/pdf.rb +25 -5
- data/lib/palapala/renderer.rb +34 -4
- data/lib/palapala/version.rb +1 -1
- data/palapala_pdf.gemspec +1 -0
- metadata +17 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 12be6d622065fdadf2611578279b723532a6ab68b25a2c5b9790692757058c17
|
4
|
+
data.tar.gz: 890152aa05e9ea2823e19ed1a2ec27eade4271951bbb6c948f44bc68d074a09f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d42e1f4c2de6eea69f8b42e57340ff191f9e87f77b42df05c3b4dca4acc21fb6d9847f2aea19085522071f0a5b37c5c62a512f5f4ceaee6e29267dc5638c359
|
7
|
+
data.tar.gz: c61845987b64ba8de756e05c81a7451da67e7eb59e50dd39e0a3c2088d201560fcd4f1723d8193c8e367c3578a7cfe2a475171c07209cbfb00dc4200d9313653
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.4.1
|
data/README.md
CHANGED
@@ -1,12 +1,10 @@
|
|
1
|
-
# PDF Generation for your Rubies
|
1
|
+
# PDF Generation for your Rails and Rubies
|
2
2
|
|
3
3
|
<div align="center"><img src="https://raw.githubusercontent.com/palapala-app/palapala_pdf/main/assets/images/logo.webp" alt="Palapala PDF Logo" width="200"></div>
|
4
4
|
|
5
|
-
This project is a Ruby gem that
|
5
|
+
This project is a Ruby gem that generates PDF files from HTML using the Chrome browser engine, making it convenient for tasks such as generating reports, invoices, or any other printable documents.
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
It leverages work from [Puppeteer](https://pptr.dev/browsers-api/) (@puppeteer/browsers) to install a local Chrome-Headless-Shell if no Chrome is running, but that requires node (npx) to be available.
|
7
|
+
On Heroku you just need to install a buildpack for Chrome and the rest is auto-configured.
|
10
8
|
|
11
9
|
This is how easy PDF generation can be in Ruby:
|
12
10
|
|
@@ -14,12 +12,15 @@ This is how easy PDF generation can be in Ruby:
|
|
14
12
|
require "palapala"
|
15
13
|
Palapala::Pdf.new("<h1>Hello, world! #{Time.now}</h1>").save('hello.pdf')
|
16
14
|
```
|
15
|
+
|
17
16
|
And this while having the most modern HTML/CSS/JS availlable to you: flex, grid, canvas, ...
|
18
17
|
|
19
18
|
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
19
|
|
21
20
|
[Example: paged_css.pdf](https://raw.githubusercontent.com/palapala-app/palapala_pdf/main/examples/paged_css.pdf)
|
22
21
|
|
22
|
+
It leverages work from [Puppeteer](https://pptr.dev/browsers-api/) (@puppeteer/browsers) to install a local Chrome-Headless-Shell if no Chrome is running (requires node/npx to be available).
|
23
|
+
|
23
24
|
## Sponsor This Project
|
24
25
|
|
25
26
|
If you find this project useful and would like to support its development, consider sponsoring or buying a coffee to help keep it going:
|
@@ -33,8 +34,8 @@ Your support is greatly appreciated and helps maintain the project!
|
|
33
34
|
|
34
35
|
To install the gem and add it to your application's Gemfile, execute the following command:
|
35
36
|
|
36
|
-
```
|
37
|
-
|
37
|
+
```sh
|
38
|
+
bundle add palapala_pdf
|
38
39
|
```
|
39
40
|
|
40
41
|
## Usage Instructions
|
@@ -179,13 +180,29 @@ Here's an example of how to use `render_to_string` to render a view template to
|
|
179
180
|
```ruby
|
180
181
|
def download_pdf
|
181
182
|
html_string = render_to_string(template: "example/template", layout: "print", locals: { } )
|
182
|
-
pdf_data = Palapala::Pdf.new(html_string).binary_data
|
183
|
+
pdf_data = Palapala::Pdf.new(html_string).binary_data(skip_metadata: true)
|
183
184
|
send_data pdf_data, filename: "document.pdf", type: "application/pdf"
|
184
185
|
end
|
185
186
|
```
|
186
187
|
|
187
188
|
In this example, `pdf_data` is the binary data of the PDF file. The `filename` option specifies the name of the file that will be downloaded by the user, and the `type` option specifies the MIME type of the file.
|
188
189
|
|
190
|
+
In the below example we generate a PDF with metadata:
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
def download_pdf
|
194
|
+
html_string = render_to_string(template: "example/template", layout: "print", locals: { } )
|
195
|
+
pdf_data = Palapala::Pdf.new(html_string).binary_data(
|
196
|
+
title: "Hello World from Palapala",
|
197
|
+
author: "Example User",
|
198
|
+
subject: "Sample Document",
|
199
|
+
producer: "Some Company",
|
200
|
+
creator: "PalapalaPDF"
|
201
|
+
)
|
202
|
+
send_data pdf_data, filename: "document.pdf", type: "application/pdf"
|
203
|
+
end
|
204
|
+
```
|
205
|
+
|
189
206
|
## Docker
|
190
207
|
|
191
208
|
TODO
|
@@ -224,27 +241,25 @@ thread safe in the sense that every web socket get's a new tab in the underlying
|
|
224
241
|
|
225
242
|
## Heroku
|
226
243
|
|
227
|
-
TODO
|
228
|
-
|
229
244
|
This buildpack installs chrome and chromedriver (chromedriver is actually not needed, but at least the buildpack is maintained)
|
230
245
|
|
231
246
|
```sh
|
232
|
-
https://
|
247
|
+
https://github.com/heroku/heroku-buildpack-chrome-for-testing
|
233
248
|
```
|
234
249
|
|
235
250
|
### launch as child process
|
236
251
|
|
237
|
-
set `HEADLESS_CHROME_PATH=chrome` as an ENV variable as
|
252
|
+
This is the current default. If HEROKU is detected, then we set `HEADLESS_CHROME_PATH=chrome` as an ENV variable as the above buildpack adds `chrome` to the path. Basically it should run out of the box as long as the buildpack is added.
|
238
253
|
|
239
254
|
### run seperately
|
240
255
|
|
241
|
-
|
256
|
+
If you prefer to run it next to your Rails app, then in your `Procfile` adjust the web worker command to
|
242
257
|
|
243
258
|
```yaml
|
244
259
|
web: bin/start
|
245
260
|
```
|
246
261
|
|
247
|
-
|
262
|
+
And create a bin/start script
|
248
263
|
|
249
264
|
```sh
|
250
265
|
#!/bin/bash
|
@@ -258,7 +273,7 @@ command_to_start_your_background_app &
|
|
258
273
|
wait -n
|
259
274
|
```
|
260
275
|
|
261
|
-
|
276
|
+
Ensure the script is executable
|
262
277
|
|
263
278
|
```sh
|
264
279
|
chmod +x bin/start
|
Binary file
|
Binary file
|
data/examples/paged_css.pdf
CHANGED
Binary file
|
data/examples/paged_css.rb
CHANGED
@@ -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", "--remote-debugging-address=0.0.0.0" ]
|
85
|
+
params = [ "--disable-gpu", "--disable-software-rasterizer", "--disable-bluetooth", "--disable-dev-shm-usage", "--remote-debugging-port=9222", "--remote-debugging-address=0.0.0.0" ]
|
86
86
|
params.concat(Palapala.chrome_params) if Palapala.chrome_params
|
87
87
|
pid = if Palapala.debug
|
88
88
|
spawn(chrome_path, *params)
|
@@ -97,7 +97,7 @@ module Palapala
|
|
97
97
|
end
|
98
98
|
|
99
99
|
def self.spawn_chrome_from_path
|
100
|
-
params = [ "--headless", "--disable-gpu", "--remote-debugging-port=9222" ]
|
100
|
+
params = [ "--headless", "--disable-gpu", "--disable-software-rasterizer", "--disable-bluetooth", "--disable-dev-shm-usage", "--remote-debugging-port=9222", "--remote-debugging-address=0.0.0.0" ]
|
101
101
|
params.concat(Palapala.chrome_params) if Palapala.chrome_params
|
102
102
|
# Spawn an existing chrome with the path and parameters
|
103
103
|
Process.spawn(chrome_path, *params)
|
data/lib/palapala/helper.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module Palapala
|
2
2
|
module Helper
|
3
|
-
def self.header(left: "", center: "", right: "", margin: "1cm")
|
3
|
+
def self.header(left: "", center: "", right: "", margin: "1cm", font_family: "Arial")
|
4
4
|
<<~HTML
|
5
|
-
<div style="display: flex; justify-content: space-between; width: 100%; margin-left: #{margin}; margin-right: #{margin};">
|
5
|
+
<div style="display: flex; justify-content: space-between; width: 100%; margin-left: #{margin}; margin-right: #{margin}; font-family: #{font_family}">
|
6
6
|
<div style="text-align: left; flex: 1;">#{left}</div>
|
7
7
|
<div style="text-align: center; flex: 1;">#{center}</div>
|
8
8
|
<div style="text-align: right; flex: 1;">#{right}</div>
|
@@ -10,13 +10,14 @@ module Palapala
|
|
10
10
|
HTML
|
11
11
|
end
|
12
12
|
|
13
|
-
def self.footer(left: "", center: "", right: "", margin: "1cm")
|
14
|
-
self.header(left:, center:, right:, margin:)
|
13
|
+
def self.footer(left: "", center: "", right: "", margin: "1cm", font_family: "Arial")
|
14
|
+
self.header(left:, center:, right:, margin:, font_family:)
|
15
15
|
end
|
16
16
|
|
17
|
-
def self.page_number
|
17
|
+
def self.page_number(style: nil)
|
18
|
+
style_attr = style.nil? ? "" : "style='#{style}'"
|
18
19
|
<<~HTML
|
19
|
-
<span class="pageNumber"></span>/<span class="totalPages"></span>
|
20
|
+
<span class="pageNumber" #{style_attr}></span>/<span class="totalPages"></span>
|
20
21
|
HTML
|
21
22
|
end
|
22
23
|
|
data/lib/palapala/pdf.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require_relative "./renderer"
|
2
2
|
require_relative "./helper"
|
3
|
+
require "combine_pdf"
|
3
4
|
|
4
5
|
module Palapala
|
5
6
|
# Page class to generate PDF from HTML content using Chrome in headless mode in a thread-safe way
|
@@ -80,21 +81,40 @@ module Palapala
|
|
80
81
|
#
|
81
82
|
# @param opts [Hash] the options to pass to the renderer
|
82
83
|
# @return [String] the PDF content as a binary string
|
83
|
-
def binary_data
|
84
|
+
def binary_data(title: "", author: "", subject: "", producer: "PalapalaPDF", creator: "PalapalaPDF", skip_metadata: false)
|
84
85
|
puts "Rendering PDF with params: #{@opts}" if Palapala.debug
|
85
|
-
Renderer.html_to_pdf(@content, params: @opts)
|
86
|
+
binary_data = Renderer.html_to_pdf(@content, params: @opts)
|
87
|
+
|
88
|
+
if skip_metadata
|
89
|
+
binary_data
|
90
|
+
else
|
91
|
+
# Load the PDF from the string
|
92
|
+
pdf = CombinePDF.parse(binary_data)
|
93
|
+
|
94
|
+
# Set metadata manually
|
95
|
+
info_hash = {
|
96
|
+
Title: title,
|
97
|
+
Author: author,
|
98
|
+
Subject: subject,
|
99
|
+
Producer: producer,
|
100
|
+
Creator: creator
|
101
|
+
}
|
102
|
+
|
103
|
+
# Embed metadata into the PDF
|
104
|
+
pdf.info.merge!(info_hash)
|
105
|
+
pdf.to_pdf
|
106
|
+
end
|
86
107
|
rescue StandardError => e
|
87
108
|
puts "Error rendering PDF: #{e.message}"
|
88
109
|
Renderer.reset
|
89
110
|
raise
|
90
111
|
end
|
91
112
|
|
92
|
-
|
93
113
|
# Save the PDF content to a file
|
94
114
|
# @param path [String] the path to save the PDF file
|
95
115
|
# @return [void]
|
96
|
-
def save(path)
|
97
|
-
File.binwrite(path, binary_data)
|
116
|
+
def save(path, **opts)
|
117
|
+
File.binwrite(path, binary_data(**opts))
|
98
118
|
end
|
99
119
|
end
|
100
120
|
end
|
data/lib/palapala/renderer.rb
CHANGED
@@ -3,6 +3,8 @@ require "net/http"
|
|
3
3
|
require "websocket/driver"
|
4
4
|
require_relative "./web_socket_client"
|
5
5
|
require_relative "./chrome_process"
|
6
|
+
require 'tempfile'
|
7
|
+
require 'webrick'
|
6
8
|
|
7
9
|
module Palapala
|
8
10
|
# Render HTML content to PDF using Chrome in headless mode with minimal dependencies
|
@@ -88,6 +90,8 @@ module Palapala
|
|
88
90
|
# Method to send a CDP command and wait for a specific method to be called
|
89
91
|
def send_command_and_wait_for_event(method, event_name:, params: {})
|
90
92
|
send_command(method, params:) do
|
93
|
+
# chrome refuses to load pages that are bigger than 2MB and returns a net::ERR_ABORTED error
|
94
|
+
raise "Page cannot be loaded" if @response.dig("result", "errorText") == "net::ERR_ABORTED"
|
91
95
|
@response && @response["method"] == event_name
|
92
96
|
end
|
93
97
|
end
|
@@ -97,10 +101,17 @@ module Palapala
|
|
97
101
|
# @param html [String] The HTML content to convert to PDF
|
98
102
|
# @param params [Hash] Additional parameters to pass to the CDP command
|
99
103
|
def html_to_pdf(html, params: {})
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
+
server = start_local_server(html)
|
105
|
+
begin
|
106
|
+
url = "http://localhost:#{server[:port]}/"
|
107
|
+
send_command_and_wait_for_event("Page.navigate", params: { url: url },
|
108
|
+
event_name: "Page.frameStoppedLoading")
|
109
|
+
result = send_command_and_wait_for_result("Page.printToPDF", params:)
|
110
|
+
Base64.decode64(result["data"])
|
111
|
+
ensure
|
112
|
+
server[:thread].kill # Stop the server after use
|
113
|
+
server[:file].unlink # Delete the temporary file
|
114
|
+
end
|
104
115
|
end
|
105
116
|
|
106
117
|
def ping
|
@@ -143,5 +154,24 @@ module Palapala
|
|
143
154
|
def data_url_for_html(html)
|
144
155
|
"data:text/html;base64,#{Base64.strict_encode64(html)}"
|
145
156
|
end
|
157
|
+
|
158
|
+
def start_local_server(html)
|
159
|
+
file = Tempfile.new(["html_content", ".html"])
|
160
|
+
file.write(html)
|
161
|
+
file.close
|
162
|
+
|
163
|
+
port = find_available_port
|
164
|
+
server = WEBrick::HTTPServer.new(Port: port, DocumentRoot: File.dirname(file.path), Logger: WEBrick::Log.new("/dev/null"), AccessLog: [])
|
165
|
+
thread = Thread.new { server.start }
|
166
|
+
|
167
|
+
{ server: server, thread: thread, file: file, port: port }
|
168
|
+
end
|
169
|
+
|
170
|
+
def find_available_port
|
171
|
+
server = TCPServer.new(0)
|
172
|
+
port = server.addr[1]
|
173
|
+
server.close
|
174
|
+
port
|
175
|
+
end
|
146
176
|
end
|
147
177
|
end
|
data/lib/palapala/version.rb
CHANGED
data/palapala_pdf.gemspec
CHANGED
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
|
|
36
36
|
# Uncomment to register a new dependency of your gem
|
37
37
|
spec.add_dependency 'base64', '~> 0'
|
38
38
|
spec.add_dependency 'websocket-driver', '~> 0'
|
39
|
+
spec.add_dependency 'combine_pdf', '~> 1'
|
39
40
|
|
40
41
|
# For more information and examples about making a new gem, check out our
|
41
42
|
# guide at: https://bundler.io/guides/creating_gem.html
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
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.23
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Koen Handekyn
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-04-29 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: base64
|
@@ -38,6 +37,20 @@ dependencies:
|
|
38
37
|
- - "~>"
|
39
38
|
- !ruby/object:Gem::Version
|
40
39
|
version: '0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: combine_pdf
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1'
|
41
54
|
description: This gem uses faw web sockets to render HTML into a PDF using Chrom(e)(ium)
|
42
55
|
with minimal dependencies.
|
43
56
|
email:
|
@@ -84,7 +97,6 @@ metadata:
|
|
84
97
|
homepage_uri: https://github.com/palapala-app/palapala_pdf
|
85
98
|
changelog_uri: https://github.com/palapala-app/palapala_pdf/blob/main/changelog.md
|
86
99
|
rubygems_mfa_required: 'true'
|
87
|
-
post_install_message:
|
88
100
|
rdoc_options: []
|
89
101
|
require_paths:
|
90
102
|
- lib
|
@@ -99,8 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
111
|
- !ruby/object:Gem::Version
|
100
112
|
version: '0'
|
101
113
|
requirements: []
|
102
|
-
rubygems_version: 3.5
|
103
|
-
signing_key:
|
114
|
+
rubygems_version: 3.6.5
|
104
115
|
specification_version: 4
|
105
116
|
summary: Convert HTML into PDF directly from Ruby using Chrome/Chromium.
|
106
117
|
test_files: []
|