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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 837d0f0eeca4484a6c8e6ccb8f5113889653d6076eb0a25b2ed13997de2c4656
4
- data.tar.gz: 34561e1c72d7ff0f363055f0a9264e5a8fe2a30f31888e82679fa62a779fd054
3
+ metadata.gz: 12be6d622065fdadf2611578279b723532a6ab68b25a2c5b9790692757058c17
4
+ data.tar.gz: 890152aa05e9ea2823e19ed1a2ec27eade4271951bbb6c948f44bc68d074a09f
5
5
  SHA512:
6
- metadata.gz: 6402a0c71a59806fd91241118a51a993a1439afdde35f2b969ffb23c6df38bc071685801b08490f4789828536b0f1a10c86f6fe5ce48af79e8a7350fe3ff8b21
7
- data.tar.gz: 40f62b4e5c9e14460c92ead8ed7439044a5d69636b670a6cb823349de069cb8e1be855a3cb2480a7dc9ae63d218d2125d2c7708aad856d64e4a06d3b13877e5f
6
+ metadata.gz: 2d42e1f4c2de6eea69f8b42e57340ff191f9e87f77b42df05c3b4dca4acc21fb6d9847f2aea19085522071f0a5b37c5c62a512f5f4ceaee6e29267dc5638c359
7
+ data.tar.gz: c61845987b64ba8de756e05c81a7451da67e7eb59e50dd39e0a3c2088d201560fcd4f1723d8193c8e367c3578a7cfe2a475171c07209cbfb00dc4200d9313653
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.3.4
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 provides functionality for generating PDF files from HTML using the Chrome browser. It allows you to easily convert HTML content into PDF documents, making it convenient for tasks such as generating reports, invoices, or any other printable documents. The gem provides a simple and intuitive API for converting HTML to PDF, and it leverages the power and flexibility of the Chrome browser's rendering engine to ensure accurate and high-quality PDF output. With this gem, you can easily integrate PDF generation capabilities into your Ruby applications.
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
- At the core, this project leverages the Chrome rendering engine, but with significantly reduced overhead and dependencies. Instead of relying on the full Grover/Puppeteer/NodeJS stack, this project uses a raw web socket to enable direct communication from Ruby to a headless Chrome or Chromium browser. This approach ensures efficieny while providing a streamlined alternative for rendering tasks without sacrificing performance or flexibility.
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
- $ bundle add palapala_pdf
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://elements.heroku.com/buildpacks/heroku/heroku-buildpack-google-chrome
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 this buildpacks adds `chrome` to the path.
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
- In your `Procfile` adjust the web worker command
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
- Create a bin/start script
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
- ensure the script is executable
276
+ Ensure the script is executable
262
277
 
263
278
  ```sh
264
279
  chmod +x bin/start
Binary file
Binary file
Binary file
@@ -180,7 +180,7 @@ end
180
180
 
181
181
  Palapala::Pdf.new(document,
182
182
  header:,
183
- footer:).save("paged_css.pdf")
183
+ footer:).save("paged_css.pdf", title: "Paged CSS", author: "Koen Handekyn", subject: "Paged CSS example")
184
184
 
185
185
  puts "Generated paged_css.pdf"
186
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", "--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)
@@ -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
@@ -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
- send_command_and_wait_for_event("Page.navigate", params: { url: data_url_for_html(html) },
101
- event_name: "Page.frameStoppedLoading")
102
- result = send_command_and_wait_for_result("Page.printToPDF", params:)
103
- Base64.decode64(result["data"])
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
@@ -1,3 +1,3 @@
1
1
  module Palapala
2
- VERSION = "0.1.19"
2
+ VERSION = "0.1.23"
3
3
  end
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.19
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: 2024-09-05 00:00:00.000000000 Z
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.17
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: []