wicked_pdf 2.0.2 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/gemfiles/5.0.gemfile CHANGED
@@ -1,8 +1,8 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'rails', '~> 5.0.0'
3
4
  gem 'rdoc'
4
- gem 'sqlite3', '~> 1.3.6'
5
5
  gem 'sprockets', '~>3.0' # v4 strips newlines from assets causing tests to fail
6
- gem 'rails', '~> 5.0.0'
6
+ gem 'sqlite3', '~> 1.3.6'
7
7
 
8
8
  gemspec path: '../'
data/gemfiles/5.1.gemfile CHANGED
@@ -1,8 +1,8 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'rails', '~> 5.1.0'
3
4
  gem 'rdoc'
4
- gem 'sqlite3', '~> 1.3.6'
5
5
  gem 'sprockets', '~>3.0' # v4 strips newlines from assets causing tests to fail
6
- gem 'rails', '~> 5.1.0'
6
+ gem 'sqlite3', '~> 1.3.6'
7
7
 
8
8
  gemspec path: '../'
data/gemfiles/5.2.gemfile CHANGED
@@ -1,10 +1,9 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'rdoc'
3
+ gem 'bootsnap' # required to run `rake test` in Rails 5.2
4
4
  gem 'rails', '~> 5.2'
5
- gem 'sqlite3', '~> 1.3.6'
5
+ gem 'rdoc'
6
6
  gem 'sprockets', '~>3.0' # v4 strips newlines from assets causing tests to fail
7
- gem 'bootsnap' # required to run `rake test` in Rails 5.2
8
- gem 'mocha', '= 1.3' # newer versions blow up
7
+ gem 'sqlite3', '~> 1.3.6'
9
8
 
10
9
  gemspec path: '../'
data/gemfiles/6.0.gemfile CHANGED
@@ -1,11 +1,10 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'bootsnap' # required to run `rake test` in Rails 6.0
3
4
  gem 'bundler', '~>2'
4
- gem 'rdoc'
5
5
  gem 'rails', '~>6.0.1'
6
- gem 'sqlite3', '~> 1.4'
6
+ gem 'rdoc'
7
7
  gem 'sprockets', '~>3.0'
8
- gem 'bootsnap' # required to run `rake test` in Rails 6.0
9
- gem 'mocha', '= 1.3' # newer versions blow up
8
+ gem 'sqlite3', '~> 1.4'
10
9
 
11
10
  gemspec path: '../'
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'bootsnap' # required to run `rake test` in Rails 6.1
4
+ gem 'bundler', '~>2'
5
+ gem 'rails', '~>6.1.0'
6
+ gem 'webpacker'
7
+ gem 'rdoc'
8
+ gem 'sprockets', '~>3.0'
9
+ gem 'sqlite3', '~> 1.4'
10
+
11
+ gemspec :path => '../'
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'bootsnap' # required to run `rake test` in Rails 7.0
4
+ gem 'bundler', '~>2'
5
+ gem 'rails', '~>7.0.0'
6
+ gem 'sprockets-rails'
7
+ gem 'rdoc'
8
+ gem 'sprockets', '~>3.0'
9
+ gem 'sqlite3', '~> 1.4'
10
+
11
+ gemspec :path => '../'
@@ -15,7 +15,16 @@ WickedPdf.config = {
15
15
  # or
16
16
  # exe_path: Gem.bin_path('wkhtmltopdf-binary', 'wkhtmltopdf')
17
17
 
18
+ # Needed for wkhtmltopdf 0.12.6+ to use many wicked_pdf asset helpers
19
+ # enable_local_file_access: true,
20
+
18
21
  # Layout file to be used for all PDFs
19
22
  # (but can be overridden in `render :pdf` calls)
20
23
  # layout: 'pdf.html',
24
+
25
+ # Using wkhtmltopdf without an X server can be achieved by enabling the
26
+ # 'use_xvfb' flag. This will wrap all wkhtmltopdf commands around the
27
+ # 'xvfb-run' command, in order to simulate an X server.
28
+ #
29
+ # use_xvfb: true,
21
30
  }
