pdfkit 0.4.6 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of pdfkit might be problematic. Click here for more details.

data/.gitignore CHANGED
@@ -20,3 +20,4 @@ pkg
20
20
 
21
21
  ## PROJECT::SPECIFIC
22
22
  .bundle
23
+ spec/custom_wkhtmltopdf_path.rb
data/Gemfile CHANGED
@@ -1,9 +1,3 @@
1
- source :rubygems
1
+ source :gemcutter
2
2
 
3
- group :development do
4
- gem "rspec", "~> 2.0.0.beta.8"
5
- gem "rspec-core", "~> 2.0.0.beta.8"
6
- gem "mocha"
7
- gem "jeweler"
8
- gem "rack"
9
- end
3
+ gemspec
@@ -1,35 +1,32 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ pdfkit (0.5.0)
5
+
1
6
  GEM
2
7
  remote: http://rubygems.org/
3
8
  specs:
4
9
  diff-lcs (1.1.2)
5
- gemcutter (0.6.1)
6
- git (1.2.5)
7
- jeweler (1.4.0)
8
- gemcutter (>= 0.1.0)
9
- git (>= 1.2.5)
10
- rubyforge (>= 2.0.0)
11
- json_pure (1.4.3)
12
- mocha (0.9.8)
10
+ mocha (0.9.10)
13
11
  rake
14
12
  rack (1.2.1)
13
+ rack-test (0.5.6)
14
+ rack (>= 1.0)
15
15
  rake (0.8.7)
16
- rspec (2.0.0.beta.19)
17
- rspec-core (= 2.0.0.beta.19)
18
- rspec-expectations (= 2.0.0.beta.19)
19
- rspec-mocks (= 2.0.0.beta.19)
20
- rspec-core (2.0.0.beta.19)
21
- rspec-expectations (2.0.0.beta.19)
22
- diff-lcs (>= 1.1.2)
23
- rspec-mocks (2.0.0.beta.19)
24
- rubyforge (2.0.4)
25
- json_pure (>= 1.1.7)
16
+ rspec (2.2.0)
17
+ rspec-core (~> 2.2)
18
+ rspec-expectations (~> 2.2)
19
+ rspec-mocks (~> 2.2)
20
+ rspec-core (2.2.1)
21
+ rspec-expectations (2.2.0)
22
+ diff-lcs (~> 1.1.2)
23
+ rspec-mocks (2.2.0)
26
24
 
27
25
  PLATFORMS
28
26
  ruby
29
27
 
30
28
  DEPENDENCIES
31
- jeweler
32
- mocha
33
- rack
34
- rspec (~> 2.0.0.beta.8)
35
- rspec-core (~> 2.0.0.beta.8)
29
+ mocha (>= 0.9.10)
30
+ pdfkit!
31
+ rack-test (>= 0.5.6)
32
+ rspec (~> 2.2.0)
@@ -0,0 +1,9 @@
1
+ 2010-12-27
2
+ ==================
3
+ * Bump to 0.5.0
4
+ * Switched to popen - adds support for JRuby and Windows
5
+ * Pulled in support for pdf rendering conditions in middleware via Rémy Coutable
6
+ * Use `which` to try and determine path to wkhtmltopdf
7
+ * Removed wkhtmltopdf auto installer
8
+ * Changed :disable\_smart\_shrinking to false for default options.
9
+ * Added History.md
@@ -1,8 +1,14 @@
1
1
  ******************************************************************
2
2
 
3
- Now install wkhtmltopdf binaries:
4
- Global: sudo `which pdfkit` --install-wkhtmltopdf
5
- or inside RVM folder: export TO=`which pdfkit | sed 's:/pdfkit:/wkhtmltopdf:'` && pdfkit --install-wkhtmltopdf
6
- (run pdfkit --help to see more options)
3
+ Install wkhtmltopdf:
7
4
 
