adzap-wicked_pdf 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/issue_template.md +15 -0
- data/.gitignore +21 -0
- data/.rubocop.yml +22 -0
- data/.rubocop_todo.yml +63 -0
- data/.travis.yml +30 -0
- data/CHANGELOG.md +135 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +446 -0
- data/Rakefile +31 -0
- data/gemfiles/4.2.gemfile +6 -0
- data/gemfiles/5.0.gemfile +6 -0
- data/gemfiles/5.1.gemfile +6 -0
- data/gemfiles/5.2.gemfile +9 -0
- data/gemfiles/rails_edge.gemfile +9 -0
- data/generators/wicked_pdf/templates/wicked_pdf.rb +21 -0
- data/generators/wicked_pdf/wicked_pdf_generator.rb +7 -0
- data/init.rb +2 -0
- data/lib/generators/wicked_pdf_generator.rb +6 -0
- data/lib/wicked_pdf.rb +29 -0
- data/lib/wicked_pdf/asset_helper.rb +141 -0
- data/lib/wicked_pdf/binary.rb +56 -0
- data/lib/wicked_pdf/command.rb +52 -0
- data/lib/wicked_pdf/document.rb +47 -0
- data/lib/wicked_pdf/middleware.rb +101 -0
- data/lib/wicked_pdf/option_parser.rb +220 -0
- data/lib/wicked_pdf/pdf_helper.rb +17 -0
- data/lib/wicked_pdf/progress.rb +33 -0
- data/lib/wicked_pdf/railtie.rb +19 -0
- data/lib/wicked_pdf/renderer.rb +121 -0
- data/lib/wicked_pdf/tempfile.rb +13 -0
- data/lib/wicked_pdf/version.rb +3 -0
- data/test/dummy/app/assets/javascripts/application.js +16 -0
- data/test/dummy/app/assets/javascripts/wicked.js +1 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/assets/stylesheets/wicked.css +1 -0
- data/test/dummy/config/database.yml +3 -0
- data/test/dummy/config/routes.rb +5 -0
- data/test/dummy/log/.gitignore +1 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/fixtures/database.yml +4 -0
- data/test/fixtures/document_with_long_line.html +16 -0
- data/test/fixtures/wicked.css +1 -0
- data/test/fixtures/wicked.js +1 -0
- data/test/functional/pdf_helper_test.rb +61 -0
- data/test/functional/wicked_pdf_asset_helper_test.rb +118 -0
- data/test/test_helper.rb +33 -0
- data/test/unit/wicked_pdf_binary_test.rb +52 -0
- data/test/unit/wicked_pdf_command_test.rb +4 -0
- data/test/unit/wicked_pdf_document_test.rb +60 -0
- data/test/unit/wicked_pdf_option_parser_test.rb +128 -0
- data/test/unit/wicked_pdf_renderer_test.rb +43 -0
- data/test/unit/wicked_pdf_test.rb +8 -0
- data/test/unit/wkhtmltopdf_location_test.rb +50 -0
- data/wicked_pdf.gemspec +38 -0
- metadata +249 -0
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rdoc/task'
|
4
|
+
require 'rails/version'
|
5
|
+
require 'bundler/gem_tasks'
|
6
|
+
|
7
|
+
desc 'Default: run unit tests.'
|
8
|
+
task :default => [:test, :rubocop]
|
9
|
+
|
10
|
+
desc 'Test the wicked_pdf plugin.'
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
12
|
+
t.libs << 'lib'
|
13
|
+
t.libs << 'test'
|
14
|
+
t.pattern = 'test/**/*_test.rb'
|
15
|
+
t.verbose = true
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Run RuboCop'
|
19
|
+
task :rubocop do
|
20
|
+
require 'rubocop/rake_task'
|
21
|
+
RuboCop::RakeTask.new
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'Generate documentation for the wicked_pdf gem.'
|
25
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
26
|
+
rdoc.rdoc_dir = 'rdoc'
|
27
|
+
rdoc.title = 'WickedPdf'
|
28
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
29
|
+
rdoc.rdoc_files.include('README.md')
|
30
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# WickedPDF Global Configuration
|
2
|
+
#
|
3
|
+
# Use this to set up shared configuration options for your entire application.
|
4
|
+
# Any of the configuration options shown here can also be applied to single
|
5
|
+
# models by passing arguments to the `render :pdf` call.
|
6
|
+
#
|
7
|
+
# To learn more, check out the README:
|
8
|
+
#
|
9
|
+
# https://github.com/adzap/wicked_pdf/blob/master/README.md
|
10
|
+
|
11
|
+
WickedPdf.config = {
|
12
|
+
# Path to the wkhtmltopdf executable: This usually isn't needed if using
|
13
|
+
# one of the wkhtmltopdf-binary family of gems.
|
14
|
+
# exe_path: '/usr/local/bin/wkhtmltopdf',
|
15
|
+
# or
|
16
|
+
# exe_path: Gem.bin_path('wkhtmltopdf-binary', 'wkhtmltopdf')
|
17
|
+
|
18
|
+
# Layout file to be used for all PDFs
|
19
|
+
# (but can be overridden in `render :pdf` calls)
|
20
|
+
# layout: 'pdf.html',
|
21
|
+
}
|
data/init.rb
ADDED
data/lib/wicked_pdf.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# wkhtml2pdf Ruby interface
|
2
|
+
# http://wkhtmltopdf.org/
|
3
|
+
|
4
|
+
require 'logger'
|
5
|
+
require 'digest/md5'
|
6
|
+
require 'rbconfig'
|
7
|
+
require 'open3'
|
8
|
+
|
9
|
+
require 'active_support/core_ext/object/blank'
|
10
|
+
|
11
|
+
require 'wicked_pdf/version'
|
12
|
+
require 'wicked_pdf/tempfile'
|
13
|
+
require 'wicked_pdf/binary'
|
14
|
+
require 'wicked_pdf/option_parser'
|
15
|
+
require 'wicked_pdf/progress'
|
16
|
+
require 'wicked_pdf/command'
|
17
|
+
require 'wicked_pdf/document'
|
18
|
+
require 'wicked_pdf/railtie' if defined?(Rails.env)
|
19
|
+
require 'wicked_pdf/middleware'
|
20
|
+
|
21
|
+
module WickedPdf
|
22
|
+
class << self
|
23
|
+
attr_accessor :config
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.new(command = WickedPdf::Command.new)
|
27
|
+
WickedPdf::Document.new(command)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
module WickedPdf
|
4
|
+
module AssetHelper
|
5
|
+
ASSET_URL_REGEX = /url\(['"]?([^'"]+?)['"]?\)/
|
6
|
+
|
7
|
+
def self.root_path
|
8
|
+
String === Rails.root ? Pathname.new(Rails.root) : Rails.root
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.add_extension(filename, extension)
|
12
|
+
filename.to_s.split('.').include?(extension) ? filename : "#{filename}.#{extension}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def wicked_pdf_asset_base64(path)
|
16
|
+
asset = find_asset(path)
|
17
|
+
raise "Could not find asset '#{path}'" if asset.nil?
|
18
|
+
base64 = Base64.encode64(asset.to_s).gsub(/\s+/, '')
|
19
|
+
"data:#{asset.content_type};base64,#{Rack::Utils.escape(base64)}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def wicked_pdf_stylesheet_link_tag(*sources)
|
23
|
+
stylesheet_contents = sources.collect do |source|
|
24
|
+
source = WickedPdf::AssetHelper.add_extension(source, 'css')
|
25
|
+
"<style type='text/css'>#{read_asset(source)}</style>"
|
26
|
+
end.join("\n")
|
27
|
+
|
28
|
+
stylesheet_contents.gsub(ASSET_URL_REGEX) do
|
29
|
+
if Regexp.last_match[1].starts_with?('data:')
|
30
|
+
"url(#{Regexp.last_match[1]})"
|
31
|
+
else
|
32
|
+
"url(#{wicked_pdf_asset_path(Regexp.last_match[1])})"
|
33
|
+
end
|
34
|
+
end.html_safe
|
35
|
+
end
|
36
|
+
|
37
|
+
def wicked_pdf_image_tag(img, options = {})
|
38
|
+
image_tag wicked_pdf_asset_path(img), options
|
39
|
+
end
|
40
|
+
|
41
|
+
def wicked_pdf_javascript_src_tag(jsfile, options = {})
|
42
|
+
jsfile = WickedPdf::AssetHelper.add_extension(jsfile, 'js')
|
43
|
+
javascript_include_tag wicked_pdf_asset_path(jsfile), options
|
44
|
+
end
|
45
|
+
|
46
|
+
def wicked_pdf_javascript_include_tag(*sources)
|
47
|
+
sources.collect do |source|
|
48
|
+
source = WickedPdf::AssetHelper.add_extension(source, 'js')
|
49
|
+
"<script type='text/javascript'>#{read_asset(source)}</script>"
|
50
|
+
end.join("\n").html_safe
|
51
|
+
end
|
52
|
+
|
53
|
+
def wicked_pdf_asset_path(asset)
|
54
|
+
if (pathname = asset_pathname(asset).to_s) =~ URI_REGEXP
|
55
|
+
pathname
|
56
|
+
else
|
57
|
+
"file:///#{pathname}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# borrowed from actionpack/lib/action_view/helpers/asset_url_helper.rb
|
64
|
+
URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}
|
65
|
+
|
66
|
+
def asset_pathname(source)
|
67
|
+
if precompiled_or_absolute_asset?(source)
|
68
|
+
asset = asset_path(source)
|
69
|
+
pathname = prepend_protocol(asset)
|
70
|
+
if pathname =~ URI_REGEXP
|
71
|
+
# asset_path returns an absolute URL using asset_host if asset_host is set
|
72
|
+
pathname
|
73
|
+
else
|
74
|
+
File.join(Rails.public_path, asset.sub(/\A#{Rails.application.config.action_controller.relative_url_root}/, ''))
|
75
|
+
end
|
76
|
+
else
|
77
|
+
asset = find_asset(source)
|
78
|
+
if asset
|
79
|
+
# older versions need pathname, Sprockets 4 supports only filename
|
80
|
+
asset.respond_to?(:filename) ? asset.filename : asset.pathname
|
81
|
+
else
|
82
|
+
File.join(Rails.public_path, source)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def find_asset(path)
|
88
|
+
if Rails.application.assets.respond_to?(:find_asset)
|
89
|
+
Rails.application.assets.find_asset(path, :base_path => Rails.application.root.to_s)
|
90
|
+
else
|
91
|
+
Sprockets::Railtie.build_environment(Rails.application).find_asset(path, :base_path => Rails.application.root.to_s)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# will prepend a http or default_protocol to a protocol relative URL
|
96
|
+
# or when no protcol is set.
|
97
|
+
def prepend_protocol(source)
|
98
|
+
protocol = WickedPdf.config[:default_protocol] || 'http'
|
99
|
+
if source[0, 2] == '//'
|
100
|
+
source = [protocol, ':', source].join
|
101
|
+
elsif source[0] != '/' && !source[0, 8].include?('://')
|
102
|
+
source = [protocol, '://', source].join
|
103
|
+
end
|
104
|
+
source
|
105
|
+
end
|
106
|
+
|
107
|
+
def precompiled_or_absolute_asset?(source)
|
108
|
+
Rails.configuration.assets.compile == false ||
|
109
|
+
source.to_s[0] == '/' ||
|
110
|
+
source.to_s.match(/\Ahttps?\:\/\//)
|
111
|
+
end
|
112
|
+
|
113
|
+
def read_asset(source)
|
114
|
+
if precompiled_or_absolute_asset?(source)
|
115
|
+
pathname = asset_pathname(source)
|
116
|
+
if pathname =~ URI_REGEXP
|
117
|
+
read_from_uri(pathname)
|
118
|
+
elsif File.file?(pathname)
|
119
|
+
IO.read(pathname)
|
120
|
+
end
|
121
|
+
else
|
122
|
+
find_asset(source).to_s
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def read_from_uri(uri)
|
127
|
+
encoding = ':UTF-8'
|
128
|
+
asset = open(uri, "r#{encoding}", &:read)
|
129
|
+
asset = gzip(asset) if WickedPdf.config[:expect_gzipped_remote_assets]
|
130
|
+
asset
|
131
|
+
end
|
132
|
+
|
133
|
+
def gzip(asset)
|
134
|
+
stringified_asset = StringIO.new(asset)
|
135
|
+
gzipper = Zlib::GzipReader.new(stringified_asset)
|
136
|
+
gzipper.read
|
137
|
+
rescue Zlib::GzipFile::Error
|
138
|
+
nil
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module WickedPdf
|
2
|
+
class Binary
|
3
|
+
EXE_NAME = 'wkhtmltopdf'.freeze
|
4
|
+
DEFAULT_BINARY_VERSION = Gem::Version.new('0.9.9')
|
5
|
+
|
6
|
+
attr_reader :path, :default_version
|
7
|
+
|
8
|
+
def initialize(binary_path = nil, default_version = DEFAULT_BINARY_VERSION)
|
9
|
+
@path = binary_path || find_binary_path
|
10
|
+
@default_version = default_version
|
11
|
+
|
12
|
+
raise "Location of #{EXE_NAME} unknown" if @path.empty?
|
13
|
+
raise "Bad #{EXE_NAME}'s path: #{@path}" unless File.exist?(@path)
|
14
|
+
raise "#{EXE_NAME} is not executable" unless File.executable?(@path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def version
|
18
|
+
@version ||= retrieve_binary_version
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_version_string(version_info)
|
22
|
+
match_data = /wkhtmltopdf\s*(\d*\.\d*\.\d*\w*)/.match(version_info)
|
23
|
+
if match_data && (match_data.length == 2)
|
24
|
+
Gem::Version.new(match_data[1])
|
25
|
+
else
|
26
|
+
default_version
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def retrieve_binary_version
|
33
|
+
_stdin, stdout, _stderr = Open3.popen3(@path + ' -V')
|
34
|
+
parse_version_string(stdout.gets(nil))
|
35
|
+
rescue StandardError
|
36
|
+
default_version
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_binary_path
|
40
|
+
return WickedPdf.config[:exe_path] if WickedPdf.config[:exe_path]
|
41
|
+
|
42
|
+
begin
|
43
|
+
detected_path = (defined?(Bundler) ? Bundler.which('wkhtmltopdf') : `which wkhtmltopdf`).chomp
|
44
|
+
return detected_path if detected_path.present?
|
45
|
+
rescue StandardError
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
possible_locations = (ENV['PATH'].split(':') + %w[/usr/bin /usr/local/bin]).uniq
|
50
|
+
possible_locations += %w[~/bin] if ENV.key?('HOME')
|
51
|
+
|
52
|
+
exe_path ||= possible_locations.map { |l| File.expand_path("#{l}/#{EXE_NAME}") }.find { |location| File.exist?(location) }
|
53
|
+
exe_path || ''
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module WickedPdf
|
2
|
+
class Command
|
3
|
+
attr_reader :binary, :option_parser
|
4
|
+
|
5
|
+
def initialize(binary: Binary.new, option_parser: nil)
|
6
|
+
@binary = binary
|
7
|
+
@option_parser = option_parser || OptionParser.new(@binary.version)
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute(options, *args)
|
11
|
+
command = [binary.path]
|
12
|
+
command += option_parser.parse(options)
|
13
|
+
command += args
|
14
|
+
|
15
|
+
print_command(command.inspect) if in_development_mode?
|
16
|
+
|
17
|
+
if track_progress?(options)
|
18
|
+
Progress.new(options[:progress]).execute(command)
|
19
|
+
else
|
20
|
+
begin
|
21
|
+
err = Open3.popen3(*command) do |_stdin, _stdout, stderr|
|
22
|
+
stderr.read
|
23
|
+
end
|
24
|
+
rescue StandardError => e
|
25
|
+
raise "Failed to execute:\n#{command}\nError: #{e}"
|
26
|
+
end
|
27
|
+
|
28
|
+
raise "Error generating PDF\n Command Error: #{err}" if options[:raise_on_all_errors] && !err.empty?
|
29
|
+
err
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def in_development_mode?
|
36
|
+
defined?(Rails.env) && Rails.env.development?
|
37
|
+
end
|
38
|
+
|
39
|
+
def print_command(cmd)
|
40
|
+
# TODO: if no Rails what then?
|
41
|
+
Rails.logger.debug '[wicked_pdf]: ' + cmd
|
42
|
+
end
|
43
|
+
|
44
|
+
def track_progress?(options)
|
45
|
+
options[:progress] && !on_windows?
|
46
|
+
end
|
47
|
+
|
48
|
+
def on_windows?
|
49
|
+
RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module WickedPdf
|
2
|
+
class Document
|
3
|
+
def initialize(command = Command.new)
|
4
|
+
@command = command
|
5
|
+
end
|
6
|
+
|
7
|
+
def pdf_from_html_file(filepath, options = {})
|
8
|
+
pdf_from_url("file:///#{filepath}", options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def pdf_from_string(string, options = {})
|
12
|
+
options = options.dup
|
13
|
+
options.merge!(WickedPdf.config) { |_key, option, _config| option }
|
14
|
+
string_file = WickedPdf::Tempfile.new('wicked_pdf.html', options[:temp_path])
|
15
|
+
string_file.binmode
|
16
|
+
string_file.write(string)
|
17
|
+
string_file.close
|
18
|
+
|
19
|
+
pdf_from_html_file(string_file.path, options)
|
20
|
+
ensure
|
21
|
+
string_file.close! if string_file
|
22
|
+
end
|
23
|
+
|
24
|
+
def pdf_from_url(url, options = {})
|
25
|
+
# merge in global config options
|
26
|
+
options.merge!(WickedPdf.config) { |_key, option, _config| option }
|
27
|
+
generated_pdf_file = WickedPdf::Tempfile.new('wicked_pdf_generated_file.pdf', options[:temp_path])
|
28
|
+
|
29
|
+
result = @command.execute(options, url, generated_pdf_file.path.to_s)
|
30
|
+
|
31
|
+
if options[:return_file]
|
32
|
+
return_file = options.delete(:return_file)
|
33
|
+
return generated_pdf_file
|
34
|
+
end
|
35
|
+
|
36
|
+
generated_pdf_file.rewind
|
37
|
+
generated_pdf_file.binmode
|
38
|
+
pdf = generated_pdf_file.read
|
39
|
+
|
40
|
+
raise "PDF could not be generated!\n Command Error: #{result}" if pdf && pdf.rstrip.empty?
|
41
|
+
|
42
|
+
pdf
|
43
|
+
ensure
|
44
|
+
generated_pdf_file.close! if generated_pdf_file && !return_file
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|