ruport 0.8.14 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/README +42 -107
  2. data/Rakefile +29 -32
  3. data/examples/centered_pdf_text_box.rb +13 -19
  4. data/examples/example.csv +3 -0
  5. data/examples/line_plotter.rb +15 -15
  6. data/examples/pdf_complex_report.rb +10 -23
  7. data/examples/pdf_table_with_title.rb +12 -12
  8. data/examples/rope_examples/itunes/Rakefile +22 -1
  9. data/examples/rope_examples/itunes/config/environment.rb +4 -0
  10. data/examples/rope_examples/itunes/lib/init.rb +32 -2
  11. data/examples/rope_examples/itunes/util/build +50 -16
  12. data/examples/rope_examples/sales_report/README +1 -1
  13. data/examples/rope_examples/sales_report/Rakefile +22 -1
  14. data/examples/rope_examples/sales_report/config/environment.rb +4 -0
  15. data/examples/rope_examples/sales_report/lib/init.rb +32 -2
  16. data/examples/rope_examples/sales_report/lib/reports/sales.rb +10 -16
  17. data/examples/rope_examples/sales_report/util/build +50 -16
  18. data/examples/row_renderer.rb +39 -0
  19. data/examples/ruport_list/png_embed.rb +61 -0
  20. data/examples/ruport_list/roadmap.png +0 -0
  21. data/examples/sample.rb +16 -0
  22. data/examples/simple_pdf_lines.rb +24 -0
  23. data/lib/ruport.rb +143 -57
  24. data/lib/ruport/acts_as_reportable.rb +246 -0
  25. data/lib/ruport/data.rb +1 -2
  26. data/lib/ruport/data/grouping.rb +311 -0
  27. data/lib/ruport/data/record.rb +113 -84
  28. data/lib/ruport/data/table.rb +275 -174
  29. data/lib/ruport/formatter.rb +149 -0
  30. data/lib/ruport/formatter/csv.rb +87 -0
  31. data/lib/ruport/formatter/html.rb +89 -0
  32. data/lib/ruport/formatter/pdf.rb +357 -0
  33. data/lib/ruport/formatter/text.rb +151 -0
  34. data/lib/ruport/generator.rb +127 -30
  35. data/lib/ruport/query.rb +46 -99
  36. data/lib/ruport/renderer.rb +238 -194
  37. data/lib/ruport/renderer/grouping.rb +67 -0
  38. data/lib/ruport/renderer/table.rb +25 -98
  39. data/lib/ruport/report.rb +45 -96
  40. data/test/acts_as_reportable_test.rb +229 -0
  41. data/test/csv_formatter_test.rb +97 -0
  42. data/test/{_test_database.rb → database_test_.rb} +0 -0
  43. data/test/grouping_test.rb +305 -0
  44. data/test/html_formatter_test.rb +104 -0
  45. data/test/pdf_formatter_test.rb +25 -0
  46. data/test/{test_query.rb → query_test.rb} +32 -121
  47. data/test/{test_record.rb → record_test.rb} +40 -23
  48. data/test/renderer_test.rb +344 -0
  49. data/test/{test_report.rb → report_test.rb} +74 -44
  50. data/test/samples/ticket_count.csv +124 -0
  51. data/test/{test_sql_split.rb → sql_split_test.rb} +0 -0
  52. data/test/{test_table.rb → table_test.rb} +255 -44
  53. data/test/text_formatter_test.rb +144 -0
  54. data/util/bench/data/record/bench_as_vs_to.rb +17 -0
  55. data/util/bench/data/record/bench_constructor.rb +46 -0
  56. data/util/bench/data/record/bench_indexing.rb +65 -0
  57. data/util/bench/data/record/bench_reorder.rb +35 -0
  58. data/util/bench/data/record/bench_to_a.rb +19 -0
  59. data/util/bench/data/table/bench_column_manip.rb +103 -0
  60. data/util/bench/data/table/bench_dup.rb +24 -0
  61. data/util/bench/data/table/bench_init.rb +67 -0
  62. data/util/bench/data/table/bench_manip.rb +125 -0
  63. data/util/bench/formatter/bench_csv.rb +14 -0
  64. data/util/bench/formatter/bench_html.rb +14 -0
  65. data/util/bench/formatter/bench_pdf.rb +14 -0
  66. data/util/bench/formatter/bench_text.rb +14 -0
  67. data/util/bench/samples/tattle.csv +1237 -0
  68. metadata +121 -143
  69. data/TODO +0 -21
  70. data/examples/invoice.rb +0 -142
  71. data/examples/invoice_report.rb +0 -29
  72. data/examples/line_graph.rb +0 -38
  73. data/examples/rope_examples/itunes/config/ruport_config.rb +0 -8
  74. data/examples/rope_examples/sales_report/config/ruport_config.rb +0 -8
  75. data/lib/ruport/attempt.rb +0 -63
  76. data/lib/ruport/config.rb +0 -204
  77. data/lib/ruport/data/groupable.rb +0 -93
  78. data/lib/ruport/data/taggable.rb +0 -80
  79. data/lib/ruport/format.rb +0 -1
  80. data/lib/ruport/format/csv.rb +0 -29
  81. data/lib/ruport/format/html.rb +0 -42
  82. data/lib/ruport/format/latex.rb +0 -47
  83. data/lib/ruport/format/pdf.rb +0 -233
  84. data/lib/ruport/format/plugin.rb +0 -31
  85. data/lib/ruport/format/svg.rb +0 -60
  86. data/lib/ruport/format/text.rb +0 -103
  87. data/lib/ruport/format/xml.rb +0 -32
  88. data/lib/ruport/layout.rb +0 -1
  89. data/lib/ruport/layout/component.rb +0 -7
  90. data/lib/ruport/mailer.rb +0 -99
  91. data/lib/ruport/renderer/graph.rb +0 -46
  92. data/lib/ruport/report/graph.rb +0 -14
  93. data/lib/ruport/system_extensions.rb +0 -71
  94. data/test/test_config.rb +0 -88
  95. data/test/test_format_text.rb +0 -63
  96. data/test/test_graph_renderer.rb +0 -97
  97. data/test/test_groupable.rb +0 -56
  98. data/test/test_mailer.rb +0 -170
  99. data/test/test_renderer.rb +0 -151
  100. data/test/test_ruport.rb +0 -58
  101. data/test/test_table_renderer.rb +0 -141
  102. data/test/test_taggable.rb +0 -52
