jsanders-ruport 1.7.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.
Files changed (76) hide show
  1. data/AUTHORS +48 -0
  2. data/LICENSE +59 -0
  3. data/README +114 -0
  4. data/Rakefile +93 -0
  5. data/examples/RWEmerson.jpg +0 -0
  6. data/examples/anon.rb +43 -0
  7. data/examples/btree/commaleon/commaleon.rb +263 -0
  8. data/examples/btree/commaleon/sample_data/ticket_count.csv +124 -0
  9. data/examples/btree/commaleon/sample_data/ticket_count2.csv +119 -0
  10. data/examples/centered_pdf_text_box.rb +83 -0
  11. data/examples/data/tattle.dump +82 -0
  12. data/examples/example.csv +3 -0
  13. data/examples/line_plotter.rb +61 -0
  14. data/examples/pdf_report_with_common_base.rb +72 -0
  15. data/examples/png_embed.rb +54 -0
  16. data/examples/roadmap.png +0 -0
  17. data/examples/row_renderer.rb +39 -0
  18. data/examples/simple_pdf_lines.rb +25 -0
  19. data/examples/simple_templating_example.rb +34 -0
  20. data/examples/tattle_ruby_version.rb +39 -0
  21. data/examples/tattle_rubygems_version.rb +37 -0
  22. data/examples/trac_ticket_status.rb +59 -0
  23. data/lib/ruport.rb +127 -0
  24. data/lib/ruport/controller.rb +616 -0
  25. data/lib/ruport/controller/grouping.rb +71 -0
  26. data/lib/ruport/controller/table.rb +54 -0
  27. data/lib/ruport/data.rb +4 -0
  28. data/lib/ruport/data/feeder.rb +111 -0
  29. data/lib/ruport/data/grouping.rb +399 -0
  30. data/lib/ruport/data/record.rb +297 -0
  31. data/lib/ruport/data/table.rb +950 -0
  32. data/lib/ruport/extensions.rb +4 -0
  33. data/lib/ruport/formatter.rb +254 -0
  34. data/lib/ruport/formatter/csv.rb +149 -0
  35. data/lib/ruport/formatter/html.rb +161 -0
  36. data/lib/ruport/formatter/pdf.rb +591 -0
  37. data/lib/ruport/formatter/template.rb +187 -0
  38. data/lib/ruport/formatter/text.rb +231 -0
  39. data/lib/uport.rb +1 -0
  40. data/test/controller_test.rb +743 -0
  41. data/test/csv_formatter_test.rb +164 -0
  42. data/test/data_feeder_test.rb +88 -0
  43. data/test/grouping_test.rb +410 -0
  44. data/test/helpers.rb +11 -0
  45. data/test/html_formatter_test.rb +201 -0
  46. data/test/pdf_formatter_test.rb +354 -0
  47. data/test/record_test.rb +332 -0
  48. data/test/samples/addressbook.csv +6 -0
  49. data/test/samples/data.csv +3 -0
  50. data/test/samples/data.tsv +3 -0
  51. data/test/samples/dates.csv +1409 -0
  52. data/test/samples/erb_test.sql +1 -0
  53. data/test/samples/query_test.sql +1 -0
  54. data/test/samples/ruport_test.sql +8 -0
  55. data/test/samples/test.sql +2 -0
  56. data/test/samples/test.yaml +3 -0
  57. data/test/samples/ticket_count.csv +124 -0
  58. data/test/table_pivot_test.rb +134 -0
  59. data/test/table_test.rb +838 -0
  60. data/test/template_test.rb +48 -0
  61. data/test/text_formatter_test.rb +258 -0
  62. data/util/bench/data/record/bench_as_vs_to.rb +18 -0
  63. data/util/bench/data/record/bench_constructor.rb +46 -0
  64. data/util/bench/data/record/bench_indexing.rb +65 -0
  65. data/util/bench/data/record/bench_reorder.rb +35 -0
  66. data/util/bench/data/record/bench_to_a.rb +19 -0
  67. data/util/bench/data/table/bench_column_manip.rb +103 -0
  68. data/util/bench/data/table/bench_dup.rb +24 -0
  69. data/util/bench/data/table/bench_init.rb +67 -0
  70. data/util/bench/data/table/bench_manip.rb +125 -0
  71. data/util/bench/formatter/bench_csv.rb +14 -0
  72. data/util/bench/formatter/bench_html.rb +14 -0
  73. data/util/bench/formatter/bench_pdf.rb +14 -0
  74. data/util/bench/formatter/bench_text.rb +14 -0
  75. data/util/bench/samples/tattle.csv +1237 -0
  76. metadata +176 -0
