rabidprawns 0.0.1

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