├── .gitignore
├── Rakefile
├── lib
├── acts_as_flying_saucer
│ ├── version.rb
│ ├── java
│ │ ├── jar
│ │ │ ├── iText-2.1.7.jar
│ │ │ ├── core-renderer.jar
│ │ │ ├── acts_as_flying_saucer.jar
│ │ │ └── xml-apis-xerces-2.9.1.jar
│ │ └── src
│ │ │ ├── Xhtml2Pdf.java
│ │ │ └── encryptPdf.java
│ ├── config.rb
│ ├── xhtml2pdf.rb
│ └── acts_as_flying_saucer_controller.rb
└── acts_as_flying_saucer.rb
├── Gemfile
├── acts_as_flying_saucer.gemspec
└── README.markdown
/.gitignore:
--------------------------------------------------------------------------------
1 | pkg/*
2 | *.gem
3 | .bundle
4 | .idea/
5 | .idea/*
6 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler'
2 | Bundler::GemHelper.install_tasks
3 |
4 |
5 |
--------------------------------------------------------------------------------
/lib/acts_as_flying_saucer/version.rb:
--------------------------------------------------------------------------------
1 | module ActsAsFlyingSaucer
2 | VERSION = "1.0.4"
3 | end
4 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | # Specify your gem's dependencies in acts_as_flying_saucer.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/lib/acts_as_flying_saucer/java/jar/iText-2.1.7.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amardaxini/acts_as_flying_saucer/HEAD/lib/acts_as_flying_saucer/java/jar/iText-2.1.7.jar
--------------------------------------------------------------------------------
/lib/acts_as_flying_saucer/java/jar/core-renderer.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amardaxini/acts_as_flying_saucer/HEAD/lib/acts_as_flying_saucer/java/jar/core-renderer.jar
--------------------------------------------------------------------------------
/lib/acts_as_flying_saucer/java/jar/acts_as_flying_saucer.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amardaxini/acts_as_flying_saucer/HEAD/lib/acts_as_flying_saucer/java/jar/acts_as_flying_saucer.jar
--------------------------------------------------------------------------------
/lib/acts_as_flying_saucer/java/jar/xml-apis-xerces-2.9.1.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amardaxini/acts_as_flying_saucer/HEAD/lib/acts_as_flying_saucer/java/jar/xml-apis-xerces-2.9.1.jar
--------------------------------------------------------------------------------
/lib/acts_as_flying_saucer.rb:
--------------------------------------------------------------------------------
1 | require 'acts_as_flying_saucer/config'
2 | require 'acts_as_flying_saucer/xhtml2pdf'
3 | require 'acts_as_flying_saucer/acts_as_flying_saucer_controller'
4 | require 'nailgun'
5 | require 'tidy_ffi'
6 | require 'net/http'
7 | require 'uri'
8 | if defined?(Rails)
9 | ActionController::Base.send(:include, ActsAsFlyingSaucer::Controller)
10 | elsif defined?(Sinatra)
11 | Sinatra::Base.send(:include, ActsAsFlyingSaucer::Controller)
12 | class Sinatra::Base
13 | acts_as_flying_saucer
14 | end
15 | end
16 |
17 | module ActsAsFlyingSaucer
18 |
19 | end
20 |
--------------------------------------------------------------------------------
/lib/acts_as_flying_saucer/java/src/Xhtml2Pdf.java:
--------------------------------------------------------------------------------
1 | import java.io.*;
2 | import org.xhtmlrenderer.pdf.ITextRenderer;
3 |
4 | public class Xhtml2Pdf
5 | {
6 | public static void main(String[] args) throws Exception {
7 |
8 | String input = args[0];
9 | String url = new File(input).toURI().toURL().toString();
10 | String output = args[1];
11 |
12 | OutputStream os = new FileOutputStream(output);
13 | ITextRenderer renderer = new ITextRenderer();
14 | renderer.setDocument(url);
15 | renderer.layout();
16 | renderer.createPDF(os);
17 | os.close();
18 |
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/acts_as_flying_saucer/java/src/encryptPdf.java:
--------------------------------------------------------------------------------
1 | import java.io.*;
2 | import org.xhtmlrenderer.pdf.ITextRenderer;
3 | import com.lowagie.text.pdf.PdfReader;
4 | import com.lowagie.text.pdf.PdfWriter;
5 | import com.lowagie.text.pdf.PdfEncryptor;
6 | public class encryptPdf
7 | {
8 | public static void main(String[] args) throws Exception {
9 |
10 | String input = args[0];
11 | String output = args[1];
12 | String password = args[2];
13 |
14 | PdfReader pr = new PdfReader(input);
15 | OutputStream os = new FileOutputStream(output);
16 | PdfEncryptor.encrypt(pr,os, false,password,password, PdfWriter.AllowAssembly |
17 | PdfWriter.AllowCopy | PdfWriter.AllowDegradedPrinting | PdfWriter.AllowFillIn | PdfWriter.AllowModifyAnnotations | PdfWriter.AllowModifyContents | PdfWriter.AllowPrinting | PdfWriter.AllowScreenReaders);
18 | os.close();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/acts_as_flying_saucer.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | $:.push File.expand_path("../lib", __FILE__)
3 | require "acts_as_flying_saucer/version"
4 |
5 | Gem::Specification.new do |s|
6 | s.name = "acts_as_flying_saucer"
7 | s.version = ActsAsFlyingSaucer::VERSION
8 | s.platform = Gem::Platform::RUBY
9 | s.authors = ["Amar Daxini"]
10 | s.email = ["amardaxini@gmail.com"]
11 | s.homepage = "http://rubygems.org/gems/acts_as_flying_saucer"
12 | s.summary = %q{XHTML to PDF using Flying Saucer java library}
13 | s.description = %q{XHTML to PDF using Flying Saucer java library}
14 | s.add_dependency "nailgun"
15 | s.add_dependency "tidy_ffi"
16 | s.rubyforge_project = "acts_as_flying_saucer"
17 |
18 | s.files = `git ls-files`.split("\n")
19 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21 | s.require_paths = ["lib"]
22 | end
23 |
--------------------------------------------------------------------------------
/lib/acts_as_flying_saucer/config.rb:
--------------------------------------------------------------------------------
1 | module ActsAsFlyingSaucer
2 |
3 | class Config
4 | # default options
5 | class << self
6 | attr_accessor :options
7 |
8 | end
9 | ActsAsFlyingSaucer::Config.options = {
10 | :java_bin => "java",
11 | :classpath_separator => ':',
12 | :tmp_path => "/tmp",
13 | :run_mode => :once,
14 | :max_memory_mb => 50,
15 | :nailgun=> false,
16 | :nailgun_port => '2113',
17 | :nailgun_host => 'localhost',
18 | }
19 | def self.setup_nailgun
20 | if ActsAsFlyingSaucer::Config.options[:nailgun]
21 | Nailgun::NailgunConfig.options= {
22 | :java_bin => ActsAsFlyingSaucer::Config.options[:java_bin],
23 | :server_address => ActsAsFlyingSaucer::Config.options[:nailgun_host],
24 | :port_no=>ActsAsFlyingSaucer::Config.options[:nailgun_port]
25 | }
26 | Nailgun::NailgunServer.new(["start"]).daemonize
27 | count =0
28 | while(!system("lsof -i -n -P|grep #{ActsAsFlyingSaucer::Config.options[:nailgun_port]}") && count<9)
29 | sleep(1)
30 | count+=1
31 | end
32 | java_dir = File.join(File.expand_path(File.dirname(__FILE__)), "java")
33 | Dir.glob("#{java_dir}/jar/*.jar") do |jar|
34 | Nailgun::NgCommand.ng_cp(jar)
35 | end
36 | # ADD IN NAILGUN CLASS
37 |
38 | Nailgun::NgCommand.ng_alias("Xhtml2Pdf","acts_as_flying_saucer.Xhtml2Pdf")
39 | Nailgun::NgCommand.ng_alias("encryptPdf", "acts_as_flying_saucer.encryptPdf")
40 |
41 | end
42 | end
43 | # cattr_accessor :options
44 | end
45 |
46 | end
47 |
--------------------------------------------------------------------------------
/lib/acts_as_flying_saucer/xhtml2pdf.rb:
--------------------------------------------------------------------------------
1 | require 'nailgun'
2 | module ActsAsFlyingSaucer
3 |
4 | # Xhtml2Pdf
5 | #
6 | class Xhtml2Pdf
7 | def self.write_pdf(options)
8 |
9 | if !File.exists?(options[:input_file])
10 | File.open(options[:input_file], 'w') do |file|
11 | file << options[:html]
12 | end
13 | end
14 | class_path = ""
15 | java_dir = File.join(File.expand_path(File.dirname(__FILE__)), "java")
16 | Dir.glob("#{java_dir}/jar/*.jar") do |jar|
17 | class_path << "#{options[:classpath_separator]}#{jar}"
18 | end
19 |
20 |
21 | if options[:nailgun]
22 | command = "#{Nailgun::NgCommand::NGPATH} --nailgun-server #{ActsAsFlyingSaucer::Config.options[:nailgun_host]} --nailgun-port #{ ActsAsFlyingSaucer::Config.options[:nailgun_port]} Xhtml2Pdf #{options[:input_file]} #{options[:output_file]}"
23 | else
24 | command = "#{options[:java_bin]} -Xmx#{options[:max_memory_mb]}m -Djava.awt.headless=true -cp #{class_path} acts_as_flying_saucer.Xhtml2Pdf #{options[:input_file]} #{options[:output_file]}"
25 | end
26 | system(command)
27 | end
28 |
29 | def self.encrypt_pdf(options,output_file_name,password)
30 | java_dir = File.join(File.expand_path(File.dirname(__FILE__)), "java")
31 | class_path = "'.#{options[:classpath_separator]}#{java_dir}/jar/acts_as_flying_saucer.jar'"
32 | if options[:nailgun]
33 | command = "#{Nailgun::NgCommand::NGPATH} --nailgun-server #{ActsAsFlyingSaucer::Config.options[:nailgun_host]} --nailgun-port #{ ActsAsFlyingSaucer::Config.options[:nailgun_port]} encryptPdf #{options[:input_file]} #{options[:output_file]}"
34 | else
35 | command = "#{options[:java_bin]} -Xmx#{options[:max_memory_mb]}m -Djava.awt.headless=true -cp #{class_path} acts_as_flying_saucer.encryptPdf #{options[:output_file]} #{output_file_name} #{password}"
36 | end
37 | system(command)
38 | end
39 |
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/acts_as_flying_saucer/acts_as_flying_saucer_controller.rb:
--------------------------------------------------------------------------------
1 | # ActsAsFlyingSaucer
2 | module ActsAsFlyingSaucer
3 |
4 | module Controller
5 | def self.included(base)
6 | base.extend(ClassMethods)
7 | end
8 |
9 | private
10 |
11 | # ClassMethods
12 | #
13 | module ClassMethods
14 |
15 | # acts_as_flying_saucer
16 | #
17 | def acts_as_flying_saucer
18 | self.send(:include, ActsAsFlyingSaucer::Controller::InstanceMethods)
19 | class_eval do
20 | attr_accessor :pdf_mode
21 | end
22 | end
23 | end
24 |
25 | # InstanceMethods
26 | #
27 | module InstanceMethods
28 |
29 | # render_pdf
30 | #
31 | def render_pdf(options = {})
32 | tidy_clean = options[:clean] || false
33 | self.pdf_mode = :create
34 | if options[:url]
35 | tidy_clean = true
36 | if options[:url].match(/\Ahttp/)
37 | html = Net::HTTP.get_response(URI.parse(options[:url])).body rescue options[:url]
38 | elsif File.exist?(options[:url])
39 | html = File.read(options[:url]) rescue ""
40 | else
41 | html = options[:url]
42 | end
43 | elsif defined?(Rails)
44 | host = ActionController::Base.asset_host
45 | ActionController::Base.asset_host = request.protocol + request.host_with_port if host.blank?
46 | html = render_to_string options
47 | if options[:debug_html]
48 | # ActionController::Base.asset_host = host
49 | response.header["Content-Type"] = "text/html; charset=utf-8"
50 | render :text => html and return
51 | end
52 | #sinatra
53 | elsif defined?(Sinatra)
54 | html = options[:template]
55 | if options[:debug_html]
56 | response.header["Content-Type"] = "text/html; charset=utf-8"
57 | response.body << html and return
58 | end
59 | end
60 | # saving the file
61 | tmp_dir = ActsAsFlyingSaucer::Config.options[:tmp_path]
62 | html = TidyFFI::Tidy.new(html,:output_xhtml=>true,:numeric_entities=>true).clean if tidy_clean
63 | html_digest = Digest::MD5.hexdigest(html)
64 | input_file =File.join(File.expand_path("#{tmp_dir}"),"#{html_digest}.html")
65 |
66 | #logger.debug("html file: #{input_file}")
67 |
68 | output_file = (options.has_key?(:pdf_file)) ? options[:pdf_file] : File.join(File.expand_path("#{tmp_dir}"),"#{html_digest}.pdf")
69 | password = (options.has_key?(:password)) ? options[:password] : ""
70 |
71 |
72 | generate_options = ActsAsFlyingSaucer::Config.options.merge({
73 | :input_file => input_file,
74 | :output_file => output_file,
75 | :html => html
76 | })
77 |
78 | ActsAsFlyingSaucer::Xhtml2Pdf.write_pdf(generate_options)
79 | if password != ""
80 | op=output_file.split(".")
81 | op.pop
82 | op << "a"
83 | op=op.to_s+".pdf"
84 | output_file_name = op
85 | ActsAsFlyingSaucer::Xhtml2Pdf.encrypt_pdf(generate_options,output_file_name,password)
86 | output_file = op
87 | end
88 | # restoring the host
89 | if defined?(Rails)
90 | ActionController::Base.asset_host = host
91 | end
92 |
93 | # sending the file to the client
94 | if options[:send_to_client] == false
95 | output_file
96 | else
97 | if options[:send_file]
98 |
99 | send_file_options = {
100 | :filename => File.basename(output_file)
101 | #:x_sendfile => true,
102 | }
103 | send_file_options.merge!(options[:send_file]) if options.respond_to?(:merge)
104 | send_file(output_file, send_file_options)
105 | end
106 | end
107 | end
108 | end
109 | end
110 |
111 | end
112 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | acts\_as\_flying\_saucer
2 | =====================
3 |
4 | acts\_as\_flying\_saucer is a library that allows to save rendered views as pdf documents using the [Flying Saucer][1] java library.
5 |
6 | [1]: https://code.google.com/p/flying-saucer/
7 |
8 | Install
9 | -------
10 |
11 | Grab the last version from Github:
12 |
13 | sudo gem install acts_as_flying_saucer
14 |
15 |
16 | Requirements
17 | ------------
18 |
19 | * JDK 1.5.x or 1.6.x
20 | * Tidy required
21 | * Ubuntu Installation
22 | * Install tidy with dev and ruby binding.
23 |
24 | * apt-get install tidy
25 | * apt-get install libtidy-dev
26 | * apt-get install libtidy-ruby
27 |
28 |
29 | Usage
30 | -----
31 | ### Rails
32 | Just call the acts\_as\_flying\_saucer method inside the controller you want to enable to generate pdf documents.
33 | Then you can call the render\_pdf method.
34 | It accepts the same options as ActionController::Base#render plus the following ones:
35 |
36 |
37 | :pdf\_file - absolute path for the generated pdf file.
38 |
39 | :send\_file - sends the generated pdf file to the browser. It's the hash the ActionController::Streaming#send\_file method will receive.
40 |
41 | :password attach password for generated pdf
42 |
43 | :debug_html - (boolean expected) generates html output to the browser for debugging purposes
44 |
45 | :clean - (boolean expected) It cleans up html using tidy (It uses tidy_ffi gem) default is false
46 |
47 | :send_to_client - (boolean expected) If it is false it returns output pdf and attachment is not sent to client.
48 |
49 | :url - url can be file path or any http url (with http or https) or string. This will generated pdf from file,url or string.
50 |
51 | class FooController < ActionController::Base
52 | acts_as_flying_saucer
53 |
54 | def create
55 | render_pdf :template => 'foo/pdf_template'
56 | end
57 | end
58 |
59 |
60 |
61 |
62 | Examples
63 | --------
64 |
65 | # Renders the template located at '/foo/bar/pdf.html.erb' and stores the pdf
66 | # in the temp path with a filename based on its md5 digest
67 | render_pdf :file => '/foo/bar/pdf.html.erb'
68 |
69 | # renders the template located at 'app/views/foo.html.erb' and saves the pdf
70 | # in '/www/docs/foo.pdf'
71 | render_pdf :template => 'foo', :pdf_file => '/www/docs/foo.pdf'
72 |
73 | # renders the 'app/views/foo.html.erb' template, saves the pdf in the temp path
74 | # and sends it to the browser with the name 'bar.pdf'
75 | render_pdf :template => 'foo', :send_file => { :filename => 'bar.pdf' }
76 |
77 | # To send file with password protection
78 | render_pdf :template => 'foo', :send_file => { :filename => 'bar.pdf' },:password=>"xxx"
79 | Now pdf is password protected
80 |
81 | Easy as pie
82 |
83 | While converting the xhtml document into a pdf, the css stylesheets and images should be referenced with absolute URLs(either local or remote) or Flying Saucer will not be able to access them.
84 | If there is no asset host defined, it will set automatically during the pdf generation so the parser can access the requested resources:
85 |
86 | View rendered in the browser:
87 |
88 | <%= stylesheet_link_tag("styles.css") %>
89 | #
90 |
91 |
92 | <%= image_tag("rails.png") %>
93 | #
94 |
95 | View rendered as pdf:
96 |
97 | <%= stylesheet_link_tag("styles.css") %>
98 | #
99 |
100 |
101 | <%= image_tag("rails.png") %>
102 | #
103 |
104 | The stylesheet media type will be set to 'print' if none was given(otherwise it would not be parsed)
105 |
106 | If you need to distinguish if the view is being rendered in the browser or as a pdf, you can use the @pdf\_mode variable, whose value will be set to :create
107 | when generating the pdf version
108 |
109 | Sinatra
110 | -------
111 | get '/' do
112 | content_type 'application/pdf'
113 | html_content = erb :index
114 | render_pdf(:template=>html_content,:pdf_file=>"test.pdf",
115 | :send_file=> {:file_name=>"test.pdf",:stream=>false},:password=>"xxx")
116 | end
117 | Ruby
118 | ----
119 | require 'digest/sha1'
120 | class Pdf
121 | include ActsAsFlyingSaucer::Controller
122 | acts_as_flying_saucer
123 | def generate_pdf(input_file_html_or_string,output_pdf)
124 | options = ActsAsFlyingSaucer::Config.options.merge({:url=>input_file_html_or_string,:pdf_file=>output_pdf})
125 | render_pdf(options)
126 | end
127 | end
128 |
129 |
130 | Configuration
131 | -------------
132 |
133 | These are the default settings which can be overwritten in your enviroment configuration file:
134 |
135 | ActsAsFlyingSaucer::Config.options = {
136 | :java_bin => "java", # java binary
137 | :classpath_separator => ':', # classpath separator. unixes system use ':' and windows ';'
138 | :tmp_path => "/tmp", # path where temporary files will be stored
139 | :max_memory_mb=>512,
140 | :nailgun =>false,
141 | :nailgun_port => '2113',
142 | :nailgun_host => 'localhost'
143 | }
144 |
145 |
146 | Advance Configuration (TODO: Manually)
147 | -------------------
148 | Now acts_as_flying_saucer call java each time on creating pdf this will speed down speed of generation of pdf.To overcome this start nailgun server that reads data from specific port and rendered pdf.so there is no need to launch the jvm every time a new pdf is generated.
149 | Generate pdf with nailgun you have to overwrite Configuration make **nailgun option to true**
150 |
151 |
152 | So to start nailgun with acts_as_flying_saucer gem:
153 |
154 |
155 | sudo gem install nailgun
156 |
157 |
158 | Start nailgun server.Before starting nailgun server make sure that your **classpath environment variable** set and point to jre/lib
159 |
160 | On startup or manually write following line
161 | You can write into config/initializers/
162 | After setting nailgun host and port
163 |
164 | ActsAsFlyingSaucer::Config.setup_nailgun
165 |
166 |
167 | You can Manage nailgun using
168 |
169 | nailgun
170 |
171 | It will generate nailgun_config binary and set configuration parameter
172 | and now you can start and stop nailgun server
173 |
174 |
175 |
176 |
177 |
--------------------------------------------------------------------------------