bidi2pdf-rails 0.0.1.alpha.1
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 +7 -0
- data/.idea/.gitignore +8 -0
- data/.idea/bidi2pdf-rails.iml +449 -0
- data/.idea/inspectionProfiles/profiles_settings.xml +5 -0
- data/.idea/misc.xml +4 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.rspec +3 -0
- data/.rubocop.yml +93 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/MIT-LICENSE +20 -0
- data/README.md +102 -0
- data/Rakefile +11 -0
- data/lib/bidi2pdf-rails.rb +4 -0
- data/lib/bidi2pdf_rails/browser_console_log_subscriber.rb +24 -0
- data/lib/bidi2pdf_rails/chromedriver_manager_singleton.rb +69 -0
- data/lib/bidi2pdf_rails/main_log_subscriber.rb +33 -0
- data/lib/bidi2pdf_rails/network_log_subscriber.rb +20 -0
- data/lib/bidi2pdf_rails/railtie.rb +44 -0
- data/lib/bidi2pdf_rails/services/html_renderer.rb +33 -0
- data/lib/bidi2pdf_rails/services/html_to_pdf_converter.rb +28 -0
- data/lib/bidi2pdf_rails/services/pdf_browser_session.rb +39 -0
- data/lib/bidi2pdf_rails/services/pdf_renderer.rb +81 -0
- data/lib/bidi2pdf_rails/services/url_to_pdf_converter.rb +82 -0
- data/lib/bidi2pdf_rails/version.rb +3 -0
- data/lib/bidi2pdf_rails.rb +141 -0
- data/lib/generators/bidi2pdf_rails/USAGE +8 -0
- data/lib/generators/bidi2pdf_rails/initializer_generator.rb +74 -0
- data/lib/generators/bidi2pdf_rails/templates/bidi2pdf_rails.rb.tt +121 -0
- data/spec/bidi2pdf_rails/bidi2pdf_rails_spec.rb +9 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/img.jpg +0 -0
- data/spec/dummy/app/assets/javascripts/paged.polyfill-4.3.js +33251 -0
- data/spec/dummy/app/assets/stylesheets/application.css +1 -0
- data/spec/dummy/app/assets/stylesheets/pdf.css +121 -0
- data/spec/dummy/app/assets/stylesheets/reset.css +449 -0
- data/spec/dummy/app/assets/stylesheets/screen.css +281 -0
- data/spec/dummy/app/controllers/application_controller.rb +4 -0
- data/spec/dummy/app/controllers/reports_controller.rb +12 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/helpers/reports_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +67 -0
- data/spec/dummy/app/views/layouts/pdf.html.erb +67 -0
- data/spec/dummy/app/views/pwa/manifest.json.erb +22 -0
- data/spec/dummy/app/views/pwa/service-worker.js +26 -0
- data/spec/dummy/app/views/reports/show.html.erb +153 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config/application.rb +40 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +57 -0
- data/spec/dummy/config/environments/production.rb +73 -0
- data/spec/dummy/config/environments/test.rb +51 -0
- data/spec/dummy/config/initializers/bidi2pdf_rails.rb +68 -0
- data/spec/dummy/config/initializers/content_security_policy.rb +25 -0
- data/spec/dummy/config/initializers/cors.rb +9 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +8 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/permissions_policy.rb +13 -0
- data/spec/dummy/config/locales/en.yml +31 -0
- data/spec/dummy/config/puma.rb +34 -0
- data/spec/dummy/config/routes.rb +18 -0
- data/spec/dummy/config.ru +6 -0
- data/spec/dummy/log/development.log +12781 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/406-unsupported-browser.html +66 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/icon.png +0 -0
- data/spec/dummy/public/icon.svg +3 -0
- data/spec/dummy/spec/helpers/reports_helper_spec.rb +15 -0
- data/spec/dummy/spec/requests/reports_spec.rb +10 -0
- data/spec/dummy/spec/views/reports/show.html.erb_spec.rb +5 -0
- data/spec/dummy/storage/test.sqlite3 +0 -0
- data/spec/dummy/tmp/README.md +69 -0
- data/spec/dummy/tmp/local_secret.txt +1 -0
- data/spec/dummy/tmp/pids/server.pid +1 -0
- data/spec/dummy/tmp/restart.txt +0 -0
- data/spec/generator/bidie2pdf_rails_initializer_generator_spec.rb +5 -0
- data/spec/generator/initializer_generator_spec.rb +5 -0
- data/spec/rails_helper.rb +60 -0
- data/spec/requests/reports_spec.rb +17 -0
- data/spec/spec_helper.rb +94 -0
- data/tasks/bidi2pdf_rails_tasks.rake +4 -0
- metadata +310 -0
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
[](https://github.com/dieter-medium/bidi2pdf-rails/blob/main/.github/workflows/ruby.yml)
|
2
|
+
[](https://codeclimate.com/github/dieter-medium/bidi2pdf-rails/maintainability)
|
3
|
+
[](https://badge.fury.io/rb/bidi2pdf-rails)
|
4
|
+
[](https://codeclimate.com/github/dieter-medium/bidi2pdf-rails/test_coverage)
|
5
|
+
[](https://www.codetriage.com/dieter-medium/bidi2pdf-rails)
|
6
|
+
|
7
|
+
# 📄 Bidi2pdfRails
|
8
|
+
|
9
|
+
**Bidi2pdfRails** is the official Rails integration for [Bidi2pdf](https://github.com/dieter-medium/bidi2pdf) – a
|
10
|
+
modern, browser-based solution for converting HTML to high-quality PDFs.
|
11
|
+
It leverages headless browsing and offers a simple, flexible interface for PDF generation directly from your Rails
|
12
|
+
application.
|
13
|
+
|
14
|
+
> **⚠️ Note:** This project is currently **under development** and **not yet recommended for production use**.
|
15
|
+
|
16
|
+
---
|
17
|
+
|
18
|
+
## 🚀 Why Bidi2pdfRails?
|
19
|
+
|
20
|
+
- Utilizes modern browser technologies for accurate rendering (similar to `grover` or `wicked_pdf`)
|
21
|
+
- Easy to integrate into existing Rails projects
|
22
|
+
- Configurable options: URL, output path, rendering settings
|
23
|
+
|
24
|
+
---
|
25
|
+
|
26
|
+
## 🔧 Installation
|
27
|
+
|
28
|
+
Add the following lines to your `Gemfile`:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
gem "bidi2pdf-rails"
|
32
|
+
# As long as the gem is not published, use:
|
33
|
+
gem "bidi2pdf-rails", github: "dieter-medium/bidi2pdf-rails", branch: "main"
|
34
|
+
gem "bidi2pdf", github: "dieter-medium/bidi2pdf", branch: "main"
|
35
|
+
|
36
|
+
# if you want a small performance boost, you can use the following:
|
37
|
+
# gem "websocket-native"
|
38
|
+
```
|
39
|
+
|
40
|
+
Then install the dependencies:
|
41
|
+
|
42
|
+
```bash
|
43
|
+
bundle
|
44
|
+
```
|
45
|
+
|
46
|
+
Generate the initializer:
|
47
|
+
|
48
|
+
```bash
|
49
|
+
bin/rails generate bidi2pdf_rails:initializer
|
50
|
+
```
|
51
|
+
|
52
|
+
Alternatively, install it manually:
|
53
|
+
|
54
|
+
```bash
|
55
|
+
gem install bidi2pdf-rails
|
56
|
+
```
|
57
|
+
|
58
|
+
---
|
59
|
+
|
60
|
+
## 🧪 Example & Getting Started
|
61
|
+
|
62
|
+
You can find a full example inside the [`spec/dummy`](spec/dummy) directory of this repository.
|
63
|
+
This demonstrates how to use `Bidi2pdfRails` in a realistic mini Rails application setup.
|
64
|
+
|
65
|
+
### Basic Usage
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
# Render html via controller action `render_to_string`
|
69
|
+
# Any controller action:
|
70
|
+
def show
|
71
|
+
render pdf: "invoice",
|
72
|
+
template: "invoices/show",
|
73
|
+
layout: "pdf",
|
74
|
+
locals: { invoice: @invoice },
|
75
|
+
print_options: { landscape: true },
|
76
|
+
wait_for_network_idle: true,
|
77
|
+
asset_host: "https://assets.example.com"
|
78
|
+
end
|
79
|
+
|
80
|
+
# Render pdf via direct url call
|
81
|
+
def show
|
82
|
+
# See: PdfRenderer for all options
|
83
|
+
render pdf: 'remote-report', url: "http://example.com", wait_for_page_loaded: false, print_options: { page: { format: :A4 } }
|
84
|
+
end
|
85
|
+
|
86
|
+
```
|
87
|
+
|
88
|
+
---
|
89
|
+
|
90
|
+
## 🙌 Contributing
|
91
|
+
|
92
|
+
Want to contribute?
|
93
|
+
Pull requests, bug reports, and ideas are warmly welcome!
|
94
|
+
|
95
|
+
*Contribution guidelines will be added soon.*
|
96
|
+
|
97
|
+
---
|
98
|
+
|
99
|
+
## 📄 License
|
100
|
+
|
101
|
+
This gem is open-source and available under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
102
|
+
Free to use – with responsibility.
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bidi2pdf/bidi/browser_console_logger"
|
4
|
+
|
5
|
+
module Bidi2pdfRails
|
6
|
+
class BrowserConsoleLogSubscriber < ActiveSupport::LogSubscriber
|
7
|
+
def browser_console_log_received(event)
|
8
|
+
payload = event.payload
|
9
|
+
timestamp = Bidi2pdf::Bidi::BrowserConsoleLogger.format_timestamp(payload[:timestamp])
|
10
|
+
|
11
|
+
logger.tagged("bidi2pdf_rails", "browser_console", timestamp) do |tagged_logger|
|
12
|
+
verbose_logger = Bidi2pdf::VerboseLogger.new(tagged_logger, Bidi2pdfRails.verbosity)
|
13
|
+
Bidi2pdf::Bidi::BrowserConsoleLogger.new(verbose_logger)
|
14
|
+
.builder
|
15
|
+
.with_level(payload[:level])
|
16
|
+
.with_prefix("")
|
17
|
+
.with_text(payload[:text])
|
18
|
+
.with_args(payload[:args])
|
19
|
+
.with_stack_trace(payload[:stack_trace])
|
20
|
+
.log_event
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bidi2pdfRails
|
4
|
+
module ChromedriverManagerSingleton
|
5
|
+
class << self
|
6
|
+
attr_reader :manager, :session
|
7
|
+
|
8
|
+
def initialize_manager
|
9
|
+
return unless running_as_server?
|
10
|
+
|
11
|
+
@mutex ||= Mutex.new
|
12
|
+
@mutex.synchronize do
|
13
|
+
return if @manager && @session
|
14
|
+
|
15
|
+
msg = Bidi2pdfRails.remote_browser_url ? "Remote session" : "ChromeDriver manager"
|
16
|
+
|
17
|
+
Bidi2pdfRails.logger.info "Initializing Bidi2pdf #{msg}"
|
18
|
+
|
19
|
+
if Bidi2pdfRails.use_remote_browser?
|
20
|
+
@session = Bidi::Session.new(
|
21
|
+
session_url: Bidi2pdfRails.remote_browser_url,
|
22
|
+
headless: Bidi2pdfRails.headless,
|
23
|
+
chrome_args: Bidi2pdfRails.chrome_session_args
|
24
|
+
)
|
25
|
+
else
|
26
|
+
@manager = Bidi2pdf::ChromedriverManager.new(
|
27
|
+
port: Bidi2pdfRails.chromedriver_port,
|
28
|
+
headless: Bidi2pdfRails.headless,
|
29
|
+
chrome_args: Bidi2pdfRails.chrome_session_args
|
30
|
+
)
|
31
|
+
@manager.start
|
32
|
+
@session = @manager.session
|
33
|
+
end
|
34
|
+
|
35
|
+
@session.start
|
36
|
+
@session.client.on_close { Bidi2pdfRails.logger.info "WebSocket session closed" }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def shutdown
|
41
|
+
return unless running_as_server?
|
42
|
+
|
43
|
+
@mutex ||= Mutex.new
|
44
|
+
@mutex.synchronize do
|
45
|
+
msg = Bidi2pdfRails.remote_browser_url ? "Remote session" : "ChromeDriver manager"
|
46
|
+
Bidi2pdfRails.logger.info "Shutting down Bidi2pdf #{msg}"
|
47
|
+
@session&.close
|
48
|
+
@manager&.stop
|
49
|
+
@session = nil
|
50
|
+
@manager = nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def running_as_server?
|
55
|
+
return false if Rails.const_defined?(:Console)
|
56
|
+
return false if defined?(Rails::Generators)
|
57
|
+
|
58
|
+
return false if File.basename($0) == "rake"
|
59
|
+
|
60
|
+
# Covers common Rails server entrypoints
|
61
|
+
server_commands = %w[server puma unicorn passenger thin webrick rackup]
|
62
|
+
cmdline = File.basename($0)
|
63
|
+
|
64
|
+
server_commands.any? { |s| cmdline.include?(s) } ||
|
65
|
+
Rails.const_defined?("Server")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bidi2pdfRails
|
4
|
+
class MainLogSubscriber < ActiveSupport::LogSubscriber
|
5
|
+
@silenced_events = []
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :silenced_events
|
9
|
+
|
10
|
+
def silence(event)
|
11
|
+
self.silenced_events << event
|
12
|
+
end
|
13
|
+
|
14
|
+
def silenced?(event)
|
15
|
+
self.silenced_events.any? { |silenced_event| silenced_event === event }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
include Bidi2pdf::Notifications::LoggingSubscriberActions
|
20
|
+
|
21
|
+
def handle_printing(event)
|
22
|
+
logger.info "Page rendered and printed: #{event.duration.round(1)}ms"
|
23
|
+
end
|
24
|
+
|
25
|
+
def logger
|
26
|
+
Bidi2pdf::VerboseLogger.new(super.tagged("bidi2pdf_rails"), Bidi2pdfRails.verbosity)
|
27
|
+
end
|
28
|
+
|
29
|
+
def silenced?(event)
|
30
|
+
MainLogSubscriber.silenced?(event) || super(event)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bidi2pdf/bidi/network_event_formatters/network_event_console_formatter"
|
4
|
+
|
5
|
+
module Bidi2pdfRails
|
6
|
+
class NetworkLogSubscriber < ActiveSupport::LogSubscriber
|
7
|
+
def network_event_received(event)
|
8
|
+
payload = event.payload
|
9
|
+
|
10
|
+
return if payload[:method] == "network.responseStarted" || payload[:method] == "network.beforeRequestSent"
|
11
|
+
|
12
|
+
logger.tagged("bidi2pdf_rails", "network") do |tagged_logger|
|
13
|
+
verbose_logger = Bidi2pdf::VerboseLogger.new(tagged_logger, Bidi2pdfRails.verbosity)
|
14
|
+
formatter = Bidi2pdf::Bidi::NetworkEventFormatters::NetworkEventConsoleFormatter.new(logger: verbose_logger)
|
15
|
+
|
16
|
+
formatter.log [payload[:event]]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "services/html_renderer"
|
4
|
+
require_relative "services/pdf_renderer"
|
5
|
+
require_relative "services/pdf_browser_session"
|
6
|
+
require_relative "services/url_to_pdf_converter"
|
7
|
+
require_relative "services/html_to_pdf_converter"
|
8
|
+
|
9
|
+
module Bidi2pdfRails
|
10
|
+
class Railtie < ::Rails::Railtie
|
11
|
+
initializer "bidi2pdf_rails.add_mime_type" do
|
12
|
+
Mime::Type.register "application/pdf", :pdf unless Mime::Type.lookup_by_extension(:pdf)
|
13
|
+
end
|
14
|
+
|
15
|
+
initializer "bidi2pdf_rails.initialize_chromedriver_manager", after: :load_config_initializers do
|
16
|
+
Bidi2pdfRails.apply_bidi2pdf_config
|
17
|
+
|
18
|
+
# defer the initialization of the ChromedriverManager to the ActionController::Base class load event
|
19
|
+
ActiveSupport.on_load(:action_controller_base) do
|
20
|
+
ChromedriverManagerSingleton.initialize_manager
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
config.after_initialize do
|
25
|
+
# Set up shutdown hook for when the application stops
|
26
|
+
at_exit do
|
27
|
+
ChromedriverManagerSingleton.shutdown
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
initializer "bidi2pdf_rails.add_pdf_renderer" do
|
32
|
+
ActionController::Renderers.add :pdf do |filename, options|
|
33
|
+
options = options.dup
|
34
|
+
|
35
|
+
pdf_content = Services::PdfRenderer.new(filename, options, self).render_pdf
|
36
|
+
|
37
|
+
send_data pdf_content,
|
38
|
+
type: Mime[:pdf],
|
39
|
+
filename: "#{filename}.pdf",
|
40
|
+
disposition: options.fetch(:disposition, "inline")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bidi2pdfRails
|
4
|
+
module Services
|
5
|
+
# app/services/html_renderer.rb
|
6
|
+
class HtmlRenderer
|
7
|
+
def initialize(controller, options)
|
8
|
+
@controller = controller
|
9
|
+
@options = options.dup
|
10
|
+
end
|
11
|
+
|
12
|
+
def render
|
13
|
+
with_overridden_asset_host { @controller.render_to_string(@options.merge(formats: [:html])) }
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def with_overridden_asset_host
|
19
|
+
original_asset_host = ActionController::Base.asset_host
|
20
|
+
|
21
|
+
if @options[:asset_host]
|
22
|
+
ActionController::Base.asset_host = @options[:asset_host]
|
23
|
+
elsif !Rails.application.config.action_controller.asset_host
|
24
|
+
ActionController::Base.asset_host = @controller.request.base_url
|
25
|
+
end
|
26
|
+
|
27
|
+
yield
|
28
|
+
ensure
|
29
|
+
ActionController::Base.asset_host = original_asset_host
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bidi2pdfRails
|
4
|
+
module Services
|
5
|
+
class HtmlToPdfConverter
|
6
|
+
include PdfBrowserSession
|
7
|
+
|
8
|
+
def initialize(html, print_options: {}, wait_for_network_idle: true, wait_for_page_loaded: true, wait_for_page_check_script: nil)
|
9
|
+
@html = html
|
10
|
+
@print_options = print_options
|
11
|
+
@wait_for_network_idle = wait_for_network_idle
|
12
|
+
@wait_for_page_loaded = wait_for_page_loaded
|
13
|
+
@wait_for_page_check_script = wait_for_page_check_script
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate
|
17
|
+
run_browser_session
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def prepare_tab(tab)
|
23
|
+
tab.render_html_content(@html)
|
24
|
+
wait_for_tab(tab)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bidi2pdfRails
|
4
|
+
module Services
|
5
|
+
module PdfBrowserSession
|
6
|
+
def run_browser_session
|
7
|
+
pdf_data = nil
|
8
|
+
|
9
|
+
thread = Thread.new do
|
10
|
+
Rails.application.executor.wrap do
|
11
|
+
browser = ChromedriverManagerSingleton.session.browser
|
12
|
+
context = browser.create_user_context
|
13
|
+
window = context.create_browser_window
|
14
|
+
tab = window.create_browser_tab
|
15
|
+
|
16
|
+
begin
|
17
|
+
prepare_tab(tab)
|
18
|
+
pdf_data = tab.print(print_options: @print_options)
|
19
|
+
ensure
|
20
|
+
tab&.close
|
21
|
+
window&.close
|
22
|
+
context&.close
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
thread.join
|
28
|
+
Base64.decode64(pdf_data)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def wait_for_tab(tab)
|
34
|
+
tab.wait_until_network_idle if @wait_for_network_idle
|
35
|
+
tab.wait_until_page_loaded(check_script: @wait_for_page_check_script) if @wait_for_page_loaded
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bidi2pdfRails
|
4
|
+
module Services
|
5
|
+
# PdfRenderer is responsible for rendering a Rails view into a PDF using headless Chrome.
|
6
|
+
#
|
7
|
+
# It accepts a mix of HTML rendering options (passed to `render_to_string`) and PDF generation options.
|
8
|
+
#
|
9
|
+
# @example Basic usage
|
10
|
+
# renderer = Bidi2pdfRails::Services::PdfRenderer.new("invoice", { template: "invoices/show" }, controller)
|
11
|
+
# renderer.render_pdf
|
12
|
+
#
|
13
|
+
# @param filename [String] the base filename for the generated PDF (used for download naming)
|
14
|
+
# @param options [Hash] rendering options including both HTML and PDF-specific options
|
15
|
+
# @option options [String] :template The template to render (or other render_to_string keys like :partial, :layout, etc.)
|
16
|
+
# @option options [String] :asset_host Optional asset host override
|
17
|
+
# @option options [Hash] :print_options Options passed to the Chrome PDF print API
|
18
|
+
# @option options [Boolean] :wait_for_network_idle Wait for network to go idle before generating PDF (default: config default)
|
19
|
+
# @option options [Boolean] :wait_for_page_loaded Wait for page load event before generating PDF (default: config default)
|
20
|
+
# @option options [String] :wait_for_page_check_script JavaScript condition to wait for before generating PDF
|
21
|
+
#
|
22
|
+
class PdfRenderer
|
23
|
+
PDF_OPTIONS = %i[
|
24
|
+
print_options
|
25
|
+
asset_host
|
26
|
+
wait_for_network_idle
|
27
|
+
wait_for_page_loaded
|
28
|
+
wait_for_page_check_script
|
29
|
+
].freeze
|
30
|
+
|
31
|
+
PRINT_URL_OPTIONS = %i[
|
32
|
+
url
|
33
|
+
auth
|
34
|
+
headers
|
35
|
+
cookies
|
36
|
+
].freeze
|
37
|
+
|
38
|
+
def initialize(filename, options, controller)
|
39
|
+
@filename = filename
|
40
|
+
@pdf_options = options.slice(*PDF_OPTIONS)
|
41
|
+
@print_url_options = options.slice(*PRINT_URL_OPTIONS)
|
42
|
+
@html_options = options.except(*(PDF_OPTIONS + PRINT_URL_OPTIONS))
|
43
|
+
@controller = controller
|
44
|
+
end
|
45
|
+
|
46
|
+
def render_pdf
|
47
|
+
ActiveSupport::Notifications.instrument("handle_printing.bidi2pdf_rails") do
|
48
|
+
print_options = Bidi2pdfRails.print_options(@pdf_options[:print_options] || {})
|
49
|
+
wait_for_network_idle = @pdf_options.fetch(:wait_for_network_idle, Bidi2pdfRails.wait_for_network_idle)
|
50
|
+
wait_for_page_loaded = @pdf_options.fetch(:wait_for_page_loaded, Bidi2pdfRails.wait_for_page_loaded)
|
51
|
+
wait_for_page_check_script = @pdf_options.fetch(:wait_for_page_check_script, Bidi2pdfRails.wait_for_page_check_script)
|
52
|
+
|
53
|
+
if @print_url_options[:url]
|
54
|
+
headers = @print_url_options[:headers] || Bidi2pdfRails.headers
|
55
|
+
cookies = @print_url_options[:cookies] || Bidi2pdfRails.cookies
|
56
|
+
auth = @print_url_options[:auth] || Bidi2pdfRails.auth
|
57
|
+
|
58
|
+
UrlToPdfConverter.new(@print_url_options[:url],
|
59
|
+
headers: headers,
|
60
|
+
cookies: cookies,
|
61
|
+
auth: auth,
|
62
|
+
print_options: print_options,
|
63
|
+
wait_for_network_idle: wait_for_network_idle,
|
64
|
+
wait_for_page_loaded: wait_for_page_loaded,
|
65
|
+
wait_for_page_check_script: wait_for_page_check_script
|
66
|
+
).generate
|
67
|
+
else
|
68
|
+
html = HtmlRenderer.new(@controller, @html_options).render
|
69
|
+
|
70
|
+
HtmlToPdfConverter.new(html,
|
71
|
+
print_options: print_options,
|
72
|
+
wait_for_network_idle: wait_for_network_idle,
|
73
|
+
wait_for_page_loaded: wait_for_page_loaded,
|
74
|
+
wait_for_page_check_script: wait_for_page_check_script
|
75
|
+
).generate
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bidi2pdfRails
|
4
|
+
module Services
|
5
|
+
class UrlToPdfConverter
|
6
|
+
include PdfBrowserSession
|
7
|
+
|
8
|
+
def initialize(url, headers: {}, cookies: {}, auth: {}, print_options: {}, wait_for_network_idle: true, wait_for_page_loaded: true, wait_for_page_check_script: nil)
|
9
|
+
@url = url
|
10
|
+
@headers = headers || {}
|
11
|
+
@cookies = cookies || {}
|
12
|
+
@auth = auth || {}
|
13
|
+
@print_options = print_options
|
14
|
+
@wait_for_network_idle = wait_for_network_idle
|
15
|
+
@wait_for_page_loaded = wait_for_page_loaded
|
16
|
+
@wait_for_page_check_script = wait_for_page_check_script
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate
|
20
|
+
run_browser_session
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def prepare_tab(tab)
|
26
|
+
add_headers(tab)
|
27
|
+
add_basic_auth(tab)
|
28
|
+
add_cookies(tab)
|
29
|
+
|
30
|
+
tab.navigate_to(@url)
|
31
|
+
wait_for_tab(tab)
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_cookies(tab)
|
35
|
+
@cookies.each do |name, value|
|
36
|
+
tab.set_cookie(
|
37
|
+
name: name,
|
38
|
+
value: value,
|
39
|
+
domain: domain,
|
40
|
+
secure: uri.scheme == "https"
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_headers(tab)
|
46
|
+
@headers.each do |name, value|
|
47
|
+
tab.add_headers(
|
48
|
+
url_patterns: url_patterns,
|
49
|
+
headers: [{ name: name, value: value }]
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_basic_auth(tab)
|
55
|
+
return unless @auth[:username] && @auth[:password]
|
56
|
+
|
57
|
+
tab.basic_auth(
|
58
|
+
url_patterns: url_patterns,
|
59
|
+
username: @auth[:username],
|
60
|
+
password: @auth[:password]
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def uri
|
65
|
+
@uri ||= URI(@url)
|
66
|
+
end
|
67
|
+
|
68
|
+
def domain
|
69
|
+
uri.host
|
70
|
+
end
|
71
|
+
|
72
|
+
def url_patterns
|
73
|
+
[{
|
74
|
+
type: "pattern",
|
75
|
+
protocol: uri.scheme,
|
76
|
+
hostname: uri.host,
|
77
|
+
port: uri.port.to_s
|
78
|
+
}]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|