phantompdf 1.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 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