@@ -0,0 +1,65 @@
1
+ class WickedPdf
2
+ class Binary
3
+ EXE_NAME = 'wkhtmltopdf'.freeze
4
+
5
+ attr_reader :path, :default_version
6
+
7
+ def initialize(binary_path, default_version = WickedPdf::DEFAULT_BINARY_VERSION)
8
+ @path = binary_path || find_binary_path
9
+ @default_version = default_version
10
+
11
+ raise "Location of #{EXE_NAME} unknown" if @path.empty?
12
+ raise "Bad #{EXE_NAME}'s path: #{@path}" unless File.exist?(@path)
13
+ raise "#{EXE_NAME} is not executable" unless File.executable?(@path)
14
+ end
15
+
16
+ def version
17
+ @version ||= retrieve_binary_version
18
+ end
19
+
20
+ def parse_version_string(version_info)
21
+ match_data = /wkhtmltopdf\s*(\d*\.\d*\.\d*\w*)/.match(version_info)
22
+ if match_data && (match_data.length == 2)
23
+ Gem::Version.new(match_data[1])
24
+ else
25
+ default_version
26
+ end
27
+ end
28
+
29
+ def xvfb_run_path
30
+ path = possible_binary_locations.map { |l| File.expand_path("#{l}/xvfb-run") }.find { |location| File.exist?(location) }
31
+ raise StandardError, 'Could not find binary xvfb-run on the system.' unless path
32
+
33
+ path
34
+ end
35
+
36
+ private
37
+
38
+ def retrieve_binary_version
39
+ _stdin, stdout, _stderr = Open3.popen3(@path + ' -V')
40
+ parse_version_string(stdout.gets(nil))
41
+ rescue StandardError
42
+ default_version
43
+ end
44
+
45
+ def find_binary_path
46
+ exe_path ||= WickedPdf.config[:exe_path] unless WickedPdf.config.empty?
47
+ exe_path ||= possible_which_path
48
+ exe_path ||= possible_binary_locations.map { |l| File.expand_path("#{l}/#{EXE_NAME}") }.find { |location| File.exist?(location) }
49
+ exe_path || ''
50
+ end
51
+
52
+ def possible_which_path
53
+ detected_path = (defined?(Bundler) ? Bundler.which('wkhtmltopdf') : `which wkhtmltopdf`).chomp
54
+ detected_path.present? && detected_path
55
+ rescue StandardError
56
+ nil
57
+ end
58
+
59
+ def possible_binary_locations
60
+ possible_locations = (ENV['PATH'].split(':') + %w[/usr/bin /usr/local/bin]).uniq
61
+ possible_locations += %w[~/bin] if ENV.key?('HOME')
62
+ possible_locations
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,230 @@
1
+ class WickedPdf
2
+ class OptionParser
3
+ BINARY_VERSION_WITHOUT_DASHES = Gem::Version.new('0.12.0')
4
+
5
+ attr_reader :binary_version, :hf_tempfiles
6
+
7
+ def initialize(binary_version = WickedPdf::DEFAULT_BINARY_VERSION)
8
+ @binary_version = binary_version
9
+ end
10
+
11
+ def parse(options)
12
+ [
13
+ parse_extra(options),
14
+ parse_others(options),
15
+ parse_global(options),
16
+ parse_outline(options.delete(:outline)),
17
+ parse_header_footer(:header => options.delete(:header),
18
+ :footer => options.delete(:footer),
19
+ :layout => options[:layout]),
20
+ parse_cover(options.delete(:cover)),
21
+ parse_toc(options.delete(:toc)),
22
+ parse_basic_auth(options)
23
+ ].flatten
24
+ end
25
+
26
+ def valid_option(name)
27
+ if binary_version < BINARY_VERSION_WITHOUT_DASHES
28
+ "--#{name}"
29
+ else
30
+ name
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def parse_extra(options)
37
+ return [] if options[:extra].nil?
38
+ return options[:extra].split if options[:extra].respond_to?(:split)
39
+
40
+ options[:extra]
41
+ end
42
+
43
+ def parse_basic_auth(options)
44
+ if options[:basic_auth]
45
+ user, passwd = Base64.decode64(options[:basic_auth]).split(':')
46
+ ['--username', user, '--password', passwd]
47
+ else
48
+ []
49
+ end
50
+ end
51
+
52
+ def parse_header_footer(options)
53
+ r = []
54
+ unless options.blank?
55
+ %i[header footer].collect do |hf|
56
+ next if options[hf].blank?
57
+
58
+ opt_hf = options[hf]
59
+ r += make_options(opt_hf, %i[center font_name left right], hf.to_s)
60
+ r += make_options(opt_hf, %i[font_size spacing], hf.to_s, :numeric)
61
+ r += make_options(opt_hf, [:line], hf.to_s, :boolean)
62
+ if options[hf] && options[hf][:content]
63
+ @hf_tempfiles = [] unless defined?(@hf_tempfiles)
64
+ @hf_tempfiles.push(tf = File.new(Dir::Tmpname.create(["wicked_#{hf}_pdf", '.html']) {}, 'w'))
65
+ tf.write options[hf][:content]
66
+ tf.flush
67
+ options[hf][:html] = {}
68
+ options[hf][:html][:url] = "file:///#{tf.path}"
69
+ end
70
+ unless opt_hf[:html].blank?
71
+ r += make_option("#{hf}-html", opt_hf[:html][:url]) unless opt_hf[:html][:url].blank?
72
+ end
73
+ end
74
+ end
75
+ r
76
+ end
77
+
78
+ def parse_cover(argument)
79
+ arg = argument.to_s
80
+ return [] if arg.blank?
81
+
82
+ # Filesystem path or URL - hand off to wkhtmltopdf
83
+ if argument.is_a?(Pathname) || (arg[0, 4] == 'http')
84
+ [valid_option('cover'), arg]
85
+ else # HTML content
86
+ @hf_tempfiles ||= []
87
+ @hf_tempfiles << tf = WickedPdf::Tempfile.new('wicked_cover_pdf.html')
88
+ tf.write arg
89
+ tf.flush
90
+ [valid_option('cover'), tf.path]
91
+ end
92
+ end
93
+
94
+ def parse_toc(options)
95
+ return [] if options.nil?
96
+
97
+ r = [valid_option('toc')]
98
+ unless options.blank?
99
+ r += make_options(options, %i[font_name header_text], 'toc')
100
+ r += make_options(options, [:xsl_style_sheet])
101
+ r += make_options(options, %i[depth
102
+ header_fs
103
+ text_size_shrink
104
+ l1_font_size
105
+ l2_font_size
106
+ l3_font_size
107
+ l4_font_size
108
+ l5_font_size
109
+ l6_font_size
110
+ l7_font_size
111
+ level_indentation
112
+ l1_indentation
113
+ l2_indentation
114
+ l3_indentation
115
+ l4_indentation
116
+ l5_indentation
117
+ l6_indentation
118
+ l7_indentation], 'toc', :numeric)
119
+ r += make_options(options, %i[no_dots
120
+ disable_links
121
+ disable_back_links], 'toc', :boolean)
122
+ r += make_options(options, %i[disable_dotted_lines
123
+ disable_toc_links], nil, :boolean)
124
+ end
125
+ r
126
+ end
127
+
128
+ def parse_outline(options)
129
+ r = []
130
+ unless options.blank?
131
+ r = make_options(options, [:outline], '', :boolean)
132
+ r += make_options(options, [:outline_depth], '', :numeric)
133
+ end
134
+ r
135
+ end
136
+
137
+ def parse_margins(options)
138
+ make_options(options, %i[top bottom left right], 'margin', :numeric)
139
+ end
140
+
141
+ def parse_global(options)
142
+ r = []
143
+ unless options.blank?
144
+ r += make_options(options, %i[orientation
145
+ dpi
146
+ page_size
147
+ page_width
148
+ title
149
+ log_level])
150
+ r += make_options(options, %i[lowquality
151
+ grayscale
152
+ no_pdf_compression
153
+ quiet], '', :boolean)
154
+ r += make_options(options, %i[image_dpi
155
+ image_quality
156
+ page_height], '', :numeric)
157
+ r += parse_margins(options.delete(:margin))
158
+ end
159
+ r
160
+ end
161
+
162
+ def parse_others(options)
163
+ r = []
164
+ unless options.blank?
165
+ r += make_options(options, %i[proxy
166
+ username
167
+ password
168
+ encoding
169
+ user_style_sheet
170
+ viewport_size
171
+ window_status
172
+ allow])
173
+ r += make_options(options, %i[cookie
174
+ post], '', :name_value)
175
+ r += make_options(options, %i[redirect_delay
176
+ zoom
177
+ page_offset
178
+ javascript_delay], '', :numeric)
179
+ r += make_options(options, %i[book
180
+ default_header
181
+ disable_javascript
182
+ enable_plugins
183
+ disable_internal_links
184
+ disable_external_links
185
+ keep_relative_links
186
+ print_media_type
187
+ disable_local_file_access
188
+ enable_local_file_access
189
+ disable_smart_shrinking
190
+ use_xserver
191
+ no_background
192
+ images
193
+ no_images
194
+ no_stop_slow_scripts], '', :boolean)
195
+ end
196
+ r
197
+ end
198
+
199
+ def make_options(options, names, prefix = '', type = :string)
200
+ return [] if options.nil?
201
+
202
+ names.collect do |o|
203
+ if options[o].blank?
204
+ []
205
+ else
206
+ make_option("#{prefix.blank? ? '' : prefix + '-'}#{o}",
207
+ options[o],
208
+ type)
209
+ end
210
+ end
211
+ end
212
+
213
+ def make_option(name, value, type = :string)
214
+ return value.collect { |v| make_option(name, v, type) } if value.is_a?(Array)
215
+
216
+ if type == :name_value
217
+ parts = value.to_s.split(' ')
218
+ ["--#{name.tr('_', '-')}", *parts]
219
+ elsif type == :boolean
220
+ if value
221
+ ["--#{name.tr('_', '-')}"]
222
+ else
223
+ []
224
+ end
225
+ else
226
+ ["--#{name.tr('_', '-')}", value.to_s]
227
+ end
228
+ end
229
+ end
230
+ end
@@ -1,21 +1,5 @@
1
1
  class WickedPdf
