bidi2pdf-rails 0.0.1.pre.alpha → 0.1.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 +4 -4
- data/.idea/bidi2pdf-rails.iml +68 -84
- data/.rubocop.yml +14 -0
- data/CHANGELOG.md +54 -0
- data/README.md +118 -28
- data/Rakefile +2 -0
- data/cliff.toml +126 -0
- data/lib/bidi2pdf_rails/browser_console_log_subscriber.rb +24 -0
- data/lib/bidi2pdf_rails/chromedriver_manager_singleton.rb +11 -11
- data/lib/bidi2pdf_rails/config.rb +137 -0
- data/lib/bidi2pdf_rails/configurable.rb +106 -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 +13 -45
- data/lib/bidi2pdf_rails/services/html_renderer.rb +33 -0
- data/lib/bidi2pdf_rails/services/html_to_pdf_converter.rb +37 -0
- data/lib/bidi2pdf_rails/services/pdf_browser_session.rb +38 -0
- data/lib/bidi2pdf_rails/services/pdf_injection.rb +31 -0
- data/lib/bidi2pdf_rails/services/pdf_renderer.rb +94 -0
- data/lib/bidi2pdf_rails/services/url_to_pdf_converter.rb +91 -0
- data/lib/bidi2pdf_rails/version.rb +1 -1
- data/lib/bidi2pdf_rails.rb +41 -58
- data/lib/generators/bidi2pdf_rails/USAGE +12 -4
- data/lib/generators/bidi2pdf_rails/initializer_generator.rb +136 -30
- data/lib/generators/bidi2pdf_rails/templates/bidi2pdf_rails.rb.tt +25 -79
- data/spec/acceptance/user_can_download_report_pdf_spec.rb +133 -0
- data/spec/acceptance/user_can_generate_pdf_from_protected_remote_url_spec.rb +173 -0
- data/spec/acceptance/user_can_inject_css_before_pdf_printing_spec.rb +132 -0
- data/spec/acceptance/user_can_inject_js_before_pdf_printing_spec.rb +158 -0
- data/spec/dummy/app/assets/javascripts/simple.js +12 -0
- data/spec/dummy/app/assets/stylesheets/simple.css +3 -0
- data/spec/dummy/app/controllers/reports_controller.rb +47 -0
- data/spec/dummy/app/controllers/secure_controller.rb +52 -0
- data/spec/dummy/app/views/layouts/simple.html.erb +17 -0
- data/spec/dummy/app/views/reports/simple.html.erb +6 -0
- data/spec/dummy/app/views/secure/show.html.erb +10 -0
- data/spec/dummy/config/environments/production.rb +1 -1
- data/spec/dummy/config/initializers/bidi2pdf_rails.rb +72 -54
- data/spec/dummy/config/initializers/cors.rb +1 -1
- data/spec/dummy/config/routes.rb +14 -0
- data/spec/dummy/log/development.log +18331 -158
- data/spec/dummy/log/test.log +87874 -0
- data/spec/dummy/tmp/pids/server.pid +1 -1
- data/spec/integration/generators/bidi2pdf_rails/initializer_generator_spec.rb +64 -0
- data/spec/rails_helper.rb +8 -1
- data/spec/spec_helper.rb +47 -5
- data/spec/support/default_dirs_helper.rb +32 -0
- data/spec/support/pdf_helper.rb +12 -0
- data/spec/support/render_setting_helpers.rb +47 -0
- data/spec/support/request_server_bootstrap.rb +44 -0
- data/spec/{bidi2pdf_rails → unit/bidi2pdf_rails}/bidi2pdf_rails_spec.rb +1 -1
- data/spec/unit/bidi2pdf_rails/configurable/base_nested_config_spec.rb +133 -0
- data/tasks/changelog.rake +29 -0
- data/tasks/coverage.rake +23 -0
- metadata +108 -27
- data/lib/bidi2pdf_rails/log_subscriber.rb +0 -13
- data/spec/dummy/spec/helpers/reports_helper_spec.rb +0 -15
- data/spec/dummy/spec/requests/reports_spec.rb +0 -10
- data/spec/dummy/spec/views/reports/show.html.erb_spec.rb +0 -5
- data/spec/generator/bidie2pdf_rails_initializer_generator_spec.rb +0 -5
- data/spec/generator/initializer_generator_spec.rb +0 -5
- data/spec/requests/reports_spec.rb +0 -17
data/cliff.toml
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
# git-cliff ~ configuration file
|
2
|
+
# https://git-cliff.org/docs/configuration
|
3
|
+
|
4
|
+
[changelog]
|
5
|
+
# template for the changelog header
|
6
|
+
header = """
|
7
|
+
<!-- generated by git-cliff start -->
|
8
|
+
# Changelog\n
|
9
|
+
All notable changes to this project will be documented in this file.
|
10
|
+
|
11
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
12
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n
|
13
|
+
"""
|
14
|
+
# template for the changelog body
|
15
|
+
# https://keats.github.io/tera/docs/#introduction
|
16
|
+
body = """
|
17
|
+
{%- macro remote_url() -%}
|
18
|
+
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
|
19
|
+
{%- endmacro -%}
|
20
|
+
|
21
|
+
{% if version -%}
|
22
|
+
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
23
|
+
{% else -%}
|
24
|
+
## [Unreleased]
|
25
|
+
{% endif -%}
|
26
|
+
|
27
|
+
{% for group, commits in commits | group_by(attribute="group") %}
|
28
|
+
### {{ group | upper_first }}
|
29
|
+
{%- for commit in commits %}
|
30
|
+
- {{ commit.message | split(pat="\n") | first | upper_first | trim }}\
|
31
|
+
{% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif -%}
|
32
|
+
{% if commit.remote.pr_number %} in \
|
33
|
+
[#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) \
|
34
|
+
{%- endif -%}
|
35
|
+
{% endfor %}
|
36
|
+
{% endfor %}
|
37
|
+
|
38
|
+
{%- if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
|
39
|
+
## New Contributors
|
40
|
+
{%- endif -%}
|
41
|
+
|
42
|
+
{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}
|
43
|
+
* @{{ contributor.username }} made their first contribution
|
44
|
+
{%- if contributor.pr_number %} in \
|
45
|
+
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
|
46
|
+
{%- endif %}
|
47
|
+
{%- endfor %}\n
|
48
|
+
"""
|
49
|
+
# template for the changelog footer
|
50
|
+
footer = """
|
51
|
+
{%- macro remote_url() -%}
|
52
|
+
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
|
53
|
+
{%- endmacro -%}
|
54
|
+
|
55
|
+
{% for release in releases -%}
|
56
|
+
{% if release.version -%}
|
57
|
+
{% if release.previous.version -%}
|
58
|
+
[{{ release.version | trim_start_matches(pat="v") }}]: \
|
59
|
+
{{ self::remote_url() }}/compare/{{ release.previous.version }}..{{ release.version }}
|
60
|
+
{% endif -%}
|
61
|
+
{% else -%}
|
62
|
+
[unreleased]: {{ self::remote_url() }}/compare/{{ release.previous.version }}..HEAD
|
63
|
+
{% endif -%}
|
64
|
+
{% endfor %}
|
65
|
+
<!-- generated by git-cliff end -->
|
66
|
+
"""
|
67
|
+
# remove the leading and trailing whitespace from the templates
|
68
|
+
trim = true
|
69
|
+
|
70
|
+
[git]
|
71
|
+
# parse the commits based on https://www.conventionalcommits.org
|
72
|
+
conventional_commits = true
|
73
|
+
# filter out the commits that are not conventional
|
74
|
+
filter_unconventional = false
|
75
|
+
# regex for preprocessing the commit messages
|
76
|
+
commit_preprocessors = [
|
77
|
+
# remove issue numbers from commits
|
78
|
+
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" },
|
79
|
+
]
|
80
|
+
# regex for parsing and grouping commits
|
81
|
+
commit_parsers = [
|
82
|
+
# ✅ Features (additions)
|
83
|
+
{ message = "^feat(?:\\([^)]+\\))?!?:", group = "🚀 Added" },
|
84
|
+
{ message = "^[aA]dd", group = "🚀 Added" },
|
85
|
+
{ message = "^[sS]upport", group = "🚀 Added" },
|
86
|
+
# ❌ Removals
|
87
|
+
{ message = "^[rR]emove", group = "🗑️ Removed" },
|
88
|
+
{ message = "^[dD]elete", group = "🗑️ Removed" },
|
89
|
+
# 🐛 Fixes
|
90
|
+
{ message = "^fix(?:\\([^)]+\\))?!?:", group = "🐛 Fixed" },
|
91
|
+
{ message = "^[tT]est", group = "🐛 Fixed" },
|
92
|
+
{ message = "^[fF]ix", group = "🐛 Fixed" },
|
93
|
+
# 🎨 Refactors
|
94
|
+
{ message = "^refactor(?:\\([^)]+\\))?!?:", group = "🎨 Refactored" },
|
95
|
+
# ⚡️ Performance
|
96
|
+
{ message = "^perf(?:\\([^)]+\\))?!?:", group = "⚡️ Performance" },
|
97
|
+
# 📝 Docs
|
98
|
+
{ message = "^docs(?:\\([^)]+\\))?!?:", group = "📝 Docs" },
|
99
|
+
# 💄 Style (formatting, whitespace, etc.)
|
100
|
+
{ message = "^style(?:\\([^)]+\\))?!?:", group = "💄 Style" },
|
101
|
+
# 🧪 Tests
|
102
|
+
{ message = "^test(?:\\([^)]+\\))?!?:", group = "🧪 Tests" },
|
103
|
+
# 🔧 Build
|
104
|
+
{ message = "^build(?:\\([^)]+\\))?!?:", group = "🔧 Build" },
|
105
|
+
# 🛠️ CI
|
106
|
+
{ message = "^ci(?:\\([^)]+\\))?!?:", skip = true },
|
107
|
+
# 🧹 Chores (skip)
|
108
|
+
{ message = "^chore\\(release\\): prepare for", skip = true },
|
109
|
+
{ message = "^chore\\(deps.*\\)", skip = true },
|
110
|
+
{ message = "^chore\\(pr\\)", skip = true },
|
111
|
+
{ message = "^chore\\(pull\\)", skip = true },
|
112
|
+
{ message = "^chore(?:\\([^)]+\\))?!?:", skip = true },
|
113
|
+
{ message = "^\\s*chore", skip = true },
|
114
|
+
# ⏪ Reverts
|
115
|
+
{ message = "^revert(?:\\([^)]+\\))?!?:", group = "⏪ Reverted" },
|
116
|
+
# 🌀 Catch-all (only if nothing else matched)
|
117
|
+
{ message = "^.*", group = "🔄 Changed" }
|
118
|
+
]
|
119
|
+
|
120
|
+
|
121
|
+
# filter out the commits that are not matched by commit parsers
|
122
|
+
filter_commits = false
|
123
|
+
# sort the tags topologically
|
124
|
+
topo_order = false
|
125
|
+
# sort the commits inside sections by oldest/newest order
|
126
|
+
sort_commits = "newest"
|
@@ -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.config.general_options.verbosity_value)
|
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
|
@@ -5,28 +5,28 @@ module Bidi2pdfRails
|
|
5
5
|
class << self
|
6
6
|
attr_reader :manager, :session
|
7
7
|
|
8
|
-
def initialize_manager
|
9
|
-
return unless running_as_server?
|
8
|
+
def initialize_manager(force: false)
|
9
|
+
return unless running_as_server? || force
|
10
10
|
|
11
11
|
@mutex ||= Mutex.new
|
12
12
|
@mutex.synchronize do
|
13
13
|
return if @manager && @session
|
14
14
|
|
15
|
-
msg = Bidi2pdfRails.
|
15
|
+
msg = Bidi2pdfRails.use_remote_browser? ? "Remote session" : "ChromeDriver manager"
|
16
16
|
|
17
17
|
Bidi2pdfRails.logger.info "Initializing Bidi2pdf #{msg}"
|
18
18
|
|
19
|
-
if Bidi2pdfRails.
|
19
|
+
if Bidi2pdfRails.use_remote_browser?
|
20
20
|
@session = Bidi::Session.new(
|
21
|
-
session_url: Bidi2pdfRails.
|
22
|
-
headless: Bidi2pdfRails.
|
23
|
-
chrome_args: Bidi2pdfRails.
|
21
|
+
session_url: Bidi2pdfRails.config.render_remote_settings.browser_url_value,
|
22
|
+
headless: Bidi2pdfRails.config.general_options.headless_value,
|
23
|
+
chrome_args: Bidi2pdfRails.config.general_options.chrome_session_args_value
|
24
24
|
)
|
25
25
|
else
|
26
26
|
@manager = Bidi2pdf::ChromedriverManager.new(
|
27
|
-
port: Bidi2pdfRails.
|
28
|
-
headless: Bidi2pdfRails.
|
29
|
-
chrome_args: Bidi2pdfRails.
|
27
|
+
port: Bidi2pdfRails.config.chromedriver_settings.port_value,
|
28
|
+
headless: Bidi2pdfRails.config.general_options.headless_value,
|
29
|
+
chrome_args: Bidi2pdfRails.config.general_options.chrome_session_args_value
|
30
30
|
)
|
31
31
|
@manager.start
|
32
32
|
@session = @manager.session
|
@@ -42,7 +42,7 @@ module Bidi2pdfRails
|
|
42
42
|
|
43
43
|
@mutex ||= Mutex.new
|
44
44
|
@mutex.synchronize do
|
45
|
-
msg = Bidi2pdfRails.
|
45
|
+
msg = Bidi2pdfRails.use_remote_browser? ? "Remote session" : "ChromeDriver manager"
|
46
46
|
Bidi2pdfRails.logger.info "Shutting down Bidi2pdf #{msg}"
|
47
47
|
@session&.close
|
48
48
|
@manager&.stop
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "configurable"
|
4
|
+
|
5
|
+
module Bidi2pdfRails
|
6
|
+
class Config
|
7
|
+
include Bidi2pdfRails::Configurable
|
8
|
+
|
9
|
+
CONFIG_OPTIONS = {
|
10
|
+
general_options: {
|
11
|
+
name: "General Options",
|
12
|
+
ask: "Configure general options? (y/n)",
|
13
|
+
options: [
|
14
|
+
{ name: :logger, desc: "The logger", default: -> { Rails.logger || ActiveSupport::TaggedLogging.new(Logger.new(nil)) }, default_as_str: "Rails.logger", ask: false, color: :yellow },
|
15
|
+
{ name: :verbosity, desc: "How verbose to be", default: Bidi2pdf::VerboseLogger::VERBOSITY_LEVELS.keys.sort_by { |k| Bidi2pdf::VerboseLogger::VERBOSITY_LEVELS[k] }.first.to_s, limited_to: Bidi2pdf::VerboseLogger::VERBOSITY_LEVELS.keys.sort_by { |k| Bidi2pdf::VerboseLogger::VERBOSITY_LEVELS[k] }.map(&:to_s), ask: true, color: :yellow },
|
16
|
+
{ name: :headless, desc: "Run Chrome in headless mode", default: -> { !Rails.env.development? }, default_as_str: "!Rails.env.development?", ask: true, color: :green },
|
17
|
+
{ name: :wait_for_network_idle, desc: "Wait for network idle", default: true, ask: true, color: :green },
|
18
|
+
{ name: :wait_for_page_loaded, desc: "Wait for page loaded", default: false, ask: true, color: :green },
|
19
|
+
{ name: :wait_for_page_check_script, desc: "Wait for page check script", default: nil, ask: false },
|
20
|
+
{ name: :notification_service, desc: "Notification service", default: -> { ActiveSupport::Notifications }, default_as_str: "-> { ActiveSupport::Notifications }", ask: false },
|
21
|
+
{ name: :default_timeout, desc: "Default timeout for various Bidi commands", default: 10, ask: true, color: :yellow },
|
22
|
+
{ name: :chrome_session_args, desc: "Chrome session arguments", default: Bidi2pdf::Bidi::Session::DEFAULT_CHROME_ARGS, ask: false }
|
23
|
+
]
|
24
|
+
},
|
25
|
+
|
26
|
+
chromedriver_settings: {
|
27
|
+
name: "Chromedriver Settings (when chromedriver run within your app)",
|
28
|
+
ask: "Configure chromedriver settings? (y/n)",
|
29
|
+
options: [
|
30
|
+
{ name: :install_dir, desc: "Chromedriver install directory", default: nil, ask: false, color: :yellow },
|
31
|
+
{ name: :port, desc: "Chromedriver port", default: 0, ask: true, color: :yellow }
|
32
|
+
]
|
33
|
+
},
|
34
|
+
|
35
|
+
proxy_settings: {
|
36
|
+
name: "Proxy Settings",
|
37
|
+
ask: "Use a proxy server? (y/n)",
|
38
|
+
options: [
|
39
|
+
{ name: :addr, desc: "Proxy address (e.g., 127.0.0.1)", default: nil, ask: true, color: :yellow },
|
40
|
+
{ name: :port, desc: "Proxy port (e.g., 8080)", default: nil, ask: true, color: :yellow },
|
41
|
+
{ name: :user, desc: "Proxy user", default: nil, ask: true, color: :yellow },
|
42
|
+
{
|
43
|
+
name: :pass,
|
44
|
+
desc: "Proxy password",
|
45
|
+
default: -> { -> { Rails.application.credentials.dig("bidi2pdf_rails", "proxy_pass") } },
|
46
|
+
default_as_str: "-> { Rails.application.credentials.dig('bidi2pdf_rails', 'proxy_pass') }",
|
47
|
+
secret: true,
|
48
|
+
ask: true,
|
49
|
+
color: :yellow
|
50
|
+
}
|
51
|
+
]
|
52
|
+
},
|
53
|
+
|
54
|
+
pdf_settings: {
|
55
|
+
name: "PDF Settings",
|
56
|
+
ask: "Configure custom PDF settings? (y/n)",
|
57
|
+
options: [
|
58
|
+
{ name: :orientation, desc: "PDF orientation (portrait/landscape)", default: "portrait", limited_to: %w[portrait landscape], ask: true },
|
59
|
+
{ name: :margins, desc: "Configure PDF margins?", default: false, ask: true, color: :green },
|
60
|
+
{ name: :margin_top, desc: "PDF margin top (cm)", default: 2.5, ask: true, color: :yellow },
|
61
|
+
{ name: :margin_bottom, desc: "PDF margin bottom (cm)", default: 2, ask: true, color: :yellow },
|
62
|
+
{ name: :margin_left, desc: "PDF margin left (cm)", default: 2, ask: true, color: :yellow },
|
63
|
+
{ name: :margin_right, desc: "PDF margin right (cm)", default: 2, ask: true, color: :yellow },
|
64
|
+
{ name: :page_format, desc: "PDF page format (e.g., A4)", default: nil, limited_to: Bidi2pdf::PAPER_FORMATS_CM.keys.map(&:to_s), ask: true },
|
65
|
+
{ name: :page_width, desc: "PDF page width (cm, not needed when format is specified)", default: Bidi2pdf::PAPER_FORMATS_CM[:a4][:width], ask: true, color: :yellow },
|
66
|
+
{ name: :page_height, desc: "PDF page height (cm, not needed when format is specified)", default: Bidi2pdf::PAPER_FORMATS_CM[:a4][:height], ask: true, color: :yellow },
|
67
|
+
{ name: :print_background, desc: "Print background graphics?", default: true, ask: true, color: :green },
|
68
|
+
{ name: :scale, desc: "PDF scale (e.g., 1.0)", default: 1.0, ask: true, color: :yellow },
|
69
|
+
{ name: :shrink_to_fit, desc: "Shrink to fit?", default: false, ask: true, color: :green },
|
70
|
+
{ name: :custom_js, desc: "Raw JavaScript code to inject before PDF generation (without <script> tags)", default: nil, ask: false, color: :yellow },
|
71
|
+
{ name: :custom_css, desc: "Raw CSS styles to inject before PDF generation (without <style> tags)", default: nil, ask: false, color: :yellow },
|
72
|
+
{ name: :custom_js_url, desc: "URL to JavaScript file to load before PDF generation", default: nil, ask: false, color: :yellow },
|
73
|
+
{ name: :custom_css_url, desc: "URL to CSS file to load before PDF generation", default: nil, ask: false, color: :yellow }
|
74
|
+
]
|
75
|
+
},
|
76
|
+
|
77
|
+
render_remote_settings: {
|
78
|
+
name: "Remote URL Settings",
|
79
|
+
ask: false,
|
80
|
+
options: [
|
81
|
+
{ name: :browser_url, desc: "Remote browser URL (e.g. http://localhost:3001/sesion)", default: nil, ask: true, color: :yellow },
|
82
|
+
{ name: :basic_auth_user, desc: "Basic auth user", default: nil },
|
83
|
+
{ name: :basic_auth_pass, desc: "Basic auth password", default: nil, default_as_str: "-> { Rails.application.credentials.dig('bidi2pdf_rails', 'basic_auth_pass') }", secret: true },
|
84
|
+
{ name: :headers, desc: "Headers to be send when allong an url", default: {}, default_as_str: '{"X-API-INFO" => "my info"}' },
|
85
|
+
{ name: :cookies, desc: "Cookies to be send when alling an url", default: {}, default_as_str: '{"session_id" => "my session"}' }
|
86
|
+
]
|
87
|
+
}
|
88
|
+
|
89
|
+
}.freeze
|
90
|
+
|
91
|
+
configure_with(CONFIG_OPTIONS)
|
92
|
+
|
93
|
+
def initialize
|
94
|
+
reset_to_defaults!
|
95
|
+
end
|
96
|
+
|
97
|
+
def pdf_settings_for_bidi_cmd(overrides = {})
|
98
|
+
overrides ||= {}
|
99
|
+
opts = {}
|
100
|
+
|
101
|
+
opts[:background] = pdf_settings.print_background_value
|
102
|
+
opts[:orientation] = pdf_settings.orientation_value
|
103
|
+
opts[:scale] = pdf_settings.scale_value
|
104
|
+
opts[:shrinkToFit] = pdf_settings.shrink_to_fit_value
|
105
|
+
|
106
|
+
# Margins
|
107
|
+
margins = {
|
108
|
+
top: pdf_settings.margin_top_value,
|
109
|
+
bottom: pdf_settings.margin_bottom_value,
|
110
|
+
left: pdf_settings.margin_left_value,
|
111
|
+
right: pdf_settings.margin_right_value
|
112
|
+
}.compact
|
113
|
+
|
114
|
+
opts[:margin] = margins unless margins.empty?
|
115
|
+
|
116
|
+
# Page size
|
117
|
+
page = {
|
118
|
+
width: pdf_settings.page_width_value,
|
119
|
+
height: pdf_settings.page_height_value,
|
120
|
+
format: pdf_settings.page_format_value
|
121
|
+
}.compact
|
122
|
+
|
123
|
+
page = overrides[:page].compact if overrides[:page]&.compact.present?
|
124
|
+
page = { format: page[:format] } if page[:format]
|
125
|
+
|
126
|
+
opts[:page] = page unless page.empty?
|
127
|
+
|
128
|
+
opts.deep_merge(overrides).compact
|
129
|
+
end
|
130
|
+
|
131
|
+
def validate_print_options!
|
132
|
+
validator = Bidi2pdf::Bidi::Commands::PrintParametersValidator.new(pdf_settings_for_bidi_cmd)
|
133
|
+
|
134
|
+
validator.validate!
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Bidi2pdfRails
|
6
|
+
module Configurable
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
included do
|
9
|
+
include Singleton
|
10
|
+
end
|
11
|
+
|
12
|
+
class BaseNestedConfig
|
13
|
+
def initialize(option_defs)
|
14
|
+
@__options = option_defs
|
15
|
+
define_accessors!
|
16
|
+
reset_to_defaults!
|
17
|
+
end
|
18
|
+
|
19
|
+
def define_accessors!
|
20
|
+
@__options.each do |opt|
|
21
|
+
name = opt[:name]
|
22
|
+
unless respond_to?(name)
|
23
|
+
define_singleton_method(name) { instance_variable_get("@#{name}") }
|
24
|
+
define_singleton_method("#{name}=") { |v| instance_variable_set("@#{name}", v) }
|
25
|
+
|
26
|
+
define_singleton_method("#{name}_value") do |*args|
|
27
|
+
value = send("#{name}")
|
28
|
+
value.respond_to?(:call) ? value.call(*args) : value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def reset_to_defaults!
|
35
|
+
@__options.each do |opt|
|
36
|
+
default = opt[:default]
|
37
|
+
value = default
|
38
|
+
send("#{opt[:name]}=", value)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def config_options
|
43
|
+
@__options
|
44
|
+
end
|
45
|
+
|
46
|
+
def configure
|
47
|
+
yield self if block_given?
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_h
|
51
|
+
@__options.to_h do |opt|
|
52
|
+
[opt[:name], send(opt[:name])]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def inspect
|
57
|
+
"#<#{self.class.name || "ConfigSection"} #{to_h.inspect}>"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def configure
|
62
|
+
yield self if block_given?
|
63
|
+
end
|
64
|
+
|
65
|
+
class_methods do
|
66
|
+
def configure_with(grouped_options)
|
67
|
+
@config_groups = grouped_options
|
68
|
+
|
69
|
+
grouped_options.each do |group_key, group_data|
|
70
|
+
group_class = Class.new(BaseNestedConfig)
|
71
|
+
|
72
|
+
define_method(group_key) do
|
73
|
+
ivar = "@#{group_key}"
|
74
|
+
instance_variable_get(ivar) || instance_variable_set(ivar, group_class.new(group_data[:options]))
|
75
|
+
end
|
76
|
+
|
77
|
+
define_method("#{group_key}=") do |val|
|
78
|
+
raise ArgumentError, "Use individual accessors, not direct assignment to config groups"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
define_method(:reset_to_defaults!) do
|
83
|
+
grouped_options.each_key do |group_key|
|
84
|
+
section = send(group_key)
|
85
|
+
section.reset_to_defaults!
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
define_method(:config_groups) do
|
90
|
+
grouped_options
|
91
|
+
end
|
92
|
+
|
93
|
+
define_method(:to_h) do
|
94
|
+
grouped_options.keys.to_h do |group_key|
|
95
|
+
section = send(group_key)
|
96
|
+
[group_key, section.to_h]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
define_method(:inspect) do
|
101
|
+
"#<#{self.class.name} #{to_h.inspect}>"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
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.config.general_options.verbosity_value)
|
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.config.general_options.verbosity_value)
|
14
|
+
formatter = Bidi2pdf::Bidi::NetworkEventFormatters::NetworkEventConsoleFormatter.new(logger: verbose_logger)
|
15
|
+
|
16
|
+
formatter.log [payload[:event]]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,5 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rails/railtie"
|
4
|
+
|
5
|
+
require_relative "services/html_renderer"
|
6
|
+
require_relative "services/pdf_renderer"
|
7
|
+
require_relative "services/pdf_browser_session"
|
8
|
+
require_relative "services/pdf_injection"
|
9
|
+
require_relative "services/url_to_pdf_converter"
|
10
|
+
require_relative "services/html_to_pdf_converter"
|
11
|
+
|
3
12
|
module Bidi2pdfRails
|
4
13
|
class Railtie < ::Rails::Railtie
|
5
14
|
initializer "bidi2pdf_rails.add_mime_type" do
|
@@ -9,7 +18,8 @@ module Bidi2pdfRails
|
|
9
18
|
initializer "bidi2pdf_rails.initialize_chromedriver_manager", after: :load_config_initializers do
|
10
19
|
Bidi2pdfRails.apply_bidi2pdf_config
|
11
20
|
|
12
|
-
|
21
|
+
# defer the initialization of the ChromedriverManager to the ActionController::Base class load event
|
22
|
+
ActiveSupport.on_load(:action_controller_base) do
|
13
23
|
ChromedriverManagerSingleton.initialize_manager
|
14
24
|
end
|
15
25
|
end
|
@@ -23,56 +33,14 @@ module Bidi2pdfRails
|
|
23
33
|
|
24
34
|
initializer "bidi2pdf_rails.add_pdf_renderer" do
|
25
35
|
ActionController::Renderers.add :pdf do |filename, options|
|
26
|
-
pdf_data = ""
|
27
36
|
options = options.dup
|
28
37
|
|
29
|
-
|
30
|
-
|
31
|
-
if options[:asset_host]
|
32
|
-
ActionController::Base.asset_host = options[:asset_host]
|
33
|
-
elsif !Rails.application.config.action_controller.asset_host
|
34
|
-
# Use request base URL as fallback if no asset host configured
|
35
|
-
ActionController::Base.asset_host = request.base_url
|
36
|
-
end
|
37
|
-
|
38
|
-
html = render_to_string(options.merge(formats: [ :html ]))
|
39
|
-
|
40
|
-
Bidi2pdf.default_timeout = 30
|
41
|
-
|
42
|
-
# without a thread, the app will be blocked in dev mode
|
43
|
-
thread = Thread.new {
|
44
|
-
Rails.application.executor.wrap do
|
45
|
-
# subscriptions in other threads will not be inherited
|
46
|
-
Bidi2pdfRails::LogSubscriber.attach_to "bidi2pdf", inherit_all: true
|
47
|
-
|
48
|
-
browser = ChromedriverManagerSingleton.session.browser
|
49
|
-
context = browser.create_user_context
|
50
|
-
window = context.create_browser_window
|
51
|
-
tab = window.create_browser_tab
|
38
|
+
pdf_content = Services::PdfRenderer.new(filename, options, self).render_pdf
|
52
39
|
|
53
|
-
|
54
|
-
|
55
|
-
tab.wait_until_network_idle
|
56
|
-
|
57
|
-
tab.wait_until_page_loaded
|
58
|
-
|
59
|
-
pdf_data = tab.print
|
60
|
-
ensure
|
61
|
-
tab&.close
|
62
|
-
window&.close
|
63
|
-
context&.close
|
64
|
-
end
|
65
|
-
}
|
66
|
-
|
67
|
-
thread.join
|
68
|
-
|
69
|
-
send_data Base64.decode64(pdf_data),
|
40
|
+
send_data pdf_content,
|
70
41
|
type: Mime[:pdf],
|
71
42
|
filename: "#{filename}.pdf",
|
72
43
|
disposition: options.fetch(:disposition, "inline")
|
73
|
-
ensure
|
74
|
-
|
75
|
-
ActionController::Base.asset_host = original_asset_host
|
76
44
|
end
|
77
45
|
end
|
78
46
|
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,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bidi2pdfRails
|
4
|
+
module Services
|
5
|
+
class HtmlToPdfConverter
|
6
|
+
include PdfBrowserSession
|
7
|
+
include PdfInjection
|
8
|
+
|
9
|
+
def initialize(html, print_options: {}, wait_for_network_idle: true, wait_for_page_loaded: true, wait_for_page_check_script: nil, custom_css: nil, custom_css_url: nil, custom_js: nil, custom_js_url: nil)
|
10
|
+
@html = html
|
11
|
+
@print_options = print_options
|
12
|
+
@wait_for_network_idle = wait_for_network_idle
|
13
|
+
@wait_for_page_loaded = wait_for_page_loaded
|
14
|
+
@wait_for_page_check_script = wait_for_page_check_script
|
15
|
+
@custom_css = custom_css
|
16
|
+
@custom_css_url = custom_css_url
|
17
|
+
@custom_js = custom_js
|
18
|
+
@custom_js_url = custom_js_url
|
19
|
+
end
|
20
|
+
|
21
|
+
def generate
|
22
|
+
run_browser_session
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def prepare_tab(tab)
|
28
|
+
tab.render_html_content(@html)
|
29
|
+
|
30
|
+
inject_custom_css(tab, @custom_css, @custom_css_url)
|
31
|
+
inject_custom_js(tab, @custom_js, @custom_js_url)
|
32
|
+
|
33
|
+
wait_for_tab(tab)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|