class Ruport::Formatter::PDF
This class provides PDF output for Ruport's Table, Group, and Grouping controllers. It wraps Austin Ziegler's PDF::Writer to provide a higher level interface and provides a number of helpers designed to make generating PDF reports much easier. You will typically want to build subclasses of this formatter to customize it as needed.
Many methods forward options to PDF::Writer, so you may wish to consult its API docs.
Rendering Options¶ ↑
General: * paper_size #=> "LETTER" * paper_orientation #=> :portrait Text: * text_format (sets options to be passed to add_text by default) Table: * table_format (a hash that can take any of the options available to PDF::SimpleTable) * table_format[:maximum_width] #=> 500 Grouping: * style (:inline,:justified,:separated,:offset)
Attributes
Public Class Methods
# File lib/ruport/formatter/pdf.rb, line 67 def initialize Ruport.quiet do require "pdf/writer" require "pdf/simpletable" end end
If you use this macro in your formatter, Ruport will automatically forward calls to the underlying PDF::Writer, for any methods that are not wrapped or redefined.
# File lib/ruport/formatter/pdf.rb, line 61 def self.proxy_to_pdf_writer include PDFWriterProxy end
Public Instance Methods
Call PDF::Writer#text with the given arguments, using
text_format
defaults, if they are defined.
Example:
options.text_format { :font_size => 14 } add_text("Hello Joe") #renders at 14pt add_text("Hello Mike",:font_size => 16) # renders at 16pt
# File lib/ruport/formatter/pdf.rb, line 147 def add_text(text, format_opts={}) format_opts = options.text_format.merge(format_opts) if options.text_format pdf_writer.text(text, format_opts) end
Hook for setting available options using a template. See the template documentation for the available options and their format.
# File lib/ruport/formatter/pdf.rb, line 76 def apply_template apply_page_format_template(template.page) apply_text_format_template(template.text) apply_table_format_template(template.table) apply_column_format_template(template.column) apply_heading_format_template(template.heading) apply_grouping_format_template(template.grouping) end
Renders the group as a table for Controller::Group.
# File lib/ruport/formatter/pdf.rb, line 113 def build_group_body render_table data, options.to_hash.merge(:formatter => pdf_writer) end
Generates a header with the group name for Controller::Group.
# File lib/ruport/formatter/pdf.rb, line 108 def build_group_header pad(10) { add_text data.name.to_s, :justification => :center } end
Determines which style to use and renders the main body for Controller::Grouping.
# File lib/ruport/formatter/pdf.rb, line 119 def build_grouping_body case options.style when :inline render_inline_grouping(options.to_hash.merge(:formatter => pdf_writer, :skip_finalize_table => true)) when :justified, :separated render_justified_or_separated_grouping when :offset render_offset_grouping else raise NotImplementedError, "Unknown style" end end
Calls the #draw_table method.
# File lib/ruport/formatter/pdf.rb, line 96 def build_table_body draw_table(data) end
-
If the image is bigger than the box, it will be scaled down until it fits.
-
If the image is smaller than the box, it won't be resized.
options:
-
:x: left bound of box
-
:y: bottom bound of box
-
:width: width of box
-
:height: height of box
# File lib/ruport/formatter/pdf.rb, line 167 def center_image_in_box(path, image_opts={}) x = image_opts[:x] y = image_opts[:y] width = image_opts[:width] height = image_opts[:height] info = ::PDF::Writer::Graphics::ImageInfo.new(File.open(path, "rb")) # reduce the size of the image until it fits into the requested box img_width, img_height = fit_image_in_box(info.width,width,info.height,height) # if the image is smaller than the box, calculate the white space buffer x, y = add_white_space(x,y,img_width,width,img_height,height) pdf_writer.add_image_from_file(path, x, y, img_width, img_height) end
Draws a PDF::SimpleTable using the given data (usually a Data::Table). Takes all the options you can set on a PDF::SimpleTable object, see the PDF::Writer API docs for details, or check our quick reference at:
stonecode.svnrepository.com/ruport/trac.cgi/wiki/PdfWriterQuickRef
# File lib/ruport/formatter/pdf.rb, line 280 def draw_table(table_data, format_opts={}) m = "PDF Formatter requires column_names to be defined" raise FormatterError, m if table_data.column_names.empty? table_data.rename_columns { |c| c.to_s } if options.table_format format_opts = Marshal.load(Marshal.dump(options.table_format.merge(format_opts))) end old = pdf_writer.font_size ::PDF::SimpleTable.new do |table| table.maximum_width = 500 table.column_order = table_data.column_names table.data = table_data table.data = [{}] if table.data.empty? apply_pdf_table_column_opts(table,table_data,format_opts) format_opts.each {|k,v| table.send("#{k}=", v) } table.render_on(pdf_writer) end pdf_writer.font_size = old end
Calls render_pdf
.
# File lib/ruport/formatter/pdf.rb, line 134 def finalize_grouping render_pdf end
Appends the results of PDF::Writer#render to output for your
pdf_writer
object.
# File lib/ruport/formatter/pdf.rb, line 103 def finalize_table render_pdf unless options.skip_finalize_table end
Adds n to pdf_writer.y, moving the vertical drawing position in the document.
# File lib/ruport/formatter/pdf.rb, line 225 def move_cursor(n) pdf_writer.y += n end
Moves the cursor to a specific y coordinate in the document.
# File lib/ruport/formatter/pdf.rb, line 230 def move_cursor_to(n) pdf_writer.y = n end
# File lib/ruport/formatter/pdf.rb, line 239 def move_down(n) pdf_writer.y -= n end
Moves the vertical drawing position in the document upwards by n.
# File lib/ruport/formatter/pdf.rb, line 235 def move_up(n) pdf_writer.y += n end
Adds a specified amount of whitespace above and below the code in your block. For example, if you want to surround the top and bottom of a line of text with 5 pixels of whitespace:
pad(5) { add_text "This will be padded top and bottom" }
# File lib/ruport/formatter/pdf.rb, line 248 def pad(y,&block) move_cursor(-y) block.call move_cursor(-y) end
Adds a specified amount of whitespace below the code in your block.
For example, if you want to add a 10 pixel buffer to the bottom of a line of text:
pad_bottom(10) { add_text "This will be padded on bottom" }
# File lib/ruport/formatter/pdf.rb, line 269 def pad_bottom(y,&block) block.call move_cursor(-y) end
Adds a specified amount of whitespace above the code in your block.
For example, if you want to add a 10 pixel buffer to the top of a line of text:
pad_top(10) { add_text "This will be padded on top" }
# File lib/ruport/formatter/pdf.rb, line 259 def pad_top(y,&block) move_cursor(-y) block.call end
Returns the current PDF::Writer object or creates a new one if it has not been set yet.
# File lib/ruport/formatter/pdf.rb, line 88 def pdf_writer @pdf_writer ||= options.formatter || ::PDF::Writer.new( :paper => options.paper_size || "LETTER", :orientation => options.paper_orientation || :portrait) end
Calls PDF::Writer#render and appends to output
.
# File lib/ruport/formatter/pdf.rb, line 153 def render_pdf output << pdf_writer.render end
Draws some text on the canvas, surrounded by a box with rounded corners.
Yields an OpenStruct which options can be defined on.
Example:
rounded_text_box(options.text) do |o| o.radius = 5 o.width = options.width || 400 o.height = options.height || 130 o.font_size = options.font_size || 12 o.heading = options.heading o.x = pdf_writer.absolute_x_middle - o.width/2 o.y = 300 end
# File lib/ruport/formatter/pdf.rb, line 201 def rounded_text_box(text) opts = OpenStruct.new yield(opts) resize_text_to_box(text, opts) pdf_writer.save_state draw_box(opts.x, opts.y, opts.width, opts.height, opts.radius, opts.fill_color, opts.stroke_color) add_text_with_bottom_border(opts.heading, opts.x, opts.y, opts.width, opts.font_size) if opts.heading pdf_writer.restore_state start_position = opts.heading ? opts.y - 20 : opts.y draw_text(text, :y => start_position, :left => opts.x, :right => opts.x + opts.width, :justification => opts.justification || :center, :font_size => opts.font_size) move_cursor_to(opts.y - opts.height) end
Private Instance Methods
# File lib/ruport/formatter/pdf.rb, line 509 def add_text_with_bottom_border(text,x,y,width,font_size) pdf_writer.line( x, y - 20, x + width, y - 20).stroke pdf_writer.fill_color(Color::RGB::Black) move_cursor_to(y - 3) add_text("<b>#{text}</b>", :absolute_left => x, :absolute_right => x + width, :justification => :center, :font_size => font_size) end
# File lib/ruport/formatter/pdf.rb, line 483 def add_white_space(x,y,img_width,box_width,img_height,box_height) if img_width < box_width white_space = box_width - img_width x = x + (white_space / 2) end if img_height < box_height white_space = box_height - img_height y = y + (white_space / 2) end return x, y end
# File lib/ruport/formatter/pdf.rb, line 535 def apply_column_format_template(t) t = (t || {}).merge(options.column_format || {}) column_opts = {} column_opts.merge!(:justification => t[:alignment]) if t[:alignment] column_opts.merge!(:width => t[:width]) if t[:width] unless column_opts.empty? if options.table_format if options.table_format[:column_options] options.table_format[:column_options] = column_opts.merge(options.table_format[:column_options]) else options.table_format.merge!(:column_options => column_opts) end else options.table_format = { :column_options => column_opts } end end end
# File lib/ruport/formatter/pdf.rb, line 586 def apply_grouping_format_template(t) t = (t || {}).merge(options.grouping_format || {}) options.style ||= t[:style] end
# File lib/ruport/formatter/pdf.rb, line 554 def apply_heading_format_template(t) t = (t || {}).merge(options.heading_format || {}) heading_opts = {} heading_opts.merge!(:justification => t[:alignment]) if t[:alignment] heading_opts.merge!(:bold => t[:bold]) unless t[:bold].nil? heading_opts.merge!(:title => t[:title]) if t[:title] unless heading_opts.empty? if options.table_format if options.table_format[:column_options] if options.table_format[:column_options][:heading] options.table_format[:column_options][:heading] = heading_opts.merge( options.table_format[:column_options][:heading] ) else options.table_format[:column_options].merge!( :heading => heading_opts ) end else options.table_format.merge!( :column_options => { :heading => heading_opts } ) end else options.table_format = { :column_options => { :heading => heading_opts } } end end end
# File lib/ruport/formatter/pdf.rb, line 519 def apply_page_format_template(t) t = (t || {}).merge(options.page_format || {}) options.paper_size ||= t[:size] options.paper_orientation ||= t[:layout] end
# File lib/ruport/formatter/pdf.rb, line 396 def apply_pdf_table_column_opts(table,table_data,format_opts) column_opts = format_opts.delete(:column_options) if column_opts heading_opts = column_opts.delete(:heading) if column_opts[:justification] heading_opts ||= {} heading_opts = { :justification => column_opts[:justification] }.merge(heading_opts) end specific = get_specific_column_options(table_data.column_names, column_opts) columns = table_data.column_names.inject({}) { |s,c| s.merge( c => ::PDF::SimpleTable::Column.new(c) { |col| col.heading = create_heading(heading_opts) column_opts.each { |k,v| col.send("#{k}=",v) } # use the specific column names now specific[c].each { |k,v| col.send("#{k}=",v) } }) } table.columns = columns end end
# File lib/ruport/formatter/pdf.rb, line 530 def apply_table_format_template(t) t = (t || {}).merge(options.table_format || {}) options.table_format = t unless t.empty? end
# File lib/ruport/formatter/pdf.rb, line 525 def apply_text_format_template(t) t = (t || {}).merge(options.text_format || {}) options.text_format = t unless t.empty? end
# File lib/ruport/formatter/pdf.rb, line 431 def create_heading(heading_opts) heading_opts ||= {} ::PDF::SimpleTable::Column::Heading.new {|head| heading_opts.each {|k,v| head.send("#{k}=",v) } } end
# File lib/ruport/formatter/pdf.rb, line 503 def draw_box(x,y,width,height,radius,fill_color=nil,stroke_color=nil) pdf_writer.fill_color(fill_color || Color::RGB::White) pdf_writer.stroke_color(stroke_color || Color::RGB::Black) pdf_writer.rounded_rectangle(x, y, width, height, radius).fill_stroke end
# File lib/ruport/formatter/pdf.rb, line 474 def fit_image_in_box(img_width,box_width,img_height,box_height) img_ratio = img_height.to_f / img_width.to_f until image_fits_in_box?(img_width,box_width,img_height,box_height) img_width -= 1 img_height = img_width * img_ratio end return img_width, img_height end
# File lib/ruport/formatter/pdf.rb, line 421 def get_specific_column_options(column_names,column_opts) column_names.inject({}) do |s,c| opts = column_opts.delete(c) || {} if opts[:heading] opts = opts.merge(:heading => create_heading(opts[:heading])) end s.merge(c => opts) end end
# File lib/ruport/formatter/pdf.rb, line 438 def grouping_columns data.data.to_a[0][1].column_names.dup.unshift(data.grouped_by) end
# File lib/ruport/formatter/pdf.rb, line 470 def image_fits_in_box?(img_width,box_width,img_height,box_height) !(img_width > box_width || img_height > box_height) end
# File lib/ruport/formatter/pdf.rb, line 446 def render_justified_or_separated_grouping table = table_with_grouped_by_column data.each do |name,group| group.each_with_index do |r,i| if i == 0 table << { data.grouped_by => "<b>#{name}</b>" }.merge(r.to_hash) else table << r end end table << [" "] if options.style == :separated end render_table table, options.to_hash.merge(:formatter => pdf_writer) end
# File lib/ruport/formatter/pdf.rb, line 461 def render_offset_grouping table = table_with_grouped_by_column data.each do |name,group| table << ["<b>#{name}</b>"] group.each {|r| table << r } end render_table table, options.to_hash.merge(:formatter => pdf_writer) end
# File lib/ruport/formatter/pdf.rb, line 495 def resize_text_to_box(text,opts) loop do sz = pdf_writer.text_width(text, opts.font_size) opts.x + sz > opts.x + opts.width or break opts.font_size -= 1 end end
# File lib/ruport/formatter/pdf.rb, line 442 def table_with_grouped_by_column Ruport::Data::Table.new(:column_names => grouping_columns) end