2
2
  module PdfHelper
3
- def self.included(base)
4
- # Protect from trying to augment modules that appear
5
- # as the result of adding other gems.
6
- return if base != ActionController::Base
7
-
8
- base.class_eval do
9
- alias_method_chain :render, :wicked_pdf
10
- alias_method_chain :render_to_string, :wicked_pdf
11
- if respond_to?(:after_action)
12
- after_action :clean_temp_files
13
- else
14
- after_filter :clean_temp_files
15
- end
16
- end
17
- end
18
-
19
3
  def self.prepended(base)
20
4
  # Protect from trying to augment modules that appear
21
5
  # as the result of adding other gems.
@@ -26,52 +10,52 @@ class WickedPdf
26
10
  end
27
11
  end
28
12
 
29
- def render(options = nil, *args, &block)
30
- render_with_wicked_pdf(options, *args, &block)
31
- end
32
-
33
- def render_to_string(options = nil, *args, &block)
34
- render_to_string_with_wicked_pdf(options, *args, &block)
35
- end
36
-
37
- def render_with_wicked_pdf(options = nil, *args, &block)
13
+ def render(*args)
14
+ options = args.first
38
15
  if options.is_a?(Hash) && options.key?(:pdf)
39
- options[:basic_auth] = set_basic_auth(options)
40
- make_and_send_pdf(options.delete(:pdf), (WickedPdf.config || {}).merge(options))
41
- elsif respond_to?(:render_without_wicked_pdf)
42
- # support alias_method_chain (module included)
43
- render_without_wicked_pdf(options, *args, &block)
16
+ render_with_wicked_pdf(options)
44
17
  else