@@ -0,0 +1,591 @@
1
+ # Ruport : Extensible Reporting System
2
+ #
3
+ # formatter/pdf.rb provides text formatting for Ruport.
4
+ #
5
+ # Created by Gregory Brown, February 2006
6
+ # Extended by James Healy, Fall 2006
7
+ # Copyright (C) 2006-2007 Gregory Brown / James Healy, All Rights Reserved.
8
+ #
9
+ # Initially inspired by some ideas and code from Simon Claret,
10
+ # with many improvements from James Healy and Michael Milner over time.
11
+ #
12
+ # This is free software distributed under the same terms as Ruby 1.8
13
+ # See LICENSE and COPYING for details.
14
+ #
15
+ module Ruport
16
+
17
+ # This class provides PDF output for Ruport's Table, Group, and Grouping
18
+ # controllers. It wraps Austin Ziegler's PDF::Writer to provide a higher
19
+ # level interface and provides a number of helpers designed to make
20
+ # generating PDF reports much easier. You will typically want to build
21
+ # subclasses of this formatter to customize it as needed.
22
+ #
23
+ # Many methods forward options to PDF::Writer, so you may wish to consult
24
+ # its API docs.
25
+ #
26
+ # === Rendering Options
27
+ # General:
28
+ # * paper_size #=> "LETTER"
29
+ # * paper_orientation #=> :portrait
30
+ #
31
+ # Text:
32
+ # * text_format (sets options to be passed to add_text by default)
33
+ #
34
+ # Table:
35
+ # * table_format (a hash that can take any of the options available
36
+ # to PDF::SimpleTable)
37
+ # * table_format[:maximum_width] #=> 500
38
+ #
39
+ # Grouping:
40
+ # * style (:inline,:justified,:separated,:offset)
41
+ #
42
+ class Formatter::PDF < Formatter
43
+
44
+ module PDFWriterProxy #:nodoc:
45
+ def method_missing(id,*args)
46
+ super(id,*args)
47
+ rescue
48
+ pdf_writer.send(id,*args)
49
+ end
50
+ end
51
+
52
+ renders :pdf, :for => [ Controller::Row, Controller::Table,
53
+ Controller::Group, Controller::Grouping ]
54
+
55
+ attr_writer :pdf_writer
56
+
57
+
58
+ # If you use this macro in your formatter, Ruport will automatically forward
59
+ # calls to the underlying PDF::Writer, for any methods that are not wrapped
60
+ # or redefined.
61
+ def self.proxy_to_pdf_writer
62
+ include PDFWriterProxy
63
+ end
64
+
65
+ save_as_binary_file
66
+
67
+ def initialize
68
+ Ruport.quiet do
69
+ require "pdf/writer"
70
+ require "pdf/simpletable"
71
+ end
72
+ end
73
+
74
+ # Hook for setting available options using a template. See the template
75
+ # documentation for the available options and their format.
76
+ def apply_template
77
+ apply_page_format_template(template.page)
78
+ apply_text_format_template(template.text)
79
+ apply_table_format_template(template.table)
80
+ apply_column_format_template(template.column)
81
+ apply_heading_format_template(template.heading)
82
+ apply_grouping_format_template(template.grouping)
83
+ end
84
+
85
+ # Returns the current PDF::Writer object or creates a new one if it has not
86
+ # been set yet.
87
+ #
88
+ def pdf_writer
89
+ @pdf_writer ||= options.formatter ||
90
+ ::PDF::Writer.new( :paper => options.paper_size || "LETTER",
91
+ :orientation => options.paper_orientation || :portrait)
92
+ end
93
+
94
+ # Calls the draw_table method.
95
+ #
96
+ def build_table_body
97
+ draw_table(data)
98
+ end
99
+
100
+ # Appends the results of PDF::Writer#render to output for your
101
+ # <tt>pdf_writer</tt> object.
102
+ #
103
+ def finalize_table
104
+ render_pdf unless options.skip_finalize_table
105
+ end
106
+
107
+ # Generates a header with the group name for Controller::Group.
108
+ def build_group_header
109
+ pad(10) { add_text data.name.to_s, :justification => :center }
110
+ end
111
+
112
+ # Renders the group as a table for Controller::Group.
113
+ def build_group_body
114
+ render_table data, options.to_hash.merge(:formatter => pdf_writer)
115
+ end
116
+
117
+ # Determines which style to use and renders the main body for
118
+ # Controller::Grouping.
119
+ def build_grouping_body
120
+ case options.style
121
+ when :inline
122
+ render_inline_grouping(options.to_hash.merge(:formatter => pdf_writer,
123
+ :skip_finalize_table => true))
124
+ when :justified, :separated
125
+ render_justified_or_separated_grouping
126
+ when :offset
127
+ render_offset_grouping
128
+ else
129
+ raise NotImplementedError, "Unknown style"
130
+ end
131
+ end
132
+
133
+ # Calls <tt>render_pdf</tt>.
134
+ def finalize_grouping
135
+ render_pdf
136
+ end
137
+
138
+ # Call PDF::Writer#text with the given arguments, using
139
+ # <tt>text_format</tt> defaults, if they are defined.
140
+ #
141
+ # Example:
142
+ #
143
+ # options.text_format { :font_size => 14 }
144
+ #
145
+ # add_text("Hello Joe") #renders at 14pt
146
+ # add_text("Hello Mike",:font_size => 16) # renders at 16pt
147
+ def add_text(text, format_opts={})
148
+ format_opts = options.text_format.merge(format_opts) if options.text_format
149
+ pdf_writer.text(text, format_opts)
150
+ end
151
+
152
+ # Calls PDF::Writer#render and appends to <tt>output</tt>.
153
+ def render_pdf
154
+ output << pdf_writer.render
155
+ end
156
+
157
+ # - If the image is bigger than the box, it will be scaled down until
158
+ # it fits.
159
+ # - If the image is smaller than the box, it won't be resized.
160
+ #
161
+ # options:
162
+ # - :x: left bound of box
163
+ # - :y: bottom bound of box
164
+ # - :width: width of box
165
+ # - :height: height of box
166
+ #
167
+ def center_image_in_box(path, image_opts={})
168
+ x = image_opts[:x]
169
+ y = image_opts[:y]
170
+ width = image_opts[:width]
171
+ height = image_opts[:height]
172
+ info = ::PDF::Writer::Graphics::ImageInfo.new(File.open(path, "rb"))
173
+
174
+ # reduce the size of the image until it fits into the requested box
175
+ img_width, img_height =
176
+ fit_image_in_box(info.width,width,info.height,height)
177
+
178
+ # if the image is smaller than the box, calculate the white space buffer
179
+ x, y = add_white_space(x,y,img_width,width,img_height,height)
180
+
181
+ pdf_writer.add_image_from_file(path, x, y, img_width, img_height)
182
+ end
183
+
184
+ # Draws some text on the canvas, surrounded by a box with rounded corners.
185
+ #
186
+ # Yields an OpenStruct which options can be defined on.
187
+ #
188
+ # Example:
189
+ #
190
+ # rounded_text_box(options.text) do |o|
191
+ # o.radius = 5
192
+ # o.width = options.width || 400
193
+ # o.height = options.height || 130
194
+ # o.font_size = options.font_size || 12
195
+ # o.heading = options.heading
196
+ #
197
+ # o.x = pdf_writer.absolute_x_middle - o.width/2
198
+ # o.y = 300
199
+ # end
200
+ #
201
+ def rounded_text_box(text)
202
+ opts = OpenStruct.new
203
+ yield(opts)
204
+
205
+ resize_text_to_box(text, opts)
206
+
207
+ pdf_writer.save_state
208
+ draw_box(opts.x, opts.y, opts.width, opts.height, opts.radius,
209
+ opts.fill_color, opts.stroke_color)
210
+ add_text_with_bottom_border(opts.heading, opts.x, opts.y,
211
+ opts.width, opts.font_size) if opts.heading
212
+ pdf_writer.restore_state
213
+
214
+ start_position = opts.heading ? opts.y - 20 : opts.y
215
+ draw_text(text, :y => start_position,
216
+ :left => opts.x,
217
+ :right => opts.x + opts.width,
218
+ :justification => opts.justification || :center,
219
+ :font_size => opts.font_size)
220
+ move_cursor_to(opts.y - opts.height)
221
+ end
222
+
223
+ # Adds n to pdf_writer.y, moving the vertical drawing position in the
224
+ # document.
225
+ def move_cursor(n)
226
+ pdf_writer.y += n
227
+ end
228
+
229
+ # Moves the cursor to a specific y coordinate in the document.
230
+ def move_cursor_to(n)
231
+ pdf_writer.y = n
232
+ end
233
+
234
+ # Moves the vertical drawing position in the document upwards by n.
235
+ def move_up(n)
236
+ pdf_writer.y += n
237
+ end
238
+
239
+ def move_down(n)
240
+ pdf_writer.y -= n
241
+ end
242
+
243
+ # Adds a specified amount of whitespace above and below the code
244
+ # in your block. For example, if you want to surround the top and
245
+ # bottom of a line of text with 5 pixels of whitespace:
246
+ #
247
+ # pad(5) { add_text "This will be padded top and bottom" }
248
+ def pad(y,&block)
249
+ move_cursor(-y)
250
+ block.call
251
+ move_cursor(-y)
252
+ end
253
+
254
+ # Adds a specified amount of whitespace above the code in your block.
255
+ # For example, if you want to add a 10 pixel buffer to the top of a
256
+ # line of text:
257
+ #
258
+ # pad_top(10) { add_text "This will be padded on top" }
259
+ def pad_top(y,&block)
260
+ move_cursor(-y)
261
+ block.call
262
+ end
263
+
264
+ # Adds a specified amount of whitespace below the code in your block.
265
+ # For example, if you want to add a 10 pixel buffer to the bottom of a
266
+ # line of text:
267
+ #
268
+ # pad_bottom(10) { add_text "This will be padded on bottom" }
269
+ def pad_bottom(y,&block)
270
+ block.call
271
+ move_cursor(-y)
272
+ end
273
+
274
+ # Draws a PDF::SimpleTable using the given data (usually a Data::Table).
275
+ # Takes all the options you can set on a PDF::SimpleTable object,
276
+ # see the PDF::Writer API docs for details, or check our quick reference
277
+ # at:
278
+ #
279
+ # http://stonecode.svnrepository.com/ruport/trac.cgi/wiki/PdfWriterQuickRef
280
+ def draw_table(table_data, format_opts={})
281
+ m = "PDF Formatter requires column_names to be defined"
282
+ raise FormatterError, m if table_data.column_names.empty?
283
+
284
+ table_data.rename_columns { |c| c.to_s }
285
+
286
+ if options.table_format
287
+ format_opts =
288
+ Marshal.load(Marshal.dump(options.table_format.merge(format_opts)))
289
+ end
290
+
291
+ old = pdf_writer.font_size
292
+
293
+ ::PDF::SimpleTable.new do |table|
294
+ table.maximum_width = 500
295
+ table.column_order = table_data.column_names
296
+ table.data = table_data
297
+ table.data = [{}] if table.data.empty?
298
+ apply_pdf_table_column_opts(table,table_data,format_opts)
299
+
300
+ format_opts.each {|k,v| table.send("#{k}=", v) }
301
+ table.render_on(pdf_writer)
302
+ end
303
+
304
+ pdf_writer.font_size = old
305
+ end
306
+
307
+ # This module provides tools to simplify some common drawing operations.
308
+ # It is included by default in the PDF formatter.
309
+ #
310
+ module DrawingHelpers
311
+
312
+ # Draws a horizontal line from x1 to x2
313
+ def horizontal_line(x1,x2)
314
+ pdf_writer.line(x1,cursor,x2,cursor)
315
+ pdf_writer.stroke
316
+ end
317
+
318
+ # Draws a horizontal line from left_boundary to right_boundary
319
+ def horizontal_rule
320
+ horizontal_line(left_boundary,right_boundary)
321
+ end
322
+
323
+ alias_method :hr, :horizontal_rule
324
+
325
+ # Draws a vertical line at x from y1 to y2
326
+ def vertical_line_at(x,y1,y2)
327
+ pdf_writer.line(x,y1,x,y2)
328
+ pdf_writer.stroke
329
+ end
330
+
331
+ # Alias for PDF::Writer#absolute_left_margin
332
+ def left_boundary
333
+ pdf_writer.absolute_left_margin
334
+ end
335
+
336
+ # Alias for PDF::Writer#absolute_right_margin
337
+ def right_boundary
338
+ pdf_writer.absolute_right_margin
339
+ end
340
+
341
+ # Alias for PDF::Writer#absolute_top_margin
342
+ def top_boundary
343
+ pdf_writer.absolute_top_margin
344
+ end
345
+
346
+ # Alias for PDF::Writer#absolute_bottom_margin
347
+ def bottom_boundary
348
+ pdf_writer.absolute_bottom_margin
349
+ end
350
+
351
+ # Alias for PDF::Writer#y
352
+ def cursor
353
+ pdf_writer.y
354
+ end
355
+
356
+ # Draws text at an absolute location, defined by
357
+ # :y, :x1|:left, :x2|:right
358
+ #
359
+ # All options to add_text are also supported.
360
+ def draw_text(text,text_opts)
361
+ ypos = cursor
362
+ move_cursor_to(text_opts[:y]) if text_opts[:y]
363
+ add_text(text,
364
+ text_opts.merge(:absolute_left => text_opts[:x1] || text_opts[:left],
365
+ :absolute_right => text_opts[:x2] || text_opts[:right]))
366
+ move_cursor_to(ypos)
367
+ end
368
+
369
+ # Draws text at an absolute location, defined by
370
+ # :y, :x1|:left
371
+ #
372
+ # The x position defaults to the left margin and the
373
+ # y position defaults to the current cursor location.
374
+ #
375
+ # Uses PDF::Writer#add_text, so it will ignore any options not supported
376
+ # by that method.
377
+ def draw_text!(text,text_opts)
378
+ ypos = cursor
379
+ pdf_writer.add_text(text_opts[:x1] || text_opts[:left] || left_boundary,
380
+ text_opts[:y] || ypos,
381
+ text,
382
+ text_opts[:font_size],
383
+ text_opts[:angle] || 0)
384
+ move_cursor_to(ypos)
385
+ end
386
+
387
+ def finalize
388
+ render_pdf
389
+ end
390
+ end
391
+
392
+ include DrawingHelpers
393
+
394
+ private
395
+
396
+ def apply_pdf_table_column_opts(table,table_data,format_opts)
397
+ column_opts = format_opts.delete(:column_options)
398
+
399
+ if column_opts
400
+ heading_opts = column_opts.delete(:heading)
401
+ if column_opts[:justification]
402
+ heading_opts ||= {}
403
+ heading_opts = {
404
+ :justification => column_opts[:justification]
405
+ }.merge(heading_opts)
406
+ end
407
+ specific = get_specific_column_options(table_data.column_names,
408
+ column_opts)
409
+ columns = table_data.column_names.inject({}) { |s,c|
410
+ s.merge( c => ::PDF::SimpleTable::Column.new(c) { |col|
411
+ col.heading = create_heading(heading_opts)
412
+ column_opts.each { |k,v| col.send("#{k}=",v) }
413
+ # use the specific column names now
414
+ specific[c].each { |k,v| col.send("#{k}=",v) }
415
+ })
416
+ }
417
+ table.columns = columns
418
+ end
419
+ end
420
+
421
+ def get_specific_column_options(column_names,column_opts)
422
+ column_names.inject({}) do |s,c|
423
+ opts = column_opts.delete(c) || {}
424
+ if opts[:heading]
425
+ opts = opts.merge(:heading => create_heading(opts[:heading]))
426
+ end
427
+ s.merge(c => opts)
428
+ end
429
+ end
430
+
431
+ def create_heading(heading_opts)
432
+ heading_opts ||= {}
433
+ ::PDF::SimpleTable::Column::Heading.new {|head|
434
+ heading_opts.each {|k,v| head.send("#{k}=",v) }
435
+ }
436
+ end
437
+
438
+ def grouping_columns
439
+ data.data.to_a[0][1].column_names.dup.unshift(data.grouped_by)
440
+ end
441
+
442
+ def table_with_grouped_by_column
443
+ Ruport::Data::Table.new(:column_names => grouping_columns)
444
+ end
445
+
446
+ def render_justified_or_separated_grouping
447
+ table = table_with_grouped_by_column
448
+ data.each do |name,group|
449
+ group.each_with_index do |r,i|
450
+ if i == 0
451
+ table << { data.grouped_by => "<b>#{name}</b>" }.merge(r.to_hash)
452
+ else
453
+ table << r
454
+ end
455
+ end
456
+ table << [" "] if options.style == :separated
457
+ end
458
+ render_table table, options.to_hash.merge(:formatter => pdf_writer)
459
+ end
460
+
461
+ def render_offset_grouping
462
+ table = table_with_grouped_by_column
463
+ data.each do |name,group|
464
+ table << ["<b>#{name}</b>"]
465
+ group.each {|r| table << r }
466
+ end
467
+ render_table table, options.to_hash.merge(:formatter => pdf_writer)
468
+ end
469
+
470
+ def image_fits_in_box?(img_width,box_width,img_height,box_height)
471
+ !(img_width > box_width || img_height > box_height)
472
+ end
473
+
474
+ def fit_image_in_box(img_width,box_width,img_height,box_height)
475
+ img_ratio = img_height.to_f / img_width.to_f
476
+ until image_fits_in_box?(img_width,box_width,img_height,box_height)
477
+ img_width -= 1
478
+ img_height = img_width * img_ratio
479
+ end
480
+ return img_width, img_height
481
+ end
482
+
483
+ def add_white_space(x,y,img_width,box_width,img_height,box_height)
484
+ if img_width < box_width
485
+ white_space = box_width - img_width
486
+ x = x + (white_space / 2)
487
+ end
488
+ if img_height < box_height
489
+ white_space = box_height - img_height
490
+ y = y + (white_space / 2)
491
+ end
492
+ return x, y
493
+ end
494
+
495
+ def resize_text_to_box(text,opts)
496
+ loop do
497
+ sz = pdf_writer.text_width(text, opts.font_size)
498
+ opts.x + sz > opts.x + opts.width or break
499
+ opts.font_size -= 1
500
+ end
501
+ end
502
+
503
+ def draw_box(x,y,width,height,radius,fill_color=nil,stroke_color=nil)
504
+ pdf_writer.fill_color(fill_color || Color::RGB::White)
505
+ pdf_writer.stroke_color(stroke_color || Color::RGB::Black)
506
+ pdf_writer.rounded_rectangle(x, y, width, height, radius).fill_stroke
507
+ end
508
+
509
+ def add_text_with_bottom_border(text,x,y,width,font_size)
510
+ pdf_writer.line( x, y - 20,
511
+ x + width, y - 20).stroke
512
+ pdf_writer.fill_color(Color::RGB::Black)
513
+ move_cursor_to(y - 3)
514
+ add_text("<b>#{text}</b>",
515
+ :absolute_left => x, :absolute_right => x + width,
516
+ :justification => :center, :font_size => font_size)
517
+ end
518
+
519
+ def apply_page_format_template(t)
520
+ t = (t || {}).merge(options.page_format || {})
521
+ options.paper_size ||= t[:size]
522
+ options.paper_orientation ||= t[:layout]
523
+ end
524
+
525
+ def apply_text_format_template(t)
526
+ t = (t || {}).merge(options.text_format || {})
527
+ options.text_format = t unless t.empty?
528
+ end
529
+
530
+ def apply_table_format_template(t)
531
+ t = (t || {}).merge(options.table_format || {})
532
+ options.table_format = t unless t.empty?
533
+ end
534
+
535
+ def apply_column_format_template(t)
536
+ t = (t || {}).merge(options.column_format || {})
537
+ column_opts = {}
538
+ column_opts.merge!(:justification => t[:alignment]) if t[:alignment]
539
+ column_opts.merge!(:width => t[:width]) if t[:width]
540
+ unless column_opts.empty?
541
+ if options.table_format
542
+ if options.table_format[:column_options]
543
+ options.table_format[:column_options] =
544
+ column_opts.merge(options.table_format[:column_options])
545
+ else
546
+ options.table_format.merge!(:column_options => column_opts)
547
+ end
548
+ else
549
+ options.table_format = { :column_options => column_opts }
550
+ end
551
+ end
552
+ end
553
+
554
+ def apply_heading_format_template(t)
555
+ t = (t || {}).merge(options.heading_format || {})
556
+ heading_opts = {}
557
+ heading_opts.merge!(:justification => t[:alignment]) if t[:alignment]
558
+ heading_opts.merge!(:bold => t[:bold]) unless t[:bold].nil?
559
+ heading_opts.merge!(:title => t[:title]) if t[:title]
560
+ unless heading_opts.empty?
561
+ if options.table_format
562
+ if options.table_format[:column_options]
563
+ if options.table_format[:column_options][:heading]
564
+ options.table_format[:column_options][:heading] =
565
+ heading_opts.merge(
566
+ options.table_format[:column_options][:heading]
567
+ )
568
+ else
569
+ options.table_format[:column_options].merge!(
570
+ :heading => heading_opts
571
+ )
572
+ end
573
+ else
574
+ options.table_format.merge!(
575
+ :column_options => { :heading => heading_opts }
576
+ )
577
+ end
578
+ else
579
+ options.table_format = {
580
+ :column_options => { :heading => heading_opts }
581
+ }
582
+ end
583
+ end
584
+ end
585
+
586
+ def apply_grouping_format_template(t)
587
+ t = (t || {}).merge(options.grouping_format || {})
588
+ options.style ||= t[:style]
589
+ end
590
+ end
591
+ end