phantompdf 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 17ce8d8da194279be8c3578a6229421e12ac9312
4
+ data.tar.gz: 1ced8f6dac53b42f7362c6f407ebaf4d00a67e65
5
+ SHA512:
6
+ metadata.gz: 6eefd81192554a6ad80b75ee9324f0ad48007637bcf1089eb6c6f4862dabec47bcd9257a99da223dc554b56f6558af20b8e3624124859ef45f3adf4f9d805089
7
+ data.tar.gz: f36cfcce568fcd1e6ff266f6036d7820153504442be8be4d33ae8b9de16d9993644a51c5778215f21f3112dbdcb5c4c98324868181f3261c2a1012976d0261da
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # using phantompdf.gemspec to specify dependencies
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,86 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ phantompdf (1.0.0)
5
+ json
6
+ phantomjs
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ Ascii85 (1.0.2)
12
+ afm (0.2.0)
13
+ byebug (2.0.0)
14
+ columnize (~> 0.3.6)
15
+ debugger-linecache (~> 1.2.0)
16
+ capybara (1.1.4)
17
+ mime-types (>= 1.16)
18
+ nokogiri (>= 1.3.3)
19
+ rack (>= 1.0.0)
20
+ rack-test (>= 0.5.4)
21
+ selenium-webdriver (~> 2.0)
22
+ xpath (~> 0.1.4)
23
+ childprocess (0.3.9)
24
+ ffi (~> 1.0, >= 1.0.11)
25
+ columnize (0.3.6)
26
+ debugger-linecache (1.2.0)
27
+ diff-lcs (1.2.4)
28
+ eventmachine (1.0.3)
29
+ faye-websocket (0.4.7)
30
+ eventmachine (>= 0.12.0)
31
+ ffi (1.9.0)
32
+ hashery (2.1.1)
33
+ http_parser.rb (0.5.3)
34
+ json (1.8.0)
35
+ mime-types (1.24)
36
+ mini_portile (0.5.1)
37
+ multi_json (1.7.9)
38
+ nokogiri (1.6.0)
39
+ mini_portile (~> 0.5.0)
40
+ pdf-reader (1.3.3)
41
+ Ascii85 (~> 1.0.0)
42
+ afm (~> 0.2.0)
43
+ hashery (~> 2.0)
44
+ ruby-rc4
45
+ ttfunk
46
+ phantomjs (1.8.1.1)
47
+ poltergeist
48
+ poltergeist (1.0.3)
49
+ capybara (~> 1.1)
50
+ childprocess (~> 0.3)
51
+ faye-websocket (~> 0.4.4)
52
+ http_parser.rb (~> 0.5.3)
53
+ multi_json (~> 1.0)
54
+ rack (1.4.5)
55
+ rack-test (0.6.2)
56
+ rack (>= 1.0)
57
+ rake (10.1.0)
58
+ rspec (2.13.0)
59
+ rspec-core (~> 2.13.0)
60
+ rspec-expectations (~> 2.13.0)
61
+ rspec-mocks (~> 2.13.0)
62
+ rspec-core (2.13.1)
63
+ rspec-expectations (2.13.0)
64
+ diff-lcs (>= 1.1.3, < 2.0)
65
+ rspec-mocks (2.13.1)
66
+ ruby-rc4 (0.1.5)
67
+ rubyzip (0.9.9)
68
+ selenium-webdriver (2.35.0)
69
+ childprocess (>= 0.2.5)
70
+ multi_json (~> 1.0)
71
+ rubyzip
72
+ websocket (~> 1.0.4)
73
+ ttfunk (1.0.3)
74
+ websocket (1.0.7)
75
+ xpath (0.1.4)
76
+ nokogiri (~> 1.3)
77
+
78
+ PLATFORMS
79
+ ruby
80
+
81
+ DEPENDENCIES
82
+ byebug
83
+ pdf-reader
84
+ phantompdf!
85
+ rake
86
+ rspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Mc.Spring
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/NEWS.md ADDED
File without changes
data/README.md ADDED
@@ -0,0 +1,146 @@
1
+ ## PhantomPDF
2
+
3
+ Generate PDF from HTML using PhantomJS!
4
+
5
+ Supporting *URL*, *FILE* and *STRING* formats for HTML resource.
6
+
7
+ ## Why PhantomPDF?
8
+
9
+ Within the Ruby community, there is no simply way to generate *PDF* from *HTML*. You must setup dependences separate and then call some wrapped methods around. When you manage several servers or need to do migrations around, things become much more terrible!
10
+
11
+ PhantomPDF was born to simplify those. It brings simple API and easy maintenance.
12
+
13
+ **Yes, PhantomPDF is simply!** You only need to take care of your HTML layout. And then put remaining work to PhantomPDF.
14
+
15
+ ## How to start?
16
+
17
+ ### Installation
18
+
19
+ ##### For Gemfile project
20
+ Adding following to your `Gemfile`
21
+
22
+ ```ruby
23
+ gem 'phantompdf'
24
+ ```
25
+
26
+ and then execute
27
+
28
+ ```ruby
29
+ $ bundle
30
+ ```
31
+
32
+ ##### For normal project
33
+ ```ruby
34
+ $ gem install phantompdf
35
+ ```
36
+
37
+ That's all! Pretty simple?!
38
+
39
+ ### Usage
40
+
41
+ ##### Quick starts
42
+ ```ruby
43
+ require 'phantompdf'
44
+
45
+ options = {
46
+ :format => 'A4',
47
+ :margin => '1cm'
48
+ }
49
+ output_file = '/tmp/phantompdf_google.pdf'
50
+
51
+ # for *URL* resource
52
+ url = 'http://www.google.com'
53
+ url_generator = PhantomPDF::Generator.new(url, output_file, options)
54
+ returned_file = url_generator.generate # If output_file is valid(writable) returned_file == output_file, otherwise returned_file should be temp file of your OS.
55
+
56
+ # for *FILE* resource
57
+ file = 'file://path/to/file.html'
58
+ file_generator = PhantomPDF::Generator.new(file, output_file, options)
59
+ returned_file = file_generator.generate
60
+
61
+ # for *STRING* resource
62
+ html = '<h1>Hello, PhantomPDF!</h1>'
63
+ html_generator = PhantomPDF::Generator.new(html, output_file, options)
64
+ returned_file = html_generator.generate
65
+
66
+ # want to dynamic output?
67
+ url_generator.generate('/tmp/dynamic_phantompdf_google.pdf') # This will output pdf file to /tmp/dynamic_phantompdf_google.pdf though you pass *output* arg when creating generator instance.
68
+ ```
69
+
70
+ ##### Configuration
71
+ You can configure PhantomPDF globally in your project, such as Rails' initializers principle.
72
+
73
+ ```ruby
74
+ PhantomPDF.configure do |config|
75
+ # default pdf output format, e.g. "5in*7.5in", "10cm*20cm", "A4", "Letter"
76
+ :format => 'A4',
77
+
78
+ # default pdf header, formatted in [headerHeight*]headerString(HTML is supported)
79
+ # default to 1.2cm when you omit headerHeight
80
+ # you can use *%{pageNum}* and *%{pageTotal}* to refer current values of page
81
+ # example: 1.2cm*<h5>PhantomPDF header %{pageNum}/%{pageTotal}
82
+ :header => nil,
83
+
84
+ # default pdf footer, formatted in footerHeight*footerString(HTML is supported)
85
+ # you can use *%{pageNum}* and *%{pageTotal}* to refer current values of page
86
+ # example: 0.6cm*<h5>PhantomPDF header %{pageNum}/%{pageTotal}
87
+ :footer => nil,
88
+
89
+ # default page margin
90
+ :margin => '1cm',
91
+
92
+ # default page orientation, 'portrait' or 'landscape'
93
+ :orientation => 'portrait',
94
+
95
+ # default page zoom factor
96
+ :zoom => 1,
97
+
98
+ # default cookies, only used for *URL* resource
99
+ :cookies => {},
100
+
101
+ # PhantomJS running timeout
102
+ :timeout => 90000,
103
+
104
+ # pdf rendering timeout, increase if your HTML page is big
105
+ :rendering_timeout => 1000
106
+ end
107
+ ```
108
+
109
+ You can also configure PhantomPDF in place when needs.
110
+
111
+ ```ruby
112
+ # for *URL* resource
113
+ url = 'http://www.google.com'
114
+ url_generator = PhantomPDF::Generator.new(url, output_file, options)
115
+ url_generator.options.header = '0.8cm*<h3>Configure PhantomPDF dynamic</h3>'
116
+ returned_file = url_generator.generate
117
+ ```
118
+
119
+ ##### APIs
120
+ ```ruby
121
+ # create PhantomPDF
122
+ PhantomPDF::Generator.new(input, output=nil, options={})
123
+
124
+ # generate pdf
125
+ PhantomPDF::Generator#generate(output=>nil)
126
+
127
+ # raise PhantomPDF::RenderingError when failed to generate pdf
128
+ PhantomPDF::Generator#generate!(output=>nil)
129
+
130
+ # get generated pdf as string
131
+ PhantomPDF::Generator#to_string
132
+ ```
133
+
134
+ ### Contributing
135
+ If you'd like to help improve PhantomPDF or you find some bugs. You can contribute to it by following:
136
+
137
+ - Submit an issue describing your problem
138
+ - Fork your repo and work your magic
139
+ - Test your code as far as possible
140
+ - Submit a pull request when you finished hack
141
+
142
+ ### License
143
+ PhantomPDF is released under [MIT license](http://www.opensource.org/licenses/MIT).
144
+
145
+ ### Authors
146
+ [Spring MC](https://twitter.com/mcspring)
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task default: :spec
6
+
@@ -0,0 +1,20 @@
1
+ module PhantomPDF
2
+ class Assets
3
+ attr_accessor :root, :javascripts
4
+
5
+ class << self
6
+ def root
7
+ @root ||= File.expand_path('../../../', __FILE__)
8
+ end
9
+
10
+ def javascripts(name)
11
+ @javascripts ||= {}
12
+
13
+ @javascripts[name] ||= "#{root}/vendor/assets/javascripts/#{name}.js"
14
+ @javascripts[name] = nil unless File.exist?(@javascripts[name])
15
+
16
+ @javascripts[name]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,32 @@
1
+ module PhantomPDF
2
+ class Config
3
+ attr_accessor :default_options
4
+ attr_reader :phantomjs
5
+
6
+ def initialize(options={})
7
+ @default_options = {
8
+ :format => 'A4',
9
+ :header => nil,
10
+ :footer => nil,
11
+ :margin => '1cm',
12
+ :orientation => 'portrait',
13
+ :zoom => 1,
14
+ :cookies => {},
15
+ :timeout => 90000,
16
+ :rendering_timeout => 1000
17
+ }
18
+
19
+ @default_options.merge! options || {}
20
+ end
21
+
22
+ def phantomjs
23
+ @phantomjs ||= ::Phantomjs.path
24
+ end
25
+
26
+ [:format, :header, :footer, :margin, :orientation, :zoom, :cookies, :timeout, :rendering_timeout].each do |key|
27
+ define_method("#{key}=") do |val|
28
+ @default_options[key] = val
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ module PhantomPDF
2
+ class Error < StandardError; end
3
+ class SourceTypeError < Error; end
4
+ class DestinationTypeError < Error; end
5
+ class DestinationPermitError < Error; end
6
+ class RenderingError < Error; end
7
+ end
@@ -0,0 +1,104 @@
1
+ require 'uri'
2
+ require 'json'
3
+ require 'tempfile'
4
+
5
+ module PhantomPDF
6
+ class Generator
7
+ attr_accessor :input, :output, :config
8
+ attr_reader :options, :cookies, :exception
9
+
10
+ def initialize(input, output=nil, options={})
11
+ @input = Source.new(input)
12
+ @output = dump_output(output, !output.nil?)
13
+ @options = Config.new(options).default_options
14
+ @exception = nil
15
+ end
16
+
17
+ def generate(path=nil)
18
+ @output = dump_output(path, true) unless path.nil?
19
+
20
+ result = run
21
+ unless $?.exitstatus == 0
22
+ @exception = result.split(/\n/)
23
+
24
+ return nil
25
+ end
26
+
27
+ result.strip
28
+ end
29
+
30
+ def generate!(path=nil)
31
+ result = generate(path)
32
+ raise RenderingError.new(@exception.join("\n")) if result.nil?
33
+
34
+ result
35
+ end
36
+
37
+ def to_string
38
+ result = generate(nil)
39
+ return '' if result.nil?
40
+
41
+ File.open(result, 'rb').read
42
+ end
43
+
44
+ protected
45
+ def run
46
+ ::Phantomjs.run(*dump_args)
47
+ end
48
+
49
+ def dump_output(path, strict)
50
+ if strict
51
+ raise DestinationTypeError.new('Destination must be a valid file path!') unless path.is_a?(String)
52
+ path = File.expand_path(path)
53
+ raise DestinationPermitError.new('Destination does not writable!') unless File.writable?(File.dirname(path))
54
+ else
55
+ path = path.is_a?(String) ? File.expand_path(path) : nil
56
+ path = Tempfile.new('temp_pdf_file').path if path.nil? || !File.writable?(File.dirname(path))
57
+ end
58
+
59
+ path
60
+ end
61
+
62
+ def dump_args
63
+ format, header, footer = options[:format], options[:header], options[:footer]
64
+ zoom, margin, orientation = options[:zoom], options[:margin], options[:orientation]
65
+ rendering_timeout, timeout = options[:rendering_timeout], options[:timeout]
66
+ cookie_file = dump_cookies(options[:cookies])
67
+
68
+ [Assets.javascripts('rasterize'),
69
+ @input.to_s,
70
+ @output,
71
+ format, dump_header(header), dump_footer(footer),
72
+ margin, orientation, zoom,
73
+ cookie_file,
74
+ rendering_timeout, timeout].map(&:to_s)
75
+ end
76
+
77
+ def dump_header(header)
78
+ return nil if header.nil? || header.empty?
79
+
80
+ return "1.2cm*#{header}" unless header.split('*')[0].to_f > 0
81
+
82
+ header
83
+ end
84
+
85
+ def dump_footer(footer)
86
+ return nil if footer.nil? || footer.empty?
87
+
88
+ return "0.7cm*#{header}" unless footer.split('*')[0].to_f > 0
89
+
90
+ footer
91
+ end
92
+
93
+ def dump_cookies(cookies)
94
+ cookie_host = @input.url? ? URI::parse(@input.to_s).host : '/'
95
+
96
+ cookie_json = cookies.inject([]) {|ck, (k, v)| ck.push({:name => k, :value => v, :domain => cookie_host}); ck}.to_json
97
+ return nil if cookie_json.empty?
98
+
99
+ cookie_file = Tempfile.new('temp_cookie_file')
100
+ cookie_file.write(cookie_json)
101
+ cookie_file.path
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,46 @@
1
+ module PhantomPDF
2
+ class Middleware
3
+ REGEXP_PDF = /\.pdf\z/i
4
+
5
+ def initialize(app, output=nil)
6
+ @app = app
7
+ @output = output
8
+ end
9
+
10
+ def call(env)
11
+ @request = Rack::Request.new(env)
12
+
13
+ return @app.call(env) unless request_pdf?
14
+
15
+ status, headers, response = @app.call(env)
16
+ return [status, headers, response] if status != 200 || headers['Content-Type'] != 'text/html'
17
+
18
+ response_body = render_pdf(response.first)
19
+
20
+ headers.merge!({
21
+ 'Content-Type' => 'application/pdf',
22
+ 'Content-Length' => response_body.size.to_s
23
+ })
24
+
25
+ [200, headers, [response_body]]
26
+ end
27
+
28
+ private
29
+ def render_pdf(html)
30
+ Generator.new(html, render_path).to_string
31
+ end
32
+
33
+ def render_path
34
+ file_name = "#{Digest::MD5.hexdigest(@request.path)}.pdf"
35
+
36
+ return Tempfile.new(file_name).path if @output.nil? || !File.directory?(@output) || !File.writable?(@output)
37
+
38
+ "#{@output}/#{file_name}"
39
+ end
40
+
41
+ def request_pdf?
42
+ return true if @request.path.match(Middleware::REGEXP_PDF)
43
+ false
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,35 @@
1
+ require 'uri'
2
+
3
+ module PhantomPDF
4
+ class Source
5
+ def initialize(src)
6
+ @source = src
7
+
8
+ raise SourceTypeError.new('Unsupported source type.') unless valid?
9
+ end
10
+
11
+ def url?
12
+ !URI.parse(@source).scheme.nil?
13
+ rescue
14
+ false
15
+ end
16
+
17
+ def file?
18
+ !url? && (@source.kind_of?(File) || File.exists?(@source))
19
+ end
20
+
21
+ def html?
22
+ !(url? || file?)
23
+ end
24
+
25
+ def valid?
26
+ url? || file? || html?
27
+ end
28
+
29
+ def to_s
30
+ return @source if url? || html?
31
+
32
+ @source.kind_of?(File) ? @source.path : @source
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module PhantomPDF
2
+ VERSION = "1.0.0"
3
+ end
data/lib/phantompdf.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'phantompdf/version'
2
+ require 'phantompdf/error'
3
+ require 'phantompdf/assets'
4
+ require 'phantompdf/source'
5
+ require 'phantompdf/config'
6
+ require 'phantompdf/generator'
7
+ require 'phantompdf/middleware'
8
+
9
+ module PhantomPDF
10
+ # Configure PhantomPDF someplace sensible,
11
+ # like config/initializers/phantompdf.rb
12
+ #
13
+ # @example
14
+ # PhantomPDF.configure do |config|
15
+ # config.format = 'A4',
16
+ # config.header = nil,
17
+ # config.footer = nil,
18
+ # config.margin = '1cm',
19
+ # config.orientation = 'portrait',
20
+ # config.zoom = 1,
21
+ # config.cookies = {},
22
+ # config.timeout = 90000,
23
+ # config.rendering_timeout = 1000
24
+ # end
25
+ class << self
26
+ attr_accessor :configuration
27
+ end
28
+
29
+ def self.configuration
30
+ @configuration ||= Config.new
31
+ end
32
+
33
+ def self.configure
34
+ yield(configuration)
35
+ end
36
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ phantompdf_lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(phantompdf_lib) unless $LOAD_PATH.include?(phantompdf_lib)
4
+
5
+ require 'phantompdf/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'phantompdf'
9
+ spec.version = PhantomPDF::VERSION
10
+ spec.authors = ['Spring MC']
11
+ spec.email = %w(Heresy.Mc@gmail.com)
12
+ spec.description = %q{Generate PDF from HTML using PhantomJS}
13
+ spec.summary = %q{A PhantomJS based PDF generator}
14
+ spec.homepage = 'https://github.com/mcspring/phantompdf'
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}).map { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = %w(lib)
19
+ spec.add_runtime_dependency 'phantomjs'
20
+ spec.add_runtime_dependency 'json'
21
+
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'rspec'
24
+ spec.add_development_dependency 'pdf-reader'
25
+ spec.add_development_dependency 'byebug'
26
+ end
@@ -0,0 +1,23 @@
1
+ <html>
2
+ <head></head>
3
+ <body>
4
+ <header>
5
+ <h1>Hello PhantomPDF!</h1>
6
+ </header>
7
+ <section>
8
+ <div>
9
+ <p>
10
+ This is a HTML file used for PhantomPDF testing.
11
+ </p>
12
+ </div>
13
+ </section>
14
+ <footer>
15
+ <div>
16
+ <p>
17
+ For more, please visit <a href="https://github.com/mcspring/phantompdf" title="Generate PDF from HTML using Phantomjs" target="_blank">https://github.com/mcspring/phantompdf</a>
18
+ </p>
19
+ </div>
20
+ </footer>
21
+ </body>
22
+ </html>
23
+
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ module PhantomPDF
4
+ describe Assets do
5
+ context ".root" do
6
+ it "should return vendor folder" do
7
+ File.directory?(Assets.root).should be_true
8
+ end
9
+ end
10
+
11
+ context ".javascripts" do
12
+ it "should return rasterize.js abs path" do
13
+ File.exist?(Assets.javascripts('rasterize')).should be_true
14
+ end
15
+
16
+ it "should return nil for un-existed file" do
17
+ Assets.javascripts('un-exist-javascript').should be_nil
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ module PhantomPDF
4
+ describe Config do
5
+ before do
6
+ @config = Config.new
7
+ end
8
+
9
+ context "#phantomjs" do
10
+ it "should respond to :phantomjs" do
11
+ @config.should respond_to(:phantomjs)
12
+ end
13
+
14
+ it "should return phantomjs bin path" do
15
+ @config.phantomjs.should == Phantomjs.path
16
+ end
17
+ end
18
+
19
+ [:format, :header, :footer, :margin, :zoom, :orientation, :cookies, :timeout, :rendering_timeout].each do |method|
20
+ method = :"#{method}="
21
+
22
+ it "should respond to #{method}" do
23
+ @config.should respond_to(method)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,128 @@
1
+ require 'spec_helper'
2
+
3
+ module PhantomPDF
4
+ describe Generator do
5
+ let(:fixtures_root) {
6
+ File.expand_path('../../fixtures', __FILE__)
7
+ }
8
+
9
+ subject { Generator.new("#{fixtures_root}/phantompdf.html") }
10
+
11
+ context "attributes" do
12
+ [:input, :output, :config].each do |rwattr|
13
+ it { should respond_to(rwattr) }
14
+ it { should respond_to("#{rwattr}=".to_sym) }
15
+ end
16
+
17
+ [:options, :cookies, :exception].each do |rattr|
18
+ it { should respond_to(rattr) }
19
+ it { should_not respond_to("#{rattr}=") }
20
+ end
21
+ end
22
+
23
+ context "#generate" do
24
+ fixtures_root = File.expand_path('../../fixtures', __FILE__)
25
+
26
+ {
27
+ url: 'http://www.google.com',
28
+ file: "#{fixtures_root}/phantompdf.html",
29
+ html: File.read("#{fixtures_root}/phantompdf.html")
30
+ }.each do |key, value|
31
+ context "with #{key}" do
32
+ it "should works" do
33
+ Generator.new(value).generate.should be_pdf_file
34
+ end
35
+ end
36
+ end
37
+
38
+ context "with output" do
39
+ before :all do
40
+ @url = 'http://www.google.com'
41
+ @file = '/tmp/google.pdf'
42
+ end
43
+
44
+ after :each do
45
+ File.exist?(@file) && File.unlink(@file)
46
+ end
47
+
48
+ it "should generate pdf file following :output" do
49
+ File.exist?(@file).should be_false
50
+ Generator.new(@url, @file).generate
51
+ File.exist?(@file).should be_true
52
+
53
+ @file.should be_pdf_file
54
+ end
55
+
56
+ it "should raise PhantomPDF::DestinationTypeError when :output is not a string" do
57
+ expect{
58
+ Generator.new(@url, Object.new)
59
+ }.to raise_error(PhantomPDF::DestinationTypeError)
60
+ end
61
+
62
+ it "should raise PhantomPDF::DestinationPermitError when :output is not writable" do
63
+ File.stub(:writable?, '/tmp') { false }
64
+
65
+ expect{
66
+ Generator.new(@url, @file)
67
+ }.to raise_error(PhantomPDF::DestinationPermitError)
68
+ end
69
+ end
70
+
71
+ context "with options for pdf format" do
72
+ before :all do
73
+ @url = 'http://www.google.com'
74
+ @file = '/tmp/google.pdf'
75
+ end
76
+
77
+ after :each do
78
+ File.exist?(@file) && File.unlink(@file)
79
+ end
80
+
81
+ pending "should support custom :header" do
82
+ header = 'Hello, PhantomPDF header!'
83
+
84
+ Generator.new(@url, @file, {:header => header}).generate
85
+
86
+ pdf_content = PDF::Reader.new(@file).page(1).text
87
+ pdf_content.should include(header)
88
+ end
89
+
90
+ pending "should support custom :footer" do
91
+ header = 'Hello, PhantomPDF footer!'
92
+
93
+ Generator.new(@url, @file, {:footer => header}).generate
94
+
95
+ pdf_content = PDF::Reader.new(@file).page(1).text
96
+ pdf_content.should include(header)
97
+ end
98
+ end
99
+ end
100
+
101
+ context "#generate!" do
102
+ before :all do
103
+ @url = 'http://www.google.com'
104
+ end
105
+
106
+ it "should raise PhantomPDF::RenderingError when failed to generate" do
107
+ $?.stub(:exitstatus) { 1 }
108
+
109
+ generator = Generator.new(@url)
110
+ generator.stub(:run) { 'rendering error' }
111
+
112
+ expect{
113
+ generator.generate!
114
+ }.to raise_error(PhantomPDF::RenderingError)
115
+ end
116
+ end
117
+
118
+ context "#to_string" do
119
+ before :all do
120
+ @url = 'http://www.google.com'
121
+ end
122
+
123
+ it "should return string" do
124
+ Generator.new(@url).to_string.should be_pdf_string
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ module PhantomPDF
4
+ describe Middleware do
5
+ include Rack::Test::Methods
6
+
7
+ let(:page) { File.read File.expand_path('../../fixtures/phantompdf.html', __FILE__) }
8
+ let(:phantompdf) do
9
+ lambda {|env| [200, {'Content-Type' => 'text/html', 'Content-Length' => page.size.to_s}, [page]]}
10
+ end
11
+ let(:app) { Middleware.new(phantompdf, '/tmp') }
12
+
13
+ it "should works" do
14
+ get '/index.pdf'
15
+
16
+ last_response.status.should == 200
17
+ last_response.body.should be_pdf_string
18
+ end
19
+
20
+ it "should respond with original data without PDF request" do
21
+ get '/'
22
+
23
+ last_response.status.should == 200
24
+ last_response.body.should_not be_pdf_string
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ module PhantomPDF
4
+ describe Source do
5
+ before do
6
+ @url = ['http://www.test.com', 'file://tmp/phantompdf.html', 'ftp://phantompdf:passwd@test.com/phantompdf.html'].sample
7
+ @file = File.expand_path('../../fixtures/phantompdf.html', __FILE__)
8
+ @html = File.read(@file)
9
+
10
+ @url_source = Source.new(@url)
11
+ @file_source = Source.new(@file)
12
+ @html_source = Source.new(@html)
13
+ end
14
+
15
+ context "#url?" do
16
+ it "should return true for url" do
17
+ @url_source.url?.should be_true
18
+ end
19
+
20
+ it "should return false for file" do
21
+ @file_source.url?.should be_false
22
+ end
23
+
24
+ it "should return false for html" do
25
+ @html_source.url?.should be_false
26
+ end
27
+ end
28
+
29
+ context "#file?" do
30
+ it "should return false for url" do
31
+ @url_source.file?.should be_false
32
+ end
33
+
34
+ it "should return true for file" do
35
+ @file_source.file?.should be_true
36
+ end
37
+
38
+ it "should return false for html" do
39
+ @html_source.file?.should be_false
40
+ end
41
+
42
+ it "should return false if file does not exist" do
43
+ Source.new('path/to/unexisted/file').file?.should be_false
44
+ end
45
+ end
46
+
47
+ context "#html?" do
48
+ it "should return false for url" do
49
+ @url_source.html?.should be_false
50
+ end
51
+
52
+ it "should return false for file" do
53
+ @file_source.html?.should be_false
54
+ end
55
+
56
+ it "should return true for html" do
57
+ @html_source.html?.should be_true
58
+ end
59
+ end
60
+
61
+ context "#valid?" do
62
+ it "should return true for url" do
63
+ @url_source.valid?.should be_true
64
+ end
65
+
66
+ it "should return true for file" do
67
+ @file_source.valid?.should be_true
68
+ end
69
+
70
+ it "should return true for html" do
71
+ @html_source.valid?.should be_true
72
+ end
73
+ end
74
+
75
+ context "#to_s" do
76
+ it "should return url for url source" do
77
+ @url_source.to_s.should == @url
78
+ end
79
+
80
+ it "should return file path for file source" do
81
+ @file_source.to_s.should == @file
82
+ end
83
+
84
+ it "should return html string for html source" do
85
+ @html_source.to_s.should == @html
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,27 @@
1
+ require 'phantomjs'
2
+ require 'phantompdf'
3
+ require 'pdf-reader'
4
+ require 'byebug'
5
+
6
+ require 'rack/test'
7
+
8
+ RSpec.configure do |config|
9
+ # some staff goes here
10
+ end
11
+
12
+ RSpec::Matchers.define :be_pdf_file do
13
+ match do |actual|
14
+ case actual
15
+ when File
16
+ actual.read[0...4] == '%PDF'
17
+ when String
18
+ File.exist?(actual) && File.open(actual).read[0...4] == '%PDF'
19
+ end
20
+ end
21
+ end
22
+
23
+ RSpec::Matchers.define :be_pdf_string do
24
+ match do |actual|
25
+ actual[0...4] == '%PDF'
26
+ end
27
+ end
@@ -0,0 +1,148 @@
1
+ var page = require('webpage').create(),
2
+ fs = require('fs'),
3
+ system = require('system'),
4
+ system_args_length = system.args.length;
5
+ if (system_args_length < 3 || system_args_length > 12) {
6
+ console.log('Usage: phantomjs rasterize.js SOURCE DESTINATION [paperWidth*paperHeight|paperFormat] [header] [footer] [margin] [orientation] [zoom] [cookie_file] [render_timeout] [timeout]');
7
+ console.log(' : paper (pdf output) examples: "5in*7.5in", "10cm*20cm", "A4", "Letter"');
8
+ phantom.exit(1);
9
+ }
10
+
11
+ var input = system.args[1],
12
+ output = system.args[2],
13
+
14
+ margin = system.args[6] || '0cm',
15
+ orientation = system.args[7] || 'portrait',
16
+ zoom = system.args[8] || '1.0',
17
+
18
+ cookie_file = system.args[9],
19
+ cookies = {},
20
+
21
+ render_timeout = system.args[10] || 10000,
22
+ timeout = system.args[11] || 90000;
23
+
24
+ window.setTimeout(function () {
25
+ console.log("Shit's being weird no result within " + timeout + "ms");
26
+ phantom.exit(1);
27
+ }, timeout);
28
+
29
+ page.viewportSize = { width:600, height:600 };
30
+ if (output.substr(-4) === '.pdf') {
31
+ var size, header, footer,
32
+ paper_size_options = {};
33
+ if (system_args_length > 3) {
34
+ size = system.args[3].split('*');
35
+ paper_size_options = size.length === 2 ? {width:size[0], height:size[1], margin:'0px'} : {format:system.args[3], orientation:orientation, margin:margin};
36
+ }
37
+
38
+ if (system_args_length > 4) {
39
+ header = system.args[4].split('*');
40
+ if (header.length >= 2) {
41
+ paper_size_options['header'] = {
42
+ height: header.shift(),
43
+ contents: phantom.callback(function(pageNum, pageTotal){
44
+ return header.join('*').replace(new RegExp('%{pageNum}', 'g'), pageNum).replace(new RegExp('%{pageTotal}', 'g'), pageTotal);
45
+ })
46
+ };
47
+ }
48
+ }
49
+
50
+ if (system_args_length > 5) {
51
+ footer = system.args[5].split('*');
52
+ if (footer.length >= 2) {
53
+ paper_size_options['footer'] = {
54
+ height: footer.shift(),
55
+ contents: phantom.callback(function(pageNum, pageTotal){
56
+ return footer.join('*').replace(new RegExp('%{pageNum}', 'g'), pageNum).replace(new RegExp('%{pageTotal}', 'g'), pageTotal);
57
+ })
58
+ };
59
+ }
60
+ }
61
+
62
+ page.paperSize = paper_size_options;
63
+ }
64
+ page.zoomFactor = zoom;
65
+
66
+ if (/<([a-z]+?\d*?).*?>[^\0]*?<\/\1>/i.test(input) === true) {
67
+ page.content = input;
68
+
69
+ window.setTimeout(function () {
70
+ page.render(output + '_tmp.pdf');
71
+
72
+ if (fs.exists(output)) {
73
+ fs.remove(output);
74
+ }
75
+
76
+ try {
77
+ fs.move(output + '_tmp.pdf', output);
78
+
79
+ console.log(output);
80
+ } catch (e) {
81
+ phantom.exit(1);
82
+ throw e;
83
+ }
84
+
85
+ phantom.exit();
86
+ }, render_timeout);
87
+ } else {
88
+ var statusCode = null;
89
+
90
+ if (cookie_file) {
91
+ try {
92
+ fd = fs.open(cookie_file, 'r');
93
+ cookies = JSON.parse(fd.read());
94
+ fs.remove(cookie_file);
95
+
96
+
97
+ phantom.cookiesEnabled = true;
98
+ phantom.cookies = cookies;
99
+ } catch (e) {
100
+ // console.log(e);
101
+ }
102
+ }
103
+
104
+ // determine the statusCode
105
+ page.onResourceReceived = function (resource) {
106
+ if (new RegExp('^'+input).test(resource.url)) {
107
+ statusCode = resource.status;
108
+ }
109
+ };
110
+
111
+ page.open(input, function (status) {
112
+ if (status !== 'success' || (statusCode !== null && statusCode != 200)) {
113
+ console.log(statusCode, 'Failed to load the input!');
114
+
115
+ if (fs.exists(output)) {
116
+ fs.remove(output);
117
+ }
118
+
119
+ try {
120
+ fs.touch(output);
121
+ } catch (e) {
122
+ phantom.exit(1);
123
+ throw e;
124
+ }
125
+
126
+ phantom.exit(1);
127
+ } else {
128
+ window.setTimeout(function () {
129
+ page.render(output + '_tmp.pdf');
130
+
131
+ if (fs.exists(output)) {
132
+ fs.remove(output);
133
+ }
134
+
135
+ try {
136
+ fs.move(output + '_tmp.pdf', output);
137
+
138
+ console.log(output);
139
+ } catch (e) {
140
+ phantom.exit(1);
141
+ throw e;
142
+ }
143
+
144
+ phantom.exit();
145
+ }, render_timeout);
146
+ }
147
+ });
148
+ }
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: phantompdf
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Spring MC
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-09-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: phantomjs
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pdf-reader
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: byebug
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Generate PDF from HTML using PhantomJS
98
+ email:
99
+ - Heresy.Mc@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .gitignore
105
+ - Gemfile
106
+ - Gemfile.lock
107
+ - LICENSE
108
+ - NEWS.md
109
+ - README.md
110
+ - Rakefile
111
+ - lib/phantompdf.rb
112
+ - lib/phantompdf/assets.rb
113
+ - lib/phantompdf/config.rb
114
+ - lib/phantompdf/error.rb
115
+ - lib/phantompdf/generator.rb
116
+ - lib/phantompdf/middleware.rb
117
+ - lib/phantompdf/source.rb
118
+ - lib/phantompdf/version.rb
119
+ - phantompdf.gemspec
120
+ - spec/fixtures/phantompdf.html
121
+ - spec/phantompdf/assets_spec.rb
122
+ - spec/phantompdf/config_spec.rb
123
+ - spec/phantompdf/generator_spec.rb
124
+ - spec/phantompdf/middleware_spec.rb
125
+ - spec/phantompdf/source_spec.rb
126
+ - spec/spec_helper.rb
127
+ - vendor/assets/javascripts/rasterize.js
128
+ homepage: https://github.com/mcspring/phantompdf
129
+ licenses: []
130
+ metadata: {}
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - '>='
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - '>='
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubyforge_project:
147
+ rubygems_version: 2.0.3
148
+ signing_key:
149
+ specification_version: 4
150
+ summary: A PhantomJS based PDF generator
151
+ test_files:
152
+ - spec/fixtures/phantompdf.html
153
+ - spec/phantompdf/assets_spec.rb
154
+ - spec/phantompdf/config_spec.rb
155
+ - spec/phantompdf/generator_spec.rb
156
+ - spec/phantompdf/middleware_spec.rb
157
+ - spec/phantompdf/source_spec.rb
158
+ - spec/spec_helper.rb