pdf_renderer 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ require 'pdf_renderer/helpers'
2
+ require 'pdf_renderer/base'
3
+ require 'pdf_renderer/latex'
4
+ require 'pdf_renderer/pdf'
5
+ require 'pdf_renderer/helpers/latex_helper'
@@ -0,0 +1,137 @@
1
+ module PdfRenderer
2
+ # Base class for rendering PDFs. PDFs are rendered in two passes. The first
3
+ # pass evaluates an LaTeX ERB template. In the second pass the evaluated
4
+ # output is piped through pdflatex. The resulting PDF is returned as a string.
5
+ #
6
+ # === Usage
7
+ #
8
+ # The usage is very similar to ActionMailer. Example:
9
+ #
10
+ # class BillPdfRenderer < PdfRenderer::Base
11
+ # def bill(model)
12
+ # body :variable => model
13
+ # end
14
+ # end
15
+ #
16
+ # Render the PDF using
17
+ #
18
+ # pdf_as_string = BillPdfRenderer.render_bill(model)
19
+ #
20
+ # The default template path is
21
+ # <code>underscored_class_name/action_name.pdf.erb</code> and is looked for in
22
+ # all specified view_paths.
23
+ #
24
+ # === Saving PDFs
25
+ #
26
+ # You can also use PdfRenderer::Base to generate and save PDFs to the file
27
+ # system. Instead of calling <code>render_</code>
28
+ #
29
+ # === Helpers
30
+ #
31
+ # To use view helpers, declare them in class scope. You can declare them as
32
+ # symbols, strings or constants, camelized or underscored. Omit the "Helper"
33
+ # suffix when using strings or symbols.
34
+ #
35
+ # The LatexHelper included in the PdfRenderer gem is automatically added to
36
+ # the ActionView instance. This helper contains methods for escaping strings
37
+ # to LaTeX.
38
+ #
39
+ # === Options
40
+ #
41
+ # Inside of render actions, there are a couple of options you can use:
42
+ #
43
+ # preprocess:: Run pdflatex twice for assigning page numbers etc.
44
+ # debug:: Save the rendered tex source to the file system for debugging.
45
+ class Base
46
+ include ActionMailer::AdvAttrAccessor
47
+ include PdfRenderer::Helpers
48
+
49
+ adv_attr_accessor :body, :template_name, :preprocess, :debug
50
+
51
+ # Contains the input for LaTeX
52
+ attr_reader :tex_out
53
+
54
+ # Paths where views are looked for in.
55
+ class_inheritable_array :view_paths
56
+
57
+ # Sets default options
58
+ #
59
+ # preprocess:: <code>false</code>
60
+ # debug:: <code>false</code>
61
+ def initialize
62
+ preprocess false
63
+ debug false
64
+ end
65
+
66
+ # Depending on the method pattern, does one of three things:
67
+ #
68
+ # * If the method name starts with <code>render_</code>, the action is
69
+ # called on a new instance of the renderer and the rendered PDF is
70
+ # returned as a string.
71
+ # * If the method starts with <code>save_</code>, the action is called,
72
+ # the PDF is generated and saved to the path given in the first method
73
+ # argument.
74
+ # * Otherwise, the action is called on a new instance of the renderer and a
75
+ # Pdf object is returned.
76
+ def self.method_missing(method, *params)
77
+ if method.to_s =~ /^render_(.*)$/
78
+ pdf = send($1, *params)
79
+ pdf.render!
80
+ elsif method.to_s =~ /^save_(.*)$/
81
+ file_name = params.shift
82
+ pdf = send($1, *params)
83
+ pdf.save(file_name)
84
+ elsif instance_methods.include?(method.to_s)
85
+ renderer_instance = new
86
+ renderer_instance.template_name = method
87
+ renderer_instance.send(renderer_instance.template_name, *params)
88
+ Pdf.new(renderer_instance)
89
+ else
90
+ super
91
+ end
92
+ end
93
+
94
+ # The root for view files.
95
+ def self.template_root
96
+ "#{RAILS_ROOT}/app/views"
97
+ end
98
+
99
+ # The directory in which templates are stored by default for this renderer.
100
+ def template_dir
101
+ self.class.name.underscore
102
+ end
103
+
104
+ # The complete path to the LaTeX ERB template.
105
+ def template_path
106
+ "#{template_dir}/#{template_name}"
107
+ end
108
+
109
+ # Delegates the render call to ActionView and runs the resulting LaTeX code
110
+ # through pdflatex.
111
+ def render(options)
112
+ @tex_out = template_instance.render options
113
+ Latex.new(:preprocess => preprocess, :debug => debug).generate_pdf(tex_out)
114
+ end
115
+
116
+ # Renders the default template.
117
+ def render!
118
+ render :file => template_path
119
+ end
120
+
121
+ protected
122
+ def self.template_class
123
+ @template_class ||= returning Class.new(ActionView::Base) do |view_class|
124
+ view_class.send(:include, ApplicationController.master_helper_module) if Object.const_defined?(:ApplicationController)
125
+ view_class.send(:include, PdfRenderer::Helpers::LatexHelper)
126
+ view_class.send(:include, self.master_helper_module)
127
+ end
128
+ end
129
+
130
+ def template_instance
131
+ returning self.class.template_class.new(self.class.template_root, body || {}, self) do |view|
132
+ view.template_format = :pdf
133
+ view.view_paths = ActionView::Base.process_view_paths(self.view_paths)
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,33 @@
1
+ module PdfRenderer
2
+ # Contains functionality for using helper modules in the views when rendering
3
+ # LaTeX ERB files.
4
+ module Helpers
5
+ def self.included(base)
6
+ base.class_inheritable_array :helpers
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ # Class-level method that declares <code>helpers</code> as helpers for
12
+ # inclusion in the view. Specify the helpers as <code>:symbol</code>,
13
+ # <code>'string'</code>, or <code>ConstantName</code>.
14
+ def helper(*helpers)
15
+ write_inheritable_array :helpers, helpers
16
+ end
17
+
18
+ protected
19
+ def master_helper_module
20
+ @master_helper_module ||= returning(Module.new) do |mod|
21
+ (helpers || []).each do |helper|
22
+ case helper
23
+ when Module
24
+ mod.send(:include, helper)
25
+ when String, Symbol
26
+ mod.send(:include, "#{helper.to_s}_helper".camelize.constantize)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,28 @@
1
+ module PdfRenderer
2
+ module Helpers
3
+ # Contains methods for escaping strings for LaTeX.
4
+ module LatexHelper
5
+ BS = "\\\\"
6
+ BACKSLASH = "#{BS}textbackslash{}"
7
+ HAT = "#{BS}textasciicircum{}"
8
+ TILDE = "#{BS}textasciitilde{}"
9
+
10
+ # Escapes the string, so it is suitable for LaTeX input. This method is
11
+ # aliased as <code>l</code>.
12
+ def latex_escape(s)
13
+ quote_count = 0
14
+ s.to_s.
15
+ gsub(/([{}_$&%#])/, "__LATEX_HELPER_TEMPORARY_BACKSLASH_PLACEHOLDER__\\1").
16
+ gsub(/\\/, BACKSLASH).
17
+ gsub(/__LATEX_HELPER_TEMPORARY_BACKSLASH_PLACEHOLDER__/, BS).
18
+ gsub(/\^/, HAT).
19
+ gsub(/~/, TILDE).
20
+ gsub(/"/) do
21
+ quote_count += 1
22
+ quote_count.odd? ? %{"`} : %{"'}
23
+ end
24
+ end
25
+ alias :l :latex_escape
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,110 @@
1
+ module PdfRenderer
2
+ # This error is raised when no suitable LaTeX executable is found.
3
+ class LatexProcessorNotFound < StandardError; end
4
+ # This error is raised when there is a syntax error in the LaTeX input.
5
+ class LatexError < StandardError; end
6
+ # This error is raised when there is an illegal character in the LaTeX input.
7
+ class InvalidCharacter < StandardError; end
8
+
9
+ # This class wraps the LaTeX command invocation.
10
+ class Latex
11
+ attr_reader :options
12
+
13
+ # Option redirection for shell output (default is '> /dev/null 2>&1' )
14
+ cattr_accessor :shell_redirect
15
+ self.shell_redirect = '> /dev/null 2>&1'
16
+ # Temporary Directory
17
+ cattr_accessor :tempdir
18
+ self.tempdir = "#{File.expand_path(RAILS_ROOT)}/tmp"
19
+ # tex command to run
20
+ cattr_accessor :tex_command
21
+ self.tex_command = "pdflatex"
22
+
23
+ def initialize(options = {})
24
+ @options = options
25
+ end
26
+
27
+ # Returns the rendered PDF as string for the given input string (LaTeX
28
+ # source).
29
+ def generate_pdf(input)
30
+ create_debug_output(input) if options[:debug]
31
+ check_for_tex_presence!
32
+ create_pdf(input)
33
+ end
34
+
35
+ protected
36
+ def processor
37
+ self.class.tex_command
38
+ end
39
+
40
+ def run(command)
41
+ %x{#{command}}
42
+ end
43
+
44
+ def create_debug_output(output)
45
+ File.open("pdf_renderer_out.tex", "wb") {|f| f.puts output}
46
+ rescue
47
+ end
48
+
49
+ def check_for_tex_presence!
50
+ system_path = ENV["PATH"]
51
+
52
+ # This is one big ugly platform dependent kludge, but it's necessary.
53
+ # See: http://lists.radiantcms.org/pipermail/radiant/2007-April/004473.html
54
+ # In short, apache doesn't see environment variables like PATH.
55
+ system_path = "/bin:/usr/bin:/usr/local/bin" if system_path.nil?
56
+
57
+ # Check for the presence of the tex processor in the path.
58
+ unless File.executable?(processor) or system_path.split(":").any?{|path| File.executable?(File.join(path, processor))}
59
+ raise LatexProcessorNotFound
60
+ end
61
+ end
62
+
63
+ def create_pdf(input)
64
+ create_temp_dir("pdf_renderer", self.class.tempdir) do
65
+ basename = "processed_pdf_renderer_file"
66
+ texfile = File.open("#{basename}.tex", "wb")
67
+ texfile.write(input)
68
+ texfile.close
69
+
70
+ tex_command = "#{processor} -interaction=nonstopmode #{texfile.path} #{self.class.shell_redirect}"
71
+ tex_return = ''
72
+ tex_return = run(tex_command)
73
+ tex_return = run(tex_command) if options[:preprocess] # One can wonder if it matters if it's always run twice...
74
+
75
+ if File.exists?("#{basename}.pdf")
76
+ # For some reason, File.read doesn't work, hence the call using the block
77
+ File.open("#{basename}.pdf",'rb') { |f| f.read }
78
+ else
79
+ if tex_return[/(Package inputenc Error: Unicode char .+)/,1]
80
+ raise InvalidCharacter.new($1)
81
+ end
82
+ raise LatexError.new("Could not generate PDF:\n>>#{tex_return}<<\n\nPath:#{texfile.path}\n\n\nInput:#{input}")
83
+ end
84
+ end
85
+ end
86
+
87
+ @@temporary_dir_number = 0;
88
+
89
+ # Creates a temp dir in location and performs the supplied code block
90
+ def create_temp_dir(name, location)
91
+ @@temporary_dir_number += 1
92
+ pid = Process.pid # This doesn't work on some platforms, according to the docs. A better way to get it would be nice.
93
+ random_number = Kernel.rand(1000000000).to_s # This is to avoid a possible symlink attack vulnerability in the creation of temporary files.
94
+ complete_dir_name = "#{location}/#{name}.#{pid}.#{random_number}.#{@@temporary_dir_number}"
95
+
96
+ yield_result = Object.new
97
+
98
+ FileUtils.mkdir_p(complete_dir_name)
99
+ Dir.chdir(complete_dir_name) do
100
+ begin
101
+ yield_result = yield
102
+ ensure
103
+ FileUtils.rmtree([complete_dir_name])
104
+ end
105
+ end
106
+
107
+ yield_result
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,30 @@
1
+ module PdfRenderer
2
+ # Wrapper class that represents the PDF to be rendered. An instance of this
3
+ # class is returned by a PdfRenderer, when an action is called without a
4
+ # prefix (i.e. without <code>render_</code> or <code>save_</code>).
5
+ class Pdf
6
+ # Contains the instance variable assigns
7
+ attr_accessor :body
8
+ # Contains the LaTeX source for this PDF.
9
+ attr_accessor :source
10
+ # Contains the rendered PDF as string.
11
+ attr_accessor :rendered
12
+
13
+ def initialize(renderer)
14
+ @renderer = renderer
15
+ @body = renderer.body
16
+ end
17
+
18
+ # Renders the PDF.
19
+ def render!
20
+ @rendered = @renderer.render!
21
+ @source = @renderer.tex_out
22
+ @rendered
23
+ end
24
+
25
+ # Renders the PDF and saves it to the file system.
26
+ def save(filename)
27
+ File.open(filename, 'wb') { |file| file.print render! }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ Description:
2
+ Stubs out a new PDF renderer and its views. Pass the renderer name, either
3
+ CamelCased or under_scored, without PdfRenderer or _pdf_renderer as the suffix,
4
+ and an optional list of actions as arguments.
5
+
6
+ This generates a renderer class in app/pdf_renderers, view templates in
7
+ app/views/renderer_name, and a functional test in test/functional.
8
+
9
+ Example:
10
+ `./script/generate pdf_renderer Bill bill invoice reminder`
11
+
12
+ creates a bill renderer class, views, and test:
13
+ Renderer: app/pdf_renderers/bill_pdf_renderer.rb
14
+ Views: app/views/bill_pdf_renderer/bill.pdf.erb [...]
15
+ Test: test/functional/bill_pdf_renderer_test.rb
@@ -0,0 +1,26 @@
1
+ class PdfRendererGenerator < Rails::Generator::NamedBase
2
+ def manifest
3
+ record do |m|
4
+ # Check for class naming collisions.
5
+ m.class_collisions "#{class_name}PdfRenderer", "#{class_name}PdfRendererTest"
6
+
7
+ # Renderer, view, and test directories.
8
+ m.directory File.join('app/pdf_renderers', class_path)
9
+ m.directory File.join('app/views', "#{file_path}_pdf_renderer")
10
+ m.directory File.join('test/functional', class_path)
11
+
12
+ # Renderer class and functional test.
13
+ m.template "pdf_renderer.rb", File.join('app/pdf_renderers', class_path, "#{file_name}_pdf_renderer.rb")
14
+ m.template "functional_test.rb", File.join('test/functional', class_path, "#{file_name}_pdf_renderer_test.rb")
15
+
16
+ # View template for each action.
17
+ actions.each do |action|
18
+ relative_path = File.join("#{file_path}_pdf_renderer", action)
19
+ view_path = File.join('app/views', "#{relative_path}.pdf.erb")
20
+
21
+ m.template "view.erb", view_path,
22
+ :assigns => { :action => action, :path => view_path }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ require 'test_helper'
2
+
3
+ class <%= class_name %>PdfRendererTest < ActiveSupport::TestCase
4
+ <% for action in actions -%>
5
+ test "<%= action %>" do
6
+ pdf = <%= class_name %>PdfRenderer.render_<%= action %>
7
+ assert pdf.source =~ /content/
8
+ end
9
+
10
+ <% end -%>
11
+ <% if actions.blank? -%>
12
+ # replace this with your real tests
13
+ test "the truth" do
14
+ assert true
15
+ end
16
+ <% end -%>
17
+ end
@@ -0,0 +1,8 @@
1
+ class <%= class_name %>PdfRenderer < ActionFaxer::Base
2
+ <% for action in actions -%>
3
+ def <%= action %>
4
+ body :greeting => 'Hi,'
5
+ end
6
+
7
+ <% end -%>
8
+ end
@@ -0,0 +1,11 @@
1
+ \documentclass[a4paper]{report}
2
+
3
+ \title{Test PDF}
4
+
5
+ \begin{document}
6
+
7
+ <%= class_name %>PdfRenderer#<%= action %>
8
+
9
+ Find me in <%= path %>
10
+
11
+ \end{document}
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pdf_renderer
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 2
9
+ version: 0.0.2
10
+ platform: ruby
11
+ authors:
12
+ - Thomas Kadauke
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-04-17 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description:
22
+ email: entwicker@imedo.de
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - lib/pdf_renderer/base.rb
31
+ - lib/pdf_renderer/helpers/latex_helper.rb
32
+ - lib/pdf_renderer/helpers.rb
33
+ - lib/pdf_renderer/latex.rb
34
+ - lib/pdf_renderer/pdf.rb
35
+ - lib/pdf_renderer.rb
36
+ - rails_generators/pdf_renderer/pdf_renderer_generator.rb
37
+ - rails_generators/pdf_renderer/templates/functional_test.rb
38
+ - rails_generators/pdf_renderer/templates/pdf_renderer.rb
39
+ - rails_generators/pdf_renderer/templates/view.erb
40
+ - rails_generators/pdf_renderer/USAGE
41
+ has_rdoc: true
42
+ homepage: http://www.imedo.de/
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options: []
47
+
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.3.6
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: Framework for rendering PDFs using LaTeX
71
+ test_files: []
72
+