pdf_renderer 0.0.2

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.
@@ -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
+