8
- ******************************************************************
5
+ 1. Install by hand (recomended):
6
+
7
+ https://github.com/jdpace/PDFKit/wiki/Installing-WKHTMLTOPDF
8
+
9
+ 2. Try using the wkhtmltopdf-binary gem (mac + linux i386)
10
+
11
+ gem install wkhtmltopdf-binary
12
+
13
+
14
+ ******************************************************************
data/README.md CHANGED
@@ -9,24 +9,30 @@ Create PDFs using plain old HTML+CSS. Uses [wkhtmltopdf](http://github.com/antia
9
9
  gem install pdfkit
10
10
 
11
11
  ### wkhtmltopdf
12
- * **Automatic**: `sudo pdfkit --install-wkhtmltopdf`
13
- install latest version into /usr/local/bin
14
- (overwrite defaults with e.g. ARCHITECTURE=amd64 TO=/home/foo/bin)
15
- * By hand: http://code.google.com/p/wkhtmltopdf/downloads/list
12
+
13
+ 1. Install by hand (recomended):
14
+
15
+ https://github.com/jdpace/PDFKit/wiki/Installing-WKHTMLTOPDF
16
+
17
+ 2. Try using the wkhtmltopdf-binary gem (mac + linux i386)
18
+
19
+ gem install wkhtmltopdf-binary
20
+
21
+ *Note:* The automated installer has been removed.
16
22
 
17
23
  ## Usage
18
-
24
+
19
25
  # PDFKit.new takes the HTML and any options for wkhtmltopdf
20
26
  # run `wkhtmltopdf --extended-help` for a full list of options
21
27
  kit = PDFKit.new(html, :page_size => 'Letter')
22
28
  kit.stylesheets << '/path/to/css/file'
23
-
29
+
24
30
  # Git an inline PDF
25
31
  pdf = kit.to_pdf
26
-
32
+
27
33
  # Save the PDF to a file
28
34
  file = kit.to_file('/path/to/save/pdf')
29
-
35
+
30
36
  # PDFKit.new can optionally accept a URL or a File.
31
37
  # Stylesheets can not be added when source is provided as a URL of File.
32
38
  kit = PDFKit.new('http://google.com')
@@ -34,7 +40,7 @@ Create PDFs using plain old HTML+CSS. Uses [wkhtmltopdf](http://github.com/antia
34
40
 
35
41
  # Add any kind of option through meta tags
36
42
  PDFKit.new('<html><head><meta name="pdfkit-page_size" content="Letter")
37
-
43
+
38
44
  ## Configuration
39
45
 
40
46
  If you're on Windows or you installed wkhtmltopdf by hand to a location other than /usr/local/bin you will need to tell PDFKit where the binary is. You can configure PDFKit like so:
@@ -55,27 +61,59 @@ PDFKit comes with a middleware that allows users to get a PDF view of any page o
55
61
  ### Middleware Setup
56
62
 
57
63
  **Non-Rails Rack apps**
58
-
64
+
59
65
  # in config.ru
60
66
  require 'pdfkit'
61
67
  use PDFKit::Middleware
62
-
68
+
63
69
  **Rails apps**
64
70
 
65
71
  # in application.rb(Rails3) or environment.rb(Rails2)
66
72
  require 'pdfkit'
67
73
  config.middleware.use PDFKit::Middleware
68
-
74
+
69
75
  **With PDFKit options**
70
76
 
71
77
  # options will be passed to PDFKit.new
72
78
  config.middleware.use PDFKit::Middleware, :print_media_type => true
73
79
 
80
+ **With conditions to limit routes that can be generated in pdf**
81
+
82
+ # conditions can be regexes (either one or an array)
83
+ config.middleware.use PDFKit::Middleware, {}, :only => %r[^/public]
84
+ config.middleware.use PDFKit::Middleware, {}, :only => [%r[^/invoice], %r[^/public]]
85
+
86
+ # conditions can be strings (either one or an array)
87
+ config.middleware.use PDFKit::Middleware, {}, :only => '/public'
88
+ config.middleware.use PDFKit::Middleware, {}, :only => ['/invoice', '/public']
89
+
90
+ ## Troubleshooting
91
+
92
+ * **Single thread issue:** In development environments it is common to run a
93
+ single server process. This can cause issues when rendering your pdf
94
+ requires wkhtmltopdf to hit your server again (for images, js, css).
95
+ This is because the resource requests will get blocked by the initial
96
+ request and the initial request will be waiting on the resource
97
+ requests causing a deadlock.
98
+
99
+ This is usually not an issue in a production environment. To get
100
+ around this issue you may want to run a server with multiple workers
101
+ like Passenger or try to embed your resources within your HTML to
102
+ avoid extra HTTP requests.
103
+
104
+ * **Resources aren't included in the PDF:** Images, CSS, or Javascript
105
+ does not seem to be downloading correctly in the PDF. This is due
106
+ to the fact that wkhtmltopdf does not know where to find those files.
107
+ Make sure you are using absolute paths (start with forward slash) to
108
+ your resources. If you are using PDFKit to generate pdfs from a raw
109
+ HTML source make sure you use complete paths (either file paths or
110
+ urls including the domain).
111
+
74
112
  ## TODO
75
113
  - add amd64 support in --install-wkhtmltopdf
76
114
 
77
115
  ## Note on Patches/Pull Requests
78
-
116
+
79
117
  * Fork the project.
80
118
  * Setup your development environment with: gem install bundler; bundle install
81
119
  * Make your feature addition or bug fix.
data/Rakefile CHANGED
@@ -1,26 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
3
  require 'bundler'
4
- Bundler.require(:development)
5
-
6
- begin
7
- require 'jeweler'
8
- Jeweler::Tasks.new do |gem|
9
- gem.name = "pdfkit"
10
- gem.summary = %Q{HTML+CSS -> PDF}
11
- gem.description = %Q{Uses wkhtmltopdf to create PDFs using HTML}
12
- gem.email = "jared@codewordstudios.com"
13
- gem.homepage = "http://github.com/jdpace/PDFKit"
14
- gem.authors = ["jdpace"]
15
- gem.add_development_dependency "rspec", "~> 2.0.0.beta.8"
16
- gem.add_development_dependency "rspec-core", "~> 2.0.0.beta.8"
17
- gem.add_development_dependency 'mocha'
18
- gem.post_install_message = File.read('POST_INSTALL')
19
- end
20
- Jeweler::GemcutterTasks.new
21
- rescue LoadError
22
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
23
- end
4
+ Bundler::GemHelper.install_tasks
24
5
 
25
6
  require 'rspec/core/rake_task'
26
7
  RSpec::Core::RakeTask.new(:spec) do |spec|
@@ -30,8 +11,6 @@ RSpec::Core::RakeTask.new(:rcov) do |spec|
30
11
  spec.rcov = true
31
12
  end
32
13
 
33
- task :spec => :check_dependencies
34
-
35
14
  task :default => :spec
36
15
 
37
16
  require 'rake/rdoctask'
@@ -1,5 +1,4 @@
1
- require 'rubygems'
2
1
  require 'pdfkit/source'
3
2
  require 'pdfkit/pdfkit'
4
3
  require 'pdfkit/middleware'
5
- require 'pdfkit/configuration'
4
+ require 'pdfkit/configuration'
@@ -1,12 +1,12 @@
1
1
  class PDFKit
2
2
  class Configuration
3
- attr_accessor :meta_tag_prefix, :wkhtmltopdf, :default_options
3
+ attr_accessor :meta_tag_prefix, :default_options
4
+ attr_writer :wkhtmltopdf
4
5
 
5
6
  def initialize
6
7
  @meta_tag_prefix = 'pdfkit-'
7
- @wkhtmltopdf = '/usr/local/bin/wkhtmltopdf'
8
8
  @default_options = {
9
- :disable_smart_shrinking => true,
9
+ :disable_smart_shrinking => false,
10
10
  :page_size => 'Letter',
11
11
  :margin_top => '0.75in',
12
12
  :margin_right => '0.75in',
@@ -15,6 +15,10 @@ class PDFKit
15
15
  :encoding => "UTF-8"
16
16
  }
17
17
  end
18
+
19
+ def wkhtmltopdf
20
+ @wkhtmltopdf ||= `which wkhtmltopdf`.chomp
21
+ end
18
22
  end
19
23
 
20
24
  class << self
@@ -28,14 +32,12 @@ class PDFKit
28
32
  # PDFKit.configure do |config|
29
33
  # config.wkhtmltopdf = '/usr/bin/wkhtmltopdf'
30
34
  # end
31
-
35
+
32
36
  def self.configuration
33
37
  @configuration ||= Configuration.new
34
38
  end
35
-
36
-
39
+
37
40
  def self.configure
38
- self.configuration
39
41
  yield(configuration)
40
42
  end
41
- end
43
+ end
@@ -1,61 +1,77 @@
1
1
  class PDFKit
2
-
2
+
3
3
  class Middleware
4
-
5
- def initialize(app, options = {})
6
- @app = app
7
- @options = options
4
+
5
+ def initialize(app, options = {}, conditions = {})
6
+ @app = app
7
+ @options = options
8
+ @conditions = conditions
8
9
  end
9
-
10
+
10
11
  def call(env)
11
- @render_pdf = false
12
- set_request_to_render_as_pdf(env) if env['PATH_INFO'].match(/\.pdf$/)
13
-
12
+ @request = Rack::Request.new(env)
13
+
14
+ set_request_to_render_as_pdf(env) if render_as_pdf?
14
15
  status, headers, response = @app.call(env)
15
-
16
- request = Rack::Request.new(env)
17
- if @render_pdf && headers['Content-Type'] =~ /text\/html|application\/xhtml\+xml/
18
- body = response.body
19
-
20
- body = translate_paths(body, env)
21
-
22
- pdf = PDFKit.new(body, @options)
23
- body = pdf.to_pdf
24
-
16
+
17
+ if rendering_pdf? && headers['Content-Type'] =~ /text\/html|application\/xhtml\+xml/
18
+ body = response.respond_to?(:body) ? response.body : response.join
19
+ body = PDFKit.new(translate_paths(body, env), @options).to_pdf
20
+ response = [body]
21
+
25
22
  # Do not cache PDFs
26
23
  headers.delete('ETag')
27
24
  headers.delete('Cache-Control')
28
-
29
- headers["Content-Length"] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s
30
- headers["Content-Type"] = "application/pdf"
31
-
32
- response = [body]
25
+
26
+ headers["Content-Length"] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s
27
+ headers["Content-Type"] = "application/pdf"
33
28
  end
34
-
29
+
35
30
  [status, headers, response]
36
31
  end
37
-
32
+
38
33
  private
39
-
40
- # Change relative paths to absolute
41
- def translate_paths(body, env)
42
- # Host with protocol
43
- root = env['rack.url_scheme'] + "://" + env['HTTP_HOST'] + "/"
44
-
45
- body.gsub(/(href|src)=(['"])\/([^\"']*|[^"']*)['"]/,'\1=\2'+root+'\3\2')
46
- end
47
-
48
- def set_request_to_render_as_pdf(env)
49
- @render_pdf = true
50
34
 
51
- path = Pathname(env['PATH_INFO'])
52
- ['PATH_INFO','REQUEST_URI'].each { |e| env[e] = path.to_s.sub(/#{path.extname}$/,'') } if path.extname == '.pdf'
53
- env['HTTP_ACCEPT'] = concat(env['HTTP_ACCEPT'], Rack::Mime.mime_type('.html'))
54
- end
55
-
56
- def concat(accepts, type)
57
- (accepts || '').split(',').unshift(type).compact.join(',')
35
+ # Change relative paths to absolute
36
+ def translate_paths(body, env)
37
+ # Host with protocol
38
+ root = "#{env['rack.url_scheme']}://#{env['HTTP_HOST']}/"
39
+
40
+ body.gsub(/(href|src)=(['"])\/([^\"']*|[^"']*)['"]/, '\1=\2' + root + '\3\2')
41
+ end
42
+
43
+ def rendering_pdf?
44
+ @render_pdf
45
+ end
46
+
47
+ def render_as_pdf?
48
+ request_path_is_pdf = @request.path.match(%r{\.pdf$})
49
+
50
+ if request_path_is_pdf && @conditions[:only]
51
+ rules = [@conditions[:only]].flatten
52
+ rules.any? do |pattern|
53
+ if pattern.is_a?(Regexp)
54
+ @request.path =~ pattern
55
+ else
56
+ @request.path[0, pattern.length] == pattern
57
+ end
58
+ end
59
+ else
60
+ request_path_is_pdf
58
61
  end
59
-
62
+ end
63
+
64
+ def set_request_to_render_as_pdf(env)
65
+ @render_pdf = true
66
+ path = @request.path.sub(%r{\.pdf$}, '')
67
+ %w[PATH_INFO REQUEST_URI].each { |e| env[e] = path }
68
+ env['HTTP_ACCEPT'] = concat(env['HTTP_ACCEPT'], Rack::Mime.mime_type('.html'))
69
+ env["Rack-Middleware-PDFKit"] = "true"
70
+ end
71
+
72
+ def concat(accepts, type)
73
+ (accepts || '').split(',').unshift(type).compact.join(',')
74
+ end
75
+
60
76
  end
61
77
  end
@@ -3,45 +3,46 @@ class PDFKit
3
3
  class NoExecutableError < StandardError
4
4
  def initialize
5
5
  msg = "No wkhtmltopdf executable found at #{PDFKit.configuration.wkhtmltopdf}\n"
6
- msg << ">> Install wkhtmltopdf by hand or try running `pdfkit --install-wkhtmltopdf`"
6
+ msg << ">> Please install wkhtmltopdf - https://github.com/jdpace/PDFKit/wiki/Installing-WKHTMLTOPDF"
7
7
  super(msg)
8
8
  end
9
9
  end
10
-
10
+
11
11
  class ImproperSourceError < StandardError
12
12
  def initialize(msg)
13
13
  super("Improper Source: #{msg}")
14
14
  end
15
15
  end
16
-
16
+
17
17
  attr_accessor :source, :stylesheets
18
18
  attr_reader :options
19
-
19
+
20
20
  def initialize(url_file_or_html, options = {})
21
21
  @source = Source.new(url_file_or_html)
22
-
22
+
23
23
  @stylesheets = []
24
24
 
25
25
  @options = PDFKit.configuration.default_options.merge(options)
26
26
  @options.merge! find_options_in_meta(url_file_or_html) unless source.url?
27
27
  @options = normalize_options(@options)
28
-
28
+
29
29
  raise NoExecutableError.new unless File.exists?(PDFKit.configuration.wkhtmltopdf)
30
30
  end
31
-
32
- def command
31
+
32
+ def command(path = nil)
33
33
  args = [executable]
34
34
  args += @options.to_a.flatten.compact
35
35
  args << '--quiet'
36
-
36
+
37
37
  if @source.html?
38
38
  args << '-' # Get HTML from stdin
39
39
  else
40
40
  args << @source.to_s
41
41
  end
42
-
43
- args << '-' # Read PDF from stdout
44
- args
42
+
43
+ args << (path || '-') # Write to file or stdout
44
+
45
+ args.map {|arg| %Q{"#{arg.gsub('"', '\"')}"}}
45
46
  end
46
47
 
47
48
  def executable
@@ -53,25 +54,29 @@ class PDFKit
53
54
  default.split('/').last
54
55
  end
55
56
  end
56
-
57
- def to_pdf
57
+
58
+ def to_pdf(path=nil)
58
59
  append_stylesheets
59
-
60
- pdf = Kernel.open('|-', "w+")
61
- exec(*command) if pdf.nil?
62
- pdf.puts(@source.to_s) if @source.html?
63
- pdf.close_write
64
- result = pdf.gets(nil)
65
- pdf.close_read
66
-
67
- raise "command failed: #{command.join(' ')}" if result.to_s.strip.empty?
60
+
61
+ args = command(path)
62
+ invoke = args.join(' ')
63
+
64
+ result = IO.popen(invoke, "w+") do |pdf|
65
+ pdf.puts(@source.to_s) if @source.html?
66
+ pdf.close_write
67
+ pdf.gets(nil)
68
+ end
69
+ result = File.read(path) if path
70
+
71
+ raise "command failed: #{invoke}" if result.to_s.strip.empty?
68
72
  return result
69
73
  end
70
-
74
+
71
75
  def to_file(path)
72
- File.open(path,'w') {|file| file << self.to_pdf}
76
+ self.to_pdf(path)
77
+ File.new(path)
73
78
  end
74
-
79
+
75
80
  protected
76
81
 
77
82
  def find_options_in_meta(body)
@@ -92,14 +97,14 @@ class PDFKit
92
97
  rescue # rexml random crash on invalid xml
93
98
  []
94
99
  end
95
-
100
+
96
101
  def style_tag_for(stylesheet)
97
102
  "<style>#{File.read(stylesheet)}</style>"
98
103
  end
99
-
104
+
100
105
  def append_stylesheets
101
106
  raise ImproperSourceError.new('Stylesheets may only be added to an HTML source') if stylesheets.any? && !@source.html?
102
-
107
+
103
108
  stylesheets.each do |stylesheet|
104
109
  if @source.to_s.match(/<\/head>/)
105
110
  @source.to_s.gsub!(/(<\/head>)/, style_tag_for(stylesheet)+'\1')
@@ -108,7 +113,7 @@ class PDFKit
108
113
  end
109
114
  end
110
115
  end
111
-
116
+
112
117
  def normalize_options(options)
113
118
  normalized_options = {}
114
119
 
@@ -119,11 +124,11 @@ class PDFKit
119
124
  end
120
125
  normalized_options
121
126
  end
122
-
127
+
123
128
  def normalize_arg(arg)
124
129
  arg.to_s.downcase.gsub(/[^a-z0-9]/,'-')
125
130
  end
126
-
131
+
127
132
  def normalize_value(value)
128
133
  case value
129
134
  when TrueClass
@@ -132,5 +137,5 @@ class PDFKit
132
137
  value.to_s
133
138
  end
134
139
  end
135
-
136
- end
140
+
141
+ end