mirror of
https://github.com/lloeki/tilt-pdf-rails.git
synced 2025-12-06 10:34:42 +01:00
Compare commits
No commits in common. "master" and "v0.9.0" have entirely different histories.
8 changed files with 336 additions and 39 deletions
98
README.mdown
98
README.mdown
|
|
@ -1,19 +1,97 @@
|
||||||
# Tilt::PDF::Rails
|
# Tilt::PDF
|
||||||
|
|
||||||
Integrates Tilt::PDF into Rails ActionView
|
Integrates PDF generation into a Tilt flow for maxxed out ease of use.
|
||||||
|
|
||||||
See [Tilt::PDF](https://github.com/lloeki/tilt-pdf) for details about the templates themselves.
|
Contrary to other solutions, all files will be rendered locally without relying
|
||||||
|
on a web server. It follows that even if used in a web server, no concurrent
|
||||||
|
requests are being made to render assets.
|
||||||
|
|
||||||
## Usage
|
## Dependencies
|
||||||
|
|
||||||
|
This gem depends on PDFKit, which in turn requires `wkhtmltopdf`. It is
|
||||||
|
recommended to use the statically compiled version of the latter, as it is
|
||||||
|
built against a patched QT that supports more features.
|
||||||
|
|
||||||
|
## Usage as a Tilt template
|
||||||
|
|
||||||
|
Add `tilt-pdf` to your Gemfile. Also add any template engine you may optionally
|
||||||
|
want, such as `less` or `slim`.
|
||||||
|
|
||||||
|
A `foo` template is currently threefold:
|
||||||
|
|
||||||
|
- `foo.rpdf`: this file is a YAML file containing options pertaining to the PDF
|
||||||
|
generation process, such as page size, orientation, metadata, support files,
|
||||||
|
headers and footers. Some options are passed as is to PDFKit, and
|
||||||
|
subsequently to `wkhtmltopdf`.
|
||||||
|
- `foo.html`: this document can be written in any template language you need
|
||||||
|
(such as ERB or Slim), and the Tilt template resolution system via extension
|
||||||
|
chaining will apply. Tilt will pass the render block to be yielded to this
|
||||||
|
document.
|
||||||
|
- `foo.css`: this stylesheet can be written in any template language you need
|
||||||
|
(such as Sass or Less), and the Tilt template resolution system via extension
|
||||||
|
chaining will apply. Tilt will *not* pass the block to be yielded to this
|
||||||
|
template.
|
||||||
|
|
||||||
|
The three files must currently be stored in the *same* directory.
|
||||||
|
|
||||||
|
Rendering is done the usual Tilt way:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
gem 'tilt-pdf-rails'
|
require 'tilt-pdf'
|
||||||
|
|
||||||
|
pdf = Tilt.new('foo.rpdf').render()
|
||||||
```
|
```
|
||||||
|
|
||||||
Have a `FooController` `respond_to :pdf` and `render some_view` as usual.
|
## The rpdf file
|
||||||
|
|
||||||
Put your `some_view` template files either *together* in the relevant
|
This file contains options. If empty, it is made to 'just work' as
|
||||||
`app/views/foo` view directory, or use absolute paths using application/engine
|
summplemental files will be looked up according to its basename.
|
||||||
root.
|
|
||||||
|
|
||||||
Work is in progress to enable better integration with Rails file layout (notably assets).
|
- `main`: document body, overriding the default derived from the basename.
|
||||||
|
- `footer` and `header`: html that will get used for (surprise!) header and
|
||||||
|
footers.
|
||||||
|
- `stylesheets`: list of stylesheets to include (used for all html, incl.
|
||||||
|
headers/footers). Defaults to one file from the basename.
|
||||||
|
- `javascripts`: list of javascripts to include (used for all html, incl.
|
||||||
|
headers/footers). Defaults to one file from the basename.
|
||||||
|
- `pdfkit`: While a few PDFKit options are made available at the toplevel for
|
||||||
|
convenience, this key passes all options as-is to PDFKit.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
title: Foorever young
|
||||||
|
page-size: A4
|
||||||
|
orientation: landscape
|
||||||
|
grayscale: true
|
||||||
|
margin-left: 0
|
||||||
|
margin-right: 0
|
||||||
|
margin-top: 0
|
||||||
|
margin-bottom: 0
|
||||||
|
pdfkit:
|
||||||
|
print-media-type: true
|
||||||
|
main: foorever_young.html.slim
|
||||||
|
stylesheets:
|
||||||
|
- novel.css.less
|
||||||
|
- common.css
|
||||||
|
javascripts:
|
||||||
|
- page_numbering.js.coffee
|
||||||
|
footer: footer.html.slim
|
||||||
|
```
|
||||||
|
|
||||||
|
Filenames can be relative or absolute. When relative, they will be evaluated
|
||||||
|
as based from the rpdf file.
|
||||||
|
|
||||||
|
## Rails and ActionView integration
|
||||||
|
|
||||||
|
Require `tilt/pdf/rails` if you want to set up and register `tilt-pdf` as an
|
||||||
|
ActionView template handler. You can do it in an initializer, or straight from
|
||||||
|
the Gemfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gem 'tilt-pdf', require: 'tilt/pdf/rails'
|
||||||
|
```
|
||||||
|
|
||||||
|
Put your three template files *together* in the relevant `app/views/foo` view
|
||||||
|
directory, or use absolute paths using application/engine root. Work is in
|
||||||
|
progress to enable better integration with Rails file layout.
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
require 'tilt/pdf/rails'
|
|
||||||
1
lib/tilt-pdf.rb
Normal file
1
lib/tilt-pdf.rb
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
require 'tilt/pdf'
|
||||||
211
lib/tilt/pdf.rb
Normal file
211
lib/tilt/pdf.rb
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
require 'pdfkit'
|
||||||
|
require 'tilt'
|
||||||
|
require 'tilt/template'
|
||||||
|
require 'yaml'
|
||||||
|
require 'tempfile'
|
||||||
|
require 'pry'
|
||||||
|
|
||||||
|
module Tilt
|
||||||
|
class PDFTemplate < Template
|
||||||
|
self.default_mime_type = 'application/pdf'
|
||||||
|
|
||||||
|
def prepare; end
|
||||||
|
|
||||||
|
def evaluate(scope, locals, &block)
|
||||||
|
files = aux_files
|
||||||
|
files << main_html_file
|
||||||
|
render_to_tmp(*files, scope, locals, block) do |tmp|
|
||||||
|
opts = pdfkit_options
|
||||||
|
|
||||||
|
if header
|
||||||
|
htmp = tmp.select { |_, f, _| f == header }.first[2]
|
||||||
|
opts.merge!('header-html' => htmp) if header
|
||||||
|
end
|
||||||
|
|
||||||
|
if footer
|
||||||
|
ftmp = tmp.select { |_, f, _| f == footer }.first[2]
|
||||||
|
opts.merge!('footer-html' => ftmp) if footer
|
||||||
|
end
|
||||||
|
|
||||||
|
main = tmp.select { |_, f, _| f == main_html_file }.first[2]
|
||||||
|
kit = PDFKit.new(File.read(main), opts)
|
||||||
|
|
||||||
|
@output = kit.to_pdf
|
||||||
|
end
|
||||||
|
|
||||||
|
@output
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def absolutize(path)
|
||||||
|
Pathname.new(path).absolute? ? path : File.join(dirname, path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def config
|
||||||
|
@config = (YAML.load(data) || {})
|
||||||
|
end
|
||||||
|
|
||||||
|
def aux_files
|
||||||
|
files = []
|
||||||
|
files.concat css_files
|
||||||
|
files.concat js_files
|
||||||
|
files << header if header
|
||||||
|
files << footer if footer
|
||||||
|
|
||||||
|
files
|
||||||
|
end
|
||||||
|
|
||||||
|
def header
|
||||||
|
if (f = config['header'])
|
||||||
|
absolutize(f)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def footer
|
||||||
|
if (f = config['footer'])
|
||||||
|
absolutize(f)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def main_html_file
|
||||||
|
main_html_from_config || find_html
|
||||||
|
end
|
||||||
|
|
||||||
|
def main_html_from_config
|
||||||
|
if (f = config['main'])
|
||||||
|
absolutize(f)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def css_files
|
||||||
|
css_from_config || find_css
|
||||||
|
end
|
||||||
|
|
||||||
|
def css_from_config
|
||||||
|
return unless config.key?('stylesheets')
|
||||||
|
|
||||||
|
config.fetch('stylesheets', []).map { |f| absolutize(f) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def js_files
|
||||||
|
js_from_config || find_js
|
||||||
|
end
|
||||||
|
|
||||||
|
def js_from_config
|
||||||
|
return unless config.key?('javascripts')
|
||||||
|
|
||||||
|
config.fetch('javascripts', []).map { |f| absolutize(f) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def pdfkit_options
|
||||||
|
toplevel_options = %w[title
|
||||||
|
orientation
|
||||||
|
grayscale
|
||||||
|
page-size
|
||||||
|
margin-left
|
||||||
|
margin-right
|
||||||
|
margin-top
|
||||||
|
margin-bottom]
|
||||||
|
|
||||||
|
options = config.select { |k, _| toplevel_options.include?(k) }
|
||||||
|
|
||||||
|
options.merge config.fetch('pdfkit', {})
|
||||||
|
end
|
||||||
|
|
||||||
|
def dirname
|
||||||
|
File.dirname(eval_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_html
|
||||||
|
Dir.glob(File.join(dirname, name + '.html*')).first
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_css
|
||||||
|
Dir.glob(File.join(dirname, name + '.css*'))
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_js
|
||||||
|
Dir.glob(File.join(dirname, name + '.js*'))
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_html(file, scope, locals, &block)
|
||||||
|
Tilt.new(file).render(scope, locals, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def inject_css!(document, stylesheet)
|
||||||
|
append_to_head!(document, "<style>#{stylesheet}</style>")
|
||||||
|
end
|
||||||
|
|
||||||
|
def inject_js!(document, script)
|
||||||
|
append_to_head!(document, "<script>#{script}</script>")
|
||||||
|
end
|
||||||
|
|
||||||
|
def append_to_head!(document, tag)
|
||||||
|
if document.match(/<\/head>/)
|
||||||
|
document.gsub!(/(<\/head>)/) { |s| tag + s }
|
||||||
|
else
|
||||||
|
document.insert(0, tag)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_to_tmp(*files, scope, locals, block)
|
||||||
|
tmps = []
|
||||||
|
no_tilt = %w[html css js]
|
||||||
|
|
||||||
|
result = files.map do |file|
|
||||||
|
ext = File.extname(file).sub(/^\./, '')
|
||||||
|
if no_tilt.include?(ext)
|
||||||
|
mime = case ext
|
||||||
|
when 'js', 'javascript' then 'application/javascript'
|
||||||
|
else "text/#{ext}"
|
||||||
|
end
|
||||||
|
rendered = File.read(file)
|
||||||
|
else
|
||||||
|
template = Tilt.new(file)
|
||||||
|
mime = template.class.default_mime_type
|
||||||
|
ext = mime.split('/').last
|
||||||
|
rendered = template.render(scope, locals, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
[ext, mime, file, rendered]
|
||||||
|
end
|
||||||
|
|
||||||
|
result.sort! do |a, b|
|
||||||
|
a[1] <=> b[1] # css < html
|
||||||
|
end
|
||||||
|
|
||||||
|
styles = result.select { |_, mime, _, _| mime == 'text/css' }
|
||||||
|
scripts = result.select { |_, mime, _, _| mime == 'application/javascript' }
|
||||||
|
result.map! do |ext, mime, file, rendered|
|
||||||
|
if mime == 'text/html'
|
||||||
|
styles.each do |_, _, _, style|
|
||||||
|
inject_css!(rendered, style)
|
||||||
|
end
|
||||||
|
scripts.each do |_, _, _, script|
|
||||||
|
inject_js!(rendered, script)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
[ext, mime, file, rendered]
|
||||||
|
end
|
||||||
|
|
||||||
|
result.map! do |ext, mime, file, rendered|
|
||||||
|
tmp = Tempfile.new([File.basename(file), '.' + ext])
|
||||||
|
tmps << tmp
|
||||||
|
tmp.write(rendered)
|
||||||
|
tmp.close
|
||||||
|
|
||||||
|
path = tmp.path
|
||||||
|
|
||||||
|
[mime, file, path]
|
||||||
|
end
|
||||||
|
|
||||||
|
yield result
|
||||||
|
ensure
|
||||||
|
tmps.each { |tmp| tmp.close! }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Tilt.register Tilt::PDFTemplate, 'rpdf'
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
module Tilt
|
|
||||||
module PDF
|
|
||||||
module Rails
|
|
||||||
VERSION = '0.10.0'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
5
lib/tilt/pdf/version.rb
Normal file
5
lib/tilt/pdf/version.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
module Tilt
|
||||||
|
module PDF
|
||||||
|
VERSION = '0.9.0'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
# -*- encoding: utf-8 -*-
|
|
||||||
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
|
||||||
require 'tilt/pdf/rails/version'
|
|
||||||
|
|
||||||
Gem::Specification.new do |s|
|
|
||||||
s.name = 'tilt-pdf-rails'
|
|
||||||
s.version = Tilt::PDF::Rails::VERSION
|
|
||||||
s.authors = ['Loic Nageleisen']
|
|
||||||
s.email = ['loic.nageleisen@gmail.com']
|
|
||||||
s.homepage = 'http://github.com/lloeki/tilt-pdf-rails'
|
|
||||||
s.summary = %q{PDF files in Rails via Tilt}
|
|
||||||
s.description = %q{Integrates PDF generation with Tilt::PDF into ActionView}
|
|
||||||
s.license = 'MIT'
|
|
||||||
s.files = `git ls-files`.split("\n")
|
|
||||||
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
||||||
s.executables = `git ls-files -- bin/*`.split("\n")
|
|
||||||
.map { |f| File.basename(f) }
|
|
||||||
s.require_paths = ['lib']
|
|
||||||
|
|
||||||
s.add_dependency 'tilt-pdf', '~> 0.10.0'
|
|
||||||
end
|
|
||||||
31
tilt-pdf.gemspec
Normal file
31
tilt-pdf.gemspec
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
||||||
|
require 'tilt/pdf/version'
|
||||||
|
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = 'tilt-pdf'
|
||||||
|
s.version = Tilt::PDF::VERSION
|
||||||
|
s.authors = ['Loic Nageleisen']
|
||||||
|
s.email = ['loic.nageleisen@gmail.com']
|
||||||
|
s.homepage = 'http://github.com/lloeki/tilt-pdf'
|
||||||
|
s.summary = %q{PDF files via Tilt}
|
||||||
|
s.description = %q{Integrates PDF generation into a Tilt flow}
|
||||||
|
s.license = 'MIT'
|
||||||
|
s.files = `git ls-files`.split("\n")
|
||||||
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
||||||
|
s.executables = `git ls-files -- bin/*`.split("\n")
|
||||||
|
.map { |f| File.basename(f) }
|
||||||
|
s.require_paths = ['lib']
|
||||||
|
|
||||||
|
s.add_dependency 'tilt', '~> 1.4.1'
|
||||||
|
s.add_dependency 'pdfkit', '~> 0.5.4'
|
||||||
|
|
||||||
|
s.add_development_dependency 'therubyracer'
|
||||||
|
s.add_development_dependency 'less'
|
||||||
|
s.add_development_dependency 'coffee-script'
|
||||||
|
s.add_development_dependency 'slim'
|
||||||
|
s.add_development_dependency 'rspec', '~> 2.14'
|
||||||
|
s.add_development_dependency 'rake'
|
||||||
|
s.add_development_dependency 'guard-rspec'
|
||||||
|
s.add_development_dependency 'pry'
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue