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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 230cc525cd5e4bbc4d2ce9ddb0a418486bc27ad6163cef9834a67d7e23442b41
4
- data.tar.gz: b283d90551ef07efe3384148b061ec0a6a18cb3bdbc9485c1e8717e6bc4979f3
3
+ metadata.gz: 3a5231c5a9994723db5fafc42682644a6320a69813444bc0939e179225e7181b
4
+ data.tar.gz: 5f3600f8fb867ee059e1a67fcda2eeeff0d7f290c8621d8bc055e6fb4cbda39c
5
5
  SHA512:
6
- metadata.gz: cfcf738f7171f679d419349cce4ce0441bb3be54fc355fa9592222b04c63654d6646c6103310f8ddf56e7112a3ccc95b30ddb92db86e0ff48a12b221f8be039c
7
- data.tar.gz: efa8277743d960b0d3e869ab970c8248a2f901b572d4451bf0c479c311eabbd2a810e678efad489913ced8ab2ada14e8ce7d4b4fbb7d3ccad61ce98f419ee1b2
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
- CHROME_HEADLESS_PATH=/var/to/chrome ruby examples/performance_benchmark.rb
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
- *It has also been reported that the Chrome process repeatedly crashes when running inside a Docker container on an M1 Mac. Chrome should work as expected when deployed to a Docker container on a non-M1 Mac.*
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
- possible buildpacks
229
+ This buildpack installs chrome and chromedriver (chromedriver is actually not needed, but at least the buildpack is maintained)
205
230
 
206
- https://github.com/heroku/heroku-buildpack-chrome-for-testing
231
+ ```sh
232
+ https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-google-chrome
233
+ ```
207
234
 
208
- this buildpack install chrome and chromedriver, which is actually not needed, but it's maintained
235
+ ### launch as child process
209
236
 
210
- https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-google-chrome
237
+ set `HEADLESS_CHROME_PATH=chrome` as an ENV variable as this buildpacks adds `chrome` to the path.
211
238
 
212
- this buildpack installs chrome, which is all we need, but it's deprecated
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
- header_template =
7
- '<div style="text-align: center; font-size: 12pt; padding: 1rem; width: 100%;">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>'
6
+ header =
7
+ '<div class="center">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>'
8
8
 
9
- footer_template =
10
- '<div style="text-align: center; font-size: 12pt; padding: 1rem; width: 100%;">Generated with Palapala PDF</div>'
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&nbsp;<span class="date"></span></span>'
11
19
 
12
20
  Palapala::Pdf.new(
13
21
  "<h1>Title</h1><p>Hello world #{Time.now}</>",
14
- header_template:,
15
- footer_template:,
16
- margin_top: 3,
17
- margin_bottom: 3).save('headers_and_footers.pdf')
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
Binary file
@@ -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="text-align: center; font-size: 12pt; padding: 1rem; width: 100%;">#{yield}</div>
169
+ <div style="font-size: 12pt;">#{yield}</div>
169
170
  HTML
170
171
  end
171
172
 
172
- footer_template = header_footer_template do
173
+ footer = header_footer_template do
173
174
  "Page <span class='pageNumber'></span> of <span class='totalPages'></span>"
174
175
  end
175
176
 
176
- header_template = header_footer_template do
177
+ header = header_footer_template do
177
178
  "Generated with Palapala PDF"
178
179
  end
179
180
 
180
181
  Palapala::Pdf.new(document,
181
- header_template:,
182
- footer_template:).save("paged_css.pdf")
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
- @opts[:headerTemplate] = header_template || Palapala.defaults[:header_template]
46
- @opts[:footerTemplate] = footer_template || Palapala.defaults[:footer_template]
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.
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Palapala
2
- VERSION = "0.1.11"
2
+ VERSION = "0.1.13"
3
3
  end
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.11
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-08-30 00:00:00.000000000 Z
11
+ date: 2024-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64