rabidprawns 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,79 @@
1
+ == RabidPrawns - Document Helpers
2
+
3
+ RabidPrawns is a simple library that loads some helpers into Prawn::Document for page layouts. Currently
4
+ two "rabids" are available to load into Prawn::Document. These helper methods should work on most versions
5
+ of Prawn and have been somewhat tested on versions starting at 0.6.3 through 0.8.4. If you find any bugs,
6
+ please create an issue on the github site.
7
+
8
+ == Loading Rabids
9
+
10
+ Loading rabids into the prawn document is easy:
11
+
12
+ require 'rubygems'
13
+ require 'rabidprawns'
14
+
15
+ RabidPrawns.load_rabid :columned_page
16
+
17
+ That's it. Your next Prawn::Document instance will have a Prawn::Document#columned_page method for writing out columned pages.
18
+
19
+ == Available Rabids
20
+
21
+ Currently, there are only two rabids available. To get a list of available rabids:
22
+
23
+ require 'rubygems'
24
+ require 'rabidprawns'
25
+
26
+ puts RabidPrawns.available_rabids
27
+
28
+ === Columned Page
29
+
30
+ The columned page rabid will provide a Prawn::Document#columned_page method. It takes the text for the pages, the title to use
31
+ for the pages and some options for how things should be laid out. If the content fills the page, it will automatically
32
+ move to the next page, reprint the title and continue printing the content. Here's a simple example script you
33
+ can run:
34
+
35
+ require 'rubygems'
36
+ require 'rabidprawns'
37
+
38
+ RabidPrawns.load_rabid :columned_page
39
+ doc = Prawn::Document.new
40
+ doc.columned_page('this is some text to print ' * 1000, 'This is the title', :columns => 3, :title_colspan => 2)
41
+ output = File.open('example.pdf', 'w+')
42
+ output.write doc.render
43
+ output.close
44
+ puts 'Example complete'
45
+
46
+ There are a few options available. Take a look at the docs for a complete listing.
47
+
48
+ === Simple Tables
49
+
50
+ This rabid will provide a Prawn::Document#simple_tables method for printing tables. The idea is simple. Provide an array of table structures
51
+ in an array, with any desired options, and print the tables on the page. Simple tables will properly break at the end of a page,
52
+ accounting for margin restrictions, and start a new page, with table title and headings. Here is a simple example:
53
+
54
+ require 'rubygems'
55
+ require 'rabidprawns'
56
+
57
+ RabidPrawns.load_rabid :simple_tables
58
+
59
+ a = Prawn::Document.new
60
+ tables = [
61
+ {
62
+ :title => 'thing',
63
+ :contents=> [
64
+ {
65
+ :headings => ["one", "two", "three", "four"],
66
+ :rows=>[[1, 2, 3, 4], [{:color => [100, 0, 0, 0], :background_color => [0, 100, 0, 0], :content => "this"}, "is", "working", "hopefully"], [nil, 'a', 'b', 'c']]
67
+ }
68
+ ]
69
+ }
70
+ ]
71
+ a.simple_tables tables
72
+ f = File.open('example.pdf', 'w+')
73
+ f.write a.render
74
+ f.close
75
+ puts 'Example complete'
76
+
77
+ Simple tables has a few nice features, like dynamically changing column numbers within a table (this is why :contents is an Array), applying scoped options at various
78
+ points during table construction, automatically sizing the table to fit within constraints, printing title and/or headings on new pages and much more. For a longer
79
+ explanation of features, and available options, take a look at the RDoc for the #simple_tables method.
@@ -0,0 +1,74 @@
1
+ module Prawn
2
+ class Document
3
+ # text:: Text to print
4
+ # title:: Title of page
5
+ # options:: Options hash
6
+ # Print a columned page
7
+ # Available options:
8
+ # :columns -> number of columns on page
9
+ # :title_colspan -> number of columns title should span
10
+ # :column_pad -> padding between columns
11
+ # :title_font -> Array of font information
12
+ # :default_font -> Array of font information
13
+ # :new_page -> lambda to be called to start a new page
14
+ # :finalize_page -> lambda to be called when a page has been completed
15
+ # :page_height -> height of the page
16
+ # :page_width -> width of the page
17
+ # :margin -> margin on all sides
18
+ # NOTE: Below margin options will have :margin added to them
19
+ # :margin_top -> margin on top
20
+ # :margin_bottom -> margin on bottom
21
+ # :margin_left -> margin on left
22
+ # :margin_right -> margin on right
23
+ # :title_to_text_pad -> padding between title and start of text
24
+ # :new_page_at_start -> move to new page before starting
25
+ def columned_page(text, title, options = {})
26
+ opts = {:columns => 2, :title_colspan => 1, :column_pad => 10,
27
+ :title_font => [font_families.keys.first, {:style => font_families.values.first.keys.first, :size => 18}],
28
+ :default_font => [font_families.keys.first, {:style => font_families.values.first.keys.first, :size => 6}],
29
+ :new_page => lambda{ start_new_page }, :finalize_page => lambda{},
30
+ :page_height => bounds.height, :page_width => bounds.width, :margin => 0,
31
+ :margin_top => 0, :margin_bottom => 0, :margin_left => 0, :margin_right => 0,
32
+ :title_to_text_pad => 5, :new_page_at_start => false
33
+ }.merge(options)
34
+ [:columns, :title_colspan, :column_pad, :page_height, :page_width,
35
+ :margin, :margin_top, :margin_bottom, :margin_left, :margin_right].each{|x| opts[x] = opts[x].to_i}
36
+ opts[:title_colspan] = opts[:columns] if opts[:title_colspan] > opts[:columns]
37
+ [:margin_top, :margin_bottom, :margin_left, :margin_right].each{|x|opts[x] += opts[:margin]} if opts[:margin] > 0
38
+ if(opts[:new_page_at_start])
39
+ opts[:new_page].call
40
+ else
41
+ opts[:new_page_at_start] = true
42
+ end
43
+ ypos = opts[:page_height] - opts[:margin_top]
44
+ columns = []
45
+ total_width = opts[:page_width] - (opts[:column_pad] * opts[:columns]) - opts[:margin_left] - opts[:margin_right]
46
+ column_width = total_width.to_f / opts[:columns]
47
+ title_height = 0
48
+ font(*opts[:title_font]) do
49
+ title_width = (column_width * opts[:title_colspan]) + (opts[:column_pad] * (opts[:title_colspan] - 1))
50
+ title_height = height_of(title, title_width)
51
+ bounding_box([opts[:margin_left], ypos], :width => title_width, :height => title_height) do
52
+ text title
53
+ end
54
+ end
55
+ font(*opts[:default_font]) do
56
+ ypos -= title_height + opts[:title_to_text_pad] # pad
57
+ below_title = ypos
58
+ opts[:columns].times do |i|
59
+ if(i < opts[:title_colspan])
60
+ ypos = below_title
61
+ else
62
+ ypos = opts[:page_height] - opts[:margin_top]
63
+ end
64
+ xpos = opts[:margin_left] + (column_width * i) + (opts[:column_pad] * i)
65
+ bounding_box([xpos, ypos], :width => column_width, :height => opts[:page_height] - (opts[:page_height] - ypos) - opts[:margin_bottom]) do
66
+ text = text_box text, :height => bounds.height, :width => bounds.width
67
+ end
68
+ end
69
+ end
70
+ opts[:finalize_page].call
71
+ columned_page(text, title, options) unless text.empty?
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,476 @@
1
+ unless([].respond_to?(:sum))
2
+ class Array
3
+ def sum
4
+ self.inject(:+)
5
+ end
6
+ end
7
+ end
8
+
9
+ module Prawn
10
+ class Document
11
+ # tables:: Array of tables
12
+ # options:: Table options
13
+ #
14
+ # Prints tables to document.
15
+ #
16
+ # tables array: Each element within this array is a single table, allowing multiple tables to be
17
+ # printed at once. The format of each element in the array is as follows:
18
+ #
19
+ # {:title => "I'm the title of the table",
20
+ # :contents => [{:headings => ['col1 heading', 'col2 heading', 'col3 heading'],
21
+ # :rows => [['col1 value', 'col2 value', 'col3 value'],
22
+ # ['col1 value2', 'col2 value2, {:content => 'col3 value3', :color => [100,0,0,0]}]]
23
+ # :options => {:headings_text_color_default => [0,100,0,0]}
24
+ # }]
25
+ # :options => {:title_padding => 20}
26
+ # }
27
+ #
28
+ # table hash explained:
29
+ # :title -> String - Title of the table
30
+ # :options -> Options applied to this table only
31
+ # :contents -> Array of Hashes of table contents. (this allows for dynamic resizing within a table to change the number
32
+ # of columns and headings without starting a new table)
33
+ # Table Hash:
34
+ # :headings -> Array of headings
35
+ # :rows -> Array of Arrays. Each array is a row
36
+ # :options -> Options applied to this set of contents only
37
+ #
38
+ # Heading and row values can be a string value, a Hash or nil. If the value is nil, it will be blackedout using
39
+ # the appropriate option for blacking out the cell. If it is a Hash, the :content key must be provided with the
40
+ # string value for the cell. Supported format for value Hashes:
41
+ #
42
+ # {:content => 'cell content', :color => [0,0,0,100], :background_color => [0,100,0,0], :font => ['Times', {:style => 'bold', :size => 10}]}
43
+ #
44
+ # Any or all of these values can by provided, with the exception of :content which MUST be provided. Any extra
45
+ # key/value pairs are ignored.
46
+ #
47
+ # Available options:
48
+ #
49
+ # :table_margin -> margin around entire all tables (the margin references the space around all tables. spacing between individual tables is set using :spacing)
50
+ # :table_margin_left -> left margin for all tables
51
+ # :table_margin_right -> right margin for all tables
52
+ # :table_margin_top -> top margin for all tables
53
+ # :table_margin_bottom -> bottom margin for all tables
54
+ # :default_font -> default font (defaults to first found font at size 6)
55
+ # :default_fill_color -> default fill color (defaults to black)
56
+ # :title_font -> font used for table title (defaults to first found font at size 18)
57
+ # :title_text_color -> text color for table title (defaults to white)
58
+ # :title_background_color -> background color for table title (defaults to blue)
59
+ # :title_text_alignment -> text alignment of title text (defaults :center)
60
+ # :title_padding -> extra padding around title text
61
+ # :headings_font -> default font used for cells in heading (defaults to first found font at font size 6)
62
+ # :headings_text_color_default -> default text color for cells in heading (defaults to black)
63
+ # :headings_text_colors -> text colors for cells in heading. zero based index corresponds to zero based column
64
+ # :headings_background_color_default -> default background color for heading cells (defaults to none)
65
+ # :headings_background_colors -> background colors for cells in heading. zero based index corresponds to zero based column
66
+ # :headings_blackout_color -> color to fill field when value does not exist (default to dark gray)
67
+ # :headings_text_alignment -> default text alignment for headings (defaults to center)
68
+ # :row_font -> default font used for cells in a row (defaults to first found font at font size 6)
69
+ # :row_text_color_default -> default text color for cells in a row (defaults to black)
70
+ # :row_text_colors -> text colors for cells in a row. zero based index corresponds to zero based column
71
+ # :row_background_color_default -> default background color for table cells (defaults to none)
72
+ # :row_background_colors -> background colors for cells in a row. zero based index corresponds to zero based column
73
+ # :row_text_alignment -> default text alignment for rows (defaults to center)
74
+ # :row_blackout_color -> color to fill field when value does not exist (defaults to dark gray)
75
+ # :border_color -> color of table border (defaults to black)
76
+ # :border_width -> width of table border
77
+ # :page_break_on_new_table -> move to a new page when a new table is started
78
+ # :show_title_after_page_break -> print table title after page break before continuing with table
79
+ # :show_headings_after_page_break -> print column headings after page break before continuing with data rows
80
+ # :new_page -> lambda called to start a new page
81
+ # :finalize_page -> lambda called before moving to the next page (example usage would be writing header/footer)
82
+ # :padding -> cell padding
83
+ # :spacing -> space between tables
84
+ # :y_initial -> initial y position when starting to write tables
85
+ # :page_width -> width of the page
86
+ # :page_height -> height of the page
87
+ # :break_for_min_width_on -> Array of single characters to break long strings on for wrapping
88
+ # :column_width_restrictions => {:fitted => [], :minimum => {}, :maximum => {}} # width restrictions (see extended explanation below)
89
+ #
90
+ # Width restrictions can be applied to columns to allow some control over column sizing. All
91
+ # restrictions work using a zero based index reference corresponding to table columns. For example:
92
+ # :fitted => [true, false, true]
93
+ # will make the first and third columns of the table fitted. Fitted columns will be as wide as the widest element and will not
94
+ # be expanded or shrunk when resizing. Minimum and maximum values can be set on columns as well:
95
+ # :minimum => {1 => 20, 4 => 40}, :maximum => {3 => 50}
96
+ # where the key is the column index and the value is the minimum/maximum number of points the column is allowed.
97
+ def simple_tables(tables, options={})
98
+ opts = st_defaults.merge(options)
99
+ new_page = opts[:new_page]
100
+ finalize_page = opts[:finalize_page]
101
+ opts.delete(:new_page)
102
+ opts.delete(:finalize_page)
103
+ break_page = lambda do
104
+ finalize_page.call
105
+ new_page.call
106
+ [opts[:xstart], opts[:ystart]]
107
+ end
108
+ started_opts = Marshal.load(Marshal.dump(opts)) # deep copy
109
+ new_opts_set = lambda{ Marshal.load(Marshal.dump(started_opts)) } # provide a copy when required
110
+ st_expand_options(opts)
111
+ tables = [tables] unless tables.is_a?(Array)
112
+ xpos = opts[:xstart]
113
+ ypos = opts[:y_initial] || opts[:ystart]
114
+ tables.each do |table|
115
+ opts = table[:options] ? new_opts_set.call.merge(table[:option]) : new_opts_set.call
116
+ st_expand_options(opts, table[:options] ? :nomargins : nil)
117
+ clean_table(table)
118
+ title = table[:title]
119
+ table[:contents] = [table[:contents]] unless table[:contents].is_a?(Array)
120
+ widths = st_find_column_widths(table[:contents].first, opts)
121
+ if((ypos - st_height_title(title, opts) - st_height_row(table[:contents].first[:headings], widths, opts, :headings) - st_height_row(table[:contents].first[:rows].first, widths, opts)) < opts[:yend])
122
+ xpos,ypos = break_page.call
123
+ end
124
+ xpos, ypos = st_write_title(title, [xpos,ypos], opts)
125
+ table[:contents].each do |row_collection|
126
+ if(row_collection[:options])
127
+ opts = opts.merge(row_collection[:options])
128
+ st_expand_options(opts, :nomargins)
129
+ end
130
+ widths = st_find_column_widths(row_collection, opts)
131
+ if((ypos - st_height_row(row_collection[:headings], widths, opts, :headings) - st_height_row(row_collection[:rows].first, widths, opts)) < opts[:yend])
132
+ xpos,ypos = break_page.call
133
+ xpos, ypos = st_write_title(title, [xpos,ypos], opts) if opts[:show_title_after_page_break]
134
+ end
135
+ xpos, ypos = st_write_row(row_collection[:headings], [xpos,ypos], widths, opts, :headings)
136
+ row_collection[:rows].each do |row|
137
+ if(ypos - st_height_row(row, widths, opts) < opts[:yend])
138
+ xpos,ypos = break_page.call
139
+ xpos,ypos = st_write_title(title, [xpos,ypos], opts) if opts[:show_title_after_page_break]
140
+ xpos,ypos = st_write_row(row_collection[:headings], [xpos,ypos], widths, opts, :headings) if opts[:show_headings_after_page_break]
141
+ end
142
+ xpos, ypos = st_write_row(row, [xpos,ypos], widths, opts)
143
+ end
144
+ end
145
+ ypos -= opts[:spacing]
146
+ end
147
+ finalize_page.call
148
+ [xpos,ypos]
149
+ end
150
+
151
+ # table:: Hash of table contents
152
+ # Cleans up the table contents filling in missing parts like heading or row information.
153
+ # Will also check for column consistency within the rows.
154
+ def clean_table(table)
155
+ table[:contents] = [table[:contents]] unless table[:contents].is_a?(Array)
156
+ table[:contents].push [] if table[:contents].empty?
157
+ table[:contents].size.times do |idx|
158
+ table[:contents][idx] = {} unless table[:contents][idx].is_a?(Hash)
159
+ table[:contents][idx][:headings] = [] unless table[:contents][idx][:headings]
160
+ table[:contents][idx][:rows] = [[]] unless table[:contents][idx][:rows]
161
+ col_width = table[:contents][idx][:headings].size
162
+ table[:contents][idx][:rows].each do |row|
163
+ raise 'Improper number of columns used in row' if row.size != col_width && col_width > 0
164
+ col_width = row.size
165
+ end
166
+ end
167
+ end
168
+
169
+ # Set the default values for building the table. All of these can be overridden when
170
+ # calling #simple_tables
171
+ def st_defaults
172
+ default_margin = @default_margin || 0
173
+ default_font = @default_font || [font_families.keys.first, {:style => font_families.values.first.keys.first, :size => 6}]
174
+ {
175
+ :table_margin => default_margin, # margin around entire all tables (the margin references the space around all tables. spacing between individual tables is set using :spacing)
176
+ :table_margin_left => 0, # left margin for all tables
177
+ :table_margin_right => 0, # right margin for all tables
178
+ :table_margin_top => 0, # top margin for all tables
179
+ :table_margin_bottom => 0, # bottom margin for all tables
180
+ :default_font => default_font, # default font (defaults to first found font at size 6)
181
+ :default_fill_color => [0,0,0,100], # default fill color (defaults to black)
182
+ :title_font => [font_families.keys.first, {:style => font_families.values.first.keys.first, :size => 18}], # font used for table title (defaults to first found font at size 18)
183
+ :title_text_color => [0,0,0,0], # text color for table title (defaults to white)
184
+ :title_background_color => [100,100,0,0], # background color for table title (defaults to blue)
185
+ :title_text_alignment => :center, # text alignment of title text
186
+ :title_padding => 0, # extra padding around title text
187
+ :headings_font => default_font, # default font used for cells in heading (defaults to first found font at font size 6)
188
+ :headings_text_color_default => [0,0,0,100], # default text color for cells in heading (defaults to black)
189
+ :headings_text_colors => [], # text colors for cells in heading. zero based index corresponds to zero based column
190
+ :headings_background_color_default => nil, #default background color for heading cells (defaults to none)
191
+ :headings_background_colors => [], # background colors for cells in heading. zero based index corresponds to zero based column
192
+ :headings_blackout_color => [0,0,0,70], # color to fill field when value does not exist (default to dark gray)
193
+ :headings_text_alignment => :center, # default text alignment for headings (defaults to center)
194
+ :row_font => default_font, # default font used for cells in a row (defaults to first found found at font size 6)
195
+ :row_text_color_default => [0,0,0,100], # default text color for cells in a row (defaults to black)
196
+ :row_text_colors => [], # text colors for cells in a row. zero based index corresponds to zero based column
197
+ :row_background_color_default => nil, # default background color for table cells (defaults to none)
198
+ :row_background_colors => [], # background colors for cells in a row. zero based index corresponds to zero based column
199
+ :row_text_alignment => :center, # default text alignment for rows (defaults to center)
200
+ :row_blackout_color => [0,0,0,70], # color to fill field when value does not exist (defaults to dark gray)
201
+ :border_color => [0,0,0,100], # color of table border (defaults to black)
202
+ :border_width => 1, # width of table border
203
+ :page_break_on_new_table => true, # move to a new page when a new table is started
204
+ :show_title_after_page_break => true, # print table title after page break before continuing with table
205
+ :show_headings_after_page_break => true, # print column headings after page break before continuing with data rows
206
+ :column_width_restrictions => {:fitted => [], :minimum => {}, :maximum => {}}, # width restrictions (see extended explanation below)
207
+ # Width restrictions can be applied to columns to allow some control over column sizing. All
208
+ # restrictions work using a zero based index reference corresponding to table columns. For example:
209
+ # :fitted => [true, false, true]
210
+ # will make the first and third columns of the table fitted. Fitted columns will be as wide as the widest element and will not
211
+ # be expanded or shrunk when resizing. Minimum and maximum values can be set on columns as well:
212
+ # :minimum => {1 => 20, 4 => 40}, :maximum => {3 => 50}
213
+ # where the key is the column index and the value is the minimum/maximum number of points the column is allowed.
214
+ :new_page => lambda{ start_new_page }, # called to start a new page
215
+ :finalize_page => lambda{}, # called before moving to the next page (example usage would be writing header/footer)
216
+ :padding => 0, # cell padding
217
+ :spacing => 0, # space between tables
218
+ :y_initial => nil, # initial y position when starting to write tables
219
+ :page_width => bounds.width, # width of the page
220
+ :page_height => bounds.height, # height of the page
221
+ :break_for_min_width_on => [',','/',' '] # must be single characters
222
+ }
223
+ end
224
+
225
+ # opts:: Hash of current options
226
+ # Fills out the options hash based on current options
227
+ def st_expand_options(opts, *args)
228
+ unless(args.include?(:nomargins))
229
+ [:table_margin_left, :table_margin_right, :table_margin_top, :table_margin_bottom].each do |sym|
230
+ opts[sym] += opts[:table_margin]
231
+ end
232
+ opts[:table_width] = opts[:page_width] - opts[:table_margin_left] - opts[:table_margin_right]
233
+ opts[:ystart] = opts[:page_height] - opts[:table_margin_top] unless opts[:ystart]
234
+ opts[:xstart] = opts[:table_margin_left] unless opts[:xstart]
235
+ opts[:yend] = opts[:table_margin_bottom] unless opts[:yend]
236
+ opts[:xstop] = opts[:page_width] - opts[:table_margin_right]
237
+ end
238
+ opts[:column_width_restrictions][:fitted] = [] unless opts[:column_width_restrictions][:fitted]
239
+ opts[:column_width_restrictions][:minimum] = {} unless opts[:column_width_restrictions][:minimum]
240
+ opts[:column_width_restrictions][:maximum] = {} unless opts[:column_width_restrictions][:maximum]
241
+ end
242
+
243
+ # args:: Arguments returned for this method
244
+ # When called with no arguments, it returns current settings for
245
+ # things modified when building the table. When arguments are
246
+ # provided, it sets them. For example:
247
+ #
248
+ # original_settings = st_settings_helper
249
+ # # do some stuff here
250
+ # st_settings_helper(*original_settings)
251
+ def st_settings_helper(*args)
252
+ if(args.empty?)
253
+ orig_fill = fill_color
254
+ orig_stroke_color = stroke_color
255
+ orig_width = line_width
256
+ [orig_fill, orig_stroke_color, orig_width]
257
+ else
258
+ fill_color args[0]
259
+ stroke_color args[1]
260
+ line_width args[2]
261
+ args
262
+ end
263
+ end
264
+
265
+ # row:: Row of data
266
+ # point:: x,y coordinate to start at
267
+ # opts:: Hash of options provided for building the table
268
+ # type:: Defaults to row. If not :row, assumes a heading is being written
269
+ # Writes a table row to the page
270
+ # Returns:: new x,y coordinate
271
+ def st_write_row(row, point, widths, opts, type=:row)
272
+ if(type != :row && row.empty?)
273
+ return point
274
+ end
275
+ xpos,ypos = point
276
+ startx = xpos
277
+ origs = st_settings_helper
278
+ height = st_height_row(row, widths, opts, type)
279
+ pad = opts[:padding]
280
+ row.each_with_index do |item,idx|
281
+ bounding_box [xpos,ypos], :height => height, :width => widths[idx] do
282
+ if(item.nil?)
283
+ fill_color opts["#{type}_blackout_color".to_sym]
284
+ fill_rectangle [0, bounds.height], bounds.width, bounds.height
285
+ else(item.is_a?(Hash))
286
+ item = {:content => item.to_s} unless item.is_a?(Hash)
287
+ background = item[:background_color] || opts["#{type}_background_colors".to_sym][idx] || opts["#{type}_background_color_default".to_sym]
288
+ if(background)
289
+ cur_fill = fill_color
290
+ fill_color background
291
+ fill_rectangle [0, bounds.height], bounds.width, bounds.height
292
+ fill_color cur_fill
293
+ end
294
+ cur_fill = fill_color
295
+ fill_color item[:color] || opts["#{type}_text_colors".to_sym][idx] || opts["#{type}_text_color_default".to_sym]
296
+ font *(item[:font] || opts["#{type}_font".to_sym]) do
297
+ alignment = item[:align] || opts["#{type}_text_alignment".to_sym]
298
+ boxed_width = bounds.width - (2 * pad)
299
+ boxed_height = bounds.height - (2 * pad)
300
+ text_height = height_of(item[:content], boxed_width)
301
+ valign_movement = text_height < boxed_height ? (boxed_height - text_height) / 2.0 : 0
302
+ bounding_box [pad, bounds.height - pad], :height => boxed_height, :width => boxed_width do
303
+ move_down valign_movement
304
+ text item[:content], :align => alignment
305
+ end
306
+ end
307
+ fill_color cur_fill
308
+ end
309
+ line_width opts[:border_width]
310
+ stroke_color opts[:border_color]
311
+ stroke_bounds
312
+ end
313
+ xpos += widths[idx]
314
+ end
315
+ st_settings_helper *origs
316
+ [startx,ypos - height]
317
+ end
318
+
319
+ # title:: String title for table
320
+ # point:: x,y coordinate to start printing
321
+ # opts:: Hash of options for table building
322
+ # Prints the table title
323
+ # Returns:: new x,y coordinate
324
+ def st_write_title(title, point, opts)
325
+ unless(title)
326
+ return point
327
+ end
328
+ xpos,ypos = point
329
+ origs = st_settings_helper
330
+ height = st_height_title(title, opts)
331
+ font *opts[:title_font] do
332
+ pad = opts[:title_padding] + opts[:padding]
333
+ bounding_box point, :width => opts[:table_width], :height => height do
334
+ fill_color opts[:title_background_color]
335
+ fill_rectangle [0, bounds.height], bounds.width, bounds.height
336
+ fill_color opts[:title_text_color]
337
+ boxed_width = bounds.width - (2 * pad)
338
+ boxed_height = bounds.height - (2 * pad)
339
+ text_height = height_of(title, boxed_width)
340
+ valign_movement = text_height < boxed_height ? (boxed_height - text_height) / 2.0 : 0
341
+ bounding_box [pad, bounds.height - pad], :height => boxed_height, :width => boxed_width do
342
+ move_down valign_movement
343
+ text title, :align => opts[:title_text_alignment]
344
+ end
345
+ line_width opts[:border_width]
346
+ stroke_color opts[:border_color]
347
+ stroke_bounds
348
+ end
349
+ end
350
+ st_settings_helper(*origs)
351
+ [xpos, ypos - height]
352
+ end
353
+
354
+ # table:: Hash of table contents
355
+ # opts:: Hash of options for building table
356
+ # Determine column widths for the table
357
+ def st_find_column_widths(table, opts)
358
+ info = {:widths => [], :min_widths => []}
359
+ ([table[:headings]] + table[:rows]).each_with_index do |row, i|
360
+ type = i == 0 ? 'headings' : 'row'
361
+ row.each_with_index do |item, idx|
362
+ item = {:content => item.to_s} unless item.is_a?(Hash)
363
+ font *(item[:font] || opts["#{type}_font".to_sym]) do
364
+ string = item[:content].to_s
365
+ w = width_of(string) + (opts[:padding] * 2)
366
+ info[:widths][idx] = w if info[:widths][idx].nil? || info[:widths][idx] < w
367
+ biggest_word = string.tr(opts[:break_for_min_width_on].join(''), opts[:break_for_min_width_on].first).strip.split(opts[:break_for_min_width_on].first).max{|a,b|a.length <=> b.length}
368
+ m = (biggest_word ? width_of(biggest_word) : 0) + (opts[:padding] * 2)
369
+ info[:min_widths][idx] = m if info[:min_widths][idx].nil? || info[:min_widths][idx] < m
370
+ end
371
+ end
372
+ end
373
+ opts[:column_width_restrictions][:minimum].each_pair do |idx, val|
374
+ info[:widths][idx] = val if info[:widths][idx] < val && info[:min_widths][idx] < val
375
+ end
376
+ if(info[:widths].sum > opts[:table_width])
377
+ info = st_shrink_widths(info, opts)
378
+ elsif(info[:widths].sum < opts[:table_width] && info[:widths].sum > 0)
379
+ info = st_expand_widths(info, opts)
380
+ end
381
+ info[:widths]
382
+ end
383
+
384
+ # info:: Hash of width and minimum width values for table columns
385
+ # opts:: Hash of options for building the table
386
+ # Shrinks column widths until table fits within bounds. Will raise error
387
+ # if unable to fit table within bounds.
388
+ def st_shrink_widths(info, opts)
389
+ current_width = info[:widths].sum
390
+ reduce_by = current_width - opts[:table_width]
391
+ no_change = false
392
+ while(reduce_by > 0 && !no_change)
393
+ no_change = true
394
+ modable = {}
395
+ info[:widths].each_with_index do |w, idx|
396
+ modable[idx] = w if !opts[:column_width_restrictions][:fitted][idx] && w != opts[:column_width_restrictions][:minimum][idx]
397
+ end
398
+ modable.each_pair do |idx, w|
399
+ removal = reduce_by * (w / modable.values.sum)
400
+ unless(w <= info[:min_widths][idx])
401
+ hard_min = [opts[:column_width_restrictions][:minimum][idx].to_f, info[:min_widths][idx]].max
402
+ if(hard_min > w - removal && w > hard_min)
403
+ no_change = false
404
+ info[:widths][idx] = hard_min
405
+ elsif(hard_min < w - removal)
406
+ no_change = false
407
+ info[:widths][idx] = w - removal
408
+ end
409
+ end
410
+ end
411
+ current_width = info[:widths].sum
412
+ reduce_by = current_width - opts[:table_width]
413
+ end
414
+ if(no_change && reduce_by > 0)
415
+ raise "Failed to fit table data within boundaries. Too wide by: #{reduce_by} units"
416
+ else
417
+ info
418
+ end
419
+ end
420
+
421
+ # info:: Hash of width and minimum width values for table columns
422
+ # opts:: Hash of options for building the table
423
+ # Expands column widths until table fills bounds. Will raise error if
424
+ # unable to expand columns enough to fit bounds (due to column size restrictions)
425
+ # TODO: add check for fitted columns. check for no change like we do when shrinking
426
+ def st_expand_widths(info, opts)
427
+ expand_by = opts[:table_width] - info[:widths].sum
428
+ while(expand_by > 0)
429
+ modable = {}
430
+ info[:widths].each_with_index do |w, idx|
431
+ modable[idx] = w if !opts[:column_width_restrictions][:fitted][idx] && w != opts[:column_width_restrictions][:maximum][idx]
432
+ end
433
+ extra_pad = expand_by / modable.size.to_f
434
+ modable.each_pair do |idx, w|
435
+ if(opts[:column_width_restrictions][:maximum][idx] && opts[:column_width_restrictions][:maximum][idx] < w + extra_pad)
436
+ info[:widths][idx] = opts[:column_width_restrictions][:maximum][idx]
437
+ else
438
+ info[:widths][idx] = w + extra_pad
439
+ end
440
+ end
441
+ expand_by = opts[:table_width] - info[:widths].sum
442
+ end
443
+ info
444
+ end
445
+
446
+ # title:: String
447
+ # opts:: Hash of options for building the table
448
+ # Returns the height of the title
449
+ def st_height_title(title, opts)
450
+ h = 0
451
+ font *opts[:title_font] do
452
+ h = height_of(title.to_s, opts[:table_width]) + (opts[:padding] * 2) + (opts[:title_padding] * 2)
453
+ end
454
+ h + 2 # bit of safety to keep from busting the cell
455
+ end
456
+
457
+ # row:: Array of row values
458
+ # widths:: Array of column width values
459
+ # opts:: Hash of options for building the table
460
+ # type:: row or heading
461
+ # Returns the max height for the given row
462
+ def st_height_row(row, widths, opts, type=:row)
463
+ return 0 if row.nil? || row.empty?
464
+ h = 0
465
+ row.each_with_index do |item, idx|
466
+ test_height = 0
467
+ item = {:content => item.to_s} unless item.is_a?(Hash)
468
+ font *(item[:font] || opts["#{type}_font".to_sym]) do
469
+ test_height = height_of(item[:content].to_s, widths[idx] - (opts[:padding] * 2))
470
+ end
471
+ h = test_height if h < test_height
472
+ end
473
+ h + (opts[:padding] * 2) + 2 # bit of safety to keep from busting the cell
474
+ end
475
+ end
476
+ end
@@ -0,0 +1,38 @@
1
+ require 'prawn'
2
+
3
+ class RabidPrawns
4
+ class << self
5
+ # Returns array of available rabids for loading
6
+ def available_rabids
7
+ libs = []
8
+ Dir.new(File.expand_path(File.expand_path(__FILE__) + '/../rabidprawns')).each do |item|
9
+ next if %w(. ..).include? item
10
+ libs << item.gsub('.rb', '').to_sym
11
+ end
12
+ libs
13
+ end
14
+
15
+ # args:: rabid symbols
16
+ # Load the given rabids
17
+ def load_rabid(*args)
18
+ list = self.available_rabids
19
+ args.each do |arg|
20
+ raise ArgumentError.new "Unknown rabid requested: #{arg}" unless list.include?(arg)
21
+ require "rabidprawns/#{arg}"
22
+ end
23
+ end
24
+
25
+ alias :load_rabids :load_rabid
26
+ end
27
+ end
28
+
29
+ module Prawn
30
+ class Document
31
+ def height_of(text, args)
32
+ unless(args.is_a?(Hash))
33
+ args = {:width => args}
34
+ end
35
+ super(text, args)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'rabidprawns'
3
+ s.version = '0.0.1'
4
+ s.summary = 'Rabid Prawns'
5
+ s.author = 'Chris Roberts'
6
+ s.email = 'chrisroberts.code@gmail.com'
7
+ s.homepage = 'http://github.com/chrisroberts/rabidprawns'
8
+ s.description = 'Rabid Prawns'
9
+ s.require_path = 'lib'
10
+ s.has_rdoc = true
11
+ s.extra_rdoc_files = ['README.rdoc']
12
+ s.files = %w(lib/rabidprawns.rb lib/rabidprawns/simple_tables.rb lib/rabidprawns/columned_page.rb README.rdoc rabidprawns.gemspec)
13
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rabidprawns
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Chris Roberts
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-08-04 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Rabid Prawns
23
+ email: chrisroberts.code@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README.rdoc
30
+ files:
31
+ - lib/rabidprawns.rb
32
+ - lib/rabidprawns/simple_tables.rb
33
+ - lib/rabidprawns/columned_page.rb
34
+ - README.rdoc
35
+ - rabidprawns.gemspec
36
+ has_rdoc: true
37
+ homepage: http://github.com/chrisroberts/rabidprawns
38
+ licenses: []
39
+
40
+ post_install_message:
41
+ rdoc_options: []
42
+
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ hash: 3
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.7
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Rabid Prawns
70
+ test_files: []
71
+