@@ -0,0 +1,149 @@
1
+ # formatter.rb : Generalized formatting base class for Ruby Reports
2
+ #
3
+ # Created by Gregory Brown. Copyright December 2006, All Rights Reserved.
4
+ #
5
+ # This is free software, please see LICENSE and COPYING for details.
6
+
7
+ module Ruport
8
+ class Formatter
9
+
10
+ module RenderingTools
11
+
12
+ # Iterates through <tt>data</tt> and passes
13
+ # each row to render_row with the given options
14
+ def render_data_by_row(options={},&block)
15
+ data.each do |r|
16
+ render_row(r,options,&block)
17
+ end
18
+ end
19
+
20
+ # Uses Renderer::Row to render the Row object with the
21
+ # given options.
22
+ #
23
+ # Sets the <tt>:io</tt> attribute by default to the existing
24
+ # formatter's <tt>output</tt> object.
25
+ def render_row(row,options={},&block)
26
+ render_helper(Renderer::Row,row,options,&block)
27
+ end
28
+
29
+ # Uses Renderer::Table to render the Table object with the
30
+ # given options.
31
+ #
32
+ # Sets the :io attribute by default to the existing formatter's
33
+ # output object.
34
+ def render_table(table,options={},&block)
35
+ render_helper(Renderer::Table,table,options,&block)
36
+ end
37
+
38
+ # Uses Renderer::Group to render the Group object with the
39
+ # given options.
40
+ #
41
+ # Sets the :io attribute by default to the existing formatter's
42
+ # output object.
43
+ def render_group(group,options={},&block)
44
+ render_helper(Renderer::Group,group,options,&block)
45
+ end
46
+
47
+ # Uses Renderer::Grouping to render the Grouping object with the
48
+ # given options.
49
+ #
50
+ # Sets the :io attribute by default to the existing formatter's
51
+ # output object.
52
+ def render_grouping(grouping,options={},&block)
53
+ render_helper(Renderer::Grouping,grouping,options,&block)
54
+ end
55
+
56
+ # Iterates through the data in the grouping and renders each group
57
+ # followed by a newline.
58
+ #
59
+ def render_inline_grouping(options={},&block)
60
+ data.each do |_,group|
61
+ render_group(group, options, &block)
62
+ output << "\n"
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def render_helper(rend_klass, source_data,options={},&block)
69
+ options = {:data => source_data, :io => output}.merge(options)
70
+ rend_klass.render(format,options) do |rend|
71
+ block[rend] if block
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ include RenderingTools
78
+
79
+ attr_accessor :data
80
+ attr_accessor :format
81
+ attr_writer :options
82
+
83
+ # Registers the formatter with one or more Renderers
84
+ #
85
+ # renders :pdf, :for => MyRenderer
86
+ # render :text, :for => [MyRenderer,YourRenderer]
87
+ # renders [:csv,:html], :for => YourRenderer
88
+ #
89
+ def self.renders(fmts,options={})
90
+ Array(fmts).each do |format|
91
+ Array(options[:for]).each do |o|
92
+ o.send(:add_format,self,format)
93
+ formats << format unless formats.include?(format)
94
+ end
95
+ end
96
+ end
97
+
98
+ # Allows the options specified to be accessed directly.
99
+ #
100
+ # opt_reader :something
101
+ # something == options.something #=> true
102
+ def self.opt_reader(*opts)
103
+ require "forwardable"
104
+ extend Forwardable
105
+ opts.each { |o| def_delegator :@options, o }
106
+ end
107
+
108
+ # Gives a list of formats registered for this formatter.
109
+ def self.formats
110
+ @formats ||= []
111
+ end
112
+
113
+ # Stores a string used for outputting formatted data.
114
+ def output
115
+ return options.io if options.io
116
+ @output ||= ""
117
+ end
118
+
119
+ # Provides a generic OpenStruct for storing formatter options
120
+ def options
121
+ @options ||= Renderer::Options.new
122
+ end
123
+
124
+ # Clears output.
125
+ def clear_output
126
+ @output.replace("")
127
+ end
128
+
129
+ # Provides a shortcut for per format handlers.
130
+ #
131
+ # Example:
132
+ #
133
+ # # will only be called if formatter is called for html output
134
+ # html { output << "Look, I'm handling html" }
135
+ #
136
+ def method_missing(id,*args)
137
+ if self.class.formats.include?(id)
138
+ yield() if format == id
139
+ else
140
+ super
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ require "ruport/formatter/csv"
147
+ require "ruport/formatter/html"
148
+ require "ruport/formatter/text"
149
+ require "ruport/formatter/pdf"
@@ -0,0 +1,87 @@
1
+ module Ruport
2
+
3
+ # This formatter implements the CSV format for tabular data output.
4
+ class Formatter::CSV < Formatter
5
+
6
+ renders :csv, :for => [ Renderer::Row, Renderer::Table,
7
+ Renderer::Group, Renderer::Grouping ]
8
+
9
+ opt_reader :show_table_headers,
10
+ :format_options,
11
+ :show_group_headers,
12
+ :style
13
+
14
+ # Generates table header by turning column_names into a CSV row.
15
+ # Uses the row renderer to generate the actual formatted output
16
+ #
17
+ # This method does not do anything if options.show_table_headers is false
18
+ # or the Data::Table has no column names.
19
+ def build_table_header
20
+ unless data.column_names.empty? || !show_table_headers
21
+ render_row data.column_names, :format_options => format_options
22
+ end
23
+ end
24
+
25
+ # Calls the row renderer for each row in the Data::Table
26
+ def build_table_body
27
+ render_data_by_row { |r|
28
+ r.options.format_options = format_options
29
+ }
30
+ end
31
+
32
+ # Produces CSV output for a data row.
33
+ def build_row
34
+ require "fastercsv"
35
+ output << FCSV.generate_line(data,format_options || {})
36
+ end
37
+
38
+ # Renders the header for a group using the group name.
39
+ #
40
+ def build_group_header
41
+ output << data.name.to_s << "\n\n"
42
+ end
43
+
44
+ # Renders the group body - uses the table renderer to generate the output.
45
+ #
46
+ def build_group_body
47
+ render_table data, options.to_hash
48
+ end
49
+
50
+ # Generates a header for the grouping using the grouped_by column and the
51
+ # column names.
52
+ #
53
+ def build_grouping_header
54
+ unless style == :inline
55
+ output << "#{data.grouped_by}," << grouping_columns
56
+ end
57
+ end
58
+
59
+ def build_grouping_body
60
+ case style
61
+ when :inline
62
+ render_inline_grouping(options)
63
+ when :justified, :raw
64
+ render_justified_or_raw_grouping
65
+ else
66
+ raise NotImplementedError, "Unknown style"
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def grouping_columns
73
+ require "fastercsv"
74
+ data.data.to_a[0][1].column_names.to_csv
75
+ end
76
+
77
+ def render_justified_or_raw_grouping
78
+ data.each do |_,group|
79
+ output << "#{group.name}" if style == :justified
80
+ group.each do |row|
81
+ output << "#{group.name if style == :raw}," << row.to_csv
82
+ end
83
+ output << "\n"
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,89 @@
1
+ module Ruport
2
+ # Produces HTML output for tabular data.
3
+ #
4
+ class Formatter::HTML < Formatter
5
+
6
+ renders :html, :for => [ Renderer::Row, Renderer::Table,
7
+ Renderer::Group, Renderer::Grouping ]
8
+
9
+ opt_reader :show_table_headers, :show_group_headers
10
+
11
+ # Generates table headers based on the column names of your Data::Table.
12
+ #
13
+ # This method does not do anything if options.show_table_headers is false or
14
+ # the Data::Table has no column names.
15
+ def build_table_header
16
+ output << "\t<table>\n"
17
+ unless data.column_names.empty? || !show_table_headers
18
+ output << "\t\t<tr>\n\t\t\t<th>" +
19
+ data.column_names.join("</th>\n\t\t\t<th>") +
20
+ "</th>\n\t\t</tr>\n"
21
+ end
22
+ end
23
+
24
+ def build_table_body
25
+ render_data_by_row do |rend|
26
+ r = rend.data
27
+ rend.data = r.map { |e| e.to_s.empty? ? "&nbsp;" : e }
28
+ end
29
+ end
30
+
31
+ # Simply closes the table tag.
32
+ def build_table_footer
33
+ output << "\t</table>"
34
+ end
35
+
36
+ # Renders individual rows for the table
37
+ def build_row
38
+ output <<
39
+ "\t\t<tr>\n\t\t\t<td>" +
40
+ data.to_a.join("</td>\n\t\t\t<td>") +
41
+ "</td>\n\t\t</tr>\n"
42
+ end
43
+
44
+ # Renders the header for a group using the group name.
45
+ #
46
+ def build_group_header
47
+ output << "\t<p>#{data.name}</p>\n"
48
+ end
49
+
50
+ # Creates the group body. Since group data is a table, just uses the
51
+ # Table renderer.
52
+ #
53
+ def build_group_body
54
+ render_table data, options.to_hash
55
+ end
56
+
57
+ # Generates the body for a grouping. Iterates through the groups and
58
+ # renders them using the group renderer.
59
+ #
60
+ def build_grouping_body
61
+ render_inline_grouping(options)
62
+ end
63
+
64
+ # Generates <table> tags enclosing the yielded content.
65
+ #
66
+ # Example:
67
+ #
68
+ # output << html_table { "<tr><td>1</td><td>2</td></tr>\n" }
69
+ # #=> "<table>\n<tr><td>1</td><td>2</td></tr>\n</table>\n"
70
+ #
71
+ def html_table
72
+ "<table>\n" << yield << "</table>\n"
73
+ end
74
+
75
+ # Uses RedCloth to turn a string containing textile markup into HTML.
76
+ #
77
+ # Example:
78
+ #
79
+ # textile "*bar*" #=> "<p><strong>foo</strong></p>"
80
+ #
81
+ def textile(s)
82
+ require "redcloth"
83
+ RedCloth.new(s).to_html
84
+ rescue LoadError
85
+ raise RuntimeError, "You need RedCloth!\n gem install RedCloth -v 3.0.3"
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,357 @@
1
+ module Ruport
2
+
3
+ # PDF generation formatter
4
+ #
5
+ # options:
6
+ # General:
7
+ # * paper_size #=> "LETTER"
8
+ # * paper_orientation #=> :portrait
9
+ #
10
+ # Text:
11
+ # * text_format
12
+ #
13
+ # Table:
14
+ # * table_format (a hash that can take any of the options available
15
+ # to PDF::SimpleTable)
16
+ # * table_format[:maximum_width] #=> 500
17
+ #
18
+ class Formatter::PDF < Formatter
19
+
20
+ renders :pdf, :for => [ Renderer::Row, Renderer::Table,
21
+ Renderer::Group, Renderer::Grouping ]
22
+
23
+ attr_writer :pdf_writer
24
+ attr_accessor :table_header_proc
25
+ attr_accessor :table_footer_proc
26
+
27
+ opt_reader :show_table_headers,
28
+ :style,
29
+ :table_format,
30
+ :text_format,
31
+ :paper_size,
32
+ :paper_orientation
33
+
34
+ def initialize
35
+ quiet do
36
+ require "pdf/writer"
37
+ require "pdf/simpletable"
38
+ end
39
+ end
40
+
41
+ # Returns the current PDF::Writer object or creates a new one if it has not
42
+ # been set yet.
43
+ #
44
+ def pdf_writer
45
+ @pdf_writer ||= options.formatter ||
46
+ ::PDF::Writer.new( :paper => paper_size || "LETTER",
47
+ :orientation => paper_orientation || :portrait)
48
+ @pdf_writer.extend(PDFWriterMemoryPatch)
49
+ end
50
+
51
+ # Calls the draw_table method.
52
+ #
53
+ def build_table_body
54
+ draw_table(data)
55
+ end
56
+
57
+ # Appends the results of PDF::Writer#render to output for your
58
+ # <tt>pdf_writer</tt> object.
59
+ #
60
+ def finalize_table
61
+ render_pdf unless options.skip_finalize_table
62
+ end
63
+
64
+ def build_group_header
65
+ pad(10) { add_text data.name.to_s, :justification => :center }
66
+ end
67
+
68
+ def build_group_body
69
+ render_table data, options.to_hash.merge(:formatter => pdf_writer)
70
+ end
71
+
72
+ def build_grouping_body
73
+ case style
74
+ when :inline
75
+ render_inline_grouping(options.to_hash.merge(:formatter => pdf_writer,
76
+ :skip_finalize_table => true))
77
+ when :justified, :separated
78
+ render_justified_or_separated_grouping
79
+ when :offset
80
+ render_offset_grouping
81
+ else
82
+ raise NotImplementedError, "Unknown style"
83
+ end
84
+ end
85
+
86
+ def finalize_grouping
87
+ render_pdf
88
+ end
89
+
90
+ # Call PDF::Writer#text with the given arguments
91
+ def add_text(text, format_opts={})
92
+ format_opts = text_format.merge(format_opts) if text_format
93
+ pdf_writer.text(text, format_opts)
94
+ end
95
+
96
+ # Calls PDF::Writer#render and appends to <tt>output</tt>
97
+ def render_pdf
98
+ output << pdf_writer.render
99
+ end
100
+
101
+ # - If the image is bigger than the box, it will be scaled down until
102
+ # it fits.
103
+ # - If the image is smaller than the box, it won't be resized.
104
+ #
105
+ # options:
106
+ # - :x: left bound of box
107
+ # - :y: bottom bound of box
108
+ # - :width: width of box
109
+ # - :height: height of box
110
+ #
111
+ def center_image_in_box(path, image_opts={})
112
+ x = image_opts[:x]
113
+ y = image_opts[:y]
114
+ width = image_opts[:width]
115
+ height = image_opts[:height]
116
+ info = ::PDF::Writer::Graphics::ImageInfo.new(File.read(path))
117
+
118
+ # reduce the size of the image until it fits into the requested box
119
+ img_width, img_height =
120
+ fit_image_in_box(info.width,width,info.height,height)
121
+
122
+ # if the image is smaller than the box, calculate the white space buffer
123
+ x, y = add_white_space(x,y,img_width,width,img_height,height)
124
+
125
+ pdf_writer.add_image_from_file(path, x, y, img_width, img_height)
126
+ end
127
+
128
+ # Draws some text on the canvas, surrounded by a box with rounded corners
129
+ #
130
+ def rounded_text_box(text)
131
+ opts = OpenStruct.new
132
+ yield(opts)
133
+
134
+ resize_text_to_box(text, opts)
135
+
136
+ pdf_writer.save_state
137
+ draw_box(opts.x, opts.y, opts.width, opts.height, opts.radius,
138
+ opts.fill_color, opts.stroke_color)
139
+ add_text_with_bottom_border(opts.heading, opts.x, opts.y,
140
+ opts.width, opts.font_size) if opts.heading
141
+ pdf_writer.restore_state
142
+
143
+ start_position = opts.heading ? opts.y - 20 : opts.y
144
+ draw_text(text, :y => start_position,
145
+ :left => opts.x,
146
+ :right => opts.x + opts.width,
147
+ :justification => opts.justification || :center,
148
+ :font_size => opts.font_size)
149
+ move_cursor_to(opts.y - opts.height)
150
+ end
151
+
152
+ # Adds an image to every page. The color and size won't be modified,
153
+ # but it will be centered.
154
+ #
155
+ def watermark(imgpath)
156
+ x = pdf_writer.absolute_left_margin
157
+ y = pdf_writer.absolute_bottom_margin
158
+ width = pdf_writer.absolute_right_margin - x
159
+ height = pdf_writer.absolute_top_margin - y
160
+
161
+ pdf_writer.open_object do |wm|
162
+ pdf_writer.save_state
163
+ center_image_in_box(imgpath, :x => x, :y => y,
164
+ :width => width, :height => height)
165
+ pdf_writer.restore_state
166
+ pdf_writer.close_object
167
+ pdf_writer.add_object(wm, :all_pages)
168
+ end
169
+ end
170
+
171
+ def move_cursor(n)
172
+ pdf_writer.y += n
173
+ end
174
+
175
+ def move_cursor_to(n)
176
+ pdf_writer.y = n
177
+ end
178
+
179
+ def pad(y,&block)
180
+ move_cursor(-y)
181
+ block.call
182
+ move_cursor(-y)
183
+ end
184
+
185
+ def pad_top(y,&block)
186
+ move_cursor(-y)
187
+ block.call
188
+ end
189
+
190
+ def pad_bottom(y,&block)
191
+ block.call
192
+ move_cursor(-y)
193
+ end
194
+
195
+ def draw_table(table_data, format_opts={})
196
+ m = "Sorry, cant build PDFs from array like things (yet)"
197
+ raise m if table_data.column_names.empty?
198
+
199
+ format_opts = table_format.merge(format_opts) if table_format
200
+
201
+ ::PDF::SimpleTable.new do |table|
202
+ table.data = table_data
203
+ table.maximum_width = 500
204
+ table.column_order = table_data.column_names
205
+
206
+ apply_pdf_table_column_opts(table,table_data,format_opts)
207
+
208
+ format_opts.each {|k,v| table.send("#{k}=", v) }
209
+
210
+ table.render_on(pdf_writer)
211
+ end
212
+ end
213
+
214
+ module DrawingHelpers
215
+
216
+ def horizontal_line(x1,x2)
217
+ pdf_writer.line(x1,cursor,x2,cursor)
218
+ pdf_writer.stroke
219
+ end
220
+
221
+ def vertical_line_at(x,y1,y2)
222
+ pdf_writer.line(x,y1,x,y2)
223
+ end
224
+
225
+ def left_boundary
226
+ pdf_writer.absolute_left_margin
227
+ end
228
+
229
+ def right_boundary
230
+ pdf_writer.absolute_right_margin
231
+ end
232
+
233
+ def top_boundary
234
+ pdf_writer.absolute_top_margin
235
+ end
236
+
237
+ def bottom_boundary
238
+ pdf_writer.absolute_bottom_margin
239
+ end
240
+
241
+ def cursor
242
+ pdf_writer.y
243
+ end
244
+
245
+ def draw_text(text,text_opts)
246
+ move_cursor_to(text_opts[:y]) if text_opts[:y]
247
+ add_text(text,
248
+ text_opts.merge(:absolute_left => text_opts[:x1] || text_opts[:left],
249
+ :absolute_right => text_opts[:x2] || text_opts[:right]))
250
+ end
251
+
252
+ end
253
+
254
+ include DrawingHelpers
255
+
256
+ module PDFWriterMemoryPatch #:nodoc:
257
+ unless self.class.instance_methods.include?("_post_transaction_rewind")
258
+ def _post_transaction_rewind
259
+ @objects.each { |e| e.instance_variable_set(:@parent,self) }
260
+ end
261
+ end
262
+ end
263
+
264
+ private
265
+
266
+ def apply_pdf_table_column_opts(table,table_data,format_opts)
267
+ column_opts = format_opts.delete(:column_options)
268
+ if column_opts
269
+ columns = table_data.column_names.inject({}) { |s,c|
270
+ s.merge( c => ::PDF::SimpleTable::Column.new(c) { |col|
271
+ column_opts.each { |k,v| col.send("#{k}=",v) }
272
+ })
273
+ }
274
+ table.columns = columns
275
+ end
276
+ end
277
+
278
+ def grouping_columns
279
+ data.data.to_a[0][1].column_names.dup.unshift(data.grouped_by)
280
+ end
281
+
282
+ def table_with_grouped_by_column
283
+ Ruport::Data::Table.new(:column_names => grouping_columns)
284
+ end
285
+
286
+ def render_justified_or_separated_grouping
287
+ table = table_with_grouped_by_column
288
+ data.each do |name,group|
289
+ group_column = { data.grouped_by => "<b>#{name}</b>\n" }
290
+ group.each_with_index do |rec,i|
291
+ i == 0 ? table << group_column.merge(rec.to_h) : table << rec
292
+ end
293
+ table << Array.new(grouping_columns.length,' ') if style == :separated
294
+ end
295
+ render_table table, options.to_hash.merge(:formatter => pdf_writer)
296
+ end
297
+
298
+ def render_offset_grouping
299
+ table = table_with_grouped_by_column
300
+ data.each do |name,group|
301
+ table << ["<b>#{name}</b>\n",nil,nil]
302
+ group.each {|r| table << r }
303
+ end
304
+ render_table table, options.to_hash.merge(:formatter => pdf_writer)
305
+ end
306
+
307
+ def image_fits_in_box?(img_width,box_width,img_height,box_height)
308
+ !(img_width > box_width || img_height > box_height)
309
+ end
310
+
311
+ def fit_image_in_box(img_width,box_width,img_height,box_height)
312
+ img_ratio = img_height.to_f / img_width.to_f
313
+ until image_fits_in_box?(img_width,box_width,img_height,box_height)
314
+ img_width -= 1
315
+ img_height = img_width * img_ratio
316
+ end
317
+ return img_width, img_height
318
+ end
319
+
320
+ def add_white_space(x,y,img_width,box_width,img_height,box_height)
321
+ if img_width < box_width
322
+ white_space = box_width - img_width
323
+ x = x + (white_space / 2)
324
+ end
325
+ if img_height < box_height
326
+ white_space = box_height - img_height
327
+ y = y + (white_space / 2)
328
+ end
329
+ return x, y
330
+ end
331
+
332
+ def resize_text_to_box(text,opts)
333
+ loop do
334
+ sz = pdf_writer.text_width(text, opts.font_size)
335
+ opts.x + sz > opts.x + opts.width or break
336
+ opts.font_size -= 1
337
+ end
338
+ end
339
+
340
+ def draw_box(x,y,width,height,radius,fill_color=nil,stroke_color=nil)
341
+ pdf_writer.fill_color(fill_color || Color::RGB::White)
342
+ pdf_writer.stroke_color(stroke_color || Color::RGB::Black)
343
+ pdf_writer.rounded_rectangle(x, y, width, height, radius).fill_stroke
344
+ end
345
+
346
+ def add_text_with_bottom_border(text,x,y,width,font_size)
347
+ pdf_writer.line( x, y - 20,
348
+ x + width, y - 20).stroke
349
+ pdf_writer.fill_color(Color::RGB::Black)
350
+ move_cursor_to(y - 3)
351
+ add_text("<b>#{text}</b>",
352
+ :absolute_left => x, :absolute_right => x + width,
353
+ :justification => :center, :font_size => font_size)
354
+ end
355
+
356
+ end
357
+ end