sskirby-prawn-layout 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Demonstrates various table and cell features.
4
+ #
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
6
+
7
+ require "rubygems"
8
+ require "prawn"
9
+ require "prawn/layout"
10
+
11
+ headers, body = nil, nil
12
+
13
+ dir = File.expand_path(File.dirname(__FILE__))
14
+
15
+ ruby_18 do
16
+ require "fastercsv"
17
+ headers, *body = FasterCSV.read("#{dir}/addressbook.csv")
18
+ end
19
+
20
+ ruby_19 do
21
+ require "csv"
22
+ headers, *body = CSV.read("#{dir}/addressbook.csv",
23
+ :encoding => "utf-8")
24
+ end
25
+
26
+ Prawn::Document.generate("fancy_table.pdf", :page_layout => :landscape) do
27
+
28
+ #font "#{Prawn::BASEDIR}/data/fonts/DejaVuSans.ttf"
29
+
30
+ mask(:y) { table body, :headers => headers,
31
+ :align => :center,
32
+ :border_style => :grid }
33
+
34
+ table [["This is", "A Test" ],
35
+ [ Prawn::Table::Cell.new( :text => "Of tables",
36
+ :background_color => "ffccff" ),
37
+ "Drawn Side"], ["By side", "and stuff" ]],
38
+ :position => 600,
39
+ :headers => ["Col A", "Col B"],
40
+ :border_width => 1,
41
+ :vertical_padding => 5,
42
+ :horizontal_padding => 3,
43
+ :font_size => 10,
44
+ :row_colors => :pdf_writer,
45
+ :column_widths => { 1 => 50 }
46
+
47
+ move_down 150
48
+
49
+ table [%w[1 2 3],%w[4 5 6],%w[7 8 9]],
50
+ :position => :center,
51
+ :border_width => 0,
52
+ :font_size => 40
53
+
54
+ cell [500,300],
55
+ :text => "This free flowing textbox shows how you can use Prawn's "+
56
+ "cells outside of a table with ease. Think of a 'cell' as " +
57
+ "simply a limited purpose bounding box that is meant for laying " +
58
+ "out blocks of text and optionally placing a border around it",
59
+ :width => 225, :padding => 10, :border_width => 2
60
+
61
+ font_size 24
62
+
63
+ cell [50,75],
64
+ :text => "This document demonstrates a number of Prawn's table features",
65
+ :border_style => :no_top, # :all, :no_bottom, :sides
66
+ :horizontal_padding => 5
67
+ end
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Generates a couple simple tables, including some UTF-8 text cells.
4
+ # Although this does not show all of the options available to table, the most
5
+ # common are used here. See fancy_table.rb for a more comprehensive example.
6
+ #
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
8
+
9
+ require "rubygems"
10
+ require "prawn"
11
+ require "prawn/layout"
12
+
13
+ Prawn::Document.generate("table.pdf") do
14
+ font "#{Prawn::BASEDIR}/data/fonts/DejaVuSans.ttf"
15
+ table [["ὕαλον ϕαγεῖν", "baaar", "1" ],
16
+ ["This is","a sample", "2" ],
17
+ ["Table", "dont\ncha\nknow?", "3" ],
18
+ [ "It", "Rules", "4" ],
19
+ [ "It", "Rules", "4" ],
20
+ [ "It", "Rules", "4123231" ],
21
+ [ "It", "Rules", "22.5" ],
22
+ [ "It", "Rules", "4" ],
23
+ [ "It", "Rules", "4" ],
24
+ [ "It", "Rules", "4" ],
25
+ [ "It", "Rules", "4" ],
26
+ [ "It", "Rules", "4" ],
27
+ [ "It", "Rules\nwith an iron fist", "x" ],
28
+ [ "It", "Rules", "4" ],
29
+ [ "It", "Rules", "4" ],
30
+ [ "It", "Rules", "4" ],
31
+ [ "It", "Rules", "4" ],
32
+ [ "It", "Rules", "4" ],
33
+ [ "It", "Rules", "4" ],
34
+ [ "It", "Rules", "4" ],
35
+ [ "It", "Rules", "4" ],
36
+ [ "It", "Rules", "4" ]],
37
+
38
+ :font_size => 24,
39
+ :horizontal_padding => 10,
40
+ :vertical_padding => 3,
41
+ :border_width => 2,
42
+ :position => :center,
43
+ :headers => ["Column A","Column B","#"],
44
+ :align => {1 => :center},
45
+ :align_headers => :center
46
+
47
+ text "This should appear in the original font size, just below the table"
48
+ move_down 10
49
+
50
+ table [[ "Wide", "columns", "streeetch"],
51
+ ["are","mighty fine", "streeeeeeeech"]],
52
+ :column_widths => { 0 => 200, 1 => 200 }, :position => 5
53
+
54
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Demonstrates the many controls over alignment and positioning in Prawn
4
+ # tables.
5
+ #
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
7
+
8
+ require "rubygems"
9
+ require "prawn"
10
+ require "prawn/layout"
11
+
12
+ Prawn::Document.generate "table_header_align.pdf" do
13
+ table [ ['01/01/2008', 'John Doe', '4.2', '125.00', '525.00'],
14
+ ['01/12/2008', 'Jane Doe', '3.2', '75.50', '241.60'] ] * 20,
15
+ :position => :center,
16
+ :headers => ['Date', 'Employee', 'Hours', 'Rate', 'Total'],
17
+ :column_widths => { 0 => 75, 1 => 100, 2 => 50, 3 => 50, 4 => 50},
18
+ :border_style => :grid,
19
+ :align => { 0 => :right, 1 => :left, 2 => :right, 3 => :right, 4 => :right },
20
+ :align_headers => { 0 => :center, 2 => :left, 3 => :left, 4 => :right }
21
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Demonstrates how to set the table border color with the :border_color
4
+ # attribute.
5
+ #
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
7
+
8
+ require "rubygems"
9
+ require "prawn"
10
+ require "prawn/layout"
11
+
12
+ Prawn::Document.generate "table_border_color.pdf" do
13
+ table [ ['01/01/2008', 'John Doe', '4.2', '125.00', '525.00'],
14
+ ['01/12/2008', 'Jane Doe', '3.2', '75.50', '241.60'] ] * 20,
15
+ :position => :center,
16
+ :headers => ['Date', 'Employee', 'Hours', 'Rate', 'Total'],
17
+ :column_widths => { 0 => 75, 1 => 100, 2 => 50, 3 => 50, 4 => 50},
18
+ :border_style => :grid,
19
+ :border_color => "ff0000"
20
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Demonstrates the use of the :col_span option when using Document#table
4
+ #
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
6
+
7
+ require "rubygems"
8
+ require "prawn"
9
+ require "prawn/layout"
10
+
11
+ Prawn::Document.generate "table_colspan.pdf" do
12
+ data = [ ['01/01/2008', 'John Doe', '4.2', '125.00', '525.00'],
13
+ ['01/12/2008', 'Jane Doe', '3.2', '75.50', '241.60'] ] * 5
14
+
15
+ data << [{:text => 'Total', :colspan => 2}, '37.0', '1002.5', '3833']
16
+
17
+ table data,
18
+ :position => :center,
19
+ :headers => ['Date', 'Employee', 'Hours', 'Rate', 'Total'],
20
+ :column_widths => { 0 => 75, 1 => 100, 2 => 50, 3 => 50, 4 => 50},
21
+ :border_style => :grid
22
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Demonstrates explicitly setting the :header_color rather than inferring
4
+ # it from :row_colors in Document#table
5
+ #
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
7
+
8
+ require "rubygems"
9
+ require "prawn"
10
+ require "prawn/layout"
11
+
12
+ Prawn::Document.generate "table_header_color.pdf" do
13
+ table [ ['01/01/2008', 'John Doe', '4.2', '125.00', '525.00'],
14
+ ['01/12/2008', 'Jane Doe', '3.2', '75.50', '241.60'] ] * 20,
15
+ :position => :center,
16
+ :headers => ['Date', 'Employee', 'Hours', 'Rate', 'Total'],
17
+ :column_widths => { 0 => 75, 1 => 100, 2 => 50, 3 => 50, 4 => 50},
18
+ :border_style => :grid,
19
+ :header_color => 'f07878',
20
+ :header_text_color => "990000",
21
+ :row_colors => ["FFCCFF","CCFFCC"]
22
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Demonstrates the :underline_header border style for Document#table.
4
+ #
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
6
+
7
+ require "rubygems"
8
+ require "prawn"
9
+ require "prawn/layout"
10
+
11
+ Prawn::Document.generate "table_header_underline.pdf" do
12
+ table [ ['01/01/2008', 'John Doe', '4.2', '125.00', '525.00'],
13
+ ['01/12/2008', 'Jane Doe', '3.2', '75.50', '241.60'] ] * 5,
14
+ :position => :center,
15
+ :headers => ['Date', 'Employee', 'Hours', 'Rate', 'Total'],
16
+ :column_widths => { 0 => 75, 1 => 100, 2 => 50, 3 => 50, 4 => 50},
17
+ :border_style => :underline_header
18
+ end
@@ -0,0 +1,64 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Generates a couple simple tables, including some UTF-8 text cells.
4
+ # Although this does not show all of the options available to table, the most
5
+ # common are used here. See fancy_table.rb for a more comprehensive example.
6
+ #
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
8
+
9
+ require "rubygems"
10
+ require "prawn"
11
+ require "prawn/layout"
12
+
13
+ Prawn::Document.generate("table_widths.pdf") do
14
+
15
+ data = [
16
+ %w(one two three four),
17
+ %w(five six seven eight),
18
+ %w(nine ten eleven twelve),
19
+ %w(thirteen fourteen fifteen sixteen),
20
+ %w(seventeen eighteen nineteen twenty)
21
+ ]
22
+ headers = ["Column A","Column B","Column C", "Column D"]
23
+
24
+ text "A table with a specified width of the document width (within margins)"
25
+ move_down 10
26
+
27
+ table data,
28
+ :position => :center,
29
+ :headers => headers,
30
+ :width => margin_box.width
31
+
32
+
33
+ move_down 20
34
+ text "A table with a specified width of the document width (within margins) and two fixed width columns"
35
+ move_down 10
36
+
37
+ table data,
38
+ :position => :center,
39
+ :headers => headers,
40
+ :width => margin_box.width,
41
+ :column_widths => {0 => 70, 1 => 70}
42
+
43
+
44
+ move_down 20
45
+ text "A table with a specified width of 300"
46
+ move_down 10
47
+
48
+ table data,
49
+ :position => :center,
50
+ :headers => headers,
51
+ :width => 300
52
+
53
+
54
+ move_down 20
55
+ text "A table with too much data is automatically limited to the document width"
56
+ move_down 10
57
+
58
+ data << ['some text', 'A long piece of text that will make this cell too wide for the page', 'some more text', 'And more text']
59
+
60
+ table data,
61
+ :position => :center,
62
+ :headers => headers
63
+
64
+ end
@@ -0,0 +1,20 @@
1
+ require "prawn/table"
2
+ require "prawn/layout/page"
3
+
4
+ module Prawn
5
+
6
+ module Errors
7
+
8
+ # This error is raised when table data is malformed
9
+ #
10
+ InvalidTableData = Class.new(StandardError)
11
+
12
+ # This error is raised when an empty or nil table is rendered
13
+ #
14
+ EmptyTable = Class.new(StandardError)
15
+ end
16
+
17
+ module Layout
18
+
19
+ end
20
+ end
@@ -0,0 +1,116 @@
1
+ # encoding: utf-8
2
+
3
+ # layout/page.rb : Provides helpers for page layout
4
+ #
5
+ # Copyright January 2009, Gregory Brown. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ module Prawn
10
+ class Document
11
+ # A LazyBoundingBox is simply a BoundingBox with an action tied to it to be
12
+ # executed later. The lazy_bounding_box method takes the same arguments as
13
+ # bounding_box, but returns a LazyBoundingBox object instead of executing
14
+ # the code block directly.
15
+ #
16
+ # You can then call LazyBoundingBox#draw at any time (or multiple times if
17
+ # you wish), and the contents of the block will then be run. This can be
18
+ # useful for assembling repeating page elements or reusable components.
19
+ #
20
+ # file = "lazy_bounding_boxes.pdf"
21
+ # Prawn::Document.generate(file, :skip_page_creation => true) do
22
+ # point = [bounds.right-50, bounds.bottom + 25]
23
+ # page_counter = lazy_bounding_box(point, :width => 50) do
24
+ # text "Page: #{page_count}"
25
+ # end
26
+ #
27
+ # 10.times do
28
+ # start_new_page
29
+ # text "Some text"
30
+ # page_counter.draw
31
+ # end
32
+ # end
33
+ #
34
+ def lazy_bounding_box(*args,&block)
35
+ translate!(args[0])
36
+ box = LazyBoundingBox.new(self,*args)
37
+ box.action(&block)
38
+ return box
39
+ end
40
+
41
+ # A bounding box with the same dimensions of its parents, minus a margin
42
+ # on all sides
43
+ #
44
+ def padded_box(margin, &block)
45
+ bounding_box [bounds.left + margin, bounds.top - margin],
46
+ :width => bounds.width - (margin * 2),
47
+ :height => bounds.height - (margin * 2), &block
48
+ end
49
+
50
+ # A header is a LazyBoundingBox drawn relative to the margins that can be
51
+ # repeated on every page of the document.
52
+ #
53
+ # Unless <tt>:width</tt> or <tt>:height</tt> are specified, the margin_box
54
+ # width and height are used.
55
+ #
56
+ # header margin_box.top_left do
57
+ # text "Here's My Fancy Header", :size => 25, :align => :center
58
+ # stroke_horizontal_rule
59
+ # end
60
+ #
61
+ def header(top_left,options={},&block)
62
+ @header = repeating_page_element(top_left,options,&block)
63
+ end
64
+
65
+ # A footer is a LazyBoundingBox drawn relative to the margins that can be
66
+ # repeated on every page of the document.
67
+ #
68
+ # Unless <tt>:width</tt> or <tt>:height</tt> are specified, the margin_box
69
+ # width and height are used.
70
+ #
71
+ # footer [margin_box.left, margin_box.bottom + 25] do
72
+ # stroke_horizontal_rule
73
+ # text "And here's a sexy footer", :size => 16
74
+ # end
75
+ #
76
+ def footer(top_left,options={},&block)
77
+ @footer = repeating_page_element(top_left,options,&block)
78
+ end
79
+
80
+ private
81
+
82
+ def repeating_page_element(top_left,options={},&block)
83
+ r = LazyBoundingBox.new(self, translate(top_left),
84
+ :width => options[:width] || margin_box.width,
85
+ :height => options[:height] || margin_box.height )
86
+ r.action(&block)
87
+ return r
88
+ end
89
+
90
+ class LazyBoundingBox < BoundingBox
91
+
92
+ # Defines the block to be executed by LazyBoundingBox#draw.
93
+ # Usually, this will be used via a higher level interface.
94
+ # See the documentation for Document#lazy_bounding_box, Document#header,
95
+ # and Document#footer
96
+ #
97
+ def action(&block)
98
+ @action = block
99
+ end
100
+
101
+ # Sets Document#bounds to use the LazyBoundingBox for its bounds,
102
+ # runs the block specified by LazyBoundingBox#action,
103
+ # and then restores the original bounds of the document.
104
+ #
105
+ def draw
106
+ @parent.mask(:y) do
107
+ parent_box = @parent.bounds
108
+ @parent.bounds = self
109
+ @parent.y = absolute_top
110
+ @action.call
111
+ @parent.bounds = parent_box
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,373 @@
1
+ # encoding: utf-8
2
+ #
3
+ # table.rb : Simple table drawing functionality
4
+ #
5
+ # Copyright June 2008, Gregory Brown. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ require "prawn/table/cell"
10
+
11
+ module Prawn
12
+ class Document
13
+
14
+ # Builds and renders a Document::Table object from raw data.
15
+ # For details on the options that can be passed, see
16
+ # Document::Table.new
17
+ #
18
+ # data = [["Gregory","Brown"],["James","Healy"],["Jia","Wu"]]
19
+ #
20
+ # Prawn::Document.generate("table.pdf") do
21
+ #
22
+ # # Default table, without headers
23
+ # table(data)
24
+ #
25
+ # # Default table with headers
26
+ # table data, :headers => ["First Name", "Last Name"]
27
+ #
28
+ # # Very close to PDF::Writer's default SimpleTable output
29
+ # table data, :headers => ["First Name", "Last Name"],
30
+ # :font_size => 10,
31
+ # :vertical_padding => 2,
32
+ # :horizontal_padding => 5,
33
+ # :position => :center,
34
+ # :row_colors => :pdf_writer,
35
+ #
36
+ # # Grid border style with explicit column widths.
37
+ # table data, :border_style => :grid,
38
+ # :column_widths => { 0 => 100, 1 => 150 }
39
+ #
40
+ # end
41
+ #
42
+ # Will raise <tt>Prawn::Errors::EmptyTable</tt> given
43
+ # a nil or empty <tt>data</tt> paramater.
44
+ #
45
+ def table(data, options={})
46
+ if data.nil? || data.empty?
47
+ raise Prawn::Errors::EmptyTable,
48
+ "data must be a non-empty, non-nil, two dimensional array of Prawn::Cells or strings"
49
+ end
50
+ Prawn::Table.new(data,self,options).draw
51
+ end
52
+ end
53
+
54
+
55
+ # This class implements simple PDF table generation.
56
+ #
57
+ # Prawn tables have the following features:
58
+ #
59
+ # * Can be generated with or without headers
60
+ # * Can tweak horizontal and vertical padding of text
61
+ # * Minimal styling support (borders / row background colors)
62
+ # * Can be positioned by bounding boxes (left/center aligned) or an
63
+ # absolute x position
64
+ # * Automated page-breaking as needed
65
+ # * Column widths can be calculated automatically or defined explictly on a
66
+ # column by column basis
67
+ # * Text alignment can be set for the whole table or by column
68
+ #
69
+ # The current implementation is a bit barebones, but covers most of the
70
+ # basic needs for PDF table generation. If you have feature requests,
71
+ # please share them at: http://groups.google.com/group/prawn-ruby
72
+ #
73
+ # Tables will be revisited before the end of the Ruby Mendicant project and
74
+ # the most commonly needed functionality will likely be added.
75
+ #
76
+ class Table
77
+
78
+ include Prawn::Configurable
79
+
80
+ attr_reader :column_widths # :nodoc:
81
+
82
+ NUMBER_PATTERN = /^-?(?:0|[1-9]\d*)(?:\.\d+(?:[eE][+-]?\d+)?)?$/ #:nodoc:
83
+
84
+ # Creates a new Document::Table object. This is generally called
85
+ # indirectly through Document#table but can also be used explictly.
86
+ #
87
+ # The <tt>data</tt> argument is a two dimensional array of strings,
88
+ # organized by row, e.g. [["r1-col1","r1-col2"],["r2-col2","r2-col2"]].
89
+ # As with all Prawn text drawing operations, strings must be UTF-8 encoded.
90
+ #
91
+ # The following options are available for customizing your tables, with
92
+ # defaults shown in [] at the end of each description.
93
+ #
94
+ # <tt>:headers</tt>:: An array of table headers, either strings or Cells. [Empty]
95
+ # <tt>:align_headers</tt>:: Alignment of header text. Specify for entire header (<tt>:left</tt>) or by column (<tt>{ 0 => :right, 1 => :left}</tt>). If omitted, the header alignment is the same as the column alignment.
96
+ # <tt>:header_text_color</tt>:: Sets the text color of the headers
97
+ # <tt>:header_color</tt>:: Manually sets the header color
98
+ # <tt>:font_size</tt>:: The font size for the text cells . [12]
99
+ # <tt>:horizontal_padding</tt>:: The horizontal cell padding in PDF points [5]
100
+ # <tt>:vertical_padding</tt>:: The vertical cell padding in PDF points [5]
101
+ # <tt>:padding</tt>:: Horizontal and vertical cell padding (overrides both)
102
+ # <tt>:border_width</tt>:: With of border lines in PDF points [1]
103
+ # <tt>:border_style</tt>:: If set to :grid, fills in all borders. If set to :underline_header, underline header only. Otherwise, borders are drawn on columns only, not rows
104
+ # <tt>:border_color</tt>:: Sets the color of the borders.
105
+ # <tt>:position</tt>:: One of <tt>:left</tt>, <tt>:center</tt> or <tt>n</tt>, where <tt>n</tt> is an x-offset from the left edge of the current bounding box
106
+ # <tt>:width:</tt> A set width for the table, defaults to the sum of all column widths
107
+ # <tt>:column_widths:</tt> A hash of indices and widths in PDF points. E.g. <tt>{ 0 => 50, 1 => 100 }</tt>
108
+ # <tt>:row_colors</tt>:: An array of row background colors which are used cyclicly.
109
+ # <tt>:align</tt>:: Alignment of text in columns, for entire table (<tt>:center</tt>) or by column (<tt>{ 0 => :left, 1 => :center}</tt>)
110
+ # <tt>:minimum_rows</tt>:: The minimum rows to display on a page, including header.
111
+ #
112
+ # Row colors are specified as html encoded values, e.g.
113
+ # ["ffffff","aaaaaa","ccaaff"]. You can also specify
114
+ # <tt>:row_colors => :pdf_writer</tt> if you wish to use the default color
115
+ # scheme from the PDF::Writer library.
116
+ #
117
+ # See Document#table for typical usage, as directly using this class is
118
+ # not recommended unless you know why you want to do it.
119
+ #
120
+ def initialize(data, document, options={})
121
+ unless data.all? { |e| Array === e }
122
+ raise Prawn::Errors::InvalidTableData,
123
+ "data must be a two dimensional array of Prawn::Cells or strings"
124
+ end
125
+
126
+ @data = data
127
+ @document = document
128
+
129
+ Prawn.verify_options [:font_size,:border_style, :border_width,
130
+ :position, :headers, :row_colors, :align, :align_headers,
131
+ :header_text_color, :border_color, :horizontal_padding,
132
+ :vertical_padding, :padding, :column_widths, :width, :header_color ],
133
+ options
134
+
135
+ configuration.update(options)
136
+
137
+ if padding = options[:padding]
138
+ C(:horizontal_padding => padding, :vertical_padding => padding)
139
+ end
140
+
141
+ if options[:row_colors] == :pdf_writer
142
+ C(:row_colors => ["ffffff","cccccc"])
143
+ end
144
+
145
+ if options[:row_colors]
146
+ C(:original_row_colors => C(:row_colors))
147
+ end
148
+
149
+ calculate_column_widths(options[:column_widths], options[:width])
150
+ end
151
+
152
+ attr_reader :column_widths #:nodoc:
153
+
154
+ # Width of the table in PDF points
155
+ #
156
+ def width
157
+ @column_widths.inject(0) { |s,r| s + r }
158
+ end
159
+
160
+ # Draws the table onto the PDF document
161
+ #
162
+ def draw
163
+ @parent_bounds = @document.bounds
164
+ case C(:position)
165
+ when :center
166
+ x = (@document.bounds.width - width) / 2.0
167
+ dy = @document.bounds.absolute_top - @document.y
168
+ @document.bounding_box [x, @parent_bounds.top], :width => width do
169
+ @document.move_down(dy)
170
+ generate_table
171
+ end
172
+ when Numeric
173
+ x, y = C(:position), @document.y - @document.bounds.absolute_bottom
174
+ @document.bounding_box([x,y], :width => width) { generate_table }
175
+ else
176
+ generate_table
177
+ end
178
+ end
179
+
180
+ private
181
+
182
+ def default_configuration
183
+ { :font_size => 12,
184
+ :border_width => 1,
185
+ :position => :left,
186
+ :horizontal_padding => 5,
187
+ :vertical_padding => 5 }
188
+ end
189
+
190
+ def calculate_column_widths(manual_widths=nil, width=nil)
191
+ @column_widths = [0] * @data[0].inject(0){ |acc, e|
192
+ acc += (e.is_a?(Hash) && e.has_key?(:colspan)) ? e[:colspan] : 1 }
193
+ renderable_data.each do |row|
194
+ row.each_with_index do |cell,i|
195
+ length = cell.to_s.lines.map { |e|
196
+ @document.width_of(e, :size => C(:font_size)) }.max.to_f +
197
+ 2*C(:horizontal_padding)
198
+ @column_widths[i] = length.ceil if length > @column_widths[i]
199
+ end
200
+ end
201
+
202
+ manual_width = 0
203
+ manual_widths.each { |k,v|
204
+ @column_widths[k] = v; manual_width += v } if manual_widths
205
+
206
+ #Ensures that the maximum width of the document is not exceeded
207
+ #Takes into consideration the manual widths specified (With full manual
208
+ # widths specified, the width can exceed the document width as manual
209
+ # widths are taken as gospel)
210
+ max_width = width || @document.margin_box.width
211
+ calculated_width = @column_widths.inject {|sum,e| sum += e }
212
+
213
+ if calculated_width > max_width
214
+ shrink_by = (max_width - manual_width).to_f /
215
+ (calculated_width - manual_width)
216
+ @column_widths.each_with_index { |c,i|
217
+ @column_widths[i] = c * shrink_by if manual_widths.nil? ||
218
+ manual_widths[i].nil?
219
+ }
220
+ elsif width && calculated_width < width
221
+ grow_by = (width - manual_width).to_f /
222
+ (calculated_width - manual_width)
223
+ @column_widths.each_with_index { |c,i|
224
+ @column_widths[i] = c * grow_by if manual_widths.nil? ||
225
+ manual_widths[i].nil?
226
+ }
227
+ end
228
+ end
229
+
230
+ def renderable_data
231
+ C(:headers) ? [C(:headers)] + @data : @data
232
+ end
233
+
234
+ def generate_table
235
+ page_contents = []
236
+ y_pos = @document.y
237
+
238
+ @document.font_size C(:font_size) do
239
+ renderable_data.each_with_index do |row,index|
240
+ c = Prawn::Table::CellBlock.new(@document)
241
+
242
+ col_index = 0
243
+ row.each do |e|
244
+ case C(:align)
245
+ when Hash
246
+ align = C(:align)[col_index]
247
+ else
248
+ align = C(:align)
249
+ end
250
+
251
+
252
+ align ||= e.to_s =~ NUMBER_PATTERN ? :right : :left
253
+
254
+ case e
255
+ when Prawn::Table::Cell
256
+ e.document = @document
257
+ e.width = @column_widths[col_index]
258
+ e.horizontal_padding = C(:horizontal_padding)
259
+ e.vertical_padding = C(:vertical_padding)
260
+ e.border_width = C(:border_width)
261
+ e.border_style = :sides
262
+ e.align = align
263
+ c << e
264
+ else
265
+ text = e.is_a?(Hash) ? e[:text] : e.to_s
266
+ width = if e.is_a?(Hash) && e.has_key?(:colspan)
267
+ @column_widths.slice(col_index, e[:colspan]).inject {
268
+ |sum, width| sum + width }
269
+ else
270
+ @column_widths[col_index]
271
+ end
272
+
273
+ cell_options = {:document => @document,
274
+ :text => text,
275
+ :width => width,
276
+ :horizontal_padding => C(:horizontal_padding),
277
+ :vertical_padding => C(:vertical_padding),
278
+ :border_width => C(:border_width),
279
+ :border_style => :sides,
280
+ :align => align}
281
+ cell_options[:font_style] = e[:font_style] if e.is_a?(Hash) && e.has_key?(:font_style)
282
+ cell_options[:font_size] = e[:font_size] if e.is_a?(Hash) && e.has_key?(:font_size)
283
+
284
+ c << Prawn::Table::Cell.new(cell_options)
285
+ end
286
+
287
+ col_index += (e.is_a?(Hash) && e.has_key?(:colspan)) ? e[:colspan] : 1
288
+ end
289
+
290
+ bbox = @parent_bounds.stretchy? ? @document.margin_box : @parent_bounds
291
+ if c.height > y_pos - bbox.absolute_bottom
292
+ if C(:headers) && page_contents.length == 1
293
+ @document.start_new_page
294
+ y_pos = @document.y
295
+ else
296
+ draw_page(page_contents)
297
+ @document.start_new_page
298
+ if C(:headers) && page_contents.any?
299
+ page_contents = [page_contents[0]]
300
+ y_pos = @document.y - page_contents[0].height
301
+ else
302
+ page_contents = []
303
+ y_pos = @document.y
304
+ end
305
+ end
306
+ end
307
+
308
+ page_contents << c
309
+
310
+ y_pos -= c.height
311
+
312
+ if index == renderable_data.length - 1
313
+ draw_page(page_contents)
314
+ end
315
+
316
+ end
317
+ end
318
+ end
319
+
320
+ def draw_page(contents)
321
+ return if contents.empty?
322
+
323
+ if C(:border_style) == :underline_header
324
+ contents.each { |e| e.border_style = :none }
325
+ contents.first.border_style = :bottom_only if C(:headers)
326
+ elsif C(:border_style) == :grid || contents.length == 1
327
+ contents.each { |e| e.border_style = :all }
328
+ else
329
+ contents.first.border_style = C(:headers) ? :all : :no_bottom
330
+ contents.last.border_style = :no_top
331
+ end
332
+
333
+ if C(:headers)
334
+ contents.first.cells.each_with_index do |e,i|
335
+ if C(:align_headers)
336
+ case C(:align_headers)
337
+ when Hash
338
+ align = C(:align_headers)[i]
339
+ else
340
+ align = C(:align_headers)
341
+ end
342
+ end
343
+ e.align = align if align
344
+ e.text_color = C(:header_text_color) if C(:header_text_color)
345
+ e.background_color = C(:header_color) if C(:header_color)
346
+ end
347
+ end
348
+
349
+ contents.each do |x|
350
+ unless x.background_color
351
+ x.background_color = next_row_color if C(:row_colors)
352
+ end
353
+ x.border_color = C(:border_color) if C(:border_color)
354
+
355
+ x.draw
356
+ end
357
+
358
+ reset_row_colors
359
+ end
360
+
361
+
362
+ def next_row_color
363
+ color = C(:row_colors).shift
364
+ C(:row_colors).push(color)
365
+ color
366
+ end
367
+
368
+ def reset_row_colors
369
+ C(:row_colors => C(:original_row_colors).dup) if C(:row_colors)
370
+ end
371
+
372
+ end
373
+ end