adzap-wicked_pdf 2.0.0.beta5 → 2.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/.rubocop.yml +7 -2
- data/.rubocop_todo.yml +2 -2
- data/lib/generators/wicked_pdf_generator.rb +1 -1
- data/lib/wicked_pdf/asset_helper.rb +4 -3
- data/lib/wicked_pdf/binary.rb +19 -3
- data/lib/wicked_pdf/command.rb +150 -10
- data/lib/wicked_pdf/configuration.rb +60 -0
- data/lib/wicked_pdf/document.rb +37 -6
- data/lib/wicked_pdf/middleware.rb +4 -4
- data/lib/wicked_pdf/option_parser.rb +9 -7
- data/lib/wicked_pdf/pdf_helper.rb +1 -1
- data/lib/wicked_pdf/progress.rb +8 -10
- data/lib/wicked_pdf/railtie.rb +4 -6
- data/lib/wicked_pdf/renderer.rb +30 -4
- data/lib/wicked_pdf/version.rb +1 -1
- data/lib/wicked_pdf.rb +21 -1
- data/test/dummy/app/assets/config/manifest.js +3 -0
- data/test/functional/wicked_pdf_asset_helper_test.rb +7 -3
- data/test/test_helper.rb +3 -4
- data/test/unit/wicked_pdf_binary_test.rb +58 -10
- data/test/unit/wicked_pdf_command_test.rb +236 -0
- data/test/unit/wicked_pdf_configuration_test.rb +70 -0
- data/test/unit/wicked_pdf_document_test.rb +105 -12
- data/test/unit/wicked_pdf_renderer_test.rb +62 -0
- data/wicked_pdf.gemspec +7 -8
- metadata +38 -56
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df55b441686f92b7087e24ccc0311fdb2c2653a6a07ee8a661f5168ba403462c
|
|
4
|
+
data.tar.gz: c9016439d082b975e932d1912f84434375897d06a17905065b5cbbf18e1f2354
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7a7568160aadb93576e342cb0185b44854658029c3f46a4fe50c5a3b69b9ef36d06692679760353f535981c2f603637af705e458cc887dce331506763d341b59
|
|
7
|
+
data.tar.gz: 739be6b379f0834f488c6db9aad4ecc5561838a981b11f5589f54ad6d1d4c7b5b202373518cdce66c68f69a78e6d9b40e5f153e31f94e68d22c7d9d8301551d5
|
data/.rubocop.yml
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
inherit_from: .rubocop_todo.yml
|
|
2
2
|
|
|
3
3
|
AllCops:
|
|
4
|
-
TargetRubyVersion:
|
|
4
|
+
TargetRubyVersion: 3.4
|
|
5
|
+
NewCops: disable
|
|
6
|
+
SuggestExtensions: false
|
|
5
7
|
Exclude:
|
|
6
8
|
- 'test/dummy/**/*'
|
|
9
|
+
- 'spike/**/*'
|
|
10
|
+
- 'gemfiles/**/*'
|
|
11
|
+
- 'vendor/**/*'
|
|
7
12
|
|
|
8
13
|
Metrics/BlockLength:
|
|
9
14
|
Exclude:
|
|
@@ -19,4 +24,4 @@ Style/SymbolArray:
|
|
|
19
24
|
EnforcedStyle: brackets
|
|
20
25
|
|
|
21
26
|
Style/SafeNavigation:
|
|
22
|
-
Enabled: false
|
|
27
|
+
Enabled: false
|
data/.rubocop_todo.yml
CHANGED
|
@@ -28,7 +28,7 @@ Metrics/CyclomaticComplexity:
|
|
|
28
28
|
|
|
29
29
|
# Offense count: 92
|
|
30
30
|
# Configuration parameters: AllowURI, URISchemes.
|
|
31
|
-
|
|
31
|
+
Layout/LineLength:
|
|
32
32
|
Max: 563
|
|
33
33
|
|
|
34
34
|
# Offense count: 14
|
|
@@ -38,7 +38,7 @@ Metrics/MethodLength:
|
|
|
38
38
|
|
|
39
39
|
# Offense count: 4
|
|
40
40
|
Metrics/PerceivedComplexity:
|
|
41
|
-
Max:
|
|
41
|
+
Max: 14
|
|
42
42
|
|
|
43
43
|
# Offense count: 2
|
|
44
44
|
Naming/AccessorMethodName:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class WickedPdfGenerator < Rails::Generators::Base
|
|
2
|
-
source_root(File.expand_path(File.dirname(__FILE__)
|
|
2
|
+
source_root(File.expand_path("#{File.dirname(__FILE__)}/../../generators/wicked_pdf/templates"))
|
|
3
3
|
def copy_initializer
|
|
4
4
|
copy_file 'wicked_pdf.rb', 'config/initializers/wicked_pdf.rb'
|
|
5
5
|
end
|
|
@@ -15,6 +15,7 @@ module WickedPdf
|
|
|
15
15
|
def wicked_pdf_asset_base64(path)
|
|
16
16
|
asset = find_asset(path)
|
|
17
17
|
raise "Could not find asset '#{path}'" if asset.nil?
|
|
18
|
+
|
|
18
19
|
base64 = Base64.encode64(asset.to_s).gsub(/\s+/, '')
|
|
19
20
|
"data:#{asset.content_type};base64,#{Rack::Utils.escape(base64)}"
|
|
20
21
|
end
|
|
@@ -95,7 +96,7 @@ module WickedPdf
|
|
|
95
96
|
# will prepend a http or default_protocol to a protocol relative URL
|
|
96
97
|
# or when no protcol is set.
|
|
97
98
|
def prepend_protocol(source)
|
|
98
|
-
protocol = WickedPdf.config
|
|
99
|
+
protocol = WickedPdf.config.default_protocol || 'http'
|
|
99
100
|
if source[0, 2] == '//'
|
|
100
101
|
source = [protocol, ':', source].join
|
|
101
102
|
elsif source[0] != '/' && !source[0, 8].include?('://')
|
|
@@ -107,7 +108,7 @@ module WickedPdf
|
|
|
107
108
|
def precompiled_or_absolute_asset?(source)
|
|
108
109
|
Rails.configuration.assets.compile == false ||
|
|
109
110
|
source.to_s[0] == '/' ||
|
|
110
|
-
source.to_s.match(/\Ahttps
|
|
111
|
+
source.to_s.match(/\Ahttps?:\/\//)
|
|
111
112
|
end
|
|
112
113
|
|
|
113
114
|
def read_asset(source)
|
|
@@ -125,7 +126,7 @@ module WickedPdf
|
|
|
125
126
|
|
|
126
127
|
def read_from_uri(uri)
|
|
127
128
|
asset = Net::HTTP.get(URI(uri))
|
|
128
|
-
asset = gzip(asset) if WickedPdf.config
|
|
129
|
+
asset = gzip(asset) if WickedPdf.config.expect_gzipped_remote_assets
|
|
129
130
|
asset
|
|
130
131
|
end
|
|
131
132
|
|
data/lib/wicked_pdf/binary.rb
CHANGED
|
@@ -5,6 +5,21 @@ module WickedPdf
|
|
|
5
5
|
|
|
6
6
|
attr_reader :path, :version
|
|
7
7
|
|
|
8
|
+
# Shared instance, so the version check (`wkhtmltopdf -V` subprocess) runs
|
|
9
|
+
# once instead of once per render. Re-resolved whenever config.exe_path
|
|
10
|
+
# changes — including back to nil (automatic path detection).
|
|
11
|
+
def self.default
|
|
12
|
+
configured_path = WickedPdf.config.exe_path
|
|
13
|
+
@default = nil if @default && @default_configured_path != configured_path
|
|
14
|
+
@default_configured_path = configured_path
|
|
15
|
+
@default ||= new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.reset_default!
|
|
19
|
+
@default = nil
|
|
20
|
+
@default_configured_path = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
8
23
|
def initialize(binary_path = nil)
|
|
9
24
|
@path = binary_path || find_binary_path
|
|
10
25
|
@version = retrieve_binary_version
|
|
@@ -27,14 +42,15 @@ module WickedPdf
|
|
|
27
42
|
private
|
|
28
43
|
|
|
29
44
|
def retrieve_binary_version
|
|
30
|
-
|
|
31
|
-
|
|
45
|
+
# argv form: no shell parsing, so paths with spaces work.
|
|
46
|
+
stdout, = Open3.capture3(@path, '-V')
|
|
47
|
+
parse_version_string(stdout)
|
|
32
48
|
rescue StandardError
|
|
33
49
|
MINIMUM_BINARY_VERSION
|
|
34
50
|
end
|
|
35
51
|
|
|
36
52
|
def find_binary_path
|
|
37
|
-
return WickedPdf.config
|
|
53
|
+
return WickedPdf.config.exe_path if WickedPdf.config.exe_path
|
|
38
54
|
|
|
39
55
|
begin
|
|
40
56
|
detected_path = (defined?(Bundler) ? Bundler.which('wkhtmltopdf') : `which wkhtmltopdf`).chomp
|
data/lib/wicked_pdf/command.rb
CHANGED
|
@@ -2,14 +2,14 @@ module WickedPdf
|
|
|
2
2
|
class Command
|
|
3
3
|
attr_reader :binary, :option_parser
|
|
4
4
|
|
|
5
|
-
def initialize(binary: Binary.
|
|
5
|
+
def initialize(binary: Binary.default, option_parser: OptionParser.new)
|
|
6
6
|
@binary = binary
|
|
7
7
|
@option_parser = option_parser
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def execute(options, *args)
|
|
11
11
|
command = [binary.path]
|
|
12
|
-
command
|
|
12
|
+
command += global_argv
|
|
13
13
|
command += option_parser.parse(options)
|
|
14
14
|
command += args
|
|
15
15
|
|
|
@@ -18,28 +18,168 @@ module WickedPdf
|
|
|
18
18
|
if track_progress?(options)
|
|
19
19
|
Progress.new(options[:progress]).execute(command)
|
|
20
20
|
else
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
err = run_process(command)
|
|
22
|
+
|
|
23
|
+
raise GenerationError, "Error generating PDF\n Command Error: #{err}" if options[:raise_on_all_errors] && !err.empty?
|
|
24
|
+
|
|
25
|
+
err
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Runs several conversions through shared wkhtmltopdf processes using
|
|
30
|
+
# --read-args-from-stdin, avoiding one process boot per document.
|
|
31
|
+
#
|
|
32
|
+
# Each job is a Hash with:
|
|
33
|
+
# options: wkhtmltopdf options for this conversion only
|
|
34
|
+
# url: input URL (file:///... for local files)
|
|
35
|
+
# output_path: file the PDF is written to
|
|
36
|
+
#
|
|
37
|
+
# Jobs are split into `concurrency` contiguous slices, each processed by
|
|
38
|
+
# its own wkhtmltopdf process. wkhtmltopdf aborts a batch at the first
|
|
39
|
+
# failing conversion, so the failed job is retried as a normal single
|
|
40
|
+
# invocation (for a proper error) and the batch resumed with the rest.
|
|
41
|
+
# Raises WickedPdf::GenerationError if a job cannot be rendered.
|
|
42
|
+
#
|
|
43
|
+
# All argv compilation happens here on the calling thread: OptionParser
|
|
44
|
+
# is not thread-safe (it accumulates tempfile state for inline
|
|
45
|
+
# header/footer content), so worker threads only ever see frozen strings.
|
|
46
|
+
def execute_batch(jobs, concurrency: 1)
|
|
47
|
+
return if jobs.empty?
|
|
48
|
+
|
|
49
|
+
compiled = jobs.map { |job| BatchJob.new(job, batch_argv(job)) }
|
|
50
|
+
|
|
51
|
+
slices = compiled.each_slice(batch_slice_size(jobs.size, concurrency)).to_a
|
|
52
|
+
errors = Array.new(slices.size)
|
|
53
|
+
|
|
54
|
+
slices.each_with_index.map do |slice, index|
|
|
55
|
+
Thread.new do
|
|
56
|
+
process_batch_slice(slice)
|
|
25
57
|
rescue StandardError => e
|
|
26
|
-
|
|
58
|
+
errors[index] = e
|
|
27
59
|
end
|
|
60
|
+
end.each(&:join)
|
|
28
61
|
|
|
29
|
-
|
|
30
|
-
|
|
62
|
+
error = errors.compact.first
|
|
63
|
+
raise error if error
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# A batch job with its argv compiled up front (thread-safe to run).
|
|
67
|
+
BatchJob = Struct.new(:job, :argv) do
|
|
68
|
+
def output_path
|
|
69
|
+
job[:output_path]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def line
|
|
73
|
+
@line ||= argv.join(' ')
|
|
31
74
|
end
|
|
32
75
|
end
|
|
33
76
|
|
|
34
77
|
private
|
|
35
78
|
|
|
79
|
+
def batch_slice_size(job_count, concurrency)
|
|
80
|
+
concurrency = concurrency.clamp(1, job_count)
|
|
81
|
+
(job_count.to_f / concurrency).ceil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def process_batch_slice(batch_jobs)
|
|
85
|
+
pending = batch_jobs
|
|
86
|
+
until pending.empty?
|
|
87
|
+
run_batch_process(pending.map(&:line))
|
|
88
|
+
pending = pending.drop_while { |batch_job| batch_output_complete?(batch_job) }
|
|
89
|
+
break if pending.empty?
|
|
90
|
+
|
|
91
|
+
retry_batch_job(pending.shift)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# A failed conversion aborts its batch process, leaving this job and all
|
|
96
|
+
# jobs after it unrendered. Retrying it individually either renders it
|
|
97
|
+
# (transient failure) or produces a proper single-invocation error.
|
|
98
|
+
def retry_batch_job(batch_job)
|
|
99
|
+
command = [binary.path] + batch_job.argv.map { |token| unquote_batch_token(token) }
|
|
100
|
+
print_command(command.inspect) if in_development_mode?
|
|
101
|
+
|
|
102
|
+
err = run_process(command)
|
|
103
|
+
return if batch_output_complete?(batch_job)
|
|
104
|
+
|
|
105
|
+
raise GenerationError, "Error generating PDF for #{batch_job.output_path}\n Command Error: #{err}"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# wkhtmltopdf aborts a failing batch conversion partway, which can leave
|
|
109
|
+
# a non-empty but truncated file behind. Only a well-formed PDF (header
|
|
110
|
+
# and end-of-file marker) counts as done; anything else is retried.
|
|
111
|
+
def batch_output_complete?(batch_job)
|
|
112
|
+
size = File.size?(batch_job.output_path)
|
|
113
|
+
return false unless size
|
|
114
|
+
|
|
115
|
+
File.open(batch_job.output_path, 'rb') do |file|
|
|
116
|
+
return false unless file.read(5) == '%PDF-'
|
|
117
|
+
|
|
118
|
+
file.seek([size - 64, 0].max)
|
|
119
|
+
file.read.include?('%%EOF')
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def batch_argv(job)
|
|
124
|
+
argv = global_argv
|
|
125
|
+
argv += option_parser.parse(job[:options].dup)
|
|
126
|
+
argv += [job[:url], job[:output_path]]
|
|
127
|
+
argv.map { |arg| batch_token(arg.to_s) }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Flags controlled by gem configuration rather than per-render options.
|
|
131
|
+
def global_argv
|
|
132
|
+
WickedPdf.config.enable_local_file_access ? ['--enable-local-file-access'] : []
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# wkhtmltopdf tokenises each stdin line itself: spaces separate arguments
|
|
136
|
+
# unless quoted. Only quoted-space handling is verified, so reject
|
|
137
|
+
# characters with unknown escaping semantics rather than corrupt a line.
|
|
138
|
+
def batch_token(arg)
|
|
139
|
+
raise ArgumentError, "Unsupported character in wkhtmltopdf batch argument: #{arg.inspect}" if arg.match?(/["\n\r]/)
|
|
140
|
+
|
|
141
|
+
arg.include?(' ') ? %("#{arg}") : arg
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Reverses batch_token quoting when a compiled token is passed directly
|
|
145
|
+
# as a process argument instead of through the stdin line protocol.
|
|
146
|
+
def unquote_batch_token(token)
|
|
147
|
+
token.start_with?('"') && token.end_with?('"') ? token[1..-2] : token
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def run_process(command)
|
|
151
|
+
Open3.popen3(*command) do |_stdin, _stdout, stderr|
|
|
152
|
+
stderr.read
|
|
153
|
+
end
|
|
154
|
+
rescue StandardError => e
|
|
155
|
+
raise Error, "Failed to execute:\n#{command}\nError: #{e}"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def run_batch_process(lines)
|
|
159
|
+
print_command(lines.inspect) if in_development_mode?
|
|
160
|
+
|
|
161
|
+
err = nil
|
|
162
|
+
Open3.popen3(binary.path, '--read-args-from-stdin') do |stdin, stdout, stderr, wait_thr|
|
|
163
|
+
stdout_reader = Thread.new { stdout.read }
|
|
164
|
+
stderr_reader = Thread.new { stderr.read }
|
|
165
|
+
lines.each { |line| stdin.puts(line) }
|
|
166
|
+
stdin.close
|
|
167
|
+
stdout_reader.value
|
|
168
|
+
err = stderr_reader.value
|
|
169
|
+
wait_thr.value
|
|
170
|
+
end
|
|
171
|
+
err
|
|
172
|
+
rescue StandardError => e
|
|
173
|
+
raise Error, "Failed to execute wkhtmltopdf batch:\n#{lines.inspect}\nError: #{e}"
|
|
174
|
+
end
|
|
175
|
+
|
|
36
176
|
def in_development_mode?
|
|
37
177
|
defined?(Rails.env) && Rails.env.development?
|
|
38
178
|
end
|
|
39
179
|
|
|
40
180
|
def print_command(cmd)
|
|
41
181
|
# TODO: if no Rails what then?
|
|
42
|
-
Rails.logger.debug
|
|
182
|
+
Rails.logger.debug "[wicked_pdf]: #{cmd}"
|
|
43
183
|
end
|
|
44
184
|
|
|
45
185
|
def track_progress?(options)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module WickedPdf
|
|
2
|
+
# Global gem configuration, set via WickedPdf.configure or by assigning a
|
|
3
|
+
# Hash to WickedPdf.config (kept for backward compatibility).
|
|
4
|
+
#
|
|
5
|
+
# Two kinds of values live here:
|
|
6
|
+
#
|
|
7
|
+
# * Typed settings, declared as accessors below, which control the gem
|
|
8
|
+
# itself (binary location, middleware URLs, asset handling).
|
|
9
|
+
# * default_options, a Hash of wkhtmltopdf render options (:footer,
|
|
10
|
+
# :orientation, :dpi, ...) merged under every render's own options.
|
|
11
|
+
# Unknown keys assigned through a Hash or #[]= land here.
|
|
12
|
+
class Configuration
|
|
13
|
+
SETTINGS = [
|
|
14
|
+
:exe_path,
|
|
15
|
+
:basic_auth,
|
|
16
|
+
:root_url,
|
|
17
|
+
:default_protocol,
|
|
18
|
+
:expect_gzipped_remote_assets,
|
|
19
|
+
:enable_local_file_access
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
attr_accessor(*SETTINGS)
|
|
23
|
+
attr_reader :default_options
|
|
24
|
+
|
|
25
|
+
def self.from_hash(hash)
|
|
26
|
+
new.tap do |config|
|
|
27
|
+
(hash || {}).each { |key, value| config[key] = value }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initialize
|
|
32
|
+
@basic_auth = false
|
|
33
|
+
@enable_local_file_access = true
|
|
34
|
+
@default_options = {}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def []=(key, value)
|
|
38
|
+
if setting?(key)
|
|
39
|
+
public_send("#{key}=", value)
|
|
40
|
+
else
|
|
41
|
+
default_options[key.to_sym] = value
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def [](key)
|
|
46
|
+
setting?(key) ? public_send(key) : default_options[key.to_sym]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def to_h
|
|
50
|
+
settings = SETTINGS.to_h { |key| [key, public_send(key)] }
|
|
51
|
+
settings.compact.merge(default_options)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def setting?(key)
|
|
57
|
+
SETTINGS.include?(key.to_sym)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/wicked_pdf/document.rb
CHANGED
|
@@ -10,7 +10,7 @@ module WickedPdf
|
|
|
10
10
|
|
|
11
11
|
def pdf_from_string(string, options = {})
|
|
12
12
|
options = options.dup
|
|
13
|
-
options.merge!(WickedPdf.config) { |_key, option, _config| option }
|
|
13
|
+
options.merge!(WickedPdf.config.default_options) { |_key, option, _config| option }
|
|
14
14
|
string_file = WickedPdf::Tempfile.new('wicked_pdf.html', options[:temp_path])
|
|
15
15
|
string_file.write_in_chunks(string)
|
|
16
16
|
|
|
@@ -22,21 +22,52 @@ module WickedPdf
|
|
|
22
22
|
string_file.close! if string_file
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# Renders many HTML strings via Command#execute_batch (shared wkhtmltopdf
|
|
26
|
+
# processes instead of one boot per document). Items are Hashes of
|
|
27
|
+
# { html: String, options: Hash }; each item's options apply to that
|
|
28
|
+
# conversion only. Returns the PDF strings in item order.
|
|
29
|
+
def pdfs_from_strings(items, concurrency: 1)
|
|
30
|
+
tempfiles = []
|
|
31
|
+
|
|
32
|
+
jobs = items.map do |item|
|
|
33
|
+
options = (item[:options] || {}).dup
|
|
34
|
+
options.merge!(WickedPdf.config.default_options) { |_key, option, _config| option }
|
|
35
|
+
|
|
36
|
+
input = WickedPdf::Tempfile.new('wicked_pdf_batch.html', options[:temp_path])
|
|
37
|
+
tempfiles << input
|
|
38
|
+
input.write_in_chunks(item.fetch(:html))
|
|
39
|
+
|
|
40
|
+
output = WickedPdf::Tempfile.new('wicked_pdf_batch.pdf', options[:temp_path])
|
|
41
|
+
tempfiles << output
|
|
42
|
+
|
|
43
|
+
{ options: options, url: "file:///#{input.path}", output_path: output.path, output_file: output }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
@command.execute_batch(jobs, concurrency: concurrency)
|
|
47
|
+
|
|
48
|
+
jobs.map do |job|
|
|
49
|
+
pdf = job[:output_file].read_in_chunks
|
|
50
|
+
raise GenerationError, "PDF could not be generated!\n Output: #{job[:output_path]}" if pdf.rstrip.empty?
|
|
51
|
+
|
|
52
|
+
pdf
|
|
53
|
+
end
|
|
54
|
+
ensure
|
|
55
|
+
tempfiles.each(&:close!)
|
|
56
|
+
end
|
|
57
|
+
|
|
25
58
|
def pdf_from_url(url, options = {})
|
|
26
59
|
# merge in global config options
|
|
27
|
-
options.merge!(WickedPdf.config) { |_key, option, _config| option }
|
|
60
|
+
options.merge!(WickedPdf.config.default_options) { |_key, option, _config| option }
|
|
28
61
|
generated_pdf_file = WickedPdf::Tempfile.new('wicked_pdf_generated_file.pdf', options[:temp_path])
|
|
29
62
|
return_file = options.delete(:return_file)
|
|
30
63
|
|
|
31
64
|
err = @command.execute(options, url, generated_pdf_file.path.to_s)
|
|
32
65
|
|
|
33
|
-
if return_file
|
|
34
|
-
return generated_pdf_file
|
|
35
|
-
end
|
|
66
|
+
return generated_pdf_file if return_file
|
|
36
67
|
|
|
37
68
|
pdf = generated_pdf_file.read_in_chunks
|
|
38
69
|
|
|
39
|
-
raise "PDF could not be generated!\n Command Error: #{err}" if pdf && pdf.rstrip.empty?
|
|
70
|
+
raise GenerationError, "PDF could not be generated!\n Command Error: #{err}" if pdf && pdf.rstrip.empty?
|
|
40
71
|
|
|
41
72
|
pdf
|
|
42
73
|
rescue Errno::EINVAL => e
|
|
@@ -2,7 +2,7 @@ module WickedPdf
|
|
|
2
2
|
class Middleware
|
|
3
3
|
def initialize(app, options = {}, conditions = {})
|
|
4
4
|
@app = app
|
|
5
|
-
@options =
|
|
5
|
+
@options = WickedPdf.config.default_options.merge(options)
|
|
6
6
|
@conditions = conditions
|
|
7
7
|
@command = command(options[:wkhtmltopdf])
|
|
8
8
|
end
|
|
@@ -42,9 +42,9 @@ module WickedPdf
|
|
|
42
42
|
# Change relative paths to absolute
|
|
43
43
|
def translate_paths(body, env)
|
|
44
44
|
# Host with protocol
|
|
45
|
-
root = WickedPdf.config
|
|
45
|
+
root = WickedPdf.config.root_url || "#{env['rack.url_scheme']}://#{env['HTTP_HOST']}/"
|
|
46
46
|
|
|
47
|
-
body.gsub(/(href|src)=(['"])\/([
|
|
47
|
+
body.gsub(/(href|src)=(['"])\/([^"']*|[^"']*)['"]/, "\\1=\\2#{root}\\3\\2")
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def rendering_pdf?
|
|
@@ -73,7 +73,7 @@ module WickedPdf
|
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
true
|
|
77
77
|
else
|
|
78
78
|
request_path_is_pdf
|
|
79
79
|
end
|
|
@@ -20,6 +20,7 @@ module WickedPdf
|
|
|
20
20
|
def parse_extra(options)
|
|
21
21
|
return [] if options[:extra].nil?
|
|
22
22
|
return options[:extra].split if options[:extra].respond_to?(:split)
|
|
23
|
+
|
|
23
24
|
options[:extra]
|
|
24
25
|
end
|
|
25
26
|
|
|
@@ -37,6 +38,7 @@ module WickedPdf
|
|
|
37
38
|
unless options.blank?
|
|
38
39
|
[:header, :footer].collect do |hf|
|
|
39
40
|
next if options[hf].blank?
|
|
41
|
+
|
|
40
42
|
opt_hf = options[hf]
|
|
41
43
|
r += make_options(opt_hf, [:center, :font_name, :left, :right], hf.to_s)
|
|
42
44
|
r += make_options(opt_hf, [:font_size, :spacing], hf.to_s, :numeric)
|
|
@@ -49,9 +51,7 @@ module WickedPdf
|
|
|
49
51
|
options[hf][:html] = {}
|
|
50
52
|
options[hf][:html][:url] = "file:///#{tf.path}"
|
|
51
53
|
end
|
|
52
|
-
|
|
53
|
-
r += make_option("#{hf}-html", opt_hf[:html][:url]) unless opt_hf[:html][:url].blank?
|
|
54
|
-
end
|
|
54
|
+
r += make_option("#{hf}-html", opt_hf[:html][:url]) if !opt_hf[:html].blank? && !opt_hf[:html][:url].blank?
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
r
|
|
@@ -60,6 +60,7 @@ module WickedPdf
|
|
|
60
60
|
def parse_cover(argument)
|
|
61
61
|
arg = argument.to_s
|
|
62
62
|
return [] if arg.blank?
|
|
63
|
+
|
|
63
64
|
# Filesystem path or URL - hand off to wkhtmltopdf
|
|
64
65
|
if argument.is_a?(Pathname) || (arg[0, 4] == 'http')
|
|
65
66
|
['cover', arg]
|
|
@@ -74,6 +75,7 @@ module WickedPdf
|
|
|
74
75
|
|
|
75
76
|
def parse_toc(options)
|
|
76
77
|
return [] if options.nil?
|
|
78
|
+
|
|
77
79
|
r = ['toc']
|
|
78
80
|
unless options.blank?
|
|
79
81
|
r += make_options(options, [:font_name, :header_text], 'toc')
|
|
@@ -172,11 +174,12 @@ module WickedPdf
|
|
|
172
174
|
|
|
173
175
|
def make_options(options, names, prefix = '', type = :string)
|
|
174
176
|
return [] if options.nil?
|
|
177
|
+
|
|
175
178
|
names.collect do |o|
|
|
176
179
|
if options[o].blank?
|
|
177
180
|
[]
|
|
178
181
|
else
|
|
179
|
-
make_option("#{prefix.blank? ? '' : prefix
|
|
182
|
+
make_option("#{prefix.blank? ? '' : "#{prefix}-"}#{o}",
|
|
180
183
|
options[o],
|
|
181
184
|
type)
|
|
182
185
|
end
|
|
@@ -184,9 +187,8 @@ module WickedPdf
|
|
|
184
187
|
end
|
|
185
188
|
|
|
186
189
|
def make_option(name, value, type = :string)
|
|
187
|
-
if value.is_a?(Array)
|
|
188
|
-
|
|
189
|
-
end
|
|
190
|
+
return value.collect { |v| make_option(name, v, type) } if value.is_a?(Array)
|
|
191
|
+
|
|
190
192
|
if type == :name_value
|
|
191
193
|
parts = value.to_s.split(' ')
|
|
192
194
|
["--#{name.tr('_', '-')}", *parts]
|
|
@@ -3,7 +3,7 @@ module WickedPdf
|
|
|
3
3
|
def self.prepended(base)
|
|
4
4
|
# Protect from trying to augment modules that appear
|
|
5
5
|
# as the result of adding other gems.
|
|
6
|
-
|
|
6
|
+
nil if base != ActionController::Base
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def render_to_string(options = nil, *args, &block)
|
data/lib/wicked_pdf/progress.rb
CHANGED
|
@@ -11,17 +11,15 @@ module WickedPdf
|
|
|
11
11
|
output = []
|
|
12
12
|
begin
|
|
13
13
|
PTY.spawn(command.join(' ')) do |stdout, _stdin, pid|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@callback.call(line) if @callback
|
|
19
|
-
end
|
|
20
|
-
rescue Errno::EIO # rubocop:disable Lint/HandleExceptions
|
|
21
|
-
# child process is terminated, this is expected behaviour
|
|
22
|
-
ensure
|
|
23
|
-
::Process.wait pid
|
|
14
|
+
stdout.sync
|
|
15
|
+
stdout.each_line("\r") do |line|
|
|
16
|
+
output << line.chomp
|
|
17
|
+
@callback.call(line) if @callback
|
|
24
18
|
end
|
|
19
|
+
rescue Errno::EIO
|
|
20
|
+
# child process is terminated, this is expected behaviour
|
|
21
|
+
ensure
|
|
22
|
+
::Process.wait pid
|
|
25
23
|
end
|
|
26
24
|
rescue PTY::ChildExited
|
|
27
25
|
puts 'The child process exited!'
|
data/lib/wicked_pdf/railtie.rb
CHANGED
|
@@ -5,17 +5,15 @@ require 'wicked_pdf/asset_helper'
|
|
|
5
5
|
module WickedPdf
|
|
6
6
|
class Railtie < Rails::Railtie
|
|
7
7
|
initializer 'wicked_pdf.register' do |_app|
|
|
8
|
-
ActiveSupport.on_load(:action_controller)
|
|
9
|
-
ActionController::Base.
|
|
8
|
+
ActiveSupport.on_load(:action_controller) do
|
|
9
|
+
ActionController::Base.prepend PdfHelper
|
|
10
10
|
ActionController::Renderers.add :pdf do |template, options|
|
|
11
11
|
WickedPdf::Renderer.new(self).render(options.merge(pdf: template))
|
|
12
12
|
end
|
|
13
|
-
|
|
13
|
+
end
|
|
14
14
|
ActiveSupport.on_load(:action_view) { include WickedPdf::AssetHelper }
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
if Mime::Type.lookup_by_extension(:pdf).nil?
|
|
20
|
-
Mime::Type.register('application/pdf', :pdf)
|
|
21
|
-
end
|
|
19
|
+
Mime::Type.register('application/pdf', :pdf) if Mime::Type.lookup_by_extension(:pdf).nil?
|
data/lib/wicked_pdf/renderer.rb
CHANGED
|
@@ -9,20 +9,46 @@ module WickedPdf
|
|
|
9
9
|
|
|
10
10
|
def render(options)
|
|
11
11
|
options[:basic_auth] = set_basic_auth(options)
|
|
12
|
-
make_and_send_pdf(options.delete(:pdf),
|
|
12
|
+
make_and_send_pdf(options.delete(:pdf), WickedPdf.config.default_options.merge(options))
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def render_to_string(options)
|
|
16
16
|
options[:basic_auth] = set_basic_auth(options)
|
|
17
17
|
options.delete :pdf
|
|
18
|
-
make_pdf(
|
|
18
|
+
make_pdf(WickedPdf.config.default_options.merge(options))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Renders the template and any header/footer templates WITHOUT invoking
|
|
22
|
+
# wkhtmltopdf, returning { html:, options: } suitable for
|
|
23
|
+
# Document#pdfs_from_strings. Header/footer tempfiles referenced by the
|
|
24
|
+
# returned options stay alive until #cleanup is called, which must happen
|
|
25
|
+
# after the PDFs have been generated.
|
|
26
|
+
#
|
|
27
|
+
# Defaults (layout, template) match #render's `render :pdf` path, so a
|
|
28
|
+
# prepared document converts identically to a single render.
|
|
29
|
+
def prepare(options)
|
|
30
|
+
options = WickedPdf.config.default_options.merge(options)
|
|
31
|
+
options[:basic_auth] = set_basic_auth(options)
|
|
32
|
+
options.delete(:pdf)
|
|
33
|
+
options[:layout] ||= false
|
|
34
|
+
options[:template] ||= File.join(controller.controller_path, controller.action_name)
|
|
35
|
+
|
|
36
|
+
{
|
|
37
|
+
html: controller.render_to_string(render_options(options)),
|
|
38
|
+
options: prerender_header_and_footer(options)
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def cleanup
|
|
43
|
+
clean_temp_files
|
|
19
44
|
end
|
|
20
45
|
|
|
21
46
|
private
|
|
22
47
|
|
|
23
48
|
def set_basic_auth(options = {})
|
|
24
|
-
options[:basic_auth] ||= WickedPdf.config.
|
|
49
|
+
options[:basic_auth] ||= WickedPdf.config.basic_auth
|
|
25
50
|
return unless options[:basic_auth] && controller.request.env['HTTP_AUTHORIZATION']
|
|
51
|
+
|
|
26
52
|
controller.request.env['HTTP_AUTHORIZATION'].split(' ').last
|
|
27
53
|
end
|
|
28
54
|
|
|
@@ -45,7 +71,7 @@ module WickedPdf
|
|
|
45
71
|
else
|
|
46
72
|
pdf_content = make_pdf(options)
|
|
47
73
|
File.open(options[:save_to_file], 'wb') { |file| file << pdf_content } if options[:save_to_file]
|
|
48
|
-
controller.send_data(pdf_content, :filename => pdf_name
|
|
74
|
+
controller.send_data(pdf_content, :filename => "#{pdf_name}.pdf", :type => 'application/pdf', :disposition => options[:disposition]) unless options[:save_only]
|
|
49
75
|
end
|
|
50
76
|
end
|
|
51
77
|
|