45
- # support inheritance (module prepended)
46
- method(:render).super_method.call(options, *args, &block)
18
+ super
47
19
  end
48
20
  end
49
21
 
50
- def render_to_string_with_wicked_pdf(options = nil, *args, &block)
22
+ def render_to_string(*args)
23
+ options = args.first
51
24
  if options.is_a?(Hash) && options.key?(:pdf)
52
- options[:basic_auth] = set_basic_auth(options)
53
- options.delete :pdf
54
- make_pdf((WickedPdf.config || {}).merge(options))
55
- elsif respond_to?(:render_to_string_without_wicked_pdf)
56
- # support alias_method_chain (module included)
57
- render_to_string_without_wicked_pdf(options, *args, &block)
25
+ render_to_string_with_wicked_pdf(options)
58
26
  else
59
- # support inheritance (module prepended)
60
- method(:render_to_string).super_method.call(options, *args, &block)
27
+ super
61
28
  end
62
29
  end
63
30
 
31
+ def render_with_wicked_pdf(options)
32
+ raise ArgumentError, 'missing keyword: pdf' unless options.is_a?(Hash) && options.key?(:pdf)
33
+
34
+ options[:basic_auth] = set_basic_auth(options)
35
+ make_and_send_pdf(options.delete(:pdf), (WickedPdf.config || {}).merge(options))
36
+ end
37
+
38
+ def render_to_string_with_wicked_pdf(options)
39
+ raise ArgumentError, 'missing keyword: pdf' unless options.is_a?(Hash) && options.key?(:pdf)
40
+
41
+ options[:basic_auth] = set_basic_auth(options)
42
+ options.delete :pdf
43
+ make_pdf((WickedPdf.config || {}).merge(options))
44
+ end
45
+
64
46
  private
65
47
 
66
48
  def set_basic_auth(options = {})
