module ThousandIsland # The Template class is where you can define elements that may be common to # all (or some) documents within your application. It is likely that a common # style will be required, so defining it in a Template and then using that # Template subclass in any custom Builders DRYs up your pdf generation, as # well as allowing for easy restyling across the whole application. # # Typically, the Template subclass would define the settings for the PrawnDocument, # as well as the settings for the header and footer. See the Docs below for the # settings method for the defaults. Add your own or override any # existing settings in the settings method. Any options passed into # the constructor as a Hash will be merged with these settings, and the defaults. # # Content for the header and footer will be defined in the methods # header_content and footer_content. These methods are # passed as a block when the pdf is rendered. Any standard Prawn methods may be # used (including bounding boxes or any other layout tools). In addition, any # of the styles from the StyleSheet can be applied as helper methods. # For instance, the default style sheet has a h1_style method that # returns a ThousandIsland::StyleHash, so in your code you can use: # h1 "My Document Header" # and Prawn will render the text in the style set in the h1_style # ThousandIsland::StyleHash. # # In addition to the supplied style methods, you can create a custom method: # def magic_style # ThousandIsland::StyleHash.new({ # size: 15 # style: bold # }) # end # As long as the method ends in the word "_style" and returns a Hash, you magically # get to do this: # magic "My magic text is bold and size 15!!" # The method may return a standard Hash, but it is safer to return a # ThousandIsland::StyleHash, as this dynamically duplicates a few keys to accommodate # using the style in normal Prawn text methods as well as formatted text boxes, which # use a slightly different convention. You don't have to worry about that if you use # the ThousandIsland::StyleHash. # # Alternatively, your method could do this: # def magic_style # h1_style.merge({ # size: 15 # style: bold # }) # end # # The following is an example of a custom template that subclasses # ThousandIsland::Template - # # class MyTemplate < ThousandIsland::Template # include MyCustomStyleSheet # optional # # # settings here are merged with and override the defaults # def settings # { # header: { # height: 55, # render:true, # repeated: true # }, # footer: { # render:true, # height: 9, # numbering_string: 'Page of ', # repeated: true # } # } # end # # def header_content # pdf.image "#{pdf_images_path}/company_logo.png", height: 30 # Standard Prawn syntax # end # # def footer_content # footer "www.mycompanyurl.com" # Using the magic method we get from the footer_style # end # # def pdf_images_path # "#{Rails.root}/app/assets/pdf_images" # This is entirely up to you # end # end # # Nb. # The Footer is a three column layout, with the numbering on the right column # and the content defined here in the middle. More flexibility will be added # in a later version. # # Optional: # # Add a body_content method to add content before whatever the # Builder defines in it's method of the same name. # class Template include ThousandIsland::StyleSheet attr_reader :pdf, :pdf_options def initialize(options={}) setup_document_options(options) setup_prawn_document calculate_bounds end # Override in inheriting class to override defaults. The default settings # are: # page_size: 'A4', # page_layout: :portrait, # left_margin: 54, # right_margin: 54, # header: { # render: true, # height: 33, # bottom_padding: 20, # repeated: true # }, # footer: { # render: true, # height: 33, # top_padding: 20, # repeated: true, # number_pages: true, # numbering_string: '', # numbering_options: { # align: :right, # start_count_at: 1, # } # The settings in the hash will be merged with the default settings. Any Prawn # setting should be valid at the top level of the hash. # The styles used in the Header and Footer are determined by the default styles in the # StyleSheet, but can be overridden in your Template class or by building your own StyleSheet def settings {} end def draw_body(&block) body_obj.draw do body_content if respond_to? :body_content yield if block_given? end end def draw_header header_obj.draw do header_content if respond_to? :header_content yield if block_given? end if render_header? end def draw_footer(&block) footer_obj.draw do yield if block_given? footer_content &block if respond_to? :footer_content end if render_footer? end private def render_header? pdf_options[:header][:render] end def render_footer? pdf_options[:footer][:render] end def calculate_bounds pdf_options[:body][:top] = body_start pdf_options[:body][:height] = body_height end def body_start @body_start ||= pdf.bounds.height - header_space end def body_height @body_height ||= body_start - footer_space end def header_space return (pdf_options[:header][:height] + pdf_options[:header][:bottom_padding]) if pdf_options[:header][:render] 0 end def footer_space return (pdf_options[:footer][:height] + pdf_options[:footer][:top_padding]) if pdf_options[:footer][:render] 0 end def setup_prawn_document @pdf = Prawn::Document.new(pdf_options) end def setup_document_options(options={}) @pdf_options = deep_merger.merge_options(options, settings, defaults, component_defaults) end def component_defaults components = { footer: footer_klass.defaults, header: header_klass.defaults, body: body_klass.defaults, } components[:footer][:style] = footer_style components end def header_klass Components::Header end def header_obj @header ||= header_klass.new(pdf, pdf_options[:header]) end def footer_klass Components::Footer end def footer_obj @footer ||= footer_klass.new(pdf, pdf_options[:footer]) end def body_klass Components::Body end def body_obj @body ||= body_klass.new(pdf, pdf_options[:body]) end def deep_merger @deep_merger ||= Utilities::DeepMerge end # Called by method missing when a style is supplied with text, ie: h1 'Header' def render_with_style(style, output) style_values = send("#{style}_style") pdf.text output, style_values end def defaults { page_size: 'A4', page_layout: :portrait, left_margin: 54, right_margin: 54, header: { render: true, }, footer: { render: true, }, body: {}, } end #Respond to methods that relate to the style_sheet known styles def method_missing(method_name, *arguments, &block) style_method = "#{method_name}_style" if respond_to?(style_method) render_with_style(method_name, arguments[0]) else super end end def respond_to_missing?(method_name, *) available_styles.include?(method_name) || super end end end