ferrum_pdf 2.0.0 → 3.0.0
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/README.md +35 -12
- data/lib/ferrum_pdf/railtie.rb +2 -8
- data/lib/ferrum_pdf/version.rb +1 -1
- data/lib/ferrum_pdf.rb +31 -19
- metadata +2 -5
- data/lib/ferrum_pdf/assets_helper.rb +0 -65
- data/lib/ferrum_pdf/html_preprocessor.rb +0 -33
- data/lib/tasks/ferrum_pdf_tasks.rake +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0c9bdbd12753b4efc5b29bb208908735218fc2614e45eb74e57b6623ca61688
|
4
|
+
data.tar.gz: cb96c86e8c45a34037db93555c9ac5d692f7ccc4d0f37c88e211e6dcb17f7aa1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3e3efc16d9c2601e6ef725d9d1aa6f73d631c8dcfaff8a4aa76a2254e933d7205e33f00a98ac780c7cf0b99fa074e2d820abee42de372fb68966a00fb533faa
|
7
|
+
data.tar.gz: 292b03680170140e5e901f8e84128970cb88a17e17147b57142ad686edca9c31da24c327e8c733505e6188a50310cac7d112a3ce1f92f89ed50b301ec7a0cd9b
|
data/README.md
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
# 📄 FerrumPdf
|
2
2
|
|
3
|
-
PDFs &
|
3
|
+
PDFs & screenshots for Rails using [Ferrum](https://github.com/rubycdp/ferrum) & headless Chrome.
|
4
4
|
|
5
5
|
Inspired by [Grover](https://github.com/Studiosity/grover), but without the Node.js and puppeteer dependencies. 🎉
|
6
6
|
|
7
|
+
<img src="ferrum_pdf.png" alt="logo" style="width:450px;"/>
|
8
|
+
|
7
9
|
## Installation
|
8
10
|
|
9
11
|
First, make sure Chrome is installed. Then run the following or add the gem to your Gemfile:
|
@@ -39,8 +41,8 @@ end
|
|
39
41
|
You can also customize which template is rendered. This will render the template to string with `render_to_string` in Rails, then pass it along to Chrome. For example, you can add headers and footers using `pdf_options` and use a specific layout:
|
40
42
|
|
41
43
|
```ruby
|
42
|
-
|
43
|
-
|
44
|
+
def show
|
45
|
+
render ferrum_pdf: {
|
44
46
|
display_header_footer: true,
|
45
47
|
header_template: FerrumPdf::DEFAULT_HEADER_TEMPLATE,
|
46
48
|
footer_template: FerrumPdf::DEFAULT_FOOTER_TEMPLATE
|
@@ -49,7 +51,7 @@ render ferrum_pdf: {
|
|
49
51
|
template: "pdf",
|
50
52
|
disposition: :inline,
|
51
53
|
filename: "example.pdf"
|
52
|
-
|
54
|
+
end
|
53
55
|
```
|
54
56
|
|
55
57
|
#### Render PDFs
|
@@ -68,10 +70,11 @@ FerrumPdf.render_pdf(
|
|
68
70
|
url: "https://example.com/page", # Provide a URL to the content
|
69
71
|
|
70
72
|
html: content, # or provide HTML
|
71
|
-
|
73
|
+
display_url: request.original_url, # When supplying content via :html its best to give Chrome a hint of the current url so that it can process relative paths in the document. If you don't provide this, http://example.com will be used instead.
|
72
74
|
|
73
75
|
authorize: { user: "username", password: "password" }, # Used for authenticating with basic auth
|
74
76
|
wait_for_idle_options: { connections: 0, duration: 0.05, timeout: 5 }, # Used for setting network wait_for_idle options
|
77
|
+
timeout_if_open_connections: true,
|
75
78
|
|
76
79
|
pdf_options: {
|
77
80
|
landscape: false, # paper orientation
|
@@ -129,8 +132,8 @@ end
|
|
129
132
|
You can also customize which template is rendered. This will render the template to string with `render_to_string` in Rails, then pass it along to Chrome.
|
130
133
|
|
131
134
|
```ruby
|
132
|
-
|
133
|
-
|
135
|
+
def show
|
136
|
+
render ferrum_screenshot: {
|
134
137
|
format: "png" # or "jpeg"
|
135
138
|
quality: nil # Integer 0-100 works for jpeg only
|
136
139
|
full: true # Boolean whether you need full page screenshot or a viewport
|
@@ -140,10 +143,10 @@ render ferrum_screenshot: {
|
|
140
143
|
background_color: nil # Ferrum::RGBA.new(0, 0, 0, 0.0)
|
141
144
|
},
|
142
145
|
layout: "example",
|
143
|
-
template: "example"
|
146
|
+
template: "example",
|
144
147
|
disposition: :inline,
|
145
148
|
filename: "example.png"
|
146
|
-
|
149
|
+
end
|
147
150
|
```
|
148
151
|
|
149
152
|
See [Ferrum screenshot docs](https://github.com/rubycdp/ferrum?tab=readme-ov-file#screenshotoptions--string--integer) for the full set of options.
|
@@ -164,7 +167,7 @@ FerrumPdf.render_screenshot(
|
|
164
167
|
url: "https://example.com/page", # Provide a URL to the content
|
165
168
|
|
166
169
|
html: content, # or provide HTML
|
167
|
-
|
170
|
+
display_url: request.original_url, # When supplying content via :html its best to give Chrome a hint of the current url so that it can process relative paths in the document. If you don't provide this, http://example.com will be used instead.
|
168
171
|
|
169
172
|
screenshot_options: {
|
170
173
|
format: "png" # or "jpeg"
|
@@ -178,7 +181,27 @@ FerrumPdf.render_screenshot(
|
|
178
181
|
)
|
179
182
|
```
|
180
183
|
|
181
|
-
##
|
184
|
+
## Configuration
|
185
|
+
|
186
|
+
You can set default values for page loads, PDF renders, and screenshot renders with the configure block.
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
FerrumPdf.configure do |config|
|
190
|
+
config.page_options.authorize = { user: "username", password: "password" }
|
191
|
+
config.page_options.wait_for_idle_options = { timeout: 90 }
|
192
|
+
config.page_options.retries = 3
|
193
|
+
|
194
|
+
config.pdf_options.margin_top = 0.2
|
195
|
+
config.pdf_options.margin_bottom = 0.2
|
196
|
+
config.pdf_options.margin_left = 0.2
|
197
|
+
config.pdf_options.margin_right = 0.2
|
198
|
+
|
199
|
+
config.screenshot_options.format = :png
|
200
|
+
config.screenshot_options.full = false
|
201
|
+
end
|
202
|
+
```
|
203
|
+
|
204
|
+
### Configuring the Browser
|
182
205
|
|
183
206
|
You can set the default browser options with the configure block.
|
184
207
|
|
@@ -211,7 +234,7 @@ RUN apt-get update && apt-get install gnupg wget -y && \
|
|
211
234
|
rm -rf /var/lib/apt/lists/*
|
212
235
|
```
|
213
236
|
|
214
|
-
|
237
|
+
#### Browser Management
|
215
238
|
|
216
239
|
FerrumPdf uses a single browser instance per Ruby process that is automatically created when needed using your configuration settings:
|
217
240
|
|
data/lib/ferrum_pdf/railtie.rb
CHANGED
@@ -1,11 +1,5 @@
|
|
1
1
|
module FerrumPdf
|
2
2
|
class Railtie < ::Rails::Railtie
|
3
|
-
initializer "ferrum_pdf.assets_helper" do
|
4
|
-
ActiveSupport.on_load(:action_view) do
|
5
|
-
include FerrumPdf::AssetsHelper
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
3
|
initializer "ferrum_pdf.controller" do
|
10
4
|
ActiveSupport.on_load(:action_controller) do
|
11
5
|
# render ferrum_pdf: { pdf options }, template: "whatever", disposition: :inline, filename: "example.pdf"
|
@@ -13,7 +7,7 @@ module FerrumPdf
|
|
13
7
|
send_data_options = options.extract!(:disposition, :filename, :status)
|
14
8
|
url = pdf_options.delete(:url)
|
15
9
|
html = render_to_string(**options.with_defaults(formats: [ :html ])) if url.blank?
|
16
|
-
pdf = FerrumPdf.render_pdf(html: html,
|
10
|
+
pdf = FerrumPdf.render_pdf(html: html, display_url: request.original_url, url: url, pdf_options: pdf_options)
|
17
11
|
send_data(pdf, **send_data_options.with_defaults(type: :pdf))
|
18
12
|
end
|
19
13
|
|
@@ -22,7 +16,7 @@ module FerrumPdf
|
|
22
16
|
send_data_options = options.extract!(:disposition, :filename, :status)
|
23
17
|
url = screenshot_options.delete(:url)
|
24
18
|
html = render_to_string(**options.with_defaults(formats: [ :html ])) if url.blank?
|
25
|
-
screenshot = FerrumPdf.render_screenshot(url: url, html: html,
|
19
|
+
screenshot = FerrumPdf.render_screenshot(url: url, html: html, display_url: request.original_url, screenshot_options: screenshot_options)
|
26
20
|
send_data(screenshot, **send_data_options.with_defaults(type: screenshot_options.fetch(:format, :png)))
|
27
21
|
end
|
28
22
|
end
|
data/lib/ferrum_pdf/version.rb
CHANGED
data/lib/ferrum_pdf.rb
CHANGED
@@ -9,12 +9,12 @@ module FerrumPdf
|
|
9
9
|
<div class='text right'><span class='pageNumber'></span>/<span class='totalPages'></span></div>
|
10
10
|
HTML
|
11
11
|
|
12
|
-
autoload :AssetsHelper, "ferrum_pdf/assets_helper"
|
13
|
-
autoload :HTMLPreprocessor, "ferrum_pdf/html_preprocessor"
|
14
|
-
|
15
12
|
mattr_accessor :browser_mutex, default: Mutex.new
|
16
13
|
mattr_accessor :config, default: ActiveSupport::OrderedOptions.new.merge(
|
17
|
-
window_size: [ 1920, 1080 ]
|
14
|
+
window_size: [ 1920, 1080 ],
|
15
|
+
page_options: ActiveSupport::OrderedOptions.new,
|
16
|
+
pdf_options: ActiveSupport::OrderedOptions.new,
|
17
|
+
screenshot_options: ActiveSupport::OrderedOptions.new
|
18
18
|
)
|
19
19
|
|
20
20
|
# This doesn't use mattr_accessor because having a `.browser` getter and also
|
@@ -42,7 +42,7 @@ module FerrumPdf
|
|
42
42
|
yield browser
|
43
43
|
else
|
44
44
|
browser_mutex.synchronize do
|
45
|
-
@@browser ||= Ferrum::Browser.new(config)
|
45
|
+
@@browser ||= Ferrum::Browser.new(config.except(:page_options, :pdf_options, :screenshot_options))
|
46
46
|
@@browser.restart unless @@browser.client.present?
|
47
47
|
yield @@browser
|
48
48
|
end
|
@@ -54,14 +54,14 @@ module FerrumPdf
|
|
54
54
|
# render_pdf(url: "https://example.org/receipts/example.pdf")
|
55
55
|
# render_pdf(html: "<h1>Hello world</h1>")
|
56
56
|
#
|
57
|
-
# For rendering HTML, we also need
|
57
|
+
# For rendering HTML, we also need display_url for so that Chrome can interpret URLs with relative paths & protocols
|
58
58
|
#
|
59
|
-
# render_pdf(html: "<h1>Hello world</h1>",
|
59
|
+
# render_pdf(html: "<h1>Hello world</h1>", display_url: "https://example.org/hello_world")
|
60
60
|
#
|
61
61
|
def render_pdf(pdf_options: {}, **load_page_args)
|
62
62
|
load_page(**load_page_args) do |browser, page|
|
63
63
|
yield browser, page if block_given?
|
64
|
-
page.pdf(**pdf_options.with_defaults(encoding: :binary))
|
64
|
+
page.pdf(**pdf_options.with_defaults(encoding: :binary, **config.pdf_options))
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
@@ -70,24 +70,25 @@ module FerrumPdf
|
|
70
70
|
# render_screenshot(url: "https://example.org/receipts/example.pdf")
|
71
71
|
# render_screenshot(html: "<h1>Hello world</h1>")
|
72
72
|
#
|
73
|
-
# For rendering HTML, we also need
|
73
|
+
# For rendering HTML, we also need display_url for so that Chrome can interpret URLs with relative paths & protocols
|
74
74
|
#
|
75
|
-
# render_screenshot(html: "<h1>Hello world</h1>",
|
75
|
+
# render_screenshot(html: "<h1>Hello world</h1>", display_url: "https://example.org/hello_world")
|
76
76
|
#
|
77
77
|
def render_screenshot(screenshot_options: {}, **load_page_args)
|
78
78
|
load_page(**load_page_args) do |browser, page|
|
79
79
|
yield browser, page if block_given?
|
80
|
-
page.screenshot(**screenshot_options.with_defaults(encoding: :binary, full: true))
|
80
|
+
page.screenshot(**screenshot_options.with_defaults(encoding: :binary, full: true, **config.screenshot_options))
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
84
|
# Loads page into the browser to be used for rendering PDFs or screenshots
|
85
85
|
#
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
wait_for_idle_options
|
86
|
+
def load_page(url: nil, html: nil, display_url: nil, authorize: nil, wait_for_idle_options: nil, timeout_if_open_connections: true, browser: nil, retries: nil)
|
87
|
+
try ||= 0
|
88
|
+
authorize ||= config.dig(:page_options, :authorize)
|
89
|
+
retries ||= config.page_options.fetch(:retries, 1)
|
90
|
+
wait_for_idle_options = config.page_options.fetch(:wait_for_idle_options, {}).merge(wait_for_idle_options || {})
|
91
|
+
timeout_if_open_connections ||= config.page_options.fetch(:timeout_if_open_connections, true)
|
91
92
|
|
92
93
|
with_browser(browser) do |browser|
|
93
94
|
# Closes page automatically after block finishes
|
@@ -97,18 +98,29 @@ module FerrumPdf
|
|
97
98
|
|
98
99
|
# Load content
|
99
100
|
if html
|
100
|
-
|
101
|
+
html_intercepted = false
|
102
|
+
page.network.intercept
|
103
|
+
page.on(:request) do |request|
|
104
|
+
if html_intercepted
|
105
|
+
request.continue
|
106
|
+
else
|
107
|
+
html_intercepted = true
|
108
|
+
request.respond(body: html.blank? ? " " : html)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
page.go_to(display_url || "http://example.com")
|
101
112
|
else
|
102
113
|
page.go_to(url)
|
103
114
|
end
|
104
115
|
|
105
116
|
# Wait for everything to load
|
106
|
-
page.network.wait_for_idle(**wait_for_idle_options)
|
117
|
+
idle = page.network.wait_for_idle(**wait_for_idle_options)
|
118
|
+
raise Ferrum::TimeoutError if timeout_if_open_connections && !idle
|
107
119
|
|
108
120
|
yield browser, page
|
109
121
|
end
|
110
122
|
end
|
111
|
-
rescue Ferrum::DeadBrowserError
|
123
|
+
rescue Ferrum::DeadBrowserError, Ferrum::TimeoutError
|
112
124
|
try += 1
|
113
125
|
if try <= retries
|
114
126
|
with_browser(&:restart)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ferrum_pdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Oliver
|
@@ -49,11 +49,8 @@ files:
|
|
49
49
|
- README.md
|
50
50
|
- Rakefile
|
51
51
|
- lib/ferrum_pdf.rb
|
52
|
-
- lib/ferrum_pdf/assets_helper.rb
|
53
|
-
- lib/ferrum_pdf/html_preprocessor.rb
|
54
52
|
- lib/ferrum_pdf/railtie.rb
|
55
53
|
- lib/ferrum_pdf/version.rb
|
56
|
-
- lib/tasks/ferrum_pdf_tasks.rake
|
57
54
|
homepage: https://github.com/excid3/ferrum_pdf
|
58
55
|
licenses:
|
59
56
|
- MIT
|
@@ -75,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
72
|
- !ruby/object:Gem::Version
|
76
73
|
version: '0'
|
77
74
|
requirements: []
|
78
|
-
rubygems_version: 3.7.
|
75
|
+
rubygems_version: 3.7.2
|
79
76
|
specification_version: 4
|
80
77
|
summary: PDFs & screenshots for Rails using Ferrum & headless Chrome
|
81
78
|
test_files: []
|
@@ -1,65 +0,0 @@
|
|
1
|
-
module FerrumPdf
|
2
|
-
class BaseAsset
|
3
|
-
def initialize(asset)
|
4
|
-
@asset = asset
|
5
|
-
end
|
6
|
-
end
|
7
|
-
|
8
|
-
class PropshaftAsset < BaseAsset
|
9
|
-
def content_type
|
10
|
-
@asset.content_type.to_s
|
11
|
-
end
|
12
|
-
|
13
|
-
def content
|
14
|
-
@asset.content
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
class SprocketsAsset < BaseAsset
|
19
|
-
def content_type
|
20
|
-
@asset.content_type
|
21
|
-
end
|
22
|
-
|
23
|
-
def content
|
24
|
-
@asset.source
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
class AssetFinder
|
29
|
-
class << self
|
30
|
-
def find(path)
|
31
|
-
if Rails.application.assets.respond_to?(:load_path)
|
32
|
-
propshaft_asset(path)
|
33
|
-
elsif Rails.application.assets.respond_to?(:find_asset)
|
34
|
-
sprockets_asset(path)
|
35
|
-
else
|
36
|
-
nil
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def propshaft_asset(path)
|
41
|
-
(asset = Rails.application.assets.load_path.find(path)) ? PropshaftAsset.new(asset) : nil
|
42
|
-
end
|
43
|
-
|
44
|
-
def sprockets_asset(path)
|
45
|
-
(asset = Rails.application.assets.find_asset(path)) ? SprocketsAsset.new(asset) : nil
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
module AssetsHelper
|
51
|
-
def ferrum_pdf_inline_stylesheet(path)
|
52
|
-
(asset = AssetFinder.find(path)) ? "<style>#{asset.content}</style>".html_safe : nil
|
53
|
-
end
|
54
|
-
|
55
|
-
def ferrum_pdf_inline_javascript(path)
|
56
|
-
(asset = AssetFinder.find(path)) ? "<script>#{asset.content}</script>".html_safe : nil
|
57
|
-
end
|
58
|
-
|
59
|
-
def ferrum_pdf_base64_asset(path)
|
60
|
-
return nil unless (asset = AssetFinder.find(path))
|
61
|
-
|
62
|
-
"data:#{asset.content_type};base64,#{Base64.encode64(asset.content).gsub(/\s+/, '')}"
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
module FerrumPdf
|
2
|
-
# Helper module for preparing HTML for conversion
|
3
|
-
#
|
4
|
-
# Sourced from the PDFKit project
|
5
|
-
# @see https://github.com/pdfkit/pdfkit
|
6
|
-
module HTMLPreprocessor
|
7
|
-
# Change relative paths to absolute, and relative protocols to absolute protocols
|
8
|
-
#
|
9
|
-
# process("Some HTML", "https://example.org")
|
10
|
-
#
|
11
|
-
def self.process(html, base_url)
|
12
|
-
return html if base_url.blank?
|
13
|
-
|
14
|
-
base_url += "/" unless base_url.end_with? "/"
|
15
|
-
protocol = base_url.split("://").first
|
16
|
-
html = translate_relative_paths(html, base_url) if base_url
|
17
|
-
html = translate_relative_protocols(html, protocol) if protocol
|
18
|
-
html
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.translate_relative_paths(html, base_url)
|
22
|
-
# Try out this regexp using rubular http://rubular.com/r/hiAxBNX7KE
|
23
|
-
html.gsub(%r{(href|src)=(['"])/([^/"']([^"']*|[^"']*))?['"]}, "\\1=\\2#{base_url}\\3\\2")
|
24
|
-
end
|
25
|
-
private_class_method :translate_relative_paths
|
26
|
-
|
27
|
-
def self.translate_relative_protocols(body, protocol)
|
28
|
-
# Try out this regexp using rubular http://rubular.com/r/0Ohk0wFYxV
|
29
|
-
body.gsub(%r{(href|src)=(['"])//([^"']*|[^"']*)['"]}, "\\1=\\2#{protocol}://\\3\\2")
|
30
|
-
end
|
31
|
-
private_class_method :translate_relative_protocols
|
32
|
-
end
|
33
|
-
end
|