67
49
  options[:basic_auth] ||= WickedPdf.config.fetch(:basic_auth) { false }
68
50
  return unless options[:basic_auth] && request.env['HTTP_AUTHORIZATION']
51
+
69
52
  request.env['HTTP_AUTHORIZATION'].split(' ').last
70
53
  end
71
54
 
72
55
  def clean_temp_files
73
56
  return unless defined?(@hf_tempfiles)
74
- @hf_tempfiles.each(&:close!)
57
+
58
+ @hf_tempfiles.each(&:close)
75
59
  end
76
60
 
77
61
  def make_pdf(options = {})
@@ -119,10 +103,11 @@ class WickedPdf
119
103
  # Given an options hash, prerenders content for the header and footer sections
120
104
  # to temp files and return a new options hash including the URLs to these files.
121
105
  def prerender_header_and_footer(options)
122
- [:header, :footer].each do |hf|
106
+ %i[header footer].each do |hf|
123
107
  next unless options[hf] && options[hf][:html] && options[hf][:html][:template]
108
+
124
109
  @hf_tempfiles = [] unless defined?(@hf_tempfiles)
125
- @hf_tempfiles.push(tf = WickedPdfTempfile.new("wicked_#{hf}_pdf.html"))
110
+ @hf_tempfiles.push(tf = WickedPdf::Tempfile.new("wicked_#{hf}_pdf.html"))
126
111
  options[hf][:html][:layout] ||= options[:layout]
127
112
  render_opts = {
128
113
  :template => options[hf][:html][:template],
@@ -6,21 +6,12 @@ class WickedPdf
6
6
  if defined?(Rails.env)
7
7
  class WickedRailtie < Rails::Railtie
8
8
  initializer 'wicked_pdf.register', :after => 'remotipart.controller_helper' do |_app|
9
- ActiveSupport.on_load(:action_controller) do
10
- if ActionController::Base.respond_to?(:prepend) &&
11
- Object.method(:new).respond_to?(:super_method)
12
- ActionController::Base.send :prepend, PdfHelper
13
- else
14
- ActionController::Base.send :include, PdfHelper
15
- end
16
- ActionView::Base.send :include, WickedPdfHelper::Assets
17
- end
9
+ ActiveSupport.on_load(:action_controller) { ActionController::Base.send :prepend, PdfHelper }
10
+ ActiveSupport.on_load(:action_view) { include WickedPdfHelper::Assets }
18
11
  end
19
12
  end
20
13
 
21
- if Mime::Type.lookup_by_extension(:pdf).nil?
22
- Mime::Type.register('application/pdf', :pdf)
23
- end
14
+ Mime::Type.register('application/pdf', :pdf) if Mime::Type.lookup_by_extension(:pdf).nil?
24
15
 
25
16
  end
26
17
  end
@@ -1,13 +1,43 @@
1
1
  require 'tempfile'
2
+ require 'stringio'
2
3
 
3
4
  class WickedPdf
4
- class WickedPdfTempfile < Tempfile
5
- # ensures the Tempfile's filename always keeps its extension
5
+ class Tempfile < ::Tempfile
6
6
  def initialize(filename, temp_dir = nil)
7
7
  temp_dir ||= Dir.tmpdir
8
8
  extension = File.extname(filename)
9
- basename = File.basename(filename, extension)
9
+ basename = File.basename(filename, extension)
10
10
  super([basename, extension], temp_dir)
11
11
  end
12
+
13
+ def write_in_chunks(input_string)
14
+ binmode
15
+ string_io = StringIO.new(input_string)
16
+ write(string_io.read(chunk_size)) until string_io.eof?
17
+ close
18
+ self
19
+ rescue Errno::EINVAL => e
20
+ raise e, file_too_large_message
21
+ end
22
+
23
+ def read_in_chunks
24
+ rewind
25
+ binmode
26
+ output_string = ''
27
+ output_string << read(chunk_size) until eof?
28
+ output_string
29
+ rescue Errno::EINVAL => e
30
+ raise e, file_too_large_message
31
+ end
32
+
33
+ private
34
+
35
+ def chunk_size
36
+ 1024 * 1024
37
+ end
38
+
39
+ def file_too_large_message
40
+ 'The HTML file is too large! Try reducing the size or using the return_file option instead.'
41
+ end
12
42
  end
13
43
  end
@@ -1,3 +1,3 @@
1
1
  class WickedPdf
2
- VERSION = '2.0.2'.freeze
2
+ VERSION = '2.7.0'.freeze
3
3
  end