juno-report 0.1.4 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Edson Júnior
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # Juno Report
2
+
3
+ Cry with Report Generation? Nevermore!
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'juno-report'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install juno-report
18
+
19
+ ## Usage
20
+
21
+ The generating reports is based on a YAML file with all rules, with the fields and their settings, divided by sections.
22
+
23
+ ### Body section
24
+
25
+ Represents the records which will be iterated to report generating
26
+
27
+ ```yaml
28
+ # example.yml
29
+ body:
30
+ settings: {posY: 40, height: 25}
31
+ fields:
32
+ test_field1: [10]
33
+ test_field2: [220]
34
+ test_field3: [430, {column: "Named Test Field 3"}]
35
+ footer:
36
+ label_total: [10, {value: "Total"}]
37
+ totalizer: [220, {behavior: count}]
38
+ test_field3: [430, {behavior: sum}]
39
+ ```
40
+ The [body] section ***must*** have three rules:
41
+
42
+ * `settings`: Defines some configurations for body, like their height and ypos.
43
+ * `fields`: Describes each field of record to be iterated.
44
+ * `footer`: Drawn at the end of all printed records and calculates fields according behavior parameter.
45
+
46
+ Each of these rules receives a array, where the first position is an integer representing the field horizontal position and
47
+ the second position is a hash with some configurations.
48
+
49
+
50
+ ##### Settings
51
+
52
+ * `height`: Set the of each row at report [Integer].
53
+ * `posY`: Relative vertical position of last row at report [Integer].
54
+ * `groups`: Describes which groups will be printed (More bellow) [Array].
55
+
56
+ ##### Fields
57
+
58
+ * `size`: Font size of the field [Integer].
59
+ * `align`: Defines the text alignment for each value [left|center|right].
60
+ * `font`: Supports all font type provided by Prawn gem (See more in http://rubydoc.info/gems/prawn/Prawn/Font/AFM).
61
+ * `style`: Stylistic variations of a font [bold|italic].
62
+ * `value`: Fixed text to be printed [string].
63
+ * `column`: The header are "humanized" automatically, but you can set other value manually [string].
64
+
65
+ ##### Footer
66
+
67
+ * `behavior`: Specify a function to be performed, sending as parameter the fieldname value [string].
68
+ * `label`: Preppends a text to fieldname value specified [string].
69
+ * `value`: Fixed text to be printed (fieldname value will be ignored) [string].
70
+
71
+ With theses configurations already is possible generate a basic report, without groups feature.
72
+ For this we need call the generate method on JunoReport module:
73
+
74
+ ```ruby
75
+ # test.rb
76
+ require 'juno-report'
77
+
78
+ data = [
79
+ {:test_field1 => 'Test Value 1', :test_field2 => "Test Value 2", :test_field3 => 50},
80
+ {:test_field1 => 'Test Value 1', :test_field2 => "Test Value 2", :test_field3 => 7},
81
+ {:test_field1 => 'Test Value 1', :test_field2 => "Test Value 2", :test_field3 => 10},
82
+ {:test_field1 => 'Test Value 1', :test_field2 => "Test Value 2", :test_field3 => 5},
83
+ {:test_field1 => 'Test Value 1', :test_field2 => "Test Value 2", :test_field3 => 2},
84
+ {:test_field1 => 'Test Value 1', :test_field2 => "Test Value 2", :test_field3 => 4},
85
+ {:test_field1 => 'Test Value 1', :test_field2 => "Test Value 2", :test_field3 => 24}
86
+ ]
87
+
88
+ JunoReport::generate(data, :report => 'example')
89
+ ```
90
+
91
+ The first parameter must be a set of hash or objects which represent the report data. And the second parameter is a hash
92
+ with the report settings, that can be:
93
+
94
+ * `report`: The source of all rules. Must be a YAML file [string].
95
+ * `type`: Specify if the report will be writed on the disc or returned to the caller such a stream [:file|:stream]
96
+ * `filename`: Defines the report name which will be writed on disc. If not specified, the default name is "report.pdf" [string].
97
+
98
+ ### Page section
99
+
100
+ You may want to print a title every time that a page is created. You simply insert a [page] section.
101
+
102
+ ```yaml
103
+ # example.yml
104
+ page:
105
+ fields:
106
+ title1: [260, {size: 24, align: center, value: "Test Report"}]
107
+ subtitle1: [260, {size: 20, posY: 20, align: center, value: "Generated by Juno Report"}]
108
+ body:
109
+ settings: {posY: 40, height: 25}
110
+ fields:
111
+ test_field1: [10]
112
+ test_field2: [220]
113
+ test_field3: [430, {column: "Named Test Field 3"}]
114
+ footer:
115
+ label_total: [10, {value: "Total"}]
116
+ totalizer: [220, {behavior: count}]
117
+ test_field3: [430, {behavior: sum}]
118
+ ```
119
+
120
+ ### Groups section
121
+
122
+ For each item in groups parameter at body section you should create a section with same name.
123
+ This section represents the header configurations to every time that the group is printed and behaves like [body] section.
124
+
125
+ ```yaml
126
+ # example.yml
127
+ page:
128
+ fields:
129
+ title1: [260, {size: 24, align: center, value: "Test Report"}]
130
+ subtitle1: [260, {size: 20, posY: 20, align: center, value: "Generated by Juno Report"}]
131
+ body:
132
+ settings: {posY: 40, height: 30, groups: [group_field1, group_field2]}
133
+ fields:
134
+ test_field1: [10]
135
+ test_field2: [220]
136
+ test_field3: [420, {column: "Named Test Field 3"}]
137
+ footer:
138
+ label_total: [10, {value: "Total"}]
139
+ totalizer1: [220, {behavior: count}]
140
+ test_field3: [420, {behavior: sum}]
141
+ group_field1:
142
+ settings: {posY: 30, height: 10}
143
+ fields:
144
+ group_field1: [10, size: 25]
145
+ footer:
146
+ group_field1: [10, {label: "Total "}]
147
+ totalizer1: [220, {behavior: count}]
148
+ test_field3: [420, {behavior: sum}]
149
+ group_field2:
150
+ settings: {posY: 30, height: 25}
151
+ fields:
152
+ group_field2: [10, size: 17]
153
+ footer:
154
+ group_field2: [10, {label: "Total "}]
155
+ totalizer1: [220, {behavior: count}]
156
+ test_field3: [420, {behavior: sum}]
157
+ ```
158
+
159
+ Every time that a "group field" value changes, the group will be printed.
160
+
161
+ ```ruby
162
+ # test.rb
163
+ require 'juno-report'
164
+
165
+ data = [
166
+ {:test_field1 => 'Test Value 1', :test_field2 => "Test Value 1", :test_field3 => 50, :group_field1 => 'Group 1', :group_field2 => 'Subgroup 1'},
167
+ {:test_field1 => 'Test Value 2', :test_field2 => "Test Value 2", :test_field3 => 16, :group_field1 => 'Group 1', :group_field2 => 'Subgroup 1'},
168
+ {:test_field1 => 'Test Value 5', :test_field2 => "Test Value 3", :test_field3 => 7, :group_field1 => 'Group 1', :group_field2 => 'Subgroup 1'},
169
+ {:test_field1 => 'Test Value 3', :test_field2 => "Test Value 9", :test_field3 => 10, :group_field1 => 'Group 1', :group_field2 => 'Subgroup 2'},
170
+ {:test_field1 => 'Test Value 3', :test_field2 => "Test Value 2", :test_field3 => 4, :group_field1 => 'Group 1', :group_field2 => 'Subgroup 2'},
171
+ {:test_field1 => 'Test Value 9', :test_field2 => "Test Value 4", :test_field3 => 10, :group_field1 => 'Group 1', :group_field2 => 'Subgroup 2'},
172
+ {:test_field1 => 'Test Value 7', :test_field2 => "Test Value 5", :test_field3 => 5, :group_field1 => 'Group 1', :group_field2 => 'Subgroup 3'},
173
+ {:test_field1 => 'Test Value 3', :test_field2 => "Test Value 5", :test_field3 => 2, :group_field1 => 'Group 1', :group_field2 => 'Subgroup 3'},
174
+ {:test_field1 => 'Test Value 3', :test_field2 => "Test Value 2", :test_field3 => 27, :group_field1 => 'Group 2', :group_field2 => 'Subgroup 1'},
175
+ {:test_field1 => 'Test Value 3', :test_field2 => "Test Value 5", :test_field3 => 2, :group_field1 => 'Group 2', :group_field2 => 'Subgroup 1'},
176
+ {:test_field1 => 'Test Value 0', :test_field2 => "Test Value 4", :test_field3 => 13, :group_field1 => 'Group 2', :group_field2 => 'Subgroup 1'},
177
+ {:test_field1 => 'Test Value 4', :test_field2 => "Test Value 7", :test_field3 => 7, :group_field1 => 'Group 2', :group_field2 => 'Subgroup 1'},
178
+ {:test_field1 => 'Test Value 1', :test_field2 => "Test Value 3", :test_field3 => 28, :group_field1 => 'Group 2', :group_field2 => 'Subgroup 1'},
179
+ {:test_field1 => 'Test Value 4', :test_field2 => "Test Value 5", :test_field3 => 4, :group_field1 => 'Group 2', :group_field2 => 'Subgroup 2'},
180
+ {:test_field1 => 'Test Value 5', :test_field2 => "Test Value 6", :test_field3 => 24, :group_field1 => 'Group 2', :group_field2 => 'Subgroup 2'}
181
+ ]
182
+
183
+ JunoReport::generate(data, :report => 'example', :filename => 'juno-report.pdf')
184
+ ```
185
+
186
+ ## Contributors
187
+
188
+ 2. Edson Júnior (http://github.com/ebfjunior)
189
+
190
+ ## Contributing
191
+
192
+ 1. Fork it
193
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
194
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
195
+ 4. Push to the branch (`git push origin my-new-feature`)
196
+ 5. Create new Pull Request
197
+
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :spec
7
+ task :test => :spec
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require 'juno-report/version'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = "juno-report"
7
+ gem.version = JunoReport::VERSION
8
+ gem.platform = Gem::Platform::RUBY
9
+ gem.authors = ["Edson Júnior"]
10
+ gem.email = ["ejunior.batista@gmail.com"]
11
+ gem.description = "A simple, but efficient, report genarator yaml based"
12
+ gem.summary = "Juno Reports generates reports with the minimum configuration and effort"
13
+ gem.homepage = "http://github.com/ebfjunior/juno-report"
14
+
15
+ gem.files = `git ls-files`.split("\n")
16
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency "prawml"
21
+
22
+ gem.add_development_dependency 'rake'
23
+ gem.add_development_dependency 'rspec'
24
+ end
@@ -0,0 +1,33 @@
1
+ require "juno-report/version"
2
+ require "juno-report/pdf"
3
+ require "juno-report/pdf/behaviors"
4
+ require "juno-report/pdf/formatters"
5
+ require "prawml"
6
+
7
+ module JunoReport
8
+ autoload :ReportObject, 'juno-report/report_object'
9
+
10
+ def self.generate(collection, options)
11
+ rules = (File.open "#{options[:report]}.yml").read
12
+
13
+ defaults = {
14
+ :page_layout => :portrait
15
+ }
16
+
17
+ pdf = Prawml::PDF.new rules, defaults.merge(options)
18
+
19
+ pdf.extend JunoReport::Pdf
20
+ report = pdf.generate(collection)
21
+
22
+ options[:type] ||= :file
23
+
24
+ if options[:type].eql? :file
25
+ report.render_file (options[:filename] || "report.pdf")
26
+ elsif options[:type].eql? :stream
27
+ return report.render
28
+ else
29
+ raise "Type options must be :file or :stream."
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,279 @@
1
+ module JunoReport
2
+ module Pdf
3
+
4
+ #Responsible for generate a report, based on rules passed as parameter in Juno::Report::generate.
5
+ #Juno Reports has support groups, just by especifying them at the rules file.
6
+ #Receives a collection as parameter, which should be a Array of records of the report.
7
+ def generate(collection)
8
+ @defaults = {
9
+ :style => :normal,
10
+ :size => 12,
11
+ :align => :left,
12
+ :format => false,
13
+ :font => 'Times-Roman',
14
+ :type => :text,
15
+ :color => '000000',
16
+ :fixed => false
17
+ }
18
+
19
+ get_sections
20
+ set_pos_y
21
+
22
+ @defaults.merge!(@sections[:defaults]) unless @sections[:defaults].nil?
23
+
24
+ collection = [collection] unless collection.is_a?(Array) or collection.is_a?(ActiveRecord::Relation)
25
+ print_section :page unless @sections[:page].nil?
26
+ set_pos_y (@sections[:body][:settings][:posY] || 0)
27
+ @current_groups = {}
28
+ @footers = {}
29
+ @count = 0
30
+
31
+ unless @sections[:groups].empty?
32
+ reset_groups_values
33
+ else
34
+ draw_columns
35
+ end
36
+
37
+ initialize_footer_values
38
+ can_print_footer = false
39
+
40
+ collection.each do |record|
41
+ @record = record.is_a?(Hash) ? ReportObject.new(record) : record #Convert the hash on a Object to futurely extend a module
42
+
43
+ headers_to_print, headers_height = calculate_header
44
+
45
+ unless headers_to_print.empty?
46
+ @count = 0
47
+ draw_footer headers_to_print, @sections[:groups] if can_print_footer
48
+ if @posY - headers_height < 2*@sections[:body][:settings][:height]
49
+ new_page
50
+ else
51
+ headers_to_print.each { |group| print_section group, @record, true }
52
+ draw_columns
53
+ end
54
+ end
55
+ can_print_footer = true
56
+
57
+ update_footer_values
58
+ print_section :body, @record
59
+ @count += 1
60
+ end
61
+
62
+ draw_footer(@sections[:body][:settings][:groups].collect {|group| group.to_sym}, @sections[:groups]) if has_groups?
63
+ draw_footer [:body], @sections
64
+
65
+ @pdf
66
+ end
67
+
68
+ protected
69
+
70
+ #Creates a new page, restarting the vertical position of the pointer.
71
+ #Print the whole header for the current groups and the columns of the report.
72
+ def new_page
73
+ @pdf.start_new_page
74
+ set_pos_y
75
+ print_section :page unless @sections[:page].nil?
76
+ set_pos_y (@sections[:body][:settings][:posY] || 0)
77
+ @current_groups.each do |field, value|
78
+ print_section field.to_sym, @record, true
79
+ end
80
+ draw_columns
81
+ end
82
+
83
+ #Generic function to print a section like :body, :page or the group sections.
84
+ def print_section(section_name, values = nil, group = false)
85
+ section = !group ? @sections[section_name] : @sections[:groups][section_name]
86
+ set_pos_y(section[:settings][:posY] || 0) unless section_name.eql?(:body) || section[:settings].nil?
87
+ new_page if @posY < 30
88
+
89
+ if section_name.eql? :body and @count % 2 != 0
90
+ @pdf.fill_color "F7F7F7"
91
+ width = @options[:page_layout] == :portrait ? 530 : 770
92
+ @pdf.fill_rectangle [0, @posY+(section[:settings][:height]/2)], width, section[:settings][:height]
93
+ end
94
+
95
+ section[:fields].each do |field, settings|
96
+ symbolize! settings[1] unless settings[1].nil?
97
+ set_pos_y settings[1][:posY] unless settings[1].nil? || settings[1][:posY].nil?
98
+ settings = [settings[0], @posY, (@defaults.merge (settings[1] || { }))]
99
+ settings[2][:style] = settings[2][:style].to_sym
100
+ set_options settings[2]
101
+
102
+ value = set_value values, settings, section_name, field, group
103
+ draw_text value, settings
104
+ end
105
+ set_pos_y (section[:settings][:height]) unless section[:settings].nil? || section[:settings][:height].nil?
106
+ end
107
+
108
+
109
+ def set_value(values, settings, section_name, field, group)
110
+ if group and !values.class.reflect_on_association(section_name).nil?
111
+ resource = values.send(section_name.to_sym)
112
+ else
113
+ resource = values
114
+ end
115
+
116
+ field.to_s.split(".").each do |part|
117
+ resource = resource.send(part) if !resource.class.reflect_on_association(part).nil?
118
+ end if settings[2][:value].nil?
119
+
120
+ field = field.to_s.split(".").last
121
+
122
+ value = settings[2][:value].nil? ? (resource.respond_to?(field) ? resource.send(field) : "") : settings[2][:value]
123
+
124
+ unless settings[2][:format].blank?
125
+ value = JunoReport::Pdf::Formatters.send(settings[2][:format], value)
126
+ end
127
+
128
+ string_cut = settings[2][:cut].nil? ? value : value[0..settings[2][:cut]]
129
+
130
+ string_cut
131
+ end
132
+
133
+ #Print a horizontal line with the whole width of the page.
134
+ def draw_line(y)
135
+ width = @options[:page_layout] == :portrait ? 530 : 770
136
+ @pdf.stroke { @pdf.horizontal_line 0, width, :at => y }
137
+ end
138
+
139
+ #Update the pointer vertical position to the specified value or 'zero' if the parameter is nil.
140
+ #Obs: Prawn pointer is decrescent, in other words, the left-top corner position is (0, 750). For
141
+ #semantic purposes, we set the same corner as (0, 0).
142
+ def set_pos_y(posY = nil)
143
+ height = @options[:page_layout] == :portrait ? 750 : 520
144
+ @posY = height if @posY.nil?
145
+ @posY = posY.nil? ? height : @posY - posY
146
+ end
147
+
148
+ #Convert to symbol all hash keys, recursively.
149
+ def symbolize! hash
150
+ hash.symbolize_keys!
151
+ hash.values.select{|v| v.is_a? Hash}.each{|h| symbolize!(h)}
152
+ end
153
+
154
+ #Convert the structure of the rules to facilitate the generating proccess.
155
+ def get_sections
156
+ symbolize! @rules
157
+ raise "[body] section on YAML file is needed to generate the report." if @rules[:body].nil?
158
+ @sections = {:page => @rules[:page], :body => @rules[:body], :defaults => @rules[:defaults], :groups => {}}
159
+ @sections[:body][:settings][:groups].each { |group| @sections[:groups][group.to_sym] = @rules[group.to_sym] } if has_groups?
160
+ end
161
+
162
+ #@current_groups storages the value for all groups. When a value is changed, the header is printed.
163
+ #This function set nil value for every item in @current_groups if the parameter is not passed. Otherwise,
164
+ #only the forward groups will be cleaned to avoid conflict problems with others groups.
165
+ def reset_groups_values current_group = nil
166
+ groups = @sections[:body][:settings][:groups]
167
+ groups.each_with_index do |group, idx|
168
+ @current_groups[group] = nil if current_group.nil? || groups.index(current_group.to_s) <= idx
169
+ end
170
+ end
171
+
172
+
173
+ #Calculates the headers which must be printed before print the current record.
174
+ #The function also returns the current header height to create a new page if the
175
+ #page remaining space is smaller than (header + a record height)
176
+ def calculate_header
177
+ headers = []
178
+ height = 0
179
+ @current_groups.each do |field, current_value|
180
+ identifier_field = @sections[:groups][field.to_sym][:settings][:identifier_field] || nil
181
+ value = (!@record.class.reflect_on_association(field).nil? and !identifier_field.nil?) ? @record.send(field.to_sym).send(identifier_field) : @record.send(field)
182
+
183
+ if value != current_value
184
+ reset_groups_values field
185
+
186
+ headers << field.to_sym
187
+ height += @sections[:groups][field.to_sym][:settings][:height] + @sections[:groups][field.to_sym][:settings][:posY]
188
+
189
+ @current_groups[field] = value
190
+ end
191
+ end unless @current_groups.empty?
192
+
193
+ [headers, height]
194
+ end
195
+
196
+ #Create a structure to calculate the footer values for all groups. Appends the footer body to total values too.
197
+ def initialize_footer_values
198
+ @sections[:body][:settings][:groups].each do |group|
199
+ current_footer = {}
200
+ @sections[:groups][group.to_sym][:footer].each { |field, settings| current_footer[field] = nil } unless @sections[:groups][group.to_sym][:footer].nil?
201
+ @footers[group.to_sym] = current_footer unless current_footer.empty?
202
+ end if has_groups?
203
+ raise "The report must have at least a footer on body section" if @sections[:body][:footer].nil?
204
+ current_footer = {}
205
+ @sections[:body][:footer].each { |field, settings| current_footer[field] = nil }
206
+ @footers[:body] = current_footer unless current_footer.empty?
207
+ end
208
+
209
+ #Call the function that calculates the footer values for all groups and the total body footer, with
210
+ #different source for each
211
+ def update_footer_values
212
+ @sections[:body][:settings][:groups].reverse_each do |group|
213
+ calculate_footer_values group, @sections[:groups][group.to_sym][:footer]
214
+ end if has_groups?
215
+ calculate_footer_values :body, @sections[:body][:footer]
216
+ end
217
+
218
+ #Returns the values to the group passed as parameter. If :behavior setting is used, so a
219
+ #function in [lib/pdf/behaviors.rb] calculates the value of current field, else the report
220
+ #method is called
221
+ def calculate_footer_values group, source
222
+ @footers[group.to_sym].each do |field, value|
223
+ footer_rule = source[field]
224
+ symbolize! footer_rule[1]
225
+ unless footer_rule[1][:behavior].nil?
226
+ @footers[group.to_sym][field] = JunoReport::Pdf::Behaviors.send footer_rule[1][:behavior].to_sym, value, (@record.respond_to?(field) ? @record.send(field) : nil)
227
+ else
228
+ if footer_rule[1][:value].blank?
229
+ value = !@record.class.reflect_on_association(group.to_sym).nil? ? @record.send(group.to_sym).send(field.to_sym) : @record.send(field)
230
+ else
231
+ value = footer_rule[1][:value]
232
+ end
233
+ @footers[group.to_sym][field] = footer_rule[1][:label].to_s + value
234
+ end unless @footers[group.to_sym].nil? || footer_rule[1].nil?
235
+ end
236
+ end
237
+
238
+ #Print the footers according to the groups and source specified
239
+ def draw_footer footers_to_print, source
240
+ footers_to_print.reverse_each do |group|
241
+ draw_line(@posY + @sections[:body][:settings][:height]/2)
242
+ source[group][:footer].each do |field, settings|
243
+ settings = [settings[0], @posY, (@defaults.merge (settings[1] || { }).symbolize_keys!)]
244
+ settings[2][:style] = settings[2][:style].to_sym
245
+ set_options settings[2]
246
+ draw_text @footers[group][field], settings
247
+ end
248
+ draw_line(@posY - @sections[:body][:settings][:height]/4)
249
+ set_pos_y @sections[:body][:settings][:height]
250
+
251
+ reset_footer group
252
+ end
253
+ end
254
+
255
+ #Resets the footer to next groups
256
+ def reset_footer(group); @footers[group].each { |field, value| @footers[group][field] = nil }; end
257
+
258
+ #Based on the Key names of the :body section at the rules, the function draw columns with
259
+ #baselines on the top and bottom of the header.
260
+ def draw_columns
261
+ @sections[:body][:fields].each do |field, settings|
262
+ settings = [settings[0], @posY, (@defaults.merge (settings[1] || { }).symbolize_keys!)]
263
+ settings[2][:style] = settings[2][:style].to_sym
264
+ set_options settings[2]
265
+ draw_line(@posY + @sections[:body][:settings][:height]/2)
266
+ field = settings[2][:column] || field.to_s.split('_').inject('') do |str, part|
267
+ str << part.camelize << " "
268
+ end
269
+ draw_text field, settings
270
+ end
271
+ draw_line(@posY - @sections[:body][:settings][:height]/2)
272
+ set_pos_y @sections[:body][:settings][:height]
273
+ end
274
+
275
+ def has_groups?
276
+ !@sections[:body][:settings][:groups].nil?
277
+ end
278
+ end
279
+ end